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