UNPKG

@markvivanco/app-version-checker

Version:

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

444 lines (439 loc) 13.3 kB
// 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; } }; export { DEFAULT_CHECK_INTERVALS, 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 }; //# sourceMappingURL=index.mjs.map //# sourceMappingURL=index.mjs.map