@edge-base/server 0.2.5 → 0.2.7

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 (135) hide show
  1. package/admin-build/_app/immutable/chunks/{DILS_-VJ.js → B3CvhH3c.js} +1 -1
  2. package/admin-build/_app/immutable/chunks/BDYewzou.js +1 -0
  3. package/admin-build/_app/immutable/chunks/{Cdm5zBRA.js → BEM1BeVF.js} +1 -1
  4. package/admin-build/_app/immutable/chunks/{Dt4vL4Df.js → BYL_uBga.js} +1 -1
  5. package/admin-build/_app/immutable/chunks/{B94PilAN.js → BYyykAbh.js} +1 -1
  6. package/admin-build/_app/immutable/chunks/BaUG2TJ-.js +1 -0
  7. package/admin-build/_app/immutable/chunks/{C72lTcG0.js → Bcs4KYNp.js} +1 -1
  8. package/admin-build/_app/immutable/chunks/{D2j3I1VQ.js → BfpUQYr3.js} +1 -1
  9. package/admin-build/_app/immutable/chunks/BhCO1Fpt.js +1 -0
  10. package/admin-build/_app/immutable/chunks/{B8s_s9QY.js → BkZCgsc3.js} +1 -1
  11. package/admin-build/_app/immutable/chunks/CIOC1v_q.js +128 -0
  12. package/admin-build/_app/immutable/chunks/CjcrXziO.js +2 -0
  13. package/admin-build/_app/immutable/chunks/CvczjTXx.js +1 -0
  14. package/admin-build/_app/immutable/chunks/D1u3u7xu.js +1 -0
  15. package/admin-build/_app/immutable/chunks/{B0HRJ657.js → DOOPbWwG.js} +1 -1
  16. package/admin-build/_app/immutable/chunks/{BqTb6Mxk.js → DaXO-sFP.js} +1 -1
  17. package/admin-build/_app/immutable/chunks/DnpbvAPi.js +1 -0
  18. package/admin-build/_app/immutable/chunks/{B6MschND.js → Dz9cUCuv.js} +1 -1
  19. package/admin-build/_app/immutable/chunks/{CaVKAiCe.js → Tea2dBJ8.js} +1 -1
  20. package/admin-build/_app/immutable/chunks/{Z41NK6i6.js → bguI1TeA.js} +1 -1
  21. package/admin-build/_app/immutable/chunks/{J2Gw0SMu.js → ejoEf2I5.js} +1 -1
  22. package/admin-build/_app/immutable/chunks/{B2TnDKF7.js → iEyeblJR.js} +1 -1
  23. package/admin-build/_app/immutable/chunks/{_teD5ji5.js → nlAMTi52.js} +1 -1
  24. package/admin-build/_app/immutable/chunks/qKdzaeX3.js +1 -0
  25. package/admin-build/_app/immutable/entry/{app.D3flihMw.js → app.DoUaxnew.js} +2 -2
  26. package/admin-build/_app/immutable/entry/start.MmZh8oBH.js +1 -0
  27. package/admin-build/_app/immutable/nodes/{0.CdczqZLK.js → 0.Dsxi8s7i.js} +1 -1
  28. package/admin-build/_app/immutable/nodes/1.Cp2l-hol.js +1 -0
  29. package/admin-build/_app/immutable/nodes/10.4oY6m8Nz.js +1 -0
  30. package/admin-build/_app/immutable/nodes/11.DfcozD4J.js +1 -0
  31. package/admin-build/_app/immutable/nodes/12.uJgZdCIA.js +1 -0
  32. package/admin-build/_app/immutable/nodes/13.CaN1kRev.js +110 -0
  33. package/admin-build/_app/immutable/nodes/14.DQ5xIi3s.js +3 -0
  34. package/admin-build/_app/immutable/nodes/15.B_EkebTJ.js +1 -0
  35. package/admin-build/_app/immutable/nodes/{16.BR7WwQrS.js → 16.Tko1ZX8-.js} +1 -1
  36. package/admin-build/_app/immutable/nodes/{17.Cm57KKXV.js → 17.BCmWMJX9.js} +1 -1
  37. package/admin-build/_app/immutable/nodes/18.hmGhl1O2.js +1 -0
  38. package/admin-build/_app/immutable/nodes/19.D-1infOo.js +2 -0
  39. package/admin-build/_app/immutable/nodes/{20.DnHeFlTv.js → 20.CY4KKcBL.js} +1 -1
  40. package/admin-build/_app/immutable/nodes/21.B9lbNUQr.js +1 -0
  41. package/admin-build/_app/immutable/nodes/22.14Vd7bnt.js +1 -0
  42. package/admin-build/_app/immutable/nodes/{23.CWSGMcKJ.js → 23.Be6jK77o.js} +2 -2
  43. package/admin-build/_app/immutable/nodes/24.CSTFkr6R.js +2 -0
  44. package/admin-build/_app/immutable/nodes/25.DRTg8fHc.js +2 -0
  45. package/admin-build/_app/immutable/nodes/26.DKt-9lwQ.js +1 -0
  46. package/admin-build/_app/immutable/nodes/27.D5caPu0F.js +1 -0
  47. package/admin-build/_app/immutable/nodes/28.hJhlnlyY.js +1 -0
  48. package/admin-build/_app/immutable/nodes/29.CDYBzFyT.js +1 -0
  49. package/admin-build/_app/immutable/nodes/{3.B6q-7qr8.js → 3.DMyKwkGn.js} +1 -1
  50. package/admin-build/_app/immutable/nodes/30.BaHNeEmc.js +1 -0
  51. package/admin-build/_app/immutable/nodes/31.C6PV5L-2.js +1 -0
  52. package/admin-build/_app/immutable/nodes/4.9E118Ftm.js +1 -0
  53. package/admin-build/_app/immutable/nodes/5.D8guAl3v.js +1 -0
  54. package/admin-build/_app/immutable/nodes/6.D1u__DtT.js +1 -0
  55. package/admin-build/_app/immutable/nodes/7.DWXHnRFf.js +1 -0
  56. package/admin-build/_app/immutable/nodes/8.Dojd8krc.js +1 -0
  57. package/admin-build/_app/immutable/nodes/9.CLtrr0K_.js +1 -0
  58. package/admin-build/_app/version.json +1 -1
  59. package/admin-build/index.html +7 -7
  60. package/openapi.json +6 -1941
  61. package/package.json +3 -3
  62. package/src/__tests__/openapi-coverage.test.ts +0 -6
  63. package/src/__tests__/push-handlers.test.ts +1 -1
  64. package/src/__tests__/room-auth-state-loss.test.ts +6 -0
  65. package/src/__tests__/room-handler-context.test.ts +0 -31
  66. package/src/__tests__/room-rate-limit-scopes.test.ts +1 -5
  67. package/src/__tests__/room-runtime-routing.test.ts +24 -111
  68. package/src/__tests__/route-parser.test.ts +6 -0
  69. package/src/__tests__/schema.test.ts +15 -6
  70. package/src/__tests__/smoke-skip-report.test.ts +1 -1
  71. package/src/durable-objects/database-do.ts +7 -1
  72. package/src/durable-objects/room-runtime-base.ts +290 -57
  73. package/src/durable-objects/rooms-do.ts +212 -1336
  74. package/src/index.ts +23 -9
  75. package/src/lib/d1-handler.ts +32 -17
  76. package/src/lib/openapi.ts +1 -4
  77. package/src/lib/postgres-handler.ts +24 -12
  78. package/src/lib/route-parser.ts +3 -0
  79. package/src/lib/schemas.ts +12 -2
  80. package/src/middleware/captcha-verify.ts +16 -3
  81. package/src/middleware/error-handler.ts +1 -1
  82. package/src/middleware/rules.ts +28 -9
  83. package/src/routes/admin-auth.ts +3 -3
  84. package/src/routes/admin.ts +13 -8
  85. package/src/routes/analytics-api.ts +3 -3
  86. package/src/routes/auth.ts +1 -1
  87. package/src/routes/backup.ts +1 -1
  88. package/src/routes/d1.ts +14 -7
  89. package/src/routes/database-live.ts +13 -6
  90. package/src/routes/kv.ts +21 -10
  91. package/src/routes/oauth.ts +1 -1
  92. package/src/routes/push.ts +119 -77
  93. package/src/routes/room.ts +203 -280
  94. package/src/routes/schema-endpoint.ts +2 -2
  95. package/src/routes/sql.ts +10 -6
  96. package/src/routes/storage.ts +4 -2
  97. package/src/routes/vectorize.ts +16 -4
  98. package/src/types.ts +1 -14
  99. package/admin-build/_app/immutable/chunks/6oMK_164.js +0 -1
  100. package/admin-build/_app/immutable/chunks/BEW7Ez_g.js +0 -1
  101. package/admin-build/_app/immutable/chunks/BoOooyH6.js +0 -1
  102. package/admin-build/_app/immutable/chunks/BvHnF5tV.js +0 -1
  103. package/admin-build/_app/immutable/chunks/CoI6jjbg.js +0 -2
  104. package/admin-build/_app/immutable/chunks/CrOZMmdF.js +0 -1
  105. package/admin-build/_app/immutable/chunks/Cw6OYcq-.js +0 -1
  106. package/admin-build/_app/immutable/chunks/DPdQ7z0T.js +0 -128
  107. package/admin-build/_app/immutable/chunks/pUxw8jfq.js +0 -1
  108. package/admin-build/_app/immutable/entry/start.Cl6sLxnz.js +0 -1
  109. package/admin-build/_app/immutable/nodes/1.DxcSsEqS.js +0 -1
  110. package/admin-build/_app/immutable/nodes/10.DuAd4aIm.js +0 -1
  111. package/admin-build/_app/immutable/nodes/11.0jgHQL92.js +0 -1
  112. package/admin-build/_app/immutable/nodes/12.CKNPqmyy.js +0 -1
  113. package/admin-build/_app/immutable/nodes/13.B1p2POXS.js +0 -110
  114. package/admin-build/_app/immutable/nodes/14.Bb-REBND.js +0 -3
  115. package/admin-build/_app/immutable/nodes/15.1uBFCX0X.js +0 -1
  116. package/admin-build/_app/immutable/nodes/18.CoiwfAuQ.js +0 -1
  117. package/admin-build/_app/immutable/nodes/19.B8ZdLlXj.js +0 -2
  118. package/admin-build/_app/immutable/nodes/21.CJFaf0Ia.js +0 -1
  119. package/admin-build/_app/immutable/nodes/22.CItETFzy.js +0 -1
  120. package/admin-build/_app/immutable/nodes/24.CWbEqNMB.js +0 -2
  121. package/admin-build/_app/immutable/nodes/25.DRkLEhKi.js +0 -2
  122. package/admin-build/_app/immutable/nodes/26.BRxO8AYH.js +0 -1
  123. package/admin-build/_app/immutable/nodes/27.BLs-nVHz.js +0 -1
  124. package/admin-build/_app/immutable/nodes/28.G79qkdBK.js +0 -1
  125. package/admin-build/_app/immutable/nodes/29.BOcI6g0N.js +0 -1
  126. package/admin-build/_app/immutable/nodes/30.DAIC7dKd.js +0 -1
  127. package/admin-build/_app/immutable/nodes/31.pl0XXjXF.js +0 -1
  128. package/admin-build/_app/immutable/nodes/4.DOdvVlZj.js +0 -1
  129. package/admin-build/_app/immutable/nodes/5.BW_zlgye.js +0 -1
  130. package/admin-build/_app/immutable/nodes/6.Dxy1CAI2.js +0 -1
  131. package/admin-build/_app/immutable/nodes/7.BG98w_o7.js +0 -1
  132. package/admin-build/_app/immutable/nodes/8.DoG5R2rG.js +0 -1
  133. package/admin-build/_app/immutable/nodes/9.Dmxf6zAC.js +0 -1
  134. package/src/__tests__/cloudflare-realtime.test.ts +0 -113
  135. package/src/lib/cloudflare-realtime.ts +0 -251
