|  |  |  | const WORKER_TIMEOUT = 60 * 1000; // one minute
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TimedOutError extends Error { | 
					
						
							|  |  |  |   constructor(message: string) { | 
					
						
							|  |  |  |     super(message); | 
					
						
							|  |  |  |     this.name = this.constructor.name; | 
					
						
							|  |  |  |     if (typeof Error.captureStackTrace === 'function') { | 
					
						
							|  |  |  |       Error.captureStackTrace(this, this.constructor); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       this.stack = new Error(message).stack; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class WorkerInterface { | 
					
						
							|  |  |  |   private readonly timeout: number; | 
					
						
							|  |  |  |   private readonly _DEBUG: boolean; | 
					
						
							|  |  |  |   private _jobCounter: number; | 
					
						
							|  |  |  |   private readonly _jobs: Record<number, any>; | 
					
						
							|  |  |  |   private readonly _worker: Worker; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(path: string, timeout = WORKER_TIMEOUT) { | 
					
						
							|  |  |  |     (process as any).dlopen = () => { | 
					
						
							|  |  |  |       throw new Error('Load native module is not safe'); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._worker = new Worker(path); | 
					
						
							|  |  |  |     this.timeout = timeout; | 
					
						
							|  |  |  |     this._jobs = Object.create(null); | 
					
						
							|  |  |  |     this._DEBUG = false; | 
					
						
							|  |  |  |     this._jobCounter = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._worker.onmessage = e => { | 
					
						
							|  |  |  |       const [jobId, errorForDisplay, result] = e.data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const job = this._getJob(jobId); | 
					
						
							|  |  |  |       if (!job) { | 
					
						
							|  |  |  |         throw new Error( | 
					
						
							|  |  |  |           `Received worker reply to job ${jobId}, but did not have it in our registry!` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const { resolve, reject, fnName } = job; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (errorForDisplay) { | 
					
						
							|  |  |  |         // eslint:disable: no-console
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         window?.log?.error(`Error received from worker job ${jobId} (${fnName}):`, errorForDisplay); | 
					
						
							|  |  |  |         return reject( | 
					
						
							|  |  |  |           new Error(`Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}`) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return resolve(result); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public async callWorker(fnName: string, ...args: any) { | 
					
						
							|  |  |  |     const jobId = this._makeJob(fnName); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return new Promise((resolve, reject) => { | 
					
						
							|  |  |  |       this._worker.postMessage([jobId, fnName, ...args]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this._updateJob(jobId, { | 
					
						
							|  |  |  |         resolve, | 
					
						
							|  |  |  |         reject, | 
					
						
							|  |  |  |         args: this._DEBUG ? args : null, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       setTimeout(() => { | 
					
						
							|  |  |  |         reject(new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`)); | 
					
						
							|  |  |  |       }, this.timeout); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _makeJob(fnName: string): number { | 
					
						
							|  |  |  |     this._jobCounter += 1; | 
					
						
							|  |  |  |     const id = this._jobCounter; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (this._DEBUG) { | 
					
						
							|  |  |  |       window.log.info(`Worker job ${id} (${fnName}) started`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this._jobs[id] = { | 
					
						
							|  |  |  |       fnName, | 
					
						
							|  |  |  |       start: Date.now(), | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return id; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _updateJob(id: number, data: any) { | 
					
						
							|  |  |  |     const { resolve, reject } = data; | 
					
						
							|  |  |  |     const { fnName, start } = this._jobs[id]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this._jobs[id] = { | 
					
						
							|  |  |  |       ...this._jobs[id], | 
					
						
							|  |  |  |       ...data, | 
					
						
							|  |  |  |       resolve: (value: any) => { | 
					
						
							|  |  |  |         this._removeJob(id); | 
					
						
							|  |  |  |         const end = Date.now(); | 
					
						
							|  |  |  |         if (this._DEBUG) { | 
					
						
							|  |  |  |           window.log.info(`Worker job ${id} (${fnName}) succeeded in ${end - start}ms`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return resolve(value); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       reject: (error: any) => { | 
					
						
							|  |  |  |         this._removeJob(id); | 
					
						
							|  |  |  |         const end = Date.now(); | 
					
						
							|  |  |  |         window.log.info( | 
					
						
							|  |  |  |           `Worker job ${id} (${fnName}) failed in ${end - start}ms with ${error.message}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return reject(error); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _removeJob(id: number) { | 
					
						
							|  |  |  |     if (this._DEBUG) { | 
					
						
							|  |  |  |       this._jobs[id].complete = true; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       delete this._jobs[id]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _getJob(id: number) { | 
					
						
							|  |  |  |     return this._jobs[id]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |