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:
KoalaSat 2022-10-07 14:10:21 +00:00 committed by GitHub
parent 1352937fe6
commit 9bda934ee5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 296 additions and 67 deletions

View File

@ -5,6 +5,7 @@ import { CssBaseline, IconButton, Link } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import UnsafeAlert from './UnsafeAlert';
import { LearnDialog } from './Dialogs';
import TorConnection from './TorConnection';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';
@ -16,6 +17,7 @@ import SchoolIcon from '@mui/icons-material/School';
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
import ZoomInIcon from '@mui/icons-material/ZoomIn';
import SettingsIcon from '@mui/icons-material/Settings';
import { systemClient } from '../services/System';
export default class App extends Component {
constructor(props) {
@ -102,6 +104,7 @@ export default class App extends Component {
open={this.state.openLearn}
onClose={() => this.setState({ openLearn: false })}
/>
<TorConnection />
<IconButton
color='inherit'
sx={{ position: 'fixed', right: '34px' }}

View File

@ -15,7 +15,7 @@ import {
} from '@mui/material';
import { saveAsJson } from '../../utils/saveFile';
import { copyToClipboard } from '../../utils/clipboard';
import { systemClient } from '../../services/System';
// Icons
import KeyIcon from '@mui/icons-material/Key';
@ -37,7 +37,7 @@ function CredentialTextfield(props) {
InputProps={{
endAdornment: (
<Tooltip disableHoverListener enterTouchDelay={0} title={props.copiedTitle}>
<IconButton onClick={async () => await copyToClipboard(props.value)}>
<IconButton onClick={() => systemClient.copyToClipboard(props.value)}>
<ContentCopy />
</IconButton>
</Tooltip>

View File

@ -37,7 +37,7 @@ import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
import { UserNinjaIcon, BitcoinIcon } from '../Icons';
import { getCookie } from '../../utils/cookies';
import { copyToClipboard } from '../../utils/clipboard';
import { systemClient } from '../../services/System';
import { getWebln } from '../../utils/webln';
import RobotAvatar from '../Robots/RobotAvatar';
@ -101,13 +101,13 @@ const ProfileDialog = ({
const robotToken = getCookie('robot_token');
if (robotToken) {
copyToClipboard(robotToken);
systemClient.copyToClipboard(robotToken);
setAppState({ copiedToken: true });
}
};
const copyReferralCodeHandler = () => {
copyToClipboard(`http://${host}/ref/${referralCode}`);
systemClient.copyToClipboard(`http://${host}/ref/${referralCode}`);
};
const handleWeblnInvoiceClicked = async (e: any) => {

View File

@ -18,9 +18,9 @@ import ReconnectingWebSocket from 'reconnecting-websocket';
import { encryptMessage, decryptMessage } from '../utils/pgp';
import { getCookie } from '../utils/cookies';
import { saveAsJson } from '../utils/saveFile';
import { copyToClipboard } from '../utils/clipboard';
import { AuditPGPDialog } from './Dialogs';
import RobotAvatar from './Robots/RobotAvatar';
import { systemClient } from '../services/System';
// Icons
import CheckIcon from '@mui/icons-material/Check';
@ -336,7 +336,7 @@ class Chat extends Component {
<IconButton
sx={{ height: 18, width: 18 }}
onClick={() =>
copyToClipboard(
systemClient.copyToClipboard(
this.state.showPGP[props.index]
? props.message.encryptedMessage
: props.message.plainTextMessage,

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

View File

@ -14,6 +14,7 @@ export { default as SellSatsIcon } from './SellSats';
export { default as SendReceiveIcon } from './SendReceive';
export { default as ExportIcon } from './Export';
export { default as UserNinjaIcon } from './UserNinja';
export { default as TorIcon } from './Tor';
// Some Flags missing on react-flags
export { default as BasqueCountryFlag } from './BasqueCountryFlag';

View File

@ -45,7 +45,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { getCookie } from '../utils/cookies';
import { pn } from '../utils/prettyNumbers';
import { copyToClipboard } from '../utils/clipboard';
import { systemClient } from '../services/System';
class MakerPage extends Component {
defaultCurrency = 1;
@ -924,7 +924,7 @@ class MakerPage extends Component {
open={this.state.openStoreToken}
onClose={() => this.setState({ openStoreToken: false })}
onClickCopy={() =>
copyToClipboard(getCookie('robot_token')) &
systemClient.copyToClipboard(getCookie('robot_token')) &
this.props.setAppState({ copiedToken: true })
}
copyIconColor={this.props.copiedToken ? 'inherit' : 'primary'}

View File

@ -52,7 +52,7 @@ import { SendReceiveIcon } from './Icons';
import { getCookie } from '../utils/cookies';
import { pn } from '../utils/prettyNumbers';
import { copyToClipboard } from '../utils/clipboard';
import { systemClient } from '../services/System';
import { getWebln } from '../utils/webln';
import { apiClient } from '../services/api';
import RobotAvatar from './Robots/RobotAvatar';
@ -527,7 +527,8 @@ class OrderPage extends Component {
open={this.state.openStoreToken}
onClose={() => this.setState({ openStoreToken: false })}
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'}
onClickBack={() => this.setState({ openStoreToken: false })}

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

View File

@ -32,7 +32,7 @@ import Countdown, { zeroPad } from 'react-countdown';
import Chat from './EncryptedChat';
import TradeSummary from './TradeSummary';
import MediaQuery from 'react-responsive';
import { copyToClipboard } from '../utils/clipboard';
import { systemClient } from '../services/System';
import { apiClient } from '../services/api';
// Icons
@ -286,7 +286,7 @@ class TradeBox extends Component {
size='small'
color='inherit'
onClick={() => {
copyToClipboard(this.props.data.bond_invoice);
systemClient.copyToClipboard(this.props.data.bond_invoice);
}}
align='center'
>
@ -420,7 +420,7 @@ class TradeBox extends Component {
size='small'
color='inherit'
onClick={() => {
copyToClipboard(this.props.data.escrow_invoice);
systemClient.copyToClipboard(this.props.data.escrow_invoice);
}}
align='center'
>
@ -1549,7 +1549,7 @@ class TradeBox extends Component {
<IconButton
color='inherit'
onClick={() => {
copyToClipboard(this.props.data.txid);
systemClient.copyToClipboard(this.props.data.txid);
}}
>
<ContentCopy sx={{ width: 16, height: 16 }} />

View File

@ -26,7 +26,7 @@ import { genBase62Token, tokenStrength } from '../utils/token';
import { genKey } from '../utils/pgp';
import { getCookie, writeCookie, deleteCookie } from '../utils/cookies';
import { saveAsJson } from '../utils/saveFile';
import { copyToClipboard } from '../utils/clipboard';
import { systemClient } from '../services/System';
import { apiClient } from '../services/api/index';
import RobotAvatar from './Robots/RobotAvatar';
@ -321,7 +321,7 @@ class UserGenPage extends Component {
!(getCookie('robot_token') === this.state.token))
}
onClick={() =>
copyToClipboard(getCookie('robot_token')) &
systemClient.copyToClipboard(getCookie('robot_token')) &
this.props.setAppState({ copiedToken: true })
}
>

View File

@ -20,7 +20,14 @@ export interface NativeWebViewMessageHttp {
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 {
resolve: (value: object | PromiseLike<object>) => void;

View File

@ -1,10 +1,12 @@
import { NativeRobosatsPromise, NativeWebViewMessage } from './index.d';
import { NativeRobosatsPromise, NativeWebViewMessage, NativeWebViewMessageSystem } from './index.d';
class NativeRobosats {
constructor() {
this.messageCounter = 0;
}
public torDaemonStatus = 'NOTINIT';
private messageCounter: number;
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 (
message,
) => {

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

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

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

View File

@ -4,7 +4,9 @@ import NativeRobosats from '../../Native';
class ApiNativeClient implements ApiClient {
constructor() {
window.NativeRobosats = new NativeRobosats();
if (!window.NativeRobosats) {
window.NativeRobosats = new NativeRobosats();
}
}
private assetsCache: { [path: string]: string } = {};

View File

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

View File

@ -7,6 +7,10 @@
"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.",
"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",
"Simple and Private LN P2P Exchange": "Simple and Private LN P2P Exchange",
"This is your trading avatar": "This is your trading avatar",

View File

@ -1,43 +1,85 @@
import React, { useRef } from 'react';
import React, { useEffect, useRef } from 'react';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
import { SafeAreaView, Text, Platform } from 'react-native';
import { torClient } from './services/Tor';
import Clipboard from '@react-native-clipboard/clipboard';
import NetInfo from '@react-native-community/netinfo';
const App = () => {
const webViewRef = useRef<WebView>();
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);
webViewRef.current?.injectJavaScript(
`(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 data = JSON.parse(event.nativeEvent.data);
if (data.category === 'http') {
sendTorStatus();
if (data.type === 'get') {
torClient.get(data.path).then((response: object) => {
injectMessage(data.id, response);
});
torClient
.get(data.path)
.then((response: object) => {
injectMessageResolve(data.id, response);
})
.finally(sendTorStatus);
} else if (data.type === 'post') {
torClient.post(data.path, data.body, data.headers).then((response: object) => {
injectMessage(data.id, response);
});
torClient
.post(data.path, data.body, data.headers)
.then((response: object) => {
injectMessageResolve(data.id, response);
})
.finally(sendTorStatus);
} else if (data.type === 'delete') {
torClient.delete(data.path, data.headers).then((response: object) => {
injectMessage(data.id, response);
});
torClient
.delete(data.path, data.headers)
.then((response: object) => {
injectMessageResolve(data.id, response);
})
.finally(sendTorStatus);
} else if (data.type === 'xhr') {
torClient.request(data.path).then((response: object) => {
injectMessage(data.id, response);
});
torClient
.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 (
<SafeAreaView style={{ flex: 1 }}>

View File

@ -8,6 +8,8 @@
"name": "robosats",
"version": "0.2.0",
"dependencies": {
"@react-native-clipboard/clipboard": "^1.11.1",
"@react-native-community/netinfo": "^9.3.4",
"react": "^18.0.0",
"react-native": "^0.69.6",
"react-native-tor": "^0.1.8",
@ -2808,6 +2810,15 @@
"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": {
"version": "8.0.6",
"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==",
"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": {
"version": "1.0.0",
"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": {
"version": "8.0.6",
"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==",
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz",

View File

@ -12,6 +12,8 @@
"format": "prettier --write '**/**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc"
},
"dependencies": {
"@react-native-clipboard/clipboard": "^1.11.1",
"@react-native-community/netinfo": "^9.3.4",
"react": "^18.0.0",
"react-native": "^0.69.6",
"react-native-tor": "^0.1.8",
@ -39,9 +41,9 @@
"eslint-plugin-promise": "^6.0.1",
"eslint-plugin-react": "^7.31.1",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^2.7.1",
"jest": "^26.6.3",
"metro-react-native-babel-preset": "^0.70.3",
"prettier": "^2.7.1",
"react-test-renderer": "18.0.0",
"typescript": "^4.4.4"
},

View File

@ -12,12 +12,16 @@ class TorClient {
});
}
public startDaemon = async () => {
await this.daemon.startIfNotStarted();
private connectDaemon: () => void = async () => {
try {
this.daemon.startIfNotStarted();
} catch {
console.log('TOR already started');
}
};
public get: (path: string) => Promise<object> = async (path) => {
await this.startDaemon();
await this.connectDaemon();
return await new Promise<object>(async (resolve, reject) => {
try {
@ -31,7 +35,7 @@ class TorClient {
};
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) => {
try {
@ -45,7 +49,7 @@ class TorClient {
};
public request: (path: string) => Promise<object> = async (path) => {
await this.startDaemon();
await this.connectDaemon();
return await new Promise<object>(async (resolve, reject) => {
try {
@ -67,7 +71,7 @@ class TorClient {
body,
headers,
) => {
await this.startDaemon();
await this.connectDaemon();
return await new Promise<object>(async (resolve, reject) => {
try {