@vinaverse/server 0.0.0-stage → 0.1.0-alpha.2
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/README.md +165 -2
- package/dist/api.cjs +528 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +453 -0
- package/dist/api.d.ts +453 -0
- package/dist/api.js +516 -0
- package/dist/api.js.map +1 -0
- package/dist/chunk-FEEJNQXV.js +64 -0
- package/dist/chunk-FEEJNQXV.js.map +1 -0
- package/dist/chunk-GTPMQKGX.js +277 -0
- package/dist/chunk-GTPMQKGX.js.map +1 -0
- package/dist/chunk-NPXO6SMZ.js +84 -0
- package/dist/chunk-NPXO6SMZ.js.map +1 -0
- package/dist/chunk-SDJFZPHJ.js +141 -0
- package/dist/chunk-SDJFZPHJ.js.map +1 -0
- package/dist/express.cjs +397 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +43 -0
- package/dist/express.d.ts +43 -0
- package/dist/express.js +87 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.cjs +363 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +62 -0
- package/dist/fastify.d.ts +62 -0
- package/dist/fastify.js +54 -0
- package/dist/fastify.js.map +1 -0
- package/dist/hono.cjs +421 -0
- package/dist/hono.cjs.map +1 -0
- package/dist/hono.d.cts +21 -0
- package/dist/hono.d.ts +21 -0
- package/dist/hono.js +34 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.cjs +1481 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +196 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +920 -0
- package/dist/index.js.map +1 -0
- package/dist/lambda.cjs +405 -0
- package/dist/lambda.cjs.map +1 -0
- package/dist/lambda.d.cts +53 -0
- package/dist/lambda.d.ts +53 -0
- package/dist/lambda.js +86 -0
- package/dist/lambda.js.map +1 -0
- package/dist/nonces-scwljZwl.d.cts +92 -0
- package/dist/nonces-scwljZwl.d.ts +92 -0
- package/dist/provider-BqfTvF3g.d.ts +2016 -0
- package/dist/provider-Miaa9V7I.d.cts +2016 -0
- package/dist/stores.cjs +315 -0
- package/dist/stores.cjs.map +1 -0
- package/dist/stores.d.cts +52 -0
- package/dist/stores.d.ts +52 -0
- package/dist/stores.js +306 -0
- package/dist/stores.js.map +1 -0
- package/dist/testing.cjs +135 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +29 -0
- package/dist/testing.d.ts +29 -0
- package/dist/testing.js +49 -0
- package/dist/testing.js.map +1 -0
- package/dist/vinaverse-v1.d-BVc4uyUd.d.cts +3460 -0
- package/dist/vinaverse-v1.d-BVc4uyUd.d.ts +3460 -0
- package/package.json +225 -3
package/dist/express.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { checkHandlerOptions, MAX_BODY_BYTES, tooLarge, handleWebhook } from './chunk-FEEJNQXV.js';
|
|
2
|
+
import './chunk-GTPMQKGX.js';
|
|
3
|
+
|
|
4
|
+
// src/adapters/express.ts
|
|
5
|
+
var RawBodyRequiredError = class extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(
|
|
8
|
+
"the Vinaverse webhook route needs the raw body: mount it before express.json(), or put express.raw({ type: 'application/json' }) in front of it"
|
|
9
|
+
);
|
|
10
|
+
this.name = "RawBodyRequiredError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function vinaverseWebhook(options) {
|
|
14
|
+
checkHandlerOptions(options);
|
|
15
|
+
const max = options.maxBodyBytes ?? MAX_BODY_BYTES;
|
|
16
|
+
return async (req, res, next) => {
|
|
17
|
+
try {
|
|
18
|
+
const body = await rawBody(req, max);
|
|
19
|
+
const response = body === void 0 ? tooLarge() : await handleWebhook(
|
|
20
|
+
options,
|
|
21
|
+
body,
|
|
22
|
+
req.headers["vinaverse-signature"]
|
|
23
|
+
);
|
|
24
|
+
send(res, response);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
next(error);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function send(res, response) {
|
|
31
|
+
res.status(response.status).type("application/json").send(response.body);
|
|
32
|
+
}
|
|
33
|
+
async function rawBody(req, max) {
|
|
34
|
+
const { body } = req;
|
|
35
|
+
if (body instanceof Uint8Array || typeof body === "string") return body;
|
|
36
|
+
const parsed = body !== void 0 && !(typeof body === "object" && body !== null && Object.keys(body).length === 0);
|
|
37
|
+
if (parsed) throw new RawBodyRequiredError();
|
|
38
|
+
const chunks = [];
|
|
39
|
+
let size = 0;
|
|
40
|
+
for await (const chunk of req) {
|
|
41
|
+
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk;
|
|
42
|
+
size += bytes.length;
|
|
43
|
+
if (size > max) return void 0;
|
|
44
|
+
chunks.push(bytes);
|
|
45
|
+
}
|
|
46
|
+
if (size === 0 && body !== void 0) throw new RawBodyRequiredError();
|
|
47
|
+
const all = new Uint8Array(size);
|
|
48
|
+
let at = 0;
|
|
49
|
+
for (const c of chunks) {
|
|
50
|
+
all.set(c, at);
|
|
51
|
+
at += c.length;
|
|
52
|
+
}
|
|
53
|
+
return all;
|
|
54
|
+
}
|
|
55
|
+
function vinaverseTools(provider, options = {}) {
|
|
56
|
+
const max = options.maxBodyBytes ?? MAX_BODY_BYTES;
|
|
57
|
+
return async (req, res, next) => {
|
|
58
|
+
try {
|
|
59
|
+
const reads = req.method !== "GET" && req.method !== "HEAD";
|
|
60
|
+
const body = reads ? await rawBody(req, max) : null;
|
|
61
|
+
const response = body === void 0 ? {
|
|
62
|
+
status: 413,
|
|
63
|
+
headers: { "content-type": "application/problem+json" },
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
status: 413,
|
|
66
|
+
code: "invalid_argument",
|
|
67
|
+
detail: "the request body is too large"
|
|
68
|
+
})
|
|
69
|
+
} : await provider.handle({
|
|
70
|
+
method: req.method,
|
|
71
|
+
target: req.originalUrl,
|
|
72
|
+
header: (name) => {
|
|
73
|
+
const value = req.headers[name.toLowerCase()];
|
|
74
|
+
return Array.isArray(value) ? value.join(", ") : value;
|
|
75
|
+
},
|
|
76
|
+
body
|
|
77
|
+
});
|
|
78
|
+
res.status(response.status).set({ ...response.headers }).send(response.body);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
next(error);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { RawBodyRequiredError, vinaverseTools, vinaverseWebhook };
|
|
86
|
+
//# sourceMappingURL=express.js.map
|
|
87
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/express.ts"],"names":[],"mappings":"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","file":"express.js","sourcesContent":["// Express (4 and 5): middleware for one route.\n//\n// app.post('/webhooks/vinaverse', vinaverseWebhook({ secret, onEvent }));\n//\n// It needs the raw body. Either mount it before any JSON parser for that\n// route (it reads the request stream itself), or put\n// `express.raw({ type: 'application/json' })` in front of it. A body already\n// parsed into an object cannot be verified: the middleware passes an error to\n// `next` saying so, instead of answering 400 for every delivery.\nimport {\n checkHandlerOptions,\n handleWebhook,\n MAX_BODY_BYTES,\n tooLarge,\n type WebhookHandlerOptions,\n type WebhookResponse,\n} from './handler.js';\n\nexport type { WebhookHandlerOptions } from './handler.js';\n\nimport type { ToolProvider } from '../tools/provider.js';\n\n/** The parts of Express's request the middleware uses (Node's `IncomingMessage`). */\nexport interface ExpressRequestLike extends AsyncIterable<unknown> {\n body?: unknown;\n headers: Readonly<Record<string, string | readonly string[] | undefined>>;\n}\n\n/** The parts of Express's response the middleware uses. */\nexport interface ExpressResponseLike {\n status(code: number): ExpressResponseLike;\n type(type: string): ExpressResponseLike;\n send(body: string): unknown;\n}\n\nexport class RawBodyRequiredError extends Error {\n constructor() {\n super(\n \"the Vinaverse webhook route needs the raw body: mount it before express.json(), or put express.raw({ type: 'application/json' }) in front of it\",\n );\n this.name = 'RawBodyRequiredError';\n }\n}\n\nexport function vinaverseWebhook(\n options: WebhookHandlerOptions,\n): (\n req: ExpressRequestLike,\n res: ExpressResponseLike,\n next: (error?: unknown) => void,\n) => Promise<void> {\n checkHandlerOptions(options);\n const max = options.maxBodyBytes ?? MAX_BODY_BYTES;\n return async (req, res, next) => {\n try {\n const body = await rawBody(req, max);\n const response =\n body === undefined\n ? tooLarge()\n : await handleWebhook(\n options,\n body,\n req.headers['vinaverse-signature'],\n );\n send(res, response);\n } catch (error) {\n next(error);\n }\n };\n}\n\nfunction send(res: ExpressResponseLike, response: WebhookResponse): void {\n res.status(response.status).type('application/json').send(response.body);\n}\n\n// The body as received, or undefined when it is over `max` bytes.\nasync function rawBody(\n req: ExpressRequestLike,\n max: number,\n): Promise<string | Uint8Array | undefined> {\n const { body } = req;\n if (body instanceof Uint8Array || typeof body === 'string') return body;\n const parsed =\n body !== undefined &&\n !(\n typeof body === 'object' &&\n body !== null &&\n Object.keys(body).length === 0\n );\n if (parsed) throw new RawBodyRequiredError();\n // Nothing read it yet (or a parser that skipped this content type left `{}`).\n const chunks: Uint8Array[] = [];\n let size = 0;\n for await (const chunk of req) {\n const bytes =\n typeof chunk === 'string'\n ? new TextEncoder().encode(chunk)\n : (chunk as Uint8Array);\n size += bytes.length;\n if (size > max) return undefined;\n chunks.push(bytes);\n }\n if (size === 0 && body !== undefined) throw new RawBodyRequiredError();\n const all = new Uint8Array(size);\n let at = 0;\n for (const c of chunks) {\n all.set(c, at);\n at += c.length;\n }\n return all;\n}\n\n/** The parts of Express's request the tools middleware also reads. */\nexport interface ExpressToolRequestLike extends ExpressRequestLike {\n method: string;\n /** The request target as received (Express keeps it even under a mounted router). */\n originalUrl: string;\n}\n\n/** The parts of Express's response the tools middleware uses. */\nexport interface ExpressToolResponseLike {\n status(code: number): ExpressToolResponseLike;\n set(headers: Record<string, string>): ExpressToolResponseLike;\n send(body: string): unknown;\n}\n\n/**\n * Express middleware serving a tenant's tools, mounted at the provider's\n * `basePath`: `app.use('/api/v2/ai', vinaverseTools(provider))`, before any\n * JSON parser for those routes (or behind `express.raw({ type:\n * 'application/json' })`). It answers every request it receives, so never\n * mount it on the whole app.\n */\nexport function vinaverseTools(\n provider: ToolProvider,\n options: { maxBodyBytes?: number } = {},\n): (\n req: ExpressToolRequestLike,\n res: ExpressToolResponseLike,\n next: (error?: unknown) => void,\n) => Promise<void> {\n const max = options.maxBodyBytes ?? MAX_BODY_BYTES;\n return async (req, res, next) => {\n try {\n const reads = req.method !== 'GET' && req.method !== 'HEAD';\n const body = reads ? await rawBody(req, max) : null;\n const response =\n body === undefined\n ? {\n status: 413,\n headers: { 'content-type': 'application/problem+json' },\n body: JSON.stringify({\n status: 413,\n code: 'invalid_argument',\n detail: 'the request body is too large',\n }),\n }\n : await provider.handle({\n method: req.method,\n target: req.originalUrl,\n header: (name) => {\n const value = req.headers[name.toLowerCase()];\n return Array.isArray(value)\n ? value.join(', ')\n : (value as string | undefined);\n },\n body,\n });\n res\n .status(response.status)\n .set({ ...response.headers })\n .send(response.body);\n } catch (error) {\n next(error);\n }\n };\n}\n"]}
|
package/dist/fastify.cjs
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/webhooks/crypto.ts
|
|
4
|
+
var WebCryptoUnavailableError = class extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(
|
|
7
|
+
"Web Crypto (crypto.subtle) is not available in this runtime; @vinaverse/server needs Node 18+, an edge runtime or another runtime with Web Crypto"
|
|
8
|
+
);
|
|
9
|
+
this.name = "WebCryptoUnavailableError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var fromNode;
|
|
13
|
+
function webCrypto() {
|
|
14
|
+
const global = globalThis.crypto;
|
|
15
|
+
if (global?.subtle) return Promise.resolve(global);
|
|
16
|
+
fromNode ??= nodeWebCrypto();
|
|
17
|
+
return fromNode;
|
|
18
|
+
}
|
|
19
|
+
async function subtleCrypto() {
|
|
20
|
+
return (await webCrypto()).subtle;
|
|
21
|
+
}
|
|
22
|
+
async function nodeWebCrypto() {
|
|
23
|
+
if (typeof globalThis.process?.versions?.node !== "string") {
|
|
24
|
+
throw new WebCryptoUnavailableError();
|
|
25
|
+
}
|
|
26
|
+
const specifier = ["node", "crypto"].join(":");
|
|
27
|
+
const mod = await import(
|
|
28
|
+
/* webpackIgnore: true */
|
|
29
|
+
/* @vite-ignore */
|
|
30
|
+
specifier
|
|
31
|
+
);
|
|
32
|
+
if (!mod.webcrypto?.subtle) throw new WebCryptoUnavailableError();
|
|
33
|
+
return mod.webcrypto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/internal/bytes.ts
|
|
37
|
+
var encoder = new TextEncoder();
|
|
38
|
+
function utf8(text) {
|
|
39
|
+
return encoder.encode(text);
|
|
40
|
+
}
|
|
41
|
+
function toBytes(body) {
|
|
42
|
+
if (typeof body === "string") return utf8(body);
|
|
43
|
+
if (body instanceof Uint8Array) return body;
|
|
44
|
+
return new Uint8Array(body);
|
|
45
|
+
}
|
|
46
|
+
function hexToBytes(hex) {
|
|
47
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
48
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
49
|
+
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
50
|
+
}
|
|
51
|
+
return bytes;
|
|
52
|
+
}
|
|
53
|
+
async function hmacKey(secret, usage) {
|
|
54
|
+
const subtle = await subtleCrypto();
|
|
55
|
+
return subtle.importKey(
|
|
56
|
+
"raw",
|
|
57
|
+
utf8(secret),
|
|
58
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
59
|
+
false,
|
|
60
|
+
[usage]
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
function concat(...parts) {
|
|
64
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
65
|
+
let at = 0;
|
|
66
|
+
for (const p of parts) {
|
|
67
|
+
out.set(p, at);
|
|
68
|
+
at += p.length;
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/webhooks/errors.ts
|
|
74
|
+
function webhookError(code, message) {
|
|
75
|
+
return { code, message };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/webhooks/events.ts
|
|
79
|
+
var WEBHOOK_EVENT_TYPES = [
|
|
80
|
+
"moderation.item.completed",
|
|
81
|
+
"moderation.job.completed",
|
|
82
|
+
"document_check.completed",
|
|
83
|
+
"session.completed",
|
|
84
|
+
"subject.erased",
|
|
85
|
+
"usage.threshold_reached",
|
|
86
|
+
"feature.state_changed",
|
|
87
|
+
"media.job.queued",
|
|
88
|
+
"media.job.progress",
|
|
89
|
+
"media.job.step.completed",
|
|
90
|
+
"media.job.completed",
|
|
91
|
+
"media.job.failed",
|
|
92
|
+
"media.job.cancelled",
|
|
93
|
+
"conversation.updated",
|
|
94
|
+
"conversation.completed",
|
|
95
|
+
"conversation.failed",
|
|
96
|
+
"conversation.expired"
|
|
97
|
+
];
|
|
98
|
+
var KNOWN = new Set(WEBHOOK_EVENT_TYPES);
|
|
99
|
+
var CONVERSATION_STATUSES = {
|
|
100
|
+
"conversation.updated": ["in_progress", "failed"],
|
|
101
|
+
"conversation.completed": ["completed"],
|
|
102
|
+
"conversation.failed": ["failed"],
|
|
103
|
+
"conversation.expired": ["expired"]
|
|
104
|
+
};
|
|
105
|
+
var ENVIRONMENTS = /* @__PURE__ */ new Set(["mock", "development", "production"]);
|
|
106
|
+
function isObject(value) {
|
|
107
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
108
|
+
}
|
|
109
|
+
function readBody(text) {
|
|
110
|
+
let parsed;
|
|
111
|
+
try {
|
|
112
|
+
parsed = JSON.parse(text);
|
|
113
|
+
} catch {
|
|
114
|
+
return webhookError("invalid_body", "the body is not JSON");
|
|
115
|
+
}
|
|
116
|
+
if (!isObject(parsed) || typeof parsed.id !== "string" || typeof parsed.type !== "string" || typeof parsed.created_at !== "string" || typeof parsed.environment !== "string" || !ENVIRONMENTS.has(parsed.environment) || !isObject(parsed.data)) {
|
|
117
|
+
return webhookError(
|
|
118
|
+
"invalid_body",
|
|
119
|
+
"the body is not a webhook envelope (id, type, created_at, environment, data)"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const envelope = parsed;
|
|
123
|
+
if (parsed.data.test === true) return { kind: "test", envelope };
|
|
124
|
+
if (!KNOWN.has(parsed.type)) return { kind: "unrecognized", envelope };
|
|
125
|
+
const statuses = CONVERSATION_STATUSES[parsed.type];
|
|
126
|
+
if (statuses) {
|
|
127
|
+
const { conversation_id, sequence, status, flow } = parsed.data;
|
|
128
|
+
if (typeof conversation_id !== "string" || !Number.isSafeInteger(sequence) || sequence < 1 || typeof status !== "string" || !statuses.includes(status) || typeof flow !== "string") {
|
|
129
|
+
return webhookError(
|
|
130
|
+
"invalid_body",
|
|
131
|
+
`a ${parsed.type} event carries a guided flow's state: conversation_id, a positive integer sequence, a flow, and status ${statuses.join(" or ")}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { kind: "event", event: parsed };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/webhooks/signature.ts
|
|
139
|
+
var TOLERANCE_SECONDS = 300;
|
|
140
|
+
var DIGITS = /^[0-9]+$/;
|
|
141
|
+
var V1 = /^[0-9a-f]{64}$/;
|
|
142
|
+
function parseSignatureHeader(header) {
|
|
143
|
+
const parts = header.split(",").map((p) => p.trim()).filter((p) => p.includes("=")).map((p) => {
|
|
144
|
+
const at = p.indexOf("=");
|
|
145
|
+
return [p.slice(0, at), p.slice(at + 1)];
|
|
146
|
+
});
|
|
147
|
+
const timestamps = parts.filter(([k]) => k === "t").map(([, v]) => v);
|
|
148
|
+
const [t] = timestamps;
|
|
149
|
+
if (timestamps.length !== 1 || t === void 0 || !DIGITS.test(t)) {
|
|
150
|
+
return webhookError(
|
|
151
|
+
"malformed",
|
|
152
|
+
"the signature header needs exactly one timestamp, t=<unix seconds>"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const signatures = parts.filter(([k, v]) => k === "v1" && V1.test(v)).map(([, v]) => hexToBytes(v));
|
|
156
|
+
if (signatures.length === 0) {
|
|
157
|
+
return webhookError(
|
|
158
|
+
"malformed",
|
|
159
|
+
"the signature header has no v1 signature"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return { timestamp: BigInt(t), signatures };
|
|
163
|
+
}
|
|
164
|
+
async function checkSignature(options) {
|
|
165
|
+
const parsed = parseSignatureHeader(options.header);
|
|
166
|
+
if ("code" in parsed) return parsed;
|
|
167
|
+
const now = BigInt(Math.floor(options.now.getTime() / 1e3));
|
|
168
|
+
const skew = now > parsed.timestamp ? now - parsed.timestamp : parsed.timestamp - now;
|
|
169
|
+
if (skew > BigInt(options.toleranceSeconds)) {
|
|
170
|
+
return webhookError(
|
|
171
|
+
"stale",
|
|
172
|
+
`the signature is ${skew} seconds from now; at most ${options.toleranceSeconds} are accepted`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const message = concat(utf8(`${parsed.timestamp}.`), options.body);
|
|
176
|
+
const subtle = await subtleCrypto();
|
|
177
|
+
for (const secret of options.secrets) {
|
|
178
|
+
const key = await hmacKey(secret, "verify");
|
|
179
|
+
for (const signature of parsed.signatures) {
|
|
180
|
+
if (await subtle.verify(
|
|
181
|
+
"HMAC",
|
|
182
|
+
key,
|
|
183
|
+
signature,
|
|
184
|
+
message
|
|
185
|
+
))
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return webhookError(
|
|
190
|
+
"mismatch",
|
|
191
|
+
"no signature matches the body with the signing secret: check the secret, and verify the raw body exactly as received"
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/webhooks/verify.ts
|
|
196
|
+
async function verifyWebhook(options) {
|
|
197
|
+
const secrets = signingSecrets(options.secret);
|
|
198
|
+
const toleranceSeconds = checkTolerance(options.toleranceSeconds);
|
|
199
|
+
const header = singleHeader(options.header);
|
|
200
|
+
if (header === void 0) {
|
|
201
|
+
return {
|
|
202
|
+
ok: false,
|
|
203
|
+
error: webhookError("missing", "no Vinaverse-Signature header")
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (header === null) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
error: webhookError(
|
|
210
|
+
"malformed",
|
|
211
|
+
"more than one Vinaverse-Signature header"
|
|
212
|
+
)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const body = toBytes(options.rawBody);
|
|
216
|
+
const refused = await checkSignature({
|
|
217
|
+
body,
|
|
218
|
+
header,
|
|
219
|
+
secrets,
|
|
220
|
+
now: options.now ?? /* @__PURE__ */ new Date(),
|
|
221
|
+
toleranceSeconds
|
|
222
|
+
});
|
|
223
|
+
if (refused) return { ok: false, error: refused };
|
|
224
|
+
let text;
|
|
225
|
+
try {
|
|
226
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
227
|
+
} catch {
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
error: webhookError("invalid_body", "the body is not UTF-8")
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const read = readBody(text);
|
|
234
|
+
return "code" in read ? { ok: false, error: read } : { ok: true, ...read };
|
|
235
|
+
}
|
|
236
|
+
function singleHeader(header) {
|
|
237
|
+
if (header === null || header === void 0) return void 0;
|
|
238
|
+
if (typeof header === "string") return header === "" ? void 0 : header;
|
|
239
|
+
if (header.length === 0) return void 0;
|
|
240
|
+
return header.length === 1 ? header[0] : null;
|
|
241
|
+
}
|
|
242
|
+
function signingSecrets(secret) {
|
|
243
|
+
const list = typeof secret === "string" ? [secret] : Array.isArray(secret) ? [...secret] : [];
|
|
244
|
+
if (list.length === 0 || list.some((s) => typeof s !== "string" || s === "")) {
|
|
245
|
+
throw new TypeError(
|
|
246
|
+
"the webhook signing secret is required (vv_whsec_\u2026), and none was given"
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return list;
|
|
250
|
+
}
|
|
251
|
+
function checkTolerance(seconds) {
|
|
252
|
+
if (seconds === void 0) return TOLERANCE_SECONDS;
|
|
253
|
+
if (!Number.isSafeInteger(seconds) || seconds < 0 || seconds > TOLERANCE_SECONDS) {
|
|
254
|
+
throw new TypeError(
|
|
255
|
+
`toleranceSeconds must be a whole number of seconds from 0 to ${TOLERANCE_SECONDS}, not ${seconds}`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return seconds;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/adapters/handler.ts
|
|
262
|
+
var MAX_BODY_BYTES = 1024 * 1024;
|
|
263
|
+
var RECEIVED = JSON.stringify({ received: true });
|
|
264
|
+
function tooLarge() {
|
|
265
|
+
return {
|
|
266
|
+
status: 413,
|
|
267
|
+
body: JSON.stringify({ error: { code: "body_too_large" } })
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function byteLength(body) {
|
|
271
|
+
return typeof body === "string" ? new TextEncoder().encode(body).length : body.byteLength;
|
|
272
|
+
}
|
|
273
|
+
async function handleWebhook(options, rawBody, header) {
|
|
274
|
+
if (byteLength(rawBody) > (options.maxBodyBytes ?? MAX_BODY_BYTES)) {
|
|
275
|
+
return tooLarge();
|
|
276
|
+
}
|
|
277
|
+
const result = await verifyWebhook({
|
|
278
|
+
rawBody,
|
|
279
|
+
header,
|
|
280
|
+
secret: options.secret,
|
|
281
|
+
...options.now ? { now: options.now() } : {},
|
|
282
|
+
...options.toleranceSeconds === void 0 ? {} : { toleranceSeconds: options.toleranceSeconds }
|
|
283
|
+
});
|
|
284
|
+
if (!result.ok) {
|
|
285
|
+
await options.onRejected?.(result.error);
|
|
286
|
+
return {
|
|
287
|
+
status: 400,
|
|
288
|
+
body: JSON.stringify({ error: { code: result.error.code } })
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (result.kind === "event") await options.onEvent(result.event);
|
|
292
|
+
else if (result.kind === "test") await options.onTest?.(result.envelope);
|
|
293
|
+
else await options.onUnrecognized?.(result.envelope);
|
|
294
|
+
return { status: 200, body: RECEIVED };
|
|
295
|
+
}
|
|
296
|
+
function checkHandlerOptions(options) {
|
|
297
|
+
signingSecrets(options.secret);
|
|
298
|
+
checkTolerance(options.toleranceSeconds);
|
|
299
|
+
if (typeof options.onEvent !== "function") {
|
|
300
|
+
throw new TypeError(
|
|
301
|
+
"onEvent is required: it receives every verified event"
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const max = options.maxBodyBytes;
|
|
305
|
+
if (max !== void 0 && (!Number.isSafeInteger(max) || max <= 0)) {
|
|
306
|
+
throw new TypeError(
|
|
307
|
+
`maxBodyBytes must be a positive whole number of bytes, not ${max}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/adapters/fastify.ts
|
|
313
|
+
function vinaverseWebhookPlugin(options) {
|
|
314
|
+
checkHandlerOptions(options);
|
|
315
|
+
return async (fastify) => {
|
|
316
|
+
fastify.removeContentTypeParser("application/json");
|
|
317
|
+
fastify.addContentTypeParser(
|
|
318
|
+
"application/json",
|
|
319
|
+
{ parseAs: "buffer", bodyLimit: options.maxBodyBytes ?? MAX_BODY_BYTES },
|
|
320
|
+
(_request, body, done) => done(null, body)
|
|
321
|
+
);
|
|
322
|
+
fastify.post(options.path, async (request, reply) => {
|
|
323
|
+
const body = request.body instanceof Uint8Array ? request.body : new Uint8Array(0);
|
|
324
|
+
const response = await handleWebhook(
|
|
325
|
+
options,
|
|
326
|
+
body,
|
|
327
|
+
request.headers["vinaverse-signature"]
|
|
328
|
+
);
|
|
329
|
+
return reply.code(response.status).type("application/json").send(response.body);
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function vinaverseToolsPlugin(options) {
|
|
334
|
+
return async (fastify) => {
|
|
335
|
+
fastify.removeContentTypeParser("application/json");
|
|
336
|
+
fastify.addContentTypeParser(
|
|
337
|
+
"application/json",
|
|
338
|
+
{ parseAs: "buffer", bodyLimit: options.maxBodyBytes ?? MAX_BODY_BYTES },
|
|
339
|
+
(_request, body, done) => done(null, body)
|
|
340
|
+
);
|
|
341
|
+
fastify.route({
|
|
342
|
+
method: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
343
|
+
url: `${options.basePath.replace(/\/+$/, "")}/*`,
|
|
344
|
+
handler: async (request, reply) => {
|
|
345
|
+
const response = await options.provider.handle({
|
|
346
|
+
method: request.method,
|
|
347
|
+
target: request.url,
|
|
348
|
+
header: (name) => {
|
|
349
|
+
const value = request.headers[name.toLowerCase()];
|
|
350
|
+
return Array.isArray(value) ? value.join(", ") : value;
|
|
351
|
+
},
|
|
352
|
+
body: request.body instanceof Uint8Array ? request.body : null
|
|
353
|
+
});
|
|
354
|
+
return reply.code(response.status).headers({ ...response.headers }).send(response.body);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
exports.vinaverseToolsPlugin = vinaverseToolsPlugin;
|
|
361
|
+
exports.vinaverseWebhookPlugin = vinaverseWebhookPlugin;
|
|
362
|
+
//# sourceMappingURL=fastify.cjs.map
|
|
363
|
+
//# sourceMappingURL=fastify.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/webhooks/crypto.ts","../src/internal/bytes.ts","../src/webhooks/errors.ts","../src/webhooks/events.ts","../src/webhooks/signature.ts","../src/webhooks/verify.ts","../src/adapters/handler.ts","../src/adapters/fastify.ts"],"names":[],"mappings":";;;AAMO,IAAM,yBAAA,GAAN,cAAwC,KAAA,CAAM;AAAA,EACnD,WAAA,GAAc;AACZ,IAAA,KAAA;AAAA,MACE;AAAA,KACF;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,2BAAA;AAAA,EACd;AACF,CAAA;AAIA,IAAI,QAAA;AAGG,SAAS,SAAA,GAA6B;AAC3C,EAAA,MAAM,SAAU,UAAA,CAAmC,MAAA;AACnD,EAAA,IAAI,MAAA,EAAQ,MAAA,EAAQ,OAAO,OAAA,CAAQ,QAAQ,MAAM,CAAA;AACjD,EAAA,QAAA,KAAa,aAAA,EAAc;AAC3B,EAAA,OAAO,QAAA;AACT;AAEA,eAAsB,YAAA,GAAsC;AAC1D,EAAA,OAAA,CAAQ,MAAM,WAAU,EAAG,MAAA;AAC7B;AAEA,eAAe,aAAA,GAAiC;AAC9C,EAAA,IAAI,OAAQ,UAAA,CAAyB,OAAA,EAAS,QAAA,EAAU,SAAS,QAAA,EAAU;AACzE,IAAA,MAAM,IAAI,yBAAA,EAA0B;AAAA,EACtC;AAGA,EAAA,MAAM,YAAY,CAAC,MAAA,EAAQ,QAAQ,CAAA,CAAE,KAAK,GAAG,CAAA;AAC7C,EAAA,MAAM,MAAO,MAAM;AAAA;AAAA;AAAA,IAC4B;AAAA,GAAA;AAE/C,EAAA,IAAI,CAAC,GAAA,CAAI,SAAA,EAAW,MAAA,EAAQ,MAAM,IAAI,yBAAA,EAA0B;AAChE,EAAA,OAAO,GAAA,CAAI,SAAA;AACb;;;ACxCA,IAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAEzB,SAAS,KAAK,IAAA,EAAuC;AAC1D,EAAA,OAAO,OAAA,CAAQ,OAAO,IAAI,CAAA;AAC5B;AAEO,SAAS,QAAQ,IAAA,EAAqD;AAC3E,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,EAAU,OAAO,KAAK,IAAI,CAAA;AAC9C,EAAA,IAAI,IAAA,YAAgB,YAAY,OAAO,IAAA;AACvC,EAAA,OAAO,IAAI,WAAW,IAAI,CAAA;AAC5B;AAEO,SAAS,WAAW,GAAA,EAAsC;AAC/D,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,GAAA,CAAI,SAAS,CAAC,CAAA;AAC3C,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,KAAA,CAAM,CAAC,CAAA,GAAI,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,KAAA,CAAM,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,GAAI,CAAC,CAAA,EAAG,EAAE,CAAA;AAAA,EAC5D;AACA,EAAA,OAAO,KAAA;AACT;AAWA,eAAsB,OAAA,CACpB,QACA,KAAA,EACoB;AACpB,EAAA,MAAM,MAAA,GAAS,MAAM,YAAA,EAAa;AAClC,EAAA,OAAO,MAAA,CAAO,SAAA;AAAA,IACZ,KAAA;AAAA,IACA,KAAK,MAAM,CAAA;AAAA,IACX,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAU;AAAA,IAChC,KAAA;AAAA,IACA,CAAC,KAAK;AAAA,GACR;AACF;AAUO,SAAS,UAAU,KAAA,EAA8C;AACtE,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAC,CAAC,CAAA;AAClE,EAAA,IAAI,EAAA,GAAK,CAAA;AACT,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,IAAA,GAAA,CAAI,GAAA,CAAI,GAAG,EAAE,CAAA;AACb,IAAA,EAAA,IAAM,CAAA,CAAE,MAAA;AAAA,EACV;AACA,EAAA,OAAO,GAAA;AACT;;;AClCO,SAAS,YAAA,CACd,MACA,OAAA,EACc;AACd,EAAA,OAAO,EAAE,MAAM,OAAA,EAAQ;AACzB;;;ACYO,IAAM,mBAAA,GAAmD;AAAA,EAC9D,2BAAA;AAAA,EACA,0BAAA;AAAA,EACA,0BAAA;AAAA,EACA,mBAAA;AAAA,EACA,gBAAA;AAAA,EACA,yBAAA;AAAA,EACA,uBAAA;AAAA,EACA,kBAAA;AAAA,EACA,oBAAA;AAAA,EACA,0BAAA;AAAA,EACA,qBAAA;AAAA,EACA,kBAAA;AAAA,EACA,qBAAA;AAAA,EACA,sBAAA;AAAA,EACA,wBAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF,CAAA;AAEA,IAAM,KAAA,GAAQ,IAAI,GAAA,CAAY,mBAAmB,CAAA;AAGjD,IAAM,qBAAA,GAAqE;AAAA,EACzE,sBAAA,EAAwB,CAAC,aAAA,EAAe,QAAQ,CAAA;AAAA,EAChD,wBAAA,EAA0B,CAAC,WAAW,CAAA;AAAA,EACtC,qBAAA,EAAuB,CAAC,QAAQ,CAAA;AAAA,EAChC,sBAAA,EAAwB,CAAC,SAAS;AACpC,CAAA;AACA,IAAM,+BAAe,IAAI,GAAA,CAAI,CAAC,MAAA,EAAQ,aAAA,EAAe,YAAY,CAAC,CAAA;AAgBlE,SAAS,SAAS,KAAA,EAA+B;AAC/C,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAWO,SAAS,SAAS,IAAA,EAAuC;AAC9D,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,YAAA,CAAa,gBAAgB,sBAAsB,CAAA;AAAA,EAC5D;AACA,EAAA,IACE,CAAC,QAAA,CAAS,MAAM,CAAA,IAChB,OAAO,MAAA,CAAO,EAAA,KAAO,QAAA,IACrB,OAAO,MAAA,CAAO,IAAA,KAAS,QAAA,IACvB,OAAO,MAAA,CAAO,UAAA,KAAe,QAAA,IAC7B,OAAO,MAAA,CAAO,WAAA,KAAgB,QAAA,IAC9B,CAAC,YAAA,CAAa,GAAA,CAAI,MAAA,CAAO,WAAW,CAAA,IACpC,CAAC,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA,EACrB;AACA,IAAA,OAAO,YAAA;AAAA,MACL,cAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,QAAA,GAAW,MAAA;AACjB,EAAA,IAAI,MAAA,CAAO,KAAK,IAAA,KAAS,IAAA,SAAa,EAAE,IAAA,EAAM,QAAQ,QAAA,EAAS;AAC/D,EAAA,IAAI,CAAC,KAAA,CAAM,GAAA,CAAI,MAAA,CAAO,IAAI,GAAG,OAAO,EAAE,IAAA,EAAM,cAAA,EAAgB,QAAA,EAAS;AACrE,EAAA,MAAM,QAAA,GAAW,qBAAA,CAAsB,MAAA,CAAO,IAAI,CAAA;AAClD,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,EAAE,eAAA,EAAiB,QAAA,EAAU,MAAA,EAAQ,IAAA,KAAS,MAAA,CAAO,IAAA;AAC3D,IAAA,IACE,OAAO,oBAAoB,QAAA,IAC3B,CAAC,OAAO,aAAA,CAAc,QAAQ,KAC7B,QAAA,GAAsB,CAAA,IACvB,OAAO,MAAA,KAAW,QAAA,IAClB,CAAC,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,IACzB,OAAO,SAAS,QAAA,EAChB;AACA,MAAA,OAAO,YAAA;AAAA,QACL,cAAA;AAAA,QACA,KAAK,MAAA,CAAO,IAAI,0GAA0G,QAAA,CAAS,IAAA,CAAK,MAAM,CAAC,CAAA;AAAA,OACjJ;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,MAAA,EAAuB;AACxD;;;ACpIO,IAAM,iBAAA,GAAoB,GAAA;AAEjC,IAAM,MAAA,GAAS,UAAA;AACf,IAAM,EAAA,GAAK,gBAAA;AAgBJ,SAAS,qBACd,MAAA,EACgC;AAChC,EAAA,MAAM,KAAA,GAAQ,OACX,KAAA,CAAM,GAAG,EACT,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,EAAM,EACnB,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,GAAG,CAAC,CAAA,CAC7B,GAAA,CAAI,CAAC,CAAA,KAAM;AACV,IAAA,MAAM,EAAA,GAAK,CAAA,CAAE,OAAA,CAAQ,GAAG,CAAA;AACxB,IAAA,OAAO,CAAC,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,GAAG,CAAA,CAAE,KAAA,CAAM,EAAA,GAAK,CAAC,CAAC,CAAA;AAAA,EACzC,CAAC,CAAA;AACH,EAAA,MAAM,aAAa,KAAA,CAAM,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA,KAAM,CAAA,KAAM,GAAG,CAAA,CAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AACpE,EAAA,MAAM,CAAC,CAAC,CAAA,GAAI,UAAA;AACZ,EAAA,IAAI,UAAA,CAAW,WAAW,CAAA,IAAK,CAAA,KAAM,UAAa,CAAC,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,EAAG;AACjE,IAAA,OAAO,YAAA;AAAA,MACL,WAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,UAAA,GAAa,MAChB,MAAA,CAAO,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM,CAAA,KAAM,IAAA,IAAQ,EAAA,CAAG,KAAK,CAAC,CAAC,CAAA,CAC3C,GAAA,CAAI,CAAC,GAAG,CAAC,CAAA,KAAM,UAAA,CAAW,CAAC,CAAC,CAAA;AAC/B,EAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,IAAA,OAAO,YAAA;AAAA,MACL,WAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,EAAE,SAAA,EAAW,MAAA,CAAO,CAAC,GAAG,UAAA,EAAW;AAC5C;AAQA,eAAsB,eAAe,OAAA,EAMJ;AAC/B,EAAA,MAAM,MAAA,GAAS,oBAAA,CAAqB,OAAA,CAAQ,MAAM,CAAA;AAClD,EAAA,IAAI,MAAA,IAAU,QAAQ,OAAO,MAAA;AAC7B,EAAA,MAAM,GAAA,GAAM,OAAO,IAAA,CAAK,KAAA,CAAM,QAAQ,GAAA,CAAI,OAAA,EAAQ,GAAI,GAAI,CAAC,CAAA;AAC3D,EAAA,MAAM,IAAA,GACJ,MAAM,MAAA,CAAO,SAAA,GAAY,MAAM,MAAA,CAAO,SAAA,GAAY,OAAO,SAAA,GAAY,GAAA;AACvE,EAAA,IAAI,IAAA,GAAO,MAAA,CAAO,OAAA,CAAQ,gBAAgB,CAAA,EAAG;AAC3C,IAAA,OAAO,YAAA;AAAA,MACL,OAAA;AAAA,MACA,CAAA,iBAAA,EAAoB,IAAI,CAAA,2BAAA,EAA8B,OAAA,CAAQ,gBAAgB,CAAA,aAAA;AAAA,KAChF;AAAA,EACF;AACA,EAAA,MAAM,OAAA,GAAU,OAAO,IAAA,CAAK,CAAA,EAAG,OAAO,SAAS,CAAA,CAAA,CAAG,CAAA,EAAG,OAAA,CAAQ,IAAI,CAAA;AACjE,EAAA,MAAM,MAAA,GAAS,MAAM,YAAA,EAAa;AAClC,EAAA,KAAA,MAAW,MAAA,IAAU,QAAQ,OAAA,EAAS;AACpC,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,MAAA,EAAQ,QAAQ,CAAA;AAC1C,IAAA,KAAA,MAAW,SAAA,IAAa,OAAO,UAAA,EAAY;AAEzC,MAAA,IACE,MAAM,MAAA,CAAO,MAAA;AAAA,QACX,MAAA;AAAA,QACA,GAAA;AAAA,QACA,SAAA;AAAA,QACA;AAAA,OACF;AAEA,QAAA,OAAO,IAAA;AAAA,IACX;AAAA,EACF;AACA,EAAA,OAAO,YAAA;AAAA,IACL,UAAA;AAAA,IACA;AAAA,GACF;AACF;;;AChEA,eAAsB,cACpB,OAAA,EAC8B;AAC9B,EAAA,MAAM,OAAA,GAAU,cAAA,CAAe,OAAA,CAAQ,MAAM,CAAA;AAC7C,EAAA,MAAM,gBAAA,GAAmB,cAAA,CAAe,OAAA,CAAQ,gBAAgB,CAAA;AAChE,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,MAAM,CAAA;AAC1C,EAAA,IAAI,WAAW,MAAA,EAAW;AACxB,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,KAAA,EAAO,YAAA,CAAa,SAAA,EAAW,+BAA+B;AAAA,KAChE;AAAA,EACF;AACA,EAAA,IAAI,WAAW,IAAA,EAAM;AACnB,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,KAAA,EAAO,YAAA;AAAA,QACL,WAAA;AAAA,QACA;AAAA;AACF,KACF;AAAA,EACF;AACA,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA;AACpC,EAAA,MAAM,OAAA,GAAU,MAAM,cAAA,CAAe;AAAA,IACnC,IAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,GAAA,EAAK,OAAA,CAAQ,GAAA,oBAAO,IAAI,IAAA,EAAK;AAAA,IAC7B;AAAA,GACD,CAAA;AACD,EAAA,IAAI,SAAS,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,OAAO,OAAA,EAAQ;AAChD,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI;AACF,IAAA,IAAA,GAAO,IAAI,YAAY,OAAA,EAAS,EAAE,OAAO,IAAA,EAAM,CAAA,CAAE,MAAA,CAAO,IAAI,CAAA;AAAA,EAC9D,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,KAAA,EAAO,YAAA,CAAa,cAAA,EAAgB,uBAAuB;AAAA,KAC7D;AAAA,EACF;AACA,EAAA,MAAM,IAAA,GAAO,SAAS,IAAI,CAAA;AAC1B,EAAA,OAAO,MAAA,IAAU,IAAA,GAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,IAAA,EAAK,GAAI,EAAE,EAAA,EAAI,IAAA,EAAM,GAAG,IAAA,EAAK;AAC3E;AAGA,SAAS,aACP,MAAA,EAC2B;AAC3B,EAAA,IAAI,MAAA,KAAW,IAAA,IAAQ,MAAA,KAAW,MAAA,EAAW,OAAO,MAAA;AACpD,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,EAAU,OAAO,MAAA,KAAW,KAAK,MAAA,GAAY,MAAA;AACnE,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA;AAChC,EAAA,OAAO,MAAA,CAAO,MAAA,KAAW,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA,GAAI,IAAA;AAC3C;AAGO,SAAS,eAAe,MAAA,EAA2B;AACxD,EAAA,MAAM,IAAA,GACJ,OAAO,MAAA,KAAW,QAAA,GACd,CAAC,MAAM,CAAA,GACP,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAClB,CAAC,GAAG,MAAM,IACV,EAAC;AACT,EAAA,IACE,IAAA,CAAK,MAAA,KAAW,CAAA,IAChB,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,OAAO,CAAA,KAAM,QAAA,IAAY,CAAA,KAAM,EAAE,CAAA,EAClD;AACA,IAAA,MAAM,IAAI,SAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAMO,SAAS,eAAe,OAAA,EAAqC;AAClE,EAAA,IAAI,OAAA,KAAY,QAAW,OAAO,iBAAA;AAClC,EAAA,IACE,CAAC,OAAO,aAAA,CAAc,OAAO,KAC7B,OAAA,GAAU,CAAA,IACV,UAAU,iBAAA,EACV;AACA,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,CAAA,6DAAA,EAAgE,iBAAiB,CAAA,MAAA,EAAS,OAAO,CAAA;AAAA,KACnG;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;;;ACxFO,IAAM,iBAAiB,IAAA,GAAO,IAAA;AACrC,IAAM,WAAW,IAAA,CAAK,SAAA,CAAU,EAAE,QAAA,EAAU,MAAM,CAAA;AAE3C,SAAS,QAAA,GAA4B;AAC1C,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,GAAA;AAAA,IACR,IAAA,EAAM,KAAK,SAAA,CAAU,EAAE,OAAO,EAAE,IAAA,EAAM,gBAAA,EAAiB,EAAG;AAAA,GAC5D;AACF;AAEO,SAAS,WAAW,IAAA,EAAiD;AAC1E,EAAA,OAAO,OAAO,IAAA,KAAS,QAAA,GACnB,IAAI,WAAA,GAAc,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA,GAC/B,IAAA,CAAK,UAAA;AACX;AAEA,eAAsB,aAAA,CACpB,OAAA,EACA,OAAA,EACA,MAAA,EAC0B;AAC1B,EAAA,IAAI,UAAA,CAAW,OAAO,CAAA,IAAK,OAAA,CAAQ,gBAAgB,cAAA,CAAA,EAAiB;AAClE,IAAA,OAAO,QAAA,EAAS;AAAA,EAClB;AACA,EAAA,MAAM,MAAA,GAAS,MAAM,aAAA,CAAc;AAAA,IACjC,OAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,GAAI,QAAQ,GAAA,GAAM,EAAE,KAAK,OAAA,CAAQ,GAAA,EAAI,EAAE,GAAI,EAAC;AAAA,IAC5C,GAAI,QAAQ,gBAAA,KAAqB,MAAA,GAC7B,EAAC,GACD,EAAE,gBAAA,EAAkB,OAAA,CAAQ,gBAAA;AAAiB,GAClD,CAAA;AACD,EAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,IAAA,MAAM,OAAA,CAAQ,UAAA,GAAa,MAAA,CAAO,KAAK,CAAA;AACvC,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,GAAA;AAAA,MACR,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,CAAO,KAAA,CAAM,IAAA,EAAK,EAAG;AAAA,KAC7D;AAAA,EACF;AACA,EAAA,IAAI,OAAO,IAAA,KAAS,OAAA,QAAe,OAAA,CAAQ,OAAA,CAAQ,OAAO,KAAK,CAAA;AAAA,OAAA,IACtD,OAAO,IAAA,KAAS,MAAA,QAAc,OAAA,CAAQ,MAAA,GAAS,OAAO,QAAQ,CAAA;AAAA,OAClE,MAAM,OAAA,CAAQ,cAAA,GAAiB,MAAA,CAAO,QAAQ,CAAA;AACnD,EAAA,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,IAAA,EAAM,QAAA,EAAS;AACvC;AAsBO,SAAS,oBAAoB,OAAA,EAAsC;AACxE,EAAA,cAAA,CAAe,QAAQ,MAAM,CAAA;AAC7B,EAAA,cAAA,CAAe,QAAQ,gBAAgB,CAAA;AACvC,EAAA,IAAI,OAAO,OAAA,CAAQ,OAAA,KAAY,UAAA,EAAY;AACzC,IAAA,MAAM,IAAI,SAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,MAAM,OAAA,CAAQ,YAAA;AACpB,EAAA,IAAI,GAAA,KAAQ,WAAc,CAAC,MAAA,CAAO,cAAc,GAAG,CAAA,IAAK,OAAO,CAAA,CAAA,EAAI;AACjE,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,8DAA8D,GAAG,CAAA;AAAA,KACnE;AAAA,EACF;AACF;;;ACrEO,SAAS,uBACd,OAAA,EACiD;AACjD,EAAA,mBAAA,CAAoB,OAAO,CAAA;AAC3B,EAAA,OAAO,OAAO,OAAA,KAAY;AAExB,IAAA,OAAA,CAAQ,wBAAwB,kBAAkB,CAAA;AAClD,IAAA,OAAA,CAAQ,oBAAA;AAAA,MACN,kBAAA;AAAA,MACA,EAAE,OAAA,EAAS,QAAA,EAAU,SAAA,EAAW,OAAA,CAAQ,gBAAgB,cAAA,EAAe;AAAA,MACvE,CAAC,QAAA,EAAU,IAAA,EAAM,IAAA,KAAS,IAAA,CAAK,MAAM,IAAI;AAAA,KAC3C;AACA,IAAA,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,OAAO,SAAS,KAAA,KAAU;AACnD,MAAA,MAAM,IAAA,GACJ,QAAQ,IAAA,YAAgB,UAAA,GAAa,QAAQ,IAAA,GAAO,IAAI,WAAW,CAAC,CAAA;AACtE,MAAA,MAAM,WAAW,MAAM,aAAA;AAAA,QACrB,OAAA;AAAA,QACA,IAAA;AAAA,QACA,OAAA,CAAQ,QAAQ,qBAAqB;AAAA,OACvC;AACA,MAAA,OAAO,KAAA,CACJ,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,CACpB,KAAK,kBAAkB,CAAA,CACvB,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA;AAAA,IACvB,CAAC,CAAA;AAAA,EACH,CAAA;AACF;AAyCO,SAAS,qBAAqB,OAAA,EAImB;AACtD,EAAA,OAAO,OAAO,OAAA,KAAY;AACxB,IAAA,OAAA,CAAQ,wBAAwB,kBAAkB,CAAA;AAClD,IAAA,OAAA,CAAQ,oBAAA;AAAA,MACN,kBAAA;AAAA,MACA,EAAE,OAAA,EAAS,QAAA,EAAU,SAAA,EAAW,OAAA,CAAQ,gBAAgB,cAAA,EAAe;AAAA,MACvE,CAAC,QAAA,EAAU,IAAA,EAAM,IAAA,KAAS,IAAA,CAAK,MAAM,IAAI;AAAA,KAC3C;AACA,IAAA,OAAA,CAAQ,KAAA,CAAM;AAAA,MACZ,QAAQ,CAAC,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,SAAS,QAAQ,CAAA;AAAA,MAChD,KAAK,CAAA,EAAG,OAAA,CAAQ,SAAS,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,MAC5C,OAAA,EAAS,OAAO,OAAA,EAAS,KAAA,KAAU;AACjC,QAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,QAAA,CAAS,MAAA,CAAO;AAAA,UAC7C,QAAQ,OAAA,CAAQ,MAAA;AAAA,UAChB,QAAQ,OAAA,CAAQ,GAAA;AAAA,UAChB,MAAA,EAAQ,CAAC,IAAA,KAAS;AAChB,YAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,aAAa,CAAA;AAChD,YAAA,OAAO,MAAM,OAAA,CAAQ,KAAK,IACtB,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,GACd,KAAA;AAAA,UACP,CAAA;AAAA,UACA,IAAA,EAAM,OAAA,CAAQ,IAAA,YAAgB,UAAA,GAAa,QAAQ,IAAA,GAAO;AAAA,SAC3D,CAAA;AACD,QAAA,OAAO,KAAA,CACJ,IAAA,CAAK,QAAA,CAAS,MAAM,EACpB,OAAA,CAAQ,EAAE,GAAG,QAAA,CAAS,OAAA,EAAS,CAAA,CAC/B,IAAA,CAAK,SAAS,IAAI,CAAA;AAAA,MACvB;AAAA,KACD,CAAA;AAAA,EACH,CAAA;AACF","file":"fastify.cjs","sourcesContent":["// Web Crypto wherever the package runs. Node 20+, AWS Lambda, edge runtimes\n// and React Native expose it as `globalThis.crypto`; Node 18 has the same API\n// but only as `webcrypto` in its crypto module, so on Node 18 it is loaded\n// from there, once.\n\n/** No Web Crypto in this runtime: the package cannot verify anything here. */\nexport class WebCryptoUnavailableError extends Error {\n constructor() {\n super(\n 'Web Crypto (crypto.subtle) is not available in this runtime; @vinaverse/server needs Node 18+, an edge runtime or another runtime with Web Crypto',\n );\n this.name = 'WebCryptoUnavailableError';\n }\n}\n\ntype MaybeNode = { process?: { versions?: { node?: unknown } } };\n\nlet fromNode: Promise<Crypto> | undefined;\n\n/** The runtime's Web Crypto: `crypto.subtle` and `crypto.getRandomValues`. */\nexport function webCrypto(): Promise<Crypto> {\n const global = (globalThis as { crypto?: Crypto }).crypto;\n if (global?.subtle) return Promise.resolve(global);\n fromNode ??= nodeWebCrypto();\n return fromNode;\n}\n\nexport async function subtleCrypto(): Promise<SubtleCrypto> {\n return (await webCrypto()).subtle;\n}\n\nasync function nodeWebCrypto(): Promise<Crypto> {\n if (typeof (globalThis as MaybeNode).process?.versions?.node !== 'string') {\n throw new WebCryptoUnavailableError();\n }\n // Built at run time on purpose: bundlers for edge runtimes and React Native,\n // where this line never runs, must not try to resolve a Node module.\n const specifier = ['node', 'crypto'].join(':');\n const mod = (await import(\n /* webpackIgnore: true */ /* @vite-ignore */ specifier\n )) as { webcrypto?: Crypto };\n if (!mod.webcrypto?.subtle) throw new WebCryptoUnavailableError();\n return mod.webcrypto;\n}\n","// Byte helpers on Web APIs only (no Buffer), shared by the signature checks.\nimport { subtleCrypto } from '../webhooks/crypto.js';\n\nconst encoder = new TextEncoder();\n\nexport function utf8(text: string): Uint8Array<ArrayBuffer> {\n return encoder.encode(text);\n}\n\nexport function toBytes(body: string | Uint8Array | ArrayBuffer): Uint8Array {\n if (typeof body === 'string') return utf8(body);\n if (body instanceof Uint8Array) return body;\n return new Uint8Array(body);\n}\n\nexport function hexToBytes(hex: string): Uint8Array<ArrayBuffer> {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n\nexport function bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport async function sha256Hex(data: Uint8Array): Promise<string> {\n const subtle = await subtleCrypto();\n return bytesToHex(new Uint8Array(await subtle.digest('SHA-256', copy(data))));\n}\n\nexport async function hmacKey(\n secret: string,\n usage: 'sign' | 'verify',\n): Promise<CryptoKey> {\n const subtle = await subtleCrypto();\n return subtle.importKey(\n 'raw',\n utf8(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n [usage],\n );\n}\n\n// Web Crypto takes an ArrayBuffer-backed view; a Node Buffer may sit on a\n// shared pool, so a view is copied when it does not own its whole buffer.\nexport function copy(data: Uint8Array): Uint8Array<ArrayBuffer> {\n const out = new Uint8Array(data.length);\n out.set(data);\n return out;\n}\n\nexport function concat(...parts: Uint8Array[]): Uint8Array<ArrayBuffer> {\n const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));\n let at = 0;\n for (const p of parts) {\n out.set(p, at);\n at += p.length;\n }\n return out;\n}\n","/**\n * Why a delivery was refused, in the conformance vectors' words\n * (vinaverse-contracts `conformance/webhooks.json`). Every one is answered with\n * `400`, so Vinaverse retries it; none of them is ever a valid delivery.\n *\n * - `missing`: no `Vinaverse-Signature` header.\n * - `malformed`: the header is not `t=<unix seconds>,v1=<hex>[,v1=<hex>]`\n * (no single timestamp, or no `v1` signature).\n * - `stale`: signed more than the tolerance (300 seconds) before or after now:\n * a replay, or a clock far off.\n * - `mismatch`: no `v1` signature matches the body with any secret: a wrong\n * secret (or the secret without its `vv_whsec_` prefix), a changed body, or a\n * body parsed and serialized again.\n * - `invalid_body`: signed by Vinaverse, but not a webhook envelope this\n * package can read.\n */\nexport type WebhookErrorCode =\n | 'missing'\n | 'malformed'\n | 'stale'\n | 'mismatch'\n | 'invalid_body';\n\nexport interface WebhookError {\n readonly code: WebhookErrorCode;\n readonly message: string;\n}\n\nexport function webhookError(\n code: WebhookErrorCode,\n message: string,\n): WebhookError {\n return { code, message };\n}\n","// The webhook events, typed from the pinned contract (vinaverse-v1.yaml,\n// `webhooks`), and the reading of a verified body into one of them.\nimport type { components, webhooks } from '../generated/vinaverse-v1.js';\nimport { type WebhookError, webhookError } from './errors.js';\n\ntype Schemas = components['schemas'];\n\n/** The envelope every webhook body has. `data` depends on `type`. */\nexport type WebhookEnvelope = Schemas['WebhookEnvelope'];\n\n/** Every event type the targeted contract defines. */\nexport type WebhookEventType = WebhookEnvelope['type'] & keyof webhooks;\n\ntype BodyOf<T extends WebhookEventType> = NonNullable<\n webhooks[T]['post']['requestBody']\n>['content']['application/json'];\n\n/** One event of type `T`: the envelope, with `data` as the contract says. */\nexport type WebhookEventOf<T extends WebhookEventType> = Omit<\n WebhookEnvelope,\n 'type' | 'data'\n> & {\n type: T;\n data: NonNullable<BodyOf<T>['data']>;\n};\n\n/** Any event this package version knows; narrow it on `type`. */\nexport type WebhookEvent = {\n [T in WebhookEventType]: WebhookEventOf<T>;\n}[WebhookEventType];\n\nexport type ConversationEventType =\n | 'conversation.updated'\n | 'conversation.completed'\n | 'conversation.failed'\n | 'conversation.expired';\n\n/** A guided-flow conversation's state changed: `data` is its `ConversationState`. */\nexport type ConversationEvent = {\n [T in ConversationEventType]: WebhookEventOf<T>;\n}[ConversationEventType];\n\nexport type ConversationState = Schemas['ConversationState'];\n\n// Kept in step with the contract by a test that reads the pinned YAML.\nexport const WEBHOOK_EVENT_TYPES: readonly WebhookEventType[] = [\n 'moderation.item.completed',\n 'moderation.job.completed',\n 'document_check.completed',\n 'session.completed',\n 'subject.erased',\n 'usage.threshold_reached',\n 'feature.state_changed',\n 'media.job.queued',\n 'media.job.progress',\n 'media.job.step.completed',\n 'media.job.completed',\n 'media.job.failed',\n 'media.job.cancelled',\n 'conversation.updated',\n 'conversation.completed',\n 'conversation.failed',\n 'conversation.expired',\n];\n\nconst KNOWN = new Set<string>(WEBHOOK_EVENT_TYPES);\n\n// The statuses each conversation event may carry (vinaverse-v1.yaml, webhooks).\nconst CONVERSATION_STATUSES: Readonly<Record<string, readonly string[]>> = {\n 'conversation.updated': ['in_progress', 'failed'],\n 'conversation.completed': ['completed'],\n 'conversation.failed': ['failed'],\n 'conversation.expired': ['expired'],\n};\nconst ENVIRONMENTS = new Set(['mock', 'development', 'production']);\n\nexport function isConversationEvent(\n event: WebhookEvent,\n): event is ConversationEvent {\n return event.type.startsWith('conversation.');\n}\n\n/** What a verified body is: a known event, a test delivery, or a type newer than this package. */\nexport type ReadBody =\n | { kind: 'event'; event: WebhookEvent }\n | { kind: 'test'; envelope: WebhookEnvelope }\n | { kind: 'unrecognized'; envelope: WebhookEnvelope };\n\ntype Json = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is Json {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Reads a verified body: it must be the webhook envelope (`id`, `type`,\n * `created_at`, `environment`, `data`). A test delivery from Studio carries\n * `data: { test: true }` and no payload. A conversation event must carry a\n * guided flow's state with the status its type allows and the fields its\n * ordering depends on (`conversation_id`, `sequence`); other payloads are as\n * Vinaverse signed them. A type newer than this package is `unrecognized`,\n * never an error: answer it 2xx and upgrade.\n */\nexport function readBody(text: string): ReadBody | WebhookError {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return webhookError('invalid_body', 'the body is not JSON');\n }\n if (\n !isObject(parsed) ||\n typeof parsed.id !== 'string' ||\n typeof parsed.type !== 'string' ||\n typeof parsed.created_at !== 'string' ||\n typeof parsed.environment !== 'string' ||\n !ENVIRONMENTS.has(parsed.environment) ||\n !isObject(parsed.data)\n ) {\n return webhookError(\n 'invalid_body',\n 'the body is not a webhook envelope (id, type, created_at, environment, data)',\n );\n }\n const envelope = parsed as WebhookEnvelope;\n if (parsed.data.test === true) return { kind: 'test', envelope };\n if (!KNOWN.has(parsed.type)) return { kind: 'unrecognized', envelope };\n const statuses = CONVERSATION_STATUSES[parsed.type];\n if (statuses) {\n const { conversation_id, sequence, status, flow } = parsed.data;\n if (\n typeof conversation_id !== 'string' ||\n !Number.isSafeInteger(sequence) ||\n (sequence as number) < 1 ||\n typeof status !== 'string' ||\n !statuses.includes(status) ||\n typeof flow !== 'string'\n ) {\n return webhookError(\n 'invalid_body',\n `a ${parsed.type} event carries a guided flow's state: conversation_id, a positive integer sequence, a flow, and status ${statuses.join(' or ')}`,\n );\n }\n }\n return { kind: 'event', event: parsed as WebhookEvent };\n}\n","// The webhook signature, exactly as the Vinaverse API signs and checks it\n// (contract: docs/conversation-state.md §1, \"Delivery\"):\n// Vinaverse-Signature: t=<unix seconds>,v1=<hex>[,v1=<hex>]\n// `v1` is the lowercase hex HMAC-SHA256, keyed with the endpoint's whole\n// signing secret as UTF-8 (the `vv_whsec_` prefix included, never decoded),\n// over `<t>.` followed by the raw request body. During a secret rotation the\n// header carries one `v1` per valid secret. A signature more than 300 seconds\n// from now, either way, is refused.\nimport { concat, hexToBytes, hmacKey, utf8 } from '../internal/bytes.js';\nimport { subtleCrypto } from './crypto.js';\nimport { type WebhookError, webhookError } from './errors.js';\n\nexport const SIGNATURE_HEADER = 'Vinaverse-Signature';\nexport const TOLERANCE_SECONDS = 300;\n\nconst DIGITS = /^[0-9]+$/;\nconst V1 = /^[0-9a-f]{64}$/;\n\nexport interface ParsedSignature {\n /** The signed timestamp, in Unix seconds. */\n readonly timestamp: bigint;\n /** Each `v1` signature, decoded. */\n readonly signatures: readonly Uint8Array[];\n}\n\n/**\n * Reads the header the way the API's own verifier does: comma-separated\n * `key=value` parts (a space around a part changes nothing), exactly one `t`\n * of digits, and the `v1` values. A `v1`\n * that is not 64 lowercase hex digits can never match, so it is left out; a\n * header left with no `v1` is malformed.\n */\nexport function parseSignatureHeader(\n header: string,\n): ParsedSignature | WebhookError {\n const parts = header\n .split(',')\n .map((p) => p.trim())\n .filter((p) => p.includes('='))\n .map((p) => {\n const at = p.indexOf('=');\n return [p.slice(0, at), p.slice(at + 1)] as const;\n });\n const timestamps = parts.filter(([k]) => k === 't').map(([, v]) => v);\n const [t] = timestamps;\n if (timestamps.length !== 1 || t === undefined || !DIGITS.test(t)) {\n return webhookError(\n 'malformed',\n 'the signature header needs exactly one timestamp, t=<unix seconds>',\n );\n }\n const signatures = parts\n .filter(([k, v]) => k === 'v1' && V1.test(v))\n .map(([, v]) => hexToBytes(v));\n if (signatures.length === 0) {\n return webhookError(\n 'malformed',\n 'the signature header has no v1 signature',\n );\n }\n return { timestamp: BigInt(t), signatures };\n}\n\n/**\n * Checks a delivery's signature: the header's form, its timestamp against\n * `now`, then each `v1` against each secret, in constant time (Web Crypto's\n * `verify`). `secrets` is more than one while the receiver rolls a new secret\n * out. Returns null when the signature is valid.\n */\nexport async function checkSignature(options: {\n body: Uint8Array;\n header: string;\n secrets: readonly string[];\n now: Date;\n toleranceSeconds: number;\n}): Promise<WebhookError | null> {\n const parsed = parseSignatureHeader(options.header);\n if ('code' in parsed) return parsed;\n const now = BigInt(Math.floor(options.now.getTime() / 1000));\n const skew =\n now > parsed.timestamp ? now - parsed.timestamp : parsed.timestamp - now;\n if (skew > BigInt(options.toleranceSeconds)) {\n return webhookError(\n 'stale',\n `the signature is ${skew} seconds from now; at most ${options.toleranceSeconds} are accepted`,\n );\n }\n const message = concat(utf8(`${parsed.timestamp}.`), options.body);\n const subtle = await subtleCrypto();\n for (const secret of options.secrets) {\n const key = await hmacKey(secret, 'verify');\n for (const signature of parsed.signatures) {\n // Every signature is decoded into its own ArrayBuffer (hexToBytes).\n if (\n await subtle.verify(\n 'HMAC',\n key,\n signature as Uint8Array<ArrayBuffer>,\n message,\n )\n )\n return null;\n }\n }\n return webhookError(\n 'mismatch',\n 'no signature matches the body with the signing secret: check the secret, and verify the raw body exactly as received',\n );\n}\n","import { toBytes } from '../internal/bytes.js';\nimport { type WebhookError, webhookError } from './errors.js';\nimport { type ReadBody, readBody } from './events.js';\nimport { checkSignature, TOLERANCE_SECONDS } from './signature.js';\n\nexport interface VerifyWebhookOptions {\n /**\n * The request body exactly as received, before any JSON parsing: a string,\n * or bytes (`Uint8Array`, which a Node `Buffer` is, or `ArrayBuffer`). A\n * body parsed and serialized again never verifies.\n */\n rawBody: string | Uint8Array | ArrayBuffer;\n /** The `Vinaverse-Signature` header (a framework's string array is accepted). */\n header: string | readonly string[] | null | undefined;\n /**\n * The endpoint's signing secret (`vv_whsec_…`), whole. Pass two while you\n * roll a new one out: a delivery verifies if either matches.\n */\n secret: string | readonly string[];\n /** The time to check the signature's timestamp against; default now. */\n now?: Date;\n /** How far from `now` a signature may be: whole seconds, 0 to 300 (the contract's window, the default). */\n toleranceSeconds?: number;\n}\n\nexport type VerifyWebhookResult =\n | ({ ok: true } & ReadBody)\n | { ok: false; error: WebhookError };\n\n/**\n * Verifies a Vinaverse webhook delivery and reads it, in one call:\n *\n * - `{ ok: true, kind: 'event', event }`: a verified event; narrow it on `event.type`.\n * - `{ ok: true, kind: 'test', envelope }`: a test delivery from Studio; answer 2xx, do nothing.\n * - `{ ok: true, kind: 'unrecognized', envelope }`: verified, but a type newer than this\n * package; answer 2xx, and upgrade to handle it.\n * - `{ ok: false, error }`: refused (`error.code` says why); answer 400.\n *\n * Deliveries are at least once: deduplicate on `event.id`, and order a\n * conversation's states with `sequenceVerdict` or `applyConversationState`.\n * It never throws for a bad delivery; it throws a `TypeError` for a missing\n * secret or a tolerance that is not a whole number of seconds from 0 to 300,\n * and `WebCryptoUnavailableError` in a runtime without Web Crypto.\n */\nexport async function verifyWebhook(\n options: VerifyWebhookOptions,\n): Promise<VerifyWebhookResult> {\n const secrets = signingSecrets(options.secret);\n const toleranceSeconds = checkTolerance(options.toleranceSeconds);\n const header = singleHeader(options.header);\n if (header === undefined) {\n return {\n ok: false,\n error: webhookError('missing', 'no Vinaverse-Signature header'),\n };\n }\n if (header === null) {\n return {\n ok: false,\n error: webhookError(\n 'malformed',\n 'more than one Vinaverse-Signature header',\n ),\n };\n }\n const body = toBytes(options.rawBody);\n const refused = await checkSignature({\n body,\n header,\n secrets,\n now: options.now ?? new Date(),\n toleranceSeconds,\n });\n if (refused) return { ok: false, error: refused };\n let text: string;\n try {\n text = new TextDecoder('utf-8', { fatal: true }).decode(body);\n } catch {\n return {\n ok: false,\n error: webhookError('invalid_body', 'the body is not UTF-8'),\n };\n }\n const read = readBody(text);\n return 'code' in read ? { ok: false, error: read } : { ok: true, ...read };\n}\n\n// undefined: absent; null: more than one; otherwise the header.\nfunction singleHeader(\n header: string | readonly string[] | null | undefined,\n): string | null | undefined {\n if (header === null || header === undefined) return undefined;\n if (typeof header === 'string') return header === '' ? undefined : header;\n if (header.length === 0) return undefined;\n return header.length === 1 ? header[0] : null;\n}\n\n/** The signing secrets, checked: a TypeError for none, an empty one, or an unset variable. */\nexport function signingSecrets(secret: unknown): string[] {\n const list =\n typeof secret === 'string'\n ? [secret]\n : Array.isArray(secret)\n ? [...secret]\n : [];\n if (\n list.length === 0 ||\n list.some((s) => typeof s !== 'string' || s === '')\n ) {\n throw new TypeError(\n 'the webhook signing secret is required (vv_whsec_…), and none was given',\n );\n }\n return list as string[];\n}\n\n/**\n * The tolerance, checked once: whole seconds from 0 to 300. A wider window is\n * a longer replay window than the contract allows.\n */\nexport function checkTolerance(seconds: number | undefined): number {\n if (seconds === undefined) return TOLERANCE_SECONDS;\n if (\n !Number.isSafeInteger(seconds) ||\n seconds < 0 ||\n seconds > TOLERANCE_SECONDS\n ) {\n throw new TypeError(\n `toleranceSeconds must be a whole number of seconds from 0 to ${TOLERANCE_SECONDS}, not ${seconds}`,\n );\n }\n return seconds;\n}\n","// What every adapter does with a delivery once it has the raw body and the\n// signature header: verify, call the tenant's handler for the result, and\n// answer. A refused delivery is answered 400 (Vinaverse retries, then gives\n// up); a handled one 200. A handler that throws is the adapter's to surface,\n// so the delivery fails and Vinaverse retries it.\nimport type { WebhookError } from '../webhooks/errors.js';\nimport type { WebhookEnvelope, WebhookEvent } from '../webhooks/events.js';\nimport {\n checkTolerance,\n signingSecrets,\n verifyWebhook,\n} from '../webhooks/verify.js';\n\nexport interface WebhookHandlerOptions {\n /** The endpoint's signing secret, or two while rolling a new one out. */\n secret: string | readonly string[];\n /**\n * Called once per verified delivery of a known event. Answer quickly (the\n * contract allows 5 seconds) and do the work afterwards; deliveries are at\n * least once, so deduplicate on `event.id`.\n */\n onEvent(event: WebhookEvent): void | Promise<void>;\n /** A test delivery from Studio; by default it is acknowledged and ignored. */\n onTest?(envelope: WebhookEnvelope): void | Promise<void>;\n /** A verified type newer than this package; by default acknowledged and ignored. */\n onUnrecognized?(envelope: WebhookEnvelope): void | Promise<void>;\n /** A refused delivery, for logging; it is answered 400 either way. */\n onRejected?(error: WebhookError): void | Promise<void>;\n /** How far from now a signature may be, in seconds; default 300. */\n toleranceSeconds?: number;\n /**\n * The largest body accepted, in bytes; default 1 MiB (a conversation\n * webhook is at most 64 KB). A larger one is answered 413 unread.\n */\n maxBodyBytes?: number;\n /** The clock; default the system clock. */\n now?: () => Date;\n}\n\nexport interface WebhookResponse {\n readonly status: 200 | 400 | 413;\n readonly body: string;\n}\n\nexport const MAX_BODY_BYTES = 1024 * 1024;\nconst RECEIVED = JSON.stringify({ received: true });\n\nexport function tooLarge(): WebhookResponse {\n return {\n status: 413,\n body: JSON.stringify({ error: { code: 'body_too_large' } }),\n };\n}\n\nexport function byteLength(body: string | Uint8Array | ArrayBuffer): number {\n return typeof body === 'string'\n ? new TextEncoder().encode(body).length\n : body.byteLength;\n}\n\nexport async function handleWebhook(\n options: WebhookHandlerOptions,\n rawBody: string | Uint8Array | ArrayBuffer,\n header: string | readonly string[] | null | undefined,\n): Promise<WebhookResponse> {\n if (byteLength(rawBody) > (options.maxBodyBytes ?? MAX_BODY_BYTES)) {\n return tooLarge();\n }\n const result = await verifyWebhook({\n rawBody,\n header,\n secret: options.secret,\n ...(options.now ? { now: options.now() } : {}),\n ...(options.toleranceSeconds === undefined\n ? {}\n : { toleranceSeconds: options.toleranceSeconds }),\n });\n if (!result.ok) {\n await options.onRejected?.(result.error);\n return {\n status: 400,\n body: JSON.stringify({ error: { code: result.error.code } }),\n };\n }\n if (result.kind === 'event') await options.onEvent(result.event);\n else if (result.kind === 'test') await options.onTest?.(result.envelope);\n else await options.onUnrecognized?.(result.envelope);\n return { status: 200, body: RECEIVED };\n}\n\n/** A header's value from a record whose names may have any case. */\nexport function headerValue(\n headers:\n | Readonly<Record<string, string | readonly string[] | undefined>>\n | null\n | undefined,\n name: string,\n): string | readonly string[] | undefined {\n if (!headers) return undefined;\n const wanted = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === wanted && value !== undefined) return value;\n }\n return undefined;\n}\n\n/**\n * Checks a handler's options once, when the adapter is made, so a wrong value\n * fails at start-up instead of answering every delivery with an error.\n */\nexport function checkHandlerOptions(options: WebhookHandlerOptions): void {\n signingSecrets(options.secret);\n checkTolerance(options.toleranceSeconds);\n if (typeof options.onEvent !== 'function') {\n throw new TypeError(\n 'onEvent is required: it receives every verified event',\n );\n }\n const max = options.maxBodyBytes;\n if (max !== undefined && (!Number.isSafeInteger(max) || max <= 0)) {\n throw new TypeError(\n `maxBodyBytes must be a positive whole number of bytes, not ${max}`,\n );\n }\n}\n","// Fastify (4 and 5): a plugin that adds the webhook route.\n//\n// await app.register(vinaverseWebhookPlugin({ path: '/webhooks/vinaverse', secret, onEvent }));\n//\n// Fastify parses JSON bodies by default; inside this plugin only, the JSON\n// content type is read as raw bytes instead (plugins are encapsulated, so the\n// rest of the application keeps its parser).\nimport {\n checkHandlerOptions,\n handleWebhook,\n MAX_BODY_BYTES,\n type WebhookHandlerOptions,\n} from './handler.js';\n\nexport type { WebhookHandlerOptions } from './handler.js';\n\nimport type { ToolProvider } from '../tools/provider.js';\n\nexport interface FastifyWebhookOptions extends WebhookHandlerOptions {\n /** The route's path, for example `/webhooks/vinaverse`. */\n path: string;\n}\n\n/** The parts of a Fastify instance the plugin uses. */\nexport interface FastifyInstanceLike {\n removeContentTypeParser(contentType: string | string[]): unknown;\n addContentTypeParser(\n contentType: string | string[] | RegExp,\n options: { parseAs: 'buffer'; bodyLimit?: number },\n parser: (\n request: unknown,\n body: Uint8Array,\n done: (error: Error | null, body?: unknown) => void,\n ) => void,\n ): unknown;\n post(\n path: string,\n handler: (\n request: FastifyRequestLike,\n reply: FastifyReplyLike,\n ) => Promise<unknown>,\n ): unknown;\n}\n\nexport interface FastifyRequestLike {\n body: unknown;\n headers: Readonly<Record<string, string | readonly string[] | undefined>>;\n}\n\nexport interface FastifyReplyLike {\n code(statusCode: number): FastifyReplyLike;\n type(contentType: string): FastifyReplyLike;\n send(payload: string): unknown;\n}\n\nexport function vinaverseWebhookPlugin(\n options: FastifyWebhookOptions,\n): (fastify: FastifyInstanceLike) => Promise<void> {\n checkHandlerOptions(options);\n return async (fastify) => {\n // Replaces Fastify's own JSON parser, in this plugin's scope only.\n fastify.removeContentTypeParser('application/json');\n fastify.addContentTypeParser(\n 'application/json',\n { parseAs: 'buffer', bodyLimit: options.maxBodyBytes ?? MAX_BODY_BYTES },\n (_request, body, done) => done(null, body),\n );\n fastify.post(options.path, async (request, reply) => {\n const body =\n request.body instanceof Uint8Array ? request.body : new Uint8Array(0);\n const response = await handleWebhook(\n options,\n body,\n request.headers['vinaverse-signature'],\n );\n return reply\n .code(response.status)\n .type('application/json')\n .send(response.body);\n });\n };\n}\n\n/** The parts of a Fastify instance the tools plugin uses. */\nexport interface FastifyToolInstanceLike {\n removeContentTypeParser(contentType: string | string[]): unknown;\n addContentTypeParser(\n contentType: string | string[] | RegExp,\n options: { parseAs: 'buffer'; bodyLimit?: number },\n parser: (\n request: unknown,\n body: Uint8Array,\n done: (error: Error | null, body?: unknown) => void,\n ) => void,\n ): unknown;\n route(options: {\n method: string[];\n url: string;\n handler: (\n request: FastifyToolRequestLike,\n reply: FastifyToolReplyLike,\n ) => Promise<unknown>;\n }): unknown;\n}\n\nexport interface FastifyToolRequestLike extends FastifyRequestLike {\n method: string;\n /** The request target as received. */\n url: string;\n}\n\nexport interface FastifyToolReplyLike {\n code(statusCode: number): FastifyToolReplyLike;\n headers(values: Record<string, string>): FastifyToolReplyLike;\n send(payload: string): unknown;\n}\n\n/**\n * A plugin serving a tenant's tools on every route below `basePath` (the\n * provider's): `await app.register(vinaverseToolsPlugin({ provider, basePath: '/api/v2/ai' }))`.\n * JSON bodies are read raw inside the plugin only.\n */\nexport function vinaverseToolsPlugin(options: {\n provider: ToolProvider;\n basePath: string;\n maxBodyBytes?: number;\n}): (fastify: FastifyToolInstanceLike) => Promise<void> {\n return async (fastify) => {\n fastify.removeContentTypeParser('application/json');\n fastify.addContentTypeParser(\n 'application/json',\n { parseAs: 'buffer', bodyLimit: options.maxBodyBytes ?? MAX_BODY_BYTES },\n (_request, body, done) => done(null, body),\n );\n fastify.route({\n method: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n url: `${options.basePath.replace(/\\/+$/, '')}/*`,\n handler: async (request, reply) => {\n const response = await options.provider.handle({\n method: request.method,\n target: request.url,\n header: (name) => {\n const value = request.headers[name.toLowerCase()];\n return Array.isArray(value)\n ? value.join(', ')\n : (value as string | undefined);\n },\n body: request.body instanceof Uint8Array ? request.body : null,\n });\n return reply\n .code(response.status)\n .headers({ ...response.headers })\n .send(response.body);\n },\n });\n };\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { W as WebhookHandlerOptions, T as ToolProvider } from './provider-Miaa9V7I.cjs';
|
|
2
|
+
import './vinaverse-v1.d-BVc4uyUd.cjs';
|
|
3
|
+
import './nonces-scwljZwl.cjs';
|
|
4
|
+
|
|
5
|
+
interface FastifyWebhookOptions extends WebhookHandlerOptions {
|
|
6
|
+
/** The route's path, for example `/webhooks/vinaverse`. */
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
/** The parts of a Fastify instance the plugin uses. */
|
|
10
|
+
interface FastifyInstanceLike {
|
|
11
|
+
removeContentTypeParser(contentType: string | string[]): unknown;
|
|
12
|
+
addContentTypeParser(contentType: string | string[] | RegExp, options: {
|
|
13
|
+
parseAs: 'buffer';
|
|
14
|
+
bodyLimit?: number;
|
|
15
|
+
}, parser: (request: unknown, body: Uint8Array, done: (error: Error | null, body?: unknown) => void) => void): unknown;
|
|
16
|
+
post(path: string, handler: (request: FastifyRequestLike, reply: FastifyReplyLike) => Promise<unknown>): unknown;
|
|
17
|
+
}
|
|
18
|
+
interface FastifyRequestLike {
|
|
19
|
+
body: unknown;
|
|
20
|
+
headers: Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
21
|
+
}
|
|
22
|
+
interface FastifyReplyLike {
|
|
23
|
+
code(statusCode: number): FastifyReplyLike;
|
|
24
|
+
type(contentType: string): FastifyReplyLike;
|
|
25
|
+
send(payload: string): unknown;
|
|
26
|
+
}
|
|
27
|
+
declare function vinaverseWebhookPlugin(options: FastifyWebhookOptions): (fastify: FastifyInstanceLike) => Promise<void>;
|
|
28
|
+
/** The parts of a Fastify instance the tools plugin uses. */
|
|
29
|
+
interface FastifyToolInstanceLike {
|
|
30
|
+
removeContentTypeParser(contentType: string | string[]): unknown;
|
|
31
|
+
addContentTypeParser(contentType: string | string[] | RegExp, options: {
|
|
32
|
+
parseAs: 'buffer';
|
|
33
|
+
bodyLimit?: number;
|
|
34
|
+
}, parser: (request: unknown, body: Uint8Array, done: (error: Error | null, body?: unknown) => void) => void): unknown;
|
|
35
|
+
route(options: {
|
|
36
|
+
method: string[];
|
|
37
|
+
url: string;
|
|
38
|
+
handler: (request: FastifyToolRequestLike, reply: FastifyToolReplyLike) => Promise<unknown>;
|
|
39
|
+
}): unknown;
|
|
40
|
+
}
|
|
41
|
+
interface FastifyToolRequestLike extends FastifyRequestLike {
|
|
42
|
+
method: string;
|
|
43
|
+
/** The request target as received. */
|
|
44
|
+
url: string;
|
|
45
|
+
}
|
|
46
|
+
interface FastifyToolReplyLike {
|
|
47
|
+
code(statusCode: number): FastifyToolReplyLike;
|
|
48
|
+
headers(values: Record<string, string>): FastifyToolReplyLike;
|
|
49
|
+
send(payload: string): unknown;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A plugin serving a tenant's tools on every route below `basePath` (the
|
|
53
|
+
* provider's): `await app.register(vinaverseToolsPlugin({ provider, basePath: '/api/v2/ai' }))`.
|
|
54
|
+
* JSON bodies are read raw inside the plugin only.
|
|
55
|
+
*/
|
|
56
|
+
declare function vinaverseToolsPlugin(options: {
|
|
57
|
+
provider: ToolProvider;
|
|
58
|
+
basePath: string;
|
|
59
|
+
maxBodyBytes?: number;
|
|
60
|
+
}): (fastify: FastifyToolInstanceLike) => Promise<void>;
|
|
61
|
+
|
|
62
|
+
export { type FastifyInstanceLike, type FastifyReplyLike, type FastifyRequestLike, type FastifyToolInstanceLike, type FastifyToolReplyLike, type FastifyToolRequestLike, type FastifyWebhookOptions, WebhookHandlerOptions, vinaverseToolsPlugin, vinaverseWebhookPlugin };
|