import * as interfaces from '../../dist_ts_interfaces/index.js'; import { type IServerOptions, TypedServer } from '../classes.typedserver.js'; import * as plugins from '../plugins.js'; import * as servertools from '../servertools/index.js'; export interface IUtilityWebsiteServerConstructorOptions { addCustomRoutes?: (typedserver: TypedServer) => 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 * * serviceworker * * pwa manifest */ export class UtilityWebsiteServer { public options: IUtilityWebsiteServerConstructorOptions; public typedserver: TypedServer; public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(optionsArg: IUtilityWebsiteServerConstructorOptions) { this.options = optionsArg; } /** * Start the website server */ public async start(portArg = 3000) { this.typedserver = new TypedServer({ cors: true, injectReload: true, watch: true, serveDir: this.options.serveDir, 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; }); // ads.txt handler this.typedserver.addRoute('/ads.txt', 'GET', async () => { const adsTxt = ['google.com, pub-4104137977476459, DIRECT, f08c47fec0942fa0'].join('\n') + '\n'; return new Response(adsTxt, { status: 200, headers: { 'Content-Type': 'text/plain' }, }); }); // Asset broker manifest handler this.typedserver.addRoute( '/assetbroker/manifest/:manifestAsset', 'GET', async (request: Request) => { let manifestAssetName = (request as any).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 smartRequest = plugins.smartrequest.SmartRequest.create(); const response = await smartRequest.url(fullOriginAssetUrl).get(); const arrayBuffer = await response.arrayBuffer(); return new Response(arrayBuffer, { status: 200, headers: { 'Content-Type': 'image/png' }, }); } ); // Add any custom routes if (this.options.addCustomRoutes) { await this.options.addCustomRoutes(this.typedserver); } // Subscribe to serve directory hash changes this.typedserver.serveDirHashSubject.subscribe((appHash: string) => { lswData = { appHash, appSemVer: '1.0.0', }; }); // Setup the typedrouter chain this.typedserver.typedrouter.addTypedRouter(this.typedrouter); // Start everything console.log('routes are all set. Starting up now!'); await this.typedserver.start(); console.log('typedserver started!'); } public async stop() { await this.typedserver.stop(); } }