Compare commits
3 Commits
7b9936dcc4
...
824b1fec65
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
824b1fec65 | ||
|
|
41d7a89830 | ||
|
|
88288317ab |
@@ -16,7 +16,10 @@ export function ActiveCall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getConnectingClass = () => {
|
const getConnectingClass = () => {
|
||||||
if(callState === CallState.CONNECTING){
|
if(callState === CallState.CONNECTING
|
||||||
|
|| callState === CallState.INCOMING
|
||||||
|
|| callState === CallState.KEY_EXCHANGE
|
||||||
|
|| callState === CallState.WEB_RTC_EXCHANGE){
|
||||||
return classes.connecting;
|
return classes.connecting;
|
||||||
}
|
}
|
||||||
if(callState === CallState.ACTIVE){
|
if(callState === CallState.ACTIVE){
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ export function Call(props: CallProps) {
|
|||||||
<Flex style={{
|
<Flex style={{
|
||||||
cursor: 'pointer'
|
cursor: 'pointer'
|
||||||
}} onClick={() => setShowCallView(false)} justify={'center'} align={'center'}>
|
}} onClick={() => setShowCallView(false)} justify={'center'} align={'center'}>
|
||||||
<IconChevronLeft size={20}></IconChevronLeft>
|
<IconChevronLeft color="white" size={20}></IconChevronLeft>
|
||||||
<Text fw={500}>Back</Text>
|
<Text fw={500} c={'white'}>Back</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Popover width={300} withArrow>
|
<Popover width={300} disabled={getKeyCast() == ''} withArrow>
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<IconQrcode size={24}></IconQrcode>
|
<IconQrcode color={getKeyCast() == '' ? 'gray' : 'white'} size={24}></IconQrcode>
|
||||||
</Popover.Target>
|
</Popover.Target>
|
||||||
<Popover.Dropdown p={'xs'}>
|
<Popover.Dropdown p={'xs'}>
|
||||||
<Flex direction={'row'} align={'center'} gap={'xs'}>
|
<Flex direction={'row'} align={'center'} gap={'xs'}>
|
||||||
|
|||||||
78
app/hooks/useSound.ts
Normal file
78
app/hooks/useSound.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
export function useSound() {
|
||||||
|
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||||
|
const loopingAudioRef = useRef<HTMLAudioElement | null>(null);
|
||||||
|
|
||||||
|
const stopSound = () => {
|
||||||
|
if (!audioRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
audioRef.current.pause();
|
||||||
|
audioRef.current.currentTime = 0;
|
||||||
|
audioRef.current.removeAttribute("src");
|
||||||
|
audioRef.current.load();
|
||||||
|
};
|
||||||
|
|
||||||
|
const playSound = (sound : string, loop: boolean = false) => {
|
||||||
|
try {
|
||||||
|
if(loop){
|
||||||
|
if (!loopingAudioRef.current) {
|
||||||
|
loopingAudioRef.current = new Audio();
|
||||||
|
loopingAudioRef.current.volume = 0.1;
|
||||||
|
loopingAudioRef.current.preload = "auto";
|
||||||
|
loopingAudioRef.current.loop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = window.mediaApi.getSoundUrl(sound);
|
||||||
|
const player = loopingAudioRef.current;
|
||||||
|
|
||||||
|
player.src = url;
|
||||||
|
const playPromise = player.play();
|
||||||
|
if (playPromise) {
|
||||||
|
void playPromise.catch((e) => {
|
||||||
|
console.error("Failed to play looping UI sound:", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!audioRef.current) {
|
||||||
|
audioRef.current = new Audio();
|
||||||
|
audioRef.current.volume = 0.1;
|
||||||
|
audioRef.current.preload = "auto";
|
||||||
|
audioRef.current.loop = loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = window.mediaApi.getSoundUrl(sound);
|
||||||
|
const player = audioRef.current;
|
||||||
|
|
||||||
|
stopSound();
|
||||||
|
|
||||||
|
player.src = url;
|
||||||
|
const playPromise = player.play();
|
||||||
|
if (playPromise) {
|
||||||
|
void playPromise.catch((e) => {
|
||||||
|
console.error("Failed to play UI sound:", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to prepare UI sound:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopLoopSound = () => {
|
||||||
|
if (!loopingAudioRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loopingAudioRef.current.pause();
|
||||||
|
loopingAudioRef.current.currentTime = 0;
|
||||||
|
loopingAudioRef.current.removeAttribute("src");
|
||||||
|
loopingAudioRef.current.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
playSound,
|
||||||
|
stopSound,
|
||||||
|
stopLoopSound
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,10 +20,19 @@ const useWindow = () => {
|
|||||||
window.api.send('window-theme', theme);
|
window.api.send('window-theme', theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setWindowPriority = (isTop: boolean) => {
|
||||||
|
if(isTop){
|
||||||
|
window.api.invoke('window-top');
|
||||||
|
} else {
|
||||||
|
window.api.invoke('window-priority-normal');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setSize,
|
setSize,
|
||||||
setResizeble,
|
setResizeble,
|
||||||
setTheme
|
setTheme,
|
||||||
|
setWindowPriority
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import { PacketWebRTC, WebRTCSignalType } from "../ProtocolProvider/protocol/pac
|
|||||||
import { PacketIceServers } from "../ProtocolProvider/protocol/packets/packet.ice.servers";
|
import { PacketIceServers } from "../ProtocolProvider/protocol/packets/packet.ice.servers";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { Button, Flex, Text } from "@mantine/core";
|
import { Button, Flex, Text } from "@mantine/core";
|
||||||
|
import { useSound } from "@/app/hooks/useSound";
|
||||||
|
import useWindow from "@/app/hooks/useWindow";
|
||||||
|
|
||||||
export interface CallContextValue {
|
export interface CallContextValue {
|
||||||
call: (callable: string) => void;
|
call: (callable: string) => void;
|
||||||
@@ -59,8 +60,9 @@ export interface CallProviderProps {
|
|||||||
export function CallProvider(props : CallProviderProps) {
|
export function CallProvider(props : CallProviderProps) {
|
||||||
const [activeCall, setActiveCall] = useState<string>("");
|
const [activeCall, setActiveCall] = useState<string>("");
|
||||||
const [callState, setCallState] = useState<CallState>(CallState.ENDED);
|
const [callState, setCallState] = useState<CallState>(CallState.ENDED);
|
||||||
const [muted, setMuted] = useState<boolean>(false);
|
const [muted, setMutedState] = useState<boolean>(false);
|
||||||
const [sound, setSound] = useState<boolean>(true);
|
const [sound, setSoundState] = useState<boolean>(true);
|
||||||
|
const durationIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const [duration, setDuration] = useState<number>(0);
|
const [duration, setDuration] = useState<number>(0);
|
||||||
const [showCallView, setShowCallView] = useState<boolean>(callState == CallState.INCOMING);
|
const [showCallView, setShowCallView] = useState<boolean>(callState == CallState.INCOMING);
|
||||||
const {info} = useConsoleLogger("CallProvider");
|
const {info} = useConsoleLogger("CallProvider");
|
||||||
@@ -73,8 +75,26 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
const roleRef = useRef<CallRole | null>(null);
|
const roleRef = useRef<CallRole | null>(null);
|
||||||
const [sharedSecret, setSharedSecret] = useState<string>("");
|
const [sharedSecret, setSharedSecret] = useState<string>("");
|
||||||
const iceServersRef = useRef<RTCIceServer[]>([]);
|
const iceServersRef = useRef<RTCIceServer[]>([]);
|
||||||
const remoteAudioRef = useRef<HTMLAudioElement>(null);
|
const remoteAudioRef = useRef<HTMLAudioElement | null>(null);
|
||||||
const iceCandidatesBufferRef = useRef<RTCIceCandidate[]>([]);
|
const iceCandidatesBufferRef = useRef<RTCIceCandidate[]>([]);
|
||||||
|
const mutedRef = useRef<boolean>(false);
|
||||||
|
const soundRef = useRef<boolean>(true);
|
||||||
|
|
||||||
|
const {playSound, stopSound, stopLoopSound} = useSound();
|
||||||
|
const {setWindowPriority} = useWindow();
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(callState == CallState.ACTIVE){
|
||||||
|
stopLoopSound();
|
||||||
|
stopSound();
|
||||||
|
playSound("connected.mp3");
|
||||||
|
setWindowPriority(false);
|
||||||
|
durationIntervalRef.current = setInterval(() => {
|
||||||
|
setDuration(prev => prev + 1);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}, [callState]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/**
|
/**
|
||||||
@@ -83,6 +103,16 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
*/
|
*/
|
||||||
let packet = new PacketIceServers();
|
let packet = new PacketIceServers();
|
||||||
send(packet);
|
send(packet);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stopSound();
|
||||||
|
if (remoteAudioRef.current) {
|
||||||
|
remoteAudioRef.current.pause();
|
||||||
|
remoteAudioRef.current.srcObject = null;
|
||||||
|
}
|
||||||
|
peerConnectionRef.current?.close();
|
||||||
|
peerConnectionRef.current = null;
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
usePacket(28, async (packet: PacketIceServers) => {
|
usePacket(28, async (packet: PacketIceServers) => {
|
||||||
@@ -168,43 +198,11 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
usePacket(26, async (packet: PacketSignalPeer) => {
|
usePacket(26, async (packet: PacketSignalPeer) => {
|
||||||
const signalType = packet.getSignalType();
|
const signalType = packet.getSignalType();
|
||||||
if(signalType == SignalType.END_CALL_BECAUSE_BUSY) {
|
if(signalType == SignalType.END_CALL_BECAUSE_BUSY) {
|
||||||
modals.open({
|
openCallsModal("Line is busy, the user is currently on another call. Please try again later.");
|
||||||
title: 'Busy',
|
|
||||||
centered: true,
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<Text size="sm">
|
|
||||||
Line is busy, the user is currently on another call. Please try again later.
|
|
||||||
</Text>
|
|
||||||
<Flex align={'center'} justify={'flex-end'}>
|
|
||||||
<Button color={'red'} variant={'subtle'} onClick={() => modals.closeAll()} mt="md">
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
withCloseButton: false
|
|
||||||
});
|
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
if(signalType == SignalType.END_CALL_BECAUSE_PEER_DISCONNECTED) {
|
if(signalType == SignalType.END_CALL_BECAUSE_PEER_DISCONNECTED) {
|
||||||
modals.open({
|
openCallsModal("The connection with the user was lost. The call has ended.")
|
||||||
title: 'Connection lost',
|
|
||||||
centered: true,
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<Text size="sm">
|
|
||||||
The connection with the user was lost. The call has ended.
|
|
||||||
</Text>
|
|
||||||
<Flex align={'center'} justify={'flex-end'}>
|
|
||||||
<Button color={'red'} variant={'subtle'} onClick={() => modals.closeAll()} mt="md">
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
withCloseButton: false
|
|
||||||
});
|
|
||||||
end();
|
end();
|
||||||
}
|
}
|
||||||
if(activeCall){
|
if(activeCall){
|
||||||
@@ -228,6 +226,8 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
/**
|
/**
|
||||||
* Нам поступает звонок
|
* Нам поступает звонок
|
||||||
*/
|
*/
|
||||||
|
setWindowPriority(true);
|
||||||
|
playSound("ringtone.mp3", true);
|
||||||
setActiveCall(packet.getSrc());
|
setActiveCall(packet.getSrc());
|
||||||
setCallState(CallState.INCOMING);
|
setCallState(CallState.INCOMING);
|
||||||
setShowCallView(true);
|
setShowCallView(true);
|
||||||
@@ -325,10 +325,13 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
/**
|
/**
|
||||||
* При получении медиа-трека с другой стороны
|
* При получении медиа-трека с другой стороны
|
||||||
*/
|
*/
|
||||||
console.info("TRACK RECV!!!!!");
|
if(remoteAudioRef.current && event.streams[0]){
|
||||||
if(remoteAudioRef.current){
|
|
||||||
console.info(event.streams);
|
console.info(event.streams);
|
||||||
remoteAudioRef.current.srcObject = event.streams[0];
|
remoteAudioRef.current.srcObject = event.streams[0];
|
||||||
|
remoteAudioRef.current.muted = !soundRef.current;
|
||||||
|
void remoteAudioRef.current.play().catch((e) => {
|
||||||
|
console.error("Failed to play remote audio:", e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,6 +358,25 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
}
|
}
|
||||||
}, [activeCall, sessionKeys]);
|
}, [activeCall, sessionKeys]);
|
||||||
|
|
||||||
|
const openCallsModal = (text : string) => {
|
||||||
|
modals.open({
|
||||||
|
centered: true,
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Text size="sm">
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
|
<Flex align={'center'} justify={'flex-end'}>
|
||||||
|
<Button color={'red'} variant={'subtle'} onClick={() => modals.closeAll()} mt="md">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
withCloseButton: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const generateSessionKeys = () => {
|
const generateSessionKeys = () => {
|
||||||
const sessionKeys = nacl.box.keyPair();
|
const sessionKeys = nacl.box.keyPair();
|
||||||
info("Generated keys for call session, len: " + sessionKeys.publicKey.length);
|
info("Generated keys for call session, len: " + sessionKeys.publicKey.length);
|
||||||
@@ -363,6 +385,14 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const call = (dialog: string) => {
|
const call = (dialog: string) => {
|
||||||
|
if(callState == CallState.ACTIVE
|
||||||
|
|| callState == CallState.CONNECTING
|
||||||
|
|| callState == CallState.KEY_EXCHANGE
|
||||||
|
|| callState == CallState.WEB_RTC_EXCHANGE){
|
||||||
|
openCallsModal("You are already on a call, please end the current call before starting a new one.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setWindowPriority(false);
|
||||||
setActiveCall(dialog);
|
setActiveCall(dialog);
|
||||||
setCallState(CallState.CONNECTING);
|
setCallState(CallState.CONNECTING);
|
||||||
setShowCallView(true);
|
setShowCallView(true);
|
||||||
@@ -372,6 +402,7 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
signalPacket.setSignalType(SignalType.CALL);
|
signalPacket.setSignalType(SignalType.CALL);
|
||||||
send(signalPacket);
|
send(signalPacket);
|
||||||
roleRef.current = CallRole.CALLER;
|
roleRef.current = CallRole.CALLER;
|
||||||
|
playSound("calling.mp3", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
@@ -384,14 +415,28 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const end = () => {
|
const end = () => {
|
||||||
|
stopLoopSound();
|
||||||
|
stopSound();
|
||||||
|
if (remoteAudioRef.current) {
|
||||||
|
remoteAudioRef.current.pause();
|
||||||
|
remoteAudioRef.current.srcObject = null;
|
||||||
|
}
|
||||||
|
setDuration(0);
|
||||||
|
durationIntervalRef.current && clearInterval(durationIntervalRef.current);
|
||||||
|
setWindowPriority(false);
|
||||||
|
playSound("end_call.mp3");
|
||||||
peerConnectionRef.current?.close();
|
peerConnectionRef.current?.close();
|
||||||
peerConnectionRef.current = null;
|
peerConnectionRef.current = null;
|
||||||
roomIdRef.current = "";
|
roomIdRef.current = "";
|
||||||
|
mutedRef.current = false;
|
||||||
|
soundRef.current = true;
|
||||||
setActiveCall("");
|
setActiveCall("");
|
||||||
setCallState(CallState.ENDED);
|
setCallState(CallState.ENDED);
|
||||||
setShowCallView(false);
|
setShowCallView(false);
|
||||||
setSessionKeys(null);
|
setSessionKeys(null);
|
||||||
setDuration(0);
|
setDuration(0);
|
||||||
|
setMutedState(false);
|
||||||
|
setSoundState(true);
|
||||||
roleRef.current = null;
|
roleRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,6 +447,9 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
*/
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setWindowPriority(false);
|
||||||
|
stopLoopSound();
|
||||||
|
stopSound();
|
||||||
/**
|
/**
|
||||||
* Звонок принят, генерируем ключи для сессии и отправляем их другой стороне для установления защищенного канала связи
|
* Звонок принят, генерируем ключи для сессии и отправляем их другой стороне для установления защищенного канала связи
|
||||||
*/
|
*/
|
||||||
@@ -428,6 +476,46 @@ export function CallProvider(props : CallProviderProps) {
|
|||||||
return sharedSecret;
|
return sharedSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const setMuted = (nextMuted: boolean) => {
|
||||||
|
if (mutedRef.current === nextMuted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutedRef.current = nextMuted;
|
||||||
|
playSound(nextMuted ? "micro_enable.mp3" : "micro_disable.mp3");
|
||||||
|
|
||||||
|
if(peerConnectionRef.current){
|
||||||
|
peerConnectionRef.current.getSenders().forEach(sender => {
|
||||||
|
if(sender.track?.kind == "audio"){
|
||||||
|
sender.track.enabled = !nextMuted;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMutedState(nextMuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
const setSound = (nextSound: boolean) => {
|
||||||
|
if (soundRef.current === nextSound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
soundRef.current = nextSound;
|
||||||
|
playSound(nextSound ? "sound_enable.mp3" : "sound_disable.mp3");
|
||||||
|
|
||||||
|
if(remoteAudioRef.current){
|
||||||
|
remoteAudioRef.current.muted = !nextSound;
|
||||||
|
if (nextSound) {
|
||||||
|
void remoteAudioRef.current.play().catch((e) => {
|
||||||
|
console.error("Failed to resume remote audio:", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSoundState(nextSound);
|
||||||
|
}
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
call,
|
call,
|
||||||
close,
|
close,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BrowserWindow, shell, ipcMain, nativeTheme, screen, powerMonitor } from 'electron'
|
import { BrowserWindow, shell, ipcMain, nativeTheme, screen, powerMonitor, app } from 'electron'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { WORKING_DIR } from './constants';
|
import { WORKING_DIR } from './constants';
|
||||||
@@ -45,7 +45,8 @@ export function createAppWindow(preloaderWindow?: BrowserWindow): void {
|
|||||||
nodeIntegrationInSubFrames: true,
|
nodeIntegrationInSubFrames: true,
|
||||||
nodeIntegrationInWorker: true,
|
nodeIntegrationInWorker: true,
|
||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
allowRunningInsecureContent: true
|
allowRunningInsecureContent: true,
|
||||||
|
autoplayPolicy: 'no-user-gesture-required'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,6 +74,7 @@ export function createAppWindow(preloaderWindow?: BrowserWindow): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function foundationIpcRegistration(mainWindow: BrowserWindow) {
|
export function foundationIpcRegistration(mainWindow: BrowserWindow) {
|
||||||
|
let bounceId: number | null = null;
|
||||||
ipcMain.removeAllListeners('window-resize');
|
ipcMain.removeAllListeners('window-resize');
|
||||||
ipcMain.removeAllListeners('window-resizeble');
|
ipcMain.removeAllListeners('window-resizeble');
|
||||||
ipcMain.removeAllListeners('window-theme');
|
ipcMain.removeAllListeners('window-theme');
|
||||||
@@ -86,6 +88,38 @@ export function foundationIpcRegistration(mainWindow: BrowserWindow) {
|
|||||||
ipcMain.removeHandler('window-minimize');
|
ipcMain.removeHandler('window-minimize');
|
||||||
ipcMain.removeHandler('showItemInFolder');
|
ipcMain.removeHandler('showItemInFolder');
|
||||||
ipcMain.removeHandler('openExternal');
|
ipcMain.removeHandler('openExternal');
|
||||||
|
ipcMain.removeHandler('window-top');
|
||||||
|
ipcMain.removeHandler('window-priority-normal');
|
||||||
|
|
||||||
|
ipcMain.handle('window-top', () => {
|
||||||
|
if (mainWindow.isMinimized()){
|
||||||
|
mainWindow.restore();
|
||||||
|
}
|
||||||
|
mainWindow.setAlwaysOnTop(true, "screen-saver"); // самый высокий уровень
|
||||||
|
mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
||||||
|
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
|
||||||
|
if (process.platform === "darwin") {
|
||||||
|
/**
|
||||||
|
* Только в macos! Подпрыгивание иконки в Dock
|
||||||
|
*/
|
||||||
|
bounceId = app.dock!.bounce("critical");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('window-priority-normal', () => {
|
||||||
|
mainWindow.setAlwaysOnTop(false);
|
||||||
|
mainWindow.setVisibleOnAllWorkspaces(false);
|
||||||
|
if(process.platform === "darwin" && bounceId !== null){
|
||||||
|
/**
|
||||||
|
* Только в macos! Отмена подпрыгивания иконки в Dock
|
||||||
|
*/
|
||||||
|
app.dock!.cancelBounce(bounceId);
|
||||||
|
bounceId = null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('open-dev-tools', () => {
|
ipcMain.handle('open-dev-tools', () => {
|
||||||
if (mainWindow.webContents.isDevToolsOpened()) {
|
if (mainWindow.webContents.isDevToolsOpened()) {
|
||||||
|
|||||||
3
lib/preload/index.d.ts
vendored
3
lib/preload/index.d.ts
vendored
@@ -13,5 +13,8 @@ declare global {
|
|||||||
downloadsPath: string;
|
downloadsPath: string;
|
||||||
deviceName: string;
|
deviceName: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
mediaApi: {
|
||||||
|
getSoundUrl: (fileName: string) => string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
import { contextBridge, ipcRenderer, shell } from 'electron'
|
import { contextBridge, ipcRenderer, shell } from 'electron'
|
||||||
import { electronAPI } from '@electron-toolkit/preload'
|
import { electronAPI } from '@electron-toolkit/preload'
|
||||||
import api from './api'
|
import api from './api'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
import path from 'node:path'
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
|
||||||
|
function resolveSound(fileName: string) {
|
||||||
|
const isDev = !process.env.APP_PACKAGED; // или свой флаг dev
|
||||||
|
const fullPath = isDev
|
||||||
|
? path.join(process.cwd(), "resources", "sounds", fileName)
|
||||||
|
: path.join(process.resourcesPath, "resources", "sounds", fileName);
|
||||||
|
|
||||||
|
if (!fs.existsSync(fullPath)) {
|
||||||
|
throw new Error(`Sound not found: ${fullPath}`);
|
||||||
|
}
|
||||||
|
return pathToFileURL(fullPath).toString();
|
||||||
|
}
|
||||||
|
|
||||||
const exposeContext = async () => {
|
const exposeContext = async () => {
|
||||||
if (process.contextIsolated) {
|
if (process.contextIsolated) {
|
||||||
try {
|
try {
|
||||||
@@ -16,6 +31,11 @@ const exposeContext = async () => {
|
|||||||
ipcRenderer.invoke('ipcCore:showItemInFolder', fullPath);
|
ipcRenderer.invoke('ipcCore:showItemInFolder', fullPath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
contextBridge.exposeInMainWorld("mediaApi", {
|
||||||
|
getSoundUrl: (fileName: string) => {
|
||||||
|
return resolveSound(fileName);
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
@@ -23,6 +43,11 @@ const exposeContext = async () => {
|
|||||||
window.electron = electronAPI
|
window.electron = electronAPI
|
||||||
window.api = api;
|
window.api = api;
|
||||||
window.shell = shell;
|
window.shell = shell;
|
||||||
|
window.mediaApi = {
|
||||||
|
getSoundUrl: (fileName: string) => {
|
||||||
|
return resolveSound(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "Rosetta",
|
"name": "Rosetta",
|
||||||
"version": "1.5.0",
|
"version": "1.5.2",
|
||||||
"description": "Rosetta Messenger",
|
"description": "Rosetta Messenger",
|
||||||
"main": "./out/main/main.js",
|
"main": "./out/main/main.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"build": {
|
"build": {
|
||||||
"electronUpdaterCompatibility": false,
|
"electronUpdaterCompatibility": false,
|
||||||
|
"extraResources": [
|
||||||
|
{ "from": "resources/", "to": "resources/" }
|
||||||
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"node_modules/sqlite3/**/*",
|
"node_modules/sqlite3/**/*",
|
||||||
"out/main/**/*",
|
"out/main/**/*",
|
||||||
|
|||||||
BIN
resources/sounds/calling.mp3
Normal file
BIN
resources/sounds/calling.mp3
Normal file
Binary file not shown.
BIN
resources/sounds/connected.mp3
Normal file
BIN
resources/sounds/connected.mp3
Normal file
Binary file not shown.
BIN
resources/sounds/end_call.mp3
Normal file
BIN
resources/sounds/end_call.mp3
Normal file
Binary file not shown.
BIN
resources/sounds/micro_disable.mp3
Normal file
BIN
resources/sounds/micro_disable.mp3
Normal file
Binary file not shown.
BIN
resources/sounds/micro_enable.mp3
Normal file
BIN
resources/sounds/micro_enable.mp3
Normal file
Binary file not shown.
BIN
resources/sounds/ringtone.mp3
Normal file
BIN
resources/sounds/ringtone.mp3
Normal file
Binary file not shown.
BIN
resources/sounds/sound_disable.mp3
Normal file
BIN
resources/sounds/sound_disable.mp3
Normal file
Binary file not shown.
BIN
resources/sounds/sound_enable.mp3
Normal file
BIN
resources/sounds/sound_enable.mp3
Normal file
Binary file not shown.
Reference in New Issue
Block a user