openvibers/openvibe.games Commits
/shared serves a stylesheet as text/css: the home page's "Your stuff across OpenVibe" panel drew unstyled because its CSS went out as JavaScript (#156)
Changes
@@@ -40,6 +40,14 @@ describe('/shared (D42)', () => {expect(r.status).toBe(304)})++ it('serves a stylesheet as text/css (the profile panel drew unstyled when its CSS went out as JavaScript)', async () => {++ const r = await fetch(`${base}/shared/profile-panel.css`)++ expect(r.status).toBe(200)++ expect(r.headers.get('content-type')).toMatch(/^text\/css/)++ expect(r.headers.get('x-content-type-options')).toBe('nosniff')++ expect((await fetch(`${base}/shared/profile-panel.js`)).headers.get('content-type')).toMatch(/javascript/)++ })++it('serves the web app manifest and touch icon the shared page shell links (/steam, /updates, world pages)', async () => {const shell = req('openvibe-shared/serve') as { manifestUrl(s: string): string | null }const url = shell.manifestUrl('games')@@@ -19,6 +19,21 @@ interface SharedServe {handler(): (req: IncomingMessage & { path: string }, res: ServerResponse, next: (err?: unknown) => void) => void}++/**++ * The file's type, as openvibe-shared/serve gives it: a stylesheet must be text/css, or the browser refuses it under++ * nosniff (the profile panel's stylesheet went out as JavaScript and its panel drew unstyled on the home page,++ * 2026-10-27).++ */++export function contentTypeOf(name: string): string {++ if (name.endsWith('.css')) return 'text/css; charset=utf-8'++ if (name.endsWith('.json')) return 'application/json; charset=utf-8'++ if (name.endsWith('.svg')) return 'image/svg+xml'++ if (name.endsWith('.png')) return 'image/png'++ if (name.endsWith('.jpg') || name.endsWith('.jpeg')) return 'image/jpeg'++ if (name.endsWith('.webp')) return 'image/webp'++ return 'application/javascript; charset=utf-8'++}++export function sharedAssetsHandler(req: NodeRequire = createRequire(import.meta.url)) {const files = req('openvibe-shared/files') as SharedFilesconst serve = req('openvibe-shared/serve') as SharedServe@@@ -40,7 +55,7 @@ export function sharedAssetsHandler(req: NodeRequire = createRequire(import.meta}const hash = serve.hashOf(name)const headers = {-- 'content-type': 'application/javascript; charset=utf-8',++ 'content-type': contentTypeOf(name),'cache-control':url.searchParams.get('v') === hash? 'public, max-age=31536000, immutable'
@@ -40,6 +40,14 @@ describe('/shared (D42)', () => {
@@ -40,6 +40,14 @@ describe('/shared (D42)', () => {
expect(r.status).toBe(304)
expect(r.status).toBe(304)
})
})
+ it('serves a stylesheet as text/css (the profile panel drew unstyled when its CSS went out as JavaScript)', async () => {
+ const r = await fetch(`${base}/shared/profile-panel.css`)
+ expect(r.status).toBe(200)
+ expect(r.headers.get('content-type')).toMatch(/^text\/css/)
+ expect(r.headers.get('x-content-type-options')).toBe('nosniff')
+ expect((await fetch(`${base}/shared/profile-panel.js`)).headers.get('content-type')).toMatch(/javascript/)
+ })
+
it('serves the web app manifest and touch icon the shared page shell links (/steam, /updates, world pages)', async () => {
it('serves the web app manifest and touch icon the shared page shell links (/steam, /updates, world pages)', async () => {
const shell = req('openvibe-shared/serve') as { manifestUrl(s: string): string | null }
const shell = req('openvibe-shared/serve') as { manifestUrl(s: string): string | null }
const url = shell.manifestUrl('games')
const url = shell.manifestUrl('games')
@@ -19,6 +19,21 @@ interface SharedServe {
@@ -19,6 +19,21 @@ interface SharedServe {
handler(): (req: IncomingMessage & { path: string }, res: ServerResponse, next: (err?: unknown) => void) => void
handler(): (req: IncomingMessage & { path: string }, res: ServerResponse, next: (err?: unknown) => void) => void
}
}
+/**
+ * The file's type, as openvibe-shared/serve gives it: a stylesheet must be text/css, or the browser refuses it under
+ * nosniff (the profile panel's stylesheet went out as JavaScript and its panel drew unstyled on the home page,
+ * 2026-10-27).
+ */
+export function contentTypeOf(name: string): string {
+ if (name.endsWith('.css')) return 'text/css; charset=utf-8'
+ if (name.endsWith('.json')) return 'application/json; charset=utf-8'
+ if (name.endsWith('.svg')) return 'image/svg+xml'
+ if (name.endsWith('.png')) return 'image/png'
+ if (name.endsWith('.jpg') || name.endsWith('.jpeg')) return 'image/jpeg'
+ if (name.endsWith('.webp')) return 'image/webp'
+ return 'application/javascript; charset=utf-8'
+}
+
export function sharedAssetsHandler(req: NodeRequire = createRequire(import.meta.url)) {
export function sharedAssetsHandler(req: NodeRequire = createRequire(import.meta.url)) {
const files = req('openvibe-shared/files') as SharedFiles
const files = req('openvibe-shared/files') as SharedFiles
const serve = req('openvibe-shared/serve') as SharedServe
const serve = req('openvibe-shared/serve') as SharedServe
@@ -40,7 +55,7 @@ export function sharedAssetsHandler(req: NodeRequire = createRequire(import.meta
@@ -40,7 +55,7 @@ export function sharedAssetsHandler(req: NodeRequire = createRequire(import.meta
}
}
const hash = serve.hashOf(name)
const hash = serve.hashOf(name)
const headers = {
const headers = {
- 'content-type': 'application/javascript; charset=utf-8',
+ 'content-type': contentTypeOf(name),
'cache-control':
'cache-control':
url.searchParams.get('v') === hash
url.searchParams.get('v') === hash
? 'public, max-age=31536000, immutable'
? 'public, max-age=31536000, immutable'