electron-chrome-web-store
Version:
Install and update Chrome extensions from the Chrome Web Store for Electron
1,557 lines (1,544 loc) • 54.1 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/browser/index.ts
var browser_exports = {};
__export(browser_exports, {
downloadExtension: () => downloadExtension,
installChromeWebStore: () => installChromeWebStore,
installExtension: () => installExtension,
loadAllExtensions: () => loadAllExtensions,
uninstallExtension: () => uninstallExtension,
updateExtensions: () => updateExtensions
});
module.exports = __toCommonJS(browser_exports);
var import_electron5 = require("electron");
var path5 = __toESM(require("node:path"));
var import_node_fs = require("node:fs");
var import_node_module = require("node:module");
// src/browser/api.ts
var import_debug3 = __toESM(require("debug"));
var import_electron3 = require("electron");
// src/browser/utils.ts
var path = __toESM(require("node:path"));
var import_electron = require("electron");
var fetch = (
// Prefer Node's fetch until net.fetch crash is fixed
// https://github.com/electron/electron/pull/45050
globalThis.fetch || import_electron.net?.fetch || (() => {
throw new Error(
"electron-chrome-web-store: Missing fetch API. Please upgrade Electron or Node."
);
})
);
var getChromeVersion = () => process.versions.chrome || "131.0.6778.109";
function compareVersions(version1, version2) {
const v1 = version1.split(".").map(Number);
const v2 = version2.split(".").map(Number);
for (let i = 0; i < 3; i++) {
if (v1[i] > v2[i]) return 1;
if (v1[i] < v2[i]) return -1;
}
return 0;
}
var getDefaultExtensionsPath = () => path.join(import_electron.app.getPath("userData"), "Extensions");
// src/common/constants.ts
var ExtensionInstallStatus = {
BLACKLISTED: "blacklisted",
BLOCKED_BY_POLICY: "blocked_by_policy",
CAN_REQUEST: "can_request",
CORRUPTED: "corrupted",
CUSTODIAN_APPROVAL_REQUIRED: "custodian_approval_required",
CUSTODIAN_APPROVAL_REQUIRED_FOR_INSTALLATION: "custodian_approval_required_for_installation",
DEPRECATED_MANIFEST_VERSION: "deprecated_manifest_version",
DISABLED: "disabled",
ENABLED: "enabled",
FORCE_INSTALLED: "force_installed",
INSTALLABLE: "installable",
REQUEST_PENDING: "request_pending",
TERMINATED: "terminated"
};
var MV2DeprecationStatus = {
INACTIVE: "inactive",
SOFT_DISABLE: "soft_disable",
WARNING: "warning"
};
var Result = {
ALREADY_INSTALLED: "already_installed",
BLACKLISTED: "blacklisted",
BLOCKED_BY_POLICY: "blocked_by_policy",
BLOCKED_FOR_CHILD_ACCOUNT: "blocked_for_child_account",
FEATURE_DISABLED: "feature_disabled",
ICON_ERROR: "icon_error",
INSTALL_ERROR: "install_error",
INSTALL_IN_PROGRESS: "install_in_progress",
INVALID_ICON_URL: "invalid_icon_url",
INVALID_ID: "invalid_id",
LAUNCH_IN_PROGRESS: "launch_in_progress",
MANIFEST_ERROR: "manifest_error",
MISSING_DEPENDENCIES: "missing_dependencies",
SUCCESS: "success",
UNKNOWN_ERROR: "unknown_error",
UNSUPPORTED_EXTENSION_TYPE: "unsupported_extension_type",
USER_CANCELLED: "user_cancelled",
USER_GESTURE_REQUIRED: "user_gesture_required"
};
var WebGlStatus = {
WEBGL_ALLOWED: "webgl_allowed",
WEBGL_BLOCKED: "webgl_blocked"
};
// src/browser/installer.ts
var fs2 = __toESM(require("node:fs"));
var os = __toESM(require("node:os"));
var path3 = __toESM(require("node:path"));
var import_node_stream = require("node:stream");
var import_promises = require("node:stream/promises");
var import_electron2 = require("electron");
var import_adm_zip = __toESM(require("adm-zip"));
var import_debug2 = __toESM(require("debug"));
// ../../node_modules/pbf/index.js
var SHIFT_LEFT_32 = (1 << 16) * (1 << 16);
var SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
var TEXT_DECODER_MIN_LENGTH = 12;
var utf8TextDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder("utf-8");
var PBF_VARINT = 0;
var PBF_FIXED64 = 1;
var PBF_BYTES = 2;
var PBF_FIXED32 = 5;
var Pbf = class {
/**
* @param {Uint8Array | ArrayBuffer} [buf]
*/
constructor(buf = new Uint8Array(16)) {
this.buf = ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf);
this.dataView = new DataView(this.buf.buffer);
this.pos = 0;
this.type = 0;
this.length = this.buf.length;
}
// === READING =================================================================
/**
* @template T
* @param {(tag: number, result: T, pbf: Pbf) => void} readField
* @param {T} result
* @param {number} [end]
*/
readFields(readField, result, end = this.length) {
while (this.pos < end) {
const val = this.readVarint(), tag = val >> 3, startPos = this.pos;
this.type = val & 7;
readField(tag, result, this);
if (this.pos === startPos) this.skip(val);
}
return result;
}
/**
* @template T
* @param {(tag: number, result: T, pbf: Pbf) => void} readField
* @param {T} result
*/
readMessage(readField, result) {
return this.readFields(readField, result, this.readVarint() + this.pos);
}
readFixed32() {
const val = this.dataView.getUint32(this.pos, true);
this.pos += 4;
return val;
}
readSFixed32() {
const val = this.dataView.getInt32(this.pos, true);
this.pos += 4;
return val;
}
// 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
readFixed64() {
const val = this.dataView.getUint32(this.pos, true) + this.dataView.getUint32(this.pos + 4, true) * SHIFT_LEFT_32;
this.pos += 8;
return val;
}
readSFixed64() {
const val = this.dataView.getUint32(this.pos, true) + this.dataView.getInt32(this.pos + 4, true) * SHIFT_LEFT_32;
this.pos += 8;
return val;
}
readFloat() {
const val = this.dataView.getFloat32(this.pos, true);
this.pos += 4;
return val;
}
readDouble() {
const val = this.dataView.getFloat64(this.pos, true);
this.pos += 8;
return val;
}
/**
* @param {boolean} [isSigned]
*/
readVarint(isSigned) {
const buf = this.buf;
let val, b;
b = buf[this.pos++];
val = b & 127;
if (b < 128) return val;
b = buf[this.pos++];
val |= (b & 127) << 7;
if (b < 128) return val;
b = buf[this.pos++];
val |= (b & 127) << 14;
if (b < 128) return val;
b = buf[this.pos++];
val |= (b & 127) << 21;
if (b < 128) return val;
b = buf[this.pos];
val |= (b & 15) << 28;
return readVarintRemainder(val, isSigned, this);
}
readVarint64() {
return this.readVarint(true);
}
readSVarint() {
const num = this.readVarint();
return num % 2 === 1 ? (num + 1) / -2 : num / 2;
}
readBoolean() {
return Boolean(this.readVarint());
}
readString() {
const end = this.readVarint() + this.pos;
const pos = this.pos;
this.pos = end;
if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) {
return utf8TextDecoder.decode(this.buf.subarray(pos, end));
}
return readUtf8(this.buf, pos, end);
}
readBytes() {
const end = this.readVarint() + this.pos, buffer = this.buf.subarray(this.pos, end);
this.pos = end;
return buffer;
}
// verbose for performance reasons; doesn't affect gzipped size
/**
* @param {number[]} [arr]
* @param {boolean} [isSigned]
*/
readPackedVarint(arr = [], isSigned) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readVarint(isSigned));
return arr;
}
/** @param {number[]} [arr] */
readPackedSVarint(arr = []) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readSVarint());
return arr;
}
/** @param {boolean[]} [arr] */
readPackedBoolean(arr = []) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readBoolean());
return arr;
}
/** @param {number[]} [arr] */
readPackedFloat(arr = []) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readFloat());
return arr;
}
/** @param {number[]} [arr] */
readPackedDouble(arr = []) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readDouble());
return arr;
}
/** @param {number[]} [arr] */
readPackedFixed32(arr = []) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readFixed32());
return arr;
}
/** @param {number[]} [arr] */
readPackedSFixed32(arr = []) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readSFixed32());
return arr;
}
/** @param {number[]} [arr] */
readPackedFixed64(arr = []) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readFixed64());
return arr;
}
/** @param {number[]} [arr] */
readPackedSFixed64(arr = []) {
const end = this.readPackedEnd();
while (this.pos < end) arr.push(this.readSFixed64());
return arr;
}
readPackedEnd() {
return this.type === PBF_BYTES ? this.readVarint() + this.pos : this.pos + 1;
}
/** @param {number} val */
skip(val) {
const type = val & 7;
if (type === PBF_VARINT) while (this.buf[this.pos++] > 127) {
}
else if (type === PBF_BYTES) this.pos = this.readVarint() + this.pos;
else if (type === PBF_FIXED32) this.pos += 4;
else if (type === PBF_FIXED64) this.pos += 8;
else throw new Error(`Unimplemented type: ${type}`);
}
// === WRITING =================================================================
/**
* @param {number} tag
* @param {number} type
*/
writeTag(tag, type) {
this.writeVarint(tag << 3 | type);
}
/** @param {number} min */
realloc(min) {
let length = this.length || 16;
while (length < this.pos + min) length *= 2;
if (length !== this.length) {
const buf = new Uint8Array(length);
buf.set(this.buf);
this.buf = buf;
this.dataView = new DataView(buf.buffer);
this.length = length;
}
}
finish() {
this.length = this.pos;
this.pos = 0;
return this.buf.subarray(0, this.length);
}
/** @param {number} val */
writeFixed32(val) {
this.realloc(4);
this.dataView.setInt32(this.pos, val, true);
this.pos += 4;
}
/** @param {number} val */
writeSFixed32(val) {
this.realloc(4);
this.dataView.setInt32(this.pos, val, true);
this.pos += 4;
}
/** @param {number} val */
writeFixed64(val) {
this.realloc(8);
this.dataView.setInt32(this.pos, val & -1, true);
this.dataView.setInt32(this.pos + 4, Math.floor(val * SHIFT_RIGHT_32), true);
this.pos += 8;
}
/** @param {number} val */
writeSFixed64(val) {
this.realloc(8);
this.dataView.setInt32(this.pos, val & -1, true);
this.dataView.setInt32(this.pos + 4, Math.floor(val * SHIFT_RIGHT_32), true);
this.pos += 8;
}
/** @param {number} val */
writeVarint(val) {
val = +val || 0;
if (val > 268435455 || val < 0) {
writeBigVarint(val, this);
return;
}
this.realloc(4);
this.buf[this.pos++] = val & 127 | (val > 127 ? 128 : 0);
if (val <= 127) return;
this.buf[this.pos++] = (val >>>= 7) & 127 | (val > 127 ? 128 : 0);
if (val <= 127) return;
this.buf[this.pos++] = (val >>>= 7) & 127 | (val > 127 ? 128 : 0);
if (val <= 127) return;
this.buf[this.pos++] = val >>> 7 & 127;
}
/** @param {number} val */
writeSVarint(val) {
this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
}
/** @param {boolean} val */
writeBoolean(val) {
this.writeVarint(+val);
}
/** @param {string} str */
writeString(str) {
str = String(str);
this.realloc(str.length * 4);
this.pos++;
const startPos = this.pos;
this.pos = writeUtf8(this.buf, str, this.pos);
const len = this.pos - startPos;
if (len >= 128) makeRoomForExtraLength(startPos, len, this);
this.pos = startPos - 1;
this.writeVarint(len);
this.pos += len;
}
/** @param {number} val */
writeFloat(val) {
this.realloc(4);
this.dataView.setFloat32(this.pos, val, true);
this.pos += 4;
}
/** @param {number} val */
writeDouble(val) {
this.realloc(8);
this.dataView.setFloat64(this.pos, val, true);
this.pos += 8;
}
/** @param {Uint8Array} buffer */
writeBytes(buffer) {
const len = buffer.length;
this.writeVarint(len);
this.realloc(len);
for (let i = 0; i < len; i++) this.buf[this.pos++] = buffer[i];
}
/**
* @template T
* @param {(obj: T, pbf: Pbf) => void} fn
* @param {T} obj
*/
writeRawMessage(fn, obj) {
this.pos++;
const startPos = this.pos;
fn(obj, this);
const len = this.pos - startPos;
if (len >= 128) makeRoomForExtraLength(startPos, len, this);
this.pos = startPos - 1;
this.writeVarint(len);
this.pos += len;
}
/**
* @template T
* @param {number} tag
* @param {(obj: T, pbf: Pbf) => void} fn
* @param {T} obj
*/
writeMessage(tag, fn, obj) {
this.writeTag(tag, PBF_BYTES);
this.writeRawMessage(fn, obj);
}
/**
* @param {number} tag
* @param {number[]} arr
*/
writePackedVarint(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedVarint, arr);
}
/**
* @param {number} tag
* @param {number[]} arr
*/
writePackedSVarint(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedSVarint, arr);
}
/**
* @param {number} tag
* @param {boolean[]} arr
*/
writePackedBoolean(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedBoolean, arr);
}
/**
* @param {number} tag
* @param {number[]} arr
*/
writePackedFloat(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedFloat, arr);
}
/**
* @param {number} tag
* @param {number[]} arr
*/
writePackedDouble(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedDouble, arr);
}
/**
* @param {number} tag
* @param {number[]} arr
*/
writePackedFixed32(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedFixed32, arr);
}
/**
* @param {number} tag
* @param {number[]} arr
*/
writePackedSFixed32(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedSFixed32, arr);
}
/**
* @param {number} tag
* @param {number[]} arr
*/
writePackedFixed64(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedFixed64, arr);
}
/**
* @param {number} tag
* @param {number[]} arr
*/
writePackedSFixed64(tag, arr) {
if (arr.length) this.writeMessage(tag, writePackedSFixed64, arr);
}
/**
* @param {number} tag
* @param {Uint8Array} buffer
*/
writeBytesField(tag, buffer) {
this.writeTag(tag, PBF_BYTES);
this.writeBytes(buffer);
}
/**
* @param {number} tag
* @param {number} val
*/
writeFixed32Field(tag, val) {
this.writeTag(tag, PBF_FIXED32);
this.writeFixed32(val);
}
/**
* @param {number} tag
* @param {number} val
*/
writeSFixed32Field(tag, val) {
this.writeTag(tag, PBF_FIXED32);
this.writeSFixed32(val);
}
/**
* @param {number} tag
* @param {number} val
*/
writeFixed64Field(tag, val) {
this.writeTag(tag, PBF_FIXED64);
this.writeFixed64(val);
}
/**
* @param {number} tag
* @param {number} val
*/
writeSFixed64Field(tag, val) {
this.writeTag(tag, PBF_FIXED64);
this.writeSFixed64(val);
}
/**
* @param {number} tag
* @param {number} val
*/
writeVarintField(tag, val) {
this.writeTag(tag, PBF_VARINT);
this.writeVarint(val);
}
/**
* @param {number} tag
* @param {number} val
*/
writeSVarintField(tag, val) {
this.writeTag(tag, PBF_VARINT);
this.writeSVarint(val);
}
/**
* @param {number} tag
* @param {string} str
*/
writeStringField(tag, str) {
this.writeTag(tag, PBF_BYTES);
this.writeString(str);
}
/**
* @param {number} tag
* @param {number} val
*/
writeFloatField(tag, val) {
this.writeTag(tag, PBF_FIXED32);
this.writeFloat(val);
}
/**
* @param {number} tag
* @param {number} val
*/
writeDoubleField(tag, val) {
this.writeTag(tag, PBF_FIXED64);
this.writeDouble(val);
}
/**
* @param {number} tag
* @param {boolean} val
*/
writeBooleanField(tag, val) {
this.writeVarintField(tag, +val);
}
};
function readVarintRemainder(l, s, p) {
const buf = p.buf;
let h, b;
b = buf[p.pos++];
h = (b & 112) >> 4;
if (b < 128) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 127) << 3;
if (b < 128) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 127) << 10;
if (b < 128) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 127) << 17;
if (b < 128) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 127) << 24;
if (b < 128) return toNum(l, h, s);
b = buf[p.pos++];
h |= (b & 1) << 31;
if (b < 128) return toNum(l, h, s);
throw new Error("Expected varint not more than 10 bytes");
}
function toNum(low, high, isSigned) {
return isSigned ? high * 4294967296 + (low >>> 0) : (high >>> 0) * 4294967296 + (low >>> 0);
}
function writeBigVarint(val, pbf) {
let low, high;
if (val >= 0) {
low = val % 4294967296 | 0;
high = val / 4294967296 | 0;
} else {
low = ~(-val % 4294967296);
high = ~(-val / 4294967296);
if (low ^ 4294967295) {
low = low + 1 | 0;
} else {
low = 0;
high = high + 1 | 0;
}
}
if (val >= 18446744073709552e3 || val < -18446744073709552e3) {
throw new Error("Given varint doesn't fit into 10 bytes");
}
pbf.realloc(10);
writeBigVarintLow(low, high, pbf);
writeBigVarintHigh(high, pbf);
}
function writeBigVarintLow(low, high, pbf) {
pbf.buf[pbf.pos++] = low & 127 | 128;
low >>>= 7;
pbf.buf[pbf.pos++] = low & 127 | 128;
low >>>= 7;
pbf.buf[pbf.pos++] = low & 127 | 128;
low >>>= 7;
pbf.buf[pbf.pos++] = low & 127 | 128;
low >>>= 7;
pbf.buf[pbf.pos] = low & 127;
}
function writeBigVarintHigh(high, pbf) {
const lsb = (high & 7) << 4;
pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 128 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 127 | ((high >>>= 7) ? 128 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 127 | ((high >>>= 7) ? 128 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 127 | ((high >>>= 7) ? 128 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 127 | ((high >>>= 7) ? 128 : 0);
if (!high) return;
pbf.buf[pbf.pos++] = high & 127;
}
function makeRoomForExtraLength(startPos, len, pbf) {
const extraLen = len <= 16383 ? 1 : len <= 2097151 ? 2 : len <= 268435455 ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7));
pbf.realloc(extraLen);
for (let i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i];
}
function writePackedVarint(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]);
}
function writePackedSVarint(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]);
}
function writePackedFloat(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]);
}
function writePackedDouble(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]);
}
function writePackedBoolean(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]);
}
function writePackedFixed32(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]);
}
function writePackedSFixed32(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]);
}
function writePackedFixed64(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]);
}
function writePackedSFixed64(arr, pbf) {
for (let i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]);
}
function readUtf8(buf, pos, end) {
let str = "";
let i = pos;
while (i < end) {
const b0 = buf[i];
let c = null;
let bytesPerSequence = b0 > 239 ? 4 : b0 > 223 ? 3 : b0 > 191 ? 2 : 1;
if (i + bytesPerSequence > end) break;
let b1, b2, b3;
if (bytesPerSequence === 1) {
if (b0 < 128) {
c = b0;
}
} else if (bytesPerSequence === 2) {
b1 = buf[i + 1];
if ((b1 & 192) === 128) {
c = (b0 & 31) << 6 | b1 & 63;
if (c <= 127) {
c = null;
}
}
} else if (bytesPerSequence === 3) {
b1 = buf[i + 1];
b2 = buf[i + 2];
if ((b1 & 192) === 128 && (b2 & 192) === 128) {
c = (b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63;
if (c <= 2047 || c >= 55296 && c <= 57343) {
c = null;
}
}
} else if (bytesPerSequence === 4) {
b1 = buf[i + 1];
b2 = buf[i + 2];
b3 = buf[i + 3];
if ((b1 & 192) === 128 && (b2 & 192) === 128 && (b3 & 192) === 128) {
c = (b0 & 15) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63;
if (c <= 65535 || c >= 1114112) {
c = null;
}
}
}
if (c === null) {
c = 65533;
bytesPerSequence = 1;
} else if (c > 65535) {
c -= 65536;
str += String.fromCharCode(c >>> 10 & 1023 | 55296);
c = 56320 | c & 1023;
}
str += String.fromCharCode(c);
i += bytesPerSequence;
}
return str;
}
function writeUtf8(buf, str, pos) {
for (let i = 0, c, lead; i < str.length; i++) {
c = str.charCodeAt(i);
if (c > 55295 && c < 57344) {
if (lead) {
if (c < 56320) {
buf[pos++] = 239;
buf[pos++] = 191;
buf[pos++] = 189;
lead = c;
continue;
} else {
c = lead - 55296 << 10 | c - 56320 | 65536;
lead = null;
}
} else {
if (c > 56319 || i + 1 === str.length) {
buf[pos++] = 239;
buf[pos++] = 191;
buf[pos++] = 189;
} else {
lead = c;
}
continue;
}
} else if (lead) {
buf[pos++] = 239;
buf[pos++] = 191;
buf[pos++] = 189;
lead = null;
}
if (c < 128) {
buf[pos++] = c;
} else {
if (c < 2048) {
buf[pos++] = c >> 6 | 192;
} else {
if (c < 65536) {
buf[pos++] = c >> 12 | 224;
} else {
buf[pos++] = c >> 18 | 240;
buf[pos++] = c >> 12 & 63 | 128;
}
buf[pos++] = c >> 6 & 63 | 128;
}
buf[pos++] = c & 63 | 128;
}
}
return pos;
}
// src/browser/crx3.ts
function readCrxFileHeader(pbf, end) {
return pbf.readFields(
readCrxFileHeaderField,
{
sha256_with_rsa: [],
sha256_with_ecdsa: [],
verified_contents: void 0,
signed_header_data: void 0
},
end
);
}
function readCrxFileHeaderField(tag, obj, pbf) {
if (tag === 2) obj.sha256_with_rsa.push(readAsymmetricKeyProof(pbf, pbf.readVarint() + pbf.pos));
else if (tag === 3)
obj.sha256_with_ecdsa.push(readAsymmetricKeyProof(pbf, pbf.readVarint() + pbf.pos));
else if (tag === 4) obj.verified_contents = pbf.readBytes();
else if (tag === 1e4) obj.signed_header_data = pbf.readBytes();
}
function readAsymmetricKeyProof(pbf, end) {
return pbf.readFields(
readAsymmetricKeyProofField,
{ public_key: void 0, signature: void 0 },
end
);
}
function readAsymmetricKeyProofField(tag, obj, pbf) {
if (tag === 1) obj.public_key = pbf.readBytes();
else if (tag === 2) obj.signature = pbf.readBytes();
}
function readSignedData(pbf, end) {
return pbf.readFields(readSignedDataField, { crx_id: void 0 }, end);
}
function readSignedDataField(tag, obj, pbf) {
if (tag === 1) obj.crx_id = pbf.readBytes();
}
// src/browser/id.ts
var import_node_crypto = require("node:crypto");
function convertHexadecimalToIDAlphabet(id) {
let result = "";
for (const ch of id) {
const val = parseInt(ch, 16);
if (!isNaN(val)) {
result += String.fromCharCode("a".charCodeAt(0) + val);
} else {
result += "a";
}
}
return result;
}
function generateIdFromHash(hash) {
const hashedId = hash.subarray(0, 16).toString("hex");
return convertHexadecimalToIDAlphabet(hashedId);
}
function generateId(input) {
const hash = (0, import_node_crypto.createHash)("sha256").update(input, "base64").digest();
return generateIdFromHash(hash);
}
// src/browser/loader.ts
var fs = __toESM(require("node:fs"));
var path2 = __toESM(require("node:path"));
var import_debug = __toESM(require("debug"));
var d = (0, import_debug.default)("electron-chrome-web-store:loader");
var manifestExists = async (dirPath) => {
if (!dirPath) return false;
const manifestPath = path2.join(dirPath, "manifest.json");
try {
return (await fs.promises.stat(manifestPath)).isFile();
} catch {
return false;
}
};
async function extensionSearch(dirPath, depth = 0) {
if (depth >= 2) return [];
const results = [];
const dirEntries = await fs.promises.readdir(dirPath, { withFileTypes: true });
for (const entry of dirEntries) {
if (entry.isDirectory()) {
if (await manifestExists(path2.join(dirPath, entry.name))) {
results.push(path2.join(dirPath, entry.name));
} else {
results.push(...await extensionSearch(path2.join(dirPath, entry.name), depth + 1));
}
}
}
return results;
}
async function discoverExtensions(extensionsPath) {
try {
const stat = await fs.promises.stat(extensionsPath);
if (!stat.isDirectory()) {
d("%s is not a directory", extensionsPath);
return [];
}
} catch {
d("%s does not exist", extensionsPath);
return [];
}
const extensionDirectories = await extensionSearch(extensionsPath);
const results = [];
for (const extPath of extensionDirectories.filter(Boolean)) {
try {
const manifestPath = path2.join(extPath, "manifest.json");
const manifestJson = (await fs.promises.readFile(manifestPath)).toString();
const manifest = JSON.parse(manifestJson);
const result = manifest.key ? {
type: "store",
path: extPath,
manifest,
id: generateId(manifest.key)
} : {
type: "unpacked",
path: extPath,
manifest
};
results.push(result);
} catch (e) {
console.error(e);
}
}
return results;
}
function filterOutdatedExtensions(extensions) {
const uniqueExtensions = [];
const storeExtMap = /* @__PURE__ */ new Map();
for (const ext of extensions) {
if (ext.type === "unpacked") {
uniqueExtensions.push(ext);
} else if (!storeExtMap.has(ext.id)) {
storeExtMap.set(ext.id, ext);
} else {
const latestExt = storeExtMap.get(ext.id);
if (compareVersions(latestExt.manifest.version, ext.manifest.version) < 0) {
storeExtMap.set(ext.id, ext);
}
}
}
storeExtMap.forEach((ext) => uniqueExtensions.push(ext));
return uniqueExtensions;
}
async function loadAllExtensions(session, extensionsPath, options = {}) {
const sessionExtensions = session.extensions || session;
let extensions = await discoverExtensions(extensionsPath);
extensions = filterOutdatedExtensions(extensions);
d("discovered %d extension(s) in %s", extensions.length, extensionsPath);
for (const ext of extensions) {
try {
let extension;
if (ext.type === "store") {
const existingExt = sessionExtensions.getExtension(ext.id);
if (existingExt) {
d("skipping loading existing extension %s", ext.id);
continue;
}
d("loading extension %s", `${ext.id}@${ext.manifest.version}`);
extension = await sessionExtensions.loadExtension(ext.path);
} else if (options.allowUnpacked) {
d("loading unpacked extension %s", ext.path);
extension = await sessionExtensions.loadExtension(ext.path);
}
if (extension && extension.manifest.manifest_version === 3 && extension.manifest.background?.service_worker) {
const scope = `chrome-extension://${extension.id}`;
await session.serviceWorkers.startWorkerForScope(scope).catch(() => {
console.error(`Failed to start worker for extension ${extension.id}`);
});
}
} catch (error) {
console.error(`Failed to load extension from ${ext.path}`);
console.error(error);
}
}
}
async function findExtensionInstall(extensionId, extensionsPath) {
const extensionPath = path2.join(extensionsPath, extensionId);
let extensions = await discoverExtensions(extensionPath);
extensions = filterOutdatedExtensions(extensions);
return extensions.length > 0 ? extensions[0] : null;
}
// src/browser/installer.ts
var d2 = (0, import_debug2.default)("electron-chrome-web-store:installer");
function getExtensionCrxURL(extensionId) {
const url = new URL("https://clients2.google.com/service/update2/crx");
url.searchParams.append("response", "redirect");
url.searchParams.append("acceptformat", ["crx2", "crx3"].join(","));
const x = new URLSearchParams();
x.append("id", extensionId);
x.append("uc", "");
url.searchParams.append("x", x.toString());
url.searchParams.append("prodversion", getChromeVersion());
return url.toString();
}
function parseCrx(buffer) {
const magicNumber = buffer.toString("utf8", 0, 4);
if (magicNumber !== "Cr24") {
throw new Error("Invalid CRX format");
}
const version = buffer.readUInt32LE(4);
const headerSize = buffer.readUInt32LE(8);
const header = buffer.subarray(12, 12 + headerSize);
const contents = buffer.subarray(12 + headerSize);
let extensionId;
let publicKey;
if (version === 2) {
const pubKeyLength = buffer.readUInt32LE(8);
const sigLength = buffer.readUInt32LE(12);
publicKey = buffer.subarray(16, 16 + pubKeyLength);
extensionId = generateId(publicKey.toString("base64"));
} else {
const crxFileHeader = readCrxFileHeader(new Pbf(header));
const crxSignedData = readSignedData(new Pbf(crxFileHeader.signed_header_data));
const declaredCrxId = crxSignedData.crx_id ? convertHexadecimalToIDAlphabet(crxSignedData.crx_id.toString("hex")) : null;
if (!declaredCrxId) {
throw new Error("Invalid CRX signed data");
}
const keyProof = crxFileHeader.sha256_with_rsa.find((proof) => {
const crxId = proof.public_key ? generateId(proof.public_key.toString("base64")) : null;
return crxId === declaredCrxId;
});
if (!keyProof) {
throw new Error("Invalid CRX key");
}
extensionId = declaredCrxId;
publicKey = keyProof.public_key;
}
return {
extensionId,
version,
header,
contents,
publicKey
};
}
async function unpackCrx(crx, destPath) {
const zip = new import_adm_zip.default(crx.contents);
zip.extractAllTo(destPath, true);
const manifestPath = path3.join(destPath, "manifest.json");
const manifestContent = await fs2.promises.readFile(manifestPath, "utf8");
const manifest = JSON.parse(manifestContent);
manifest.key = crx.publicKey.toString("base64");
await fs2.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
return manifest;
}
async function readCrx(crxPath) {
const crxBuffer = await fs2.promises.readFile(crxPath);
return parseCrx(crxBuffer);
}
async function downloadCrx(url, dest) {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to download extension");
}
const fileStream = fs2.createWriteStream(dest);
const downloadStream = import_node_stream.Readable.fromWeb(response.body);
await (0, import_promises.pipeline)(downloadStream, fileStream);
}
async function downloadExtensionFromURL(url, extensionsDir, expectedExtensionId) {
d2("downloading %s", url);
const installUuid = crypto.randomUUID();
const crxPath = path3.join(os.tmpdir(), `electron-cws-download_${installUuid}.crx`);
try {
await downloadCrx(url, crxPath);
const crx = await readCrx(crxPath);
if (expectedExtensionId && expectedExtensionId !== crx.extensionId) {
throw new Error(
`CRX mismatches expected extension ID: ${expectedExtensionId} !== ${crx.extensionId}`
);
}
const unpackedPath = path3.join(extensionsDir, crx.extensionId, installUuid);
await fs2.promises.mkdir(unpackedPath, { recursive: true });
const manifest = await unpackCrx(crx, unpackedPath);
if (!manifest.version) {
throw new Error("Installed extension is missing manifest version");
}
const versionedPath = path3.join(extensionsDir, crx.extensionId, `${manifest.version}_0`);
await fs2.promises.rename(unpackedPath, versionedPath);
return versionedPath;
} finally {
await fs2.promises.rm(crxPath, { force: true });
}
}
async function downloadExtension(extensionId, extensionsDir) {
const url = getExtensionCrxURL(extensionId);
return await downloadExtensionFromURL(url, extensionsDir, extensionId);
}
async function installExtension(extensionId, opts = {}) {
d2("installing %s", extensionId);
const session = opts.session || import_electron2.session.defaultSession;
const sessionExtensions = session.extensions || session;
const extensionsPath = opts.extensionsPath || getDefaultExtensionsPath();
const existingExtension = sessionExtensions.getExtension(extensionId);
if (existingExtension) {
d2("%s already loaded", extensionId);
return existingExtension;
}
const existingExtensionInfo = await findExtensionInstall(extensionId, extensionsPath);
if (existingExtensionInfo && existingExtensionInfo.type === "store") {
d2("%s already installed", extensionId);
return await sessionExtensions.loadExtension(
existingExtensionInfo.path,
opts.loadExtensionOptions
);
}
const extensionPath = await downloadExtension(extensionId, extensionsPath);
const extension = await sessionExtensions.loadExtension(extensionPath, opts.loadExtensionOptions);
d2("installed %s", extensionId);
return extension;
}
async function uninstallExtension(extensionId, opts = {}) {
d2("uninstalling %s", extensionId);
const session = opts.session || import_electron2.session.defaultSession;
const sessionExtensions = session.extensions || session;
const extensionsPath = opts.extensionsPath || getDefaultExtensionsPath();
const extensions = sessionExtensions.getAllExtensions();
const existingExt = extensions.find((ext) => ext.id === extensionId);
if (existingExt) {
sessionExtensions.removeExtension(extensionId);
}
const extensionDir = path3.join(extensionsPath, extensionId);
try {
const stat = await fs2.promises.stat(extensionDir);
if (stat.isDirectory()) {
await fs2.promises.rm(extensionDir, { recursive: true, force: true });
}
} catch (error) {
if (error?.code !== "ENOENT") {
throw error;
}
}
}
// src/browser/api.ts
var d3 = (0, import_debug3.default)("electron-chrome-web-store:api");
var WEBSTORE_URL = "https://chromewebstore.google.com";
function getExtensionInfo(ext) {
const manifest = ext.manifest;
return {
description: manifest.description || "",
enabled: !manifest.disabled,
homepageUrl: manifest.homepage_url || "",
hostPermissions: manifest.host_permissions || [],
icons: Object.entries(manifest?.icons || {}).map(([size, url]) => ({
size: parseInt(size),
url: `chrome://extension-icon/${ext.id}/${size}/0`
})),
id: ext.id,
installType: "normal",
isApp: !!manifest.app,
mayDisable: true,
name: manifest.name,
offlineEnabled: !!manifest.offline_enabled,
optionsUrl: manifest.options_page ? `chrome-extension://${ext.id}/${manifest.options_page}` : "",
permissions: manifest.permissions || [],
shortName: manifest.short_name || manifest.name,
type: manifest.app ? "app" : "extension",
updateUrl: manifest.update_url || "",
version: manifest.version
};
}
function getExtensionInstallStatus(state, extensionId, manifest) {
if (manifest && manifest.manifest_version < state.minimumManifestVersion) {
return ExtensionInstallStatus.DEPRECATED_MANIFEST_VERSION;
}
if (state.denylist?.has(extensionId)) {
return ExtensionInstallStatus.BLOCKED_BY_POLICY;
}
if (state.allowlist && !state.allowlist.has(extensionId)) {
return ExtensionInstallStatus.BLOCKED_BY_POLICY;
}
const sessionExtensions = state.session.extensions || state.session;
const extensions = sessionExtensions.getAllExtensions();
const extension = extensions.find((ext) => ext.id === extensionId);
if (!extension) {
return ExtensionInstallStatus.INSTALLABLE;
}
if (extension.manifest.disabled) {
return ExtensionInstallStatus.DISABLED;
}
return ExtensionInstallStatus.ENABLED;
}
async function beginInstall({ sender, senderFrame }, state, details) {
const extensionId = details.id;
try {
if (state.installing.has(extensionId)) {
return { result: Result.INSTALL_IN_PROGRESS };
}
let manifest;
try {
manifest = JSON.parse(details.manifest);
} catch {
return { result: Result.MANIFEST_ERROR };
}
const installStatus = getExtensionInstallStatus(state, extensionId, manifest);
switch (installStatus) {
case ExtensionInstallStatus.INSTALLABLE:
break;
// good to go
case ExtensionInstallStatus.BLOCKED_BY_POLICY:
return { result: Result.BLOCKED_BY_POLICY };
default: {
d3('unable to install extension %s with status "%s"', extensionId, installStatus);
return { result: Result.UNKNOWN_ERROR };
}
}
let iconUrl;
try {
iconUrl = new URL(details.iconUrl);
} catch {
return { result: Result.INVALID_ICON_URL };
}
let icon;
try {
const response = await fetch(iconUrl.href);
const imageBuffer = Buffer.from(await response.arrayBuffer());
icon = import_electron3.nativeImage.createFromBuffer(imageBuffer);
} catch {
return { result: Result.ICON_ERROR };
}
const browserWindow = import_electron3.BrowserWindow.fromWebContents(sender);
if (!senderFrame || senderFrame.isDestroyed()) {
return { result: Result.UNKNOWN_ERROR };
}
if (state.beforeInstall) {
const result = await state.beforeInstall({
id: extensionId,
localizedName: details.localizedName,
manifest,
icon,
frame: senderFrame,
browserWindow: browserWindow || void 0
});
if (typeof result !== "object" || typeof result.action !== "string") {
return { result: Result.UNKNOWN_ERROR };
} else if (result.action !== "allow") {
return { result: Result.USER_CANCELLED };
}
}
state.installing.add(extensionId);
await installExtension(extensionId, state);
return { result: Result.SUCCESS };
} catch (error) {
console.error("Extension installation failed:", error);
return {
result: Result.INSTALL_ERROR,
message: error instanceof Error ? error.message : String(error)
};
} finally {
state.installing.delete(extensionId);
}
}
var handledIpcChannels = /* @__PURE__ */ new Map();
function registerWebStoreApi(webStoreState) {
const handle = (channel, handle2) => {
let handlersMap = handledIpcChannels.get(channel);
if (!handlersMap) {
handlersMap = /* @__PURE__ */ new Map();
handledIpcChannels.set(channel, handlersMap);
import_electron3.ipcMain.handle(channel, async function handleWebStoreIpc(event, ...args) {
d3("received %s", channel);
const senderOrigin = event.senderFrame?.origin;
if (!senderOrigin || !senderOrigin.startsWith(WEBSTORE_URL)) {
d3("ignoring webstore request from %s", senderOrigin);
return;
}
const session = event.sender.session;
const handler = handlersMap?.get(session);
if (!handler) {
d3("no handler for session %s", session.storagePath);
return;
}
const result = await handler(event, ...args);
d3("%s result", channel, result);
return result;
});
}
handlersMap.set(webStoreState.session, handle2);
};
handle("chromeWebstore.beginInstall", async (event, details) => {
const { senderFrame } = event;
d3("beginInstall", details);
const result = await beginInstall(event, webStoreState, details);
if (result.result === Result.SUCCESS) {
queueMicrotask(() => {
const sessionExtensions = webStoreState.session.extensions || webStoreState.session;
const ext = sessionExtensions.getExtension(details.id);
if (ext && senderFrame && !senderFrame.isDestroyed()) {
try {
senderFrame.send("chrome.management.onInstalled", getExtensionInfo(ext));
} catch (error) {
console.error(error);
}
}
});
}
return result;
});
handle("chromeWebstore.completeInstall", async (event, id) => {
return Result.SUCCESS;
});
handle("chromeWebstore.enableAppLauncher", async (event, enable) => {
return true;
});
handle("chromeWebstore.getBrowserLogin", async () => {
return "";
});
handle("chromeWebstore.getExtensionStatus", async (_event, id, manifestJson) => {
const manifest = JSON.parse(manifestJson);
return getExtensionInstallStatus(webStoreState, id, manifest);
});
handle("chromeWebstore.getFullChromeVersion", async () => {
return {
version_number: process.versions.chrome,
app_name: import_electron3.app.getName()
};
});
handle("chromeWebstore.getIsLauncherEnabled", async () => {
return true;
});
handle("chromeWebstore.getMV2DeprecationStatus", async () => {
return webStoreState.minimumManifestVersion > 2 ? MV2DeprecationStatus.SOFT_DISABLE : MV2DeprecationStatus.INACTIVE;
});
handle("chromeWebstore.getReferrerChain", async () => {
return "EgIIAA==";
});
handle("chromeWebstore.getStoreLogin", async () => {
return "";
});
handle("chromeWebstore.getWebGLStatus", async () => {
await import_electron3.app.getGPUInfo("basic");
const features = import_electron3.app.getGPUFeatureStatus();
return features.webgl.startsWith("enabled") ? WebGlStatus.WEBGL_ALLOWED : WebGlStatus.WEBGL_BLOCKED;
});
handle("chromeWebstore.install", async (event, id, silentInstall) => {
return Result.SUCCESS;
});
handle("chromeWebstore.isInIncognitoMode", async () => {
return false;
});
handle("chromeWebstore.isPendingCustodianApproval", async (event, id) => {
return false;
});
handle("chromeWebstore.setStoreLogin", async (event, login) => {
return true;
});
handle("chrome.runtime.getManifest", async () => {
return {};
});
handle("chrome.management.getAll", async (event) => {
const sessionExtensions = webStoreState.session.extensions || webStoreState.session;
const extensions = sessionExtensions.getAllExtensions();
return extensions.map(getExtensionInfo);
});
handle("chrome.management.setEnabled", async (event, id, enabled) => {
return true;
});
handle(
"chrome.management.uninstall",
async (event, id, options) => {
if (options?.showConfirmDialog) {
}
try {
await uninstallExtension(id, webStoreState);
queueMicrotask(() => {
event.sender.send("chrome.management.onUninstalled", id);
});
return Result.SUCCESS;
} catch (error) {
console.error(error);
return Result.UNKNOWN_ERROR;
}
}
);
}
// src/browser/updater.ts
var fs3 = __toESM(require("node:fs"));
var path4 = __toESM(require("node:path"));
var import_debug4 = __toESM(require("debug"));
var import_electron4 = require("electron");
var d4 = (0, import_debug4.default)("electron-chrome-web-store:updater");
var SYSTEM_IDLE_DURATION = 1 * 60 * 60 * 1e3;
var UPDATE_CHECK_INTERVAL = 5 * 60 * 60 * 1e3;
var MIN_UPDATE_INTERVAL = 3 * 60 * 60 * 1e3;
var lastUpdateCheck;
var ALLOWED_UPDATE_URLS = /* @__PURE__ */ new Set(["https://clients2.google.com/service/update2/crx"]);
var getSessionId = /* @__PURE__ */ (() => {
let sessionId;
return () => sessionId || (sessionId = crypto.randomUUID());
})();
var getOmahaPlatform = () => {
switch (process.platform) {
case "win32":
return "win";
case "darwin":
return "mac";
default:
return process.platform;
}
};
var getOmahaArch = () => {
switch (process.arch) {
case "ia32":
return "x86";
case "x64":
return "x64";
default:
return process.arch;
}
};
function filterWebStoreExtension(extension) {
const manifest = extension.manifest;
if (!manifest) return false;
return manifest.key && manifest.update_url && ALLOWED_UPDATE_URLS.has(manifest.update_url);
}
async function fetchAvailableUpdates(extensions) {
if (extensions.length === 0) return [];
const extensionIds = extensions.map((extension) => extension.id);
const extensionMap = extensions.reduce(
(map, ext) => ({
...map,
[ext.id]: ext
}),
{}
);
const chromeVersion = getChromeVersion();
const url = "https://update.googleapis.com/service/update2/json";
const body = {
request: {
"@updater": "electron-chrome-web-store",
acceptformat: "crx3",
app: [
...extensions.map((extension) => ({
appid: extension.id,
updatecheck: {}
// API always reports 'noupdate' when version is set :thinking:
// version: extension.version,
}))
],
os: {
platform: getOmahaPlatform(),
arch: getOmahaArch()
},
prodversion: chromeVersion,
protocol: "3.1",
requestid: crypto.randomUUID(),
sessionid: getSessionId(),
testsource: process.env.NODE_ENV === "production" ? "" : "electron_dev"
}
};
const response = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
"X-Goog-Update-Interactivity": "bg",
"X-Goog-Update-AppId": extensionIds.join(","),
"X-Goog-Update-Updater": `chromiumcrx-${chromeVersion}`
},
body: JSON.stringify(body)
});
if (!response.ok) {
d4("update response not ok");
return [];
}
const text = await response.text();
const prefix = `)]}'
`;
if (!text.startsWith(prefix)) {
d4("unexpected update response: %s", text);
return [];
}
const json = text.substring(prefix.length);
const result = JSON.parse(json);
let updates;
try {
const apps = result?.response?.app || [];
updates = apps.filter((app5) => app5.updatecheck.status === "ok").map((app5) => {
const extensionId = app5.appid;
const extension = extensionMap[extensionId];
const manifest = app5.updatecheck.manifest;
const pkg = manifest.packages.package[0];
return {
extension,
id: extensionId,
version: manifest.version,
name: pkg.name,
url: app5.updatecheck.urls.url[0].codebase
};
}).filter((update) => {
const extension = extensionMap[update.id];
return compareVersions(extension.version, update.version) < 0;
});
} catch (error) {
console.error("Unable to read extension updates response", error);
return [];
}
return updates;
}
async function updateExtension(session, update) {
const sessionExtensions = session.extensions || session;
const extensionId = update.id;
const oldExtension = update.extension;
d4("updating %s %s -> %s", extensionId, oldExtension.version, update.version);
const oldVersionDirectoryName = path4.basename(oldExtension.path);
if (!oldVersionDirectoryName.startsWith(oldExtension.version)) {
console.error(
`updateExtension: extension ${extensionId} must conform to versioned directory names`,
{
oldPath: oldExtension.path
}
);
d4("skipping %s update due to invalid install path %s", extensionId, oldExtension.path);
return;
}
const extensionsPath = path4.join(oldExtension.path, "..", "..");
const updatePath = await downloadExtensionFromURL(update.url, extensionsPath, extensionId);
d4("downloaded update %s@%s", extensionId, update.version);
if (sessionExtensions.getExtension(extensionId)) {
sessionExtensions.removeExtension(extensionId);
await sessionExtensions.loadExtension(updatePath);
d4("loaded update %s@%s", extensionId, update.version);
}
await fs3.promises.rm(oldExtension.path, { recursive: true, force: true });
}
async function checkForUpdates(session) {
const sessionExtensions = session.extensions || session;
const extensions = sessionExtensions.getAllExtensions().filter(filterWebStoreExtension);
d4("checking for updates: %s", extensions.map((ext) => `${ext.id}@${ext.version}`).join(","));
const updates = await fetchAvailableUpdates(extensions);
if (!updates || updates.length === 0) {
d4("no updates found");
return [];
}
return updates;
}
async function installUpdates(session, updates) {
d4("updating %d extension(s)", updates.length);
for (const update of updates) {
try {
await updateExtension(session, update);