@rstreamlabs/tunnels 3.1.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/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,18 +60,22 @@ __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,
60
67
  listTunnelsResponseSchema: () => listTunnelsResponseSchema,
61
68
  normalizeEngineAddress: () => normalizeEngineAddress,
62
69
  parseWebTTYServers: () => parseWebTTYServers,
70
+ projectLogEventsSchema: () => projectLogEventsSchema,
63
71
  resolveControlPlaneCredentials: () => resolveControlPlaneCredentials,
64
72
  resolveManagedTunnelsProject: () => resolveManagedTunnelsProject,
65
73
  resolveTunnelsAPIURL: () => resolveTunnelsAPIURL,
66
74
  resolveTunnelsCredentials: () => resolveTunnelsCredentials,
67
75
  resolveTunnelsEngine: () => resolveTunnelsEngine,
76
+ signWebhookPayload: () => signWebhookPayload,
68
77
  streamEndpointSchema: () => streamEndpointSchema,
78
+ streamSummaryEventSchema: () => streamSummaryEventSchema,
69
79
  streamSummarySchema: () => streamSummarySchema,
70
80
  streamTLSInfoSchema: () => streamTLSInfoSchema,
71
81
  tunnelFilterSchema: () => tunnelFilterSchema,
@@ -547,15 +557,23 @@ var commonEventsSchema = [
547
557
  object: import_tunnel.tunnelSchema
548
558
  })
549
559
  ];
560
+ var webhookDeliverableEventsSchema = [
561
+ commonEventsSchema[0],
562
+ commonEventsSchema[2],
563
+ commonEventsSchema[3],
564
+ commonEventsSchema[5]
565
+ ];
550
566
  var wsEventsSchema = z3.union([
551
567
  stateInitialEventSchema,
552
568
  ...commonEventsSchema
553
569
  ]);
