livekit-server-sdk 2.18.0 → 2.19.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/README.md +1 -1
- package/dist/TwirpRPC.cjs +5 -0
- package/dist/TwirpRPC.cjs.map +1 -1
- package/dist/TwirpRPC.d.cts +2 -1
- package/dist/TwirpRPC.d.ts +2 -1
- package/dist/TwirpRPC.d.ts.map +1 -1
- package/dist/TwirpRPC.js +4 -0
- package/dist/TwirpRPC.js.map +1 -1
- package/dist/crypto/digest.cjs +4 -16
- package/dist/crypto/digest.cjs.map +1 -1
- package/dist/crypto/digest.d.ts.map +1 -1
- package/dist/crypto/digest.js +4 -6
- package/dist/crypto/digest.js.map +1 -1
- package/dist/crypto/uuid.cjs +25 -17
- package/dist/crypto/uuid.cjs.map +1 -1
- package/dist/crypto/uuid.d.cts +2 -1
- package/dist/crypto/uuid.d.ts +2 -1
- package/dist/crypto/uuid.d.ts.map +1 -1
- package/dist/crypto/uuid.js +23 -6
- package/dist/crypto/uuid.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
- package/src/TwirpRPC.test.ts +76 -4
- package/src/TwirpRPC.ts +8 -1
- package/src/crypto/digest.ts +5 -7
- package/src/crypto/uuid.ts +24 -6
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -175,7 +175,7 @@ try {
|
|
|
175
175
|
});
|
|
176
176
|
} catch (e) {
|
|
177
177
|
if (e instanceof SipCallError) {
|
|
178
|
-
console.log(e.message); // e.g. "SIP call failed: 486 Busy Here (
|
|
178
|
+
console.log(e.message); // e.g. "SIP call failed: 486 Busy Here (failed_precondition)"
|
|
179
179
|
if (e.sipStatusCode === 486) {
|
|
180
180
|
// callee is busy
|
|
181
181
|
}
|
package/dist/TwirpRPC.cjs
CHANGED
|
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var TwirpRPC_exports = {};
|
|
20
20
|
__export(TwirpRPC_exports, {
|
|
21
|
+
REQUEST_ID_HEADER: () => REQUEST_ID_HEADER,
|
|
21
22
|
ServerError: () => ServerError,
|
|
22
23
|
SipCallError: () => SipCallError,
|
|
23
24
|
TwirpError: () => TwirpError,
|
|
@@ -25,9 +26,11 @@ __export(TwirpRPC_exports, {
|
|
|
25
26
|
livekitPackage: () => livekitPackage
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(TwirpRPC_exports);
|
|
29
|
+
var import_uuid = require("./crypto/uuid.cjs");
|
|
28
30
|
var import_failover = require("./failover.cjs");
|
|
29
31
|
var import_version = require("./version.cjs");
|
|
30
32
|
const USER_AGENT = `livekit-server-sdk-node/${import_version.SDK_VERSION}`;
|
|
33
|
+
const REQUEST_ID_HEADER = "X-Livekit-Request-Id";
|
|
31
34
|
const defaultPrefix = "/twirp";
|
|
32
35
|
const defaultTimeoutSeconds = 10;
|
|
33
36
|
const livekitPackage = "livekit";
|
|
@@ -109,6 +112,7 @@ class TwirpRpc {
|
|
|
109
112
|
"User-Agent": USER_AGENT,
|
|
110
113
|
...headers
|
|
111
114
|
};
|
|
115
|
+
requestHeaders[REQUEST_ID_HEADER] = await (0, import_uuid.randomUUID)();
|
|
112
116
|
const origin = new URL(this.host);
|
|
113
117
|
const maxAttempts = (0, import_failover.failoverAttempts)(
|
|
114
118
|
this.failover,
|
|
@@ -187,6 +191,7 @@ async function toTwirpError(response) {
|
|
|
187
191
|
}
|
|
188
192
|
// Annotate the CommonJS export names for ESM import in node:
|
|
189
193
|
0 && (module.exports = {
|
|
194
|
+
REQUEST_ID_HEADER,
|
|
190
195
|
ServerError,
|
|
191
196
|
SipCallError,
|
|
192
197
|
TwirpError,
|
package/dist/TwirpRPC.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/TwirpRPC.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { JsonValue } from '@bufbuild/protobuf';\nimport {\n FAILOVER_BACKOFF_BASE_MS,\n failoverAttempts,\n hostKey,\n pickNext,\n regionOrigins,\n sleep,\n} from './failover.js';\nimport { SDK_VERSION } from './version.js';\n\n// Identifies the SDK and version to the server on every request. Browsers forbid\n// setting User-Agent via fetch and silently drop it; Node honors it.\nconst USER_AGENT = `livekit-server-sdk-node/${SDK_VERSION}`;\n\n// twirp RPC adapter for client implementation\n\ntype Options = {\n /** Prefix for the RPC requests */\n prefix?: string;\n /** Timeout for fetch requests, in seconds. Must be within the valid range for abort signal timeouts. */\n requestTimeout?: number;\n /** Whether region failover is enabled (LiveKit Cloud hosts only). Defaults to true. */\n failover?: boolean;\n /** @internal test-only: force failover regardless of host. */\n failoverForce?: boolean;\n /** @internal test-only: base retry backoff in ms. */\n failoverBackoffMs?: number;\n};\n\nconst defaultPrefix = '/twirp';\nconst defaultTimeoutSeconds = 10;\n\nexport const livekitPackage = 'livekit';\nexport interface Rpc {\n request(\n service: string,\n method: string,\n data: JsonValue,\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout?: number,\n ): Promise<string>;\n}\n\nexport class ServerError extends Error {\n status: number;\n code?: string;\n metadata?: Record<string, string>;\n\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(message);\n this.name = name;\n this.status = status;\n this.code = code;\n this.metadata = metadata;\n }\n}\n\n/** @deprecated use {@link ServerError} */\nexport const TwirpError = ServerError;\n/** @deprecated use {@link ServerError} */\nexport type TwirpError = ServerError;\n\n/**\n * A {@link ServerError} from a SIP dialing call (`createSipParticipant` /\n * `transferSipParticipant`) that failed with a SIP response status. The SIP code\n * and reason are exposed as getters; any other error metadata remains available\n * via {@link ServerError.metadata}.\n */\nexport class SipCallError extends ServerError {\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(name, SipCallError.describe(message, code, metadata), status, code, metadata);\n this.name = 'SipCallError';\n }\n\n /** The SIP response code of the failed call, e.g. 486 (Busy Here). */\n get sipStatusCode(): number | undefined {\n const raw = this.metadata?.sip_status_code;\n return raw !== undefined ? Number(raw) : undefined;\n }\n\n /** The SIP reason phrase of the failed call, e.g. \"Busy Here\". */\n get sipStatus(): string | undefined {\n return this.metadata?.sip_status;\n }\n\n /** Builds a SipCallError from a ServerError, preserving its code and metadata. */\n static fromServerError(err: ServerError): SipCallError {\n return new SipCallError(err.name, err.message, err.status, err.code, err.metadata);\n }\n\n // describe renders a clear message: the SIP status, the error code, and any\n // other metadata the server attached. Falls back to the raw message when the\n // error carries no SIP status.\n private static describe(fallback: string, code?: string, metadata?: Record<string, string>) {\n const sipCode = metadata?.sip_status_code;\n if (!sipCode) {\n return fallback;\n }\n const reason = metadata?.sip_status;\n let msg = `SIP call failed: ${sipCode}${reason ? ` ${reason}` : ''}`;\n if (code) {\n msg += ` (${code})`;\n }\n const extra = Object.entries(metadata ?? {})\n .filter(([k]) => k !== 'sip_status_code' && k !== 'sip_status' && k !== 'error_details')\n .map(([k, v]) => `${k}=${v}`);\n if (extra.length) {\n msg += ` [${extra.join(', ')}]`;\n }\n return msg;\n }\n}\n\n/**\n * JSON based Twirp V7 RPC\n */\nexport class TwirpRpc {\n host: string;\n\n pkg: string;\n\n prefix: string;\n\n requestTimeout: number;\n\n failover: boolean;\n\n private failoverForce: boolean;\n\n private failoverBackoffMs: number;\n\n constructor(host: string, pkg: string, options?: Options) {\n if (host.startsWith('ws')) {\n host = host.replace('ws', 'http');\n }\n this.host = host;\n this.pkg = pkg;\n this.requestTimeout = options?.requestTimeout ?? defaultTimeoutSeconds;\n this.prefix = options?.prefix || defaultPrefix;\n this.failover = options?.failover ?? true;\n this.failoverForce = options?.failoverForce ?? false;\n this.failoverBackoffMs = options?.failoverBackoffMs ?? FAILOVER_BACKOFF_BASE_MS;\n }\n\n /**\n * Issues a Twirp request, failing over to alternative regions on retryable\n * errors. On any transport error or HTTP 5xx it discovers regions via\n * /settings/regions and replays the request — body and headers intact —\n * against the next untried region, with exponential backoff. A 4xx is\n * returned immediately.\n */\n async request(\n service: string,\n method: string,\n data: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout = this.requestTimeout,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<any> {\n const path = `${this.prefix}/${this.pkg}.${service}/${method}`;\n const body = JSON.stringify(data);\n const requestHeaders = {\n 'Content-Type': 'application/json;charset=UTF-8',\n 'User-Agent': USER_AGENT,\n ...headers,\n };\n\n const origin = new URL(this.host);\n const maxAttempts = failoverAttempts(\n this.failover,\n origin.hostname,\n this.failoverForce,\n timeout,\n );\n const attempted = new Set([hostKey(origin)]);\n let regions: string[] | undefined;\n let current = this.host;\n\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const isLast = attempt + 1 >= maxAttempts;\n const init: RequestInit = { method: 'POST', headers: requestHeaders, body };\n if (timeout) {\n init.signal = AbortSignal.timeout(timeout * 1000);\n }\n\n let response: Response | undefined;\n let transportError: unknown;\n try {\n response = await fetch(new URL(path, current), init);\n } catch (e) {\n transportError = e;\n }\n\n if (response?.ok) {\n // Return the raw JSON. Every caller parses it with protobuf-es\n // fromJson(), which per the proto3 JSON spec accepts both the proto\n // field names (snake_case) and their json_name (camelCase), so no key\n // conversion is needed. Converting keys would also corrupt map<string,…>\n // entries (e.g. participant attributes), whose keys are user data.\n return (await response.json()) as Record<string, unknown>;\n }\n\n // Only retryable failures (a transport error or HTTP 5xx) continue;\n // a 4xx is terminal.\n const retryable = transportError !== undefined || (!!response && response.status >= 500);\n let next: string | undefined;\n if (retryable && !isLast) {\n if (!regions) {\n regions = await regionOrigins(origin, headers);\n }\n next = pickNext(regions, attempted);\n }\n\n if (!retryable || next === undefined) {\n if (response) {\n throw await toTwirpError(response);\n }\n throw transportError;\n }\n\n const reason = response ? `status ${response.status}` : transportError;\n console.warn(\n `livekit API request to ${new URL(current).host} failed (${reason}), retrying with fallback url ${next}`,\n );\n await sleep(this.failoverBackoffMs * 2 ** attempt);\n attempted.add(hostKey(new URL(next)));\n current = next;\n }\n\n throw new Error('failover loop exited without returning'); // unreachable\n }\n}\n\n/** Builds a TwirpError from a non-2xx response, mirroring Twirp's JSON error shape. */\nasync function toTwirpError(response: Response): Promise<TwirpError> {\n const isJson = response.headers.get('content-type') === 'application/json';\n let errorMessage = 'Unknown internal error';\n let errorCode: string | undefined = undefined;\n let metadata: Record<string, string> | undefined = undefined;\n try {\n if (isJson) {\n const parsedError = (await response.json()) as Record<string, unknown>;\n if ('msg' in parsedError) {\n errorMessage = <string>parsedError.msg;\n }\n if ('code' in parsedError) {\n errorCode = <string>parsedError.code;\n }\n if ('meta' in parsedError) {\n metadata = <Record<string, string>>parsedError.meta;\n }\n } else {\n errorMessage = await response.text();\n }\n } catch (e) {\n // parsing went wrong, no op and we keep default error message\n console.debug(`Error when trying to parse error message, using defaults`, e);\n }\n return new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/TwirpRPC.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { JsonValue } from '@bufbuild/protobuf';\nimport { randomUUID } from './crypto/uuid.js';\nimport {\n FAILOVER_BACKOFF_BASE_MS,\n failoverAttempts,\n hostKey,\n pickNext,\n regionOrigins,\n sleep,\n} from './failover.js';\nimport { SDK_VERSION } from './version.js';\n\n// Identifies the SDK and version to the server on every request. Browsers forbid\n// setting User-Agent via fetch and silently drop it; Node honors it.\nconst USER_AGENT = `livekit-server-sdk-node/${SDK_VERSION}`;\n\n// Carries a per-request idempotency key. The SDK's auto-retries (see failover)\n// keep the same key across attempts, so the server can identify and deduplicate\n// repeated requests.\nexport const REQUEST_ID_HEADER = 'X-Livekit-Request-Id';\n\n// twirp RPC adapter for client implementation\n\ntype Options = {\n /** Prefix for the RPC requests */\n prefix?: string;\n /** Timeout for fetch requests, in seconds. Must be within the valid range for abort signal timeouts. */\n requestTimeout?: number;\n /** Whether region failover is enabled (LiveKit Cloud hosts only). Defaults to true. */\n failover?: boolean;\n /** @internal test-only: force failover regardless of host. */\n failoverForce?: boolean;\n /** @internal test-only: base retry backoff in ms. */\n failoverBackoffMs?: number;\n};\n\nconst defaultPrefix = '/twirp';\nconst defaultTimeoutSeconds = 10;\n\nexport const livekitPackage = 'livekit';\nexport interface Rpc {\n request(\n service: string,\n method: string,\n data: JsonValue,\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout?: number,\n ): Promise<string>;\n}\n\nexport class ServerError extends Error {\n status: number;\n code?: string;\n metadata?: Record<string, string>;\n\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(message);\n this.name = name;\n this.status = status;\n this.code = code;\n this.metadata = metadata;\n }\n}\n\n/** @deprecated use {@link ServerError} */\nexport const TwirpError = ServerError;\n/** @deprecated use {@link ServerError} */\nexport type TwirpError = ServerError;\n\n/**\n * A {@link ServerError} from a SIP dialing call (`createSipParticipant` /\n * `transferSipParticipant`) that failed with a SIP response status. The SIP code\n * and reason are exposed as getters; any other error metadata remains available\n * via {@link ServerError.metadata}.\n */\nexport class SipCallError extends ServerError {\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(name, SipCallError.describe(message, code, metadata), status, code, metadata);\n this.name = 'SipCallError';\n }\n\n /** The SIP response code of the failed call, e.g. 486 (Busy Here). */\n get sipStatusCode(): number | undefined {\n const raw = this.metadata?.sip_status_code;\n return raw !== undefined ? Number(raw) : undefined;\n }\n\n /** The SIP reason phrase of the failed call, e.g. \"Busy Here\". */\n get sipStatus(): string | undefined {\n return this.metadata?.sip_status;\n }\n\n /** Builds a SipCallError from a ServerError, preserving its code and metadata. */\n static fromServerError(err: ServerError): SipCallError {\n return new SipCallError(err.name, err.message, err.status, err.code, err.metadata);\n }\n\n // describe renders a clear message: the SIP status, the error code, and any\n // other metadata the server attached. Falls back to the raw message when the\n // error carries no SIP status.\n private static describe(fallback: string, code?: string, metadata?: Record<string, string>) {\n const sipCode = metadata?.sip_status_code;\n if (!sipCode) {\n return fallback;\n }\n const reason = metadata?.sip_status;\n let msg = `SIP call failed: ${sipCode}${reason ? ` ${reason}` : ''}`;\n if (code) {\n msg += ` (${code})`;\n }\n const extra = Object.entries(metadata ?? {})\n .filter(([k]) => k !== 'sip_status_code' && k !== 'sip_status' && k !== 'error_details')\n .map(([k, v]) => `${k}=${v}`);\n if (extra.length) {\n msg += ` [${extra.join(', ')}]`;\n }\n return msg;\n }\n}\n\n/**\n * JSON based Twirp V7 RPC\n */\nexport class TwirpRpc {\n host: string;\n\n pkg: string;\n\n prefix: string;\n\n requestTimeout: number;\n\n failover: boolean;\n\n private failoverForce: boolean;\n\n private failoverBackoffMs: number;\n\n constructor(host: string, pkg: string, options?: Options) {\n if (host.startsWith('ws')) {\n host = host.replace('ws', 'http');\n }\n this.host = host;\n this.pkg = pkg;\n this.requestTimeout = options?.requestTimeout ?? defaultTimeoutSeconds;\n this.prefix = options?.prefix || defaultPrefix;\n this.failover = options?.failover ?? true;\n this.failoverForce = options?.failoverForce ?? false;\n this.failoverBackoffMs = options?.failoverBackoffMs ?? FAILOVER_BACKOFF_BASE_MS;\n }\n\n /**\n * Issues a Twirp request, failing over to alternative regions on retryable\n * errors. On any transport error or HTTP 5xx it discovers regions via\n * /settings/regions and replays the request — body and headers intact —\n * against the next untried region, with exponential backoff. A 4xx is\n * returned immediately.\n */\n async request(\n service: string,\n method: string,\n data: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout = this.requestTimeout,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<any> {\n const path = `${this.prefix}/${this.pkg}.${service}/${method}`;\n const body = JSON.stringify(data);\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json;charset=UTF-8',\n 'User-Agent': USER_AGENT,\n ...headers,\n };\n requestHeaders[REQUEST_ID_HEADER] = await randomUUID();\n\n const origin = new URL(this.host);\n const maxAttempts = failoverAttempts(\n this.failover,\n origin.hostname,\n this.failoverForce,\n timeout,\n );\n const attempted = new Set([hostKey(origin)]);\n let regions: string[] | undefined;\n let current = this.host;\n\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const isLast = attempt + 1 >= maxAttempts;\n const init: RequestInit = { method: 'POST', headers: requestHeaders, body };\n if (timeout) {\n init.signal = AbortSignal.timeout(timeout * 1000);\n }\n\n let response: Response | undefined;\n let transportError: unknown;\n try {\n response = await fetch(new URL(path, current), init);\n } catch (e) {\n transportError = e;\n }\n\n if (response?.ok) {\n // Return the raw JSON. Every caller parses it with protobuf-es\n // fromJson(), which per the proto3 JSON spec accepts both the proto\n // field names (snake_case) and their json_name (camelCase), so no key\n // conversion is needed. Converting keys would also corrupt map<string,…>\n // entries (e.g. participant attributes), whose keys are user data.\n return (await response.json()) as Record<string, unknown>;\n }\n\n // Only retryable failures (a transport error or HTTP 5xx) continue;\n // a 4xx is terminal.\n const retryable = transportError !== undefined || (!!response && response.status >= 500);\n let next: string | undefined;\n if (retryable && !isLast) {\n if (!regions) {\n regions = await regionOrigins(origin, headers);\n }\n next = pickNext(regions, attempted);\n }\n\n if (!retryable || next === undefined) {\n if (response) {\n throw await toTwirpError(response);\n }\n throw transportError;\n }\n\n const reason = response ? `status ${response.status}` : transportError;\n console.warn(\n `livekit API request to ${new URL(current).host} failed (${reason}), retrying with fallback url ${next}`,\n );\n await sleep(this.failoverBackoffMs * 2 ** attempt);\n attempted.add(hostKey(new URL(next)));\n current = next;\n }\n\n throw new Error('failover loop exited without returning'); // unreachable\n }\n}\n\n/** Builds a TwirpError from a non-2xx response, mirroring Twirp's JSON error shape. */\nasync function toTwirpError(response: Response): Promise<TwirpError> {\n const isJson = response.headers.get('content-type') === 'application/json';\n let errorMessage = 'Unknown internal error';\n let errorCode: string | undefined = undefined;\n let metadata: Record<string, string> | undefined = undefined;\n try {\n if (isJson) {\n const parsedError = (await response.json()) as Record<string, unknown>;\n if ('msg' in parsedError) {\n errorMessage = <string>parsedError.msg;\n }\n if ('code' in parsedError) {\n errorCode = <string>parsedError.code;\n }\n if ('meta' in parsedError) {\n metadata = <Record<string, string>>parsedError.meta;\n }\n } else {\n errorMessage = await response.text();\n }\n } catch (e) {\n // parsing went wrong, no op and we keep default error message\n console.debug(`Error when trying to parse error message, using defaults`, e);\n }\n return new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);\n}\n"],"mappings":"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","names":[]}
|
package/dist/TwirpRPC.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JsonValue } from '@bufbuild/protobuf';
|
|
2
2
|
|
|
3
|
+
declare const REQUEST_ID_HEADER = "X-Livekit-Request-Id";
|
|
3
4
|
type Options = {
|
|
4
5
|
/** Prefix for the RPC requests */
|
|
5
6
|
prefix?: string;
|
|
@@ -67,4 +68,4 @@ declare class TwirpRpc {
|
|
|
67
68
|
timeout?: number): Promise<any>;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
export { type Rpc, ServerError, SipCallError, TwirpError, TwirpRpc, livekitPackage };
|
|
71
|
+
export { REQUEST_ID_HEADER, type Rpc, ServerError, SipCallError, TwirpError, TwirpRpc, livekitPackage };
|
package/dist/TwirpRPC.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JsonValue } from '@bufbuild/protobuf';
|
|
2
2
|
|
|
3
|
+
declare const REQUEST_ID_HEADER = "X-Livekit-Request-Id";
|
|
3
4
|
type Options = {
|
|
4
5
|
/** Prefix for the RPC requests */
|
|
5
6
|
prefix?: string;
|
|
@@ -67,4 +68,4 @@ declare class TwirpRpc {
|
|
|
67
68
|
timeout?: number): Promise<any>;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
export { type Rpc, ServerError, SipCallError, TwirpError, TwirpRpc, livekitPackage };
|
|
71
|
+
export { REQUEST_ID_HEADER, type Rpc, ServerError, SipCallError, TwirpError, TwirpRpc, livekitPackage };
|
package/dist/TwirpRPC.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TwirpRPC.d.ts","sourceRoot":"","sources":["../src/TwirpRPC.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"TwirpRPC.d.ts","sourceRoot":"","sources":["../src/TwirpRPC.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAmBpD,eAAO,MAAM,iBAAiB,yBAAyB,CAAC;AAIxD,KAAK,OAAO,GAAG;IACb,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wGAAwG;IACxG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAKF,eAAO,MAAM,cAAc,YAAY,CAAC;AACxC,MAAM,WAAW,GAAG;IAClB,OAAO,CACL,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,GAAG,EAAE,yDAAyD;IACvE,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,qBAAa,WAAY,SAAQ,KAAK;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAGhC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAQpC;AAED,0CAA0C;AAC1C,eAAO,MAAM,UAAU,oBAAc,CAAC;AACtC,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC;AAErC;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,WAAW;gBAEzC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAMnC,sEAAsE;IACtE,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAGtC;IAED,kEAAkE;IAClE,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,kFAAkF;IAClF,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,YAAY;IAOtD,OAAO,CAAC,MAAM,CAAC,QAAQ;CAkBxB;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,IAAI,EAAE,MAAM,CAAC;IAEb,GAAG,EAAE,MAAM,CAAC;IAEZ,MAAM,EAAE,MAAM,CAAC;IAEf,cAAc,EAAE,MAAM,CAAC;IAEvB,QAAQ,EAAE,OAAO,CAAC;IAElB,OAAO,CAAC,aAAa,CAAU;IAE/B,OAAO,CAAC,iBAAiB,CAAS;gBAEtB,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;IAaxD;;;;;;OAMG;IACG,OAAO,CACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,GAAG,EAAE,yDAAyD;IACpE,OAAO,EAAE,GAAG,EAAE,yDAAyD;IACvE,OAAO,SAAsB,GAE5B,OAAO,CAAC,GAAG,CAAC;CA0EhB"}
|
package/dist/TwirpRPC.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "./crypto/uuid.js";
|
|
1
2
|
import {
|
|
2
3
|
FAILOVER_BACKOFF_BASE_MS,
|
|
3
4
|
failoverAttempts,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
} from "./failover.js";
|
|
9
10
|
import { SDK_VERSION } from "./version.js";
|
|
10
11
|
const USER_AGENT = `livekit-server-sdk-node/${SDK_VERSION}`;
|
|
12
|
+
const REQUEST_ID_HEADER = "X-Livekit-Request-Id";
|
|
11
13
|
const defaultPrefix = "/twirp";
|
|
12
14
|
const defaultTimeoutSeconds = 10;
|
|
13
15
|
const livekitPackage = "livekit";
|
|
@@ -89,6 +91,7 @@ class TwirpRpc {
|
|
|
89
91
|
"User-Agent": USER_AGENT,
|
|
90
92
|
...headers
|
|
91
93
|
};
|
|
94
|
+
requestHeaders[REQUEST_ID_HEADER] = await randomUUID();
|
|
92
95
|
const origin = new URL(this.host);
|
|
93
96
|
const maxAttempts = failoverAttempts(
|
|
94
97
|
this.failover,
|
|
@@ -166,6 +169,7 @@ async function toTwirpError(response) {
|
|
|
166
169
|
return new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);
|
|
167
170
|
}
|
|
168
171
|
export {
|
|
172
|
+
REQUEST_ID_HEADER,
|
|
169
173
|
ServerError,
|
|
170
174
|
SipCallError,
|
|
171
175
|
TwirpError,
|
package/dist/TwirpRPC.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/TwirpRPC.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { JsonValue } from '@bufbuild/protobuf';\nimport {\n FAILOVER_BACKOFF_BASE_MS,\n failoverAttempts,\n hostKey,\n pickNext,\n regionOrigins,\n sleep,\n} from './failover.js';\nimport { SDK_VERSION } from './version.js';\n\n// Identifies the SDK and version to the server on every request. Browsers forbid\n// setting User-Agent via fetch and silently drop it; Node honors it.\nconst USER_AGENT = `livekit-server-sdk-node/${SDK_VERSION}`;\n\n// twirp RPC adapter for client implementation\n\ntype Options = {\n /** Prefix for the RPC requests */\n prefix?: string;\n /** Timeout for fetch requests, in seconds. Must be within the valid range for abort signal timeouts. */\n requestTimeout?: number;\n /** Whether region failover is enabled (LiveKit Cloud hosts only). Defaults to true. */\n failover?: boolean;\n /** @internal test-only: force failover regardless of host. */\n failoverForce?: boolean;\n /** @internal test-only: base retry backoff in ms. */\n failoverBackoffMs?: number;\n};\n\nconst defaultPrefix = '/twirp';\nconst defaultTimeoutSeconds = 10;\n\nexport const livekitPackage = 'livekit';\nexport interface Rpc {\n request(\n service: string,\n method: string,\n data: JsonValue,\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout?: number,\n ): Promise<string>;\n}\n\nexport class ServerError extends Error {\n status: number;\n code?: string;\n metadata?: Record<string, string>;\n\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(message);\n this.name = name;\n this.status = status;\n this.code = code;\n this.metadata = metadata;\n }\n}\n\n/** @deprecated use {@link ServerError} */\nexport const TwirpError = ServerError;\n/** @deprecated use {@link ServerError} */\nexport type TwirpError = ServerError;\n\n/**\n * A {@link ServerError} from a SIP dialing call (`createSipParticipant` /\n * `transferSipParticipant`) that failed with a SIP response status. The SIP code\n * and reason are exposed as getters; any other error metadata remains available\n * via {@link ServerError.metadata}.\n */\nexport class SipCallError extends ServerError {\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(name, SipCallError.describe(message, code, metadata), status, code, metadata);\n this.name = 'SipCallError';\n }\n\n /** The SIP response code of the failed call, e.g. 486 (Busy Here). */\n get sipStatusCode(): number | undefined {\n const raw = this.metadata?.sip_status_code;\n return raw !== undefined ? Number(raw) : undefined;\n }\n\n /** The SIP reason phrase of the failed call, e.g. \"Busy Here\". */\n get sipStatus(): string | undefined {\n return this.metadata?.sip_status;\n }\n\n /** Builds a SipCallError from a ServerError, preserving its code and metadata. */\n static fromServerError(err: ServerError): SipCallError {\n return new SipCallError(err.name, err.message, err.status, err.code, err.metadata);\n }\n\n // describe renders a clear message: the SIP status, the error code, and any\n // other metadata the server attached. Falls back to the raw message when the\n // error carries no SIP status.\n private static describe(fallback: string, code?: string, metadata?: Record<string, string>) {\n const sipCode = metadata?.sip_status_code;\n if (!sipCode) {\n return fallback;\n }\n const reason = metadata?.sip_status;\n let msg = `SIP call failed: ${sipCode}${reason ? ` ${reason}` : ''}`;\n if (code) {\n msg += ` (${code})`;\n }\n const extra = Object.entries(metadata ?? {})\n .filter(([k]) => k !== 'sip_status_code' && k !== 'sip_status' && k !== 'error_details')\n .map(([k, v]) => `${k}=${v}`);\n if (extra.length) {\n msg += ` [${extra.join(', ')}]`;\n }\n return msg;\n }\n}\n\n/**\n * JSON based Twirp V7 RPC\n */\nexport class TwirpRpc {\n host: string;\n\n pkg: string;\n\n prefix: string;\n\n requestTimeout: number;\n\n failover: boolean;\n\n private failoverForce: boolean;\n\n private failoverBackoffMs: number;\n\n constructor(host: string, pkg: string, options?: Options) {\n if (host.startsWith('ws')) {\n host = host.replace('ws', 'http');\n }\n this.host = host;\n this.pkg = pkg;\n this.requestTimeout = options?.requestTimeout ?? defaultTimeoutSeconds;\n this.prefix = options?.prefix || defaultPrefix;\n this.failover = options?.failover ?? true;\n this.failoverForce = options?.failoverForce ?? false;\n this.failoverBackoffMs = options?.failoverBackoffMs ?? FAILOVER_BACKOFF_BASE_MS;\n }\n\n /**\n * Issues a Twirp request, failing over to alternative regions on retryable\n * errors. On any transport error or HTTP 5xx it discovers regions via\n * /settings/regions and replays the request — body and headers intact —\n * against the next untried region, with exponential backoff. A 4xx is\n * returned immediately.\n */\n async request(\n service: string,\n method: string,\n data: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout = this.requestTimeout,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<any> {\n const path = `${this.prefix}/${this.pkg}.${service}/${method}`;\n const body = JSON.stringify(data);\n const requestHeaders = {\n 'Content-Type': 'application/json;charset=UTF-8',\n 'User-Agent': USER_AGENT,\n ...headers,\n };\n\n const origin = new URL(this.host);\n const maxAttempts = failoverAttempts(\n this.failover,\n origin.hostname,\n this.failoverForce,\n timeout,\n );\n const attempted = new Set([hostKey(origin)]);\n let regions: string[] | undefined;\n let current = this.host;\n\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const isLast = attempt + 1 >= maxAttempts;\n const init: RequestInit = { method: 'POST', headers: requestHeaders, body };\n if (timeout) {\n init.signal = AbortSignal.timeout(timeout * 1000);\n }\n\n let response: Response | undefined;\n let transportError: unknown;\n try {\n response = await fetch(new URL(path, current), init);\n } catch (e) {\n transportError = e;\n }\n\n if (response?.ok) {\n // Return the raw JSON. Every caller parses it with protobuf-es\n // fromJson(), which per the proto3 JSON spec accepts both the proto\n // field names (snake_case) and their json_name (camelCase), so no key\n // conversion is needed. Converting keys would also corrupt map<string,…>\n // entries (e.g. participant attributes), whose keys are user data.\n return (await response.json()) as Record<string, unknown>;\n }\n\n // Only retryable failures (a transport error or HTTP 5xx) continue;\n // a 4xx is terminal.\n const retryable = transportError !== undefined || (!!response && response.status >= 500);\n let next: string | undefined;\n if (retryable && !isLast) {\n if (!regions) {\n regions = await regionOrigins(origin, headers);\n }\n next = pickNext(regions, attempted);\n }\n\n if (!retryable || next === undefined) {\n if (response) {\n throw await toTwirpError(response);\n }\n throw transportError;\n }\n\n const reason = response ? `status ${response.status}` : transportError;\n console.warn(\n `livekit API request to ${new URL(current).host} failed (${reason}), retrying with fallback url ${next}`,\n );\n await sleep(this.failoverBackoffMs * 2 ** attempt);\n attempted.add(hostKey(new URL(next)));\n current = next;\n }\n\n throw new Error('failover loop exited without returning'); // unreachable\n }\n}\n\n/** Builds a TwirpError from a non-2xx response, mirroring Twirp's JSON error shape. */\nasync function toTwirpError(response: Response): Promise<TwirpError> {\n const isJson = response.headers.get('content-type') === 'application/json';\n let errorMessage = 'Unknown internal error';\n let errorCode: string | undefined = undefined;\n let metadata: Record<string, string> | undefined = undefined;\n try {\n if (isJson) {\n const parsedError = (await response.json()) as Record<string, unknown>;\n if ('msg' in parsedError) {\n errorMessage = <string>parsedError.msg;\n }\n if ('code' in parsedError) {\n errorCode = <string>parsedError.code;\n }\n if ('meta' in parsedError) {\n metadata = <Record<string, string>>parsedError.meta;\n }\n } else {\n errorMessage = await response.text();\n }\n } catch (e) {\n // parsing went wrong, no op and we keep default error message\n console.debug(`Error when trying to parse error message, using defaults`, e);\n }\n return new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/TwirpRPC.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { JsonValue } from '@bufbuild/protobuf';\nimport { randomUUID } from './crypto/uuid.js';\nimport {\n FAILOVER_BACKOFF_BASE_MS,\n failoverAttempts,\n hostKey,\n pickNext,\n regionOrigins,\n sleep,\n} from './failover.js';\nimport { SDK_VERSION } from './version.js';\n\n// Identifies the SDK and version to the server on every request. Browsers forbid\n// setting User-Agent via fetch and silently drop it; Node honors it.\nconst USER_AGENT = `livekit-server-sdk-node/${SDK_VERSION}`;\n\n// Carries a per-request idempotency key. The SDK's auto-retries (see failover)\n// keep the same key across attempts, so the server can identify and deduplicate\n// repeated requests.\nexport const REQUEST_ID_HEADER = 'X-Livekit-Request-Id';\n\n// twirp RPC adapter for client implementation\n\ntype Options = {\n /** Prefix for the RPC requests */\n prefix?: string;\n /** Timeout for fetch requests, in seconds. Must be within the valid range for abort signal timeouts. */\n requestTimeout?: number;\n /** Whether region failover is enabled (LiveKit Cloud hosts only). Defaults to true. */\n failover?: boolean;\n /** @internal test-only: force failover regardless of host. */\n failoverForce?: boolean;\n /** @internal test-only: base retry backoff in ms. */\n failoverBackoffMs?: number;\n};\n\nconst defaultPrefix = '/twirp';\nconst defaultTimeoutSeconds = 10;\n\nexport const livekitPackage = 'livekit';\nexport interface Rpc {\n request(\n service: string,\n method: string,\n data: JsonValue,\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout?: number,\n ): Promise<string>;\n}\n\nexport class ServerError extends Error {\n status: number;\n code?: string;\n metadata?: Record<string, string>;\n\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(message);\n this.name = name;\n this.status = status;\n this.code = code;\n this.metadata = metadata;\n }\n}\n\n/** @deprecated use {@link ServerError} */\nexport const TwirpError = ServerError;\n/** @deprecated use {@link ServerError} */\nexport type TwirpError = ServerError;\n\n/**\n * A {@link ServerError} from a SIP dialing call (`createSipParticipant` /\n * `transferSipParticipant`) that failed with a SIP response status. The SIP code\n * and reason are exposed as getters; any other error metadata remains available\n * via {@link ServerError.metadata}.\n */\nexport class SipCallError extends ServerError {\n constructor(\n name: string,\n message: string,\n status: number,\n code?: string,\n metadata?: Record<string, string>,\n ) {\n super(name, SipCallError.describe(message, code, metadata), status, code, metadata);\n this.name = 'SipCallError';\n }\n\n /** The SIP response code of the failed call, e.g. 486 (Busy Here). */\n get sipStatusCode(): number | undefined {\n const raw = this.metadata?.sip_status_code;\n return raw !== undefined ? Number(raw) : undefined;\n }\n\n /** The SIP reason phrase of the failed call, e.g. \"Busy Here\". */\n get sipStatus(): string | undefined {\n return this.metadata?.sip_status;\n }\n\n /** Builds a SipCallError from a ServerError, preserving its code and metadata. */\n static fromServerError(err: ServerError): SipCallError {\n return new SipCallError(err.name, err.message, err.status, err.code, err.metadata);\n }\n\n // describe renders a clear message: the SIP status, the error code, and any\n // other metadata the server attached. Falls back to the raw message when the\n // error carries no SIP status.\n private static describe(fallback: string, code?: string, metadata?: Record<string, string>) {\n const sipCode = metadata?.sip_status_code;\n if (!sipCode) {\n return fallback;\n }\n const reason = metadata?.sip_status;\n let msg = `SIP call failed: ${sipCode}${reason ? ` ${reason}` : ''}`;\n if (code) {\n msg += ` (${code})`;\n }\n const extra = Object.entries(metadata ?? {})\n .filter(([k]) => k !== 'sip_status_code' && k !== 'sip_status' && k !== 'error_details')\n .map(([k, v]) => `${k}=${v}`);\n if (extra.length) {\n msg += ` [${extra.join(', ')}]`;\n }\n return msg;\n }\n}\n\n/**\n * JSON based Twirp V7 RPC\n */\nexport class TwirpRpc {\n host: string;\n\n pkg: string;\n\n prefix: string;\n\n requestTimeout: number;\n\n failover: boolean;\n\n private failoverForce: boolean;\n\n private failoverBackoffMs: number;\n\n constructor(host: string, pkg: string, options?: Options) {\n if (host.startsWith('ws')) {\n host = host.replace('ws', 'http');\n }\n this.host = host;\n this.pkg = pkg;\n this.requestTimeout = options?.requestTimeout ?? defaultTimeoutSeconds;\n this.prefix = options?.prefix || defaultPrefix;\n this.failover = options?.failover ?? true;\n this.failoverForce = options?.failoverForce ?? false;\n this.failoverBackoffMs = options?.failoverBackoffMs ?? FAILOVER_BACKOFF_BASE_MS;\n }\n\n /**\n * Issues a Twirp request, failing over to alternative regions on retryable\n * errors. On any transport error or HTTP 5xx it discovers regions via\n * /settings/regions and replays the request — body and headers intact —\n * against the next untried region, with exponential backoff. A 4xx is\n * returned immediately.\n */\n async request(\n service: string,\n method: string,\n data: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any\n timeout = this.requestTimeout,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<any> {\n const path = `${this.prefix}/${this.pkg}.${service}/${method}`;\n const body = JSON.stringify(data);\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json;charset=UTF-8',\n 'User-Agent': USER_AGENT,\n ...headers,\n };\n requestHeaders[REQUEST_ID_HEADER] = await randomUUID();\n\n const origin = new URL(this.host);\n const maxAttempts = failoverAttempts(\n this.failover,\n origin.hostname,\n this.failoverForce,\n timeout,\n );\n const attempted = new Set([hostKey(origin)]);\n let regions: string[] | undefined;\n let current = this.host;\n\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const isLast = attempt + 1 >= maxAttempts;\n const init: RequestInit = { method: 'POST', headers: requestHeaders, body };\n if (timeout) {\n init.signal = AbortSignal.timeout(timeout * 1000);\n }\n\n let response: Response | undefined;\n let transportError: unknown;\n try {\n response = await fetch(new URL(path, current), init);\n } catch (e) {\n transportError = e;\n }\n\n if (response?.ok) {\n // Return the raw JSON. Every caller parses it with protobuf-es\n // fromJson(), which per the proto3 JSON spec accepts both the proto\n // field names (snake_case) and their json_name (camelCase), so no key\n // conversion is needed. Converting keys would also corrupt map<string,…>\n // entries (e.g. participant attributes), whose keys are user data.\n return (await response.json()) as Record<string, unknown>;\n }\n\n // Only retryable failures (a transport error or HTTP 5xx) continue;\n // a 4xx is terminal.\n const retryable = transportError !== undefined || (!!response && response.status >= 500);\n let next: string | undefined;\n if (retryable && !isLast) {\n if (!regions) {\n regions = await regionOrigins(origin, headers);\n }\n next = pickNext(regions, attempted);\n }\n\n if (!retryable || next === undefined) {\n if (response) {\n throw await toTwirpError(response);\n }\n throw transportError;\n }\n\n const reason = response ? `status ${response.status}` : transportError;\n console.warn(\n `livekit API request to ${new URL(current).host} failed (${reason}), retrying with fallback url ${next}`,\n );\n await sleep(this.failoverBackoffMs * 2 ** attempt);\n attempted.add(hostKey(new URL(next)));\n current = next;\n }\n\n throw new Error('failover loop exited without returning'); // unreachable\n }\n}\n\n/** Builds a TwirpError from a non-2xx response, mirroring Twirp's JSON error shape. */\nasync function toTwirpError(response: Response): Promise<TwirpError> {\n const isJson = response.headers.get('content-type') === 'application/json';\n let errorMessage = 'Unknown internal error';\n let errorCode: string | undefined = undefined;\n let metadata: Record<string, string> | undefined = undefined;\n try {\n if (isJson) {\n const parsedError = (await response.json()) as Record<string, unknown>;\n if ('msg' in parsedError) {\n errorMessage = <string>parsedError.msg;\n }\n if ('code' in parsedError) {\n errorCode = <string>parsedError.code;\n }\n if ('meta' in parsedError) {\n metadata = <Record<string, string>>parsedError.meta;\n }\n } else {\n errorMessage = await response.text();\n }\n } catch (e) {\n // parsing went wrong, no op and we keep default error message\n console.debug(`Error when trying to parse error message, using defaults`, e);\n }\n return new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);\n}\n"],"mappings":"AAIA,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAmB;AAI5B,MAAM,aAAa,2BAA2B,WAAW;AAKlD,MAAM,oBAAoB;AAiBjC,MAAM,gBAAgB;AACtB,MAAM,wBAAwB;AAEvB,MAAM,iBAAiB;AAWvB,MAAM,oBAAoB,MAAM;AAAA,EAKrC,YACE,MACA,SACA,QACA,MACA,UACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAGO,MAAM,aAAa;AAUnB,MAAM,qBAAqB,YAAY;AAAA,EAC5C,YACE,MACA,SACA,QACA,MACA,UACA;AACA,UAAM,MAAM,aAAa,SAAS,SAAS,MAAM,QAAQ,GAAG,QAAQ,MAAM,QAAQ;AAClF,SAAK,OAAO;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,gBAAoC;AAjG1C;AAkGI,UAAM,OAAM,UAAK,aAAL,mBAAe;AAC3B,WAAO,QAAQ,SAAY,OAAO,GAAG,IAAI;AAAA,EAC3C;AAAA;AAAA,EAGA,IAAI,YAAgC;AAvGtC;AAwGI,YAAO,UAAK,aAAL,mBAAe;AAAA,EACxB;AAAA;AAAA,EAGA,OAAO,gBAAgB,KAAgC;AACrD,WAAO,IAAI,aAAa,IAAI,MAAM,IAAI,SAAS,IAAI,QAAQ,IAAI,MAAM,IAAI,QAAQ;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAe,SAAS,UAAkB,MAAe,UAAmC;AAC1F,UAAM,UAAU,qCAAU;AAC1B,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AACA,UAAM,SAAS,qCAAU;AACzB,QAAI,MAAM,oBAAoB,OAAO,GAAG,SAAS,IAAI,MAAM,KAAK,EAAE;AAClE,QAAI,MAAM;AACR,aAAO,KAAK,IAAI;AAAA,IAClB;AACA,UAAM,QAAQ,OAAO,QAAQ,YAAY,CAAC,CAAC,EACxC,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,qBAAqB,MAAM,gBAAgB,MAAM,eAAe,EACtF,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE;AAC9B,QAAI,MAAM,QAAQ;AAChB,aAAO,KAAK,MAAM,KAAK,IAAI,CAAC;AAAA,IAC9B;AACA,WAAO;AAAA,EACT;AACF;AAKO,MAAM,SAAS;AAAA,EAepB,YAAY,MAAc,KAAa,SAAmB;AACxD,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,aAAO,KAAK,QAAQ,MAAM,MAAM;AAAA,IAClC;AACA,SAAK,OAAO;AACZ,SAAK,MAAM;AACX,SAAK,kBAAiB,mCAAS,mBAAkB;AACjD,SAAK,UAAS,mCAAS,WAAU;AACjC,SAAK,YAAW,mCAAS,aAAY;AACrC,SAAK,iBAAgB,mCAAS,kBAAiB;AAC/C,SAAK,qBAAoB,mCAAS,sBAAqB;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,QACJ,SACA,QACA,MACA,SACA,UAAU,KAAK,gBAED;AACd,UAAM,OAAO,GAAG,KAAK,MAAM,IAAI,KAAK,GAAG,IAAI,OAAO,IAAI,MAAM;AAC5D,UAAM,OAAO,KAAK,UAAU,IAAI;AAChC,UAAM,iBAAyC;AAAA,MAC7C,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,GAAG;AAAA,IACL;AACA,mBAAe,iBAAiB,IAAI,MAAM,WAAW;AAErD,UAAM,SAAS,IAAI,IAAI,KAAK,IAAI;AAChC,UAAM,cAAc;AAAA,MAClB,KAAK;AAAA,MACL,OAAO;AAAA,MACP,KAAK;AAAA,MACL;AAAA,IACF;AACA,UAAM,YAAY,oBAAI,IAAI,CAAC,QAAQ,MAAM,CAAC,CAAC;AAC3C,QAAI;AACJ,QAAI,UAAU,KAAK;AAEnB,aAAS,UAAU,GAAG,UAAU,aAAa,WAAW,GAAG;AACzD,YAAM,SAAS,UAAU,KAAK;AAC9B,YAAM,OAAoB,EAAE,QAAQ,QAAQ,SAAS,gBAAgB,KAAK;AAC1E,UAAI,SAAS;AACX,aAAK,SAAS,YAAY,QAAQ,UAAU,GAAI;AAAA,MAClD;AAEA,UAAI;AACJ,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,MAAM,IAAI,IAAI,MAAM,OAAO,GAAG,IAAI;AAAA,MACrD,SAAS,GAAG;AACV,yBAAiB;AAAA,MACnB;AAEA,UAAI,qCAAU,IAAI;AAMhB,eAAQ,MAAM,SAAS,KAAK;AAAA,MAC9B;AAIA,YAAM,YAAY,mBAAmB,UAAc,CAAC,CAAC,YAAY,SAAS,UAAU;AACpF,UAAI;AACJ,UAAI,aAAa,CAAC,QAAQ;AACxB,YAAI,CAAC,SAAS;AACZ,oBAAU,MAAM,cAAc,QAAQ,OAAO;AAAA,QAC/C;AACA,eAAO,SAAS,SAAS,SAAS;AAAA,MACpC;AAEA,UAAI,CAAC,aAAa,SAAS,QAAW;AACpC,YAAI,UAAU;AACZ,gBAAM,MAAM,aAAa,QAAQ;AAAA,QACnC;AACA,cAAM;AAAA,MACR;AAEA,YAAM,SAAS,WAAW,UAAU,SAAS,MAAM,KAAK;AACxD,cAAQ;AAAA,QACN,0BAA0B,IAAI,IAAI,OAAO,EAAE,IAAI,YAAY,MAAM,iCAAiC,IAAI;AAAA,MACxG;AACA,YAAM,MAAM,KAAK,oBAAoB,KAAK,OAAO;AACjD,gBAAU,IAAI,QAAQ,IAAI,IAAI,IAAI,CAAC,CAAC;AACpC,gBAAU;AAAA,IACZ;AAEA,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACF;AAGA,eAAe,aAAa,UAAyC;AACnE,QAAM,SAAS,SAAS,QAAQ,IAAI,cAAc,MAAM;AACxD,MAAI,eAAe;AACnB,MAAI,YAAgC;AACpC,MAAI,WAA+C;AACnD,MAAI;AACF,QAAI,QAAQ;AACV,YAAM,cAAe,MAAM,SAAS,KAAK;AACzC,UAAI,SAAS,aAAa;AACxB,uBAAuB,YAAY;AAAA,MACrC;AACA,UAAI,UAAU,aAAa;AACzB,oBAAoB,YAAY;AAAA,MAClC;AACA,UAAI,UAAU,aAAa;AACzB,mBAAmC,YAAY;AAAA,MACjD;AAAA,IACF,OAAO;AACL,qBAAe,MAAM,SAAS,KAAK;AAAA,IACrC;AAAA,EACF,SAAS,GAAG;AAEV,YAAQ,MAAM,4DAA4D,CAAC;AAAA,EAC7E;AACA,SAAO,IAAI,WAAW,SAAS,YAAY,cAAc,SAAS,QAAQ,WAAW,QAAQ;AAC/F;","names":[]}
|
package/dist/crypto/digest.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
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
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
var digest_exports = {};
|
|
30
20
|
__export(digest_exports, {
|
|
@@ -33,13 +23,11 @@ __export(digest_exports, {
|
|
|
33
23
|
module.exports = __toCommonJS(digest_exports);
|
|
34
24
|
async function digest(data) {
|
|
35
25
|
var _a;
|
|
36
|
-
if ((_a = globalThis.crypto) == null ? void 0 : _a.subtle) {
|
|
37
|
-
|
|
38
|
-
return crypto.subtle.digest("SHA-256", encoder.encode(data));
|
|
39
|
-
} else {
|
|
40
|
-
const nodeCrypto = await import("node:crypto");
|
|
41
|
-
return nodeCrypto.createHash("sha256").update(data).digest();
|
|
26
|
+
if (!((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) {
|
|
27
|
+
throw new Error("Web Crypto API is required (globalThis.crypto.subtle)");
|
|
42
28
|
}
|
|
29
|
+
const encoder = new TextEncoder();
|
|
30
|
+
return crypto.subtle.digest("SHA-256", encoder.encode(data));
|
|
43
31
|
}
|
|
44
32
|
// Annotate the CommonJS export names for ESM import in node:
|
|
45
33
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/crypto/digest.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n//
|
|
1
|
+
{"version":3,"sources":["../../src/crypto/digest.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n// Web Crypto only — no import('node:crypto') so isolate/edge bundlers can resolve this module\nexport async function digest(data: string): Promise<ArrayBuffer> {\n if (!globalThis.crypto?.subtle) {\n throw new Error('Web Crypto API is required (globalThis.crypto.subtle)');\n }\n const encoder = new TextEncoder();\n return crypto.subtle.digest('SHA-256', encoder.encode(data));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,eAAsB,OAAO,MAAoC;AALjE;AAME,MAAI,GAAC,gBAAW,WAAX,mBAAmB,SAAQ;AAC9B,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AACA,QAAM,UAAU,IAAI,YAAY;AAChC,SAAO,OAAO,OAAO,OAAO,WAAW,QAAQ,OAAO,IAAI,CAAC;AAC7D;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/crypto/digest.ts"],"names":[],"mappings":"AAKA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/crypto/digest.ts"],"names":[],"mappings":"AAKA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAM/D"}
|
package/dist/crypto/digest.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
async function digest(data) {
|
|
2
2
|
var _a;
|
|
3
|
-
if ((_a = globalThis.crypto) == null ? void 0 : _a.subtle) {
|
|
4
|
-
|
|
5
|
-
return crypto.subtle.digest("SHA-256", encoder.encode(data));
|
|
6
|
-
} else {
|
|
7
|
-
const nodeCrypto = await import("node:crypto");
|
|
8
|
-
return nodeCrypto.createHash("sha256").update(data).digest();
|
|
3
|
+
if (!((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) {
|
|
4
|
+
throw new Error("Web Crypto API is required (globalThis.crypto.subtle)");
|
|
9
5
|
}
|
|
6
|
+
const encoder = new TextEncoder();
|
|
7
|
+
return crypto.subtle.digest("SHA-256", encoder.encode(data));
|
|
10
8
|
}
|
|
11
9
|
export {
|
|
12
10
|
digest
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/crypto/digest.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n//
|
|
1
|
+
{"version":3,"sources":["../../src/crypto/digest.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n// Web Crypto only — no import('node:crypto') so isolate/edge bundlers can resolve this module\nexport async function digest(data: string): Promise<ArrayBuffer> {\n if (!globalThis.crypto?.subtle) {\n throw new Error('Web Crypto API is required (globalThis.crypto.subtle)');\n }\n const encoder = new TextEncoder();\n return crypto.subtle.digest('SHA-256', encoder.encode(data));\n}\n"],"mappings":"AAKA,eAAsB,OAAO,MAAoC;AALjE;AAME,MAAI,GAAC,gBAAW,WAAX,mBAAmB,SAAQ;AAC9B,UAAM,IAAI,MAAM,uDAAuD;AAAA,EACzE;AACA,QAAM,UAAU,IAAI,YAAY;AAChC,SAAO,OAAO,OAAO,OAAO,WAAW,QAAQ,OAAO,IAAI,CAAC;AAC7D;","names":[]}
|
package/dist/crypto/uuid.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,30 +15,40 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
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
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
var uuid_exports = {};
|
|
30
20
|
__export(uuid_exports, {
|
|
31
|
-
getRandomBytes: () => getRandomBytes
|
|
21
|
+
getRandomBytes: () => getRandomBytes,
|
|
22
|
+
randomUUID: () => randomUUID
|
|
32
23
|
});
|
|
33
24
|
module.exports = __toCommonJS(uuid_exports);
|
|
34
25
|
async function getRandomBytes(size = 16) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const nodeCrypto = await import("node:crypto");
|
|
39
|
-
return nodeCrypto.getRandomValues(new Uint8Array(size));
|
|
26
|
+
var _a;
|
|
27
|
+
if (!((_a = globalThis.crypto) == null ? void 0 : _a.getRandomValues)) {
|
|
28
|
+
throw new Error("Web Crypto API is required (globalThis.crypto.getRandomValues)");
|
|
40
29
|
}
|
|
30
|
+
return crypto.getRandomValues(new Uint8Array(size));
|
|
31
|
+
}
|
|
32
|
+
async function randomUUID() {
|
|
33
|
+
var _a;
|
|
34
|
+
if (typeof ((_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) === "function") {
|
|
35
|
+
return crypto.randomUUID();
|
|
36
|
+
}
|
|
37
|
+
const bytes = await getRandomBytes(16);
|
|
38
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
39
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
40
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
41
|
+
return [
|
|
42
|
+
hex.slice(0, 8),
|
|
43
|
+
hex.slice(8, 12),
|
|
44
|
+
hex.slice(12, 16),
|
|
45
|
+
hex.slice(16, 20),
|
|
46
|
+
hex.slice(20)
|
|
47
|
+
].join("-");
|
|
41
48
|
}
|
|
42
49
|
// Annotate the CommonJS export names for ESM import in node:
|
|
43
50
|
0 && (module.exports = {
|
|
44
|
-
getRandomBytes
|
|
51
|
+
getRandomBytes,
|
|
52
|
+
randomUUID
|
|
45
53
|
});
|
|
46
54
|
//# sourceMappingURL=uuid.cjs.map
|
package/dist/crypto/uuid.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/crypto/uuid.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n//
|
|
1
|
+
{"version":3,"sources":["../../src/crypto/uuid.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n// Web Crypto only — no import('node:crypto') so isolate/edge bundlers can resolve this module\nexport async function getRandomBytes(size: number = 16): Promise<Uint8Array> {\n if (!globalThis.crypto?.getRandomValues) {\n throw new Error('Web Crypto API is required (globalThis.crypto.getRandomValues)');\n }\n return crypto.getRandomValues(new Uint8Array(size));\n}\n\n// A random RFC 4122 v4 UUID. Prefers the platform's randomUUID (Node 19+, edge\n// runtimes, browsers in a secure context) and otherwise formats random bytes,\n// so it works everywhere getRandomBytes does.\nexport async function randomUUID(): Promise<string> {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n const bytes = await getRandomBytes(16);\n bytes[6] = (bytes[6]! & 0x0f) | 0x40; // version 4\n bytes[8] = (bytes[8]! & 0x3f) | 0x80; // variant 1\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20),\n ].join('-');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,eAAsB,eAAe,OAAe,IAAyB;AAL7E;AAME,MAAI,GAAC,gBAAW,WAAX,mBAAmB,kBAAiB;AACvC,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACA,SAAO,OAAO,gBAAgB,IAAI,WAAW,IAAI,CAAC;AACpD;AAKA,eAAsB,aAA8B;AAfpD;AAgBE,MAAI,SAAO,gBAAW,WAAX,mBAAmB,gBAAe,YAAY;AACvD,WAAO,OAAO,WAAW;AAAA,EAC3B;AACA,QAAM,QAAQ,MAAM,eAAe,EAAE;AACrC,QAAM,CAAC,IAAK,MAAM,CAAC,IAAK,KAAQ;AAChC,QAAM,CAAC,IAAK,MAAM,CAAC,IAAK,KAAQ;AAChC,QAAM,MAAM,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC7E,SAAO;AAAA,IACL,IAAI,MAAM,GAAG,CAAC;AAAA,IACd,IAAI,MAAM,GAAG,EAAE;AAAA,IACf,IAAI,MAAM,IAAI,EAAE;AAAA,IAChB,IAAI,MAAM,IAAI,EAAE;AAAA,IAChB,IAAI,MAAM,EAAE;AAAA,EACd,EAAE,KAAK,GAAG;AACZ;","names":[]}
|
package/dist/crypto/uuid.d.cts
CHANGED
package/dist/crypto/uuid.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../../src/crypto/uuid.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../../src/crypto/uuid.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAK3E;AAKD,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAelD"}
|
package/dist/crypto/uuid.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
async function getRandomBytes(size = 16) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const nodeCrypto = await import("node:crypto");
|
|
6
|
-
return nodeCrypto.getRandomValues(new Uint8Array(size));
|
|
2
|
+
var _a;
|
|
3
|
+
if (!((_a = globalThis.crypto) == null ? void 0 : _a.getRandomValues)) {
|
|
4
|
+
throw new Error("Web Crypto API is required (globalThis.crypto.getRandomValues)");
|
|
7
5
|
}
|
|
6
|
+
return crypto.getRandomValues(new Uint8Array(size));
|
|
7
|
+
}
|
|
8
|
+
async function randomUUID() {
|
|
9
|
+
var _a;
|
|
10
|
+
if (typeof ((_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) === "function") {
|
|
11
|
+
return crypto.randomUUID();
|
|
12
|
+
}
|
|
13
|
+
const bytes = await getRandomBytes(16);
|
|
14
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
15
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
16
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
17
|
+
return [
|
|
18
|
+
hex.slice(0, 8),
|
|
19
|
+
hex.slice(8, 12),
|
|
20
|
+
hex.slice(12, 16),
|
|
21
|
+
hex.slice(16, 20),
|
|
22
|
+
hex.slice(20)
|
|
23
|
+
].join("-");
|
|
8
24
|
}
|
|
9
25
|
export {
|
|
10
|
-
getRandomBytes
|
|
26
|
+
getRandomBytes,
|
|
27
|
+
randomUUID
|
|
11
28
|
};
|
|
12
29
|
//# sourceMappingURL=uuid.js.map
|
package/dist/crypto/uuid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/crypto/uuid.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n//
|
|
1
|
+
{"version":3,"sources":["../../src/crypto/uuid.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n// Web Crypto only — no import('node:crypto') so isolate/edge bundlers can resolve this module\nexport async function getRandomBytes(size: number = 16): Promise<Uint8Array> {\n if (!globalThis.crypto?.getRandomValues) {\n throw new Error('Web Crypto API is required (globalThis.crypto.getRandomValues)');\n }\n return crypto.getRandomValues(new Uint8Array(size));\n}\n\n// A random RFC 4122 v4 UUID. Prefers the platform's randomUUID (Node 19+, edge\n// runtimes, browsers in a secure context) and otherwise formats random bytes,\n// so it works everywhere getRandomBytes does.\nexport async function randomUUID(): Promise<string> {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n const bytes = await getRandomBytes(16);\n bytes[6] = (bytes[6]! & 0x0f) | 0x40; // version 4\n bytes[8] = (bytes[8]! & 0x3f) | 0x80; // variant 1\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20),\n ].join('-');\n}\n"],"mappings":"AAKA,eAAsB,eAAe,OAAe,IAAyB;AAL7E;AAME,MAAI,GAAC,gBAAW,WAAX,mBAAmB,kBAAiB;AACvC,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACA,SAAO,OAAO,gBAAgB,IAAI,WAAW,IAAI,CAAC;AACpD;AAKA,eAAsB,aAA8B;AAfpD;AAgBE,MAAI,SAAO,gBAAW,WAAX,mBAAmB,gBAAe,YAAY;AACvD,WAAO,OAAO,WAAW;AAAA,EAC3B;AACA,QAAM,QAAQ,MAAM,eAAe,EAAE;AACrC,QAAM,CAAC,IAAK,MAAM,CAAC,IAAK,KAAQ;AAChC,QAAM,CAAC,IAAK,MAAM,CAAC,IAAK,KAAQ;AAChC,QAAM,MAAM,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC7E,SAAO;AAAA,IACL,IAAI,MAAM,GAAG,CAAC;AAAA,IACd,IAAI,MAAM,GAAG,EAAE;AAAA,IACf,IAAI,MAAM,IAAI,EAAE;AAAA,IAChB,IAAI,MAAM,IAAI,EAAE;AAAA,IAChB,IAAI,MAAM,EAAE;AAAA,EACd,EAAE,KAAK,GAAG;AACZ;","names":[]}
|
package/dist/version.cjs
CHANGED
|
@@ -21,7 +21,7 @@ __export(version_exports, {
|
|
|
21
21
|
SDK_VERSION: () => SDK_VERSION
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(version_exports);
|
|
24
|
-
const SDK_VERSION = "2.
|
|
24
|
+
const SDK_VERSION = "2.19.0";
|
|
25
25
|
// Annotate the CommonJS export names for ESM import in node:
|
|
26
26
|
0 && (module.exports = {
|
|
27
27
|
SDK_VERSION
|
package/dist/version.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["export const SDK_VERSION = \"2.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["export const SDK_VERSION = \"2.19.0\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,MAAM,cAAc;","names":[]}
|
package/dist/version.d.cts
CHANGED
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["export const SDK_VERSION = \"2.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["export const SDK_VERSION = \"2.19.0\";\n"],"mappings":"AAAO,MAAM,cAAc;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livekit-server-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
4
4
|
"description": "Server-side SDK for LiveKit",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"require": "dist/index.cjs",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@bufbuild/protobuf": "^1.10.1",
|
|
34
|
-
"@livekit/protocol": "1.
|
|
34
|
+
"@livekit/protocol": "1.51.0",
|
|
35
35
|
"jose": "^5.1.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"vitest": "^4.0.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
51
|
+
"node": ">=19"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"prebuild": "node -p \"'export const SDK_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts",
|
package/src/TwirpRPC.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
-
import { ServerError, SipCallError } from './TwirpRPC.js';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { REQUEST_ID_HEADER, ServerError, SipCallError, TwirpRpc } from './TwirpRPC.js';
|
|
6
6
|
|
|
7
7
|
describe('SipCallError', () => {
|
|
8
8
|
it('renders the SIP status, Twirp code, and extra metadata', () => {
|
|
@@ -11,7 +11,7 @@ describe('SipCallError', () => {
|
|
|
11
11
|
'Too Many Requests',
|
|
12
12
|
'twirp error: sip status 486',
|
|
13
13
|
429,
|
|
14
|
-
'
|
|
14
|
+
'failed_precondition',
|
|
15
15
|
{
|
|
16
16
|
sip_status_code: '486',
|
|
17
17
|
sip_status: 'Busy Here',
|
|
@@ -30,7 +30,7 @@ describe('SipCallError', () => {
|
|
|
30
30
|
expect(printed).toContain('SipCallError');
|
|
31
31
|
expect(printed).toContain('486');
|
|
32
32
|
expect(printed).toContain('Busy Here');
|
|
33
|
-
expect(printed).toContain('
|
|
33
|
+
expect(printed).toContain('failed_precondition');
|
|
34
34
|
expect(printed).toContain('region=us-east'); // other metadata is surfaced
|
|
35
35
|
expect(printed).not.toContain('error_details'); // opaque blob is omitted
|
|
36
36
|
});
|
|
@@ -40,3 +40,75 @@ describe('SipCallError', () => {
|
|
|
40
40
|
expect(err.message).toBe('boom');
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
|
+
|
|
44
|
+
describe('request id', () => {
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.restoreAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const okResponse = () =>
|
|
50
|
+
({ ok: true, status: 200, json: async () => ({}) }) as unknown as Response;
|
|
51
|
+
|
|
52
|
+
const errorResponse = (status: number) =>
|
|
53
|
+
({
|
|
54
|
+
ok: false,
|
|
55
|
+
status,
|
|
56
|
+
statusText: 'Service Unavailable',
|
|
57
|
+
headers: { get: () => null },
|
|
58
|
+
text: async () => 'unavailable',
|
|
59
|
+
}) as unknown as Response;
|
|
60
|
+
|
|
61
|
+
// The header lets the server dedup a request that the SDK replayed.
|
|
62
|
+
it('stamps a request id on every call', async () => {
|
|
63
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(okResponse());
|
|
64
|
+
|
|
65
|
+
const rpc = new TwirpRpc('https://test.livekit.cloud', 'livekit', { failover: false });
|
|
66
|
+
await rpc.request('RoomService', 'CreateRoom', {}, {});
|
|
67
|
+
await rpc.request('RoomService', 'CreateRoom', {}, {});
|
|
68
|
+
|
|
69
|
+
const ids = fetchSpy.mock.calls.map(
|
|
70
|
+
([, init]) => (init!.headers as Record<string, string>)[REQUEST_ID_HEADER],
|
|
71
|
+
);
|
|
72
|
+
expect(ids[0]).toBeTruthy();
|
|
73
|
+
expect(ids[1]).toBeTruthy();
|
|
74
|
+
// A new logical call is a new request, so it gets its own id.
|
|
75
|
+
expect(ids[0]).not.toBe(ids[1]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// The id is generated once per logical call, so every failover attempt must
|
|
79
|
+
// carry the same value.
|
|
80
|
+
it('keeps the same request id across failover attempts', async () => {
|
|
81
|
+
let attempt = 0;
|
|
82
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
|
|
83
|
+
// Region discovery, not a replay of the request itself.
|
|
84
|
+
if (`${input}`.endsWith('/settings/regions')) {
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
status: 200,
|
|
88
|
+
headers: { get: () => 'max-age=0' },
|
|
89
|
+
json: async () => ({
|
|
90
|
+
regions: [
|
|
91
|
+
{ url: 'https://r1.retryid.livekit.cloud' },
|
|
92
|
+
{ url: 'https://r2.retryid.livekit.cloud' },
|
|
93
|
+
],
|
|
94
|
+
}),
|
|
95
|
+
} as unknown as Response;
|
|
96
|
+
}
|
|
97
|
+
attempt += 1;
|
|
98
|
+
return attempt < 3 ? errorResponse(503) : okResponse();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const rpc = new TwirpRpc('https://primary.retryid.livekit.cloud', 'livekit', {
|
|
102
|
+
failoverBackoffMs: 0,
|
|
103
|
+
});
|
|
104
|
+
await rpc.request('RoomService', 'CreateRoom', {}, {});
|
|
105
|
+
|
|
106
|
+
const ids = fetchSpy.mock.calls
|
|
107
|
+
.filter(([input]) => !`${input}`.endsWith('/settings/regions'))
|
|
108
|
+
.map(([, init]) => (init!.headers as Record<string, string>)[REQUEST_ID_HEADER]);
|
|
109
|
+
|
|
110
|
+
expect(ids).toHaveLength(3);
|
|
111
|
+
expect(ids[0]).toBeTruthy();
|
|
112
|
+
expect(new Set(ids).size).toBe(1);
|
|
113
|
+
});
|
|
114
|
+
});
|
package/src/TwirpRPC.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import type { JsonValue } from '@bufbuild/protobuf';
|
|
5
|
+
import { randomUUID } from './crypto/uuid.js';
|
|
5
6
|
import {
|
|
6
7
|
FAILOVER_BACKOFF_BASE_MS,
|
|
7
8
|
failoverAttempts,
|
|
@@ -16,6 +17,11 @@ import { SDK_VERSION } from './version.js';
|
|
|
16
17
|
// setting User-Agent via fetch and silently drop it; Node honors it.
|
|
17
18
|
const USER_AGENT = `livekit-server-sdk-node/${SDK_VERSION}`;
|
|
18
19
|
|
|
20
|
+
// Carries a per-request idempotency key. The SDK's auto-retries (see failover)
|
|
21
|
+
// keep the same key across attempts, so the server can identify and deduplicate
|
|
22
|
+
// repeated requests.
|
|
23
|
+
export const REQUEST_ID_HEADER = 'X-Livekit-Request-Id';
|
|
24
|
+
|
|
19
25
|
// twirp RPC adapter for client implementation
|
|
20
26
|
|
|
21
27
|
type Options = {
|
|
@@ -175,11 +181,12 @@ export class TwirpRpc {
|
|
|
175
181
|
): Promise<any> {
|
|
176
182
|
const path = `${this.prefix}/${this.pkg}.${service}/${method}`;
|
|
177
183
|
const body = JSON.stringify(data);
|
|
178
|
-
const requestHeaders = {
|
|
184
|
+
const requestHeaders: Record<string, string> = {
|
|
179
185
|
'Content-Type': 'application/json;charset=UTF-8',
|
|
180
186
|
'User-Agent': USER_AGENT,
|
|
181
187
|
...headers,
|
|
182
188
|
};
|
|
189
|
+
requestHeaders[REQUEST_ID_HEADER] = await randomUUID();
|
|
183
190
|
|
|
184
191
|
const origin = new URL(this.host);
|
|
185
192
|
const maxAttempts = failoverAttempts(
|
package/src/crypto/digest.ts
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Web Crypto only — no import('node:crypto') so isolate/edge bundlers can resolve this module
|
|
6
6
|
export async function digest(data: string): Promise<ArrayBuffer> {
|
|
7
|
-
if (globalThis.crypto?.subtle) {
|
|
8
|
-
|
|
9
|
-
return crypto.subtle.digest('SHA-256', encoder.encode(data));
|
|
10
|
-
} else {
|
|
11
|
-
const nodeCrypto = await import('node:crypto');
|
|
12
|
-
return nodeCrypto.createHash('sha256').update(data).digest();
|
|
7
|
+
if (!globalThis.crypto?.subtle) {
|
|
8
|
+
throw new Error('Web Crypto API is required (globalThis.crypto.subtle)');
|
|
13
9
|
}
|
|
10
|
+
const encoder = new TextEncoder();
|
|
11
|
+
return crypto.subtle.digest('SHA-256', encoder.encode(data));
|
|
14
12
|
}
|
package/src/crypto/uuid.ts
CHANGED
|
@@ -2,12 +2,30 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Web Crypto only — no import('node:crypto') so isolate/edge bundlers can resolve this module
|
|
6
6
|
export async function getRandomBytes(size: number = 16): Promise<Uint8Array> {
|
|
7
|
-
if (globalThis.crypto) {
|
|
8
|
-
|
|
9
|
-
} else {
|
|
10
|
-
const nodeCrypto = await import('node:crypto');
|
|
11
|
-
return nodeCrypto.getRandomValues(new Uint8Array(size));
|
|
7
|
+
if (!globalThis.crypto?.getRandomValues) {
|
|
8
|
+
throw new Error('Web Crypto API is required (globalThis.crypto.getRandomValues)');
|
|
12
9
|
}
|
|
10
|
+
return crypto.getRandomValues(new Uint8Array(size));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// A random RFC 4122 v4 UUID. Prefers the platform's randomUUID (Node 19+, edge
|
|
14
|
+
// runtimes, browsers in a secure context) and otherwise formats random bytes,
|
|
15
|
+
// so it works everywhere getRandomBytes does.
|
|
16
|
+
export async function randomUUID(): Promise<string> {
|
|
17
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
18
|
+
return crypto.randomUUID();
|
|
19
|
+
}
|
|
20
|
+
const bytes = await getRandomBytes(16);
|
|
21
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x40; // version 4
|
|
22
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80; // variant 1
|
|
23
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
24
|
+
return [
|
|
25
|
+
hex.slice(0, 8),
|
|
26
|
+
hex.slice(8, 12),
|
|
27
|
+
hex.slice(12, 16),
|
|
28
|
+
hex.slice(16, 20),
|
|
29
|
+
hex.slice(20),
|
|
30
|
+
].join('-');
|
|
13
31
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "2.
|
|
1
|
+
export const SDK_VERSION = "2.19.0";
|