import { Blockchain, Env, JsonRpcRequestPayload, JsonRpcResponsePayload, Network, unsupportedChainError, userRejectedRequest, } from '@haechi-labs/face-types'; import { IFRAME_URL } from '@haechi-labs/shared'; import { getBundleId } from 'react-native-device-info'; import { InAppBrowser } from 'react-native-inappbrowser-reborn'; import { type, version } from './SdkInfo'; import { decodeNativePayload, encodeNativePayload } from './utils/format'; import { formatJsonRpc } from './utils/json-rpc'; const defaultWebviewUrl = IFRAME_URL.Local; const SDK_TYPE = type; const SDK_VERSION = version; export class Webview { private config: { apiKey: string; network: Network; blockchain: Blockchain; env: Env }; private webviewUrl: string; private scheme: string; constructor( apiKey: string, network: Network, blockchain: Blockchain, env: Env, webviewUrl: string = defaultWebviewUrl, scheme: string ) { this.config = { apiKey, network, blockchain, env, }; this.webviewUrl = webviewUrl; this.scheme = scheme; } setBlockchain(blockchain: Blockchain) { this.config.blockchain = blockchain; } setNetwork(network: Network) { this.config.network = network; } async openWebview( _payload: JsonRpcRequestPayload ): Promise<[JsonRpcResponsePayload | null, JsonRpcRequestPayload]> { const redirectUrl = `${this.scheme}://${this.scheme}`; const config = Object.entries({ api_key: this.config.apiKey, network: this.config.network, blockchain: this.config.blockchain, env: this.config.env, hostname: getBundleId(), version: SDK_VERSION ?? '', type: SDK_TYPE, redirectUrl, }) .map((e) => e.join('=')) .join('&'); const payload = formatJsonRpc({ ..._payload }); const url = `${this.webviewUrl}/?request=${encodeNativePayload(payload)}&${config}`; const result = await InAppBrowser.openAuth(url, redirectUrl, { // iOS Properties ephemeralWebSession: false, // Android Properties showTitle: false, enableUrlBarHiding: true, enableDefaultShare: false, // prevents webview closing forceCloseOnRedirection: false, showInRecents: true, }); if ((result.type as string) === 'cancel') { throw userRejectedRequest(); } /* * facewebview://facewebview?response={"id":"4ef8f309-adf8-4745-ac42-ef08b211f7aa","result":{...},"jsonrpc":"2.0","to":"FACE_NATIVE_SDK","from":"FACE_IFRAME"} */ const decodedPayload = decodeNativePayload( (result as { url: string })?.url.split(/\?(?:response|request)=/)[1] ); /** * {"error": {"code": 5000, "isFaceError": true, "message": "Face Error: [5000] unknown error", "origin": {"data": [Object], "message": "Request failed with status code 401", "name": "AxiosError"}}, "from": "FACE_IFRAME", "id": "5f708550-6768-464c-b0df-234ffe2c2675", "jsonrpc": "2.0", "to": "FACE_NATIVE_SDK"} */ if (decodedPayload.error) { throw decodedPayload.error; } return [decodedPayload, payload]; } throwExceptionUnsupportedBlockchain(blackList: Blockchain[]) { if (blackList.includes(this.config.blockchain)) { throw unsupportedChainError(); } } allowBlockchain(allowList: Blockchain[]) { if (!allowList.includes(this.config.blockchain)) { throw unsupportedChainError(); } } }