Files
brenden 682afacd30 Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 +
tools/ci) with a "Connect to a Different Server" menu item and popout
that lets the desktop app switch to any self-hosted Fluxer instance,
plus fixes for well-known discovery on single-domain self-hosted
deployments and a false-positive ERR_ABORTED on same-origin client
redirects during the switch. Defaults to chat.fluxr.chat and uses an
isolated userData directory from the official build.
2026-07-01 18:22:43 -04:00

111 lines
3.8 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-or-later
const {existsSync} = require('node:fs');
const {join, sep} = require('node:path');
const {createNativeLoadError, loadNativeBinding} = require('./loader-diagnostics.cjs');
const MODULE_NAME = '@fluxer/linux-notifications';
const SKIP_NATIVE_PROBE_ENV = 'FLUXER_LINUX_NOTIFICATIONS_SKIP_NATIVE_PROBE';
const REQUIRED_FREEDESKTOP_METHODS = ['notify', 'closeNotification', 'getServerCapabilities', 'close'];
function resolveNativeRoot() {
const asarSegment = `${sep}app.asar${sep}`;
if (!__dirname.includes(asarSegment)) return __dirname;
const unpackedDir = __dirname.replace(asarSegment, `${sep}app.asar.unpacked${sep}`);
return existsSync(unpackedDir) ? unpackedDir : __dirname;
}
function nativeFileName() {
if (process.platform !== 'linux') {
throw new Error(`@fluxer/linux-notifications is only supported on Linux, got ${process.platform}`);
}
switch (process.arch) {
case 'x64':
return 'linux-notifications.linux-x64-gnu.node';
case 'arm64':
return 'linux-notifications.linux-arm64-gnu.node';
default:
throw new Error(`Unsupported Linux architecture: ${process.arch}`);
}
}
function ownPropertyNames(value) {
if ((typeof value !== 'object' && typeof value !== 'function') || value == null) return [];
return Object.getOwnPropertyNames(value).sort();
}
function bindingSurface(bindingValue) {
const freedesktopNotifications = bindingValue ? bindingValue.FreedesktopNotifications : null;
const prototype = freedesktopNotifications ? freedesktopNotifications.prototype : null;
return {
bindingKeys: ownPropertyNames(bindingValue),
freedesktopNotificationsType: typeof freedesktopNotifications,
freedesktopNotificationsName: freedesktopNotifications ? freedesktopNotifications.name : null,
freedesktopNotificationsPrototypeKeys: ownPropertyNames(prototype),
getServerInformationType: typeof (bindingValue ? bindingValue.getServerInformation : null),
};
}
function validateBindingSurface(bindingValue, nativeRoot, nativePath) {
const surface = bindingSurface(bindingValue);
const missing = [];
if (typeof bindingValue?.FreedesktopNotifications !== 'function') {
missing.push('FreedesktopNotifications constructor');
} else {
for (const method of REQUIRED_FREEDESKTOP_METHODS) {
if (typeof bindingValue.FreedesktopNotifications.prototype?.[method] !== 'function') {
missing.push(`FreedesktopNotifications.prototype.${method}`);
}
}
}
if (typeof bindingValue?.getServerInformation !== 'function') {
missing.push('getServerInformation');
}
if (missing.length === 0) return;
throw createNativeLoadError({
moduleName: MODULE_NAME,
nativePath,
nativeRoot,
packageDir: __dirname,
reason: `native binding surface mismatch: missing ${missing.join(', ')}`,
skipNativeProbeEnv: SKIP_NATIVE_PROBE_ENV,
extraDiagnostics: [{name: 'bindingSurface', text: JSON.stringify(surface, null, 2)}],
});
}
let binding = null;
let loadError = null;
if (process.platform === 'linux') {
try {
const nativeRoot = resolveNativeRoot();
const nativePath = join(nativeRoot, nativeFileName());
const loaded = loadNativeBinding({
moduleName: MODULE_NAME,
nativePath,
nativeRoot,
packageDir: __dirname,
skipNativeProbeEnv: SKIP_NATIVE_PROBE_ENV,
});
binding = loaded.binding;
loadError = loaded.loadError;
if (loadError) throw loadError;
validateBindingSurface(binding, nativeRoot, nativePath);
} catch (error) {
loadError = createNativeLoadError({
moduleName: MODULE_NAME,
nativeRoot: resolveNativeRoot(),
packageDir: __dirname,
reason: 'native loader threw before binding load completed',
cause: error,
skipNativeProbeEnv: SKIP_NATIVE_PROBE_ENV,
});
throw loadError;
}
}
module.exports = {
FreedesktopNotifications: binding ? binding.FreedesktopNotifications : null,
getServerInformation: binding ? binding.getServerInformation : null,
loadError,
};