Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quellcode-Bibliothek Push.sys.mjs   Sprache: unbekannt

 
/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "Push",
  });
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "PushService",
  "@mozilla.org/push/Service;1",
  "nsIPushService"
);

/**
 * The Push component runs in the child process and exposes the Push API
 * to the web application. The PushService running in the parent process is the
 * one actually performing all operations.
 */
export class Push {
  constructor() {
    lazy.console.debug("Push()");
  }

  get contractID() {
    return "@mozilla.org/push/PushManager;1";
  }

  get classID() {
    return Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
  }

  get QueryInterface() {
    return ChromeUtils.generateQI([
      "nsIDOMGlobalPropertyInitializer",
      "nsISupportsWeakReference",
      "nsIObserver",
    ]);
  }

  init(win) {
    lazy.console.debug("init()");

    this._window = win;

    // Get the client principal from the window. This won't be null because the
    // service worker should be available when accessing the push manager.
    this._principal = win.clientPrincipal;

    if (!this._principal) {
      throw new Error(" The client principal of the window is not available");
    }

    try {
      this._topLevelPrincipal = win.top.document.nodePrincipal;
    } catch (error) {
      // Accessing the top-level document might fails if cross-origin
      this._topLevelPrincipal = undefined;
    }
  }

  __init(scope) {
    this._scope = scope;
  }

  askPermission() {
    lazy.console.debug("askPermission()");

    let hasValidTransientUserGestureActivation =
      this._window.document.hasValidTransientUserGestureActivation;

    return new this._window.Promise((resolve, reject) => {
      // Test permission before requesting to support GeckoView:
      // * GeckoViewPermissionChild wants to return early when requested without user activation
      //   before doing actual permission check:
      //   https://searchfox.org/mozilla-central/rev/0ba4632ee85679a1ccaf652df79c971fa7e9b9f7/mobile/android/actors/GeckoViewPermissionChild.sys.mjs#46-56
      //   which is partly because:
      // * GeckoView test runner has no real permission check but just returns VALUE_ALLOW.
      //   https://searchfox.org/mozilla-central/rev/6e5b9a5a1edab13a1b2e2e90944b6e06b4d8149c/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java#108-123
      if (this.#testPermission() === Ci.nsIPermissionManager.ALLOW_ACTION) {
        resolve();
        return;
      }

      let permissionDenied = () => {
        reject(
          new this._window.DOMException(
            "User denied permission to use the Push API.",
            "NotAllowedError"
          )
        );
      };

      if (
        Services.prefs.getBoolPref("dom.push.testing.ignorePermission", false)
      ) {
        resolve();
        return;
      }

      this.#requestPermission(
        hasValidTransientUserGestureActivation,
        resolve,
        permissionDenied
      );
    });
  }

  subscribe(options) {
    lazy.console.debug("subscribe()", this._scope);

    return this.askPermission().then(
      () =>
        new this._window.Promise((resolve, reject) => {
          let callback = new PushSubscriptionCallback(this, resolve, reject);

          if (!options || options.applicationServerKey === null) {
            lazy.PushService.subscribe(this._scope, this._principal, callback);
            return;
          }

          let keyView = this.#normalizeAppServerKey(
            options.applicationServerKey
          );
          if (keyView.byteLength === 0) {
            callback.rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
            return;
          }
          lazy.PushService.subscribeWithKey(
            this._scope,
            this._principal,
            keyView,
            callback
          );
        })
    );
  }

  #normalizeAppServerKey(appServerKey) {
    let key;
    if (typeof appServerKey == "string") {
      try {
        key = Cu.cloneInto(
          ChromeUtils.base64URLDecode(appServerKey, {
            padding: "reject",
          }),
          this._window
        );
      } catch (e) {
        throw new this._window.DOMException(
          "String contains an invalid character",
          "InvalidCharacterError"
        );
      }
    } else if (this._window.ArrayBuffer.isView(appServerKey)) {
      key = appServerKey.buffer;
    } else {
      // `appServerKey` is an array buffer.
      key = appServerKey;
    }
    return new this._window.Uint8Array(key);
  }

  getSubscription() {
    lazy.console.debug("getSubscription()", this._scope);

    return new this._window.Promise((resolve, reject) => {
      let callback = new PushSubscriptionCallback(this, resolve, reject);
      lazy.PushService.getSubscription(this._scope, this._principal, callback);
    });
  }

  permissionState() {
    lazy.console.debug("permissionState()", this._scope);

    return new this._window.Promise((resolve, reject) => {
      let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;

      try {
        permission = this.#testPermission();
      } catch (e) {
        reject();
        return;
      }

      let pushPermissionStatus = "prompt";
      if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
        pushPermissionStatus = "granted";
      } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
        pushPermissionStatus = "denied";
      }
      resolve(pushPermissionStatus);
    });
  }

  #testPermission() {
    let permission = Services.perms.testExactPermissionFromPrincipal(
      this._principal,
      "desktop-notification"
    );
    if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
      return permission;
    }
    try {
      if (Services.prefs.getBoolPref("dom.push.testing.ignorePermission")) {
        permission = Ci.nsIPermissionManager.ALLOW_ACTION;
      }
    } catch (e) {}
    return permission;
  }

  #requestPermission(
    hasValidTransientUserGestureActivation,
    allowCallback,
    cancelCallback
  ) {
    // Create an array with a single nsIContentPermissionType element.
    let type = {
      type: "desktop-notification",
      options: [],
      QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionType"]),
    };
    let typeArray = Cc["@mozilla.org/array;1"].createInstance(
      Ci.nsIMutableArray
    );
    typeArray.appendElement(type);

    // create a nsIContentPermissionRequest
    let request = {
      QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionRequest"]),
      types: typeArray,
      principal: this._principal,
      hasValidTransientUserGestureActivation,
      topLevelPrincipal: this._topLevelPrincipal,
      allow: allowCallback,
      cancel: cancelCallback,
      window: this._window,
    };

    // Using askPermission from nsIDOMWindowUtils that takes care of the
    // remoting if needed.
    let windowUtils = this._window.windowUtils;
    windowUtils.askPermission(request);
  }
}

