UNPKG

@turnkey/http

Version:

Typed HTTP client for interacting with Turnkey API

160 lines (157 loc) 5.11 kB
import { signWithApiKey } from '@turnkey/api-key-stamper'; import { fetch } from './universal.mjs'; import { getConfig, getBrowserConfig } from './config.mjs'; import { stringToBase64urlString } from '@turnkey/encoding'; import { getWebAuthnAssertion } from './webauthn.mjs'; const sharedHeaders = {}; 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 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 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 } = getConfig(); return baseUrl; } catch (e) { const { baseUrl } = 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 = getConfig(); apiPublicKey = config.apiPublicKey; } if (!apiPrivateKey) { const config = getConfig(); apiPrivateKey = config.apiPrivateKey; } const sealedBody = stableStringify(body); const signature = await signWithApiKey({ content: sealedBody, privateKey: apiPrivateKey, publicKey: apiPublicKey, }); const sealedStamp = stableStringify({ publicKey: apiPublicKey, scheme: "SIGNATURE_SCHEME_TK_API_P256", signature: signature, }); const xStamp = stringToBase64urlString(sealedStamp); return { sealedBody, xStamp, }; } 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; } } export { TurnkeyRequestError, fetch, request, sealAndStampRequestBody, signedRequest }; //# sourceMappingURL=base.mjs.map