Skip to content

Commit 577e611

Browse files
committed
1 parent b2c09fa commit 577e611

16 files changed

+127
-286
lines changed

jestSetup.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
jest.mock('./lib/storage');
2-
jest.mock('./lib/storage/platforms/index.native', () => require('./lib/storage/__mocks__'));
3-
jest.mock('./lib/storage/platforms/index', () => require('./lib/storage/__mocks__'));
4-
jest.mock('./lib/storage/providers/IDBKeyValProvider', () => require('./lib/storage/__mocks__'));
2+
jest.mock('./lib/storage/NativeStorage', () => require('./lib/storage/__mocks__'));
3+
jest.mock('./lib/storage/WebStorage', () => require('./lib/storage/__mocks__'));
4+
jest.mock('./lib/storage/providers/IDBKeyVal', () => require('./lib/storage/__mocks__'));
55

66
jest.mock('react-native-device-info', () => ({getFreeDiskStorage: () => {}}));
77
jest.mock('react-native-quick-sqlite', () => ({

lib/Onyx.js

+8-10
Original file line numberDiff line numberDiff line change
@@ -1611,16 +1611,6 @@ function setMemoryOnlyKeys(keyList) {
16111611
* });
16121612
*/
16131613
function init({keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = Boolean(global.localStorage), debugSetState = false} = {}) {
1614-
Storage.init();
1615-
1616-
if (shouldSyncMultipleInstances) {
1617-
Storage.keepInstancesSync((key, value) => {
1618-
const prevValue = cache.getValue(key, false);
1619-
cache.set(key, value);
1620-
keyChanged(key, value, prevValue);
1621-
});
1622-
}
1623-
16241614
if (debugSetState) {
16251615
PerformanceUtils.setShouldDebugSetState(true);
16261616
}
@@ -1651,6 +1641,14 @@ function init({keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCache
16511641

16521642
// Initialize all of our keys with data provided then give green light to any pending connections
16531643
Promise.all([addAllSafeEvictionKeysToRecentlyAccessedList(), initializeWithDefaultKeyStates()]).then(deferredInitTask.resolve);
1644+
1645+
if (shouldSyncMultipleInstances && _.isFunction(Storage.keepInstancesSync)) {
1646+
Storage.keepInstancesSync((key, value) => {
1647+
const prevValue = cache.getValue(key, false);
1648+
cache.set(key, value);
1649+
keyChanged(key, value, prevValue);
1650+
});
1651+
}
16541652
}
16551653

16561654
const Onyx = {

lib/storage/InstanceSync/index.ts

-16
This file was deleted.

lib/storage/InstanceSync/index.web.ts

-65
This file was deleted.

lib/storage/NativeStorage.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import SQLiteStorage from './providers/SQLiteStorage';
2+
3+
export default SQLiteStorage;

lib/storage/WebStorage.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* This file is here to wrap IDBKeyVal with a layer that provides data-changed events like the ones that exist
3+
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
4+
* data changes and then stay up-to-date with everything happening in Onyx.
5+
*/
6+
import Storage from './providers/IDBKeyVal';
7+
import type {KeyList, Key} from './providers/types';
8+
import type StorageProvider from './providers/types';
9+
10+
const SYNC_ONYX = 'SYNC_ONYX';
11+
12+
/**
13+
* Raise an event thorough `localStorage` to let other tabs know a value changed
14+
*/
15+
function raiseStorageSyncEvent(onyxKey: Key) {
16+
global.localStorage.setItem(SYNC_ONYX, onyxKey);
17+
global.localStorage.removeItem(SYNC_ONYX);
18+
}
19+
20+
function raiseStorageSyncManyKeysEvent(onyxKeys: KeyList) {
21+
onyxKeys.forEach((onyxKey) => {
22+
raiseStorageSyncEvent(onyxKey);
23+
});
24+
}
25+
26+
const webStorage: StorageProvider = {
27+
...Storage,
28+
/**
29+
* @param onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
30+
*/
31+
keepInstancesSync(onStorageKeyChanged) {
32+
// Override set, remove and clear to raise storage events that we intercept in other tabs
33+
this.setItem = (key, value) => Storage.setItem(key, value).then(() => raiseStorageSyncEvent(key));
34+
35+
this.removeItem = (key) => Storage.removeItem(key).then(() => raiseStorageSyncEvent(key));
36+
37+
this.removeItems = (keys) => Storage.removeItems(keys).then(() => raiseStorageSyncManyKeysEvent(keys));
38+
39+
this.mergeItem = (key, batchedChanges, modifiedData) => Storage.mergeItem(key, batchedChanges, modifiedData).then(() => raiseStorageSyncEvent(key));
40+
41+
// If we just call Storage.clear other tabs will have no idea which keys were available previously
42+
// so that they can call keysChanged for them. That's why we iterate over every key and raise a storage sync
43+
// event for each one
44+
this.clear = () => {
45+
let allKeys: KeyList;
46+
47+
// The keys must be retrieved before storage is cleared or else the list of keys would be empty
48+
return Storage.getAllKeys()
49+
.then((keys) => {
50+
allKeys = keys;
51+
})
52+
.then(() => Storage.clear())
53+
.then(() => {
54+
// Now that storage is cleared, the storage sync event can happen which is a more atomic action
55+
// for other browser tabs
56+
allKeys.forEach(raiseStorageSyncEvent);
57+
});
58+
};
59+
60+
// This listener will only be triggered by events coming from other tabs
61+
global.addEventListener('storage', (event) => {
62+
// Ignore events that don't originate from the SYNC_ONYX logic
63+
if (event.key !== SYNC_ONYX || !event.newValue) {
64+
return;
65+
}
66+
67+
const onyxKey = event.newValue;
68+
Storage.getItem(onyxKey).then((value) => onStorageKeyChanged(onyxKey, value));
69+
});
70+
},
71+
};
72+
73+
export default webStorage;

lib/storage/__mocks__/index.ts

-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const set = jest.fn((key, value) => {
1010
});
1111

1212
const idbKeyvalMock: StorageProvider = {
13-
init: () => undefined,
1413
setItem(key, value) {
1514
return set(key, value);
1615
},
@@ -61,12 +60,10 @@ const idbKeyvalMock: StorageProvider = {
6160
},
6261
// eslint-disable-next-line @typescript-eslint/no-empty-function
6362
setMemoryOnlyKeys() {},
64-
keepInstancesSync: () => undefined,
6563
};
6664

6765
const idbKeyvalMockSpy = {
6866
idbKeyvalSet: set,
69-
init: jest.fn(idbKeyvalMock.init),
7067
setItem: jest.fn(idbKeyvalMock.setItem),
7168
getItem: jest.fn(idbKeyvalMock.getItem),
7269
removeItem: jest.fn(idbKeyvalMock.removeItem),
@@ -83,7 +80,6 @@ const idbKeyvalMockSpy = {
8380
}),
8481
getDatabaseSize: jest.fn(idbKeyvalMock.getDatabaseSize),
8582
setMemoryOnlyKeys: jest.fn(idbKeyvalMock.setMemoryOnlyKeys),
86-
keepInstancesSync: jest.fn(idbKeyvalMock.keepInstancesSync),
8783
};
8884

8985
export default idbKeyvalMockSpy;

lib/storage/index.native.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import NativeStorage from './NativeStorage';
2+
3+
export default NativeStorage;

lib/storage/index.ts

+2-136
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,3 @@
1-
import PlatformStorage from './platforms';
2-
import InstanceSync from './InstanceSync';
3-
import type StorageProvider from './providers/types';
1+
import WebStorage from './WebStorage';
42

5-
const provider = PlatformStorage;
6-
let shouldKeepInstancesSync = false;
7-
8-
type Storage = {
9-
getStorageProvider: () => StorageProvider;
10-
} & StorageProvider;
11-
12-
const Storage: Storage = {
13-
/**
14-
* Returns the storage provider currently in use
15-
*/
16-
getStorageProvider() {
17-
return provider;
18-
},
19-
20-
/**
21-
* Initializes all providers in the list of storage providers
22-
* and enables fallback providers if necessary
23-
*/
24-
init() {
25-
provider.init();
26-
},
27-
28-
/**
29-
* Get the value of a given key or return `null` if it's not available
30-
*/
31-
getItem: (key) => provider.getItem(key),
32-
33-
/**
34-
* Get multiple key-value pairs for the give array of keys in a batch
35-
*/
36-
multiGet: (keys) => provider.multiGet(keys),
37-
38-
/**
39-
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
40-
*/
41-
setItem: (key, value) => {
42-
const promise = provider.setItem(key, value);
43-
44-
if (shouldKeepInstancesSync) {
45-
return promise.then(() => InstanceSync.setItem(key));
46-
}
47-
48-
return promise;
49-
},
50-
51-
/**
52-
* Stores multiple key-value pairs in a batch
53-
*/
54-
multiSet: (pairs) => provider.multiSet(pairs),
55-
56-
/**
57-
* Merging an existing value with a new one
58-
*/
59-
mergeItem: (key, changes, modifiedData) => {
60-
const promise = provider.mergeItem(key, changes, modifiedData);
61-
62-
if (shouldKeepInstancesSync) {
63-
return promise.then(() => InstanceSync.mergeItem(key));
64-
}
65-
66-
return promise;
67-
},
68-
69-
/**
70-
* Multiple merging of existing and new values in a batch
71-
* This function also removes all nested null values from an object.
72-
*/
73-
multiMerge: (pairs) => provider.multiMerge(pairs),
74-
75-
/**
76-
* Removes given key and its value
77-
*/
78-
removeItem: (key) => {
79-
const promise = provider.removeItem(key);
80-
81-
if (shouldKeepInstancesSync) {
82-
return promise.then(() => InstanceSync.removeItem(key));
83-
}
84-
85-
return promise;
86-
},
87-
88-
/**
89-
* Remove given keys and their values
90-
*/
91-
removeItems: (keys) => {
92-
const promise = provider.removeItems(keys);
93-
94-
if (shouldKeepInstancesSync) {
95-
return promise.then(() => InstanceSync.removeItems(keys));
96-
}
97-
98-
return promise;
99-
},
100-
101-
/**
102-
* Clears everything
103-
*/
104-
clear: () => {
105-
if (shouldKeepInstancesSync) {
106-
return InstanceSync.clear(() => provider.clear());
107-
}
108-
109-
return provider.clear();
110-
},
111-
112-
// This is a noop for now in order to keep clients from crashing see https://github.com/Expensify/Expensify/issues/312438
113-
setMemoryOnlyKeys: () => provider.setMemoryOnlyKeys(),
114-
115-
/**
116-
* Returns all available keys
117-
*/
118-
getAllKeys: () => provider.getAllKeys(),
119-
120-
/**
121-
* Gets the total bytes of the store
122-
*/
123-
getDatabaseSize: () => provider.getDatabaseSize(),
124-
125-
/**
126-
* @param onStorageKeyChanged - Storage synchronization mechanism keeping all opened tabs in sync (web only)
127-
*/
128-
keepInstancesSync(onStorageKeyChanged) {
129-
// If InstanceSync is null, it means we're on a native platform and we don't need to keep instances in sync
130-
if (InstanceSync == null) return;
131-
132-
shouldKeepInstancesSync = true;
133-
InstanceSync.init(onStorageKeyChanged);
134-
},
135-
};
136-
137-
export default Storage;
3+
export default WebStorage;

lib/storage/platforms/index.native.ts

-3
This file was deleted.

lib/storage/platforms/index.ts

-3
This file was deleted.

0 commit comments

Comments
 (0)