UNPKG

@api.global/typedserver

Version:

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

297 lines 24.2 kB
import * as plugins from './typedserver_web.plugins.js'; import * as interfaces from '../dist_ts_interfaces/index.js'; import { logger } from './typedserver_web.logger.js'; logger.log('info', `TypedServer-Devtools initialized!`); import { TypedserverStatusPill } from './typedserver_web.statuspill.js'; export class ReloadChecker { reloadJustified = false; backendConnectionLost = false; statusPill = new TypedserverStatusPill(); store = new plugins.webstore.WebStore({ dbName: 'apiglobal__typedserver', storeName: 'apiglobal__typedserver', }); storeKey = 'lastServerChange'; typedsocket; typedrouter = new plugins.typedrequest.TypedRouter(); swStatusUnsubscribe = null; trafficLoggingEnabled = false; constructor() { // Listen to browser online/offline events window.addEventListener('online', () => { this.statusPill.updateStatus({ source: 'network', type: 'online', message: 'Back online', persist: false, timestamp: Date.now(), }); }); window.addEventListener('offline', () => { this.statusPill.updateStatus({ source: 'network', type: 'offline', message: 'No internet connection', persist: true, timestamp: Date.now(), }); }); } async reload() { // this looks a bit hacky, but apparently is the safest way to really reload stuff window.location.reload(); } /** * Subscribe to service worker status updates */ subscribeToServiceWorker() { // Check if service worker client is available if (globalThis.globalSw?.actionManager) { this.swStatusUnsubscribe = globalThis.globalSw.actionManager.subscribeToStatusUpdates((status) => { this.statusPill.updateStatus({ source: status.source, type: status.type, message: status.message, details: status.details, persist: status.persist || false, timestamp: status.timestamp, }); }); logger.log('info', 'Subscribed to service worker status updates'); // Get initial SW status this.fetchServiceWorkerStatus(); } else { logger.log('note', 'Service worker client not available yet, will retry...'); // Retry after a delay setTimeout(() => this.subscribeToServiceWorker(), 2000); } } /** * Fetch and display initial service worker status */ async fetchServiceWorkerStatus() { if (!globalThis.globalSw?.actionManager) return; try { const status = await globalThis.globalSw.actionManager.getServiceWorkerStatus(); if (status) { this.statusPill.updateStatus({ source: 'serviceworker', type: status.isActive ? 'connected' : 'disconnected', message: status.isActive ? 'Service worker active' : 'Service worker inactive', details: { cacheHitRate: status.cacheHitRate, resourceCount: status.resourceCount, connectionType: status.connectionType, }, persist: false, timestamp: Date.now(), }); } } catch (error) { logger.log('warn', `Failed to get SW status: ${error}`); } } /** * starts the reload checker */ async performHttpRequest() { logger.log('info', 'performing http check...'); (await this.store.get(this.storeKey)) ? null : await this.store.set(this.storeKey, globalThis.typedserver.lastReload); let response; try { const controller = new AbortController(); plugins.smartdelay.delayFor(5000).then(() => { controller.abort(); }); response = await fetch('/typedserver/reloadcheck', { method: 'POST', signal: controller.signal, }); } catch (err) { } if (response?.status !== 200) { this.backendConnectionLost = true; logger.log('warn', `got a status ${response?.status}.`); this.statusPill.updateStatus({ source: 'backend', type: 'disconnected', message: `Backend connection lost (${response?.status || 'timeout'})`, persist: true, timestamp: Date.now(), }); } if (response?.status === 200 && this.backendConnectionLost) { this.backendConnectionLost = false; this.statusPill.updateStatus({ source: 'backend', type: 'connected', message: 'Backend connection restored', persist: false, timestamp: Date.now(), }); } return response; } async checkReload(lastServerChange) { let reloadJustified = false; let storedLastServerChange = await this.store.get(this.storeKey); if (storedLastServerChange && storedLastServerChange !== lastServerChange) { reloadJustified = true; } else { } if (reloadJustified) { this.store.set(this.storeKey, lastServerChange); const hasSw = !!globalThis.globalSw; this.statusPill.updateStatus({ source: 'serviceworker', type: 'update', message: hasSw ? 'Updating app...' : 'Upgrading...', persist: true, timestamp: Date.now(), }); if (globalThis.globalSw?.purgeCache) { await globalThis.globalSw.purgeCache(); } else if ('caches' in window) { // Fallback: clear caches via Cache API when service worker client isn't initialized try { const cacheKeys = await caches.keys(); await Promise.all(cacheKeys.map(key => caches.delete(key))); logger.log('ok', 'Cleared caches via Cache API fallback'); } catch (err) { logger.log('warn', `Failed to clear caches via Cache API: ${err}`); } } else { console.log('globalThis.globalSw not found and Cache API not available...'); } this.statusPill.updateStatus({ source: 'serviceworker', type: 'cache', message: 'Cache cleared, reloading...', persist: true, timestamp: Date.now(), }); await plugins.smartdelay.delayFor(200); this.reload(); return; } else { // All good, hide after brief show return; } } async connectTypedsocket() { if (!this.typedsocket) { this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler('pushLatestServerChangeTime', async (dataArg) => { this.checkReload(dataArg.time); return {}; })); this.typedsocket = await plugins.typedsocket.TypedSocket.createClient(this.typedrouter, plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl()); await this.typedsocket.setTag('typedserver_frontend', {}); this.typedsocket.statusSubject.subscribe(async (statusArg) => { console.log(`typedsocket status: ${statusArg}`); if (statusArg === 'disconnected' || statusArg === 'reconnecting') { this.backendConnectionLost = true; this.statusPill.updateStatus({ source: 'backend', type: statusArg === 'disconnected' ? 'disconnected' : 'reconnecting', message: `TypedSocket ${statusArg}`, persist: true, timestamp: Date.now(), }); } else if (statusArg === 'connected' && this.backendConnectionLost) { this.backendConnectionLost = false; this.statusPill.updateStatus({ source: 'backend', type: 'connected', message: 'TypedSocket connected', persist: false, timestamp: Date.now(), }); // lets check if a reload is necessary const getLatestServerChangeTime = this.typedsocket.createTypedRequest('getLatestServerChangeTime'); const response = await getLatestServerChangeTime.fire({}); this.checkReload(response.time); } }); logger.log('success', `ReloadChecker connected through typedsocket!`); // Enable traffic logging for sw-dash this.enableTrafficLogging(); } } started = false; async start() { this.started = true; logger.log('info', `starting ReloadChecker...`); // Subscribe to service worker status updates this.subscribeToServiceWorker(); while (this.started) { const response = await this.performHttpRequest(); if (response?.status === 200) { logger.log('info', `ReloadChecker reached backend!`); await this.checkReload(parseInt(await response.text())); await this.connectTypedsocket(); } await plugins.smartdelay.delayFor(120000); } } async stop() { this.started = false; if (this.swStatusUnsubscribe) { this.swStatusUnsubscribe(); this.swStatusUnsubscribe = null; } // Clear global hooks when stopping if (this.trafficLoggingEnabled) { plugins.typedrequest.TypedRouter.clearGlobalHooks(); this.trafficLoggingEnabled = false; } } /** * Enable TypedRequest traffic logging to the service worker * Sets up global hooks on TypedRouter to capture all request/response traffic */ enableTrafficLogging() { if (this.trafficLoggingEnabled) { logger.log('note', 'Traffic logging already enabled'); return; } // Check if service worker client is available if (!globalThis.globalSw?.actionManager) { logger.log('note', 'Service worker client not available, will retry traffic logging setup...'); setTimeout(() => this.enableTrafficLogging(), 2000); return; } const actionManager = globalThis.globalSw.actionManager; // Helper function to log entries const logEntry = (entry) => { // Skip logging serviceworker_* methods to avoid infinite loops // These are internal SW communication methods, not app traffic if (entry.method.startsWith('serviceworker_')) { return; } actionManager.logTypedRequest(entry); }; // Set up global hooks on TypedRouter plugins.typedrequest.TypedRouter.setGlobalHooks({ onOutgoingRequest: logEntry, onIncomingResponse: logEntry, onIncomingRequest: logEntry, onOutgoingResponse: logEntry, }); this.trafficLoggingEnabled = true; logger.log('success', 'TypedRequest traffic logging enabled'); } } const reloadCheckInstance = new ReloadChecker(); reloadCheckInstance.start(); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c193ZWJfaW5qZWN0L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sOEJBQThCLENBQUM7QUFDeEQsT0FBTyxLQUFLLFVBQVUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUM3RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDckQsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsbUNBQW1DLENBQUMsQ0FBQztBQUV4RCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUt4RSxNQUFNLE9BQU8sYUFBYTtJQUNqQixlQUFlLEdBQUcsS0FBSyxDQUFDO0lBQ3hCLHFCQUFxQixHQUFHLEtBQUssQ0FBQztJQUM5QixVQUFVLEdBQUcsSUFBSSxxQkFBcUIsRUFBRSxDQUFDO0lBQ3pDLEtBQUssR0FBRyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDO1FBQzNDLE1BQU0sRUFBRSx3QkFBd0I7UUFDaEMsU0FBUyxFQUFFLHdCQUF3QjtLQUNwQyxDQUFDLENBQUM7SUFDSSxRQUFRLEdBQUcsa0JBQWtCLENBQUM7SUFFOUIsV0FBVyxDQUFrQztJQUM3QyxXQUFXLEdBQUcsSUFBSSxPQUFPLENBQUMsWUFBWSxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ3BELG1CQUFtQixHQUF3QixJQUFJLENBQUM7SUFDaEQscUJBQXFCLEdBQUcsS0FBSyxDQUFDO0lBRXRDO1FBQ0UsMENBQTBDO1FBQzFDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsR0FBRyxFQUFFO1lBQ3JDLElBQUksQ0FBQyxVQUFVLENBQUMsWUFBWSxDQUFDO2dCQUMzQixNQUFNLEVBQUUsU0FBUztnQkFDakIsSUFBSSxFQUFFLFFBQVE7Z0JBQ2QsT0FBTyxFQUFFLGFBQWE7Z0JBQ3RCLE9BQU8sRUFBRSxLQUFLO2dC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