UNPKG

@markvivanco/app-version-checker

Version:

React App version checking and update prompts for React, React Native, and web applications

1,087 lines (1,077 loc) 33.1 kB
import React, { createContext, useState, useMemo, useEffect, useCallback, useContext, useRef } from 'react'; // src/core/types.ts var DEFAULT_CHECK_INTERVALS = { MIN_CHECK_INTERVAL: 60 * 60 * 1e3, // 1 hour minimum between checks REMIND_LATER_DURATION: 24 * 60 * 60 * 1e3 // 24 hours for "remind me later" }; // src/core/version-compare.ts function compareVersions(v1, v2) { const parts1 = v1.split(".").map((num) => parseInt(num, 10)); const parts2 = v2.split(".").map((num) => parseInt(num, 10)); const maxLength = Math.max(parts1.length, parts2.length); while (parts1.length < maxLength) parts1.push(0); while (parts2.length < maxLength) parts2.push(0); for (let i = 0; i < maxLength; i++) { if (parts1[i] < parts2[i]) return -1; if (parts1[i] > parts2[i]) return 1; } return 0; } function isUpdateAvailable(currentVersion, latestVersion) { return compareVersions(currentVersion, latestVersion) < 0; } function parseVersion(version) { const parts = version.split(".").map((num) => parseInt(num, 10)); return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0, build: parts[3] // Optional build number }; } function formatVersion(major, minor, patch, build) { const parts = [major, minor, patch]; if (build !== void 0) { parts.push(build); } return parts.join("."); } function getMajorVersion(version) { return parseVersion(version).major; } function getMinorVersion(version) { return parseVersion(version).minor; } function getPatchVersion(version) { return parseVersion(version).patch; } function isValidVersion(version) { const regex = /^\d+\.\d+\.\d+(\.\d+)?$/; return regex.test(version); } function getVersionDiff(v1, v2) { const comparison = compareVersions(v1, v2); if (comparison === 0) { return { type: "none", fromVersion: v1, toVersion: v2 }; } const parsed1 = parseVersion(v1); const parsed2 = parseVersion(v2); let type = "build"; if (parsed1.major !== parsed2.major) { type = "major"; } else if (parsed1.minor !== parsed2.minor) { type = "minor"; } else if (parsed1.patch !== parsed2.patch) { type = "patch"; } return { type, fromVersion: comparison < 0 ? v1 : v2, toVersion: comparison < 0 ? v2 : v1 }; } // src/core/version-formatter.ts function formatVersionWithBuild(platformVersion, buildNumber) { if (buildNumber !== void 0 && buildNumber !== null && buildNumber !== "") { return `${platformVersion}.${buildNumber}`; } return platformVersion; } function extractBuildNumber(version) { const parts = version.split("."); if (parts.length > 3) { return parts[3]; } return void 0; } function extractBaseVersion(version) { const parts = version.split("."); return parts.slice(0, 3).join("."); } function normalizeVersion(version, padToLength = 3) { const parts = version.split("."); const numericParts = parts.map((part) => parseInt(part, 10)).filter((num) => !isNaN(num)); while (numericParts.length < padToLength) { numericParts.push(0); } return numericParts.join("."); } function formatDisplayVersion(version, includePrefix = true) { const normalized = normalizeVersion(version, 3); return includePrefix ? `v${normalized}` : normalized; } function sortVersions(versions, descending = true) { const sorted = [...versions].sort((a, b) => { const aParts = a.split(".").map((num) => parseInt(num, 10)); const bParts = b.split(".").map((num) => parseInt(num, 10)); const maxLength = Math.max(aParts.length, bParts.length); for (let i = 0; i < maxLength; i++) { const aPart = aParts[i] || 0; const bPart = bParts[i] || 0; if (aPart !== bPart) { return descending ? bPart - aPart : aPart - bPart; } } return 0; }); return sorted; } function getLatestVersion(versions) { if (!versions || versions.length === 0) { return null; } const sorted = sortVersions(versions, true); return sorted[0]; } // src/core/stores.ts function getIosStoreUrl(appStoreId, customUrl) { if (customUrl) { return customUrl; } if (!appStoreId) { return null; } return `https://apps.apple.com/app/id${appStoreId}`; } function getAndroidStoreUrl(packageName, customUrl) { if (customUrl) { return customUrl; } if (!packageName) { return null; } return `https://play.google.com/store/apps/details?id=${packageName}`; } function getStoreUrl(platform, config) { switch (platform) { case "ios": return getIosStoreUrl(config.iosAppStoreId, config.iosStoreUrl); case "android": return getAndroidStoreUrl(config.androidPackageName, config.androidStoreUrl); case "web": return null; default: return null; } } function isValidIosAppStoreId(appStoreId) { return /^\d{9,10}$/.test(appStoreId); } function isValidAndroidPackageName(packageName) { return /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/.test(packageName); } function extractAppIdFromUrl(url, platform) { if (platform === "ios") { const match = url.match(/\/id(\d+)/); return match ? match[1] : null; } if (platform === "android") { const match = url.match(/[?&]id=([^&]+)/); return match ? match[1] : null; } return null; } function getStoreName(platform) { switch (platform) { case "ios": return "App Store"; case "android": return "Google Play Store"; case "web": return "Web"; default: return "Unknown"; } } function getStoreBadgeUrl(platform, _locale = "en-US") { switch (platform) { case "ios": return `https://developer.apple.com/app-store/marketing/guidelines/images/badge-download-on-the-app-store.svg`; case "android": return `https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png`; default: return null; } } // src/core/version-checker.ts var VersionChecker = class { constructor(dataProvider, storageProvider, options = {}) { this.initialized = false; this.dataProvider = dataProvider; this.storageProvider = storageProvider; this.options = { minCheckInterval: options.minCheckInterval ?? DEFAULT_CHECK_INTERVALS.MIN_CHECK_INTERVAL, remindLaterDuration: options.remindLaterDuration ?? DEFAULT_CHECK_INTERVALS.REMIND_LATER_DURATION, skipWebPlatform: options.skipWebPlatform ?? true, getPlatform: options.getPlatform ?? (() => this.detectPlatform()) }; } /** * Initialize the version checker */ async initialize() { if (this.initialized) { return; } if (this.dataProvider.initialize) { await this.dataProvider.initialize(); } if (this.storageProvider.initialize) { await this.storageProvider.initialize(); } this.initialized = true; } /** * Detect the current platform */ detectPlatform() { if (this.dataProvider.getCurrentPlatform) { return this.dataProvider.getCurrentPlatform(); } if (typeof window !== "undefined") { const userAgent = window.navigator?.userAgent || ""; if (/android/i.test(userAgent)) { return "android"; } if (/iPad|iPhone|iPod/.test(userAgent)) { return "ios"; } return "web"; } return "web"; } /** * Get the current platform */ getPlatform() { return this.options.getPlatform(); } /** * Get version information */ async getVersionInfo() { const platform = this.getPlatform(); const currentVersion = await this.dataProvider.getCurrentVersion(); const latestVersion = await this.dataProvider.getLatestVersion(platform); const appStoreConfig = await this.dataProvider.getAppStoreConfig(); const updateAvailable = latestVersion ? isUpdateAvailable(currentVersion, latestVersion) : false; const storeUrl = getStoreUrl(platform, appStoreConfig); return { currentVersion, latestVersion, updateAvailable, storeUrl, platform }; } /** * Check if an update is available */ async isUpdateAvailable() { const platform = this.getPlatform(); if (platform === "web" && this.options.skipWebPlatform) { return false; } const versionInfo = await this.getVersionInfo(); return versionInfo.updateAvailable; } /** * Check if we should show the update prompt */ async shouldShowUpdatePrompt() { const platform = this.getPlatform(); if (platform === "web" && this.options.skipWebPlatform) { const versionInfo = await this.getVersionInfo(); return { shouldShowPrompt: false, versionInfo, skipReason: "web_platform" }; } try { const versionInfo = await this.getVersionInfo(); if (!versionInfo.updateAvailable) { return { shouldShowPrompt: false, versionInfo, skipReason: "no_update" }; } const remindLaterTime = await this.storageProvider.getRemindLaterTime(); if (remindLaterTime && Date.now() < remindLaterTime) { return { shouldShowPrompt: false, versionInfo, skipReason: "remind_later" }; } const lastCheckTime = await this.storageProvider.getLastCheckTime(); if (lastCheckTime && Date.now() - lastCheckTime < this.options.minCheckInterval) { return { shouldShowPrompt: false, versionInfo, skipReason: "too_soon" }; } if (this.storageProvider.getLastShownVersion) { const lastShownVersion = await this.storageProvider.getLastShownVersion(); if (lastShownVersion === versionInfo.latestVersion) { if (this.dataProvider.isUpdateMandatory) { const isMandatory = await this.dataProvider.isUpdateMandatory( versionInfo.currentVersion, versionInfo.latestVersion ); if (!isMandatory) { return { shouldShowPrompt: false, versionInfo, skipReason: "remind_later" }; } } else { return { shouldShowPrompt: false, versionInfo, skipReason: "remind_later" }; } } } await this.storageProvider.setLastCheckTime(Date.now()); if (this.storageProvider.setLastShownVersion && versionInfo.latestVersion) { await this.storageProvider.setLastShownVersion(versionInfo.latestVersion); } return { shouldShowPrompt: true, versionInfo }; } catch (error) { console.error("Error checking for updates:", error); const versionInfo = await this.getVersionInfo(); return { shouldShowPrompt: false, versionInfo, skipReason: "error" }; } } /** * Set "remind me later" for the update prompt */ async setRemindMeLater() { const remindTime = Date.now() + this.options.remindLaterDuration; await this.storageProvider.setRemindLaterTime(remindTime); if (this.storageProvider.incrementDismissCount) { await this.storageProvider.incrementDismissCount(); } } /** * Clear the "remind me later" setting */ async clearRemindMeLater() { await this.storageProvider.clearRemindLaterTime(); } /** * Check if update is mandatory */ async isUpdateMandatory() { if (!this.dataProvider.isUpdateMandatory) { return false; } const versionInfo = await this.getVersionInfo(); if (!versionInfo.latestVersion) { return false; } return await this.dataProvider.isUpdateMandatory( versionInfo.currentVersion, versionInfo.latestVersion ); } /** * Get changelog for the latest version */ async getChangeLog() { if (!this.dataProvider.getChangeLog) { return null; } const versionInfo = await this.getVersionInfo(); if (!versionInfo.latestVersion) { return null; } return await this.dataProvider.getChangeLog(versionInfo.latestVersion); } /** * Reset all version check data (useful for testing) */ async resetVersionCheckData() { await this.storageProvider.clearRemindLaterTime(); await this.storageProvider.setLastCheckTime(0); if (this.storageProvider.clearAll) { await this.storageProvider.clearAll(); } } /** * Get formatted version string */ async getFormattedVersion() { if (this.dataProvider.getFormattedVersion) { return await this.dataProvider.getFormattedVersion(); } return await this.dataProvider.getCurrentVersion(); } /** * Dispose of resources */ async dispose() { if (this.dataProvider.dispose) { await this.dataProvider.dispose(); } if (this.storageProvider.dispose) { await this.storageProvider.dispose(); } this.initialized = false; } }; // src/providers/data-provider.interface.ts var BaseVersionDataProvider = class { getCurrentPlatform() { if (typeof window !== "undefined" && !("ReactNativeWebView" in window)) { return "web"; } return "web"; } async initialize() { } async dispose() { } }; // src/providers/storage-provider.interface.ts var BaseStorageProvider = class { async getDismissCount() { return 0; } async incrementDismissCount() { } async initialize() { } async dispose() { } }; var InMemoryStorageProvider = class extends BaseStorageProvider { constructor() { super(...arguments); this.storage = /* @__PURE__ */ new Map(); } async getLastCheckTime() { return this.storage.get("lastCheckTime") || null; } async setLastCheckTime(timestamp) { this.storage.set("lastCheckTime", timestamp); } async getRemindLaterTime() { return this.storage.get("remindLaterTime") || null; } async setRemindLaterTime(timestamp) { this.storage.set("remindLaterTime", timestamp); } async clearRemindLaterTime() { this.storage.delete("remindLaterTime"); } async getDismissCount() { return this.storage.get("dismissCount") || 0; } async incrementDismissCount() { const current = await this.getDismissCount(); this.storage.set("dismissCount", current + 1); } async getLastShownVersion() { return this.storage.get("lastShownVersion") || null; } async setLastShownVersion(version) { this.storage.set("lastShownVersion", version); } async clearAll() { this.storage.clear(); } async getAllPreferences() { const preferences = {}; this.storage.forEach((value, key) => { preferences[key] = value; }); return preferences; } }; // src/adapters/stores/local-storage-provider.ts var LocalStorageProvider = class extends BaseStorageProvider { constructor(prefix = "app_version_check_") { super(); this.prefix = prefix; } getKey(key) { return `${this.prefix}${key}`; } async getLastCheckTime() { try { const value = localStorage.getItem(this.getKey("lastCheckTime")); return value ? parseInt(value, 10) : null; } catch (error) { console.error("Error reading last check time:", error); return null; } } async setLastCheckTime(timestamp) { try { localStorage.setItem(this.getKey("lastCheckTime"), timestamp.toString()); } catch (error) { console.error("Error setting last check time:", error); } } async getRemindLaterTime() { try { const value = localStorage.getItem(this.getKey("remindLaterTime")); return value ? parseInt(value, 10) : null; } catch (error) { console.error("Error reading remind later time:", error); return null; } } async setRemindLaterTime(timestamp) { try { localStorage.setItem(this.getKey("remindLaterTime"), timestamp.toString()); } catch (error) { console.error("Error setting remind later time:", error); } } async clearRemindLaterTime() { try { localStorage.removeItem(this.getKey("remindLaterTime")); } catch (error) { console.error("Error clearing remind later time:", error); } } async getDismissCount() { try { const value = localStorage.getItem(this.getKey("dismissCount")); return value ? parseInt(value, 10) : 0; } catch (error) { console.error("Error reading dismiss count:", error); return 0; } } async incrementDismissCount() { try { const current = await this.getDismissCount(); localStorage.setItem(this.getKey("dismissCount"), (current + 1).toString()); } catch (error) { console.error("Error incrementing dismiss count:", error); } } async getLastShownVersion() { try { return localStorage.getItem(this.getKey("lastShownVersion")); } catch (error) { console.error("Error reading last shown version:", error); return null; } } async setLastShownVersion(version) { try { localStorage.setItem(this.getKey("lastShownVersion"), version); } catch (error) { console.error("Error setting last shown version:", error); } } async getAutoUpdateEnabled() { try { const value = localStorage.getItem(this.getKey("autoUpdateEnabled")); return value === "true"; } catch (error) { console.error("Error reading auto update preference:", error); return false; } } async setAutoUpdateEnabled(enabled) { try { localStorage.setItem(this.getKey("autoUpdateEnabled"), enabled.toString()); } catch (error) { console.error("Error setting auto update preference:", error); } } async getAllPreferences() { const preferences = {}; const keys = [ "lastCheckTime", "remindLaterTime", "dismissCount", "lastShownVersion", "autoUpdateEnabled" ]; for (const key of keys) { try { const value = localStorage.getItem(this.getKey(key)); if (value !== null) { preferences[key] = value; } } catch (error) { console.error(`Error reading ${key}:`, error); } } return preferences; } async clearAll() { const keysToRemove = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith(this.prefix)) { keysToRemove.push(key); } } for (const key of keysToRemove) { try { localStorage.removeItem(key); } catch (error) { console.error(`Error removing ${key}:`, error); } } } }; // src/adapters/stores/async-storage-provider.ts var AsyncStorageProvider = class extends BaseStorageProvider { constructor(storage, prefix = "app_version_check_") { super(); this.storage = storage; this.prefix = prefix; } getKey(key) { return `${this.prefix}${key}`; } async getLastCheckTime() { try { const value = await this.storage.getItem(this.getKey("lastCheckTime")); return value ? parseInt(value, 10) : null; } catch (error) { console.error("Error reading last check time:", error); return null; } } async setLastCheckTime(timestamp) { try { await this.storage.setItem(this.getKey("lastCheckTime"), timestamp.toString()); } catch (error) { console.error("Error setting last check time:", error); } } async getRemindLaterTime() { try { const value = await this.storage.getItem(this.getKey("remindLaterTime")); return value ? parseInt(value, 10) : null; } catch (error) { console.error("Error reading remind later time:", error); return null; } } async setRemindLaterTime(timestamp) { try { await this.storage.setItem(this.getKey("remindLaterTime"), timestamp.toString()); } catch (error) { console.error("Error setting remind later time:", error); } } async clearRemindLaterTime() { try { await this.storage.removeItem(this.getKey("remindLaterTime")); } catch (error) { console.error("Error clearing remind later time:", error); } } async getDismissCount() { try { const value = await this.storage.getItem(this.getKey("dismissCount")); return value ? parseInt(value, 10) : 0; } catch (error) { console.error("Error reading dismiss count:", error); return 0; } } async incrementDismissCount() { try { const current = await this.getDismissCount(); await this.storage.setItem(this.getKey("dismissCount"), (current + 1).toString()); } catch (error) { console.error("Error incrementing dismiss count:", error); } } async getLastShownVersion() { try { return await this.storage.getItem(this.getKey("lastShownVersion")); } catch (error) { console.error("Error reading last shown version:", error); return null; } } async setLastShownVersion(version) { try { await this.storage.setItem(this.getKey("lastShownVersion"), version); } catch (error) { console.error("Error setting last shown version:", error); } } async getAutoUpdateEnabled() { try { const value = await this.storage.getItem(this.getKey("autoUpdateEnabled")); return value === "true"; } catch (error) { console.error("Error reading auto update preference:", error); return false; } } async setAutoUpdateEnabled(enabled) { try { await this.storage.setItem(this.getKey("autoUpdateEnabled"), enabled.toString()); } catch (error) { console.error("Error setting auto update preference:", error); } } async getAllPreferences() { const preferences = {}; const keys = [ "lastCheckTime", "remindLaterTime", "dismissCount", "lastShownVersion", "autoUpdateEnabled" ]; for (const key of keys) { try { const value = await this.storage.getItem(this.getKey(key)); if (value !== null) { preferences[key] = value; } } catch (error) { console.error(`Error reading ${key}:`, error); } } return preferences; } async clearAll() { if (this.storage.getAllKeys && this.storage.multiRemove) { try { const allKeys = await this.storage.getAllKeys(); const keysToRemove = allKeys.filter((key) => key.startsWith(this.prefix)); if (keysToRemove.length > 0) { await this.storage.multiRemove(keysToRemove); } } catch (error) { console.error("Error clearing all preferences:", error); } } else { const keys = [ "lastCheckTime", "remindLaterTime", "dismissCount", "lastShownVersion", "autoUpdateEnabled" ]; for (const key of keys) { try { await this.storage.removeItem(this.getKey(key)); } catch (error) { console.error(`Error removing ${key}:`, error); } } } } }; var VersionCheckContext = createContext(void 0); var VersionCheckProvider = ({ children, dataProvider, storageProvider, options = {}, checkOnMount = true, checkOnForeground = false, onOpenStore, onShowUpdateDialog, onHideUpdateDialog, renderDialog = true, dialogComponent: DialogComponent }) => { const [versionInfo, setVersionInfo] = useState(null); const [showUpdateDialog, setShowUpdateDialog] = useState(false); const [isChecking, setIsChecking] = useState(false); const [error, setError] = useState(null); const [currentVersion, setCurrentVersion] = useState(null); const [formattedVersion, setFormattedVersion] = useState(null); const versionChecker = useMemo( () => new VersionChecker(dataProvider, storageProvider, options), [dataProvider, storageProvider, options] ); useEffect(() => { versionChecker.initialize().catch(console.error); return () => { versionChecker.dispose().catch(console.error); }; }, [versionChecker]); useEffect(() => { const loadVersions = async () => { try { const current = await dataProvider.getCurrentVersion(); setCurrentVersion(current); const formatted = dataProvider.getFormattedVersion ? await dataProvider.getFormattedVersion() : current; setFormattedVersion(formatted); } catch (err) { console.error("Error loading versions:", err); } }; loadVersions(); }, [dataProvider]); const checkForUpdates = useCallback(async () => { if (isChecking) return; setIsChecking(true); setError(null); try { const result = await versionChecker.shouldShowUpdatePrompt(); const info = result.versionInfo; setVersionInfo(info); if (result.shouldShowPrompt) { setShowUpdateDialog(true); onShowUpdateDialog?.(info); } } catch (err) { const error2 = err instanceof Error ? err : new Error(String(err)); setError(error2); console.error("Error checking for updates:", error2); } finally { setIsChecking(false); } }, [versionChecker, isChecking, onShowUpdateDialog]); const handleUpdateNow = useCallback(async () => { setShowUpdateDialog(false); onHideUpdateDialog?.(); if (versionInfo?.storeUrl) { if (onOpenStore) { await onOpenStore(versionInfo.storeUrl); } else { if (typeof window !== "undefined" && window.open) { window.open(versionInfo.storeUrl, "_blank"); } } } }, [versionInfo, onOpenStore, onHideUpdateDialog]); const handleRemindLater = useCallback(async () => { setShowUpdateDialog(false); onHideUpdateDialog?.(); await versionChecker.setRemindMeLater(); }, [versionChecker, onHideUpdateDialog]); const resetVersionCheck = useCallback(async () => { await versionChecker.resetVersionCheckData(); setVersionInfo(null); setShowUpdateDialog(false); setError(null); }, [versionChecker]); const getChangeLog = useCallback(async () => { return await versionChecker.getChangeLog(); }, [versionChecker]); const isUpdateMandatory = useCallback(async () => { return await versionChecker.isUpdateMandatory(); }, [versionChecker]); useEffect(() => { if (checkOnMount) { checkForUpdates(); } }, [checkOnMount]); useEffect(() => { if (!checkOnForeground) return; }, [checkOnForeground, checkForUpdates]); const contextValue = useMemo( () => ({ versionInfo, isUpdateAvailable: versionInfo?.updateAvailable || false, currentVersion, formattedVersion, showUpdateDialog, isChecking, error, checkForUpdates, handleUpdateNow, handleRemindLater, resetVersionCheck, getChangeLog, isUpdateMandatory }), [ versionInfo, currentVersion, formattedVersion, showUpdateDialog, isChecking, error, checkForUpdates, handleUpdateNow, handleRemindLater, resetVersionCheck, getChangeLog, isUpdateMandatory ] ); return /* @__PURE__ */ React.createElement(VersionCheckContext.Provider, { value: contextValue }, children, renderDialog && DialogComponent && versionInfo && /* @__PURE__ */ React.createElement( DialogComponent, { visible: showUpdateDialog, versionInfo, onUpdateNow: handleUpdateNow, onRemindLater: handleRemindLater } )); }; var useVersionCheck = () => { const context = useContext(VersionCheckContext); if (context === void 0) { throw new Error("useVersionCheck must be used within a VersionCheckProvider"); } return context; }; var useAppStateVersionCheck = (appStateModule, enabled = true) => { const { checkForUpdates } = useVersionCheck(); const [appState, setAppState] = useState("active"); useEffect(() => { if (!enabled || !appStateModule) return; const currentState = appStateModule.currentState || "active"; setAppState(currentState); const handleAppStateChange = (nextAppState) => { if (appState.match(/inactive|background/) && nextAppState === "active") { checkForUpdates(); } setAppState(nextAppState); }; const subscription = appStateModule.addEventListener("change", handleAppStateChange); return () => { subscription?.remove?.(); }; }, [appState, checkForUpdates, enabled, appStateModule]); return appState; }; var usePeriodicVersionCheck = (intervalMs = 60 * 60 * 1e3, enabled = true) => { const { checkForUpdates } = useVersionCheck(); const intervalRef = useRef(null); useEffect(() => { if (!enabled) { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } return; } intervalRef.current = setInterval(() => { checkForUpdates(); }, intervalMs); return () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; }, [checkForUpdates, intervalMs, enabled]); }; var useVisibilityVersionCheck = (enabled = true) => { const { checkForUpdates } = useVersionCheck(); useEffect(() => { if (!enabled || typeof document === "undefined") return; const handleVisibilityChange = () => { if (!document.hidden) { checkForUpdates(); } }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [checkForUpdates, enabled]); }; var useStandaloneVersionChecker = (dataProvider, storageProvider, options) => { const [versionInfo, setVersionInfo] = useState(null); const [isChecking, setIsChecking] = useState(false); const [error, setError] = useState(null); const [showUpdatePrompt, setShowUpdatePrompt] = useState(false); const versionCheckerRef = useRef(null); useEffect(() => { const checker = new VersionChecker(dataProvider, storageProvider); versionCheckerRef.current = checker; checker.initialize().catch(console.error); return () => { checker.dispose().catch(console.error); }; }, [dataProvider, storageProvider]); const checkForUpdates = useCallback(async () => { if (!versionCheckerRef.current || isChecking) return; setIsChecking(true); setError(null); try { const result = await versionCheckerRef.current.shouldShowUpdatePrompt(); setVersionInfo(result.versionInfo); setShowUpdatePrompt(result.shouldShowPrompt); } catch (err) { const error2 = err instanceof Error ? err : new Error(String(err)); setError(error2); } finally { setIsChecking(false); } }, [isChecking]); const setRemindMeLater = useCallback(async () => { if (!versionCheckerRef.current) return; await versionCheckerRef.current.setRemindMeLater(); setShowUpdatePrompt(false); }, []); useEffect(() => { if (options?.checkOnMount !== false) { checkForUpdates(); } }, []); useEffect(() => { if (!options?.checkOnFocus) return; const handleFocus = () => checkForUpdates(); window.addEventListener("focus", handleFocus); return () => window.removeEventListener("focus", handleFocus); }, [checkForUpdates, options?.checkOnFocus]); useEffect(() => { if (!options?.checkInterval) return; const interval = setInterval(checkForUpdates, options.checkInterval); return () => clearInterval(interval); }, [checkForUpdates, options?.checkInterval]); return { versionInfo, isChecking, error, showUpdatePrompt, checkForUpdates, setRemindMeLater, isUpdateAvailable: versionInfo?.updateAvailable || false }; }; var useVersionInfo = () => { const { versionInfo, currentVersion, formattedVersion } = useVersionCheck(); return { current: currentVersion, latest: versionInfo?.latestVersion, formatted: formattedVersion, updateAvailable: versionInfo?.updateAvailable || false, platform: versionInfo?.platform, storeUrl: versionInfo?.storeUrl }; }; var useUpdateStatus = () => { const { isUpdateAvailable: isUpdateAvailable2, isChecking, error, showUpdateDialog } = useVersionCheck(); return { isUpdateAvailable: isUpdateAvailable2, isChecking, hasError: !!error, error, isDialogVisible: showUpdateDialog }; }; export { AsyncStorageProvider, BaseStorageProvider, BaseVersionDataProvider, DEFAULT_CHECK_INTERVALS, InMemoryStorageProvider, LocalStorageProvider, VersionCheckProvider, VersionChecker, compareVersions, extractAppIdFromUrl, extractBaseVersion, extractBuildNumber, formatDisplayVersion, formatVersion, formatVersionWithBuild, getAndroidStoreUrl, getIosStoreUrl, getLatestVersion, getMajorVersion, getMinorVersion, getPatchVersion, getStoreBadgeUrl, getStoreName, getStoreUrl, getVersionDiff, isUpdateAvailable, isValidAndroidPackageName, isValidIosAppStoreId, isValidVersion, normalizeVersion, parseVersion, sortVersions, useAppStateVersionCheck, usePeriodicVersionCheck, useStandaloneVersionChecker, useUpdateStatus, useVersionCheck, useVersionInfo, useVisibilityVersionCheck }; //# sourceMappingURL=index.mjs.map //# sourceMappingURL=index.mjs.map