@labyrinthe/connect 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/LICENSE +21 -0
- package/README.fr.md +819 -0
- package/README.md +808 -2
- package/dist/express.cjs +87 -0
- package/dist/express.d.cts +49 -0
- package/dist/express.d.ts +49 -0
- package/dist/express.js +85 -0
- package/dist/index.cjs +1721 -0
- package/dist/index.d.cts +943 -0
- package/dist/index.d.ts +943 -0
- package/dist/index.js +1667 -0
- package/dist/next.cjs +48 -0
- package/dist/next.d.cts +31 -0
- package/dist/next.d.ts +31 -0
- package/dist/next.js +46 -0
- package/package.json +119 -4
package/dist/express.cjs
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var connect = require('@labyrinthe/connect');
|
|
4
|
+
|
|
5
|
+
// src/express.ts
|
|
6
|
+
function labyrintheWebhook(options = {}) {
|
|
7
|
+
return async (req, res, next) => {
|
|
8
|
+
let event;
|
|
9
|
+
try {
|
|
10
|
+
const secret = options.secret ?? readEnv("LABYRINTHE_WEBHOOK_SECRET");
|
|
11
|
+
if (secret === void 0 || secret === "") {
|
|
12
|
+
throw new Error("No webhook secret: pass `secret` or set LABYRINTHE_WEBHOOK_SECRET.");
|
|
13
|
+
}
|
|
14
|
+
const rawBody = await readRawBody(req);
|
|
15
|
+
const signature = header(req, "x-signature");
|
|
16
|
+
event = await connect.Webhooks.constructEvent(
|
|
17
|
+
rawBody,
|
|
18
|
+
signature,
|
|
19
|
+
secret,
|
|
20
|
+
options.toleranceSeconds === void 0 ? {} : { toleranceSeconds: options.toleranceSeconds }
|
|
21
|
+
);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error instanceof connect.SignatureVerificationError || error instanceof connect.InvalidWebhookPayloadError) {
|
|
24
|
+
send(res, 400, { error: { code: error.code, message: error.message } });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
next(error);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (options.onEvent === void 0) {
|
|
31
|
+
req.labyrintheEvent = event;
|
|
32
|
+
next();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await options.onEvent(event, req, res);
|
|
37
|
+
if (res.headersSent !== true) {
|
|
38
|
+
send(res, 200, { received: true });
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
next(error);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function readRawBody(req) {
|
|
46
|
+
const body = req.body;
|
|
47
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
48
|
+
return body;
|
|
49
|
+
}
|
|
50
|
+
if (body !== void 0 && body !== null && !(typeof body === "object" && Object.keys(body).length === 0)) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"The webhook body was already parsed (express.json()?): mount labyrintheWebhook() before it, or use express.raw({ type: 'application/json' }) on this route."
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const chunks = [];
|
|
56
|
+
let length = 0;
|
|
57
|
+
for await (const chunk of req) {
|
|
58
|
+
const bytes2 = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk;
|
|
59
|
+
chunks.push(bytes2);
|
|
60
|
+
length += bytes2.length;
|
|
61
|
+
}
|
|
62
|
+
const bytes = new Uint8Array(length);
|
|
63
|
+
let offset = 0;
|
|
64
|
+
for (const chunk of chunks) {
|
|
65
|
+
bytes.set(chunk, offset);
|
|
66
|
+
offset += chunk.length;
|
|
67
|
+
}
|
|
68
|
+
return bytes;
|
|
69
|
+
}
|
|
70
|
+
function header(req, name) {
|
|
71
|
+
const value = req.headers[name];
|
|
72
|
+
return Array.isArray(value) ? value[0] : value;
|
|
73
|
+
}
|
|
74
|
+
function send(res, status, body) {
|
|
75
|
+
res.statusCode = status;
|
|
76
|
+
res.setHeader("Content-Type", "application/json");
|
|
77
|
+
res.end(JSON.stringify(body));
|
|
78
|
+
}
|
|
79
|
+
function readEnv(name) {
|
|
80
|
+
try {
|
|
81
|
+
return globalThis.process?.env?.[name];
|
|
82
|
+
} catch {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
exports.labyrintheWebhook = labyrintheWebhook;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { WebhookEvent } from '@labyrinthe/connect';
|
|
2
|
+
export { WebhookEvent } from '@labyrinthe/connect';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Express (and Connect / plain `node:http`) webhook middleware.
|
|
6
|
+
*
|
|
7
|
+
* import express from 'express';
|
|
8
|
+
* import { labyrintheWebhook } from '@labyrinthe/connect/express';
|
|
9
|
+
*
|
|
10
|
+
* app.post('/webhooks/labyrinthe', labyrintheWebhook({
|
|
11
|
+
* secret: process.env.LABYRINTHE_WEBHOOK_SECRET!,
|
|
12
|
+
* onEvent: async (event) => { … },
|
|
13
|
+
* }));
|
|
14
|
+
*
|
|
15
|
+
* Mount it BEFORE `express.json()` for this route (or use `express.raw({ type: 'application/json' })`):
|
|
16
|
+
* the signature is computed on the raw body.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** The parts of an Express / Node request the middleware reads. */
|
|
20
|
+
interface WebhookRequest extends AsyncIterable<unknown> {
|
|
21
|
+
headers: Record<string, string | string[] | undefined>;
|
|
22
|
+
body?: unknown;
|
|
23
|
+
/** Set by the middleware when there is no `onEvent`. */
|
|
24
|
+
labyrintheEvent?: WebhookEvent;
|
|
25
|
+
}
|
|
26
|
+
/** The parts of an Express / Node response the middleware uses. */
|
|
27
|
+
interface WebhookResponse {
|
|
28
|
+
statusCode: number;
|
|
29
|
+
headersSent?: boolean;
|
|
30
|
+
setHeader(name: string, value: string): unknown;
|
|
31
|
+
end(body?: string): unknown;
|
|
32
|
+
}
|
|
33
|
+
type NextFunction = (error?: unknown) => void;
|
|
34
|
+
interface LabyrintheWebhookOptions {
|
|
35
|
+
/** Endpoint secret (client area > Developers > Webhooks). Defaults to `process.env.LABYRINTHE_WEBHOOK_SECRET`. */
|
|
36
|
+
secret?: string;
|
|
37
|
+
/** Max age of a delivery, in seconds (default 300; 0 disables the check). */
|
|
38
|
+
toleranceSeconds?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Handles a verified event; the middleware then answers 200. If it throws,
|
|
41
|
+
* the error goes to `next(error)` (Express answers 500 and the delivery is
|
|
42
|
+
* retried). Without `onEvent`, the event is set on `req.labyrintheEvent`
|
|
43
|
+
* and `next()` is called.
|
|
44
|
+
*/
|
|
45
|
+
onEvent?: (event: WebhookEvent, req: WebhookRequest, res: WebhookResponse) => unknown;
|
|
46
|
+
}
|
|
47
|
+
declare function labyrintheWebhook(options?: LabyrintheWebhookOptions): (req: WebhookRequest, res: WebhookResponse, next: NextFunction) => Promise<void>;
|
|
48
|
+
|
|
49
|
+
export { type LabyrintheWebhookOptions, type NextFunction, type WebhookRequest, type WebhookResponse, labyrintheWebhook };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { WebhookEvent } from '@labyrinthe/connect';
|
|
2
|
+
export { WebhookEvent } from '@labyrinthe/connect';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Express (and Connect / plain `node:http`) webhook middleware.
|
|
6
|
+
*
|
|
7
|
+
* import express from 'express';
|
|
8
|
+
* import { labyrintheWebhook } from '@labyrinthe/connect/express';
|
|
9
|
+
*
|
|
10
|
+
* app.post('/webhooks/labyrinthe', labyrintheWebhook({
|
|
11
|
+
* secret: process.env.LABYRINTHE_WEBHOOK_SECRET!,
|
|
12
|
+
* onEvent: async (event) => { … },
|
|
13
|
+
* }));
|
|
14
|
+
*
|
|
15
|
+
* Mount it BEFORE `express.json()` for this route (or use `express.raw({ type: 'application/json' })`):
|
|
16
|
+
* the signature is computed on the raw body.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** The parts of an Express / Node request the middleware reads. */
|
|
20
|
+
interface WebhookRequest extends AsyncIterable<unknown> {
|
|
21
|
+
headers: Record<string, string | string[] | undefined>;
|
|
22
|
+
body?: unknown;
|
|
23
|
+
/** Set by the middleware when there is no `onEvent`. */
|
|
24
|
+
labyrintheEvent?: WebhookEvent;
|
|
25
|
+
}
|
|
26
|
+
/** The parts of an Express / Node response the middleware uses. */
|
|
27
|
+
interface WebhookResponse {
|
|
28
|
+
statusCode: number;
|
|
29
|
+
headersSent?: boolean;
|
|
30
|
+
setHeader(name: string, value: string): unknown;
|
|
31
|
+
end(body?: string): unknown;
|
|
32
|
+
}
|
|
33
|
+
type NextFunction = (error?: unknown) => void;
|
|
34
|
+
interface LabyrintheWebhookOptions {
|
|
35
|
+
/** Endpoint secret (client area > Developers > Webhooks). Defaults to `process.env.LABYRINTHE_WEBHOOK_SECRET`. */
|
|
36
|
+
secret?: string;
|
|
37
|
+
/** Max age of a delivery, in seconds (default 300; 0 disables the check). */
|
|
38
|
+
toleranceSeconds?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Handles a verified event; the middleware then answers 200. If it throws,
|
|
41
|
+
* the error goes to `next(error)` (Express answers 500 and the delivery is
|
|
42
|
+
* retried). Without `onEvent`, the event is set on `req.labyrintheEvent`
|
|
43
|
+
* and `next()` is called.
|
|
44
|
+
*/
|
|
45
|
+
onEvent?: (event: WebhookEvent, req: WebhookRequest, res: WebhookResponse) => unknown;
|
|
46
|
+
}
|
|
47
|
+
declare function labyrintheWebhook(options?: LabyrintheWebhookOptions): (req: WebhookRequest, res: WebhookResponse, next: NextFunction) => Promise<void>;
|
|
48
|
+
|
|
49
|
+
export { type LabyrintheWebhookOptions, type NextFunction, type WebhookRequest, type WebhookResponse, labyrintheWebhook };
|
package/dist/express.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Webhooks, SignatureVerificationError, InvalidWebhookPayloadError } from '@labyrinthe/connect';
|
|
2
|
+
|
|
3
|
+
// src/express.ts
|
|
4
|
+
function labyrintheWebhook(options = {}) {
|
|
5
|
+
return async (req, res, next) => {
|
|
6
|
+
let event;
|
|
7
|
+
try {
|
|
8
|
+
const secret = options.secret ?? readEnv("LABYRINTHE_WEBHOOK_SECRET");
|
|
9
|
+
if (secret === void 0 || secret === "") {
|
|
10
|
+
throw new Error("No webhook secret: pass `secret` or set LABYRINTHE_WEBHOOK_SECRET.");
|
|
11
|
+
}
|
|
12
|
+
const rawBody = await readRawBody(req);
|
|
13
|
+
const signature = header(req, "x-signature");
|
|
14
|
+
event = await Webhooks.constructEvent(
|
|
15
|
+
rawBody,
|
|
16
|
+
signature,
|
|
17
|
+
secret,
|
|
18
|
+
options.toleranceSeconds === void 0 ? {} : { toleranceSeconds: options.toleranceSeconds }
|
|
19
|
+
);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error instanceof SignatureVerificationError || error instanceof InvalidWebhookPayloadError) {
|
|
22
|
+
send(res, 400, { error: { code: error.code, message: error.message } });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
next(error);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (options.onEvent === void 0) {
|
|
29
|
+
req.labyrintheEvent = event;
|
|
30
|
+
next();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await options.onEvent(event, req, res);
|
|
35
|
+
if (res.headersSent !== true) {
|
|
36
|
+
send(res, 200, { received: true });
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
next(error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function readRawBody(req) {
|
|
44
|
+
const body = req.body;
|
|
45
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
46
|
+
return body;
|
|
47
|
+
}
|
|
48
|
+
if (body !== void 0 && body !== null && !(typeof body === "object" && Object.keys(body).length === 0)) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"The webhook body was already parsed (express.json()?): mount labyrintheWebhook() before it, or use express.raw({ type: 'application/json' }) on this route."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const chunks = [];
|
|
54
|
+
let length = 0;
|
|
55
|
+
for await (const chunk of req) {
|
|
56
|
+
const bytes2 = typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk;
|
|
57
|
+
chunks.push(bytes2);
|
|
58
|
+
length += bytes2.length;
|
|
59
|
+
}
|
|
60
|
+
const bytes = new Uint8Array(length);
|
|
61
|
+
let offset = 0;
|
|
62
|
+
for (const chunk of chunks) {
|
|
63
|
+
bytes.set(chunk, offset);
|
|
64
|
+
offset += chunk.length;
|
|
65
|
+
}
|
|
66
|
+
return bytes;
|
|
67
|
+
}
|
|
68
|
+
function header(req, name) {
|
|
69
|
+
const value = req.headers[name];
|
|
70
|
+
return Array.isArray(value) ? value[0] : value;
|
|
71
|
+
}
|
|
72
|
+
function send(res, status, body) {
|
|
73
|
+
res.statusCode = status;
|
|
74
|
+
res.setHeader("Content-Type", "application/json");
|
|
75
|
+
res.end(JSON.stringify(body));
|
|
76
|
+
}
|
|
77
|
+
function readEnv(name) {
|
|
78
|
+
try {
|
|
79
|
+
return globalThis.process?.env?.[name];
|
|
80
|
+
} catch {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { labyrintheWebhook };
|