// @flow import JsonRpcWs, { InvalidReply } from './jsonrpc-ws-ipc'; import { object, string, arrayOf, number } from 'validated/schema'; import { validate } from 'validated/object'; import type { Coordinate2d } from '../types'; export type AccountData = {expiry: string}; export type AccountToken = string; export type Ip = string; export type Location = { latlong: Coordinate2d, country: string, city: string, }; const LocationSchema = object({ latlong: arrayOf(number), country: string, city: string, }); export type SecurityState = 'secured' | 'unsecured'; export type BackendState = { state: SecurityState, target_state: SecurityState, }; export interface IpcFacade { setConnectionString(string): void, getAccountData(AccountToken): Promise, getAccount(): Promise, setAccount(accountToken: AccountToken): Promise, setCountry(address: string): Promise, connect(): Promise, disconnect(): Promise, getIp(): Promise, getLocation(): Promise, getState(): Promise, registerStateListener((BackendState) => void): void, } export class RealIpc implements IpcFacade { _ipc: JsonRpcWs; constructor(connectionString: string) { this._ipc = new JsonRpcWs(connectionString); } setConnectionString(str: string) { this._ipc.setConnectionString(str); } getAccountData(accountToken: AccountToken): Promise { // send the IPC with 30s timeout since the backend will wait // for a HTTP request before replying return this._ipc.send('get_account_data', accountToken, 30000) .then(raw => { if (typeof raw === 'object' && raw && raw.expiry) { return raw; } else { throw new InvalidReply(raw, 'Expected an object with expiry'); } }); } getAccount(): Promise { return this._ipc.send('get_account') .then( raw => { if (raw === undefined || raw === null || typeof raw === 'string') { return raw; } else { throw new InvalidReply(raw); } }); } setAccount(accountToken: AccountToken): Promise { return this._ipc.send('set_account', accountToken) .then(this._ignoreResponse); } _ignoreResponse(_response: mixed): void { return; } setCountry(address: string): Promise { return this._ipc.send('set_country', address) .then(this._ignoreResponse); } connect(): Promise { return this._ipc.send('connect') .then(this._ignoreResponse); } disconnect(): Promise { return this._ipc.send('disconnect') .then(this._ignoreResponse); } getIp(): Promise { return this._ipc.send('get_ip') .then(raw => { if (typeof raw === 'string' && raw) { return raw; } else { throw new InvalidReply(raw, 'Expected a string'); } }); } getLocation(): Promise { return this._ipc.send('get_location') .then(raw => { try { const validated: any = validate(LocationSchema, raw); return (validated: Location); } catch (e) { throw new InvalidReply(raw, e); } }); } getState(): Promise { return this._ipc.send('get_state') .then(raw => { return this._parseBackendState(raw); }); } _parseBackendState(raw: mixed): BackendState { if (raw && raw.state && raw.target_state) { const uncheckedRaw: any = raw; const states: Array = ['secured', 'unsecured']; const correctState = states.includes(uncheckedRaw.state); const correctTargetState = states.includes(uncheckedRaw.target_state); if (!correctState || !correctTargetState) { throw new InvalidReply(raw); } return (uncheckedRaw: BackendState); } else { throw new InvalidReply(raw); } } registerStateListener(listener: (BackendState) => void) { this._ipc.on('new_state', (rawEvent) => { const parsedEvent : BackendState = this._parseBackendState(rawEvent); listener(parsedEvent); }); } }