react-native-simple-version-update
Version:
react-native-simple-version-update is a lightweight and easy-to-use custom React Native hook that checks if your app needs to be updated by comparing the installed version with the latest version available on the App Store (iOS) or Google Play Store (Andr
250 lines (249 loc) • 12.5 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = require("react");
var react_native_device_info_1 = __importDefault(require("react-native-device-info"));
var react_native_1 = require("react-native");
/**
* Custom React Native hook to check if your app needs to be updated
* by comparing the installed version with the latest version available
* on the App Store (iOS) or Google Play Store (Android).
*
* @returns {UseVersionUpdateReturn} Object containing version info, update status, and helper functions
*/
var useVersionUpdate = function () {
var _a = (0, react_1.useState)(null), currentVersion = _a[0], setCurrentVersion = _a[1];
var _b = (0, react_1.useState)(null), liveVersion = _b[0], setLiveVersion = _b[1];
var _c = (0, react_1.useState)(false), needUpdate = _c[0], setNeedUpdate = _c[1];
var _d = (0, react_1.useState)(null), updateUrl = _d[0], setUpdateUrl = _d[1];
var _e = (0, react_1.useState)(true), isLoading = _e[0], setIsLoading = _e[1];
var _f = (0, react_1.useState)(null), error = _f[0], setError = _f[1];
/**
* Properly compares two semantic version strings
* @param latestVersion - The latest version string (e.g., "1.2.3")
* @param currentVersion - The current version string (e.g., "1.2.2")
* @returns true if latestVersion is greater than currentVersion
*/
var compareVersions = (0, react_1.useCallback)(function (latestVersion, currentVersion) {
var normalizeVersion = function (version) {
var cleaned = version.replace(/[^0-9.]/g, '');
// Handle "109" as equivalent to "1.0.9" (3 digits = major.minor.patch)
var parts = !cleaned.includes('.') && /^\d{3}$/.test(cleaned)
? cleaned.split('').map(function (d) { return d; })
: cleaned.split('.');
return parts
.map(function (part) { return parseInt(part || '0', 10); })
.concat([0, 0, 0])
.slice(0, 3);
};
var latest = normalizeVersion(latestVersion);
var current = normalizeVersion(currentVersion);
for (var i = 0; i < 3; i++) {
if (latest[i] > current[i]) {
return true;
}
if (latest[i] < current[i]) {
return false;
}
}
return false; // Versions are equal
}, []);
/**
* Fetches the latest version from the appropriate app store
*/
var getLatestStoreVersion = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
var bundleId, response, data, appInfo, response, html, latestVersion, patterns, _i, patterns_1, pattern, match, error_1, errorMessage;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 7, , 8]);
bundleId = react_native_device_info_1.default.getBundleId();
if (!(react_native_1.Platform.OS === 'ios')) return [3 /*break*/, 3];
return [4 /*yield*/, fetch("https://itunes.apple.com/lookup?bundleId=".concat(bundleId))];
case 1:
response = _b.sent();
if (!response.ok) {
throw new Error("Failed to fetch iOS app info: ".concat(response.statusText));
}
return [4 /*yield*/, response.json()];
case 2:
data = _b.sent();
if (data.resultCount > 0 && ((_a = data.results) === null || _a === void 0 ? void 0 : _a[0])) {
appInfo = data.results[0];
return [2 /*return*/, {
version: appInfo.version || null,
url: appInfo.trackViewUrl || null,
}];
}
return [3 /*break*/, 6];
case 3: return [4 /*yield*/, fetch("https://play.google.com/store/apps/details?id=".concat(bundleId, "&hl=en&gl=US"))];
case 4:
response = _b.sent();
if (!response.ok) {
throw new Error("Failed to fetch Android app info: ".concat(response.statusText));
}
return [4 /*yield*/, response.text()];
case 5:
html = _b.sent();
latestVersion = null;
patterns = [
/Current Version.+?>([\d.-]+)<\/span>/,
/\[\[\["([\d-.]+?)"\]\]/,
/"versionName":"([\d.]+)"/,
/Version[\s\S]*?([\d.]+)/i,
];
for (_i = 0, patterns_1 = patterns; _i < patterns_1.length; _i++) {
pattern = patterns_1[_i];
match = html.match(pattern);
if (match && match[1]) {
latestVersion = match[1].trim();
break;
}
}
return [2 /*return*/, {
version: latestVersion,
url: "https://play.google.com/store/apps/details?id=".concat(bundleId),
}];
case 6: return [2 /*return*/, { version: null, url: null }];
case 7:
error_1 = _b.sent();
errorMessage = error_1 instanceof Error ? error_1.message : 'Unknown error';
throw new Error("Error fetching the latest version: ".concat(errorMessage));
case 8: return [2 /*return*/];
}
});
}); }, []);
/**
* Checks for app updates by comparing current and live versions
*/
var checkForUpdate = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
var appVersion, _a, latestVersion, url, err_1, error_2;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
setIsLoading(true);
setError(null);
_b.label = 1;
case 1:
_b.trys.push([1, 3, 4, 5]);
appVersion = react_native_device_info_1.default.getVersion();
setCurrentVersion(appVersion);
return [4 /*yield*/, getLatestStoreVersion()];
case 2:
_a = _b.sent(), latestVersion = _a.version, url = _a.url;
if (latestVersion) {
setLiveVersion(latestVersion);
setUpdateUrl(url);
if (compareVersions(latestVersion, appVersion)) {
setNeedUpdate(true);
}
else {
setNeedUpdate(false);
}
}
else {
setLiveVersion(null);
setNeedUpdate(false);
}
return [3 /*break*/, 5];
case 3:
err_1 = _b.sent();
error_2 = err_1 instanceof Error ? err_1 : new Error('Unknown error occurred');
setError(error_2);
console.error('Error checking for update:', error_2);
return [3 /*break*/, 5];
case 4:
setIsLoading(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); }, [getLatestStoreVersion, compareVersions]);
/**
* Opens the app store page for updating the app
*/
var handleUpdate = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
var canOpen, err_2, error_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!updateUrl) {
console.warn('No update URL available');
return [2 /*return*/];
}
_a.label = 1;
case 1:
_a.trys.push([1, 6, , 7]);
return [4 /*yield*/, react_native_1.Linking.canOpenURL(updateUrl)];
case 2:
canOpen = _a.sent();
if (!canOpen) return [3 /*break*/, 4];
return [4 /*yield*/, react_native_1.Linking.openURL(updateUrl)];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4: throw new Error("Cannot open URL: ".concat(updateUrl));
case 5: return [3 /*break*/, 7];
case 6:
err_2 = _a.sent();
error_3 = err_2 instanceof Error ? err_2 : new Error('Failed to open update URL');
setError(error_3);
console.error('Error opening update URL:', error_3);
return [3 /*break*/, 7];
case 7: return [2 /*return*/];
}
});
}); }, [updateUrl]);
(0, react_1.useEffect)(function () {
checkForUpdate();
}, [checkForUpdate]);
return {
currentVersion: currentVersion,
liveVersion: liveVersion,
needUpdate: needUpdate,
isLoading: isLoading,
error: error,
handleUpdate: handleUpdate,
checkForUpdate: checkForUpdate,
};
};
exports.default = useVersionUpdate;