177 lines
6.3 KiB
TypeScript
177 lines
6.3 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import * as interfaces from './env.js';
|
|
|
|
// imports
|
|
import { CacheManager } from './classes.cachemanager.js';
|
|
import { Deferred } from '@push.rocks/smartpromise';
|
|
|
|
import { logger } from './logging.js';
|
|
|
|
// imported classes
|
|
import { UpdateManager } from './classes.updatemanager.js';
|
|
import { NetworkManager } from './classes.networkmanager.js';
|
|
import { TaskManager } from './classes.taskmanager.js';
|
|
import { ServiceworkerBackend } from './classes.backend.js';
|
|
import { getPersistentStore } from './classes.persistentstore.js';
|
|
|
|
export class ServiceWorker {
|
|
// STATIC
|
|
|
|
// INSTANCE
|
|
public serviceWindowRef: interfaces.ServiceWindow;
|
|
public leleServiceWorkerBackend: ServiceworkerBackend;
|
|
|
|
public cacheManager: CacheManager;
|
|
|
|
public updateManager: UpdateManager;
|
|
public networkManager: NetworkManager;
|
|
public taskManager: TaskManager;
|
|
public store: plugins.webstore.WebStore;
|
|
|
|
// TypedSocket connection for server communication
|
|
public typedsocket: plugins.typedsocket.TypedSocket;
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
private handlersInitialized = false;
|
|
|
|
constructor(selfArg: interfaces.ServiceWindow) {
|
|
logger.log('info', `Service worker instantiating at ${Date.now()}`);
|
|
this.serviceWindowRef = selfArg;
|
|
this.leleServiceWorkerBackend = new ServiceworkerBackend({
|
|
self: selfArg,
|
|
purgeCache: async (reqArg) => {
|
|
await this.cacheManager.cleanCaches(),
|
|
logger.log('info', `cleaned caches in serviceworker as per request from frontend.`);
|
|
return {}
|
|
}
|
|
});
|
|
|
|
this.updateManager = new UpdateManager(this);
|
|
this.networkManager = new NetworkManager(this);
|
|
this.taskManager = new TaskManager(this);
|
|
|
|
this.cacheManager = new CacheManager(this);
|
|
|
|
this.store = new plugins.webstore.WebStore({
|
|
dbName: 'losslessServiceworker',
|
|
storeName: 'losslessServiceworker',
|
|
});
|
|
|
|
// =================================
|
|
// Installation and Activation
|
|
// =================================
|
|
this.serviceWindowRef.addEventListener('install', async (event: interfaces.ServiceEvent) => {
|
|
const done = new Deferred();
|
|
event.waitUntil(done.promise);
|
|
// its important to not go async before event.waitUntil
|
|
try {
|
|
logger.log('success', `service worker installed! TimeStamp = ${new Date().toISOString()}`);
|
|
|
|
// Log installation event
|
|
const persistentStore = getPersistentStore();
|
|
await persistentStore.init();
|
|
await persistentStore.logEvent('sw_installed', 'Service worker installed', {
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
|
|
selfArg.skipWaiting();
|
|
logger.log('note', `Called skip waiting!`);
|
|
done.resolve();
|
|
} catch (error) {
|
|
logger.log('error', `Service worker installation error: ${error}`);
|
|
done.reject(error);
|
|
}
|
|
});
|
|
|
|
this.serviceWindowRef.addEventListener('activate', async (event: interfaces.ServiceEvent) => {
|
|
const done = new Deferred();
|
|
event.waitUntil(done.promise);
|
|
|
|
// its important to not go async before event.waitUntil
|
|
try {
|
|
await selfArg.clients.claim();
|
|
logger.log('ok', 'Clients claimed successfully');
|
|
|
|
await this.cacheManager.cleanCaches('new service worker loaded! :)');
|
|
logger.log('ok', 'Caches cleaned successfully');
|
|
|
|
// Log activation event
|
|
const persistentStore = getPersistentStore();
|
|
await persistentStore.init(); // Ensure store is initialized (safe to call multiple times)
|
|
await persistentStore.logEvent('sw_activated', 'Service worker activated', {
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
|
|
done.resolve();
|
|
logger.log('success', `Service worker activated at ${new Date().toISOString()}`);
|
|
|
|
// Connect to TypedServer for cache invalidation after activation
|
|
this.connectToServer();
|
|
} catch (error) {
|
|
logger.log('error', `Service worker activation error: ${error}`);
|
|
done.reject(error);
|
|
}
|
|
});
|
|
|
|
// Handle wake-up scenario: if already activated, connect immediately
|
|
// (install/activate events don't fire on wake-up)
|
|
if (selfArg.registration?.active) {
|
|
logger.log('info', 'SW woke up (already activated) - connecting to server');
|
|
this.connectToServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize typed handlers (idempotent - safe to call multiple times)
|
|
*/
|
|
private initHandlers(): void {
|
|
if (this.handlersInitialized) return;
|
|
this.handlersInitialized = true;
|
|
|
|
// Register handler for cache invalidation from server
|
|
this.typedrouter.addTypedHandler<interfaces.serviceworker.IRequest_Serviceworker_CacheInvalidate>(
|
|
new plugins.typedrequest.TypedHandler('serviceworker_cacheInvalidate', async (reqArg) => {
|
|
logger.log('info', `Cache invalidation requested from server: ${reqArg.reason}`);
|
|
|
|
// Log cache invalidation event (survives)
|
|
const persistentStore = getPersistentStore();
|
|
await persistentStore.init(); // Ensure store is initialized
|
|
await persistentStore.logEvent('cache_invalidated', `Cache invalidated: ${reqArg.reason}`, {
|
|
reason: reqArg.reason,
|
|
timestamp: reqArg.timestamp,
|
|
});
|
|
|
|
// Reset cumulative metrics (they don't survive cache invalidation)
|
|
await persistentStore.resetCumulativeMetrics();
|
|
|
|
await this.cacheManager.cleanCaches(reqArg.reason);
|
|
// Notify all clients to reload
|
|
await this.leleServiceWorkerBackend.triggerReloadAll();
|
|
return { success: true };
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Connect to TypedServer via TypedSocket for cache invalidation
|
|
*/
|
|
private async connectToServer(): Promise<void> {
|
|
try {
|
|
this.initHandlers();
|
|
|
|
// Connect to server via TypedSocket
|
|
this.typedsocket = await plugins.typedsocket.TypedSocket.createClient(
|
|
this.typedrouter,
|
|
this.serviceWindowRef.location.origin
|
|
);
|
|
|
|
// Tag this connection as a service worker for server-side filtering
|
|
await this.typedsocket.setTag('serviceworker', {});
|
|
|
|
logger.log('ok', 'Service worker connected to TypedServer via TypedSocket');
|
|
} catch (error: any) {
|
|
logger.log('warn', `Service worker TypedSocket connection failed: ${error?.message || error}`);
|
|
// Retry connection after a delay
|
|
setTimeout(() => this.connectToServer(), 10000);
|
|
}
|
|
}
|
|
} |