UNPKG

@api.global/typedserver

Version:

A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.

207 lines 17.8 kB
import * as plugins from './plugins.js'; import * as paths from './paths.js'; import * as interfaces from '../dist_ts_interfaces/index.js'; import * as servertools from './servertools/index.js'; import {} from './servertools/classes.compressor.js'; export class TypedServer { constructor(optionsArg) { this.serveDirHashSubject = new plugins.smartrx.rxjs.ReplaySubject(1); this.serveHash = '000000'; this.typedrouter = new plugins.typedrequest.TypedRouter(); this.lastReload = Date.now(); this.ended = false; const standardOptions = { port: 3000, injectReload: false, serveDir: null, watch: false, cors: true, }; this.options = { ...standardOptions, ...optionsArg, }; this.server = new servertools.Server(this.options); // add routes to the smartexpress instance this.server.addRoute('/typedserver/:request', new servertools.Handler('ALL', async (req, res) => { switch (req.params.request) { case 'devtools': res.setHeader('Content-Type', 'text/javascript'); res.status(200); res.write(plugins.smartfile.fs.toStringSync(paths.injectBundlePath)); res.end(); break; case 'reloadcheck': console.log('got request for reloadcheck'); res.setHeader('Content-Type', 'text/plain'); res.status(200); if (this.ended) { res.write('end'); res.end(); return; } res.write(this.lastReload.toString()); res.end(); break; default: res.status(404); res.write('Unknown request type'); res.end(); break; } })); this.server.addRoute('/typedrequest', new servertools.HandlerTypedRouter(this.typedrouter)); } /** * inits and starts the server */ async start() { // Validate essential configuration before starting if (this.options.injectReload && !this.options.serveDir) { throw new Error('You set to inject the reload script without a serve dir. This is not supported at the moment.'); } if (this.options.serveDir) { this.server.addRoute('/*splat', new servertools.HandlerStatic(this.options.serveDir, { responseModifier: async (responseArg) => { if (plugins.path.parse(responseArg.path).ext === '.html') { let fileString = responseArg.responseContent.toString(); const fileStringArray = fileString.split('<head>'); if (this.options.injectReload && fileStringArray.length === 2) { fileStringArray[0] = `${fileStringArray[0]}<head> <!-- injected by @apiglobal/typedserver start --> <script async defer type="module" src="/typedserver/devtools"></script> <script> globalThis.typedserver = { lastReload: ${this.lastReload}, versionInfo: ${JSON.stringify({}, null, 2)}, } </script> <!-- injected by @apiglobal/typedserver stop --> `; fileString = fileStringArray.join(''); console.log('injected typedserver script.'); responseArg.responseContent = Buffer.from(fileString); } else if (this.options.injectReload) { console.log('Could not insert typedserver script - no <head> tag found'); } } const headers = responseArg.headers; headers.appHash = this.serveHash; headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'; headers['Pragma'] = 'no-cache'; headers['Expires'] = '0'; return { headers, path: responseArg.path, responseContent: responseArg.responseContent, travelData: responseArg.travelData, }; }, serveIndexHtmlDefault: true, enableCompression: this.options.enableCompression, preferredCompressionMethod: this.options.preferredCompressionMethod, })); } if (this.options.watch && this.options.serveDir) { try { this.smartchokInstance = new plugins.smartchok.Smartchok([this.options.serveDir]); await this.smartchokInstance.start(); (await this.smartchokInstance.getObservableFor('change')).subscribe(async () => { await this.createServeDirHash(); this.reload(); }); await this.createServeDirHash(); } catch (error) { console.error('Failed to initialize file watching:', error); // Continue without file watching rather than crashing } } // lets start the server await this.server.start(); try { this.typedsocket = await plugins.typedsocket.TypedSocket.createServer(this.typedrouter, this.server); // lets setup typedrouter this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler('getLatestServerChangeTime', async () => { return { time: this.lastReload, }; })); } catch (error) { console.error('Failed to initialize TypedSocket:', error); // Continue without WebSocket support rather than crashing } } /** * reloads the page */ async reload() { this.lastReload = Date.now(); if (!this.typedsocket) { console.warn('TypedSocket not initialized, skipping client notifications'); return; } try { const connections = await this.typedsocket.findAllTargetConnectionsByTag('typedserver_frontend'); for (const connection of connections) { const pushTime = this.typedsocket.createTypedRequest('pushLatestServerChangeTime', connection); pushTime.fire({ time: this.lastReload, }); } } catch (error) { console.error('Failed to notify clients about reload:', error); } } /** * Stops the server and cleans up resources */ async stop() { this.ended = true; const stopWithErrorHandling = async (stopFn, componentName) => { try { await stopFn(); } catch (err) { console.error(`Error stopping ${componentName}:`, err); } }; const tasks = []; // Stop server if (this.server) { tasks.push(stopWithErrorHandling(() => this.server.stop(), 'server')); } // Stop TypedSocket if (this.typedsocket) { tasks.push(stopWithErrorHandling(() => this.typedsocket.stop(), 'TypedSocket')); } // Stop file watcher if (this.smartchokInstance) { tasks.push(stopWithErrorHandling(() => this.smartchokInstance.stop(), 'file watcher')); } await Promise.all(tasks); } /** * Calculates a hash of the served directory for cache busting */ async createServeDirHash() { try { const serveDirHash = await plugins.smartfile.fs.fileTreeToHash(this.options.serveDir, '**/*'); this.serveHash = serveDirHash; console.log('Current ServeDir hash: ' + serveDirHash); this.serveDirHashSubject.next(serveDirHash); } catch (error) { console.error('Failed to create serve directory hash:', error); // Use a timestamp-based hash as fallback const fallbackHash = Date.now().toString(16).slice(-6); this.serveHash = fallbackHash; console.log('Using fallback hash: ' + fallbackHash); this.serveDirHashSubject.next(fallbackHash); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy50eXBlZHNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMudHlwZWRzZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxLQUFLLEtBQUssTUFBTSxZQUFZLENBQUM7QUFDcEMsT0FBTyxLQUFLLFVBQVUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUM3RCxPQUFPLEtBQUssV0FBVyxNQUFNLHdCQUF3QixDQUFDO0FBQ3RELE9BQU8sRUFBMkIsTUFBTSxxQ0FBcUMsQ0FBQztBQWdFOUUsTUFBTSxPQUFPLFdBQVc7SUFnQnRCLFlBQVksVUFBMEI7UUFSL0Isd0JBQW1CLEdBQUcsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQVMsQ0FBQyxDQUFDLENBQUM7UUFDeEUsY0FBUyxHQUFXLFFBQVEsQ0FBQztRQUU3QixnQkFBVyxHQUFHLElBQUksT0FBTyxDQUFDLFlBQVksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUVyRCxlQUFVLEdBQVcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ2hDLFVBQUssR0FBRyxLQUFLLENBQUM7UUFHbkIsTUFBTSxlQUFlLEdBQW1CO1lBQ3RDLElBQUksRUFBRSxJQUFJO1lBQ1YsWUFBWSxFQUFFLEtBQUs7WUFDbkIsUUFBUSxFQUFFLElBQUk7WUFDZCxLQUFLLEVBQUUsS0FBSztZQUNaLElBQUksRUFBRSxJQUFJO1NBQ1gsQ0FBQztRQUNGLElBQUksQ0FBQyxPQUFPLEdBQUc7WUFDYixHQUFHLGVBQWU7WUFDbEIsR0FBRyxVQUFVO1NBQ2QsQ0FBQztRQUVGLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxXQUFXLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNuRCwwQ0FBMEM7UUFDMUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQ2xC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