@appspacer/react-native 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ import { getNativeModule } from "./native";
2
+ export const initCrashReporter = (getSessionId) => {
3
+ const globalHandler = global.ErrorUtils;
4
+ if (!globalHandler || typeof globalHandler.setGlobalHandler !== "function") {
5
+ console.warn("[AppSpacer] Crash Analytics: global.ErrorUtils not available");
6
+ return;
7
+ }
8
+ const defaultHandler = globalHandler.getGlobalHandler();
9
+ globalHandler.setGlobalHandler(async (error, isFatal) => {
10
+ try {
11
+ const crashData = {
12
+ id: "js-" + Date.now().toString() + "-" + Math.random().toString(36).slice(2, 7),
13
+ type: "javascript",
14
+ session_id: getSessionId(),
15
+ payload: JSON.stringify({
16
+ message: error?.message || "Unknown error",
17
+ stack: error?.stack || "",
18
+ isFatal: !!isFatal,
19
+ }),
20
+ timestamp: new Date().toISOString(),
21
+ };
22
+ const native = getNativeModule();
23
+ if (__DEV__)
24
+ console.log(`[AppSpacer] Captured JS ${isFatal ? 'FATAL ' : ''}crash. Saving to native storage: ${crashData.id}`);
25
+ // Synchronous-ish call to native to ensure it starts before process exits
26
+ native.saveCrashReport(JSON.stringify(crashData)).catch((err) => {
27
+ console.warn(`[AppSpacer] Failed to save JS crash report ${crashData.id}`, err);
28
+ });
29
+ }
30
+ catch (e) {
31
+ // Ignore errors in the crash reporter itself
32
+ }
33
+ // Preserve original handler — shows red box in dev, crashes in prod
34
+ if (typeof defaultHandler === "function") {
35
+ defaultHandler(error, isFatal);
36
+ }
37
+ });
38
+ if (__DEV__) {
39
+ console.log("[AppSpacer] Crash Analytics: Active in DEV mode. Crashes saved locally — reload the app to upload.");
40
+ }
41
+ };
@@ -0,0 +1,34 @@
1
+ import type { TurboModule } from "react-native";
2
+ /**
3
+ * TurboModule specification for AppSpacer.
4
+ *
5
+ * This file is used by React Native's Codegen to generate
6
+ * type-safe native module interfaces for the New Architecture.
7
+ *
8
+ * For Old Architecture (Bridge), the module falls back to
9
+ * NativeModules.AppSpacerModule automatically.
10
+ */
11
+ export interface Spec extends TurboModule {
12
+ getOtaStoragePath(): Promise<string>;
13
+ downloadPackage(url: string, destPath: string): Promise<string>;
14
+ verifyHash(filePath: string, expectedHash: string): Promise<boolean>;
15
+ unzipPackage(zipPath: string, destDir: string): Promise<void>;
16
+ installUpdate(unzippedDir: string): Promise<void>;
17
+ reloadApp(): void;
18
+ getCurrentPackageHash(): Promise<string | null>;
19
+ setCurrentPackageInfo(info: string): Promise<void>;
20
+ getCurrentPackageInfo(): Promise<string | null>;
21
+ markSuccess(): Promise<void>;
22
+ rollback(): Promise<void>;
23
+ getInstallId(): Promise<string>;
24
+ getAssetSourceDirectory(): Promise<string | null>;
25
+ clearOldUpdates(): Promise<number>;
26
+ getUpdateMetadata(): Promise<string | null>;
27
+ saveCrashReport(reportJson: string): Promise<void>;
28
+ getPendingCrashReports(): Promise<string>;
29
+ deleteCrashReport(id: string): Promise<void>;
30
+ addListener(eventName: string): void;
31
+ removeListeners(count: number): void;
32
+ }
33
+ declare const _default: Spec | null;
34
+ export default _default;
@@ -0,0 +1,2 @@
1
+ import { TurboModuleRegistry } from "react-native";
2
+ export default TurboModuleRegistry.get("AppSpacerModule");
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Initialize the OTA asset resolver.
3
+ *
4
+ * This patches React Native's `Image.resolveAssetSource` so that assets
5
+ * bundled with OTA updates (images, fonts, icons, JSON) are loaded from the
6
+ * OTA directory instead of the default app binary location.
7
+ *
8
+ * Called automatically by `AppSpacer.init()` — no manual setup needed.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // Automatic (recommended) — called inside AppSpacer.init()
13
+ * AppSpacer.init({ deploymentKey, appVersion });
14
+ *
15
+ * // Manual (advanced)
16
+ * import { initAssetResolver } from "react-native-appspacer";
17
+ * await initAssetResolver();
18
+ * ```
19
+ */
20
+ export declare function initAssetResolver(): Promise<void>;
21
+ /**
22
+ * Get the current OTA asset directory, or null if no OTA update is active.
23
+ */
24
+ export declare function getOtaAssetDirectory(): string | null;
@@ -0,0 +1,130 @@
1
+ import { Platform, Image } from "react-native";
2
+ import { getNativeModule } from "./native";
3
+ /** Cached OTA asset directory path */
4
+ let otaAssetDir = null;
5
+ let isInitialized = false;
6
+ /**
7
+ * Initialize the OTA asset resolver.
8
+ *
9
+ * This patches React Native's `Image.resolveAssetSource` so that assets
10
+ * bundled with OTA updates (images, fonts, icons, JSON) are loaded from the
11
+ * OTA directory instead of the default app binary location.
12
+ *
13
+ * Called automatically by `AppSpacer.init()` — no manual setup needed.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // Automatic (recommended) — called inside AppSpacer.init()
18
+ * AppSpacer.init({ deploymentKey, appVersion });
19
+ *
20
+ * // Manual (advanced)
21
+ * import { initAssetResolver } from "react-native-appspacer";
22
+ * await initAssetResolver();
23
+ * ```
24
+ */
25
+ export async function initAssetResolver() {
26
+ if (isInitialized)
27
+ return;
28
+ try {
29
+ const native = getNativeModule();
30
+ otaAssetDir = await native.getAssetSourceDirectory();
31
+ isInitialized = true;
32
+ if (!otaAssetDir)
33
+ return; // No OTA update active, use default resolution
34
+ patchAssetResolver(otaAssetDir);
35
+ }
36
+ catch {
37
+ // Native module not ready or no OTA update — fall through silently
38
+ isInitialized = true;
39
+ }
40
+ }
41
+ /**
42
+ * Get the current OTA asset directory, or null if no OTA update is active.
43
+ */
44
+ export function getOtaAssetDirectory() {
45
+ return otaAssetDir;
46
+ }
47
+ /**
48
+ * Patch React Native's Image.resolveAssetSource to redirect asset URIs
49
+ * to the OTA update directory when an OTA update is active.
50
+ *
51
+ * How it works:
52
+ * - Metro bundler records assets as numeric IDs with metadata (httpServerLocation, name, type)
53
+ * - RN's default resolver turns these into URIs pointing to the app binary
54
+ * - We intercept and rewrite the URI to point to the OTA directory instead
55
+ */
56
+ function patchAssetResolver(assetDir) {
57
+ const originalResolveAssetSource = Image.resolveAssetSource;
58
+ if (!originalResolveAssetSource)
59
+ return;
60
+ Image.resolveAssetSource = (source) => {
61
+ const resolved = originalResolveAssetSource(source);
62
+ // Only patch asset objects with httpServerLocation (bundled assets)
63
+ if (resolved &&
64
+ typeof source === "number" && // Only numeric asset IDs from require()
65
+ resolved.uri) {
66
+ try {
67
+ // For assets bundled via Metro, the source object has metadata
68
+ // like httpServerLocation, name, type, scales, etc.
69
+ const asset = Image.resolveAssetSource.call(null, source);
70
+ if (asset && !asset.uri.startsWith("http")) {
71
+ // Build a file:// URI pointing to the OTA assets directory
72
+ const assetPath = buildOtaAssetUri(assetDir, resolved);
73
+ if (assetPath) {
74
+ return { ...resolved, uri: assetPath };
75
+ }
76
+ }
77
+ }
78
+ catch {
79
+ // Fall through to default resolution
80
+ }
81
+ }
82
+ return resolved;
83
+ };
84
+ }
85
+ /**
86
+ * Build a file:// URI pointing to an asset in the OTA directory.
87
+ *
88
+ * React Native Metro bundles assets with a structure like:
89
+ * assets/src/images/logo.png (original)
90
+ * assets/src/images/logo@2x.png (scaled)
91
+ *
92
+ * The OTA zip preserves this structure under the assets/ subdirectory.
93
+ */
94
+ function buildOtaAssetUri(assetDir, resolved) {
95
+ // On Android, the asset URI looks like: "asset:///src/images/logo.png"
96
+ // On iOS, it's a file path within the app bundle
97
+ const uri = resolved.uri;
98
+ if (Platform.OS === "android") {
99
+ // Android: strip "asset:///" prefix and reconstruct with OTA path
100
+ if (uri.startsWith("asset:///")) {
101
+ const relativePath = uri.replace("asset:///", "");
102
+ return `file://${assetDir}/assets/${relativePath}`;
103
+ }
104
+ // Drawable resources (e.g. "res/drawable-mdpi/src_images_logo.png")
105
+ // These are compiled into the APK and can't be OTA-updated via file path.
106
+ // But Metro's --assets-dest produces flat files, so we handle that:
107
+ if (uri.startsWith("file://")) {
108
+ // Already a file URI, but pointing to the wrong location
109
+ const filename = uri.split("/").pop();
110
+ if (filename) {
111
+ return `file://${assetDir}/assets/${filename}`;
112
+ }
113
+ }
114
+ }
115
+ else if (Platform.OS === "ios") {
116
+ // iOS: replace the bundle path with OTA path
117
+ // Default URI looks like: "/path/to/App.app/assets/src/images/logo.png"
118
+ const assetsIndex = uri.indexOf("/assets/");
119
+ if (assetsIndex !== -1) {
120
+ const relativePath = uri.substring(assetsIndex);
121
+ return `file://${assetDir}${relativePath}`;
122
+ }
123
+ // Fallback: try to extract the filename
124
+ const filename = uri.split("/").pop();
125
+ if (filename) {
126
+ return `file://${assetDir}/assets/${filename}`;
127
+ }
128
+ }
129
+ return null;
130
+ }
@@ -0,0 +1,8 @@
1
+ export { AppSpacer, AppSpacerManager } from "./AppSpacer";
2
+ export { useAppSpacerUpdate } from "./useAppSpacerUpdate";
3
+ import { withAppSpacer } from "./withAppSpacer";
4
+ export { withAppSpacer };
5
+ export default withAppSpacer;
6
+ export { initAssetResolver, getOtaAssetDirectory } from "./assetResolver";
7
+ export * from "./types";
8
+ export { initCrashReporter } from "./CrashReporter";
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // Core
2
+ export { AppSpacer, AppSpacerManager } from "./AppSpacer";
3
+ // React Hooks and HOCs
4
+ export { useAppSpacerUpdate } from "./useAppSpacerUpdate";
5
+ import { withAppSpacer } from "./withAppSpacer";
6
+ export { withAppSpacer };
7
+ export default withAppSpacer;
8
+ // Asset Resolver
9
+ export { initAssetResolver, getOtaAssetDirectory } from "./assetResolver";
10
+ // Types
11
+ export * from "./types";
12
+ // Crash Reporter
13
+ export { initCrashReporter } from "./CrashReporter";
@@ -0,0 +1,88 @@
1
+ import { NativeEventEmitter } from "react-native";
2
+ export interface AppSpacerNativeModule {
3
+ /**
4
+ * Get the OTA storage directory path.
5
+ */
6
+ getOtaStoragePath(): Promise<string>;
7
+ /**
8
+ * Download a zip file from URL to a local temporary path.
9
+ */
10
+ downloadPackage(url: string, destPath: string): Promise<string>;
11
+ /**
12
+ * Verify the SHA256 hash of a file.
13
+ * Returns true if valid, throws or returns false otherwise.
14
+ */
15
+ verifyHash(filePath: string, expectedHash: string): Promise<boolean>;
16
+ /**
17
+ * Unzip a package into the specified destination directory.
18
+ */
19
+ unzipPackage(zipPath: string, destDir: string): Promise<void>;
20
+ /**
21
+ * Install the update (moves current to previous, and new to current).
22
+ */
23
+ installUpdate(unzippedDir: string): Promise<void>;
24
+ /**
25
+ * Reload the JavaScript bundle (restarts the React Native bridge).
26
+ */
27
+ reloadApp(): void;
28
+ /**
29
+ * Get the current installed package hash, if any.
30
+ */
31
+ getCurrentPackageHash(): Promise<string | null>;
32
+ /**
33
+ * Save metadata about the currently installed update.
34
+ */
35
+ setCurrentPackageInfo(info: string): Promise<void>;
36
+ /**
37
+ * Get metadata about the currently installed update.
38
+ */
39
+ getCurrentPackageInfo(): Promise<string | null>;
40
+ /**
41
+ * Mark the current update as successful to prevent crash loop rollback.
42
+ */
43
+ markSuccess(): Promise<void>;
44
+ /**
45
+ * Rollback to the previous bundle manually.
46
+ */
47
+ rollback(): Promise<void>;
48
+ /**
49
+ * Get the persistent anonymous installation ID for deterministic rollout percentages.
50
+ */
51
+ getInstallId(): Promise<string>;
52
+ /**
53
+ * Get the OTA asset source directory path.
54
+ * Returns the root directory of the current OTA update where assets
55
+ * (images, fonts, icons, JSON) are stored. Returns null if no OTA update is active.
56
+ */
57
+ getAssetSourceDirectory(): Promise<string | null>;
58
+ /**
59
+ * Remove old update directories that are no longer active.
60
+ * Returns the number of directories removed.
61
+ */
62
+ clearOldUpdates(): Promise<number>;
63
+ /**
64
+ * Get metadata about the currently installed update.
65
+ * Returns JSON string with hash, releaseId, description, label, installedAt, isPending.
66
+ */
67
+ getUpdateMetadata(): Promise<string | null>;
68
+ /**
69
+ * Save a crash report to local storage.
70
+ */
71
+ saveCrashReport(reportJson: string): Promise<void>;
72
+ /**
73
+ * Get all pending crash reports as a JSON string.
74
+ */
75
+ getPendingCrashReports(): Promise<string>;
76
+ deleteCrashReport(id: string): Promise<void>;
77
+ testNativeCrash(): void;
78
+ addBreadcrumb(message: string): void;
79
+ setUserIdentity(userId: string, metadata: string): void;
80
+ addListener(eventName: string): void;
81
+ removeListeners(count: number): void;
82
+ }
83
+ /**
84
+ * Access the native AppSpacer module.
85
+ * Throws if the native module isn't linked.
86
+ */
87
+ export declare function getNativeModule(): AppSpacerNativeModule;
88
+ export declare function getEventEmitter(): NativeEventEmitter;
package/dist/native.js ADDED
@@ -0,0 +1,25 @@
1
+ import { NativeModules, Platform, NativeEventEmitter } from "react-native";
2
+ const NativeAppSpacer = NativeModules.AppSpacerModule;
3
+ /**
4
+ * Access the native AppSpacer module.
5
+ * Throws if the native module isn't linked.
6
+ */
7
+ export function getNativeModule() {
8
+ if (!NativeAppSpacer) {
9
+ throw new Error(`react-native-appspacer: Native module not found. ` +
10
+ `Make sure you have run 'pod install' (iOS) and rebuilt the app. ` +
11
+ `Platform: ${Platform.OS}`);
12
+ }
13
+ return NativeAppSpacer;
14
+ }
15
+ /**
16
+ * Event emitter for native download progress events.
17
+ */
18
+ let _emitter = null;
19
+ export function getEventEmitter() {
20
+ if (!_emitter) {
21
+ const nativeModule = getNativeModule();
22
+ _emitter = new NativeEventEmitter(nativeModule);
23
+ }
24
+ return _emitter;
25
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * AppSpacer SDK configuration.
3
+ */
4
+ export interface AppSpacerConfig {
5
+ /** The Deployment Key for this environment (e.g. "sk_xxxx" or "pk_xxxx") */
6
+ deploymentKey: string;
7
+ /** Current app version (e.g. "1.0.0") */
8
+ appVersion: string;
9
+ /**
10
+ * How often to check for updates in seconds.
11
+ * Set to 0 to only check manually. Default: 0 (manual only).
12
+ */
13
+ checkInterval?: number;
14
+ /**
15
+ * Enable automatic crash analytics (Catches JS, iOS native, and Android native crashes)
16
+ * Defaults to false.
17
+ */
18
+ enableCrashReporting?: boolean;
19
+ }
20
+ /**
21
+ * Represents a crash report generated by the SDK.
22
+ */
23
+ export interface CrashReport {
24
+ /** Unique ID for the crash report */
25
+ id: string;
26
+ /** Layer where the crash originated */
27
+ type: "javascript" | "native";
28
+ /** Serialized crash payload (stack trace, device info) */
29
+ payload: string;
30
+ /** ISO 8601 timestamp of when the crash occurred */
31
+ timestamp: string;
32
+ /** List of breadcrumbs leading up to the crash */
33
+ breadcrumbs?: string[];
34
+ /** Unique session ID for grouping crashes */
35
+ session_id?: string;
36
+ user_id?: string;
37
+ user_metadata?: object;
38
+ }
39
+ /**
40
+ * Represents a user activity breadcrumb.
41
+ */
42
+ export interface Breadcrumb {
43
+ message: string;
44
+ category?: string;
45
+ data?: object;
46
+ timestamp: string;
47
+ }
48
+ /**
49
+ * Represents an available OTA update.
50
+ */
51
+ export interface AppSpacerUpdate {
52
+ /** Whether an update is available */
53
+ updateAvailable: boolean;
54
+ /** Server-assigned release ID (used for install status reporting) */
55
+ releaseId?: string;
56
+ /** URL to download the OTA package zip */
57
+ packageUrl?: string;
58
+ /** SHA256 Hash of the package */
59
+ hash?: string;
60
+ /** Whether this update is mandatory */
61
+ mandatory?: boolean;
62
+ /** Release description */
63
+ description?: string;
64
+ /** Release label (e.g. v1, v2) */
65
+ label?: string;
66
+ /** Size of the package in bytes */
67
+ packageSize?: number;
68
+ }
69
+ /**
70
+ * Current state of the update process.
71
+ */
72
+ export declare enum UpdateStatus {
73
+ /** No update activity */
74
+ IDLE = "IDLE",
75
+ /** Checking the server for updates */
76
+ CHECKING = "CHECKING",
77
+ /** An update is available but not yet downloaded */
78
+ UPDATE_AVAILABLE = "UPDATE_AVAILABLE",
79
+ /** Downloading the bundle */
80
+ DOWNLOADING = "DOWNLOADING",
81
+ /** Bundle downloaded, verified and ready to install */
82
+ INSTALLING = "INSTALLING",
83
+ /** Update installed, awaiting restart */
84
+ INSTALLED = "INSTALLED",
85
+ /** App is already up to date */
86
+ UP_TO_DATE = "UP_TO_DATE",
87
+ /** An error occurred */
88
+ ERROR = "ERROR"
89
+ }
90
+ /**
91
+ * Installation modes for AppSpacer.sync()
92
+ */
93
+ export declare enum InstallMode {
94
+ /** Install and restart immediately */
95
+ IMMEDIATE = "IMMEDIATE",
96
+ /** Install and apply on next app restart */
97
+ ON_NEXT_RESTART = "ON_NEXT_RESTART",
98
+ /** Install and apply when app next resumes from background */
99
+ ON_NEXT_RESUME = "ON_NEXT_RESUME"
100
+ }
101
+ /**
102
+ * Metadata about the currently installed OTA update.
103
+ */
104
+ export interface UpdateMetadata {
105
+ /** SHA256 hash of the installed package */
106
+ hash: string;
107
+ /** Server-assigned release ID */
108
+ releaseId?: string;
109
+ /** Release description */
110
+ description?: string;
111
+ /** Release label (e.g. v1, v2) */
112
+ label?: string;
113
+ /** ISO 8601 timestamp of when the update was installed */
114
+ installedAt?: string;
115
+ /** Whether the update is pending (hasn't been confirmed via markSuccess yet) */
116
+ isPending?: boolean;
117
+ }
118
+ /**
119
+ * Options for the built-in update dialog.
120
+ */
121
+ export interface UpdateDialogOptions {
122
+ /** Title of the popup (Default: "Update Available") */
123
+ title?: string;
124
+ /** Message in the popup (Default: "An update is available for this app. Would you like to install it now?") */
125
+ message?: string;
126
+ /** Label for the 'Install' button (Default: "Install") */
127
+ installButtonLabel?: string;
128
+ /** Label for the 'Ignore' button on optional updates (Default: "Ignore") */
129
+ ignoreButtonLabel?: string;
130
+ /** Label for the 'Install' button on mandatory updates (Default: "Continue") */
131
+ mandatoryContinueButtonLabel?: string;
132
+ /** Message to display for mandatory updates (Default: "An update is available that must be installed.") */
133
+ mandatoryUpdateMessage?: string;
134
+ /** Whether to append the release description to the message (Default: true) */
135
+ appendReleaseDescription?: boolean;
136
+ }
137
+ /**
138
+ * Visual themes for the built-in update dialog.
139
+ */
140
+ export declare enum UpdateUIType {
141
+ /** Premium dark theme with colored glow (Default) */
142
+ PREMIUM_DARK = "PREMIUM_DARK",
143
+ /** Modern glassmorphism with high transparency */
144
+ GLASS = "GLASS",
145
+ /** Ultra-clean minimalist light theme */
146
+ MINIMAL = "MINIMAL",
147
+ /** Modern bottom sheet layout (iOS-inspired) */
148
+ BOTTOM_SHEET = "BOTTOM_SHEET",
149
+ /** Immersive full-screen update experience */
150
+ FULL_SCREEN = "FULL_SCREEN"
151
+ }
152
+ /**
153
+ * Options for the AppSpacer.sync() lifecycle.
154
+ */
155
+ export interface SyncOptions {
156
+ /** Where to install/apply (IMMEDIATE, ON_NEXT_RESTART, ON_NEXT_RESUME) */
157
+ installMode?: InstallMode;
158
+ /** Mode to use specifically for mandatory updates (Overrides installMode) */
159
+ mandatoryInstallMode?: InstallMode;
160
+ /** Whether to show a native prompt if an update is available */
161
+ updateDialog?: boolean | UpdateDialogOptions;
162
+ /** The UI theme to use for the update dialog (Default: PREMIUM_DARK) */
163
+ updateUI?: UpdateUIType;
164
+ /** Minimum background duration before applying (if ON_NEXT_RESUME) */
165
+ minimumBackgroundDuration?: number;
166
+ }
package/dist/types.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Current state of the update process.
3
+ */
4
+ export var UpdateStatus;
5
+ (function (UpdateStatus) {
6
+ /** No update activity */
7
+ UpdateStatus["IDLE"] = "IDLE";
8
+ /** Checking the server for updates */
9
+ UpdateStatus["CHECKING"] = "CHECKING";
10
+ /** An update is available but not yet downloaded */
11
+ UpdateStatus["UPDATE_AVAILABLE"] = "UPDATE_AVAILABLE";
12
+ /** Downloading the bundle */
13
+ UpdateStatus["DOWNLOADING"] = "DOWNLOADING";
14
+ /** Bundle downloaded, verified and ready to install */
15
+ UpdateStatus["INSTALLING"] = "INSTALLING";
16
+ /** Update installed, awaiting restart */
17
+ UpdateStatus["INSTALLED"] = "INSTALLED";
18
+ /** App is already up to date */
19
+ UpdateStatus["UP_TO_DATE"] = "UP_TO_DATE";
20
+ /** An error occurred */
21
+ UpdateStatus["ERROR"] = "ERROR";
22
+ })(UpdateStatus || (UpdateStatus = {}));
23
+ /**
24
+ * Installation modes for AppSpacer.sync()
25
+ */
26
+ export var InstallMode;
27
+ (function (InstallMode) {
28
+ /** Install and restart immediately */
29
+ InstallMode["IMMEDIATE"] = "IMMEDIATE";
30
+ /** Install and apply on next app restart */
31
+ InstallMode["ON_NEXT_RESTART"] = "ON_NEXT_RESTART";
32
+ /** Install and apply when app next resumes from background */
33
+ InstallMode["ON_NEXT_RESUME"] = "ON_NEXT_RESUME";
34
+ })(InstallMode || (InstallMode = {}));
35
+ /**
36
+ * Visual themes for the built-in update dialog.
37
+ */
38
+ export var UpdateUIType;
39
+ (function (UpdateUIType) {
40
+ /** Premium dark theme with colored glow (Default) */
41
+ UpdateUIType["PREMIUM_DARK"] = "PREMIUM_DARK";
42
+ /** Modern glassmorphism with high transparency */
43
+ UpdateUIType["GLASS"] = "GLASS";
44
+ /** Ultra-clean minimalist light theme */
45
+ UpdateUIType["MINIMAL"] = "MINIMAL";
46
+ /** Modern bottom sheet layout (iOS-inspired) */
47
+ UpdateUIType["BOTTOM_SHEET"] = "BOTTOM_SHEET";
48
+ /** Immersive full-screen update experience */
49
+ UpdateUIType["FULL_SCREEN"] = "FULL_SCREEN";
50
+ })(UpdateUIType || (UpdateUIType = {}));
@@ -0,0 +1,31 @@
1
+ import type { AppSpacerUpdate, InstallMode, UpdateMetadata } from "./types";
2
+ import { UpdateStatus } from "./types";
3
+ interface UseAppSpacerUpdateResult {
4
+ /** Current status of the update process */
5
+ status: UpdateStatus;
6
+ /** Available update info (if any) */
7
+ update: AppSpacerUpdate | null;
8
+ /** Error message if status is ERROR */
9
+ error: string | null;
10
+ /** Download progress (0 to 1) while status is DOWNLOADING */
11
+ downloadProgress: number;
12
+ /** Manually check for updates */
13
+ checkForUpdate: () => Promise<void>;
14
+ /** Download, verify and prepare the update for installation */
15
+ downloadUpdate: () => Promise<void>;
16
+ /** Restart the app to apply the update */
17
+ restartApp: () => void;
18
+ /** Full lifecycle helper: check, download, and optionally restart */
19
+ sync: (options?: {
20
+ installMode?: InstallMode;
21
+ }) => Promise<void>;
22
+ /** Remove old update directories that are no longer active */
23
+ clearOldUpdates: () => Promise<number>;
24
+ /** Get metadata about the currently installed update */
25
+ getUpdateMetadata: () => Promise<UpdateMetadata | null>;
26
+ }
27
+ /**
28
+ * Production-grade React hook for AppSpacer OTA updates.
29
+ */
30
+ export declare function useAppSpacerUpdate(): UseAppSpacerUpdateResult;
31
+ export {};