diff --git a/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts b/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts index ee18cb8b9..a4c951c9f 100644 --- a/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts +++ b/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts @@ -5,7 +5,7 @@ import { saveV2OpenGroupRoom, } from '../../data/opengroups'; import { FSv2 } from '../../fileserver/'; -import { sendViaOnion } from '../../session/onions/onionSend'; +import { sendViaOnionToNonSnode } from '../../session/onions/onionSend'; import { PubKey } from '../../session/types'; import { OpenGroupRequestCommonType, OpenGroupV2Info, OpenGroupV2Request } from './ApiUtil'; import { @@ -120,7 +120,7 @@ export async function sendApiV2Request( } headers.Authorization = token; - const res = await sendViaOnion( + const res = await sendViaOnionToNonSnode( destinationX25519Key, builtUrl, { @@ -156,7 +156,7 @@ export async function sendApiV2Request( return res as object; } else { // no need for auth, just do the onion request - const res = await sendViaOnion(destinationX25519Key, builtUrl, { + const res = await sendViaOnionToNonSnode(destinationX25519Key, builtUrl, { method: request.method, headers, body, diff --git a/ts/opengroup/opengroupV2/OpenGroupAPIV2CompactPoll.ts b/ts/opengroup/opengroupV2/OpenGroupAPIV2CompactPoll.ts index 6bf7ebe3e..8ff7d55bd 100644 --- a/ts/opengroup/opengroupV2/OpenGroupAPIV2CompactPoll.ts +++ b/ts/opengroup/opengroupV2/OpenGroupAPIV2CompactPoll.ts @@ -3,10 +3,10 @@ import { OpenGroupV2Room, saveV2OpenGroupRoom, } from '../../data/opengroups'; -import { OpenGroupV2CompactPollRequest, OpenGroupV2Info, parseMessages } from './ApiUtil'; +import { OpenGroupV2CompactPollRequest, parseMessages } from './ApiUtil'; import { parseStatusCodeFromOnionRequest } from './OpenGroupAPIV2Parser'; import _ from 'lodash'; -import { sendViaOnion } from '../../session/onions/onionSend'; +import { sendViaOnionToNonSnode } from '../../session/onions/onionSend'; import { OpenGroupMessageV2 } from './OpenGroupMessageV2'; import { downloadPreviewOpenGroupV2, getMemberCount } from './OpenGroupAPIV2'; import { getAuthToken } from './ApiAuth'; @@ -244,7 +244,7 @@ async function sendOpenGroupV2RequestCompactPoll( // this will throw if the url is not valid const builtUrl = new URL(`${serverUrl}/${endpoint}`); - const res = await sendViaOnion( + const res = await sendViaOnionToNonSnode( serverPubKey, builtUrl, { diff --git a/ts/pushnotification/PnServer.ts b/ts/pushnotification/PnServer.ts index 7df502da9..4952c852f 100644 --- a/ts/pushnotification/PnServer.ts +++ b/ts/pushnotification/PnServer.ts @@ -1,22 +1,72 @@ -import { serverRequest } from '../session/onions/onionSend'; +import _ from 'lodash'; +import { sendViaOnionToNonSnode } from '../session/onions/onionSend'; const pnServerPubkeyHex = '642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049'; export const hrefPnServerProd = 'live.apns.getsession.org'; export const hrefPnServerDev = 'dev.apns.getsession.org'; + const pnServerUrl = `https://${hrefPnServerProd}`; -export async function notify(plainTextBuffer: ArrayBuffer, sentTo: string) { - const options = { +export async function notifyPnServer(wrappedEnvelope: ArrayBuffer, sentTo: string) { + const options: ServerRequestOptionsType = { method: 'post', objBody: { - data: await window.callWorker('arrayBufferToStringBase64', plainTextBuffer), + data: await window.callWorker('arrayBufferToStringBase64', wrappedEnvelope), send_to: sentTo, }, }; const endpoint = 'notify'; - return serverRequest(`${pnServerUrl}/${endpoint}`, { - ...options, - srvPubKey: pnServerPubkeyHex, - }); + return serverRequest(`${pnServerUrl}/${endpoint}`, options); } + +type ServerRequestOptionsType = { + method: string; + objBody: any; +}; + +/** The PN server only speaks onion request language */ +// tslint:disable-next-line: max-func-body-length +// tslint:disable-next-line: cyclomatic-complexity +const serverRequest = async ( + endpoint: string, + options: ServerRequestOptionsType +): Promise => { + const { method, objBody } = options; + + const url = new URL(endpoint); + const fetchOptions: any = {}; + const headers: any = {}; + try { + headers['Content-Type'] = 'application/json'; + fetchOptions.body = JSON.stringify(objBody); + fetchOptions.headers = headers; + fetchOptions.method = method; + } catch (e) { + window?.log?.error('onionSend:::notifyPnServer - set up error:', e.code, e.message); + return false; + } + + try { + const onionResponse = await sendViaOnionToNonSnode(pnServerPubkeyHex, url, fetchOptions); + if ( + !onionResponse || + !onionResponse.result || + (onionResponse.result.status as number) !== 200 + ) { + throw new Error(`Failed to do PN notify call, no response, ${onionResponse}`); + } + } catch (e) { + window?.log?.error( + 'onionSend:::serverRequest error', + e.code, + e.message, + 'attempting connection to', + url.toString() + ); + + return false; + } + + return true; +}; diff --git a/ts/session/onions/onionPath.ts b/ts/session/onions/onionPath.ts index c86bc3fef..05cc10f35 100644 --- a/ts/session/onions/onionPath.ts +++ b/ts/session/onions/onionPath.ts @@ -11,6 +11,7 @@ const desiredGuardCount = 3; const minimumGuardCount = 2; import { updateOnionPaths } from '../../state/ducks/onion'; +import { ERROR_CODE_NO_CONNECT } from '../snode_api/SNodeAPI'; const ONION_REQUEST_HOPS = 3; export let onionPaths: Array> = []; diff --git a/ts/session/onions/onionSend.ts b/ts/session/onions/onionSend.ts index bec9304c5..a0803dc06 100644 --- a/ts/session/onions/onionSend.ts +++ b/ts/session/onions/onionSend.ts @@ -1,27 +1,13 @@ // tslint:disable: cyclomatic-complexity import { OnionPaths } from '.'; -import { - FinalRelayOptions, - sendOnionRequestLsrpcDest, - snodeHttpsAgent, - SnodeResponse, -} from '../snode_api/onions'; +import { FinalRelayOptions, sendOnionRequestLsrpcDest, SnodeResponse } from '../snode_api/onions'; import _, { toNumber } from 'lodash'; -import { default as insecureNodeFetch } from 'node-fetch'; import { PROTOCOLS } from '../constants'; import { toHex } from '../utils/String'; import pRetry from 'p-retry'; import { Snode } from '../../data/data'; -// FIXME audric we should soon be able to get rid of that -const FILESERVER_HOSTS = [ - 'file-dev.lokinet.org', - 'file.lokinet.org', - 'file-dev.getsession.org', - 'file.getsession.org', -]; - type OnionFetchOptions = { method: string; body?: string; @@ -127,10 +113,15 @@ const sendViaOnionRetryable = async ({ }; /** + * + * This function can be used to make a request via onion to a non snode server. + * + * A non Snode server is for instance the Push Notification server or an OpengroupV2 server. + * * FIXME the type for this is not correct for open group api v2 returned values * result is status_code and whatever the body should be */ -export const sendViaOnion = async ( +export const sendViaOnionToNonSnode = async ( destinationX25519Key: string, url: URL, fetchOptions: OnionFetchOptions, @@ -222,163 +213,3 @@ export const sendViaOnion = async ( } return { result, txtResponse, response: body }; }; - -// FIXME this is really dirty -type ServerRequestOptionsType = { - params?: Record; - method?: string; - rawBody?: any; - objBody?: any; - token?: string; - srvPubKey?: string; - forceFreshToken?: boolean; - - retry?: number; - noJson?: boolean; -}; - -// tslint:disable-next-line: max-func-body-length -export const serverRequest = async ( - endpoint: string, - options: ServerRequestOptionsType = {} -): Promise => { - const { - params = {}, - method, - rawBody, - objBody, - token, - srvPubKey, - forceFreshToken = false, - } = options; - - const url = new URL(endpoint); - if (!_.isEmpty(params)) { - const builtParams = new URLSearchParams(params).toString(); - url.search = `?${builtParams}`; - } - const fetchOptions: any = {}; - const headers: any = {}; - try { - if (token) { - headers.Authorization = `Bearer ${token}`; - } - if (method) { - fetchOptions.method = method; - } - if (objBody) { - headers['Content-Type'] = 'application/json'; - fetchOptions.body = JSON.stringify(objBody); - } else if (rawBody) { - fetchOptions.body = rawBody; - } - fetchOptions.headers = headers; - - // domain ends in .loki - if (url.host.match(/\.loki$/i)) { - fetchOptions.agent = snodeHttpsAgent; - } - } catch (e) { - window?.log?.error('onionSend:::serverRequest - set up error:', e.code, e.message); - return { - err: e, - ok: false, - }; - } - - let response; - let result; - let txtResponse; - let mode = 'insecureNodeFetch'; - try { - const host = url.host.toLowerCase(); - // log.info('host', host, FILESERVER_HOSTS); - if (window.lokiFeatureFlags.useFileOnionRequests && FILESERVER_HOSTS.includes(host)) { - mode = 'sendViaOnion'; - if (!srvPubKey) { - throw new Error('useFileOnionRequests=true but we do not have a server pubkey set.'); - } - const onionResponse = await sendViaOnion(srvPubKey, url, fetchOptions, options); - if (onionResponse) { - ({ response, txtResponse, result } = onionResponse); - } - } else if (window.lokiFeatureFlags.useFileOnionRequests) { - if (!srvPubKey) { - throw new Error('useFileOnionRequests=true but we do not have a server pubkey set.'); - } - mode = 'sendViaOnionOG'; - const onionResponse = await sendViaOnion(srvPubKey, url, fetchOptions, options); - if (onionResponse) { - ({ response, txtResponse, result } = onionResponse); - } - } else { - // we end up here only if window.lokiFeatureFlags.useFileOnionRequests is false - window?.log?.info(`insecureNodeFetch => plaintext for ${url}`); - result = await insecureNodeFetch(url, fetchOptions); - - txtResponse = await result.text(); - // cloudflare timeouts (504s) will be html... - response = options.noJson ? txtResponse : JSON.parse(txtResponse); - - // result.status will always be 200 - // emulate the correct http code if available - if (response && response.meta && response.meta.code) { - result.status = response.meta.code; - } - } - } catch (e) { - if (txtResponse) { - window?.log?.error( - `onionSend:::serverRequest - ${mode} error`, - e.code, - e.message, - `json: ${txtResponse}`, - 'attempting connection to', - url.toString() - ); - } else { - window?.log?.error( - `onionSend:::serverRequest - ${mode} error`, - e.code, - e.message, - 'attempting connection to', - url.toString() - ); - } - - return { - err: e, - ok: false, - }; - } - - if (!result) { - return { - err: 'noResult', - response, - ok: false, - }; - } - - // if it's a response style with a meta - if (result.status !== 200) { - if (!forceFreshToken && (!response.meta || response.meta.code === 401)) { - // retry with forcing a fresh token - return serverRequest(endpoint, { - ...options, - forceFreshToken: true, - }); - } - return { - err: 'statusCode', - statusCode: result.status, - response, - ok: false, - }; - } - return { - statusCode: result.status, - response, - ok: result.status >= 200 && result.status <= 299, - }; -}; diff --git a/ts/session/sending/MessageSentHandler.ts b/ts/session/sending/MessageSentHandler.ts index fb499ca78..330226efd 100644 --- a/ts/session/sending/MessageSentHandler.ts +++ b/ts/session/sending/MessageSentHandler.ts @@ -94,7 +94,7 @@ export class MessageSentHandler { window?.log?.warn('Should send PN notify but no wrapped envelope set.'); } else { // we do not really care about the retsult. - await PnServer.notify(wrappedEnvelope, sentMessage.device); + await PnServer.notifyPnServer(wrappedEnvelope, sentMessage.device); } }