summaryrefslogtreecommitdiffstats
path: root/toolkit/components/normandy/lib/NormandyAddonManager.sys.mjs
blob: 514e1b51986523a08e911b881318b5efcc256588 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/* 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.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
});

export const NormandyAddonManager = {
  async downloadAndInstall({
    createError,
    extensionDetails,
    applyNormandyChanges,
    undoNormandyChanges,
    onInstallStarted,
    reportError,
  }) {
    const { extension_id, hash, hash_algorithm, version, xpi } =
      extensionDetails;

    const downloadDeferred = Promise.withResolvers();
    const installDeferred = Promise.withResolvers();

    const install = await lazy.AddonManager.getInstallForURL(xpi, {
      hash: `${hash_algorithm}:${hash}`,
      telemetryInfo: { source: "internal" },
    });

    const listener = {
      onInstallStarted(cbInstall) {
        const versionMatches = cbInstall.addon.version === version;
        const idMatches = cbInstall.addon.id === extension_id;

        if (!versionMatches || !idMatches) {
          installDeferred.reject(createError("metadata-mismatch"));
          return false; // cancel the installation, server metadata does not match downloaded add-on
        }

        if (onInstallStarted) {
          return onInstallStarted(cbInstall, installDeferred);
        }

        return true;
      },

      onDownloadFailed() {
        downloadDeferred.reject(
          createError("download-failure", {
            detail: lazy.AddonManager.errorToString(install.error),
          })
        );
      },

      onDownloadEnded() {
        downloadDeferred.resolve();
        return false; // temporarily pause installation for Normandy bookkeeping
      },

      onInstallFailed() {
        installDeferred.reject(
          createError("install-failure", {
            detail: lazy.AddonManager.errorToString(install.error),
          })
        );
      },

      onInstallEnded() {
        installDeferred.resolve();
      },
    };

    install.addListener(listener);

    // Download the add-on
    try {
      install.install();
      await downloadDeferred.promise;
    } catch (err) {
      reportError(err);
      install.removeListener(listener);
      throw err;
    }

    // Complete any book-keeping
    try {
      await applyNormandyChanges(install);
    } catch (err) {
      reportError(err);
      install.removeListener(listener);
      install.cancel();
      throw err;
    }

    // Finish paused installation
    try {
      install.install();
      await installDeferred.promise;
    } catch (err) {
      reportError(err);
      install.removeListener(listener);
      await undoNormandyChanges();
      throw err;
    }

    install.removeListener(listener);

    return [install.addon.id, install.addon.version];
  },
};