Files
Haven/test/nativeScreenClient.test.js
T
Bernardo Costa 0c31d06598 Negotiated native screen sharing transport, off by default (#5555)
Server-side signaling and codec negotiation for a native Desktop screen share path. Off unless the user opts in from Settings, and the toggle only appears inside Haven Desktop builds that carry the native helper. Browser and per-app audio capture are unchanged. Thanks to @bernardokcosta.
2026-09-05 16:34:57 -04:00

1366 lines
43 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
const VOICE_SOURCE = fs.readFileSync(
path.join(__dirname, '..', 'public/js/voice.js'),
'utf8'
);
function loadVoiceManager(globals = {}) {
const context = vm.createContext({
module: { exports: {} },
navigator: { userAgent: '', platform: '', maxTouchPoints: 0 },
localStorage: { getItem: () => null, setItem() {}, removeItem() {} },
console: { log() {}, warn() {}, error() {} },
setTimeout,
clearTimeout,
Date,
...globals,
});
vm.runInContext(`${VOICE_SOURCE}\nmodule.exports = VoiceManager;`, context, {
filename: 'voice.js',
});
return context.module.exports;
}
function completeNativeApi(overrides = {}) {
return {
getCapabilities: async () => ({ supported: true }),
start: async () => ({
started: true,
sessionId: 'native-session-1234',
codec: 'H264',
hasAudio: false,
}),
stop: async () => {},
addPeer: async () => {},
removePeer: async () => {},
setRemoteDescription: async () => {},
addIceCandidate: async () => {},
onSignal() {},
...overrides,
};
}
test('native screen start is transactional when the helper returns invalid state', async () => {
let stopCalls = 0;
const api = completeNativeApi({
start: async () => ({ started: true, sessionId: '../invalid' }),
stop: async () => { stopCalls++; },
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map(),
currentChannel: 'a1b2c3d4',
inVoice: true,
socket: { emit() {} },
});
assert.equal(await voice._tryStartNativeScreenShare(), null);
assert.equal(stopCalls, 1);
assert.notEqual(voice._nativeScreenSharing, true);
});
test('native picker cancellation does not fall through to another picker', async () => {
const api = completeNativeApi({
start: async () => ({ started: false, cancelled: true }),
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map(),
currentChannel: 'a1b2c3d4',
inVoice: true,
socket: { emit() {} },
});
assert.equal(await voice._tryStartNativeScreenShare(), false);
});
test('native transport falls back when a viewer does not support the protocol', async () => {
let stopCalls = 0;
const api = completeNativeApi({ stop: async () => { stopCalls++; } });
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map([[7, {}]]),
socket: {
connected: true,
emit(event, payload, callback) {
if (event === 'screen-share-started') callback?.({ ok: false, error: 'incompatible_viewer' });
},
},
});
assert.equal(await voice._tryStartNativeScreenShare(8, 'a1b2c3d4', 3), null);
assert.equal(stopCalls, 1);
assert.notEqual(voice._nativeScreenSharing, true);
});
test('voice bots do not participate in native screen codec negotiation', () => {
const VoiceManager = loadVoiceManager({ window: {} });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
peers: new Map([[7, {}], [8, {}]]),
_nativeScreenPeerCapabilities: new Map(),
_nativeScreenCodecs: () => ['H264', 'AV1'],
});
voice._rememberNativeScreenPeer({
id: 7,
isBot: true,
nativeScreenVersion: 0,
});
voice._rememberNativeScreenPeer({
id: 8,
nativeScreenVersion: 2,
nativeScreenCodecs: ['H264'],
});
assert.deepEqual(Array.from(voice._nativeScreenCodecIntersection()), ['H264']);
});
test('screen sharing cannot start while signaling is disconnected', async () => {
let startCalls = 0;
const api = completeNativeApi({ start: async () => { startCalls++; } });
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
isScreenSharing: false,
_screenStartInFlight: false,
socket: { connected: false },
});
assert.equal(await voice.shareScreen(), false);
assert.equal(startCalls, 0);
});
test('rebuilding a voice peer does not tear down its independent native screen peer', () => {
let closed = 0;
let nativeCloseCalls = 0;
let nativeRemoveCalls = 0;
const api = completeNativeApi({ removePeer: async () => { nativeRemoveCalls++; } });
const VoiceManager = loadVoiceManager({
window: { havenDesktop: { nativeScreen: api } },
document: { getElementById: () => null },
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
peers: new Map([[7, { connection: { close: () => { closed++; } } }]]),
gainNodes: new Map(),
screenGainNodes: new Map(),
_screenDelivered: new Set(),
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
_closeNativeScreenPeer: () => { nativeCloseCalls++; },
_stopAnalyser() {},
});
voice._removePeer(7);
assert.equal(closed, 1);
assert.equal(nativeCloseCalls, 0);
assert.equal(nativeRemoveCalls, 0);
});
test('native recovery ignores video receivers on the voice connection', () => {
const VoiceManager = loadVoiceManager({ window: {} });
const track = { kind: 'video', readyState: 'live', muted: false, id: 'camera-track' };
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
_nativeScreenAnnouncements: new Map([[7, 'native-session-1234']]),
_nativeScreenPeers: new Map(),
peers: new Map([[7, {
connection: { getReceivers: () => [{ track }] },
}]]),
screenSharers: new Set([7]),
});
assert.equal(voice._deliverScreenFromReceivers(7), false);
assert.equal(voice._screenStillLive(7), false);
});
test('native screen announcements keep voice video and audio on their own paths', async () => {
let connection;
let webcamStreams = 0;
let screenStreams = 0;
let voiceAudio = 0;
let screenAudio = 0;
class FakeMediaStream {
constructor(tracks = []) {
this.tracks = [...tracks];
this.id = `stream-${Math.random()}`;
}
addTrack(track) { this.tracks.push(track); }
getVideoTracks() { return this.tracks.filter(track => track.kind === 'video'); }
}
class FakePeerConnection {
constructor() {
connection = this;
this.signalingState = 'stable';
}
addEventListener() {}
}
const VoiceManager = loadVoiceManager({
window: {},
MediaStream: FakeMediaStream,
RTCPeerConnection: FakePeerConnection,
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
peers: new Map(),
rtcConfig: {},
localStream: null,
audioBitrate: 0,
screenStream: null,
isScreenSharing: false,
webcamStream: null,
isWebcamActive: false,
_nativeScreenAnnouncements: new Map([[7, 'native-session-1234']]),
screenSharers: new Set([7]),
webcamUsers: new Set(),
_screenDelivered: new Set(),
socket: { emit() {} },
onWebcamStream: () => { webcamStreams++; },
onScreenStream: () => { screenStreams++; },
_playAudio: () => { voiceAudio++; },
_playScreenAudio: () => { screenAudio++; },
});
await voice._createPeer(7, 'Viewer', false);
const videoTrack = {
kind: 'video', id: 'camera-track', readyState: 'live', getSettings: () => ({}),
};
connection.ontrack({
track: videoTrack,
streams: [new FakeMediaStream([videoTrack])],
});
const audioTrack = { kind: 'audio', id: 'voice-track', readyState: 'live' };
connection.ontrack({
track: audioTrack,
streams: [new FakeMediaStream([audioTrack])],
});
assert.equal(webcamStreams, 1);
assert.equal(screenStreams, 0);
assert.equal(voice._screenDelivered.has(7), false);
assert.equal(voiceAudio, 1);
assert.equal(screenAudio, 0);
});
test('stale browser track callbacks cannot overwrite a native share', async () => {
let connection;
let screenStreams = 0;
class FakeMediaStream {
constructor(tracks = []) { this.tracks = [...tracks]; }
addTrack(track) { this.tracks.push(track); }
getVideoTracks() { return this.tracks.filter(track => track.kind === 'video'); }
}
class FakePeerConnection {
constructor() {
connection = this;
this.signalingState = 'stable';
}
addEventListener() {}
}
const VoiceManager = loadVoiceManager({
window: {},
MediaStream: FakeMediaStream,
RTCPeerConnection: FakePeerConnection,
setTimeout: callback => { callback(); return 1; },
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
peers: new Map(),
rtcConfig: {},
localStream: null,
audioBitrate: 0,
screenStream: null,
isScreenSharing: false,
webcamStream: null,
isWebcamActive: false,
_nativeScreenAnnouncements: new Map(),
screenSharers: new Set([7]),
webcamUsers: new Set(),
_screenDelivered: new Set(),
socket: { emit() {} },
onScreenStream: () => { screenStreams++; },
});
await voice._createPeer(7, 'Viewer', false);
const track = {
kind: 'video', id: 'browser-screen', readyState: 'live', getSettings: () => ({}),
};
connection.ontrack({ track, streams: [new FakeMediaStream([track])] });
assert.equal(screenStreams, 1);
voice._nativeScreenAnnouncements.set(7, 'native-session-1234');
voice._screenDelivered.add(7);
track.onunmute();
track.onended();
assert.equal(screenStreams, 1);
assert.equal(voice._screenDelivered.has(7), true);
});
test('native receiver drains ICE that arrives while applying the offer', async () => {
let releaseRemoteDescription;
const addedCandidates = [];
class FakePeerConnection {
constructor() {
this.remoteDescription = null;
this.connectionState = 'new';
}
setRemoteDescription() {
return new Promise(resolve => {
releaseRemoteDescription = () => {
this.remoteDescription = { type: 'offer' };
resolve();
};
});
}
async createAnswer() { return { type: 'answer', sdp: 'v=0' }; }
async setLocalDescription() {}
async addIceCandidate(candidate) { addedCandidates.push(candidate); }
close() {}
}
const emitted = [];
const VoiceManager = loadVoiceManager({
window: {},
RTCPeerConnection: FakePeerConnection,
MediaStream: class {},
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
currentChannel: 'a1b2c3d4',
rtcConfig: {},
screenSharers: new Set([7]),
_screenDelivered: new Set(),
_nativeScreenPeers: new Map(),
_pendingNativeScreenCandidates: new Map(),
_nativeScreenAnnouncements: new Map([[7, 'native-session-1234']]),
socket: {
emit: (event, payload, callback) => {
emitted.push({ event, payload });
if (event === 'screen-share-started') callback?.({ ok: true, viewerIds: [7] });
},
},
});
const offerPromise = voice._handleNativeScreenOffer({
from: { id: 7 },
channelCode: 'a1b2c3d4',
sessionId: 'native-session-1234',
negotiationId: 'negotiation-1234',
offer: { type: 'offer', sdp: 'v=0' },
});
await Promise.resolve();
await voice._handleNativeScreenIceCandidate({
from: { id: 7 },
channelCode: 'a1b2c3d4',
sessionId: 'native-session-1234',
negotiationId: 'negotiation-1234',
candidate: { candidate: 'candidate:1' },
});
releaseRemoteDescription();
await offerPromise;
assert.deepEqual(addedCandidates, [{ candidate: 'candidate:1' }]);
assert.equal(emitted.at(-1).event, 'native-screen-answer');
});
test('fatal helper errors without a peer stop the native share', async () => {
let signalHandler;
let stopCalls = 0;
const api = completeNativeApi({
onSignal: handler => { signalHandler = handler; },
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
currentChannel: 'a1b2c3d4',
socket: { emit() {} },
stopScreenShare: async () => { stopCalls++; },
});
voice._setupNativeScreenBridge();
signalHandler({
type: 'error',
sessionId: 'native-session-1234',
peerId: null,
message: 'pipeline failed',
fatal: true,
});
await Promise.resolve();
assert.equal(stopCalls, 1);
});
test('sender queues native ICE until the browser answer is applied', async () => {
let releaseAnswer;
const calls = [];
const api = completeNativeApi({
setRemoteDescription: async () => {
calls.push('answer-start');
await new Promise(resolve => { releaseAnswer = resolve; });
calls.push('answer-done');
},
addIceCandidate: async ({ candidate }) => {
calls.push(candidate ? candidate.candidate : 'end-of-candidates');
},
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
_nativeScreenSenderStates: new Map([[
'7:native-session-1234:negotiation-1234',
{ ready: false, applying: null, candidates: [] },
]]),
currentChannel: 'a1b2c3d4',
});
const answer = voice._handleNativeScreenAnswer({
from: { id: 7 },
channelCode: 'a1b2c3d4',
sessionId: 'native-session-1234',
negotiationId: 'negotiation-1234',
answer: { type: 'answer', sdp: 'v=0' },
});
await Promise.resolve();
await voice._handleNativeScreenIceCandidate({
from: { id: 7 },
channelCode: 'a1b2c3d4',
sessionId: 'native-session-1234',
negotiationId: 'negotiation-1234',
candidate: { candidate: 'candidate:1' },
});
await voice._handleNativeScreenIceCandidate({
from: { id: 7 },
channelCode: 'a1b2c3d4',
sessionId: 'native-session-1234',
negotiationId: 'negotiation-1234',
candidate: null,
});
assert.deepEqual(calls, ['answer-start']);
releaseAnswer();
await answer;
assert.deepEqual(calls, ['answer-start', 'answer-done', 'candidate:1', 'end-of-candidates']);
});
test('native start is stopped without announcement after leaving during the picker', async () => {
let resolveStart;
let stopCalls = 0;
const emitted = [];
const api = completeNativeApi({
start: () => new Promise(resolve => { resolveStart = resolve; }),
stop: async () => { stopCalls++; },
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map(),
socket: {
emit: (event, payload, callback) => {
emitted.push({ event, payload });
if (event === 'screen-share-started') callback?.({ ok: true, viewerIds: [7] });
},
},
});
const pending = voice._tryStartNativeScreenShare(8, 'a1b2c3d4', 3);
await new Promise(resolve => setImmediate(resolve));
voice.inVoice = false;
voice.currentChannel = null;
voice._screenStartOperation++;
resolveStart({ started: true, sessionId: 'native-session-1234' });
assert.equal(await pending, false);
assert.equal(stopCalls, 1);
assert.equal(emitted.length, 0);
assert.notEqual(voice._nativeScreenSharing, true);
});
test('a stale rejected start cannot stop or clear a newer native session', async () => {
let rejectStart;
const stops = [];
const api = completeNativeApi({
start: () => new Promise((_, reject) => { rejectStart = reject; }),
stop: async data => { stops.push(data || 'all'); },
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map(),
socket: { connected: true, emit() {} },
});
const staleStart = voice._tryStartNativeScreenShare(8, 'a1b2c3d4', 3);
await new Promise(resolve => setImmediate(resolve));
voice._screenStartOperation = 9;
voice._nativeScreenSharing = true;
voice._nativeScreenSessionId = 'native-session-new1';
voice.isScreenSharing = true;
rejectStart(new Error('old start failed'));
assert.equal(await staleStart, null);
assert.deepEqual(stops, []);
assert.equal(voice._nativeScreenSharing, true);
assert.equal(voice._nativeScreenSessionId, 'native-session-new1');
assert.equal(voice.isScreenSharing, true);
});
test('stale native answers cannot replace the current viewer negotiation', async () => {
let applyCalls = 0;
const api = completeNativeApi({
setRemoteDescription: async () => { applyCalls++; },
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
_nativeScreenSenderStates: new Map([[
'7:native-session-1234:negotiation-current',
{ ready: false, applying: null, candidates: [] },
]]),
currentChannel: 'a1b2c3d4',
});
await voice._handleNativeScreenAnswer({
from: { id: 7 },
channelCode: 'a1b2c3d4',
sessionId: 'native-session-1234',
negotiationId: 'negotiation-stale',
answer: { type: 'answer', sdp: 'v=0' },
});
assert.equal(applyCalls, 0);
});
test('an empty screen snapshot removes stale local sharers', () => {
const handlers = new Map();
const removed = [];
const VoiceManager = loadVoiceManager({
window: {},
document: { querySelectorAll: () => [] },
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
socket: { on: (event, handler) => handlers.set(event, handler) },
currentChannel: 'a1b2c3d4',
localUserId: 1,
screenSharers: new Set([7]),
webcamUsers: new Set(),
_screenDelivered: new Set([7]),
_nativeScreenPeers: new Map(),
_pendingNativeScreenCandidates: new Map(),
_nativeScreenAnnouncements: new Map([[7, 'native-session-1234']]),
_screenWatchdogTimers: new Map(),
onScreenStream: (userId, stream) => removed.push({ userId, stream }),
});
voice._setupSocketListeners();
handlers.get('active-screen-sharers')({ channelCode: 'a1b2c3d4', sharers: [] });
assert.equal(voice.screenSharers.size, 0);
assert.equal(voice._nativeScreenAnnouncements.size, 0);
assert.deepEqual(removed, [{ userId: 7, stream: null }]);
});
test('an active screen snapshot preserves the authoritative no-audio state', () => {
const handlers = new Map();
const noAudio = [];
const VoiceManager = loadVoiceManager({
window: {},
document: { querySelectorAll: () => [] },
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
socket: { on: (event, handler) => handlers.set(event, handler) },
currentChannel: 'a1b2c3d4',
localUserId: 1,
screenSharers: new Set(),
webcamUsers: new Set(),
_screenDelivered: new Set(),
_nativeScreenPeers: new Map(),
_pendingNativeScreenCandidates: new Map(),
_nativeScreenAnnouncements: new Map(),
_screenWatchdogTimers: new Map(),
_watchForScreenStream() {},
onScreenNoAudio: userId => noAudio.push(userId),
});
voice._setupSocketListeners();
handlers.get('active-screen-sharers')({
channelCode: 'a1b2c3d4',
sharers: [{
id: 7,
username: 'Remote user',
transport: 'native',
sessionId: 'native-session-1234',
codec: 'H264',
hasAudio: false,
}],
});
assert.deepEqual(noAudio, [7]);
});
test('an empty recovery snapshot preserves an active local share', () => {
const handlers = new Map();
const removed = [];
const VoiceManager = loadVoiceManager({
window: {},
document: { querySelectorAll: () => [] },
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
socket: { on: (event, handler) => handlers.set(event, handler) },
currentChannel: 'a1b2c3d4',
localUserId: 1,
isScreenSharing: true,
screenSharers: new Set([1]),
webcamUsers: new Set(),
_screenDelivered: new Set([1]),
_nativeScreenPeers: new Map(),
_pendingNativeScreenCandidates: new Map(),
_nativeScreenAnnouncements: new Map([[1, 'native-session-1234']]),
_screenWatchdogTimers: new Map(),
onScreenStream: (userId, stream) => removed.push({ userId, stream }),
});
voice._setupSocketListeners();
handlers.get('active-screen-sharers')({ channelCode: 'a1b2c3d4', sharers: [] });
assert.equal(voice.screenSharers.has(1), true);
assert.equal(removed.length, 0);
});
test('a stale server snapshot cannot restore a stopped local share', () => {
const handlers = new Map();
const VoiceManager = loadVoiceManager({
window: {},
document: { querySelectorAll: () => [] },
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
socket: { on: (event, handler) => handlers.set(event, handler) },
currentChannel: 'a1b2c3d4',
localUserId: 1,
isScreenSharing: false,
screenSharers: new Set(),
webcamUsers: new Set(),
_screenDelivered: new Set(),
_nativeScreenPeers: new Map(),
_pendingNativeScreenCandidates: new Map(),
_nativeScreenAnnouncements: new Map(),
_screenWatchdogTimers: new Map(),
});
voice._setupSocketListeners();
handlers.get('active-screen-sharers')({
channelCode: 'a1b2c3d4',
sharers: [{
id: 1,
username: 'Local user',
transport: 'native',
sessionId: 'native-session-1234',
codec: 'H264',
}],
});
assert.equal(voice.screenSharers.has(1), false);
assert.equal(voice._nativeScreenAnnouncements.has(1), false);
});
test('browser screen sharing is reannounced after voice rejoin', async () => {
const emitted = [];
const VoiceManager = loadVoiceManager({ window: {} });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
isScreenSharing: true,
_nativeScreenSharing: false,
currentChannel: 'a1b2c3d4',
screenStream: { getAudioTracks: () => [{}] },
socket: {
emit: (event, payload, callback) => {
emitted.push({ event, payload });
callback?.({ ok: true, viewerIds: [] });
},
},
});
await voice._reannounceScreenShare([]);
assert.deepEqual(JSON.parse(JSON.stringify(emitted)), [{
event: 'screen-share-started',
payload: { code: 'a1b2c3d4', hasAudio: true, transport: 'browser' },
}]);
});
test('screen reannouncement stops local sharing when the server rejects it', async () => {
let stopCalls = 0;
const VoiceManager = loadVoiceManager({ window: {} });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
isScreenSharing: true,
_nativeScreenSharing: false,
currentChannel: 'a1b2c3d4',
screenStream: { getAudioTracks: () => [] },
socket: {
emit(event, payload, callback) {
callback?.({ ok: false, error: 'rate_limited' });
},
},
stopScreenShare: async () => { stopCalls++; },
});
assert.equal(await voice._reannounceScreenShare([]), false);
assert.equal(stopCalls, 1);
});
test('unknown native ICE negotiations are bounded per sharer session', async () => {
const VoiceManager = loadVoiceManager({ window: {} });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
_nativeScreenSharing: false,
currentChannel: 'a1b2c3d4',
_nativeScreenAnnouncements: new Map([[7, 'native-session-1234']]),
_nativeScreenPeers: new Map(),
_pendingNativeScreenCandidates: new Map(),
});
for (let index = 0; index < 10; index++) {
await voice._handleNativeScreenIceCandidate({
from: { id: 7 },
channelCode: 'a1b2c3d4',
sessionId: 'native-session-1234',
negotiationId: `negotiation-${index}`,
candidate: { candidate: `candidate:${index}` },
});
}
assert.equal(voice._pendingNativeScreenCandidates.size, 4);
});
test('native peer recovery clears stale UI and rearms retries', () => {
const removed = [];
let requested = 0;
let watched = 0;
const VoiceManager = loadVoiceManager({ window: {} });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
screenSharers: new Set([7]),
_screenDelivered: new Set([7]),
_nativeScreenPeers: new Map(),
_pendingNativeScreenCandidates: new Map(),
_screenWatchdogTimers: new Map(),
onScreenStream: (userId, stream) => removed.push({ userId, stream }),
requestScreenStream: () => { requested++; },
_watchForScreenStream: () => { watched++; },
});
voice._recoverNativeScreenPeer(7);
assert.equal(voice._screenDelivered.has(7), false);
assert.deepEqual(removed, [{ userId: 7, stream: null }]);
assert.equal(requested, 1);
assert.equal(watched, 1);
});
test('a no-op voice rejoin does not churn native screen peers', async () => {
const handlers = new Map();
let reannounced = 0;
const VoiceManager = loadVoiceManager({
window: {},
document: { querySelectorAll: () => [] },
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
socket: { on: (event, handler) => handlers.set(event, handler) },
peers: new Map(),
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 2,
_reannounceScreenShare: async () => { reannounced++; },
_rearmScreenWatchdogs() {},
});
voice._setupSocketListeners();
await handlers.get('voice-existing-users')({
channelCode: 'a1b2c3d4',
users: [],
rejoin: true,
skipRenegotiate: true,
});
assert.equal(reannounced, 0);
});
test('voice rejoin flushes a screen stop queued while disconnected', async () => {
const handlers = new Map();
const emitted = [];
const VoiceManager = loadVoiceManager({
window: {},
document: { querySelectorAll: () => [] },
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
socket: {
connected: true,
on: (event, handler) => handlers.set(event, handler),
emit: (event, payload, callback) => {
emitted.push({ event, payload });
callback?.({ ok: true });
},
},
peers: new Map(),
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 2,
_pendingScreenStop: { code: 'a1b2c3d4', sessionId: 'native-session-1234' },
_rearmScreenWatchdogs() {},
});
voice._setupSocketListeners();
await handlers.get('voice-existing-users')({
channelCode: 'a1b2c3d4',
users: [],
rejoin: true,
skipRenegotiate: true,
});
assert.deepEqual(JSON.parse(JSON.stringify(emitted[0])), {
event: 'screen-share-stopped',
payload: { code: 'a1b2c3d4', sessionId: 'native-session-1234' },
});
assert.equal(voice._pendingScreenStop, null);
});
test('native start revalidates voice ownership after attaching initial peers', async () => {
let resolvePeer;
const stops = [];
const emitted = [];
const api = completeNativeApi({
addPeer: () => new Promise(resolve => { resolvePeer = resolve; }),
stop: async data => { stops.push(data); },
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map([[7, {}]]),
_nativeScreenSenderStates: new Map(),
socket: {
emit: (event, payload, callback) => {
emitted.push({ event, payload });
if (event === 'screen-share-started') callback?.({ ok: true, viewerIds: [7] });
},
},
});
const pending = voice._tryStartNativeScreenShare(8, 'a1b2c3d4', 3);
await new Promise(resolve => setImmediate(resolve));
voice.inVoice = false;
voice.currentChannel = null;
voice._screenStartOperation++;
resolvePeer();
assert.equal(await pending, false);
assert.deepEqual(JSON.parse(JSON.stringify(stops)), [{ sessionId: 'native-session-1234' }]);
assert.equal(emitted.at(-1).event, 'screen-share-stopped');
assert.equal(voice.isScreenSharing, false);
});
test('native start cleanup follows a channel code rotation', async () => {
let resolvePeer;
const emitted = [];
const api = completeNativeApi({
addPeer: () => new Promise(resolve => { resolvePeer = resolve; }),
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map([[7, {}]]),
_nativeScreenSenderStates: new Map(),
socket: {
connected: true,
emit: (event, payload, callback) => {
emitted.push({ event, payload });
if (event === 'screen-share-started') callback?.({ ok: true, viewerIds: [7] });
},
},
});
const pending = voice._tryStartNativeScreenShare(8, 'a1b2c3d4', 3);
await new Promise(resolve => setImmediate(resolve));
voice.currentChannel = 'd4c3b2a1';
resolvePeer();
assert.equal(await pending, false);
assert.deepEqual(JSON.parse(JSON.stringify(emitted.at(-1))), {
event: 'screen-share-stopped',
payload: { code: 'd4c3b2a1', sessionId: 'native-session-1234' },
});
});
test('an active native share is reannounced after channel rotation', async () => {
const emitted = [];
const VoiceManager = loadVoiceManager({ window: {} });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
peers: new Map(),
inVoice: true,
currentChannel: 'd4c3b2a1',
_voiceSessionGeneration: 3,
isScreenSharing: true,
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
socket: {
emit: (event, payload, callback) => {
emitted.push({ event, payload });
if (event === 'screen-share-started') callback?.({ ok: true, viewerIds: [] });
},
},
_healPeerConnections() {},
});
await voice._healPeerConnectionsAfterChannelRotation('a1b2c3d4');
await Promise.resolve();
assert.deepEqual(JSON.parse(JSON.stringify(emitted[0])), {
event: 'screen-share-started',
payload: {
code: 'd4c3b2a1',
hasAudio: false,
transport: 'native',
sessionId: 'native-session-1234',
codec: 'H264',
},
});
});
test('native stop signals the server even when the helper hangs', async () => {
const emitted = [];
let screenCleanupCalls = 0;
const api = completeNativeApi({ stop: () => new Promise(() => {}) });
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
localUserId: 1,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_nativeScreenOperationTimeoutMs: 5,
isScreenSharing: true,
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
_nativeScreenSenderStates: new Map(),
screenSharers: new Set([1]),
_nativeScreenAnnouncements: new Map([[1, 'native-session-1234']]),
socket: {
connected: true,
emit: (event, payload) => emitted.push({ event, payload }),
},
onScreenStream: () => { screenCleanupCalls++; },
});
const stopping = voice.stopScreenShare();
assert.deepEqual(JSON.parse(JSON.stringify(emitted[0])), {
event: 'screen-share-stopped',
payload: { code: 'a1b2c3d4', sessionId: 'native-session-1234' },
});
assert.equal(screenCleanupCalls, 1);
await stopping;
assert.equal(voice.isScreenSharing, false);
assert.equal(screenCleanupCalls, 1);
});
test('native stop is queued until signaling reconnects', async () => {
const emitted = [];
const api = completeNativeApi();
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const socket = {
connected: false,
emit: (event, payload) => emitted.push({ event, payload }),
};
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
localUserId: 1,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
isScreenSharing: true,
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
_nativeScreenSenderStates: new Map(),
screenSharers: new Set([1]),
_nativeScreenAnnouncements: new Map([[1, 'native-session-1234']]),
socket,
});
await voice.stopScreenShare();
assert.equal(emitted.length, 0);
assert.deepEqual(
JSON.parse(JSON.stringify(voice._pendingScreenStop)),
{ code: 'a1b2c3d4', sessionId: 'native-session-1234' }
);
socket.connected = true;
assert.equal(voice._flushPendingScreenStop('a1b2c3d4'), true);
assert.deepEqual(JSON.parse(JSON.stringify(emitted[0])), {
event: 'screen-share-stopped',
payload: { code: 'a1b2c3d4', sessionId: 'native-session-1234' },
});
});
test('native start uses the gentler relay bitrate profile', async () => {
let startOptions;
const api = completeNativeApi({
start: async options => {
startOptions = options;
return {
started: true,
sessionId: 'native-session-1234',
codec: 'H264',
hasAudio: false,
};
},
});
const VoiceManager = loadVoiceManager({
window: { havenDesktop: { nativeScreen: api } },
localStorage: {
getItem: key => key === 'haven_screen_relay_profile' ? '1' : null,
setItem() {},
removeItem() {},
},
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map(),
_nativeScreenSenderStates: new Map(),
socket: {
connected: true,
emit(event, payload, callback) {
if (event === 'screen-share-started') callback?.({ ok: true, viewerIds: [] });
},
},
});
assert.equal(await voice._tryStartNativeScreenShare(8, 'a1b2c3d4', 3), true);
assert.equal(startOptions.bitrate, 3_000_000);
});
test('fatal helper failure invalidates a start waiting on initial peers', async () => {
let resolvePeer;
const api = completeNativeApi({
addPeer: () => new Promise(resolve => { resolvePeer = resolve; }),
});
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
localUserId: 1,
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map([[7, {}]]),
screenSharers: new Set(),
_nativeScreenAnnouncements: new Map(),
_nativeScreenSenderStates: new Map(),
socket: {
emit(event, payload, callback) {
if (event === 'screen-share-started') callback?.({ ok: true, viewerIds: [7] });
},
},
});
const pending = voice._tryStartNativeScreenShare(8, 'a1b2c3d4', 3);
await new Promise(resolve => setImmediate(resolve));
voice._handleNativeScreenFailure('pipeline failed');
await new Promise(resolve => setImmediate(resolve));
resolvePeer();
assert.equal(await pending, false);
assert.equal(voice.isScreenSharing, false);
});
test('an ended native track uses the retrying recovery path', async () => {
let connection;
class FakePeerConnection {
constructor() {
connection = this;
this.connectionState = 'new';
this.remoteDescription = null;
}
async setRemoteDescription(description) { this.remoteDescription = description; }
async createAnswer() { return { type: 'answer', sdp: 'v=0' }; }
async setLocalDescription() {}
close() {}
}
const VoiceManager = loadVoiceManager({
window: {},
RTCPeerConnection: FakePeerConnection,
MediaStream: class {},
});
const voice = Object.create(VoiceManager.prototype);
let recoveries = 0;
Object.assign(voice, {
currentChannel: 'a1b2c3d4',
rtcConfig: {},
screenSharers: new Set([7]),
_screenDelivered: new Set(),
_nativeScreenPeers: new Map(),
_pendingNativeScreenCandidates: new Map(),
_nativeScreenAnnouncements: new Map([[7, 'native-session-1234']]),
_recoverNativeScreenPeer: () => { recoveries++; },
socket: { emit() {} },
});
await voice._handleNativeScreenOffer({
from: { id: 7 },
channelCode: 'a1b2c3d4',
sessionId: 'native-session-1234',
negotiationId: 'negotiation-1234',
offer: { type: 'offer', sdp: 'v=0' },
});
const track = { kind: 'video' };
connection.ontrack({ track, streams: [{}] });
track.onended();
assert.equal(recoveries, 1);
});
test('stopping a native share removes a local snapshot badge', async () => {
const api = completeNativeApi();
const VoiceManager = loadVoiceManager({ window: { havenDesktop: { nativeScreen: api } } });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
localUserId: 1,
currentChannel: 'a1b2c3d4',
isScreenSharing: true,
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
_nativeScreenSenderStates: new Map(),
screenSharers: new Set([1]),
_nativeScreenAnnouncements: new Map([[1, 'native-session-1234']]),
socket: { emit() {} },
});
await voice.stopScreenShare();
assert.equal(voice.screenSharers.has(1), false);
assert.equal(voice._nativeScreenAnnouncements.has(1), false);
});
test('browser fallback revalidates the voice operation after renegotiation', async () => {
let resolveRenegotiation;
let renegotiationCalls = 0;
let stopCalls = 0;
const emitted = [];
const videoTrack = { kind: 'video', readyState: 'live', stop() { stopCalls++; } };
const stream = {
getTracks: () => [videoTrack],
getVideoTracks: () => [videoTrack],
getAudioTracks: () => [],
};
const VoiceManager = loadVoiceManager({
window: {},
navigator: {
userAgent: '', platform: '', maxTouchPoints: 0,
mediaDevices: { getDisplayMedia: async () => stream },
},
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
_screenStartInFlight: false,
isScreenSharing: false,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
rtcConfig: { iceServers: [] },
peers: new Map([[7, {
connection: { addTrack() {}, getSenders: () => [], removeTrack() {} },
}]]),
localUserId: 1,
screenSharers: new Set([1]),
_nativeScreenAnnouncements: new Map(),
socket: {
connected: true,
emit: (event, payload, callback) => {
emitted.push({ event, payload });
if (event === 'screen-share-started') callback?.({ ok: true, viewerIds: [7] });
},
},
_applyScreenBitrate() {},
_renegotiate: () => {
if (renegotiationCalls++ > 0) return Promise.resolve();
return new Promise(resolve => { resolveRenegotiation = resolve; });
},
});
const pending = voice.shareScreen();
await new Promise(resolve => setImmediate(resolve));
voice.currentChannel = 'd4c3b2a1';
voice._screenStartOperation++;
resolveRenegotiation();
assert.equal(await pending, false);
assert.equal(stopCalls, 1);
assert.equal(voice.isScreenSharing, false);
assert.equal(voice.screenStream, null);
assert.deepEqual(JSON.parse(JSON.stringify(emitted.at(-1))), {
event: 'screen-share-stopped',
payload: { code: 'd4c3b2a1' },
});
});
test('browser capture is rolled back when the server rejects screen start', async () => {
let stopCalls = 0;
const videoTrack = { kind: 'video', readyState: 'live', stop() { stopCalls++; } };
const stream = {
getTracks: () => [videoTrack],
getVideoTracks: () => [videoTrack],
getAudioTracks: () => [],
};
const VoiceManager = loadVoiceManager({
window: {},
navigator: {
userAgent: '', platform: '', maxTouchPoints: 0,
mediaDevices: { getDisplayMedia: async () => stream },
},
});
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
inVoice: true,
currentChannel: 'a1b2c3d4',
_voiceSessionGeneration: 3,
_screenStartOperation: 8,
_screenStartInFlight: false,
isScreenSharing: false,
screenResolution: 1080,
screenFrameRate: 30,
_screenBitrates: { 1080: 8_000_000, 0: 8_000_000 },
peers: new Map(),
socket: {
connected: true,
emit(event, payload, callback) {
if (event === 'screen-share-started') callback?.({ ok: false, error: 'streams_disabled' });
},
},
});
assert.equal(await voice.shareScreen(), false);
assert.equal(stopCalls, 1);
assert.equal(voice.isScreenSharing, false);
assert.equal(voice.screenStream, null);
});
test('an incompatible late viewer stops an active native share', async () => {
const handlers = new Map();
let stopCalls = 0;
let warnings = 0;
const VoiceManager = loadVoiceManager({ window: {} });
const voice = Object.create(VoiceManager.prototype);
Object.assign(voice, {
socket: { on: (event, handler) => handlers.set(event, handler) },
currentChannel: 'a1b2c3d4',
_nativeScreenSharing: true,
_nativeScreenSessionId: 'native-session-1234',
onScreenShareWarning: () => { warnings++; },
stopScreenShare: async () => { stopCalls++; },
});
voice._setupSocketListeners();
handlers.get('native-screen-incompatible-peer')({
channelCode: 'a1b2c3d4',
userId: 7,
sessionId: 'native-session-old1',
});
await Promise.resolve();
assert.equal(stopCalls, 0);
assert.equal(warnings, 0);
handlers.get('native-screen-incompatible-peer')({
channelCode: 'a1b2c3d4',
userId: 7,
sessionId: 'native-session-1234',
});
await Promise.resolve();
assert.equal(stopCalls, 1);
assert.equal(warnings, 1);
});