livekit-server-sdk 2.16.0 → 2.17.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/AgentDispatchClient.cjs +5 -3
- package/dist/AgentDispatchClient.cjs.map +1 -1
- package/dist/AgentDispatchClient.js +5 -3
- package/dist/AgentDispatchClient.js.map +1 -1
- package/dist/ClientOptions.cjs.map +1 -1
- package/dist/ClientOptions.d.cts +12 -0
- package/dist/ClientOptions.d.ts +12 -0
- package/dist/ClientOptions.d.ts.map +1 -1
- package/dist/ConnectorClient.cjs +9 -5
- package/dist/ConnectorClient.cjs.map +1 -1
- package/dist/ConnectorClient.d.cts +6 -3
- package/dist/ConnectorClient.d.ts +6 -3
- package/dist/ConnectorClient.d.ts.map +1 -1
- package/dist/ConnectorClient.js +9 -5
- package/dist/ConnectorClient.js.map +1 -1
- package/dist/EgressClient.cjs +5 -3
- package/dist/EgressClient.cjs.map +1 -1
- package/dist/EgressClient.js +5 -3
- package/dist/EgressClient.js.map +1 -1
- package/dist/IngressClient.cjs +5 -3
- package/dist/IngressClient.cjs.map +1 -1
- package/dist/IngressClient.js +5 -3
- package/dist/IngressClient.js.map +1 -1
- package/dist/LiveKitAPI.cjs +80 -0
- package/dist/LiveKitAPI.cjs.map +1 -0
- package/dist/LiveKitAPI.d.cts +77 -0
- package/dist/LiveKitAPI.d.ts +77 -0
- package/dist/LiveKitAPI.d.ts.map +1 -0
- package/dist/LiveKitAPI.js +56 -0
- package/dist/LiveKitAPI.js.map +1 -0
- package/dist/RoomServiceClient.cjs +5 -3
- package/dist/RoomServiceClient.cjs.map +1 -1
- package/dist/RoomServiceClient.js +5 -3
- package/dist/RoomServiceClient.js.map +1 -1
- package/dist/ServiceBase.cjs +9 -9
- package/dist/ServiceBase.cjs.map +1 -1
- package/dist/ServiceBase.d.cts +20 -1
- package/dist/ServiceBase.d.ts +20 -1
- package/dist/ServiceBase.d.ts.map +1 -1
- package/dist/ServiceBase.js +9 -9
- package/dist/ServiceBase.js.map +1 -1
- package/dist/SipClient.cjs +40 -19
- package/dist/SipClient.cjs.map +1 -1
- package/dist/SipClient.d.cts +4 -2
- package/dist/SipClient.d.ts +4 -2
- package/dist/SipClient.d.ts.map +1 -1
- package/dist/SipClient.js +41 -20
- package/dist/SipClient.js.map +1 -1
- package/dist/TwirpRPC.cjs +135 -48
- package/dist/TwirpRPC.cjs.map +1 -1
- package/dist/TwirpRPC.d.cts +38 -2
- package/dist/TwirpRPC.d.ts +38 -2
- package/dist/TwirpRPC.d.ts.map +1 -1
- package/dist/TwirpRPC.js +140 -38
- package/dist/TwirpRPC.js.map +1 -1
- package/dist/dialTimeout.cjs +39 -0
- package/dist/dialTimeout.cjs.map +1 -0
- package/dist/dialTimeout.d.cts +21 -0
- package/dist/dialTimeout.d.ts +21 -0
- package/dist/dialTimeout.d.ts.map +1 -0
- package/dist/dialTimeout.js +13 -0
- package/dist/dialTimeout.js.map +1 -0
- package/dist/failover.cjs +139 -0
- package/dist/failover.cjs.map +1 -0
- package/dist/failover.d.cts +30 -0
- package/dist/failover.d.ts +30 -0
- package/dist/failover.d.ts.map +1 -0
- package/dist/failover.js +107 -0
- package/dist/failover.js.map +1 -0
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/version.cjs +29 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.d.cts +3 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -2
- package/src/AgentDispatchClient.ts +5 -5
- package/src/ClientOptions.ts +12 -0
- package/src/ConnectorClient.ts +20 -11
- package/src/EgressClient.ts +5 -5
- package/src/IngressClient.ts +5 -5
- package/src/LiveKitAPI.ts +126 -0
- package/src/RoomServiceClient.ts +5 -5
- package/src/ServiceBase.ts +35 -4
- package/src/SipClient.ts +55 -23
- package/src/TwirpRPC.test.ts +42 -0
- package/src/TwirpRPC.ts +187 -39
- package/src/dialTimeout.test.ts +41 -0
- package/src/dialTimeout.ts +37 -0
- package/src/failover.test.ts +188 -0
- package/src/failover.ts +170 -0
- package/src/index.ts +3 -1
- package/src/version.ts +1 -0
package/dist/TwirpRPC.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,26 +15,23 @@ 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 TwirpRPC_exports = {};
|
|
30
20
|
__export(TwirpRPC_exports, {
|
|
21
|
+
ServerError: () => ServerError,
|
|
22
|
+
SipCallError: () => SipCallError,
|
|
31
23
|
TwirpError: () => TwirpError,
|
|
32
24
|
TwirpRpc: () => TwirpRpc,
|
|
33
25
|
livekitPackage: () => livekitPackage
|
|
34
26
|
});
|
|
35
27
|
module.exports = __toCommonJS(TwirpRPC_exports);
|
|
28
|
+
var import_failover = require("./failover.cjs");
|
|
29
|
+
var import_version = require("./version.cjs");
|
|
30
|
+
const USER_AGENT = `livekit-server-sdk-node/${import_version.SDK_VERSION}`;
|
|
36
31
|
const defaultPrefix = "/twirp";
|
|
37
|
-
const defaultTimeoutSeconds =
|
|
32
|
+
const defaultTimeoutSeconds = 10;
|
|
38
33
|
const livekitPackage = "livekit";
|
|
39
|
-
class
|
|
34
|
+
class ServerError extends Error {
|
|
40
35
|
constructor(name, message, status, code, metadata) {
|
|
41
36
|
super(message);
|
|
42
37
|
this.name = name;
|
|
@@ -45,6 +40,47 @@ class TwirpError extends Error {
|
|
|
45
40
|
this.metadata = metadata;
|
|
46
41
|
}
|
|
47
42
|
}
|
|
43
|
+
const TwirpError = ServerError;
|
|
44
|
+
class SipCallError extends ServerError {
|
|
45
|
+
constructor(name, message, status, code, metadata) {
|
|
46
|
+
super(name, SipCallError.describe(message, code, metadata), status, code, metadata);
|
|
47
|
+
this.name = "SipCallError";
|
|
48
|
+
}
|
|
49
|
+
/** The SIP response code of the failed call, e.g. 486 (Busy Here). */
|
|
50
|
+
get sipStatusCode() {
|
|
51
|
+
var _a;
|
|
52
|
+
const raw = (_a = this.metadata) == null ? void 0 : _a.sip_status_code;
|
|
53
|
+
return raw !== void 0 ? Number(raw) : void 0;
|
|
54
|
+
}
|
|
55
|
+
/** The SIP reason phrase of the failed call, e.g. "Busy Here". */
|
|
56
|
+
get sipStatus() {
|
|
57
|
+
var _a;
|
|
58
|
+
return (_a = this.metadata) == null ? void 0 : _a.sip_status;
|
|
59
|
+
}
|
|
60
|
+
/** Builds a SipCallError from a ServerError, preserving its code and metadata. */
|
|
61
|
+
static fromServerError(err) {
|
|
62
|
+
return new SipCallError(err.name, err.message, err.status, err.code, err.metadata);
|
|
63
|
+
}
|
|
64
|
+
// describe renders a clear message: the SIP status, the error code, and any
|
|
65
|
+
// other metadata the server attached. Falls back to the raw message when the
|
|
66
|
+
// error carries no SIP status.
|
|
67
|
+
static describe(fallback, code, metadata) {
|
|
68
|
+
const sipCode = metadata == null ? void 0 : metadata.sip_status_code;
|
|
69
|
+
if (!sipCode) {
|
|
70
|
+
return fallback;
|
|
71
|
+
}
|
|
72
|
+
const reason = metadata == null ? void 0 : metadata.sip_status;
|
|
73
|
+
let msg = `SIP call failed: ${sipCode}${reason ? ` ${reason}` : ""}`;
|
|
74
|
+
if (code) {
|
|
75
|
+
msg += ` (${code})`;
|
|
76
|
+
}
|
|
77
|
+
const extra = Object.entries(metadata ?? {}).filter(([k]) => k !== "sip_status_code" && k !== "sip_status" && k !== "error_details").map(([k, v]) => `${k}=${v}`);
|
|
78
|
+
if (extra.length) {
|
|
79
|
+
msg += ` [${extra.join(", ")}]`;
|
|
80
|
+
}
|
|
81
|
+
return msg;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
48
84
|
class TwirpRpc {
|
|
49
85
|
constructor(host, pkg, options) {
|
|
50
86
|
if (host.startsWith("ws")) {
|
|
@@ -54,54 +90,105 @@ class TwirpRpc {
|
|
|
54
90
|
this.pkg = pkg;
|
|
55
91
|
this.requestTimeout = (options == null ? void 0 : options.requestTimeout) ?? defaultTimeoutSeconds;
|
|
56
92
|
this.prefix = (options == null ? void 0 : options.prefix) || defaultPrefix;
|
|
93
|
+
this.failover = (options == null ? void 0 : options.failover) ?? true;
|
|
94
|
+
this.failoverForce = (options == null ? void 0 : options.failoverForce) ?? false;
|
|
95
|
+
this.failoverBackoffMs = (options == null ? void 0 : options.failoverBackoffMs) ?? import_failover.FAILOVER_BACKOFF_BASE_MS;
|
|
57
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Issues a Twirp request, failing over to alternative regions on retryable
|
|
99
|
+
* errors. On any transport error or HTTP 5xx it discovers regions via
|
|
100
|
+
* /settings/regions and replays the request — body and headers intact —
|
|
101
|
+
* against the next untried region, with exponential backoff. A 4xx is
|
|
102
|
+
* returned immediately.
|
|
103
|
+
*/
|
|
58
104
|
async request(service, method, data, headers, timeout = this.requestTimeout) {
|
|
59
105
|
const path = `${this.prefix}/${this.pkg}.${service}/${method}`;
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
...headers
|
|
66
|
-
},
|
|
67
|
-
body: JSON.stringify(data)
|
|
106
|
+
const body = JSON.stringify(data);
|
|
107
|
+
const requestHeaders = {
|
|
108
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
109
|
+
"User-Agent": USER_AGENT,
|
|
110
|
+
...headers
|
|
68
111
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
const origin = new URL(this.host);
|
|
113
|
+
const maxAttempts = (0, import_failover.failoverAttempts)(
|
|
114
|
+
this.failover,
|
|
115
|
+
origin.hostname,
|
|
116
|
+
this.failoverForce,
|
|
117
|
+
timeout
|
|
118
|
+
);
|
|
119
|
+
const attempted = /* @__PURE__ */ new Set([(0, import_failover.hostKey)(origin)]);
|
|
120
|
+
let regions;
|
|
121
|
+
let current = this.host;
|
|
122
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
123
|
+
const isLast = attempt + 1 >= maxAttempts;
|
|
124
|
+
const init = { method: "POST", headers: requestHeaders, body };
|
|
125
|
+
if (timeout) {
|
|
126
|
+
init.signal = AbortSignal.timeout(timeout * 1e3);
|
|
127
|
+
}
|
|
128
|
+
let response;
|
|
129
|
+
let transportError;
|
|
78
130
|
try {
|
|
79
|
-
|
|
80
|
-
const parsedError = await response.json();
|
|
81
|
-
if ("msg" in parsedError) {
|
|
82
|
-
errorMessage = parsedError.msg;
|
|
83
|
-
}
|
|
84
|
-
if ("code" in parsedError) {
|
|
85
|
-
errorCode = parsedError.code;
|
|
86
|
-
}
|
|
87
|
-
if ("meta" in parsedError) {
|
|
88
|
-
metadata = parsedError.meta;
|
|
89
|
-
}
|
|
90
|
-
} else {
|
|
91
|
-
errorMessage = await response.text();
|
|
92
|
-
}
|
|
131
|
+
response = await fetch(new URL(path, current), init);
|
|
93
132
|
} catch (e) {
|
|
94
|
-
|
|
133
|
+
transportError = e;
|
|
134
|
+
}
|
|
135
|
+
if (response == null ? void 0 : response.ok) {
|
|
136
|
+
return await response.json();
|
|
137
|
+
}
|
|
138
|
+
const retryable = transportError !== void 0 || !!response && response.status >= 500;
|
|
139
|
+
let next;
|
|
140
|
+
if (retryable && !isLast) {
|
|
141
|
+
if (!regions) {
|
|
142
|
+
regions = await (0, import_failover.regionOrigins)(origin, headers);
|
|
143
|
+
}
|
|
144
|
+
next = (0, import_failover.pickNext)(regions, attempted);
|
|
145
|
+
}
|
|
146
|
+
if (!retryable || next === void 0) {
|
|
147
|
+
if (response) {
|
|
148
|
+
throw await toTwirpError(response);
|
|
149
|
+
}
|
|
150
|
+
throw transportError;
|
|
151
|
+
}
|
|
152
|
+
const reason = response ? `status ${response.status}` : transportError;
|
|
153
|
+
console.warn(
|
|
154
|
+
`livekit API request to ${new URL(current).host} failed (${reason}), retrying with fallback url ${next}`
|
|
155
|
+
);
|
|
156
|
+
await (0, import_failover.sleep)(this.failoverBackoffMs * 2 ** attempt);
|
|
157
|
+
attempted.add((0, import_failover.hostKey)(new URL(next)));
|
|
158
|
+
current = next;
|
|
159
|
+
}
|
|
160
|
+
throw new Error("failover loop exited without returning");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function toTwirpError(response) {
|
|
164
|
+
const isJson = response.headers.get("content-type") === "application/json";
|
|
165
|
+
let errorMessage = "Unknown internal error";
|
|
166
|
+
let errorCode = void 0;
|
|
167
|
+
let metadata = void 0;
|
|
168
|
+
try {
|
|
169
|
+
if (isJson) {
|
|
170
|
+
const parsedError = await response.json();
|
|
171
|
+
if ("msg" in parsedError) {
|
|
172
|
+
errorMessage = parsedError.msg;
|
|
173
|
+
}
|
|
174
|
+
if ("code" in parsedError) {
|
|
175
|
+
errorCode = parsedError.code;
|
|
176
|
+
}
|
|
177
|
+
if ("meta" in parsedError) {
|
|
178
|
+
metadata = parsedError.meta;
|
|
95
179
|
}
|
|
96
|
-
|
|
180
|
+
} else {
|
|
181
|
+
errorMessage = await response.text();
|
|
97
182
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return camelcaseKeys(parsedResp, { deep: true });
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.debug(`Error when trying to parse error message, using defaults`, e);
|
|
101
185
|
}
|
|
186
|
+
return new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);
|
|
102
187
|
}
|
|
103
188
|
// Annotate the CommonJS export names for ESM import in node:
|
|
104
189
|
0 && (module.exports = {
|
|
190
|
+
ServerError,
|
|
191
|
+
SipCallError,
|
|
105
192
|
TwirpError,
|
|
106
193
|
TwirpRpc,
|
|
107
194
|
livekitPackage
|
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';\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};\n\nconst defaultPrefix = '/twirp';\nconst defaultTimeoutSeconds = 60;\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 TwirpError 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/**\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 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 }\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 url = new URL(path, this.host);\n const init: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json;charset=UTF-8',\n ...headers,\n },\n body: JSON.stringify(data),\n };\n\n if (timeout) {\n init.signal = AbortSignal.timeout(timeout * 1000);\n }\n\n const response = await fetch(url, init);\n\n if (!response.ok) {\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\n throw new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);\n }\n const parsedResp = (await response.json()) as Record<string, unknown>;\n\n const camelcaseKeys = await import('camelcase-keys').then((mod) => mod.default);\n return camelcaseKeys(parsedResp, { deep: true });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,MAAM,gBAAgB;AACtB,MAAM,wBAAwB;AAEvB,MAAM,iBAAiB;AAWvB,MAAM,mBAAmB,MAAM;AAAA,EAKpC,YACE,MACA,SACA,QACA,MACA,UACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAKO,MAAM,SAAS;AAAA,EASpB,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;AAAA,EACnC;AAAA,EAEA,MAAM,QACJ,SACA,QACA,MACA,SACA,UAAU,KAAK,gBAED;AACd,UAAM,OAAO,GAAG,KAAK,MAAM,IAAI,KAAK,GAAG,IAAI,OAAO,IAAI,MAAM;AAC5D,UAAM,MAAM,IAAI,IAAI,MAAM,KAAK,IAAI;AACnC,UAAM,OAAoB;AAAA,MACxB,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAG;AAAA,MACL;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B;AAEA,QAAI,SAAS;AACX,WAAK,SAAS,YAAY,QAAQ,UAAU,GAAI;AAAA,IAClD;AAEA,UAAM,WAAW,MAAM,MAAM,KAAK,IAAI;AAEtC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,SAAS,QAAQ,IAAI,cAAc,MAAM;AACxD,UAAI,eAAe;AACnB,UAAI,YAAgC;AACpC,UAAI,WAA+C;AACnD,UAAI;AACF,YAAI,QAAQ;AACV,gBAAM,cAAe,MAAM,SAAS,KAAK;AACzC,cAAI,SAAS,aAAa;AACxB,2BAAuB,YAAY;AAAA,UACrC;AACA,cAAI,UAAU,aAAa;AACzB,wBAAoB,YAAY;AAAA,UAClC;AACA,cAAI,UAAU,aAAa;AACzB,uBAAmC,YAAY;AAAA,UACjD;AAAA,QACF,OAAO;AACL,yBAAe,MAAM,SAAS,KAAK;AAAA,QACrC;AAAA,MACF,SAAS,GAAG;AAEV,gBAAQ,MAAM,4DAA4D,CAAC;AAAA,MAC7E;AAEA,YAAM,IAAI,WAAW,SAAS,YAAY,cAAc,SAAS,QAAQ,WAAW,QAAQ;AAAA,IAC9F;AACA,UAAM,aAAc,MAAM,SAAS,KAAK;AAExC,UAAM,gBAAgB,MAAM,OAAO,gBAAgB,EAAE,KAAK,CAAC,QAAQ,IAAI,OAAO;AAC9E,WAAO,cAAc,YAAY,EAAE,MAAM,KAAK,CAAC;AAAA,EACjD;AACF;","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 {\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":[]}
|
package/dist/TwirpRPC.d.cts
CHANGED
|
@@ -5,18 +5,44 @@ type Options = {
|
|
|
5
5
|
prefix?: string;
|
|
6
6
|
/** Timeout for fetch requests, in seconds. Must be within the valid range for abort signal timeouts. */
|
|
7
7
|
requestTimeout?: number;
|
|
8
|
+
/** Whether region failover is enabled (LiveKit Cloud hosts only). Defaults to true. */
|
|
9
|
+
failover?: boolean;
|
|
10
|
+
/** @internal test-only: force failover regardless of host. */
|
|
11
|
+
failoverForce?: boolean;
|
|
12
|
+
/** @internal test-only: base retry backoff in ms. */
|
|
13
|
+
failoverBackoffMs?: number;
|
|
8
14
|
};
|
|
9
15
|
declare const livekitPackage = "livekit";
|
|
10
16
|
interface Rpc {
|
|
11
17
|
request(service: string, method: string, data: JsonValue, headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
12
18
|
timeout?: number): Promise<string>;
|
|
13
19
|
}
|
|
14
|
-
declare class
|
|
20
|
+
declare class ServerError extends Error {
|
|
15
21
|
status: number;
|
|
16
22
|
code?: string;
|
|
17
23
|
metadata?: Record<string, string>;
|
|
18
24
|
constructor(name: string, message: string, status: number, code?: string, metadata?: Record<string, string>);
|
|
19
25
|
}
|
|
26
|
+
/** @deprecated use {@link ServerError} */
|
|
27
|
+
declare const TwirpError: typeof ServerError;
|
|
28
|
+
/** @deprecated use {@link ServerError} */
|
|
29
|
+
type TwirpError = ServerError;
|
|
30
|
+
/**
|
|
31
|
+
* A {@link ServerError} from a SIP dialing call (`createSipParticipant` /
|
|
32
|
+
* `transferSipParticipant`) that failed with a SIP response status. The SIP code
|
|
33
|
+
* and reason are exposed as getters; any other error metadata remains available
|
|
34
|
+
* via {@link ServerError.metadata}.
|
|
35
|
+
*/
|
|
36
|
+
declare class SipCallError extends ServerError {
|
|
37
|
+
constructor(name: string, message: string, status: number, code?: string, metadata?: Record<string, string>);
|
|
38
|
+
/** The SIP response code of the failed call, e.g. 486 (Busy Here). */
|
|
39
|
+
get sipStatusCode(): number | undefined;
|
|
40
|
+
/** The SIP reason phrase of the failed call, e.g. "Busy Here". */
|
|
41
|
+
get sipStatus(): string | undefined;
|
|
42
|
+
/** Builds a SipCallError from a ServerError, preserving its code and metadata. */
|
|
43
|
+
static fromServerError(err: ServerError): SipCallError;
|
|
44
|
+
private static describe;
|
|
45
|
+
}
|
|
20
46
|
/**
|
|
21
47
|
* JSON based Twirp V7 RPC
|
|
22
48
|
*/
|
|
@@ -25,10 +51,20 @@ declare class TwirpRpc {
|
|
|
25
51
|
pkg: string;
|
|
26
52
|
prefix: string;
|
|
27
53
|
requestTimeout: number;
|
|
54
|
+
failover: boolean;
|
|
55
|
+
private failoverForce;
|
|
56
|
+
private failoverBackoffMs;
|
|
28
57
|
constructor(host: string, pkg: string, options?: Options);
|
|
58
|
+
/**
|
|
59
|
+
* Issues a Twirp request, failing over to alternative regions on retryable
|
|
60
|
+
* errors. On any transport error or HTTP 5xx it discovers regions via
|
|
61
|
+
* /settings/regions and replays the request — body and headers intact —
|
|
62
|
+
* against the next untried region, with exponential backoff. A 4xx is
|
|
63
|
+
* returned immediately.
|
|
64
|
+
*/
|
|
29
65
|
request(service: string, method: string, data: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
30
66
|
headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
31
67
|
timeout?: number): Promise<any>;
|
|
32
68
|
}
|
|
33
69
|
|
|
34
|
-
export { type Rpc, TwirpError, TwirpRpc, livekitPackage };
|
|
70
|
+
export { type Rpc, ServerError, SipCallError, TwirpError, TwirpRpc, livekitPackage };
|
package/dist/TwirpRPC.d.ts
CHANGED
|
@@ -5,18 +5,44 @@ type Options = {
|
|
|
5
5
|
prefix?: string;
|
|
6
6
|
/** Timeout for fetch requests, in seconds. Must be within the valid range for abort signal timeouts. */
|
|
7
7
|
requestTimeout?: number;
|
|
8
|
+
/** Whether region failover is enabled (LiveKit Cloud hosts only). Defaults to true. */
|
|
9
|
+
failover?: boolean;
|
|
10
|
+
/** @internal test-only: force failover regardless of host. */
|
|
11
|
+
failoverForce?: boolean;
|
|
12
|
+
/** @internal test-only: base retry backoff in ms. */
|
|
13
|
+
failoverBackoffMs?: number;
|
|
8
14
|
};
|
|
9
15
|
declare const livekitPackage = "livekit";
|
|
10
16
|
interface Rpc {
|
|
11
17
|
request(service: string, method: string, data: JsonValue, headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
12
18
|
timeout?: number): Promise<string>;
|
|
13
19
|
}
|
|
14
|
-
declare class
|
|
20
|
+
declare class ServerError extends Error {
|
|
15
21
|
status: number;
|
|
16
22
|
code?: string;
|
|
17
23
|
metadata?: Record<string, string>;
|
|
18
24
|
constructor(name: string, message: string, status: number, code?: string, metadata?: Record<string, string>);
|
|
19
25
|
}
|
|
26
|
+
/** @deprecated use {@link ServerError} */
|
|
27
|
+
declare const TwirpError: typeof ServerError;
|
|
28
|
+
/** @deprecated use {@link ServerError} */
|
|
29
|
+
type TwirpError = ServerError;
|
|
30
|
+
/**
|
|
31
|
+
* A {@link ServerError} from a SIP dialing call (`createSipParticipant` /
|
|
32
|
+
* `transferSipParticipant`) that failed with a SIP response status. The SIP code
|
|
33
|
+
* and reason are exposed as getters; any other error metadata remains available
|
|
34
|
+
* via {@link ServerError.metadata}.
|
|
35
|
+
*/
|
|
36
|
+
declare class SipCallError extends ServerError {
|
|
37
|
+
constructor(name: string, message: string, status: number, code?: string, metadata?: Record<string, string>);
|
|
38
|
+
/** The SIP response code of the failed call, e.g. 486 (Busy Here). */
|
|
39
|
+
get sipStatusCode(): number | undefined;
|
|
40
|
+
/** The SIP reason phrase of the failed call, e.g. "Busy Here". */
|
|
41
|
+
get sipStatus(): string | undefined;
|
|
42
|
+
/** Builds a SipCallError from a ServerError, preserving its code and metadata. */
|
|
43
|
+
static fromServerError(err: ServerError): SipCallError;
|
|
44
|
+
private static describe;
|
|
45
|
+
}
|
|
20
46
|
/**
|
|
21
47
|
* JSON based Twirp V7 RPC
|
|
22
48
|
*/
|
|
@@ -25,10 +51,20 @@ declare class TwirpRpc {
|
|
|
25
51
|
pkg: string;
|
|
26
52
|
prefix: string;
|
|
27
53
|
requestTimeout: number;
|
|
54
|
+
failover: boolean;
|
|
55
|
+
private failoverForce;
|
|
56
|
+
private failoverBackoffMs;
|
|
28
57
|
constructor(host: string, pkg: string, options?: Options);
|
|
58
|
+
/**
|
|
59
|
+
* Issues a Twirp request, failing over to alternative regions on retryable
|
|
60
|
+
* errors. On any transport error or HTTP 5xx it discovers regions via
|
|
61
|
+
* /settings/regions and replays the request — body and headers intact —
|
|
62
|
+
* against the next untried region, with exponential backoff. A 4xx is
|
|
63
|
+
* returned immediately.
|
|
64
|
+
*/
|
|
29
65
|
request(service: string, method: string, data: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
30
66
|
headers: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
31
67
|
timeout?: number): Promise<any>;
|
|
32
68
|
}
|
|
33
69
|
|
|
34
|
-
export { type Rpc, TwirpError, TwirpRpc, livekitPackage };
|
|
70
|
+
export { 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;AAiBpD,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;CAyEhB"}
|
package/dist/TwirpRPC.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FAILOVER_BACKOFF_BASE_MS,
|
|
3
|
+
failoverAttempts,
|
|
4
|
+
hostKey,
|
|
5
|
+
pickNext,
|
|
6
|
+
regionOrigins,
|
|
7
|
+
sleep
|
|
8
|
+
} from "./failover.js";
|
|
9
|
+
import { SDK_VERSION } from "./version.js";
|
|
10
|
+
const USER_AGENT = `livekit-server-sdk-node/${SDK_VERSION}`;
|
|
1
11
|
const defaultPrefix = "/twirp";
|
|
2
|
-
const defaultTimeoutSeconds =
|
|
12
|
+
const defaultTimeoutSeconds = 10;
|
|
3
13
|
const livekitPackage = "livekit";
|
|
4
|
-
class
|
|
14
|
+
class ServerError extends Error {
|
|
5
15
|
constructor(name, message, status, code, metadata) {
|
|
6
16
|
super(message);
|
|
7
17
|
this.name = name;
|
|
@@ -10,6 +20,47 @@ class TwirpError extends Error {
|
|
|
10
20
|
this.metadata = metadata;
|
|
11
21
|
}
|
|
12
22
|
}
|
|
23
|
+
const TwirpError = ServerError;
|
|
24
|
+
class SipCallError extends ServerError {
|
|
25
|
+
constructor(name, message, status, code, metadata) {
|
|
26
|
+
super(name, SipCallError.describe(message, code, metadata), status, code, metadata);
|
|
27
|
+
this.name = "SipCallError";
|
|
28
|
+
}
|
|
29
|
+
/** The SIP response code of the failed call, e.g. 486 (Busy Here). */
|
|
30
|
+
get sipStatusCode() {
|
|
31
|
+
var _a;
|
|
32
|
+
const raw = (_a = this.metadata) == null ? void 0 : _a.sip_status_code;
|
|
33
|
+
return raw !== void 0 ? Number(raw) : void 0;
|
|
34
|
+
}
|
|
35
|
+
/** The SIP reason phrase of the failed call, e.g. "Busy Here". */
|
|
36
|
+
get sipStatus() {
|
|
37
|
+
var _a;
|
|
38
|
+
return (_a = this.metadata) == null ? void 0 : _a.sip_status;
|
|
39
|
+
}
|
|
40
|
+
/** Builds a SipCallError from a ServerError, preserving its code and metadata. */
|
|
41
|
+
static fromServerError(err) {
|
|
42
|
+
return new SipCallError(err.name, err.message, err.status, err.code, err.metadata);
|
|
43
|
+
}
|
|
44
|
+
// describe renders a clear message: the SIP status, the error code, and any
|
|
45
|
+
// other metadata the server attached. Falls back to the raw message when the
|
|
46
|
+
// error carries no SIP status.
|
|
47
|
+
static describe(fallback, code, metadata) {
|
|
48
|
+
const sipCode = metadata == null ? void 0 : metadata.sip_status_code;
|
|
49
|
+
if (!sipCode) {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
const reason = metadata == null ? void 0 : metadata.sip_status;
|
|
53
|
+
let msg = `SIP call failed: ${sipCode}${reason ? ` ${reason}` : ""}`;
|
|
54
|
+
if (code) {
|
|
55
|
+
msg += ` (${code})`;
|
|
56
|
+
}
|
|
57
|
+
const extra = Object.entries(metadata ?? {}).filter(([k]) => k !== "sip_status_code" && k !== "sip_status" && k !== "error_details").map(([k, v]) => `${k}=${v}`);
|
|
58
|
+
if (extra.length) {
|
|
59
|
+
msg += ` [${extra.join(", ")}]`;
|
|
60
|
+
}
|
|
61
|
+
return msg;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
13
64
|
class TwirpRpc {
|
|
14
65
|
constructor(host, pkg, options) {
|
|
15
66
|
if (host.startsWith("ws")) {
|
|
@@ -19,53 +70,104 @@ class TwirpRpc {
|
|
|
19
70
|
this.pkg = pkg;
|
|
20
71
|
this.requestTimeout = (options == null ? void 0 : options.requestTimeout) ?? defaultTimeoutSeconds;
|
|
21
72
|
this.prefix = (options == null ? void 0 : options.prefix) || defaultPrefix;
|
|
73
|
+
this.failover = (options == null ? void 0 : options.failover) ?? true;
|
|
74
|
+
this.failoverForce = (options == null ? void 0 : options.failoverForce) ?? false;
|
|
75
|
+
this.failoverBackoffMs = (options == null ? void 0 : options.failoverBackoffMs) ?? FAILOVER_BACKOFF_BASE_MS;
|
|
22
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Issues a Twirp request, failing over to alternative regions on retryable
|
|
79
|
+
* errors. On any transport error or HTTP 5xx it discovers regions via
|
|
80
|
+
* /settings/regions and replays the request — body and headers intact —
|
|
81
|
+
* against the next untried region, with exponential backoff. A 4xx is
|
|
82
|
+
* returned immediately.
|
|
83
|
+
*/
|
|
23
84
|
async request(service, method, data, headers, timeout = this.requestTimeout) {
|
|
24
85
|
const path = `${this.prefix}/${this.pkg}.${service}/${method}`;
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
...headers
|
|
31
|
-
},
|
|
32
|
-
body: JSON.stringify(data)
|
|
86
|
+
const body = JSON.stringify(data);
|
|
87
|
+
const requestHeaders = {
|
|
88
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
89
|
+
"User-Agent": USER_AGENT,
|
|
90
|
+
...headers
|
|
33
91
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
92
|
+
const origin = new URL(this.host);
|
|
93
|
+
const maxAttempts = failoverAttempts(
|
|
94
|
+
this.failover,
|
|
95
|
+
origin.hostname,
|
|
96
|
+
this.failoverForce,
|
|
97
|
+
timeout
|
|
98
|
+
);
|
|
99
|
+
const attempted = /* @__PURE__ */ new Set([hostKey(origin)]);
|
|
100
|
+
let regions;
|
|
101
|
+
let current = this.host;
|
|
102
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
103
|
+
const isLast = attempt + 1 >= maxAttempts;
|
|
104
|
+
const init = { method: "POST", headers: requestHeaders, body };
|
|
105
|
+
if (timeout) {
|
|
106
|
+
init.signal = AbortSignal.timeout(timeout * 1e3);
|
|
107
|
+
}
|
|
108
|
+
let response;
|
|
109
|
+
let transportError;
|
|
43
110
|
try {
|
|
44
|
-
|
|
45
|
-
const parsedError = await response.json();
|
|
46
|
-
if ("msg" in parsedError) {
|
|
47
|
-
errorMessage = parsedError.msg;
|
|
48
|
-
}
|
|
49
|
-
if ("code" in parsedError) {
|
|
50
|
-
errorCode = parsedError.code;
|
|
51
|
-
}
|
|
52
|
-
if ("meta" in parsedError) {
|
|
53
|
-
metadata = parsedError.meta;
|
|
54
|
-
}
|
|
55
|
-
} else {
|
|
56
|
-
errorMessage = await response.text();
|
|
57
|
-
}
|
|
111
|
+
response = await fetch(new URL(path, current), init);
|
|
58
112
|
} catch (e) {
|
|
59
|
-
|
|
113
|
+
transportError = e;
|
|
114
|
+
}
|
|
115
|
+
if (response == null ? void 0 : response.ok) {
|
|
116
|
+
return await response.json();
|
|
117
|
+
}
|
|
118
|
+
const retryable = transportError !== void 0 || !!response && response.status >= 500;
|
|
119
|
+
let next;
|
|
120
|
+
if (retryable && !isLast) {
|
|
121
|
+
if (!regions) {
|
|
122
|
+
regions = await regionOrigins(origin, headers);
|
|
123
|
+
}
|
|
124
|
+
next = pickNext(regions, attempted);
|
|
125
|
+
}
|
|
126
|
+
if (!retryable || next === void 0) {
|
|
127
|
+
if (response) {
|
|
128
|
+
throw await toTwirpError(response);
|
|
129
|
+
}
|
|
130
|
+
throw transportError;
|
|
131
|
+
}
|
|
132
|
+
const reason = response ? `status ${response.status}` : transportError;
|
|
133
|
+
console.warn(
|
|
134
|
+
`livekit API request to ${new URL(current).host} failed (${reason}), retrying with fallback url ${next}`
|
|
135
|
+
);
|
|
136
|
+
await sleep(this.failoverBackoffMs * 2 ** attempt);
|
|
137
|
+
attempted.add(hostKey(new URL(next)));
|
|
138
|
+
current = next;
|
|
139
|
+
}
|
|
140
|
+
throw new Error("failover loop exited without returning");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function toTwirpError(response) {
|
|
144
|
+
const isJson = response.headers.get("content-type") === "application/json";
|
|
145
|
+
let errorMessage = "Unknown internal error";
|
|
146
|
+
let errorCode = void 0;
|
|
147
|
+
let metadata = void 0;
|
|
148
|
+
try {
|
|
149
|
+
if (isJson) {
|
|
150
|
+
const parsedError = await response.json();
|
|
151
|
+
if ("msg" in parsedError) {
|
|
152
|
+
errorMessage = parsedError.msg;
|
|
153
|
+
}
|
|
154
|
+
if ("code" in parsedError) {
|
|
155
|
+
errorCode = parsedError.code;
|
|
156
|
+
}
|
|
157
|
+
if ("meta" in parsedError) {
|
|
158
|
+
metadata = parsedError.meta;
|
|
60
159
|
}
|
|
61
|
-
|
|
160
|
+
} else {
|
|
161
|
+
errorMessage = await response.text();
|
|
62
162
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return camelcaseKeys(parsedResp, { deep: true });
|
|
163
|
+
} catch (e) {
|
|
164
|
+
console.debug(`Error when trying to parse error message, using defaults`, e);
|
|
66
165
|
}
|
|
166
|
+
return new TwirpError(response.statusText, errorMessage, response.status, errorCode, metadata);
|
|
67
167
|
}
|
|
68
168
|
export {
|
|
169
|
+
ServerError,
|
|
170
|
+
SipCallError,
|
|
69
171
|
TwirpError,
|
|
70
172
|
TwirpRpc,
|
|
71
173
|
livekitPackage
|