@turnkey/http
Version:
Typed HTTP client for interacting with Turnkey API
160 lines (157 loc) • 5.11 kB
JavaScript
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