1 import {Injectable} from '@angular/core';
3 /** Service to relay requests to/from our IndexedDB shared worker
4 * Beware requests will be rejected when SharedWorker's are not supported.
9 action: 'selectWhereIn',
12 ).then(value => console.log('my value', value)
13 ).catch(_ => console.log('SharedWorker's not supported));
17 // TODO: move to a more generic location.
18 const WORKER_URL = '/js/ui/default/staff/offline-db-worker.js';
20 // Tell TS about SharedWorkers
21 // https://stackoverflow.com/questions/13296549/typescript-enhanced-sharedworker-portmessage-channel-contracts
22 interface SharedWorker extends AbstractWorker {
26 // eslint-disable-next-line no-redeclare, no-var
27 declare var SharedWorker: {
28 prototype: SharedWorker;
29 new (scriptUrl: any, name?: any): SharedWorker;
33 // Requests in flight to the shared worker
34 interface ActiveRequest {
36 resolve(response: any): any;
37 reject(error: any): any;
40 // Shared worker request structure. This is the request that's
41 // relayed to the shared worker.
42 // DbStoreRequest.id === ActiveRequest.id
43 interface DbStoreRequest {
53 // Expected response structure from the shared worker.
54 // Note callers only recive the 'result' content, which may
56 interface DbStoreResponse {
63 @Injectable({providedIn: 'root'})
64 export class DbStoreService {
66 autoId = 0; // each request gets a unique id.
67 cannotConnect: boolean;
69 activeRequests: {[id: number]: ActiveRequest} = {};
71 // Schemas we should connect to
72 activeSchemas: string[] = ['cache']; // add 'offline' in the offline UI
74 // Schemas we are in the process of connecting to
75 schemasInProgress: {[schema: string]: Promise<any>} = {};
77 // Schemas we have successfully connected to
78 schemasConnected: {[schema: string]: boolean} = {};
80 worker: SharedWorker = null;
84 // Returns true if connection is successful, false otherwise
85 private connectToWorker(): boolean {
86 if (this.worker) { return true; }
87 if (this.cannotConnect) { return false; }
90 this.worker = new SharedWorker(WORKER_URL);
92 console.warn('SharedWorker() not supported', E);
93 this.cannotConnect = true;
97 this.worker.onerror = err => {
98 this.cannotConnect = true;
99 console.error('Cannot connect to DB shared worker', err);
102 // List for responses and resolve the matching pending request.
103 this.worker.port.addEventListener(
104 'message', evt => this.handleMessage(evt));
106 this.worker.port.start();
110 private handleMessage(evt: MessageEvent) {
111 const response: DbStoreResponse = evt.data as DbStoreResponse;
112 const reqId = response.id;
113 const req = this.activeRequests[reqId];
116 console.error('Recieved response for unknown request', reqId);
120 // Request is no longer active.
121 delete this.activeRequests[reqId];
123 if (response.status === 'OK') {
124 req.resolve(response.result);
126 console.error('worker request failed with', response.error);
127 req.reject(response.error);
131 // Send a request to the web worker and register the request
132 // for future resolution. Store the request ID in the request
133 // arguments, so it's included in the response, and in the
134 // activeRequests list for linking.
135 // Returns a rejected promise if shared workers are not supported.
136 private relayRequest(req: DbStoreRequest): Promise<any> {
138 if (!this.connectToWorker()) {
139 return Promise.reject('Shared Workers not supported');
142 return new Promise((resolve, reject) => {
143 const id = req.id = this.autoId++;
144 this.activeRequests[id] = {id: id, resolve: resolve, reject: reject};
145 this.worker.port.postMessage(req);
149 // Connect to all active schemas, requesting each be created
151 private connectToSchemas(): Promise<any> {
154 this.activeSchemas.forEach(schema =>
155 promises.push(this.connectToOneSchema(schema)));
157 return Promise.all(promises).then(
159 err => this.cannotConnect = true
163 private connectToOneSchema(schema: string): Promise<any> {
165 if (this.schemasConnected[schema]) {
166 return Promise.resolve();
169 if (this.schemasInProgress[schema]) {
170 return this.schemasInProgress[schema];
173 const promise = new Promise((resolve, reject) => {
175 this.relayRequest({schema: schema, action: 'createSchema'})
178 this.relayRequest({schema: schema, action: 'connect'}))
182 this.schemasConnected[schema] = true;
183 delete this.schemasInProgress[schema];
190 return this.schemasInProgress[schema] = promise;
193 // Request may be rejected if SharedWorker's are not supported.
194 // All calls to this method should include an error handler in
195 // the .then() or a .cache() handler after the .then().
196 request(req: DbStoreRequest): Promise<any> {
197 return this.connectToSchemas().then(_ => this.relayRequest(req));