fix(core): update
This commit is contained in:
		
							
								
								
									
										31
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								package.json
									
									
									
									
									
								
							| @@ -2,13 +2,19 @@ | ||||
|   "name": "@api.global/typedserver", | ||||
|   "version": "3.0.29", | ||||
|   "description": "A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.", | ||||
|   "main": "dist_ts/index.js", | ||||
|   "typings": "dist_ts/index.d.ts", | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
|     ".": "./dist_ts/index.js", | ||||
|     "./ts": "./dist_ts/index.js", | ||||
|     "./ts_web_inject": "./dist_ts_web_inject/index.js", | ||||
|     "./ts_web_serviceworker": "./dist_web_serviceworker", | ||||
|     "./ts_web_serviceworker_client": "./dist_web_serviceworker_client" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "test": "npm run build && tstest test/", | ||||
|     "build": "tsbuild --web --allowimplicitany --skiplibcheck && tsbundle --from ./ts_web/index.ts --to ./dist_ts_web/bundle.js", | ||||
|     "buildDocs": "tsdoc" | ||||
|     "build": "tsbuild tsfolders --web --allowimplicitany --skiplibcheck && tsbundle --from ./ts_web/index.ts --to ./dist_ts_web/bundle.js", | ||||
|     "interfaces": "tsbuild interfaces --web --allowimplicitany --skiplibcheck", | ||||
|     "docs": "tsdoc aidoc" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
| @@ -49,11 +55,12 @@ | ||||
|   ], | ||||
|   "homepage": "https://github.com/pushrocks/easyserve", | ||||
|   "dependencies": { | ||||
|     "@api.global/typedrequest": "^3.0.21", | ||||
|     "@api.global/typedrequest-interfaces": "^3.0.18", | ||||
|     "@api.global/typedrequest": "^3.0.23", | ||||
|     "@api.global/typedrequest-interfaces": "^3.0.19", | ||||
|     "@api.global/typedsocket": "^3.0.1", | ||||
|     "@design.estate/dees-comms": "^1.0.24", | ||||
|     "@push.rocks/lik": "^6.0.15", | ||||
|     "@push.rocks/smartchok": "^1.0.33", | ||||
|     "@push.rocks/smartchok": "^1.0.34", | ||||
|     "@push.rocks/smartdelay": "^3.0.5", | ||||
|     "@push.rocks/smartenv": "^5.0.12", | ||||
|     "@push.rocks/smartfeed": "^1.0.11", | ||||
| @@ -64,13 +71,15 @@ | ||||
|     "@push.rocks/smartmanifest": "^2.0.2", | ||||
|     "@push.rocks/smartmime": "^1.0.5", | ||||
|     "@push.rocks/smartopen": "^2.0.0", | ||||
|     "@push.rocks/smartpath": "^5.0.14", | ||||
|     "@push.rocks/smartpath": "^5.0.18", | ||||
|     "@push.rocks/smartpromise": "^4.0.2", | ||||
|     "@push.rocks/smartrequest": "^2.0.22", | ||||
|     "@push.rocks/smartrx": "^3.0.7", | ||||
|     "@push.rocks/smartsitemap": "^2.0.3", | ||||
|     "@push.rocks/smartstream": "^3.0.34", | ||||
|     "@push.rocks/smartstream": "^3.0.35", | ||||
|     "@push.rocks/smarttime": "^4.0.6", | ||||
|     "@push.rocks/taskbuffer": "^3.1.7", | ||||
|     "@push.rocks/webrequest": "^3.0.37", | ||||
|     "@push.rocks/webstore": "^2.0.14", | ||||
|     "@tsclass/tsclass": "^4.0.54", | ||||
|     "@types/express": "^4.17.21", | ||||
| @@ -81,12 +90,12 @@ | ||||
|     "lit": "^3.1.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@git.zone/tsbuild": "^2.1.72", | ||||
|     "@git.zone/tsbuild": "^2.1.75", | ||||
|     "@git.zone/tsbundle": "^2.0.15", | ||||
|     "@git.zone/tsrun": "^1.2.44", | ||||
|     "@git.zone/tstest": "^1.0.90", | ||||
|     "@push.rocks/tapbundle": "^5.0.23", | ||||
|     "@types/node": "^20.12.7" | ||||
|     "@types/node": "^20.12.11" | ||||
|   }, | ||||
|   "private": false, | ||||
|   "browserslist": [ | ||||
|   | ||||
							
								
								
									
										896
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										896
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@api.global/typedserver', | ||||
|   version: '3.0.29', | ||||
|   version: '3.0.30', | ||||
|   description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.' | ||||
| } | ||||
|   | ||||
							
								
								
									
										7
									
								
								ts/paths.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								ts/paths.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
