Harden packaged identity and local listener binding
This commit is contained in:
@@ -21,8 +21,7 @@ class RemoteServer {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this._config = opts;
|
this._config = opts;
|
||||||
|
|
||||||
const wssOpts = { port: opts.port, maxPayload: 256 * 1024 };
|
const wssOpts = { port: opts.port, host: opts.host || '127.0.0.1', maxPayload: 256 * 1024 };
|
||||||
if (opts.host) wssOpts.host = opts.host;
|
|
||||||
this._wss = new WebSocketServer(wssOpts, () => {
|
this._wss = new WebSocketServer(wssOpts, () => {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
process.env.UV_THREADPOOL_SIZE = process.env.UV_THREADPOOL_SIZE || '8';
|
process.env.UV_THREADPOOL_SIZE = process.env.UV_THREADPOOL_SIZE || '8';
|
||||||
const { monitorEventLoopDelay, PerformanceObserver } = require('perf_hooks');
|
const { monitorEventLoopDelay, PerformanceObserver } = require('perf_hooks');
|
||||||
const { app, BrowserWindow, ipcMain, dialog, clipboard, nativeTheme, Tray, Menu, nativeImage } = require('electron');
|
const { app, BrowserWindow, ipcMain, dialog, clipboard, nativeTheme, Tray, Menu, nativeImage } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
app.setPath('userData', path.join(app.getPath('appData'), 'multi-hoster-uploader'));
|
||||||
|
app.setName('Multi Hoster Uploader');
|
||||||
|
app.setAppUserModelId('com.multihoster.uploader');
|
||||||
const { configureStartupRenderer, createStartupWindow, resolveStartupLanguage } = require('./lib/startup-renderer');
|
const { configureStartupRenderer, createStartupWindow, resolveStartupLanguage } = require('./lib/startup-renderer');
|
||||||
configureStartupRenderer(app);
|
configureStartupRenderer(app);
|
||||||
nativeTheme.themeSource = 'dark';
|
nativeTheme.themeSource = 'dark';
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const ConfigStore = require('./lib/config-store');
|
const ConfigStore = require('./lib/config-store');
|
||||||
const UploadManager = require('./lib/upload-manager');
|
const UploadManager = require('./lib/upload-manager');
|
||||||
@@ -2187,7 +2190,7 @@ ipcMain.handle('start-upload', async (_event, payload) => {
|
|||||||
updateTrayTooltip(`Upload: ${data.activeJobs} aktiv - ${speedMb} MB/s`);
|
updateTrayTooltip(`Upload: ${data.activeJobs} aktiv - ${speedMb} MB/s`);
|
||||||
_maybeLogEventLoopDelay(data.activeJobs);
|
_maybeLogEventLoopDelay(data.activeJobs);
|
||||||
} else {
|
} else {
|
||||||
updateTrayTooltip('Multi-Hoster-Upload');
|
updateTrayTooltip('Multi Hoster Uploader');
|
||||||
}
|
}
|
||||||
} catch (e) { debugLog(`stats listener error: ${e && e.message}`); }
|
} catch (e) { debugLog(`stats listener error: ${e && e.message}`); }
|
||||||
});
|
});
|
||||||
@@ -3373,6 +3376,7 @@ async function startRemoteServer() {
|
|||||||
try {
|
try {
|
||||||
await remoteServer.start({
|
await remoteServer.start({
|
||||||
port: remote.port || 9100,
|
port: remote.port || 9100,
|
||||||
|
host: '127.0.0.1',
|
||||||
token,
|
token,
|
||||||
allowInput: remote.allowInput !== false,
|
allowInput: remote.allowInput !== false,
|
||||||
mainWindow,
|
mainWindow,
|
||||||
|
|||||||
+6
-1
@@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.multihoster.uploader",
|
"appId": "com.multihoster.uploader",
|
||||||
"productName": "Multi-Hoster-Upload",
|
"productName": "Multi Hoster Uploader",
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "assets",
|
"buildResources": "assets",
|
||||||
"output": "release"
|
"output": "release"
|
||||||
@@ -47,14 +47,19 @@
|
|||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
"icon": "assets/app_icon.ico",
|
"icon": "assets/app_icon.ico",
|
||||||
|
"executableName": "Multi Hoster Uploader",
|
||||||
"signAndEditExecutable": false
|
"signAndEditExecutable": false
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
|
"artifactName": "Multi-Hoster-Upload Setup ${version}.${ext}",
|
||||||
"oneClick": false,
|
"oneClick": false,
|
||||||
"perMachine": false,
|
"perMachine": false,
|
||||||
"allowToChangeInstallationDirectory": true,
|
"allowToChangeInstallationDirectory": true,
|
||||||
"createDesktopShortcut": true
|
"createDesktopShortcut": true
|
||||||
},
|
},
|
||||||
|
"portable": {
|
||||||
|
"artifactName": "Multi-Hoster-Upload ${version}.${ext}"
|
||||||
|
},
|
||||||
"afterPack": "scripts/afterPack.cjs"
|
"afterPack": "scripts/afterPack.cjs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-30
@@ -1,44 +1,32 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
module.exports = async function afterPack(context) {
|
module.exports = async function afterPack(context) {
|
||||||
let rcedit;
|
const rcedit = require("rcedit");
|
||||||
try {
|
|
||||||
rcedit = require("rcedit");
|
|
||||||
} catch {
|
|
||||||
console.warn(" rcedit: skipped - rcedit not installed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const productFilename = context.packager?.appInfo?.productFilename;
|
const productFilename = context.packager?.appInfo?.productFilename;
|
||||||
const version = context.packager?.appInfo?.version;
|
const version = context.packager?.appInfo?.version;
|
||||||
if (!productFilename || !version) {
|
if (!productFilename || !version) {
|
||||||
console.warn(" rcedit: skipped - application metadata not available");
|
throw new Error("Application metadata is unavailable");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const exePath = path.join(context.appOutDir, `${productFilename}.exe`);
|
const exePath = path.join(context.appOutDir, `${productFilename}.exe`);
|
||||||
const iconPath = path.resolve(__dirname, "..", "assets", "app_icon.ico");
|
const iconPath = path.resolve(__dirname, "..", "assets", "app_icon.ico");
|
||||||
|
|
||||||
try {
|
if (!fs.existsSync(iconPath)) {
|
||||||
const fs = require("fs");
|
throw new Error("Application icon is unavailable");
|
||||||
if (!fs.existsSync(iconPath)) {
|
|
||||||
console.warn(" rcedit: skipped - app_icon.ico not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(` rcedit: branding executable -> ${exePath}`);
|
|
||||||
await rcedit(exePath, {
|
|
||||||
icon: iconPath,
|
|
||||||
"file-version": version,
|
|
||||||
"product-version": version,
|
|
||||||
"version-string": {
|
|
||||||
CompanyName: "Sucukdeluxe",
|
|
||||||
FileDescription: "Multi Hoster Uploader",
|
|
||||||
InternalName: productFilename,
|
|
||||||
OriginalFilename: `${productFilename}.exe`,
|
|
||||||
ProductName: "Multi Hoster Uploader"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(` rcedit: failed - ${String(error)}`);
|
|
||||||
}
|
}
|
||||||
|
console.log(` rcedit: branding executable -> ${exePath}`);
|
||||||
|
await rcedit(exePath, {
|
||||||
|
icon: iconPath,
|
||||||
|
"file-version": version,
|
||||||
|
"product-version": version,
|
||||||
|
"version-string": {
|
||||||
|
CompanyName: "Sucukdeluxe",
|
||||||
|
FileDescription: "Multi Hoster Uploader",
|
||||||
|
InternalName: productFilename,
|
||||||
|
OriginalFilename: `${productFilename}.exe`,
|
||||||
|
ProductName: "Multi Hoster Uploader"
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const test = require('node:test');
|
const test = require('node:test');
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
const Module = require('node:module');
|
const Module = require('node:module');
|
||||||
|
const fs = require('node:fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const packageJson = require('../package.json');
|
const packageJson = require('../package.json');
|
||||||
|
|
||||||
@@ -10,6 +11,19 @@ test('packages every Electron preload referenced by the main process', () => {
|
|||||||
assert.equal(packageJson.build.win.signAndEditExecutable, false);
|
assert.equal(packageJson.build.win.signAndEditExecutable, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('packaged identity is exact while public artifact routing remains stable', () => {
|
||||||
|
assert.equal(packageJson.build.productName, 'Multi Hoster Uploader');
|
||||||
|
assert.equal(packageJson.build.win.executableName, 'Multi Hoster Uploader');
|
||||||
|
assert.equal(packageJson.build.nsis.artifactName, 'Multi-Hoster-Upload Setup ${version}.${ext}');
|
||||||
|
assert.equal(packageJson.build.portable.artifactName, 'Multi-Hoster-Upload ${version}.${ext}');
|
||||||
|
|
||||||
|
const source = fs.readFileSync(path.join(__dirname, '..', 'main.js'), 'utf8');
|
||||||
|
assert.match(source, /app\.setPath\('userData', path\.join\(app\.getPath\('appData'\), 'multi-hoster-uploader'\)\)/);
|
||||||
|
assert.match(source, /app\.setName\('Multi Hoster Uploader'\)/);
|
||||||
|
assert.match(source, /app\.setAppUserModelId\('com\.multihoster\.uploader'\)/);
|
||||||
|
assert.doesNotMatch(source, /updateTrayTooltip\('Multi-Hoster-Upload'\)/);
|
||||||
|
});
|
||||||
|
|
||||||
test('floating drop target resolves native paths through Electron webUtils', () => {
|
test('floating drop target resolves native paths through Electron webUtils', () => {
|
||||||
let exposedApi = null;
|
let exposedApi = null;
|
||||||
const nativeFile = { name: 'fixture.mkv' };
|
const nativeFile = { name: 'fixture.mkv' };
|
||||||
@@ -57,25 +71,45 @@ test('afterPack brands the executable metadata shown by Windows', async () => {
|
|||||||
const afterPack = require(afterPackPath);
|
const afterPack = require(afterPackPath);
|
||||||
await afterPack({
|
await afterPack({
|
||||||
appOutDir: 'C:\\release',
|
appOutDir: 'C:\\release',
|
||||||
packager: { appInfo: { productFilename: 'Multi-Hoster-Upload', version: '9.8.7' } }
|
packager: { appInfo: { productFilename: 'Multi Hoster Uploader', version: '9.8.7' } }
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
Module._load = originalLoad;
|
Module._load = originalLoad;
|
||||||
delete require.cache[afterPackPath];
|
delete require.cache[afterPackPath];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.equal(editCall.exePath, path.join('C:\\release', 'Multi-Hoster-Upload.exe'));
|
assert.equal(editCall.exePath, path.join('C:\\release', 'Multi Hoster Uploader.exe'));
|
||||||
assert.equal(editCall.options['file-version'], '9.8.7');
|
assert.equal(editCall.options['file-version'], '9.8.7');
|
||||||
assert.equal(editCall.options['product-version'], '9.8.7');
|
assert.equal(editCall.options['product-version'], '9.8.7');
|
||||||
assert.deepEqual(editCall.options['version-string'], {
|
assert.deepEqual(editCall.options['version-string'], {
|
||||||
CompanyName: 'Sucukdeluxe',
|
CompanyName: 'Sucukdeluxe',
|
||||||
FileDescription: 'Multi Hoster Uploader',
|
FileDescription: 'Multi Hoster Uploader',
|
||||||
InternalName: 'Multi-Hoster-Upload',
|
InternalName: 'Multi Hoster Uploader',
|
||||||
OriginalFilename: 'Multi-Hoster-Upload.exe',
|
OriginalFilename: 'Multi Hoster Uploader.exe',
|
||||||
ProductName: 'Multi Hoster Uploader'
|
ProductName: 'Multi Hoster Uploader'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('afterPack fails the build when executable branding fails', async () => {
|
||||||
|
const originalLoad = Module._load;
|
||||||
|
const afterPackPath = require.resolve('../scripts/afterPack.cjs');
|
||||||
|
delete require.cache[afterPackPath];
|
||||||
|
Module._load = function (request, parent, isMain) {
|
||||||
|
if (request === 'rcedit') return async () => { throw new Error('branding failed'); };
|
||||||
|
return originalLoad.call(this, request, parent, isMain);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const afterPack = require(afterPackPath);
|
||||||
|
await assert.rejects(afterPack({
|
||||||
|
appOutDir: 'C:\\release',
|
||||||
|
packager: { appInfo: { productFilename: 'Multi Hoster Uploader', version: '9.8.7' } }
|
||||||
|
}), /branding failed/);
|
||||||
|
} finally {
|
||||||
|
Module._load = originalLoad;
|
||||||
|
delete require.cache[afterPackPath];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('close readiness is signaled only after the renderer explicitly finishes initialization', () => {
|
test('close readiness is signaled only after the renderer explicitly finishes initialization', () => {
|
||||||
const listeners = new Map();
|
const listeners = new Map();
|
||||||
const sent = [];
|
const sent = [];
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const { describe, it, beforeEach, afterEach } = require('node:test');
|
const { describe, it, beforeEach, afterEach } = require('node:test');
|
||||||
const assert = require('node:assert');
|
const assert = require('node:assert');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
// Test the module can be required and has the expected API
|
// Test the module can be required and has the expected API
|
||||||
describe('RemoteServer', () => {
|
describe('RemoteServer', () => {
|
||||||
@@ -39,4 +41,30 @@ describe('RemoteServer', () => {
|
|||||||
assert.strictEqual(server.getClientCount(), 0);
|
assert.strictEqual(server.getClientCount(), 0);
|
||||||
server.stop();
|
server.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('binds to loopback when no host is supplied', async () => {
|
||||||
|
const RemoteServer = require('../lib/remote-server');
|
||||||
|
const server = new RemoteServer();
|
||||||
|
try {
|
||||||
|
await server.start({
|
||||||
|
port: 0,
|
||||||
|
token: 'test-token-123',
|
||||||
|
onSignalingToCapture: () => {},
|
||||||
|
onCreateCaptureWindow: () => {},
|
||||||
|
onDestroyCaptureWindow: () => {}
|
||||||
|
});
|
||||||
|
assert.strictEqual(server._wss.address().address, '127.0.0.1');
|
||||||
|
} finally {
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the application remote-control listener on loopback', () => {
|
||||||
|
const source = fs.readFileSync(path.join(__dirname, '..', 'main.js'), 'utf8');
|
||||||
|
const start = source.indexOf('async function startRemoteServer()');
|
||||||
|
const end = source.indexOf("ipcMain.on('remote:signaling-from-capture'", start);
|
||||||
|
assert.notStrictEqual(start, -1);
|
||||||
|
assert.notStrictEqual(end, -1);
|
||||||
|
assert.match(source.slice(start, end), /host:\s*'127\.0\.0\.1'/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user