@open-everywhere/provider-notion 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.
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +48 -0
- package/agent/channels/notion-acting-identity.ts +88 -0
- package/agent/channels/notion-ancestry-trust.ts +458 -0
- package/agent/channels/notion-api.ts +1290 -0
- package/agent/channels/notion-bot-user.ts +250 -0
- package/agent/channels/notion-catch-up.ts +339 -0
- package/agent/channels/notion-credentials.ts +417 -0
- package/agent/channels/notion-engagement.ts +372 -0
- package/agent/channels/notion-event-normalizer.ts +587 -0
- package/agent/channels/notion-identity.ts +271 -0
- package/agent/channels/notion-ingress-drain.ts +289 -0
- package/agent/channels/notion-ingress.ts +182 -0
- package/agent/channels/notion-message-ops.ts +140 -0
- package/agent/channels/notion-origin-codec.ts +38 -0
- package/agent/channels/notion-post-arm.ts +189 -0
- package/agent/channels/notion-question-return.ts +223 -0
- package/agent/channels/notion-read.ts +283 -0
- package/agent/channels/notion-scope.ts +560 -0
- package/agent/channels/notion-stop.ts +169 -0
- package/agent/channels/notion-supervisor.ts +856 -0
- package/agent/channels/notion-webhook-timing.ts +131 -0
- package/agent/channels/notion-webhook-verifier.ts +55 -0
- package/agent/channels/notion-webhook.ts +963 -0
- package/agent/channels/notion-work-search.ts +74 -0
- package/agent/channels/notion-work.ts +144 -0
- package/agent/channels/notion.ts +1242 -0
- package/agent/contributions.ts +39 -0
- package/agent/docs-generated.ts +15 -0
- package/agent/memory/blocks.ts +295 -0
- package/agent/memory/memory.ts +622 -0
- package/agent/provider-events.ts +198 -0
- package/agent/register.ts +144 -0
- package/agent/schedules/notion-ingress.ts +20 -0
- package/agent/surface-parity.ts +105 -0
- package/agent/tools/notion_search.ts +110 -0
- package/agent/tools/read_notion_comments.ts +250 -0
- package/agent/tools/read_notion_page.ts +351 -0
- package/dist/agent/channels/notion-acting-identity.d.ts +23 -0
- package/dist/agent/channels/notion-acting-identity.d.ts.map +1 -0
- package/dist/agent/channels/notion-acting-identity.js +85 -0
- package/dist/agent/channels/notion-acting-identity.js.map +1 -0
- package/dist/agent/channels/notion-ancestry-trust.d.ts +208 -0
- package/dist/agent/channels/notion-ancestry-trust.d.ts.map +1 -0
- package/dist/agent/channels/notion-ancestry-trust.js +331 -0
- package/dist/agent/channels/notion-ancestry-trust.js.map +1 -0
- package/dist/agent/channels/notion-api.d.ts +655 -0
- package/dist/agent/channels/notion-api.d.ts.map +1 -0
- package/dist/agent/channels/notion-api.js +938 -0
- package/dist/agent/channels/notion-api.js.map +1 -0
- package/dist/agent/channels/notion-bot-user.d.ts +107 -0
- package/dist/agent/channels/notion-bot-user.d.ts.map +1 -0
- package/dist/agent/channels/notion-bot-user.js +192 -0
- package/dist/agent/channels/notion-bot-user.js.map +1 -0
- package/dist/agent/channels/notion-catch-up.d.ts +64 -0
- package/dist/agent/channels/notion-catch-up.d.ts.map +1 -0
- package/dist/agent/channels/notion-catch-up.js +289 -0
- package/dist/agent/channels/notion-catch-up.js.map +1 -0
- package/dist/agent/channels/notion-credentials.d.ts +210 -0
- package/dist/agent/channels/notion-credentials.d.ts.map +1 -0
- package/dist/agent/channels/notion-credentials.js +360 -0
- package/dist/agent/channels/notion-credentials.js.map +1 -0
- package/dist/agent/channels/notion-engagement.d.ts +219 -0
- package/dist/agent/channels/notion-engagement.d.ts.map +1 -0
- package/dist/agent/channels/notion-engagement.js +283 -0
- package/dist/agent/channels/notion-engagement.js.map +1 -0
- package/dist/agent/channels/notion-event-normalizer.d.ts +194 -0
- package/dist/agent/channels/notion-event-normalizer.d.ts.map +1 -0
- package/dist/agent/channels/notion-event-normalizer.js +542 -0
- package/dist/agent/channels/notion-event-normalizer.js.map +1 -0
- package/dist/agent/channels/notion-identity.d.ts +119 -0
- package/dist/agent/channels/notion-identity.d.ts.map +1 -0
- package/dist/agent/channels/notion-identity.js +205 -0
- package/dist/agent/channels/notion-identity.js.map +1 -0
- package/dist/agent/channels/notion-ingress-drain.d.ts +66 -0
- package/dist/agent/channels/notion-ingress-drain.d.ts.map +1 -0
- package/dist/agent/channels/notion-ingress-drain.js +185 -0
- package/dist/agent/channels/notion-ingress-drain.js.map +1 -0
- package/dist/agent/channels/notion-ingress.d.ts +64 -0
- package/dist/agent/channels/notion-ingress.d.ts.map +1 -0
- package/dist/agent/channels/notion-ingress.js +130 -0
- package/dist/agent/channels/notion-ingress.js.map +1 -0
- package/dist/agent/channels/notion-message-ops.d.ts +3 -0
- package/dist/agent/channels/notion-message-ops.d.ts.map +1 -0
- package/dist/agent/channels/notion-message-ops.js +126 -0
- package/dist/agent/channels/notion-message-ops.js.map +1 -0
- package/dist/agent/channels/notion-origin-codec.d.ts +7 -0
- package/dist/agent/channels/notion-origin-codec.d.ts.map +1 -0
- package/dist/agent/channels/notion-origin-codec.js +32 -0
- package/dist/agent/channels/notion-origin-codec.js.map +1 -0
- package/dist/agent/channels/notion-post-arm.d.ts +3 -0
- package/dist/agent/channels/notion-post-arm.d.ts.map +1 -0
- package/dist/agent/channels/notion-post-arm.js +185 -0
- package/dist/agent/channels/notion-post-arm.js.map +1 -0
- package/dist/agent/channels/notion-question-return.d.ts +77 -0
- package/dist/agent/channels/notion-question-return.d.ts.map +1 -0
- package/dist/agent/channels/notion-question-return.js +183 -0
- package/dist/agent/channels/notion-question-return.js.map +1 -0
- package/dist/agent/channels/notion-read.d.ts +62 -0
- package/dist/agent/channels/notion-read.d.ts.map +1 -0
- package/dist/agent/channels/notion-read.js +257 -0
- package/dist/agent/channels/notion-read.js.map +1 -0
- package/dist/agent/channels/notion-scope.d.ts +188 -0
- package/dist/agent/channels/notion-scope.d.ts.map +1 -0
- package/dist/agent/channels/notion-scope.js +433 -0
- package/dist/agent/channels/notion-scope.js.map +1 -0
- package/dist/agent/channels/notion-stop.d.ts +48 -0
- package/dist/agent/channels/notion-stop.d.ts.map +1 -0
- package/dist/agent/channels/notion-stop.js +141 -0
- package/dist/agent/channels/notion-stop.js.map +1 -0
- package/dist/agent/channels/notion-supervisor.d.ts +462 -0
- package/dist/agent/channels/notion-supervisor.d.ts.map +1 -0
- package/dist/agent/channels/notion-supervisor.js +482 -0
- package/dist/agent/channels/notion-supervisor.js.map +1 -0
- package/dist/agent/channels/notion-webhook-timing.d.ts +35 -0
- package/dist/agent/channels/notion-webhook-timing.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook-timing.js +99 -0
- package/dist/agent/channels/notion-webhook-timing.js.map +1 -0
- package/dist/agent/channels/notion-webhook-verifier.d.ts +5 -0
- package/dist/agent/channels/notion-webhook-verifier.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook-verifier.js +53 -0
- package/dist/agent/channels/notion-webhook-verifier.js.map +1 -0
- package/dist/agent/channels/notion-webhook.d.ts +242 -0
- package/dist/agent/channels/notion-webhook.d.ts.map +1 -0
- package/dist/agent/channels/notion-webhook.js +675 -0
- package/dist/agent/channels/notion-webhook.js.map +1 -0
- package/dist/agent/channels/notion-work-search.d.ts +9 -0
- package/dist/agent/channels/notion-work-search.d.ts.map +1 -0
- package/dist/agent/channels/notion-work-search.js +69 -0
- package/dist/agent/channels/notion-work-search.js.map +1 -0
- package/dist/agent/channels/notion-work.d.ts +68 -0
- package/dist/agent/channels/notion-work.d.ts.map +1 -0
- package/dist/agent/channels/notion-work.js +126 -0
- package/dist/agent/channels/notion-work.js.map +1 -0
- package/dist/agent/channels/notion.d.ts +95 -0
- package/dist/agent/channels/notion.d.ts.map +1 -0
- package/dist/agent/channels/notion.js +1081 -0
- package/dist/agent/channels/notion.js.map +1 -0
- package/dist/agent/contributions.d.ts +32 -0
- package/dist/agent/contributions.d.ts.map +1 -0
- package/dist/agent/contributions.js +37 -0
- package/dist/agent/contributions.js.map +1 -0
- package/dist/agent/docs-generated.d.ts +3 -0
- package/dist/agent/docs-generated.d.ts.map +1 -0
- package/dist/agent/docs-generated.js +13 -0
- package/dist/agent/docs-generated.js.map +1 -0
- package/dist/agent/memory/blocks.d.ts +58 -0
- package/dist/agent/memory/blocks.d.ts.map +1 -0
- package/dist/agent/memory/blocks.js +264 -0
- package/dist/agent/memory/blocks.js.map +1 -0
- package/dist/agent/memory/memory.d.ts +157 -0
- package/dist/agent/memory/memory.d.ts.map +1 -0
- package/dist/agent/memory/memory.js +471 -0
- package/dist/agent/memory/memory.js.map +1 -0
- package/dist/agent/provider-events.d.ts +3 -0
- package/dist/agent/provider-events.d.ts.map +1 -0
- package/dist/agent/provider-events.js +196 -0
- package/dist/agent/provider-events.js.map +1 -0
- package/dist/agent/register.d.ts +5 -0
- package/dist/agent/register.d.ts.map +1 -0
- package/dist/agent/register.js +128 -0
- package/dist/agent/register.js.map +1 -0
- package/dist/agent/schedules/notion-ingress.d.ts +6 -0
- package/dist/agent/schedules/notion-ingress.d.ts.map +1 -0
- package/dist/agent/schedules/notion-ingress.js +16 -0
- package/dist/agent/schedules/notion-ingress.js.map +1 -0
- package/dist/agent/surface-parity.d.ts +3 -0
- package/dist/agent/surface-parity.d.ts.map +1 -0
- package/dist/agent/surface-parity.js +99 -0
- package/dist/agent/surface-parity.js.map +1 -0
- package/dist/agent/tools/notion_search.d.ts +56 -0
- package/dist/agent/tools/notion_search.d.ts.map +1 -0
- package/dist/agent/tools/notion_search.js +100 -0
- package/dist/agent/tools/notion_search.js.map +1 -0
- package/dist/agent/tools/read_notion_comments.d.ts +57 -0
- package/dist/agent/tools/read_notion_comments.d.ts.map +1 -0
- package/dist/agent/tools/read_notion_comments.js +236 -0
- package/dist/agent/tools/read_notion_comments.js.map +1 -0
- package/dist/agent/tools/read_notion_page.d.ts +53 -0
- package/dist/agent/tools/read_notion_page.d.ts.map +1 -0
- package/dist/agent/tools/read_notion_page.js +327 -0
- package/dist/agent/tools/read_notion_page.js.map +1 -0
- package/package.json +231 -0
- package/tsconfig.build.json +22 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// notion-webhook-timing โ WHERE A NOTION WEBHOOK ACK'S SECONDS WENT (OPE-1929).
|
|
2
|
+
//
|
|
3
|
+
// Notion's comment route deliberately acknowledges only after the wake has been durably accepted.
|
|
4
|
+
// That makes every REST/store/queue wait on the path part of the webhook's observed latency. Keep
|
|
5
|
+
// this collector pure and injectable: production records one content-free sample, while a fake
|
|
6
|
+
// clock can prove the attribution without sleeping or calling a service.
|
|
7
|
+
|
|
8
|
+
export const NOTION_WEBHOOK_TIMING_ACTION = "notion.webhook_timing";
|
|
9
|
+
|
|
10
|
+
export type NotionWebhookTimingPhase =
|
|
11
|
+
| "credential"
|
|
12
|
+
| "botTriggerPage"
|
|
13
|
+
| "ancestorSiblingTrust"
|
|
14
|
+
| "engagementDedupe"
|
|
15
|
+
/** Inclusive dispatch span. The public detail subtracts the nested Eve and final-audit spans. */
|
|
16
|
+
| "dispatch"
|
|
17
|
+
| "eveSend"
|
|
18
|
+
| "finalAudit";
|
|
19
|
+
|
|
20
|
+
export interface NotionWebhookTimingDetail {
|
|
21
|
+
readonly [key: string]: unknown;
|
|
22
|
+
readonly outcome: string;
|
|
23
|
+
readonly failedPhase?: NotionWebhookTimingPhase;
|
|
24
|
+
readonly hmacCredentialMs?: number;
|
|
25
|
+
readonly botTriggerPageMs?: number;
|
|
26
|
+
readonly ancestorSiblingTrustMs?: number;
|
|
27
|
+
readonly engagementStoreDedupeMs?: number;
|
|
28
|
+
readonly dispatchPreflightCatchUpMs?: number;
|
|
29
|
+
readonly eveSendMs?: number;
|
|
30
|
+
readonly finalAuditMs?: number;
|
|
31
|
+
readonly totalMs: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface NotionWebhookTimingRecorder {
|
|
35
|
+
readonly measure: <T>(phase: NotionWebhookTimingPhase, operation: () => Promise<T>) => Promise<T>;
|
|
36
|
+
readonly measureSync: <T>(phase: NotionWebhookTimingPhase, operation: () => T) => T;
|
|
37
|
+
readonly detail: (outcome: string) => NotionWebhookTimingDetail;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Run one whole route request and emit exactly one final sample. The caller decides when a normal
|
|
42
|
+
* outcome is final; any throw before that point becomes `threw` and is rethrown unchanged. Emission
|
|
43
|
+
* itself is telemetry and therefore fail-soft โ it can never replace the HTTP result or error.
|
|
44
|
+
*/
|
|
45
|
+
export async function runNotionWebhookTimed<T>(
|
|
46
|
+
timing: NotionWebhookTimingRecorder,
|
|
47
|
+
emit: (detail: NotionWebhookTimingDetail) => void,
|
|
48
|
+
operation: (finish: (outcome: string) => void) => Promise<T>,
|
|
49
|
+
): Promise<T> {
|
|
50
|
+
let emitted = false;
|
|
51
|
+
const finish = (outcome: string): void => {
|
|
52
|
+
if (emitted) return;
|
|
53
|
+
emitted = true;
|
|
54
|
+
try {
|
|
55
|
+
emit(timing.detail(outcome));
|
|
56
|
+
} catch {
|
|
57
|
+
// Timing telemetry never changes the route's own result.
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
return await operation(finish);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
finish("threw");
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function duration(start: number, end: number): number {
|
|
69
|
+
const elapsed = end - start;
|
|
70
|
+
return Number.isFinite(elapsed) && elapsed > 0 ? elapsed : 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const millis = (elapsed: number): number => Math.round(elapsed);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create one request-scoped recorder. Phases accumulate because some operations in a phase are
|
|
77
|
+
* separated by gates. `dispatch` deliberately contains `eveSend` and `finalAudit`; detail() removes
|
|
78
|
+
* those nested spans so the published buckets are mutually exclusive rather than double-counted.
|
|
79
|
+
*/
|
|
80
|
+
export function createNotionWebhookTiming(now: () => number = () => performance.now()): NotionWebhookTimingRecorder {
|
|
81
|
+
const startedAt = now();
|
|
82
|
+
const elapsed = new Map<NotionWebhookTimingPhase, number>();
|
|
83
|
+
let failedPhase: NotionWebhookTimingPhase | undefined;
|
|
84
|
+
const add = (phase: NotionWebhookTimingPhase, start: number): void => {
|
|
85
|
+
elapsed.set(phase, (elapsed.get(phase) ?? 0) + duration(start, now()));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
async measure<T>(phase: NotionWebhookTimingPhase, operation: () => Promise<T>): Promise<T> {
|
|
90
|
+
const start = now();
|
|
91
|
+
try {
|
|
92
|
+
return await operation();
|
|
93
|
+
} catch (err) {
|
|
94
|
+
failedPhase ??= phase;
|
|
95
|
+
throw err;
|
|
96
|
+
} finally {
|
|
97
|
+
add(phase, start);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
measureSync<T>(phase: NotionWebhookTimingPhase, operation: () => T): T {
|
|
101
|
+
const start = now();
|
|
102
|
+
try {
|
|
103
|
+
return operation();
|
|
104
|
+
} catch (err) {
|
|
105
|
+
failedPhase ??= phase;
|
|
106
|
+
throw err;
|
|
107
|
+
} finally {
|
|
108
|
+
add(phase, start);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
detail(outcome: string): NotionWebhookTimingDetail {
|
|
112
|
+
const eveSendMs = elapsed.get("eveSend");
|
|
113
|
+
const finalAuditMs = elapsed.get("finalAudit");
|
|
114
|
+
const dispatchMs = elapsed.get("dispatch");
|
|
115
|
+
return {
|
|
116
|
+
outcome,
|
|
117
|
+
...(failedPhase ? { failedPhase } : {}),
|
|
118
|
+
...(elapsed.has("credential") ? { hmacCredentialMs: millis(elapsed.get("credential")!) } : {}),
|
|
119
|
+
...(elapsed.has("botTriggerPage") ? { botTriggerPageMs: millis(elapsed.get("botTriggerPage")!) } : {}),
|
|
120
|
+
...(elapsed.has("ancestorSiblingTrust") ? { ancestorSiblingTrustMs: millis(elapsed.get("ancestorSiblingTrust")!) } : {}),
|
|
121
|
+
...(elapsed.has("engagementDedupe") ? { engagementStoreDedupeMs: millis(elapsed.get("engagementDedupe")!) } : {}),
|
|
122
|
+
...(dispatchMs === undefined
|
|
123
|
+
? {}
|
|
124
|
+
: { dispatchPreflightCatchUpMs: millis(Math.max(0, dispatchMs - (eveSendMs ?? 0) - (finalAuditMs ?? 0))) }),
|
|
125
|
+
...(eveSendMs === undefined ? {} : { eveSendMs: millis(eveSendMs) }),
|
|
126
|
+
...(finalAuditMs === undefined ? {} : { finalAuditMs: millis(finalAuditMs) }),
|
|
127
|
+
totalMs: millis(duration(startedAt, now())),
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Notion's webhook signature scheme, as a registry entry (OPE-164) โ what lets a GENERIC hook
|
|
2
|
+
// (`POST /webhooks/<id>`) declare `source: "notion"` and be verified the way Notion signs.
|
|
3
|
+
//
|
|
4
|
+
// ZERO NEW CRYPTO. The digest is this bundle's own `verifyNotionSignature` (channels/notion-api.ts),
|
|
5
|
+
// the same function the native notion webhook route uses โ one implementation of Notion's scheme in
|
|
6
|
+
// the tree, not two.
|
|
7
|
+
//
|
|
8
|
+
// THE SCHEME (https://developers.notion.com/reference/webhooks):
|
|
9
|
+
// X-Notion-Signature: sha256=<hex> HMAC-SHA256 of the RAW body, keyed by the ONE-TIME
|
|
10
|
+
// `verification_token` Notion hands over during subscription setup.
|
|
11
|
+
// Notion, verbatim, on why the raw bytes matter: *"Pass the raw request body exactly as it arrived
|
|
12
|
+
// (re-serialized JSON produces different bytes and fails verification)"*.
|
|
13
|
+
//
|
|
14
|
+
// ๐ด `signedTimestamp: false` โ nothing in the signature binds freshness. Notion's payloads carry a
|
|
15
|
+
// timestamp FIELD, but it is not covered in a way that bounds replay, so replay against a notion
|
|
16
|
+
// hook is undefended and is declared as such on the reviewed allowlist.
|
|
17
|
+
//
|
|
18
|
+
// ROTATION: Notion documents no overlap window โ *"If you ever rotate or recreate your webhook
|
|
19
|
+
// subscription, be sure to update your stored verification_token"* โ so the `keys[]` list is how an
|
|
20
|
+
// operator gets one on our side (new token at index 0, outgoing at index 1 until the switch).
|
|
21
|
+
|
|
22
|
+
import { verifyNotionSignature } from "./notion-api.js";
|
|
23
|
+
import type { WebhookVerifier } from "@open-everywhere/core/agent/adapter-api.js";
|
|
24
|
+
|
|
25
|
+
/** The registry key โ what a generic hook declares as its `source` to be verified as Notion. */
|
|
26
|
+
export const NOTION_WEBHOOK_SOURCE = "notion";
|
|
27
|
+
|
|
28
|
+
const NOTION_SIGNATURE_HEADER = "x-notion-signature";
|
|
29
|
+
|
|
30
|
+
export const notionWebhookVerifier: WebhookVerifier = {
|
|
31
|
+
source: NOTION_WEBHOOK_SOURCE,
|
|
32
|
+
provider: "notion",
|
|
33
|
+
keyKind: "shared-secret",
|
|
34
|
+
// ๐ด `keyOrigin: "vendor-issued"` (OPE-186) โ the key is the `verification_token` NOTION mints and
|
|
35
|
+
// posts to us once during subscription setup. We cannot choose it, so a secret WE minted can never
|
|
36
|
+
// verify a Notion delivery. This is what keeps `notion` OUT of the chat-mintable source vocabulary
|
|
37
|
+
// (`webhooks/source-vocabulary.ts` โ `acceptsMintedSecret`). ๐ด THAT CONDITIONAL IS NO LONGER
|
|
38
|
+
// HYPOTHETICAL: OPE-189 added `notion-event-normalizer.ts`, so `notion` now has BOTH halves the
|
|
39
|
+
// vocabulary derivation looks for and `keyOrigin` is the ONLY thing excluding it. The exclusion is
|
|
40
|
+
// pinned live rather than asserted here โ `scripts/webhook-source-vocabulary-live-test.ts` ยง6a
|
|
41
|
+
// fails any mintable source whose normalizer declares no `deliveryIdHeader`, and Notion's declares
|
|
42
|
+
// none (its delivery id is in the body). Flip this field and that test goes red. Only an
|
|
43
|
+
// OPERATOR-DECLARED hook (`loadWebhooks`, whose secret ref the operator points at the stored token)
|
|
44
|
+
// can use this verifier.
|
|
45
|
+
keyOrigin: "vendor-issued",
|
|
46
|
+
signedTimestamp: false,
|
|
47
|
+
verify({ rawBody, headers, keys }) {
|
|
48
|
+
const signature = headers.get(NOTION_SIGNATURE_HEADER);
|
|
49
|
+
if (!signature) return { ok: false, failure: "missing-signature" };
|
|
50
|
+
for (const [i, key] of keys.entries()) {
|
|
51
|
+
if (verifyNotionSignature(key, rawBody, signature)) return { ok: true, keyIndex: i };
|
|
52
|
+
}
|
|
53
|
+
return { ok: false, failure: "signature-mismatch" };
|
|
54
|
+
},
|
|
55
|
+
};
|