@teardown/react-native 2.0.1 → 2.0.2
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.
- package/package.json +46 -18
- package/src/clients/api/api.client.ts +2 -2
- package/src/clients/device/{expo-adapter.ts → adapters/basic.adapter.ts} +1 -1
- package/src/clients/device/{device.adpater-interface.ts → adapters/device.adpater-interface.ts} +1 -1
- package/src/clients/device/adapters/expo.adapter.ts +90 -0
- package/src/clients/device/device.client.test.ts +1 -1
- package/src/clients/device/device.client.ts +5 -1
- package/src/clients/device/index.ts +1 -1
- package/src/clients/force-update/force-update.client.test.ts +238 -6
- package/src/clients/force-update/force-update.client.ts +71 -11
- package/src/clients/identity/identity.client.test.ts +888 -223
- package/src/clients/identity/identity.client.ts +59 -20
- package/src/clients/storage/adapters/async-storage.adapter.ts +81 -0
- package/src/clients/storage/{mmkv-adapter.ts → adapters/mmkv.adapter.ts} +7 -10
- package/src/clients/storage/adapters/storage.adpater-interface.ts +30 -0
- package/src/clients/storage/index.ts +2 -1
- package/src/clients/storage/storage.client.ts +9 -20
- package/src/clients/utils/utils.client.ts +1 -57
- package/src/exports/adapters/async-storage.ts +1 -0
- package/src/exports/adapters/expo.ts +1 -0
- package/src/exports/adapters/mmkv.ts +1 -0
- package/src/hooks/use-force-update.ts +12 -3
- package/src/hooks/use-session.ts +7 -4
- package/src/teardown.core.ts +16 -6
- package/src/exports/expo.ts +0 -1
- package/src/exports/mmkv.ts +0 -1
|
@@ -71,7 +71,11 @@ export type VersionStatusChangeEvents = {
|
|
|
71
71
|
export type ForceUpdateClientOptions = {
|
|
72
72
|
/** Min ms between foreground checks (default: 30000) */
|
|
73
73
|
throttleMs?: number;
|
|
74
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Min ms since last successful check before re-checking (default: 300000 = 5min)
|
|
76
|
+
* Set this to 0 to disable cooldown and check immediately on every foreground transition.
|
|
77
|
+
* Set to -1 disable checking entirely.
|
|
78
|
+
*/
|
|
75
79
|
checkCooldownMs?: number;
|
|
76
80
|
};
|
|
77
81
|
|
|
@@ -84,11 +88,12 @@ export const VERSION_STATUS_STORAGE_KEY = "VERSION_STATUS";
|
|
|
84
88
|
|
|
85
89
|
export class ForceUpdateClient {
|
|
86
90
|
private emitter = new EventEmitter<VersionStatusChangeEvents>();
|
|
87
|
-
private versionStatus: VersionStatus;
|
|
91
|
+
private versionStatus: VersionStatus = { type: "initializing" };
|
|
88
92
|
private unsubscribe: (() => void) | null = null;
|
|
89
93
|
private appStateSubscription: NativeEventSubscription | null = null;
|
|
90
94
|
private lastCheckTime: number | null = null;
|
|
91
95
|
private lastForegroundTime: number | null = null;
|
|
96
|
+
private initialized = false;
|
|
92
97
|
|
|
93
98
|
private readonly logger: Logger;
|
|
94
99
|
private readonly storage: SupportedStorage;
|
|
@@ -103,18 +108,55 @@ export class ForceUpdateClient {
|
|
|
103
108
|
this.logger = logging.createLogger({ name: "ForceUpdateClient" });
|
|
104
109
|
this.storage = storage.createStorage("version");
|
|
105
110
|
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
111
|
+
// Don't initialize here - defer to initialize()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
initialize(): void {
|
|
115
|
+
if (this.initialized) {
|
|
116
|
+
this.logger.debug("ForceUpdateClient already initialized");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.initialized = true;
|
|
120
|
+
|
|
121
|
+
// Load from storage, subscribe to events, and sync with current identity state
|
|
106
122
|
this.versionStatus = this.getVersionStatusFromStorage();
|
|
123
|
+
this.logger.debug(`Initialized with version status: ${this.versionStatus.type}`);
|
|
107
124
|
this.subscribeToIdentity();
|
|
125
|
+
this.initializeFromCurrentIdentityState();
|
|
108
126
|
this.subscribeToAppState();
|
|
109
127
|
}
|
|
110
128
|
|
|
129
|
+
private initializeFromCurrentIdentityState() {
|
|
130
|
+
const currentState = this.identity.getIdentifyState();
|
|
131
|
+
this.logger.debug(`Current identity state during init: ${currentState.type}`);
|
|
132
|
+
if (currentState.type === "identified") {
|
|
133
|
+
this.logger.debug(`Identity already identified, syncing version status from: ${currentState.version_info.status}`);
|
|
134
|
+
this.updateFromVersionStatus(currentState.version_info.status);
|
|
135
|
+
} else {
|
|
136
|
+
this.logger.debug(`Identity not yet identified (${currentState.type}), waiting for identify event`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
111
140
|
private getVersionStatusFromStorage(): VersionStatus {
|
|
112
141
|
const stored = this.storage.getItem(VERSION_STATUS_STORAGE_KEY);
|
|
142
|
+
|
|
113
143
|
if (stored == null) {
|
|
144
|
+
this.logger.debug("No stored version status, returning initializing");
|
|
145
|
+
return InitializingVersionStatusSchema.parse({ type: "initializing" });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const parsed = VersionStatusSchema.parse(JSON.parse(stored));
|
|
149
|
+
this.logger.debug(`Parsed version status from storage: ${parsed.type}`);
|
|
150
|
+
|
|
151
|
+
// "checking" and "initializing" are transient states - if we restore them, reset to initializing
|
|
152
|
+
// This can happen if the app was killed during a version check
|
|
153
|
+
if (parsed.type === "checking" || parsed.type === "initializing") {
|
|
154
|
+
this.logger.debug(`Found stale '${parsed.type}' state in storage, resetting to initializing`);
|
|
155
|
+
this.storage.removeItem(VERSION_STATUS_STORAGE_KEY);
|
|
114
156
|
return InitializingVersionStatusSchema.parse({ type: "initializing" });
|
|
115
157
|
}
|
|
116
158
|
|
|
117
|
-
return
|
|
159
|
+
return parsed;
|
|
118
160
|
}
|
|
119
161
|
|
|
120
162
|
private saveVersionStatusToStorage(status: VersionStatus): void {
|
|
@@ -123,10 +165,15 @@ export class ForceUpdateClient {
|
|
|
123
165
|
|
|
124
166
|
private subscribeToIdentity() {
|
|
125
167
|
this.unsubscribe = this.identity.onIdentifyStateChange((state) => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
168
|
+
this.logger.debug(`Identity state changed: ${state.type}`);
|
|
169
|
+
switch (state.type) {
|
|
170
|
+
case "identifying":
|
|
171
|
+
this.setVersionStatus({ type: "checking" });
|
|
172
|
+
break;
|
|
173
|
+
case "identified":
|
|
174
|
+
this.logger.debug(`Identified with version_info.status: ${state.version_info.status}`);
|
|
175
|
+
this.updateFromVersionStatus(state.version_info.status ?? IdentifyVersionStatusEnum.UP_TO_DATE);
|
|
176
|
+
break;
|
|
130
177
|
}
|
|
131
178
|
});
|
|
132
179
|
}
|
|
@@ -164,9 +211,22 @@ export class ForceUpdateClient {
|
|
|
164
211
|
|
|
165
212
|
private handleAppStateChange = (nextState: AppStateStatus) => {
|
|
166
213
|
if (nextState === "active") {
|
|
214
|
+
this.logger.debug("App state changed to active");
|
|
215
|
+
|
|
216
|
+
// If checkCooldownMs is -1, disable checking entirely
|
|
217
|
+
if (this.options.checkCooldownMs === -1) {
|
|
218
|
+
this.logger.debug("Version checking disabled (checkCooldownMs = -1)");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
167
222
|
const now = Date.now();
|
|
168
223
|
const throttleOk = !this.lastForegroundTime || now - this.lastForegroundTime >= this.options.throttleMs;
|
|
169
|
-
|
|
224
|
+
|
|
225
|
+
// If checkCooldownMs is 0, always allow check (no cooldown)
|
|
226
|
+
// Otherwise, check if enough time has passed since last successful check
|
|
227
|
+
const cooldownOk = this.options.checkCooldownMs === 0
|
|
228
|
+
|| !this.lastCheckTime
|
|
229
|
+
|| now - this.lastCheckTime >= this.options.checkCooldownMs;
|
|
170
230
|
|
|
171
231
|
this.lastForegroundTime = now;
|
|
172
232
|
|
|
@@ -177,11 +237,11 @@ export class ForceUpdateClient {
|
|
|
177
237
|
};
|
|
178
238
|
|
|
179
239
|
private async checkVersionOnForeground() {
|
|
180
|
-
this.logger.
|
|
240
|
+
this.logger.debug("Checking version status on foreground");
|
|
181
241
|
const result = await this.identity.identify();
|
|
182
242
|
|
|
183
243
|
if (!result) {
|
|
184
|
-
this.logger.
|
|
244
|
+
this.logger.debug("Skipping version check - not identified");
|
|
185
245
|
return;
|
|
186
246
|
}
|
|
187
247
|
|
|
@@ -203,7 +263,7 @@ export class ForceUpdateClient {
|
|
|
203
263
|
}
|
|
204
264
|
|
|
205
265
|
private setVersionStatus(newStatus: VersionStatus) {
|
|
206
|
-
this.logger.
|
|
266
|
+
this.logger.debug(`Version status changing: ${this.versionStatus.type} -> ${newStatus.type}`);
|
|
207
267
|
this.versionStatus = newStatus;
|
|
208
268
|
this.saveVersionStatusToStorage(newStatus);
|
|
209
269
|
this.emitter.emit("VERSION_STATUS_CHANGED", newStatus);
|