xxscreeps-mod-client 0.3.6 → 0.3.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/backend.js +58 -32
- package/package.json +2 -2
package/backend.js
CHANGED
|
@@ -58,17 +58,17 @@ function normalizeMount(input) {
|
|
|
58
58
|
const mountPath = normalizeMount(process.env.SCREEPS_MOD_CLIENT_MOUNT_PATH ?? '/')
|
|
59
59
|
const rootRedirect = readBool('SCREEPS_MOD_CLIENT_ROOT_REDIRECT', mountPath !== '/')
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
// Top-level client SPA routes. A path that isn't a real file in dist/ but matches
|
|
62
|
+
// one of these is a client-side deep link (or a reload of one) and gets the SPA
|
|
63
|
+
// shell; every other path is handed to xxscreeps, so its API/website routes are
|
|
64
|
+
// never shadowed and genuinely-unknown paths get a real 404 instead of the shell.
|
|
65
|
+
// Mirrors the route table in screeps-client/src/stores/routeStore.ts +
|
|
66
|
+
// utils/gameRoutes.ts — keep in sync when the client gains a new top-level route.
|
|
67
|
+
const SPA_ROUTES = ['/user', '/profile', '/messages', '/market', '/room-overview', '/map', '/room']
|
|
68
|
+
|
|
69
|
+
function isSpaRoute(relPath) {
|
|
70
|
+
return SPA_ROUTES.some(prefix => relPath === prefix || relPath.startsWith(prefix + '/'))
|
|
71
|
+
}
|
|
72
72
|
|
|
73
73
|
function resolveFile(relPath) {
|
|
74
74
|
const rel = relPath.replace(/^\/+/, '')
|
|
@@ -90,15 +90,46 @@ function sendFile(ctx, filePath, stat) {
|
|
|
90
90
|
ctx.body = createReadStream(filePath)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function
|
|
93
|
+
function jsonForScript(value) {
|
|
94
|
+
return JSON.stringify(value).replace(/</g, '\\u003c')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The client fetches `/api/version` on load (pre-login and again post-connect) to
|
|
98
|
+
// configure itself: welcome text, shards, history settings, and the auth/feature
|
|
99
|
+
// gates. Since the server already has this on hand when it renders index.html, we
|
|
100
|
+
// prefetch it once in-process and inline it as `window.__SCREEPS_BOOTSTRAP__`, so
|
|
101
|
+
// the embedded client is configured from the first frame with zero round-trips.
|
|
102
|
+
// Cached like the client's own 5-min version cache; failures just omit the global
|
|
103
|
+
// and the client falls back to its normal fetch.
|
|
104
|
+
const VERSION_TTL_MS = 5 * 60_000
|
|
105
|
+
let versionCache = null
|
|
106
|
+
|
|
107
|
+
async function bootstrapVersion(ctx) {
|
|
108
|
+
const now = Date.now()
|
|
109
|
+
if (versionCache && now < versionCache.expires) return versionCache.data
|
|
110
|
+
try {
|
|
111
|
+
const origin = `${ctx.protocol}://${ctx.host}`
|
|
112
|
+
const res = await fetch(`${origin}/api/version`)
|
|
113
|
+
if (!res.ok) return null
|
|
114
|
+
const data = await res.json()
|
|
115
|
+
versionCache = { data, expires: now + VERSION_TTL_MS }
|
|
116
|
+
return data
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error('[xxscreeps-mod-client] failed to prefetch /api/version for bootstrap:', err)
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderInjectedIndex(filePath, version) {
|
|
94
124
|
const mountDisplay = mountPath === '/' ? '/' : mountPath + '/'
|
|
95
125
|
const baseTag = `<base href="${mountDisplay}">`
|
|
96
|
-
const metadata =
|
|
126
|
+
const metadata = jsonForScript({
|
|
97
127
|
kind: 'xxscreeps-mod',
|
|
98
128
|
packageName: pkg.name,
|
|
99
129
|
version: pkg.version,
|
|
100
|
-
})
|
|
101
|
-
const
|
|
130
|
+
})
|
|
131
|
+
const bootstrap = version ? `;window.__SCREEPS_BOOTSTRAP__=${jsonForScript(version)}` : ''
|
|
132
|
+
const script = `<script>window.__SCREEPS_CLIENT_EMBEDDED__=${metadata}${bootstrap}</script>`
|
|
102
133
|
let html = readFileSync(filePath, 'utf8')
|
|
103
134
|
// Inject base tag first so relative asset URLs resolve from the mount root,
|
|
104
135
|
// not from the current SPA route (e.g. /room/E11N2).
|
|
@@ -106,10 +137,11 @@ function renderInjectedIndex(filePath) {
|
|
|
106
137
|
return html.includes('</head>') ? html.replace('</head>', `${script}</head>`) : html + script
|
|
107
138
|
}
|
|
108
139
|
|
|
109
|
-
function sendInjectedIndex(ctx) {
|
|
140
|
+
async function sendInjectedIndex(ctx) {
|
|
141
|
+
const version = await bootstrapVersion(ctx)
|
|
110
142
|
ctx.type = 'text/html'
|
|
111
143
|
ctx.set('Cache-Control', REVALIDATE_CACHE)
|
|
112
|
-
ctx.body = renderInjectedIndex(indexFile)
|
|
144
|
+
ctx.body = renderInjectedIndex(indexFile, version)
|
|
113
145
|
}
|
|
114
146
|
|
|
115
147
|
// Advertise the server's guest/registration/Steam settings at `/api/version` so
|
|
@@ -135,9 +167,6 @@ hooks.register('middleware', koa => {
|
|
|
135
167
|
|
|
136
168
|
const mountDisplay = mountPath === '/' ? '/' : mountPath + '/'
|
|
137
169
|
console.log(`[xxscreeps-mod-client] serving client at ${mountDisplay} (rootRedirect=${rootRedirect})`)
|
|
138
|
-
if (mountPath === '/') {
|
|
139
|
-
console.log(`[xxscreeps-mod-client] excluded prefixes: ${excludePrefixes.join(', ')}`)
|
|
140
|
-
}
|
|
141
170
|
|
|
142
171
|
koa.use(async (ctx, next) => {
|
|
143
172
|
if (ctx.method !== 'GET' && ctx.method !== 'HEAD') return next()
|
|
@@ -149,8 +178,6 @@ hooks.register('middleware', koa => {
|
|
|
149
178
|
|
|
150
179
|
let relPath
|
|
151
180
|
if (mountPath === '/') {
|
|
152
|
-
// When mounted at root, skip paths that belong to the game server.
|
|
153
|
-
if (excludePrefixes.some(p => ctx.path.startsWith(p))) return next()
|
|
154
181
|
relPath = ctx.path
|
|
155
182
|
} else if (ctx.path === mountPath || ctx.path === mountPath + '/') {
|
|
156
183
|
relPath = '/'
|
|
@@ -161,7 +188,7 @@ hooks.register('middleware', koa => {
|
|
|
161
188
|
}
|
|
162
189
|
|
|
163
190
|
if (relPath === '/' || relPath === '/index.html') {
|
|
164
|
-
sendInjectedIndex(ctx)
|
|
191
|
+
await sendInjectedIndex(ctx)
|
|
165
192
|
return
|
|
166
193
|
}
|
|
167
194
|
|
|
@@ -172,14 +199,13 @@ hooks.register('middleware', koa => {
|
|
|
172
199
|
return
|
|
173
200
|
}
|
|
174
201
|
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
sendInjectedIndex(ctx)
|
|
202
|
+
// Not a real file: claim only the client's own SPA routes (deep links and
|
|
203
|
+
// reloads) with the index shell, and leave every other path to xxscreeps.
|
|
204
|
+
if (isSpaRoute(relPath)) {
|
|
205
|
+
await sendInjectedIndex(ctx)
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return next()
|
|
184
210
|
})
|
|
185
211
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xxscreeps-mod-client",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "xxscreeps mod that serves the screeps-client and connects it to the same server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"xxscreeps": true,
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"node": ">=20"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"screeps-client": "^0.
|
|
26
|
+
"screeps-client": "^0.18.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"xxscreeps": "*"
|