/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ const lazy = {}; ChromeUtils.defineLazyGetter(lazy, "console", () => { return console.createInstance({ prefix: "NotificationStorage", maxLogLevelPref: "dom.webnotifications.loglevel", }); }); const kMessageGetAllOk = "GetAll:Return:OK"; const kMessageGetAllKo = "GetAll:Return:KO"; const kMessageSaveKo = "Save:Return:KO"; const kMessageDeleteKo = "Delete:Return:KO"; export class NotificationStorage { #requests = {}; #requestCount = 0; constructor() { Services.obs.addObserver(this, "xpcom-shutdown"); // Register for message listeners. this.registerListeners(); } storageQualifier() { return "Notification"; } prefixStorageQualifier(message) { return `${this.storageQualifier()}:${message}`; } formatMessageType(message) { return this.prefixStorageQualifier(message); } supportedMessages() { return [ this.formatMessageType(kMessageGetAllOk), this.formatMessageType(kMessageGetAllKo), this.formatMessageType(kMessageSaveKo), this.formatMessageType(kMessageDeleteKo), ]; } registerListeners() { for (let message of this.supportedMessages()) { Services.cpmm.addMessageListener(message, this); } } unregisterListeners() { for (let message of this.supportedMessages()) { Services.cpmm.removeMessageListener(message, this); } } observe(aSubject, aTopic) { lazy.console.debug(`Topic: ${aTopic}`); if (aTopic === "xpcom-shutdown") { Services.obs.removeObserver(this, "xpcom-shutdown"); this.unregisterListeners(); } } put(aOrigin, aEntry, aScope) { lazy.console.debug(`PUT: ${aOrigin} ${aEntry.id}: ${aEntry.title}`); let notification = { ...aEntry, // XPCOM objects cannot be sent as-is. See also bug 1937194 to skip this step actions: aEntry.actions.map(rawAction => { let actionEntry = { ...rawAction }; delete actionEntry.QueryInterface; return actionEntry; }), timestamp: new Date().getTime(), serviceWorkerRegistrationScope: aScope, }; delete notification.QueryInterface; Services.cpmm.sendAsyncMessage(this.formatMessageType("Save"), { origin: aOrigin, notification, }); } get(origin, scope, tag, callback) { lazy.console.debug(`GET: ${origin} ${tag}`); this.#fetchFromDB(origin, scope, tag, callback); } delete(origin, id) { lazy.console.debug(`DELETE: ${id}`); Services.cpmm.sendAsyncMessage(this.formatMessageType("Delete"), { origin, id, }); } receiveMessage(message) { var request = this.#requests[message.data.requestID]; switch (message.name) { case this.formatMessageType(kMessageGetAllOk): delete this.#requests[message.data.requestID]; this.#returnNotifications(message.data.notifications, request.callback); break; case this.formatMessageType(kMessageGetAllKo): delete this.#requests[message.data.requestID]; try { request.callback.done(); } catch (e) { lazy.console.debug(`Error calling callback done: ${e}`); } break; case this.formatMessageType(kMessageSaveKo): case this.formatMessageType(kMessageDeleteKo): lazy.console.debug( `Error received when treating: '${message.name}': ${message.data.errorMsg}` ); break; default: lazy.console.debug(`Unrecognized message: ${message.name}`); break; } } #getUniqueRequestID() { // This assumes the count will never go above MAX_SAFE_INTEGER, as // notifications are not supposed to happen that frequently. this.#requestCount += 1; return this.#requestCount; } #fetchFromDB(origin, scope, tag, callback) { var request = { origin, scope, tag, callback, }; var requestID = this.#getUniqueRequestID(); this.#requests[requestID] = request; Services.cpmm.sendAsyncMessage(this.formatMessageType("GetAll"), { origin, scope, tag, requestID, }); } #returnNotifications(notifications, callback) { // Pass each notification back separately. // The callback is called asynchronously to match the behaviour when // fetching from the database. try { Services.tm.dispatchToMainThread(() => callback.done(notifications)); } catch (e) { lazy.console.debug(`Error calling callback handle: ${e}`); } } QueryInterface = ChromeUtils.generateQI(["nsINotificationStorage"]); } export class MemoryNotificationStorage extends NotificationStorage { storageQualifier() { return "MemoryNotification"; } }