react-native-onyx 3.0.86 → 3.0.87

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.
@@ -69,6 +69,10 @@ function getBudgetedHealErrorLabel(error) {
69
69
  return 'connection lost';
70
70
  return 'unknown';
71
71
  }
72
+ /** Union of all error types indicating a stale/dead IDB connection. Used by the visibilitychange probe. */
73
+ function isStaleConnectionError(error) {
74
+ return isInvalidStateError(error) || isBackingStoreError(error) || isConnectionLostError(error);
75
+ }
72
76
  // This is a copy of the createStore function from idb-keyval, we need a custom implementation
73
77
  // because we need to create the database manually in order to ensure that the store exists before we use it.
74
78
  // If the store does not exist, idb-keyval will throw an error
@@ -146,6 +150,52 @@ function createStore(dbName, storeName) {
146
150
  healAttemptsRemaining = HEAL_ATTEMPTS_MAX;
147
151
  return result;
148
152
  }
153
+ // Proactive IDB health check when tab returns to foreground.
154
+ // Safari kills IDB connections for backgrounded tabs. By probing as soon as
155
+ // the tab becomes visible, we drop the stale dbp early so the first real
156
+ // operation opens a fresh connection instead of failing.
157
+ document.addEventListener('visibilitychange', () => {
158
+ if (document.visibilityState !== 'visible' || !dbp) {
159
+ return;
160
+ }
161
+ Logger.logInfo('IDB visibilitychange probe: tab became visible, checking connection health', { dbName, storeName });
162
+ const probePromise = dbp;
163
+ const dropCacheIfStale = (error) => {
164
+ if (dbp !== probePromise || !isStaleConnectionError(error)) {
165
+ return;
166
+ }
167
+ Logger.logAlert('IDB visibilitychange probe: stale connection detected, dropping cached connection', {
168
+ dbName,
169
+ storeName,
170
+ errorMessage: error instanceof Error ? error.message : String(error),
171
+ });
172
+ dbp = undefined;
173
+ };
174
+ probePromise
175
+ .then((db) => {
176
+ if (dbp !== probePromise) {
177
+ return;
178
+ }
179
+ try {
180
+ const tx = db.transaction(storeName, 'readonly');
181
+ const probeStore = tx.objectStore(storeName);
182
+ const req = probeStore.count();
183
+ req.onsuccess = () => {
184
+ Logger.logInfo('IDB visibilitychange probe: connection is healthy', { dbName, storeName });
185
+ };
186
+ req.onerror = () => {
187
+ dropCacheIfStale(req.error);
188
+ };
189
+ }
190
+ catch (error) {
191
+ dropCacheIfStale(error);
192
+ }
193
+ })
194
+ .catch(() => {
195
+ // The cached open promise rejected; cacheOpenPromise already cleared dbp on its own
196
+ // branch. Swallow here so the probe's separate branch doesn't surface an unhandled rejection.
197
+ });
198
+ });
149
199
  // Handles three recoverable error classes:
150
200
  // 1. InvalidStateError — connection closed between getDB() resolving and db.transaction().
151
201
  // Retry once with a fresh connection. No budget limit (transient, always worth one reopen).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "3.0.86",
3
+ "version": "3.0.87",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",