From eab1eba3b13038f944057b835cf55c74269ce235 Mon Sep 17 00:00:00 2001 From: Tommaso Ascani Date: Thu, 19 Feb 2026 10:54:21 +0100 Subject: [PATCH] fix: update phoneIslandTokenLogin/logout handling --- src/renderer/src/hooks/usePhoneIsland.ts | 9 +++-- src/shared/useNethVoiceAPI.ts | 43 +++++++++++++++++++----- src/shared/useNetwork.ts | 12 +++++++ 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/hooks/usePhoneIsland.ts b/src/renderer/src/hooks/usePhoneIsland.ts index b0e9b236..86ee6254 100644 --- a/src/renderer/src/hooks/usePhoneIsland.ts +++ b/src/renderer/src/hooks/usePhoneIsland.ts @@ -8,14 +8,19 @@ export const usePhoneIsland = () => { const { NethVoiceAPI } = useLoggedNethVoiceAPI() const createDataConfig = async (account: Account): Promise<[Extension, string]> => { - const phoneIslandTokenLoginResponse = (await NethVoiceAPI.Authentication.phoneIslandTokenLogin()).token + const tokenResponse = await NethVoiceAPI.Authentication.phoneIslandTokenLogin() + const phoneIslandToken = tokenResponse?.token + if (!phoneIslandToken) { + throw new Error('Unable to retrieve dedicated Phone Island token') + } + const deviceInformationObject: Extension | undefined = account.data!.endpoints.extension.find((e) => e.type === 'nethlink') if (deviceInformationObject) { const hostname = account!.host const config: PhoneIslandConfig = { hostname, username: account.username, - authToken: phoneIslandTokenLoginResponse, + authToken: phoneIslandToken, sipExten: deviceInformationObject.id, sipSecret: deviceInformationObject.secret, sipHost: account.sipHost || '', diff --git a/src/shared/useNethVoiceAPI.ts b/src/shared/useNethVoiceAPI.ts index 228e25d9..a7fb70d1 100644 --- a/src/shared/useNethVoiceAPI.ts +++ b/src/shared/useNethVoiceAPI.ts @@ -24,7 +24,7 @@ const PRIMARY_API_BASE_PATH = '/api' const FALLBACK_API_BASE_PATH = '/webrest' export const useNethVoiceAPI = (loggedAccount: Account | undefined = undefined) => { - const { GET, POST } = useNetwork() + const { GET, POST, DELETE } = useNetwork() let isFirstHeartbeat = true let account: Account | undefined = loggedAccount || undefined // Use account's stored API path preference, or default to primary @@ -46,9 +46,6 @@ export const useNethVoiceAPI = (loggedAccount: Account | undefined = undefined) if (endpoint === '/logout') { return `${FALLBACK_API_BASE_PATH}/authentication/logout` } - if (endpoint === '/authentication/phone_island_token_login') { - return `${FALLBACK_API_BASE_PATH}/authentication/phone_island_token_login` - } // For other endpoints, use webrest format return `${FALLBACK_API_BASE_PATH}${endpoint}` } @@ -147,6 +144,17 @@ export const useNethVoiceAPI = (loggedAccount: Account | undefined = undefined) } } + async function _DELETE(path: string, hasAuth = true): Promise { + try { + return (await DELETE(_joinUrl(path), _getHeaders(hasAuth))) + } catch (e) { + if (!path.includes('login') && !path.includes('2fa/verify-otp')) { + console.error(e) + } + throw e + } + } + function shouldTryFallback(path: string, error: any): boolean { // Only try fallback if we're using primary path if (currentApiBasePath !== PRIMARY_API_BASE_PATH) { @@ -393,11 +401,30 @@ export const useNethVoiceAPI = (loggedAccount: Account | undefined = undefined) }) }, - phoneIslandTokenLogin: async (): Promise<{ username: string, token: string }> => - await _POST(buildApiPath('/authentication/phone_island_token_login'), { subtype: 'nethlink' }), + // Dedicated token for Phone Island in NethLink (kept separate by design). + phoneIslandTokenLogin: async (): Promise<{ username: string, token: string }> => { + try { + return await _POST(buildApiPath('/tokens/persistent/nethlink')) + } catch (reason: any) { + if (reason?.response?.status === 404) { + // Legacy middleware fallback. + return await _POST(buildApiPath('/authentication/phone_island_token_login'), { subtype: 'nethlink' }) + } + throw reason + } + }, - phoneIslandTokenLogout: async (): Promise<{ username: string, token: string }> => - await _POST(buildApiPath('/authentication/persistent_token_remove'), { type: 'phone-island', subtype: 'nethlink' }), + phoneIslandTokenLogout: async (): Promise => { + try { + return await _DELETE(buildApiPath('/tokens/persistent/nethlink')) + } catch (reason: any) { + if (reason?.response?.status === 404) { + // Legacy middleware fallback. + return await _POST(buildApiPath('/authentication/persistent_token_remove'), { type: 'phone-island', subtype: 'nethlink' }) + } + throw reason + } + }, } const CustCard = {} diff --git a/src/shared/useNetwork.ts b/src/shared/useNetwork.ts index 79f20142..f55ca20d 100644 --- a/src/shared/useNetwork.ts +++ b/src/shared/useNetwork.ts @@ -27,6 +27,17 @@ export const useNetwork = () => { } } + async function DELETE(path: string, config: { headers: { Authorization?: string | undefined; 'Content-Type': string } } | undefined = { headers: { 'Content-Type': 'application/json' } }): Promise { + try { + const response = await axios.delete(path, config) + return response.data + } catch (e: any) { + const err: AxiosError = e + Log.error('during fetch DELETE', err.name, err.code, err.message, path, config) + throw e + } + } + async function HEAD(path: string, timeoutMs: number = 5000): Promise { try { await axios.head(path, { @@ -43,6 +54,7 @@ export const useNetwork = () => { return { GET, POST, + DELETE, HEAD } }