Обфускація застосунку Electron

@afterpack/electron підключає main, preload і renderer з одним сідом одним викликом і відхиляє bytecodePlugin з electron-vite та незахищені резервні копії, перш ніж вони встигнуть випустити ваш вихідний код.

@afterpack/electron одним викликом обфускує всі три програми, які випускає застосунок Electron (main, preload, renderer), і всі вони використовують один сід збірки. Якщо підключати вручну, кожна частина — це окрема збірка бандлера з власним випадковим сідом, тож частини одного застосунку виходять структурно не пов'язаними між собою.

electron-builder — не бандлер. Він пакує вже зібраний застосунок в інсталятор і ніколи не чіпає ваш JavaScript, тож над ним у будь-якому разі потрібен бандлер, незалежно від AfterPack.

Встановлення

$ npm install -D @afterpack/electron
// electron.vite.config.ts
import { withAfterpack } from "@afterpack/electron";
import { defineConfig, externalizeDepsPlugin } from "electron-vite";

export default withAfterpack(
  defineConfig({
    main: { plugins: [externalizeDepsPlugin()] },
    preload: { plugins: [externalizeDepsPlugin()] },
    renderer: {},
  }),
);

withAfterpack обгортає всю конфігурацію electron-vite (об'єкт, функцію або проміс, які повертає defineConfig) і підключає @afterpack/vite до кожної частини, яку оголошує конфігурація, і ніколи до тієї, якої вона не оголошує. Peer-залежність: vite ^5 \| ^6 \| ^7 \| ^8.

Збірка

Як підключати прохід, залежить від того, чи ваш інструментарій налаштовує всі три частини одним викликом, чи окремою конфігурацією для кожної частини.

По одній частині

Інструментарії, які налаштовують кожну частину окремо (Electron Forge з @electron-forge/plugin-vite), натомість викликають afterpackElectron один раз для кожної конфігурації:

// vite.main.config.ts   (and vite.preload.config.ts / vite.renderer.config.ts)
import { afterpackElectron } from "@afterpack/electron";

export default { plugins: [afterpackElectron({ leg: "main" })] };

leg обов'язковий.

Один сід, три способи

  • electron-vite (withAfterpack) і Forge + plugin-vite: усі три конфігурації збираються в одному процесі Node, тож частини отримують спільний сід автоматично.
  • vite-plugin-electron: теж один процес. Поставте afterpackElectron({ leg: "renderer" }) останнім у plugins, після самого vite-plugin-electron, щоб generateBundle AfterPack запечатував уже готовий бандл.
  • Власні скрипти, які запускають кожну частину окремою командою: спільного процесу немає, тож закріпіть сід для всіх через змінну середовища:
// package.json
"build": "AFTERPACK_seed=git vite build -c vite.main.ts && vite build -c vite.renderer.ts"

AFTERPACK_seed приймає ті самі значення, що й seed: ціле число, "git" (виводиться з HEAD) або будь-який інший рядок, який рушій хешує. Явний параметр seed усе одно має над ним пріоритет. Стратегія сіду пояснює, коли його закріплювати, а коли ні.

Перевірка

Protection Map записується окремо для кожної частини, у .afterpack/<leg>/protectionMap.html, коли вдалося знайти source map і збірка не працює в CI (CI=true або CI=1). Продакшн-режим її не вимикає, але electron-vite не видає source map, якщо ви їх не запросите, тож мапа за замовчуванням вимкнена. Досліджуйте main, preload і renderer окремо. Кожен звіт містить увесь ваш вихідний код. Ніколи не комітьте його і не роздавайте з сервера.

Ніколи не випускайте source map усередині застосунку

.map в app.asar веде до вашого оригінального вихідного коду. Тримайте генерацію source map вимкненою (так за замовчуванням у продакшні) для всього, що ви пакуєте.

Параметри

Будь-який ключ конфігурації працює і тут: для всіх частин через withAfterpack(config, options) або для кожної частини окремо через afterpackElectron(options). Ці параметри існують лише в цьому пакеті:

ПараметрТипЗа замовчуванням
leg"main" | "preload" | "renderer"обов'язковий в afterpackElectron
projectRootstringprocess.cwd(). electron-vite запускає збірку renderer від кореня src/renderer/. Перевизначте його, якщо ваша збірка запускається з іншого місця

seed за замовчуванням — одне нове значення, спільне для всіх частин у межах сесії збірки. protectionMap.enabled підпорядковується звичайному правилу щодо source map, і кожна частина записує власний звіт у .afterpack/<leg>/protectionMap.html.

// Heavier protection, reproducible per commit, applied to every leg:
withAfterpack(config, { preset: "hard", seed: "git" });

Директиви

Кожна частина під капотом підключає @afterpack/vite, тож збір директив працює там так само.

Особливі випадки

Матриця підтримки

ІнструментарійПідключенняОдин сід автоматично?
electron-vitewithAfterpack(defineConfig({ … })), один викликТак
Forge + plugin-viteafterpackElectron({ leg }) у кожній із трьох конфігураційТак
Forge + plugin-webpack@afterpack/webpack у кожній конфігураціїТак
vite-plugin-electronafterpackElectron({ leg: "renderer" }), останнімТак, якщо стоїть після цього плагіна
electron-builderвін пакує, а не збирає бандл; підключайте бандлер над нимн/д
Власні скрипти (окремі процеси)плагін для кожної частини або npx afterpack@latest <dir>Ні; закріпіть AFTERPACK_seed

Що він відхиляє

Дві перевірки з зупинкою з помилкою (fail closed) діють для будь-якої частини, підключеної через цей пакет.

bytecodePlugin. Власний bytecodePlugin з electron-vite замінює кожен згенерований чанк 3-рядковою заглушкою-завантажувачем і випускає справжній застосунок як байткод V8 (.jsc). Ці двоє не можуть працювати разом, бо байткод замінює чанк, згенерований Rollup. Збірка завершується помилкою на частині, де ввімкнено bytecodePlugin, або там, де файл .jsc/.cjsc згодом з'являється в результаті. Приберіть одне або інше.

build.backup: true. Резервні копії відхиляються повністю: виклик кидає виняток замість того, щоб продовжити, бо .backup.<hash>.js — це ваш повний оригінальний вихідний код, записаний поруч із результатом усередині дерева, яке ваш пакувальник копіює в app.asar. Кожен користувач, який встановить застосунок, отримав би його.

Той самий ризик стосується будь-якого артефакту, про який ваш пакувальник не знає, що його треба виключити. Стандартний glob files в electron-builder збігається з dot: true і за замовчуванням не виключає .afterpack/. Виключіть ці шляхи з того, що ви пакуєте:

// electron-builder
"files": ["**/*", "!.afterpack/**", "!**/*.backup.*", "!**/*.map"]
// electron-forge
packagerConfig: { ignore: [/^\/\.afterpack/, /\.backup\./, /\.map$/] }

На source map видається попередження, а не відмова: .map із sourcesContent усередині app.asar — це повна деобфускація. Залишайте sourceMap: { enabled: true, emitUrl: true } лише для локальних налагоджувальних збірок і виключайте *.map з того, що ви пакуєте.

Канали IPC

Назви каналів мають збігатися байт у байт в обох бандлах. ipcMain.on("save-file", …) і ipcRenderer.send("save-file", …) — це той самий рядок з обох боків. Це звичайні рядкові літерали у звичайному згенерованому JavaScript, тож саме однаковий сід і однаковий рівень захисту (preset / complexity) для обох частин тримають дві половини узгодженими.

Якщо назва каналу складається під час виконання, а не записана літералом, тримайте цю конструкцію в модулі, який ви виключаєте через paths.exclude, або знизьте рівень захисту для цієї ділянки через запис regions у плагіні бандлера (Pro).

Без плагіна: CLI

Не хочете додавати залежність або збираєте частину чимось іншим, ніж Vite? Натомість запустіть npx afterpack@latest один раз для кожного каталогу виводу після збірки:

npx afterpack@latest out/main --seed=git
npx afterpack@latest out/renderer --seed=git

Без аргументу шляху CLI визначає out/, каталог, у який пише electron-vite; якщо ваш відрізняється, вкажіть каталог явно. Кожен запуск обходить каталог, збирає кожен .js/.mjs/.cjs і обфускує їх на місці; аргументом може бути й один файл. Закріпіть однаковий --seed для всіх запусків: git, що виводиться з поточного коміту, — найпростіший спосіб це гарантувати. Запуск CLI не має жодної з наведених вище відмов; він обфускує будь-який JavaScript, який знайде. Тримайте однакові --seed і --preset для всіх запусків.

Далі