summaryrefslogtreecommitdiffstats
path: root/mobile/android/components/geckoview/GeckoViewPush.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/components/geckoview/GeckoViewPush.sys.mjs')
-rw-r--r--mobile/android/components/geckoview/GeckoViewPush.sys.mjs264
1 files changed, 264 insertions, 0 deletions
diff --git a/mobile/android/components/geckoview/GeckoViewPush.sys.mjs b/mobile/android/components/geckoview/GeckoViewPush.sys.mjs
new file mode 100644
index 0000000000..d6625942e8
--- /dev/null
+++ b/mobile/android/components/geckoview/GeckoViewPush.sys.mjs
@@ -0,0 +1,264 @@
+/* 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/. */
+
+import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
+
+const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPush");
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
+ PushCrypto: "resource://gre/modules/PushCrypto.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);
+}
+
+export 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) {
+ const keyView = new Uint8Array(appServerKey);
+
+ if (appServerKey != null) {
+ try {
+ await lazy.PushCrypto.validateAppServerKey(keyView);
+ } catch (error) {
+ callback.onPushSubscription(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR, null);
+ return;
+ }
+ }
+
+ try {
+ const response = await lazy.EventDispatcher.instance.sendRequestForResult(
+ {
+ type: "GeckoView:PushSubscribe",
+ scope: scopeWithAttrs(scope, principal.originAttributes),
+ appServerKey: appServerKey
+ ? ChromeUtils.base64URLEncode(keyView, {
+ 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",
+]);