diff --git a/.gitignore b/.gitignore index 0db3c80f..f5d79e09 100755 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ *.py[cod] __pycache__ -# C extensions -*.so - # Packages *.egg *.egg-info diff --git a/frontend/src/basic/RobotPage/Onboarding.tsx b/frontend/src/basic/RobotPage/Onboarding.tsx index 38d54aac..1d6d2fad 100644 --- a/frontend/src/basic/RobotPage/Onboarding.tsx +++ b/frontend/src/basic/RobotPage/Onboarding.tsx @@ -178,7 +178,7 @@ const Onboarding = ({ /> - {slot?.hashId ? ( + {slot?.nickname ? ( {t('Hi! My name is')} diff --git a/frontend/src/basic/RobotPage/RobotProfile.tsx b/frontend/src/basic/RobotPage/RobotProfile.tsx index 2ac4f354..4a21bfbb 100644 --- a/frontend/src/basic/RobotPage/RobotProfile.tsx +++ b/frontend/src/basic/RobotPage/RobotProfile.tsx @@ -90,7 +90,7 @@ const RobotProfile = ({ sx={{ width: '100%' }} > - {slot?.hashId ? ( + {slot?.nickname ? (
{ - {garage.getSlot()?.nickname !== undefined && ( + {!garage.getSlot()?.nickname && (
= ({ const backgroundImage = `url(data:${backgroundData.mime};base64,${backgroundData.data})`; const className = placeholderType === 'loading' ? 'loadingAvatar' : 'generatingAvatar'; - // useEffect(() => { - // // TODO: HANDLE ANDROID AVATARS TOO (when window.NativeRobosats !== undefined) - // if (hashId !== undefined) { - // robohash - // .generate(hashId, small ? 'small' : 'large') - // .then((avatar) => { - // setAvatarSrc(avatar); - // }) - // .catch(() => { - // setAvatarSrc(''); - // }); - // setTimeout(() => { - // setActiveBackground(false); - // }, backgroundFadeTime); - // } - // }, [hashId]); + useEffect(() => { + if (hashId !== undefined) { + roboidentitiesClient + .generateRobohash(hashId, small ? 'small' : 'large') + .then((avatar) => { + setAvatarSrc(avatar); + }) + .catch(() => { + setAvatarSrc(''); + }); + setTimeout(() => { + setActiveBackground(false); + }, backgroundFadeTime); + } + }, [hashId]); useEffect(() => { if (shortAlias !== undefined) { @@ -78,9 +77,7 @@ const RobotAvatar: React.FC = ({ ); } else { setAvatarSrc( - `file:///android_asset/Web.bundle/assets/federation/avatars/${shortAlias}${ - small ? ' .small' : '' - }.webp`, + `file:///android_asset/Web.bundle/assets/federation/avatars/${shortAlias}.webp`, ); } setTimeout(() => { diff --git a/frontend/src/models/Coordinator.model.ts b/frontend/src/models/Coordinator.model.ts index 09e74a30..ef1abb19 100644 --- a/frontend/src/models/Coordinator.model.ts +++ b/frontend/src/models/Coordinator.model.ts @@ -6,11 +6,11 @@ import { type Order, type Garage, } from '.'; +import { roboidentitiesClient } from '../services/Roboidentities/Web'; 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; @@ -174,9 +174,9 @@ export class Coordinator { }; generateAllMakerAvatars = async (data: [PublicOrder]): Promise => { - // for (const order of data) { - // void robohash.generate(order.maker_hash_id, 'small'); - // } + for (const order of data) { + roboidentitiesClient.generateRobohash(order.maker_hash_id, 'small'); + } }; loadBook = (onDataLoad: () => void = () => {}): void => { diff --git a/frontend/src/models/Garage.model.ts b/frontend/src/models/Garage.model.ts index a6258cd1..47204b91 100644 --- a/frontend/src/models/Garage.model.ts +++ b/frontend/src/models/Garage.model.ts @@ -59,7 +59,9 @@ class Garage { const rawSlots = JSON.parse(slotsDump); Object.values(rawSlots).forEach((rawSlot: Record) => { if (rawSlot?.token) { - this.slots[rawSlot.token] = new Slot(rawSlot.token, Object.keys(rawSlot.robots), {}); + this.slots[rawSlot.token] = new Slot(rawSlot.token, Object.keys(rawSlot.robots), {}, () => + this.triggerHook('onRobotUpdate'), + ); Object.keys(rawSlot.robots).forEach((shortAlias) => { const rawRobot = rawSlot.robots[shortAlias]; @@ -113,9 +115,10 @@ class Garage { if (!token || !shortAliases) return; if (this.getSlot(token) === null) { - this.slots[token] = new Slot(token, shortAliases, attributes); + this.slots[token] = new Slot(token, shortAliases, attributes, () => + this.triggerHook('onRobotUpdate'), + ); this.save(); - this.triggerHook('onRobotUpdate'); } }; diff --git a/frontend/src/models/Slot.model.ts b/frontend/src/models/Slot.model.ts index b043dd7b..76909bf0 100644 --- a/frontend/src/models/Slot.model.ts +++ b/frontend/src/models/Slot.model.ts @@ -1,17 +1,24 @@ import { sha256 } from 'js-sha256'; import { Robot, type Order } from '.'; -// import { robohash } from '../components/RobotAvatar/RobohashGenerator'; -// import { generate_roboname } from 'robo-identities-wasm'; +import { roboidentitiesClient } from '../services/Roboidentities/Web'; class Slot { - constructor(token: string, shortAliases: string[], robotAttributes: Record) { + constructor( + token: string, + shortAliases: string[], + robotAttributes: Record, + onRobotUpdate: () => void, + ) { this.token = token; this.hashId = sha256(sha256(this.token)); - this.nickname = 'No Nick Display (WIP)'; - // trigger RoboHash avatar generation in webworker and store in RoboHash class cache. - // void robohash.generate(this.hashId, 'small'); - // void robohash.generate(this.hashId, 'large'); + this.nickname = null; + roboidentitiesClient.generateRoboname(this.hashId).then((nickname) => { + this.nickname = nickname; + onRobotUpdate(); + }); + roboidentitiesClient.generateRobohash(this.hashId, 'small'); + roboidentitiesClient.generateRobohash(this.hashId, 'large'); this.robots = shortAliases.reduce((acc: Record, shortAlias: string) => { acc[shortAlias] = new Robot(robotAttributes); @@ -22,6 +29,7 @@ class Slot { this.activeShortAlias = null; this.lastShortAlias = null; this.copiedToken = false; + onRobotUpdate(); } token: string | null; diff --git a/frontend/src/services/Native/index.d.ts b/frontend/src/services/Native/index.d.ts index e7dc6f68..680c8427 100644 --- a/frontend/src/services/Native/index.d.ts +++ b/frontend/src/services/Native/index.d.ts @@ -30,7 +30,19 @@ export interface NativeWebViewMessageSystem { detail?: string; } -export declare type NativeWebViewMessage = NativeWebViewMessageHttp | NativeWebViewMessageSystem; +export interface NativeWebViewMessageRoboidentities { + id?: number; + category: 'roboidentities'; + type: 'roboname' | 'robohash'; + string?: string; + size?: string; +} + +export declare type NativeWebViewMessage = + | NativeWebViewMessageHttp + | NativeWebViewMessageSystem + | NativeWebViewMessageRoboidentities + | NA; export interface NativeRobosatsPromise { resolve: (value: object | PromiseLike) => void; diff --git a/frontend/src/services/Roboidentities/Native.ts b/frontend/src/services/Roboidentities/Native.ts new file mode 100644 index 00000000..250b9a81 --- /dev/null +++ b/frontend/src/services/Roboidentities/Native.ts @@ -0,0 +1,4 @@ +import RoboidentitiesClientNativeClient from './RoboidentitiesNativeClient'; +import { RoboidentitiesClient } from './type'; + +export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesClientNativeClient(); diff --git a/frontend/src/services/Roboidentities/RoboidentitiesNativeClient/index.ts b/frontend/src/services/Roboidentities/RoboidentitiesNativeClient/index.ts new file mode 100644 index 00000000..85b55b8b --- /dev/null +++ b/frontend/src/services/Roboidentities/RoboidentitiesNativeClient/index.ts @@ -0,0 +1,42 @@ +import { type RoboidentitiesClient } from '../type'; + +class RoboidentitiesNativeClient implements RoboidentitiesClient { + private robonames: Record = {}; + private robohashes: Record = {}; + + public generateRoboname: (initialString: string) => Promise = async (initialString) => { + if (this.robonames[initialString]) { + return this.robonames[initialString]; + } else { + const response = await window.NativeRobosats?.postMessage({ + category: 'roboidentities', + type: 'roboname', + detail: initialString, + }); + const result = response ? Object.values(response)[0] : ''; + this.robonames[initialString] = result; + return result; + } + }; + + public generateRobohash: (initialString: string, size: 'small' | 'large') => Promise = + async (initialString, size) => { + const key = `${initialString};${size === 'small' ? 80 : 256}`; + + if (this.robohashes[key]) { + return this.robohashes[key]; + } else { + const response = await window.NativeRobosats?.postMessage({ + category: 'roboidentities', + type: 'robohash', + detail: key, + }); + const result = response ? Object.values(response)[0] : ''; + const image = `data:image/png;base64,${result}`; + this.robohashes[key] = image; + return image; + } + }; +} + +export default RoboidentitiesNativeClient; diff --git a/frontend/src/components/RobotAvatar/RobohashGenerator.ts b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/RobohashGenerator.ts similarity index 98% rename from frontend/src/components/RobotAvatar/RobohashGenerator.ts rename to frontend/src/services/Roboidentities/RoboidentitiesWebClient/RobohashGenerator.ts index 038cdf92..f00075e0 100644 --- a/frontend/src/components/RobotAvatar/RobohashGenerator.ts +++ b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/RobohashGenerator.ts @@ -81,7 +81,7 @@ class RoboGenerator { hash, size, ) => { - const cacheKey = `${size}px;${hash}`; + const cacheKey = `${hash};${size}`; if (this.assetsCache[cacheKey]) { return this.assetsCache[cacheKey]; } else { diff --git a/frontend/src/services/Roboidentities/RoboidentitiesWebClient/index.ts b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/index.ts new file mode 100644 index 00000000..20e2e137 --- /dev/null +++ b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/index.ts @@ -0,0 +1,18 @@ +import { type RoboidentitiesClient } from '../type'; +import { generate_roboname } from 'robo-identities-wasm'; +import { robohash } from './RobohashGenerator'; + +class RoboidentitiesClientWebClient implements RoboidentitiesClient { + public generateRoboname: (initialString: string) => Promise = async (initialString) => { + return new Promise(async (resolve, _reject) => { + resolve(generate_roboname(initialString)); + }); + }; + + public generateRobohash: (initialString: string, size: 'small' | 'large') => Promise = + async (initialString, size) => { + return robohash.generate(initialString, size); + }; +} + +export default RoboidentitiesClientWebClient; diff --git a/frontend/src/components/RobotAvatar/robohash.worker.ts b/frontend/src/services/Roboidentities/RoboidentitiesWebClient/robohash.worker.ts similarity index 100% rename from frontend/src/components/RobotAvatar/robohash.worker.ts rename to frontend/src/services/Roboidentities/RoboidentitiesWebClient/robohash.worker.ts diff --git a/frontend/src/services/Roboidentities/Web.ts b/frontend/src/services/Roboidentities/Web.ts new file mode 100644 index 00000000..8730491c --- /dev/null +++ b/frontend/src/services/Roboidentities/Web.ts @@ -0,0 +1,4 @@ +import RoboidentitiesClientWebClient from './RoboidentitiesWebClient'; +import { RoboidentitiesClient } from './type'; + +export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesClientWebClient(); diff --git a/frontend/src/services/Roboidentities/type.ts b/frontend/src/services/Roboidentities/type.ts new file mode 100644 index 00000000..4a54ea99 --- /dev/null +++ b/frontend/src/services/Roboidentities/type.ts @@ -0,0 +1,4 @@ +export interface RoboidentitiesClient { + generateRoboname: (initialString: string) => Promise; + generateRobohash: (initialString: string, size: 'small' | 'large') => Promise; +} diff --git a/frontend/src/services/api/ApiNativeClient/index.ts b/frontend/src/services/api/ApiNativeClient/index.ts index 9ab27d91..6f794eec 100644 --- a/frontend/src/services/api/ApiNativeClient/index.ts +++ b/frontend/src/services/api/ApiNativeClient/index.ts @@ -30,7 +30,6 @@ class ApiNativeClient implements ApiClient { }; private readonly parseResponse = (response: Record): object => { - console.log('response', response); if (response.headers['set-cookie'] != null) { response.headers['set-cookie'].forEach((cookie: string) => { const keySplit: string[] = cookie.split('='); diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index d3625542..442ae97c 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -56,6 +56,15 @@ const configMobile: Configuration = { async: true, }, }, + { + test: path.resolve(__dirname, 'src/services/Roboidentities/Web.ts'), + loader: 'file-replace-loader', + options: { + condition: 'if-replacement-exists', + replacement: path.resolve(__dirname, 'src/services/Roboidentities/Native.ts'), + async: true, + }, + }, { test: path.resolve(__dirname, 'src/components/RobotAvatar/placeholder.json'), loader: 'file-replace-loader', @@ -81,6 +90,10 @@ const configMobile: Configuration = { from: path.resolve(__dirname, 'static/assets/sounds'), to: path.resolve(__dirname, '../mobile/html/Web.bundle/assets/sounds'), }, + { + from: path.resolve(__dirname, 'static/federation'), + to: path.resolve(__dirname, '../mobile/html/Web.bundle/assets/federation'), + }, ], }), ], diff --git a/mobile/App.tsx b/mobile/App.tsx index e2bb3905..d61444be 100644 --- a/mobile/App.tsx +++ b/mobile/App.tsx @@ -6,6 +6,7 @@ import Clipboard from '@react-native-clipboard/clipboard'; import EncryptedStorage from 'react-native-encrypted-storage'; import { name as app_name, version as app_version } from './package.json'; import TorModule from './native/TorModule'; +import RoboIdentitiesModule from './native/RoboIdentitiesModule'; const backgroundColors = { light: 'white', @@ -129,6 +130,14 @@ const App = () => { } else if (data.type === 'deleteCookie') { EncryptedStorage.removeItem(data.key); } + } else if (data.category === 'roboidentities') { + if (data.type === 'roboname') { + const roboname = await RoboIdentitiesModule.generateRoboname(data.detail); + injectMessageResolve(data.id, { roboname }); + } else if (data.type === 'robohash') { + const robohash = await RoboIdentitiesModule.generateRobohash(data.detail); + injectMessageResolve(data.id, { robohash }); + } } }; diff --git a/mobile/android/app/src/main/java/com/robosats/RoboIdentities.java b/mobile/android/app/src/main/java/com/robosats/RoboIdentities.java new file mode 100644 index 00000000..c37f8505 --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/RoboIdentities.java @@ -0,0 +1,22 @@ +package com.robosats; + +import android.util.Log; + +public class RoboIdentities { + static { + System.loadLibrary("robonames"); + System.loadLibrary("robohash"); + } + + public String generateRoboname(String initial_string) { + return nativeGenerateRoboname(initial_string); + } + + public String generateRobohash(String initial_string) { + return nativeGenerateRobohash(initial_string); + } + + // Native functions implemented in Rust. + private static native String nativeGenerateRoboname(String initial_string); + private static native String nativeGenerateRobohash(String initial_string); +} diff --git a/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java b/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java index a421fab8..2eca0080 100644 --- a/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java +++ b/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java @@ -4,6 +4,7 @@ import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; +import com.robosats.modules.RoboIdentitiesModule; import com.robosats.modules.TorModule; import java.util.ArrayList; @@ -22,6 +23,7 @@ public class RobosatsPackage implements ReactPackage { List modules = new ArrayList<>(); modules.add(new TorModule(reactContext)); + modules.add(new RoboIdentitiesModule(reactContext)); return modules; } diff --git a/mobile/android/app/src/main/java/com/robosats/modules/RoboIdentitiesModule.java b/mobile/android/app/src/main/java/com/robosats/modules/RoboIdentitiesModule.java new file mode 100644 index 00000000..c18d131b --- /dev/null +++ b/mobile/android/app/src/main/java/com/robosats/modules/RoboIdentitiesModule.java @@ -0,0 +1,37 @@ +package com.robosats.modules; + +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.robosats.RoboIdentities; + +public class RoboIdentitiesModule extends ReactContextBaseJavaModule { + private ReactApplicationContext context; + + public RoboIdentitiesModule(ReactApplicationContext reactContext) { + context = reactContext; + } + + @Override + public String getName() { + return "RoboIdentitiesModule"; + } + + @ReactMethod + public void generateRoboname(String initial_string, final Promise promise) { + String roboname = new RoboIdentities().generateRoboname(initial_string); + promise.resolve(roboname); + } + + @ReactMethod + public void generateRobohash(String initial_string, final Promise promise) { + String robohash = new RoboIdentities().generateRobohash(initial_string); + promise.resolve(robohash); + } +} diff --git a/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java b/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java index 0705c347..224bc5d8 100644 --- a/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java +++ b/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java @@ -43,7 +43,6 @@ public class TorModule extends ReactContextBaseJavaModule { @ReactMethod public void sendRequest(String action, String url, String headers, String body, final Promise promise) throws JSONException { - Log.d("RobosatsUrl", url); OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout .readTimeout(30, TimeUnit.SECONDS) // Set read timeout @@ -75,7 +74,6 @@ public class TorModule extends ReactContextBaseJavaModule { @Override public void onResponse(Call call, Response response) throws IOException { - Log.d("RobosatsCode", String.valueOf(response.code())); String body = response.body() != null ? response.body().string() : "{}"; JSONObject headersJson = new JSONObject(); response.headers().names().forEach(name -> { diff --git a/mobile/android/app/src/main/jniLibs/arm64-v8a/librobohash.so b/mobile/android/app/src/main/jniLibs/arm64-v8a/librobohash.so new file mode 100755 index 00000000..8bc01999 Binary files /dev/null and b/mobile/android/app/src/main/jniLibs/arm64-v8a/librobohash.so differ diff --git a/mobile/android/app/src/main/jniLibs/arm64-v8a/librobonames.so b/mobile/android/app/src/main/jniLibs/arm64-v8a/librobonames.so new file mode 100755 index 00000000..3e13c6d8 Binary files /dev/null and b/mobile/android/app/src/main/jniLibs/arm64-v8a/librobonames.so differ diff --git a/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobohash.so b/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobohash.so new file mode 100755 index 00000000..3775d2f0 Binary files /dev/null and b/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobohash.so differ diff --git a/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobonames.so b/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobonames.so new file mode 100755 index 00000000..2cf9568e Binary files /dev/null and b/mobile/android/app/src/main/jniLibs/armeabi-v7a/librobonames.so differ diff --git a/mobile/native/RoboIdentitiesModule.ts b/mobile/native/RoboIdentitiesModule.ts new file mode 100644 index 00000000..a5c3c83e --- /dev/null +++ b/mobile/native/RoboIdentitiesModule.ts @@ -0,0 +1,9 @@ +import { NativeModules } from 'react-native'; +const { RoboIdentitiesModule } = NativeModules; + +interface RoboIdentitiesModuleInterface { + generateRoboname: (initialString: String) => Promise; + generateRobohash: (initialString: String) => Promise; +} + +export default RoboIdentitiesModule as RoboIdentitiesModuleInterface;