Electron app को obfuscate करें

@afterpack/electron main, preload और renderer को एक ही call में एक seed के साथ wire करता है, और electron-vite के bytecodePlugin और असुरक्षित backups को आपका source ship करने से पहले ही अस्वीकार कर देता है।

@afterpack/electron एक Electron app के ship किए जाने वाले तीनों programs (main, preload, renderer) को एक ही call से obfuscate करता है, और उन सबमें एक build seed साझा करता है। हाथ से wire करने पर हर leg अपनी अलग bundler build होता है और अपना random seed लेता है, इसलिए एक ही application के हिस्से संरचना में एक-दूसरे से असंबंधित निकलते हैं।

electron-builder कोई bundler नहीं है। यह पहले से built app को installer में package करता है और आपके JavaScript को कभी नहीं छूता, इसलिए AfterPack हो या न हो, इसके ऊपर एक bundler चाहिए।

Install

$ 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 config को wrap करता है (object, function या promise, जिस भी रूप में defineConfig लौटाए) और config में घोषित हर leg में @afterpack/vite wire करता है, किसी अघोषित leg में कभी नहीं। Peer dependency: vite ^5 \| ^6 \| ^7 \| ^8।

Build

Pass को कैसे wire करें, यह इस पर निर्भर है कि आपका toolchain तीनों legs को एक call में configure करता है, या हर leg के लिए एक config।

एक समय में एक leg

जो toolchains हर leg को अलग से configure करते हैं (@electron-forge/plugin-vite के साथ Electron Forge), वे इसके बजाय हर config में एक बार afterpackElectron call करते हैं:

// 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 ज़रूरी है।

एक seed, तीन तरीके

  • electron-vite (withAfterpack) और Forge + plugin-vite: तीनों configs एक ही Node process में build होते हैं, इसलिए legs अपने आप एक seed साझा करते हैं।
  • vite-plugin-electron: यह भी एक process है। afterpackElectron({ leg: "renderer" }) को plugins में सबसे आख़िर में, vite-plugin-electron के बाद रखें, ताकि AfterPack का generateBundle तैयार bundle को seal करे।
  • हाथ से लिखी scripts जो हर leg को अलग command के रूप में चलाती हैं: कोई साझा process नहीं है, इसलिए उनमें seed को एक environment variable से pin करें:
// package.json
"build": "AFTERPACK_seed=git vite build -c vite.main.ts && vite build -c vite.renderer.ts"

AFTERPACK_seed वही values लेता है जो seed: एक integer, "git" (HEAD से निकाला गया), या कोई भी दूसरी string, जिसे engine hash करता है। स्पष्ट seed option फिर भी इससे ऊपर रहता है। Seed strategy बताती है कि seed कब pin करें और कब नहीं।

Verify

Protection Map हर leg के लिए अलग, .afterpack/<leg>/protectionMap.html में लिखा जाता है, जब कोई source map मिल सके और build CI (CI=true या CI=1) में न चल रही हो। Production mode इसे बंद नहीं करता, लेकिन electron-vite तब तक कोई source maps emit नहीं करता जब तक आप उन्हें न माँगें, इसलिए map डिफ़ॉल्ट रूप से बंद रहता है। main, preload और renderer को अलग-अलग जाँचें। हर रिपोर्ट में आपका पूरा source होता है। इसे कभी commit या serve न करें।

App के अंदर source maps कभी ship न करें

app.asar में मौजूद एक .map आपके मूल source तक पहुँचता है। जो कुछ भी आप package करते हैं, उसके लिए source-map emission बंद रखें (production में डिफ़ॉल्ट यही है)।

Options

हर configuration key यहाँ भी काम करती है, withAfterpack(config, options) से हर leg पर लागू, या afterpackElectron(options) से हर leg के लिए आगे भेजी गई। ये options केवल इसी package में हैं:

OptionTypeDefault
leg"main" | "preload" | "renderer"afterpackElectron पर ज़रूरी
projectRootstringprocess.cwd()। electron-vite renderer build का root src/renderer/ रखता है। अगर आपकी build कहीं और से चलती है तो इसे override करें

seed डिफ़ॉल्ट रूप से एक नया value होता है जो एक build session के हर leg में साझा होता है। protectionMap.enabled सामान्य source-map नियम का पालन करता है, और हर leg अपनी रिपोर्ट .afterpack/<leg>/protectionMap.html में लिखता है।

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

Directives

