mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Android Tor icon and copy to clipboard (#269)
* Android Clipboard and Tor Status Icon * working clipboard and lintern * Fix * Add style for Tor connection component * Fix Freeze and Internet out * Fix Typo Co-authored-by: Reckless_Satoshi <reckless.satoshi@protonmail.com>
This commit is contained in:
parent
1352937fe6
commit
9bda934ee5
@ -5,6 +5,7 @@ import { CssBaseline, IconButton, Link } from '@mui/material';
|
|||||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
import UnsafeAlert from './UnsafeAlert';
|
import UnsafeAlert from './UnsafeAlert';
|
||||||
import { LearnDialog } from './Dialogs';
|
import { LearnDialog } from './Dialogs';
|
||||||
|
import TorConnection from './TorConnection';
|
||||||
|
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
@ -16,6 +17,7 @@ import SchoolIcon from '@mui/icons-material/School';
|
|||||||
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
|
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
|
||||||
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
|
import { systemClient } from '../services/System';
|
||||||
|
|
||||||
export default class App extends Component {
|
export default class App extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -102,6 +104,7 @@ export default class App extends Component {
|
|||||||
open={this.state.openLearn}
|
open={this.state.openLearn}
|
||||||
onClose={() => this.setState({ openLearn: false })}
|
onClose={() => this.setState({ openLearn: false })}
|
||||||
/>
|
/>
|
||||||
|
<TorConnection />
|
||||||
<IconButton
|
<IconButton
|
||||||
color='inherit'
|
color='inherit'
|
||||||
sx={{ position: 'fixed', right: '34px' }}
|
sx={{ position: 'fixed', right: '34px' }}
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { saveAsJson } from '../../utils/saveFile';
|
import { saveAsJson } from '../../utils/saveFile';
|
||||||
import { copyToClipboard } from '../../utils/clipboard';
|
import { systemClient } from '../../services/System';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import KeyIcon from '@mui/icons-material/Key';
|
import KeyIcon from '@mui/icons-material/Key';
|
||||||
@ -37,7 +37,7 @@ function CredentialTextfield(props) {
|
|||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<Tooltip disableHoverListener enterTouchDelay={0} title={props.copiedTitle}>
|
<Tooltip disableHoverListener enterTouchDelay={0} title={props.copiedTitle}>
|
||||||
<IconButton onClick={async () => await copyToClipboard(props.value)}>
|
<IconButton onClick={() => systemClient.copyToClipboard(props.value)}>
|
||||||
<ContentCopy />
|
<ContentCopy />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -37,7 +37,7 @@ import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
|
|||||||
import { UserNinjaIcon, BitcoinIcon } from '../Icons';
|
import { UserNinjaIcon, BitcoinIcon } from '../Icons';
|
||||||
|
|
||||||
import { getCookie } from '../../utils/cookies';
|
import { getCookie } from '../../utils/cookies';
|
||||||
import { copyToClipboard } from '../../utils/clipboard';
|
import { systemClient } from '../../services/System';
|
||||||
import { getWebln } from '../../utils/webln';
|
import { getWebln } from '../../utils/webln';
|
||||||
import RobotAvatar from '../Robots/RobotAvatar';
|
import RobotAvatar from '../Robots/RobotAvatar';
|
||||||
|
|
||||||
@ -101,13 +101,13 @@ const ProfileDialog = ({
|
|||||||
const robotToken = getCookie('robot_token');
|
const robotToken = getCookie('robot_token');
|
||||||
|
|
||||||
if (robotToken) {
|
if (robotToken) {
|
||||||
copyToClipboard(robotToken);
|
systemClient.copyToClipboard(robotToken);
|
||||||
setAppState({ copiedToken: true });
|
setAppState({ copiedToken: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyReferralCodeHandler = () => {
|
const copyReferralCodeHandler = () => {
|
||||||
copyToClipboard(`http://${host}/ref/${referralCode}`);
|
systemClient.copyToClipboard(`http://${host}/ref/${referralCode}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWeblnInvoiceClicked = async (e: any) => {
|
const handleWeblnInvoiceClicked = async (e: any) => {
|
||||||
|
@ -18,9 +18,9 @@ import ReconnectingWebSocket from 'reconnecting-websocket';
|
|||||||
import { encryptMessage, decryptMessage } from '../utils/pgp';
|
import { encryptMessage, decryptMessage } from '../utils/pgp';
|
||||||
import { getCookie } from '../utils/cookies';
|
import { getCookie } from '../utils/cookies';
|
||||||
import { saveAsJson } from '../utils/saveFile';
|
import { saveAsJson } from '../utils/saveFile';
|
||||||
import { copyToClipboard } from '../utils/clipboard';
|
|
||||||
import { AuditPGPDialog } from './Dialogs';
|
import { AuditPGPDialog } from './Dialogs';
|
||||||
import RobotAvatar from './Robots/RobotAvatar';
|
import RobotAvatar from './Robots/RobotAvatar';
|
||||||
|
import { systemClient } from '../services/System';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
@ -336,7 +336,7 @@ class Chat extends Component {
|
|||||||
<IconButton
|
<IconButton
|
||||||
sx={{ height: 18, width: 18 }}
|
sx={{ height: 18, width: 18 }}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
copyToClipboard(
|
systemClient.copyToClipboard(
|
||||||
this.state.showPGP[props.index]
|
this.state.showPGP[props.index]
|
||||||
? props.message.encryptedMessage
|
? props.message.encryptedMessage
|
||||||
: props.message.plainTextMessage,
|
: props.message.plainTextMessage,
|
||||||
|
10
frontend/src/components/Icons/Tor.tsx
Normal file
10
frontend/src/components/Icons/Tor.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { SvgIcon } from '@mui/material';
|
||||||
|
|
||||||
|
export default function TorIcon(props) {
|
||||||
|
return (
|
||||||
|
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 180 180'>
|
||||||
|
<path d='M90.1846205,163.631147 L90.1846205,152.721073 C124.743583,152.621278 152.726063,124.581416 152.726063,89.9975051 C152.726063,55.4160892 124.743583,27.3762266 90.1846205,27.2764318 L90.1846205,16.366358 C130.768698,16.4686478 163.633642,49.3909741 163.633642,89.9975051 C163.633642,130.606531 130.768698,163.531352 90.1846205,163.631147 Z M90.1846205,125.444642 C109.677053,125.342352 125.454621,109.517381 125.454621,89.9975051 C125.454621,70.4801242 109.677053,54.6551533 90.1846205,54.5528636 L90.1846205,43.6452847 C115.704663,43.7450796 136.364695,64.4550091 136.364695,89.9975051 C136.364695,115.542496 115.704663,136.252426 90.1846205,136.35222 L90.1846205,125.444642 Z M90.1846205,70.9167267 C100.640628,71.0165216 109.090758,79.5165493 109.090758,89.9975051 C109.090758,100.480956 100.640628,108.980984 90.1846205,109.080778 L90.1846205,70.9167267 Z M0,89.9975051 C0,139.705328 40.2921772,180 90,180 C139.705328,180 180,139.705328 180,89.9975051 C180,40.2921772 139.705328,0 90,0 C40.2921772,0 0,40.2921772 0,89.9975051 Z'></path>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
@ -14,6 +14,7 @@ export { default as SellSatsIcon } from './SellSats';
|
|||||||
export { default as SendReceiveIcon } from './SendReceive';
|
export { default as SendReceiveIcon } from './SendReceive';
|
||||||
export { default as ExportIcon } from './Export';
|
export { default as ExportIcon } from './Export';
|
||||||
export { default as UserNinjaIcon } from './UserNinja';
|
export { default as UserNinjaIcon } from './UserNinja';
|
||||||
|
export { default as TorIcon } from './Tor';
|
||||||
|
|
||||||
// Some Flags missing on react-flags
|
// Some Flags missing on react-flags
|
||||||
export { default as BasqueCountryFlag } from './BasqueCountryFlag';
|
export { default as BasqueCountryFlag } from './BasqueCountryFlag';
|
||||||
|
@ -45,7 +45,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|||||||
|
|
||||||
import { getCookie } from '../utils/cookies';
|
import { getCookie } from '../utils/cookies';
|
||||||
import { pn } from '../utils/prettyNumbers';
|
import { pn } from '../utils/prettyNumbers';
|
||||||
import { copyToClipboard } from '../utils/clipboard';
|
import { systemClient } from '../services/System';
|
||||||
|
|
||||||
class MakerPage extends Component {
|
class MakerPage extends Component {
|
||||||
defaultCurrency = 1;
|
defaultCurrency = 1;
|
||||||
@ -924,7 +924,7 @@ class MakerPage extends Component {
|
|||||||
open={this.state.openStoreToken}
|
open={this.state.openStoreToken}
|
||||||
onClose={() => this.setState({ openStoreToken: false })}
|
onClose={() => this.setState({ openStoreToken: false })}
|
||||||
onClickCopy={() =>
|
onClickCopy={() =>
|
||||||
copyToClipboard(getCookie('robot_token')) &
|
systemClient.copyToClipboard(getCookie('robot_token')) &
|
||||||
this.props.setAppState({ copiedToken: true })
|
this.props.setAppState({ copiedToken: true })
|
||||||
}
|
}
|
||||||
copyIconColor={this.props.copiedToken ? 'inherit' : 'primary'}
|
copyIconColor={this.props.copiedToken ? 'inherit' : 'primary'}
|
||||||
|
@ -52,7 +52,7 @@ import { SendReceiveIcon } from './Icons';
|
|||||||
|
|
||||||
import { getCookie } from '../utils/cookies';
|
import { getCookie } from '../utils/cookies';
|
||||||
import { pn } from '../utils/prettyNumbers';
|
import { pn } from '../utils/prettyNumbers';
|
||||||
import { copyToClipboard } from '../utils/clipboard';
|
import { systemClient } from '../services/System';
|
||||||
import { getWebln } from '../utils/webln';
|
import { getWebln } from '../utils/webln';
|
||||||
import { apiClient } from '../services/api';
|
import { apiClient } from '../services/api';
|
||||||
import RobotAvatar from './Robots/RobotAvatar';
|
import RobotAvatar from './Robots/RobotAvatar';
|
||||||
@ -527,7 +527,8 @@ class OrderPage extends Component {
|
|||||||
open={this.state.openStoreToken}
|
open={this.state.openStoreToken}
|
||||||
onClose={() => this.setState({ openStoreToken: false })}
|
onClose={() => this.setState({ openStoreToken: false })}
|
||||||
onClickCopy={() =>
|
onClickCopy={() =>
|
||||||
copyToClipboard(getCookie('robot_token')) & this.props.setAppState({ copiedToken: true })
|
systemClient.copyToClipboard(getCookie('robot_token')) &
|
||||||
|
this.props.setAppState({ copiedToken: true })
|
||||||
}
|
}
|
||||||
copyIconColor={this.props.copiedToken ? 'inherit' : 'primary'}
|
copyIconColor={this.props.copiedToken ? 'inherit' : 'primary'}
|
||||||
onClickBack={() => this.setState({ openStoreToken: false })}
|
onClickBack={() => this.setState({ openStoreToken: false })}
|
||||||
|
80
frontend/src/components/TorConnection.tsx
Normal file
80
frontend/src/components/TorConnection.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Box, CircularProgress, Tooltip } from '@mui/material';
|
||||||
|
import { TorIcon } from './Icons';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const TorConnection = (): JSX.Element => {
|
||||||
|
const [torStatus, setTorStatus] = useState<string>('NOTINIT');
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('torStatus', (event) => {
|
||||||
|
setTorStatus(event?.detail);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (window?.NativeRobosats && (torStatus === 'NOTINIT' || torStatus === 'STARTING')) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'inline-flex', position: 'fixed', left: '0.5em', top: '0.5em' }}>
|
||||||
|
<Tooltip open={true} placement='right' title={t('Connecting to TOR network')}>
|
||||||
|
<CircularProgress color='warning' thickness={6} size={23} />
|
||||||
|
</Tooltip>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TorIcon color='warning' sx={{ width: 20, height: 20 }} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else if (window?.NativeRobosats && (torStatus === '"Done"' || torStatus === 'DONE')) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'fixed',
|
||||||
|
left: '0.5em',
|
||||||
|
top: '0.5em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
enterTouchDelay={0}
|
||||||
|
enterDelay={1000}
|
||||||
|
placement='right'
|
||||||
|
title={t('Connected to TOR network')}
|
||||||
|
>
|
||||||
|
<TorIcon color='success' sx={{ width: 20, height: 20 }} />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else if (true) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'fixed',
|
||||||
|
left: '0.5em',
|
||||||
|
top: '0.5em',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip open={true} placement='right' title={t('TOR connection error')}>
|
||||||
|
<TorIcon color='error' sx={{ width: 20, height: 20 }} />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TorConnection;
|
@ -32,7 +32,7 @@ import Countdown, { zeroPad } from 'react-countdown';
|
|||||||
import Chat from './EncryptedChat';
|
import Chat from './EncryptedChat';
|
||||||
import TradeSummary from './TradeSummary';
|
import TradeSummary from './TradeSummary';
|
||||||
import MediaQuery from 'react-responsive';
|
import MediaQuery from 'react-responsive';
|
||||||
import { copyToClipboard } from '../utils/clipboard';
|
import { systemClient } from '../services/System';
|
||||||
import { apiClient } from '../services/api';
|
import { apiClient } from '../services/api';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
@ -286,7 +286,7 @@ class TradeBox extends Component {
|
|||||||
size='small'
|
size='small'
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboard(this.props.data.bond_invoice);
|
systemClient.copyToClipboard(this.props.data.bond_invoice);
|
||||||
}}
|
}}
|
||||||
align='center'
|
align='center'
|
||||||
>
|
>
|
||||||
@ -420,7 +420,7 @@ class TradeBox extends Component {
|
|||||||
size='small'
|
size='small'
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboard(this.props.data.escrow_invoice);
|
systemClient.copyToClipboard(this.props.data.escrow_invoice);
|
||||||
}}
|
}}
|
||||||
align='center'
|
align='center'
|
||||||
>
|
>
|
||||||
@ -1549,7 +1549,7 @@ class TradeBox extends Component {
|
|||||||
<IconButton
|
<IconButton
|
||||||
color='inherit'
|
color='inherit'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboard(this.props.data.txid);
|
systemClient.copyToClipboard(this.props.data.txid);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContentCopy sx={{ width: 16, height: 16 }} />
|
<ContentCopy sx={{ width: 16, height: 16 }} />
|
||||||
|
@ -26,7 +26,7 @@ import { genBase62Token, tokenStrength } from '../utils/token';
|
|||||||
import { genKey } from '../utils/pgp';
|
import { genKey } from '../utils/pgp';
|
||||||
import { getCookie, writeCookie, deleteCookie } from '../utils/cookies';
|
import { getCookie, writeCookie, deleteCookie } from '../utils/cookies';
|
||||||
import { saveAsJson } from '../utils/saveFile';
|
import { saveAsJson } from '../utils/saveFile';
|
||||||
import { copyToClipboard } from '../utils/clipboard';
|
import { systemClient } from '../services/System';
|
||||||
import { apiClient } from '../services/api/index';
|
import { apiClient } from '../services/api/index';
|
||||||
import RobotAvatar from './Robots/RobotAvatar';
|
import RobotAvatar from './Robots/RobotAvatar';
|
||||||
|
|
||||||
@ -321,7 +321,7 @@ class UserGenPage extends Component {
|
|||||||
!(getCookie('robot_token') === this.state.token))
|
!(getCookie('robot_token') === this.state.token))
|
||||||
}
|
}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
copyToClipboard(getCookie('robot_token')) &
|
systemClient.copyToClipboard(getCookie('robot_token')) &
|
||||||
this.props.setAppState({ copiedToken: true })
|
this.props.setAppState({ copiedToken: true })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
9
frontend/src/services/Native/index.d.ts
vendored
9
frontend/src/services/Native/index.d.ts
vendored
@ -20,7 +20,14 @@ export interface NativeWebViewMessageHttp {
|
|||||||
body?: object;
|
body?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare type NativeWebViewMessage = NativeWebViewMessageHttp;
|
export interface NativeWebViewMessageSystem {
|
||||||
|
id?: number;
|
||||||
|
category: 'system';
|
||||||
|
type: 'torStatus' | 'copyToClipboardString';
|
||||||
|
detail: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type NativeWebViewMessage = NativeWebViewMessageHttp | NativeWebViewMessageSystem;
|
||||||
|
|
||||||
export interface NativeRobosatsPromise {
|
export interface NativeRobosatsPromise {
|
||||||
resolve: (value: object | PromiseLike<object>) => void;
|
resolve: (value: object | PromiseLike<object>) => void;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { NativeRobosatsPromise, NativeWebViewMessage } from './index.d';
|
import { NativeRobosatsPromise, NativeWebViewMessage, NativeWebViewMessageSystem } from './index.d';
|
||||||
|
|
||||||
class NativeRobosats {
|
class NativeRobosats {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.messageCounter = 0;
|
this.messageCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public torDaemonStatus = 'NOTINIT';
|
||||||
|
|
||||||
private messageCounter: number;
|
private messageCounter: number;
|
||||||
|
|
||||||
private pendingMessages: { [id: number]: NativeRobosatsPromise } = {};
|
private pendingMessages: { [id: number]: NativeRobosatsPromise } = {};
|
||||||
@ -29,6 +31,13 @@ class NativeRobosats {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public onMessage: (message: NativeWebViewMessageSystem) => void = (message) => {
|
||||||
|
if (message.type === 'torStatus') {
|
||||||
|
this.torDaemonStatus = message.detail;
|
||||||
|
window.dispatchEvent(new CustomEvent('torStatus', { detail: this.torDaemonStatus }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public postMessage: (message: NativeWebViewMessage) => Promise<{ [key: string]: any }> = async (
|
public postMessage: (message: NativeWebViewMessage) => Promise<{ [key: string]: any }> = async (
|
||||||
message,
|
message,
|
||||||
) => {
|
) => {
|
||||||
|
20
frontend/src/services/System/SystemNativeClient/index.ts
Normal file
20
frontend/src/services/System/SystemNativeClient/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { SystemClient } from '..';
|
||||||
|
import NativeRobosats from '../../Native';
|
||||||
|
|
||||||
|
class SystemNativeClient implements SystemClient {
|
||||||
|
constructor() {
|
||||||
|
if (!window.NativeRobosats) {
|
||||||
|
window.NativeRobosats = new NativeRobosats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public copyToClipboard: (value: string) => void = (value) => {
|
||||||
|
return window.NativeRobosats?.postMessage({
|
||||||
|
category: 'system',
|
||||||
|
type: 'copyToClipboardString',
|
||||||
|
detail: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemNativeClient;
|
29
frontend/src/services/System/SystemWebClient/index.ts
Normal file
29
frontend/src/services/System/SystemWebClient/index.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { SystemClient } from '..';
|
||||||
|
|
||||||
|
class SystemWebClient implements SystemClient {
|
||||||
|
public copyToClipboard: (value: string) => void = (value) => {
|
||||||
|
// navigator clipboard api needs a secure context (https)
|
||||||
|
// this function attempts to copy also on http contexts
|
||||||
|
// useful on the http i2p site and on torified browsers
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
// navigator clipboard api method'
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
} else {
|
||||||
|
// text area method
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = value;
|
||||||
|
// make the textarea out of viewport
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
textArea.style.left = '-999999px';
|
||||||
|
textArea.style.top = '-999999px';
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
// here the magic happens
|
||||||
|
document.execCommand('copy');
|
||||||
|
textArea.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemWebClient;
|
9
frontend/src/services/System/index.ts
Normal file
9
frontend/src/services/System/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import SystemNativeClient from './SystemNativeClient';
|
||||||
|
import SystemWebClient from './SystemWebClient';
|
||||||
|
|
||||||
|
export interface SystemClient {
|
||||||
|
copyToClipboard: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const systemClient: SystemClient =
|
||||||
|
window.ReactNativeWebView != null ? new SystemNativeClient() : new SystemWebClient();
|
@ -4,7 +4,9 @@ import NativeRobosats from '../../Native';
|
|||||||
|
|
||||||
class ApiNativeClient implements ApiClient {
|
class ApiNativeClient implements ApiClient {
|
||||||
constructor() {
|
constructor() {
|
||||||
window.NativeRobosats = new NativeRobosats();
|
if (!window.NativeRobosats) {
|
||||||
|
window.NativeRobosats = new NativeRobosats();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private assetsCache: { [path: string]: string } = {};
|
private assetsCache: { [path: string]: string } = {};
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
export function copyToClipboard(textToCopy) {
|
|
||||||
// navigator clipboard api needs a secure context (https)
|
|
||||||
// this function attempts to copy also on http contexts
|
|
||||||
// useful on the http i2p site and on torified browsers
|
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
|
||||||
// navigator clipboard api method'
|
|
||||||
return navigator.clipboard.writeText(textToCopy);
|
|
||||||
} else {
|
|
||||||
// text area method
|
|
||||||
const textArea = document.createElement('textarea');
|
|
||||||
textArea.value = textToCopy;
|
|
||||||
// make the textarea out of viewport
|
|
||||||
textArea.style.position = 'fixed';
|
|
||||||
textArea.style.left = '-999999px';
|
|
||||||
textArea.style.top = '-999999px';
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
textArea.select();
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
// here the magic happens
|
|
||||||
document.execCommand('copy') ? res() : rej();
|
|
||||||
textArea.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,10 @@
|
|||||||
"You are self-hosting RoboSats": "You are self-hosting RoboSats",
|
"You are self-hosting RoboSats": "You are self-hosting RoboSats",
|
||||||
"RoboSats client is served from your own node granting you the strongest security and privacy.": "RoboSats client is served from your own node granting you the strongest security and privacy.",
|
"RoboSats client is served from your own node granting you the strongest security and privacy.": "RoboSats client is served from your own node granting you the strongest security and privacy.",
|
||||||
|
|
||||||
|
"Connected to TOR network": "Connected to TOR network",
|
||||||
|
"TOR connection error": "TOR connection error",
|
||||||
|
"Connecting to TOR network": "Connecting to TOR network",
|
||||||
|
|
||||||
"USER GENERATION PAGE - UserGenPage.js": "Landing Page and User Generation",
|
"USER GENERATION PAGE - UserGenPage.js": "Landing Page and User Generation",
|
||||||
"Simple and Private LN P2P Exchange": "Simple and Private LN P2P Exchange",
|
"Simple and Private LN P2P Exchange": "Simple and Private LN P2P Exchange",
|
||||||
"This is your trading avatar": "This is your trading avatar",
|
"This is your trading avatar": "This is your trading avatar",
|
||||||
|
@ -1,43 +1,85 @@
|
|||||||
import React, { useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
import { WebView, WebViewMessageEvent } from 'react-native-webview';
|
||||||
import { SafeAreaView, Text, Platform } from 'react-native';
|
import { SafeAreaView, Text, Platform } from 'react-native';
|
||||||
import { torClient } from './services/Tor';
|
import { torClient } from './services/Tor';
|
||||||
|
import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
|
import NetInfo from '@react-native-community/netinfo';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const webViewRef = useRef<WebView>();
|
const webViewRef = useRef<WebView>();
|
||||||
const uri = (Platform.OS === 'android' ? 'file:///android_asset/' : '') + 'Web.bundle/index.html';
|
const uri = (Platform.OS === 'android' ? 'file:///android_asset/' : '') + 'Web.bundle/index.html';
|
||||||
|
|
||||||
const injectMessage = (id: string, data: object) => {
|
const injectMessageResolve = (id: string, data: object) => {
|
||||||
const json = JSON.stringify(data);
|
const json = JSON.stringify(data);
|
||||||
webViewRef.current?.injectJavaScript(
|
webViewRef.current?.injectJavaScript(
|
||||||
`(function() {window.NativeRobosats.onMessageResolve(${id}, ${json});})();`,
|
`(function() {window.NativeRobosats.onMessageResolve(${id}, ${json});})();`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const injectMessage = (message: object) => {
|
||||||
|
const json = JSON.stringify(message);
|
||||||
|
webViewRef.current?.injectJavaScript(
|
||||||
|
`(function() {window.NativeRobosats?.onMessage(${json});})();`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const onMessage = async (event: WebViewMessageEvent) => {
|
const onMessage = async (event: WebViewMessageEvent) => {
|
||||||
const data = JSON.parse(event.nativeEvent.data);
|
const data = JSON.parse(event.nativeEvent.data);
|
||||||
if (data.category === 'http') {
|
if (data.category === 'http') {
|
||||||
|
sendTorStatus();
|
||||||
|
|
||||||
if (data.type === 'get') {
|
if (data.type === 'get') {
|
||||||
torClient.get(data.path).then((response: object) => {
|
torClient
|
||||||
injectMessage(data.id, response);
|
.get(data.path)
|
||||||
});
|
.then((response: object) => {
|
||||||
|
injectMessageResolve(data.id, response);
|
||||||
|
})
|
||||||
|
.finally(sendTorStatus);
|
||||||
} else if (data.type === 'post') {
|
} else if (data.type === 'post') {
|
||||||
torClient.post(data.path, data.body, data.headers).then((response: object) => {
|
torClient
|
||||||
injectMessage(data.id, response);
|
.post(data.path, data.body, data.headers)
|
||||||
});
|
.then((response: object) => {
|
||||||
|
injectMessageResolve(data.id, response);
|
||||||
|
})
|
||||||
|
.finally(sendTorStatus);
|
||||||
} else if (data.type === 'delete') {
|
} else if (data.type === 'delete') {
|
||||||
torClient.delete(data.path, data.headers).then((response: object) => {
|
torClient
|
||||||
injectMessage(data.id, response);
|
.delete(data.path, data.headers)
|
||||||
});
|
.then((response: object) => {
|
||||||
|
injectMessageResolve(data.id, response);
|
||||||
|
})
|
||||||
|
.finally(sendTorStatus);
|
||||||
} else if (data.type === 'xhr') {
|
} else if (data.type === 'xhr') {
|
||||||
torClient.request(data.path).then((response: object) => {
|
torClient
|
||||||
injectMessage(data.id, response);
|
.request(data.path)
|
||||||
});
|
.then((response: object) => {
|
||||||
|
injectMessageResolve(data.id, response);
|
||||||
|
})
|
||||||
|
.finally(sendTorStatus);
|
||||||
|
}
|
||||||
|
} else if (data.category === 'system') {
|
||||||
|
if (data.type === 'copyToClipboardString') {
|
||||||
|
Clipboard.setString(data.detail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
torClient.startDaemon();
|
const sendTorStatus = async () => {
|
||||||
|
NetInfo.fetch().then(async (state) => {
|
||||||
|
let daemonStatus = 'ERROR';
|
||||||
|
if (state.isInternetReachable) {
|
||||||
|
try {
|
||||||
|
daemonStatus = await torClient.daemon.getDaemonStatus();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
injectMessage({
|
||||||
|
category: 'system',
|
||||||
|
type: 'torStatus',
|
||||||
|
detail: daemonStatus,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
|
31
mobile/package-lock.json
generated
31
mobile/package-lock.json
generated
@ -8,6 +8,8 @@
|
|||||||
"name": "robosats",
|
"name": "robosats",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native-clipboard/clipboard": "^1.11.1",
|
||||||
|
"@react-native-community/netinfo": "^9.3.4",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-native": "^0.69.6",
|
"react-native": "^0.69.6",
|
||||||
"react-native-tor": "^0.1.8",
|
"react-native-tor": "^0.1.8",
|
||||||
@ -2808,6 +2810,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-clipboard/clipboard": {
|
||||||
|
"version": "1.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.11.1.tgz",
|
||||||
|
"integrity": "sha512-nvSIIHzybVWqYxcJE5hpT17ekxAAg383Ggzw5WrYHtkKX61N1AwaKSNmXs5xHV7pmKSOe/yWjtSwxIzfW51I5Q==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.0",
|
||||||
|
"react-native": ">=0.57.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native-community/cli": {
|
"node_modules/@react-native-community/cli": {
|
||||||
"version": "8.0.6",
|
"version": "8.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz",
|
||||||
@ -4348,6 +4359,14 @@
|
|||||||
"integrity": "sha512-o6aam+0Ug1xGK3ABYmBm0B1YuEKfM/5kaoZO0eHbZwSpw9UzDX4G5y4Nx/K20FHqUmJHkZmLvOUFYwN4N+HqKA==",
|
"integrity": "sha512-o6aam+0Ug1xGK3ABYmBm0B1YuEKfM/5kaoZO0eHbZwSpw9UzDX4G5y4Nx/K20FHqUmJHkZmLvOUFYwN4N+HqKA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-community/netinfo": {
|
||||||
|
"version": "9.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-9.3.4.tgz",
|
||||||
|
"integrity": "sha512-IXbJ+L8p4oE2ssDPfXCyxx9xVo5WuTMv6HA5YJw2McuRLLtVKR/vambycrB47AWTkHCTj3e0VOz28iUOvTSVPw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-native": ">=0.59"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native/assets": {
|
"node_modules/@react-native/assets": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz",
|
||||||
@ -17949,6 +17968,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@react-native-clipboard/clipboard": {
|
||||||
|
"version": "1.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.11.1.tgz",
|
||||||
|
"integrity": "sha512-nvSIIHzybVWqYxcJE5hpT17ekxAAg383Ggzw5WrYHtkKX61N1AwaKSNmXs5xHV7pmKSOe/yWjtSwxIzfW51I5Q==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@react-native-community/cli": {
|
"@react-native-community/cli": {
|
||||||
"version": "8.0.6",
|
"version": "8.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz",
|
||||||
@ -19068,6 +19093,12 @@
|
|||||||
"integrity": "sha512-o6aam+0Ug1xGK3ABYmBm0B1YuEKfM/5kaoZO0eHbZwSpw9UzDX4G5y4Nx/K20FHqUmJHkZmLvOUFYwN4N+HqKA==",
|
"integrity": "sha512-o6aam+0Ug1xGK3ABYmBm0B1YuEKfM/5kaoZO0eHbZwSpw9UzDX4G5y4Nx/K20FHqUmJHkZmLvOUFYwN4N+HqKA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@react-native-community/netinfo": {
|
||||||
|
"version": "9.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-9.3.4.tgz",
|
||||||
|
"integrity": "sha512-IXbJ+L8p4oE2ssDPfXCyxx9xVo5WuTMv6HA5YJw2McuRLLtVKR/vambycrB47AWTkHCTj3e0VOz28iUOvTSVPw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@react-native/assets": {
|
"@react-native/assets": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz",
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
"format": "prettier --write '**/**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc"
|
"format": "prettier --write '**/**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native-clipboard/clipboard": "^1.11.1",
|
||||||
|
"@react-native-community/netinfo": "^9.3.4",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-native": "^0.69.6",
|
"react-native": "^0.69.6",
|
||||||
"react-native-tor": "^0.1.8",
|
"react-native-tor": "^0.1.8",
|
||||||
@ -39,9 +41,9 @@
|
|||||||
"eslint-plugin-promise": "^6.0.1",
|
"eslint-plugin-promise": "^6.0.1",
|
||||||
"eslint-plugin-react": "^7.31.1",
|
"eslint-plugin-react": "^7.31.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"metro-react-native-babel-preset": "^0.70.3",
|
"metro-react-native-babel-preset": "^0.70.3",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
"react-test-renderer": "18.0.0",
|
"react-test-renderer": "18.0.0",
|
||||||
"typescript": "^4.4.4"
|
"typescript": "^4.4.4"
|
||||||
},
|
},
|
||||||
|
@ -12,12 +12,16 @@ class TorClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public startDaemon = async () => {
|
private connectDaemon: () => void = async () => {
|
||||||
await this.daemon.startIfNotStarted();
|
try {
|
||||||
|
this.daemon.startIfNotStarted();
|
||||||
|
} catch {
|
||||||
|
console.log('TOR already started');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public get: (path: string) => Promise<object> = async (path) => {
|
public get: (path: string) => Promise<object> = async (path) => {
|
||||||
await this.startDaemon();
|
await this.connectDaemon();
|
||||||
|
|
||||||
return await new Promise<object>(async (resolve, reject) => {
|
return await new Promise<object>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
@ -31,7 +35,7 @@ class TorClient {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public delete: (path: string, headers: object) => Promise<object> = async (path, headers) => {
|
public delete: (path: string, headers: object) => Promise<object> = async (path, headers) => {
|
||||||
await this.startDaemon();
|
await this.connectDaemon();
|
||||||
|
|
||||||
return await new Promise<object>(async (resolve, reject) => {
|
return await new Promise<object>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
@ -45,7 +49,7 @@ class TorClient {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public request: (path: string) => Promise<object> = async (path) => {
|
public request: (path: string) => Promise<object> = async (path) => {
|
||||||
await this.startDaemon();
|
await this.connectDaemon();
|
||||||
|
|
||||||
return await new Promise<object>(async (resolve, reject) => {
|
return await new Promise<object>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
@ -67,7 +71,7 @@ class TorClient {
|
|||||||
body,
|
body,
|
||||||
headers,
|
headers,
|
||||||
) => {
|
) => {
|
||||||
await this.startDaemon();
|
await this.connectDaemon();
|
||||||
|
|
||||||
return await new Promise<object>(async (resolve, reject) => {
|
return await new Promise<object>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
|
Loading…
Reference in New Issue
Block a user