UNPKG

@turnkey/http

Version:

Typed HTTP client for interacting with Turnkey API

173 lines (169 loc) 5.63 kB
'use strict'; var apiKeyStamper = require('@turnkey/api-key-stamper'); var universal = require('./universal.js'); var config = require('./config.js'); var encoding = require('@turnkey/encoding'); var webauthn = require('./webauthn.js'); const sharedHeaders = { "Content-Type": "application/json", }; const sharedRequestOptions = { redirect: "follow", }; /** * @deprecated */ async function signedRequest(input) { const { uri: inputUri, query: inputQuery = {}, substitution: inputSubstitution = {}, body: inputBody = {}, } = input; const url = constructUrl({ uri: inputUri, query: inputQuery, substitution: inputSubstitution, }); const body = JSON.stringify(inputBody); const stamp = await webauthn.getWebAuthnAssertion(body, input.options); return { url: url.toString(), body, stamp, }; } async function request(input) { const { uri: inputUri, method, headers: inputHeaders = {}, query: inputQuery = {}, substitution: inputSubstitution = {}, body: inputBody = {}, } = input; const url = constructUrl({ uri: inputUri, query: inputQuery, substitution: inputSubstitution, }); const { sealedBody, xStamp } = await sealAndStampRequestBody({ body: inputBody, }); const response = await universal.fetch(url.toString(), { ...sharedRequestOptions, method, headers: { ...sharedHeaders, ...inputHeaders, "X-Stamp": xStamp, }, body: sealedBody, }); if (!response.ok) { // Can't use native `cause` here because it's not well supported on Node v16 // https://node.green/#ES2022-features-Error-cause-property let res; try { res = await response.json(); } catch (_) { throw new Error(`${response.status} ${response.statusText}`); } throw new TurnkeyRequestError(res); } const data = await response.json(); return data; } function constructUrl(input) { const { uri, query, substitution } = input; const baseUrl = getBaseUrl(); const url = new URL(substitutePath(uri, substitution), baseUrl); for (const key in query) { const value = query[key]; if (Array.isArray(value)) { for (const item of value) { url.searchParams.append(key, item); } } else { url.searchParams.append(key, value ?? ""); } } return url; } function getBaseUrl() { try { const { baseUrl } = config.getConfig(); return baseUrl; } catch (e) { const { baseUrl } = config.getBrowserConfig(); return baseUrl; } } function substitutePath(uri, substitutionMap) { let result = uri; const keyList = Object.keys(substitutionMap); for (const key of keyList) { const output = result.replaceAll(`{${key}}`, substitutionMap[key]); invariant(output !== result, `Substitution error: cannot find "${key}" in URI "${uri}". \`substitutionMap\`: ${JSON.stringify(substitutionMap)}`); result = output; } invariant(!/\{.*\}/.test(result), `Substitution error: found unsubstituted components in "${result}"`); return result; } function invariant(condition, message) { if (!condition) { throw new Error(message); } } function stableStringify(input) { return JSON.stringify(input); } /** * Seals and stamps the request body with your Turnkey API credentials. * * You can either: * - Before calling `sealAndStampRequestBody(...)`, initialize with your Turnkey API credentials via `init(...)` * - Or, provide `apiPublicKey` and `apiPrivateKey` here as arguments */ async function sealAndStampRequestBody(input) { const { body } = input; let { apiPublicKey, apiPrivateKey } = input; if (!apiPublicKey) { const config$1 = config.getConfig(); apiPublicKey = config$1.apiPublicKey; } if (!apiPrivateKey) { const config$1 = config.getConfig(); apiPrivateKey = config$1.apiPrivateKey; } const sealedBody = stableStringify(body); const signature = await apiKeyStamper.signWithApiKey({ content: sealedBody, privateKey: apiPrivateKey, publicKey: apiPublicKey, }); const sealedStamp = stableStringify({ publicKey: apiPublicKey, scheme: "SIGNATURE_SCHEME_TK_API_P256", signature: signature, }); const xStamp = encoding.stringToBase64urlString(sealedStamp); return { sealedBody, xStamp, }; } // Check if the client is an instance of TurnkeyClient. We check the name field here since the 'instanceof' operator does not work across if the http client isn't EXACTLY the same (mismatching versions). function isHttpClient(client) { return client?.name === "TurnkeyClient"; } class TurnkeyRequestError extends Error { constructor(input) { let turnkeyErrorMessage = `Turnkey error ${input.code}: ${input.message}`; if (input.details != null) { turnkeyErrorMessage += ` (Details: ${JSON.stringify(input.details)})`; } super(turnkeyErrorMessage); this.name = "TurnkeyRequestError"; this.details = input.details ?? null; this.code = input.code; } } exports.fetch = universal.fetch; exports.TurnkeyRequestError = TurnkeyRequestError; exports.isHttpClient = isHttpClient; exports.request = request; exports.sealAndStampRequestBody = sealAndStampRequestBody; exports.signedRequest = signedRequest; //# sourceMappingURL=base.js.map