/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ /// #if MTPROTO_SW import '../mtproto/mtproto.worker'; /// #endif import type { Modify, WorkerTaskTemplate, WorkerTaskVoidTemplate } from '../../types'; import type { WebPushApiManager } from '../mtproto/webPushApiManager'; import type { PushNotificationObject } from './push'; import type { ToggleStorageTask } from '../mtproto/mtprotoworker'; import type { MyUploadFile } from '../mtproto/apiFileManager'; import { logger, LogTypes } from '../logger'; import { CancellablePromise } from '../../helpers/cancellablePromise'; import { CACHE_ASSETS_NAME, requestCache } from './cache'; import onStreamFetch from './stream'; import { closeAllNotifications, onPing } from './push'; import CacheStorageController from '../cacheStorage'; import { IS_SAFARI } from '../../environment/userAgent'; export const log = logger('SW', LogTypes.Error | LogTypes.Debug | LogTypes.Log | LogTypes.Warn); const ctx = self as any as ServiceWorkerGlobalScope; export const deferredPromises: Map}> = new Map(); export interface RequestFilePartTask extends Modify { type: 'requestFilePart', payload: { docId: DocId, dcId: number, offset: number, limit: number } }; export interface RequestFilePartTaskResponse extends Modify { type: 'requestFilePart', payload?: MyUploadFile, originalPayload?: RequestFilePartTask['payload'] }; export interface ServiceWorkerPingTask extends WorkerTaskVoidTemplate { type: 'ping', payload: { localNotifications: boolean, lang: { push_action_mute1d: string push_action_settings: string push_message_nopreview: string }, settings: WebPushApiManager['settings'] } }; export interface ServiceWorkerNotificationsClearTask extends WorkerTaskVoidTemplate { type: 'notifications_clear' }; export interface ServiceWorkerPushClickTask extends WorkerTaskVoidTemplate { type: 'push_click', payload: PushNotificationObject }; export type ServiceWorkerTask = RequestFilePartTaskResponse | ServiceWorkerPingTask | ServiceWorkerNotificationsClearTask | ToggleStorageTask; /// #if !MTPROTO_SW const taskListeners: { [type in ServiceWorkerTask['type']]: (task: any, event: ExtendableMessageEvent) => void } = { notifications_clear: () => { closeAllNotifications(); }, ping: (task: ServiceWorkerPingTask, event) => { onPing(task, event); }, requestFilePart: (task: RequestFilePartTaskResponse, e: ExtendableMessageEvent) => { const windowClient = e.source as WindowClient; const promises = deferredPromises.get(windowClient.id); if(!promises) { return; } const promise = promises[task.id]; if(promise) { if(task.error) { promise.reject(task.error); } else { promise.resolve(task.payload); } delete promises[task.id]; } }, toggleStorages: (task: ToggleStorageTask) => { const {enabled, clearWrite} = task.payload; CacheStorageController.toggleStorage(enabled, clearWrite); } }; ctx.addEventListener('message', (e) => { const task = e.data as ServiceWorkerTask; const callback = taskListeners[task.type]; if(callback) { callback(task, e); } }); /// #endif //const cacheStorage = new CacheStorageController('cachedAssets'); /* let taskId = 0; export function getTaskId() { return taskId; } export function incrementTaskId() { return taskId++; } */ const onFetch = (event: FetchEvent): void => { /// #if !DEBUG if( !IS_SAFARI && event.request.url.indexOf(location.origin + '/') === 0 && event.request.url.match(/\.(js|css|jpe?g|json|wasm|png|mp3|svg|tgs|ico|woff2?|ttf|webmanifest?)(?:\?.*)?$/) ) { return event.respondWith(requestCache(event)); } /// #endif try { const [, url, scope, params] = /http[:s]+\/\/.*?(\/(.*?)(?:$|\/(.*)$))/.exec(event.request.url) || []; //log.debug('[fetch]:', event); switch(scope) { case 'stream': { onStreamFetch(event, params); break; } } } catch(err) { event.respondWith(new Response('', { status: 500, statusText: 'Internal Server Error', })); } }; const onChangeState = () => { ctx.onfetch = onFetch; }; ctx.addEventListener('install', (event) => { log('installing'); event.waitUntil(ctx.skipWaiting()); // Activate worker immediately }); ctx.addEventListener('activate', (event) => { log('activating', ctx); event.waitUntil(ctx.caches.delete(CACHE_ASSETS_NAME)); event.waitUntil(ctx.clients.claim()); }); ctx.onerror = (error) => { log.error('error:', error); }; ctx.onunhandledrejection = (error) => { log.error('onunhandledrejection:', error); }; ctx.onoffline = ctx.ononline = onChangeState; onChangeState();