All checks were successful
Queue Release Build / prepare (push) Successful in 14s
Deploy Web Apps / deploy (push) Successful in 14m39s
Queue Release Build / build-linux (push) Successful in 40m59s
Queue Release Build / build-windows (push) Successful in 28m59s
Queue Release Build / finalize (push) Successful in 1m58s
345 lines
13 KiB
TypeScript
345 lines
13 KiB
TypeScript
import { contextBridge, ipcRenderer } from 'electron';
|
|
import { Command, Query } from './cqrs/types';
|
|
|
|
const LINUX_SCREEN_SHARE_MONITOR_AUDIO_CHUNK_CHANNEL = 'linux-screen-share-monitor-audio-chunk';
|
|
const LINUX_SCREEN_SHARE_MONITOR_AUDIO_ENDED_CHANNEL = 'linux-screen-share-monitor-audio-ended';
|
|
const AUTO_UPDATE_STATE_CHANGED_CHANNEL = 'auto-update-state-changed';
|
|
const DEEP_LINK_RECEIVED_CHANNEL = 'deep-link-received';
|
|
const WINDOW_STATE_CHANGED_CHANNEL = 'window-state-changed';
|
|
|
|
export interface LinuxScreenShareAudioRoutingInfo {
|
|
available: boolean;
|
|
active: boolean;
|
|
monitorCaptureSupported: boolean;
|
|
screenShareSinkName: string;
|
|
screenShareMonitorSourceName: string;
|
|
voiceSinkName: string;
|
|
reason?: string;
|
|
}
|
|
|
|
export interface LinuxScreenShareMonitorCaptureInfo {
|
|
bitsPerSample: number;
|
|
captureId: string;
|
|
channelCount: number;
|
|
sampleRate: number;
|
|
sourceName: string;
|
|
}
|
|
|
|
export interface LinuxScreenShareMonitorAudioChunkPayload {
|
|
captureId: string;
|
|
chunk: Uint8Array;
|
|
}
|
|
|
|
export interface LinuxScreenShareMonitorAudioEndedPayload {
|
|
captureId: string;
|
|
reason?: string;
|
|
}
|
|
|
|
export interface ClipboardFilePayload {
|
|
data: string;
|
|
lastModified: number;
|
|
mime: string;
|
|
name: string;
|
|
path?: string;
|
|
}
|
|
|
|
export type DesktopUpdateServerVersionStatus = 'unknown' | 'reported' | 'missing' | 'unavailable';
|
|
|
|
export interface DesktopUpdateServerContext {
|
|
manifestUrls: string[];
|
|
serverVersion: string | null;
|
|
serverVersionStatus: DesktopUpdateServerVersionStatus;
|
|
}
|
|
|
|
export interface DesktopUpdateServerHealthSnapshot {
|
|
manifestUrl: string | null;
|
|
serverVersion: string | null;
|
|
serverVersionStatus: DesktopUpdateServerVersionStatus;
|
|
}
|
|
|
|
export interface DesktopUpdateState {
|
|
autoUpdateMode: 'auto' | 'off' | 'version';
|
|
availableVersions: string[];
|
|
configuredManifestUrls: string[];
|
|
currentVersion: string;
|
|
defaultManifestUrls: string[];
|
|
isSupported: boolean;
|
|
lastCheckedAt: number | null;
|
|
latestVersion: string | null;
|
|
manifestUrl: string | null;
|
|
manifestUrls: string[];
|
|
minimumServerVersion: string | null;
|
|
preferredVersion: string | null;
|
|
restartRequired: boolean;
|
|
serverBlocked: boolean;
|
|
serverBlockMessage: string | null;
|
|
serverVersion: string | null;
|
|
serverVersionStatus: DesktopUpdateServerVersionStatus;
|
|
status:
|
|
| 'idle'
|
|
| 'disabled'
|
|
| 'checking'
|
|
| 'downloading'
|
|
| 'up-to-date'
|
|
| 'restart-required'
|
|
| 'unsupported'
|
|
| 'no-manifest'
|
|
| 'target-unavailable'
|
|
| 'target-older-than-installed'
|
|
| 'error';
|
|
statusMessage: string | null;
|
|
targetVersion: string | null;
|
|
}
|
|
|
|
export interface DesktopNotificationPayload {
|
|
body: string;
|
|
requestAttention: boolean;
|
|
title: string;
|
|
}
|
|
|
|
export interface WindowStateSnapshot {
|
|
isFocused: boolean;
|
|
isMinimized: boolean;
|
|
}
|
|
|
|
export interface SavedThemeFileDescriptor {
|
|
fileName: string;
|
|
modifiedAt: number;
|
|
path: string;
|
|
}
|
|
|
|
function readLinuxDisplayServer(): string {
|
|
if (process.platform !== 'linux') {
|
|
return 'N/A';
|
|
}
|
|
|
|
try {
|
|
const displayServer = ipcRenderer.sendSync('get-linux-display-server');
|
|
|
|
return typeof displayServer === 'string' && displayServer.trim().length > 0
|
|
? displayServer
|
|
: 'Unknown (Linux)';
|
|
} catch {
|
|
return 'Unknown (Linux)';
|
|
}
|
|
}
|
|
|
|
export interface ContextMenuParams {
|
|
posX: number;
|
|
posY: number;
|
|
isEditable: boolean;
|
|
selectionText: string;
|
|
linkURL: string;
|
|
mediaType: string;
|
|
srcURL: string;
|
|
editFlags: {
|
|
canCut: boolean;
|
|
canCopy: boolean;
|
|
canPaste: boolean;
|
|
canSelectAll: boolean;
|
|
};
|
|
}
|
|
|
|
export interface ElectronAPI {
|
|
linuxDisplayServer: string;
|
|
minimizeWindow: () => void;
|
|
maximizeWindow: () => void;
|
|
closeWindow: () => void;
|
|
|
|
openExternal: (url: string) => Promise<boolean>;
|
|
getSources: () => Promise<{ id: string; name: string; thumbnail: string }[]>;
|
|
prepareLinuxScreenShareAudioRouting: () => Promise<LinuxScreenShareAudioRoutingInfo>;
|
|
activateLinuxScreenShareAudioRouting: () => Promise<LinuxScreenShareAudioRoutingInfo>;
|
|
deactivateLinuxScreenShareAudioRouting: () => Promise<boolean>;
|
|
startLinuxScreenShareMonitorCapture: () => Promise<LinuxScreenShareMonitorCaptureInfo>;
|
|
stopLinuxScreenShareMonitorCapture: (captureId?: string) => Promise<boolean>;
|
|
onLinuxScreenShareMonitorAudioChunk: (listener: (payload: LinuxScreenShareMonitorAudioChunkPayload) => void) => () => void;
|
|
onLinuxScreenShareMonitorAudioEnded: (listener: (payload: LinuxScreenShareMonitorAudioEndedPayload) => void) => () => void;
|
|
getAppDataPath: () => Promise<string>;
|
|
getSavedThemesPath: () => Promise<string>;
|
|
listSavedThemes: () => Promise<SavedThemeFileDescriptor[]>;
|
|
readSavedTheme: (fileName: string) => Promise<string>;
|
|
writeSavedTheme: (fileName: string, text: string) => Promise<boolean>;
|
|
deleteSavedTheme: (fileName: string) => Promise<boolean>;
|
|
consumePendingDeepLink: () => Promise<string | null>;
|
|
getDesktopSettings: () => Promise<{
|
|
autoUpdateMode: 'auto' | 'off' | 'version';
|
|
autoStart: boolean;
|
|
closeToTray: boolean;
|
|
hardwareAcceleration: boolean;
|
|
manifestUrls: string[];
|
|
preferredVersion: string | null;
|
|
runtimeHardwareAcceleration: boolean;
|
|
restartRequired: boolean;
|
|
}>;
|
|
showDesktopNotification: (payload: DesktopNotificationPayload) => Promise<boolean>;
|
|
requestWindowAttention: () => Promise<boolean>;
|
|
clearWindowAttention: () => Promise<boolean>;
|
|
onWindowStateChanged: (listener: (state: WindowStateSnapshot) => void) => () => void;
|
|
getAutoUpdateState: () => Promise<DesktopUpdateState>;
|
|
getAutoUpdateServerHealth: (serverUrl: string) => Promise<DesktopUpdateServerHealthSnapshot>;
|
|
configureAutoUpdateContext: (context: Partial<DesktopUpdateServerContext>) => Promise<DesktopUpdateState>;
|
|
checkForAppUpdates: () => Promise<DesktopUpdateState>;
|
|
restartToApplyUpdate: () => Promise<boolean>;
|
|
onAutoUpdateStateChanged: (listener: (state: DesktopUpdateState) => void) => () => void;
|
|
setDesktopSettings: (patch: {
|
|
autoUpdateMode?: 'auto' | 'off' | 'version';
|
|
autoStart?: boolean;
|
|
closeToTray?: boolean;
|
|
hardwareAcceleration?: boolean;
|
|
manifestUrls?: string[];
|
|
preferredVersion?: string | null;
|
|
vaapiVideoEncode?: boolean;
|
|
}) => Promise<{
|
|
autoUpdateMode: 'auto' | 'off' | 'version';
|
|
autoStart: boolean;
|
|
closeToTray: boolean;
|
|
hardwareAcceleration: boolean;
|
|
manifestUrls: string[];
|
|
preferredVersion: string | null;
|
|
runtimeHardwareAcceleration: boolean;
|
|
restartRequired: boolean;
|
|
}>;
|
|
relaunchApp: () => Promise<boolean>;
|
|
onDeepLinkReceived: (listener: (url: string) => void) => () => void;
|
|
readClipboardFiles: () => Promise<ClipboardFilePayload[]>;
|
|
readFile: (filePath: string) => Promise<string>;
|
|
writeFile: (filePath: string, data: string) => Promise<boolean>;
|
|
saveFileAs: (defaultFileName: string, data: string) => Promise<{ saved: boolean; cancelled: boolean }>;
|
|
fileExists: (filePath: string) => Promise<boolean>;
|
|
deleteFile: (filePath: string) => Promise<boolean>;
|
|
ensureDir: (dirPath: string) => Promise<boolean>;
|
|
|
|
onContextMenu: (listener: (params: ContextMenuParams) => void) => () => void;
|
|
copyImageToClipboard: (srcURL: string) => Promise<boolean>;
|
|
|
|
command: <T = unknown>(command: Command) => Promise<T>;
|
|
query: <T = unknown>(query: Query) => Promise<T>;
|
|
}
|
|
|
|
const electronAPI: ElectronAPI = {
|
|
linuxDisplayServer: readLinuxDisplayServer(),
|
|
minimizeWindow: () => ipcRenderer.send('window-minimize'),
|
|
maximizeWindow: () => ipcRenderer.send('window-maximize'),
|
|
closeWindow: () => ipcRenderer.send('window-close'),
|
|
|
|
openExternal: (url) => ipcRenderer.invoke('open-external', url),
|
|
getSources: () => ipcRenderer.invoke('get-sources'),
|
|
prepareLinuxScreenShareAudioRouting: () => ipcRenderer.invoke('prepare-linux-screen-share-audio-routing'),
|
|
activateLinuxScreenShareAudioRouting: () => ipcRenderer.invoke('activate-linux-screen-share-audio-routing'),
|
|
deactivateLinuxScreenShareAudioRouting: () => ipcRenderer.invoke('deactivate-linux-screen-share-audio-routing'),
|
|
startLinuxScreenShareMonitorCapture: () => ipcRenderer.invoke('start-linux-screen-share-monitor-capture'),
|
|
stopLinuxScreenShareMonitorCapture: (captureId) => ipcRenderer.invoke('stop-linux-screen-share-monitor-capture', captureId),
|
|
onLinuxScreenShareMonitorAudioChunk: (listener) => {
|
|
const wrappedListener = (_event: Electron.IpcRendererEvent, payload: LinuxScreenShareMonitorAudioChunkPayload) => {
|
|
const chunk = payload.chunk instanceof Uint8Array
|
|
? payload.chunk
|
|
: Uint8Array.from((payload as { chunk?: Iterable<number> }).chunk || []);
|
|
|
|
listener({
|
|
...payload,
|
|
chunk
|
|
});
|
|
};
|
|
|
|
ipcRenderer.on(LINUX_SCREEN_SHARE_MONITOR_AUDIO_CHUNK_CHANNEL, wrappedListener);
|
|
|
|
return () => {
|
|
ipcRenderer.removeListener(LINUX_SCREEN_SHARE_MONITOR_AUDIO_CHUNK_CHANNEL, wrappedListener);
|
|
};
|
|
},
|
|
onLinuxScreenShareMonitorAudioEnded: (listener) => {
|
|
const wrappedListener = (_event: Electron.IpcRendererEvent, payload: LinuxScreenShareMonitorAudioEndedPayload) => {
|
|
listener(payload);
|
|
};
|
|
|
|
ipcRenderer.on(LINUX_SCREEN_SHARE_MONITOR_AUDIO_ENDED_CHANNEL, wrappedListener);
|
|
|
|
return () => {
|
|
ipcRenderer.removeListener(LINUX_SCREEN_SHARE_MONITOR_AUDIO_ENDED_CHANNEL, wrappedListener);
|
|
};
|
|
},
|
|
getAppDataPath: () => ipcRenderer.invoke('get-app-data-path'),
|
|
getSavedThemesPath: () => ipcRenderer.invoke('get-saved-themes-path'),
|
|
listSavedThemes: () => ipcRenderer.invoke('list-saved-themes'),
|
|
readSavedTheme: (fileName) => ipcRenderer.invoke('read-saved-theme', fileName),
|
|
writeSavedTheme: (fileName, text) => ipcRenderer.invoke('write-saved-theme', fileName, text),
|
|
deleteSavedTheme: (fileName) => ipcRenderer.invoke('delete-saved-theme', fileName),
|
|
consumePendingDeepLink: () => ipcRenderer.invoke('consume-pending-deep-link'),
|
|
getDesktopSettings: () => ipcRenderer.invoke('get-desktop-settings'),
|
|
showDesktopNotification: (payload) => ipcRenderer.invoke('show-desktop-notification', payload),
|
|
requestWindowAttention: () => ipcRenderer.invoke('request-window-attention'),
|
|
clearWindowAttention: () => ipcRenderer.invoke('clear-window-attention'),
|
|
onWindowStateChanged: (listener) => {
|
|
const wrappedListener = (_event: Electron.IpcRendererEvent, state: WindowStateSnapshot) => {
|
|
listener(state);
|
|
};
|
|
|
|
ipcRenderer.on(WINDOW_STATE_CHANGED_CHANNEL, wrappedListener);
|
|
|
|
return () => {
|
|
ipcRenderer.removeListener(WINDOW_STATE_CHANGED_CHANNEL, wrappedListener);
|
|
};
|
|
},
|
|
getAutoUpdateState: () => ipcRenderer.invoke('get-auto-update-state'),
|
|
getAutoUpdateServerHealth: (serverUrl) => ipcRenderer.invoke('get-auto-update-server-health', serverUrl),
|
|
configureAutoUpdateContext: (context) => ipcRenderer.invoke('configure-auto-update-context', context),
|
|
checkForAppUpdates: () => ipcRenderer.invoke('check-for-app-updates'),
|
|
restartToApplyUpdate: () => ipcRenderer.invoke('restart-to-apply-update'),
|
|
onAutoUpdateStateChanged: (listener) => {
|
|
const wrappedListener = (_event: Electron.IpcRendererEvent, state: DesktopUpdateState) => {
|
|
listener(state);
|
|
};
|
|
|
|
ipcRenderer.on(AUTO_UPDATE_STATE_CHANGED_CHANNEL, wrappedListener);
|
|
|
|
return () => {
|
|
ipcRenderer.removeListener(AUTO_UPDATE_STATE_CHANGED_CHANNEL, wrappedListener);
|
|
};
|
|
},
|
|
setDesktopSettings: (patch) => ipcRenderer.invoke('set-desktop-settings', patch),
|
|
relaunchApp: () => ipcRenderer.invoke('relaunch-app'),
|
|
onDeepLinkReceived: (listener) => {
|
|
const wrappedListener = (_event: Electron.IpcRendererEvent, url: string) => {
|
|
listener(url);
|
|
};
|
|
|
|
ipcRenderer.on(DEEP_LINK_RECEIVED_CHANNEL, wrappedListener);
|
|
|
|
return () => {
|
|
ipcRenderer.removeListener(DEEP_LINK_RECEIVED_CHANNEL, wrappedListener);
|
|
};
|
|
},
|
|
readClipboardFiles: () => ipcRenderer.invoke('read-clipboard-files'),
|
|
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
|
|
writeFile: (filePath, data) => ipcRenderer.invoke('write-file', filePath, data),
|
|
saveFileAs: (defaultFileName, data) => ipcRenderer.invoke('save-file-as', defaultFileName, data),
|
|
fileExists: (filePath) => ipcRenderer.invoke('file-exists', filePath),
|
|
deleteFile: (filePath) => ipcRenderer.invoke('delete-file', filePath),
|
|
ensureDir: (dirPath) => ipcRenderer.invoke('ensure-dir', dirPath),
|
|
|
|
onContextMenu: (listener) => {
|
|
const wrappedListener = (_event: Electron.IpcRendererEvent, params: ContextMenuParams) => {
|
|
listener(params);
|
|
};
|
|
|
|
ipcRenderer.on('show-context-menu', wrappedListener);
|
|
|
|
return () => {
|
|
ipcRenderer.removeListener('show-context-menu', wrappedListener);
|
|
};
|
|
},
|
|
copyImageToClipboard: (srcURL) => ipcRenderer.invoke('copy-image-to-clipboard', srcURL),
|
|
|
|
command: (command) => ipcRenderer.invoke('cqrs:command', command),
|
|
query: (query) => ipcRenderer.invoke('cqrs:query', query)
|
|
};
|
|
|
|
contextBridge.exposeInMainWorld('electronAPI', electronAPI);
|
|
|
|
declare global {
|
|
interface Window {
|
|
electronAPI: ElectronAPI;
|
|
}
|
|
}
|