mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-18 20:21:35 +00:00
Add robo-identity-wasm
This commit is contained in:
parent
2408190869
commit
7e97c325f1
6
frontend/package-lock.json
generated
6
frontend/package-lock.json
generated
@ -47,6 +47,7 @@
|
||||
"react-smooth-image": "^1.1.0",
|
||||
"react-world-flags": "^1.6.0",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"robo-identities-wasm": "^0.1.0",
|
||||
"simple-plist": "^1.3.1",
|
||||
"webln": "^0.3.2",
|
||||
"websocket": "^1.0.34"
|
||||
@ -14282,6 +14283,11 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/robo-identities-wasm": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/robo-identities-wasm/-/robo-identities-wasm-0.1.0.tgz",
|
||||
"integrity": "sha512-q6+1Vgq+8d2F5k8Nqm39qwQJYe9uTC7TlR3NbBQ6k2ImBNccdAEoZgb0ikKjN59cK4MvqejlgBV1ybaLXoHbhA=="
|
||||
},
|
||||
"node_modules/run-applescript": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
|
||||
|
@ -86,6 +86,7 @@
|
||||
"react-smooth-image": "^1.1.0",
|
||||
"react-world-flags": "^1.6.0",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"robo-identities-wasm": "^0.1.0",
|
||||
"simple-plist": "^1.3.1",
|
||||
"webln": "^0.3.2",
|
||||
"websocket": "^1.0.34"
|
||||
|
73
frontend/src/components/RobotAvatar/RobohashGenerator.ts
Normal file
73
frontend/src/components/RobotAvatar/RobohashGenerator.ts
Normal file
@ -0,0 +1,73 @@
|
||||
class RoboGenerator {
|
||||
private assetsCache: Record<string, string> = {};
|
||||
private assetsPromises: Record<string, Promise<string>> = {};
|
||||
private readonly workers: Worker[] = [];
|
||||
|
||||
constructor() {
|
||||
// limit to 8 workers
|
||||
const numCores = Math.min(navigator.hardwareConcurrency || 1, 8);
|
||||
|
||||
for (let i = 0; i < numCores; i++) {
|
||||
const worker = new Worker(new URL('./robohash.worker.ts', import.meta.url));
|
||||
this.workers.push(worker);
|
||||
}
|
||||
}
|
||||
|
||||
public generate: (hash: string, size: 'small' | 'large') => Promise<string> = async (
|
||||
hash,
|
||||
size,
|
||||
) => {
|
||||
const cacheKey = `${size}px;${hash}`;
|
||||
if (this.assetsCache[cacheKey]) {
|
||||
return this.assetsCache[cacheKey];
|
||||
} else if (cacheKey in this.assetsPromises) {
|
||||
return await this.assetsPromises[cacheKey];
|
||||
}
|
||||
|
||||
const workerIndex = Object.keys(this.assetsPromises).length % this.workers.length;
|
||||
const worker = this.workers[workerIndex];
|
||||
|
||||
this.assetsPromises[cacheKey] = new Promise<string>((resolve, reject) => {
|
||||
// const avatarB64 = async_generate_robohash(hash, size == 'small' ? 80 : 256).then((avatarB64)=> resolve(`data:image/png;base64,${avatarB64}`));
|
||||
// Create a message object with the necessary data
|
||||
const message = { hash, size, cacheKey, workerIndex };
|
||||
|
||||
// Listen for messages from the worker
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const { cacheKey, imageUrl } = event.data;
|
||||
|
||||
// Update the cache and resolve the promise
|
||||
this.assetsCache[cacheKey] = imageUrl;
|
||||
delete this.assetsPromises[cacheKey];
|
||||
resolve(imageUrl);
|
||||
};
|
||||
|
||||
// Add the event listener for messages
|
||||
worker.addEventListener('message', handleMessage);
|
||||
|
||||
// Send the message to the worker
|
||||
worker.postMessage(message);
|
||||
|
||||
// Clean up the event listener after receiving the result
|
||||
const cleanup = () => {
|
||||
worker.removeEventListener('message', handleMessage);
|
||||
};
|
||||
|
||||
// Reject the promise if an error occurs
|
||||
worker.addEventListener('error', (error) => {
|
||||
cleanup();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
// Reject the promise if the worker times out
|
||||
setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error('Generation timed out'));
|
||||
}, 5000); // Adjust the timeout duration as needed
|
||||
});
|
||||
|
||||
return await this.assetsPromises[cacheKey];
|
||||
};
|
||||
}
|
||||
|
||||
export const robohash = new RoboGenerator();
|
@ -1,11 +1,10 @@
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import SmoothImage from 'react-smooth-image';
|
||||
import { Avatar, Badge, Tooltip } from '@mui/material';
|
||||
import { SendReceiveIcon } from '../Icons';
|
||||
import { apiClient } from '../../services/api';
|
||||
import placeholder from './placeholder.json';
|
||||
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
||||
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
||||
import { robohash } from './RobohashGenerator';
|
||||
|
||||
interface Props {
|
||||
nickname: string | undefined;
|
||||
@ -59,21 +58,39 @@ const RobotAvatar: React.FC<Props> = ({
|
||||
const className = placeholderType === 'loading' ? 'loadingAvatar' : 'generatingAvatar';
|
||||
|
||||
useEffect(() => {
|
||||
if (nickname !== undefined) {
|
||||
// TODO: HANDLE ANDROID AVATARS TOO (when window.NativeRobosats !== undefined)
|
||||
if (nickname !== undefined && !coordinator) {
|
||||
robohash
|
||||
.generate(nickname, small ? 'small' : 'large') // TODO: should hash_id
|
||||
.then((avatar) => {
|
||||
setAvatarSrc(avatar);
|
||||
})
|
||||
.catch(() => {
|
||||
setAvatarSrc('');
|
||||
});
|
||||
setNicknameReady(true);
|
||||
setActiveBackground(false);
|
||||
}
|
||||
|
||||
if (coordinator) {
|
||||
if (window.NativeRobosats === undefined) {
|
||||
setAvatarSrc(`${baseUrl}${path}${nickname}${small ? '.small' : ''}.webp`);
|
||||
setNicknameReady(true);
|
||||
} else if (baseUrl != null && apiClient.fileImageUrl !== undefined) {
|
||||
setNicknameReady(true);
|
||||
void apiClient
|
||||
.fileImageUrl(baseUrl, `${path}${nickname}${small ? '.small' : ''}.webp`)
|
||||
.then(setAvatarSrc);
|
||||
setAvatarSrc(
|
||||
`${baseUrl}/static/federation/avatars/${nickname}${small ? '.small' : ''}.webp`,
|
||||
);
|
||||
} else {
|
||||
setAvatarSrc(
|
||||
`file:///android_asset/Web.bundle/assets/federation/avatars/${nickname}${
|
||||
small ? ' .small' : ''
|
||||
}.webp`,
|
||||
);
|
||||
}
|
||||
setNicknameReady(true);
|
||||
setActiveBackground(false);
|
||||
} else {
|
||||
setNicknameReady(false);
|
||||
setActiveBackground(true);
|
||||
}
|
||||
}, [nickname]);
|
||||
}, [nickname]); // TODO: should hash_id
|
||||
|
||||
const statusBadge = (
|
||||
<div style={{ position: 'relative', left: '0.428em', top: '0.07em' }}>
|
||||
|
15
frontend/src/components/RobotAvatar/robohash.worker.ts
Normal file
15
frontend/src/components/RobotAvatar/robohash.worker.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { async_generate_robohash } from 'robo-identities-wasm';
|
||||
|
||||
// Listen for messages from the main thread
|
||||
self.addEventListener('message', async (event) => {
|
||||
const { hash, size, cacheKey, workerIndex } = event.data;
|
||||
|
||||
// Generate the image using async_image_base
|
||||
const t0 = performance.now();
|
||||
const avatarB64 = await async_generate_robohash(hash, size == 'small' ? 80 : 256);
|
||||
const imageUrl = `data:image/png;base64,${avatarB64}`;
|
||||
const t1 = performance.now();
|
||||
console.log(`Worker ${workerIndex} :: Time to generate avatar: ${t1 - t0} ms`);
|
||||
// Send the result back to the main thread
|
||||
self.postMessage({ cacheKey, imageUrl });
|
||||
});
|
@ -151,11 +151,19 @@ export const useFederationStore = (): UseFederationStoreType => {
|
||||
const slot = garage.getSlot();
|
||||
const robot = slot?.getRobot();
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (robot != null && garage.currentSlot != null) {
|
||||
if (open.profile && slot?.avatarLoaded === true && slot.token != null) {
|
||||
void federation.fetchRobot(garage, slot.token); // refresh/update existing robot
|
||||
} else if (
|
||||
!(slot?.avatarLoaded === true) &&
|
||||
=======
|
||||
if (robot != null && garage.currentSlot) {
|
||||
if (open.profile && slot?.avatarLoaded && slot.token) {
|
||||
void federation.fetchRobot(garage, slot.token); // refresh/update existing robot
|
||||
} else if (
|
||||
!slot?.avatarLoaded &&
|
||||
>>>>>>> f861207a (Add robo-identity-wasm)
|
||||
robot.token !== undefined &&
|
||||
robot.encPrivKey !== undefined &&
|
||||
robot.pubKey !== undefined
|
||||
|
@ -20,6 +20,7 @@ export interface PublicOrder {
|
||||
maker: number;
|
||||
escrow_duration: number;
|
||||
maker_nick: string;
|
||||
maker_hash_id: string;
|
||||
price: number;
|
||||
maker_status: 'Active' | 'Seen recently' | 'Inactive';
|
||||
coordinatorShortAlias?: string;
|
||||
|
@ -10,6 +10,7 @@ import { apiClient } from '../services/api';
|
||||
import { validateTokenEntropy } from '../utils';
|
||||
import { compareUpdateLimit } from './Limit.model';
|
||||
import { defaultOrder } from './Order.model';
|
||||
import { robohash } from '../components/RobotAvatar/RobohashGenerator';
|
||||
|
||||
export interface Contact {
|
||||
nostr?: string | undefined;
|
||||
@ -156,6 +157,12 @@ export class Coordinator {
|
||||
this.loadInfo(onDataLoad);
|
||||
};
|
||||
|
||||
generateAllMakerAvatars = (data: [PublicOrder]) => {
|
||||
for (const order of data) {
|
||||
robohash.generate(order.maker_hash_id, 'small');
|
||||
}
|
||||
};
|
||||
|
||||
loadBook = (onDataLoad: () => void = () => {}): void => {
|
||||
if (this.enabled === false) return;
|
||||
if (this.loadingBook) return;
|
||||
@ -170,6 +177,7 @@ export class Coordinator {
|
||||
order.coordinatorShortAlias = this.shortAlias;
|
||||
return order;
|
||||
});
|
||||
this.generateAllMakerAvatars(data);
|
||||
onDataLoad();
|
||||
}
|
||||
})
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { sha256 } from 'js-sha256';
|
||||
import { hexToBase91 } from '../utils';
|
||||
import { robohash } from '../components/RobotAvatar/RobohashGenerator';
|
||||
import { generate_roboname } from 'robo-identities-wasm';
|
||||
|
||||
interface AuthHeaders {
|
||||
tokenSHA256: string;
|
||||
@ -13,6 +15,7 @@ class Robot {
|
||||
constructor(garageRobot?: Robot) {
|
||||
if (garageRobot != null) {
|
||||
this.token = garageRobot?.token ?? undefined;
|
||||
this.hash_id = garageRobot?.hash_id ?? undefined;
|
||||
this.tokenSHA256 =
|
||||
garageRobot?.tokenSHA256 ?? (this.token != null ? hexToBase91(sha256(this.token)) : '');
|
||||
this.pubKey = garageRobot?.pubKey ?? undefined;
|
||||
@ -22,6 +25,7 @@ class Robot {
|
||||
|
||||
public nickname?: string;
|
||||
public token?: string;
|
||||
public hash_id?: string;
|
||||
public bitsEntropy?: number;
|
||||
public shannonEntropy?: number;
|
||||
public tokenSHA256: string = '';
|
||||
@ -41,6 +45,14 @@ class Robot {
|
||||
|
||||
update = (attributes: Record<string, any>): void => {
|
||||
Object.assign(this, attributes);
|
||||
if (attributes.token != null) {
|
||||
const hash_id = sha256(sha256(attributes.token));
|
||||
this.hash_id = hash_id;
|
||||
this.nickname = generate_roboname(hash_id);
|
||||
// trigger RoboHash avatar generation in webworker and store in RoboHash class cache.
|
||||
robohash.generate(hash_id, 'small');
|
||||
robohash.generate(hash_id, 'large');
|
||||
}
|
||||
};
|
||||
|
||||
getAuthHeaders = (): AuthHeaders | null => {
|
||||
|
@ -18,6 +18,7 @@ const config: Configuration = {
|
||||
},
|
||||
],
|
||||
},
|
||||
experiments: { asyncWebAssembly: true },
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user