@todoist/automations-sdk 0.0.0 → 1.0.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 +20 -33
- package/dist/authoring.d.ts +76 -0
- package/dist/authoring.js +162 -0
- package/dist/automations-api-CLcJvEjY.d.ts +6093 -0
- package/dist/base-client.d.ts +2 -0
- package/dist/base-client.js +1 -0
- package/dist/chat-turn.d.ts +314 -0
- package/dist/chat-turn.js +710 -0
- package/dist/connection-selection-BjNp9A-d.js +226 -0
- package/dist/connection-selection-C9yMkCvL.d.ts +29 -0
- package/dist/context-CxEkwuyE.js +109 -0
- package/dist/context-DsrXdqq8.d.ts +65 -0
- package/dist/index.d.ts +1060 -0
- package/dist/index.js +6030 -0
- package/dist/request-error-DoLXUa2p.js +34 -0
- package/dist/webhook-event-subscription.constants-CBmz0huQ.js +31 -0
- package/dist/webhooks.d.ts +52 -0
- package/dist/webhooks.js +83 -0
- package/package.json +83 -8
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/errors/request-error.ts
|
|
2
|
+
var AutomationsNetworkError = class extends Error {
|
|
3
|
+
constructor(message, cause) {
|
|
4
|
+
super(message, { cause });
|
|
5
|
+
this.name = "AutomationsNetworkError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var AutomationsRequestError = class extends Error {
|
|
9
|
+
httpStatusCode;
|
|
10
|
+
responseData;
|
|
11
|
+
authChallenge;
|
|
12
|
+
requiredScope;
|
|
13
|
+
requestId;
|
|
14
|
+
constructor(message, httpStatusCode, responseData, authChallenge, requestId) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "AutomationsRequestError";
|
|
17
|
+
this.httpStatusCode = httpStatusCode;
|
|
18
|
+
this.responseData = responseData;
|
|
19
|
+
this.authChallenge = authChallenge;
|
|
20
|
+
this.requiredScope = parseChallengeScope(authChallenge);
|
|
21
|
+
this.requestId = requestId;
|
|
22
|
+
}
|
|
23
|
+
isAuthError() {
|
|
24
|
+
return this.httpStatusCode === 401 || this.httpStatusCode === 403;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function parseChallengeScope(header) {
|
|
28
|
+
return header?.match(/scope="([^"]+)"/)?.[1];
|
|
29
|
+
}
|
|
30
|
+
function isInsufficientScopeChallenge(header) {
|
|
31
|
+
return header?.includes("error=\"insufficient_scope\"") ?? false;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { AutomationsRequestError as n, isInsufficientScopeChallenge as r, AutomationsNetworkError as t };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region ../core/dist/esm/constants/webhook-event-subscription.constants.js
|
|
2
|
+
const WEBHOOK_EVENT_TYPES = [
|
|
3
|
+
"automation:run:started",
|
|
4
|
+
"automation:run:finished",
|
|
5
|
+
"automation:created",
|
|
6
|
+
"automation:updated",
|
|
7
|
+
"automation:deleted",
|
|
8
|
+
"automation:enabled",
|
|
9
|
+
"automation:disabled",
|
|
10
|
+
"connection:broken"
|
|
11
|
+
];
|
|
12
|
+
const ACCOUNT_SCOPED_WEBHOOK_EVENT_TYPES = ["connection:broken"];
|
|
13
|
+
function getAccountScopedEventTypes(eventTypes) {
|
|
14
|
+
const accountScoped = new Set(ACCOUNT_SCOPED_WEBHOOK_EVENT_TYPES);
|
|
15
|
+
return eventTypes.filter((type) => accountScoped.has(type));
|
|
16
|
+
}
|
|
17
|
+
const WEBHOOK_EVENT_SUBSCRIPTION_STATUSES = ["active", "disabled"];
|
|
18
|
+
const WEBHOOK_EVENT_SUBSCRIPTION_DISABLED_REASONS = ["consecutive_failures"];
|
|
19
|
+
const WEBHOOK_EVENT_DELIVERY_STATUSES = [
|
|
20
|
+
"pending",
|
|
21
|
+
"succeeded",
|
|
22
|
+
"failed",
|
|
23
|
+
"exhausted",
|
|
24
|
+
"aborted"
|
|
25
|
+
];
|
|
26
|
+
const WEBHOOK_SECRET_PREFIX = "whsec_";
|
|
27
|
+
const WEBHOOK_ID_HEADER = "webhook-id";
|
|
28
|
+
const WEBHOOK_TIMESTAMP_HEADER = "webhook-timestamp";
|
|
29
|
+
const WEBHOOK_SIGNATURE_HEADER = "webhook-signature";
|
|
30
|
+
//#endregion
|
|
31
|
+
export { WEBHOOK_ID_HEADER as a, WEBHOOK_TIMESTAMP_HEADER as c, WEBHOOK_EVENT_TYPES as i, getAccountScopedEventTypes as l, WEBHOOK_EVENT_SUBSCRIPTION_DISABLED_REASONS as n, WEBHOOK_SECRET_PREFIX as o, WEBHOOK_EVENT_SUBSCRIPTION_STATUSES as r, WEBHOOK_SIGNATURE_HEADER as s, WEBHOOK_EVENT_DELIVERY_STATUSES as t };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region ../core/dist/types/webhooks/signing.d.ts
|
|
2
|
+
type WebhookVerificationResult = {
|
|
3
|
+
valid: true;
|
|
4
|
+
} | {
|
|
5
|
+
valid: false;
|
|
6
|
+
reason: 'timestamp_invalid' | 'timestamp_expired' | 'signature_mismatch';
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Verify an inbound delivery the way a receiver should: reject stale timestamps,
|
|
10
|
+
* recompute the expected signature, and constant-time compare against every
|
|
11
|
+
* entry in the header (any match passes — supports multi-sig rotation).
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyWebhook(params: {
|
|
14
|
+
/**
|
|
15
|
+
* The signing secret, or several to support dual-key overlap during secret
|
|
16
|
+
* rotation — the delivery validates if it matches any of them.
|
|
17
|
+
*/
|
|
18
|
+
secret: string | readonly string[];
|
|
19
|
+
webhookId: string;
|
|
20
|
+
timestamp: number | string;
|
|
21
|
+
signatureHeader: string;
|
|
22
|
+
rawBody: string;
|
|
23
|
+
/** Unix seconds; defaults to now. Injectable for deterministic tests. */
|
|
24
|
+
nowSeconds?: number;
|
|
25
|
+
toleranceSeconds?: number;
|
|
26
|
+
}): WebhookVerificationResult;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/webhooks/verify.d.ts
|
|
29
|
+
export type HeaderSource = Headers | Record<string, string | string[] | undefined>;
|
|
30
|
+
export type VerifyWebhookRequestParams = {
|
|
31
|
+
/**
|
|
32
|
+
* The subscription's signing secret (`whsec_…`), or several to support
|
|
33
|
+
* dual-key overlap during secret rotation — any match passes.
|
|
34
|
+
*/
|
|
35
|
+
secret: string | readonly string[];
|
|
36
|
+
/** Inbound request headers — a `Headers` instance or a plain header map. */
|
|
37
|
+
headers: HeaderSource;
|
|
38
|
+
/** The exact raw request body bytes, as received (do not re-serialize). */
|
|
39
|
+
rawBody: string;
|
|
40
|
+
/** Unix seconds; defaults to now. Injectable for deterministic tests. */
|
|
41
|
+
nowSeconds?: number;
|
|
42
|
+
toleranceSeconds?: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Verify an inbound Standard Webhooks delivery from request headers + raw body.
|
|
46
|
+
* Pulls the `webhook-id` / `webhook-timestamp` / `webhook-signature` headers and
|
|
47
|
+
* delegates to the shared signing helper. A missing required header is reported
|
|
48
|
+
* as a signature mismatch.
|
|
49
|
+
*/
|
|
50
|
+
export declare function verifyWebhookRequest(params: VerifyWebhookRequestParams): WebhookVerificationResult;
|
|
51
|
+
//#endregion
|
|
52
|
+
export type { WebhookVerificationResult };
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { a as WEBHOOK_ID_HEADER, c as WEBHOOK_TIMESTAMP_HEADER, o as WEBHOOK_SECRET_PREFIX, s as WEBHOOK_SIGNATURE_HEADER } from "./webhook-event-subscription.constants-CBmz0huQ.js";
|
|
2
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
|
+
//#region ../core/dist/esm/webhooks/signing.js
|
|
4
|
+
const SIGNATURE_VERSION = "v1";
|
|
5
|
+
function decodeWebhookSecret(secret) {
|
|
6
|
+
const base64 = secret.startsWith("whsec_") ? secret.slice(WEBHOOK_SECRET_PREFIX.length) : secret;
|
|
7
|
+
return Buffer.from(base64, "base64");
|
|
8
|
+
}
|
|
9
|
+
function computeWebhookSignature(params) {
|
|
10
|
+
const { secret, webhookId, timestamp, rawBody } = params;
|
|
11
|
+
return createHmac("sha256", decodeWebhookSecret(secret)).update(`${webhookId}.${timestamp}.`, "utf8").update(rawBody, "utf8").digest("base64");
|
|
12
|
+
}
|
|
13
|
+
function constantTimeEquals(a, b) {
|
|
14
|
+
const aBuf = Buffer.from(a, "utf8");
|
|
15
|
+
const bBuf = Buffer.from(b, "utf8");
|
|
16
|
+
if (aBuf.length !== bBuf.length) return false;
|
|
17
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
18
|
+
}
|
|
19
|
+
function verifyWebhook(params) {
|
|
20
|
+
const { secret, webhookId, timestamp, signatureHeader, rawBody, nowSeconds = Math.floor(Date.now() / 1e3), toleranceSeconds = 300 } = params;
|
|
21
|
+
const timestampSeconds = typeof timestamp === "string" ? Number(timestamp) : timestamp;
|
|
22
|
+
if (!Number.isFinite(timestampSeconds)) return {
|
|
23
|
+
valid: false,
|
|
24
|
+
reason: "timestamp_invalid"
|
|
25
|
+
};
|
|
26
|
+
if (Math.abs(nowSeconds - timestampSeconds) > toleranceSeconds) return {
|
|
27
|
+
valid: false,
|
|
28
|
+
reason: "timestamp_expired"
|
|
29
|
+
};
|
|
30
|
+
const secrets = typeof secret === "string" ? [secret] : secret;
|
|
31
|
+
let expectedSignatures;
|
|
32
|
+
return signatureHeader.split(" ").map((entry) => entry.split(",", 2)).filter(([version]) => version === SIGNATURE_VERSION).some(([, candidate]) => {
|
|
33
|
+
if (!candidate) return false;
|
|
34
|
+
expectedSignatures ??= secrets.map((candidateSecret) => computeWebhookSignature({
|
|
35
|
+
secret: candidateSecret,
|
|
36
|
+
webhookId,
|
|
37
|
+
timestamp,
|
|
38
|
+
rawBody
|
|
39
|
+
}));
|
|
40
|
+
return expectedSignatures.some((expected) => constantTimeEquals(candidate, expected));
|
|
41
|
+
}) ? { valid: true } : {
|
|
42
|
+
valid: false,
|
|
43
|
+
reason: "signature_mismatch"
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/webhooks/verify.ts
|
|
48
|
+
function readWebhookHeaders(headers) {
|
|
49
|
+
if (headers instanceof Headers) return {
|
|
50
|
+
webhookId: headers.get(WEBHOOK_ID_HEADER),
|
|
51
|
+
timestamp: headers.get(WEBHOOK_TIMESTAMP_HEADER),
|
|
52
|
+
signatureHeader: headers.get(WEBHOOK_SIGNATURE_HEADER)
|
|
53
|
+
};
|
|
54
|
+
const normalized = {};
|
|
55
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
56
|
+
if (value === void 0) continue;
|
|
57
|
+
normalized[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
webhookId: normalized["webhook-id"] ?? null,
|
|
61
|
+
timestamp: normalized["webhook-timestamp"] ?? null,
|
|
62
|
+
signatureHeader: normalized["webhook-signature"] ?? null
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function verifyWebhookRequest(params) {
|
|
66
|
+
const { secret, headers, rawBody, nowSeconds, toleranceSeconds } = params;
|
|
67
|
+
const { webhookId, timestamp, signatureHeader } = readWebhookHeaders(headers);
|
|
68
|
+
if (!webhookId || !timestamp || !signatureHeader) return {
|
|
69
|
+
valid: false,
|
|
70
|
+
reason: "signature_mismatch"
|
|
71
|
+
};
|
|
72
|
+
return verifyWebhook({
|
|
73
|
+
secret,
|
|
74
|
+
webhookId,
|
|
75
|
+
timestamp,
|
|
76
|
+
signatureHeader,
|
|
77
|
+
rawBody,
|
|
78
|
+
nowSeconds,
|
|
79
|
+
toleranceSeconds
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { verifyWebhook, verifyWebhookRequest };
|
package/package.json
CHANGED
|
@@ -1,10 +1,85 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
"name": "@todoist/automations-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TypeScript SDK for the Todoist Automations API",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/todoist/automations.git",
|
|
9
|
+
"directory": "packages/sdk"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/todoist/automations/tree/main/packages/sdk#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/todoist/automations/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./base-client": {
|
|
27
|
+
"types": "./dist/base-client.d.ts",
|
|
28
|
+
"default": "./dist/base-client.js"
|
|
29
|
+
},
|
|
30
|
+
"./webhooks": {
|
|
31
|
+
"types": "./dist/webhooks.d.ts",
|
|
32
|
+
"default": "./dist/webhooks.js"
|
|
33
|
+
},
|
|
34
|
+
"./chat-turn": {
|
|
35
|
+
"types": "./dist/chat-turn.d.ts",
|
|
36
|
+
"default": "./dist/chat-turn.js"
|
|
37
|
+
},
|
|
38
|
+
"./authoring": {
|
|
39
|
+
"types": "./dist/authoring.d.ts",
|
|
40
|
+
"default": "./dist/authoring.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
46
|
+
"sideEffects": false,
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"prepack": "npm run build",
|
|
50
|
+
"check:publish": "publint . && attw --pack . --profile esm-only && node scripts/check-no-admin.mjs",
|
|
51
|
+
"release": "semantic-release",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"lint": "oxlint src",
|
|
54
|
+
"lint:fix": "oxlint src --fix",
|
|
55
|
+
"format": "oxfmt src --check",
|
|
56
|
+
"format:fix": "oxfmt src",
|
|
57
|
+
"check": "oxfmt src --check && oxlint src",
|
|
58
|
+
"check:fix": "oxfmt src && oxlint src --fix",
|
|
59
|
+
"type-check": "tsc -p tsconfig.typecheck.json --noEmit"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@types/json-schema": "7.0.15",
|
|
63
|
+
"ai": "7.0.163",
|
|
64
|
+
"json-stable-stringify": "1.3.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"zod": "^4.6.0"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
71
|
+
"@todoist/automations-core": "*",
|
|
72
|
+
"@todoist/automations-sdk-test-utils": "*",
|
|
73
|
+
"@todoist/tsconfig": "2.0.2",
|
|
74
|
+
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
75
|
+
"msw": "3.0.3",
|
|
76
|
+
"publint": "0.3.25",
|
|
77
|
+
"semantic-release": "25.0.9",
|
|
78
|
+
"tsdown": "0.23.1",
|
|
79
|
+
"typescript": "6.0.3",
|
|
80
|
+
"zod": "4.6.7"
|
|
81
|
+
},
|
|
82
|
+
"engines": {
|
|
83
|
+
"node": ">=24"
|
|
84
|
+
}
|
|
10
85
|
}
|