/* * 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 CacheStorageController from '../cacheStorage'; import type { WorkerTaskTemplate, WorkerTaskVoidTemplate } from '../../types'; import type { InputFileLocation, UploadFile } from '../../layer'; import type { WebPushApiManager } from '../mtproto/webPushApiManager'; import type { PushNotificationObject } from './push'; 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'; export const log = logger('SW', LogTypes.Error | LogTypes.Debug | LogTypes.Log | LogTypes.Warn); const ctx = self as any as ServiceWorkerGlobalScope; export const deferredPromises: {[taskId: number]: CancellablePromise} = {}; export interface RequestFilePartTask extends WorkerTaskTemplate { type: 'requestFilePart', payload: [number, InputFileLocation, number, number] }; export interface RequestFilePartTaskResponse extends WorkerTaskTemplate { type: 'requestFilePart', payload?: UploadFile.uploadFile, 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; /// #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) => { const promise = deferredPromises[task.id]; if(task.error) { promise.reject(task.error); } else { promise.resolve(task.payload); } delete deferredPromises[task.id]; } }; 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(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)); } 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();