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 declare var SharedWorker: {
27 prototype: SharedWorker;
28 new (scriptUrl: any, name?: any): SharedWorker;
32 // Requests in flight to the shared worker
33 interface ActiveRequest {
35 resolve(response: any): any;
36 reject(error: any): any;
39 // Shared worker request structure. This is the request that's
40 // relayed to the shared worker.
41 // DbStoreRequest.id === ActiveRequest.id
42 interface DbStoreRequest {
52 // Expected response structure from the shared worker.
53 // Note callers only recive the 'result' content, which may
55 interface DbStoreResponse {
62 @Injectable({providedIn: 'root'})
63 export class DbStoreService {
65 autoId = 0; // each request gets a unique id.
66 cannotConnect: boolean;
68 activeRequests: {[id: number]: ActiveRequest} = {};
70 // Schemas we should connect to
71 activeSchemas: string[] = ['cache']; // add 'offline' in the offline UI
73 // Schemas we are in the process of connecting to
74 schemasInProgress: {[schema: string]: Promise<any>} = {};
76 // Schemas we have successfully connected to
77 schemasConnected: {[schema: string]: boolean} = {};
79 worker: SharedWorker = null;
83 // Returns true if connection is successful, false otherwise
84 private connectToWorker(): boolean {
85 if (this.worker) { return true; }
86 if (this.cannotConnect) { return false; }
89 this.worker = new SharedWorker(WORKER_URL);
91 console.warn('SharedWorker() not supported', E);
92 this.cannotConnect = true;
96 this.worker.onerror = err => {
97 this.cannotConnect = true;
98 console.error('Cannot connect to DB shared worker', err);
101 // List for responses and resolve the matching pending request.
102 this.worker.port.addEventListener(
103 'message', evt => this.handleMessage(evt));
105 this.worker.port.start();
109 private handleMessage(evt: MessageEvent) {
110 const response: DbStoreResponse = evt.data as DbStoreResponse;
111 const reqId = response.id;
112 const req = this.activeRequests[reqId];
115 console.error('Recieved response for unknown request', reqId);
119 // Request is no longer active.
120 delete this.activeRequests[reqId];
122 if (response.status === 'OK') {
123 req.resolve(response.result);
125 console.error('worker request failed with', response.error);
126 req.reject(response.error);
130 // Send a request to the web worker and register the request
131 // for future resolution. Store the request ID in the request
132 // arguments, so it's included in the response, and in the
133 // activeRequests list for linking.
134 // Returns a rejected promise if shared workers are not supported.
135 private relayRequest(req: DbStoreRequest): Promise<any> {
137 if (!this.connectToWorker()) {
138 return Promise.reject('Shared Workers not supported');
141 return new Promise((resolve, reject) => {
142 const id = req.id = this.autoId++;
143 this.activeRequests[id] = {id: id, resolve: resolve, reject: reject};
144 this.worker.port.postMessage(req);
148 // Connect to all active schemas, requesting each be created
150 private connectToSchemas(): Promise<any> {
153 this.activeSchemas.forEach(schema =>
154 promises.push(this.connectToOneSchema(schema)));
156 return Promise.all(promises).then(
158 err => this.cannotConnect = true
162 private connectToOneSchema(schema: string): Promise<any> {
164 if (this.schemasConnected[schema]) {
165 return Promise.resolve();
168 if (this.schemasInProgress[schema]) {
169 return this.schemasInProgress[schema];
172 const promise = new Promise((resolve, reject) => {
174 this.relayRequest({schema: schema, action: 'createSchema'})
177 this.relayRequest({schema: schema, action: 'connect'}))
181 this.schemasConnected[schema] = true;
182 delete this.schemasInProgress[schema];
189 return this.schemasInProgress[schema] = promise;
192 // Request may be rejected if SharedWorker's are not supported.
193 // All calls to this method should include an error handler in
194 // the .then() or a .cache() handler after the .then().
195 request(req: DbStoreRequest): Promise<any> {
196 return this.connectToSchemas().then(_ => this.relayRequest(req));