UNPKG

@hot-updater/react-native

Version:

React Native OTA solution for self-hosted

321 lines (315 loc) 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _exportNames = { HotUpdater: true, extractSignatureFailure: true, isSignatureVerificationError: true }; exports.HotUpdater = void 0; Object.defineProperty(exports, "extractSignatureFailure", { enumerable: true, get: function () { return _types.extractSignatureFailure; } }); Object.defineProperty(exports, "isSignatureVerificationError", { enumerable: true, get: function () { return _types.isSignatureVerificationError; } }); var _checkForUpdate = require("./checkForUpdate.js"); var _DefaultResolver = require("./DefaultResolver.js"); var _native = require("./native.js"); var _store = require("./store.js"); Object.keys(_store).forEach(function (key) { if (key === "default" || key === "__esModule") return; if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; if (key in exports && exports[key] === _store[key]) return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _store[key]; } }); }); var _wrap = require("./wrap.js"); var _types = require("./types.js"); (0, _native.addListener)("onProgress", ({ progress }) => { _store.hotUpdaterStore.setState({ progress }); }); /** * Register getBaseURL to global objects for use without imports. * This is needed for Expo DOM components and Babel plugin generated code. */ const registerGlobalGetBaseURL = () => { const fn = _native.getBaseURL; // Register to globalThis (modern, cross-platform) if (typeof globalThis !== "undefined") { if (!globalThis.HotUpdaterGetBaseURL) { globalThis.HotUpdaterGetBaseURL = fn; } } // Register to global (React Native, Node.js) if (typeof global !== "undefined") { if (!global.HotUpdaterGetBaseURL) { global.HotUpdaterGetBaseURL = fn; } } }; // Call registration immediately on module load registerGlobalGetBaseURL(); /** * Creates a HotUpdater client instance with all update management methods. * This function is called once on module initialization to create a singleton instance. */ function createHotUpdaterClient() { // Global configuration stored from wrap const globalConfig = { resolver: null }; const ensureGlobalResolver = methodName => { if (!globalConfig.resolver) { throw new Error(`[HotUpdater] ${methodName} requires HotUpdater.wrap() to be used.\n\n` + `To fix this issue, wrap your root component with HotUpdater.wrap():\n\n` + `Option 1: With automatic updates\n` + ` export default HotUpdater.wrap({\n` + ` baseURL: "<your-update-server-url>",\n` + ` updateStrategy: "appVersion",\n` + ` updateMode: "auto"\n` + ` })(App);\n\n` + `Option 2: Manual updates only (custom flow)\n` + ` export default HotUpdater.wrap({\n` + ` baseURL: "<your-update-server-url>",\n` + ` updateMode: "manual"\n` + ` })(App);\n\n` + `For more information, visit: https://hot-updater.dev/docs/react-native-api/wrap`); } return globalConfig.resolver; }; return { /** * `HotUpdater.wrap` checks for updates at the entry point, and if there is a bundle to update, it downloads the bundle and applies the update strategy. * * @param {object} options - Configuration options * @param {string} options.source - Update server URL * @param {object} [options.requestHeaders] - Request headers * @param {React.ComponentType} [options.fallbackComponent] - Component to display during updates * @param {boolean} [options.reloadOnForceUpdate=true] - Whether to automatically reload the app on force updates * @param {Function} [options.onUpdateProcessCompleted] - Callback after update process completes * @param {Function} [options.onProgress] - Callback to track bundle download progress * @returns {Function} Higher-order component that wraps the app component * * @example * ```tsx * export default HotUpdater.wrap({ * baseURL: "<your-update-server-url>", * updateStrategy: "appVersion", * updateMode: "auto", * requestHeaders: { * "Authorization": "Bearer <your-access-token>", * }, * })(App); * ``` */ wrap: options => { let normalizedOptions; if ("baseURL" in options && options.baseURL) { const { baseURL, ...rest } = options; normalizedOptions = { ...rest, resolver: (0, _DefaultResolver.createDefaultResolver)(baseURL) }; } else if ("resolver" in options && options.resolver) { normalizedOptions = options; } else { throw new Error(`[HotUpdater] Either baseURL or resolver must be provided.\n\n` + `Option 1: Using baseURL (recommended for most cases)\n` + ` export default HotUpdater.wrap({\n` + ` baseURL: "<your-update-server-url>",\n` + ` updateStrategy: "appVersion",\n` + ` updateMode: "auto"\n` + ` })(App);\n\n` + `Option 2: Using custom resolver (advanced)\n` + ` export default HotUpdater.wrap({\n` + ` resolver: {\n` + ` checkUpdate: async (params) => { /* custom logic */ },\n` + ` notifyAppReady: async (params) => { /* custom logic */ }\n` + ` },\n` + ` updateMode: "manual"\n` + ` })(App);\n\n` + `For more information, visit: https://hot-updater.dev/docs/react-native-api/wrap`); } globalConfig.resolver = normalizedOptions.resolver; globalConfig.requestHeaders = options.requestHeaders; globalConfig.requestTimeout = options.requestTimeout; return (0, _wrap.wrap)(normalizedOptions); }, /** * Reloads the app. */ reload: _native.reload, /** * Returns whether an update has finished downloading in this app session. * * When it returns true, calling `HotUpdater.reload()` (or restarting the app) * will apply the downloaded update bundle. * * - Derived from `progress` reaching 1.0 * - Resets to false when a new download starts (progress < 1) * * @returns {boolean} True if a downloaded update is ready to apply * @example * ```ts * if (HotUpdater.isUpdateDownloaded()) { * await HotUpdater.reload(); * } * ``` */ isUpdateDownloaded: () => _store.hotUpdaterStore.getSnapshot().isUpdateDownloaded, /** * Fetches the current app version. */ getAppVersion: _native.getAppVersion, /** * Fetches the current bundle ID of the app. */ getBundleId: _native.getBundleId, /** * Retrieves the initial bundle ID based on the build time of the native app. */ getMinBundleId: _native.getMinBundleId, /** * Fetches the current channel of the app. * * If no channel is specified, the app is assigned to the 'production' channel. * * @returns {string} The current release channel of the app * @default "production" * @example * ```ts * const channel = HotUpdater.getChannel(); * console.log(`Current channel: ${channel}`); * ``` */ getChannel: _native.getChannel, /** * Adds a listener to HotUpdater events. * * @param {keyof HotUpdaterEvent} eventName - The name of the event to listen for * @param {(event: HotUpdaterEvent[T]) => void} listener - The callback function to handle the event * @returns {() => void} A cleanup function that removes the event listener * * @example * ```ts * const unsubscribe = HotUpdater.addListener("onProgress", ({ progress }) => { * console.log(`Update progress: ${progress * 100}%`); * }); * * // Unsubscribe when no longer needed * unsubscribe(); * ``` */ addListener: _native.addListener, /** * Manually checks for updates. * * @param {Object} config - Update check configuration * @param {string} config.source - Update server URL * @param {Record<string, string>} [config.requestHeaders] - Request headers * * @returns {Promise<UpdateInfo | null>} Update information or null if up to date * * @example * ```ts * const updateInfo = await HotUpdater.checkForUpdate({ * source: "<your-update-server-url>", * requestHeaders: { * Authorization: "Bearer <your-access-token>", * }, * }); * * if (!updateInfo) { * console.log("App is up to date"); * return; * } * * await HotUpdater.updateBundle(updateInfo.id, updateInfo.fileUrl); * if (updateInfo.shouldForceUpdate) { * await HotUpdater.reload(); * } * ``` */ checkForUpdate: config => { const resolver = ensureGlobalResolver("checkForUpdate"); const mergedConfig = { ...config, resolver, requestHeaders: { ...globalConfig.requestHeaders, ...config.requestHeaders }, requestTimeout: config.requestTimeout ?? globalConfig.requestTimeout }; return (0, _checkForUpdate.checkForUpdate)(mergedConfig); }, /** * Updates the bundle of the app. * * @param {UpdateBundleParams} params - Parameters object required for bundle update * @param {string} params.bundleId - The bundle ID of the app * @param {string|null} params.fileUrl - The URL of the zip file * * @returns {Promise<boolean>} Whether the update was successful * * @example * ```ts * const updateInfo = await HotUpdater.checkForUpdate({ * source: "<your-update-server-url>", * requestHeaders: { * Authorization: "Bearer <your-access-token>", * }, * }); * * if (!updateInfo) { * return { * status: "UP_TO_DATE", * }; * } * * await HotUpdater.updateBundle({ * bundleId: updateInfo.id, * fileUrl: updateInfo.fileUrl * }); * if (updateInfo.shouldForceUpdate) { * await HotUpdater.reload(); * } * ``` */ updateBundle: params => { ensureGlobalResolver("updateBundle"); return (0, _native.updateBundle)(params); }, /** * Fetches the fingerprint of the app. * * @returns {string} The fingerprint of the app * * @example * ```ts * const fingerprint = HotUpdater.getFingerprintHash(); * console.log(`Fingerprint: ${fingerprint}`); * ``` */ getFingerprintHash: _native.getFingerprintHash, /** * Gets the list of bundle IDs that have been marked as crashed. * These bundles will be rejected if attempted to install again. * * @returns {string[]} Array of crashed bundle IDs * * @example * ```ts * const crashedBundles = HotUpdater.getCrashHistory(); * console.log("Crashed bundles:", crashedBundles); * ``` */ getCrashHistory: _native.getCrashHistory, /** * Clears the crashed bundle history, allowing previously crashed bundles * to be installed again. * * @returns {boolean} true if clearing was successful * * @example * ```ts * // Clear crash history to allow retrying a previously failed bundle * HotUpdater.clearCrashHistory(); * ``` */ clearCrashHistory: _native.clearCrashHistory }; } const HotUpdater = exports.HotUpdater = createHotUpdaterClient(); //# sourceMappingURL=index.js.map