@rstreamlabs/tunnels 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -3
- package/dist/index.d.mts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +83 -6
- package/dist/index.mjs +75 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -190,7 +190,8 @@ For a receiving backend, verify the signature before touching the JSON payload
|
|
|
190
190
|
and use the event id as the idempotency key.
|
|
191
191
|
|
|
192
192
|
```ts
|
|
193
|
-
import { RstreamWebhookResource
|
|
193
|
+
import { RstreamWebhookResource } from "@rstreamlabs/tunnels";
|
|
194
|
+
import { type WebhookEvent } from "@rstreamlabs/tunnels";
|
|
194
195
|
|
|
195
196
|
const webhooks = new RstreamWebhookResource();
|
|
196
197
|
|
|
@@ -225,18 +226,55 @@ export async function receiveWebhook(request: Request) {
|
|
|
225
226
|
const rawBody = Buffer.from(await request.arrayBuffer());
|
|
226
227
|
const signature = request.headers.get("rstream-signature");
|
|
227
228
|
if (!signature) throw new Error("Missing webhook signature.");
|
|
228
|
-
|
|
229
229
|
const event = await webhooks.event(
|
|
230
230
|
rawBody,
|
|
231
231
|
signature,
|
|
232
232
|
process.env.WEBHOOK_SECRET!,
|
|
233
233
|
);
|
|
234
234
|
if (!event.id) throw new Error("Missing webhook event id.");
|
|
235
|
-
|
|
236
235
|
await handleLifecycleEvent(event);
|
|
237
236
|
}
|
|
238
237
|
```
|
|
239
238
|
|
|
239
|
+
For local receiver tests, the SDK exposes the same signing primitives used by
|
|
240
|
+
the CLI and Engine dispatcher:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { buildWebhookHeaders } from "@rstreamlabs/tunnels";
|
|
244
|
+
import { generateWebhookSigningSecret } from "@rstreamlabs/tunnels";
|
|
245
|
+
import { type WebhookEvent } from "@rstreamlabs/tunnels";
|
|
246
|
+
|
|
247
|
+
const secret = generateWebhookSigningSecret();
|
|
248
|
+
const event: WebhookEvent & { id: string } = {
|
|
249
|
+
id: "evt_test",
|
|
250
|
+
type: "tunnel.created",
|
|
251
|
+
created_at: new Date().toISOString(),
|
|
252
|
+
object: {
|
|
253
|
+
id: "tunnel_test",
|
|
254
|
+
labels: {
|
|
255
|
+
device: "device_123",
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
const body = JSON.stringify(event);
|
|
260
|
+
const headers = buildWebhookHeaders(body, event, secret, {
|
|
261
|
+
deliveryId: "del_test",
|
|
262
|
+
webhookId: "we_test",
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
await fetch("http://localhost:3000/api/rstream/webhook", {
|
|
266
|
+
body,
|
|
267
|
+
headers: {
|
|
268
|
+
"content-type": "application/json",
|
|
269
|
+
...headers,
|
|
270
|
+
},
|
|
271
|
+
method: "POST",
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`rstream events --webhook` uses the same request body and header contract when
|
|
276
|
+
forwarding live local events to a receiver.
|
|
277
|
+
|
|
240
278
|
## WebTTY Helpers
|
|
241
279
|
|
|
242
280
|
Use `parseWebTTYServers()` to derive WebTTY server metadata from tunnel
|
package/dist/index.d.mts
CHANGED
|
@@ -2967,8 +2967,26 @@ type WebhookEvent = z.infer<typeof webhookEventsSchema>;
|
|
|
2967
2967
|
type ProjectLogEvent = z.infer<typeof projectLogEventsSchema>;
|
|
2968
2968
|
type Event = z.infer<typeof eventSchema>;
|
|
2969
2969
|
|
|
2970
|
+
declare const WEBHOOK_SIGNATURE_HEADER = "rstream-signature";
|
|
2971
|
+
declare const WEBHOOK_EVENT_ID_HEADER = "rstream-event-id";
|
|
2972
|
+
declare const WEBHOOK_EVENT_TYPE_HEADER = "rstream-event-type";
|
|
2973
|
+
declare const WEBHOOK_ID_HEADER = "rstream-webhook-id";
|
|
2974
|
+
declare const WEBHOOK_DELIVERY_ID_HEADER = "rstream-delivery-id";
|
|
2975
|
+
type WebhookPayload = string | Buffer;
|
|
2976
|
+
type WebhookHeaders = Record<typeof WEBHOOK_SIGNATURE_HEADER | typeof WEBHOOK_EVENT_ID_HEADER | typeof WEBHOOK_EVENT_TYPE_HEADER | typeof WEBHOOK_ID_HEADER | typeof WEBHOOK_DELIVERY_ID_HEADER, string>;
|
|
2977
|
+
interface BuildWebhookHeadersOptions {
|
|
2978
|
+
deliveryId: string;
|
|
2979
|
+
timestamp?: Date | number;
|
|
2980
|
+
webhookId: string;
|
|
2981
|
+
}
|
|
2982
|
+
declare function generateWebhookSigningSecret(): string;
|
|
2983
|
+
declare function signWebhookPayload(payload: WebhookPayload, secret: string, timestamp?: Date | number): string;
|
|
2984
|
+
declare function buildWebhookHeaders(payload: WebhookPayload, event: WebhookEvent, secret: string, options: BuildWebhookHeadersOptions): WebhookHeaders;
|
|
2970
2985
|
declare class RstreamWebhookResource {
|
|
2971
|
-
event(payload:
|
|
2986
|
+
event(payload: WebhookPayload, header: string | Buffer | Array<string>, secret: string, tolerance?: number, receivedAt?: number): Promise<WebhookEvent>;
|
|
2987
|
+
generateSigningSecret(): string;
|
|
2988
|
+
sign(payload: WebhookPayload, secret: string, timestamp?: Date | number): string;
|
|
2989
|
+
headers(payload: WebhookPayload, event: WebhookEvent, secret: string, options: BuildWebhookHeadersOptions): WebhookHeaders;
|
|
2972
2990
|
}
|
|
2973
2991
|
|
|
2974
2992
|
interface RstreamTunnelsConfig {
|
|
@@ -3682,4 +3700,4 @@ declare const webttyServerSchema: z.ZodObject<{
|
|
|
3682
3700
|
type WebTTYServer = z.infer<typeof webttyServerSchema>;
|
|
3683
3701
|
declare function parseWebTTYServers(tunnels: Tunnel[]): WebTTYServer[];
|
|
3684
3702
|
|
|
3685
|
-
export { type Client, type ClientFilter, type CreateAPITURNCredentialsOptions, type CreateAPPTURNCredentialsOptions, type CreateAuthTokenFromClientCredentialsOptions, type CreatePATTURNCredentialsOptions, type CreateTURNCredentialsOptions, type Event, type InitialState, type ListClientsParams, type ListClientsResponse, type ListTunnelsParams, type ListTunnelsResponse, type ProjectLogEvent, type RstreamAuth, RstreamAuthResource, RstreamAuthResource as RstreamAuthRessource, RstreamTunnelsClient as RstreamClient, RstreamClientsResource, RstreamClientsResource as RstreamClientsRessource, RstreamTURNResource, RstreamTunnelsClient, type RstreamTunnelsConfig, RstreamTunnelsResource, RstreamTunnelsResource as RstreamTunnelsRessource, RstreamWebhookResource, RstreamWebhookResource as RstreamWebhookRessource, type StreamEndpoint, type StreamSummary, type StreamTLSInfo, type TURNCredentialMode, type TunnelFilter, RstreamTunnelsClient as Tunnels, type TunnelsResolutionConfig, Watch, type WatchConfig, type WatchWsEvents, type WebTTYServer, type WebhookEvent, type WsEvent, clientFilterSchema, clientSchema, createAPITURNCredentials, createAPPTURNCredentials, createAuthTokenFromClientCredentials, createPATTURNCredentials, createTURNCredentials, eventSchema, formatTunnelHost, listClientsParamsSchema, listClientsResponseSchema, listTunnelsParamsSchema, listTunnelsResponseSchema, normalizeEngineAddress, parseWebTTYServers, projectLogEventsSchema, resolveControlPlaneCredentials, resolveManagedTunnelsProject, resolveTunnelsAPIURL, resolveTunnelsCredentials, resolveTunnelsEngine, streamEndpointSchema, streamSummaryEventSchema, streamSummarySchema, streamTLSInfoSchema, tunnelFilterSchema, turnCredentialModeSchema, webhookEventsSchema, webttyServerSchema, wsEventsSchema };
|
|
3703
|
+
export { type BuildWebhookHeadersOptions, type Client, type ClientFilter, type CreateAPITURNCredentialsOptions, type CreateAPPTURNCredentialsOptions, type CreateAuthTokenFromClientCredentialsOptions, type CreatePATTURNCredentialsOptions, type CreateTURNCredentialsOptions, type Event, type InitialState, type ListClientsParams, type ListClientsResponse, type ListTunnelsParams, type ListTunnelsResponse, type ProjectLogEvent, type RstreamAuth, RstreamAuthResource, RstreamAuthResource as RstreamAuthRessource, RstreamTunnelsClient as RstreamClient, RstreamClientsResource, RstreamClientsResource as RstreamClientsRessource, RstreamTURNResource, RstreamTunnelsClient, type RstreamTunnelsConfig, RstreamTunnelsResource, RstreamTunnelsResource as RstreamTunnelsRessource, RstreamWebhookResource, RstreamWebhookResource as RstreamWebhookRessource, type StreamEndpoint, type StreamSummary, type StreamTLSInfo, type TURNCredentialMode, type TunnelFilter, RstreamTunnelsClient as Tunnels, type TunnelsResolutionConfig, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_ID_HEADER, WEBHOOK_SIGNATURE_HEADER, Watch, type WatchConfig, type WatchWsEvents, type WebTTYServer, type WebhookEvent, type WebhookHeaders, type WebhookPayload, type WsEvent, buildWebhookHeaders, clientFilterSchema, clientSchema, createAPITURNCredentials, createAPPTURNCredentials, createAuthTokenFromClientCredentials, createPATTURNCredentials, createTURNCredentials, eventSchema, formatTunnelHost, generateWebhookSigningSecret, listClientsParamsSchema, listClientsResponseSchema, listTunnelsParamsSchema, listTunnelsResponseSchema, normalizeEngineAddress, parseWebTTYServers, projectLogEventsSchema, resolveControlPlaneCredentials, resolveManagedTunnelsProject, resolveTunnelsAPIURL, resolveTunnelsCredentials, resolveTunnelsEngine, signWebhookPayload, streamEndpointSchema, streamSummaryEventSchema, streamSummarySchema, streamTLSInfoSchema, tunnelFilterSchema, turnCredentialModeSchema, webhookEventsSchema, webttyServerSchema, wsEventsSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -2967,8 +2967,26 @@ type WebhookEvent = z.infer<typeof webhookEventsSchema>;
|
|
|
2967
2967
|
type ProjectLogEvent = z.infer<typeof projectLogEventsSchema>;
|
|
2968
2968
|
type Event = z.infer<typeof eventSchema>;
|
|
2969
2969
|
|
|
2970
|
+
declare const WEBHOOK_SIGNATURE_HEADER = "rstream-signature";
|
|
2971
|
+
declare const WEBHOOK_EVENT_ID_HEADER = "rstream-event-id";
|
|
2972
|
+
declare const WEBHOOK_EVENT_TYPE_HEADER = "rstream-event-type";
|
|
2973
|
+
declare const WEBHOOK_ID_HEADER = "rstream-webhook-id";
|
|
2974
|
+
declare const WEBHOOK_DELIVERY_ID_HEADER = "rstream-delivery-id";
|
|
2975
|
+
type WebhookPayload = string | Buffer;
|
|
2976
|
+
type WebhookHeaders = Record<typeof WEBHOOK_SIGNATURE_HEADER | typeof WEBHOOK_EVENT_ID_HEADER | typeof WEBHOOK_EVENT_TYPE_HEADER | typeof WEBHOOK_ID_HEADER | typeof WEBHOOK_DELIVERY_ID_HEADER, string>;
|
|
2977
|
+
interface BuildWebhookHeadersOptions {
|
|
2978
|
+
deliveryId: string;
|
|
2979
|
+
timestamp?: Date | number;
|
|
2980
|
+
webhookId: string;
|
|
2981
|
+
}
|
|
2982
|
+
declare function generateWebhookSigningSecret(): string;
|
|
2983
|
+
declare function signWebhookPayload(payload: WebhookPayload, secret: string, timestamp?: Date | number): string;
|
|
2984
|
+
declare function buildWebhookHeaders(payload: WebhookPayload, event: WebhookEvent, secret: string, options: BuildWebhookHeadersOptions): WebhookHeaders;
|
|
2970
2985
|
declare class RstreamWebhookResource {
|
|
2971
|
-
event(payload:
|
|
2986
|
+
event(payload: WebhookPayload, header: string | Buffer | Array<string>, secret: string, tolerance?: number, receivedAt?: number): Promise<WebhookEvent>;
|
|
2987
|
+
generateSigningSecret(): string;
|
|
2988
|
+
sign(payload: WebhookPayload, secret: string, timestamp?: Date | number): string;
|
|
2989
|
+
headers(payload: WebhookPayload, event: WebhookEvent, secret: string, options: BuildWebhookHeadersOptions): WebhookHeaders;
|
|
2972
2990
|
}
|
|
2973
2991
|
|
|
2974
2992
|
interface RstreamTunnelsConfig {
|
|
@@ -3682,4 +3700,4 @@ declare const webttyServerSchema: z.ZodObject<{
|
|
|
3682
3700
|
type WebTTYServer = z.infer<typeof webttyServerSchema>;
|
|
3683
3701
|
declare function parseWebTTYServers(tunnels: Tunnel[]): WebTTYServer[];
|
|
3684
3702
|
|
|
3685
|
-
export { type Client, type ClientFilter, type CreateAPITURNCredentialsOptions, type CreateAPPTURNCredentialsOptions, type CreateAuthTokenFromClientCredentialsOptions, type CreatePATTURNCredentialsOptions, type CreateTURNCredentialsOptions, type Event, type InitialState, type ListClientsParams, type ListClientsResponse, type ListTunnelsParams, type ListTunnelsResponse, type ProjectLogEvent, type RstreamAuth, RstreamAuthResource, RstreamAuthResource as RstreamAuthRessource, RstreamTunnelsClient as RstreamClient, RstreamClientsResource, RstreamClientsResource as RstreamClientsRessource, RstreamTURNResource, RstreamTunnelsClient, type RstreamTunnelsConfig, RstreamTunnelsResource, RstreamTunnelsResource as RstreamTunnelsRessource, RstreamWebhookResource, RstreamWebhookResource as RstreamWebhookRessource, type StreamEndpoint, type StreamSummary, type StreamTLSInfo, type TURNCredentialMode, type TunnelFilter, RstreamTunnelsClient as Tunnels, type TunnelsResolutionConfig, Watch, type WatchConfig, type WatchWsEvents, type WebTTYServer, type WebhookEvent, type WsEvent, clientFilterSchema, clientSchema, createAPITURNCredentials, createAPPTURNCredentials, createAuthTokenFromClientCredentials, createPATTURNCredentials, createTURNCredentials, eventSchema, formatTunnelHost, listClientsParamsSchema, listClientsResponseSchema, listTunnelsParamsSchema, listTunnelsResponseSchema, normalizeEngineAddress, parseWebTTYServers, projectLogEventsSchema, resolveControlPlaneCredentials, resolveManagedTunnelsProject, resolveTunnelsAPIURL, resolveTunnelsCredentials, resolveTunnelsEngine, streamEndpointSchema, streamSummaryEventSchema, streamSummarySchema, streamTLSInfoSchema, tunnelFilterSchema, turnCredentialModeSchema, webhookEventsSchema, webttyServerSchema, wsEventsSchema };
|
|
3703
|
+
export { type BuildWebhookHeadersOptions, type Client, type ClientFilter, type CreateAPITURNCredentialsOptions, type CreateAPPTURNCredentialsOptions, type CreateAuthTokenFromClientCredentialsOptions, type CreatePATTURNCredentialsOptions, type CreateTURNCredentialsOptions, type Event, type InitialState, type ListClientsParams, type ListClientsResponse, type ListTunnelsParams, type ListTunnelsResponse, type ProjectLogEvent, type RstreamAuth, RstreamAuthResource, RstreamAuthResource as RstreamAuthRessource, RstreamTunnelsClient as RstreamClient, RstreamClientsResource, RstreamClientsResource as RstreamClientsRessource, RstreamTURNResource, RstreamTunnelsClient, type RstreamTunnelsConfig, RstreamTunnelsResource, RstreamTunnelsResource as RstreamTunnelsRessource, RstreamWebhookResource, RstreamWebhookResource as RstreamWebhookRessource, type StreamEndpoint, type StreamSummary, type StreamTLSInfo, type TURNCredentialMode, type TunnelFilter, RstreamTunnelsClient as Tunnels, type TunnelsResolutionConfig, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_ID_HEADER, WEBHOOK_SIGNATURE_HEADER, Watch, type WatchConfig, type WatchWsEvents, type WebTTYServer, type WebhookEvent, type WebhookHeaders, type WebhookPayload, type WsEvent, buildWebhookHeaders, clientFilterSchema, clientSchema, createAPITURNCredentials, createAPPTURNCredentials, createAuthTokenFromClientCredentials, createPATTURNCredentials, createTURNCredentials, eventSchema, formatTunnelHost, generateWebhookSigningSecret, listClientsParamsSchema, listClientsResponseSchema, listTunnelsParamsSchema, listTunnelsResponseSchema, normalizeEngineAddress, parseWebTTYServers, projectLogEventsSchema, resolveControlPlaneCredentials, resolveManagedTunnelsProject, resolveTunnelsAPIURL, resolveTunnelsCredentials, resolveTunnelsEngine, signWebhookPayload, streamEndpointSchema, streamSummaryEventSchema, streamSummarySchema, streamTLSInfoSchema, tunnelFilterSchema, turnCredentialModeSchema, webhookEventsSchema, webttyServerSchema, wsEventsSchema };
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,13 @@ __export(index_exports, {
|
|
|
42
42
|
RstreamWebhookResource: () => RstreamWebhookResource,
|
|
43
43
|
RstreamWebhookRessource: () => RstreamWebhookResource,
|
|
44
44
|
Tunnels: () => RstreamTunnelsClient,
|
|
45
|
+
WEBHOOK_DELIVERY_ID_HEADER: () => WEBHOOK_DELIVERY_ID_HEADER,
|
|
46
|
+
WEBHOOK_EVENT_ID_HEADER: () => WEBHOOK_EVENT_ID_HEADER,
|
|
47
|
+
WEBHOOK_EVENT_TYPE_HEADER: () => WEBHOOK_EVENT_TYPE_HEADER,
|
|
48
|
+
WEBHOOK_ID_HEADER: () => WEBHOOK_ID_HEADER,
|
|
49
|
+
WEBHOOK_SIGNATURE_HEADER: () => WEBHOOK_SIGNATURE_HEADER,
|
|
45
50
|
Watch: () => Watch,
|
|
51
|
+
buildWebhookHeaders: () => buildWebhookHeaders,
|
|
46
52
|
clientFilterSchema: () => clientFilterSchema,
|
|
47
53
|
clientSchema: () => clientSchema,
|
|
48
54
|
createAPITURNCredentials: () => createAPITURNCredentials,
|
|
@@ -54,6 +60,7 @@ __export(index_exports, {
|
|
|
54
60
|
engineOpenApiJson: () => engineOpenApiJson,
|
|
55
61
|
eventSchema: () => eventSchema,
|
|
56
62
|
formatTunnelHost: () => formatTunnelHost,
|
|
63
|
+
generateWebhookSigningSecret: () => generateWebhookSigningSecret,
|
|
57
64
|
listClientsParamsSchema: () => listClientsParamsSchema,
|
|
58
65
|
listClientsResponseSchema: () => listClientsResponseSchema,
|
|
59
66
|
listTunnelsParamsSchema: () => listTunnelsParamsSchema,
|
|
@@ -66,6 +73,7 @@ __export(index_exports, {
|
|
|
66
73
|
resolveTunnelsAPIURL: () => resolveTunnelsAPIURL,
|
|
67
74
|
resolveTunnelsCredentials: () => resolveTunnelsCredentials,
|
|
68
75
|
resolveTunnelsEngine: () => resolveTunnelsEngine,
|
|
76
|
+
signWebhookPayload: () => signWebhookPayload,
|
|
69
77
|
streamEndpointSchema: () => streamEndpointSchema,
|
|
70
78
|
streamSummaryEventSchema: () => streamSummaryEventSchema,
|
|
71
79
|
streamSummarySchema: () => streamSummarySchema,
|
|
@@ -1477,9 +1485,18 @@ var RstreamTURNResource = class {
|
|
|
1477
1485
|
|
|
1478
1486
|
// src/webhooks-resource.ts
|
|
1479
1487
|
var import_crypto2 = __toESM(require("crypto"));
|
|
1488
|
+
var WEBHOOK_SIGNATURE_HEADER = "rstream-signature";
|
|
1489
|
+
var WEBHOOK_EVENT_ID_HEADER = "rstream-event-id";
|
|
1490
|
+
var WEBHOOK_EVENT_TYPE_HEADER = "rstream-event-type";
|
|
1491
|
+
var WEBHOOK_ID_HEADER = "rstream-webhook-id";
|
|
1492
|
+
var WEBHOOK_DELIVERY_ID_HEADER = "rstream-delivery-id";
|
|
1480
1493
|
var SIGNATURE_HEX_BYTES = 32;
|
|
1494
|
+
var webhookSecretBytes = 32;
|
|
1481
1495
|
var signatureHexSchema = /^[0-9a-fA-F]{64}$/;
|
|
1482
1496
|
var timestampSchema = /^\d+$/;
|
|
1497
|
+
function payloadBuffer(payload) {
|
|
1498
|
+
return Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
1499
|
+
}
|
|
1483
1500
|
function parseSignatureTimestamp(timestamp) {
|
|
1484
1501
|
if (!timestampSchema.test(timestamp)) {
|
|
1485
1502
|
throw new Error("Invalid signature timestamp");
|
|
@@ -1496,6 +1513,19 @@ function normalizeTolerance(tolerance) {
|
|
|
1496
1513
|
}
|
|
1497
1514
|
return tolerance;
|
|
1498
1515
|
}
|
|
1516
|
+
function normalizeTimestamp(timestamp) {
|
|
1517
|
+
if (timestamp === void 0) {
|
|
1518
|
+
return Math.floor(Date.now() / 1e3);
|
|
1519
|
+
}
|
|
1520
|
+
const seconds = timestamp instanceof Date ? timestamp.getTime() / 1e3 : timestamp;
|
|
1521
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
1522
|
+
throw new Error("Invalid webhook signature timestamp");
|
|
1523
|
+
}
|
|
1524
|
+
return Math.floor(seconds);
|
|
1525
|
+
}
|
|
1526
|
+
function signedPayload(timestamp, payload) {
|
|
1527
|
+
return Buffer.concat([Buffer.from(`${timestamp}.`, "utf8"), payload]);
|
|
1528
|
+
}
|
|
1499
1529
|
function readSignature(signature) {
|
|
1500
1530
|
if (!signatureHexSchema.test(signature)) {
|
|
1501
1531
|
return void 0;
|
|
@@ -1512,9 +1542,42 @@ function assertSecretConfigured(secret) {
|
|
|
1512
1542
|
throw new Error("Webhook signing secret is required");
|
|
1513
1543
|
}
|
|
1514
1544
|
}
|
|
1545
|
+
function assertNonEmpty(value, label) {
|
|
1546
|
+
const normalized = value.trim();
|
|
1547
|
+
if (normalized.length === 0) {
|
|
1548
|
+
throw new Error(`${label} is required`);
|
|
1549
|
+
}
|
|
1550
|
+
return normalized;
|
|
1551
|
+
}
|
|
1552
|
+
function generateWebhookSigningSecret() {
|
|
1553
|
+
return `whsec_${import_crypto2.default.randomBytes(webhookSecretBytes).toString("base64url")}`;
|
|
1554
|
+
}
|
|
1555
|
+
function signWebhookPayload(payload, secret, timestamp) {
|
|
1556
|
+
assertSecretConfigured(secret);
|
|
1557
|
+
const timestampSeconds = normalizeTimestamp(timestamp).toString();
|
|
1558
|
+
const signature = import_crypto2.default.createHmac("sha256", secret).update(signedPayload(timestampSeconds, payloadBuffer(payload))).digest("hex");
|
|
1559
|
+
return `t=${timestampSeconds},v1=${signature}`;
|
|
1560
|
+
}
|
|
1561
|
+
function buildWebhookHeaders(payload, event, secret, options) {
|
|
1562
|
+
const eventId = assertNonEmpty(event.id ?? "", "Webhook event id");
|
|
1563
|
+
const eventType = assertNonEmpty(event.type, "Webhook event type");
|
|
1564
|
+
const webhookId = assertNonEmpty(options.webhookId, "Webhook id");
|
|
1565
|
+
const deliveryId = assertNonEmpty(options.deliveryId, "Webhook delivery id");
|
|
1566
|
+
return {
|
|
1567
|
+
[WEBHOOK_SIGNATURE_HEADER]: signWebhookPayload(
|
|
1568
|
+
payload,
|
|
1569
|
+
secret,
|
|
1570
|
+
options.timestamp
|
|
1571
|
+
),
|
|
1572
|
+
[WEBHOOK_EVENT_ID_HEADER]: eventId,
|
|
1573
|
+
[WEBHOOK_EVENT_TYPE_HEADER]: eventType,
|
|
1574
|
+
[WEBHOOK_ID_HEADER]: webhookId,
|
|
1575
|
+
[WEBHOOK_DELIVERY_ID_HEADER]: deliveryId
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1515
1578
|
var RstreamWebhookResource = class {
|
|
1516
1579
|
async event(payload, header, secret, tolerance = 300, receivedAt = Date.now()) {
|
|
1517
|
-
const
|
|
1580
|
+
const rawPayload = payloadBuffer(payload);
|
|
1518
1581
|
const signatureHeader = Array.isArray(header) ? header[0] : header.toString();
|
|
1519
1582
|
if (!signatureHeader) {
|
|
1520
1583
|
throw new Error("No signature header");
|
|
@@ -1534,8 +1597,7 @@ var RstreamWebhookResource = class {
|
|
|
1534
1597
|
`Webhook signature timestamp outside tolerance: ${now}, ${timestampNum}`
|
|
1535
1598
|
);
|
|
1536
1599
|
}
|
|
1537
|
-
const
|
|
1538
|
-
const expectedSignature = import_crypto2.default.createHmac("sha256", secret).update(signedPayload).digest();
|
|
1600
|
+
const expectedSignature = import_crypto2.default.createHmac("sha256", secret).update(signedPayload(timestamp, rawPayload)).digest();
|
|
1539
1601
|
const signatureMatched = signatures.some(
|
|
1540
1602
|
(signature) => signatureMatches(signature, expectedSignature)
|
|
1541
1603
|
);
|
|
@@ -1543,9 +1605,7 @@ var RstreamWebhookResource = class {
|
|
|
1543
1605
|
throw new Error("Signature verification failed");
|
|
1544
1606
|
}
|
|
1545
1607
|
try {
|
|
1546
|
-
return webhookEventsSchema.parse(
|
|
1547
|
-
JSON.parse(payloadBuffer.toString("utf8"))
|
|
1548
|
-
);
|
|
1608
|
+
return webhookEventsSchema.parse(JSON.parse(rawPayload.toString("utf8")));
|
|
1549
1609
|
} catch (error) {
|
|
1550
1610
|
throw new Error(
|
|
1551
1611
|
`Failed to parse webhook payload: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -1553,6 +1613,15 @@ var RstreamWebhookResource = class {
|
|
|
1553
1613
|
);
|
|
1554
1614
|
}
|
|
1555
1615
|
}
|
|
1616
|
+
generateSigningSecret() {
|
|
1617
|
+
return generateWebhookSigningSecret();
|
|
1618
|
+
}
|
|
1619
|
+
sign(payload, secret, timestamp) {
|
|
1620
|
+
return signWebhookPayload(payload, secret, timestamp);
|
|
1621
|
+
}
|
|
1622
|
+
headers(payload, event, secret, options) {
|
|
1623
|
+
return buildWebhookHeaders(payload, event, secret, options);
|
|
1624
|
+
}
|
|
1556
1625
|
};
|
|
1557
1626
|
|
|
1558
1627
|
// src/tunnels.ts
|
|
@@ -1985,7 +2054,13 @@ function parseWebTTYServers(tunnels) {
|
|
|
1985
2054
|
RstreamWebhookResource,
|
|
1986
2055
|
RstreamWebhookRessource,
|
|
1987
2056
|
Tunnels,
|
|
2057
|
+
WEBHOOK_DELIVERY_ID_HEADER,
|
|
2058
|
+
WEBHOOK_EVENT_ID_HEADER,
|
|
2059
|
+
WEBHOOK_EVENT_TYPE_HEADER,
|
|
2060
|
+
WEBHOOK_ID_HEADER,
|
|
2061
|
+
WEBHOOK_SIGNATURE_HEADER,
|
|
1988
2062
|
Watch,
|
|
2063
|
+
buildWebhookHeaders,
|
|
1989
2064
|
clientFilterSchema,
|
|
1990
2065
|
clientSchema,
|
|
1991
2066
|
createAPITURNCredentials,
|
|
@@ -1997,6 +2072,7 @@ function parseWebTTYServers(tunnels) {
|
|
|
1997
2072
|
engineOpenApiJson,
|
|
1998
2073
|
eventSchema,
|
|
1999
2074
|
formatTunnelHost,
|
|
2075
|
+
generateWebhookSigningSecret,
|
|
2000
2076
|
listClientsParamsSchema,
|
|
2001
2077
|
listClientsResponseSchema,
|
|
2002
2078
|
listTunnelsParamsSchema,
|
|
@@ -2009,6 +2085,7 @@ function parseWebTTYServers(tunnels) {
|
|
|
2009
2085
|
resolveTunnelsAPIURL,
|
|
2010
2086
|
resolveTunnelsCredentials,
|
|
2011
2087
|
resolveTunnelsEngine,
|
|
2088
|
+
signWebhookPayload,
|
|
2012
2089
|
streamEndpointSchema,
|
|
2013
2090
|
streamSummaryEventSchema,
|
|
2014
2091
|
streamSummarySchema,
|
package/dist/index.mjs
CHANGED
|
@@ -730,9 +730,18 @@ var RstreamTURNResource = class {
|
|
|
730
730
|
|
|
731
731
|
// src/webhooks-resource.ts
|
|
732
732
|
import crypto2 from "crypto";
|
|
733
|
+
var WEBHOOK_SIGNATURE_HEADER = "rstream-signature";
|
|
734
|
+
var WEBHOOK_EVENT_ID_HEADER = "rstream-event-id";
|
|
735
|
+
var WEBHOOK_EVENT_TYPE_HEADER = "rstream-event-type";
|
|
736
|
+
var WEBHOOK_ID_HEADER = "rstream-webhook-id";
|
|
737
|
+
var WEBHOOK_DELIVERY_ID_HEADER = "rstream-delivery-id";
|
|
733
738
|
var SIGNATURE_HEX_BYTES = 32;
|
|
739
|
+
var webhookSecretBytes = 32;
|
|
734
740
|
var signatureHexSchema = /^[0-9a-fA-F]{64}$/;
|
|
735
741
|
var timestampSchema = /^\d+$/;
|
|
742
|
+
function payloadBuffer(payload) {
|
|
743
|
+
return Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
744
|
+
}
|
|
736
745
|
function parseSignatureTimestamp(timestamp) {
|
|
737
746
|
if (!timestampSchema.test(timestamp)) {
|
|
738
747
|
throw new Error("Invalid signature timestamp");
|
|
@@ -749,6 +758,19 @@ function normalizeTolerance(tolerance) {
|
|
|
749
758
|
}
|
|
750
759
|
return tolerance;
|
|
751
760
|
}
|
|
761
|
+
function normalizeTimestamp(timestamp) {
|
|
762
|
+
if (timestamp === void 0) {
|
|
763
|
+
return Math.floor(Date.now() / 1e3);
|
|
764
|
+
}
|
|
765
|
+
const seconds = timestamp instanceof Date ? timestamp.getTime() / 1e3 : timestamp;
|
|
766
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
767
|
+
throw new Error("Invalid webhook signature timestamp");
|
|
768
|
+
}
|
|
769
|
+
return Math.floor(seconds);
|
|
770
|
+
}
|
|
771
|
+
function signedPayload(timestamp, payload) {
|
|
772
|
+
return Buffer.concat([Buffer.from(`${timestamp}.`, "utf8"), payload]);
|
|
773
|
+
}
|
|
752
774
|
function readSignature(signature) {
|
|
753
775
|
if (!signatureHexSchema.test(signature)) {
|
|
754
776
|
return void 0;
|
|
@@ -765,9 +787,42 @@ function assertSecretConfigured(secret) {
|
|
|
765
787
|
throw new Error("Webhook signing secret is required");
|
|
766
788
|
}
|
|
767
789
|
}
|
|
790
|
+
function assertNonEmpty(value, label) {
|
|
791
|
+
const normalized = value.trim();
|
|
792
|
+
if (normalized.length === 0) {
|
|
793
|
+
throw new Error(`${label} is required`);
|
|
794
|
+
}
|
|
795
|
+
return normalized;
|
|
796
|
+
}
|
|
797
|
+
function generateWebhookSigningSecret() {
|
|
798
|
+
return `whsec_${crypto2.randomBytes(webhookSecretBytes).toString("base64url")}`;
|
|
799
|
+
}
|
|
800
|
+
function signWebhookPayload(payload, secret, timestamp) {
|
|
801
|
+
assertSecretConfigured(secret);
|
|
802
|
+
const timestampSeconds = normalizeTimestamp(timestamp).toString();
|
|
803
|
+
const signature = crypto2.createHmac("sha256", secret).update(signedPayload(timestampSeconds, payloadBuffer(payload))).digest("hex");
|
|
804
|
+
return `t=${timestampSeconds},v1=${signature}`;
|
|
805
|
+
}
|
|
806
|
+
function buildWebhookHeaders(payload, event, secret, options) {
|
|
807
|
+
const eventId = assertNonEmpty(event.id ?? "", "Webhook event id");
|
|
808
|
+
const eventType = assertNonEmpty(event.type, "Webhook event type");
|
|
809
|
+
const webhookId = assertNonEmpty(options.webhookId, "Webhook id");
|
|
810
|
+
const deliveryId = assertNonEmpty(options.deliveryId, "Webhook delivery id");
|
|
811
|
+
return {
|
|
812
|
+
[WEBHOOK_SIGNATURE_HEADER]: signWebhookPayload(
|
|
813
|
+
payload,
|
|
814
|
+
secret,
|
|
815
|
+
options.timestamp
|
|
816
|
+
),
|
|
817
|
+
[WEBHOOK_EVENT_ID_HEADER]: eventId,
|
|
818
|
+
[WEBHOOK_EVENT_TYPE_HEADER]: eventType,
|
|
819
|
+
[WEBHOOK_ID_HEADER]: webhookId,
|
|
820
|
+
[WEBHOOK_DELIVERY_ID_HEADER]: deliveryId
|
|
821
|
+
};
|
|
822
|
+
}
|
|
768
823
|
var RstreamWebhookResource = class {
|
|
769
824
|
async event(payload, header, secret, tolerance = 300, receivedAt = Date.now()) {
|
|
770
|
-
const
|
|
825
|
+
const rawPayload = payloadBuffer(payload);
|
|
771
826
|
const signatureHeader = Array.isArray(header) ? header[0] : header.toString();
|
|
772
827
|
if (!signatureHeader) {
|
|
773
828
|
throw new Error("No signature header");
|
|
@@ -787,8 +842,7 @@ var RstreamWebhookResource = class {
|
|
|
787
842
|
`Webhook signature timestamp outside tolerance: ${now}, ${timestampNum}`
|
|
788
843
|
);
|
|
789
844
|
}
|
|
790
|
-
const
|
|
791
|
-
const expectedSignature = crypto2.createHmac("sha256", secret).update(signedPayload).digest();
|
|
845
|
+
const expectedSignature = crypto2.createHmac("sha256", secret).update(signedPayload(timestamp, rawPayload)).digest();
|
|
792
846
|
const signatureMatched = signatures.some(
|
|
793
847
|
(signature) => signatureMatches(signature, expectedSignature)
|
|
794
848
|
);
|
|
@@ -796,9 +850,7 @@ var RstreamWebhookResource = class {
|
|
|
796
850
|
throw new Error("Signature verification failed");
|
|
797
851
|
}
|
|
798
852
|
try {
|
|
799
|
-
return webhookEventsSchema.parse(
|
|
800
|
-
JSON.parse(payloadBuffer.toString("utf8"))
|
|
801
|
-
);
|
|
853
|
+
return webhookEventsSchema.parse(JSON.parse(rawPayload.toString("utf8")));
|
|
802
854
|
} catch (error) {
|
|
803
855
|
throw new Error(
|
|
804
856
|
`Failed to parse webhook payload: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -806,6 +858,15 @@ var RstreamWebhookResource = class {
|
|
|
806
858
|
);
|
|
807
859
|
}
|
|
808
860
|
}
|
|
861
|
+
generateSigningSecret() {
|
|
862
|
+
return generateWebhookSigningSecret();
|
|
863
|
+
}
|
|
864
|
+
sign(payload, secret, timestamp) {
|
|
865
|
+
return signWebhookPayload(payload, secret, timestamp);
|
|
866
|
+
}
|
|
867
|
+
headers(payload, event, secret, options) {
|
|
868
|
+
return buildWebhookHeaders(payload, event, secret, options);
|
|
869
|
+
}
|
|
809
870
|
};
|
|
810
871
|
|
|
811
872
|
// src/tunnels.ts
|
|
@@ -1237,7 +1298,13 @@ export {
|
|
|
1237
1298
|
RstreamWebhookResource,
|
|
1238
1299
|
RstreamWebhookResource as RstreamWebhookRessource,
|
|
1239
1300
|
RstreamTunnelsClient as Tunnels,
|
|
1301
|
+
WEBHOOK_DELIVERY_ID_HEADER,
|
|
1302
|
+
WEBHOOK_EVENT_ID_HEADER,
|
|
1303
|
+
WEBHOOK_EVENT_TYPE_HEADER,
|
|
1304
|
+
WEBHOOK_ID_HEADER,
|
|
1305
|
+
WEBHOOK_SIGNATURE_HEADER,
|
|
1240
1306
|
Watch,
|
|
1307
|
+
buildWebhookHeaders,
|
|
1241
1308
|
clientFilterSchema,
|
|
1242
1309
|
clientSchema,
|
|
1243
1310
|
createAPITURNCredentials,
|
|
@@ -1249,6 +1316,7 @@ export {
|
|
|
1249
1316
|
engineOpenApiJson,
|
|
1250
1317
|
eventSchema,
|
|
1251
1318
|
formatTunnelHost,
|
|
1319
|
+
generateWebhookSigningSecret,
|
|
1252
1320
|
listClientsParamsSchema,
|
|
1253
1321
|
listClientsResponseSchema,
|
|
1254
1322
|
listTunnelsParamsSchema,
|
|
@@ -1261,6 +1329,7 @@ export {
|
|
|
1261
1329
|
resolveTunnelsAPIURL,
|
|
1262
1330
|
resolveTunnelsCredentials,
|
|
1263
1331
|
resolveTunnelsEngine,
|
|
1332
|
+
signWebhookPayload,
|
|
1264
1333
|
streamEndpointSchema,
|
|
1265
1334
|
streamSummaryEventSchema,
|
|
1266
1335
|
streamSummarySchema,
|