diff options
Diffstat (limited to '')
-rw-r--r-- | mobile/android/components/geckoview/GeckoViewPush.jsm | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/mobile/android/components/geckoview/GeckoViewPush.jsm b/mobile/android/components/geckoview/GeckoViewPush.jsm new file mode 100644 index 0000000000..5899bfd3d8 --- /dev/null +++ b/mobile/android/components/geckoview/GeckoViewPush.jsm @@ -0,0 +1,257 @@ +/* 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/. */ +"use strict"; + +var EXPORTED_SYMBOLS = ["PushService"]; + +const { GeckoViewUtils } = ChromeUtils.importESModule( + "resource://gre/modules/GeckoViewUtils.sys.mjs" +); + +const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPush"); + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + EventDispatcher: "resource://gre/modules/Messaging.sys.mjs", +}); + +// Observer notification topics for push messages and subscription status +// changes. These are duplicated and used in `nsIPushNotifier`. They're exposed +// on `nsIPushService` so that JS callers only need to import this service. +const OBSERVER_TOPIC_PUSH = "push-message"; +const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change"; +const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified"; + +function createSubscription({ + scope, + principal, + browserPublicKey, + authSecret, + endpoint, + appServerKey, +}) { + const decodedBrowserKey = ChromeUtils.base64URLDecode(browserPublicKey, { + padding: "ignore", + }); + const decodedAuthSecret = ChromeUtils.base64URLDecode(authSecret, { + padding: "ignore", + }); + + return new PushSubscription({ + endpoint, + scope, + p256dhKey: decodedBrowserKey, + authenticationSecret: decodedAuthSecret, + appServerKey, + }); +} + +function scopeWithAttrs(scope, attrs) { + return scope + ChromeUtils.originAttributesToSuffix(attrs); +} + +class PushService { + constructor() { + this.wrappedJSObject = this; + } + + pushTopic = OBSERVER_TOPIC_PUSH; + subscriptionChangeTopic = OBSERVER_TOPIC_SUBSCRIPTION_CHANGE; + subscriptionModifiedTopic = OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED; + + // nsIObserver methods + + observe(subject, topic, data) {} + + // nsIPushService methods + + subscribe(scope, principal, callback) { + this.subscribeWithKey(scope, principal, null, callback); + } + + async subscribeWithKey(scope, principal, appServerKey, callback) { + try { + const response = await lazy.EventDispatcher.instance.sendRequestForResult( + { + type: "GeckoView:PushSubscribe", + scope: scopeWithAttrs(scope, principal.originAttributes), + appServerKey: appServerKey + ? ChromeUtils.base64URLEncode(new Uint8Array(appServerKey), { + pad: true, + }) + : null, + } + ); + + let subscription = null; + if (response) { + subscription = createSubscription({ + ...response, + scope, + principal, + appServerKey, + }); + } + + callback.onPushSubscription(Cr.NS_OK, subscription); + } catch (e) { + callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null); + } + } + + async unsubscribe(scope, principal, callback) { + try { + await lazy.EventDispatcher.instance.sendRequestForResult({ + type: "GeckoView:PushUnsubscribe", + scope: scopeWithAttrs(scope, principal.originAttributes), + }); + + callback.onUnsubscribe(Cr.NS_OK, true); + } catch (e) { + callback.onUnsubscribe(Cr.NS_ERROR_FAILURE, false); + } + } + + async getSubscription(scope, principal, callback) { + try { + const response = await lazy.EventDispatcher.instance.sendRequestForResult( + { + type: "GeckoView:PushGetSubscription", + scope: scopeWithAttrs(scope, principal.originAttributes), + } + ); + + let subscription = null; + if (response) { + subscription = createSubscription({ + ...response, + scope, + principal, + }); + } + + callback.onPushSubscription(Cr.NS_OK, subscription); + } catch (e) { + callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null); + } + } + + clearForDomain(domain, callback) { + callback.onClear(Cr.NS_OK); + } + + // nsIPushQuotaManager methods + + notificationForOriginShown(origin) {} + + notificationForOriginClosed(origin) {} + + // nsIPushErrorReporter methods + + reportDeliveryError(messageId, reason) {} +} + +PushService.prototype.classID = Components.ID( + "{a54d84d7-98a4-4fec-b664-e42e512ae9cc}" +); +PushService.prototype.contractID = "@mozilla.org/push/Service;1"; +PushService.prototype.QueryInterface = ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + "nsIPushService", + "nsIPushQuotaManager", + "nsIPushErrorReporter", +]); + +/** `PushSubscription` instances are passed to all subscription callbacks. */ +class PushSubscription { + constructor(props) { + this._props = props; + } + + /** The URL for sending messages to this subscription. */ + get endpoint() { + return this._props.endpoint; + } + + /** The last time a message was sent to this subscription. */ + get lastPush() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + /** The total number of messages sent to this subscription. */ + get pushCount() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + /** + * The app will take care of throttling, so we don't + * care about the quota stuff here. + */ + get quota() { + return -1; + } + + /** + * Indicates whether this subscription was created with the system principal. + * System subscriptions are exempt from the background message quota and + * permission checks. + */ + get isSystemSubscription() { + return false; + } + + /** The private key used to decrypt incoming push messages, in JWK format */ + get p256dhPrivateKey() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + /** + * Indicates whether this subscription is subject to the background message + * quota. + */ + quotaApplies() { + return false; + } + + /** + * Indicates whether this subscription exceeded the background message quota, + * or the user revoked the notification permission. The caller must request a + * new subscription to continue receiving push messages. + */ + isExpired() { + return false; + } + + /** + * Returns a key for encrypting messages sent to this subscription. JS + * callers receive the key buffer as a return value, while C++ callers + * receive the key size and buffer as out parameters. + */ + getKey(name) { + switch (name) { + case "p256dh": + return this._getRawKey(this._props.p256dhKey); + + case "auth": + return this._getRawKey(this._props.authenticationSecret); + + case "appServer": + return this._getRawKey(this._props.appServerKey); + } + return []; + } + + _getRawKey(key) { + if (!key) { + return []; + } + return new Uint8Array(key); + } +} + +PushSubscription.prototype.QueryInterface = ChromeUtils.generateQI([ + "nsIPushSubscription", +]); |