Browse Source

[RN] Protect AbstractApp and localStorage initialization

master
Lyubo Marinov 7 years ago
parent
commit
62c9762793

+ 27
- 19
react/features/app/components/AbstractApp.js View File

@@ -97,17 +97,22 @@ export class AbstractApp extends Component {
97 97
         };
98 98
 
99 99
         /**
100
-         * This way we make the mobile version wait until the
101
-         * {@code AsyncStorage} implementation of {@code Storage}
102
-         * properly initializes. On web it does actually nothing, see
103
-         * {@link #_initStorage}.
100
+         * Make the mobile {@code AbstractApp} wait until the
101
+         * {@code AsyncStorage} implementation of {@code Storage} initializes
102
+         * fully.
103
+         *
104
+         * @private
105
+         * @see {@link #_initStorage}
106
+         * @type {Promise}
104 107
          */
105
-        this.init = this._initStorage().then(() => {
106
-            this.setState({
107
-                route: undefined,
108
-                store: this._maybeCreateStore(props)
109
-            });
110
-        });
108
+        this._init
109
+            = this._initStorage()
110
+                .catch(() => { /* AbstractApp should always initialize! */ })
111
+                .then(() =>
112
+                    this.setState({
113
+                        route: undefined,
114
+                        store: this._maybeCreateStore(props)
115
+                    }));
111 116
     }
112 117
 
113 118
     /**
@@ -117,7 +122,7 @@ export class AbstractApp extends Component {
117 122
      * @inheritdoc
118 123
      */