class PushSubscriptionCallback {
  constructor(pushManager, resolve, reject) {
    this.pushManager = pushManager;
    this.resolve = resolve;
    this.reject = reject;
  }

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIPushSubscriptionCallback"]);
  }

  onPushSubscription(ok, subscription) {
    let { pushManager } = this;
    if (!Components.isSuccessCode(ok)) {
      this.rejectWithError(ok);
      return;
    }

    if (!subscription) {
      this.resolve(null);
      return;
    }

    let p256dhKey = this.#getKey(subscription, "p256dh");
    let authSecret = this.#getKey(subscription, "auth");
    let options = {
      endpoint: subscription.endpoint,
      scope: pushManager._scope,
      p256dhKey,
      authSecret,
    };
    let appServerKey = this.#getKey(subscription, "appServer");
    if (appServerKey) {
      // Avoid passing null keys to work around bug 1256449.
      options.appServerKey = appServerKey;
    }
    let sub = new pushManager._window.PushSubscription(options);
    this.resolve(sub);
  }

  #getKey(subscription, name) {
    let rawKey = Cu.cloneInto(
      subscription.getKey(name),
      this.pushManager._window
    );
    if (!rawKey.length) {
      return null;
    }

    let key = new this.pushManager._window.ArrayBuffer(rawKey.length);
    let keyView = new this.pushManager._window.Uint8Array(key);
    keyView.set(rawKey);
    return key;
  }

  rejectWithError(result) {
    let error;
    switch (result) {
      case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR:
        error = new this.pushManager._window.DOMException(
          "Invalid raw ECDSA P-256 public key.",
          "InvalidAccessError"
        );
        break;

      case Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR:
        error = new this.pushManager._window.DOMException(
          "A subscription with a different application server key already exists.",
          "InvalidStateError"
        );
        break;

      default:
        error = new this.pushManager._window.DOMException(
          "Error retrieving push subscription.",
          "AbortError"
        );
    }
    this.reject(error);
  }
}

[ 0.51Quellennavigators  Projekt   ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge