2023-09-15 17:35:44 -05:00
|
|
|
import { CapacitorHttp } from '@capacitor/core'
|
2025-07-01 11:33:51 -05:00
|
|
|
import { AbsDatabase } from '@/plugins/capacitor'
|
2023-09-15 17:35:44 -05:00
|
|
|
|
|
|
|
export default function ({ store }, inject) {
|
|
|
|
const nativeHttp = {
|
2025-07-01 11:33:51 -05:00
|
|
|
async request(method, _url, data, options = {}) {
|
|
|
|
// When authorizing before a config is set, server config gets passed in as an option
|
|
|
|
let serverConnectionConfig = options.serverConnectionConfig || store.state.user.serverConnectionConfig
|
|
|
|
delete options.serverConnectionConfig
|
|
|
|
|
2023-09-15 17:35:44 -05:00
|
|
|
let url = _url
|
|
|
|
const headers = {}
|
|
|
|
if (!url.startsWith('http') && !url.startsWith('capacitor')) {
|
|
|
|
const bearerToken = store.getters['user/getToken']
|
|
|
|
if (bearerToken) {
|
|
|
|
headers['Authorization'] = `Bearer ${bearerToken}`
|
|
|
|
} else {
|
|
|
|
console.warn('[nativeHttp] No Bearer Token for request')
|
|
|
|
}
|
2025-07-01 11:33:51 -05:00
|
|
|
if (serverConnectionConfig?.address) {
|
|
|
|
url = `${serverConnectionConfig.address}${url}`
|
2023-09-15 17:35:44 -05:00
|
|
|
}
|
|
|
|
}
|
2023-09-16 15:12:20 -05:00
|
|
|
if (data) {
|
|
|
|
headers['Content-Type'] = 'application/json'
|
|
|
|
}
|
2023-09-15 17:35:44 -05:00
|
|
|
console.log(`[nativeHttp] Making ${method} request to ${url}`)
|
|
|
|
return CapacitorHttp.request({
|
|
|
|
method,
|
|
|
|
url,
|
|
|
|
data,
|
|
|
|
headers,
|
|
|
|
...options
|
2025-07-01 11:33:51 -05:00
|
|
|
}).then((res) => {
|
|
|
|
if (res.status === 401) {
|
|
|
|
console.error(`[nativeHttp] 401 status for url "${url}"`)
|
|
|
|
// Handle refresh token automatically
|
|
|
|
return this.handleTokenRefresh(method, url, data, headers, options, serverConnectionConfig)
|
|
|
|
}
|
2023-11-25 14:38:40 -06:00
|
|
|
if (res.status >= 400) {
|
|
|
|
console.error(`[nativeHttp] ${res.status} status for url "${url}"`)
|
|
|
|
throw new Error(res.data)
|
|
|
|
}
|
|
|
|
return res.data
|
|
|
|
})
|
2023-09-15 17:35:44 -05:00
|
|
|
},
|
2025-07-01 11:33:51 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles token refresh when a 401 Unauthorized response is received
|
|
|
|
* @param {string} method - HTTP method
|
|
|
|
* @param {string} url - Full URL
|
|
|
|
* @param {*} data - Request data
|
|
|
|
* @param {Object} headers - Request headers
|
|
|
|
* @param {Object} options - Additional options
|
|
|
|
* @param {{ id: string, address: string }} serverConnectionConfig
|
|
|
|
* @returns {Promise} - Promise that resolves with the response data
|
|
|
|
*/
|
|
|
|
async handleTokenRefresh(method, url, data, headers, options, serverConnectionConfig) {
|
|
|
|
try {
|
|
|
|
console.log('[nativeHttp] Attempting to refresh token...')
|
|
|
|
|
|
|
|
if (!serverConnectionConfig?.id) {
|
|
|
|
console.error('[nativeHttp] No server connection config ID available for token refresh')
|
|
|
|
throw new Error('No server connection available')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get refresh token from secure storage
|
|
|
|
const refreshTokenData = await this.getRefreshToken(serverConnectionConfig.id)
|
|
|
|
if (!refreshTokenData || !refreshTokenData.refreshToken) {
|
|
|
|
console.error('[nativeHttp] No refresh token available')
|
|
|
|
throw new Error('No refresh token available')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to refresh the token
|
|
|
|
const newTokens = await this.refreshAccessToken(refreshTokenData.refreshToken, serverConnectionConfig.address)
|
|
|
|
if (!newTokens || !newTokens.accessToken) {
|
|
|
|
console.error('[nativeHttp] Failed to refresh access token')
|
|
|
|
throw new Error('Failed to refresh access token')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the store with new tokens
|
|
|
|
await this.updateTokens(newTokens, serverConnectionConfig)
|
|
|
|
|
|
|
|
// Retry the original request with the new token
|
|
|
|
console.log('[nativeHttp] Retrying original request with new token...')
|
|
|
|
const newHeaders = options?.headers ? { ...options.headers } : { ...headers }
|
|
|
|
newHeaders['Authorization'] = `Bearer ${newTokens.accessToken}`
|
|
|
|
|
|
|
|
const retryResponse = await CapacitorHttp.request({
|
|
|
|
method,
|
|
|
|
url,
|
|
|
|
data,
|
|
|
|
...options,
|
|
|
|
headers: newHeaders
|
|
|
|
})
|
|
|
|
|
|
|
|
if (retryResponse.status >= 400) {
|
|
|
|
console.error(`[nativeHttp] Retry request failed with status ${retryResponse.status}`)
|
|
|
|
throw new Error(retryResponse.data)
|
|
|
|
}
|
|
|
|
|
|
|
|
return retryResponse.data
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[nativeHttp] Token refresh failed:', error)
|
|
|
|
|
|
|
|
// If refresh fails, redirect to login
|
|
|
|
await this.handleRefreshFailure()
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves refresh token from secure storage
|
|
|
|
* @param {string} serverConnectionConfigId - Server connection config ID
|
|
|
|
* @returns {Promise<Object|null>} - Promise that resolves with refresh token data or null
|
|
|
|
*/
|
|
|
|
async getRefreshToken(serverConnectionConfigId) {
|
|
|
|
try {
|
|
|
|
console.log('[nativeHttp] Getting refresh token...')
|
|
|
|
return await AbsDatabase.getRefreshToken({ serverConnectionConfigId })
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[nativeHttp] Failed to get refresh token:', error)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Refreshes the access token using the refresh token
|
|
|
|
* @param {string} refreshToken - The refresh token
|
|
|
|
* @param {string} serverAddress - The server address
|
|
|
|
* @returns {Promise<Object|null>} - Promise that resolves with new tokens or null
|
|
|
|
*/
|
|
|
|
async refreshAccessToken(refreshToken, serverAddress) {
|
|
|
|
try {
|
|
|
|
if (!serverAddress) {
|
|
|
|
throw new Error('No server address available')
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('[nativeHttp] Refreshing access token...')
|
|
|
|
|
|
|
|
const response = await CapacitorHttp.post({
|
|
|
|
url: `${serverAddress}/auth/refresh`,
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
Authorization: `Bearer ${refreshToken}`,
|
|
|
|
'X-Return-Tokens': 'true'
|
|
|
|
},
|
|
|
|
data: {}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (response.status !== 200) {
|
|
|
|
console.error('[nativeHttp] Token refresh request failed:', response.status)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
const userResponseData = response.data
|
|
|
|
if (!userResponseData.user?.accessToken) {
|
|
|
|
console.error('[nativeHttp] No access token in refresh response')
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('[nativeHttp] Successfully refreshed access token')
|
|
|
|
return {
|
|
|
|
accessToken: userResponseData.user.accessToken,
|
|
|
|
refreshToken: userResponseData.user.refreshToken || refreshToken // Use new refresh token if provided, otherwise keep the old one
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[nativeHttp] Failed to refresh access token:', error)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the store and secure storage with new tokens
|
|
|
|
* @param {Object} tokens - Object containing accessToken and refreshToken
|
|
|
|
* @param {{ id: string, address: string }} serverConnectionConfig
|
|
|
|
* @returns {Promise} - Promise that resolves when tokens are updated
|
|
|
|
*/
|
|
|
|
async updateTokens(tokens, serverConnectionConfig) {
|
|
|
|
try {
|
|
|
|
if (!serverConnectionConfig?.id) {
|
|
|
|
throw new Error('No server connection config ID available')
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the config with new tokens
|
|
|
|
const updatedConfig = {
|
|
|
|
...serverConnectionConfig,
|
|
|
|
token: tokens.accessToken,
|
|
|
|
refreshToken: tokens.refreshToken
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save updated config to secure storage
|
|
|
|
const savedConfig = await AbsDatabase.setCurrentServerConnectionConfig(updatedConfig)
|
|
|
|
|
|
|
|
// Update the store
|
|
|
|
store.commit('user/setAccessToken', tokens.accessToken)
|
|
|
|
|
|
|
|
if (savedConfig) {
|
|
|
|
store.commit('user/setServerConnectionConfig', savedConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('[nativeHttp] Successfully updated tokens in store and secure storage')
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[nativeHttp] Failed to update tokens:', error)
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles the case when token refresh fails
|
|
|
|
* @returns {Promise} - Promise that resolves when logout is complete
|
|
|
|
*/
|
|
|
|
async handleRefreshFailure() {
|
|
|
|
try {
|
|
|
|
console.log('[nativeHttp] Handling refresh failure - logging out user')
|
|
|
|
|
|
|
|
// Clear the store
|
|
|
|
store.commit('user/setUser', null)
|
|
|
|
store.commit('user/setAccessToken', null)
|
|
|
|
store.commit('user/setServerConnectionConfig', null)
|
|
|
|
|
|
|
|
// Logout from database
|
|
|
|
await AbsDatabase.logout()
|
|
|
|
|
|
|
|
// Redirect to login page
|
|
|
|
if (window.location.pathname !== '/connect') {
|
|
|
|
window.location.href = '/connect'
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[nativeHttp] Failed to handle refresh failure:', error)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets device data from the database
|
|
|
|
* @returns {Promise<Object>} - Promise that resolves with device data
|
|
|
|
*/
|
|
|
|
async getDeviceData() {
|
|
|
|
try {
|
|
|
|
return await AbsDatabase.getDeviceData()
|
|
|
|
} catch (error) {
|
|
|
|
console.error('[nativeHttp] Failed to get device data:', error)
|
|
|
|
return { serverConnectionConfigs: [] }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-09-15 17:35:44 -05:00
|
|
|
get(url, options = {}) {
|
|
|
|
return this.request('GET', url, undefined, options)
|
|
|
|
},
|
|
|
|
post(url, data, options = {}) {
|
|
|
|
return this.request('POST', url, data, options)
|
|
|
|
},
|
|
|
|
patch(url, data, options = {}) {
|
|
|
|
return this.request('PATCH', url, data, options)
|
2023-09-17 09:28:43 -05:00
|
|
|
},
|
|
|
|
delete(url, options = {}) {
|
|
|
|
return this.request('DELETE', url, undefined, options)
|
2023-09-15 17:35:44 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
inject('nativeHttp', nativeHttp)
|
2025-07-01 11:33:51 -05:00
|
|
|
}
|