@dermacore/node 0.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.
@@ -0,0 +1,128 @@
1
+ import { APIConnectionError, APITimeoutError, errorFromResponse } from "../errors.js";
2
+ export class HttpClient {
3
+ #config;
4
+ constructor(config) {
5
+ if (!config.apiKey) {
6
+ throw new Error("DermaCore: `apiKey` is required.");
7
+ }
8
+ this.#config = {
9
+ apiKey: config.apiKey,
10
+ baseURL: (config.baseURL ?? "https://api.dermacore.ai").replace(/\/+$/, ""),
11
+ timeoutMs: config.timeoutMs ?? 30_000,
12
+ maxRetries: config.maxRetries ?? 2,
13
+ defaultHeaders: config.defaultHeaders ?? {},
14
+ fetchImpl: config.fetch ?? fetch,
15
+ };
16
+ }
17
+ async request(method, path, options = {}) {
18
+ const url = new URL(this.#config.baseURL + path);
19
+ if (options.query) {
20
+ for (const [key, value] of Object.entries(options.query)) {
21
+ if (value === undefined)
22
+ continue;
23
+ url.searchParams.set(key, String(value));
24
+ }
25
+ }
26
+ const headers = new Headers(this.#config.defaultHeaders);
27
+ headers.set("Authorization", `ApiKey ${this.#config.apiKey}`);
28
+ headers.set("Accept", "application/json");
29
+ if (options.idempotencyKey)
30
+ headers.set("Idempotency-Key", options.idempotencyKey);
31
+ if (options.headers) {
32
+ for (const [key, value] of Object.entries(options.headers))
33
+ headers.set(key, value);
34
+ }
35
+ let body;
36
+ if (options.form) {
37
+ // Do not set Content-Type — fetch derives the multipart boundary itself.
38
+ body = options.form;
39
+ }
40
+ else if (options.json !== undefined) {
41
+ headers.set("Content-Type", "application/json");
42
+ body = JSON.stringify(options.json);
43
+ }
44
+ const maxAttempts = this.#config.maxRetries + 1;
45
+ let lastNetworkError;
46
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
47
+ const timeoutSignal = AbortSignal.timeout(this.#config.timeoutMs);
48
+ const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
49
+ const isLastAttempt = attempt === maxAttempts - 1;
50
+ let response;
51
+ try {
52
+ response = await this.#config.fetchImpl(url, { method, headers, signal, ...(body !== undefined && { body }) });
53
+ }
54
+ catch (err) {
55
+ lastNetworkError = err;
56
+ if (!isLastAttempt) {
57
+ await sleep(backoffMs(attempt));
58
+ continue;
59
+ }
60
+ const isTimeout = err instanceof Error && err.name === "TimeoutError";
61
+ const message = `Request to ${path} failed: ${err instanceof Error ? err.message : String(err)}`;
62
+ throw isTimeout
63
+ ? new APITimeoutError(message, { cause: err })
64
+ : new APIConnectionError(message, { cause: err });
65
+ }
66
+ const requestId = response.headers.get("x-request-id") ?? undefined;
67
+ if (response.ok) {
68
+ const data = (await safeJson(response));
69
+ return { data, requestId, rateLimit: readRateLimit(response.headers) };
70
+ }
71
+ const retryAfterHeader = response.headers.get("retry-after");
72
+ const retryAfterSeconds = retryAfterHeader !== null ? Number(retryAfterHeader) : undefined;
73
+ const isRetryableStatus = response.status === 429 || response.status >= 500;
74
+ if (isRetryableStatus && !isLastAttempt) {
75
+ const delayMs = retryAfterSeconds !== undefined && !Number.isNaN(retryAfterSeconds)
76
+ ? retryAfterSeconds * 1000
77
+ : backoffMs(attempt);
78
+ await sleep(delayMs);
79
+ continue;
80
+ }
81
+ const errorBody = (await safeJson(response));
82
+ throw errorFromResponse(response.status, errorBody, {
83
+ requestId,
84
+ retryAfterSeconds: retryAfterSeconds !== undefined && !Number.isNaN(retryAfterSeconds)
85
+ ? retryAfterSeconds
86
+ : undefined,
87
+ instance: path,
88
+ });
89
+ }
90
+ // Every loop iteration either returns or throws; this satisfies the
91
+ // compiler's control-flow analysis without being reachable at runtime.
92
+ throw lastNetworkError instanceof Error
93
+ ? lastNetworkError
94
+ : new APIConnectionError(`Request to ${path} failed.`);
95
+ }
96
+ }
97
+ function sleep(ms) {
98
+ return new Promise((resolve) => setTimeout(resolve, ms));
99
+ }
100
+ function backoffMs(attempt) {
101
+ const capped = Math.min(300 * 2 ** attempt, 5_000);
102
+ return capped + Math.random() * 100;
103
+ }
104
+ async function safeJson(response) {
105
+ const text = await response.text();
106
+ if (!text)
107
+ return undefined;
108
+ try {
109
+ return JSON.parse(text);
110
+ }
111
+ catch {
112
+ return undefined;
113
+ }
114
+ }
115
+ function readRateLimit(headers) {
116
+ return {
117
+ limit: toNumber(headers.get("x-ratelimit-limit")),
118
+ remaining: toNumber(headers.get("x-ratelimit-remaining")),
119
+ resetSeconds: toNumber(headers.get("x-ratelimit-reset")),
120
+ };
121
+ }
122
+ function toNumber(value) {
123
+ if (value === null)
124
+ return undefined;
125
+ const n = Number(value);
126
+ return Number.isNaN(n) ? undefined : n;
127
+ }
128
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/internal/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,kBAAkB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAiDzG,MAAM,OAAO,UAAU;IACZ,OAAO,CAAiB;IAEjC,YAAY,MAAuB;QACjC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,OAAO,GAAG;YACb,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,0BAA0B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3E,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM;YACrC,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,CAAC;YAClC,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,EAAE;YAC3C,SAAS,EAAE,MAAM,CAAC,KAAK,IAAI,KAAK;SACjC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAI,MAAc,EAAE,IAAY,EAAE,UAA0B,EAAE;QACzE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACjD,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzD,IAAI,KAAK,KAAK,SAAS;oBAAE,SAAS;gBAClC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAC,cAAc;YAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;QACnF,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtF,CAAC;QAED,IAAI,IAAmC,CAAC;QACxC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,yEAAyE;YACzE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACtB,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;YAChD,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,GAAG,CAAC,CAAC;QAChD,IAAI,gBAAyB,CAAC;QAE9B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YACvD,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAClE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YACjG,MAAM,aAAa,GAAG,OAAO,KAAK,WAAW,GAAG,CAAC,CAAC;YAElD,IAAI,QAAkB,CAAC;YACvB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YACjH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,gBAAgB,GAAG,GAAG,CAAC;gBACvB,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;oBAChC,SAAS;gBACX,CAAC;gBACD,MAAM,SAAS,GAAG,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,CAAC;gBACtE,MAAM,OAAO,GAAG,cAAc,IAAI,YAAY,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjG,MAAM,SAAS;oBACb,CAAC,CAAC,IAAI,eAAe,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;oBAC9C,CAAC,CAAC,IAAI,kBAAkB,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAC;YAEpE,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAM,CAAC;gBAC7C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACzE,CAAC;YAED,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YAC7D,MAAM,iBAAiB,GAAG,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC3F,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC;YAE5E,IAAI,iBAAiB,IAAI,CAAC,aAAa,EAAE,CAAC;gBACxC,MAAM,OAAO,GACX,iBAAiB,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC;oBACjE,CAAC,CAAC,iBAAiB,GAAG,IAAI;oBAC1B,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;gBACzB,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;gBACrB,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAsC,CAAC;YAClF,MAAM,iBAAiB,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE;gBAClD,SAAS;gBACT,iBAAiB,EAAE,iBAAiB,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC;oBACpF,CAAC,CAAC,iBAAiB;oBACnB,CAAC,CAAC,SAAS;gBACb,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;QACL,CAAC;QAED,oEAAoE;QACpE,uEAAuE;QACvE,MAAM,gBAAgB,YAAY,KAAK;YACrC,CAAC,CAAC,gBAAgB;YAClB,CAAC,CAAC,IAAI,kBAAkB,CAAC,cAAc,IAAI,UAAU,CAAC,CAAC;IAC3D,CAAC;CACF;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,SAAS,CAAC,OAAe;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,OAAO,EAAE,KAAK,CAAC,CAAC;IACnD,OAAO,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,QAAkB;IACxC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,OAAgB;IACrC,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QACjD,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACzD,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAoB;IACpC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,69 @@
1
+ import type { HttpClient } from "../internal/http.js";
2
+ import { type BinaryInput } from "../internal/binary.js";
3
+ import type { AnalysisListResponse, AnalysisResponse, BatchJobAccepted, BatchJobResponse } from "../types.js";
4
+ export interface SubmitAnalysisParams {
5
+ /** Front-facing image. Required when using named angle fields (preferred over `images`). */
6
+ imageFront?: BinaryInput;
7
+ imageLeft?: BinaryInput;
8
+ imageRight?: BinaryInput;
9
+ imageChin?: BinaryInput;
10
+ /** Legacy: 1-5 same-person images, all treated as frontal. Prefer the named angle fields above. */
11
+ images?: BinaryInput[];
12
+ mode?: "standard" | "premium";
13
+ /** Your own reference data, echoed back on every response. Max 1 KB once serialized. */
14
+ metadata?: string | Record<string, unknown>;
15
+ /** Links this analysis to a patient created via `patients.upsert()`. */
16
+ patientId?: string;
17
+ /**
18
+ * Deduplicates retries of this exact submission within 24h. Auto-generated
19
+ * if omitted, so a network-error retry never double-charges your quota.
20
+ */
21
+ idempotencyKey?: string;
22
+ }
23
+ export interface ListAnalysesParams {
24
+ limit?: number;
25
+ cursor?: string;
26
+ status?: "queued" | "processing" | "completed" | "failed";
27
+ fromDate?: string;
28
+ toDate?: string;
29
+ patientId?: string;
30
+ }
31
+ export interface UpdateAnalysisMetadataParams {
32
+ customerRef?: string;
33
+ patientId?: string;
34
+ notes?: string;
35
+ }
36
+ export interface AnalysisMetadataResult {
37
+ id: string;
38
+ customer_ref: string | null;
39
+ patient_id: string | null;
40
+ notes: string | null;
41
+ }
42
+ export interface BatchImageItem {
43
+ /** Your own reference ID for this image, echoed back in results. */
44
+ imageId: string;
45
+ data: BinaryInput;
46
+ mimeType?: string;
47
+ metadata?: string | Record<string, unknown>;
48
+ }
49
+ export interface SubmitBatchAnalysisParams {
50
+ images: BatchImageItem[];
51
+ webhookUrl?: string;
52
+ notifyOnCompletion?: boolean;
53
+ idempotencyKey?: string;
54
+ }
55
+ export declare class AnalysisResource {
56
+ private readonly http;
57
+ readonly batch: {
58
+ submit: (params: SubmitBatchAnalysisParams) => Promise<BatchJobAccepted>;
59
+ get: (jobId: string) => Promise<BatchJobResponse>;
60
+ };
61
+ constructor(http: HttpClient);
62
+ submit(params: SubmitAnalysisParams): Promise<AnalysisResponse>;
63
+ get(id: string): Promise<AnalysisResponse>;
64
+ list(params?: ListAnalysesParams): Promise<AnalysisListResponse>;
65
+ updateMetadata(id: string, params: UpdateAnalysisMetadataParams): Promise<AnalysisMetadataResult>;
66
+ private submitBatch;
67
+ private getBatch;
68
+ }
69
+ //# sourceMappingURL=analysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../src/resources/analysis.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAA2B,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE9G,MAAM,WAAW,oBAAoB;IACnC,4FAA4F;IAC5F,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,mGAAmG;IACnG,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAC9B,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,gBAAgB;IAMf,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,QAAQ,CAAC,KAAK,EAAE;QACd,MAAM,EAAE,CAAC,MAAM,EAAE,yBAAyB,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACzE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;KACnD,CAAC;gBAE2B,IAAI,EAAE,UAAU;IAOvC,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2B/D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAK1C,IAAI,CAAC,MAAM,GAAE,kBAAuB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAcpE,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,sBAAsB,CAAC;YAezF,WAAW;YAsBX,QAAQ;CAOvB"}
@@ -0,0 +1,91 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { toBase64DataUrl, toBlob } from "../internal/binary.js";
3
+ export class AnalysisResource {
4
+ http;
5
+ batch;
6
+ constructor(http) {
7
+ this.http = http;
8
+ this.batch = {
9
+ submit: (params) => this.submitBatch(params),
10
+ get: (jobId) => this.getBatch(jobId),
11
+ };
12
+ }
13
+ async submit(params) {
14
+ const form = new FormData();
15
+ if (params.imageFront)
16
+ form.append("image_front", await toBlob(params.imageFront), "image_front.jpg");
17
+ if (params.imageLeft)
18
+ form.append("image_left", await toBlob(params.imageLeft), "image_left.jpg");
19
+ if (params.imageRight)
20
+ form.append("image_right", await toBlob(params.imageRight), "image_right.jpg");
21
+ if (params.imageChin)
22
+ form.append("image_chin", await toBlob(params.imageChin), "image_chin.jpg");
23
+ if (params.images) {
24
+ for (const image of params.images) {
25
+ form.append("images", await toBlob(image), "image.jpg");
26
+ }
27
+ }
28
+ if (params.mode)
29
+ form.append("mode", params.mode);
30
+ if (params.metadata !== undefined) {
31
+ form.append("metadata", typeof params.metadata === "string" ? params.metadata : JSON.stringify(params.metadata));
32
+ }
33
+ if (params.patientId)
34
+ form.append("patient_id", params.patientId);
35
+ const { data } = await this.http.request("POST", "/v1/analysis", {
36
+ form,
37
+ idempotencyKey: params.idempotencyKey ?? randomUUID(),
38
+ });
39
+ return data;
40
+ }
41
+ async get(id) {
42
+ const { data } = await this.http.request("GET", `/v1/analysis/${encodeURIComponent(id)}`);
43
+ return data;
44
+ }
45
+ async list(params = {}) {
46
+ const { data } = await this.http.request("GET", "/v1/analysis", {
47
+ query: {
48
+ limit: params.limit,
49
+ cursor: params.cursor,
50
+ status: params.status,
51
+ from_date: params.fromDate,
52
+ to_date: params.toDate,
53
+ patient_id: params.patientId,
54
+ },
55
+ });
56
+ return data;
57
+ }
58
+ async updateMetadata(id, params) {
59
+ const { data } = await this.http.request("PATCH", `/v1/analysis/${encodeURIComponent(id)}/metadata`, {
60
+ json: {
61
+ ...(params.customerRef !== undefined && { customer_ref: params.customerRef }),
62
+ ...(params.patientId !== undefined && { patient_id: params.patientId }),
63
+ ...(params.notes !== undefined && { notes: params.notes }),
64
+ },
65
+ });
66
+ return data;
67
+ }
68
+ async submitBatch(params) {
69
+ const images = await Promise.all(params.images.map(async (image) => ({
70
+ image_id: image.imageId,
71
+ data: await toBase64DataUrl(image.data, image.mimeType ?? "image/jpeg"),
72
+ ...(image.metadata !== undefined && {
73
+ metadata: typeof image.metadata === "string" ? image.metadata : JSON.stringify(image.metadata),
74
+ }),
75
+ })));
76
+ const { data } = await this.http.request("POST", "/v1/analysis/batch", {
77
+ json: {
78
+ images,
79
+ ...(params.webhookUrl !== undefined && { webhook_url: params.webhookUrl }),
80
+ ...(params.notifyOnCompletion !== undefined && { notify_on_completion: params.notifyOnCompletion }),
81
+ },
82
+ idempotencyKey: params.idempotencyKey ?? randomUUID(),
83
+ });
84
+ return data;
85
+ }
86
+ async getBatch(jobId) {
87
+ const { data } = await this.http.request("GET", `/v1/analysis/batch/${encodeURIComponent(jobId)}`);
88
+ return data;
89
+ }
90
+ }
91
+ //# sourceMappingURL=analysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis.js","sourceRoot":"","sources":["../../src/resources/analysis.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { HttpClient } from "../internal/http.js";
2
+ import type { PatientSummary } from "../types.js";
3
+ export interface UpsertPatientParams {
4
+ fullName: string;
5
+ gender?: "female" | "male";
6
+ /** E.164 format. When provided, upserts by `(organization_id, phone)`. */
7
+ phone?: string;
8
+ email?: string;
9
+ birthDate?: string;
10
+ }
11
+ export interface SearchPatientsParams {
12
+ q?: string;
13
+ limit?: number;
14
+ }
15
+ export interface ListPatientsParams {
16
+ search?: string;
17
+ cursor?: string;
18
+ limit?: number;
19
+ }
20
+ export interface PatientListResponse {
21
+ data: PatientSummary[];
22
+ pagination: {
23
+ has_more: boolean;
24
+ next_cursor: string | null;
25
+ };
26
+ }
27
+ export declare class PatientsResource {
28
+ private readonly http;
29
+ constructor(http: HttpClient);
30
+ /** Creates a patient, or upserts by `(organization_id, phone)` when `phone` is provided. */
31
+ upsert(params: UpsertPatientParams): Promise<PatientSummary>;
32
+ search(params?: SearchPatientsParams): Promise<PatientSummary[]>;
33
+ list(params?: ListPatientsParams): Promise<PatientListResponse>;
34
+ update(id: string, params: UpsertPatientParams): Promise<PatientSummary>;
35
+ }
36
+ //# sourceMappingURL=patients.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patients.d.ts","sourceRoot":"","sources":["../../src/resources/patients.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,UAAU,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC/D;AAED,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C,4FAA4F;IACtF,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;IAO5D,MAAM,CAAC,MAAM,GAAE,oBAAyB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAOpE,IAAI,CAAC,MAAM,GAAE,kBAAuB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAOnE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;CAM/E"}
@@ -0,0 +1,41 @@
1
+ export class PatientsResource {
2
+ http;
3
+ constructor(http) {
4
+ this.http = http;
5
+ }
6
+ /** Creates a patient, or upserts by `(organization_id, phone)` when `phone` is provided. */
7
+ async upsert(params) {
8
+ const { data } = await this.http.request("POST", "/v1/patients", {
9
+ json: toPatientBody(params),
10
+ });
11
+ return data;
12
+ }
13
+ async search(params = {}) {
14
+ const { data } = await this.http.request("GET", "/v1/patients", {
15
+ query: { q: params.q, limit: params.limit },
16
+ });
17
+ return data;
18
+ }
19
+ async list(params = {}) {
20
+ const { data } = await this.http.request("GET", "/v1/patients/list", {
21
+ query: { search: params.search, cursor: params.cursor, limit: params.limit },
22
+ });
23
+ return data;
24
+ }
25
+ async update(id, params) {
26
+ const { data } = await this.http.request("PATCH", `/v1/patients/${encodeURIComponent(id)}`, {
27
+ json: toPatientBody(params),
28
+ });
29
+ return data;
30
+ }
31
+ }
32
+ function toPatientBody(params) {
33
+ return {
34
+ full_name: params.fullName,
35
+ ...(params.gender !== undefined && { gender: params.gender }),
36
+ ...(params.phone !== undefined && { phone: params.phone }),
37
+ ...(params.email !== undefined && { email: params.email }),
38
+ ...(params.birthDate !== undefined && { birth_date: params.birthDate }),
39
+ };
40
+ }
41
+ //# sourceMappingURL=patients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patients.js","sourceRoot":"","sources":["../../src/resources/patients.ts"],"names":[],"mappings":"AA4BA,MAAM,OAAO,gBAAgB;IACE;IAA7B,YAA6B,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD,4FAA4F;IAC5F,KAAK,CAAC,MAAM,CAAC,MAA2B;QACtC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAiB,MAAM,EAAE,cAAc,EAAE;YAC/E,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC;SAC5B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAA+B,EAAE;QAC5C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAmB,KAAK,EAAE,cAAc,EAAE;YAChF,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;SAC5C,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAA6B,EAAE;QACxC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAsB,KAAK,EAAE,mBAAmB,EAAE;YACxF,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;SAC7E,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU,EAAE,MAA2B;QAClD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAiB,OAAO,EAAE,gBAAgB,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE;YAC1G,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC;SAC5B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,SAAS,aAAa,CAAC,MAA2B;IAChD,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,QAAQ;QAC1B,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;QAC7D,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QAC1D,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QAC1D,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;KACxE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Response shapes, hand-mirrored from docs/api/b2b-partner.openapi.yaml.
3
+ * Field names are kept snake_case to match the wire format exactly.
4
+ */
5
+ export type AnalysisStatus = "queued" | "processing" | "completed" | "failed";
6
+ export type AnalysisMode = "standard" | "premium";
7
+ export type ScanAngle = "front" | "left" | "right" | "chin" | "top" | "unknown";
8
+ export type Severity = "none" | "mild" | "moderate" | "severe";
9
+ export type Concern = "acne" | "breakouts" | "dark_circles" | "hydration" | "lines" | "oiliness" | "pigmentation" | "pore_visibility" | "redness" | "sagging" | "under_eye_puffiness" | "uniformness" | "wrinkles";
10
+ export interface SkinProfile {
11
+ skin_type: "oily" | "dry" | "combination" | "normal";
12
+ sensitivity: "low" | "moderate" | "high";
13
+ }
14
+ export interface ImageQuality {
15
+ score: number;
16
+ lighting_condition: string;
17
+ }
18
+ export interface IssueMask {
19
+ angle: ScanAngle;
20
+ data_url: string;
21
+ blend_mode: string;
22
+ provenance: string;
23
+ preview_photo_jpeg: string | null;
24
+ score: number | null;
25
+ severity: string | null;
26
+ confidence: number | null;
27
+ }
28
+ export interface ConcernResult {
29
+ concern: Concern;
30
+ severity: Severity;
31
+ severity_score: number;
32
+ confidence: number;
33
+ affected_regions: string[];
34
+ model_version: string;
35
+ notes: string | null;
36
+ region_scores: Record<string, number> | null;
37
+ accuracy_flags: string[];
38
+ spatial_provenance: "detected_features" | "computed_signal" | "anatomical_template" | "region_only" | null;
39
+ issue_masks: IssueMask[] | null;
40
+ markers: Array<[number, number]> | null;
41
+ marker_radii: number[] | null;
42
+ }
43
+ export interface QualityFlag {
44
+ code: string;
45
+ severity: "warning" | "error";
46
+ message: string;
47
+ }
48
+ export interface ScanImageSlot {
49
+ index: number;
50
+ angle: ScanAngle;
51
+ processed: boolean;
52
+ quality_score: number;
53
+ flag_codes: string[];
54
+ preview_photo_jpeg: string | null;
55
+ concern_scores: Record<string, {
56
+ score: number;
57
+ severity: string;
58
+ confidence: number;
59
+ }> | null;
60
+ concern_masks: Record<string, {
61
+ data_url: string;
62
+ blend_mode: string;
63
+ provenance: string;
64
+ }> | null;
65
+ }
66
+ export interface PatientSummary {
67
+ id: string;
68
+ full_name: string;
69
+ gender: "female" | "male";
70
+ phone: string | null;
71
+ email: string | null;
72
+ birth_date: string | null;
73
+ }
74
+ export interface AnalysisResponse {
75
+ id: string;
76
+ status: AnalysisStatus;
77
+ created_at: string;
78
+ completed_at: string | null;
79
+ estimated_seconds: number | null;
80
+ image_count: number | null;
81
+ face_analysis_provider: "dermacore" | "perfect_corp" | null;
82
+ highlight_style: "heatmap" | "none" | null;
83
+ skin_profile: SkinProfile | null;
84
+ image_quality: ImageQuality | null;
85
+ concerns: ConcernResult[] | null;
86
+ quality_flags: QualityFlag[] | null;
87
+ preview_photo_jpeg: string | null;
88
+ scan_images: ScanImageSlot[] | null;
89
+ error_code: string | null;
90
+ error_message: string | null;
91
+ model_pipeline_version: string | null;
92
+ metadata: string | null;
93
+ notes: string | null;
94
+ patient: PatientSummary | null;
95
+ disclaimer: string;
96
+ }
97
+ export interface AnalysisSummary {
98
+ id: string;
99
+ status: AnalysisStatus;
100
+ image_count: number | null;
101
+ skin_profile: SkinProfile | null;
102
+ concern_scores: Array<{
103
+ concern: string;
104
+ severity_score: number;
105
+ severity: string;
106
+ confidence: number;
107
+ }> | null;
108
+ created_at: string;
109
+ completed_at: string | null;
110
+ metadata: string | null;
111
+ }
112
+ export interface Page<T> {
113
+ data: T[];
114
+ pagination: {
115
+ has_more: boolean;
116
+ next_cursor: string | null;
117
+ total_count?: number | null;
118
+ };
119
+ }
120
+ export type AnalysisListResponse = Page<AnalysisSummary>;
121
+ export interface BatchJobAccepted {
122
+ job_id: string;
123
+ status: "queued";
124
+ image_count: number;
125
+ estimated_seconds: number;
126
+ quota_consumed: number;
127
+ quota_remaining: number;
128
+ created_at: string;
129
+ }
130
+ export interface BatchItemResult {
131
+ image_id: string;
132
+ status: "processing" | "completed" | "failed";
133
+ analysis_id: string | null;
134
+ error_code: string | null;
135
+ error_message: string | null;
136
+ }
137
+ export interface BatchJobResponse {
138
+ job_id: string;
139
+ status: "queued" | "processing" | "completed" | "failed" | "partial";
140
+ image_count: number;
141
+ completed_count: number;
142
+ failed_count: number;
143
+ created_at: string;
144
+ completed_at: string | null;
145
+ results: BatchItemResult[];
146
+ }
147
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Response shapes, hand-mirrored from docs/api/b2b-partner.openapi.yaml.
3
+ * Field names are kept snake_case to match the wire format exactly.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,30 @@
1
+ export interface WebhookEvent<T = Record<string, unknown>> {
2
+ event: string;
3
+ timestamp: string;
4
+ data: T;
5
+ }
6
+ /** The header name Derma Core sends the HMAC signature under. */
7
+ export declare const SIGNATURE_HEADER_NAME = "x-dermacore-signature";
8
+ export interface ConstructEventOptions {
9
+ /**
10
+ * Reject payloads whose `timestamp` is older than this many seconds, to
11
+ * guard against replay of a captured request. Default: `300` (5 minutes).
12
+ * Set to `0` to disable.
13
+ */
14
+ toleranceSeconds?: number;
15
+ }
16
+ /**
17
+ * Verifies the `X-DermaCore-Signature: sha256=<hex>` header against the raw
18
+ * request body and returns the parsed event. Always verify before trusting a
19
+ * webhook payload.
20
+ *
21
+ * `payload` must be the **raw, unparsed** request body — if your framework's
22
+ * body parser has already turned it into a JS object, re-serializing it will
23
+ * not reproduce the exact bytes that were signed and verification will fail.
24
+ */
25
+ export declare function constructEvent(payload: string | Buffer, signatureHeader: string | null | undefined, secret: string, options?: ConstructEventOptions): WebhookEvent;
26
+ export declare const webhooks: {
27
+ constructEvent: typeof constructEvent;
28
+ SIGNATURE_HEADER_NAME: string;
29
+ };
30
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,CAAC,CAAC;CACT;AAED,iEAAiE;AACjE,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAE7D,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC1C,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,qBAA0B,GAClC,YAAY,CAmCd;AAED,eAAO,MAAM,QAAQ;;;CAA4C,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ import { SignatureVerificationError } from "./errors.js";
3
+ /** The header name Derma Core sends the HMAC signature under. */
4
+ export const SIGNATURE_HEADER_NAME = "x-dermacore-signature";
5
+ /**
6
+ * Verifies the `X-DermaCore-Signature: sha256=<hex>` header against the raw
7
+ * request body and returns the parsed event. Always verify before trusting a
8
+ * webhook payload.
9
+ *
10
+ * `payload` must be the **raw, unparsed** request body — if your framework's
11
+ * body parser has already turned it into a JS object, re-serializing it will
12
+ * not reproduce the exact bytes that were signed and verification will fail.
13
+ */
14
+ export function constructEvent(payload, signatureHeader, secret, options = {}) {
15
+ if (!signatureHeader) {
16
+ throw new SignatureVerificationError("Missing webhook signature header.");
17
+ }
18
+ const raw = typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
19
+ const expected = `sha256=${createHmac("sha256", secret).update(raw).digest("hex")}`;
20
+ const providedBuf = Buffer.from(signatureHeader);
21
+ const expectedBuf = Buffer.from(expected);
22
+ const signaturesMatch = providedBuf.length === expectedBuf.length && timingSafeEqual(providedBuf, expectedBuf);
23
+ if (!signaturesMatch) {
24
+ throw new SignatureVerificationError("Webhook signature does not match the payload.");
25
+ }
26
+ let event;
27
+ try {
28
+ event = JSON.parse(raw.toString("utf8"));
29
+ }
30
+ catch {
31
+ throw new SignatureVerificationError("Webhook payload is not valid JSON.");
32
+ }
33
+ const toleranceSeconds = options.toleranceSeconds ?? 300;
34
+ if (toleranceSeconds > 0) {
35
+ const ageSeconds = (Date.now() - new Date(event.timestamp).getTime()) / 1000;
36
+ if (Number.isNaN(ageSeconds) || Math.abs(ageSeconds) > toleranceSeconds) {
37
+ throw new SignatureVerificationError(`Webhook timestamp is outside the ${toleranceSeconds}s tolerance window (possible replay).`);
38
+ }
39
+ }
40
+ return event;
41
+ }
42
+ export const webhooks = { constructEvent, SIGNATURE_HEADER_NAME };
43
+ //# sourceMappingURL=webhooks.js.map