import { Capacitor, registerPlugin } from '@capacitor/core' // Define the plugin interface interface AppUpdatePlugin { getAppVersion(): Promise<{ versionName: string; versionCode: number }> downloadAndInstall(options: { url: string }): Promise<{ success: boolean }> } // Register the plugin const AppUpdate = registerPlugin('AppUpdate') // Update server configuration - use proxy to avoid CORS issues const UPDATE_CHECK_URL = 'https://app.logship.de/api/mobile/app-version' const HISTORY_URL = 'https://app.logship.de/api/mobile/app-versions' const UPDATE_FILE_BASE = 'https://www.logyou.de/logship' export const useAppUpdate = () => { const isNative = ref(false) const currentVersion = ref(null) const latestVersion = ref(null) const apkUrl = ref(null) const releaseNotes = ref(null) const updateAvailable = ref(false) const isChecking = ref(false) const isUpdating = ref(false) const error = ref(null) // Version history (admin-only UI): previously released builds, newest first. const versionHistory = ref([]) const isLoadingHistory = ref(false) const historyError = ref(null) const installingUrl = ref(null) // which history row is currently installing onMounted(async () => { isNative.value = Capacitor.isNativePlatform() if (isNative.value) { try { const info = await AppUpdate.getAppVersion() currentVersion.value = info.versionName console.log('[AppUpdate] Current version:', info.versionName, 'code:', info.versionCode) } catch (e) { console.error('[AppUpdate] Failed to get app version:', e) } } }) const checkForUpdate = async (): Promise => { console.log('[AppUpdate] checkForUpdate called, isNative:', isNative.value) // Allow checking even if not detected as native (for testing) isChecking.value = true error.value = null try { console.log('[AppUpdate] Fetching:', UPDATE_CHECK_URL) const response = await fetch(UPDATE_CHECK_URL, { method: 'GET', cache: 'no-store', headers: { 'Accept': 'application/json', }, }) console.log('[AppUpdate] Response status:', response.status) if (!response.ok) { const text = await response.text() throw new Error(`HTTP ${response.status}: ${text.substring(0, 100)}`) } const data = await response.json() console.log('[AppUpdate] Response data:', data) latestVersion.value = data.versionName releaseNotes.value = data.releaseNotes || null // Build full APK URL from relative path if (data.apkUrl) { // Convert relative URL to absolute URL using the update server apkUrl.value = data.apkUrl.startsWith('http') ? data.apkUrl : `https://www.logyou.de/logship/${data.apkUrl}` } // Compare versions - if no current version, assume update available if (latestVersion.value) { if (currentVersion.value) { updateAvailable.value = compareVersions(latestVersion.value, currentVersion.value) > 0 } else { // Can't read the installed version (e.g. an older APK without the AppUpdate plugin, or // getAppVersion rejected) → offer the update instead of leaving the user stuck. Once they // install a build that CAN report its version, the normal comparison takes over. updateAvailable.value = true } } console.log('[AppUpdate] Latest version:', latestVersion.value, 'Current:', currentVersion.value, 'Update available:', updateAvailable.value) return updateAvailable.value } catch (e: any) { const errorMsg = e.message || 'Unknown error' error.value = `Fehler: ${errorMsg}` console.error('[AppUpdate] Check failed:', e) return false } finally { isChecking.value = false } } const downloadAndInstall = async (): Promise => { console.log('[AppUpdate] downloadAndInstall called, isNative:', isNative.value, 'updateAvailable:', updateAvailable.value, 'apkUrl:', apkUrl.value) if (!isNative.value) { error.value = 'Nur in der nativen App verfügbar' console.warn('[AppUpdate] Not running in native app') return false } if (!updateAvailable.value) { error.value = 'Kein Update verfügbar' console.warn('[AppUpdate] No update available') return false } if (!apkUrl.value) { error.value = 'Keine APK-URL vorhanden' console.warn('[AppUpdate] No APK URL') return false } isUpdating.value = true error.value = null try { console.log('[AppUpdate] Starting download from:', apkUrl.value) const result = await AppUpdate.downloadAndInstall({ url: apkUrl.value }) console.log('[AppUpdate] Download and install result:', result) return result.success } catch (e: any) { error.value = e.message || 'Download fehlgeschlagen' console.error('[AppUpdate] Update failed:', e) return false } finally { isUpdating.value = false } } // Load the release history (for the admin-only "Versionsverlauf"). Resolves each entry's relative // apkUrl to an absolute URL the native downloader can use. const fetchHistory = async (): Promise => { isLoadingHistory.value = true historyError.value = null try { const response = await fetch(HISTORY_URL, { method: 'GET', cache: 'no-store', headers: { 'Accept': 'application/json' }, }) if (!response.ok) throw new Error(`HTTP ${response.status}`) const data = await response.json() const list = Array.isArray(data) ? data : [] versionHistory.value = list .map((v: any) => ({ ...v, apkUrlAbs: v?.apkUrl ? (String(v.apkUrl).startsWith('http') ? v.apkUrl : `${UPDATE_FILE_BASE}/${v.apkUrl}`) : null, })) .sort((a: any, b: any) => (Number(b.versionCode) || 0) - (Number(a.versionCode) || 0)) return versionHistory.value } catch (e: any) { historyError.value = `Fehler: ${e.message || 'Verlauf konnte nicht geladen werden'}` console.error('[AppUpdate] History fetch failed:', e) return [] } finally { isLoadingHistory.value = false } } // Install a specific (older) build by absolute APK URL — same native path as a normal update. // The APK must be signed with the same key as the installed app or Android refuses the sideload. const installVersion = async (url: string): Promise => { if (!isNative.value) { error.value = 'Nur in der nativen App verfügbar' return false } if (!url) { error.value = 'Keine APK-URL vorhanden' return false } installingUrl.value = url isUpdating.value = true error.value = null try { const result = await AppUpdate.downloadAndInstall({ url }) return result.success } catch (e: any) { error.value = e.message || 'Download fehlgeschlagen' console.error('[AppUpdate] Install-version failed:', e) return false } finally { isUpdating.value = false installingUrl.value = null } } // Compare semver versions: returns 1 if a > b, -1 if a < b, 0 if equal const compareVersions = (a: string, b: string): number => { const aParts = a.split('.').map(Number) const bParts = b.split('.').map(Number) const maxLen = Math.max(aParts.length, bParts.length) for (let i = 0; i < maxLen; i++) { const aVal = aParts[i] || 0 const bVal = bParts[i] || 0 if (aVal > bVal) return 1 if (aVal < bVal) return -1 } return 0 } return { isNative: readonly(isNative), currentVersion: readonly(currentVersion), latestVersion: readonly(latestVersion), releaseNotes: readonly(releaseNotes), updateAvailable: readonly(updateAvailable), isChecking: readonly(isChecking), isUpdating: readonly(isUpdating), error: readonly(error), checkForUpdate, downloadAndInstall, // version history (admin-only UI) versionHistory: readonly(versionHistory), isLoadingHistory: readonly(isLoadingHistory), historyError: readonly(historyError), installingUrl: readonly(installingUrl), fetchHistory, installVersion, } }