@rstreamlabs/tunnels 3.8.1 → 3.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/index.d.mts +240 -224
- package/dist/index.d.ts +240 -224
- package/dist/index.js +160 -45
- package/dist/index.mjs +156 -41
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -404,7 +404,7 @@ var RstreamTunnelsResource = class {
|
|
|
404
404
|
|
|
405
405
|
// src/tunnels.ts
|
|
406
406
|
import { getTunnelsProjectEngine as getTunnelsProjectEngine2 } from "@rstreamlabs/rstream";
|
|
407
|
-
import { isClientCredentials as
|
|
407
|
+
import { isClientCredentials as isClientCredentials4 } from "@rstreamlabs/rstream";
|
|
408
408
|
import { readEnvironment as readEnvironment2 } from "@rstreamlabs/rstream";
|
|
409
409
|
|
|
410
410
|
// src/turn.ts
|
|
@@ -412,6 +412,52 @@ import { isClientCredentials as isClientCredentials2 } from "@rstreamlabs/rstrea
|
|
|
412
412
|
import { isTokenCredentials as isTokenCredentials2 } from "@rstreamlabs/rstream";
|
|
413
413
|
import { RstreamClient as RstreamClient2 } from "@rstreamlabs/rstream";
|
|
414
414
|
import { turnCredentialsSchema } from "@rstreamlabs/rstream/turn";
|
|
415
|
+
|
|
416
|
+
// src/control-plane-fetch.ts
|
|
417
|
+
function createRequestLifetime(parent, timeoutMilliseconds) {
|
|
418
|
+
const controller = new AbortController();
|
|
419
|
+
const abort = () => controller.abort(parent.reason);
|
|
420
|
+
if (parent.aborted) {
|
|
421
|
+
abort();
|
|
422
|
+
} else {
|
|
423
|
+
parent.addEventListener("abort", abort, { once: true });
|
|
424
|
+
}
|
|
425
|
+
const timeout = setTimeout(
|
|
426
|
+
() => controller.abort(new Error("Control plane request timed out.")),
|
|
427
|
+
timeoutMilliseconds
|
|
428
|
+
);
|
|
429
|
+
return {
|
|
430
|
+
close: () => {
|
|
431
|
+
clearTimeout(timeout);
|
|
432
|
+
parent.removeEventListener("abort", abort);
|
|
433
|
+
},
|
|
434
|
+
signal: controller.signal
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function createBoundedControlPlaneFetch(timeoutMilliseconds) {
|
|
438
|
+
return async (input, init) => {
|
|
439
|
+
const request = new Request(input, init);
|
|
440
|
+
const headers = new Headers(request.headers);
|
|
441
|
+
headers.set("Connection", "close");
|
|
442
|
+
const lifetime = createRequestLifetime(request.signal, timeoutMilliseconds);
|
|
443
|
+
try {
|
|
444
|
+
const response = await globalThis.fetch(request, {
|
|
445
|
+
headers,
|
|
446
|
+
signal: lifetime.signal
|
|
447
|
+
});
|
|
448
|
+
const responseBody = response.body === null ? null : new Uint8Array(await response.arrayBuffer());
|
|
449
|
+
return new Response(responseBody, {
|
|
450
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
451
|
+
status: response.status,
|
|
452
|
+
statusText: response.statusText
|
|
453
|
+
});
|
|
454
|
+
} finally {
|
|
455
|
+
lifetime.close();
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/turn.ts
|
|
415
461
|
import * as z from "zod";
|
|
416
462
|
import crypto from "crypto";
|
|
417
463
|
var defaultTURNPort = 3478;
|
|
@@ -419,6 +465,8 @@ var defaultTURNSPort = 5349;
|
|
|
419
465
|
var defaultTURNCredentialTTLSeconds = 10 * 60;
|
|
420
466
|
var maxTURNCredentialTTLSeconds = 60 * 60;
|
|
421
467
|
var maxTURNKeyringBytes = 8 * 1024;
|
|
468
|
+
var defaultAPIRequestTimeoutMilliseconds = 1e4;
|
|
469
|
+
var maxAPIRequestTimeoutMilliseconds = 6e4;
|
|
422
470
|
var turnTokenClaimsSchema = z.object({
|
|
423
471
|
exp: z.number().int().optional(),
|
|
424
472
|
iat: z.number().int().optional(),
|
|
@@ -452,6 +500,15 @@ function normalizeTTLSeconds(ttlSeconds) {
|
|
|
452
500
|
}
|
|
453
501
|
return ttl;
|
|
454
502
|
}
|
|
503
|
+
function normalizeAPIRequestTimeoutMilliseconds(timeoutMilliseconds) {
|
|
504
|
+
const timeout = timeoutMilliseconds ?? defaultAPIRequestTimeoutMilliseconds;
|
|
505
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > maxAPIRequestTimeoutMilliseconds) {
|
|
506
|
+
throw new Error(
|
|
507
|
+
"TURN API request timeout must be an integer between 1 and 60000 milliseconds."
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
return timeout;
|
|
511
|
+
}
|
|
455
512
|
function createAPITURNCredentialsParams(ttlSeconds) {
|
|
456
513
|
return ttlSeconds === void 0 ? {} : { ttlSeconds: normalizeTTLSeconds(ttlSeconds) };
|
|
457
514
|
}
|
|
@@ -468,14 +525,14 @@ function normalizeTURNPort(port) {
|
|
|
468
525
|
function normalizeTURNSPort(port) {
|
|
469
526
|
return normalizePort(port, defaultTURNSPort, "TURNS port");
|
|
470
527
|
}
|
|
471
|
-
function createTURNURLs(
|
|
528
|
+
function createTURNURLs(turnDomain, turnPort, turnsPort) {
|
|
472
529
|
const normalizedTURNPort = normalizeTURNPort(turnPort);
|
|
473
530
|
const normalizedTURNSPort = normalizeTURNSPort(turnsPort);
|
|
474
531
|
return [
|
|
475
|
-
`turn:${
|
|
476
|
-
`turn:${
|
|
477
|
-
`turns:${
|
|
478
|
-
`turns:${
|
|
532
|
+
`turn:${turnDomain}:${normalizedTURNPort}?transport=udp`,
|
|
533
|
+
`turn:${turnDomain}:${normalizedTURNPort}?transport=tcp`,
|
|
534
|
+
`turns:${turnDomain}:${normalizedTURNSPort}?transport=udp`,
|
|
535
|
+
`turns:${turnDomain}:${normalizedTURNSPort}?transport=tcp`
|
|
479
536
|
];
|
|
480
537
|
}
|
|
481
538
|
function parseTURNTokenClaims(token) {
|
|
@@ -645,11 +702,16 @@ async function createAPITURNCredentials(options) {
|
|
|
645
702
|
"Project ID or project endpoint is required for TURN API mode."
|
|
646
703
|
);
|
|
647
704
|
}
|
|
705
|
+
const requestTimeoutMilliseconds = normalizeAPIRequestTimeoutMilliseconds(
|
|
706
|
+
options.requestTimeoutMilliseconds
|
|
707
|
+
);
|
|
708
|
+
const params = createAPITURNCredentialsParams(options.ttlSeconds);
|
|
709
|
+
const request = options.fetch ?? createBoundedControlPlaneFetch(requestTimeoutMilliseconds);
|
|
648
710
|
const client = new RstreamClient2({
|
|
649
711
|
apiUrl: options.apiUrl,
|
|
650
|
-
credentials: options.credentials
|
|
712
|
+
credentials: options.credentials,
|
|
713
|
+
fetch: request
|
|
651
714
|
});
|
|
652
|
-
const params = createAPITURNCredentialsParams(options.ttlSeconds);
|
|
653
715
|
if (projectId) {
|
|
654
716
|
return await client.tunnels.projects.createTurnCredentials(
|
|
655
717
|
projectId,
|
|
@@ -667,9 +729,14 @@ async function createAPITURNCredentials(options) {
|
|
|
667
729
|
);
|
|
668
730
|
}
|
|
669
731
|
function createPATTURNCredentials(options) {
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
732
|
+
const legacyDomain = normalizeClusterDomain(options.clusterDomain);
|
|
733
|
+
const turnDomain = normalizeClusterDomain(options.turnDomain) ?? legacyDomain;
|
|
734
|
+
const turnRealm = normalizeClusterDomain(options.turnRealm) ?? legacyDomain;
|
|
735
|
+
if (!turnDomain) {
|
|
736
|
+
throw new Error("TURN domain is required for TURN PAT mode.");
|
|
737
|
+
}
|
|
738
|
+
if (!turnRealm) {
|
|
739
|
+
throw new Error("TURN realm is required for TURN PAT mode.");
|
|
673
740
|
}
|
|
674
741
|
const projectEndpoint = normalizeOptionalString2(options.projectEndpoint);
|
|
675
742
|
if (!projectEndpoint) {
|
|
@@ -683,12 +750,19 @@ function createPATTURNCredentials(options) {
|
|
|
683
750
|
if (claims.type !== "pat") {
|
|
684
751
|
throw new Error("TURN PAT mode requires a PAT token.");
|
|
685
752
|
}
|
|
686
|
-
const
|
|
753
|
+
const claimsTokenEndpoint = normalizeOptionalString2(
|
|
754
|
+
claims.token_endpoint ?? claims.tokendpoint
|
|
755
|
+
);
|
|
756
|
+
const configuredTokenEndpoint = normalizeOptionalString2(
|
|
757
|
+
options.tokenEndpoint
|
|
758
|
+
);
|
|
759
|
+
const tokenEndpoint = configuredTokenEndpoint ?? claimsTokenEndpoint;
|
|
687
760
|
if (!tokenEndpoint) {
|
|
688
|
-
throw new Error(
|
|
761
|
+
throw new Error(
|
|
762
|
+
"TURN PAT mode requires a PAT token carrying a token endpoint."
|
|
763
|
+
);
|
|
689
764
|
}
|
|
690
|
-
|
|
691
|
-
if (claimsTokenEndpoint !== void 0 && claimsTokenEndpoint !== tokenEndpoint) {
|
|
765
|
+
if (configuredTokenEndpoint !== void 0 && claimsTokenEndpoint !== void 0 && claimsTokenEndpoint !== configuredTokenEndpoint) {
|
|
692
766
|
throw new Error("Token endpoint does not match the PAT token.");
|
|
693
767
|
}
|
|
694
768
|
const now = normalizeNow(options.now);
|
|
@@ -701,7 +775,7 @@ function createPATTURNCredentials(options) {
|
|
|
701
775
|
crypto.hkdfSync(
|
|
702
776
|
"sha256",
|
|
703
777
|
tokenHash,
|
|
704
|
-
Buffer.from(
|
|
778
|
+
Buffer.from(turnRealm, "utf8"),
|
|
705
779
|
Buffer.from("turn-pat-v1", "utf8"),
|
|
706
780
|
32
|
|
707
781
|
)
|
|
@@ -710,14 +784,19 @@ function createPATTURNCredentials(options) {
|
|
|
710
784
|
return turnCredentialsSchema.parse({
|
|
711
785
|
credential,
|
|
712
786
|
ttl,
|
|
713
|
-
urls: createTURNURLs(
|
|
787
|
+
urls: createTURNURLs(turnDomain, options.turnPort, options.turnsPort),
|
|
714
788
|
username
|
|
715
789
|
});
|
|
716
790
|
}
|
|
717
791
|
async function createAPPTURNCredentials(options) {
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
792
|
+
const legacyDomain = normalizeClusterDomain(options.clusterDomain);
|
|
793
|
+
const turnDomain = normalizeClusterDomain(options.turnDomain) ?? legacyDomain;
|
|
794
|
+
const turnRealm = normalizeClusterDomain(options.turnRealm) ?? legacyDomain;
|
|
795
|
+
if (!turnDomain) {
|
|
796
|
+
throw new Error("TURN domain is required for TURN APP mode.");
|
|
797
|
+
}
|
|
798
|
+
if (!turnRealm) {
|
|
799
|
+
throw new Error("TURN realm is required for TURN APP mode.");
|
|
721
800
|
}
|
|
722
801
|
const projectEndpoint = normalizeOptionalString2(options.projectEndpoint);
|
|
723
802
|
if (!projectEndpoint) {
|
|
@@ -735,7 +814,7 @@ async function createAPPTURNCredentials(options) {
|
|
|
735
814
|
const exp = normalizeNow(options.now) + ttlSeconds;
|
|
736
815
|
const username = `v1:${exp}:app:${projectEndpoint}:${clientId}`;
|
|
737
816
|
const clientPrivateKey = parseAPPPrivateKey(clientSecret);
|
|
738
|
-
const serverPublicKeyHex = normalizeOptionalString2(options.serverPublicKeyHex) ?? await loadTURNServerPublicKeyHex(
|
|
817
|
+
const serverPublicKeyHex = normalizeOptionalString2(options.serverPublicKeyHex) ?? await loadTURNServerPublicKeyHex(turnRealm, options);
|
|
739
818
|
const serverPublicKey = parseTURNServerPublicKeyHex(serverPublicKeyHex);
|
|
740
819
|
requireMatchingCurve(clientPrivateKey, serverPublicKey);
|
|
741
820
|
const sharedSecret = crypto.diffieHellman({
|
|
@@ -746,7 +825,7 @@ async function createAPPTURNCredentials(options) {
|
|
|
746
825
|
crypto.hkdfSync(
|
|
747
826
|
"sha256",
|
|
748
827
|
sharedSecret,
|
|
749
|
-
Buffer.from(
|
|
828
|
+
Buffer.from(turnRealm, "utf8"),
|
|
750
829
|
Buffer.from("turn-app-v1", "utf8"),
|
|
751
830
|
32
|
|
752
831
|
)
|
|
@@ -755,7 +834,7 @@ async function createAPPTURNCredentials(options) {
|
|
|
755
834
|
return turnCredentialsSchema.parse({
|
|
756
835
|
credential,
|
|
757
836
|
ttl: ttlSeconds,
|
|
758
|
-
urls: createTURNURLs(
|
|
837
|
+
urls: createTURNURLs(turnDomain, options.turnPort, options.turnsPort),
|
|
759
838
|
username
|
|
760
839
|
});
|
|
761
840
|
}
|
|
@@ -773,33 +852,38 @@ async function createTURNCredentials(options) {
|
|
|
773
852
|
return await createAPITURNCredentials({
|
|
774
853
|
apiUrl: options.apiUrl,
|
|
775
854
|
credentials: controlPlaneCredentials,
|
|
855
|
+
fetch: options.fetch,
|
|
776
856
|
projectEndpoint,
|
|
777
857
|
projectId: options.projectId,
|
|
858
|
+
requestTimeoutMilliseconds: options.requestTimeoutMilliseconds,
|
|
778
859
|
ttlSeconds: options.ttlSeconds
|
|
779
860
|
});
|
|
780
861
|
}
|
|
781
|
-
const
|
|
862
|
+
const legacyDomain = normalizeClusterDomain(options.clusterDomain);
|
|
863
|
+
const turnDomain = normalizeClusterDomain(options.turnDomain) ?? legacyDomain ?? (projectEndpoint && options.engine ? deriveClusterDomainFromEngine(projectEndpoint, options.engine) : void 0);
|
|
864
|
+
const turnRealm = normalizeClusterDomain(options.turnRealm) ?? legacyDomain;
|
|
782
865
|
if (mode === "pat") {
|
|
783
866
|
if (!isTokenCredentials2(credentials)) {
|
|
784
867
|
throw new Error("A PAT token is required for TURN PAT mode.");
|
|
785
868
|
}
|
|
786
869
|
return createPATTURNCredentials({
|
|
787
|
-
clusterDomain: requireOption(
|
|
788
|
-
clusterDomain,
|
|
789
|
-
"Cluster domain is required for TURN PAT mode."
|
|
790
|
-
),
|
|
791
870
|
now: options.now,
|
|
792
871
|
projectEndpoint: requireOption(
|
|
793
872
|
projectEndpoint,
|
|
794
873
|
"Project endpoint is required for TURN PAT mode."
|
|
795
874
|
),
|
|
796
875
|
token: credentials.token,
|
|
797
|
-
tokenEndpoint:
|
|
798
|
-
normalizeOptionalString2(options.tokenEndpoint),
|
|
799
|
-
"Token endpoint is required for TURN PAT mode."
|
|
800
|
-
),
|
|
876
|
+
tokenEndpoint: options.tokenEndpoint,
|
|
801
877
|
ttlSeconds: options.ttlSeconds,
|
|
878
|
+
turnDomain: requireOption(
|
|
879
|
+
turnDomain,
|
|
880
|
+
"TURN domain is required for TURN PAT mode."
|
|
881
|
+
),
|
|
802
882
|
turnPort: options.turnPort,
|
|
883
|
+
turnRealm: requireOption(
|
|
884
|
+
turnRealm,
|
|
885
|
+
"TURN realm is required for TURN PAT mode."
|
|
886
|
+
),
|
|
803
887
|
turnsPort: options.turnsPort
|
|
804
888
|
});
|
|
805
889
|
}
|
|
@@ -810,10 +894,6 @@ async function createTURNCredentials(options) {
|
|
|
810
894
|
apiUrl: options.apiUrl,
|
|
811
895
|
clientId: credentials.clientId,
|
|
812
896
|
clientSecret: credentials.clientSecret,
|
|
813
|
-
clusterDomain: requireOption(
|
|
814
|
-
clusterDomain,
|
|
815
|
-
"Cluster domain is required for TURN APP mode."
|
|
816
|
-
),
|
|
817
897
|
keyringBaseUrl: options.keyringBaseUrl,
|
|
818
898
|
now: options.now,
|
|
819
899
|
projectEndpoint: requireOption(
|
|
@@ -822,24 +902,36 @@ async function createTURNCredentials(options) {
|
|
|
822
902
|
),
|
|
823
903
|
serverPublicKeyHex: options.serverPublicKeyHex,
|
|
824
904
|
ttlSeconds: options.ttlSeconds,
|
|
905
|
+
turnDomain: requireOption(
|
|
906
|
+
turnDomain,
|
|
907
|
+
"TURN domain is required for TURN APP mode."
|
|
908
|
+
),
|
|
825
909
|
turnPort: options.turnPort,
|
|
910
|
+
turnRealm: requireOption(
|
|
911
|
+
turnRealm,
|
|
912
|
+
"TURN realm is required for TURN APP mode."
|
|
913
|
+
),
|
|
826
914
|
turnsPort: options.turnsPort
|
|
827
915
|
});
|
|
828
916
|
}
|
|
829
917
|
|
|
830
918
|
// src/turn-resource.ts
|
|
919
|
+
import { isClientCredentials as isClientCredentials3 } from "@rstreamlabs/rstream";
|
|
831
920
|
var RstreamTURNResource = class {
|
|
832
921
|
client;
|
|
833
922
|
constructor(client) {
|
|
834
923
|
this.client = client;
|
|
835
924
|
}
|
|
836
925
|
async createCredentials(options = {}) {
|
|
837
|
-
const
|
|
926
|
+
const credentials = options.credentials ?? this.client.credentials;
|
|
927
|
+
const localMode = options.mode === "app" || options.mode === "pat" || options.mode === void 0 && isClientCredentials3(credentials);
|
|
928
|
+
const target = localMode && (options.turnDomain === void 0 || options.turnRealm === void 0) && options.clusterDomain === void 0 ? await this.client.getTURNTarget() : void 0;
|
|
929
|
+
const engine = options.engine ?? (options.clusterDomain === void 0 && options.turnDomain === void 0 && target === void 0 ? await this.client.getEngine().catch(() => void 0) : void 0);
|
|
838
930
|
return await createTURNCredentials({
|
|
839
931
|
apiUrl: options.apiUrl ?? this.client.apiUrl,
|
|
840
932
|
clusterDomain: options.clusterDomain,
|
|
841
933
|
controlPlaneCredentials: options.controlPlaneCredentials ?? this.client.controlPlaneCredentials,
|
|
842
|
-
credentials
|
|
934
|
+
credentials,
|
|
843
935
|
engine,
|
|
844
936
|
keyringBaseUrl: options.keyringBaseUrl,
|
|
845
937
|
mode: options.mode,
|
|
@@ -849,8 +941,10 @@ var RstreamTURNResource = class {
|
|
|
849
941
|
serverPublicKeyHex: options.serverPublicKeyHex,
|
|
850
942
|
tokenEndpoint: options.tokenEndpoint,
|
|
851
943
|
ttlSeconds: options.ttlSeconds,
|
|
852
|
-
|
|
853
|
-
|
|
944
|
+
turnDomain: options.turnDomain ?? target?.turnDomain,
|
|
945
|
+
turnPort: options.turnPort ?? target?.turnPort,
|
|
946
|
+
turnRealm: options.turnRealm ?? target?.turnRealm,
|
|
947
|
+
turnsPort: options.turnsPort ?? target?.turnsPort
|
|
854
948
|
});
|
|
855
949
|
}
|
|
856
950
|
};
|
|
@@ -1072,6 +1166,27 @@ var RstreamTunnelsClient = class {
|
|
|
1072
1166
|
region: this.config?.region
|
|
1073
1167
|
});
|
|
1074
1168
|
}
|
|
1169
|
+
async getTURNTarget() {
|
|
1170
|
+
const project = await this.getManagedProject();
|
|
1171
|
+
if (project === void 0) {
|
|
1172
|
+
throw new Error(
|
|
1173
|
+
"Managed project endpoint is required to resolve the TURN target."
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
const turnDomain = project.turnDomain?.trim() || project.domain.trim();
|
|
1177
|
+
const turnRealm = project.turnRealm?.trim();
|
|
1178
|
+
if (!turnRealm) {
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
"Managed project metadata does not expose the TURN realm; use API credential mode or provide turnRealm explicitly."
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
return {
|
|
1184
|
+
turnDomain,
|
|
1185
|
+
turnPort: project.turnPort,
|
|
1186
|
+
turnRealm,
|
|
1187
|
+
turnsPort: project.turnsPort
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1075
1190
|
async getManagedProject() {
|
|
1076
1191
|
const projectEndpoint = normalizeOptionalString3(
|
|
1077
1192
|
this.config?.projectEndpoint
|
|
@@ -1148,7 +1263,7 @@ var RstreamTunnelsClient = class {
|
|
|
1148
1263
|
if (!credentials) {
|
|
1149
1264
|
return void 0;
|
|
1150
1265
|
}
|
|
1151
|
-
if (!
|
|
1266
|
+
if (!isClientCredentials4(credentials)) {
|
|
1152
1267
|
return credentials.token;
|
|
1153
1268
|
}
|
|
1154
1269
|
const tokenParams = await this.getEngineAuthTokenParams();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rstreamlabs/tunnels",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.3",
|
|
4
4
|
"description": "JS/TS SDK for the rstream Engine API and tunnel runtime APIs.",
|
|
5
5
|
"author": "@uartnet <hello@rstream.io>",
|
|
6
6
|
"repository": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"typescript-config": "*"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@rstreamlabs/rstream": "3.8.
|
|
50
|
+
"@rstreamlabs/rstream": "3.8.2",
|
|
51
51
|
"@types/jsonwebtoken": "^9.0.10",
|
|
52
52
|
"jsonwebtoken": "^9.0.3",
|
|
53
53
|
"zod": "^4.4.3"
|