livekit-server-sdk 2.16.0 → 2.18.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.
Files changed (105) hide show
  1. package/README.md +82 -15
  2. package/dist/AgentDispatchClient.cjs +5 -3
  3. package/dist/AgentDispatchClient.cjs.map +1 -1
  4. package/dist/AgentDispatchClient.js +5 -3
  5. package/dist/AgentDispatchClient.js.map +1 -1
  6. package/dist/ClientOptions.cjs.map +1 -1
  7. package/dist/ClientOptions.d.cts +12 -0
  8. package/dist/ClientOptions.d.ts +12 -0
  9. package/dist/ClientOptions.d.ts.map +1 -1
  10. package/dist/ConnectorClient.cjs +9 -5
  11. package/dist/ConnectorClient.cjs.map +1 -1
  12. package/dist/ConnectorClient.d.cts +6 -3
  13. package/dist/ConnectorClient.d.ts +6 -3
  14. package/dist/ConnectorClient.d.ts.map +1 -1
  15. package/dist/ConnectorClient.js +9 -5
  16. package/dist/ConnectorClient.js.map +1 -1
  17. package/dist/EgressClient.cjs +19 -3
  18. package/dist/EgressClient.cjs.map +1 -1
  19. package/dist/EgressClient.d.cts +7 -1
  20. package/dist/EgressClient.d.ts +7 -1
  21. package/dist/EgressClient.d.ts.map +1 -1
  22. package/dist/EgressClient.js +20 -3
  23. package/dist/EgressClient.js.map +1 -1
  24. package/dist/IngressClient.cjs +5 -3
  25. package/dist/IngressClient.cjs.map +1 -1
  26. package/dist/IngressClient.js +5 -3
  27. package/dist/IngressClient.js.map +1 -1
  28. package/dist/LiveKitAPI.cjs +80 -0
  29. package/dist/LiveKitAPI.cjs.map +1 -0
  30. package/dist/LiveKitAPI.d.cts +77 -0
  31. package/dist/LiveKitAPI.d.ts +77 -0
  32. package/dist/LiveKitAPI.d.ts.map +1 -0
  33. package/dist/LiveKitAPI.js +56 -0
  34. package/dist/LiveKitAPI.js.map +1 -0
  35. package/dist/RoomServiceClient.cjs +5 -3
  36. package/dist/RoomServiceClient.cjs.map +1 -1
  37. package/dist/RoomServiceClient.js +5 -3
  38. package/dist/RoomServiceClient.js.map +1 -1
  39. package/dist/ServiceBase.cjs +9 -9
  40. package/dist/ServiceBase.cjs.map +1 -1
  41. package/dist/ServiceBase.d.cts +20 -1
  42. package/dist/ServiceBase.d.ts +20 -1
  43. package/dist/ServiceBase.d.ts.map +1 -1
  44. package/dist/ServiceBase.js +9 -9
  45. package/dist/ServiceBase.js.map +1 -1
  46. package/dist/SipClient.cjs +40 -19
  47. package/dist/SipClient.cjs.map +1 -1
  48. package/dist/SipClient.d.cts +4 -2
  49. package/dist/SipClient.d.ts +4 -2
  50. package/dist/SipClient.d.ts.map +1 -1
  51. package/dist/SipClient.js +41 -20
  52. package/dist/SipClient.js.map +1 -1
  53. package/dist/TwirpRPC.cjs +135 -48
  54. package/dist/TwirpRPC.cjs.map +1 -1
  55. package/dist/TwirpRPC.d.cts +38 -2
  56. package/dist/TwirpRPC.d.ts +38 -2
  57. package/dist/TwirpRPC.d.ts.map +1 -1
  58. package/dist/TwirpRPC.js +140 -38
  59. package/dist/TwirpRPC.js.map +1 -1
  60. package/dist/dialTimeout.cjs +39 -0
  61. package/dist/dialTimeout.cjs.map +1 -0
  62. package/dist/dialTimeout.d.cts +21 -0
  63. package/dist/dialTimeout.d.ts +21 -0
  64. package/dist/dialTimeout.d.ts.map +1 -0
  65. package/dist/dialTimeout.js +13 -0
  66. package/dist/dialTimeout.js.map +1 -0
  67. package/dist/failover.cjs +139 -0
  68. package/dist/failover.cjs.map +1 -0
  69. package/dist/failover.d.cts +30 -0
  70. package/dist/failover.d.ts +30 -0
  71. package/dist/failover.d.ts.map +1 -0
  72. package/dist/failover.js +107 -0
  73. package/dist/failover.js.map +1 -0
  74. package/dist/index.cjs +22 -0
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.cts +4 -3
  77. package/dist/index.d.ts +4 -3
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +20 -1
  80. package/dist/index.js.map +1 -1
  81. package/dist/version.cjs +29 -0
  82. package/dist/version.cjs.map +1 -0
  83. package/dist/version.d.cts +3 -0
  84. package/dist/version.d.ts +3 -0
  85. package/dist/version.d.ts.map +1 -0
  86. package/dist/version.js +5 -0
  87. package/dist/version.js.map +1 -0
  88. package/package.json +3 -3
  89. package/src/AgentDispatchClient.ts +5 -5
  90. package/src/ClientOptions.ts +12 -0
  91. package/src/ConnectorClient.ts +20 -11
  92. package/src/EgressClient.ts +21 -5
  93. package/src/IngressClient.ts +5 -5
  94. package/src/LiveKitAPI.ts +126 -0
  95. package/src/RoomServiceClient.ts +5 -5
  96. package/src/ServiceBase.ts +35 -4
  97. package/src/SipClient.ts +55 -23
  98. package/src/TwirpRPC.test.ts +42 -0
  99. package/src/TwirpRPC.ts +187 -39
  100. package/src/dialTimeout.test.ts +41 -0
  101. package/src/dialTimeout.ts +37 -0
  102. package/src/failover.test.ts +188 -0
  103. package/src/failover.ts +170 -0
  104. package/src/index.ts +11 -1
  105. package/src/version.ts +1 -0