@@ -1,113 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import {
3
- CloudflareRealtimeClient,
4
- assertCloudflareRealtimeConfig,
5
- createCloudflareRealtimeClient,
6
- hasCloudflareRealtimeConfig,
7
- } from '../lib/cloudflare-realtime.js';
8
-
9
- describe('cloudflare realtime helpers', () => {
10
- afterEach(() => {
11
- vi.restoreAllMocks();
12
- vi.unstubAllGlobals();
13
- });
14
-
15
- it('detects whether the required realtime config is present', () => {
16
- expect(hasCloudflareRealtimeConfig({})).toBe(false);
17
- expect(hasCloudflareRealtimeConfig({
18
- CF_REALTIME_APP_ID: ' app-123 ',
19
- CF_REALTIME_APP_SECRET: ' secret-456 ',
20
- })).toBe(true);
21
- });
22
-
23
- it('normalizes realtime config and defaults the base URL', () => {
24
- expect(assertCloudflareRealtimeConfig({
25
- CF_REALTIME_APP_ID: ' app-123 ',
26
- CF_REALTIME_APP_SECRET: ' secret-456 ',
27
- CF_REALTIME_TURN_KEY_ID: ' key-1 ',
28
- CF_REALTIME_TURN_API_TOKEN: ' token-1 ',
29
- })).toEqual({
30
- appId: 'app-123',
31
- appSecret: 'secret-456',
32
- baseUrl: 'https://rtc.live.cloudflare.com/v1',
33
- turnKeyId: 'key-1',
34
- turnApiToken: 'token-1',
35
- });
36
-
37
- expect(() => assertCloudflareRealtimeConfig({
38
- CF_REALTIME_APP_ID: 'missing-secret',
39
- })).toThrow('Cloudflare Realtime is not configured');
40
- });
41
-
42
- it('creates a realtime client and sends authenticated session requests', async () => {
43
- const fetchMock = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) =>
44
- new Response(JSON.stringify({ sessionId: 'sess-1' }), {
45
- status: 200,
46
- headers: { 'Content-Type': 'application/json' },
47
- }));
48
- vi.stubGlobal('fetch', fetchMock);
49
-
50
- const client = createCloudflareRealtimeClient({
51
- CF_REALTIME_APP_ID: 'app-123',
52
- CF_REALTIME_APP_SECRET: 'secret-456',
53
- } as never);
54
-
55
- expect(client).toBeInstanceOf(CloudflareRealtimeClient);
56
- await expect(client.createSession({
57
- sessionDescription: {
58
- sdp: 'offer-sdp',
59
- type: 'offer',
60
- },
61
- }, {
62
- thirdparty: true,
63
- correlationId: 'corr-1',
64
- })).resolves.toEqual({ sessionId: 'sess-1' });
65
-
66
- expect(fetchMock).toHaveBeenCalledTimes(1);
67
- expect(fetchMock.mock.calls[0]?.[0]).toBe(
68
- 'https://rtc.live.cloudflare.com/v1/apps/app-123/sessions/new?thirdparty=true&correlationId=corr-1',
69
- );
70
- expect(fetchMock.mock.calls[0]?.[1]).toMatchObject({
71
- method: 'POST',
72
- headers: {
73
- Authorization: 'Bearer secret-456',
74
- 'Content-Type': 'application/json',
75
- },
76
- });
77
- });
78
-
79
- it('uses TURN credentials when generating ICE servers', async () => {
80
- const fetchMock = vi.fn(async () =>
81
- new Response(JSON.stringify({
82
- iceServers: [{ urls: 'turn:global.example.com', username: 'user', credential: 'pass' }],
83
- }), {
84
- status: 200,
85
- headers: { 'Content-Type': 'application/json' },
86
- }));
87
- vi.stubGlobal('fetch', fetchMock);
88
-
89
- const client = new CloudflareRealtimeClient({
90
- CF_REALTIME_APP_ID: 'app-123',
91
- CF_REALTIME_APP_SECRET: 'secret-456',
92
- CF_REALTIME_BASE_URL: 'https://rtc.example.com/base/',
93
- CF_REALTIME_TURN_KEY_ID: 'turn-key',
94
- CF_REALTIME_TURN_API_TOKEN: 'turn-token',
95
- });
96
-
97
- await expect(client.generateIceServers(120)).resolves.toEqual({
98
- iceServers: [{ urls: 'turn:global.example.com', username: 'user', credential: 'pass' }],
99
- });
100
-
101
- expect(fetchMock).toHaveBeenCalledWith(
102
- 'https://rtc.example.com/base/turn/keys/turn-key/credentials/generate-ice-servers',
103
- {
104
- method: 'POST',
105
- headers: {
106
- Authorization: 'Bearer turn-token',
107
- 'Content-Type': 'application/json',
108
- },
109
- body: JSON.stringify({ ttl: 120 }),
110
- },
111
- );
112
- });
113
- });
@@ -1,251 +0,0 @@
1
- import type { Env } from '../types.js';
2
-
3
- const DEFAULT_CLOUDFLARE_REALTIME_BASE_URL = 'https://rtc.live.cloudflare.com/v1';
4
-
5
- export interface CloudflareRealtimeSessionDescription {
6
- sdp: string;
7
- type: 'offer' | 'answer';
8
- }
9
-
10
- export interface CloudflareRealtimeTrackObject {
11
- location: 'local' | 'remote';
12
- mid?: string;
13
- sessionId?: string;
14
- trackName?: string;
15
- bidirectionalMediaStream?: boolean;
16
- kind?: string;
17
- simulcast?: {
18
- preferredRid?: string;
19
- priorityOrdering?: 'none' | 'asciibetical';
20
- ridNotAvailable?: 'none' | 'asciibetical';
21
- };
22
- }
23
-
24
- export interface CloudflareRealtimeNewSessionRequest {
25
- sessionDescription?: CloudflareRealtimeSessionDescription;
26
- }
27
-
28
- export interface CloudflareRealtimeNewSessionResponse {
29
- errorCode?: string;
30
- errorDescription?: string;
31
- sessionDescription?: CloudflareRealtimeSessionDescription;
32
- sessionId: string;
33
- }
34
-
35
- export interface CloudflareRealtimeTracksRequest {
36
- sessionDescription?: CloudflareRealtimeSessionDescription;
37
- tracks: CloudflareRealtimeTrackObject[];
38
- autoDiscover?: boolean;
39
- }
40
-
41
- export interface CloudflareRealtimeTracksResponse {
42
- errorCode?: string;
43
- errorDescription?: string;
44
- requiresImmediateRenegotiation?: boolean;
45
- sessionDescription?: CloudflareRealtimeSessionDescription;
46
- tracks?: Array<CloudflareRealtimeTrackObject & {
47
- errorCode?: string;
48
- errorDescription?: string;
49
- }>;
50
- }
51
-
52
- export interface CloudflareRealtimeRenegotiateRequest {
53
- sessionDescription: CloudflareRealtimeSessionDescription;
54
- }
55
-
56
- export interface CloudflareRealtimeCloseTracksRequest {
57
- sessionDescription?: CloudflareRealtimeSessionDescription;
58
- tracks: Array<{ mid: string }>;
59
- force?: boolean;
60
- }
61
-
62
- export interface CloudflareRealtimeIceServer {
63
- urls: string[] | string;
64
- username?: string;
65
- credential?: string;
66
- }
67
-
68
- export interface CloudflareRealtimeIceServersResponse {
69
- iceServers: CloudflareRealtimeIceServer[];
70
- }
71
-
72
- export interface CloudflareRealtimeEnv {
73
- CF_REALTIME_APP_ID?: string;
74
- CF_REALTIME_APP_SECRET?: string;
75
- CF_REALTIME_BASE_URL?: string;
76
- CF_REALTIME_TURN_KEY_ID?: string;
77
- CF_REALTIME_TURN_API_TOKEN?: string;
78
- }
79
-
80
- function trimString(value: unknown): string | undefined {
81
- return typeof value === 'string' && value.trim() ? value.trim() : undefined;
82
- }
83
-
84
- async function parseRealtimeResponse<T>(response: Response): Promise<T> {
85
- const data = (await response.json().catch(() => ({}))) as Record<string, unknown>;
86
- if (!response.ok) {
87
- const message =
88
- (typeof data.errorDescription === 'string' && data.errorDescription)
89
- || (typeof data.message === 'string' && data.message)
90
- || `Cloudflare Realtime request failed (${response.status})`;
91
- throw new Error(message);
92
- }
93
- return data as T;
94
- }
95
-
96
- export function hasCloudflareRealtimeConfig(env: CloudflareRealtimeEnv): boolean {
97
- return !!trimString(env.CF_REALTIME_APP_ID) && !!trimString(env.CF_REALTIME_APP_SECRET);
98
- }
99
-
100
- export function assertCloudflareRealtimeConfig(env: CloudflareRealtimeEnv): {
101
- appId: string;
102
- appSecret: string;
103
- baseUrl: string;
104
- turnKeyId?: string;
105
- turnApiToken?: string;
106
- } {
107
- const appId = trimString(env.CF_REALTIME_APP_ID);
108
- const appSecret = trimString(env.CF_REALTIME_APP_SECRET);
109
- if (!appId || !appSecret) {
110
- throw new Error('Cloudflare Realtime is not configured. Set CF_REALTIME_APP_ID and CF_REALTIME_APP_SECRET.');
111
- }
112
-
113
- return {
114
- appId,
115
- appSecret,
116
- baseUrl: trimString(env.CF_REALTIME_BASE_URL) ?? DEFAULT_CLOUDFLARE_REALTIME_BASE_URL,
117
- turnKeyId: trimString(env.CF_REALTIME_TURN_KEY_ID),
118
- turnApiToken: trimString(env.CF_REALTIME_TURN_API_TOKEN),
119
- };
120
- }
121
-
122
- export class CloudflareRealtimeClient {
123
- private readonly appId: string;
124
- private readonly appSecret: string;
125
- private readonly baseUrl: string;
126
- private readonly turnKeyId?: string;
127
- private readonly turnApiToken?: string;
128
-
129
- constructor(env: CloudflareRealtimeEnv) {
130
- const config = assertCloudflareRealtimeConfig(env);
131
- this.appId = config.appId;
132
- this.appSecret = config.appSecret;
133
- this.baseUrl = config.baseUrl;
134
- this.turnKeyId = config.turnKeyId;
135
- this.turnApiToken = config.turnApiToken;
136
- }
137
-
138
- async createSession(
139
- body: CloudflareRealtimeNewSessionRequest = {},
140
- query?: { thirdparty?: boolean; correlationId?: string },
141
- ): Promise<CloudflareRealtimeNewSessionResponse> {
142
- const url = this.buildSessionUrl('/sessions/new', query);
143
- const response = await fetch(url, {
144
- method: 'POST',
145
- headers: this.buildAuthHeaders(),
146
- body: JSON.stringify(body),
147
- });
148
- return parseRealtimeResponse<CloudflareRealtimeNewSessionResponse>(response);
149
- }
150
-
151
- async addTracks(
152
- sessionId: string,
153
- body: CloudflareRealtimeTracksRequest,
154
- ): Promise<CloudflareRealtimeTracksResponse> {
155
- const response = await fetch(
156
- this.buildSessionUrl(`/sessions/${encodeURIComponent(sessionId)}/tracks/new`),
157
- {
158
- method: 'POST',
159
- headers: this.buildAuthHeaders(),
160
- body: JSON.stringify(body),
161
- },
162
- );
163
- return parseRealtimeResponse<CloudflareRealtimeTracksResponse>(response);
164
- }
165
-
166
- async renegotiate(
167
- sessionId: string,
168
- body: CloudflareRealtimeRenegotiateRequest,
169
- ): Promise<CloudflareRealtimeTracksResponse> {
170
- const response = await fetch(
171
- this.buildSessionUrl(`/sessions/${encodeURIComponent(sessionId)}/renegotiate`),
172
- {
173
- method: 'PUT',
174
- headers: this.buildAuthHeaders(),
175
- body: JSON.stringify(body),
176
- },
177
- );
178
- return parseRealtimeResponse<CloudflareRealtimeTracksResponse>(response);
179
- }
180
-
181
- async closeTracks(
182
- sessionId: string,
183
- body: CloudflareRealtimeCloseTracksRequest,
184
- ): Promise<CloudflareRealtimeTracksResponse> {
185
- const response = await fetch(
186
- this.buildSessionUrl(`/sessions/${encodeURIComponent(sessionId)}/tracks/close`),
187
- {
188
- method: 'PUT',
189
- headers: this.buildAuthHeaders(),
190
- body: JSON.stringify(body),
191
- },
192
- );
193
- return parseRealtimeResponse<CloudflareRealtimeTracksResponse>(response);
194
- }
195
-
196
- async getSession(sessionId: string): Promise<{
197
- tracks?: Array<CloudflareRealtimeTrackObject & { status?: string }>;
198
- }> {
199
- const response = await fetch(
200
- this.buildSessionUrl(`/sessions/${encodeURIComponent(sessionId)}`),
201
- {
202
- method: 'GET',
203
- headers: this.buildAuthHeaders(),
204
- },
205
- );
206
- return parseRealtimeResponse<{ tracks?: Array<CloudflareRealtimeTrackObject & { status?: string }> }>(response);
207
- }
208
-
209
- async generateIceServers(ttl = 3600): Promise<CloudflareRealtimeIceServersResponse> {
210
- if (!this.turnKeyId || !this.turnApiToken) {
211
- throw new Error('Cloudflare TURN is not configured. Set CF_REALTIME_TURN_KEY_ID and CF_REALTIME_TURN_API_TOKEN.');
212
- }
213
-
214
- const response = await fetch(
215
- `${this.baseUrl.replace(/\/$/, '')}/turn/keys/${encodeURIComponent(this.turnKeyId)}/credentials/generate-ice-servers`,
216
- {
217
- method: 'POST',
218
- headers: {
219
- Authorization: `Bearer ${this.turnApiToken}`,
220
- 'Content-Type': 'application/json',
221
- },
222
- body: JSON.stringify({ ttl }),
223
- },
224
- );
225
- return parseRealtimeResponse<CloudflareRealtimeIceServersResponse>(response);
226
- }
227
-
228
- private buildSessionUrl(pathname: string, query?: { thirdparty?: boolean; correlationId?: string }): string {
229
- const url = new URL(
230
- `${this.baseUrl.replace(/\/$/, '')}/apps/${encodeURIComponent(this.appId)}${pathname}`,
231
- );
232
- if (query?.thirdparty !== undefined) {
233
- url.searchParams.set('thirdparty', String(query.thirdparty));
234
- }
235
- if (query?.correlationId) {
236
- url.searchParams.set('correlationId', query.correlationId);
237
- }
238
- return url.toString();
239
- }
240
-
241
- private buildAuthHeaders(): HeadersInit {
242
- return {
243
- Authorization: `Bearer ${this.appSecret}`,
244
- 'Content-Type': 'application/json',
245
- };
246
- }
247
- }
248
-
249
- export function createCloudflareRealtimeClient(env: Env): CloudflareRealtimeClient {
250
- return new CloudflareRealtimeClient(env);
251
- }