@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
JavaScript
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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