Compare commits
2 Commits
1cdd1c5d2b
...
v1.0.68
| Author | SHA1 | Date | |
|---|---|---|---|
| c862c2fe03 | |||
| 4faa62864d |
58
electron/app/auto-start.ts
Normal file
58
electron/app/auto-start.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { app } from 'electron';
|
||||||
|
import AutoLaunch from 'auto-launch';
|
||||||
|
import { readDesktopSettings } from '../desktop-settings';
|
||||||
|
|
||||||
|
let autoLauncher: AutoLaunch | null = null;
|
||||||
|
|
||||||
|
function resolveLaunchPath(): string {
|
||||||
|
// AppImage runs from a temporary mount; APPIMAGE points to the real file path.
|
||||||
|
const appImagePath = process.platform === 'linux'
|
||||||
|
? String(process.env['APPIMAGE'] || '').trim()
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return appImagePath || process.execPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAutoLauncher(): AutoLaunch | null {
|
||||||
|
if (!app.isPackaged) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!autoLauncher) {
|
||||||
|
autoLauncher = new AutoLaunch({
|
||||||
|
name: app.getName(),
|
||||||
|
path: resolveLaunchPath()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return autoLauncher;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setAutoStartEnabled(enabled: boolean): Promise<void> {
|
||||||
|
const launcher = getAutoLauncher();
|
||||||
|
|
||||||
|
if (!launcher) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentlyEnabled = await launcher.isEnabled();
|
||||||
|
|
||||||
|
if (currentlyEnabled === enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
await launcher.enable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await launcher.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function synchronizeAutoStartSetting(enabled = readDesktopSettings().autoStart): Promise<void> {
|
||||||
|
try {
|
||||||
|
await setAutoStartEnabled(enabled);
|
||||||
|
} catch {
|
||||||
|
// Auto-launch integration should never block app startup or settings saves.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { app, BrowserWindow } from 'electron';
|
import { app, BrowserWindow } from 'electron';
|
||||||
import { cleanupLinuxScreenShareAudioRouting } from '../audio/linux-screen-share-routing';
|
import { cleanupLinuxScreenShareAudioRouting } from '../audio/linux-screen-share-routing';
|
||||||
import { initializeDesktopUpdater, shutdownDesktopUpdater } from '../update/desktop-updater';
|
import { initializeDesktopUpdater, shutdownDesktopUpdater } from '../update/desktop-updater';
|
||||||
|
import { synchronizeAutoStartSetting } from './auto-start';
|
||||||
import {
|
import {
|
||||||
initializeDatabase,
|
initializeDatabase,
|
||||||
destroyDatabase,
|
destroyDatabase,
|
||||||
@@ -24,6 +25,7 @@ export function registerAppLifecycle(): void {
|
|||||||
setupCqrsHandlers();
|
setupCqrsHandlers();
|
||||||
setupWindowControlHandlers();
|
setupWindowControlHandlers();
|
||||||
setupSystemHandlers();
|
setupSystemHandlers();
|
||||||
|
await synchronizeAutoStartSetting();
|
||||||
initializeDesktopUpdater();
|
initializeDesktopUpdater();
|
||||||
await createWindow();
|
await createWindow();
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export type AutoUpdateMode = 'auto' | 'off' | 'version';
|
|||||||
|
|
||||||
export interface DesktopSettings {
|
export interface DesktopSettings {
|
||||||
autoUpdateMode: AutoUpdateMode;
|
autoUpdateMode: AutoUpdateMode;
|
||||||
|
autoStart: boolean;
|
||||||
hardwareAcceleration: boolean;
|
hardwareAcceleration: boolean;
|
||||||
manifestUrls: string[];
|
manifestUrls: string[];
|
||||||
preferredVersion: string | null;
|
preferredVersion: string | null;
|
||||||
@@ -19,6 +20,7 @@ export interface DesktopSettingsSnapshot extends DesktopSettings {
|
|||||||
|
|
||||||
const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||||
autoUpdateMode: 'auto',
|
autoUpdateMode: 'auto',
|
||||||
|
autoStart: true,
|
||||||
hardwareAcceleration: true,
|
hardwareAcceleration: true,
|
||||||
manifestUrls: [],
|
manifestUrls: [],
|
||||||
preferredVersion: null,
|
preferredVersion: null,
|
||||||
@@ -81,6 +83,9 @@ export function readDesktopSettings(): DesktopSettings {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
autoUpdateMode: normalizeAutoUpdateMode(parsed.autoUpdateMode),
|
autoUpdateMode: normalizeAutoUpdateMode(parsed.autoUpdateMode),
|
||||||
|
autoStart: typeof parsed.autoStart === 'boolean'
|
||||||
|
? parsed.autoStart
|
||||||
|
: DEFAULT_DESKTOP_SETTINGS.autoStart,
|
||||||
vaapiVideoEncode: typeof parsed.vaapiVideoEncode === 'boolean'
|
vaapiVideoEncode: typeof parsed.vaapiVideoEncode === 'boolean'
|
||||||
? parsed.vaapiVideoEncode
|
? parsed.vaapiVideoEncode
|
||||||
: DEFAULT_DESKTOP_SETTINGS.vaapiVideoEncode,
|
: DEFAULT_DESKTOP_SETTINGS.vaapiVideoEncode,
|
||||||
@@ -102,6 +107,9 @@ export function updateDesktopSettings(patch: Partial<DesktopSettings>): DesktopS
|
|||||||
};
|
};
|
||||||
const nextSettings: DesktopSettings = {
|
const nextSettings: DesktopSettings = {
|
||||||
autoUpdateMode: normalizeAutoUpdateMode(mergedSettings.autoUpdateMode),
|
autoUpdateMode: normalizeAutoUpdateMode(mergedSettings.autoUpdateMode),
|
||||||
|
autoStart: typeof mergedSettings.autoStart === 'boolean'
|
||||||
|
? mergedSettings.autoStart
|
||||||
|
: DEFAULT_DESKTOP_SETTINGS.autoStart,
|
||||||
hardwareAcceleration: typeof mergedSettings.hardwareAcceleration === 'boolean'
|
hardwareAcceleration: typeof mergedSettings.hardwareAcceleration === 'boolean'
|
||||||
? mergedSettings.hardwareAcceleration
|
? mergedSettings.hardwareAcceleration
|
||||||
: DEFAULT_DESKTOP_SETTINGS.hardwareAcceleration,
|
: DEFAULT_DESKTOP_SETTINGS.hardwareAcceleration,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
type DesktopUpdateServerContext
|
type DesktopUpdateServerContext
|
||||||
} from '../update/desktop-updater';
|
} from '../update/desktop-updater';
|
||||||
import { consumePendingDeepLink } from '../app/deep-links';
|
import { consumePendingDeepLink } from '../app/deep-links';
|
||||||
|
import { synchronizeAutoStartSetting } from '../app/auto-start';
|
||||||
|
|
||||||
const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
||||||
const FILE_CLIPBOARD_FORMATS = [
|
const FILE_CLIPBOARD_FORMATS = [
|
||||||
@@ -329,6 +330,7 @@ export function setupSystemHandlers(): void {
|
|||||||
ipcMain.handle('set-desktop-settings', async (_event, patch: Partial<DesktopSettings>) => {
|
ipcMain.handle('set-desktop-settings', async (_event, patch: Partial<DesktopSettings>) => {
|
||||||
const snapshot = updateDesktopSettings(patch);
|
const snapshot = updateDesktopSettings(patch);
|
||||||
|
|
||||||
|
await synchronizeAutoStartSetting(snapshot.autoStart);
|
||||||
await handleDesktopSettingsChanged();
|
await handleDesktopSettingsChanged();
|
||||||
return snapshot;
|
return snapshot;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export interface ElectronAPI {
|
|||||||
consumePendingDeepLink: () => Promise<string | null>;
|
consumePendingDeepLink: () => Promise<string | null>;
|
||||||
getDesktopSettings: () => Promise<{
|
getDesktopSettings: () => Promise<{
|
||||||
autoUpdateMode: 'auto' | 'off' | 'version';
|
autoUpdateMode: 'auto' | 'off' | 'version';
|
||||||
|
autoStart: boolean;
|
||||||
hardwareAcceleration: boolean;
|
hardwareAcceleration: boolean;
|
||||||
manifestUrls: string[];
|
manifestUrls: string[];
|
||||||
preferredVersion: string | null;
|
preferredVersion: string | null;
|
||||||
@@ -132,12 +133,14 @@ export interface ElectronAPI {
|
|||||||
onAutoUpdateStateChanged: (listener: (state: DesktopUpdateState) => void) => () => void;
|
onAutoUpdateStateChanged: (listener: (state: DesktopUpdateState) => void) => () => void;
|
||||||
setDesktopSettings: (patch: {
|
setDesktopSettings: (patch: {
|
||||||
autoUpdateMode?: 'auto' | 'off' | 'version';
|
autoUpdateMode?: 'auto' | 'off' | 'version';
|
||||||
|
autoStart?: boolean;
|
||||||
hardwareAcceleration?: boolean;
|
hardwareAcceleration?: boolean;
|
||||||
manifestUrls?: string[];
|
manifestUrls?: string[];
|
||||||
preferredVersion?: string | null;
|
preferredVersion?: string | null;
|
||||||
vaapiVideoEncode?: boolean;
|
vaapiVideoEncode?: boolean;
|
||||||
}) => Promise<{
|
}) => Promise<{
|
||||||
autoUpdateMode: 'auto' | 'off' | 'version';
|
autoUpdateMode: 'auto' | 'off' | 'version';
|
||||||
|
autoStart: boolean;
|
||||||
hardwareAcceleration: boolean;
|
hardwareAcceleration: boolean;
|
||||||
manifestUrls: string[];
|
manifestUrls: string[];
|
||||||
preferredVersion: string | null;
|
preferredVersion: string | null;
|
||||||
|
|||||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -24,6 +24,7 @@
|
|||||||
"@spartan-ng/cli": "^0.0.1-alpha.589",
|
"@spartan-ng/cli": "^0.0.1-alpha.589",
|
||||||
"@spartan-ng/ui-core": "^0.0.1-alpha.380",
|
"@spartan-ng/ui-core": "^0.0.1-alpha.380",
|
||||||
"@timephy/rnnoise-wasm": "^1.0.0",
|
"@timephy/rnnoise-wasm": "^1.0.0",
|
||||||
|
"auto-launch": "^5.0.6",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cytoscape": "^3.33.1",
|
"cytoscape": "^3.33.1",
|
||||||
@@ -45,11 +46,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/build": "^21.0.4",
|
"@angular/build": "^21.0.4",
|
||||||
"@angular/cli": "^21.2.1",
|
"@angular/cli": "^21.0.4",
|
||||||
"@angular/compiler-cli": "^21.0.0",
|
"@angular/compiler-cli": "^21.0.0",
|
||||||
"@eslint/js": "^9.39.3",
|
"@eslint/js": "^9.39.3",
|
||||||
"@stylistic/eslint-plugin-js": "^4.4.1",
|
"@stylistic/eslint-plugin-js": "^4.4.1",
|
||||||
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
||||||
|
"@types/auto-launch": "^5.0.5",
|
||||||
"@types/simple-peer": "^9.11.9",
|
"@types/simple-peer": "^9.11.9",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"angular-eslint": "21.2.0",
|
"angular-eslint": "21.2.0",
|
||||||
@@ -10816,6 +10818,13 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/auto-launch": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/auto-launch/-/auto-launch-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-/nGvQZSzM/pvCMCh4Gt2kIeiUmOP/cKGJbjlInI+A+5MoV/7XmT56DJ6EU8bqc3+ItxEe4UC2GVspmPzcCc8cg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/body-parser": {
|
"node_modules/@types/body-parser": {
|
||||||
"version": "1.19.6",
|
"version": "1.19.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||||
@@ -12875,6 +12884,11 @@
|
|||||||
"node": ">= 6.0.0"
|
"node": ">= 6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/applescript": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ=="
|
||||||
|
},
|
||||||
"node_modules/arg": {
|
"node_modules/arg": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
@@ -12968,6 +12982,22 @@
|
|||||||
"node": ">= 4.0.0"
|
"node": ">= 4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/auto-launch": {
|
||||||
|
"version": "5.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/auto-launch/-/auto-launch-5.0.6.tgz",
|
||||||
|
"integrity": "sha512-OgxiAm4q9EBf9EeXdPBiVNENaWE3jUZofwrhAkWjHDYGezu1k3FRZHU8V2FBxGuSJOHzKmTJEd0G7L7/0xDGFA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"applescript": "^1.0.0",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"path-is-absolute": "^1.0.0",
|
||||||
|
"untildify": "^3.0.2",
|
||||||
|
"winreg": "1.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.23",
|
"version": "10.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
|
||||||
@@ -22285,9 +22315,7 @@
|
|||||||
"version": "0.5.6",
|
"version": "0.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.6"
|
"minimist": "^1.2.6"
|
||||||
},
|
},
|
||||||
@@ -23745,7 +23773,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -29571,6 +29598,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/untildify": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/upath": {
|
"node_modules/upath": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz",
|
||||||
@@ -31161,6 +31197,12 @@
|
|||||||
"integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
|
"integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/winreg": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-IHpzORub7kYlb8A43Iig3reOvlcBJGX9gZ0WycHhghHtA65X0LYnMRuJs+aH1abVnMJztQkvQNlltnbPi5aGIA==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
"@spartan-ng/cli": "^0.0.1-alpha.589",
|
"@spartan-ng/cli": "^0.0.1-alpha.589",
|
||||||
"@spartan-ng/ui-core": "^0.0.1-alpha.380",
|
"@spartan-ng/ui-core": "^0.0.1-alpha.380",
|
||||||
"@timephy/rnnoise-wasm": "^1.0.0",
|
"@timephy/rnnoise-wasm": "^1.0.0",
|
||||||
|
"auto-launch": "^5.0.6",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cytoscape": "^3.33.1",
|
"cytoscape": "^3.33.1",
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
"@eslint/js": "^9.39.3",
|
"@eslint/js": "^9.39.3",
|
||||||
"@stylistic/eslint-plugin-js": "^4.4.1",
|
"@stylistic/eslint-plugin-js": "^4.4.1",
|
||||||
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
||||||
|
"@types/auto-launch": "^5.0.5",
|
||||||
"@types/simple-peer": "^9.11.9",
|
"@types/simple-peer": "^9.11.9",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"angular-eslint": "21.2.0",
|
"angular-eslint": "21.2.0",
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import {
|
|||||||
signal,
|
signal,
|
||||||
effect
|
effect
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import { NavigationEnd, Router } from '@angular/router';
|
||||||
NavigationEnd,
|
|
||||||
Router
|
|
||||||
} from '@angular/router';
|
|
||||||
import { take } from 'rxjs';
|
import { take } from 'rxjs';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { WebRTCService } from './webrtc.service';
|
import { WebRTCService } from './webrtc.service';
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ export class DebuggingService {
|
|||||||
|
|
||||||
const rawMessage = args.map((arg) => this.stringifyPreview(arg)).join(' ')
|
const rawMessage = args.map((arg) => this.stringifyPreview(arg)).join(' ')
|
||||||
.trim() || '(empty console call)';
|
.trim() || '(empty console call)';
|
||||||
|
|
||||||
// Use only string args for label/message extraction so that
|
// Use only string args for label/message extraction so that
|
||||||
// stringified object payloads don't pollute the parsed message.
|
// stringified object payloads don't pollute the parsed message.
|
||||||
// Object payloads are captured separately via extractConsolePayload.
|
// Object payloads are captured separately via extractConsolePayload.
|
||||||
@@ -226,7 +225,6 @@ export class DebuggingService {
|
|||||||
.filter((arg): arg is string => typeof arg === 'string')
|
.filter((arg): arg is string => typeof arg === 'string')
|
||||||
.join(' ')
|
.join(' ')
|
||||||
.trim() || rawMessage;
|
.trim() || rawMessage;
|
||||||
|
|
||||||
const consoleMetadata = this.extractConsoleMetadata(metadataSource);
|
const consoleMetadata = this.extractConsoleMetadata(metadataSource);
|
||||||
const payload = this.extractConsolePayload(args);
|
const payload = this.extractConsolePayload(args);
|
||||||
const payloadText = payload === undefined
|
const payloadText = payload === undefined
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Injectable, signal } from '@angular/core';
|
import { Injectable, signal } from '@angular/core';
|
||||||
export type SettingsPage = 'network' | 'voice' | 'updates' | 'debugging' | 'server' | 'members' | 'bans' | 'permissions';
|
export type SettingsPage = 'general' | 'network' | 'voice' | 'updates' | 'debugging' | 'server' | 'members' | 'bans' | 'permissions';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SettingsModalService {
|
export class SettingsModalService {
|
||||||
readonly isOpen = signal(false);
|
readonly isOpen = signal(false);
|
||||||
readonly activePage = signal<SettingsPage>('network');
|
readonly activePage = signal<SettingsPage>('general');
|
||||||
readonly targetServerId = signal<string | null>(null);
|
readonly targetServerId = signal<string | null>(null);
|
||||||
|
|
||||||
open(page: SettingsPage = 'network', serverId?: string): void {
|
open(page: SettingsPage = 'general', serverId?: string): void {
|
||||||
this.activePage.set(page);
|
this.activePage.set(page);
|
||||||
this.targetServerId.set(serverId ?? null);
|
this.targetServerId.set(serverId ?? null);
|
||||||
this.isOpen.set(true);
|
this.isOpen.set(true);
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<div class="space-y-6 max-w-xl">
|
||||||
|
<section>
|
||||||
|
<div class="flex items-center gap-2 mb-3">
|
||||||
|
<ng-icon
|
||||||
|
name="lucidePower"
|
||||||
|
class="w-5 h-5 text-muted-foreground"
|
||||||
|
/>
|
||||||
|
<h4 class="text-sm font-semibold text-foreground">Application</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="rounded-lg border border-border bg-secondary/20 p-4 transition-opacity"
|
||||||
|
[class.opacity-60]="!isElectron"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-foreground">Launch on system startup</p>
|
||||||
|
|
||||||
|
@if (isElectron) {
|
||||||
|
<p class="text-xs text-muted-foreground">Automatically start MetoYou when you sign in</p>
|
||||||
|
} @else {
|
||||||
|
<p class="text-xs text-muted-foreground">This setting is only available in the desktop app.</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label
|
||||||
|
class="relative inline-flex items-center"
|
||||||
|
[class.cursor-pointer]="isElectron && !savingAutoStart()"
|
||||||
|
[class.cursor-not-allowed]="!isElectron || savingAutoStart()"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[checked]="autoStart()"
|
||||||
|
[disabled]="!isElectron || savingAutoStart()"
|
||||||
|
(change)="onAutoStartChange($event)"
|
||||||
|
id="general-auto-start-toggle"
|
||||||
|
aria-label="Toggle launch on startup"
|
||||||
|
class="sr-only peer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="w-10 h-5 bg-secondary rounded-full peer peer-checked:bg-primary peer-disabled:bg-muted/80 peer-disabled:after:bg-muted-foreground/40 peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all"
|
||||||
|
></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/member-ordering */
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
inject,
|
||||||
|
signal
|
||||||
|
} from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||||
|
import { lucidePower } from '@ng-icons/lucide';
|
||||||
|
|
||||||
|
import { PlatformService } from '../../../../core/services/platform.service';
|
||||||
|
|
||||||
|
interface DesktopSettingsSnapshot {
|
||||||
|
autoStart: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GeneralSettingsElectronApi {
|
||||||
|
getDesktopSettings?: () => Promise<DesktopSettingsSnapshot>;
|
||||||
|
setDesktopSettings?: (patch: { autoStart?: boolean }) => Promise<DesktopSettingsSnapshot>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeneralSettingsWindow = Window & {
|
||||||
|
electronAPI?: GeneralSettingsElectronApi;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-general-settings',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, NgIcon],
|
||||||
|
viewProviders: [
|
||||||
|
provideIcons({
|
||||||
|
lucidePower
|
||||||
|
})
|
||||||
|
],
|
||||||
|
templateUrl: './general-settings.component.html'
|
||||||
|
})
|
||||||
|
export class GeneralSettingsComponent {
|
||||||
|
private platform = inject(PlatformService);
|
||||||
|
|
||||||
|
readonly isElectron = this.platform.isElectron;
|
||||||
|
autoStart = signal(false);
|
||||||
|
savingAutoStart = signal(false);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (this.isElectron) {
|
||||||
|
void this.loadDesktopSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onAutoStartChange(event: Event): Promise<void> {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const enabled = !!input.checked;
|
||||||
|
const api = this.getElectronApi();
|
||||||
|
|
||||||
|
if (!this.isElectron || !api?.setDesktopSettings) {
|
||||||
|
input.checked = this.autoStart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.savingAutoStart.set(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const snapshot = await api.setDesktopSettings({ autoStart: enabled });
|
||||||
|
|
||||||
|
this.autoStart.set(snapshot.autoStart);
|
||||||
|
} catch {
|
||||||
|
input.checked = this.autoStart();
|
||||||
|
} finally {
|
||||||
|
this.savingAutoStart.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadDesktopSettings(): Promise<void> {
|
||||||
|
const api = this.getElectronApi();
|
||||||
|
|
||||||
|
if (!api?.getDesktopSettings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const snapshot = await api.getDesktopSettings();
|
||||||
|
|
||||||
|
this.autoStart.set(snapshot.autoStart);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getElectronApi(): GeneralSettingsElectronApi | null {
|
||||||
|
return typeof window !== 'undefined'
|
||||||
|
? (window as GeneralSettingsWindow).electronAPI ?? null
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,6 +116,9 @@
|
|||||||
<div class="flex items-center justify-between px-6 py-4 border-b border-border flex-shrink-0">
|
<div class="flex items-center justify-between px-6 py-4 border-b border-border flex-shrink-0">
|
||||||
<h3 class="text-lg font-semibold text-foreground">
|
<h3 class="text-lg font-semibold text-foreground">
|
||||||
@switch (activePage()) {
|
@switch (activePage()) {
|
||||||
|
@case ('general') {
|
||||||
|
General
|
||||||
|
}
|
||||||
@case ('network') {
|
@case ('network') {
|
||||||
Network
|
Network
|
||||||
}
|
}
|
||||||
@@ -157,6 +160,9 @@
|
|||||||
<!-- Scrollable Content Area -->
|
<!-- Scrollable Content Area -->
|
||||||
<div class="flex-1 overflow-y-auto p-6">
|
<div class="flex-1 overflow-y-auto p-6">
|
||||||
@switch (activePage()) {
|
@switch (activePage()) {
|
||||||
|
@case ('general') {
|
||||||
|
<app-general-settings />
|
||||||
|
}
|
||||||
@case ('network') {
|
@case ('network') {
|
||||||
<app-network-settings />
|
<app-network-settings />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { Room, UserRole } from '../../../core/models/index';
|
|||||||
import { findRoomMember } from '../../../store/rooms/room-members.helpers';
|
import { findRoomMember } from '../../../store/rooms/room-members.helpers';
|
||||||
import { WebRTCService } from '../../../core/services/webrtc.service';
|
import { WebRTCService } from '../../../core/services/webrtc.service';
|
||||||
|
|
||||||
|
import { GeneralSettingsComponent } from './general-settings/general-settings.component';
|
||||||
import { NetworkSettingsComponent } from './network-settings/network-settings.component';
|
import { NetworkSettingsComponent } from './network-settings/network-settings.component';
|
||||||
import { VoiceSettingsComponent } from './voice-settings/voice-settings.component';
|
import { VoiceSettingsComponent } from './voice-settings/voice-settings.component';
|
||||||
import { ServerSettingsComponent } from './server-settings/server-settings.component';
|
import { ServerSettingsComponent } from './server-settings/server-settings.component';
|
||||||
@@ -48,6 +49,7 @@ import { THIRD_PARTY_LICENSES, type ThirdPartyLicense } from './third-party-lice
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgIcon,
|
NgIcon,
|
||||||
|
GeneralSettingsComponent,
|
||||||
NetworkSettingsComponent,
|
NetworkSettingsComponent,
|
||||||
VoiceSettingsComponent,
|
VoiceSettingsComponent,
|
||||||
UpdatesSettingsComponent,
|
UpdatesSettingsComponent,
|
||||||
@@ -89,6 +91,9 @@ export class SettingsModalComponent {
|
|||||||
activePage = this.modal.activePage;
|
activePage = this.modal.activePage;
|
||||||
|
|
||||||
readonly globalPages: { id: SettingsPage; label: string; icon: string }[] = [
|
readonly globalPages: { id: SettingsPage; label: string; icon: string }[] = [
|
||||||
|
{ id: 'general',
|
||||||
|
label: 'General',
|
||||||
|
icon: 'lucideSettings' },
|
||||||
{ id: 'network',
|
{ id: 'network',
|
||||||
label: 'Network',
|
label: 'Network',
|
||||||
icon: 'lucideGlobe' },
|
icon: 'lucideGlobe' },
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ import {
|
|||||||
lucideRefreshCw
|
lucideRefreshCw
|
||||||
} from '@ng-icons/lucide';
|
} from '@ng-icons/lucide';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import {
|
import { selectCurrentRoom, selectIsSignalServerReconnecting } from '../../store/rooms/rooms.selectors';
|
||||||
selectCurrentRoom,
|
|
||||||
selectIsSignalServerReconnecting
|
|
||||||
} from '../../store/rooms/rooms.selectors';
|
|
||||||
import { RoomsActions } from '../../store/rooms/rooms.actions';
|
import { RoomsActions } from '../../store/rooms/rooms.actions';
|
||||||
import { selectCurrentUser } from '../../store/users/users.selectors';
|
import { selectCurrentUser } from '../../store/users/users.selectors';
|
||||||
import { ServerDirectoryService } from '../../core/services/server-directory.service';
|
import { ServerDirectoryService } from '../../core/services/server-directory.service';
|
||||||
@@ -56,8 +53,8 @@ type ElectronWindow = Window & {
|
|||||||
lucideX,
|
lucideX,
|
||||||
lucideChevronLeft,
|
lucideChevronLeft,
|
||||||
lucideHash,
|
lucideHash,
|
||||||
lucideMenu,
|
lucideMenu,
|
||||||
lucideRefreshCw })
|
lucideRefreshCw })
|
||||||
],
|
],
|
||||||
templateUrl: './title-bar.component.html'
|
templateUrl: './title-bar.component.html'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export class VoicePlaybackService {
|
|||||||
this.temporaryOutputDeviceId = this.webrtc.forceDefaultRemotePlaybackOutput()
|
this.temporaryOutputDeviceId = this.webrtc.forceDefaultRemotePlaybackOutput()
|
||||||
? 'default'
|
? 'default'
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
void this.applyEffectiveOutputDeviceToAllPipelines();
|
void this.applyEffectiveOutputDeviceToAllPipelines();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,17 @@ export class DebugConsoleToolbarComponent {
|
|||||||
|
|
||||||
readonly tabs: ('logs' | 'network')[] = ['logs', 'network'];
|
readonly tabs: ('logs' | 'network')[] = ['logs', 'network'];
|
||||||
|
|
||||||
|
@HostListener('document:click', ['$event'])
|
||||||
|
onDocumentClick(event: MouseEvent): void {
|
||||||
|
if (!this.exportMenuOpen())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
|
||||||
|
if (!target.closest('[data-export-menu]'))
|
||||||
|
this.closeExportMenu();
|
||||||
|
}
|
||||||
|
|
||||||
setActiveTab(tab: 'logs' | 'network'): void {
|
setActiveTab(tab: 'logs' | 'network'): void {
|
||||||
this.activeTabChange.emit(tab);
|
this.activeTabChange.emit(tab);
|
||||||
}
|
}
|
||||||
@@ -138,17 +149,6 @@ export class DebugConsoleToolbarComponent {
|
|||||||
this.closeExportMenu();
|
this.closeExportMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:click', ['$event'])
|
|
||||||
onDocumentClick(event: MouseEvent): void {
|
|
||||||
if (!this.exportMenuOpen())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const target = event.target as HTMLElement;
|
|
||||||
|
|
||||||
if (!target.closest('[data-export-menu]'))
|
|
||||||
this.closeExportMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
getDetachLabel(): string {
|
getDetachLabel(): string {
|
||||||
return this.detached() ? 'Dock' : 'Detach';
|
return this.detached() ? 'Dock' : 'Detach';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,16 @@
|
|||||||
<header class="border-b border-border p-5">
|
<header class="border-b border-border p-5">
|
||||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 id="screen-share-source-picker-title" class="text-lg font-semibold text-foreground">
|
<h2
|
||||||
|
id="screen-share-source-picker-title"
|
||||||
|
class="text-lg font-semibold text-foreground"
|
||||||
|
>
|
||||||
Choose what to share
|
Choose what to share
|
||||||
</h2>
|
</h2>
|
||||||
<p id="screen-share-source-picker-description" class="mt-1 text-sm text-muted-foreground">
|
<p
|
||||||
|
id="screen-share-source-picker-description"
|
||||||
|
class="mt-1 text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
Select a screen or window to start sharing.
|
Select a screen or window to start sharing.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,7 +61,11 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 flex flex-wrap gap-2" role="tablist" aria-label="Share source type">
|
<div
|
||||||
|
class="mt-4 flex flex-wrap gap-2"
|
||||||
|
role="tablist"
|
||||||
|
aria-label="Share source type"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
class="inline-flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
@@ -129,7 +139,11 @@
|
|||||||
<div class="flex items-start justify-between gap-3">
|
<div class="flex items-start justify-between gap-3">
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<span class="screen-share-source-picker__preview">
|
<span class="screen-share-source-picker__preview">
|
||||||
<img [ngSrc]="source.thumbnail" [alt]="source.name" fill />
|
<img
|
||||||
|
[ngSrc]="source.thumbnail"
|
||||||
|
[alt]="source.name"
|
||||||
|
fill
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<p class="mt-3 truncate font-medium">{{ source.name }}</p>
|
<p class="mt-3 truncate font-medium">{{ source.name }}</p>
|
||||||
@@ -156,13 +170,13 @@
|
|||||||
} @else {
|
} @else {
|
||||||
<div class="flex min-h-52 items-center justify-center px-5 py-8 text-center">
|
<div class="flex min-h-52 items-center justify-center px-5 py-8 text-center">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-foreground">
|
<p class="text-sm font-medium text-foreground">No {{ activeTab() === 'screen' ? 'screens' : 'windows' }} available</p>
|
||||||
No {{ activeTab() === 'screen' ? 'screens' : 'windows' }} available
|
|
||||||
</p>
|
|
||||||
<p class="mt-1 text-sm text-muted-foreground">
|
<p class="mt-1 text-sm text-muted-foreground">
|
||||||
{{ activeTab() === 'screen'
|
{{
|
||||||
? 'No displays were reported by Electron right now.'
|
activeTab() === 'screen'
|
||||||
: 'Restore the window you want to share and try again.' }}
|
? 'No displays were reported by Electron right now.'
|
||||||
|
: 'Restore the window you want to share and try again.'
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Sync-lifecycle effects for the messages store slice.
|
* Sync-lifecycle effects for the messages store slice.
|
||||||
*
|
*
|
||||||
* These effects manage the periodic sync polling, peer-connect
|
* These effects manage the periodic sync polling, peer-connect
|
||||||
* handshakes, and join-room kickoff that keep message databases
|
* handshakes, and room-activation kickoff that keep message databases
|
||||||
* in sync across peers.
|
* in sync across peers.
|
||||||
*
|
*
|
||||||
* Extracted from the monolithic MessagesEffects to keep each
|
* Extracted from the monolithic MessagesEffects to keep each
|
||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
exhaustMap,
|
exhaustMap,
|
||||||
switchMap,
|
switchMap,
|
||||||
repeat,
|
repeat,
|
||||||
takeUntil
|
startWith
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { MessagesActions } from './messages.actions';
|
import { MessagesActions } from './messages.actions';
|
||||||
import { RoomsActions } from '../rooms/rooms.actions';
|
import { RoomsActions } from '../rooms/rooms.actions';
|
||||||
@@ -103,13 +103,13 @@ export class MessagesSyncEffects {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the user joins a room, sends a summary and inventory
|
* When the user joins or views a room, sends a summary and inventory
|
||||||
* request to every already-connected peer.
|
* request to every already-connected peer.
|
||||||
*/
|
*/
|
||||||
joinRoomSyncKickoff$ = createEffect(
|
roomActivationSyncKickoff$ = createEffect(
|
||||||
() =>
|
() =>
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(RoomsActions.joinRoomSuccess),
|
ofType(RoomsActions.joinRoomSuccess, RoomsActions.viewServerSuccess),
|
||||||
withLatestFrom(this.store.select(selectCurrentRoom)),
|
withLatestFrom(this.store.select(selectCurrentRoom)),
|
||||||
mergeMap(([{ room }, currentRoom]) => {
|
mergeMap(([{ room }, currentRoom]) => {
|
||||||
const activeRoom = currentRoom || room;
|
const activeRoom = currentRoom || room;
|
||||||
@@ -152,63 +152,83 @@ export class MessagesSyncEffects {
|
|||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the polling cadence when the active room changes so the next
|
||||||
|
* room does not inherit a stale slow-poll delay.
|
||||||
|
*/
|
||||||
|
resetPeriodicSyncOnRoomActivation$ = createEffect(
|
||||||
|
() =>
|
||||||
|
this.actions$.pipe(
|
||||||
|
ofType(RoomsActions.joinRoomSuccess, RoomsActions.viewServerSuccess),
|
||||||
|
tap(() => {
|
||||||
|
this.lastSyncClean = false;
|
||||||
|
this.syncReset$.next();
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternates between fast (10 s) and slow (15 min) sync intervals.
|
* Alternates between fast (10 s) and slow (15 min) sync intervals.
|
||||||
* Sends inventory requests to all connected peers.
|
* Sends inventory requests to all connected peers for the active room.
|
||||||
*/
|
*/
|
||||||
periodicSyncPoll$ = createEffect(() =>
|
periodicSyncPoll$ = createEffect(() =>
|
||||||
timer(SYNC_POLL_FAST_MS).pipe(
|
this.syncReset$.pipe(
|
||||||
repeat({
|
startWith(undefined),
|
||||||
delay: () =>
|
switchMap(() =>
|
||||||
timer(
|
timer(SYNC_POLL_FAST_MS).pipe(
|
||||||
this.lastSyncClean ? SYNC_POLL_SLOW_MS : SYNC_POLL_FAST_MS
|
repeat({
|
||||||
)
|
delay: () =>
|
||||||
}),
|
timer(
|
||||||
takeUntil(this.syncReset$),
|
this.lastSyncClean ? SYNC_POLL_SLOW_MS : SYNC_POLL_FAST_MS
|
||||||
withLatestFrom(this.store.select(selectCurrentRoom)),
|
)
|
||||||
filter(
|
}),
|
||||||
([, room]) =>
|
withLatestFrom(this.store.select(selectCurrentRoom)),
|
||||||
!!room && this.webrtc.getConnectedPeers().length > 0
|
filter(
|
||||||
),
|
([, room]) =>
|
||||||
exhaustMap(([, room]) => {
|
!!room && this.webrtc.getConnectedPeers().length > 0
|
||||||
const peers = this.webrtc.getConnectedPeers();
|
),
|
||||||
|
exhaustMap(([, room]) => {
|
||||||
|
const peers = this.webrtc.getConnectedPeers();
|
||||||
|
|
||||||
if (!room || peers.length === 0) {
|
if (!room || peers.length === 0) {
|
||||||
return of(MessagesActions.syncComplete());
|
return of(MessagesActions.syncComplete());
|
||||||
}
|
|
||||||
|
|
||||||
return from(
|
|
||||||
this.db.getMessages(room.id, INVENTORY_LIMIT, 0)
|
|
||||||
).pipe(
|
|
||||||
map(() => {
|
|
||||||
for (const pid of peers) {
|
|
||||||
try {
|
|
||||||
this.webrtc.sendToPeer(pid, {
|
|
||||||
type: 'chat-inventory-request',
|
|
||||||
roomId: room.id
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
this.debugging.warn('messages', 'Failed to request peer inventory during sync poll', {
|
|
||||||
error,
|
|
||||||
peerId: pid,
|
|
||||||
roomId: room.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessagesActions.startSync();
|
return from(
|
||||||
}),
|
this.db.getMessages(room.id, INVENTORY_LIMIT, 0)
|
||||||
catchError((error) => {
|
).pipe(
|
||||||
this.lastSyncClean = false;
|
map(() => {
|
||||||
this.debugging.warn('messages', 'Periodic sync poll failed', {
|
for (const pid of peers) {
|
||||||
error,
|
try {
|
||||||
roomId: room.id
|
this.webrtc.sendToPeer(pid, {
|
||||||
});
|
type: 'chat-inventory-request',
|
||||||
|
roomId: room.id
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.debugging.warn('messages', 'Failed to request peer inventory during sync poll', {
|
||||||
|
error,
|
||||||
|
peerId: pid,
|
||||||
|
roomId: room.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return of(MessagesActions.syncComplete());
|
return MessagesActions.startSync();
|
||||||
|
}),
|
||||||
|
catchError((error) => {
|
||||||
|
this.lastSyncClean = false;
|
||||||
|
this.debugging.warn('messages', 'Periodic sync poll failed', {
|
||||||
|
error,
|
||||||
|
roomId: room.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return of(MessagesActions.syncComplete());
|
||||||
|
})
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
})
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -348,7 +348,11 @@ export class RoomsEffects {
|
|||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(RoomsActions.createRoomSuccess, RoomsActions.joinRoomSuccess),
|
ofType(RoomsActions.createRoomSuccess, RoomsActions.joinRoomSuccess),
|
||||||
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectSavedRooms)),
|
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectSavedRooms)),
|
||||||
tap(([{ room }, user, savedRooms]) => {
|
tap(([
|
||||||
|
{ room },
|
||||||
|
user,
|
||||||
|
savedRooms
|
||||||
|
]) => {
|
||||||
this.connectToRoomSignaling(room, user ?? null, undefined, savedRooms);
|
this.connectToRoomSignaling(room, user ?? null, undefined, savedRooms);
|
||||||
|
|
||||||
this.router.navigate(['/room', room.id]);
|
this.router.navigate(['/room', room.id]);
|
||||||
@@ -362,7 +366,11 @@ export class RoomsEffects {
|
|||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(RoomsActions.viewServer),
|
ofType(RoomsActions.viewServer),
|
||||||
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectSavedRooms)),
|
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectSavedRooms)),
|
||||||
switchMap(([{ room }, user, savedRooms]) => {
|
switchMap(([
|
||||||
|
{ room },
|
||||||
|
user,
|
||||||
|
savedRooms
|
||||||
|
]) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return of(RoomsActions.joinRoomFailure({ error: 'Not logged in' }));
|
return of(RoomsActions.joinRoomFailure({ error: 'Not logged in' }));
|
||||||
}
|
}
|
||||||
@@ -573,7 +581,6 @@ export class RoomsEffects {
|
|||||||
hasPassword: nextHasPassword,
|
hasPassword: nextHasPassword,
|
||||||
maxUsers: settings.maxUsers ?? room.maxUsers
|
maxUsers: settings.maxUsers ?? room.maxUsers
|
||||||
};
|
};
|
||||||
|
|
||||||
const localRoomUpdates: Partial<Room> = {
|
const localRoomUpdates: Partial<Room> = {
|
||||||
...updatedSettings,
|
...updatedSettings,
|
||||||
password: hasPasswordUpdate ? (normalizedPassword || undefined) : room.password,
|
password: hasPasswordUpdate ? (normalizedPassword || undefined) : room.password,
|
||||||
@@ -837,10 +844,7 @@ export class RoomsEffects {
|
|||||||
return EMPTY;
|
return EMPTY;
|
||||||
|
|
||||||
this.knownVoiceUsers.delete(signalingMessage.oderId);
|
this.knownVoiceUsers.delete(signalingMessage.oderId);
|
||||||
return [
|
return [RoomsActions.setSignalServerReconnecting({ isReconnecting: false }), UsersActions.userLeft({ userId: signalingMessage.oderId })];
|
||||||
RoomsActions.setSignalServerReconnecting({ isReconnecting: false }),
|
|
||||||
UsersActions.userLeft({ userId: signalingMessage.oderId })
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'access_denied': {
|
case 'access_denied': {
|
||||||
|
|||||||
Reference in New Issue
Block a user