@limitry/sdk 0.0.0-stage → 1.0.0-rc.1
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 +202 -0
- package/NOTICE +2 -0
- package/README.md +25 -2
- package/dist/brand.d.ts +6 -0
- package/dist/brand.js +7 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.js +18 -0
- package/dist/index.d.ts +483 -0
- package/dist/index.js +73 -0
- package/dist/product/index.d.ts +24 -0
- package/dist/product/index.js +7 -0
- package/dist/product/limits.d.ts +110 -0
- package/dist/product/limits.js +234 -0
- package/dist/resources.d.ts +71 -0
- package/dist/resources.gen.d.ts +400 -0
- package/dist/resources.gen.js +83 -0
- package/dist/resources.js +40 -0
- package/dist/retry.d.ts +42 -0
- package/dist/retry.js +116 -0
- package/dist/v1.gen.d.ts +4207 -0
- package/dist/v1.gen.js +1 -0
- package/dist/webhooks.d.ts +39 -0
- package/dist/webhooks.js +88 -0
- package/package.json +36 -4
package/dist/v1.gen.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifying a webhook delivery (decided 2026-10-11): `client.webhooks.verify`
|
|
3
|
+
* checks that a request came from us and is recent, then gives you the
|
|
4
|
+
* event. Deliveries are signed Svix-compatibly (packages/jobs/src/webhooks.ts):
|
|
5
|
+
* HMAC-SHA256 over `<webhook-id>.<webhook-timestamp>.<body>` with the
|
|
6
|
+
* endpoint's secret (`whsec_<base64 key>`), sent as `webhook-signature:
|
|
7
|
+
* v1,<base64>` (several, space-separated, while a secret rotates). No
|
|
8
|
+
* dependency: Web Crypto, so it runs in Node, Workers, Deno and browsers.
|
|
9
|
+
*/
|
|
10
|
+
/** The event a delivery carries. */
|
|
11
|
+
export type WebhookEvent = {
|
|
12
|
+
/** The event's id (`evt_…`), the same in every delivery of it: dedupe on it. */
|
|
13
|
+
id: string;
|
|
14
|
+
/** What happened, such as `member.joined`. */
|
|
15
|
+
type: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
data: Record<string, unknown>;
|
|
18
|
+
[field: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
/** A delivery that failed verification: not from us, altered, or too old. */
|
|
21
|
+
export declare class WebhookVerificationError extends Error {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
export type VerifyOptions = {
|
|
25
|
+
/** How old a delivery may be, in seconds (default 300). Refuses replays. */
|
|
26
|
+
toleranceSeconds?: number;
|
|
27
|
+
/** The time to check against (tests); default now. */
|
|
28
|
+
now?: Date;
|
|
29
|
+
};
|
|
30
|
+
type HeaderSource = Headers | Record<string, string | string[] | undefined> | Iterable<[string, string]>;
|
|
31
|
+
/**
|
|
32
|
+
* Checks a delivery and returns its event. `body` is the request body
|
|
33
|
+
* exactly as received (the raw text, before any JSON parsing); `headers`
|
|
34
|
+
* the request's headers; `secret` the endpoint's signing secret. Throws
|
|
35
|
+
* WebhookVerificationError when the signature doesn't match or the
|
|
36
|
+
* delivery is older than `toleranceSeconds`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifyWebhook(body: string | Uint8Array, headers: HeaderSource, secret: string, options?: VerifyOptions): Promise<WebhookEvent>;
|
|
39
|
+
export {};
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifying a webhook delivery (decided 2026-10-11): `client.webhooks.verify`
|
|
3
|
+
* checks that a request came from us and is recent, then gives you the
|
|
4
|
+
* event. Deliveries are signed Svix-compatibly (packages/jobs/src/webhooks.ts):
|
|
5
|
+
* HMAC-SHA256 over `<webhook-id>.<webhook-timestamp>.<body>` with the
|
|
6
|
+
* endpoint's secret (`whsec_<base64 key>`), sent as `webhook-signature:
|
|
7
|
+
* v1,<base64>` (several, space-separated, while a secret rotates). No
|
|
8
|
+
* dependency: Web Crypto, so it runs in Node, Workers, Deno and browsers.
|
|
9
|
+
*/
|
|
10
|
+
/** A delivery that failed verification: not from us, altered, or too old. */
|
|
11
|
+
export class WebhookVerificationError extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "WebhookVerificationError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function header(headers, name) {
|
|
18
|
+
if (typeof headers.get === "function") {
|
|
19
|
+
return headers.get(name) ?? undefined;
|
|
20
|
+
}
|
|
21
|
+
if (Symbol.iterator in headers && !Array.isArray(headers)) {
|
|
22
|
+
for (const [key, value] of headers) {
|
|
23
|
+
if (key.toLowerCase() === name)
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
29
|
+
if (key.toLowerCase() === name) {
|
|
30
|
+
return Array.isArray(value) ? value[0] : value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const fromBase64 = (text) => Uint8Array.from(atob(text), (c) => c.charCodeAt(0));
|
|
36
|
+
/** Equal in constant time (for equal lengths). */
|
|
37
|
+
function same(a, b) {
|
|
38
|
+
if (a.length !== b.length)
|
|
39
|
+
return false;
|
|
40
|
+
let diff = 0;
|
|
41
|
+
for (let i = 0; i < a.length; i++)
|
|
42
|
+
diff |= a[i] ^ b[i];
|
|
43
|
+
return diff === 0;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Checks a delivery and returns its event. `body` is the request body
|
|
47
|
+
* exactly as received (the raw text, before any JSON parsing); `headers`
|
|
48
|
+
* the request's headers; `secret` the endpoint's signing secret. Throws
|
|
49
|
+
* WebhookVerificationError when the signature doesn't match or the
|
|
50
|
+
* delivery is older than `toleranceSeconds`.
|
|
51
|
+
*/
|
|
52
|
+
export async function verifyWebhook(body, headers, secret, options = {}) {
|
|
53
|
+
const id = header(headers, "webhook-id");
|
|
54
|
+
const timestamp = header(headers, "webhook-timestamp");
|
|
55
|
+
const signatures = header(headers, "webhook-signature");
|
|
56
|
+
if (!id || !timestamp || !signatures) {
|
|
57
|
+
throw new WebhookVerificationError("Missing webhook-id, webhook-timestamp or webhook-signature header");
|
|
58
|
+
}
|
|
59
|
+
const sentAt = Number(timestamp);
|
|
60
|
+
const now = Math.floor((options.now ?? new Date()).getTime() / 1000);
|
|
61
|
+
if (!Number.isInteger(sentAt) ||
|
|
62
|
+
Math.abs(now - sentAt) > (options.toleranceSeconds ?? 300)) {
|
|
63
|
+
throw new WebhookVerificationError("The delivery is too old (or its timestamp is invalid)");
|
|
64
|
+
}
|
|
65
|
+
const text = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
66
|
+
const key = await crypto.subtle.importKey("raw", fromBase64(secret.startsWith("whsec_") ? secret.slice(6) : secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
67
|
+
const expected = new Uint8Array(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${id}.${timestamp}.${text}`)));
|
|
68
|
+
const matches = signatures.split(" ").some((part) => {
|
|
69
|
+
const [version, value] = part.split(",", 2);
|
|
70
|
+
if (version !== "v1" || !value)
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
return same(fromBase64(value), expected);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (!matches) {
|
|
80
|
+
throw new WebhookVerificationError("The signature doesn't match");
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(text);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new WebhookVerificationError("The body isn't JSON");
|
|
87
|
+
}
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@limitry/sdk",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
|
+
"description": "Limitry API client",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"NOTICE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.build.json",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"openapi-fetch": "0.17.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "26.6.5",
|
|
26
|
+
"typescript": "6.0.3",
|
|
27
|
+
"vitest": "4.1.11"
|
|
28
|
+
},
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"homepage": "https://limitry.com/docs/sdk/",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/limitry/limitry-typescript.git"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22"
|
|
37
|
+
}
|
|
38
|
+
}
|