Add robo-identity-wasm

This commit is contained in:
Reckless_Satoshi 2023-12-02 11:31:21 +00:00
parent 2408190869
commit 7e97c325f1
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
10 changed files with 154 additions and 12 deletions

View File

@ -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",

View File

@ -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"

View 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();

View File

@ -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' }}>

View 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 });
});

View File

@ -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

View File

@ -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;

View File

@ -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();
}
})

View File

@ -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 => {

View File

@ -18,6 +18,7 @@ const config: Configuration = {
},
],
},
experiments: { asyncWebAssembly: true },
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js'],
},