119 124
     componentWillMount() {
120
-        this.init.then(() => {
125
+        this._init.then(() => {
121 126
             const { dispatch } = this._getStore();
122 127
 
123 128
             dispatch(appWillMount(this));
@@ -175,7 +180,7 @@ export class AbstractApp extends Component {
175 180
     componentWillReceiveProps(nextProps) {
176 181
         const { props } = this;
177 182
 
178
-        this.init.then(() => {
183
+        this._init.then(() => {
179 184
             // The consumer of this AbstractApp did not provide a redux store.
180 185
             if (typeof nextProps.store === 'undefined'
181 186
 
@@ -236,18 +241,21 @@ export class AbstractApp extends Component {
236 241
     }
237 242
 
238 243
     /**
239
-     * Delays app start until the {@code Storage} implementation initialises.
240
-     * This is instantaneous on web, but is async on mobile.
244
+     * Delays this {@code AbstractApp}'s startup until the {@code Storage}
245
+     * implementation of {@code localStorage} initializes. While the
246
+     * initialization is instantaneous on Web (with Web Storage API), it is
247
+     * asynchronous on mobile/react-native.
241 248
      *
242 249
      * @private
243
-     * @returns {ReactElement}
250
+     * @returns {Promise}
244 251
      */
245 252
     _initStorage() {
246
-        if (typeof window.localStorage._initialized !== 'undefined') {
247
-            return window.localStorage._initialized;
248
-        }
253
+        const localStorageInitializing = window.localStorage._initializing;
249 254
 
250
-        return Promise.resolve();
255
+        return (
256
+            typeof localStorageInitializing === 'undefined'
257
+                ? Promise.resolve()
258
+                : localStorageInitializing);
251 259
     }
252 260
 
253 261
     /**

+ 67
- 42
react/features/base/storage/native/Storage.js View File

@@ -30,14 +30,77 @@ export default class Storage {
30 30
          */
31 31
         this._keyPrefix = keyPrefix;
32 32
 
33
+        // Perform optional asynchronous initialization.
34
+        const initializing = this._initializeAsync();
35
+
36
+        if (initializing) {
37
+            // Indicate that asynchronous initialization is under way.
38
+            this._initializing = initializing;
39
+
40
+            // When the asynchronous initialization completes, indicate its
41
+            // completion.
42
+            initializing.finally(() => {
43
+                if (this._initializing === initializing) {
44
+                    this._initializing = undefined;
45
+                }
46
+            });
47
+        }
48
+    }
49
+
50
+    /**
51
+     * Removes all keys from this storage.
52
+     *
53
+     * @returns {void}
54
+     */
55
+    clear() {
56
+        for (const key of Object.keys(this)) {
57
+            this.removeItem(key);
58
+        }
59
+    }
60
+
61
+    /**
62
+     * Returns the value associated with a specific key in this storage.
63
+     *
64
+     * @param {string} key - The name of the key to retrieve the value of.
65
+     * @returns {string|null} The value associated with {@code key} or
66
+     * {@code null}.
67
+     */
68
+    getItem(key) {
69
+        return this.hasOwnProperty(key) ? this[key] : null;
70
+    }
71
+
72
+    /**
73
+     * Returns the value associated with a specific key in this {@code Storage}
74
+     * in an async manner. The method is required for the cases where we need
75
+     * the stored data but we're not sure yet whether this {@code Storage} is
76
+     * already initialized (e.g. on app start).
77
+     *
78
+     * @param {string} key - The name of the key to retrieve the value of.
79
+     * @returns {Promise}
80
+     */
81
+    _getItemAsync(key) {
82
+        return (
83
+            (this._initializing || Promise.resolve())
84
+                .catch(() => { /* _getItemAsync should always resolve! */ })
85
+                .then(() => this.getItem(key)));
86
+    }
87
+
88
+    /**
89
+     * Performs asynchronous initialization of this {@code Storage} instance
90
+     * such as loading all keys from {@link AsyncStorage}.
91
+     *
92
+     * @private
93
+     * @returns {Promise}
94
+     */
95
+    _initializeAsync() {
33 96
         if (typeof this._keyPrefix !== 'undefined') {
34 97
             // Load all previously persisted data items from React Native's
35 98
             // AsyncStorage.
36 99
 
37
-            this._initialized = new Promise(resolve => {
100
+            return new Promise(resolve => {
38 101
                 AsyncStorage.getAllKeys().then((...getAllKeysCallbackArgs) => {
39
-                    // XXX The keys argument of getAllKeys' callback may
40
-                    // or may not be preceded by an error argument.
102
+                    // XXX The keys argument of getAllKeys' callback may or may
103
+                    // not be preceded by an error argument.
41 104
                     const keys
42 105
                         = getAllKeysCallbackArgs[
43 106
                             getAllKeysCallbackArgs.length - 1
@@ -73,46 +136,8 @@ export default class Storage {
73 136
                 });
74 137
             });
75 138
         }
76
-    }
77
-
78
-    /**
79
-     * Removes all keys from this storage.
80
-     *
81
-     * @returns {void}
82
-     */
83
-    clear() {
84
-        for (const key of Object.keys(this)) {
85
-            this.removeItem(key);
86
-        }
87
-    }
88 139
 
89
-    /**
90
-     * Returns the value associated with a specific key in this storage.
91
-     *
92
-     * @param {string} key - The name of the key to retrieve the value of.
93
-     * @returns {string|null} The value associated with {@code key} or
94
-     * {@code null}.
95
-     */
96
-    getItem(key) {
97
-        return this.hasOwnProperty(key) ? this[key] : null;
98
-    }
99
-
100
-    /**
101
-     * Returns the value associated with a specific key in this storage in an
102
-     * async manner. This method is required for those cases where we need the
103
-     * stored data but we're not sure yet whether the {@code Storage} is already
104
-     * initialised or not - e.g. on app start.
105
-     *
106
-     * @param {string} key - The name of the key to retrieve the value of.
107
-     * @private
108
-     * @returns {Promise}
109
-     */
110
-    _getItemAsync(key) {
111
-        return new Promise(
112
-            resolve =>
113
-                AsyncStorage.getItem(
114
-                    `${String(this._keyPrefix)}${key}`,
115
-                    (error, result) => resolve(result ? result : null)));
140
+        return undefined;
116 141
     }
117 142
 
118 143
     /**

Loading…
Cancel
Save