Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions src/main/classes/controllers/AccountController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ export class AccountController {
return false
}

// The saved SIP settings may be stale (e.g. NethVoice updated while NethLink was closed):
// refresh them from the server before the PhoneIsland is built with them
const sipChanged = await this.refreshServerConfig(lastLoggedAccount)

// Update store with the saved account (don't do a new login!)
// IMPORTANT: Preserve auth.lastUser and auth.lastUserCryptPsw so they are saved to disk
// IMPORTANT: Set connection: true to prevent "No internet connection" banner
Expand All @@ -154,6 +158,9 @@ export class AccountController {
lastUserCryptPsw: authAppData.lastUserCryptPsw
}
}, 'autoLogin')
if (sipChanged) {
store.saveToDisk()
}

return true
} else {
Expand All @@ -176,15 +183,13 @@ export class AccountController {
return false
}

let loggedAccount: Account = {
const loggedAccount: Account = {
...lastLoggedAccount,
...tempLoggedAccount,
theme: lastLoggedAccount.theme || tempLoggedAccount.theme,
}

const { parseConfig } = useLogin()
const config: string = await NetworkController.instance.get(`https://${loggedAccount.host}/config/config.production.js`)
loggedAccount = parseConfig(loggedAccount, config)
await this.refreshServerConfig(loggedAccount)
await this.saveLoggedAccount(loggedAccount, password)
return true
} catch (e) {
Expand All @@ -196,6 +201,48 @@ export class AccountController {
return false
}

/**
* Re-read the host config.production.js and update the SIP settings of the given account in place.
* It never throws: on network or parsing errors the cached values are kept.
* @returns true when sipHost or sipPort changed
*/
async refreshServerConfig(account: Account): Promise<boolean> {
try {
const { parseConfig } = useLogin()
const config: string = await NetworkController.instance.get(`https://${account.host}/config/config.production.js`)
const previous = { sipHost: account.sipHost, sipPort: account.sipPort }
parseConfig(account, config)
const changed = previous.sipHost !== account.sipHost || previous.sipPort !== account.sipPort
if (changed) {
Log.info('server SIP config changed', { previous, current: { sipHost: account.sipHost, sipPort: account.sipPort } })
}
return changed
} catch (e) {
Log.warning('unable to refresh server config, keeping cached SIP settings:', e)
return false
}
}

/**
* Refresh the SIP settings of the logged account from the server and persist them if changed.
* @returns true when sipHost or sipPort changed
*/
async syncLoggedAccountServerConfig(): Promise<boolean> {
const account = store.store.account
if (!account) return false
const changed = await this.refreshServerConfig(account)
if (changed) {
store.set('account', account, true)
const auth = store.store.auth
if (auth) {
auth.availableAccounts[getAccountUID(account)] = account
store.set('auth', auth, true)
}
store.saveToDisk()
}
return changed
}

async saveLoggedAccount(account: Account, password: string): Promise<Account> {
try {
// Wait for encryption to become available (max 10 seconds with 500ms intervals)
Expand Down
14 changes: 0 additions & 14 deletions src/main/classes/controllers/PhoneIslandController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,20 +365,6 @@ export class PhoneIslandController {
}
}

reconnect() {
try {
Log.info('PHONE ISLAND RECONNECT')
this.window.emit(IPC_EVENTS.RECONNECT_PHONE_ISLAND)
once(IPC_EVENTS.LOGOUT_COMPLETED, () => {
Log.info('PHONE ISLAND RECONNECTION AFTER LOGOUT')
this.window.quit(false)
new PhoneIslandController()
})
} catch (e) {
Log.warning('error during emitting reconnect event to the PhoneIslandWindow:', e)
}
}

muteAudio() {
try {
const window = this.window.getWindow()
Expand Down
54 changes: 54 additions & 0 deletions src/main/lib/ipcEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ export function isCallActive(): boolean {
return hasActiveCall
}

// Set when the SIP settings changed during a call: the PhoneIsland reload is deferred to the call end
let isPhoneIslandReloadPending = false

// Minimum interval between two server config checks triggered by PhoneIsland connection errors
const SERVER_CONFIG_CHECK_MIN_INTERVAL = 10_000
let lastServerConfigCheck = 0

/**
* Reload the PhoneIsland so that it registers again with the current SIP host/port.
* When a call is active the reload is deferred to the call end, since it would drop the call.
*/
function reloadPhoneIslandForSipChange(trigger: string) {
if (isCallActive()) {
Log.info(`SIP config changed (${trigger}) but a call is active - PhoneIsland reload deferred to call end`)
isPhoneIslandReloadPending = true
return
}
isPhoneIslandReloadPending = false
Log.info(`SIP config changed (${trigger}) - reloading PhoneIsland`)
PhoneIslandController.instance?.window?.getWindow()?.reload()
}

function onSyncEmitter<T>(
channel: IPC_EVENTS,
asyncCallback: (...args: any[]) => Promise<T>
Expand Down Expand Up @@ -475,6 +497,14 @@ export function registerIpcEvents() {
ipcMain.on(IPC_EVENTS.EMIT_CALL_END, (_) => {
Log.info('Call ended - setting hasActiveCall = false')
hasActiveCall = false
if (isPhoneIslandReloadPending) {
// give the PhoneIsland the time to close the call UI before reloading it
setTimeout(() => {
if (isPhoneIslandReloadPending) {
reloadPhoneIslandForSipChange('call ended')
}
}, 1000)
}
try {
NethLinkController.instance.window.emit(IPC_EVENTS.EMIT_CALL_END)
} catch (e) {
Expand Down Expand Up @@ -508,13 +538,37 @@ export function registerIpcEvents() {

ipcMain.on(IPC_EVENTS.RECONNECT_SOCKET, async () => {
try {
const previous = { sipHost: store.store.account?.sipHost, sipPort: store.store.account?.sipPort }
// autoLogin refreshes the SIP settings from the server config too
await AccountController.instance.autoLogin()
NethLinkController.instance.window.emit(IPC_EVENTS.RECONNECT_SOCKET)
const account = store.store.account
if (account && (account.sipHost !== previous.sipHost || account.sipPort !== previous.sipPort)) {
reloadPhoneIslandForSipChange('socket reconnected')
}
} catch (e) {
Log.error('SOCKET Reconnection error on logout', e)
}
})

ipcMain.on(IPC_EVENTS.CHECK_SERVER_CONFIG, async () => {
// A PhoneIsland WebRTC failure may be caused by SIP settings changed on the server (e.g. NethVoice
// update while NethLink is running): re-read them and reload the PhoneIsland if they changed.
const now = Date.now()
if (now - lastServerConfigCheck < SERVER_CONFIG_CHECK_MIN_INTERVAL) {
return
}
lastServerConfigCheck = now
try {
const changed = await AccountController.instance.syncLoggedAccountServerConfig()
if (changed) {
reloadPhoneIslandForSipChange('webrtc down')
}
} catch (e) {
Log.error('CHECK_SERVER_CONFIG error', e)
}
})

ipcMain.on(IPC_EVENTS.FULLSCREEN_ENTER, () => {
try {
PhoneIslandController.instance.window.getWindow()?.setFullScreen(true);
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/src/hooks/usePhoneIslandEventListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,13 @@ export const usePhoneIslandEventListener = () => {
// Check if both WebRTC and Socket are ready
checkAndTriggerPhoneIslandReady()
}),
...eventHandler(PHONE_ISLAND_EVENTS["phone-island-alert-set"], (data) => {
// A WebRTC failure may be due to SIP settings changed on the server (e.g. after a NethVoice
// update): ask the main process to re-check the server config
if (data?.type === 'webrtc_down') {
window.electron.send(IPC_EVENTS.CHECK_SERVER_CONFIG)
}
}),
...eventHandler(PHONE_ISLAND_EVENTS["phone-island-all-alerts-removed"]),
...eventHandler(PHONE_ISLAND_EVENTS["phone-island-fullscreen-entered"], () => {
window.electron.send(IPC_EVENTS.FULLSCREEN_ENTER)
Expand Down
4 changes: 0 additions & 4 deletions src/renderer/src/pages/PhoneIslandPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,6 @@ export function PhoneIslandPage() {
})
})

window.electron.receive(IPC_EVENTS.RECONNECT_PHONE_ISLAND, () => {
logout()
})

window.electron.receive(IPC_EVENTS.CHANGE_DEFAULT_DEVICE, async (deviceInformationObject, force) => {
Log.debug('CHANGE_DEFAULT_DEVICE', { force, deviceInformationObject, })
const changed = await NethVoiceAPI.User.default_device(deviceInformationObject, force)
Expand Down
3 changes: 2 additions & 1 deletion src/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ export enum IPC_EVENTS {
REQUEST_SHARED_STATE = "REQUEST_SHARED_STATE",
GET_NETHVOICE_CONFIG = "GET_NETHVOICE_CONFIG",
SET_NETHVOICE_CONFIG = "SET_NETHVOICE_CONFIG",
RECONNECT_PHONE_ISLAND = "RECONNECT_PHONE_ISLAND",
RECONNECT_SOCKET = "RECONNECT_SOCKET",
CHECK_SERVER_CONFIG = "CHECK_SERVER_CONFIG",
LOGOUT_COMPLETED = "LOGOUT_COMPLETED",
SHOW_NO_CONNECTION = "SHOW_NO_CONNECTION",
HIDE_NO_CONNECTION = "HIDE_NO_CONNECTION",
Expand Down Expand Up @@ -237,6 +237,7 @@ export enum PHONE_ISLAND_EVENTS {
'phone-island-internet-connected' = 'phone-island-internet-connected',
'phone-island-internet-disconnected' = 'phone-island-internet-disconnected',
// Alerts
'phone-island-alert-set' = 'phone-island-alert-set',
'phone-island-all-alerts-removed' = 'phone-island-all-alerts-removed',
// Videocall
'phone-island-fullscreen-entered' = 'phone-island-fullscreen-entered',
Expand Down
Loading