import * as plugins from './typedserver.plugins.js'; import * as paths from './typedserver.paths.js'; import * as interfaces from './interfaces/index.js'; import * as servertools from './servertools/index.js'; export interface IServerOptions { /** * serve a particular directory */ serveDir?: string; /** * inject a reload script that takes care of live reloading */ injectReload?: boolean; /** * watch the serve directory? */ watch?: boolean; cors: boolean; /** * a default answer given in case there is no other handler. * @returns */ defaultAnswer?: () => Promise; /** * will try to reroute traffic to an ssl connection using headers */ forceSsl?: boolean; /** * allows serving manifests */ manifest?: plugins.smartmanifest.ISmartManifestConstructorOptions; /** * the port to listen on * can be overwritten when actually starting the server */ port?: number | string; publicKey?: string; privateKey?: string; sitemap?: boolean; feed?: boolean; robots?: boolean; domain?: string; /** * convey information about the app being served */ appVersion?: string; feedMetadata?: plugins.smartfeed.IFeedOptions; articleGetterFunction?: () => Promise; blockWaybackMachine?: boolean; } export class TypedServer { // static // nothing here yet // instance public options: IServerOptions; public server: servertools.Server; public smartchokInstance: plugins.smartchok.Smartchok; public serveDirHashSubject = new plugins.smartrx.rxjs.ReplaySubject(1); public serveHash: string = '000000'; public typedsocket: plugins.typedsocket.TypedSocket; public typedrouter = new plugins.typedrequest.TypedRouter(); public lastReload: number = Date.now(); public ended = false; constructor(optionsArg: IServerOptions) { const standardOptions: IServerOptions = { 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.bundlePath)); 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(); } }) ); } /** * inits and starts the server */ public async start() { if (this.options.serveDir) { this.server.addRoute( '/*', new servertools.HandlerStatic(this.options.serveDir, { responseModifier: async (responseArg) => { let fileString = responseArg.responseContent; if (plugins.path.parse(responseArg.path).ext === '.html') { const fileStringArray = fileString.split(''); if (this.options.injectReload && fileStringArray.length === 2) { fileStringArray[0] = `${fileStringArray[0]} `; fileString = fileStringArray.join(''); console.log('injected typedserver script.'); } else if (this.options.injectReload) { console.log('Could not insert typedserver script'); } } 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: fileString, }; }, serveIndexHtmlDefault: true, }) ); } else if (this.options.injectReload) { throw new Error( 'You set to inject the reload script without a serve dir. This is not supported at the moment.' ); } if (this.options.watch && this.options.serveDir) { 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(); } // lets start the server await this.server.start(); this.typedsocket = await plugins.typedsocket.TypedSocket.createServer( this.typedrouter, this.server ); // lets setup typedrouter this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler('getLatestServerChangeTime', async (reqDataArg) => { return { time: this.lastReload, }; }) ); // console.log('open url in browser'); // await plugins.smartopen.openUrl(`http://testing.git.zone:${this.options.port}`); } /** * reloads the page */ public async reload() { this.lastReload = Date.now(); for (const connectionArg of await this.typedsocket.findAllTargetConnectionsByTag( 'typedserver_frontend' )) { const pushTime = this.typedsocket.createTypedRequest( 'pushLatestServerChangeTime', connectionArg ); pushTime.fire({ time: this.lastReload, }); } } public async stop() { this.ended = true; await this.server.stop(); await this.typedsocket.stop(); if (this.smartchokInstance) { await this.smartchokInstance.stop(); } } public async createServeDirHash() { const serveDirHash = await plugins.smartfile.fs.fileTreeToHash(this.options.serveDir, '**/*'); this.serveHash = serveDirHash; console.log('Current ServeDir hash: ' + serveDirHash); this.serveDirHashSubject.next(serveDirHash); } }