UNPKG

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
"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);