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

273 lines
8.3 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {EventEmitter} from 'node:events';
import {createRequire} from 'node:module';
import {test} from 'node:test';
const requireSrc = createRequire(import.meta.url);
const modulePath = requireSrc.resolve('./index.js');
function freshModule() {
delete requireSrc.cache[modulePath];
return requireSrc('./index.js');
}
test('module loads without throwing on non-Windows hosts', () => {
const mod = freshModule();
assert.equal(typeof mod.isSupported, 'function');
assert.equal(typeof mod.pidFromHwnd, 'function');
assert.equal(typeof mod.resolveAudioRootPid, 'function');
assert.equal(typeof mod.ProcessLoopback, 'function');
});
test('non-Windows hosts surface a load error', {skip: process.platform === 'win32'}, () => {
const mod = freshModule();
assert.ok(mod.loadError instanceof Error, 'expected loadError to be set on non-Windows');
assert.match(mod.loadError.message, /not supported on platform/i);
});
test('isSupported / pidFromHwnd / resolveAudioRootPid fail soft when binding is absent', () => {
const mod = freshModule();
mod.__setBindingForTests(null, new Error('test: binary missing'));
assert.equal(mod.isSupported(), false);
assert.equal(mod.pidFromHwnd(123n), 0);
assert.equal(mod.resolveAudioRootPid(1234), 1234);
assert.deepEqual(
{
processSupported: mod.getBackendInfo().processSupported,
systemSupported: mod.getBackendInfo().systemSupported,
sessionMixerSupported: mod.getBackendInfo().sessionMixerSupported,
systemLoopbackMode: mod.getBackendInfo().systemLoopbackMode,
},
{
processSupported: false,
systemSupported: false,
sessionMixerSupported: false,
systemLoopbackMode: 'unavailable',
},
);
});
test('constructing ProcessLoopback without a binding throws the recorded loadError', () => {
const mod = freshModule();
const recorded = new Error('synthetic: binary unavailable');
mod.__setBindingForTests(null, recorded);
assert.throws(
() => new mod.ProcessLoopback(1234, {}),
(error) => error === recorded,
);
});
test('isSupported swallows binding-side exceptions and returns false', () => {
const mod = freshModule();
mod.__setBindingForTests({
isSupported: () => {
throw new Error('binding crashed during isSupported');
},
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: function FakeProcessLoopback() {},
});
assert.equal(mod.isSupported(), false);
});
test('ProcessLoopback wires native frame/error/closed events to the EventEmitter', async () => {
const mod = freshModule();
let frameCb;
let errorCb;
let closedCb;
let startedCb;
class FakeNative {
constructor(pid, opts, onFrame, onError, onClosed, onStarted) {
this.pid = pid;
this.opts = opts;
frameCb = onFrame;
errorCb = onError;
closedCb = onClosed;
startedCb = onStarted;
}
start() {
this.started = true;
}
dispose() {
this.disposed = true;
}
}
mod.__setBindingForTests({
isSupported: () => true,
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: FakeNative,
});
const loopback = new mod.ProcessLoopback(4242, {includeProcessTree: false});
assert.ok(loopback instanceof EventEmitter);
const frames = [];
const errors = [];
let closedCount = 0;
loopback.on('frame', (frame) => frames.push(frame));
loopback.on('error', (error) => errors.push(error));
loopback.on('closed', () => closedCount++);
const startPromise = loopback.start();
startedCb();
await startPromise;
frameCb({samples: new Float32Array(2), sampleRate: 48000, channels: 2, timestampUs: 0});
errorCb(new Error('boom'));
closedCb();
closedCb();
assert.equal(frames.length, 1);
assert.equal(errors.length, 1);
assert.equal(closedCount, 1);
});
test('ProcessLoopback.start waits for the native started callback', async () => {
const mod = freshModule();
let startedCb;
mod.__setBindingForTests({
isSupported: () => true,
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: function FakeNative(_pid, _opts, _onFrame, _onError, _onClosed, onStarted) {
startedCb = onStarted;
return {start() {}, dispose() {}};
},
});
const loopback = new mod.ProcessLoopback(4242);
let resolved = false;
const startPromise = loopback.start().then(() => {
resolved = true;
});
await Promise.resolve();
assert.equal(resolved, false);
startedCb();
await startPromise;
assert.equal(resolved, true);
});
test('ProcessLoopback.start rejects if native capture errors before start', async () => {
const mod = freshModule();
let errorCb;
mod.__setBindingForTests({
isSupported: () => true,
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: function FakeNative(_pid, _opts, _onFrame, onError) {
errorCb = onError;
return {start() {}, dispose() {}};
},
});
const loopback = new mod.ProcessLoopback(4242);
loopback.on('error', () => {});
const startPromise = loopback.start();
const error = new Error('activation failed');
errorCb(error);
await assert.rejects(startPromise, (actual) => actual === error);
});
test('opts default includeProcessTree to true and forward sample/channel options', () => {
const mod = freshModule();
let observedOpts;
mod.__setBindingForTests({
isSupported: () => true,
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: function FakeNative(_pid, opts) {
observedOpts = opts;
return {start() {}, dispose() {}};
},
});
const ctor = mod.ProcessLoopback;
void new ctor(1, {sampleRate: 44100, channels: 1});
assert.deepEqual(observedOpts, {includeProcessTree: true, sampleRate: 44100, channels: 1, captureScope: 'process'});
void new ctor(1, {includeProcessTree: false});
assert.equal(observedOpts.includeProcessTree, false);
});
test('system capture scope forwards EXCLUDE target process tree mode', () => {
const mod = freshModule();
let observedOpts;
mod.__setBindingForTests({
isSupported: () => true,
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: function FakeNative(_pid, opts) {
observedOpts = opts;
return {start() {}, dispose() {}};
},
});
void new mod.ProcessLoopback(1, {captureScope: 'system', includeProcessTree: true});
assert.deepEqual(observedOpts, {
includeProcessTree: false,
sampleRate: undefined,
channels: undefined,
captureScope: 'system',
});
});
test('session mixer capture scope is forwarded and never captures the target process tree directly', () => {
const mod = freshModule();
let observedOpts;
mod.__setBindingForTests({
isSupported: () => true,
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: function FakeNative(_pid, opts) {
observedOpts = opts;
return {start() {}, dispose() {}};
},
});
void new mod.ProcessLoopback(1, {captureScope: 'session-mixer', includeProcessTree: true});
assert.deepEqual(observedOpts, {
includeProcessTree: false,
sampleRate: undefined,
channels: undefined,
captureScope: 'session-mixer',
});
});
function loopbackWithNativeSinkResult(setScreenAudioSink) {
const mod = freshModule();
mod.__setBindingForTests({
isSupported: () => true,
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: function FakeNative() {
return {start() {}, dispose() {}, setScreenAudioSink};
},
});
return new mod.ProcessLoopback(4242);
}
test('setScreenAudioSink reports true when the native attach succeeds', () => {
const loopback = loopbackWithNativeSinkResult(() => undefined);
assert.equal(loopback.setScreenAudioSink({}), true);
});
test('setScreenAudioSink reports false when the native attach returns false', () => {
const loopback = loopbackWithNativeSinkResult(() => false);
assert.equal(loopback.setScreenAudioSink({}), false);
});
test('setScreenAudioSink reports false when the native attach throws', () => {
const loopback = loopbackWithNativeSinkResult(() => {
throw new Error('attach failed');
});
assert.equal(loopback.setScreenAudioSink({}), false);
});
test('setScreenAudioSink reports false when the native method is missing', () => {
const mod = freshModule();
mod.__setBindingForTests({
isSupported: () => true,
pidFromHwnd: () => 0,
resolveAudioRootPid: (pid) => pid,
ProcessLoopback: function FakeNative() {
return {start() {}, dispose() {}};
},
});
const loopback = new mod.ProcessLoopback(4242);
assert.equal(loopback.setScreenAudioSink({}), false);
});