Persist Data on Android (#274)

* Android Cookies

* Android Fix POST headers

* Format

* App & Cookies Working

* Fix token on UserGen
This commit is contained in:
KoalaSat 2022-10-10 12:40:22 +00:00 committed by GitHub
parent f2dc7d0f90
commit e78b5e9c8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 247 additions and 119 deletions

View File

@ -134,6 +134,13 @@ export default class App extends Component {
} }
} }
const root = ReactDOM.createRoot(document.getElementById('app')); const loadApp = () => {
if (systemClient.loading) {
setTimeout(loadApp, 200);
} else {
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<App />);
}
};
root.render(<App />); loadApp();

View File

@ -19,6 +19,7 @@ import MediaQuery from 'react-responsive';
import Flags from 'country-flag-icons/react/3x2'; import Flags from 'country-flag-icons/react/3x2';
import { Link as LinkRouter } from 'react-router-dom'; import { Link as LinkRouter } from 'react-router-dom';
import { apiClient } from '../services/api'; import { apiClient } from '../services/api';
import { systemClient } from '../services/System';
import RobotAvatar from './Robots/RobotAvatar'; import RobotAvatar from './Robots/RobotAvatar';
// Icons // Icons
@ -41,7 +42,6 @@ import {
UpdateClientDialog, UpdateClientDialog,
} from './Dialogs'; } from './Dialogs';
import { getCookie } from '../utils/cookies';
import checkVer from '../utils/checkVer'; import checkVer from '../utils/checkVer';
class BottomBar extends Component { class BottomBar extends Component {
@ -172,9 +172,8 @@ class BottomBar extends Component {
showProfileButton = () => { showProfileButton = () => {
return ( return (
this.props.avatarLoaded && this.props.avatarLoaded &&
(window.NativeRobosats || (this.props.token ? systemClient.getCookie('robot_token') === this.props.token : true) &&
((this.props.token ? getCookie('robot_token') === this.props.token : true) && systemClient.getCookie('sessionid')
getCookie('sessionid')))
); );
}; };
@ -467,7 +466,7 @@ class BottomBar extends Component {
handleClickOpenExchangeSummary = () => { handleClickOpenExchangeSummary = () => {
// avoid calling getInfo while sessionid not yet set. Temporary fix. // avoid calling getInfo while sessionid not yet set. Temporary fix.
if (getCookie('sessionid')) { if (systemClient.getCookie('sessionid')) {
this.getInfo(); this.getInfo();
} }
this.setState({ openExchangeSummary: true }); this.setState({ openExchangeSummary: true });

View File

@ -36,7 +36,6 @@ import PersonAddAltIcon from '@mui/icons-material/PersonAddAlt';
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
import { UserNinjaIcon, BitcoinIcon } from '../Icons'; import { UserNinjaIcon, BitcoinIcon } from '../Icons';
import { getCookie } from '../../utils/cookies';
import { systemClient } from '../../services/System'; 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';
@ -98,7 +97,7 @@ const ProfileDialog = ({
}, [showRewards]); }, [showRewards]);
const copyTokenHandler = () => { const copyTokenHandler = () => {
const robotToken = getCookie('robot_token'); const robotToken = systemClient.getCookie('robot_token');
if (robotToken) { if (robotToken) {
systemClient.copyToClipboard(robotToken); systemClient.copyToClipboard(robotToken);
@ -226,12 +225,12 @@ const ProfileDialog = ({
</ListItemIcon> </ListItemIcon>
<ListItemText secondary={t('Your token (will not remain here)')}> <ListItemText secondary={t('Your token (will not remain here)')}>
{getCookie('robot_token') ? ( {systemClient.getCookie('robot_token') ? (
<TextField <TextField
disabled disabled
sx={{ width: '100%', maxWidth: '450px' }} sx={{ width: '100%', maxWidth: '450px' }}
label={t('Back it up!')} label={t('Back it up!')}
value={getCookie('robot_token')} value={systemClient.getCookie('robot_token')}
variant='filled' variant='filled'
size='small' size='small'
InputProps={{ InputProps={{

View File

@ -12,7 +12,7 @@ import {
Button, Button,
Grid, Grid,
} from '@mui/material'; } from '@mui/material';
import { getCookie } from '../../utils/cookies'; import { systemClient } from '../../services/System';
import ContentCopy from '@mui/icons-material/ContentCopy'; import ContentCopy from '@mui/icons-material/ContentCopy';
interface Props { interface Props {
@ -50,7 +50,7 @@ const StoreTokenDialog = ({
sx={{ width: '100%', maxWidth: '550px' }} sx={{ width: '100%', maxWidth: '550px' }}
disabled disabled
label={t('Back it up!')} label={t('Back it up!')}
value={getCookie('robot_token')} value={systemClient.getCookie('robot_token')}
variant='filled' variant='filled'
size='small' size='small'
InputProps={{ InputProps={{

View File

@ -16,7 +16,6 @@ import {
} from '@mui/material'; } from '@mui/material';
import ReconnectingWebSocket from 'reconnecting-websocket'; import ReconnectingWebSocket from 'reconnecting-websocket';
import { encryptMessage, decryptMessage } from '../utils/pgp'; import { encryptMessage, decryptMessage } from '../utils/pgp';
import { getCookie } from '../utils/cookies';
import { saveAsJson } from '../utils/saveFile'; import { saveAsJson } from '../utils/saveFile';
import { AuditPGPDialog } from './Dialogs'; import { AuditPGPDialog } from './Dialogs';
import RobotAvatar from './Robots/RobotAvatar'; import RobotAvatar from './Robots/RobotAvatar';
@ -37,10 +36,10 @@ class Chat extends Component {
} }
state = { state = {
own_pub_key: getCookie('pub_key').split('\\').join('\n'), own_pub_key: systemClient.getCookie('pub_key').split('\\').join('\n'),
own_enc_priv_key: getCookie('enc_priv_key').split('\\').join('\n'), own_enc_priv_key: systemClient.getCookie('enc_priv_key').split('\\').join('\n'),
peer_pub_key: null, peer_pub_key: null,
token: getCookie('robot_token'), token: systemClient.getCookie('robot_token'),
messages: [], messages: [],
value: '', value: '',
connected: false, connected: false,

View File

@ -43,7 +43,6 @@ import LockIcon from '@mui/icons-material/Lock';
import HourglassTopIcon from '@mui/icons-material/HourglassTop'; import HourglassTopIcon from '@mui/icons-material/HourglassTop';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { getCookie } from '../utils/cookies';
import { pn } from '../utils/prettyNumbers'; import { pn } from '../utils/prettyNumbers';
import { systemClient } from '../services/System'; import { systemClient } from '../services/System';
@ -919,12 +918,12 @@ class MakerPage extends Component {
const { t } = this.props; const { t } = this.props;
return ( return (
<Grid container align='center' spacing={1} sx={{ minWidth: '60%' }}> <Grid container align='center' spacing={1} sx={{ minWidth: '60%' }}>
{getCookie('robot_token') ? ( {systemClient.getCookie('robot_token') ? (
<StoreTokenDialog <StoreTokenDialog
open={this.state.openStoreToken} open={this.state.openStoreToken}
onClose={() => this.setState({ openStoreToken: false })} onClose={() => this.setState({ openStoreToken: false })}
onClickCopy={() => onClickCopy={() =>
systemClient.copyToClipboard(getCookie('robot_token')) & systemClient.copyToClipboard(systemClient.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'}

View File

@ -50,7 +50,6 @@ import HourglassTopIcon from '@mui/icons-material/HourglassTop';
import CheckIcon from '@mui/icons-material/Check'; import CheckIcon from '@mui/icons-material/Check';
import { SendReceiveIcon } from './Icons'; import { SendReceiveIcon } from './Icons';
import { getCookie } from '../utils/cookies';
import { pn } from '../utils/prettyNumbers'; import { pn } from '../utils/prettyNumbers';
import { systemClient } from '../services/System'; import { systemClient } from '../services/System';
import { getWebln } from '../utils/webln'; import { getWebln } from '../utils/webln';
@ -522,12 +521,12 @@ class OrderPage extends Component {
}; };
tokenDialog = () => { tokenDialog = () => {
return getCookie('robot_token') ? ( return systemClient.getCookie('robot_token') ? (
<StoreTokenDialog <StoreTokenDialog
open={this.state.openStoreToken} open={this.state.openStoreToken}
onClose={() => this.setState({ openStoreToken: false })} onClose={() => this.setState({ openStoreToken: false })}
onClickCopy={() => onClickCopy={() =>
systemClient.copyToClipboard(getCookie('robot_token')) & systemClient.copyToClipboard(systemClient.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'}

View File

@ -52,7 +52,6 @@ import RocketLaunchIcon from '@mui/icons-material/RocketLaunch';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { NewTabIcon } from './Icons'; import { NewTabIcon } from './Icons';
import { getCookie } from '../utils/cookies';
import { pn } from '../utils/prettyNumbers'; import { pn } from '../utils/prettyNumbers';
class TradeBox extends Component { class TradeBox extends Component {

View File

@ -24,7 +24,6 @@ import { RoboSatsNoTextIcon } from './Icons';
import { sha256 } from 'js-sha256'; import { sha256 } from 'js-sha256';
import { genBase62Token, tokenStrength } from '../utils/token'; import { genBase62Token, tokenStrength } from '../utils/token';
import { genKey } from '../utils/pgp'; import { genKey } from '../utils/pgp';
import { getCookie, writeCookie, deleteCookie } from '../utils/cookies';
import { saveAsJson } from '../utils/saveFile'; import { saveAsJson } from '../utils/saveFile';
import { systemClient } from '../services/System'; import { systemClient } from '../services/System';
import { apiClient } from '../services/api/index'; import { apiClient } from '../services/api/index';
@ -47,10 +46,13 @@ class UserGenPage extends Component {
// Displays the existing one // Displays the existing one
if (this.props.nickname != null) { if (this.props.nickname != null) {
this.setState({ this.setState({
nickname: this.props.nickname,
token: this.props.token ? this.props.token : '', token: this.props.token ? this.props.token : '',
loadingRobot: false, loadingRobot: false,
}); });
} else if (window.NativeRobosats && systemClient.getCookie('robot_token')) {
const token = systemClient.getCookie('robot_token');
this.props.setAppState({ token });
this.setState({ token, loadingRobot: false });
} else { } else {
const newToken = genBase62Token(36); const newToken = genBase62Token(36);
this.setState({ this.setState({
@ -78,7 +80,6 @@ class UserGenPage extends Component {
requestBody.then((body) => requestBody.then((body) =>
apiClient.post('/api/user/', body).then((data) => { apiClient.post('/api/user/', body).then((data) => {
this.setState({ this.setState({
nickname: data.nickname,
bit_entropy: data.token_bits_entropy, bit_entropy: data.token_bits_entropy,
shannon_entropy: data.token_shannon_entropy, shannon_entropy: data.token_shannon_entropy,
bad_request: data.bad_request, bad_request: data.bad_request,
@ -110,9 +111,12 @@ class UserGenPage extends Component {
tgBotName: data.tg_bot_name, tgBotName: data.tg_bot_name,
tgToken: data.tg_token, tgToken: data.tg_token,
}) & }) &
writeCookie('robot_token', token) & systemClient.setCookie('robot_token', token) &
writeCookie('pub_key', data.public_key.split('\n').join('\\')) & systemClient.setCookie('pub_key', data.public_key.split('\n').join('\\')) &
writeCookie('enc_priv_key', data.encrypted_private_key.split('\n').join('\\'))) & systemClient.setCookie(
'enc_priv_key',
data.encrypted_private_key.split('\n').join('\\'),
)) &
// If the robot has been found (recovered) we assume the token is backed up // If the robot has been found (recovered) we assume the token is backed up
(data.found ? this.props.setAppState({ copiedToken: true }) : null); (data.found ? this.props.setAppState({ copiedToken: true }) : null);
}), }),
@ -122,10 +126,10 @@ class UserGenPage extends Component {
delGeneratedUser() { delGeneratedUser() {
apiClient.delete('/api/user'); apiClient.delete('/api/user');
deleteCookie('sessionid'); systemClient.deleteCookie('sessionid');
deleteCookie('robot_token'); systemClient.deleteCookie('robot_token');
deleteCookie('pub_key'); systemClient.deleteCookie('pub_key');
deleteCookie('enc_priv_key'); systemClient.deleteCookie('enc_priv_key');
} }
handleClickNewRandomToken = () => { handleClickNewRandomToken = () => {
@ -168,11 +172,11 @@ class UserGenPage extends Component {
createJsonFile = () => { createJsonFile = () => {
return { return {
token: getCookie('robot_token'), token: systemClient.getCookie('robot_token'),
token_shannon_entropy: this.state.shannon_entropy, token_shannon_entropy: this.state.shannon_entropy,
token_bit_entropy: this.state.bit_entropy, token_bit_entropy: this.state.bit_entropy,
public_key: getCookie('pub_key').split('\\').join('\n'), public_key: systemClient.getCookie('pub_key').split('\\').join('\n'),
encrypted_private_key: getCookie('enc_priv_key').split('\\').join('\n'), encrypted_private_key: systemClient.getCookie('enc_priv_key').split('\\').join('\n'),
}; };
}; };
@ -191,12 +195,12 @@ class UserGenPage extends Component {
align='center' align='center'
sx={{ width: 370 * fontSizeFactor, height: 260 * fontSizeFactor }} sx={{ width: 370 * fontSizeFactor, height: 260 * fontSizeFactor }}
> >
{this.props.avatarLoaded && this.state.nickname ? ( {this.props.avatarLoaded && this.props.nickname ? (
<div> <div>
<Grid item xs={12} align='center'> <Grid item xs={12} align='center'>
<Typography component='h5' variant='h5'> <Typography component='h5' variant='h5'>
<b> <b>
{this.state.nickname && getCookie('sessionid') ? ( {this.props.nickname && systemClient.getCookie('sessionid') ? (
<div <div
style={{ style={{
display: 'flex', display: 'flex',
@ -213,7 +217,7 @@ class UserGenPage extends Component {
width: 33 * fontSizeFactor, width: 33 * fontSizeFactor,
}} }}
/> />
<a>{this.state.nickname}</a> <a>{this.props.nickname}</a>
<BoltIcon <BoltIcon
sx={{ sx={{
color: '#fcba03', color: '#fcba03',
@ -230,7 +234,7 @@ class UserGenPage extends Component {
</Grid> </Grid>
<Grid item xs={12} align='center'> <Grid item xs={12} align='center'>
<RobotAvatar <RobotAvatar
nickname={this.state.nickname} nickname={this.props.nickname}
smooth={true} smooth={true}
style={{ maxWidth: 203 * fontSizeFactor, maxHeight: 203 * fontSizeFactor }} style={{ maxWidth: 203 * fontSizeFactor, maxHeight: 203 * fontSizeFactor }}
imageStyle={{ imageStyle={{
@ -297,11 +301,10 @@ class UserGenPage extends Component {
color='primary' color='primary'
disabled={ disabled={
!this.props.avatarLoaded || !this.props.avatarLoaded ||
(!window.NativeRobosats && !(systemClient.getCookie('robot_token') === this.state.token)
!(getCookie('robot_token') === this.state.token))
} }
onClick={() => onClick={() =>
saveAsJson(this.state.nickname + '.json', this.createJsonFile()) saveAsJson(this.props.nickname + '.json', this.createJsonFile())
} }
> >
<DownloadIcon <DownloadIcon
@ -317,11 +320,10 @@ class UserGenPage extends Component {
color={this.props.copiedToken ? 'inherit' : 'primary'} color={this.props.copiedToken ? 'inherit' : 'primary'}
disabled={ disabled={
!this.props.avatarLoaded || !this.props.avatarLoaded ||
(!window.NativeRobosats && !(systemClient.getCookie('robot_token') === this.state.token)
!(getCookie('robot_token') === this.state.token))
} }
onClick={() => onClick={() =>
systemClient.copyToClipboard(getCookie('robot_token')) & systemClient.copyToClipboard(systemClient.getCookie('robot_token')) &
this.props.setAppState({ copiedToken: true }) this.props.setAppState({ copiedToken: true })
} }
> >
@ -374,8 +376,9 @@ class UserGenPage extends Component {
<Button <Button
disabled={ disabled={
this.state.loadingRobot !== false || this.state.loadingRobot !== false ||
(!window.NativeRobosats && !(this.props.token
!(this.props.token ? getCookie('robot_token') === this.props.token : true)) ? systemClient.getCookie('robot_token') === this.props.token
: true)
} }
color='primary' color='primary'
to='/make/' to='/make/'
@ -394,8 +397,9 @@ class UserGenPage extends Component {
<Button <Button
disabled={ disabled={
this.state.loadingRobot !== false || this.state.loadingRobot !== false ||
(!window.NativeRobosats && !(this.props.token
!(this.props.token ? getCookie('robot_token') === this.props.token : true)) ? systemClient.getCookie('robot_token') === this.props.token
: true)
} }
color='secondary' color='secondary'
to='/book/' to='/book/'

View File

@ -23,8 +23,9 @@ export interface NativeWebViewMessageHttp {
export interface NativeWebViewMessageSystem { export interface NativeWebViewMessageSystem {
id?: number; id?: number;
category: 'system'; category: 'system';
type: 'torStatus' | 'copyToClipboardString'; type: 'init' | 'torStatus' | 'copyToClipboardString' | 'setCookie' | 'deleteCookie';
detail: string; key?: string;
detail?: string;
} }
export declare type NativeWebViewMessage = NativeWebViewMessageHttp | NativeWebViewMessageSystem; export declare type NativeWebViewMessage = NativeWebViewMessageHttp | NativeWebViewMessageSystem;

View File

@ -1,16 +1,19 @@
import { systemClient } from '../System';
import { NativeRobosatsPromise, NativeWebViewMessage, NativeWebViewMessageSystem } from './index.d'; import { NativeRobosatsPromise, NativeWebViewMessage, NativeWebViewMessageSystem } from './index.d';
class NativeRobosats { class NativeRobosats {
constructor() {
this.messageCounter = 0;
}
public torDaemonStatus = 'NOTINIT'; public torDaemonStatus = 'NOTINIT';
private messageCounter: number; private messageCounter: number = 0;
private pendingMessages: { [id: number]: NativeRobosatsPromise } = {}; private pendingMessages: { [id: number]: NativeRobosatsPromise } = {};
public cookies: { [key: string]: string } = {};
public loadCookie = (cookie: { key: string; value: string }) => {
this.cookies[cookie.key] = cookie.value;
};
public onMessageResolve: (messageId: number, response?: object) => void = ( public onMessageResolve: (messageId: number, response?: object) => void = (
messageId, messageId,
response = {}, response = {},
@ -33,8 +36,12 @@ class NativeRobosats {
public onMessage: (message: NativeWebViewMessageSystem) => void = (message) => { public onMessage: (message: NativeWebViewMessageSystem) => void = (message) => {
if (message.type === 'torStatus') { if (message.type === 'torStatus') {
this.torDaemonStatus = message.detail; this.torDaemonStatus = message.detail || 'ERROR';
window.dispatchEvent(new CustomEvent('torStatus', { detail: this.torDaemonStatus })); window.dispatchEvent(new CustomEvent('torStatus', { detail: this.torDaemonStatus }));
} else if (message.type === 'setCookie') {
if (message.key !== undefined) {
this.cookies[message.key] = message.detail;
}
} }
}; };

View File

@ -3,11 +3,17 @@ import NativeRobosats from '../../Native';
class SystemNativeClient implements SystemClient { class SystemNativeClient implements SystemClient {
constructor() { constructor() {
if (!window.NativeRobosats) { window.NativeRobosats = new NativeRobosats();
window.NativeRobosats = new NativeRobosats(); window.NativeRobosats.postMessage({
} category: 'system',
type: 'init',
}).then(() => {
this.loading = false;
});
} }
public loading = true;
public copyToClipboard: (value: string) => void = (value) => { public copyToClipboard: (value: string) => void = (value) => {
return window.NativeRobosats?.postMessage({ return window.NativeRobosats?.postMessage({
category: 'system', category: 'system',
@ -15,6 +21,30 @@ class SystemNativeClient implements SystemClient {
detail: value, detail: value,
}); });
}; };
public getCookie: (key: string) => string | undefined = (key) => {
return window.NativeRobosats?.cookies[key];
};
public setCookie: (key: string, value: string) => void = (key, value) => {
delete window.NativeRobosats?.cookies[key];
window.NativeRobosats?.postMessage({
category: 'system',
type: 'setCookie',
key,
detail: value,
});
};
public deleteCookie: (key: string) => void = (key) => {
delete window.NativeRobosats?.cookies[key];
window.NativeRobosats?.postMessage({
category: 'system',
type: 'deleteCookie',
key,
});
};
} }
export default SystemNativeClient; export default SystemNativeClient;

View File

@ -1,6 +1,8 @@
import { SystemClient } from '..'; import { SystemClient } from '..';
class SystemWebClient implements SystemClient { class SystemWebClient implements SystemClient {
public loading = false;
public copyToClipboard: (value: string) => void = (value) => { public copyToClipboard: (value: string) => void = (value) => {
// navigator clipboard api needs a secure context (https) // navigator clipboard api needs a secure context (https)
// this function attempts to copy also on http contexts // this function attempts to copy also on http contexts
@ -24,6 +26,31 @@ class SystemWebClient implements SystemClient {
textArea.remove(); textArea.remove();
} }
}; };
public getCookie: (key: string) => string | undefined = (key) => {
let cookieValue = null;
if (document.cookie && 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=Strict`;
};
public deleteCookie: (key: string) => void = (key) => {
document.cookie = `${name}= ; expires = Thu, 01 Jan 1970 00:00:00 GMT`;
};
} }
export default SystemWebClient; export default SystemWebClient;

View File

@ -2,7 +2,11 @@ import SystemNativeClient from './SystemNativeClient';
import SystemWebClient from './SystemWebClient'; import SystemWebClient from './SystemWebClient';
export interface SystemClient { export interface SystemClient {
loading: boolean;
copyToClipboard: (value: string) => void; copyToClipboard: (value: string) => void;
getCookie: (key: string) => string | undefined;
setCookie: (key: string, value: string) => void;
deleteCookie: (key: string) => void;
} }
export const systemClient: SystemClient = export const systemClient: SystemClient =

View File

@ -1,19 +1,42 @@
import { ApiClient } from '../api'; import { ApiClient } from '../api';
import { getCookie } from '../../../utils/cookies'; import { systemClient } from '../../System';
import NativeRobosats from '../../Native'; import NativeRobosats from '../../Native';
class ApiNativeClient implements ApiClient { class ApiNativeClient implements ApiClient {
constructor() {
if (!window.NativeRobosats) {
window.NativeRobosats = new NativeRobosats();
}
}
private assetsCache: { [path: string]: string } = {}; private assetsCache: { [path: string]: string } = {};
private assetsPromises: { [path: string]: Promise<string | undefined> } = {}; private assetsPromises: { [path: string]: Promise<string | undefined> } = {};
private readonly getHeaders: () => HeadersInit = () => { private readonly getHeaders: () => HeadersInit = () => {
return { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') || '' }; let headers = {
'Content-Type': 'application/json',
};
const sessionid = systemClient.getCookie('sessionid');
if (sessionid) {
const robotToken = systemClient.getCookie('robot_token');
const csrftoken = systemClient.getCookie('csrftoken');
const pubKey = systemClient.getCookie('pub_key');
headers = {
...headers,
...{
'X-CSRFToken': csrftoken,
Cookie: `sessionid=${sessionid};robot_token=${robotToken};csrftoken=${csrftoken};pub_key=${pubKey}`,
},
};
}
return headers;
};
private readonly parseResponse = (response: { [key: string]: any }): object => {
if (response.headers['set-cookie']) {
response.headers['set-cookie'].forEach((cookie: string) => {
const keySplit: string[] = cookie.split('=');
systemClient.setCookie(keySplit[0], keySplit[1].split(';')[0]);
});
}
return response.json;
}; };
public put: (path: string, body: object) => Promise<object | undefined> = async (path, body) => { public put: (path: string, body: object) => Promise<object | undefined> = async (path, body) => {
@ -26,7 +49,7 @@ class ApiNativeClient implements ApiClient {
type: 'delete', type: 'delete',
path, path,
headers: this.getHeaders(), headers: this.getHeaders(),
}); }).then(this.parseResponse);
}; };
public post: (path: string, body: object) => Promise<object | undefined> = async (path, body) => { public post: (path: string, body: object) => Promise<object | undefined> = async (path, body) => {
@ -36,7 +59,7 @@ class ApiNativeClient implements ApiClient {
path, path,
body, body,
headers: this.getHeaders(), headers: this.getHeaders(),
}); }).then(this.parseResponse);
}; };
public get: (path: string) => Promise<object | undefined> = async (path) => { public get: (path: string) => Promise<object | undefined> = async (path) => {
@ -44,7 +67,8 @@ class ApiNativeClient implements ApiClient {
category: 'http', category: 'http',
type: 'get', type: 'get',
path, path,
}); headers: this.getHeaders(),
}).then(this.parseResponse);
}; };
public fileImageUrl: (path: string) => Promise<string | undefined> = async (path) => { public fileImageUrl: (path: string) => Promise<string | undefined> = async (path) => {

View File

@ -1,9 +1,12 @@
import { ApiClient } from '../api'; import { ApiClient } from '../api';
import { getCookie } from '../../../utils/cookies'; import { systemClient } from '../../System';
class ApiWebClient implements ApiClient { class ApiWebClient implements ApiClient {
private readonly getHeaders: () => HeadersInit = () => { private readonly getHeaders: () => HeadersInit = () => {
return { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') || '' }; return {
'Content-Type': 'application/json',
'X-CSRFToken': systemClient.getCookie('csrftoken') || '',
};
}; };
public post: (path: string, body: object) => Promise<object | undefined> = async (path, body) => { public post: (path: string, body: object) => Promise<object | undefined> = async (path, body) => {

View File

@ -1,23 +0,0 @@
export const getCookie = (name) => {
let cookieValue = null;
if (document.cookie && 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 name we want?
if (cookie.substring(0, name.length + 1) === name + '=') {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
};
export const writeCookie = (key, value) => {
document.cookie = `${key}=${value};path=/;SameSite=Strict`;
};
export const deleteCookie = (name) => {
document.cookie = `${name}= ; expires = Thu, 01 Jan 1970 00:00:00 GMT`;
};

View File

@ -1,16 +1,18 @@
import React, { useEffect, 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 Clipboard from '@react-native-clipboard/clipboard';
import NetInfo from '@react-native-community/netinfo'; import NetInfo from '@react-native-community/netinfo';
import EncryptedStorage from 'react-native-encrypted-storage';
const App = () => { const App = () => {
const torClient = new TorClient();
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 injectMessageResolve = (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});})();`,
); );
@ -23,17 +25,36 @@ const App = () => {
); );
}; };
const init = (reponseId: string) => {
const loadCookie = (key: string) => {
return EncryptedStorage.getItem(key).then((value) => {
if (value) {
const json = JSON.stringify({ key, value });
webViewRef.current?.injectJavaScript(
`(function() {window.NativeRobosats?.loadCookie(${json});})();`,
);
}
});
};
loadCookie('sessionid');
loadCookie('robot_token');
loadCookie('csrftoken');
loadCookie('pub_key');
loadCookie('enc_priv_key').then(() => injectMessageResolve(reponseId));
};
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(); sendTorStatus();
if (data.type === 'get') { if (data.type === 'get') {
torClient torClient
.get(data.path) .get(data.path, data.headers)
.then((response: object) => { .then((response: object) => {
injectMessageResolve(data.id, response); injectMessageResolve(data.id, response);
}) })
.catch(sendTorStatus)
.finally(sendTorStatus); .finally(sendTorStatus);
} else if (data.type === 'post') { } else if (data.type === 'post') {
torClient torClient
@ -41,6 +62,7 @@ const App = () => {
.then((response: object) => { .then((response: object) => {
injectMessageResolve(data.id, response); injectMessageResolve(data.id, response);
}) })
.catch(sendTorStatus)
.finally(sendTorStatus); .finally(sendTorStatus);
} else if (data.type === 'delete') { } else if (data.type === 'delete') {
torClient torClient
@ -48,6 +70,7 @@ const App = () => {
.then((response: object) => { .then((response: object) => {
injectMessageResolve(data.id, response); injectMessageResolve(data.id, response);
}) })
.catch(sendTorStatus)
.finally(sendTorStatus); .finally(sendTorStatus);
} else if (data.type === 'xhr') { } else if (data.type === 'xhr') {
torClient torClient
@ -55,15 +78,35 @@ const App = () => {
.then((response: object) => { .then((response: object) => {
injectMessageResolve(data.id, response); injectMessageResolve(data.id, response);
}) })
.catch(sendTorStatus)
.finally(sendTorStatus); .finally(sendTorStatus);
} }
} else if (data.category === 'system') { } else if (data.category === 'system') {
if (data.type === 'copyToClipboardString') { if (data.type === 'init') {
init(data.id);
} else if (data.type === 'copyToClipboardString') {
Clipboard.setString(data.detail); Clipboard.setString(data.detail);
} else if (data.type === 'setCookie') {
setCookie(data.key, data.detail);
} else if (data.type === 'deleteCookie') {
EncryptedStorage.removeItem(data.key);
} }
} }
}; };
const setCookie = async (key: string, value: string) => {
try {
await EncryptedStorage.setItem(key, value);
const storedValue = await EncryptedStorage.getItem(key);
injectMessage({
category: 'system',
type: 'setCookie',
key,
detail: storedValue,
});
} catch (error) {}
};
const sendTorStatus = async () => { const sendTorStatus = async () => {
NetInfo.fetch().then(async (state) => { NetInfo.fetch().then(async (state) => {
let daemonStatus = 'ERROR'; let daemonStatus = 'ERROR';

View File

@ -12,6 +12,7 @@
"@react-native-community/netinfo": "^9.3.4", "@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-encrypted-storage": "^4.0.2",
"react-native-tor": "^0.1.8", "react-native-tor": "^0.1.8",
"react-native-v8": "^1.5.0", "react-native-v8": "^1.5.0",
"react-native-webview": "^11.22.3", "react-native-webview": "^11.22.3",
@ -13411,6 +13412,15 @@
"nullthrows": "^1.1.1" "nullthrows": "^1.1.1"
} }
}, },
"node_modules/react-native-encrypted-storage": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/react-native-encrypted-storage/-/react-native-encrypted-storage-4.0.2.tgz",
"integrity": "sha512-vneDkHGDuTvLQjUBztqb2YI8QoH1zxdJonPGTS+g57lfJZff9fAjoLSSb6NgMBebpXFcK3I3sEresGyL+3AArw==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-gradle-plugin": { "node_modules/react-native-gradle-plugin": {
"version": "0.0.7", "version": "0.0.7",
"resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz", "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz",
@ -25923,6 +25933,12 @@
"nullthrows": "^1.1.1" "nullthrows": "^1.1.1"
} }
}, },
"react-native-encrypted-storage": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/react-native-encrypted-storage/-/react-native-encrypted-storage-4.0.2.tgz",
"integrity": "sha512-vneDkHGDuTvLQjUBztqb2YI8QoH1zxdJonPGTS+g57lfJZff9fAjoLSSb6NgMBebpXFcK3I3sEresGyL+3AArw==",
"requires": {}
},
"react-native-gradle-plugin": { "react-native-gradle-plugin": {
"version": "0.0.7", "version": "0.0.7",
"resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz", "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz",

View File

@ -16,6 +16,7 @@
"@react-native-community/netinfo": "^9.3.4", "@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-encrypted-storage": "^4.0.2",
"react-native-tor": "^0.1.8", "react-native-tor": "^0.1.8",
"react-native-v8": "^1.5.0", "react-native-v8": "^1.5.0",
"react-native-webview": "^11.22.3", "react-native-webview": "^11.22.3",

View File

@ -20,14 +20,12 @@ class TorClient {
} }
}; };
public get: (path: string) => Promise<object> = async (path) => { public get: (path: string, headers: object) => Promise<object> = async (path, headers) => {
await this.connectDaemon();
return await new Promise<object>(async (resolve, reject) => { return await new Promise<object>(async (resolve, reject) => {
try { try {
const response = await this.daemon.get(`${this.baseUrl}${path}`); const response = await this.daemon.get(`${this.baseUrl}${path}`, headers);
resolve(response.json); resolve(response);
} catch (error) { } catch (error) {
reject(error); reject(error);
} }
@ -35,13 +33,11 @@ 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.connectDaemon();
return await new Promise<object>(async (resolve, reject) => { return await new Promise<object>(async (resolve, reject) => {
try { try {
const response = await this.daemon.delete(`${this.baseUrl}${path}`, '', headers); const response = await this.daemon.delete(`${this.baseUrl}${path}`, '', headers);
resolve(response.json); resolve(response);
} catch (error) { } catch (error) {
reject(error); reject(error);
} }
@ -49,8 +45,6 @@ class TorClient {
}; };
public request: (path: string) => Promise<object> = async (path) => { public request: (path: string) => Promise<object> = async (path) => {
await this.connectDaemon();
return await new Promise<object>(async (resolve, reject) => { return await new Promise<object>(async (resolve, reject) => {
try { try {
const response = await this.daemon const response = await this.daemon
@ -71,14 +65,12 @@ class TorClient {
body, body,
headers, headers,
) => { ) => {
await this.connectDaemon();
return await new Promise<object>(async (resolve, reject) => { return await new Promise<object>(async (resolve, reject) => {
try { try {
const json = JSON.stringify(body); const json = JSON.stringify(body);
const response = await this.daemon.post(`${this.baseUrl}${path}`, json, headers); const response = await this.daemon.post(`${this.baseUrl}${path}`, json, headers);
resolve(response.json); resolve(response);
} catch (error) { } catch (error) {
reject(error); reject(error);
} }
@ -86,6 +78,4 @@ class TorClient {
}; };
} }
export const torClient = new TorClient();
export default TorClient; export default TorClient;