@minipim/sdk 0.1.0 → 0.2.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/LICENSE +15 -0
- package/README.md +58 -17
- package/dist/attributes.d.ts +47 -0
- package/dist/attributes.d.ts.map +1 -0
- package/dist/attributes.js +76 -0
- package/dist/attributes.js.map +1 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +30 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +7 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/paginate.d.ts +34 -0
- package/dist/paginate.d.ts.map +1 -0
- package/dist/paginate.js +43 -0
- package/dist/paginate.js.map +1 -0
- package/dist/webhook-edge.d.ts +24 -0
- package/dist/webhook-edge.d.ts.map +1 -0
- package/dist/webhook-edge.js +32 -0
- package/dist/webhook-edge.js.map +1 -0
- package/dist/webhook-shared.d.ts +28 -0
- package/dist/webhook-shared.d.ts.map +1 -0
- package/dist/webhook-shared.js +30 -0
- package/dist/webhook-shared.js.map +1 -0
- package/dist/webhook.d.ts +9 -50
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +19 -73
- package/dist/webhook.js.map +1 -1
- package/package.json +15 -7
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic pagination helper. Every MiniPim list endpoint returns
|
|
3
|
+
* `{ data, limit, offset, hasMore }`; this walks the pages so consumers
|
|
4
|
+
* don't hand-roll the `hasMore` loop. Yields one item at a time.
|
|
5
|
+
*
|
|
6
|
+
* Typed loosely on purpose — openapi-fetch's per-path generics don't
|
|
7
|
+
* compose into a single reusable signature without a lot of conditional-
|
|
8
|
+
* type machinery, and the envelope shape is uniform across list endpoints.
|
|
9
|
+
* The item type is yours to specify via the generic.
|
|
10
|
+
*/
|
|
11
|
+
import type { MinipimClient } from './client.js';
|
|
12
|
+
export interface PaginateOptions {
|
|
13
|
+
/** Query params (filters, sort, withTotal, etc). `limit`/`offset` are managed for you. */
|
|
14
|
+
query?: Record<string, unknown>;
|
|
15
|
+
/** Page size. Default 200 (the API max). */
|
|
16
|
+
pageSize?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Async-iterate every item across all pages of a MiniPim list endpoint.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { paginate } from '@minipim/sdk';
|
|
24
|
+
* for await (const product of paginate<Product>(pim, '/v1/products', {
|
|
25
|
+
* query: { status: 'active' },
|
|
26
|
+
* })) {
|
|
27
|
+
* await ingest(product);
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function paginate<T = unknown>(client: MinipimClient, path: string, opts?: PaginateOptions): AsyncGenerator<T, void, unknown>;
|
|
32
|
+
/** Collect every page into an array. Convenience around {@link paginate}. */
|
|
33
|
+
export declare function collectAll<T = unknown>(client: MinipimClient, path: string, opts?: PaginateOptions): Promise<T[]>;
|
|
34
|
+
//# sourceMappingURL=paginate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paginate.d.ts","sourceRoot":"","sources":["../src/paginate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAUjD,MAAM,WAAW,eAAe;IAC9B,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAuB,QAAQ,CAAC,CAAC,GAAG,OAAO,EACzC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,eAAoB,GACzB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAwBlC;AAED,6EAA6E;AAC7E,wBAAsB,UAAU,CAAC,CAAC,GAAG,OAAO,EAC1C,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,CAAC,EAAE,CAAC,CAId"}
|
package/dist/paginate.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async-iterate every item across all pages of a MiniPim list endpoint.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { paginate } from '@minipim/sdk';
|
|
7
|
+
* for await (const product of paginate<Product>(pim, '/v1/products', {
|
|
8
|
+
* query: { status: 'active' },
|
|
9
|
+
* })) {
|
|
10
|
+
* await ingest(product);
|
|
11
|
+
* }
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export async function* paginate(client, path, opts = {}) {
|
|
15
|
+
const pageSize = opts.pageSize ?? 200;
|
|
16
|
+
let offset = 0;
|
|
17
|
+
for (;;) {
|
|
18
|
+
// openapi-fetch is typed per-path; this helper is path-generic, so we
|
|
19
|
+
// call through an untyped view. Callers get their item type via <T>.
|
|
20
|
+
const get = client.GET;
|
|
21
|
+
const { data, error, response } = await get(path, {
|
|
22
|
+
params: { query: { ...(opts.query ?? {}), limit: pageSize, offset } },
|
|
23
|
+
});
|
|
24
|
+
if (error || !data) {
|
|
25
|
+
throw new Error(`paginate ${path} failed at offset ${offset} (HTTP ${response?.status})`);
|
|
26
|
+
}
|
|
27
|
+
for (const item of data.data)
|
|
28
|
+
yield item;
|
|
29
|
+
// Prefer the server's hasMore; fall back to a short-page check.
|
|
30
|
+
const more = data.hasMore ?? data.data.length === pageSize;
|
|
31
|
+
if (!more || data.data.length === 0)
|
|
32
|
+
return;
|
|
33
|
+
offset += pageSize;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Collect every page into an array. Convenience around {@link paginate}. */
|
|
37
|
+
export async function collectAll(client, path, opts = {}) {
|
|
38
|
+
const out = [];
|
|
39
|
+
for await (const item of paginate(client, path, opts))
|
|
40
|
+
out.push(item);
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=paginate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paginate.js","sourceRoot":"","sources":["../src/paginate.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,QAAQ,CAC7B,MAAqB,EACrB,IAAY,EACZ,OAAwB,EAAE;IAE1B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC;IACtC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,SAAS,CAAC;QACR,sEAAsE;QACtE,qEAAqE;QACrE,MAAM,GAAG,GAAG,MAAM,CAAC,GAG0D,CAAC;QAC9E,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE;YAChD,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;SACtE,CAAC,CAAC;QACH,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,YAAY,IAAI,qBAAqB,MAAM,UAAU,QAAQ,EAAE,MAAM,GAAG,CACzE,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,CAAC;QACzC,gEAAgE;QAChE,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC;QAC3D,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC5C,MAAM,IAAI,QAAQ,CAAC;IACrB,CAAC;AACH,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAqB,EACrB,IAAY,EACZ,OAAwB,EAAE;IAE1B,MAAM,GAAG,GAAQ,EAAE,CAAC;IACpB,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,QAAQ,CAAI,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge / browser webhook signature verification, via WebCrypto. Use in
|
|
3
|
+
* Cloudflare Workers, Vercel Edge Runtime, Deno, or browsers. Async because
|
|
4
|
+
* `crypto.subtle` is async. For Node use `@minipim/sdk/webhook` (synchronous).
|
|
5
|
+
*
|
|
6
|
+
* This entry point imports no `node:` modules, so it's safe in any Edge
|
|
7
|
+
* bundle.
|
|
8
|
+
*/
|
|
9
|
+
import { type VerifyWebhookOptions } from './webhook-shared.js';
|
|
10
|
+
export type { VerifyWebhookOptions } from './webhook-shared.js';
|
|
11
|
+
/**
|
|
12
|
+
* Verify a MiniPim webhook signature in an Edge/WebCrypto runtime. Same
|
|
13
|
+
* contract as the Node `verifyMinipimWebhook` but async.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { verifyMinipimWebhookEdge } from '@minipim/sdk/webhook-edge';
|
|
18
|
+
*
|
|
19
|
+
* const ok = await verifyMinipimWebhookEdge({ rawBody, signature, secret });
|
|
20
|
+
* if (!ok) return new Response('invalid signature', { status: 401 });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyMinipimWebhookEdge(opts: VerifyWebhookOptions): Promise<boolean>;
|
|
24
|
+
//# sourceMappingURL=webhook-edge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-edge.d.ts","sourceRoot":"","sources":["../src/webhook-edge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,OAAO,CAAC,CAgBlB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge / browser webhook signature verification, via WebCrypto. Use in
|
|
3
|
+
* Cloudflare Workers, Vercel Edge Runtime, Deno, or browsers. Async because
|
|
4
|
+
* `crypto.subtle` is async. For Node use `@minipim/sdk/webhook` (synchronous).
|
|
5
|
+
*
|
|
6
|
+
* This entry point imports no `node:` modules, so it's safe in any Edge
|
|
7
|
+
* bundle.
|
|
8
|
+
*/
|
|
9
|
+
import { decodeHex, stripPrefix, timingSafeEqualBytes, } from './webhook-shared.js';
|
|
10
|
+
/**
|
|
11
|
+
* Verify a MiniPim webhook signature in an Edge/WebCrypto runtime. Same
|
|
12
|
+
* contract as the Node `verifyMinipimWebhook` but async.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { verifyMinipimWebhookEdge } from '@minipim/sdk/webhook-edge';
|
|
17
|
+
*
|
|
18
|
+
* const ok = await verifyMinipimWebhookEdge({ rawBody, signature, secret });
|
|
19
|
+
* if (!ok) return new Response('invalid signature', { status: 401 });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export async function verifyMinipimWebhookEdge(opts) {
|
|
23
|
+
const enc = new TextEncoder();
|
|
24
|
+
const keyData = enc.encode(opts.secret);
|
|
25
|
+
const bodyData = typeof opts.rawBody === 'string' ? enc.encode(opts.rawBody) : new Uint8Array(opts.rawBody);
|
|
26
|
+
const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
27
|
+
const sigBuf = await crypto.subtle.sign('HMAC', key, bodyData);
|
|
28
|
+
const expected = new Uint8Array(sigBuf);
|
|
29
|
+
const got = decodeHex(stripPrefix(opts.signature));
|
|
30
|
+
return timingSafeEqualBytes(expected, got);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=webhook-edge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-edge.js","sourceRoot":"","sources":["../src/webhook-edge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,SAAS,EACT,WAAW,EACX,oBAAoB,GAErB,MAAM,qBAAqB,CAAC;AAI7B;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,IAA0B;IAE1B,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,QAAQ,GACZ,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7F,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACvC,KAAK,EACL,OAAO,EACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACnD,OAAO,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers shared by the Node and Edge webhook verifiers. Deliberately
|
|
3
|
+
* free of `node:crypto` and WebCrypto so importing this file drags neither
|
|
4
|
+
* runtime's crypto into a bundle.
|
|
5
|
+
*/
|
|
6
|
+
export interface VerifyWebhookOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The **raw** request body — bytes or a string. **Not** the parsed JSON.
|
|
9
|
+
* Re-stringifying the parsed body reorders keys / changes whitespace and
|
|
10
|
+
* the HMAC will not match.
|
|
11
|
+
*
|
|
12
|
+
* Next.js route handler: `const raw = await req.text()`.
|
|
13
|
+
* Express: `express.raw({ type: 'application/json' })`, then `req.body`.
|
|
14
|
+
*/
|
|
15
|
+
rawBody: string | Uint8Array;
|
|
16
|
+
/**
|
|
17
|
+
* Value of the `X-MiniPim-Signature-256` header — `sha256=<hex>`. Pass it
|
|
18
|
+
* verbatim; the verifier strips the prefix.
|
|
19
|
+
*/
|
|
20
|
+
signature: string;
|
|
21
|
+
/** The secret configured on the headless connector. */
|
|
22
|
+
secret: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function decodeHex(s: string): Uint8Array;
|
|
25
|
+
export declare function stripPrefix(sig: string): string;
|
|
26
|
+
/** Constant-time byte compare — used by the Edge verifier (Node uses crypto.timingSafeEqual). */
|
|
27
|
+
export declare function timingSafeEqualBytes(a: Uint8Array, b: Uint8Array): boolean;
|
|
28
|
+
//# sourceMappingURL=webhook-shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-shared.d.ts","sourceRoot":"","sources":["../src/webhook-shared.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAS/C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,iGAAiG;AACjG,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAK1E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers shared by the Node and Edge webhook verifiers. Deliberately
|
|
3
|
+
* free of `node:crypto` and WebCrypto so importing this file drags neither
|
|
4
|
+
* runtime's crypto into a bundle.
|
|
5
|
+
*/
|
|
6
|
+
export function decodeHex(s) {
|
|
7
|
+
if (s.length % 2 !== 0)
|
|
8
|
+
return new Uint8Array(0);
|
|
9
|
+
const out = new Uint8Array(s.length / 2);
|
|
10
|
+
for (let i = 0; i < out.length; i++) {
|
|
11
|
+
const byte = parseInt(s.slice(i * 2, i * 2 + 2), 16);
|
|
12
|
+
if (Number.isNaN(byte))
|
|
13
|
+
return new Uint8Array(0);
|
|
14
|
+
out[i] = byte;
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
export function stripPrefix(sig) {
|
|
19
|
+
return sig.startsWith('sha256=') ? sig.slice(7) : sig;
|
|
20
|
+
}
|
|
21
|
+
/** Constant-time byte compare — used by the Edge verifier (Node uses crypto.timingSafeEqual). */
|
|
22
|
+
export function timingSafeEqualBytes(a, b) {
|
|
23
|
+
if (a.length !== b.length)
|
|
24
|
+
return false;
|
|
25
|
+
let diff = 0;
|
|
26
|
+
for (let i = 0; i < a.length; i++)
|
|
27
|
+
diff |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
28
|
+
return diff === 0;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=webhook-shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-shared.js","sourceRoot":"","sources":["../src/webhook-shared.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrD,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;QACjD,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACxD,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,oBAAoB,CAAC,CAAa,EAAE,CAAa;IAC/D,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrE,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC"}
|
package/dist/webhook.d.ts
CHANGED
|
@@ -1,44 +1,14 @@
|
|
|
1
|
+
import { type VerifyWebhookOptions } from './webhook-shared.js';
|
|
2
|
+
export type { VerifyWebhookOptions } from './webhook-shared.js';
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `X-MiniPim-Signature-256` header containing `sha256=<hex>` — the HMAC of
|
|
6
|
-
* the raw request body keyed by the connector's secret. Verifying it
|
|
7
|
-
* correctly is the #1 thing webhook consumers get wrong, so the SDK ships a
|
|
8
|
-
* reference implementation.
|
|
9
|
-
*
|
|
10
|
-
* Two variants exported:
|
|
11
|
-
* - `verifyMinipimWebhook` — sync, Node `crypto`. Use in any Node
|
|
12
|
-
* server (Express, Fastify, Next App Router
|
|
13
|
-
* route handlers).
|
|
14
|
-
* - `verifyMinipimWebhookEdge` — async, WebCrypto subtle. For Cloudflare
|
|
15
|
-
* Workers, Vercel Edge, Deno, browsers.
|
|
16
|
-
*/
|
|
17
|
-
export interface VerifyWebhookOptions {
|
|
18
|
-
/**
|
|
19
|
-
* The **raw** request body — bytes or a string. **Not** the parsed JSON.
|
|
20
|
-
* If you re-stringify the parsed body, whitespace and key ordering will
|
|
21
|
-
* differ from the original, and the HMAC will not match.
|
|
22
|
-
*
|
|
23
|
-
* In Next.js route handlers: `const rawBody = await req.text()`.
|
|
24
|
-
* In Express: register `express.raw({ type: 'application/json' })` and
|
|
25
|
-
* read `req.body` (it'll be a Buffer).
|
|
26
|
-
*/
|
|
27
|
-
rawBody: string | Uint8Array;
|
|
28
|
-
/**
|
|
29
|
-
* Value of the `X-MiniPim-Signature-256` header. Format:
|
|
30
|
-
* `sha256=<hex>` — pass it verbatim. The function strips the prefix.
|
|
31
|
-
*/
|
|
32
|
-
signature: string;
|
|
33
|
-
/** The secret you configured on the headless connector. */
|
|
34
|
-
secret: string;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Verify a MiniPim webhook signature in a Node environment.
|
|
4
|
+
* Verify a MiniPim webhook signature in Node. Returns `true` only when the
|
|
5
|
+
* signature is valid; returns `false` on any problem (wrong signature,
|
|
6
|
+
* malformed header, length mismatch). Constant-time comparison.
|
|
38
7
|
*
|
|
39
8
|
* @example
|
|
40
9
|
* ```ts
|
|
41
|
-
*
|
|
10
|
+
* import { verifyMinipimWebhook } from '@minipim/sdk/webhook';
|
|
11
|
+
*
|
|
42
12
|
* export async function POST(req: Request) {
|
|
43
13
|
* const raw = await req.text();
|
|
44
14
|
* const sig = req.headers.get('x-minipim-signature-256') ?? '';
|
|
@@ -46,21 +16,10 @@ export interface VerifyWebhookOptions {
|
|
|
46
16
|
* return new Response('invalid signature', { status: 401 });
|
|
47
17
|
* }
|
|
48
18
|
* const event = JSON.parse(raw);
|
|
49
|
-
* // ...
|
|
19
|
+
* // ...handle event, respond 2xx
|
|
50
20
|
* return Response.json({ ok: true });
|
|
51
21
|
* }
|
|
52
22
|
* ```
|
|
53
|
-
*
|
|
54
|
-
* Returns `false` on any verification problem (wrong signature, malformed
|
|
55
|
-
* header, length mismatch). Constant-time comparison — safe against timing
|
|
56
|
-
* attacks. Throws only if `node:crypto` is unavailable; if you're on Edge,
|
|
57
|
-
* use `verifyMinipimWebhookEdge` instead.
|
|
58
|
-
*/
|
|
59
|
-
export declare function verifyMinipimWebhook(opts: VerifyWebhookOptions): Promise<boolean>;
|
|
60
|
-
/**
|
|
61
|
-
* Edge / browser variant using WebCrypto. Same shape as
|
|
62
|
-
* {@link verifyMinipimWebhook}; use this in Cloudflare Workers, Vercel
|
|
63
|
-
* Edge Runtime, Deno, or browsers.
|
|
64
23
|
*/
|
|
65
|
-
export declare function
|
|
24
|
+
export declare function verifyMinipimWebhook(opts: VerifyWebhookOptions): boolean;
|
|
66
25
|
//# sourceMappingURL=webhook.d.ts.map
|
package/dist/webhook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAKxE"}
|
package/dist/webhook.js
CHANGED
|
@@ -1,48 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Two variants exported:
|
|
11
|
-
* - `verifyMinipimWebhook` — sync, Node `crypto`. Use in any Node
|
|
12
|
-
* server (Express, Fastify, Next App Router
|
|
13
|
-
* route handlers).
|
|
14
|
-
* - `verifyMinipimWebhookEdge` — async, WebCrypto subtle. For Cloudflare
|
|
15
|
-
* Workers, Vercel Edge, Deno, browsers.
|
|
2
|
+
* Node webhook signature verification. **Synchronous** — Node's HMAC is
|
|
3
|
+
* synchronous, so there's no reason to return a Promise (and a Promise is
|
|
4
|
+
* always truthy, which made the obvious `if (!verify(...))` guard silently
|
|
5
|
+
* accept every request). For Edge/WebCrypto runtimes use
|
|
6
|
+
* `@minipim/sdk/webhook-edge` instead — this module imports `node:crypto`
|
|
7
|
+
* at the top, which Edge bundlers reject, so it's kept on its own entry
|
|
8
|
+
* point and never pulled into the client or the Edge verifier.
|
|
16
9
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return new Uint8Array(0);
|
|
20
|
-
const out = new Uint8Array(s.length / 2);
|
|
21
|
-
for (let i = 0; i < out.length; i++) {
|
|
22
|
-
const byte = parseInt(s.slice(i * 2, i * 2 + 2), 16);
|
|
23
|
-
if (Number.isNaN(byte))
|
|
24
|
-
return new Uint8Array(0);
|
|
25
|
-
out[i] = byte;
|
|
26
|
-
}
|
|
27
|
-
return out;
|
|
28
|
-
}
|
|
29
|
-
function stripPrefix(sig) {
|
|
30
|
-
return sig.startsWith('sha256=') ? sig.slice(7) : sig;
|
|
31
|
-
}
|
|
32
|
-
function timingSafeEqualBytes(a, b) {
|
|
33
|
-
if (a.length !== b.length)
|
|
34
|
-
return false;
|
|
35
|
-
let diff = 0;
|
|
36
|
-
for (let i = 0; i < a.length; i++)
|
|
37
|
-
diff |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
38
|
-
return diff === 0;
|
|
39
|
-
}
|
|
10
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
11
|
+
import { decodeHex, stripPrefix, } from './webhook-shared.js';
|
|
40
12
|
/**
|
|
41
|
-
* Verify a MiniPim webhook signature in
|
|
13
|
+
* Verify a MiniPim webhook signature in Node. Returns `true` only when the
|
|
14
|
+
* signature is valid; returns `false` on any problem (wrong signature,
|
|
15
|
+
* malformed header, length mismatch). Constant-time comparison.
|
|
42
16
|
*
|
|
43
17
|
* @example
|
|
44
18
|
* ```ts
|
|
45
|
-
*
|
|
19
|
+
* import { verifyMinipimWebhook } from '@minipim/sdk/webhook';
|
|
20
|
+
*
|
|
46
21
|
* export async function POST(req: Request) {
|
|
47
22
|
* const raw = await req.text();
|
|
48
23
|
* const sig = req.headers.get('x-minipim-signature-256') ?? '';
|
|
@@ -50,45 +25,16 @@ function timingSafeEqualBytes(a, b) {
|
|
|
50
25
|
* return new Response('invalid signature', { status: 401 });
|
|
51
26
|
* }
|
|
52
27
|
* const event = JSON.parse(raw);
|
|
53
|
-
* // ...
|
|
28
|
+
* // ...handle event, respond 2xx
|
|
54
29
|
* return Response.json({ ok: true });
|
|
55
30
|
* }
|
|
56
31
|
* ```
|
|
57
|
-
*
|
|
58
|
-
* Returns `false` on any verification problem (wrong signature, malformed
|
|
59
|
-
* header, length mismatch). Constant-time comparison — safe against timing
|
|
60
|
-
* attacks. Throws only if `node:crypto` is unavailable; if you're on Edge,
|
|
61
|
-
* use `verifyMinipimWebhookEdge` instead.
|
|
62
32
|
*/
|
|
63
|
-
export
|
|
64
|
-
// Lazy-import so the runtime exception is only thrown when actually
|
|
65
|
-
// calling this on Edge. The async signature matches the Edge variant
|
|
66
|
-
// so consumers don't have to branch.
|
|
67
|
-
const { createHmac } = await import('node:crypto');
|
|
33
|
+
export function verifyMinipimWebhook(opts) {
|
|
68
34
|
const expected = createHmac('sha256', opts.secret).update(opts.rawBody).digest();
|
|
69
35
|
const got = decodeHex(stripPrefix(opts.signature));
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* Edge / browser variant using WebCrypto. Same shape as
|
|
74
|
-
* {@link verifyMinipimWebhook}; use this in Cloudflare Workers, Vercel
|
|
75
|
-
* Edge Runtime, Deno, or browsers.
|
|
76
|
-
*/
|
|
77
|
-
export async function verifyMinipimWebhookEdge(opts) {
|
|
78
|
-
const enc = new TextEncoder();
|
|
79
|
-
// Normalize both inputs to fresh Uint8Arrays — `new Uint8Array(view)`
|
|
80
|
-
// creates a new ArrayBuffer-backed copy, which is what WebCrypto's
|
|
81
|
-
// `subtle.sign` requires under TS 5.7's tightened typings. Without the
|
|
82
|
-
// re-wrap, a Buffer passed in from Node code can be `Uint8Array<ArrayBufferLike>`
|
|
83
|
-
// and trips the type checker (runtime is fine either way).
|
|
84
|
-
const keyData = enc.encode(opts.secret);
|
|
85
|
-
const bodyData = typeof opts.rawBody === 'string'
|
|
86
|
-
? enc.encode(opts.rawBody)
|
|
87
|
-
: new Uint8Array(opts.rawBody);
|
|
88
|
-
const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
89
|
-
const sigBuf = await crypto.subtle.sign('HMAC', key, bodyData);
|
|
90
|
-
const expected = new Uint8Array(sigBuf);
|
|
91
|
-
const got = decodeHex(stripPrefix(opts.signature));
|
|
92
|
-
return timingSafeEqualBytes(expected, got);
|
|
36
|
+
if (got.length !== expected.length)
|
|
37
|
+
return false;
|
|
38
|
+
return timingSafeEqual(expected, got);
|
|
93
39
|
}
|
|
94
40
|
//# sourceMappingURL=webhook.js.map
|
package/dist/webhook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EACL,SAAS,EACT,WAAW,GAEZ,MAAM,qBAAqB,CAAC;AAI7B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAA0B;IAC7D,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;IACjF,MAAM,GAAG,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IACnD,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACjD,OAAO,eAAe,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AACxC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minipim/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Typed TypeScript client for the MiniPim API.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/Epic-Design-Labs/minipim",
|
|
@@ -22,6 +22,14 @@
|
|
|
22
22
|
"import": "./dist/webhook.js",
|
|
23
23
|
"types": "./dist/webhook.d.ts"
|
|
24
24
|
},
|
|
25
|
+
"./webhook-edge": {
|
|
26
|
+
"import": "./dist/webhook-edge.js",
|
|
27
|
+
"types": "./dist/webhook-edge.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./attributes": {
|
|
30
|
+
"import": "./dist/attributes.js",
|
|
31
|
+
"types": "./dist/attributes.d.ts"
|
|
32
|
+
},
|
|
25
33
|
"./openapi": {
|
|
26
34
|
"import": "./dist/openapi.js",
|
|
27
35
|
"types": "./dist/openapi.d.ts"
|
|
@@ -40,11 +48,6 @@
|
|
|
40
48
|
"openapi",
|
|
41
49
|
"typescript"
|
|
42
50
|
],
|
|
43
|
-
"scripts": {
|
|
44
|
-
"build": "tsc -b",
|
|
45
|
-
"typecheck": "tsc --noEmit",
|
|
46
|
-
"gen": "openapi-typescript https://api.minipim.com/docs/json -o src/openapi.ts"
|
|
47
|
-
},
|
|
48
51
|
"dependencies": {
|
|
49
52
|
"openapi-fetch": "^0.13.0"
|
|
50
53
|
},
|
|
@@ -54,5 +57,10 @@
|
|
|
54
57
|
},
|
|
55
58
|
"publishConfig": {
|
|
56
59
|
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsc -b",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"gen": "openapi-typescript https://api.minipim.com/docs/json -o src/openapi.ts"
|
|
57
65
|
}
|
|
58
|
-
}
|
|
66
|
+
}
|