import * as interfaces from '../../dist_ts_interfaces/index.js'; import { type IServerOptions, TypedServer } from '../classes.typedserver.js'; import type { Request, Response } from '../index.js'; import * as plugins from '../plugins.js'; import * as servertools from '../servertools/index.js'; export interface IUtilityWebsiteServerConstructorOptions { addCustomRoutes?: (serverArg: servertools.Server) => Promise; appSemVer?: string; domain: string; serveDir: string; feedMetadata: IServerOptions['feedMetadata']; } /** * the utility website server implements a best practice server for websites * It supports: * * live reload * * compression * * serviceworker * * pwa manifest */ export class UtilityWebsiteServer { public options: IUtilityWebsiteServerConstructorOptions; public typedserver: TypedServer; public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(optionsArg: IUtilityWebsiteServerConstructorOptions) { this.options = optionsArg; } /** * */ public async start(portArg = 3000) { this.typedserver = new TypedServer({ cors: true, injectReload: true, watch: true, serveDir: this.options.serveDir, enableCompression: true, preferredCompressionMethod: 'gzip', domain: this.options.domain, forceSsl: false, manifest: { name: this.options.domain, short_name: this.options.domain, start_url: '/', display_override: ['window-controls-overlay'], lang: 'en', background_color: '#000000', scope: '/', }, port: portArg, // features robots: true, sitemap: true, }); let lswData: interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo['response'] = { appHash: 'xxxxxx', appSemVer: this.options.appSemVer || 'x.x.x', }; // -> /lsw* - anything regarding serviceworker servertools.serviceworker.addServiceWorkerRoute(this.typedserver, () => { return lswData; }); // lets add ads.txt this.typedserver.server.addRoute( '/ads.txt', new servertools.Handler('GET', async (req, res) => { res.type('txt/plain'); const adsTxt = ['google.com, pub-4104137977476459, DIRECT, f08c47fec0942fa0'].join('\n') + '\n'; res.write(adsTxt); res.end(); }) ); this.typedserver.server.addRoute( '/assetbroker/manifest/:manifestAsset', new servertools.Handler('GET', async (req, res) => { let manifestAssetName = req.params.manifestAsset; if (manifestAssetName === 'favicon.png') { manifestAssetName = `favicon_${this.options.domain .replace('.', '') .replace('losslesscom', 'lossless')}@2x_transparent.png`; } const fullOriginAssetUrl = `https://assetbroker.lossless.one/brandfiles/00general/${manifestAssetName}`; console.log(`Getting ${manifestAssetName} from ${fullOriginAssetUrl}`); const dataBuffer: Buffer = (await plugins.smartrequest.getBinary(fullOriginAssetUrl)).body; res.type('.png'); res.write(dataBuffer); res.end(); }) ); // lets add any custom routes if (this.options.addCustomRoutes) { await this.options.addCustomRoutes(this.typedserver.server); } // -> /* - serve the files this.typedserver.serveDirHashSubject.subscribe((appHash: string) => { lswData = { appHash, appSemVer: '1.0.0', }; }); // lets setup the typedrouter chain this.typedserver.typedrouter.addTypedRouter(this.typedrouter); // lets start everything console.log('routes are all set. Startin up now!'); await this.typedserver.start(); console.log('typedserver started!'); } public async stop() { await this.typedserver.stop(); } /** * allows you to hanlde requests from other server instances without the need to listen for yourself * note smartexpress allows you start the instance wuith passing >>false<< as second parameter to .start(); * @param req * @param res */ public async handleRequest(req: Request, res: Response) { await this.typedserver.server.handleReqRes(req, res); } }