@relume_io/partner-node 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/dist/bin.cjs +44 -4
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +39 -5
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-HPVVBUR7.js → chunk-6WSDAFUV.js} +189 -1
- package/dist/chunk-6WSDAFUV.js.map +1 -0
- package/dist/index.cjs +35 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +26 -176
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-HPVVBUR7.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/guards.ts","../src/calls.ts","../src/response.ts","../src/session.ts","../src/signing.ts"],"sourcesContent":["// Value checks shared by the call handler (calls.ts) and the conformance\n// checker (check.ts): the checker is only useful if it accepts exactly what\n// the handler, and Relume behind it, accept.\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function isString(\n value: unknown,\n min: number,\n max: number,\n): value is string {\n return (\n typeof value === 'string' && value.length >= min && value.length <= max\n );\n}\n\nexport function isWhole(value: unknown, min: number): value is number {\n return (\n typeof value === 'number' && Number.isSafeInteger(value) && value >= min\n );\n}\n\n/** null and '' count as a field left out, as they do for Relume. */\nexport function absent(value: unknown): boolean {\n return value === undefined || value === null || value === '';\n}\n\n// Zod 4's default email pattern, which Relume validates user.get with\n// (backend/src/services/partners/partner-calls.ts). Change both together.\nconst EMAIL =\n /^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/;\n\nexport function isEmail(value: unknown): value is string {\n return typeof value === 'string' && EMAIL.test(value);\n}\n\nexport function isHttpsUrl(value: unknown): value is string {\n if (typeof value !== 'string') return false;\n try {\n return new URL(value).protocol === 'https:';\n } catch {\n return false;\n }\n}\n","// Validates the body of a signed call and what each callback returns. Hand\n// written so the package has no runtime dependencies. Unknown request fields\n// are dropped (the protocol may add fields at any time); unknown `action` and\n// `reason` values are accepted. A call for the other environment is refused,\n// so a test call can never move a live wallet.\n\nimport {\n absent,\n isEmail,\n isHttpsUrl,\n isRecord,\n isString,\n isWhole,\n} from './guards.ts';\nimport type {\n CallType,\n Charge,\n ChargeResult,\n Credits,\n Environment,\n Reversal,\n ReversalResult,\n UserProfile,\n} from './types.ts';\n\nexport const CALL_TYPES: readonly CallType[] = [\n 'user.get',\n 'credits.get',\n 'credits.charge',\n 'credits.reverse',\n];\n\n/** The environment a secret key belongs to, from its prefix; null for neither. */\nexport function keyEnvironment(secretKey: string): Environment | null {\n if (secretKey.startsWith('sk_test_')) return 'test';\n if (secretKey.startsWith('sk_live_')) return 'live';\n return null;\n}\n\nexport type IncomingCall =\n | { type: 'user.get'; userId: string }\n | { type: 'credits.get'; userId: string }\n | { type: 'credits.charge'; charge: Charge }\n | { type: 'credits.reverse'; reversal: Reversal };\n\nexport type ParseCallResult =\n | { ok: true; call: IncomingCall }\n | {\n ok: false;\n code:\n | 'invalid_request'\n | 'unsupported_call_type'\n | 'environment_mismatch';\n message: string;\n /** The call's type, when it had a known one. */\n type?: CallType;\n };\n\n/** Thrown when a callback returns something the protocol doesn't allow. */\nexport class InvalidCallbackResultError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidCallbackResultError';\n }\n}\n\n/** Optional strings arrive omitted; null counts as omitted too. */\nfunction optionalString(\n value: unknown,\n max: number,\n): { ok: true; value: string | undefined } | { ok: false } {\n if (value === undefined || value === null) {\n return { ok: true, value: undefined };\n }\n return isString(value, 1, max) ? { ok: true, value } : { ok: false };\n}\n\nfunction isCallType(value: unknown): value is CallType {\n return CALL_TYPES.some((type) => type === value);\n}\n\n/** `environment` is the secret key's: a call for the other one is refused. */\nexport function parseCall(\n body: unknown,\n environment: Environment,\n): ParseCallResult {\n if (!isRecord(body)) {\n return {\n ok: false,\n code: 'invalid_request',\n message: 'The body must be a JSON object.',\n };\n }\n const { type } = body;\n if (!isCallType(type)) {\n return {\n ok: false,\n code: 'unsupported_call_type',\n message: `Unsupported call type: ${JSON.stringify(type ?? null).slice(0, 64)}.`,\n };\n }\n const invalid = (field: string): ParseCallResult => ({\n ok: false,\n code: 'invalid_request',\n message: `Invalid ${field} for ${type}.`,\n type,\n });\n\n // A call from a Relume deploy older than the field has no environment;\n // that is nothing to check, until a later major release requires it. A\n // value Relume never sends is refused.\n if (\n body.environment !== undefined &&\n body.environment !== 'test' &&\n body.environment !== 'live'\n ) {\n return invalid('environment');\n }\n if (body.environment !== undefined && body.environment !== environment) {\n return {\n ok: false,\n code: 'environment_mismatch',\n message: `This ${type} is for ${body.environment}, but this server's secret key is for ${environment}.`,\n type,\n };\n }\n\n const { userId } = body;\n if (!isString(userId, 1, 128)) return invalid('userId');\n\n switch (type) {\n case 'user.get':\n case 'credits.get':\n return { ok: true, call: { type, userId } };\n\n case 'credits.charge': {\n const { chargeId, partnerCredits, relumeCredits, action, description } =\n body;\n if (!isString(chargeId, 1, 64)) return invalid('chargeId');\n if (!isWhole(partnerCredits, 1)) return invalid('partnerCredits');\n if (!isWhole(relumeCredits, 0)) return invalid('relumeCredits');\n if (!isString(action, 1, 200)) return invalid('action');\n if (!isString(description, 0, 200)) return invalid('description');\n const siteId = optionalString(body.siteId, 200);\n if (!siteId.ok) return invalid('siteId');\n const pageId = optionalString(body.pageId, 200);\n if (!pageId.ok) return invalid('pageId');\n const charge: Charge = {\n userId,\n chargeId,\n partnerCredits,\n relumeCredits,\n action,\n description,\n };\n if (siteId.value !== undefined) charge.siteId = siteId.value;\n if (pageId.value !== undefined) charge.pageId = pageId.value;\n return { ok: true, call: { type, charge } };\n }\n\n case 'credits.reverse': {\n const { reversalId, partnerCredits, relumeCredits, reason } = body;\n if (!isString(reversalId, 1, 64)) return invalid('reversalId');\n if (!isWhole(partnerCredits, 1)) return invalid('partnerCredits');\n if (!isWhole(relumeCredits, 0)) return invalid('relumeCredits');\n if (!isString(reason, 1, 200)) return invalid('reason');\n const chargeId = optionalString(body.chargeId, 64);\n if (!chargeId.ok) return invalid('chargeId');\n const reversal: Reversal = {\n userId,\n reversalId,\n partnerCredits,\n relumeCredits,\n reason,\n };\n if (chargeId.value !== undefined) reversal.chargeId = chargeId.value;\n return { ok: true, call: { type, reversal } };\n }\n }\n}\n\nfunction balanceOrThrow(value: unknown, callback: string): number {\n if (!isWhole(value, 0)) {\n throw new InvalidCallbackResultError(\n `${callback} must return a balance that is a whole number, zero or more; got ${JSON.stringify(value)}.`,\n );\n }\n return value;\n}\n\n/** The 200 body for user.get, or null for a 404. */\nexport function checkUserProfile(value: unknown): UserProfile | null {\n if (value === null || value === undefined) return null;\n if (!isRecord(value)) {\n throw new InvalidCallbackResultError(\n 'onGetUser must return { name?, email?, avatarUrl? } or null.',\n );\n }\n const profile: UserProfile = {};\n const { name, email, avatarUrl } = value;\n if (!absent(name)) {\n if (!isString(name, 1, 200)) {\n throw new InvalidCallbackResultError(\n 'onGetUser: name must be a string of at most 200 characters.',\n );\n }\n profile.name = name;\n }\n if (!absent(email)) {\n if (!isEmail(email)) {\n throw new InvalidCallbackResultError(\n 'onGetUser: email must be a valid email address.',\n );\n }\n profile.email = email;\n }\n if (!absent(avatarUrl)) {\n if (!isHttpsUrl(avatarUrl)) {\n throw new InvalidCallbackResultError(\n 'onGetUser: avatarUrl must be an https URL.',\n );\n }\n profile.avatarUrl = avatarUrl;\n }\n return profile;\n}\n\nexport function checkCredits(value: unknown): Credits {\n if (!isRecord(value)) {\n throw new InvalidCallbackResultError(\n 'onGetCredits must return { balance }.',\n );\n }\n return { balance: balanceOrThrow(value.balance, 'onGetCredits') };\n}\n\nfunction optionalBalance(\n value: Record<string, unknown>,\n callback: string,\n): { balance?: number } {\n return absent(value.balance)\n ? {}\n : { balance: balanceOrThrow(value.balance, callback) };\n}\n\nexport function checkChargeResult(\n value: unknown,\n): Required<Pick<ChargeResult, 'ok'>> & { balance?: number } {\n if (!isRecord(value) || typeof value.ok !== 'boolean') {\n throw new InvalidCallbackResultError(\n 'onChargeCredits must return { ok: boolean, balance? }.',\n );\n }\n return { ok: value.ok, ...optionalBalance(value, 'onChargeCredits') };\n}\n\nexport function checkReversalResult(value: unknown): ReversalResult & {\n balance?: number;\n} {\n if (value === undefined || value === null) return {};\n if (!isRecord(value)) {\n throw new InvalidCallbackResultError(\n 'onReverseCharge must return { balance? } or nothing.',\n );\n }\n return optionalBalance(value, 'onReverseCharge');\n}\n","import type { RelumeResponse } from './types.ts';\n\n/** Bodies above this are refused with 413. Relume's calls are a few hundred bytes. */\nexport const MAX_BODY_BYTES = 1024 * 1024;\n\nconst JSON_HEADERS = {\n 'Content-Type': 'application/json; charset=utf-8',\n 'Cache-Control': 'no-store',\n} as const;\n\nexport function jsonResponse(\n status: number,\n body: unknown,\n headers: Record<string, string> = {},\n): RelumeResponse {\n return { status, headers: { ...JSON_HEADERS, ...headers }, body };\n}\n\nexport function errorResponse(\n status: number,\n code: string,\n message: string,\n headers?: Record<string, string>,\n): RelumeResponse {\n return jsonResponse(status, { error: { code, message } }, headers);\n}\n\nexport type Route = 'session' | 'calls';\n\nconst METHOD_FOR: Record<Route, string> = { session: 'GET', calls: 'POST' };\n\n/**\n * fetchHandler: which route a path is for, by its last segment. The framework\n * already routed the request here (a Next.js route file, Hono's\n * `app.all('/relume/*')`), so this works under any prefix.\n */\nexport function routeOf(pathname: string): Route | null {\n const last = pathname.replace(/\\/+$/, '').split('/').pop();\n return last === 'session' || last === 'calls' ? last : null;\n}\n\n/**\n * express(): exact paths only. Middleware sees every request in the app, so\n * matching any path ending in /calls would swallow the partner's own routes.\n * Under app.use('/relume', …) Express strips the mount path (and sets\n * req.baseUrl); mounted at the root, or behind bare node:http, the full\n * /relume/… path is expected.\n */\nexport function mountedRouteOf(\n pathname: string,\n mounted: boolean,\n): Route | null {\n const path = pathname.replace(/(.)\\/+$/, '$1');\n const prefix = mounted ? '' : '/relume';\n if (path === `${prefix}/session`) return 'session';\n if (path === `${prefix}/calls`) return 'calls';\n return null;\n}\n\n/** A 405 for the wrong method on a known route, or null when it's right. */\nexport function methodNotAllowed(\n route: Route,\n method: string | undefined,\n): RelumeResponse | null {\n const allowed = METHOD_FOR[route];\n // No HEAD on the session route: it would mint a client secret per probe.\n // OPTIONS is a CORS preflight, which the adapters leave to the app.\n if (method === allowed) return null;\n return errorResponse(\n 405,\n 'method_not_allowed',\n `Use ${allowed} for this route.`,\n { Allow: allowed },\n );\n}\n\nexport function payloadTooLarge(): RelumeResponse {\n return errorResponse(413, 'payload_too_large', 'The body is too large.');\n}\n\n/** A preflight that reached the handler: no CORS headers, so no grant. */\nexport function preflight(route: Route): RelumeResponse {\n return { status: 204, headers: { Allow: METHOD_FOR[route] }, body: null };\n}\n\n/** The parts of a client the adapters use. */\nexport interface Handlers<TRequest> {\n handleCall(input: {\n rawBody: string | Uint8Array;\n signatureHeader: string | null | undefined;\n requestId?: string | null;\n }): Promise<RelumeResponse>;\n handleSession(request: TRequest): Promise<RelumeResponse>;\n}\n","// Defences for `GET /relume/session`, which mints a client secret for whoever\n// the request's cookies say is signed in. Any site can make a signed-in\n// user's browser send that request; only the partner's CORS stops it reading\n// the answer, and a CORS setup that reflects Origin would hand the secret\n// over. So the route also refuses what only another site sends.\n\nimport { isRecord } from './guards.ts';\nimport { errorResponse } from './response.ts';\nimport type { RelumeResponse } from './types.ts';\n\n/**\n * Sent by `@relume_io/partner-embed` on every session request. A custom\n * header makes a cross-origin request need a CORS preflight, which a plain\n * link, form or `<img>` on another site can't get.\n */\nexport const EMBED_HEADER = 'Relume-Embed';\nexport const EMBED_HEADER_VALUE = '1';\n\n/**\n * What `handleSession` needs from a request: its headers. The fetch\n * `Request`, Express's request and node:http's `IncomingMessage` all fit.\n */\nexport interface SessionRequest {\n headers: unknown;\n}\n\ntype HeaderReader = (name: string) => string | null;\n\ninterface HeadersLike {\n get(name: string): string | null;\n}\n\nfunction isHeadersLike(value: unknown): value is HeadersLike {\n return isRecord(value) && typeof value.get === 'function';\n}\n\n/**\n * The request's headers, from a fetch `Request`, a node:http (Express)\n * request, or any object with `headers`. Null when it has none we can read.\n */\nfunction headerReader(request: unknown): HeaderReader | null {\n const headers = isRecord(request) ? request.headers : undefined;\n if (isHeadersLike(headers)) return (name) => headers.get(name);\n if (!isRecord(headers)) return null;\n return (name) => {\n const wanted = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== wanted) continue;\n const first = Array.isArray(value) ? value[0] : value;\n return typeof first === 'string' ? first : null;\n }\n return null;\n };\n}\n\nexport interface SessionRefusal {\n response: RelumeResponse;\n /** For onHandlerError: a refusal that points at the partner's setup. */\n error?: Error;\n}\n\n/** Why the session route refuses this request, or null to go on. */\nexport function refuseSessionRequest(request: unknown): SessionRefusal | null {\n const header = headerReader(request);\n if (!header) {\n return {\n response: errorResponse(\n 500,\n 'internal_error',\n 'The partner failed to handle the call.',\n ),\n error: new TypeError(\n \"handleSession couldn't read the request's headers. Pass it the fetch Request, the Node request, or an object with headers.\",\n ),\n };\n }\n // Browsers send Sec-Fetch-Site on every request. Another site is never a\n // legitimate caller, whatever the partner's CORS allows.\n if (header('sec-fetch-site')?.trim().toLowerCase() === 'cross-site') {\n return {\n response: errorResponse(\n 403,\n 'cross_site_request',\n 'The session route only answers your own pages.',\n ),\n };\n }\n if (header(EMBED_HEADER)?.trim() !== EMBED_HEADER_VALUE) {\n return {\n response: errorResponse(\n 400,\n 'embed_header_missing',\n `The session route needs the ${EMBED_HEADER} header that @relume_io/partner-embed sends.`,\n ),\n error: new Error(\n `GET /relume/session was refused: no ${EMBED_HEADER}: ${EMBED_HEADER_VALUE} header. @relume_io/partner-embed sends it from 2.0.0; update the script tag or package. On another origin, allow the header in the route's CORS preflight.`,\n ),\n };\n }\n return null;\n}\n","// Relume-Signature verification (docs/partner-protocol/v1.md section 6).\n//\n// Relume-Signature: t=<unix seconds>,v1=<hex hmac-sha256(sk, t + \".\" + rawBody)>[,v1=…]\n//\n// Mirrors backend/src/services/partners/signing.ts and passes\n// docs/partner-protocol/test-vectors.json. Uses Web Crypto rather than\n// node:crypto so the same code runs on Node, Workers, Deno, Bun and edge\n// runtimes. Never log a secret or a signature.\n\nexport const SIGNATURE_HEADER = 'Relume-Signature';\nexport const REQUEST_ID_HEADER = 'Relume-Request-Id';\nexport const SIGNATURE_SCHEME = 'v1';\n/** How far a call's timestamp may be from this server's clock, either way. */\nexport const SIGNATURE_TOLERANCE_SECONDS = 5 * 60;\n/** The current key plus the previous one during a rotation overlap. */\nexport const MAX_SIGNATURES = 2;\n\nconst HEX_SHA256 = /^[0-9a-f]{64}$/i;\n// Canonical decimal: other implementations HMAC the t= text as written, so a\n// leading zero would verify here and fail there.\nconst UNIX_SECONDS = /^(0|[1-9]\\d{0,11})$/;\n\nexport type SignatureFailure =\n | 'malformed_header'\n | 'signature_mismatch'\n | 'timestamp_out_of_tolerance';\n\nexport type VerifySignatureResult =\n | { valid: true; timestamp: number }\n | { valid: false; reason: SignatureFailure };\n\nexport interface ParsedSignatureHeader {\n timestamp: number;\n /** Every v1= value in header order, including ones that aren't hex. */\n signatures: string[];\n}\n\nconst encoder = new TextEncoder();\n\nfunction bodyBytes(rawBody: string | Uint8Array): Uint8Array {\n return typeof rawBody === 'string' ? encoder.encode(rawBody) : rawBody;\n}\n\n/** `t + \".\" + rawBody` as bytes: the exact message the HMAC covers. */\nfunction signedMessage(\n timestamp: number,\n rawBody: string | Uint8Array,\n): Uint8Array<ArrayBuffer> {\n const prefix = encoder.encode(`${timestamp}.`);\n const body = bodyBytes(rawBody);\n const message = new Uint8Array(prefix.length + body.length);\n message.set(prefix, 0);\n message.set(body, prefix.length);\n return message;\n}\n\nfunction hmacKey(secret: string, usage: 'sign' | 'verify'): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n [usage],\n );\n}\n\nfunction toHex(bytes: ArrayBuffer): string {\n return Array.from(new Uint8Array(bytes), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('');\n}\n\nfunction fromHex(hex: string): Uint8Array<ArrayBuffer> {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i += 1) {\n bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\nfunction assertTimestamp(timestamp: number): void {\n if (!Number.isSafeInteger(timestamp) || timestamp < 0) {\n throw new TypeError('timestamp must be whole unix seconds');\n }\n}\n\n/** Lowercase hex HMAC-SHA256 of `t + \".\" + rawBody`, keyed by the secret key. */\nexport async function computeSignature(\n secret: string,\n timestamp: number,\n rawBody: string | Uint8Array,\n): Promise<string> {\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new TypeError('secret must be a non-empty string');\n }\n assertTimestamp(timestamp);\n const key = await hmacKey(secret, 'sign');\n return toHex(\n await crypto.subtle.sign('HMAC', key, signedMessage(timestamp, rawBody)),\n );\n}\n\n/**\n * Builds a Relume-Signature header the way Relume does: newest secret first,\n * a second one only during a rotation. For a partner's own tests; a partner's\n * server never needs to sign anything.\n */\nexport async function signCall(\n secrets: string | readonly string[],\n timestamp: number,\n rawBody: string | Uint8Array,\n): Promise<string> {\n const list = typeof secrets === 'string' ? [secrets] : secrets;\n if (list.length === 0 || list.length > MAX_SIGNATURES) {\n throw new TypeError(\n `expected 1 to ${MAX_SIGNATURES} secrets, got ${list.length}`,\n );\n }\n const signatures = await Promise.all(\n list.map(async (secret) => {\n const hex = await computeSignature(secret, timestamp, rawBody);\n return `${SIGNATURE_SCHEME}=${hex}`;\n }),\n );\n return [`t=${timestamp}`, ...signatures].join(',');\n}\n\n/**\n * Null when the header has no `t`, more than one `t`, a `t` that isn't\n * canonical unix seconds, no v1= value, or more than two v1= values. Entries\n * with another name (a future `v2=`) are ignored.\n */\nexport function parseSignatureHeader(\n header: string,\n): ParsedSignatureHeader | null {\n let timestamp: number | null = null;\n const signatures: string[] = [];\n for (const part of header.split(',')) {\n const separator = part.indexOf('=');\n if (separator === -1) continue;\n const name = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (name === 't') {\n if (timestamp !== null || !UNIX_SECONDS.test(value)) return null;\n timestamp = Number(value);\n } else if (name === SIGNATURE_SCHEME) {\n if (signatures.length === MAX_SIGNATURES) return null;\n signatures.push(value);\n }\n }\n if (timestamp === null || signatures.length === 0) return null;\n return { timestamp, signatures };\n}\n\n/**\n * Verifies a call: the header parses, any v1= value matches the HMAC under\n * `secret` (compared in constant time by Web Crypto), and `t` is within 300\n * seconds of `nowSeconds`. The signature is checked before the timestamp, so\n * a stale result is only reported for a call Relume really signed.\n */\nexport async function verifySignature(input: {\n secret: string;\n header: string | null | undefined;\n rawBody: string | Uint8Array;\n /** Defaults to the current time. */\n nowSeconds?: number;\n}): Promise<VerifySignatureResult> {\n const parsed = input.header ? parseSignatureHeader(input.header) : null;\n if (parsed === null) return { valid: false, reason: 'malformed_header' };\n // A blank key (an unset env var) rejects the call; it never skips the check.\n if (typeof input.secret !== 'string' || input.secret.length === 0) {\n return { valid: false, reason: 'signature_mismatch' };\n }\n\n const key = await hmacKey(input.secret, 'verify');\n const message = signedMessage(parsed.timestamp, input.rawBody);\n // Check every value, so the time taken doesn't depend on which one matched.\n const results = await Promise.all(\n parsed.signatures.map((signature) =>\n HEX_SHA256.test(signature)\n ? crypto.subtle.verify('HMAC', key, fromHex(signature), message)\n : Promise.resolve(false),\n ),\n );\n if (!results.includes(true)) {\n return { valid: false, reason: 'signature_mismatch' };\n }\n\n const now = input.nowSeconds ?? Math.floor(Date.now() / 1000);\n if (Math.abs(now - parsed.timestamp) > SIGNATURE_TOLERANCE_SECONDS) {\n return { valid: false, reason: 'timestamp_out_of_tolerance' };\n }\n return { valid: true, timestamp: parsed.timestamp };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -419,7 +419,7 @@ function isHttpsUrl(value) {
|
|
|
419
419
|
|
|
420
420
|
// src/api.ts
|
|
421
421
|
var DEFAULT_API_BASE_URL = "https://apis.relume.io/nova/partner/v1";
|
|
422
|
-
var SDK_VERSION = "2.0
|
|
422
|
+
var SDK_VERSION = "2.1.0";
|
|
423
423
|
var USER_AGENT = `relume-partner-node/${SDK_VERSION}`;
|
|
424
424
|
var DEFAULT_API_TIMEOUT_MS = 15e3;
|
|
425
425
|
var DEFAULT_MAX_RETRIES = 2;
|
|
@@ -577,6 +577,11 @@ var CALL_TYPES = [
|
|
|
577
577
|
"credits.charge",
|
|
578
578
|
"credits.reverse"
|
|
579
579
|
];
|
|
580
|
+
function keyEnvironment(secretKey) {
|
|
581
|
+
if (secretKey.startsWith("sk_test_")) return "test";
|
|
582
|
+
if (secretKey.startsWith("sk_live_")) return "live";
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
580
585
|
var InvalidCallbackResultError = class extends Error {
|
|
581
586
|
constructor(message) {
|
|
582
587
|
super(message);
|
|
@@ -592,7 +597,7 @@ function optionalString(value, max) {
|
|
|
592
597
|
function isCallType(value) {
|
|
593
598
|
return CALL_TYPES.some((type) => type === value);
|
|
594
599
|
}
|
|
595
|
-
function parseCall(body) {
|
|
600
|
+
function parseCall(body, environment) {
|
|
596
601
|
if (!isRecord(body)) {
|
|
597
602
|
return {
|
|
598
603
|
ok: false,
|
|
@@ -614,6 +619,17 @@ function parseCall(body) {
|
|
|
614
619
|
message: `Invalid ${field} for ${type}.`,
|
|
615
620
|
type
|
|
616
621
|
});
|
|
622
|
+
if (body.environment !== void 0 && body.environment !== "test" && body.environment !== "live") {
|
|
623
|
+
return invalid("environment");
|
|
624
|
+
}
|
|
625
|
+
if (body.environment !== void 0 && body.environment !== environment) {
|
|
626
|
+
return {
|
|
627
|
+
ok: false,
|
|
628
|
+
code: "environment_mismatch",
|
|
629
|
+
message: `This ${type} is for ${body.environment}, but this server's secret key is for ${environment}.`,
|
|
630
|
+
type
|
|
631
|
+
};
|
|
632
|
+
}
|
|
617
633
|
const { userId } = body;
|
|
618
634
|
if (!isString(userId, 1, 128)) return invalid("userId");
|
|
619
635
|
switch (type) {
|
|
@@ -974,6 +990,7 @@ var Relume = class _Relume {
|
|
|
974
990
|
*/
|
|
975
991
|
fetchHandler;
|
|
976
992
|
#secretKey;
|
|
993
|
+
#environment;
|
|
977
994
|
#api;
|
|
978
995
|
#callbacks = {};
|
|
979
996
|
#onHandlerError;
|
|
@@ -993,6 +1010,12 @@ var Relume = class _Relume {
|
|
|
993
1010
|
"Relume.init: that is a publishable key. Pass the secret key (sk_\u2026); the publishable key goes to the embed."
|
|
994
1011
|
);
|
|
995
1012
|
}
|
|
1013
|
+
const environment = keyEnvironment(secretKey);
|
|
1014
|
+
if (environment === null) {
|
|
1015
|
+
throw new TypeError(
|
|
1016
|
+
"Relume.init: secretKey must start with sk_test_ or sk_live_."
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
996
1019
|
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
997
1020
|
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) {
|
|
998
1021
|
throw new TypeError(
|
|
@@ -1000,6 +1023,7 @@ var Relume = class _Relume {
|
|
|
1000
1023
|
);
|
|
1001
1024
|
}
|
|
1002
1025
|
this.#secretKey = secretKey;
|
|
1026
|
+
this.#environment = environment;
|
|
1003
1027
|
this.#now = options.now ?? Date.now;
|
|
1004
1028
|
this.#api = {
|
|
1005
1029
|
secretKey,
|
|
@@ -1075,8 +1099,9 @@ var Relume = class _Relume {
|
|
|
1075
1099
|
}
|
|
1076
1100
|
/**
|
|
1077
1101
|
* `POST /relume/calls`, framework-agnostic: verifies the signature and the
|
|
1078
|
-
* 5-minute window,
|
|
1079
|
-
*
|
|
1102
|
+
* 5-minute window, refuses a call for another environment than the secret
|
|
1103
|
+
* key's (`400 environment_mismatch`), routes by `type`, runs your callback
|
|
1104
|
+
* and validates what it returns. Pass the raw body exactly as received.
|
|
1080
1105
|
*/
|
|
1081
1106
|
async handleCall(input) {
|
|
1082
1107
|
const requestId = input.requestId ?? null;
|
|
@@ -1100,10 +1125,14 @@ var Relume = class _Relume {
|
|
|
1100
1125
|
"The body is not valid JSON."
|
|
1101
1126
|
);
|
|
1102
1127
|
}
|
|
1103
|
-
const parsed = parseCall(body);
|
|
1128
|
+
const parsed = parseCall(body, this.#environment);
|
|
1104
1129
|
if (!parsed.ok) return errorResponse(400, parsed.code, parsed.message);
|
|
1105
1130
|
const { call } = parsed;
|
|
1106
|
-
const context = {
|
|
1131
|
+
const context = {
|
|
1132
|
+
type: call.type,
|
|
1133
|
+
environment: this.#environment,
|
|
1134
|
+
requestId
|
|
1135
|
+
};
|
|
1107
1136
|
const callbackName = CALLBACK_FOR[call.type];
|
|
1108
1137
|
if (!this.#callbacks[callbackName]) {
|
|
1109
1138
|
this.#onHandlerError(
|