diff --git a/spec/file-depencies_test.js b/spec/file-depencies_test.js new file mode 100644 index 0000000000000000000000000000000000000000..15a619f3a141a936ed91e3a097cea8aaea1c9b27 --- /dev/null +++ b/spec/file-depencies_test.js @@ -0,0 +1,58 @@ +import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from '@jest/globals' +import mockfs from 'mock-fs' +import request from 'supertest' +import { createApp } from '../src/createApp' +import { createMockServer, generateSimpleViteManifest, getRandomPort } from './util.js' + +describe('JS files with dependencies contain events', () => { + let app + let mockserver + const port = getRandomPort() + + beforeAll(() => { + mockfs({ + './config/manifests': { + 'urls.yaml': `manifests: + - http://localhost:${port}/api/manifest.json` + } + }) + app = createApp() + }) + + afterAll(() => { + mockfs.restore() + }) + + beforeEach(async () => { + mockserver = await createMockServer({ port }) + mockserver.respondWith({ + '/api/manifest.json': generateSimpleViteManifest({ + 'example.js': {}, + 'main.css': {}, + 'index.html': { + file: 'index.html.js', + isEntry: true, + imports: ['example.js'], + css: ['main.css'] + } + }), + '/example.js': (req, res) => res.setHeader('content-type', 'application/javascript').status(200).send('this is example'), + '/index.html.js': (req, res) => res.setHeader('content-type', 'application/javascript').status(200).send('console.log("this is index.html.js")'), + '/main.css': (req, res) => res.setHeader('content-type', 'text/css').status(200).send('.foo { color: #000; }'), + '/index.html': (req, res) => res.setHeader('content-type', 'text/html').status(200).send('<html><head></head><body>it\'s me</body></html>') + }) + await request(app).get('/ready') + }) + + afterEach(() => { + mockserver.close() + process.env.CACHE_TTL = 30000 + }) + + it('javascript file contains dispatcher for dependencies', async () => { + const response = await request(app).get('/index.html.js') + expect(response.statusCode).toBe(200) + expect(response.headers.dependencies).toEqual('main.css') + expect(response.text).toEqual('console.log("this is index.html.js")\n/*injected by manifest-service*/document.dispatchEvent(new CustomEvent("load-css",{detail:{css:["main.css"]}}))') + }) +}) diff --git a/src/files.js b/src/files.js index f5fc29f2458c43444f267e642de6eaf4e8a5ace6..ced74abc33a6277805d7e86bb4e52e13b1a20df3 100644 --- a/src/files.js +++ b/src/files.js @@ -3,13 +3,20 @@ import crypto from 'crypto' import { config } from './config.js' import promClient from 'prom-client' import { getLogger } from '@open-xchange/logging' +import { isJSFile } from './util.js' const logger = getLogger() -async function fetchData (path, baseUrl) { +async function fetchData (path, baseUrl, appendix) { const response = await fetch(new URL(path, baseUrl)) if (!response.ok) throw new Error(`Error fetching file: ${path}`) - const content = Buffer.from(await response.arrayBuffer()) + const resBuffer = await response.arrayBuffer() + const appendixLength = appendix?.length || 0 + const content = Buffer.alloc(resBuffer.byteLength + appendixLength) + + content.fill(Buffer.from(resBuffer), 0, resBuffer.byteLength) + if (appendix) content.write(appendix, resBuffer.byteLength) + const sha256Sum = crypto.createHash('sha256').update(content).digest('base64') return [path, { 'content-type': response.headers.get('content-type'), @@ -49,7 +56,12 @@ class FileCache { logger.error(`could not find manifest for "${file}"`) return null } - return await fetchData(file, manifest.meta.baseUrl) + let appendix + if (manifest.css && isJSFile(file)) { + const cssString = manifest.css.map(file => `"${file}"`).join(',') + appendix = `\n/*injected by manifest-service*/document.dispatchEvent(new CustomEvent("load-css",{detail:{css:[${cssString}]}}))` + } + return await fetchData(file, manifest.meta.baseUrl, appendix) } catch (e) { fileErrorCounter.inc() logger.error(e) diff --git a/src/util.js b/src/util.js index 2238880b6b439be19102c00aaa3c48d7a3548742..55c804338bf0eb5c4a790b68187b7f9dacaf631a 100644 --- a/src/util.js +++ b/src/util.js @@ -1,3 +1,5 @@ +import path from 'path' + // totaly awesome hash function. Do not use this for encryption (crypto.subtle.digest etc would be overkill for this) export function hash (array) { const string = JSON.stringify(array) @@ -14,3 +16,8 @@ export function hash (array) { return new Uint32Array([hash]).toString() } + +export function isJSFile (name) { + const extname = path.extname(name) + return extname === '.js' +}