@rstreamlabs/tunnels 1.0.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,1365 @@
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
+ RstreamAuthResource: () => RstreamAuthResource,
34
+ RstreamAuthRessource: () => RstreamAuthResource,
35
+ RstreamClient: () => RstreamTunnelsClient,
36
+ RstreamClientsResource: () => RstreamClientsResource,
37
+ RstreamClientsRessource: () => RstreamClientsResource,
38
+ RstreamTURNResource: () => RstreamTURNResource,
39
+ RstreamTunnelsClient: () => RstreamTunnelsClient,
40
+ RstreamTunnelsResource: () => RstreamTunnelsResource,
41
+ RstreamTunnelsRessource: () => RstreamTunnelsResource,
42
+ RstreamWebhookResource: () => RstreamWebhookResource,
43
+ RstreamWebhookRessource: () => RstreamWebhookResource,
44
+ Tunnels: () => RstreamTunnelsClient,
45
+ Watch: () => Watch,
46
+ authTokenPermissionsSchema: () => authTokenPermissionsSchema,
47
+ authTokenSchema: () => authTokenSchema,
48
+ authTokenScopesSchema: () => authTokenScopesSchema,
49
+ authTokenTunnelsScopesSchema: () => authTokenTunnelsScopesSchema,
50
+ clientSchema: () => clientSchema,
51
+ createAPITURNCredentials: () => createAPITURNCredentials,
52
+ createAPPTURNCredentials: () => createAPPTURNCredentials,
53
+ createAuthTokenFromClientCredentials: () => createAuthTokenFromClientCredentials,
54
+ createAuthTokenParamsSchema: () => createAuthTokenParamsSchema,
55
+ createAuthTokenResponseSchema: () => createAuthTokenResponseSchema,
56
+ createPATTURNCredentials: () => createPATTURNCredentials,
57
+ createTURNCredentials: () => createTURNCredentials,
58
+ eventSchema: () => eventSchema,
59
+ listClientsParamsSchema: () => listClientsParamsSchema,
60
+ listClientsResponseSchema: () => listClientsResponseSchema,
61
+ listTunnelsParamsSchema: () => listTunnelsParamsSchema,
62
+ listTunnelsResponseSchema: () => listTunnelsResponseSchema,
63
+ parseWebTTYServers: () => parseWebTTYServers,
64
+ resolveControlPlaneCredentials: () => resolveControlPlaneCredentials,
65
+ resolveTunnelsAPIURL: () => resolveTunnelsAPIURL,
66
+ resolveTunnelsCredentials: () => resolveTunnelsCredentials,
67
+ resolveTunnelsEngine: () => resolveTunnelsEngine,
68
+ streamSummarySchema: () => streamSummarySchema,
69
+ tokenSchema: () => tokenSchema,
70
+ tunnelSchema: () => tunnelSchema,
71
+ turnCredentialModeSchema: () => turnCredentialModeSchema,
72
+ webhookEventsSchema: () => webhookEventsSchema,
73
+ webttyServerSchema: () => webttyServerSchema,
74
+ wsEventsSchema: () => wsEventsSchema
75
+ });
76
+ module.exports = __toCommonJS(index_exports);
77
+
78
+ // src/auth-resource.ts
79
+ var import_rstream = require("@rstreamlabs/rstream");
80
+ var import_rstream2 = require("@rstreamlabs/rstream");
81
+ var RstreamAuthResource = class {
82
+ client;
83
+ constructor(client) {
84
+ this.client = client;
85
+ }
86
+ async createAuthToken(params, options) {
87
+ const credentials = options?.credentials || this.client.credentials;
88
+ if (!(0, import_rstream2.isClientCredentials)(credentials)) {
89
+ throw new Error(
90
+ "Application credentials (client id, client secret) are required to create an auth token."
91
+ );
92
+ }
93
+ const engine = options?.engine ?? await this.client.getEngine().catch(() => void 0);
94
+ return createAuthTokenFromClientCredentials(credentials, params, {
95
+ engine
96
+ });
97
+ }
98
+ };
99
+ function createAuthTokenFromClientCredentials(credentials, params, options) {
100
+ const payload = {
101
+ metadata: {
102
+ engine: options?.engine,
103
+ scopes: params?.scopes
104
+ },
105
+ permissions: null,
106
+ project_id: params?.project_id,
107
+ workspace_id: params?.workspace_id
108
+ };
109
+ const { token } = (0, import_rstream.createClientCredentialsToken)(credentials, {
110
+ claims: payload,
111
+ expiresInSeconds: params?.expires_in
112
+ });
113
+ return { token };
114
+ }
115
+
116
+ // src/zod.ts
117
+ var z = __toESM(require("zod"));
118
+ var StringFilter = z.union([
119
+ z.string(),
120
+ z.object({ exact: z.string() }),
121
+ z.object({ oneof: z.array(z.string()) }),
122
+ z.object({ regex: z.string() })
123
+ ]);
124
+ function transform(field) {
125
+ if (field instanceof z.ZodOptional) {
126
+ return z.optional(transform(field.unwrap()));
127
+ }
128
+ if (field instanceof z.ZodString) {
129
+ return z.optional(StringFilter);
130
+ }
131
+ if (field instanceof z.ZodRecord) {
132
+ return z.optional(z.record(field.keyType, transform(field.valueType)));
133
+ }
134
+ return z.optional(field);
135
+ }
136
+ function filters(base) {
137
+ const entries = Object.entries(base.shape).map(([key, field]) => [
138
+ key,
139
+ transform(field)
140
+ ]);
141
+ const shape = Object.fromEntries(entries);
142
+ const node = z.lazy(
143
+ () => z.union([
144
+ z.object(shape),
145
+ z.object({ AND: z.array(node) }),
146
+ z.object({ OR: z.array(node) })
147
+ ])
148
+ );
149
+ return node;
150
+ }
151
+ function select(base) {
152
+ const entries = Object.entries(base.shape).map(([key]) => {
153
+ return [key, z.boolean().optional()];
154
+ });
155
+ const shape = Object.fromEntries(entries);
156
+ return z.object(shape);
157
+ }
158
+
159
+ // src/tunnel.ts
160
+ var z2 = __toESM(require("zod"));
161
+ var tunnelSchema = z2.object({
162
+ // common properties
163
+ client_id: z2.string(),
164
+ user_id: z2.string().optional(),
165
+ workspace_id: z2.string().optional(),
166
+ project_id: z2.string().optional(),
167
+ cluster_id: z2.string().optional(),
168
+ plan: z2.string().optional(),
169
+ provider: z2.string().optional(),
170
+ region: z2.string().optional(),
171
+ status: z2.enum(["online", "offline"]),
172
+ // tunnel properties
173
+ id: z2.string(),
174
+ creation_date: z2.union([z2.string().datetime(), z2.date()]).optional(),
175
+ name: z2.string().optional(),
176
+ type: z2.enum(["bytestream", "datagram"]).optional(),
177
+ publish: z2.boolean().optional(),
178
+ protocol: z2.enum(["tls", "dtls", "quic", "http"]).optional(),
179
+ labels: z2.record(z2.string(), z2.string()).optional(),
180
+ geo_ip: z2.array(z2.string()).optional(),
181
+ trusted_ips: z2.array(z2.string()).optional(),
182
+ host: z2.string().optional(),
183
+ tls_mode: z2.enum(["passthrough", "terminated"]).optional(),
184
+ tls_alpns: z2.array(z2.string()).optional(),
185
+ tls_min_version: z2.string().optional(),
186
+ tls_ciphers: z2.array(z2.string()).optional(),
187
+ mtls: z2.boolean().optional(),
188
+ mtls_ca_cert_pem: z2.string().optional(),
189
+ http_version: z2.enum(["http/1.1", "h2", "h2c", "h3"]).optional(),
190
+ http_use_tls: z2.boolean().optional(),
191
+ token_auth: z2.boolean().optional(),
192
+ rstream_auth: z2.boolean().optional(),
193
+ challenge_mode: z2.boolean().optional()
194
+ });
195
+ var listTunnelsParamsSchema = z2.object({
196
+ limit: z2.number().optional(),
197
+ filters: tunnelSchema.pick({
198
+ status: true,
199
+ client_id: true,
200
+ protocol: true,
201
+ publish: true,
202
+ labels: true
203
+ }).partial().optional()
204
+ });
205
+ var listTunnelsResponseSchema = z2.array(tunnelSchema);
206
+
207
+ // src/auth.ts
208
+ var z3 = __toESM(require("zod"));
209
+ var authTokenPermissionsSchema = z3.array(z3.string());
210
+ var authTokenTunnelsScopesSchema = z3.object({
211
+ // Scopes for creating tunnels
212
+ create: z3.union([
213
+ z3.boolean(),
214
+ z3.object({
215
+ filters: filters(
216
+ tunnelSchema.omit({
217
+ client_id: true,
218
+ user_id: true,
219
+ workspace_id: true,
220
+ project_id: true,
221
+ cluster_id: true,
222
+ plan: true,
223
+ provider: true,
224
+ region: true,
225
+ status: true,
226
+ id: true,
227
+ creation_date: true
228
+ })
229
+ ).optional()
230
+ })
231
+ ]).optional(),
232
+ // Scopes for connecting to tunnels
233
+ connect: z3.union([
234
+ z3.boolean(),
235
+ z3.object({
236
+ filters: filters(tunnelSchema).optional(),
237
+ params: filters(
238
+ z3.object({
239
+ path: z3.string().optional()
240
+ })
241
+ ).optional()
242
+ })
243
+ ]).optional(),
244
+ // Scopes for listing tunnels
245
+ list: z3.union([
246
+ z3.boolean(),
247
+ z3.object({
248
+ filters: filters(tunnelSchema).optional(),
249
+ select: select(tunnelSchema).optional()
250
+ })
251
+ ]).optional()
252
+ });
253
+ var authTokenScopesSchema = z3.object({
254
+ tunnels: authTokenTunnelsScopesSchema.optional()
255
+ // Scopes related to tunnels
256
+ });
257
+ var authTokenSchema = z3.union([
258
+ z3.object({
259
+ type: z3.literal("auth"),
260
+ userId: z3.string(),
261
+ permissions: authTokenPermissionsSchema.nullable()
262
+ }),
263
+ z3.object({
264
+ type: z3.literal("pat"),
265
+ token_endpoint: z3.string().optional()
266
+ }),
267
+ z3.object({
268
+ type: z3.literal("app"),
269
+ clientId: z3.string(),
270
+ permissions: authTokenPermissionsSchema.nullable()
271
+ })
272
+ ]).and(
273
+ z3.object({
274
+ workspace_id: z3.string().optional(),
275
+ project_id: z3.string().optional(),
276
+ metadata: z3.object({
277
+ engine: z3.string().optional(),
278
+ scopes: authTokenScopesSchema.optional()
279
+ }).optional()
280
+ })
281
+ );
282
+ var tokenSchema = authTokenSchema;
283
+ var createAuthTokenParamsSchema = z3.object({
284
+ expires_in: z3.number().default(60),
285
+ // 1 minute
286
+ workspace_id: z3.string().optional(),
287
+ project_id: z3.string().optional(),
288
+ scopes: authTokenScopesSchema.optional(),
289
+ metadata: z3.unknown().optional()
290
+ // Additional metadata
291
+ });
292
+ var createAuthTokenResponseSchema = z3.object({
293
+ token: z3.string()
294
+ });
295
+
296
+ // src/client.ts
297
+ var z4 = __toESM(require("zod"));
298
+ var clientSchema = z4.object({
299
+ id: z4.string(),
300
+ status: z4.enum(["online", "offline"]),
301
+ user_id: z4.string().optional(),
302
+ workspace_id: z4.string().optional(),
303
+ project_id: z4.string().optional(),
304
+ cluster_id: z4.string().optional(),
305
+ plan: z4.string().optional(),
306
+ provider: z4.string().optional(),
307
+ region: z4.string().optional(),
308
+ agent: z4.string().optional(),
309
+ channel: z4.string().optional(),
310
+ version: z4.string().optional(),
311
+ os: z4.string().optional(),
312
+ arch: z4.string().optional(),
313
+ protocol_version: z4.string().optional(),
314
+ labels: z4.record(z4.string(), z4.string()).optional()
315
+ });
316
+ var listClientsParamsSchema = z4.object({
317
+ limit: z4.number().optional(),
318
+ filters: clientSchema.pick({
319
+ labels: true
320
+ }).partial().optional()
321
+ });
322
+ var listClientsResponseSchema = z4.array(clientSchema);
323
+
324
+ // src/clients-resource.ts
325
+ var RstreamClientsResource = class {
326
+ client;
327
+ constructor(client) {
328
+ this.client = client;
329
+ }
330
+ async list(params) {
331
+ const path = params === void 0 ? "/clients" : `/clients?params=${encodeURIComponent(JSON.stringify(params))}`;
332
+ const response = await this.client.request(path, {
333
+ method: "GET"
334
+ });
335
+ return listClientsResponseSchema.parse(response);
336
+ }
337
+ async get(id) {
338
+ const response = await this.client.request(`/clients/${id}`, {
339
+ method: "GET"
340
+ });
341
+ return clientSchema.parse(response);
342
+ }
343
+ };
344
+
345
+ // src/stream.ts
346
+ var z5 = __toESM(require("zod"));
347
+ var nonEmptyStringSchema = z5.string().min(1);
348
+ var isoDateTimeSchema = z5.string().datetime();
349
+ var ipAddressSchema = z5.string();
350
+ var intNonNegSchema = z5.number().int().nonnegative();
351
+ var numNonNegSchema = z5.number().nonnegative();
352
+ var geolocationSchema = z5.object({
353
+ country: z5.string().optional(),
354
+ region: z5.string().optional()
355
+ });
356
+ var endpointSchema = z5.object({
357
+ ip: ipAddressSchema.optional(),
358
+ geolocation: geolocationSchema.optional()
359
+ });
360
+ var httpHeadersSchema = z5.record(
361
+ z5.string(),
362
+ z5.union([z5.string(), z5.array(z5.string())])
363
+ );
364
+ var httpProtocolSchema = z5.enum(["http/1.1", "h2", "h2c", "h3"]);
365
+ var httpRequestSchema = z5.object({
366
+ method: nonEmptyStringSchema,
367
+ scheme: z5.string().min(1).optional(),
368
+ host: nonEmptyStringSchema.optional(),
369
+ path: nonEmptyStringSchema.optional(),
370
+ query: z5.string().optional(),
371
+ url: nonEmptyStringSchema.optional(),
372
+ headers: httpHeadersSchema.optional(),
373
+ userAgent: z5.string().optional()
374
+ });
375
+ var httpResponseSchema = z5.object({
376
+ status: z5.number().int().min(100).max(999),
377
+ headers: httpHeadersSchema.optional()
378
+ });
379
+ var httpRequestStageSchema = z5.object({
380
+ protocol: httpProtocolSchema.optional(),
381
+ request: httpRequestSchema
382
+ });
383
+ var httpResponseStageSchema = z5.object({
384
+ protocol: httpProtocolSchema.optional(),
385
+ response: httpResponseSchema
386
+ });
387
+ var tokenSourceSchema = z5.enum([
388
+ "authorization",
389
+ "query",
390
+ "client",
391
+ "unknown"
392
+ ]);
393
+ var tokenTypeSchema = z5.enum(["auth", "pat", "app", "unknown"]);
394
+ var firewallRuleSchema = z5.discriminatedUnion("kind", [
395
+ z5.object({ kind: z5.literal("none") }),
396
+ z5.object({ kind: z5.literal("mtls") }),
397
+ z5.object({
398
+ kind: z5.literal("rstream_auth"),
399
+ source: z5.literal("rstream_auth")
400
+ }),
401
+ z5.object({
402
+ kind: z5.literal("token"),
403
+ source: tokenSourceSchema.optional(),
404
+ tokenType: tokenTypeSchema.optional()
405
+ })
406
+ ]);
407
+ var unauthorizedSourceSchema = z5.object({
408
+ source: tokenSourceSchema
409
+ });
410
+ var unauthorizedReasonSchema = (shape) => z5.object(shape).merge(unauthorizedSourceSchema);
411
+ var unauthorizedSchema = z5.discriminatedUnion("reason", [
412
+ unauthorizedReasonSchema({
413
+ reason: z5.literal("missing_token")
414
+ }),
415
+ unauthorizedReasonSchema({
416
+ reason: z5.literal("invalid_token")
417
+ }),
418
+ unauthorizedReasonSchema({
419
+ reason: z5.literal("expired_token")
420
+ }),
421
+ unauthorizedReasonSchema({
422
+ reason: z5.literal("invalid_secret")
423
+ }),
424
+ unauthorizedReasonSchema({
425
+ reason: z5.literal("user_mismatch")
426
+ }),
427
+ unauthorizedReasonSchema({
428
+ reason: z5.literal("policy_denied"),
429
+ policy: z5.enum(["ip", "geo", "mtls", "challenge", "other"]),
430
+ value: z5.string().optional()
431
+ })
432
+ ]);
433
+ var failureSchema = z5.discriminatedUnion("kind", [
434
+ z5.object({
435
+ kind: z5.literal("timeout"),
436
+ timeoutMs: intNonNegSchema.optional(),
437
+ message: z5.string().optional()
438
+ }),
439
+ z5.object({
440
+ kind: z5.literal("unauthorized"),
441
+ why: unauthorizedSchema,
442
+ message: z5.string().optional()
443
+ }),
444
+ z5.object({
445
+ kind: z5.literal("not_found"),
446
+ resource: z5.enum(["tunnel", "client", "route"]),
447
+ message: z5.string().optional()
448
+ }),
449
+ z5.object({
450
+ kind: z5.literal("refused"),
451
+ reason: z5.enum(["capacity", "policy", "unsupported", "other"]),
452
+ message: z5.string().optional()
453
+ }),
454
+ z5.object({ kind: z5.literal("io"), message: z5.string().optional() }),
455
+ z5.object({ kind: z5.literal("internal"), message: z5.string().optional() })
456
+ ]);
457
+ var firewallSchema = z5.discriminatedUnion("decision", [
458
+ z5.object({ decision: z5.literal("allowed"), rule: firewallRuleSchema }),
459
+ z5.object({
460
+ decision: z5.literal("denied"),
461
+ rule: firewallRuleSchema,
462
+ failure: failureSchema
463
+ })
464
+ ]);
465
+ var routedTunnelSchema = z5.object({
466
+ tunnelId: nonEmptyStringSchema.optional(),
467
+ tunnelName: z5.string().optional(),
468
+ protocol: z5.string().optional(),
469
+ published: z5.boolean().optional(),
470
+ type: z5.string().optional(),
471
+ tlsMode: z5.string().optional(),
472
+ httpVersion: z5.string().optional(),
473
+ httpUseTls: z5.boolean().optional(),
474
+ tokenAuth: z5.boolean().optional(),
475
+ mtls: z5.boolean().optional()
476
+ });
477
+ var routingSchema = z5.discriminatedUnion("decision", [
478
+ z5.object({ decision: z5.literal("routed"), tunnel: routedTunnelSchema }),
479
+ z5.object({ decision: z5.literal("not_routed"), failure: failureSchema })
480
+ ]);
481
+ var streamConnectedMetricsSchema = z5.object({
482
+ establish_ms: numNonNegSchema,
483
+ duration_ms: numNonNegSchema,
484
+ upstream_bytes: intNonNegSchema,
485
+ downstream_bytes: intNonNegSchema
486
+ });
487
+ var streamFailedMetricsSchema = z5.object({
488
+ time_to_fail_ms: numNonNegSchema.optional(),
489
+ establish_ms: numNonNegSchema.optional()
490
+ });
491
+ var terminationSchema = z5.object({
492
+ by: z5.enum(["downstream", "upstream", "server", "unknown"]),
493
+ reason: z5.enum(["closed", "reset", "timeout", "error", "unknown"]),
494
+ message: z5.string().optional()
495
+ });
496
+ var responseConnectedSchema = z5.object({
497
+ outcome: z5.literal("connected"),
498
+ established_at: isoDateTimeSchema,
499
+ upstream: endpointSchema.optional(),
500
+ metrics: streamConnectedMetricsSchema,
501
+ termination: terminationSchema.optional(),
502
+ http: httpResponseStageSchema.optional()
503
+ });
504
+ var responseFailedSchema = z5.object({
505
+ outcome: z5.literal("failed"),
506
+ failure: failureSchema,
507
+ upstream: endpointSchema.optional(),
508
+ metrics: streamFailedMetricsSchema.optional(),
509
+ termination: terminationSchema.optional(),
510
+ http: httpResponseStageSchema.optional()
511
+ });
512
+ var responseSchema = z5.discriminatedUnion("outcome", [
513
+ responseConnectedSchema,
514
+ responseFailedSchema
515
+ ]);
516
+ var streamSummarySchema = z5.object({
517
+ workspace_id: nonEmptyStringSchema.optional(),
518
+ project_id: nonEmptyStringSchema.optional(),
519
+ cluster_id: nonEmptyStringSchema.optional(),
520
+ user_id: nonEmptyStringSchema.optional(),
521
+ client_id: nonEmptyStringSchema.optional(),
522
+ tunnel_id: nonEmptyStringSchema.optional(),
523
+ stream_id: nonEmptyStringSchema,
524
+ created_at: isoDateTimeSchema,
525
+ terminated_at: isoDateTimeSchema,
526
+ request: z5.object({
527
+ downstream: endpointSchema,
528
+ http: httpRequestStageSchema.optional()
529
+ }),
530
+ firewall: firewallSchema,
531
+ routing: routingSchema,
532
+ response: responseSchema
533
+ });
534
+
535
+ // src/event.ts
536
+ var z6 = __toESM(require("zod"));
537
+ var isoDateTimeSchema2 = z6.string().datetime();
538
+ var initialStateSchema = z6.object({
539
+ snapshot_at: isoDateTimeSchema2,
540
+ clients: z6.array(clientSchema),
541
+ tunnels: z6.array(tunnelSchema)
542
+ });
543
+ var eventBaseSchema = z6.object({
544
+ id: z6.string().optional(),
545
+ created_at: isoDateTimeSchema2.optional(),
546
+ user_id: z6.string().optional(),
547
+ workspace_id: z6.string().optional(),
548
+ project_id: z6.string().optional(),
549
+ cluster_id: z6.string().optional()
550
+ });
551
+ var withEventBase = (shape) => z6.object(shape).merge(eventBaseSchema).passthrough();
552
+ var stateInitialEventSchema = withEventBase({
553
+ type: z6.literal("state.initial"),
554
+ object: initialStateSchema
555
+ });
556
+ var streamSummaryEventSchema = withEventBase({
557
+ type: z6.literal("stream.summary"),
558
+ object: streamSummarySchema
559
+ });
560
+ var commonEventsSchema = [
561
+ withEventBase({
562
+ type: z6.literal("client.created"),
563
+ object: clientSchema
564
+ }),
565
+ withEventBase({
566
+ type: z6.literal("client.updated"),
567
+ object: clientSchema
568
+ }),
569
+ withEventBase({
570
+ type: z6.literal("client.deleted"),
571
+ object: clientSchema
572
+ }),
573
+ withEventBase({
574
+ type: z6.literal("tunnel.created"),
575
+ object: tunnelSchema
576
+ }),
577
+ withEventBase({
578
+ type: z6.literal("tunnel.updated"),
579
+ object: tunnelSchema
580
+ }),
581
+ withEventBase({
582
+ type: z6.literal("tunnel.deleted"),
583
+ object: tunnelSchema
584
+ })
585
+ ];
586
+ var wsEventsSchema = z6.union([
587
+ stateInitialEventSchema,
588
+ ...commonEventsSchema
589
+ ]);
590
+ var webhookEventsSchema = z6.union([
591
+ ...commonEventsSchema,
592
+ streamSummaryEventSchema
593
+ ]);
594
+ var eventSchema = z6.union([wsEventsSchema, webhookEventsSchema]);
595
+
596
+ // src/resolution.ts
597
+ var import_rstream3 = require("@rstreamlabs/rstream");
598
+ var import_rstream4 = require("@rstreamlabs/rstream");
599
+ var import_rstream5 = require("@rstreamlabs/rstream");
600
+ var import_rstream6 = require("@rstreamlabs/rstream");
601
+ var import_rstream7 = require("@rstreamlabs/rstream");
602
+ var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
603
+ function trimOptionalString(value) {
604
+ const normalized = value?.trim();
605
+ return normalized ? normalized : void 0;
606
+ }
607
+ function getEngineFromToken(token) {
608
+ if (!token) {
609
+ return void 0;
610
+ }
611
+ const parsed = authTokenSchema.safeParse(
612
+ import_jsonwebtoken.default.decode(token, { complete: false })
613
+ );
614
+ if (!parsed.success) {
615
+ return void 0;
616
+ }
617
+ return trimOptionalString(parsed.data.metadata?.engine);
618
+ }
619
+ function resolveTunnelsAPIURL(apiUrl) {
620
+ return trimOptionalString(apiUrl) ?? (0, import_rstream6.readEnvironment)().apiUrl ?? import_rstream3.DEFAULT_API_URL;
621
+ }
622
+ function resolveTunnelsCredentials(credentials) {
623
+ if (credentials !== void 0) {
624
+ return credentials;
625
+ }
626
+ const token = (0, import_rstream6.readEnvironment)().token;
627
+ if (!token) {
628
+ return void 0;
629
+ }
630
+ return { token };
631
+ }
632
+ function resolveControlPlaneCredentials(controlPlaneCredentials, credentials, authToken) {
633
+ if (controlPlaneCredentials !== void 0) {
634
+ return controlPlaneCredentials;
635
+ }
636
+ if (credentials !== void 0) {
637
+ return credentials;
638
+ }
639
+ if (authToken) {
640
+ return { token: authToken };
641
+ }
642
+ const environmentToken = (0, import_rstream6.readEnvironment)().token;
643
+ if (!environmentToken) {
644
+ return void 0;
645
+ }
646
+ return { token: environmentToken };
647
+ }
648
+ async function resolveTunnelsEngine(config) {
649
+ const explicitEngine = trimOptionalString(config.engine);
650
+ if (explicitEngine) {
651
+ return explicitEngine;
652
+ }
653
+ const environmentEngine = (0, import_rstream6.readEnvironment)().engine;
654
+ if (environmentEngine) {
655
+ return environmentEngine;
656
+ }
657
+ const token = trimOptionalString(config.token) ?? ((0, import_rstream5.isTokenCredentials)(config.credentials) ? config.credentials.token : void 0);
658
+ const tokenEngine = getEngineFromToken(token);
659
+ if (tokenEngine) {
660
+ return tokenEngine;
661
+ }
662
+ const projectEndpoint = trimOptionalString(config.projectEndpoint);
663
+ if (projectEndpoint) {
664
+ const credentials = resolveControlPlaneCredentials(
665
+ config.controlPlaneCredentials,
666
+ config.credentials,
667
+ token
668
+ );
669
+ if (credentials === void 0) {
670
+ throw new Error(
671
+ "Control-plane credentials are required to resolve a managed project endpoint."
672
+ );
673
+ }
674
+ const controlPlane = new import_rstream7.RstreamClient({
675
+ apiUrl: resolveTunnelsAPIURL(config.apiUrl),
676
+ credentials
677
+ });
678
+ const project = await controlPlane.tunnels.projects.resolveByEndpoint(projectEndpoint);
679
+ const engine = (0, import_rstream4.getTunnelsProjectEngine)(project);
680
+ if (engine) {
681
+ return engine;
682
+ }
683
+ throw new Error(
684
+ "Failed to resolve the engine address from the managed tunnels project."
685
+ );
686
+ }
687
+ throw new Error(
688
+ "Engine URL is not defined. Provide an engine, use RSTREAM_ENGINE, or configure a managed project endpoint with control-plane access."
689
+ );
690
+ }
691
+
692
+ // src/tunnels-resource.ts
693
+ var RstreamTunnelsResource = class {
694
+ client;
695
+ constructor(client) {
696
+ this.client = client;
697
+ }
698
+ async list(params) {
699
+ const path = params === void 0 ? "/tunnels" : `/tunnels?params=${encodeURIComponent(JSON.stringify(params))}`;
700
+ const response = await this.client.request(path, {
701
+ method: "GET"
702
+ });
703
+ return listTunnelsResponseSchema.parse(response);
704
+ }
705
+ async get(id) {
706
+ const response = await this.client.request(`/tunnels/${id}`, {
707
+ method: "GET"
708
+ });
709
+ return tunnelSchema.parse(response);
710
+ }
711
+ };
712
+
713
+ // src/tunnels.ts
714
+ var import_rstream12 = require("@rstreamlabs/rstream");
715
+
716
+ // src/turn.ts
717
+ var import_rstream8 = require("@rstreamlabs/rstream");
718
+ var import_rstream9 = require("@rstreamlabs/rstream");
719
+ var import_rstream10 = require("@rstreamlabs/rstream");
720
+ var import_rstream11 = require("@rstreamlabs/rstream");
721
+ var z7 = __toESM(require("zod"));
722
+ var import_crypto = __toESM(require("crypto"));
723
+ var defaultTURNPort = 3478;
724
+ var defaultTURNSPort = 5349;
725
+ var defaultTURNCredentialTTLSeconds = 24 * 60 * 60;
726
+ var turnTokenClaimsSchema = z7.object({
727
+ token_endpoint: z7.string().optional(),
728
+ type: z7.string()
729
+ });
730
+ var turnCredentialModeSchema = z7.enum(["api", "app", "pat"]);
731
+ function normalizeOptionalString(value) {
732
+ const normalized = value?.trim();
733
+ return normalized ? normalized : void 0;
734
+ }
735
+ function normalizeClusterDomain(clusterDomain) {
736
+ const normalized = normalizeOptionalString(clusterDomain);
737
+ return normalized?.toLowerCase();
738
+ }
739
+ function normalizeNow(now) {
740
+ if (now instanceof Date) {
741
+ return Math.floor(now.getTime() / 1e3);
742
+ }
743
+ if (typeof now === "number" && Number.isFinite(now)) {
744
+ return Math.floor(now);
745
+ }
746
+ return Math.floor(Date.now() / 1e3);
747
+ }
748
+ function normalizeTTLSeconds(ttlSeconds) {
749
+ if (typeof ttlSeconds === "number" && Number.isFinite(ttlSeconds) && ttlSeconds > 0) {
750
+ return Math.floor(ttlSeconds);
751
+ }
752
+ return defaultTURNCredentialTTLSeconds;
753
+ }
754
+ function normalizeTURNPort(port) {
755
+ if (typeof port === "number" && Number.isFinite(port) && port > 0) {
756
+ return Math.floor(port);
757
+ }
758
+ return defaultTURNPort;
759
+ }
760
+ function normalizeTURNSPort(port) {
761
+ if (typeof port === "number" && Number.isFinite(port) && port > 0) {
762
+ return Math.floor(port);
763
+ }
764
+ return defaultTURNSPort;
765
+ }
766
+ function createTURNURLs(clusterDomain, turnPort, turnsPort) {
767
+ const normalizedTURNPort = normalizeTURNPort(turnPort);
768
+ const normalizedTURNSPort = normalizeTURNSPort(turnsPort);
769
+ return [
770
+ `turn:${clusterDomain}:${normalizedTURNPort}?transport=udp`,
771
+ `turn:${clusterDomain}:${normalizedTURNPort}?transport=tcp`,
772
+ `turns:${clusterDomain}:${normalizedTURNSPort}?transport=udp`,
773
+ `turns:${clusterDomain}:${normalizedTURNSPort}?transport=tcp`
774
+ ];
775
+ }
776
+ function parseTURNTokenClaims(token) {
777
+ const parts = token.split(".");
778
+ const payloadPart = parts.at(1);
779
+ if (!payloadPart) {
780
+ throw new Error("Invalid token format.");
781
+ }
782
+ const payload = Buffer.from(payloadPart, "base64url").toString("utf8");
783
+ const parsed = turnTokenClaimsSchema.safeParse(JSON.parse(payload));
784
+ if (!parsed.success) {
785
+ throw new Error("Invalid token format.");
786
+ }
787
+ return parsed.data;
788
+ }
789
+ function deriveClusterDomainFromEngine(projectEndpoint, engine) {
790
+ const normalizedProjectEndpoint = normalizeOptionalString(projectEndpoint);
791
+ const normalizedEngine = normalizeOptionalString(engine);
792
+ if (!normalizedProjectEndpoint || !normalizedEngine) {
793
+ throw new Error(
794
+ "Failed to derive the cluster domain from the engine address."
795
+ );
796
+ }
797
+ const url = new URL(`https://${normalizedEngine}`);
798
+ const prefix = `${normalizedProjectEndpoint}.`;
799
+ if (!url.hostname.startsWith(prefix)) {
800
+ throw new Error(
801
+ "Failed to derive the cluster domain from the engine address."
802
+ );
803
+ }
804
+ const clusterDomain = url.hostname.slice(prefix.length);
805
+ if (!clusterDomain) {
806
+ throw new Error(
807
+ "Failed to derive the cluster domain from the engine address."
808
+ );
809
+ }
810
+ return clusterDomain;
811
+ }
812
+ function requireOption(value, message) {
813
+ if (value) {
814
+ return value;
815
+ }
816
+ throw new Error(message);
817
+ }
818
+ function resolveMode(credentials, mode) {
819
+ if (mode !== void 0) {
820
+ return turnCredentialModeSchema.parse(mode);
821
+ }
822
+ if ((0, import_rstream8.isClientCredentials)(credentials)) {
823
+ return "app";
824
+ }
825
+ if ((0, import_rstream9.isTokenCredentials)(credentials)) {
826
+ const claims = parseTURNTokenClaims(credentials.token);
827
+ if (claims.type === "pat" && normalizeOptionalString(claims.token_endpoint)) {
828
+ return "pat";
829
+ }
830
+ }
831
+ return "api";
832
+ }
833
+ async function loadTURNServerPublicKeyHex(clusterDomain, options) {
834
+ const configuredKey = normalizeOptionalString(options.keyringBaseUrl);
835
+ const keyringBaseURL = configuredKey ?? normalizeOptionalString(options.apiUrl) ?? "https://rstream.io";
836
+ const url = new URL(
837
+ `/keyrings/turn/${clusterDomain}.spki.der.hex`,
838
+ keyringBaseURL
839
+ );
840
+ const response = await fetch(url);
841
+ if (!response.ok) {
842
+ throw new Error("Failed to load TURN keyring.");
843
+ }
844
+ return (await response.text()).trim();
845
+ }
846
+ async function createAPITURNCredentials(options) {
847
+ const projectId = normalizeOptionalString(options.projectId);
848
+ const projectEndpoint = normalizeOptionalString(options.projectEndpoint);
849
+ if (!projectId && !projectEndpoint) {
850
+ throw new Error(
851
+ "Project ID or project endpoint is required for TURN API mode."
852
+ );
853
+ }
854
+ const client = new import_rstream10.RstreamClient({
855
+ apiUrl: options.apiUrl,
856
+ credentials: options.credentials
857
+ });
858
+ if (projectId) {
859
+ return await client.tunnels.projects.createTurnCredentials(projectId);
860
+ }
861
+ if (!projectEndpoint) {
862
+ throw new Error(
863
+ "Project ID or project endpoint is required for TURN API mode."
864
+ );
865
+ }
866
+ return await client.tunnels.projects.createTurnCredentialsByEndpoint(
867
+ projectEndpoint
868
+ );
869
+ }
870
+ function createPATTURNCredentials(options) {
871
+ const clusterDomain = normalizeClusterDomain(options.clusterDomain);
872
+ if (!clusterDomain) {
873
+ throw new Error("Cluster domain is required for TURN PAT mode.");
874
+ }
875
+ const projectEndpoint = normalizeOptionalString(options.projectEndpoint);
876
+ if (!projectEndpoint) {
877
+ throw new Error("Project endpoint is required for TURN PAT mode.");
878
+ }
879
+ const token = normalizeOptionalString(options.token);
880
+ if (!token) {
881
+ throw new Error("Token is required for TURN PAT mode.");
882
+ }
883
+ const claims = parseTURNTokenClaims(token);
884
+ if (claims.type !== "pat") {
885
+ throw new Error("TURN PAT mode requires a PAT token.");
886
+ }
887
+ const tokenEndpoint = normalizeOptionalString(claims.token_endpoint);
888
+ if (!tokenEndpoint) {
889
+ throw new Error(
890
+ "TURN PAT mode requires a PAT token carrying a token endpoint."
891
+ );
892
+ }
893
+ const ttlSeconds = normalizeTTLSeconds(options.ttlSeconds);
894
+ const exp = normalizeNow(options.now) + ttlSeconds;
895
+ const username = `v1:${exp}:pat:${projectEndpoint}:${tokenEndpoint}`;
896
+ const tokenHash = import_crypto.default.createHash("sha256").update(token, "utf8").digest();
897
+ const key = Buffer.from(
898
+ import_crypto.default.hkdfSync(
899
+ "sha256",
900
+ tokenHash,
901
+ Buffer.from(clusterDomain, "utf8"),
902
+ Buffer.from("turn-pat-v1", "utf8"),
903
+ 32
904
+ )
905
+ );
906
+ const credential = import_crypto.default.createHmac("sha256", key).update(username, "utf8").digest("base64");
907
+ return import_rstream11.turnCredentialsSchema.parse({
908
+ credential,
909
+ ttl: ttlSeconds,
910
+ urls: createTURNURLs(clusterDomain, options.turnPort, options.turnsPort),
911
+ username
912
+ });
913
+ }
914
+ async function createAPPTURNCredentials(options) {
915
+ const clusterDomain = normalizeClusterDomain(options.clusterDomain);
916
+ if (!clusterDomain) {
917
+ throw new Error("Cluster domain is required for TURN APP mode.");
918
+ }
919
+ const projectEndpoint = normalizeOptionalString(options.projectEndpoint);
920
+ if (!projectEndpoint) {
921
+ throw new Error("Project endpoint is required for TURN APP mode.");
922
+ }
923
+ const clientId = normalizeOptionalString(options.clientId);
924
+ if (!clientId) {
925
+ throw new Error("Client ID is required for TURN APP mode.");
926
+ }
927
+ const clientSecret = normalizeOptionalString(options.clientSecret);
928
+ if (!clientSecret) {
929
+ throw new Error("Client secret is required for TURN APP mode.");
930
+ }
931
+ const ttlSeconds = normalizeTTLSeconds(options.ttlSeconds);
932
+ const exp = normalizeNow(options.now) + ttlSeconds;
933
+ const username = `v1:${exp}:app:${projectEndpoint}:${clientId}`;
934
+ const clientPrivateKey = import_crypto.default.createPrivateKey({
935
+ key: Buffer.from(clientSecret, "hex"),
936
+ format: "der",
937
+ type: "pkcs8"
938
+ });
939
+ const serverPublicKeyHex = normalizeOptionalString(options.serverPublicKeyHex) ?? await loadTURNServerPublicKeyHex(clusterDomain, options);
940
+ const serverPublicKey = import_crypto.default.createPublicKey({
941
+ key: Buffer.from(serverPublicKeyHex, "hex"),
942
+ format: "der",
943
+ type: "spki"
944
+ });
945
+ const sharedSecret = import_crypto.default.diffieHellman({
946
+ privateKey: clientPrivateKey,
947
+ publicKey: serverPublicKey
948
+ });
949
+ const key = Buffer.from(
950
+ import_crypto.default.hkdfSync(
951
+ "sha256",
952
+ sharedSecret,
953
+ Buffer.from(clusterDomain, "utf8"),
954
+ Buffer.from("turn-app-v1", "utf8"),
955
+ 32
956
+ )
957
+ );
958
+ const credential = import_crypto.default.createHmac("sha256", key).update(username, "utf8").digest("base64");
959
+ return import_rstream11.turnCredentialsSchema.parse({
960
+ credential,
961
+ ttl: ttlSeconds,
962
+ urls: createTURNURLs(clusterDomain, options.turnPort, options.turnsPort),
963
+ username
964
+ });
965
+ }
966
+ async function createTURNCredentials(options) {
967
+ const credentials = options.credentials;
968
+ const mode = resolveMode(credentials, options.mode);
969
+ const projectEndpoint = normalizeOptionalString(options.projectEndpoint);
970
+ if (mode === "api") {
971
+ const controlPlaneCredentials = options.controlPlaneCredentials ?? credentials;
972
+ if (!controlPlaneCredentials) {
973
+ throw new Error(
974
+ "Control-plane credentials are required for TURN API mode."
975
+ );
976
+ }
977
+ return await createAPITURNCredentials({
978
+ apiUrl: options.apiUrl,
979
+ credentials: controlPlaneCredentials,
980
+ projectEndpoint,
981
+ projectId: options.projectId
982
+ });
983
+ }
984
+ const clusterDomain = normalizeClusterDomain(options.clusterDomain) ?? (projectEndpoint && options.engine ? deriveClusterDomainFromEngine(projectEndpoint, options.engine) : void 0);
985
+ if (mode === "pat") {
986
+ if (!(0, import_rstream9.isTokenCredentials)(credentials)) {
987
+ throw new Error("A PAT token is required for TURN PAT mode.");
988
+ }
989
+ return createPATTURNCredentials({
990
+ clusterDomain: requireOption(
991
+ clusterDomain,
992
+ "Cluster domain is required for TURN PAT mode."
993
+ ),
994
+ now: options.now,
995
+ projectEndpoint: requireOption(
996
+ projectEndpoint,
997
+ "Project endpoint is required for TURN PAT mode."
998
+ ),
999
+ token: credentials.token,
1000
+ ttlSeconds: options.ttlSeconds,
1001
+ turnPort: options.turnPort,
1002
+ turnsPort: options.turnsPort
1003
+ });
1004
+ }
1005
+ if (!(0, import_rstream8.isClientCredentials)(credentials)) {
1006
+ throw new Error("Client credentials are required for TURN APP mode.");
1007
+ }
1008
+ return await createAPPTURNCredentials({
1009
+ apiUrl: options.apiUrl,
1010
+ clientId: credentials.clientId,
1011
+ clientSecret: credentials.clientSecret,
1012
+ clusterDomain: requireOption(
1013
+ clusterDomain,
1014
+ "Cluster domain is required for TURN APP mode."
1015
+ ),
1016
+ keyringBaseUrl: options.keyringBaseUrl,
1017
+ now: options.now,
1018
+ projectEndpoint: requireOption(
1019
+ projectEndpoint,
1020
+ "Project endpoint is required for TURN APP mode."
1021
+ ),
1022
+ serverPublicKeyHex: options.serverPublicKeyHex,
1023
+ ttlSeconds: options.ttlSeconds,
1024
+ turnPort: options.turnPort,
1025
+ turnsPort: options.turnsPort
1026
+ });
1027
+ }
1028
+
1029
+ // src/turn-resource.ts
1030
+ var RstreamTURNResource = class {
1031
+ client;
1032
+ constructor(client) {
1033
+ this.client = client;
1034
+ }
1035
+ async createCredentials(options = {}) {
1036
+ const engine = options.engine ?? (options.clusterDomain === void 0 ? await this.client.getEngine().catch(() => void 0) : void 0);
1037
+ return await createTURNCredentials({
1038
+ apiUrl: options.apiUrl ?? this.client.apiUrl,
1039
+ clusterDomain: options.clusterDomain,
1040
+ controlPlaneCredentials: options.controlPlaneCredentials ?? this.client.controlPlaneCredentials,
1041
+ credentials: options.credentials ?? this.client.credentials,
1042
+ engine,
1043
+ keyringBaseUrl: options.keyringBaseUrl,
1044
+ mode: options.mode,
1045
+ now: options.now,
1046
+ projectEndpoint: options.projectEndpoint ?? this.client.projectEndpoint,
1047
+ projectId: options.projectId,
1048
+ serverPublicKeyHex: options.serverPublicKeyHex,
1049
+ ttlSeconds: options.ttlSeconds,
1050
+ turnPort: options.turnPort,
1051
+ turnsPort: options.turnsPort
1052
+ });
1053
+ }
1054
+ };
1055
+
1056
+ // src/webhooks-resource.ts
1057
+ var import_crypto2 = __toESM(require("crypto"));
1058
+ var RstreamWebhookResource = class {
1059
+ async event(payload, header, secret, tolerance = 300, receivedAt = Date.now()) {
1060
+ const payloadBuffer = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
1061
+ const signatureHeader = Array.isArray(header) ? header[0] : header.toString();
1062
+ if (!signatureHeader) {
1063
+ throw new Error("No signature header");
1064
+ }
1065
+ const elements = signatureHeader.split(",").map((element) => element.trim());
1066
+ const timestamp = elements.find((element) => element.startsWith("t="))?.substring(2);
1067
+ const signatures = elements.filter((element) => element.startsWith("v1=")).map((element) => element.substring(3));
1068
+ if (!timestamp || signatures.length === 0) {
1069
+ throw new Error("Invalid signature header format");
1070
+ }
1071
+ const timestampNum = parseInt(timestamp, 10);
1072
+ const now = Math.floor(receivedAt / 1e3);
1073
+ if (Math.abs(now - timestampNum) > tolerance) {
1074
+ throw new Error(
1075
+ `Webhook signature timestamp outside tolerance: ${now}, ${timestampNum}`
1076
+ );
1077
+ }
1078
+ const signedPayload = `${timestamp}.${payloadBuffer.toString("utf8")}`;
1079
+ const expectedSignature = import_crypto2.default.createHmac("sha256", secret).update(signedPayload).digest("hex");
1080
+ const signatureMatched = signatures.some((signature) => {
1081
+ try {
1082
+ return import_crypto2.default.timingSafeEqual(
1083
+ Buffer.from(signature),
1084
+ Buffer.from(expectedSignature)
1085
+ );
1086
+ } catch {
1087
+ return false;
1088
+ }
1089
+ });
1090
+ if (!signatureMatched) {
1091
+ throw new Error("Signature verification failed");
1092
+ }
1093
+ try {
1094
+ return webhookEventsSchema.parse(
1095
+ JSON.parse(payloadBuffer.toString("utf8"))
1096
+ );
1097
+ } catch (error) {
1098
+ throw new Error(
1099
+ `Failed to parse webhook payload: ${error instanceof Error ? error.message : String(error)}`,
1100
+ { cause: error }
1101
+ );
1102
+ }
1103
+ }
1104
+ };
1105
+
1106
+ // src/tunnels.ts
1107
+ var RstreamTunnelsClient = class {
1108
+ config;
1109
+ constructor(config) {
1110
+ this.config = config;
1111
+ }
1112
+ get apiUrl() {
1113
+ return resolveTunnelsAPIURL(this.config?.apiUrl);
1114
+ }
1115
+ get credentials() {
1116
+ return resolveTunnelsCredentials(this.config?.credentials);
1117
+ }
1118
+ get controlPlaneCredentials() {
1119
+ return resolveControlPlaneCredentials(
1120
+ this.config?.controlPlaneCredentials,
1121
+ this.credentials
1122
+ );
1123
+ }
1124
+ get projectEndpoint() {
1125
+ return this.config?.projectEndpoint;
1126
+ }
1127
+ async getEngine() {
1128
+ return await resolveTunnelsEngine({
1129
+ apiUrl: this.config?.apiUrl,
1130
+ controlPlaneCredentials: this.controlPlaneCredentials,
1131
+ credentials: this.credentials,
1132
+ engine: this.config?.engine,
1133
+ projectEndpoint: this.config?.projectEndpoint
1134
+ });
1135
+ }
1136
+ get auth() {
1137
+ return new RstreamAuthResource(this);
1138
+ }
1139
+ get clients() {
1140
+ return new RstreamClientsResource(this);
1141
+ }
1142
+ get tunnels() {
1143
+ return new RstreamTunnelsResource(this);
1144
+ }
1145
+ get webhooks() {
1146
+ return new RstreamWebhookResource();
1147
+ }
1148
+ get turn() {
1149
+ return new RstreamTURNResource(this);
1150
+ }
1151
+ async getToken(engine) {
1152
+ const credentials = this.credentials;
1153
+ if (!credentials) {
1154
+ return void 0;
1155
+ }
1156
+ if (!(0, import_rstream12.isClientCredentials)(credentials)) {
1157
+ return credentials.token;
1158
+ }
1159
+ return (await this.auth.createAuthToken(void 0, {
1160
+ credentials,
1161
+ engine
1162
+ })).token;
1163
+ }
1164
+ async request(path, options) {
1165
+ const engine = await this.getEngine();
1166
+ const url = `https://${engine}/api${path}`;
1167
+ const headers = new Headers(options?.headers || {});
1168
+ const token = await this.getToken(engine);
1169
+ if (token) {
1170
+ headers.set("Authorization", `Bearer ${token}`);
1171
+ }
1172
+ const response = await fetch(url, {
1173
+ ...options,
1174
+ headers
1175
+ });
1176
+ if (!response.ok) {
1177
+ const errorText = await response.text();
1178
+ throw new Error(`HTTP error ${response.status}: ${errorText}`);
1179
+ }
1180
+ return await response.json();
1181
+ }
1182
+ };
1183
+
1184
+ // src/watch.ts
1185
+ var Watch = class {
1186
+ connection = null;
1187
+ connectionState = "preparing";
1188
+ config;
1189
+ events;
1190
+ constructor(config, events) {
1191
+ this.config = config;
1192
+ this.events = events;
1193
+ }
1194
+ async connect() {
1195
+ if (this.connectionState !== "preparing") {
1196
+ throw new Error("Watch: Connection already started or closed.");
1197
+ }
1198
+ this.connectionState = "connecting";
1199
+ const token = typeof this.config.auth === "function" ? await this.config.auth() : this.config.auth;
1200
+ const engine = await resolveTunnelsEngine({
1201
+ apiUrl: this.config.apiUrl,
1202
+ controlPlaneCredentials: this.config.controlPlaneCredentials,
1203
+ engine: this.config.engine,
1204
+ projectEndpoint: this.config.projectEndpoint,
1205
+ token
1206
+ });
1207
+ const transport = this.config.transport ?? "sse";
1208
+ const base = `https://${engine}`;
1209
+ if (transport === "sse") {
1210
+ const url = new URL(`/api/sse`, base);
1211
+ url.searchParams.set("rstream.token", token);
1212
+ this.connection = new EventSource(url.toString());
1213
+ } else {
1214
+ const url = new URL(`/api/websocket`, base);
1215
+ url.searchParams.set("rstream.token", token);
1216
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
1217
+ this.connection = new WebSocket(url.toString());
1218
+ }
1219
+ this.connection.onopen = () => {
1220
+ this.connectionState = "connected";
1221
+ this.events.onConnect?.();
1222
+ };
1223
+ this.connection.onmessage = (msg) => {
1224
+ const parsed = wsEventsSchema.parse(JSON.parse(msg.data));
1225
+ this.events.onEvent?.(parsed);
1226
+ };
1227
+ this.connection.onerror = () => {
1228
+ if (this.connectionState !== "closed") {
1229
+ this.disconnect();
1230
+ }
1231
+ };
1232
+ if (this.connection instanceof WebSocket) {
1233
+ this.connection.onclose = () => {
1234
+ if (this.connectionState !== "closed") {
1235
+ this.disconnect();
1236
+ }
1237
+ };
1238
+ }
1239
+ }
1240
+ disconnect() {
1241
+ if (this.connection) {
1242
+ this.connection.onerror = null;
1243
+ this.connection.onmessage = null;
1244
+ this.connection.onopen = null;
1245
+ this.connection.close();
1246
+ this.connection = null;
1247
+ }
1248
+ if (this.connectionState !== "closed") {
1249
+ this.connectionState = "closed";
1250
+ this.events.onClose?.();
1251
+ }
1252
+ }
1253
+ };
1254
+
1255
+ // src/webtty.ts
1256
+ var z8 = __toESM(require("zod"));
1257
+ var osFamilies = [
1258
+ "linux",
1259
+ "macos",
1260
+ "windows",
1261
+ "netbsd",
1262
+ "openbsd",
1263
+ "freebsd"
1264
+ ];
1265
+ var webttyServerSchema = z8.object({
1266
+ tunnel_id: z8.string(),
1267
+ host: z8.string(),
1268
+ token_auth: z8.boolean(),
1269
+ os_family: z8.enum(osFamilies).optional(),
1270
+ arch: z8.string().optional(),
1271
+ os_id: z8.string().optional(),
1272
+ // /etc/os-release::ID (ubuntu, debian, rocky, etc.)
1273
+ os_version_id: z8.string().optional(),
1274
+ // /etc/os-release::VERSION_ID (24.04, 22.04, 11, 10.0.19045, ...)
1275
+ os_version_codename: z8.string().optional(),
1276
+ // /etc/os-release::VERSION_CODENAME (jammy, noble...)
1277
+ os_pretty_name: z8.string().optional(),
1278
+ // /etc/os-release::PRETTY_NAME
1279
+ kernel_release: z8.string().optional(),
1280
+ // uname -r
1281
+ hostname: z8.string().optional(),
1282
+ // uname -n
1283
+ labels: z8.record(z8.string(), z8.string().optional()).optional()
1284
+ });
1285
+ function parser(tunnel) {
1286
+ if (tunnel.status !== "online") return null;
1287
+ if (tunnel.publish !== true) return null;
1288
+ if (tunnel.protocol !== "http") return null;
1289
+ const tunnelLabels = tunnel.labels ?? {};
1290
+ if (tunnelLabels["application-protocol"] !== "rstream.webtty") {
1291
+ return null;
1292
+ }
1293
+ const labels = {};
1294
+ for (const [key, value] of Object.entries(tunnelLabels)) {
1295
+ if (!key.startsWith("rstream.webtty.label.")) continue;
1296
+ const labelKey = key.slice("rstream.webtty.label.".length);
1297
+ if (labelKey.length === 0) continue;
1298
+ labels[labelKey] = value;
1299
+ }
1300
+ const candidate = {
1301
+ tunnel_id: tunnel.id,
1302
+ host: tunnel.host,
1303
+ token_auth: tunnel.token_auth === true,
1304
+ os_family: tunnelLabels["rstream.webtty.os_family"],
1305
+ arch: tunnelLabels["rstream.webtty.arch"],
1306
+ os_id: tunnelLabels["rstream.webtty.os_id"],
1307
+ os_version_id: tunnelLabels["rstream.webtty.os_version_id"],
1308
+ os_version_codename: tunnelLabels["rstream.webtty.os_version_codename"],
1309
+ os_pretty_name: tunnelLabels["rstream.webtty.os_pretty_name"],
1310
+ kernel_release: tunnelLabels["rstream.webtty.kernel_release"],
1311
+ hostname: tunnelLabels["rstream.webtty.hostname"],
1312
+ labels: Object.keys(labels).length > 0 ? labels : void 0
1313
+ };
1314
+ const parsed = webttyServerSchema.safeParse(candidate);
1315
+ if (!parsed.success) return null;
1316
+ return parsed.data;
1317
+ }
1318
+ function parseWebTTYServers(tunnels) {
1319
+ return tunnels.map((tunnel) => parser(tunnel)).filter((server) => server !== null);
1320
+ }
1321
+ // Annotate the CommonJS export names for ESM import in node:
1322
+ 0 && (module.exports = {
1323
+ RstreamAuthResource,
1324
+ RstreamAuthRessource,
1325
+ RstreamClient,
1326
+ RstreamClientsResource,
1327
+ RstreamClientsRessource,
1328
+ RstreamTURNResource,
1329
+ RstreamTunnelsClient,
1330
+ RstreamTunnelsResource,
1331
+ RstreamTunnelsRessource,
1332
+ RstreamWebhookResource,
1333
+ RstreamWebhookRessource,
1334
+ Tunnels,
1335
+ Watch,
1336
+ authTokenPermissionsSchema,
1337
+ authTokenSchema,
1338
+ authTokenScopesSchema,
1339
+ authTokenTunnelsScopesSchema,
1340
+ clientSchema,
1341
+ createAPITURNCredentials,
1342
+ createAPPTURNCredentials,
1343
+ createAuthTokenFromClientCredentials,
1344
+ createAuthTokenParamsSchema,
1345
+ createAuthTokenResponseSchema,
1346
+ createPATTURNCredentials,
1347
+ createTURNCredentials,
1348
+ eventSchema,
1349
+ listClientsParamsSchema,
1350
+ listClientsResponseSchema,
1351
+ listTunnelsParamsSchema,
1352
+ listTunnelsResponseSchema,
1353
+ parseWebTTYServers,
1354
+ resolveControlPlaneCredentials,
1355
+ resolveTunnelsAPIURL,
1356
+ resolveTunnelsCredentials,
1357
+ resolveTunnelsEngine,
1358
+ streamSummarySchema,
1359
+ tokenSchema,
1360
+ tunnelSchema,
1361
+ turnCredentialModeSchema,
1362
+ webhookEventsSchema,
1363
+ webttyServerSchema,
1364
+ wsEventsSchema
1365
+ });