mirror of
https://github.com/RoboSats/robosats.git
synced 2025-08-02 16:11:56 +00:00
TS <> Korlin communication
This commit is contained in:
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@ -51,6 +51,7 @@
|
|||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"robo-identities-wasm": "^0.1.0",
|
"robo-identities-wasm": "^0.1.0",
|
||||||
"simple-plist": "^1.3.1",
|
"simple-plist": "^1.3.1",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
"webln": "^0.3.2",
|
"webln": "^0.3.2",
|
||||||
"websocket": "^1.0.35"
|
"websocket": "^1.0.35"
|
||||||
},
|
},
|
||||||
@ -17013,6 +17014,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "11.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||||
|
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/esm/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/v8-compile-cache-lib": {
|
"node_modules/v8-compile-cache-lib": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
@ -98,6 +98,7 @@
|
|||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"robo-identities-wasm": "^0.1.0",
|
"robo-identities-wasm": "^0.1.0",
|
||||||
"simple-plist": "^1.3.1",
|
"simple-plist": "^1.3.1",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
"webln": "^0.3.2",
|
"webln": "^0.3.2",
|
||||||
"websocket": "^1.0.35"
|
"websocket": "^1.0.35"
|
||||||
}
|
}
|
||||||
|
28
frontend/src/services/Android/index.ts
Normal file
28
frontend/src/services/Android/index.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
AndroidAppRobosats?: AndroidAppRobosats;
|
||||||
|
AndroidRobosats?: AndroidRobosats;
|
||||||
|
RobosatsSettings: 'web-basic' | 'web-pro' | 'selfhosted-basic' | 'selfhosted-pro';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AndroidAppRobosats {
|
||||||
|
generateRoboname: (uuid: string, initialString: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AndroidRobosats {
|
||||||
|
private promises: Record<string, (value: string | PromiseLike<string>) => void> = {};
|
||||||
|
|
||||||
|
public storePromise: (
|
||||||
|
uuid: string,
|
||||||
|
promise: (value: string | PromiseLike<string>) => void,
|
||||||
|
) => void = (uuid, promise) => {
|
||||||
|
this.promises[uuid] = promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
public onResolvePromise: (uuid: string, response: string) => void = (uuid, respone) => {
|
||||||
|
this.promises[uuid](respone);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AndroidRobosats;
|
4
frontend/src/services/Roboidentities/Android.ts
Normal file
4
frontend/src/services/Roboidentities/Android.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import RoboidentitiesAndroidClient from './RoboidentitiesAndroidClient';
|
||||||
|
import { type RoboidentitiesClient } from './type';
|
||||||
|
|
||||||
|
export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesAndroidClient();
|
@ -0,0 +1,44 @@
|
|||||||
|
import { type RoboidentitiesClient } from '../type';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
class RoboidentitiesAndroidClient implements RoboidentitiesClient {
|
||||||
|
private robonames: Record<string, string> = {};
|
||||||
|
private robohashes: Record<string, string> = {};
|
||||||
|
|
||||||
|
public generateRoboname: (initialString: string) => Promise<string> = async (initialString) => {
|
||||||
|
if (this.robonames[initialString]) {
|
||||||
|
return this.robonames[initialString];
|
||||||
|
} else {
|
||||||
|
const result = await new Promise<string>((resolve) => {
|
||||||
|
const uuid: string = uuidv4();
|
||||||
|
window.AndroidAppRobosats?.generateRoboname(uuid, initialString);
|
||||||
|
window.AndroidRobosats?.storePromise(uuid, resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.robonames[initialString] = result;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public generateRobohash: (initialString: string, size: 'small' | 'large') => Promise<string> =
|
||||||
|
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: string = response ? Object.values(response)[0] : '';
|
||||||
|
const image: string = `data:image/png;base64,${result}`;
|
||||||
|
this.robohashes[key] = image;
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoboidentitiesAndroidClient;
|
55
frontend/src/services/System/SystemAndroidClient/index.ts
Normal file
55
frontend/src/services/System/SystemAndroidClient/index.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { type SystemClient } from '..';
|
||||||
|
import AndroidRobosats from '../../Android';
|
||||||
|
|
||||||
|
class SystemAndroidClient implements SystemClient {
|
||||||
|
constructor() {
|
||||||
|
window.AndroidRobosats = new AndroidRobosats();
|
||||||
|
}
|
||||||
|
|
||||||
|
public loading = false;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
public copyToClipboard: (value: string) => void = () => {};
|
||||||
|
|
||||||
|
// Cookies
|
||||||
|
public getCookie: (key: string) => string = (key) => {
|
||||||
|
let cookieValue = null;
|
||||||
|
if (document?.cookie !== '') {
|
||||||
|
const cookies = document.cookie.split(';');
|
||||||
|
for (let i = 0; i < cookies.length; i++) {
|
||||||
|
const cookie = cookies[i].trim();
|
||||||
|
// Does this cookie string begin with the key we want?
|
||||||
|
if (cookie.substring(0, key.length + 1) === key + '=') {
|
||||||
|
cookieValue = decodeURIComponent(cookie.substring(key.length + 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookieValue ?? '';
|
||||||
|
};
|
||||||
|
|
||||||
|
public setCookie: (key: string, value: string) => void = (key, value) => {
|
||||||
|
document.cookie = `${key}=${value};path=/;SameSite=None;Secure`;
|
||||||
|
};
|
||||||
|
|
||||||
|
public deleteCookie: (key: string) => void = (key) => {
|
||||||
|
document.cookie = `${key}= ;path=/; expires = Thu, 01 Jan 1970 00:00:00 GMT`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Local storage
|
||||||
|
public getItem: (key: string) => string = (key) => {
|
||||||
|
const value = window.localStorage.getItem(key);
|
||||||
|
return value ?? '';
|
||||||
|
};
|
||||||
|
|
||||||
|
public setItem: (key: string, value: string) => void = (key, value) => {
|
||||||
|
window.localStorage.setItem(key, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
public deleteItem: (key: string) => void = (key) => {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemAndroidClient;
|
@ -1,6 +1,7 @@
|
|||||||
import SystemNativeClient from './SystemNativeClient';
|
import SystemNativeClient from './SystemNativeClient';
|
||||||
import SystemWebClient from './SystemWebClient';
|
import SystemWebClient from './SystemWebClient';
|
||||||
import SystemDesktopClient from './SystemDesktopClient';
|
import SystemDesktopClient from './SystemDesktopClient';
|
||||||
|
import SystemAndroidClient from './SystemAndroidClient';
|
||||||
|
|
||||||
export interface SystemClient {
|
export interface SystemClient {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -21,6 +22,9 @@ function getSystemClient(): SystemClient {
|
|||||||
} else if (window.navigator.userAgent.includes('Electron')) {
|
} else if (window.navigator.userAgent.includes('Electron')) {
|
||||||
// If userAgent has "Electron", we assume the app is running inside of an Electron app.
|
// If userAgent has "Electron", we assume the app is running inside of an Electron app.
|
||||||
return new SystemDesktopClient();
|
return new SystemDesktopClient();
|
||||||
|
} else if (window.navigator.userAgent.includes('AndroidRobosats')) {
|
||||||
|
// If userAgent has "AndroidRobosats", we assume the app is running inside the Kotlin webview of the RoboSats Android app.
|
||||||
|
return new SystemAndroidClient();
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we assume the app is running in a web browser.
|
// Otherwise, we assume the app is running in a web browser.
|
||||||
return new SystemWebClient();
|
return new SystemWebClient();
|
||||||
|
@ -152,7 +152,7 @@ const configNode: Configuration = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const configMobile: Configuration = {
|
const configNative: Configuration = {
|
||||||
...config,
|
...config,
|
||||||
module: {
|
module: {
|
||||||
...config.module,
|
...config.module,
|
||||||
@ -163,7 +163,7 @@ const configMobile: Configuration = {
|
|||||||
loader: 'file-replace-loader',
|
loader: 'file-replace-loader',
|
||||||
options: {
|
options: {
|
||||||
condition: 'if-replacement-exists',
|
condition: 'if-replacement-exists',
|
||||||
replacement: path.resolve(__dirname, 'src/i18n/Native.js'),
|
replacement: path.resolve(__dirname, 'src/i18n/Mobile.js'),
|
||||||
async: true,
|
async: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -172,7 +172,7 @@ const configMobile: Configuration = {
|
|||||||
loader: 'file-replace-loader',
|
loader: 'file-replace-loader',
|
||||||
options: {
|
options: {
|
||||||
condition: 'if-replacement-exists',
|
condition: 'if-replacement-exists',
|
||||||
replacement: path.resolve(__dirname, 'src/geo/Native.js'),
|
replacement: path.resolve(__dirname, 'src/geo/Mobile.js'),
|
||||||
async: true,
|
async: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -236,6 +236,63 @@ const configMobile: Configuration = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const configAndroid: Configuration = {
|
||||||
|
...config,
|
||||||
|
module: {
|
||||||
|
...config.module,
|
||||||
|
rules: [
|
||||||
|
...(config?.module?.rules || []),
|
||||||
|
{
|
||||||
|
test: path.resolve(__dirname, 'src/i18n/Web.js'),
|
||||||
|
loader: 'file-replace-loader',
|
||||||
|
options: {
|
||||||
|
condition: 'if-replacement-exists',
|
||||||
|
replacement: path.resolve(__dirname, 'src/i18n/Mobile.js'),
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: path.resolve(__dirname, 'src/geo/Web.js'),
|
||||||
|
loader: 'file-replace-loader',
|
||||||
|
options: {
|
||||||
|
condition: 'if-replacement-exists',
|
||||||
|
replacement: path.resolve(__dirname, 'src/geo/Mobile.js'),
|
||||||
|
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/Android.ts'),
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: path.resolve(__dirname, 'src/components/RobotAvatar/placeholder.json'),
|
||||||
|
loader: 'file-replace-loader',
|
||||||
|
options: {
|
||||||
|
condition: 'if-replacement-exists',
|
||||||
|
replacement: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'src/components/RobotAvatar/placeholder_highres.json',
|
||||||
|
),
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, '../mobile_new/app/src/main/assets/static/frontend'),
|
||||||
|
filename: `main.v${version}.[contenthash].js`,
|
||||||
|
clean: true,
|
||||||
|
publicPath: './static/frontend/',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: path.resolve(__dirname, 'templates/frontend/index.ejs'),
|
template: path.resolve(__dirname, 'templates/frontend/index.ejs'),
|
||||||
templateParameters: {
|
templateParameters: {
|
||||||
@ -293,4 +350,4 @@ const configMobile: Configuration = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [configNode, configMobile];
|
export default [configNode, configNative, configAndroid];
|
||||||
|
@ -6,6 +6,7 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.*
|
import android.webkit.*
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.koalasat.robosats.tor.WebAppInterface
|
||||||
import com.robosats.tor.TorKmp
|
import com.robosats.tor.TorKmp
|
||||||
import com.robosats.tor.TorKmpManager
|
import com.robosats.tor.TorKmpManager
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
@ -394,6 +395,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webView.settings.userAgentString = "AndroidRobosats"
|
||||||
|
|
||||||
|
// Add the JavaScript interface
|
||||||
|
webView.addJavascriptInterface(WebAppInterface(this, webView), "AndroidAppRobosats")
|
||||||
|
|
||||||
// Now it's safe to load the local HTML file
|
// Now it's safe to load the local HTML file
|
||||||
webView.loadUrl("file:///android_asset/index.html")
|
webView.loadUrl("file:///android_asset/index.html")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.koalasat.robosats.tor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.webkit.JavascriptInterface
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.widget.Toast
|
||||||
|
|
||||||
|
class WebAppInterface(private val context: Context, private val webView: WebView) {
|
||||||
|
@JavascriptInterface
|
||||||
|
fun generateRoboname(uuid: String, message: String) {
|
||||||
|
// Handle the message received from JavaScript
|
||||||
|
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
webView.post {
|
||||||
|
webView.evaluateJavascript("javascript:window.AndroidRobosats.onResolvePromise('${uuid}', '${message}')", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user