Electron技术问询:exe/dmg提取触发事件及用户Token获取方案
Great question! Handling per-user tokens without rebuilding your Electron app for every user is a smart approach, and modifying the installer filename is a clever workaround. Let’s break down how to make this work across Windows (exe) and macOS (dmg), plus cover your questions about events and post-extraction actions.
1. Triggering Actions During Install/Extraction
The ability to hook into events depends on the installer type you’re using (Electron Builder is the most common tool here):
Windows (NSIS/WiX Installers)
- NSIS (the default for Electron Builder) lets you run custom scripts before, during, or after installation. You can access the installer’s filename directly in the script to extract the token, then run any follow-up logic.
- WiX installers support similar custom actions, though NSIS is more straightforward for quick scripted tasks.
macOS (DMGs)
- macOS doesn’t natively trigger events when a DMG is mounted or the app is copied to Applications. Instead, you’ll want to handle this on the app’s first launch: check if the token exists in your config, and if not, attempt to extract it from the DMG’s name (if the app is still running from the mounted DMG) or fall back to a backup method.
2. Extracting the Token from the Filename
Windows NSIS Script Example
If your installer filename follows a format like MyApp-{token}.exe, add this to your Electron Builder nsis configuration (in package.json or electron-builder.yml):
"nsis": { "include": "installer-script.nsh" }
Then create installer-script.nsh with this logic to extract and save the token:
; Get the full path of the installer exe StrCpy $installerPath $EXEPATH ; Extract the filename (e.g., "MyApp-xyz123.exe") StrCpy $installerName $installerPath -1 -1 ; Split the filename to isolate the token StrSplit $installerName "-" $prefix $tokenWithExt StrSplit $tokenWithExt "." $token $ext ; Save the token to the app's config file in the installation directory WriteIniStr "$INSTDIR\config.json" "user" "token" $token
macOS First-Launch Logic
In your main Electron process, add code to check for the token on first run:
const { app, dialog } = require('electron'); const fs = require('fs'); const path = require('path'); app.on('ready', async () => { const configPath = path.join(app.getPath('userData'), 'app-config.json'); let appConfig = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : {}; // Skip if token already exists if (appConfig.userToken) { console.log('Using existing token:', appConfig.userToken); return; } // Try to extract token from DMG mount directory (if app is running from DMG) const appExecPath = app.getPath('exe'); const appDir = path.dirname(appExecPath); let userToken = null; if (appDir.startsWith('/Volumes/')) { const dmgMountName = path.basename(appDir); // Assume DMG name format: "MyApp-xyz123.dmg" const nameParts = dmgMountName.split('-'); if (nameParts.length > 1) { userToken = nameParts[1].split('.')[0]; } } if (userToken) { // Save token to config appConfig.userToken = userToken; fs.writeFileSync(configPath, JSON.stringify(appConfig, null, 2)); console.log('Token extracted and saved:', userToken); // Run post-extraction actions here (see section 3) await postTokenActions(userToken); } else { // Fallback: prompt user to enter token if extraction fails const inputResult = await dialog.showMessageBox({ type: 'info', message: 'Enter your unique token:', buttons: ['Save', 'Cancel'], defaultId: 0, inputPlaceholder: 'Your token here' }); if (inputResult.response === 0 && inputResult.inputValue) { appConfig.userToken = inputResult.inputValue; fs.writeFileSync(configPath, JSON.stringify(appConfig, null, 2)); await postTokenActions(inputResult.inputValue); } } }); // Example post-extraction actions async function postTokenActions(token) { // 1. Update a JSON config file const appSettingsPath = path.join(app.getAppPath(), 'settings.json'); let settings = fs.existsSync(appSettingsPath) ? JSON.parse(fs.readFileSync(appSettingsPath)) : {}; settings.authenticated = true; settings.token = token; fs.writeFileSync(appSettingsPath, JSON.stringify(settings, null, 2)); // 2. Send token to your server for validation/registration try { await fetch('https://your-server.com/api/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, deviceId: app.getVersion() }) }); } catch (err) { console.error('Failed to register token:', err); } // 3. Download user-specific assets (e.g., custom themes) // const assetResponse = await fetch(`https://your-server.com/assets/${token}`); // const assetData = await assetResponse.buffer(); // fs.writeFileSync(path.join(app.getPath('documents'), 'user-asset.zip'), assetData); }
3. Supported Post-Extraction Actions
Yes, you can absolutely do all the actions you mentioned:
- Update JSON files: Use Node.js’s
fsmodule to write to config files in the app’suserDatadirectory (safe for user-specific data) or the app’s installation directory (note: on macOS, the installation directory is read-only for non-admin users, so useuserDatainstead). - Push data to your server: Use
fetchor libraries likeaxiosto send the token and any other user/device info to your backend for validation or registration. - Download user-specific files: Fetch assets from your server using the token as an identifier, then save them to a user-accessible directory (like
documentsoruserData).
Key Considerations
- Filename tampering: Users might rename the installer, so always validate the token with your server after extracting it to prevent malicious use.
- Portable Windows apps: If you distribute portable exes (not installers), you can read the exe’s filename directly via
path.basename(process.execPath)in your Electron main process. - Fallback mechanism: Always include a backup way to get the token (like a user input prompt) in case filename extraction fails.
内容的提问来源于stack exchange,提问作者Sarah Sh

