@edge-base/server 0.2.6 → 0.2.8
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/admin-build/_app/immutable/chunks/{CbfX3ELZ.js → B9efkx2V.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CLHN9MVr.js → BMXWUTG-.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DemDWbs-.js → Bt4AyT3o.js} +3 -3
- package/admin-build/_app/immutable/chunks/CKVjMXZi.js +1 -0
- package/admin-build/_app/immutable/chunks/{BvoGcDFV.js → CMYgGhZR.js} +1 -1
- package/admin-build/_app/immutable/chunks/{LL3ulaxa.js → CTRjWhGs.js} +1 -1
- package/admin-build/_app/immutable/chunks/{BN_-k-Ck.js → CwyE59Yt.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DQVP4KC-.js → D8aeTKry.js} +1 -1
- package/admin-build/_app/immutable/chunks/{Ff90owjx.js → DGAHkap7.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CR37B8DX.js → DPgR4-0v.js} +1 -1
- package/admin-build/_app/immutable/chunks/{DdvsFblq.js → DYtrHeVQ.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CrwlCAM0.js → DcVb45Ds.js} +1 -1
- package/admin-build/_app/immutable/chunks/Djnkhy-S.js +1 -0
- package/admin-build/_app/immutable/chunks/{DmDTovpg.js → fPy6xmgG.js} +1 -1
- package/admin-build/_app/immutable/chunks/{CCUxCptE.js → j4jxnAKj.js} +1 -1
- package/admin-build/_app/immutable/chunks/{qBm6xof8.js → zl2AUKMP.js} +1 -1
- package/admin-build/_app/immutable/entry/{app.CP83Ni80.js → app.Cmz0WjMl.js} +2 -2
- package/admin-build/_app/immutable/entry/start.JE7dcbK1.js +1 -0
- package/admin-build/_app/immutable/nodes/{0.DiRq7puO.js → 0.y6D_QyUb.js} +1 -1
- package/admin-build/_app/immutable/nodes/{1.BFeyKLGT.js → 1.CndRxhbH.js} +1 -1
- package/admin-build/_app/immutable/nodes/{10.zcee7hJx.js → 10.CdA5FmXy.js} +1 -1
- package/admin-build/_app/immutable/nodes/{11.BW7wLs2Y.js → 11.DG8SzMp_.js} +1 -1
- package/admin-build/_app/immutable/nodes/{12.CxJRlYSd.js → 12.CvmQqpFa.js} +1 -1
- package/admin-build/_app/immutable/nodes/{13.pp0F_5hn.js → 13.BbGNdswT.js} +1 -1
- package/admin-build/_app/immutable/nodes/{14.t3AfGiGo.js → 14.CZKsN7-O.js} +1 -1
- package/admin-build/_app/immutable/nodes/{15.B3agc7NX.js → 15.A7-CYgkG.js} +1 -1
- package/admin-build/_app/immutable/nodes/{16.C4uG2-i8.js → 16.hgJT9H-x.js} +1 -1
- package/admin-build/_app/immutable/nodes/{17.CwGxi1Bn.js → 17.DkWZbcN2.js} +1 -1
- package/admin-build/_app/immutable/nodes/{18.CrQyN_gU.js → 18.sX3Fb5gh.js} +1 -1
- package/admin-build/_app/immutable/nodes/{19.NEPUOXl7.js → 19.VAZUW-1K.js} +1 -1
- package/admin-build/_app/immutable/nodes/{20.DGHO8ipr.js → 20.DkIKxacG.js} +1 -1
- package/admin-build/_app/immutable/nodes/21.DOjJlQKc.js +1 -0
- package/admin-build/_app/immutable/nodes/{22.Dri5It7a.js → 22.BDaHvtaw.js} +1 -1
- package/admin-build/_app/immutable/nodes/{23.BPQP_Zte.js → 23.BVRzw_pD.js} +1 -1
- package/admin-build/_app/immutable/nodes/{24.D580FdSS.js → 24.CVhSJyG0.js} +1 -1
- package/admin-build/_app/immutable/nodes/{25.BMNPOZwF.js → 25.Bme-9bZn.js} +1 -1
- package/admin-build/_app/immutable/nodes/{26.XcpEcbiz.js → 26.Dsx7RIIs.js} +1 -1
- package/admin-build/_app/immutable/nodes/{27.C1zHHcYv.js → 27.DMGQnzFM.js} +1 -1
- package/admin-build/_app/immutable/nodes/{28.CuKzzrY8.js → 28.GGwFmEhZ.js} +1 -1
- package/admin-build/_app/immutable/nodes/{29.nLpBMXnM.js → 29.Dnghr0nk.js} +1 -1
- package/admin-build/_app/immutable/nodes/{3.5G_aseoL.js → 3.Cg7zZJP1.js} +1 -1
- package/admin-build/_app/immutable/nodes/{30.CQC4nLoU.js → 30.C0J24z3I.js} +1 -1
- package/admin-build/_app/immutable/nodes/{31.Bet8kxOK.js → 31.MdxFI8v6.js} +1 -1
- package/admin-build/_app/immutable/nodes/{4.nmJDYJpC.js → 4.DCAOVzGE.js} +1 -1
- package/admin-build/_app/immutable/nodes/{5.CnbYLG4E.js → 5.DzUQ-cTc.js} +1 -1
- package/admin-build/_app/immutable/nodes/{6.KA01b-3y.js → 6.CptBYTVj.js} +1 -1
- package/admin-build/_app/immutable/nodes/{7.CP9fkn1L.js → 7.DfeeQ0Rg.js} +1 -1
- package/admin-build/_app/immutable/nodes/{8.BTzDb---.js → 8.CIcvctW7.js} +1 -1
- package/admin-build/_app/immutable/nodes/{9.DkNJg_J6.js → 9.QKrvq4RA.js} +1 -1
- package/admin-build/_app/version.json +1 -1
- package/admin-build/index.html +7 -7
- package/openapi.json +6 -1941
- package/package.json +3 -3
- package/src/__tests__/admin-assets.test.ts +7 -7
- package/src/__tests__/frontend-assets.test.ts +75 -0
- package/src/__tests__/frontend-config.test.ts +16 -0
- package/src/__tests__/frontend-routing.test.ts +200 -0
- package/src/__tests__/openapi-coverage.test.ts +0 -6
- package/src/__tests__/room-auth-state-loss.test.ts +6 -0
- package/src/__tests__/room-handler-context.test.ts +0 -31
- package/src/__tests__/room-rate-limit-scopes.test.ts +1 -5
- package/src/__tests__/room-runtime-routing.test.ts +1 -111
- package/src/__tests__/smoke-skip-report.test.ts +1 -1
- package/src/durable-objects/room-runtime-base.ts +243 -17
- package/src/durable-objects/rooms-do.ts +190 -1345
- package/src/index.ts +97 -3
- package/src/lib/admin-assets.ts +5 -5
- package/src/lib/frontend-assets.ts +129 -0
- package/src/lib/frontend-config.ts +11 -0
- package/src/lib/openapi.ts +1 -4
- package/src/routes/room.ts +0 -285
- package/src/types.ts +1 -14
- package/admin-build/_app/immutable/chunks/Q3vAxeY-.js +0 -1
- package/admin-build/_app/immutable/chunks/SQVAC3Cv.js +0 -1
- package/admin-build/_app/immutable/entry/start.DY6YakU0.js +0 -1
- package/admin-build/_app/immutable/nodes/21.UVKBDvp4.js +0 -1
- package/src/__tests__/cloudflare-realtime.test.ts +0 -113
- package/src/lib/cloudflare-realtime.ts +0 -251
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HonoEnv } from './lib/hono.js';
|
|
2
2
|
import type { OpenApiSpec } from './lib/openapi.js';
|
|
3
3
|
import type { Env } from './types.js';
|
|
4
|
+
import type { FrontendConfigLike } from './lib/frontend-config.js';
|
|
4
5
|
import { ensureServerStartup } from './lib/runtime-startup.js';
|
|
5
6
|
|
|
6
7
|
// ─── DO Re-exports (wrangler needs exports from main entry) ───
|
|
@@ -11,12 +12,14 @@ export { RoomsDO } from './durable-objects/rooms-do.js';
|
|
|
11
12
|
export { LogsDO } from './durable-objects/logs-do.js';
|
|
12
13
|
|
|
13
14
|
let appPromise: Promise<Awaited<ReturnType<typeof buildApp>>> | null = null;
|
|
15
|
+
const FRONTEND_ASSET_REDIRECT_STATUSES = new Set([301, 302, 307, 308]);
|
|
16
|
+
const FRONTEND_ASSET_REDIRECT_LIMIT = 4;
|
|
14
17
|
|
|
15
18
|
function assetUnavailableMessage(
|
|
16
|
-
assetName: 'admin dashboard' | 'harness assets',
|
|
19
|
+
assetName: 'admin dashboard' | 'frontend bundle' | 'harness assets',
|
|
17
20
|
): string {
|
|
18
21
|
const label = `${assetName[0].toUpperCase()}${assetName.slice(1)}`;
|
|
19
|
-
const verb = assetName === '
|
|
22
|
+
const verb = assetName === 'harness assets' ? 'are' : 'is';
|
|
20
23
|
return `${label} ${verb} not deployed for this worker. Deploy the assets bundle or configure ADMIN_ORIGIN if they are hosted elsewhere.`;
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -56,6 +59,7 @@ async function buildApp() {
|
|
|
56
59
|
analyticsRouteModule,
|
|
57
60
|
adminAssetsModule,
|
|
58
61
|
adminRoutingModule,
|
|
62
|
+
frontendAssetsModule,
|
|
59
63
|
schemasModule,
|
|
60
64
|
pluginMigrationsModule,
|
|
61
65
|
pluginMigrationRoutingModule,
|
|
@@ -95,6 +99,7 @@ async function buildApp() {
|
|
|
95
99
|
import('./routes/analytics-api.js'),
|
|
96
100
|
import('./lib/admin-assets.js'),
|
|
97
101
|
import('./lib/admin-routing.js'),
|
|
102
|
+
import('./lib/frontend-assets.js'),
|
|
98
103
|
import('./lib/schemas.js'),
|
|
99
104
|
import('./lib/plugin-migrations.js'),
|
|
100
105
|
import('./lib/plugin-migration-routing.js'),
|
|
@@ -116,6 +121,7 @@ async function buildApp() {
|
|
|
116
121
|
const { SERVER_VERSION } = versionModule;
|
|
117
122
|
const { createAdminAssetRequest } = adminAssetsModule;
|
|
118
123
|
const { resolveAdminFaviconTarget, resolveAdminRedirectTarget } = adminRoutingModule;
|
|
124
|
+
const { applyFrontendAssetHeaders, createFrontendAssetRequest } = frontendAssetsModule;
|
|
119
125
|
const { zodDefaultHook } = schemasModule;
|
|
120
126
|
const { executePluginMigrations } = pluginMigrationsModule;
|
|
121
127
|
const { shouldRunPluginMigrationsForRequestPath } = pluginMigrationRoutingModule;
|
|
@@ -163,8 +169,75 @@ async function buildApp() {
|
|
|
163
169
|
app.route('/admin/api', adminRouteModule.adminRoute);
|
|
164
170
|
app.route('/admin/api/backup', backupRouteModule.backupRoute);
|
|
165
171
|
|
|
166
|
-
|
|
172
|
+
function getFrontendConfig(env: Env): FrontendConfigLike | undefined {
|
|
173
|
+
return (doRouterModule.parseConfig(env) as { frontend?: FrontendConfigLike } | undefined)?.frontend;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function fetchFrontendAssetResponse(
|
|
177
|
+
assetsBinding: { fetch(request: Request): Promise<Response> },
|
|
178
|
+
assetRequest: Request,
|
|
179
|
+
): Promise<Response> {
|
|
180
|
+
let currentRequest = assetRequest;
|
|
181
|
+
const visitedUrls = new Set<string>();
|
|
182
|
+
|
|
183
|
+
for (let attempt = 0; attempt <= FRONTEND_ASSET_REDIRECT_LIMIT; attempt += 1) {
|
|
184
|
+
visitedUrls.add(currentRequest.url);
|
|
185
|
+
const assetResponse = await assetsBinding.fetch(currentRequest);
|
|
186
|
+
if (!FRONTEND_ASSET_REDIRECT_STATUSES.has(assetResponse.status)) {
|
|
187
|
+
return assetResponse;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const location = assetResponse.headers.get('location');
|
|
191
|
+
if (!location) {
|
|
192
|
+
return assetResponse;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const nextUrl = new URL(location, currentRequest.url);
|
|
196
|
+
if (nextUrl.origin !== new URL(currentRequest.url).origin) {
|
|
197
|
+
return assetResponse;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (visitedUrls.has(nextUrl.toString())) {
|
|
201
|
+
return assetResponse;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
currentRequest = new Request(nextUrl.toString(), currentRequest);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return assetsBinding.fetch(currentRequest);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function serveFrontendAsset(c: { env: Env; req: { raw: Request } }): Promise<Response | null> {
|
|
211
|
+
const frontend = getFrontendConfig(c.env);
|
|
212
|
+
if (!frontend) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!c.env.ASSETS) {
|
|
217
|
+
return new Response(
|
|
218
|
+
JSON.stringify({ code: 404, message: assetUnavailableMessage('frontend bundle') }),
|
|
219
|
+
{
|
|
220
|
+
status: 404,
|
|
221
|
+
headers: { 'content-type': 'application/json; charset=UTF-8' },
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const assetRequest = createFrontendAssetRequest(c.req.raw, frontend);
|
|
227
|
+
if (!assetRequest) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const assetResponse = await fetchFrontendAssetResponse(c.env.ASSETS, assetRequest);
|
|
232
|
+
return applyFrontendAssetHeaders(assetResponse, new URL(assetRequest.url).pathname);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
app.get('/', async (c) => {
|
|
167
236
|
const env = c.env as Env;
|
|
237
|
+
const frontendResponse = await serveFrontendAsset({ env, req: c.req });
|
|
238
|
+
if (frontendResponse) {
|
|
239
|
+
return frontendResponse;
|
|
240
|
+
}
|
|
168
241
|
const externalAdminUrl = resolveAdminRedirectTarget(c.req.url, env.ADMIN_ORIGIN);
|
|
169
242
|
if (externalAdminUrl) {
|
|
170
243
|
return c.redirect(externalAdminUrl, 302);
|
|
@@ -181,6 +254,10 @@ async function buildApp() {
|
|
|
181
254
|
|
|
182
255
|
app.get('/favicon.ico', async (c) => {
|
|
183
256
|
const env = c.env as Env;
|
|
257
|
+
const frontendResponse = await serveFrontendAsset({ env, req: c.req });
|
|
258
|
+
if (frontendResponse) {
|
|
259
|
+
return frontendResponse;
|
|
260
|
+
}
|
|
184
261
|
const externalFaviconUrl = resolveAdminFaviconTarget(env.ADMIN_ORIGIN);
|
|
185
262
|
if (externalFaviconUrl) {
|
|
186
263
|
return c.redirect(externalFaviconUrl, 302);
|
|
@@ -197,6 +274,10 @@ async function buildApp() {
|
|
|
197
274
|
|
|
198
275
|
app.get('/favicon.svg', async (c) => {
|
|
199
276
|
const env = c.env as Env;
|
|
277
|
+
const frontendResponse = await serveFrontendAsset({ env, req: c.req });
|
|
278
|
+
if (frontendResponse) {
|
|
279
|
+
return frontendResponse;
|
|
280
|
+
}
|
|
200
281
|
const externalFaviconUrl = resolveAdminFaviconTarget(env.ADMIN_ORIGIN);
|
|
201
282
|
if (externalFaviconUrl) {
|
|
202
283
|
return c.redirect(externalFaviconUrl, 302);
|
|
@@ -275,6 +356,19 @@ async function buildApp() {
|
|
|
275
356
|
return c.json(normalizeOpenApiDocument(spec as OpenApiSpec, new URL(c.req.url).origin));
|
|
276
357
|
});
|
|
277
358
|
|
|
359
|
+
app.on(['GET', 'HEAD'], '*', async (c) => {
|
|
360
|
+
const env = c.env as Env;
|
|
361
|
+
const frontendResponse = await serveFrontendAsset({ env, req: c.req });
|
|
362
|
+
if (frontendResponse) {
|
|
363
|
+
return frontendResponse;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return c.json({
|
|
367
|
+
code: 404,
|
|
368
|
+
message: `Path '${new URL(c.req.url).pathname}' was not found on this EdgeBase server.`,
|
|
369
|
+
}, 404);
|
|
370
|
+
});
|
|
371
|
+
|
|
278
372
|
app.notFound((c) => {
|
|
279
373
|
return c.json({
|
|
280
374
|
code: 404,
|
package/src/lib/admin-assets.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function resolveAdminAssetPath(pathname: string): string {
|
|
2
2
|
if (pathname === '/admin' || pathname === '/admin/') {
|
|
3
|
-
return '/';
|
|
3
|
+
return '/admin/index.html';
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
if (!pathname.startsWith('/admin/')) {
|
|
@@ -9,19 +9,19 @@ export function resolveAdminAssetPath(pathname: string): string {
|
|
|
9
9
|
|
|
10
10
|
const assetPath = pathname.slice('/admin'.length) || '/';
|
|
11
11
|
if (assetPath === '/' || assetPath === '') {
|
|
12
|
-
return '/';
|
|
12
|
+
return '/admin/index.html';
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
if (assetPath.startsWith('/_app/')) {
|
|
16
|
-
return assetPath
|
|
16
|
+
return `/admin${assetPath}`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const lastSegment = assetPath.split('/').pop() ?? '';
|
|
20
20
|
if (lastSegment.includes('.')) {
|
|
21
|
-
return assetPath
|
|
21
|
+
return `/admin${assetPath}`;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
return '/';
|
|
24
|
+
return '/admin/index.html';
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function createAdminAssetRequest(request: Request): Request {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { normalizeFrontendMountPath, type FrontendConfigLike } from './frontend-config.js';
|
|
2
|
+
|
|
3
|
+
interface ResolveFrontendAssetPathOptions {
|
|
4
|
+
method?: string;
|
|
5
|
+
accept?: string | null;
|
|
6
|
+
mountPath?: string;
|
|
7
|
+
spaFallback?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const HTML_ACCEPT_MARKERS = ['text/html', 'application/xhtml+xml'];
|
|
11
|
+
const HASHED_ASSET_PATTERN = /(?:^|[-._])[A-Za-z0-9]{8,}\.[A-Za-z0-9]+$/;
|
|
12
|
+
|
|
13
|
+
function isExplicitAssetPath(pathname: string): boolean {
|
|
14
|
+
const lastSegment = pathname.split('/').pop() ?? '';
|
|
15
|
+
return lastSegment.includes('.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isHtmlNavigationRequest(method: string | undefined, accept: string | null | undefined): boolean {
|
|
19
|
+
if (method && method !== 'GET' && method !== 'HEAD') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!accept) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return HTML_ACCEPT_MARKERS.some((marker) => accept.includes(marker));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function stripMountPath(pathname: string, mountPath: string): string | null {
|
|
31
|
+
if (mountPath === '/') {
|
|
32
|
+
return pathname || '/';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (pathname === mountPath || pathname === `${mountPath}/`) {
|
|
36
|
+
return '/';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!pathname.startsWith(`${mountPath}/`)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return pathname.slice(mountPath.length) || '/';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveFrontendAssetPath(
|
|
47
|
+
pathname: string,
|
|
48
|
+
options: ResolveFrontendAssetPathOptions = {},
|
|
49
|
+
): string | null {
|
|
50
|
+
const mountPath = normalizeFrontendMountPath(options.mountPath);
|
|
51
|
+
const relativePath = stripMountPath(pathname || '/', mountPath);
|
|
52
|
+
if (relativePath === null) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const assetPrefix = mountPath === '/' ? '' : mountPath;
|
|
57
|
+
|
|
58
|
+
if (relativePath === '/' || relativePath === '') {
|
|
59
|
+
return `${assetPrefix}/index.html`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const explicitAssetPath = `${assetPrefix}${relativePath}`;
|
|
63
|
+
if (isExplicitAssetPath(relativePath)) {
|
|
64
|
+
return explicitAssetPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (options.spaFallback && isHtmlNavigationRequest(options.method, options.accept)) {
|
|
68
|
+
return `${assetPrefix}/index.html`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return explicitAssetPath;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createFrontendAssetRequest(
|
|
75
|
+
request: Request,
|
|
76
|
+
config: FrontendConfigLike,
|
|
77
|
+
): Request | null {
|
|
78
|
+
const url = new URL(request.url);
|
|
79
|
+
const pathname = resolveFrontendAssetPath(url.pathname, {
|
|
80
|
+
method: request.method,
|
|
81
|
+
accept: request.headers.get('accept'),
|
|
82
|
+
mountPath: config.mountPath,
|
|
83
|
+
spaFallback: config.spaFallback,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!pathname) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
url.pathname = pathname;
|
|
91
|
+
return new Request(url.toString(), request);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getFrontendCacheControl(pathname: string): string | null {
|
|
95
|
+
const assetName = pathname.split('/').pop() ?? '';
|
|
96
|
+
|
|
97
|
+
if (assetName === 'index.html' || assetName === 'manifest.webmanifest' || assetName === 'sw.js') {
|
|
98
|
+
return 'no-cache';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (HASHED_ASSET_PATTERN.test(assetName)) {
|
|
102
|
+
return 'public, max-age=31536000, immutable';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (isExplicitAssetPath(pathname)) {
|
|
106
|
+
return 'public, max-age=300';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function applyFrontendAssetHeaders(response: Response, pathname: string): Response {
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const cacheControl = getFrontendCacheControl(pathname);
|
|
118
|
+
if (!cacheControl) {
|
|
119
|
+
return response;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const headers = new Headers(response.headers);
|
|
123
|
+
headers.set('Cache-Control', cacheControl);
|
|
124
|
+
return new Response(response.body, {
|
|
125
|
+
status: response.status,
|
|
126
|
+
statusText: response.statusText,
|
|
127
|
+
headers,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface FrontendConfigLike {
|
|
2
|
+
directory: string;
|
|
3
|
+
mountPath?: string;
|
|
4
|
+
spaFallback?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeFrontendMountPath(mountPath: string | undefined): string {
|
|
8
|
+
if (!mountPath) return '/';
|
|
9
|
+
if (mountPath === '/') return '/';
|
|
10
|
+
return mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath;
|
|
11
|
+
}
|
package/src/lib/openapi.ts
CHANGED
|
@@ -47,10 +47,7 @@ const USER_BEARER_PATHS = new Set([
|
|
|
47
47
|
'/api/push/topic/unsubscribe',
|
|
48
48
|
]);
|
|
49
49
|
|
|
50
|
-
const USER_BEARER_PREFIXES = [
|
|
51
|
-
'/api/room/media/realtime/',
|
|
52
|
-
'/api/room/media/cloudflare_realtimekit/',
|
|
53
|
-
];
|
|
50
|
+
const USER_BEARER_PREFIXES: string[] = [];
|
|
54
51
|
|
|
55
52
|
const SERVICE_KEY_ONLY_PATHS = new Set([
|
|
56
53
|
'/api/db/broadcast',
|
package/src/routes/room.ts
CHANGED
|
@@ -65,109 +65,6 @@ const roomSummaryCollectionSchema = z.object({
|
|
|
65
65
|
deniedIds: z.array(z.string()),
|
|
66
66
|
updatedAt: z.string(),
|
|
67
67
|
});
|
|
68
|
-
const roomRealtimeSessionDescriptionSchema = z.object({
|
|
69
|
-
sdp: z.string().openapi({ description: 'WebRTC session description payload' }),
|
|
70
|
-
type: z.enum(['offer', 'answer']).openapi({ description: 'Session description type' }),
|
|
71
|
-
});
|
|
72
|
-
const roomRealtimeTrackSchema = z.object({
|
|
73
|
-
location: z.enum(['local', 'remote']).openapi({ description: 'Track direction relative to the caller' }),
|
|
74
|
-
mid: z.string().optional().openapi({ description: 'WebRTC media ID' }),
|
|
75
|
-
sessionId: z.string().optional().openapi({ description: 'Provider session ID associated with this track' }),
|
|
76
|
-
trackName: z.string().optional().openapi({ description: 'Track name used by the provider' }),
|
|
77
|
-
bidirectionalMediaStream: z.boolean().optional().openapi({ description: 'Whether the track should be bidirectional' }),
|
|
78
|
-
kind: z.string().optional().openapi({ description: 'Track kind reported by the provider' }),
|
|
79
|
-
simulcast: z.object({
|
|
80
|
-
preferredRid: z.string().optional(),
|
|
81
|
-
priorityOrdering: z.enum(['none', 'asciibetical']).optional(),
|
|
82
|
-
ridNotAvailable: z.enum(['none', 'asciibetical']).optional(),
|
|
83
|
-
}).optional().openapi({ description: 'Optional simulcast preferences' }),
|
|
84
|
-
errorCode: z.string().optional().openapi({ description: 'Provider-level error code for this track' }),
|
|
85
|
-
errorDescription: z.string().optional().openapi({ description: 'Provider-level error description for this track' }),
|
|
86
|
-
});
|
|
87
|
-
const roomRealtimeCreateSessionBodySchema = z.object({
|
|
88
|
-
connectionId: z.string().optional().openapi({ description: 'Specific room connection ID to bind the realtime session to' }),
|
|
89
|
-
correlationId: z.string().optional().openapi({ description: 'Optional provider correlation ID' }),
|
|
90
|
-
thirdparty: z.boolean().optional().openapi({ description: 'Forward Cloudflare Realtime thirdparty mode' }),
|
|
91
|
-
sessionDescription: roomRealtimeSessionDescriptionSchema.optional(),
|
|
92
|
-
});
|
|
93
|
-
const roomCloudflareRealtimeKitCreateSessionBodySchema = z.object({
|
|
94
|
-
connectionId: z.string().optional().openapi({ description: 'Specific room connection ID to bind the Cloudflare RealtimeKit participant to' }),
|
|
95
|
-
customParticipantId: z.string().optional().openapi({ description: 'Optional custom participant identifier for the provisioned RealtimeKit participant' }),
|
|
96
|
-
name: z.string().optional().openapi({ description: 'Optional display name for the provisioned RealtimeKit participant' }),
|
|
97
|
-
picture: z.string().optional().openapi({ description: 'Optional avatar URL for the provisioned RealtimeKit participant' }),
|
|
98
|
-
});
|
|
99
|
-
const roomRealtimeCreateSessionResponseSchema = z.object({
|
|
100
|
-
sessionId: z.string().openapi({ description: 'Realtime provider session ID' }),
|
|
101
|
-
sessionDescription: roomRealtimeSessionDescriptionSchema.optional(),
|
|
102
|
-
errorCode: z.string().optional(),
|
|
103
|
-
errorDescription: z.string().optional(),
|
|
104
|
-
connectionId: z.string().optional().openapi({ description: 'Room connection ID associated with the session' }),
|
|
105
|
-
reused: z.boolean().optional().openapi({ description: 'Whether an existing provider session was reused' }),
|
|
106
|
-
});
|
|
107
|
-
const roomCloudflareRealtimeKitCreateSessionResponseSchema = z.object({
|
|
108
|
-
sessionId: z.string().openapi({ description: 'Cloudflare RealtimeKit participant ID' }),
|
|
109
|
-
meetingId: z.string().openapi({ description: 'Cloudflare RealtimeKit meeting ID backing the room session' }),
|
|
110
|
-
participantId: z.string().openapi({ description: 'Cloudflare RealtimeKit participant ID' }),
|
|
111
|
-
authToken: z.string().openapi({ description: 'RealtimeKit auth token for the provisioned participant' }),
|
|
112
|
-
presetName: z.string().optional().openapi({ description: 'RealtimeKit preset used for the provisioned participant' }),
|
|
113
|
-
connectionId: z.string().optional().openapi({ description: 'Room connection ID associated with the session' }),
|
|
114
|
-
reused: z.boolean().optional().openapi({ description: 'Whether an existing provider participant was reused' }),
|
|
115
|
-
});
|
|
116
|
-
const roomRealtimeSessionStateSchema = z.object({
|
|
117
|
-
sessionId: z.string().openapi({ description: 'Realtime provider session ID' }),
|
|
118
|
-
connectionId: z.string().optional().openapi({ description: 'Room connection ID associated with the session' }),
|
|
119
|
-
createdAt: z.number().openapi({ description: 'Unix epoch milliseconds when the session was created' }),
|
|
120
|
-
updatedAt: z.number().openapi({ description: 'Unix epoch milliseconds when the session was last updated' }),
|
|
121
|
-
});
|
|
122
|
-
const roomRealtimeIceServerSchema = z.object({
|
|
123
|
-
urls: z.union([z.array(z.string()), z.string()]).openapi({ description: 'ICE server URL or URL list' }),
|
|
124
|
-
username: z.string().optional(),
|
|
125
|
-
credential: z.string().optional(),
|
|
126
|
-
});
|
|
127
|
-
const roomRealtimeIceServersBodySchema = z.object({
|
|
128
|
-
ttl: z.number().optional().openapi({ description: 'Requested TURN credential TTL in seconds' }),
|
|
129
|
-
});
|
|
130
|
-
const roomRealtimeIceServersResponseSchema = z.object({
|
|
131
|
-
iceServers: z.array(roomRealtimeIceServerSchema).openapi({ description: 'ICE servers returned by Cloudflare TURN' }),
|
|
132
|
-
});
|
|
133
|
-
const roomRealtimeTracksResponseSchema = z.object({
|
|
134
|
-
errorCode: z.string().optional(),
|
|
135
|
-
errorDescription: z.string().optional(),
|
|
136
|
-
requiresImmediateRenegotiation: z.boolean().optional(),
|
|
137
|
-
sessionDescription: roomRealtimeSessionDescriptionSchema.optional(),
|
|
138
|
-
tracks: z.array(roomRealtimeTrackSchema).optional(),
|
|
139
|
-
});
|
|
140
|
-
const roomRealtimeTracksBodySchema = z.object({
|
|
141
|
-
sessionId: z.string().openapi({ description: 'Realtime provider session ID' }),
|
|
142
|
-
connectionId: z.string().optional().openapi({ description: 'Specific room connection ID to bind the track operation to' }),
|
|
143
|
-
sessionDescription: roomRealtimeSessionDescriptionSchema.optional(),
|
|
144
|
-
tracks: z.array(roomRealtimeTrackSchema).min(1).openapi({ description: 'Tracks to create or subscribe to' }),
|
|
145
|
-
autoDiscover: z.boolean().optional().openapi({ description: 'Ask the provider to auto-discover remote tracks' }),
|
|
146
|
-
publish: z.object({
|
|
147
|
-
kind: z.enum(['audio', 'video', 'screen']).optional(),
|
|
148
|
-
trackId: z.string().optional(),
|
|
149
|
-
deviceId: z.string().optional(),
|
|
150
|
-
muted: z.boolean().optional(),
|
|
151
|
-
}).optional().openapi({ description: 'Optional room media state updates to apply after track creation' }),
|
|
152
|
-
});
|
|
153
|
-
const roomRealtimeRenegotiateBodySchema = z.object({
|
|
154
|
-
sessionId: z.string().openapi({ description: 'Realtime provider session ID' }),
|
|
155
|
-
connectionId: z.string().optional().openapi({ description: 'Specific room connection ID to bind the renegotiation to' }),
|
|
156
|
-
sessionDescription: roomRealtimeSessionDescriptionSchema,
|
|
157
|
-
});
|
|
158
|
-
const roomRealtimeCloseTracksBodySchema = z.object({
|
|
159
|
-
sessionId: z.string().openapi({ description: 'Realtime provider session ID' }),
|
|
160
|
-
connectionId: z.string().optional().openapi({ description: 'Specific room connection ID to bind the close operation to' }),
|
|
161
|
-
sessionDescription: roomRealtimeSessionDescriptionSchema.optional(),
|
|
162
|
-
tracks: z.array(z.object({
|
|
163
|
-
mid: z.string().openapi({ description: 'Track MID to close' }),
|
|
164
|
-
})).min(1).openapi({ description: 'Tracks to close' }),
|
|
165
|
-
force: z.boolean().optional().openapi({ description: 'Force close even if the provider reports the track as active' }),
|
|
166
|
-
unpublish: z.object({
|
|
167
|
-
kind: z.enum(['audio', 'video', 'screen']).optional(),
|
|
168
|
-
}).optional().openapi({ description: 'Optional room media state cleanup after closing tracks' }),
|
|
169
|
-
});
|
|
170
|
-
|
|
171
68
|
function isRoomOperationPublic(
|
|
172
69
|
namespaceConfig: RoomNamespaceConfig | null | undefined,
|
|
173
70
|
operation: 'metadata' | 'join' | 'action',
|
|
@@ -705,185 +602,3 @@ roomRoute.openapi(getRoomSummaries, async (c) => {
|
|
|
705
602
|
updatedAt: new Date().toISOString(),
|
|
706
603
|
});
|
|
707
604
|
});
|
|
708
|
-
|
|
709
|
-
const getRoomRealtimeSession = createRoute({
|
|
710
|
-
operationId: 'getRoomRealtimeSession',
|
|
711
|
-
method: 'get',
|
|
712
|
-
path: '/media/realtime/session',
|
|
713
|
-
tags: ['client'],
|
|
714
|
-
summary: 'Get the active room realtime media session',
|
|
715
|
-
description: 'Returns the provider session currently bound to the authenticated room member.',
|
|
716
|
-
request: {
|
|
717
|
-
query: roomQuerySchema.extend({
|
|
718
|
-
connectionId: z.string().optional().openapi({ description: 'Optional room connection ID override' }),
|
|
719
|
-
}),
|
|
720
|
-
},
|
|
721
|
-
responses: {
|
|
722
|
-
200: { description: 'Active room realtime session', content: { 'application/json': { schema: roomRealtimeSessionStateSchema } } },
|
|
723
|
-
400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
724
|
-
401: { description: 'Authentication required', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
725
|
-
403: { description: 'Forbidden', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
726
|
-
404: { description: 'No active session or runtime not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
727
|
-
},
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
const createRoomRealtimeSession = createRoute({
|
|
731
|
-
operationId: 'createRoomRealtimeSession',
|
|
732
|
-
method: 'post',
|
|
733
|
-
path: '/media/realtime/session',
|
|
734
|
-
tags: ['client'],
|
|
735
|
-
summary: 'Create a room realtime media session',
|
|
736
|
-
description: 'Creates a Cloudflare Realtime session for the authenticated room member.',
|
|
737
|
-
request: {
|
|
738
|
-
query: roomQuerySchema,
|
|
739
|
-
body: { content: { 'application/json': { schema: roomRealtimeCreateSessionBodySchema } }, required: false },
|
|
740
|
-
},
|
|
741
|
-
responses: {
|
|
742
|
-
200: { description: 'Realtime session created', content: { 'application/json': { schema: roomRealtimeCreateSessionResponseSchema } } },
|
|
743
|
-
400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
744
|
-
401: { description: 'Authentication required', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
745
|
-
403: { description: 'Forbidden', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
746
|
-
404: { description: 'Room runtime not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
747
|
-
409: { description: 'Conflicting existing published media', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
748
|
-
},
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
const createRoomCloudflareRealtimeKitSession = createRoute({
|
|
752
|
-
operationId: 'createRoomCloudflareRealtimeKitSession',
|
|
753
|
-
method: 'post',
|
|
754
|
-
path: '/media/cloudflare_realtimekit/session',
|
|
755
|
-
tags: ['client'],
|
|
756
|
-
summary: 'Create a room Cloudflare RealtimeKit session',
|
|
757
|
-
description: 'Creates a Cloudflare RealtimeKit session for the authenticated room member.',
|
|
758
|
-
request: {
|
|
759
|
-
query: roomQuerySchema,
|
|
760
|
-
body: { content: { 'application/json': { schema: roomCloudflareRealtimeKitCreateSessionBodySchema } }, required: false },
|
|
761
|
-
},
|
|
762
|
-
responses: {
|
|
763
|
-
200: { description: 'Cloudflare RealtimeKit session created', content: { 'application/json': { schema: roomCloudflareRealtimeKitCreateSessionResponseSchema } } },
|
|
764
|
-
400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
765
|
-
401: { description: 'Authentication required', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
766
|
-
403: { description: 'Forbidden', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
767
|
-
404: { description: 'Room runtime not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
768
|
-
409: { description: 'Conflicting existing published media', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
769
|
-
},
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
const createRoomRealtimeIceServers = createRoute({
|
|
773
|
-
operationId: 'createRoomRealtimeIceServers',
|
|
774
|
-
method: 'post',
|
|
775
|
-
path: '/media/realtime/turn',
|
|
776
|
-
tags: ['client'],
|
|
777
|
-
summary: 'Generate TURN / ICE credentials for room realtime media',
|
|
778
|
-
description: 'Generates ICE server credentials for the authenticated room member.',
|
|
779
|
-
request: {
|
|
780
|
-
query: roomQuerySchema,
|
|
781
|
-
body: { content: { 'application/json': { schema: roomRealtimeIceServersBodySchema } }, required: false },
|
|
782
|
-
},
|
|
783
|
-
responses: {
|
|
784
|
-
200: { description: 'ICE servers generated', content: { 'application/json': { schema: roomRealtimeIceServersResponseSchema } } },
|
|
785
|
-
400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
786
|
-
401: { description: 'Authentication required', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
787
|
-
403: { description: 'Forbidden', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
788
|
-
404: { description: 'Room runtime not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
789
|
-
},
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
const addRoomRealtimeTracks = createRoute({
|
|
793
|
-
operationId: 'addRoomRealtimeTracks',
|
|
794
|
-
method: 'post',
|
|
795
|
-
path: '/media/realtime/tracks/new',
|
|
796
|
-
tags: ['client'],
|
|
797
|
-
summary: 'Add realtime media tracks to a room session',
|
|
798
|
-
description: 'Creates or subscribes realtime tracks for the authenticated room member.',
|
|
799
|
-
request: {
|
|
800
|
-
query: roomQuerySchema,
|
|
801
|
-
body: { content: { 'application/json': { schema: roomRealtimeTracksBodySchema } }, required: true },
|
|
802
|
-
},
|
|
803
|
-
responses: {
|
|
804
|
-
200: { description: 'Realtime tracks updated', content: { 'application/json': { schema: roomRealtimeTracksResponseSchema } } },
|
|
805
|
-
400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
806
|
-
401: { description: 'Authentication required', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
807
|
-
403: { description: 'Forbidden', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
808
|
-
404: { description: 'Room runtime not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
809
|
-
},
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
const renegotiateRoomRealtimeSession = createRoute({
|
|
813
|
-
operationId: 'renegotiateRoomRealtimeSession',
|
|
814
|
-
method: 'put',
|
|
815
|
-
path: '/media/realtime/renegotiate',
|
|
816
|
-
tags: ['client'],
|
|
817
|
-
summary: 'Renegotiate a room realtime media session',
|
|
818
|
-
description: 'Submits a new session description for an existing room realtime media session.',
|
|
819
|
-
request: {
|
|
820
|
-
query: roomQuerySchema,
|
|
821
|
-
body: { content: { 'application/json': { schema: roomRealtimeRenegotiateBodySchema } }, required: true },
|
|
822
|
-
},
|
|
823
|
-
responses: {
|
|
824
|
-
200: { description: 'Realtime session renegotiated', content: { 'application/json': { schema: roomRealtimeTracksResponseSchema } } },
|
|
825
|
-
400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
826
|
-
401: { description: 'Authentication required', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
827
|
-
403: { description: 'Forbidden', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
828
|
-
404: { description: 'Room runtime not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
829
|
-
},
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
const closeRoomRealtimeTracks = createRoute({
|
|
833
|
-
operationId: 'closeRoomRealtimeTracks',
|
|
834
|
-
method: 'put',
|
|
835
|
-
path: '/media/realtime/tracks/close',
|
|
836
|
-
tags: ['client'],
|
|
837
|
-
summary: 'Close room realtime media tracks',
|
|
838
|
-
description: 'Closes provider tracks for the authenticated room member and optionally unpublishes room media state.',
|
|
839
|
-
request: {
|
|
840
|
-
query: roomQuerySchema,
|
|
841
|
-
body: { content: { 'application/json': { schema: roomRealtimeCloseTracksBodySchema } }, required: true },
|
|
842
|
-
},
|
|
843
|
-
responses: {
|
|
844
|
-
200: { description: 'Realtime tracks closed', content: { 'application/json': { schema: roomRealtimeTracksResponseSchema } } },
|
|
845
|
-
400: { description: 'Bad request', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
846
|
-
401: { description: 'Authentication required', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
847
|
-
403: { description: 'Forbidden', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
848
|
-
404: { description: 'Room runtime not found', content: { 'application/json': { schema: errorResponseSchema } } },
|
|
849
|
-
},
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
roomRoute.openapi(getRoomRealtimeSession, async (c) =>
|
|
853
|
-
proxyRoomDoRequest(c, '/media/realtime/session', 'GET', { requireAuth: true }));
|
|
854
|
-
|
|
855
|
-
roomRoute.openapi(createRoomRealtimeSession, async (c) =>
|
|
856
|
-
proxyRoomDoRequest(c, '/media/realtime/session', 'POST', {
|
|
857
|
-
requireAuth: true,
|
|
858
|
-
validatedJson: c.req.valid('json'),
|
|
859
|
-
}));
|
|
860
|
-
|
|
861
|
-
roomRoute.openapi(createRoomRealtimeIceServers, async (c) =>
|
|
862
|
-
proxyRoomDoRequest(c, '/media/realtime/turn', 'POST', {
|
|
863
|
-
requireAuth: true,
|
|
864
|
-
validatedJson: c.req.valid('json'),
|
|
865
|
-
}));
|
|
866
|
-
|
|
867
|
-
roomRoute.openapi(addRoomRealtimeTracks, async (c) =>
|
|
868
|
-
proxyRoomDoRequest(c, '/media/realtime/tracks/new', 'POST', {
|
|
869
|
-
requireAuth: true,
|
|
870
|
-
validatedJson: c.req.valid('json'),
|
|
871
|
-
}));
|
|
872
|
-
|
|
873
|
-
roomRoute.openapi(renegotiateRoomRealtimeSession, async (c) =>
|
|
874
|
-
proxyRoomDoRequest(c, '/media/realtime/renegotiate', 'PUT', {
|
|
875
|
-
requireAuth: true,
|
|
876
|
-
validatedJson: c.req.valid('json'),
|
|
877
|
-
}));
|
|
878
|
-
|
|
879
|
-
roomRoute.openapi(closeRoomRealtimeTracks, async (c) =>
|
|
880
|
-
proxyRoomDoRequest(c, '/media/realtime/tracks/close', 'PUT', {
|
|
881
|
-
requireAuth: true,
|
|
882
|
-
validatedJson: c.req.valid('json'),
|
|
883
|
-
}));
|
|
884
|
-
|
|
885
|
-
roomRoute.openapi(createRoomCloudflareRealtimeKitSession, async (c) =>
|
|
886
|
-
proxyRoomDoRequest(c, '/media/cloudflare_realtimekit/session', 'POST', {
|
|
887
|
-
requireAuth: true,
|
|
888
|
-
validatedJson: c.req.valid('json'),
|
|
889
|
-
}));
|
package/src/types.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface Env {
|
|
|
7
7
|
DATABASE: DurableObjectNamespace;
|
|
8
8
|
AUTH: DurableObjectNamespace;
|
|
9
9
|
DATABASE_LIVE: DurableObjectNamespace;
|
|
10
|
-
/** Room DO — per-room state synchronization, members, signals
|
|
10
|
+
/** Room DO — per-room state synchronization, members, and signals */
|
|
11
11
|
ROOMS: DurableObjectNamespace;
|
|
12
12
|
|
|
13
13
|
// ─── R2 Storage ───
|
|
@@ -66,19 +66,6 @@ export interface Env {
|
|
|
66
66
|
TURNSTILE_SECRET?: string;
|
|
67
67
|
/** Turnstile site key — public, returned to clients via GET /api/config (§34) */
|
|
68
68
|
CAPTCHA_SITE_KEY?: string;
|
|
69
|
-
/** Cloudflare Realtime app ID for SFU session control. */
|
|
70
|
-
CF_REALTIME_APP_ID?: string;
|
|
71
|
-
/** Cloudflare RealtimeKit preset name used when creating participant tokens. */
|
|
72
|
-
CF_REALTIME_PRESET_NAME?: string;
|
|
73
|
-
/** Cloudflare Realtime app secret for SFU session control. */
|
|
74
|
-
CF_REALTIME_APP_SECRET?: string;
|
|
75
|
-
/** Optional override for the Cloudflare Realtime API base URL. */
|
|
76
|
-
CF_REALTIME_BASE_URL?: string;
|
|
77
|
-
/** Cloudflare TURN key ID used to mint short-lived ICE credentials. */
|
|
78
|
-
CF_REALTIME_TURN_KEY_ID?: string;
|
|
79
|
-
/** Cloudflare TURN API token returned when the TURN key is created. */
|
|
80
|
-
CF_REALTIME_TURN_API_TOKEN?: string;
|
|
81
|
-
|
|
82
69
|
// ─── Environment Identification ───
|
|
83
70
|
/** Server environment name for Service Key constraints.env evaluation */
|
|
84
71
|
ENVIRONMENT?: string;
|