|  |  |  | /* global log, libloki, textsecure, getStoragePubKey, lokiSnodeAPI, StringView, | 
					
						
							|  |  |  |   libsignal, window, TextDecoder, TextEncoder, dcodeIO, process */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const nodeFetch = require('node-fetch'); | 
					
						
							|  |  |  | const https = require('https'); | 
					
						
							|  |  |  | const primitives = require('./loki_primitives'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const snodeHttpsAgent = new https.Agent({ | 
					
						
							|  |  |  |   rejectUnauthorized: false, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const endpointBase = '/storage_rpc/v1'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Returns the actual ciphertext, symmetric key that will be used
 | 
					
						
							|  |  |  | // for decryption, and an ephemeral_key to send to the next hop
 | 
					
						
							|  |  |  | const encryptForPubKey = async (pubKeyX25519hex, reqObj) => { | 
					
						
							|  |  |  |   const reqStr = JSON.stringify(reqObj); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const textEncoder = new TextEncoder(); | 
					
						
							|  |  |  |   const plaintext = textEncoder.encode(reqStr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return libloki.crypto.encryptForPubkey(pubKeyX25519hex, plaintext); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // `ctx` holds info used by `node` to relay further
 | 
					
						
							|  |  |  | const encryptForRelay = async (relayX25519hex, destination, ctx) => { | 
					
						
							|  |  |  |   // ctx contains: ciphertext, symmetricKey, ephemeralKey
 | 
					
						
							|  |  |  |   const payload = ctx.ciphertext; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!destination.host && !destination.destination) { | 
					
						
							|  |  |  |     log.warn(`loki_rpc::encryptForRelay - no destination`, destination); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const reqObj = { | 
					
						
							|  |  |  |     ...destination, | 
					
						
							|  |  |  |     ciphertext: dcodeIO.ByteBuffer.wrap(payload).toString('base64'), | 
					
						
							|  |  |  |     ephemeral_key: StringView.arrayBufferToHex(ctx.ephemeralKey), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return encryptForPubKey(relayX25519hex, reqObj); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const makeGuardPayload = guardCtx => { | 
					
						
							|  |  |  |   const ciphertextBase64 = dcodeIO.ByteBuffer.wrap( | 
					
						
							|  |  |  |     guardCtx.ciphertext | 
					
						
							|  |  |  |   ).toString('base64'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const guardPayloadObj = { | 
					
						
							|  |  |  |     ciphertext: ciphertextBase64, | 
					
						
							|  |  |  |     ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeralKey), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   return guardPayloadObj; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // we just need the targetNode.pubkey_ed25519 for the encryption
 | 
					
						
							|  |  |  | // targetPubKey is ed25519 if snode is the target
 | 
					
						
							|  |  |  | const makeOnionRequest = async ( | 
					
						
							|  |  |  |   nodePath, | 
					
						
							|  |  |  |   destCtx, | 
					
						
							|  |  |  |   targetED25519Hex, | 
					
						
							|  |  |  |   finalRelayOptions = false, | 
					
						
							|  |  |  |   id = '' | 
					
						
							|  |  |  | ) => { | 
					
						
							|  |  |  |   const ctxes = [destCtx]; | 
					
						
							|  |  |  |   // from (3) 2 to 0
 | 
					
						
							|  |  |  |   const firstPos = nodePath.length - 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (let i = firstPos; i > -1; i -= 1) { | 
					
						
							|  |  |  |     let dest; | 
					
						
							|  |  |  |     const relayingToFinalDestination = i === firstPos; // if last position
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (relayingToFinalDestination && finalRelayOptions) { | 
					
						
							|  |  |  |       dest = { | 
					
						
							|  |  |  |         host: finalRelayOptions.host, | 
					
						
							|  |  |  |         target: '/loki/v1/lsrpc', | 
					
						
							|  |  |  |         method: 'POST', | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       // set x25519 if destination snode
 | 
					
						
							|  |  |  |       let pubkeyHex = targetED25519Hex; // relayingToFinalDestination
 | 
					
						
							|  |  |  |       // or ed25519 snode destination
 | 
					
						
							|  |  |  |       if (!relayingToFinalDestination) { | 
					
						
							|  |  |  |         pubkeyHex = nodePath[i + 1].pubkey_ed25519; | 
					
						
							|  |  |  |         if (!pubkeyHex) { | 
					
						
							|  |  |  |           log.error( | 
					
						
							|  |  |  |             `loki_rpc:::makeOnionRequest ${id} - no ed25519 for`, | 
					
						
							|  |  |  |             nodePath[i + 1], | 
					
						
							|  |  |  |             'path node', | 
					
						
							|  |  |  |             i + 1 | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       // destination takes a hex key
 | 
					
						
							|  |  |  |       dest = { | 
					
						
							|  |  |  |         destination: pubkeyHex, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // eslint-disable-next-line no-await-in-loop
 | 
					
						
							|  |  |  |       const ctx = await encryptForRelay( | 
					
						
							|  |  |  |         nodePath[i].pubkey_x25519, | 
					
						
							|  |  |  |         dest, | 
					
						
							|  |  |  |         ctxes[ctxes.length - 1] | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       ctxes.push(ctx); | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |       log.error( | 
					
						
							|  |  |  |         `loki_rpc:::makeOnionRequest ${id} - encryptForRelay failure`, | 
					
						
							|  |  |  |         e.code, | 
					
						
							|  |  |  |         e.message | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       throw e; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const guardCtx = ctxes[ctxes.length - 1]; // last ctx
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const payloadObj = makeGuardPayload(guardCtx); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // all these requests should use AesGcm
 | 
					
						
							|  |  |  |   return payloadObj; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // finalDestOptions is an object
 | 
					
						
							|  |  |  | // FIXME: internally track reqIdx, not externally
 | 
					
						
							|  |  |  | const sendOnionRequest = async ( | 
					
						
							|  |  |  |   reqIdx, | 
					
						
							|  |  |  |   nodePath, | 
					
						
							|  |  |  |   destX25519Any, | 
					
						
							|  |  |  |   finalDestOptions, | 
					
						
							|  |  |  |   finalRelayOptions = false, | 
					
						
							|  |  |  |   lsrpcIdx | 
					
						
							|  |  |  | ) => { | 
					
						
							|  |  |  |   if (!destX25519Any) { | 
					
						
							|  |  |  |     log.error('loki_rpc::sendOnionRequest - no destX25519Any given'); | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // loki-storage may need this to function correctly
 | 
					
						
							|  |  |  |   // but ADN calls will not always have a body
 | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |   if (!finalDestOptions.body) { | 
					
						
							|  |  |  |     finalDestOptions.body = ''; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let id = ''; | 
					
						
							|  |  |  |   if (lsrpcIdx !== undefined) { | 
					
						
							|  |  |  |     id += `${lsrpcIdx}=>`; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (reqIdx !== undefined) { | 
					
						
							|  |  |  |     id += `${reqIdx}`; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // get destination pubkey in array buffer format
 | 
					
						
							|  |  |  |   let destX25519hex = destX25519Any; | 
					
						
							|  |  |  |   if (typeof destX25519hex !== 'string') { | 
					
						
							|  |  |  |     // convert AB to hex
 | 
					
						
							|  |  |  |     destX25519hex = StringView.arrayBufferToHex(destX25519Any); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // safely build destination
 | 
					
						
							|  |  |  |   let targetEd25519hex; | 
					
						
							|  |  |  |   if (finalDestOptions) { | 
					
						
							|  |  |  |     if (finalDestOptions.destination_ed25519_hex) { | 
					
						
							|  |  |  |       // snode destination
 | 
					
						
							|  |  |  |       targetEd25519hex = finalDestOptions.destination_ed25519_hex; | 
					
						
							|  |  |  |       // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |       delete finalDestOptions.destination_ed25519_hex; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // else it's lsrpc...
 | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |     finalDestOptions = {}; | 
					
						
							|  |  |  |     log.warn(`loki_rpc::sendOnionRequest ${id} - no finalDestOptions`); | 
					
						
							|  |  |  |     return {}; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const options = finalDestOptions; // lint
 | 
					
						
							|  |  |  |   // do we need this?
 | 
					
						
							|  |  |  |   if (options.headers === undefined) { | 
					
						
							|  |  |  |     options.headers = ''; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let destCtx; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     destCtx = await encryptForPubKey(destX25519hex, options); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     log.error( | 
					
						
							|  |  |  |       `loki_rpc::sendOnionRequest ${id} - encryptForPubKey failure [`, | 
					
						
							|  |  |  |       e.code, | 
					
						
							|  |  |  |       e.message, | 
					
						
							|  |  |  |       '] destination X25519', | 
					
						
							|  |  |  |       destX25519hex.substr(0, 32), | 
					
						
							|  |  |  |       '...', | 
					
						
							|  |  |  |       destX25519hex.substr(32), | 
					
						
							|  |  |  |       'options', | 
					
						
							|  |  |  |       options | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     throw e; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const payloadObj = await makeOnionRequest( | 
					
						
							|  |  |  |     nodePath, | 
					
						
							|  |  |  |     destCtx, | 
					
						
							|  |  |  |     targetEd25519hex, | 
					
						
							|  |  |  |     finalRelayOptions, | 
					
						
							|  |  |  |     id | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const guardFetchOptions = { | 
					
						
							|  |  |  |     method: 'POST', | 
					
						
							|  |  |  |     body: JSON.stringify(payloadObj), | 
					
						
							|  |  |  |     // we are talking to a snode...
 | 
					
						
							|  |  |  |     agent: snodeHttpsAgent, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const guardUrl = `https://${nodePath[0].ip}:${nodePath[0].port}/onion_req`; | 
					
						
							|  |  |  |   const response = await nodeFetch(guardUrl, guardFetchOptions); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return processOnionResponse(reqIdx, response, destCtx.symmetricKey, true); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const sendOnionRequestSnodeDest = async ( | 
					
						
							|  |  |  |   reqIdx, | 
					
						
							|  |  |  |   nodePath, | 
					
						
							|  |  |  |   targetNode, | 
					
						
							|  |  |  |   plaintext | 
					
						
							|  |  |  | ) => | 
					
						
							|  |  |  |   sendOnionRequest(reqIdx, nodePath, targetNode.pubkey_x25519, { | 
					
						
							|  |  |  |     destination_ed25519_hex: targetNode.pubkey_ed25519, | 
					
						
							|  |  |  |     body: plaintext, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // need relay node's pubkey_x25519_hex
 | 
					
						
							|  |  |  | // always the same target: /loki/v1/lsrpc
 | 
					
						
							|  |  |  | const sendOnionRequestLsrpcDest = async ( | 
					
						
							|  |  |  |   reqIdx, | 
					
						
							|  |  |  |   nodePath, | 
					
						
							|  |  |  |   destX25519Any, | 
					
						
							|  |  |  |   host, | 
					
						
							|  |  |  |   payloadObj, | 
					
						
							|  |  |  |   lsrpcIdx = 0 | 
					
						
							|  |  |  | ) => | 
					
						
							|  |  |  |   sendOnionRequest( | 
					
						
							|  |  |  |     reqIdx, | 
					
						
							|  |  |  |     nodePath, | 
					
						
							|  |  |  |     destX25519Any, | 
					
						
							|  |  |  |     payloadObj, | 
					
						
							|  |  |  |     { host }, | 
					
						
							|  |  |  |     lsrpcIdx | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const BAD_PATH = 'bad_path'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Process a response as it arrives from `nodeFetch`, handling
 | 
					
						
							|  |  |  | // http errors and attempting to decrypt the body with `sharedKey`
 | 
					
						
							|  |  |  | // May return false BAD_PATH, indicating that we should try a new path.
 | 
					
						
							|  |  |  | const processOnionResponse = async ( | 
					
						
							|  |  |  |   reqIdx, | 
					
						
							|  |  |  |   response, | 
					
						
							|  |  |  |   sharedKey, | 
					
						
							|  |  |  |   useAesGcm, | 
					
						
							|  |  |  |   debug | 
					
						
							|  |  |  | ) => { | 
					
						
							|  |  |  |   // FIXME: 401/500 handling?
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // detect SNode is not ready (not in swarm; not done syncing)
 | 
					
						
							|  |  |  |   if (response.status === 503) { | 
					
						
							|  |  |  |     log.warn(`(${reqIdx}) [path] Got 503: snode not ready`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return BAD_PATH; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (response.status === 504) { | 
					
						
							|  |  |  |     log.warn(`(${reqIdx}) [path] Got 504: Gateway timeout`); | 
					
						
							|  |  |  |     return BAD_PATH; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (response.status === 404) { | 
					
						
							|  |  |  |     // Why would we get this error on testnet?
 | 
					
						
							|  |  |  |     log.warn(`(${reqIdx}) [path] Got 404: Gateway timeout`); | 
					
						
							|  |  |  |     return BAD_PATH; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (response.status !== 200) { | 
					
						
							|  |  |  |     log.warn( | 
					
						
							|  |  |  |       `(${reqIdx}) [path] lokiRpc::processOnionResponse - fetch unhandled error code: ${response.status}` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const ciphertext = await response.text(); | 
					
						
							|  |  |  |   if (!ciphertext) { | 
					
						
							|  |  |  |     log.warn( | 
					
						
							|  |  |  |       `(${reqIdx}) [path] lokiRpc::processOnionResponse - Target node return empty ciphertext` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (debug) { | 
					
						
							|  |  |  |     log.debug( | 
					
						
							|  |  |  |       `(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertext`, | 
					
						
							|  |  |  |       ciphertext | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let plaintext; | 
					
						
							|  |  |  |   let ciphertextBuffer; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     ciphertextBuffer = dcodeIO.ByteBuffer.wrap( | 
					
						
							|  |  |  |       ciphertext, | 
					
						
							|  |  |  |       'base64' | 
					
						
							|  |  |  |     ).toArrayBuffer(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (debug) { | 
					
						
							|  |  |  |       log.debug( | 
					
						
							|  |  |  |         `(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, | 
					
						
							|  |  |  |         StringView.arrayBufferToHex(ciphertextBuffer), | 
					
						
							|  |  |  |         'useAesGcm', | 
					
						
							|  |  |  |         useAesGcm | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const decryptFn = useAesGcm | 
					
						
							|  |  |  |       ? libloki.crypto.DecryptGCM | 
					
						
							|  |  |  |       : libloki.crypto.DHDecrypt; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const plaintextBuffer = await decryptFn(sharedKey, ciphertextBuffer, debug); | 
					
						
							|  |  |  |     if (debug) { | 
					
						
							|  |  |  |       log.debug( | 
					
						
							|  |  |  |         'lokiRpc::processOnionResponse - plaintextBuffer', | 
					
						
							|  |  |  |         plaintextBuffer.toString() | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const textDecoder = new TextDecoder(); | 
					
						
							|  |  |  |     plaintext = textDecoder.decode(plaintextBuffer); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     log.error( | 
					
						
							|  |  |  |       `(${reqIdx}) [path] lokiRpc::processOnionResponse - decode error`, | 
					
						
							|  |  |  |       e.code, | 
					
						
							|  |  |  |       e.message | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     log.error( | 
					
						
							|  |  |  |       `(${reqIdx}) [path] lokiRpc::processOnionResponse - symKey`, | 
					
						
							|  |  |  |       StringView.arrayBufferToHex(sharedKey) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (ciphertextBuffer) { | 
					
						
							|  |  |  |       log.error( | 
					
						
							|  |  |  |         `(${reqIdx}) [path] lokiRpc::processOnionResponse - ciphertextBuffer`, | 
					
						
							|  |  |  |         StringView.arrayBufferToHex(ciphertextBuffer) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (debug) { | 
					
						
							|  |  |  |     log.debug('lokiRpc::processOnionResponse - plaintext', plaintext); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     const jsonRes = JSON.parse(plaintext); | 
					
						
							|  |  |  |     // emulate nodeFetch response...
 | 
					
						
							|  |  |  |     jsonRes.json = () => { | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         const res = JSON.parse(jsonRes.body); | 
					
						
							|  |  |  |         return res; | 
					
						
							|  |  |  |       } catch (e) { | 
					
						
							|  |  |  |         log.error( | 
					
						
							|  |  |  |           `(${reqIdx}) [path] lokiRpc::processOnionResponse - parse error inner json: `, | 
					
						
							|  |  |  |           jsonRes.body | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     return jsonRes; | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     log.error( | 
					
						
							|  |  |  |       `(${reqIdx}) [path] lokiRpc::processOnionResponse - parse error outer json`, | 
					
						
							|  |  |  |       e.code, | 
					
						
							|  |  |  |       e.message, | 
					
						
							|  |  |  |       `json:`, | 
					
						
							|  |  |  |       plaintext | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const sendToProxy = async (options = {}, targetNode, retryNumber = 0) => { | 
					
						
							|  |  |  |   const _ = window.Lodash; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let snodePool = await lokiSnodeAPI.getRandomSnodePool(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (snodePool.length < 2) { | 
					
						
							|  |  |  |     // this is semi-normal to happen
 | 
					
						
							|  |  |  |     log.info( | 
					
						
							|  |  |  |       'lokiRpc::sendToProxy - Not enough service nodes for a proxy request, only have:', | 
					
						
							|  |  |  |       snodePool.length, | 
					
						
							|  |  |  |       'snode, attempting refresh' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     await lokiSnodeAPI.refreshRandomPool(); | 
					
						
							|  |  |  |     snodePool = await lokiSnodeAPI.getRandomSnodePool(); | 
					
						
							|  |  |  |     if (snodePool.length < 2) { | 
					
						
							|  |  |  |       log.error( | 
					
						
							|  |  |  |         'lokiRpc::sendToProxy - Not enough service nodes for a proxy request, only have:', | 
					
						
							|  |  |  |         snodePool.length, | 
					
						
							|  |  |  |         'failing' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Making sure the proxy node is not the same as the target node:
 | 
					
						
							|  |  |  |   const snodePoolSafe = _.without( | 
					
						
							|  |  |  |     snodePool, | 
					
						
							|  |  |  |     _.find(snodePool, { pubkey_ed25519: targetNode.pubkey_ed25519 }) | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const randSnode = window.Lodash.sample(snodePoolSafe); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Don't allow arbitrary URLs, only snodes and loki servers
 | 
					
						
							|  |  |  |   const url = `https://${randSnode.ip}:${randSnode.port}/proxy`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const snPubkeyHex = StringView.hexToArrayBuffer(targetNode.pubkey_x25519); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const myKeys = await libloki.crypto.generateEphemeralKeyPair(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const symmetricKey = await libsignal.Curve.async.calculateAgreement( | 
					
						
							|  |  |  |     snPubkeyHex, | 
					
						
							|  |  |  |     myKeys.privKey | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const textEncoder = new TextEncoder(); | 
					
						
							|  |  |  |   const body = JSON.stringify(options); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const plainText = textEncoder.encode(body); | 
					
						
							|  |  |  |   const ivAndCiphertext = await libloki.crypto.DHEncrypt( | 
					
						
							|  |  |  |     symmetricKey, | 
					
						
							|  |  |  |     plainText | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const firstHopOptions = { | 
					
						
							|  |  |  |     method: 'POST', | 
					
						
							|  |  |  |     body: ivAndCiphertext, | 
					
						
							|  |  |  |     headers: { | 
					
						
							|  |  |  |       'X-Sender-Public-Key': StringView.arrayBufferToHex(myKeys.pubKey), | 
					
						
							|  |  |  |       'X-Target-Snode-Key': targetNode.pubkey_ed25519, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     agent: snodeHttpsAgent, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // we only proxy to snodes...
 | 
					
						
							|  |  |  |   const response = await nodeFetch(url, firstHopOptions); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (response.status === 401) { | 
					
						
							|  |  |  |     // decom or dereg
 | 
					
						
							|  |  |  |     // remove
 | 
					
						
							|  |  |  |     // but which the proxy or the target...
 | 
					
						
							|  |  |  |     // we got a ton of randomPool nodes, let's just not worry about this one
 | 
					
						
							|  |  |  |     lokiSnodeAPI.markRandomNodeUnreachable(randSnode); | 
					
						
							|  |  |  |     const randomPoolRemainingCount = lokiSnodeAPI.getRandomPoolLength(); | 
					
						
							|  |  |  |     const ciphertext = await response.text(); | 
					
						
							|  |  |  |     log.warn( | 
					
						
							|  |  |  |       `lokiRpc:::sendToProxy -`, | 
					
						
							|  |  |  |       `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, | 
					
						
							|  |  |  |       `snode is decom or dereg: `, | 
					
						
							|  |  |  |       ciphertext, | 
					
						
							|  |  |  |       // `marking random snode bad ${randomPoolRemainingCount} remaining`
 | 
					
						
							|  |  |  |       `Try #${retryNumber}`, | 
					
						
							|  |  |  |       `removing randSnode leaving ${randomPoolRemainingCount} in the random pool` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     // retry, just count it happening 5 times to be the target for now
 | 
					
						
							|  |  |  |     return sendToProxy(options, targetNode, retryNumber + 1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // 504 is only present in 2.0.3 and after
 | 
					
						
							|  |  |  |   // relay is fine but destination is not good
 | 
					
						
							|  |  |  |   if (response.status === 504) { | 
					
						
							|  |  |  |     const pRetryNumber = retryNumber + 1; | 
					
						
							|  |  |  |     if (pRetryNumber > 3) { | 
					
						
							|  |  |  |       log.warn( | 
					
						
							|  |  |  |         `lokiRpc:::sendToProxy - snode ${randSnode.ip}:${randSnode.port}`, | 
					
						
							|  |  |  |         `can not relay to target node ${targetNode.ip}:${targetNode.port}`, | 
					
						
							|  |  |  |         `after 3 retries` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       if (options.ourPubKey) { | 
					
						
							|  |  |  |         lokiSnodeAPI.unreachableNode(options.ourPubKey, targetNode); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // we don't have to wait here
 | 
					
						
							|  |  |  |     // because we're not marking the random snode bad
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // grab a fresh random one
 | 
					
						
							|  |  |  |     return sendToProxy(options, targetNode, pRetryNumber); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   // 502 is "Next node not found"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // detect SNode is not ready (not in swarm; not done syncing)
 | 
					
						
							|  |  |  |   // 503 can be proxy target or destination in pre 2.0.3
 | 
					
						
							|  |  |  |   // 2.0.3 and after means target
 | 
					
						
							|  |  |  |   if (response.status === 503 || response.status === 500) { | 
					
						
							|  |  |  |     // this doesn't mean the random node is bad, it could be the target node
 | 
					
						
							|  |  |  |     // but we got a ton of randomPool nodes, let's just not worry about this one
 | 
					
						
							|  |  |  |     lokiSnodeAPI.markRandomNodeUnreachable(randSnode); | 
					
						
							|  |  |  |     const randomPoolRemainingCount = lokiSnodeAPI.getRandomPoolLength(); | 
					
						
							|  |  |  |     const ciphertext = await response.text(); | 
					
						
							|  |  |  |     log.warn( | 
					
						
							|  |  |  |       `lokiRpc:::sendToProxy -`, | 
					
						
							|  |  |  |       `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, | 
					
						
							|  |  |  |       `code ${response.status} error`, | 
					
						
							|  |  |  |       ciphertext, | 
					
						
							|  |  |  |       // `marking random snode bad ${randomPoolRemainingCount} remaining`
 | 
					
						
							|  |  |  |       `Try #${retryNumber}`, | 
					
						
							|  |  |  |       `removing randSnode leaving ${randomPoolRemainingCount} in the random pool` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     // mark as bad for this round (should give it some time and improve success rates)
 | 
					
						
							|  |  |  |     // retry for a new working snode
 | 
					
						
							|  |  |  |     const pRetryNumber = retryNumber + 1; | 
					
						
							|  |  |  |     if (pRetryNumber > 5) { | 
					
						
							|  |  |  |       // it's likely a net problem or an actual problem on the target node
 | 
					
						
							|  |  |  |       // lets mark the target node bad for now
 | 
					
						
							|  |  |  |       // we'll just rotate it back in if it's a net problem
 | 
					
						
							|  |  |  |       log.warn( | 
					
						
							|  |  |  |         `lokiRpc:::sendToProxy - Failing ${targetNode.ip}:${targetNode.port} after 5 retries` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       if (options.ourPubKey) { | 
					
						
							|  |  |  |         lokiSnodeAPI.unreachableNode(options.ourPubKey, targetNode); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // 500 burns through a node too fast,
 | 
					
						
							|  |  |  |     // let's slow the retry to give it more time to recover
 | 
					
						
							|  |  |  |     if (response.status === 500) { | 
					
						
							|  |  |  |       await primitives.sleepFor(5000); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return sendToProxy(options, targetNode, pRetryNumber); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   /* | 
					
						
							|  |  |  |   if (response.status === 500) { | 
					
						
							|  |  |  |     // usually when the server returns nothing...
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // FIXME: handle nodeFetch errors/exceptions...
 | 
					
						
							|  |  |  |   if (response.status !== 200) { | 
					
						
							|  |  |  |     // let us know we need to create handlers for new unhandled codes
 | 
					
						
							|  |  |  |     log.warn( | 
					
						
							|  |  |  |       'lokiRpc:::sendToProxy - fetch non-200 statusCode', | 
					
						
							|  |  |  |       response.status, | 
					
						
							|  |  |  |       `from snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const ciphertext = await response.text(); | 
					
						
							|  |  |  |   if (!ciphertext) { | 
					
						
							|  |  |  |     // avoid base64 decode failure
 | 
					
						
							|  |  |  |     // usually a 500 but not always
 | 
					
						
							|  |  |  |     // could it be a timeout?
 | 
					
						
							|  |  |  |     log.warn( | 
					
						
							|  |  |  |       'lokiRpc:::sendToProxy - Server did not return any data for', | 
					
						
							|  |  |  |       options, | 
					
						
							|  |  |  |       targetNode | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let plaintext; | 
					
						
							|  |  |  |   let ciphertextBuffer; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     ciphertextBuffer = dcodeIO.ByteBuffer.wrap( | 
					
						
							|  |  |  |       ciphertext, | 
					
						
							|  |  |  |       'base64' | 
					
						
							|  |  |  |     ).toArrayBuffer(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const plaintextBuffer = await libloki.crypto.DHDecrypt( | 
					
						
							|  |  |  |       symmetricKey, | 
					
						
							|  |  |  |       ciphertextBuffer | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const textDecoder = new TextDecoder(); | 
					
						
							|  |  |  |     plaintext = textDecoder.decode(plaintextBuffer); | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     log.error( | 
					
						
							|  |  |  |       'lokiRpc:::sendToProxy - decode error', | 
					
						
							|  |  |  |       e.code, | 
					
						
							|  |  |  |       e.message, | 
					
						
							|  |  |  |       `from ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port} ciphertext:`, | 
					
						
							|  |  |  |       ciphertext | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (ciphertextBuffer) { | 
					
						
							|  |  |  |       log.error('ciphertextBuffer', ciphertextBuffer); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     const jsonRes = JSON.parse(plaintext); | 
					
						
							|  |  |  |     // emulate nodeFetch response...
 | 
					
						
							|  |  |  |     jsonRes.json = () => { | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         if (jsonRes.body === 'Timestamp error: check your clock') { | 
					
						
							|  |  |  |           log.error( | 
					
						
							|  |  |  |             `lokiRpc:::sendToProxy - Timestamp error: check your clock`, | 
					
						
							|  |  |  |             Date.now() | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return JSON.parse(jsonRes.body); | 
					
						
							|  |  |  |       } catch (e) { | 
					
						
							|  |  |  |         log.error( | 
					
						
							|  |  |  |           'lokiRpc:::sendToProxy - (inner) parse error', | 
					
						
							|  |  |  |           e.code, | 
					
						
							|  |  |  |           e.message, | 
					
						
							|  |  |  |           `from ${randSnode.ip}:${randSnode.port} json:`, | 
					
						
							|  |  |  |           jsonRes.body | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     if (retryNumber) { | 
					
						
							|  |  |  |       log.debug( | 
					
						
							|  |  |  |         `lokiRpc:::sendToProxy - request succeeded,`, | 
					
						
							|  |  |  |         `snode ${randSnode.ip}:${randSnode.port} to ${targetNode.ip}:${targetNode.port}`, | 
					
						
							|  |  |  |         `on retry #${retryNumber}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return jsonRes; | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     log.error( | 
					
						
							|  |  |  |       'lokiRpc:::sendToProxy - (outer) parse error', | 
					
						
							|  |  |  |       e.code, | 
					
						
							|  |  |  |       e.message, | 
					
						
							|  |  |  |       `from ${randSnode.ip}:${randSnode.port} json:`, | 
					
						
							|  |  |  |       plaintext | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // A small wrapper around node-fetch which deserializes response
 | 
					
						
							|  |  |  | // returns nodeFetch response or false
 | 
					
						
							|  |  |  | const lokiFetch = async (url, options = {}, targetNode = null) => { | 
					
						
							|  |  |  |   const timeout = options.timeout || 10000; | 
					
						
							|  |  |  |   const method = options.method || 'GET'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const fetchOptions = { | 
					
						
							|  |  |  |     ...options, | 
					
						
							|  |  |  |     timeout, | 
					
						
							|  |  |  |     method, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async function checkResponse(response, type) { | 
					
						
							|  |  |  |     // Wrong swarm
 | 
					
						
							|  |  |  |     if (response.status === 421) { | 
					
						
							|  |  |  |       const result = await response.json(); | 
					
						
							|  |  |  |       log.warn( | 
					
						
							|  |  |  |         `lokirpc:::lokiFetch ${type} - wrong swarm, now looking at snodes`, | 
					
						
							|  |  |  |         result.snodes | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       const newSwarm = result.snodes ? result.snodes : []; | 
					
						
							|  |  |  |       throw new textsecure.WrongSwarmError(newSwarm); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Wrong PoW difficulty
 | 
					
						
							|  |  |  |     if (response.status === 432) { | 
					
						
							|  |  |  |       const result = await response.json(); | 
					
						
							|  |  |  |       log.error(`lokirpc:::lokiFetch ${type} - WRONG POW`, result); | 
					
						
							|  |  |  |       throw new textsecure.WrongDifficultyError(result.difficulty); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (response.status === 406) { | 
					
						
							|  |  |  |       throw new textsecure.TimestampError( | 
					
						
							|  |  |  |         'Invalid Timestamp (check your clock)' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     // Absence of targetNode indicates that we want a direct connection
 | 
					
						
							|  |  |  |     // (e.g. to connect to a seed node for the first time)
 | 
					
						
							|  |  |  |     if (window.lokiFeatureFlags.useOnionRequests && targetNode) { | 
					
						
							|  |  |  |       // Loop until the result is not BAD_PATH
 | 
					
						
							|  |  |  |       // eslint-disable-next-line no-constant-condition
 | 
					
						
							|  |  |  |       while (true) { | 
					
						
							|  |  |  |         // Get a path excluding `targetNode`:
 | 
					
						
							|  |  |  |         // eslint-disable-next-line no-await-in-loop
 | 
					
						
							|  |  |  |         const path = await lokiSnodeAPI.getOnionPath(targetNode); | 
					
						
							|  |  |  |         const thisIdx = window.lokiSnodeAPI.assignOnionRequestNumber(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // eslint-disable-next-line no-await-in-loop
 | 
					
						
							|  |  |  |         const result = await sendOnionRequestSnodeDest( | 
					
						
							|  |  |  |           thisIdx, | 
					
						
							|  |  |  |           path, | 
					
						
							|  |  |  |           targetNode, | 
					
						
							|  |  |  |           fetchOptions.body | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const getPathString = pathObjArr => | 
					
						
							|  |  |  |           pathObjArr.map(node => `${node.ip}:${node.port}`).join(', '); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (result === BAD_PATH) { | 
					
						
							|  |  |  |           log.error( | 
					
						
							|  |  |  |             `[path] Error on the path: ${getPathString(path)} to ${ | 
					
						
							|  |  |  |               targetNode.ip | 
					
						
							|  |  |  |             }:${targetNode.port}`
 | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           lokiSnodeAPI.markPathAsBad(path); | 
					
						
							|  |  |  |           return false; | 
					
						
							|  |  |  |         } else if (result) { | 
					
						
							|  |  |  |           // not bad_path
 | 
					
						
							|  |  |  |           // will throw if there's a problem
 | 
					
						
							|  |  |  |           // eslint-disable-next-line no-await-in-loop
 | 
					
						
							|  |  |  |           await checkResponse(result, 'onion'); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           // not truish and not bad_path
 | 
					
						
							|  |  |  |           // false could mean, fail to parse results
 | 
					
						
							|  |  |  |           // or status code wasn't 200
 | 
					
						
							|  |  |  |           // or can't decrypt
 | 
					
						
							|  |  |  |           // it's not a bad_path, so we don't need to mark the path as bad
 | 
					
						
							|  |  |  |           log.error( | 
					
						
							|  |  |  |             `[path] sendOnionRequest gave false for path: ${getPathString( | 
					
						
							|  |  |  |               path | 
					
						
							|  |  |  |             )} to ${targetNode.ip}:${targetNode.port}`
 | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return result ? result.json() : false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (window.lokiFeatureFlags.useSnodeProxy && targetNode) { | 
					
						
							|  |  |  |       const result = await sendToProxy(fetchOptions, targetNode); | 
					
						
							|  |  |  |       if (result === false) { | 
					
						
							|  |  |  |         // should we retry?
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // even though we can't be sure our caller is going to log or handle the failure
 | 
					
						
							|  |  |  |         // we do know that sendToProxy should be logging
 | 
					
						
							|  |  |  |         // so I don't think we need or want a log item here...
 | 
					
						
							|  |  |  |         // log.warn(`lokiRpc:::lokiFetch - sendToProxy failed`);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // one case is:
 | 
					
						
							|  |  |  |         //   snodePool didn't have enough
 | 
					
						
							|  |  |  |         //   even after a refresh
 | 
					
						
							|  |  |  |         //   likely a network disconnect?
 | 
					
						
							|  |  |  |         // another is:
 | 
					
						
							|  |  |  |         //   failure to send to target node after 3 retries
 | 
					
						
							|  |  |  |         // what else?
 | 
					
						
							|  |  |  |         /* | 
					
						
							|  |  |  |         log.warn( | 
					
						
							|  |  |  |           'lokiRpc:::lokiFetch - useSnodeProxy failure, could not refresh randomPool, offline?' | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         */ | 
					
						
							|  |  |  |         // pass the false value up
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } else if (result) { | 
					
						
							|  |  |  |         // will throw if there's a problem
 | 
					
						
							|  |  |  |         await checkResponse(result, 'proxy'); | 
					
						
							|  |  |  |       } // result is not truish and not explicitly false
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // if not result, maybe we should throw??
 | 
					
						
							|  |  |  |       // [] would make _retrieveNextMessages return undefined
 | 
					
						
							|  |  |  |       // which would break messages.length
 | 
					
						
							|  |  |  |       return result ? result.json() : false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (url.match(/https:\/\//)) { | 
					
						
							|  |  |  |       // import that this does not get set in sendToProxy fetchOptions
 | 
					
						
							|  |  |  |       fetchOptions.agent = snodeHttpsAgent; | 
					
						
							|  |  |  |       process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       log.debug('lokirpc:::lokiFetch - http communication', url); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const response = await nodeFetch(url, fetchOptions); | 
					
						
							|  |  |  |     // restore TLS checking
 | 
					
						
							|  |  |  |     process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // will throw if there's a problem
 | 
					
						
							|  |  |  |     await checkResponse(response, 'direct'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!response.ok) { | 
					
						
							|  |  |  |       throw new textsecure.HTTPError('Loki_rpc error', response); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let result; | 
					
						
							|  |  |  |     if (response.headers.get('Content-Type') === 'application/json') { | 
					
						
							|  |  |  |       result = await response.json(); | 
					
						
							|  |  |  |     } else if (options.responseType === 'arraybuffer') { | 
					
						
							|  |  |  |       result = await response.buffer(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       result = await response.text(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     if (e.code === 'ENOTFOUND') { | 
					
						
							|  |  |  |       throw new textsecure.NotFoundError('Failed to resolve address', e); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     throw e; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Wrapper for a JSON RPC request
 | 
					
						
							|  |  |  | // Annoyngly, this is used for Lokid requests too
 | 
					
						
							|  |  |  | const lokiRpc = ( | 
					
						
							|  |  |  |   address, | 
					
						
							|  |  |  |   port, | 
					
						
							|  |  |  |   method, | 
					
						
							|  |  |  |   params, | 
					
						
							|  |  |  |   options = {}, | 
					
						
							|  |  |  |   endpoint = endpointBase, | 
					
						
							|  |  |  |   targetNode | 
					
						
							|  |  |  | ) => { | 
					
						
							|  |  |  |   const headers = options.headers || {}; | 
					
						
							|  |  |  |   const portString = port ? `:${port}` : ''; | 
					
						
							|  |  |  |   const url = `${address}${portString}${endpoint}`; | 
					
						
							|  |  |  |   // TODO: The jsonrpc and body field will be ignored on storage server
 | 
					
						
							|  |  |  |   if (params.pubKey) { | 
					
						
							|  |  |  |     // Ensure we always take a copy
 | 
					
						
							|  |  |  |     // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |     params = { | 
					
						
							|  |  |  |       ...params, | 
					
						
							|  |  |  |       pubKey: getStoragePubKey(params.pubKey), | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const body = { | 
					
						
							|  |  |  |     jsonrpc: '2.0', | 
					
						
							|  |  |  |     id: '0', | 
					
						
							|  |  |  |     method, | 
					
						
							|  |  |  |     params, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const fetchOptions = { | 
					
						
							|  |  |  |     method: 'POST', | 
					
						
							|  |  |  |     ...options, | 
					
						
							|  |  |  |     body: JSON.stringify(body), | 
					
						
							|  |  |  |     headers: { | 
					
						
							|  |  |  |       'Content-Type': 'application/json', | 
					
						
							|  |  |  |       ...headers, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return lokiFetch(url, fetchOptions, targetNode); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = { | 
					
						
							|  |  |  |   lokiRpc, | 
					
						
							|  |  |  |   BAD_PATH, | 
					
						
							|  |  |  |   sendOnionRequestLsrpcDest, | 
					
						
							|  |  |  | }; |