@@ -0,0 +1,170 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ // Region failover for the Twirp API clients.
6
+ //
7
+ // On a retryable failure (any transport error or HTTP 5xx) the client discovers
8
+ // alternative LiveKit Cloud regions via /settings/regions and replays the
9
+ // request against the next region, with exponential backoff. 4xx responses are
10
+ // returned immediately.
11
+
12
+ // Total attempts (the original request + fallback regions) and the base retry
13
+ // backoff are fixed, not user-configurable, so retries can't be tuned to values
14
+ // that could overwhelm the server.
15
+ export const FAILOVER_MAX_ATTEMPTS = 3;
16
+ export const FAILOVER_BACKOFF_BASE_MS = 200;
17
+ // Below this per-request timeout, a retry is unlikely to help and many clients
18
+ // would retry in lockstep across regions, so a short request gets a single
19
+ // attempt (thundering-herd guard).
20
+ export const MIN_FAILOVER_TIMEOUT_SECONDS = 5;
21
+
22
+ /**
23
+ * Total request attempts for a host; 1 means no failover. Failover only engages
24
+ * when enabled, the host is a LiveKit Cloud domain, and the request timeout is
25
+ * long enough to retry. `force` bypasses the cloud-host check (test-only).
26
+ */
27
+ export function failoverAttempts(
28
+ enabled: boolean,
29
+ hostname: string,
30
+ force = false,
31
+ timeoutSeconds = 0,
32
+ ): number {
33
+ if (!enabled || !(force || isCloud(hostname))) {
34
+ return 1;
35
+ }
36
+ if (timeoutSeconds > 0 && timeoutSeconds < MIN_FAILOVER_TIMEOUT_SECONDS) {
37
+ return 1;
38
+ }
39
+ return FAILOVER_MAX_ATTEMPTS;
40
+ }
41
+
42
+ // Failover only engages for LiveKit Cloud project domains.
43
+ function isCloud(hostname: string): boolean {
44
+ return hostname.endsWith('.livekit.cloud');
45
+ }
46
+
47
+ /** Normalizes a region URL to an http(s) scheme (ws -> http, wss -> https). */
48
+ function toHttp(url: string): string {
49
+ return url.startsWith('ws') ? `http${url.slice(2)}` : url;
50
+ }
51
+
52
+ /** A stable key identifying a host (including port) for dedup across attempts. */
53
+ export function hostKey(url: URL): string {
54
+ return url.host.toLowerCase();
55
+ }
56
+
57
+ /** Returns the first region origin whose host has not yet been attempted. */
58
+ export function pickNext(regionOrigins: string[], attempted: Set<string>): string | undefined {
59
+ for (const origin of regionOrigins) {
60
+ try {
61
+ if (!attempted.has(hostKey(new URL(origin)))) {
62
+ return origin;
63
+ }
64
+ } catch {
65
+ // skip malformed URLs
66
+ }
67
+ }
68
+ return undefined;
69
+ }
70
+
71
+ export function sleep(ms: number): Promise<void> {
72
+ return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
73
+ }
74
+
75
+ type CacheEntry = {
76
+ origins: string[];
77
+ fetchedAt: number;
78
+ ttl: number; // ms
79
+ };
80
+
81
+ // Shared across all clients in the process so the region list is fetched once.
82
+ const regionCache = new Map<string, CacheEntry>();
83
+ // Coalesces concurrent discovery fetches per origin: while one /settings/regions
84
+ // request is in flight, other callers for the same origin await its result
85
+ // rather than issuing their own (avoids a thundering herd when many requests
86
+ // fail over at once).
87
+ const inflight = new Map<string, Promise<string[]>>();
88
+
89
+ /**
90
+ * Returns alternative region origins for `origin`, fetching /settings/regions
91
+ * if the cache is stale. Best-effort: on a fetch failure it serves a stale
92
+ * cached list when available, otherwise an empty list. Forwards `headers` so a
93
+ * valid token — and any test directives — reach the discovery endpoint.
94
+ */
95
+ export async function regionOrigins(origin: URL, headers: unknown): Promise<string[]> {
96
+ const key = hostKey(origin);
97
+ const cached = regionCache.get(key);
98
+ if (cached && Date.now() - cached.fetchedAt < cached.ttl) {
99
+ return cached.origins;
100
+ }
101
+
102
+ const existing = inflight.get(key);
103
+ if (existing) {
104
+ return existing;
105
+ }
106
+ const request = (async () => {
107
+ try {
108
+ const { origins, ttl } = await fetchRegions(origin, headers);
109
+ // A zero TTL (e.g. Cache-Control: max-age=0) means "do not cache".
110
+ if (ttl > 0) {
111
+ regionCache.set(key, { origins, fetchedAt: Date.now(), ttl });
112
+ }
113
+ return origins;
114
+ } catch {
115
+ return cached?.origins ?? [];
116
+ } finally {
117
+ inflight.delete(key);
118
+ }
119
+ })();
120
+
121
+ inflight.set(key, request);
122
+ return request;
123
+ }
124
+
125
+ async function fetchRegions(
126
+ origin: URL,
127
+ headers: unknown,
128
+ ): Promise<{ origins: string[]; ttl: number }> {
129
+ // Forward the caller's headers (auth + any custom), minus body-specific ones.
130
+ const fetchHeaders: Record<string, string> = {};
131
+ for (const [k, v] of Object.entries((headers as Record<string, string>) ?? {})) {
132
+ if (k.toLowerCase() === 'content-type' || k.toLowerCase() === 'content-length') continue;
133
+ fetchHeaders[k] = v;
134
+ }
135
+
136
+ const response = await fetch(new URL('/settings/regions', origin.origin), {
137
+ method: 'GET',
138
+ headers: fetchHeaders,
139
+ // Short timeout so a slow/unreachable discovery endpoint doesn't stall the
140
+ // failover path.
141
+ signal: AbortSignal.timeout(2000),
142
+ });
143
+ if (!response.ok) {
144
+ throw new Error(`region discovery failed: ${response.status}`);
145
+ }
146
+ const ttl = parseMaxAge(response.headers.get('cache-control'));
147
+ const body = (await response.json()) as { regions?: Array<{ url?: string }> };
148
+ const origins = (body.regions ?? [])
149
+ .filter((r) => !!r.url)
150
+ .map((r) => new URL(toHttp(r.url!)).origin);
151
+ return { origins, ttl };
152
+ }
153
+
154
+ /**
155
+ * Returns the `max-age` from a Cache-Control header in milliseconds, or 0 when
156
+ * absent, non-positive, or unparseable (meaning "do not cache"). Only `max-age`
157
+ * is honored; other directives (including `s-maxage`, which targets shared
158
+ * proxies) are ignored.
159
+ */
160
+ export function parseMaxAge(cacheControl: string | null): number {
161
+ if (!cacheControl) return 0;
162
+ for (const directive of cacheControl.split(',')) {
163
+ const trimmed = directive.trim().toLowerCase();
164
+ if (trimmed.startsWith('max-age=')) {
165
+ const secs = parseInt(trimmed.slice('max-age='.length), 10);
166
+ return Number.isFinite(secs) && secs > 0 ? secs * 1000 : 0;
167
+ }
168
+ }
169
+ return 0;
170
+ }
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ export {
24
24
  EncodedFileType,
25
25
  EncodingOptions,
26
26
  EncodingOptionsPreset,
27
+ FileOutput,
27
28
  GCPUpload,
28
29
  ImageCodec,
29
30
  ImageFileSuffix,
@@ -38,10 +39,13 @@ export {
38
39
  IngressVideoEncodingPreset,
39
40
  IngressVideoOptions,
40
41
  JobRestartPolicy,
42
+ MediaSource,
43
+ Output,
41
44
  ParticipantEgressRequest,
42
45
  ParticipantInfo,
43
46
  ParticipantInfo_State,
44
47
  ParticipantPermission,
48
+ ParticipantVideo,
45
49
  Room,
46
50
  RoomAgentDispatch,
47
51
  RoomCompositeEgressRequest,
@@ -61,14 +65,18 @@ export {
61
65
  SIPCallStatus,
62
66
  SegmentedFileOutput,
63
67
  SegmentedFileProtocol,
68
+ StartEgressRequest,
69
+ StorageConfig,
64
70
  StreamOutput,
65
71
  StreamProtocol,
72
+ TemplateSource,
66
73
  TrackCompositeEgressRequest,
67
74
  TrackEgressRequest,
68
75
  TrackInfo,
69
76
  TrackSource,
70
77
  TrackType,
71
78
  WebEgressRequest,
79
+ WebSource,
72
80
  VideoCodec,
73
81
  WebhookConfig,
74
82
  } from '@livekit/protocol';
@@ -78,7 +86,9 @@ export * from './ConnectorClient.js';
78
86
  export * from './EgressClient.js';
79
87
  export * from './grants.js';
80
88
  export * from './IngressClient.js';
89
+ export * from './LiveKitAPI.js';
81
90
  export * from './RoomServiceClient.js';
82
91
  export * from './SipClient.js';
83
- export { TwirpError } from './TwirpRPC.js';
92
+ export { ServerError, SipCallError, TwirpError } from './TwirpRPC.js';
93
+ export type { ClientOptions } from './ClientOptions.js';
84
94
  export * from './WebhookReceiver.js';
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const SDK_VERSION = "2.18.0";