@rstreamlabs/rstream 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,543 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Rstream: () => RstreamClient,
34
+ RstreamAuthRessource: () => RstreamAuthRessource,
35
+ RstreamClient: () => RstreamClient,
36
+ RstreamClientsRessource: () => RstreamClientsRessource,
37
+ RstreamTunnelsRessource: () => RstreamTunnelsRessource,
38
+ RstreamWebHooksRessource: () => RstreamWebHooksRessource,
39
+ Watch: () => Watch,
40
+ clientSchema: () => clientSchema,
41
+ createShortTermTokenParamsSchema: () => createShortTermTokenParamsSchema,
42
+ createShortTermTokenResponseSchema: () => createShortTermTokenResponseSchema,
43
+ eventSchema: () => eventSchema,
44
+ listClientsParamsSchema: () => listClientsParamsSchema,
45
+ listClientsResponseSchema: () => listClientsResponseSchema,
46
+ listTunnelsParamsSchema: () => listTunnelsParamsSchema,
47
+ listTunnelsResponseSchema: () => listTunnelsResponseSchema,
48
+ rstreamAuthPayloadSchema: () => rstreamAuthPayloadSchema,
49
+ tunnelSchema: () => tunnelSchema
50
+ });
51
+ module.exports = __toCommonJS(index_exports);
52
+
53
+ // src/auth-ressource.ts
54
+ var import_crypto = __toESM(require("crypto"));
55
+ var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
56
+ var RstreamAuthRessource = class {
57
+ client;
58
+ constructor(client) {
59
+ this.client = client;
60
+ }
61
+ async createShortTermToken(params, options) {
62
+ const credentials = options?.credentials || this.client.credentials;
63
+ if (!credentials || !("clientId" in credentials)) {
64
+ throw new Error(
65
+ "Application credentials (client id, client secret) are required to create a short term token."
66
+ );
67
+ }
68
+ const now = Math.floor(Date.now() / 1e3);
69
+ const exp = now + (params?.expires_in ?? 60);
70
+ const payload = {
71
+ iat: now,
72
+ // Issued at
73
+ exp,
74
+ // Expiration time
75
+ type: "app",
76
+ clientId: credentials.clientId,
77
+ metadata: {
78
+ engine: this.client.engine,
79
+ permissions: params?.permissions
80
+ }
81
+ };
82
+ const pk = import_crypto.default.createPrivateKey({
83
+ key: Buffer.from(credentials.clientSecret, "hex"),
84
+ format: "der",
85
+ type: "pkcs8"
86
+ });
87
+ const token = import_jsonwebtoken.default.sign(payload, pk, {
88
+ algorithm: "ES512"
89
+ });
90
+ const result = {
91
+ token
92
+ };
93
+ return result;
94
+ }
95
+ };
96
+
97
+ // src/zod.ts
98
+ var z = __toESM(require("zod"));
99
+ var StringFilter = z.union([
100
+ z.string(),
101
+ z.object({ exact: z.string() }),
102
+ z.object({ oneof: z.array(z.string()) }),
103
+ z.object({ regex: z.string() })
104
+ ]);
105
+ function transform(field) {
106
+ if (field instanceof z.ZodOptional) {
107
+ return transform(field.unwrap()).optional();
108
+ }
109
+ if (field instanceof z.ZodString) {
110
+ return StringFilter.optional();
111
+ }
112
+ if (field instanceof z.ZodRecord) {
113
+ return z.record(field.keySchema, transform(field.valueSchema)).optional();
114
+ }
115
+ return field.optional();
116
+ }
117
+ function filters(base) {
118
+ const entries = Object.entries(base.shape).map(([key, field]) => [
119
+ key,
120
+ transform(field)
121
+ ]);
122
+ const shape = Object.fromEntries(entries);
123
+ const node = z.lazy(
124
+ () => z.union([
125
+ z.object(shape),
126
+ z.object({ AND: z.array(node) }),
127
+ z.object({ OR: z.array(node) })
128
+ ])
129
+ );
130
+ return node;
131
+ }
132
+ function select(base) {
133
+ const entries = Object.entries(base.shape).map(([key]) => {
134
+ return [key, z.boolean().optional()];
135
+ });
136
+ const shape = Object.fromEntries(entries);
137
+ return z.object(shape);
138
+ }
139
+
140
+ // src/tunnel.ts
141
+ var z2 = __toESM(require("zod"));
142
+ var tunnelSchema = z2.object({
143
+ id: z2.string(),
144
+ client_id: z2.string(),
145
+ user_id: z2.string(),
146
+ status: z2.enum(["online", "offline"]),
147
+ name: z2.string().optional(),
148
+ protocol: z2.string(),
149
+ publish: z2.boolean(),
150
+ labels: z2.record(z2.string().optional()).optional(),
151
+ host: z2.string().optional(),
152
+ tls_mode: z2.string(),
153
+ tls_min_version: z2.string().optional(),
154
+ mtls: z2.boolean(),
155
+ token_auth: z2.boolean(),
156
+ path: z2.string().optional()
157
+ });
158
+ var listTunnelsParamsSchema = z2.object({
159
+ limit: z2.number().optional(),
160
+ filters: tunnelSchema.pick({
161
+ status: true,
162
+ client_id: true,
163
+ protocol: true,
164
+ publish: true,
165
+ labels: true
166
+ }).partial().optional()
167
+ });
168
+ var listTunnelsResponseSchema = z2.object({
169
+ tunnels: z2.array(tunnelSchema)
170
+ });
171
+
172
+ // src/auth.ts
173
+ var z3 = __toESM(require("zod"));
174
+ var createShortTermTokenParamsSchema = z3.object({
175
+ expires_in: z3.number().default(60),
176
+ // 1 minute
177
+ permissions: z3.object({
178
+ // Permissions for creating a tunnel
179
+ create: z3.union([
180
+ z3.boolean(),
181
+ z3.object({
182
+ filters: filters(tunnelSchema).optional()
183
+ })
184
+ ]).optional(),
185
+ // Permissions for connecting to a tunnel
186
+ connect: z3.union([
187
+ z3.boolean(),
188
+ z3.object({
189
+ filters: filters(tunnelSchema).optional()
190
+ })
191
+ ]).optional(),
192
+ // Permissions for listing tunnels
193
+ list: z3.union([
194
+ z3.boolean(),
195
+ z3.object({
196
+ filters: filters(tunnelSchema).optional(),
197
+ select: select(tunnelSchema).optional()
198
+ })
199
+ ]).optional()
200
+ }),
201
+ // Additional metadata
202
+ metadata: z3.unknown().optional()
203
+ });
204
+ var createShortTermTokenResponseSchema = z3.object({
205
+ token: z3.string()
206
+ });
207
+ var rstreamAuthPayloadSchema = z3.discriminatedUnion("type", [
208
+ z3.object({
209
+ type: z3.literal("pat")
210
+ }),
211
+ z3.object({
212
+ type: z3.literal("app"),
213
+ clientId: z3.string()
214
+ })
215
+ ]).and(
216
+ z3.object({
217
+ metadata: z3.object({
218
+ engine: z3.string().optional(),
219
+ permissions: createShortTermTokenParamsSchema.shape.permissions.optional()
220
+ }).optional()
221
+ })
222
+ );
223
+
224
+ // src/client.ts
225
+ var z4 = __toESM(require("zod"));
226
+ var clientSchema = z4.object({
227
+ id: z4.string(),
228
+ user_id: z4.string(),
229
+ status: z4.enum(["online", "offline"]),
230
+ details: z4.object({
231
+ agent: z4.string().optional(),
232
+ os: z4.string().optional(),
233
+ version: z4.string().optional(),
234
+ protocol_version: z4.string().optional()
235
+ }).optional(),
236
+ labels: z4.record(z4.string().optional()).optional()
237
+ });
238
+ var listClientsParamsSchema = z4.object({
239
+ limit: z4.number().optional(),
240
+ filters: clientSchema.pick({
241
+ labels: true
242
+ }).partial().optional()
243
+ });
244
+ var listClientsResponseSchema = z4.object({
245
+ clients: z4.array(clientSchema)
246
+ });
247
+
248
+ // src/clients-ressource.ts
249
+ var RstreamClientsRessource = class {
250
+ client;
251
+ constructor(client) {
252
+ this.client = client;
253
+ }
254
+ async list(params) {
255
+ const response = await this.client.request(
256
+ `/clients?params=${encodeURIComponent(JSON.stringify(params))}`,
257
+ {
258
+ method: "GET"
259
+ }
260
+ );
261
+ return listClientsResponseSchema.parse(response);
262
+ }
263
+ async get(id) {
264
+ const response = await this.client.request(`/clients/${id}`, {
265
+ method: "GET"
266
+ });
267
+ return clientSchema.parse(response);
268
+ }
269
+ };
270
+
271
+ // src/event.ts
272
+ var z5 = __toESM(require("zod"));
273
+ var eventSchema = z5.union([
274
+ z5.object({
275
+ type: z5.literal("client.created"),
276
+ object: clientSchema
277
+ }),
278
+ z5.object({
279
+ type: z5.literal("client.updated"),
280
+ object: clientSchema
281
+ }),
282
+ z5.object({
283
+ type: z5.literal("client.deleted"),
284
+ object: clientSchema
285
+ }),
286
+ z5.object({
287
+ type: z5.literal("tunnel.created"),
288
+ object: tunnelSchema
289
+ }),
290
+ z5.object({
291
+ type: z5.literal("tunnel.updated"),
292
+ object: tunnelSchema
293
+ }),
294
+ z5.object({
295
+ type: z5.literal("tunnel.deleted"),
296
+ object: tunnelSchema
297
+ })
298
+ ]);
299
+
300
+ // src/tunnels-ressource.ts
301
+ var RstreamTunnelsRessource = class {
302
+ client;
303
+ constructor(client) {
304
+ this.client = client;
305
+ }
306
+ async list(params) {
307
+ const response = await this.client.request(
308
+ `/tunnels?params=${encodeURIComponent(JSON.stringify(params))}`,
309
+ {
310
+ method: "GET"
311
+ }
312
+ );
313
+ return listTunnelsResponseSchema.parse(response);
314
+ }
315
+ async get(id) {
316
+ const response = await this.client.request(`/tunnels/${id}`, {
317
+ method: "GET"
318
+ });
319
+ return tunnelSchema.parse(response);
320
+ }
321
+ };
322
+
323
+ // src/webhooks-ressource.ts
324
+ var import_crypto2 = __toESM(require("crypto"));
325
+ var RstreamWebHooksRessource = class {
326
+ client;
327
+ constructor(client) {
328
+ this.client = client;
329
+ }
330
+ // Constructs and verifies the signature of an Event from the provided details.
331
+ async event(payload, header, secret, tolerance = 300, receivedAt = Date.now()) {
332
+ const payloadBuffer = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
333
+ const signatureHeader = Array.isArray(header) ? header[0] : header.toString();
334
+ if (!signatureHeader) {
335
+ throw new Error("No signature header");
336
+ }
337
+ const elements = signatureHeader.split(",").map((element) => element.trim());
338
+ let timestamp;
339
+ const signatures = [];
340
+ for (const element of elements) {
341
+ if (element.startsWith("t=")) {
342
+ timestamp = element.substring(2);
343
+ } else if (element.startsWith("v1=")) {
344
+ signatures.push(element.substring(3));
345
+ }
346
+ }
347
+ if (!timestamp || signatures.length === 0) {
348
+ throw new Error("Invalid signature header format");
349
+ }
350
+ const timestampNum = parseInt(timestamp, 10);
351
+ const now = Math.floor(receivedAt / 1e3);
352
+ if (Math.abs(now - timestampNum) > tolerance) {
353
+ throw new Error(
354
+ `Webhook signature timestamp outside tolerance: ${now}, ${timestampNum}`
355
+ );
356
+ }
357
+ const signedPayload = `${timestamp}.${payloadBuffer.toString("utf8")}`;
358
+ const expectedSignature = import_crypto2.default.createHmac("sha256", secret).update(signedPayload).digest("hex");
359
+ let signatureMatched = false;
360
+ for (const signature of signatures) {
361
+ try {
362
+ if (import_crypto2.default.timingSafeEqual(
363
+ Buffer.from(signature),
364
+ Buffer.from(expectedSignature)
365
+ )) {
366
+ signatureMatched = true;
367
+ break;
368
+ }
369
+ } catch (error) {
370
+ console.log("Error comparing signatures:", error);
371
+ continue;
372
+ }
373
+ }
374
+ if (!signatureMatched) {
375
+ throw new Error("Signature verification failed");
376
+ }
377
+ try {
378
+ return eventSchema.parse(JSON.parse(payloadBuffer.toString("utf8")));
379
+ } catch (error) {
380
+ throw new Error(
381
+ `Failed to parse webhook payload: ${error instanceof Error ? error.message : String(error)}`
382
+ );
383
+ }
384
+ }
385
+ };
386
+
387
+ // src/rstream.ts
388
+ var RstreamClient = class {
389
+ cfg;
390
+ constructor(config) {
391
+ this.cfg = config;
392
+ }
393
+ get engine() {
394
+ return this.cfg?.engine || process.env.RSTREAM_DEFAULT_ENGINE || "engine.rstream.io:443";
395
+ }
396
+ get credentials() {
397
+ if (this.cfg?.credentials && "token" in this.cfg.credentials) {
398
+ return this.cfg.credentials;
399
+ }
400
+ if (process.env.RSTREAM_DEFAULT_AUTHENTICATION_TOKEN) {
401
+ return {
402
+ token: process.env.RSTREAM_DEFAULT_AUTHENTICATION_TOKEN
403
+ };
404
+ }
405
+ if (this.cfg?.credentials && "clientId" in this.cfg.credentials) {
406
+ return this.cfg.credentials;
407
+ }
408
+ if (process.env.RSTREAM_DEFAULT_CLIENT_ID && process.env.RSTREAM_DEFAULT_CLIENT_SECRET) {
409
+ return {
410
+ clientId: process.env.RSTREAM_DEFAULT_CLIENT_ID,
411
+ clientSecret: process.env.RSTREAM_DEFAULT_CLIENT_SECRET
412
+ };
413
+ }
414
+ return void 0;
415
+ }
416
+ get api() {
417
+ return `https://${this.engine}/api`;
418
+ }
419
+ get auth() {
420
+ return new RstreamAuthRessource(this);
421
+ }
422
+ get clients() {
423
+ return new RstreamClientsRessource(this);
424
+ }
425
+ get tunnels() {
426
+ return new RstreamTunnelsRessource(this);
427
+ }
428
+ get webhooks() {
429
+ return new RstreamWebHooksRessource(this);
430
+ }
431
+ async getToken() {
432
+ const credentials = this.credentials;
433
+ if (credentials && "token" in credentials) {
434
+ return credentials.token;
435
+ }
436
+ if (credentials && "clientId" in credentials) {
437
+ return (await this.auth.createShortTermToken(void 0, {
438
+ credentials: {
439
+ clientId: credentials.clientId,
440
+ clientSecret: credentials.clientSecret
441
+ }
442
+ })).token;
443
+ }
444
+ return void 0;
445
+ }
446
+ async request(path, options) {
447
+ const url = `${this.api}${path}`;
448
+ const headers = new Headers(options?.headers || {});
449
+ const token = await this.getToken();
450
+ if (token) {
451
+ headers.set("Authorization", `Bearer ${token}`);
452
+ }
453
+ const response = await fetch(url, {
454
+ ...options,
455
+ headers
456
+ });
457
+ if (!response.ok) {
458
+ const errorText = await response.text();
459
+ throw new Error(`HTTP error ${response.status}: ${errorText}`);
460
+ }
461
+ return await response.json();
462
+ }
463
+ };
464
+
465
+ // src/watch.ts
466
+ var import_jsonwebtoken2 = __toESM(require("jsonwebtoken"));
467
+ var Watch = class {
468
+ connection = null;
469
+ connectionState = "preparing";
470
+ config;
471
+ events;
472
+ constructor(config, events) {
473
+ this.config = config;
474
+ this.events = events;
475
+ }
476
+ async connect() {
477
+ if (this.connectionState !== "preparing") {
478
+ throw new Error("Watch: Connection already started or closed.");
479
+ }
480
+ this.connectionState = "connecting";
481
+ const token = await this.config.auth.token();
482
+ const payload = rstreamAuthPayloadSchema.parse(
483
+ import_jsonwebtoken2.default.decode(token, { complete: false })
484
+ );
485
+ const base = `https://${this.config.engine || payload.metadata?.engine || "engine.rstream.io:443"}`;
486
+ if (this.config.transport === "sse") {
487
+ const url = new URL(`/api/sse`, base);
488
+ url.searchParams.set("rstream.token", token);
489
+ this.connection = new EventSource(url.toString());
490
+ } else {
491
+ const url = new URL(`/api/websocket`, base);
492
+ url.searchParams.set("rstream.token", token);
493
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
494
+ this.connection = new WebSocket(url.toString());
495
+ }
496
+ this.connection.onopen = () => {
497
+ this.connectionState = "connected";
498
+ this.events.onConnect?.();
499
+ };
500
+ this.connection.onmessage = (msg) => {
501
+ const parsed = eventSchema.parse(JSON.parse(msg.data));
502
+ this.events.onEvent?.(parsed);
503
+ };
504
+ this.connection.onerror = () => {
505
+ if (this.connectionState !== "closed") {
506
+ this.disconnect();
507
+ }
508
+ };
509
+ }
510
+ disconnect() {
511
+ if (this.connection) {
512
+ this.connection.onerror = null;
513
+ this.connection.onmessage = null;
514
+ this.connection.onopen = null;
515
+ this.connection.close();
516
+ this.connection = null;
517
+ }
518
+ if (this.connectionState !== "closed") {
519
+ this.connectionState = "closed";
520
+ this.events.onClose?.();
521
+ }
522
+ }
523
+ };
524
+ // Annotate the CommonJS export names for ESM import in node:
525
+ 0 && (module.exports = {
526
+ Rstream,
527
+ RstreamAuthRessource,
528
+ RstreamClient,
529
+ RstreamClientsRessource,
530
+ RstreamTunnelsRessource,
531
+ RstreamWebHooksRessource,
532
+ Watch,
533
+ clientSchema,
534
+ createShortTermTokenParamsSchema,
535
+ createShortTermTokenResponseSchema,
536
+ eventSchema,
537
+ listClientsParamsSchema,
538
+ listClientsResponseSchema,
539
+ listTunnelsParamsSchema,
540
+ listTunnelsResponseSchema,
541
+ rstreamAuthPayloadSchema,
542
+ tunnelSchema
543
+ });