554
- var webhookEventsSchema = z3.union([
555
- ...commonEventsSchema,
556
- streamSummaryEventSchema
570
+ var webhookEventsSchema = z3.union(webhookDeliverableEventsSchema);
571
+ var projectLogEventsSchema = streamSummaryEventSchema;
572
+ var eventSchema = z3.union([
573
+ wsEventsSchema,
574
+ webhookEventsSchema,
575
+ projectLogEventsSchema
557
576
  ]);
558
- var eventSchema = z3.union([wsEventsSchema, webhookEventsSchema]);
559
577
 
560
578
  // src/tunnel.ts
561
579
  var import_tunnel2 = require("@rstreamlabs/rstream/tunnel");
@@ -1467,9 +1485,18 @@ var RstreamTURNResource = class {
1467
1485
 
1468
1486
  // src/webhooks-resource.ts
1469
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";
1470
1493
  var SIGNATURE_HEX_BYTES = 32;
1494
+ var webhookSecretBytes = 32;
1471
1495
  var signatureHexSchema = /^[0-9a-fA-F]{64}$/;
1472
1496
  var timestampSchema = /^\d+$/;
1497
+ function payloadBuffer(payload) {
1498
+ return Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
1499
+ }
1473
1500
  function parseSignatureTimestamp(timestamp) {
1474
1501
  if (!timestampSchema.test(timestamp)) {
1475
1502
  throw new Error("Invalid signature timestamp");
@@ -1486,6 +1513,19 @@ function normalizeTolerance(tolerance) {
1486
1513
  }
1487
1514
  return tolerance;
1488
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
+ }
1489
1529
  function readSignature(signature) {
1490
1530
  if (!signatureHexSchema.test(signature)) {
1491
1531
  return void 0;
@@ -1502,9 +1542,42 @@ function assertSecretConfigured(secret) {
1502
1542
  throw new Error("Webhook signing secret is required");
1503
1543
  }
1504
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
+ }
1505
1578
  var RstreamWebhookResource = class {
1506
1579
  async event(payload, header, secret, tolerance = 300, receivedAt = Date.now()) {
1507
- const payloadBuffer = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
1580
+ const rawPayload = payloadBuffer(payload);
1508
1581
  const signatureHeader = Array.isArray(header) ? header[0] : header.toString();
1509
1582
  if (!signatureHeader) {
1510
1583
  throw new Error("No signature header");
@@ -1524,8 +1597,7 @@ var RstreamWebhookResource = class {
1524
1597
  `Webhook signature timestamp outside tolerance: ${now}, ${timestampNum}`
1525
1598
  );
1526
1599
  }
1527
- const signedPayload = `${timestamp}.${payloadBuffer.toString("utf8")}`;
1528
- 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();
1529
1601
  const signatureMatched = signatures.some(
1530
1602
  (signature) => signatureMatches(signature, expectedSignature)
1531
1603
  );
@@ -1533,9 +1605,7 @@ var RstreamWebhookResource = class {
1533
1605
  throw new Error("Signature verification failed");
1534
1606
  }
1535
1607
  try {
1536
- return webhookEventsSchema.parse(
1537
- JSON.parse(payloadBuffer.toString("utf8"))
1538
- );
1608
+ return webhookEventsSchema.parse(JSON.parse(rawPayload.toString("utf8")));
1539
1609
  } catch (error) {
1540
1610
  throw new Error(
1541
1611
  `Failed to parse webhook payload: ${error instanceof Error ? error.message : String(error)}`,
@@ -1543,6 +1613,15 @@ var RstreamWebhookResource = class {
1543
1613
  );
1544
1614
  }
1545
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
+ }
1546
1625
  };
1547
1626
 
1548
1627
  // src/tunnels.ts
@@ -1975,7 +2054,13 @@ function parseWebTTYServers(tunnels) {
1975
2054
  RstreamWebhookResource,
1976
2055
  RstreamWebhookRessource,
1977
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,
1978
2062
  Watch,
2063
+ buildWebhookHeaders,
1979
2064
  clientFilterSchema,
1980
2065
  clientSchema,
1981
2066
  createAPITURNCredentials,
@@ -1987,18 +2072,22 @@ function parseWebTTYServers(tunnels) {
1987
2072
  engineOpenApiJson,
1988
2073
  eventSchema,
1989
2074
  formatTunnelHost,
2075
+ generateWebhookSigningSecret,
1990
2076
  listClientsParamsSchema,
1991
2077
  listClientsResponseSchema,
1992
2078
  listTunnelsParamsSchema,
1993
2079
  listTunnelsResponseSchema,
1994
2080
  normalizeEngineAddress,
1995
2081
  parseWebTTYServers,
2082
+ projectLogEventsSchema,
1996
2083
  resolveControlPlaneCredentials,
1997
2084
  resolveManagedTunnelsProject,
1998
2085
  resolveTunnelsAPIURL,
1999
2086
  resolveTunnelsCredentials,
2000
2087
  resolveTunnelsEngine,
2088
+ signWebhookPayload,
2001
2089
  streamEndpointSchema,
2090
+ streamSummaryEventSchema,
2002
2091
  streamSummarySchema,
2003
2092
  streamTLSInfoSchema,
2004
2093
  tunnelFilterSchema,
package/dist/index.mjs CHANGED
@@ -9,13 +9,15 @@ import {
9
9
  listClientsResponseSchema,
10
10
  listTunnelsParamsSchema,
11
11
  listTunnelsResponseSchema,
12
+ projectLogEventsSchema,
12
13
  streamEndpointSchema,
14
+ streamSummaryEventSchema,
13
15
  streamSummarySchema,
14
16
  streamTLSInfoSchema,
15
17
  tunnelFilterSchema,
16
18
  webhookEventsSchema,
17
19
  wsEventsSchema
18
- } from "./chunk-UUZYVELQ.mjs";
20
+ } from "./chunk-VUAM3LRU.mjs";
19
21
 
20
22
  // src/auth-resource.ts
21
23
  import { createAuthTokenParamsSchema } from "@rstreamlabs/rstream/auth-token";
@@ -728,9 +730,18 @@ var RstreamTURNResource = class {
728
730
 
729
731
  // src/webhooks-resource.ts
730
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";
731
738
  var SIGNATURE_HEX_BYTES = 32;
739
+ var webhookSecretBytes = 32;
732
740
  var signatureHexSchema = /^[0-9a-fA-F]{64}$/;
733
741
  var timestampSchema = /^\d+$/;
742
+ function payloadBuffer(payload) {
743
+ return Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
744
+ }
734
745
  function parseSignatureTimestamp(timestamp) {
735
746
  if (!timestampSchema.test(timestamp)) {
736
747
  throw new Error("Invalid signature timestamp");
@@ -747,6 +758,19 @@ function normalizeTolerance(tolerance) {
747
758
  }
748
759
  return tolerance;
749
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
+ }
750
774
  function readSignature(signature) {
751
775
  if (!signatureHexSchema.test(signature)) {
752
776
  return void 0;
@@ -763,9 +787,42 @@ function assertSecretConfigured(secret) {
763
787
  throw new Error("Webhook signing secret is required");
764
788
  }
765
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
+ }
766
823
  var RstreamWebhookResource = class {
767
824
  async event(payload, header, secret, tolerance = 300, receivedAt = Date.now()) {
768
- const payloadBuffer = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
825
+ const rawPayload = payloadBuffer(payload);
769
826
  const signatureHeader = Array.isArray(header) ? header[0] : header.toString();
770
827
  if (!signatureHeader) {
771
828
  throw new Error("No signature header");
@@ -785,8 +842,7 @@ var RstreamWebhookResource = class {
785
842
  `Webhook signature timestamp outside tolerance: ${now}, ${timestampNum}`
786
843
  );
787
844
  }
788
- const signedPayload = `${timestamp}.${payloadBuffer.toString("utf8")}`;
789
- const expectedSignature = crypto2.createHmac("sha256", secret).update(signedPayload).digest();
845
+ const expectedSignature = crypto2.createHmac("sha256", secret).update(signedPayload(timestamp, rawPayload)).digest();
790
846
  const signatureMatched = signatures.some(
791
847
  (signature) => signatureMatches(signature, expectedSignature)
792
848
  );
@@ -794,9 +850,7 @@ var RstreamWebhookResource = class {
794
850
  throw new Error("Signature verification failed");
795
851
  }
796
852
  try {
797
- return webhookEventsSchema.parse(
798
- JSON.parse(payloadBuffer.toString("utf8"))
799
- );
853
+ return webhookEventsSchema.parse(JSON.parse(rawPayload.toString("utf8")));
800
854
  } catch (error) {
801
855
  throw new Error(
802
856
  `Failed to parse webhook payload: ${error instanceof Error ? error.message : String(error)}`,
@@ -804,6 +858,15 @@ var RstreamWebhookResource = class {
804
858
  );
805
859
  }
806
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
+ }
807
870
  };
808
871
 
809
872
  // src/tunnels.ts
@@ -1235,7 +1298,13 @@ export {
1235
1298
  RstreamWebhookResource,
1236
1299
  RstreamWebhookResource as RstreamWebhookRessource,
1237
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,
1238
1306
  Watch,
1307
+ buildWebhookHeaders,
1239
1308
  clientFilterSchema,
1240
1309
  clientSchema,
1241
1310
  createAPITURNCredentials,
@@ -1247,18 +1316,22 @@ export {
1247
1316
  engineOpenApiJson,
1248
1317
  eventSchema,
1249
1318
  formatTunnelHost,
1319
+ generateWebhookSigningSecret,
1250
1320
  listClientsParamsSchema,
1251
1321
  listClientsResponseSchema,
1252
1322
  listTunnelsParamsSchema,
1253
1323
  listTunnelsResponseSchema,
1254
1324
  normalizeEngineAddress,
1255
1325
  parseWebTTYServers,
1326
+ projectLogEventsSchema,
1256
1327
  resolveControlPlaneCredentials,
1257
1328
  resolveManagedTunnelsProject,
1258
1329
  resolveTunnelsAPIURL,
1259
1330
  resolveTunnelsCredentials,
1260
1331
  resolveTunnelsEngine,
1332
+ signWebhookPayload,
1261
1333
  streamEndpointSchema,
1334
+ streamSummaryEventSchema,
1262
1335
  streamSummarySchema,
1263
1336
  streamTLSInfoSchema,
1264
1337
  tunnelFilterSchema,
package/dist/openapi.js CHANGED
@@ -353,15 +353,23 @@ var commonEventsSchema = [
353
353
  object: import_tunnel2.tunnelSchema
354
354
  })
355
355
  ];
356
+ var webhookDeliverableEventsSchema = [
357
+ commonEventsSchema[0],
358
+ commonEventsSchema[2],
359
+ commonEventsSchema[3],
360
+ commonEventsSchema[5]
361
+ ];
356
362
  var wsEventsSchema = z4.union([
357
363
  stateInitialEventSchema,
358
364
  ...commonEventsSchema
359
365
  ]);
360
- var webhookEventsSchema = z4.union([
361
- ...commonEventsSchema,
362
- streamSummaryEventSchema
366
+ var webhookEventsSchema = z4.union(webhookDeliverableEventsSchema);
367
+ var projectLogEventsSchema = streamSummaryEventSchema;
368
+ var eventSchema = z4.union([
369
+ wsEventsSchema,
370
+ webhookEventsSchema,
371
+ projectLogEventsSchema
363
372
  ]);
364
- var eventSchema = z4.union([wsEventsSchema, webhookEventsSchema]);
365
373
 
366
374
  // src/openapi.ts
367
375
  var z5 = __toESM(require("zod"));
package/dist/openapi.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  engineOpenApiDocument,
3
3
  engineOpenApiJson
4
- } from "./chunk-UUZYVELQ.mjs";
4
+ } from "./chunk-VUAM3LRU.mjs";
5
5
  export {
6
6
  engineOpenApiDocument,
7
7
  engineOpenApiJson
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rstreamlabs/tunnels",
3
- "version": "3.1.0",
3
+ "version": "3.2.1",
4
4
  "description": "JS/TS SDK for the rstream Engine API and tunnel runtime APIs.",
5
5
  "author": "@uartnet <hello@rstream.io>",
6
6
  "repository": {
@@ -47,7 +47,7 @@
47
47
  "typescript-config": "*"
48
48
  },
49
49
  "dependencies": {
50
- "@rstreamlabs/rstream": "3.0.0",
50
+ "@rstreamlabs/rstream": "3.1.0",
51
51
  "@types/jsonwebtoken": "^9.0.10",
52
52
  "jsonwebtoken": "^9.0.3",
53
53
  "zod": "^4.3.6"