playcademy 0.18.0 → 0.18.2
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/cli.js +1 -1
- package/dist/index.d.ts +24 -12
- package/dist/index.js +465 -255
- package/dist/utils.js +558 -351
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/constants/src/achievements.ts +0 -107
- package/dist/constants/src/auth.ts +0 -13
- package/dist/constants/src/character.ts +0 -16
- package/dist/constants/src/domains.ts +0 -50
- package/dist/constants/src/env-vars.ts +0 -20
- package/dist/constants/src/index.ts +0 -18
- package/dist/constants/src/overworld.ts +0 -330
- package/dist/constants/src/system.ts +0 -10
- package/dist/constants/src/timeback.ts +0 -118
- package/dist/constants/src/typescript.ts +0 -21
- package/dist/constants/src/workers.ts +0 -36
- package/dist/edge-play/src/constants.ts +0 -27
- package/dist/edge-play/src/entry/middleware.ts +0 -247
- package/dist/edge-play/src/entry/queue.test.ts +0 -279
- package/dist/edge-play/src/entry/queue.ts +0 -107
- package/dist/edge-play/src/entry/session.ts +0 -45
- package/dist/edge-play/src/entry/setup.ts +0 -78
- package/dist/edge-play/src/entry/types.ts +0 -30
- package/dist/edge-play/src/entry.ts +0 -94
- package/dist/edge-play/src/html.d.ts +0 -5
- package/dist/edge-play/src/index.ts +0 -4
- package/dist/edge-play/src/lib/errors.ts +0 -51
- package/dist/edge-play/src/lib/index.ts +0 -3
- package/dist/edge-play/src/lib/self-dispatch.test.ts +0 -244
- package/dist/edge-play/src/lib/self-dispatch.ts +0 -41
- package/dist/edge-play/src/lib/validation.test.ts +0 -190
- package/dist/edge-play/src/lib/validation.ts +0 -64
- package/dist/edge-play/src/polyfills.js +0 -54
- package/dist/edge-play/src/register-routes.ts +0 -59
- package/dist/edge-play/src/routes/health.ts +0 -104
- package/dist/edge-play/src/routes/index.ts +0 -66
- package/dist/edge-play/src/routes/integrations/timeback/end-activity.ts +0 -181
- package/dist/edge-play/src/routes/integrations/timeback/get-xp.ts +0 -159
- package/dist/edge-play/src/routes/root.html +0 -253
- package/dist/edge-play/src/routes/root.ts +0 -22
- package/dist/edge-play/src/stub-entry.ts +0 -161
- package/dist/edge-play/src/types.ts +0 -124
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TimeBack Integration Constants
|
|
3
|
-
*
|
|
4
|
-
* Constants related to TimeBack Caliper integration.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* TimeBack LTI issuer URLs by environment
|
|
9
|
-
* Used for JWT token verification (iss claim)
|
|
10
|
-
*/
|
|
11
|
-
export const TIMEBACK_LTI_ISSUER_URLS = {
|
|
12
|
-
production: 'https://timeback.com',
|
|
13
|
-
staging: 'https://staging.timeback.com',
|
|
14
|
-
} as const
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* TimeBack Caliper sensor URLs
|
|
18
|
-
* Allowed sensor URLs for Caliper event validation
|
|
19
|
-
*/
|
|
20
|
-
export const TIMEBACK_CALIPER_SENSORS = [
|
|
21
|
-
'https://samuraimath.com',
|
|
22
|
-
'https://app.alphamath.school',
|
|
23
|
-
'https://mathraiders.com',
|
|
24
|
-
] as const
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* TimeBack integration route paths (on game backend)
|
|
28
|
-
* These routes are available on game backends when TimeBack is enabled
|
|
29
|
-
*
|
|
30
|
-
* NOTE:
|
|
31
|
-
* These paths are relative to /api/ - the full paths are /api/integrations/timeback/*
|
|
32
|
-
*/
|
|
33
|
-
export const TIMEBACK_ROUTES = {
|
|
34
|
-
END_ACTIVITY: '/integrations/timeback/end-activity',
|
|
35
|
-
GET_XP: '/integrations/timeback/xp',
|
|
36
|
-
} as const
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* TimeBack OneRoster organization identifier
|
|
40
|
-
* This is the default organization sourcedId for Playcademy games
|
|
41
|
-
*/
|
|
42
|
-
export const TIMEBACK_ORG_SOURCED_ID = 'PLAYCADEMY' as const
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Default organization name for Playcademy games
|
|
46
|
-
*/
|
|
47
|
-
export const TIMEBACK_ORG_NAME = 'Playcademy Studios' as const
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Default organization type for Playcademy games
|
|
51
|
-
*/
|
|
52
|
-
export const TIMEBACK_ORG_TYPE = 'department' as const
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// TimeBack Resource Defaults
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Default course configuration values
|
|
60
|
-
*/
|
|
61
|
-
export const TIMEBACK_COURSE_DEFAULTS = {
|
|
62
|
-
gradingScheme: 'STANDARD',
|
|
63
|
-
level: {
|
|
64
|
-
elementary: 'Elementary',
|
|
65
|
-
middle: 'Middle',
|
|
66
|
-
high: 'High',
|
|
67
|
-
ap: 'AP',
|
|
68
|
-
},
|
|
69
|
-
goals: {
|
|
70
|
-
dailyXp: 50,
|
|
71
|
-
dailyLessons: 3,
|
|
72
|
-
},
|
|
73
|
-
metrics: {
|
|
74
|
-
totalXp: 1000,
|
|
75
|
-
totalLessons: 50,
|
|
76
|
-
},
|
|
77
|
-
} as const
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Default resource configuration values
|
|
81
|
-
*/
|
|
82
|
-
export const TIMEBACK_RESOURCE_DEFAULTS = {
|
|
83
|
-
vendorId: 'playcademy',
|
|
84
|
-
roles: ['primary'] as const,
|
|
85
|
-
importance: 'primary' as const,
|
|
86
|
-
metadata: {
|
|
87
|
-
type: 'interactive' as const,
|
|
88
|
-
toolProvider: 'Playcademy',
|
|
89
|
-
instructionalMethod: 'exploratory' as const,
|
|
90
|
-
language: 'en-US',
|
|
91
|
-
},
|
|
92
|
-
} as const
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Default component configuration values
|
|
96
|
-
*/
|
|
97
|
-
export const TIMEBACK_COMPONENT_DEFAULTS = {
|
|
98
|
-
sortOrder: 1,
|
|
99
|
-
prerequisiteCriteria: 'ALL' as const,
|
|
100
|
-
} as const
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Default componentResource configuration values
|
|
104
|
-
*/
|
|
105
|
-
export const TIMEBACK_COMPONENT_RESOURCE_DEFAULTS = {
|
|
106
|
-
sortOrder: 1,
|
|
107
|
-
lessonType: 'quiz',
|
|
108
|
-
} as const
|
|
109
|
-
|
|
110
|
-
// ============================================================================
|
|
111
|
-
// 2HL (Two-Hour Learning) Model
|
|
112
|
-
// ============================================================================
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Daily XP threshold for the 2HL model.
|
|
116
|
-
* Students must earn this much XP daily to unlock non-enrolled games.
|
|
117
|
-
*/
|
|
118
|
-
export const DAILY_XP_THRESHOLD = 120
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript tooling configuration.
|
|
3
|
-
*
|
|
4
|
-
* Centralizes the TypeScript runner used for workspace type checking.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export const TypeScriptPackages = {
|
|
8
|
-
tsc: 'tsc',
|
|
9
|
-
nativePreview: '@typescript/native-preview',
|
|
10
|
-
nativePreviewPinned: '@typescript/native-preview@7.0.0-dev.20260221.1',
|
|
11
|
-
} as const
|
|
12
|
-
|
|
13
|
-
type TypeScriptRunner = Readonly<{
|
|
14
|
-
package: (typeof TypeScriptPackages)[keyof typeof TypeScriptPackages]
|
|
15
|
-
bin: 'tsc' | 'tsgo'
|
|
16
|
-
}>
|
|
17
|
-
|
|
18
|
-
export const TYPESCRIPT_RUNNER: TypeScriptRunner = {
|
|
19
|
-
package: TypeScriptPackages.nativePreviewPinned,
|
|
20
|
-
bin: 'tsgo',
|
|
21
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker Naming Constants
|
|
3
|
-
*
|
|
4
|
-
* Conventions for naming Cloudflare workers to avoid collisions
|
|
5
|
-
* between staging and production deployments.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Worker naming patterns
|
|
10
|
-
* - Production workers: {slug} → accessible at {slug}.playcademy.gg
|
|
11
|
-
* - Staging workers: staging-{slug} → accessible at {slug}-staging.playcademy.gg
|
|
12
|
-
*
|
|
13
|
-
* Note: Worker names use a PREFIX (staging-) while hostnames use a SUFFIX (-staging)
|
|
14
|
-
* to follow subdomain naming conventions.
|
|
15
|
-
*/
|
|
16
|
-
export const WORKER_NAMING = {
|
|
17
|
-
/** Prefix for staging worker names (e.g., "staging-bamboo") */
|
|
18
|
-
STAGING_PREFIX: 'staging-',
|
|
19
|
-
/** Suffix for staging worker hostnames (e.g., "bamboo-staging.playcademy.gg") */
|
|
20
|
-
STAGING_SUFFIX: '-staging',
|
|
21
|
-
} as const
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Prefix for user-defined secrets in Cloudflare Worker bindings.
|
|
25
|
-
*
|
|
26
|
-
* Cloudflare bindings are flat, so we namespace user secrets with this prefix
|
|
27
|
-
* to distinguish them from platform bindings (like GAME_ID, PLAYCADEMY_API_KEY).
|
|
28
|
-
*
|
|
29
|
-
* Usage:
|
|
30
|
-
* - When setting secrets: `bindings[SECRETS_PREFIX + key] = value`
|
|
31
|
-
* - When reading secrets: strip prefix and expose as `c.env.secrets.KEY`
|
|
32
|
-
*
|
|
33
|
-
* @see reconstructSecrets in edge-play/entry/setup.ts
|
|
34
|
-
* @see prefixSecrets in edge-play/entry/setup.ts
|
|
35
|
-
*/
|
|
36
|
-
export const SECRETS_PREFIX = 'secrets_'
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Constants for game backend workers
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { TIMEBACK_ROUTES, WORKER_ENV_VARS, WORKER_NAMING } from '@playcademy/constants'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Re-export shared constants from @playcademy/constants
|
|
9
|
-
*/
|
|
10
|
-
export { WORKER_ENV_VARS as ENV_VARS, WORKER_NAMING }
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Built-in API routes
|
|
14
|
-
*/
|
|
15
|
-
export const ROUTES = {
|
|
16
|
-
/** Route index (lists available routes) */
|
|
17
|
-
INDEX: '/api',
|
|
18
|
-
|
|
19
|
-
/** Health check endpoint */
|
|
20
|
-
HEALTH: '/api/health',
|
|
21
|
-
|
|
22
|
-
/** TimeBack integration routes */
|
|
23
|
-
TIMEBACK: {
|
|
24
|
-
END_ACTIVITY: `/api${TIMEBACK_ROUTES.END_ACTIVITY}`,
|
|
25
|
-
GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`,
|
|
26
|
-
},
|
|
27
|
-
} as const
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware Configuration
|
|
3
|
-
*
|
|
4
|
-
* All middleware for the game backend worker
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { cors } from 'hono/cors'
|
|
8
|
-
|
|
9
|
-
import { PlaycademyClient, verifyGameToken } from '@playcademy/sdk/server'
|
|
10
|
-
|
|
11
|
-
import { resolveRawSelfWorker, wrapSelfWorkerWithPathResolution } from '../lib/self-dispatch'
|
|
12
|
-
import { populateProcessEnv, reconstructSecrets } from './setup'
|
|
13
|
-
|
|
14
|
-
import type { Context, Hono } from 'hono'
|
|
15
|
-
|
|
16
|
-
import type { AssetsFetcher, HonoEnv } from '../types'
|
|
17
|
-
import type { RuntimeConfig } from './types'
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Register CORS middleware
|
|
21
|
-
*
|
|
22
|
-
* Allows cross-origin requests with credentials support.
|
|
23
|
-
* This is required for authentication systems like Better Auth that use cookies.
|
|
24
|
-
*
|
|
25
|
-
* TODO: Harden CORS in production - restrict to trusted origins:
|
|
26
|
-
* - Game's deploymentUrl (for hosted games)
|
|
27
|
-
* - Game's externalUrl (for external games)
|
|
28
|
-
* - Platform domains (hub.playcademy.com, hub.dev.playcademy.net)
|
|
29
|
-
*/
|
|
30
|
-
export function registerCors(app: Hono<HonoEnv>): void {
|
|
31
|
-
app.use(
|
|
32
|
-
'*',
|
|
33
|
-
cors({
|
|
34
|
-
origin: origin => origin, // Echo back the origin (permissive but allows credentials)
|
|
35
|
-
credentials: true, // Required for cookies/sessions (e.g., Better Auth)
|
|
36
|
-
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
37
|
-
allowHeaders: ['Content-Type', 'Authorization'],
|
|
38
|
-
// exposeHeaders: ['set-auth-token'],
|
|
39
|
-
}),
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Register environment setup middleware
|
|
45
|
-
*
|
|
46
|
-
* Populates process.env, reconstructs secrets, and sets config
|
|
47
|
-
*/
|
|
48
|
-
export function registerEnvSetup(app: Hono<HonoEnv>, config: RuntimeConfig): void {
|
|
49
|
-
app.use('*', async (c, next) => {
|
|
50
|
-
c.env = { ...c.env }
|
|
51
|
-
populateProcessEnv(c.env)
|
|
52
|
-
c.env.secrets = reconstructSecrets(c.env)
|
|
53
|
-
c.env.SELF = wrapSelfWorkerWithPathResolution(resolveRawSelfWorker(c.env), c.req.url)
|
|
54
|
-
c.set('config', config)
|
|
55
|
-
c.set('routeMetadata', config.__routeMetadata || [])
|
|
56
|
-
await next()
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Register SDK initialization middleware
|
|
62
|
-
*
|
|
63
|
-
* Lazily initializes the Playcademy SDK on first request and
|
|
64
|
-
* makes it available via c.get('sdk')
|
|
65
|
-
*/
|
|
66
|
-
export function registerSdkInit(app: Hono<HonoEnv>, config: RuntimeConfig): void {
|
|
67
|
-
let sdkPromise: Promise<PlaycademyClient> | null = null
|
|
68
|
-
|
|
69
|
-
app.use('*', async (c, next) => {
|
|
70
|
-
if (!sdkPromise) {
|
|
71
|
-
sdkPromise = PlaycademyClient.init({
|
|
72
|
-
apiKey: c.env.PLAYCADEMY_API_KEY,
|
|
73
|
-
gameId: c.env.GAME_ID,
|
|
74
|
-
baseUrl: c.env.PLAYCADEMY_BASE_URL,
|
|
75
|
-
config,
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
c.set('sdk', await sdkPromise)
|
|
80
|
-
await next()
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Register Playcademy user middleware
|
|
86
|
-
*
|
|
87
|
-
* Verifies platform game tokens and populates c.get('playcademyUser')
|
|
88
|
-
* if a valid token is present in the Authorization header
|
|
89
|
-
*/
|
|
90
|
-
export function registerPlaycademyUser(app: Hono<HonoEnv>): void {
|
|
91
|
-
app.use('*', async (c, next) => {
|
|
92
|
-
const authHeader = c.req.header('Authorization')
|
|
93
|
-
|
|
94
|
-
if (authHeader?.startsWith('Bearer ')) {
|
|
95
|
-
const token = authHeader.slice(7)
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const result = await verifyGameToken(token)
|
|
99
|
-
|
|
100
|
-
c.set('playcademyUser', result.user)
|
|
101
|
-
} catch {
|
|
102
|
-
// Invalid/expired token - that's fine, just don't set user
|
|
103
|
-
// Routes can decide if they require authentication
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
await next()
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Register API 404 handler
|
|
113
|
-
*
|
|
114
|
-
* Returns a helpful JSON response for unmatched API routes.
|
|
115
|
-
* Should be registered after all API routes but before asset fallback.
|
|
116
|
-
*/
|
|
117
|
-
export function registerApiNotFoundHandler(app: Hono<HonoEnv>): void {
|
|
118
|
-
app.all('/api/*', async c =>
|
|
119
|
-
c.json(
|
|
120
|
-
{
|
|
121
|
-
error: 'Not Found',
|
|
122
|
-
message: `Route ${c.req.method} ${c.req.path} not found`,
|
|
123
|
-
path: c.req.path,
|
|
124
|
-
method: c.req.method,
|
|
125
|
-
},
|
|
126
|
-
404,
|
|
127
|
-
),
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Detect if ASSETS binding is an R2 bucket or Workers Assets Fetcher
|
|
133
|
-
* Returns true for R2Bucket, false for AssetsFetcher
|
|
134
|
-
*/
|
|
135
|
-
function isR2AssetsBinding(assets: AssetsFetcher | R2Bucket): assets is R2Bucket {
|
|
136
|
-
// R2Bucket has .get() method, Fetcher has .fetch() method
|
|
137
|
-
return 'get' in assets && typeof assets.get === 'function' && !('fetch' in assets)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Serve assets from Workers Assets binding (Fetcher)
|
|
142
|
-
* Default strategy for games with files <25MB
|
|
143
|
-
*/
|
|
144
|
-
async function serveFromWorkersAssets(
|
|
145
|
-
c: Context<HonoEnv>,
|
|
146
|
-
assets: AssetsFetcher,
|
|
147
|
-
): Promise<Response> {
|
|
148
|
-
// Try to fetch the requested asset
|
|
149
|
-
const response = await assets.fetch(c.req.raw)
|
|
150
|
-
|
|
151
|
-
// If found, return it
|
|
152
|
-
if (response.status !== 404) {
|
|
153
|
-
return response
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// If not found and it's a file request (has extension), return 404
|
|
157
|
-
const path = new URL(c.req.url).pathname
|
|
158
|
-
|
|
159
|
-
if (path.includes('.')) {
|
|
160
|
-
return response
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Otherwise, fall back to index.html for SPA routing
|
|
164
|
-
const indexUrl = new URL(c.req.url)
|
|
165
|
-
|
|
166
|
-
indexUrl.pathname = '/index.html'
|
|
167
|
-
|
|
168
|
-
return await assets.fetch(new Request(indexUrl.toString()))
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Serve assets from R2 bucket binding
|
|
173
|
-
* Fallback strategy for games with large files (≥25MB) like Godot WASM
|
|
174
|
-
*/
|
|
175
|
-
async function serveFromR2(c: Context<HonoEnv>, bucket: R2Bucket): Promise<Response> {
|
|
176
|
-
const path = new URL(c.req.url).pathname
|
|
177
|
-
const key = path === '/' ? 'index.html' : path.slice(1)
|
|
178
|
-
|
|
179
|
-
// Try to fetch the requested asset
|
|
180
|
-
const object = await bucket.get(key)
|
|
181
|
-
|
|
182
|
-
if (object) {
|
|
183
|
-
return new Response(object.body, {
|
|
184
|
-
headers: {
|
|
185
|
-
'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
|
|
186
|
-
// Use 1-hour caching - balances performance with update propagation
|
|
187
|
-
// (Godot and other engines use non-hashed filenames)
|
|
188
|
-
'Cache-Control': key === 'index.html' ? 'no-cache' : 'public, max-age=3600',
|
|
189
|
-
ETag: object.etag,
|
|
190
|
-
},
|
|
191
|
-
})
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// If not found and it's a file request (has extension), return 404
|
|
195
|
-
if (path.includes('.')) {
|
|
196
|
-
return c.json({ error: 'Not Found', message: 'Asset not found', path }, 404)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Otherwise, fall back to index.html for SPA routing
|
|
200
|
-
const indexObject = await bucket.get('index.html')
|
|
201
|
-
|
|
202
|
-
if (indexObject) {
|
|
203
|
-
return new Response(indexObject.body, {
|
|
204
|
-
headers: {
|
|
205
|
-
'Content-Type': 'text/html',
|
|
206
|
-
'Cache-Control': 'no-cache',
|
|
207
|
-
},
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return c.json({ error: 'Not Found', message: 'Asset not found', path }, 404)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Register asset fallback handler
|
|
216
|
-
*
|
|
217
|
-
* Serves static assets from either Workers Assets or R2 bucket binding.
|
|
218
|
-
* Automatically detects which binding type is present and routes accordingly.
|
|
219
|
-
* MUST be registered last as it's a catch-all for non-API routes.
|
|
220
|
-
*
|
|
221
|
-
* Supports two deployment strategies:
|
|
222
|
-
* - Workers Assets (default): For games with files <25MB
|
|
223
|
-
* - R2 Bucket (fallback): For games with large files like Godot WASM
|
|
224
|
-
*
|
|
225
|
-
* SPA Routing Support:
|
|
226
|
-
* - First tries to fetch the requested path (e.g., /assets/main.js)
|
|
227
|
-
* - If 404 and NOT an API route, falls back to /index.html
|
|
228
|
-
* - This allows client-side routing (React Router) to work on refresh
|
|
229
|
-
*/
|
|
230
|
-
export function registerAssetFallback(app: Hono<HonoEnv>): void {
|
|
231
|
-
app.get('*', async c => {
|
|
232
|
-
if (!c.env.ASSETS) {
|
|
233
|
-
return c.json(
|
|
234
|
-
{
|
|
235
|
-
error: 'Not Found',
|
|
236
|
-
message: 'Asset not found',
|
|
237
|
-
path: c.req.path,
|
|
238
|
-
},
|
|
239
|
-
404,
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return isR2AssetsBinding(c.env.ASSETS)
|
|
244
|
-
? await serveFromR2(c, c.env.ASSETS)
|
|
245
|
-
: await serveFromWorkersAssets(c, c.env.ASSETS)
|
|
246
|
-
})
|
|
247
|
-
}
|