हर leg के नीचे @afterpack/vite wire होता है, इसलिए directive capture वहाँ भी उसी तरह काम करता है।

Edge cases

Support matrix

ToolchainWiringअपने आप एक seed?
electron-vitewithAfterpack(defineConfig({ … })), एक callहाँ
Forge + plugin-viteतीनों configs में से हर एक में afterpackElectron({ leg })हाँ
Forge + plugin-webpackहर config में @afterpack/webpackहाँ
vite-plugin-electronafterpackElectron({ leg: "renderer" }), सबसे आख़िर में रखा गयाहाँ, जब उस plugin के बाद रखा जाए
electron-builderयह package करता है, bundle नहीं; इसके ऊपर वाले bundler को wire करेंलागू नहीं
हाथ से लिखी scripts (अलग processes)हर leg के लिए एक plugin, या npx afterpack@latest <dir>नहीं; AFTERPACK_seed pin करें

यह क्या अस्वीकार करता है

इस package से wire किए गए किसी भी leg पर दो fail-closed checks लागू होते हैं।

bytecodePlugin। electron-vite का अपना bytecodePlugin emit किए गए हर chunk को 3 लाइन के loader stub से बदल देता है और असली application को V8 bytecode (.jsc) के रूप में ship करता है। दोनों एक साथ नहीं चल सकते, क्योंकि bytecode उस chunk को बदल देता है जिसे Rollup ने generate किया था। Build उस leg पर fail होती है जहाँ bytecodePlugin चालू है, या जहाँ भी बाद में output में कोई .jsc/.cjsc फ़ाइल दिखे। दोनों में से एक को हटाएँ।

build.backup: true। Backups सीधे अस्वीकार किए जाते हैं: call आगे बढ़ने के बजाय throw करता है, क्योंकि .backup.<hash>.js आपका पूरा मूल source है, जो output के बगल में उसी tree में लिखा जाता है जिसे आपका packager app.asar में copy करता है। App install करने वाले हर user को यह मिल जाता।

यही जोखिम हर उस artifact पर लागू होता है जिसे बाहर रखना आपका packager नहीं जानता। electron-builder का डिफ़ॉल्ट files glob dot: true के साथ match करता है और डिफ़ॉल्ट रूप से .afterpack/ को बाहर नहीं रखता। इन्हें package की जाने वाली चीज़ों से बाहर रखें:

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

Source maps पर अस्वीकार के बजाय एक warning मिलती है: app.asar के अंदर sourcesContent वाला .map पूरा deobfuscation है। sourceMap: { enabled: true, emitUrl: true } केवल local debug builds तक सीमित रखें और package की जाने वाली चीज़ों से *.map बाहर रखें।

IPC channels

Channel names दोनों bundles में byte-for-byte मेल खाने चाहिए। ipcMain.on("save-file", …) और ipcRenderer.send("save-file", …) दोनों तरफ़ एक ही string हैं। वे सामान्य emit किए गए JavaScript में सामान्य string literals हैं, इसलिए दोनों legs को एक ही seed और एक ही posture (preset / complexity) पर रखना ही दोनों हिस्सों को consistent रखता है।

अगर कोई channel name literal के रूप में लिखे जाने के बजाय runtime पर बनाया जाता है, तो उस construction को ऐसे module में रखें जिसे आप paths.exclude से बाहर रखते हैं, या bundler-plugin की एक regions entry (Pro) से उस region का posture कम करें।

Plugin के बिना: CLI

Dependency नहीं जोड़ना चाहते, या किसी leg को Vite के अलावा किसी और चीज़ से build कर रहे हैं? तो build के बाद हर output directory पर एक बार npx afterpack@latest चलाएँ:

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

Path argument न दें, तो CLI out/ पहचान लेता है, वह directory जिसमें electron-vite लिखता है; आपकी directory अलग हो तो उसका नाम दें। हर run directory में जाता है, हर .js/.mjs/.cjs इकट्ठा करता है, और उन्हें उसी जगह obfuscate करता है; argument के रूप में एक अकेली फ़ाइल भी काम करती है। हर run में एक ही --seed pin करें: मौजूदा commit से निकाला गया git इसकी गारंटी का सबसे आसान तरीका है। CLI run पर ऊपर के कोई भी अस्वीकार लागू नहीं होते; उसे जो भी JavaScript मिलता है, उसे obfuscate कर देता है। सभी runs में एक ही --seed और --preset रखें।

आगे