|  | ||||
| export const packageDir = plugins.path.join( | ||||
|   plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), | ||||
|   '../' | ||||
| ); | ||||
| export const distBundleDir = plugins.path.join(packageDir, './dist_bundle'); | ||||
| @@ -1,7 +1,7 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import { Handler } from './classes.handler.js'; | ||||
|  | ||||
| import * as interfaces from '../interfaces/index.js'; | ||||
| import * as interfaces from '../../dist_ts_interfaces/index.js'; | ||||
|  | ||||
| export class HandlerProxy extends Handler { | ||||
|   /** | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import * as interfaces from '../interfaces/index.js'; | ||||
| import * as interfaces from '../../dist_ts_interfaces/index.js'; | ||||
|  | ||||
| import { Handler } from './classes.handler.js'; | ||||
| import { Compressor, type TCompressionMethod, type ICompressionResult } from './classes.compressor.js'; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import { Handler } from './classes.handler.js'; | ||||
|  | ||||
| import * as interfaces from '../interfaces/index.js'; | ||||
| import * as interfaces from '../../dist_ts_interfaces/index.js'; | ||||
|  | ||||
| export class HandlerTypedRouter extends Handler { | ||||
|   /** | ||||
|   | ||||
							
								
								
									
										60
									
								
								ts/servertools/tools.serviceworker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ts/servertools/tools.serviceworker.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import * as paths from '../paths.js'; | ||||
|  | ||||
| import * as interfaces from '../../dist_ts_interfaces/index.js' | ||||
| import { Handler } from './classes.handler.js'; | ||||
| import type { TypedServer } from '../typedserver.classes.typedserver.js'; | ||||
| import { HandlerTypedRouter } from './classes.handlertypedrouter.js'; | ||||
|  | ||||
| const lswJS: string = plugins.smartfile.fs.toStringSync( | ||||
|   plugins.path.join(paths.distBundleDir, './lsw.js') | ||||
| ); | ||||
| const lswJSMeta: string = plugins.smartfile.fs.toStringSync( | ||||
|   plugins.path.join(paths.distBundleDir, './lsw.js.map') | ||||
| ); | ||||
| let lswVersionInfo: interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo['response'] = | ||||
|   null; | ||||
| const serviceworkerHandler = new Handler( | ||||
|   'GET', | ||||
|   async (req, res) => { | ||||
|     if (req.path === '/lsw.js') { | ||||
|       res.status(200); | ||||
|       res.set('Content-Type', 'text/javascript'); | ||||
|       res.write(lswJS + '\n' + `/** appSemVer: ${lswVersionInfo?.appSemVer || 'not set'} */`); | ||||
|     } else if (req.path === '/lsw.js.map') { | ||||
|       res.status(200); | ||||
|       res.set('Content-Type', 'application/json'); | ||||
|       res.write(lswJSMeta); | ||||
|     } | ||||
|     res.end(); | ||||
|   } | ||||
| ); | ||||
|  | ||||
| export const addServiceWorkerRoute = ( | ||||
|   typedserverInstance: TypedServer, | ||||
|   lswData: () => interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo['response'] | ||||
| ) => { | ||||
|   // lets the version info as unique string; | ||||
|   lswVersionInfo = lswData(); | ||||
|  | ||||
|   // the basic stuff | ||||
|   typedserverInstance.server.addRoute('/lsw.js*', serviceworkerHandler); | ||||
|  | ||||
|   // the typed stuff | ||||
|   const typedrouter = new plugins.typedrequest.TypedRouter(); | ||||
|  | ||||
|   typedrouter.addTypedHandler( | ||||
|     new plugins.typedrequest.TypedHandler<interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo>( | ||||
|       'serviceworker_versionInfo', | ||||
|       async (req) => { | ||||
|         const versionInfoResponse = lswData(); | ||||
|         return versionInfoResponse; | ||||
|       } | ||||
|     ) | ||||
|   ); | ||||
|  | ||||
|   typedserverInstance.server.addRoute( | ||||
|     '/lsw-typedrequest', | ||||
|     new HandlerTypedRouter(typedrouter) | ||||
|   ); | ||||
| }; | ||||
| @@ -1,6 +1,6 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as paths from './typedserver.paths.js'; | ||||
| import * as interfaces from './interfaces/index.js'; | ||||
| import * as interfaces from '../dist_ts_interfaces/index.js'; | ||||
| import * as servertools from './servertools/index.js'; | ||||
| import { type TCompressionMethod } from './servertools/classes.compressor.js'; | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,9 @@ | ||||
| export * from './requestmodifier.js'; | ||||
| export * from './responsemodifier.js'; | ||||
| export * from './typedrequests.js'; | ||||
| 
 | ||||
| import * as serviceworker from './serviceworker.js'; | ||||
| 
 | ||||
| export { | ||||
|   serviceworker, | ||||
| } | ||||
							
								
								
									
										5
									
								
								ts_interfaces/plugins.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								ts_interfaces/plugins.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import * as typedrequestInterfaces from '@api.global/typedrequest-interfaces'; | ||||
|  | ||||
| export { | ||||
|   typedrequestInterfaces, | ||||
| } | ||||
							
								
								
									
										127
									
								
								ts_interfaces/serviceworker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								ts_interfaces/serviceworker.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
|  | ||||
| export interface CacheStorage { | ||||
|   keys: () => Promise<string[]>; | ||||
|   match: any; | ||||
|   open: any; | ||||
|   delete: any; | ||||
| } | ||||
| export declare var caches: CacheStorage; | ||||
|  | ||||
|  | ||||
| // ============================= | ||||
| // Interfaces for communication | ||||
| // ============================= | ||||
|  | ||||
| export interface IMessage_Serviceworker_Client_UpdateInfo | ||||
|   extends plugins.typedrequestInterfaces.implementsTR< | ||||
|     plugins.typedrequestInterfaces.ITypedRequest, | ||||
|     IMessage_Serviceworker_Client_UpdateInfo | ||||
|   > { | ||||
|   method: 'serviceworker_newVersion'; | ||||
|   request: { | ||||
|     appVersion: string; | ||||
|     appHash: string; | ||||
|   }; | ||||
|   response: {}; | ||||
| } | ||||
|  | ||||
| export interface IMessage_Serviceworker_Client_RequestReload | ||||
|   extends plugins.typedrequestInterfaces.implementsTR< | ||||
|     plugins.typedrequestInterfaces.ITypedRequest, | ||||
|     IMessage_Serviceworker_Client_RequestReload | ||||
|   > { | ||||
|   method: 'serviceworker_requestReload'; | ||||
|   request: {}; | ||||
|   response: {}; | ||||
| } | ||||
|  | ||||
| export interface IRequest_Serviceworker_Backend_VersionInfo | ||||
|   extends plugins.typedrequestInterfaces.implementsTR< | ||||
|     plugins.typedrequestInterfaces.ITypedRequest, | ||||
|     IRequest_Serviceworker_Backend_VersionInfo | ||||
|   > { | ||||
|   method: 'serviceworker_versionInfo'; | ||||
|   request: {}; | ||||
|   response: { | ||||
|     appHash: string; | ||||
|     appSemVer: string; | ||||
|   }; | ||||
| } | ||||
|  | ||||
| // =============== | ||||
| // web | ||||
| // =============== | ||||
| /** | ||||
|  * purges the service workers cache | ||||
|  */ | ||||
| export interface IRequest_PurgeServiceWorkerCache extends plugins.typedrequestInterfaces.implementsTR< | ||||
|   plugins.typedrequestInterfaces.ITypedRequest, | ||||
|   IRequest_PurgeServiceWorkerCache | ||||
| > { | ||||
|   method: 'purgeServiceWorkerCache'; | ||||
|   request: {}; | ||||
|   response: {}; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * updates the info in all connected tabs | ||||
|  */ | ||||
| export interface IMessage_Serviceworker_Client_UpdateInfo | ||||
|   extends plugins.typedrequestInterfaces.implementsTR< | ||||
|   plugins.typedrequestInterfaces.ITypedRequest, | ||||
|   IMessage_Serviceworker_Client_UpdateInfo | ||||
|   > { | ||||
|   method: 'serviceworker_newVersion'; | ||||
|   request: { | ||||
|     appVersion: string; | ||||
|     appHash: string; | ||||
|   }; | ||||
|   response: {}; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * requests all clients to reload | ||||
|  */ | ||||
| export interface IMessage_Serviceworker_Client_RequestReload | ||||
|   extends plugins.typedrequestInterfaces.implementsTR< | ||||
|   plugins.typedrequestInterfaces.ITypedRequest, | ||||
|   IMessage_Serviceworker_Client_RequestReload | ||||
|   > { | ||||
|   method: 'serviceworker_requestReload'; | ||||
|   request: {}; | ||||
|   response: {}; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * updates version infos | ||||
|  */ | ||||
| export interface IRequest_Serviceworker_Backend_VersionInfo | ||||
|   extends plugins.typedrequestInterfaces.implementsTR< | ||||
|   plugins.typedrequestInterfaces.ITypedRequest, | ||||
|   IRequest_Serviceworker_Backend_VersionInfo | ||||
|   > { | ||||
|   method: 'serviceworker_versionInfo'; | ||||
|   request: {}; | ||||
|   response: { | ||||
|     appHash: string; | ||||
|     appSemVer: string; | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ensures a stable connection between clients and the serviceworker | ||||
|  */ | ||||
| export interface IRequest_Client_Serviceworker_ConnectionPolling | ||||
|   extends plugins.typedrequestInterfaces.implementsTR< | ||||
|   plugins.typedrequestInterfaces.ITypedRequest, | ||||
|   IRequest_Client_Serviceworker_ConnectionPolling | ||||
|   > { | ||||
|   method: 'broadcastConnectionPolling', | ||||
|   request: { | ||||
|     tabId: string; | ||||
|   }, | ||||
|   response: { | ||||
|     serviceworkerId: string; | ||||
|   } | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import * as plugins from './typedserver_web.plugins.js'; | ||||
| import * as interfaces from '../ts/interfaces/index.js'; | ||||
| import * as interfaces from '../ts_interfaces/index.js'; | ||||
| import { logger } from './typedserver_web.logger.js'; | ||||
| logger.log('info', `TypedServer-Devtools initialized!`); | ||||
| 
 | ||||
							
								
								
									
										8
									
								
								ts_web_serviceworker/00_commitinfo_data.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								ts_web_serviceworker/00_commitinfo_data.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| /** | ||||
|  * autocreated commitinfo by @pushrocks/commitinfo | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@losslessone_private/lole-serviceworker', | ||||
|   version: '1.0.206', | ||||
|   description: 'serviceworker implementation for lossless websites' | ||||
| } | ||||
							
								
								
									
										53
									
								
								ts_web_serviceworker/classes.backend.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								ts_web_serviceworker/classes.backend.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as interfaces from '../dist_ts_interfaces/index.js'; | ||||
|  | ||||
| /** | ||||
|  * This class is meant to be used only on the backend side | ||||
|  */ | ||||
| export class ServiceworkerBackend { | ||||
|   public deesComms = new plugins.deesComms.DeesComms(); | ||||
|  | ||||
|   constructor(optionsArg: { | ||||
|     self: any; | ||||
|     purgeCache: (reqArg: interfaces.serviceworker.IRequest_PurgeServiceWorkerCache['request']) => Promise<interfaces.serviceworker.IRequest_PurgeServiceWorkerCache['response']>; | ||||
|   }) { | ||||
|  | ||||
|     // lets handle wakestuff | ||||
|     optionsArg.self.addEventListener('message', (event) => { | ||||
|       if (event.data && event.data.type === 'wakeUpCall') { | ||||
|         console.log('sw-backend: got wake up call'); | ||||
|       } | ||||
|     }); | ||||
|     this.deesComms.createTypedHandler<interfaces.serviceworker.IRequest_Client_Serviceworker_ConnectionPolling>('broadcastConnectionPolling', async reqArg => { | ||||
|       return { | ||||
|         serviceworkerId: '123' | ||||
|       }; | ||||
|     }) | ||||
|  | ||||
|     this.deesComms.createTypedHandler<interfaces.serviceworker.IRequest_PurgeServiceWorkerCache>('purgeServiceWorkerCache', async reqArg => { | ||||
|       console.log(`Executing purge cache in serviceworker backend.`) | ||||
|       return await optionsArg.purgeCache?.(reqArg); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * reloads all clients | ||||
|    */ | ||||
|   public async triggerReloadAll() { | ||||
|  | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * display notification | ||||
|    */ | ||||
|   public async addNotification(notificationArg: { | ||||
|     title: string; | ||||
|     body: string; | ||||
|   }) { | ||||
|      | ||||
|   } | ||||
|  | ||||
|   public async alert(alertText: string) { | ||||
|      | ||||
|   } | ||||
| } | ||||
							
								
								
									
										20
									
								
								ts_web_serviceworker/env.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								ts_web_serviceworker/env.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| export * from '../dist_ts_interfaces/index.js'; | ||||
|  | ||||
| // ============================= | ||||
| // Interfaces for the service worker | ||||
| // ============================= | ||||
| // tslint:disable-next-line: interface-name | ||||
| export interface ServiceEvent extends Event { | ||||
|   request: any; | ||||
|   respondWith: any; | ||||
|   waitUntil: any; | ||||
| } | ||||
|  | ||||
| // tslint:disable-next-line: interface-name | ||||
| export interface ServiceWindow extends Window { | ||||
|   addEventListener: any; | ||||
|   location: any; | ||||
|   skipWaiting: any; | ||||
|   clients: any; | ||||
| } | ||||
| declare var self: Window; | ||||
							
								
								
									
										8
									
								
								ts_web_serviceworker/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								ts_web_serviceworker/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| // TypeScript declatations | ||||
| import * as env from './env.js'; | ||||
| declare var self: env.ServiceWindow; | ||||
|  | ||||
| import { LosslessServiceWorker } from './serviceworker.classes.serviceworker.js'; | ||||
|  | ||||
| const losslessServiceWorkerInstance = new LosslessServiceWorker(self); | ||||
|  | ||||
							
								
								
									
										25
									
								
								ts_web_serviceworker/plugins.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								ts_web_serviceworker/plugins.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| // @losslessone_private scope | ||||
| import * as interfaces from '../dist_ts_interfaces/index.js'; | ||||
|  | ||||
| export { interfaces }; | ||||
|  | ||||
| // @apiglobal scope | ||||
| import * as typedrequest from '@api.global/typedrequest'; | ||||
|  | ||||
| export { typedrequest }; | ||||
|  | ||||
| // @pushrocks scope | ||||
| import * as smartdelay from '@push.rocks/smartdelay'; | ||||
| import * as smartpromise from '@push.rocks/smartpromise'; | ||||
| import * as webrequest from '@push.rocks/webrequest'; | ||||
| import * as webstore from '@push.rocks/webstore'; | ||||
| import * as taskbuffer from '@push.rocks/taskbuffer'; | ||||
|  | ||||
| export { smartdelay, smartpromise, webrequest, webstore, taskbuffer }; | ||||
|  | ||||
| // @design.estate scope | ||||
| import * as deesComms from '@design.estate/dees-comms'; | ||||
|  | ||||
| export { | ||||
|   deesComms, | ||||
| } | ||||
							
								
								
									
										224
									
								
								ts_web_serviceworker/serviceworker.classes.cachemanager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								ts_web_serviceworker/serviceworker.classes.cachemanager.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as interfaces from './env.js'; | ||||
| import { logger } from './serviceworker.logging.js'; | ||||
| import { LosslessServiceWorker } from './serviceworker.classes.serviceworker.js'; | ||||
|  | ||||
| export class CacheManager { | ||||
|   public losslessServiceWorkerRef: LosslessServiceWorker; | ||||
|  | ||||
|   public usedCacheNames = { | ||||
|     runtimeCacheName: 'runtime' | ||||
|   }; | ||||
|  | ||||
|   constructor(losslessServiceWorkerRefArg: LosslessServiceWorker) { | ||||
|     this.losslessServiceWorkerRef = losslessServiceWorkerRefArg; | ||||
|     this._setupCache(); | ||||
|   } | ||||
|  | ||||
|   private _setupCache = () => { | ||||
|     const createMatchRequest = (requestArg: Request) => { | ||||
|       // lets create a matchRequest | ||||
|       let matchRequest: Request; | ||||
|       if (requestArg.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin)) { | ||||
|         // internal request | ||||
|         matchRequest = requestArg; | ||||
|       } else { | ||||
|         matchRequest = new Request(requestArg.url, { | ||||
|           ...requestArg.clone(), | ||||
|           mode: 'cors' | ||||
|         }); | ||||
|       } | ||||
|       return matchRequest; | ||||
|     }; | ||||
|    | ||||
|     /** | ||||
|      * creates a 500 response | ||||
|      */ | ||||
|     const create500Response = async (requestArg: Request, responseArg: Response) => { | ||||
|       return new Response( | ||||
|         ` | ||||
|         <html> | ||||
|           <head> | ||||
|             <style> | ||||
|               .note { | ||||
|                 padding: 10px; | ||||
|                 color: #fff; | ||||
|                 background: #000; | ||||
|                 border-bottom: 1px solid #e4002b; | ||||
|                 margin-bottom: 20px; | ||||
|               } | ||||
|             </style> | ||||
|           </head> | ||||
|           <body> | ||||
|            | ||||
|           <div class="note"> | ||||
|             <strong>serviceworker running, but status 500</strong><br> | ||||
|           </div> | ||||
|           serviceworker is unable to fetch this request<br> | ||||
|           Here is some info about the request/response pair:<br> | ||||
|           <br> | ||||
|           requestUrl: ${requestArg.url}<br> | ||||
|           responseType: ${responseArg.type}<br> | ||||
|           responseBody: ${await responseArg.clone().text()}<br> | ||||
|           </body> | ||||
|         </html> | ||||
|       `, | ||||
|         { | ||||
|           headers: { | ||||
|             "Content-Type": "text/html" | ||||
|           }, | ||||
|           status: 500 | ||||
|         } | ||||
|       ); | ||||
|     }; | ||||
|    | ||||
|     // A list of local resources we always want to be cached. | ||||
|     this.losslessServiceWorkerRef.serviceWindowRef.addEventListener('fetch', async (fetchEventArg: any) => { | ||||
|       // Lets block scopes we don't want to be passing through the serviceworker | ||||
|       const parsedUrl = new URL(fetchEventArg.request.url) | ||||
|       if ( | ||||
|         parsedUrl.hostname.includes('paddle.com') | ||||
|         || parsedUrl.hostname.includes('paypal.com') | ||||
|         || parsedUrl.hostname.includes('reception.lossless.one') | ||||
|         || parsedUrl.pathname.startsWith('/socket.io') | ||||
|       ) { | ||||
|         logger.log('note',`serviceworker not active for ${parsedUrl.toString()}`); | ||||
|         return; | ||||
|       } | ||||
|        | ||||
|       // lets continue for the rest | ||||
|       const done = plugins.smartpromise.defer<Response>(); | ||||
|       fetchEventArg.respondWith(done.promise); | ||||
|       const originalRequest: Request = fetchEventArg.request; | ||||
|    | ||||
|       if ( | ||||
|         (originalRequest.method === 'GET' && | ||||
|           (originalRequest.url.startsWith(this.losslessServiceWorkerRef.serviceWindowRef.location.origin) && | ||||
|             !originalRequest.url.includes('/api/') && | ||||
|             !originalRequest.url.includes('smartserve/reloadcheck'))) || | ||||
|         originalRequest.url.includes('https://assetbroker.lossless.one/public') || | ||||
|         originalRequest.url.includes('https://assetbroker.lossless.one/brandfiles') || | ||||
|         originalRequest.url.includes('https://assetbroker.lossless.one/websites') || | ||||
|         originalRequest.url.includes('https://unpkg.com') || | ||||
|         originalRequest.url.includes('https://fonts.googleapis.com') || | ||||
|         originalRequest.url.includes('https://fonts.gstatic.com') | ||||
|       ) { | ||||
|    | ||||
|         // lets see if things need to be updated | ||||
|         // not waiting here | ||||
|         this.losslessServiceWorkerRef.updateManager.checkUpdate(this); | ||||
|    | ||||
|         // this code block is executed for local requests | ||||
|         const matchRequest = createMatchRequest(originalRequest); | ||||
|         const cachedResponse = await caches.match(matchRequest); | ||||
|         if (cachedResponse) { | ||||
|           logger.log('ok', `CACHED: found cached response for ${matchRequest.url}`); | ||||
|           done.resolve(cachedResponse); | ||||
|           return; | ||||
|         } | ||||
|    | ||||
|         // in case there is no cached response | ||||
|         logger.log('info', `NOTYETCACHED: trying to cache ${matchRequest.url}`); | ||||
|         const newResponse: Response = await fetch(matchRequest).catch(async err => { | ||||
|           return await create500Response(matchRequest, new Response(err.message)); | ||||
|         }); | ||||
|    | ||||
|         // fill cache | ||||
|         // Put a copy of the response in the runtime cache. | ||||
|         if (newResponse.status > 299 || newResponse.type === 'opaque') { | ||||
|           logger.log( | ||||
|             'error', | ||||
|             `NOTCACHED: can't cache response for ${matchRequest.url} due to status ${ | ||||
|               newResponse.status | ||||
|             } and type ${newResponse.type}` | ||||
|           ); | ||||
|           done.resolve(await create500Response(matchRequest, newResponse)); | ||||
|         } else { | ||||
|           const cache = await caches.open(this.usedCacheNames.runtimeCacheName); | ||||
|           const responseToPutToCache = newResponse.clone(); | ||||
|           const headers = new Headers(); | ||||
|           responseToPutToCache.headers.forEach((value, key) => { | ||||
|             if ( | ||||
|               value !== 'Cache-Control' | ||||
|               && value !== 'cache-control' | ||||
|               && value !== 'Expires' | ||||
|               && value !== 'expires' | ||||
|               && value !== 'Pragma' | ||||
|               && value !== 'pragma' | ||||
|             ) { | ||||
|               headers.set(key, value); | ||||
|             } | ||||
|           }); | ||||
|           headers.set('Cache-Control', 'no-cache, no-store, must-revalidate'); | ||||
|           headers.set('Pragma', 'no-cache'); | ||||
|           headers.set('Expires', '0'); | ||||
|           await cache.put(matchRequest, new Response(responseToPutToCache.body, { | ||||
|             ...responseToPutToCache, | ||||
|             headers | ||||
|           })); | ||||
|           logger.log( | ||||
|             'ok', | ||||
|             `NOWCACHED: cached response for ${matchRequest.url} for subsequent requests!` | ||||
|           ); | ||||
|           done.resolve(newResponse); | ||||
|         } | ||||
|       } else { | ||||
|         // this code block is executed for remote requests | ||||
|         logger.log( | ||||
|           'ok', | ||||
|           `NOTCACHED: not caching any responses for ${ | ||||
|             originalRequest.url | ||||
|           }. Fetching from origin now...` | ||||
|         ); | ||||
|         done.resolve( | ||||
|           await fetch(originalRequest).catch(async err => { | ||||
|             return await create500Response(originalRequest, new Response(err.message)); | ||||
|           }) | ||||
|         ); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * update caches | ||||
|    * @param reasonArg | ||||
|    */ | ||||
|  | ||||
|   /** | ||||
|    * cleans all caches | ||||
|    * should only be run when running a new service worker | ||||
|    * @param reasonArg | ||||
|    */ | ||||
|   public cleanCaches = async (reasonArg = 'no reason given') => { | ||||
|     logger.log('info', `MAJOR CACHEEVENT: cleaning caches now! Reason: ${reasonArg}`); | ||||
|     const cacheNames = await caches.keys(); | ||||
|      | ||||
|     const deletePromises = cacheNames.map(cacheToDelete => { | ||||
|         const deletePromise = caches.delete(cacheToDelete); | ||||
|         deletePromise.then(() => { | ||||
|           logger.log('ok', `Deleted cache ${cacheToDelete}`); | ||||
|         }); | ||||
|         return deletePromise; | ||||
|       }); | ||||
|     await Promise.all(deletePromises); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * revalidate cache | ||||
|    */ | ||||
|   public async revalidateCache() { | ||||
|     const runtimeCache = await caches.open(this.usedCacheNames.runtimeCacheName); | ||||
|     const cacheKeys = await runtimeCache.keys(); | ||||
|     for (const requestArg of cacheKeys) { | ||||
|       const cachedResponse = runtimeCache.match(requestArg); | ||||
|  | ||||
|       // lets get a new response for comparison | ||||
|       const clonedRequest = requestArg.clone(); | ||||
|       const response = await plugins.smartpromise.timeoutWrap(fetch(clonedRequest), 1000); | ||||
|       if (response && response.status >= 200 && response.status < 300) { | ||||
|         await runtimeCache.delete(requestArg); | ||||
|         await runtimeCache.put(requestArg, response); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										33
									
								
								ts_web_serviceworker/serviceworker.classes.networkmanager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								ts_web_serviceworker/serviceworker.classes.networkmanager.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import { LosslessServiceWorker } from './serviceworker.classes.serviceworker.js'; | ||||
|  | ||||
| export class NetworkManager { | ||||
|   public serviceWorkerRef: LosslessServiceWorker; | ||||
|   public webRequest: plugins.webrequest.WebRequest; | ||||
|  | ||||
|   public previousState: string; | ||||
|  | ||||
|   constructor(serviceWorkerRefArg: LosslessServiceWorker) { | ||||
|     this.serviceWorkerRef = serviceWorkerRefArg; | ||||
|     this.webRequest = new plugins.webrequest.WebRequest(); | ||||
|     this.getConnection()?.addEventListener('change', () => { | ||||
|       this.updateConnectionStatus(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * gets the connection | ||||
|    */ | ||||
|   public getConnection() { | ||||
|     const navigatorLocal: any = self.navigator; | ||||
|     return navigatorLocal?.connection; | ||||
|   } | ||||
|  | ||||
|   public getEffectiveType() { | ||||
|     return this.getConnection()?.effectiveType || '4g'; | ||||
|   } | ||||
|  | ||||
|   public updateConnectionStatus() { | ||||
|     console.log(`Connection type changed from ${this.previousState} to ${this.getEffectiveType()}`); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										75
									
								
								ts_web_serviceworker/serviceworker.classes.serviceworker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								ts_web_serviceworker/serviceworker.classes.serviceworker.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import * as interfaces from './env.js'; | ||||
|  | ||||
| // imports | ||||
| import { CacheManager } from './serviceworker.classes.cachemanager.js'; | ||||
| import { Deferred } from '@push.rocks/smartpromise'; | ||||
|  | ||||
| import { logger } from './serviceworker.logging.js'; | ||||
|  | ||||
| // imported classes | ||||
| import { UpdateManager } from './serviceworker.classes.updatemanager.js'; | ||||
| import { NetworkManager } from './serviceworker.classes.networkmanager.js'; | ||||
| import { TaskManager } from './serviceworker.classes.taskmanager.js'; | ||||
|  | ||||
| export class LosslessServiceWorker { | ||||
|   // STATIC | ||||
|  | ||||
|   // INSTANCE | ||||
|   public serviceWindowRef: interfaces.ServiceWindow; | ||||
|   public leleServiceWorkerBackend: plugins.leleServiceworker.LosslessServiceworkerBackend; | ||||
|  | ||||
|   public cacheManager: CacheManager; | ||||
|  | ||||
|   public updateManager: UpdateManager; | ||||
|   public networkManager: NetworkManager; | ||||
|   public taskManager: TaskManager; | ||||
|   public store: plugins.webstore.WebStore; | ||||
|  | ||||
|   constructor(selfArg: interfaces.ServiceWindow) { | ||||
|     logger.log('info', `Service worker instantiating at ${Date.now()}`); | ||||
|     this.serviceWindowRef = selfArg; | ||||
|     this.leleServiceWorkerBackend = plugins.leleServiceworker.getServiceWorkerBackend({ | ||||
|       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 | ||||
|       done.resolve(); | ||||
|       logger.log('success', `service worker installed! TimeStamp = ${new Date().toISOString()}`); | ||||
|       selfArg.skipWaiting(); | ||||
|       logger.log('note', `Called skip waiting!`); | ||||
|     }); | ||||
|  | ||||
|     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 | ||||
|       await selfArg.clients.claim(); | ||||
|       await this.cacheManager.cleanCaches('new service worker loaded! :)'); | ||||
|       done.resolve(); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										15
									
								
								ts_web_serviceworker/serviceworker.classes.taskmanager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								ts_web_serviceworker/serviceworker.classes.taskmanager.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import { LosslessServiceWorker } from './serviceworker.classes.serviceworker.js'; | ||||
|  | ||||
| /** | ||||
|  * Taskmanager | ||||
|  * should use times allocated by browser | ||||
|  */ | ||||
| export class TaskManager { | ||||
|   public serviceworkerRef: LosslessServiceWorker; | ||||
|  | ||||
|   constructor(serviceWorkerRefArg: LosslessServiceWorker) { | ||||
|     this.serviceworkerRef = serviceWorkerRefArg; | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
							
								
								
									
										90
									
								
								ts_web_serviceworker/serviceworker.classes.updatemanager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								ts_web_serviceworker/serviceworker.classes.updatemanager.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import { LosslessServiceWorker } from './serviceworker.classes.serviceworker.js'; | ||||
| import { logger } from './serviceworker.logging.js'; | ||||
| import { CacheManager } from './serviceworker.classes.cachemanager.js'; | ||||
|  | ||||
| export class UpdateManager { | ||||
|   public lastUpdateCheck: number = 0; | ||||
|   public lastVersionInfo: plugins.lointServiceworker.IRequest_Serviceworker_Backend_VersionInfo['response']; | ||||
|  | ||||
|   public serviceworkerRef: LosslessServiceWorker; | ||||
|  | ||||
|   constructor(serviceWorkerRefArg: LosslessServiceWorker) { | ||||
|     this.serviceworkerRef = serviceWorkerRefArg; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * checks wether an update is needed | ||||
|    */ | ||||
|   public async checkUpdate(cacheManager: CacheManager): Promise<boolean> { | ||||
|     const lswVersionInfoKey = 'versionInfo'; | ||||
|     if (!this.lastVersionInfo && !(await this.serviceworkerRef.store.check(lswVersionInfoKey))) { | ||||
|       this.lastVersionInfo = { | ||||
|         appHash: '', | ||||
|         appSemVer: 'v0.0.0', | ||||
|       }; | ||||
|     } else if ( | ||||
|       !this.lastVersionInfo && | ||||
|       (await this.serviceworkerRef.store.check(lswVersionInfoKey)) | ||||
|     ) { | ||||
|       this.lastVersionInfo = await this.serviceworkerRef.store.get(lswVersionInfoKey); | ||||
|     } | ||||
|  | ||||
|     const now = Date.now(); | ||||
|     const millisSinceLastCheck = now - this.lastUpdateCheck; | ||||
|     if (millisSinceLastCheck < 100000) { | ||||
|       // TODO account for being offline | ||||
|       return false; | ||||
|     } | ||||
|     logger.log('info', 'checking for update of the app by comparing app hashes...'); | ||||
|     this.lastUpdateCheck = now; | ||||
|     const currentVersionInfo = await this.getVersionInfoFromServer(); | ||||
|     logger.log('info', `old versionInfo: ${JSON.stringify(this.lastVersionInfo)}`); | ||||
|     logger.log('info', `current versionInfo: ${JSON.stringify(currentVersionInfo)}`); | ||||
|     const needsUpdate = this.lastVersionInfo.appHash !== currentVersionInfo.appHash ? true : false; | ||||
|     if (needsUpdate) { | ||||
|       logger.log('info', 'Caches need to be updated'); | ||||
|       logger.log('info', 'starting a debounced update task'); | ||||
|       this.performAsyncUpdateDebouncedTask.trigger(); | ||||
|       this.lastVersionInfo = currentVersionInfo; | ||||
|       await this.serviceworkerRef.store.set(lswVersionInfoKey, this.lastVersionInfo); | ||||
|     } else { | ||||
|       logger.log('ok', 'caches are still valid, performing revalidation in a bit...'); | ||||
|       this.performAsyncCacheRevalidationDebouncedTask.trigger(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * gets the apphash from the server | ||||
|    */ | ||||
|   public async getVersionInfoFromServer() { | ||||
|     const getAppHashRequest = new plugins.typedrequest.TypedRequest< | ||||
|       plugins.lointServiceworker.IRequest_Serviceworker_Backend_VersionInfo | ||||
|     >('/lsw-typedrequest', 'serviceworker_versionInfo'); | ||||
|     const result = await getAppHashRequest.fire({}); | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   // tasks | ||||
|   /** | ||||
|    * this task is executed once we know that there is a new version available | ||||
|    */ | ||||
|   public performAsyncUpdateDebouncedTask = new plugins.taskbuffer.TaskDebounced({ | ||||
|     name: 'performAsyncUpdate', | ||||
|     taskFunction: async () => { | ||||
|       logger.log('info', 'trying to update PWA with serviceworker'); | ||||
|       await this.serviceworkerRef.cacheManager.cleanCaches('a new app version has been communicated by the server.'); | ||||
|       // lets notify all current clients about the update | ||||
|       await this.serviceworkerRef.leleServiceWorkerBackend.triggerReloadAll(); | ||||
|     }, | ||||
|     debounceTimeInMillis: 2000, | ||||
|   }); | ||||
|  | ||||
|   public performAsyncCacheRevalidationDebouncedTask = new plugins.taskbuffer.TaskDebounced({ | ||||
|     name: 'performAsyncCacheRevalidation', | ||||
|     taskFunction: async () => { | ||||
|       await this.serviceworkerRef.cacheManager.revalidateCache(); | ||||
|     }, | ||||
|     debounceTimeInMillis: 6000 | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										17
									
								
								ts_web_serviceworker/serviceworker.logging.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								ts_web_serviceworker/serviceworker.logging.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| import * as smartlog from '@push.rocks/smartlog'; | ||||
| import { SmartlogDestinationDevtools } from '@push.rocks/smartlog-destination-devtools'; | ||||
|  | ||||
| export const logger = new smartlog.Smartlog({ | ||||
|   logContext: { | ||||
|     company: 'Lossless GmbH', | ||||
|     companyunit: 'Lossless Cloud', | ||||
|     containerName: 'web', | ||||
|     environment: 'production', | ||||
|     runtime: 'chrome', | ||||
|     zone: 'servezone' | ||||
|   }, | ||||
|   minimumLogLevel: 'info' | ||||
| }); | ||||
|  | ||||
| logger.addLogDestination(new SmartlogDestinationDevtools()); | ||||
| logger.log('note', 'serviceworker console initialized!'); | ||||
							
								
								
									
										8
									
								
								ts_web_serviceworker_client/00_commitinfo_data.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								ts_web_serviceworker_client/00_commitinfo_data.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| /** | ||||
|  * autocreated commitinfo by @pushrocks/commitinfo | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@losslessone_private/lele-serviceworker', | ||||
|   version: '1.0.58', | ||||
|   description: 'the mainthread of the serviceworker' | ||||
| } | ||||
							
								
								
									
										24
									
								
								ts_web_serviceworker_client/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								ts_web_serviceworker_client/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| // types | ||||
| import type * as interfaces from './interfaces/index.js'; | ||||
| export type { | ||||
|   interfaces | ||||
| } | ||||
|  | ||||
| // ==================================== | ||||
| // imports | ||||
| // ==================================== | ||||
|  | ||||
| import { logger } from './serviceworker.logging.js'; | ||||
| logger.log('note', 'mainthread console initialized!'); | ||||
|  | ||||
| import { LosslessServiceworker } from './lele-serviceworker.classes.serviceworker.js'; | ||||
|  | ||||
| export type { | ||||
|   LosslessServiceworker | ||||
| } | ||||
|  | ||||
| export const getServiceWorker = async () => { | ||||
|   const losslessServiceWorkerInstance = await LosslessServiceworker.createServiceWorker(); // lets setup the service worker | ||||
|   logger.log('ok', 'service worker ready!'); // and wait for it to be ready | ||||
|   return losslessServiceWorkerInstance; | ||||
| }; | ||||
							
								
								
									
										2
									
								
								ts_web_serviceworker_client/interfaces/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								ts_web_serviceworker_client/interfaces/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| import * as plugins from '../lele-serviceworker.plugins.js'; | ||||
|  | ||||
| @@ -0,0 +1,58 @@ | ||||
| import * as plugins from './lele-serviceworker.plugins.js'; | ||||
| import * as interfaces from './interfaces/index.js'; | ||||
| import { logger } from './serviceworker.logging.js'; | ||||
|  | ||||
| /** | ||||
|  * MessageManager implements two ways of serviceworker communication | ||||
|  * * the serviceWorker method | ||||
|  * * the deesComms method using BroadcastChannel | ||||
|  */ | ||||
| export class ActionManager { | ||||
|   public deesComms = new plugins.deesComms.DeesComms(); | ||||
|  | ||||
|   constructor() { | ||||
|     // lets define handlers on the client/tab side | ||||
|     this.deesComms.createTypedHandler<interfaces.IMessage_Serviceworker_Client_UpdateInfo>('serviceworker_newVersion', async req => { | ||||
|       setTimeout(() => { | ||||
|         window.location.reload(); | ||||
|       }, 200); | ||||
|       return {}; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public async waitForServiceWorkerConnection () { | ||||
|     console.log('waiting for service worker connection...') | ||||
|     const tr = this.deesComms.createTypedRequest<interfaces.IRequest_Client_Serviceworker_ConnectionPolling>('broadcastConnectionPolling'); | ||||
|     let connected = false; | ||||
|     while (!connected) { | ||||
|       tr.fire({ | ||||
|         tabId: '123' | ||||
|       }).then(response => { | ||||
|         if (response.serviceworkerId) { | ||||
|           console.log('connected to serviceworker!'); | ||||
|           connected = true; | ||||
|         } | ||||
|       }).catch(); | ||||
|       await plugins.smartdelay.delayFor(777); | ||||
|       if (!connected) { | ||||
|         // lets wake it up. | ||||
|         navigator.serviceWorker.controller.postMessage({ | ||||
|           type: 'wakeUpCall', | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|     console.log('ok, got serviceworker connection.') | ||||
|   } | ||||
|  | ||||
|   public async purgeServiceWorkerCache () { | ||||
|     const tr = this.deesComms.createTypedRequest<interfaces.IRequest_PurgeServiceWorkerCache>('purgeServiceWorkerCache'); | ||||
|     const response = await tr.fire({}); | ||||
|     return response; | ||||
|   } | ||||
|  | ||||
|   public async getVersionInfo () { | ||||
|     const tr = this.deesComms.createTypedRequest<interfaces.IRequest_Serviceworker_Backend_VersionInfo>('serviceworker_versionInfo'); | ||||
|     const response = await tr.fire({}); | ||||
|     return response; | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,29 @@ | ||||
| import * as plugins from './lele-serviceworker.plugins.js'; | ||||
| import { LosslessServiceworker } from './lele-serviceworker.classes.serviceworker.js'; | ||||
|  | ||||
| export class GlobalSW { | ||||
|   losslessSw: LosslessServiceworker; | ||||
|   constructor(losslessServiceWorkerInstanceArg: LosslessServiceworker) { | ||||
|     this.losslessSw = losslessServiceWorkerInstanceArg; | ||||
|     globalThis.globalSw = this; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * purges the cache of the app's serviceworker | ||||
|    * @returns | ||||
|    */ | ||||
|   public async purgeCache() { | ||||
|     await this.losslessSw.actionManager.waitForServiceWorkerConnection(); | ||||
|     console.log(`purgeCache() was executed via globalThis.globalSw`); | ||||
|     const result = await this.losslessSw.actionManager.purgeServiceWorkerCache(); | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * attempts to reload the app | ||||
|    */ | ||||
|   public async reloadApp() { | ||||
|     await this.purgeCache(); | ||||
|     window.location.reload(); | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,16 @@ | ||||
| import * as plugins from './lele-serviceworker.plugins.js'; | ||||
| import * as interfaces from './interfaces/index.js'; | ||||
| import { logger } from "./serviceworker.logging.js"; | ||||
|  | ||||
| export class NotificationManager { | ||||
|  | ||||
|   constructor() { | ||||
|     // this.askPermission(); | ||||
|   } | ||||
|  | ||||
|   public askPermission () { | ||||
|     Notification.requestPermission((status) => { | ||||
|       console.log('Notification permission status:', status); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,69 @@ | ||||
| import * as plugins from './lele-serviceworker.plugins.js'; | ||||
| import * as interfaces from './interfaces/index.js'; | ||||
| import { logger } from "./serviceworker.logging.js"; | ||||
| import { NotificationManager } from './lele-serviceworker.classes.notificationmanager.js'; | ||||
| import { ActionManager } from './lele-serviceworker.classes.actionmanager.js'; | ||||
| import { GlobalSW } from './lele-serviceworker.classes.globalsw.js' | ||||
|  | ||||
| export class LosslessServiceworker { | ||||
|   // STATIC | ||||
|   public static async createServiceWorker(): Promise<LosslessServiceworker> { | ||||
|     if ('serviceWorker' in navigator) { | ||||
|       try { | ||||
|         logger.log('info', 'trying to register serviceworker'); | ||||
|         // this is some magic for Parcel to not pick up the serviceworker | ||||
|         const serviceworkerInNavigator: ServiceWorkerContainer = navigator.serviceWorker; | ||||
|         const swRegistration: ServiceWorkerRegistration = await serviceworkerInNavigator.register('/lsw.js', { | ||||
|           scope: '/', | ||||
|           updateViaCache: 'none' | ||||
|         }); | ||||
|         plugins.smartdelay.delayFor(2000).then(async () => { | ||||
|           swRegistration.onupdatefound = () => { | ||||
|             logger.log('info', 'update found for service worker!'); | ||||
|             logger.log('warn', 'trying to find convenient time to update'); | ||||
|           }; | ||||
|           while(true) { | ||||
|             await plugins.smartdelay.delayFor(60000); | ||||
|             swRegistration.update(); | ||||
|           } | ||||
|         }); | ||||
|         logger.log('ok', 'serviceworker registered'); | ||||
|         await navigator.serviceWorker.ready; | ||||
|         logger.log('ok', 'serviceworker is ready!'); | ||||
|         await this.waitForController(); | ||||
|         const losslessServiceWorkerInstance = new LosslessServiceworker(); | ||||
|         return losslessServiceWorkerInstance; | ||||
|       } catch (err) { | ||||
|         // sentry integration here | ||||
|         console.log(err); | ||||
|         console.log(err.stack); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private static async waitForController() { | ||||
|     const done = new plugins.smartpromise.Deferred(); | ||||
|     const checkReady = () => { | ||||
|       if(navigator.serviceWorker.controller) { | ||||
|         logger.log('ok', 'controller is ready'); | ||||
|         done.resolve(); | ||||
|       } else { | ||||
|         logger.log('warn', 'controller not ready'); | ||||
|       } | ||||
|     }; | ||||
|     navigator.serviceWorker.oncontrollerchange = checkReady; | ||||
|     checkReady(); | ||||
|     await done.promise; | ||||
|   } | ||||
|  | ||||
|   // INSTANCE | ||||
|   public notificationManager: NotificationManager; | ||||
|   public actionManager: ActionManager; | ||||
|   public globalSw: GlobalSW; | ||||
|  | ||||
|   constructor() { | ||||
|     this.notificationManager = new NotificationManager(); | ||||
|     this.actionManager = new ActionManager(); | ||||
|     this.globalSw = new GlobalSW(this); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										22
									
								
								ts_web_serviceworker_client/lele-serviceworker.plugins.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								ts_web_serviceworker_client/lele-serviceworker.plugins.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| // @apiglobal scope | ||||
| import * as typedrequestInterfaces from '@api.global/typedrequest-interfaces'; | ||||
|  | ||||
| export { typedrequestInterfaces }; | ||||
|  | ||||
| // designestate | ||||
| import * as deesComms from '@design.estate/dees-comms'; | ||||
|  | ||||
| export { | ||||
|   deesComms | ||||
| }; | ||||
|  | ||||
| // @pushrocks scope | ||||
| import * as smartdelay from '@push.rocks/smartdelay'; | ||||
| import * as smartpromise from '@push.rocks/smartpromise'; | ||||
| import * as webstore from '@push.rocks/webstore'; | ||||
|  | ||||
| export { | ||||
|   smartdelay, | ||||
|   smartpromise, | ||||
|   webstore | ||||
| }; | ||||
							
								
								
									
										16
									
								
								ts_web_serviceworker_client/serviceworker.logging.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								ts_web_serviceworker_client/serviceworker.logging.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import * as smartlog from '@push.rocks/smartlog'; | ||||
| import { SmartlogDestinationDevtools } from '@push.rocks/smartlog-destination-devtools'; | ||||
|  | ||||
| export const logger = new smartlog.Smartlog({ | ||||
|   logContext: { | ||||
|     company: 'Lossless GmbH', | ||||
|     companyunit: 'Lossless Cloud', | ||||
|     containerName: 'web', | ||||
|     environment: 'production', | ||||
|     runtime: 'chrome', | ||||
|     zone: 'servezone' | ||||
|   }, | ||||
|   minimumLogLevel: 'info' | ||||
| }); | ||||
|  | ||||
| logger.addLogDestination(new SmartlogDestinationDevtools()); | ||||
		Reference in New Issue
	
	Block a user