/**
 * 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/.
 */

/**
 * Class implementing the nsICredentialChooserService.
 *
 * This class shows UI to the user for the Credential Chooser for the
 * Credential Management API.
 *
 * @class CredentialChooserService
 */
export class CredentialChooserService {
  classID = Components.ID("{673ddc19-03e2-4b30-a868-06297e8fed89}");
  QueryInterface = ChromeUtils.generateQI(["nsICredentialChooserService"]);

  /**
   * A service function to help any UI. Fetches and serializes images to
   * data urls, which can be used in chrome UI.
   *
   * @param {Window} window - Window which should perform the fetch
   * @param {nsIURI} uri - Icon location to be fetched from
   * @returns {Promise<string, Error>} The data URI encoded as a string representing the icon that was loaded
   */
  async fetchImageToDataURI(window, uri) {
    if (uri.protocol === "data:") {
      return uri.href;
    }
    let request = new window.Request(uri.spec, { mode: "cors" });
    request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_IMAGE);
    let blob;
    let response = await window.fetch(request);
    if (!response.ok) {
      return Promise.reject(new Error("HTTP failure on Fetch"));
    }
    blob = await response.blob();
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.onloadend = () => resolve(reader.result);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
  }

  /**
   * A helper function that performs precisely the right Fetch for the well-known resource for FedCM.
   *
   * @param {nsIURI} uri - Well known resource location
   * @param {nsIPrincipal} triggeringPrincipal - Principal of the IDP triggering this request
   * @returns {Promise} A promise that will be the result of fetching the resource and parsing the body as JSON,
   *          or reject along the way.
   */
  async fetchWellKnown(uri, triggeringPrincipal) {
    let request = new Request(uri.spec, {
      mode: "no-cors",
      referrerPolicy: "no-referrer",
      // We use a Null Principal here because we don't want to send any
      // cookies or an Origin header here before we get user permission.
      triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
        triggeringPrincipal.originAttributes
      ),
      // and we want to be able to read the response, so we don't let CORS hide it
      // because we are no-cors
      neverTaint: true,
      credentials: "omit",
      headers: [["Accept", "application/json"]],
    });
    // This overrides the Sec-Fetch-Dest to `webidentity` rather than `empty`
    request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_IDENTITY);
    try {
      let response = await fetch(request);
      return response.json();
    } catch (e) {
      return Promise.reject(e);
    }
  }

  /**
   * A helper function that performs precisely the right Fetch for the IDP configuration resource for FedCM.
   *
   * @param {nsIURI} uri - Well known resource location
   * @param {nsIPrincipal} triggeringPrincipal - Principal of the IDP triggering this request
   * @returns {Promise} A promise that will be the result of fetching the resource and parsing the body as JSON,
   *          or reject along the way.
   */
  async fetchConfig(uri, triggeringPrincipal) {
    let request = new Request(uri.spec, {
      mode: "no-cors",
      referrerPolicy: "no-referrer",
      redirect: "error",
      // We use a Null Principal here because we don't want to send any
      // cookies or an Origin header here before we get user permission.
      triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
        triggeringPrincipal.originAttributes
      ),
      // and we want to be able to read the response, so we don't let CORS hide it
      // because we are no-cors
      neverTaint: true,
      credentials: "omit",
      headers: [["Accept", "application/json"]],
    });
    // This overrides the Sec-Fetch-Dest to `webidentity` rather than `empty`
    request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_IDENTITY);
    try {
      let response = await fetch(request);
      return response.json();
    } catch (e) {
      return Promise.reject(e);
    }
  }

  /**
   * A helper function that performs precisely the right Fetch for the account list for FedCM.
   *
   * @param {nsIURI} uri - Well known resource location
   * @param {nsIPrincipal} triggeringPrincipal - Principal of the IDP triggering this request
   * @returns {Promise} A promise that will be the result of fetching the resource and parsing the body as JSON,
   *          or reject along the way.
   */
  async fetchAccounts(uri, triggeringPrincipal) {
    let request = new Request(uri.spec, {
      mode: "no-cors",
      redirect: "error",
      referrerPolicy: "no-referrer",
      triggeringPrincipal,
      // and we want to be able to read the response, so we don't let CORS hide it
      // because we are no-cors
      neverTaint: true,
      credentials: "include",
      headers: [["Accept", "application/json"]],
    });
    // This overrides the Sec-Fetch-Dest to `webidentity` rather than `empty`
    request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_IDENTITY);
    try {
      let response = await fetch(request);
      return response.json();
    } catch (e) {
      return Promise.reject(e);
    }
  }

  /**
   * A helper function that performs precisely the right Fetch for the token request for FedCM.
   *
   * @param {nsIURI} uri - Well known resource location
   * @param {string} body - Body to be sent with the fetch, pre-serialized.
   * @param {nsIPrincipal} triggeringPrincipal - Principal of the IDP triggering this request
   * @returns {Promise} A promise that will be the result of fetching the resource and parsing the body as JSON,
   *          or reject along the way.
   */
  async fetchToken(uri, body, triggeringPrincipal) {
    let request = new Request(uri.spec, {
      mode: "cors",
      method: "POST",
      redirect: "error",
      triggeringPrincipal,
      body,
      credentials: "include",
      headers: [["Content-type", "application/x-www-form-urlencoded"]],
    });
    // This overrides the Sec-Fetch-Dest to `webidentity` rather than `empty`
    request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_IDENTITY);
    try {
      let response = await fetch(request);
      return response.json();
    } catch (e) {
      return Promise.reject(e);
    }
  }

  /**
   * A helper function that performs precisely the right Fetch for the disconnect request for FedCM.
   *
   * @param {nsIURI} uri - Well known resource location
   * @param {string} body - Body to be sent with the fetch, pre-serialized.
   * @param {nsIPrincipal} triggeringPrincipal - Principal of the IDP triggering this request
   * @returns {Promise} A promise that will be the result of fetching the resource and parsing the body as JSON,
   *          or reject along the way.
   */
  async fetchDisconnect(uri, body, triggeringPrincipal) {
    let request = new Request(uri.spec, {
      mode: "cors",
      method: "POST",
      redirect: "error",
      triggeringPrincipal,
      body,
      credentials: "include",
      headers: [["Content-type", "application/x-www-form-urlencoded"]],
    });
    // This overrides the Sec-Fetch-Dest to `webidentity` rather than `empty`
    request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_IDENTITY);
    try {
      let response = await fetch(request);
      return response.json();
    } catch (e) {
      return Promise.reject(e);
    }
  }
}
