You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Storage.js 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { AsyncStorage } from 'react-native';
  2. /**
  3. * A Web Sorage API implementation used for polyfilling
  4. * {@code window.localStorage} and/or {@code window.sessionStorage}.
  5. * <p>
  6. * The Web Storage API is synchronous whereas React Native's builtin generic
  7. * storage API {@code AsyncStorage} is asynchronous so the implementation with
  8. * persistence is optimistic: it will first store the value locally in memory so
  9. * that results can be served synchronously and then persist the value
  10. * asynchronously. If an asynchronous operation produces an error, it's ignored.
  11. */
  12. export default class Storage {
  13. /**
  14. * Initializes a new {@code Storage} instance. Loads all previously
  15. * persisted data items from React Native's {@code AsyncStorage} if
  16. * necessary.
  17. *
  18. * @param {string|undefined} keyPrefix - The prefix of the
  19. * {@code AsyncStorage} keys to be persisted by this storage.
  20. */
  21. constructor(keyPrefix) {
  22. /**
  23. * The prefix of the {@code AsyncStorage} keys persisted by this
  24. * storage. If {@code undefined}, then the data items stored in this
  25. * storage will not be persisted.
  26. *
  27. * @private
  28. * @type {string}
  29. */
  30. this._keyPrefix = keyPrefix;
  31. if (typeof this._keyPrefix !== 'undefined') {
  32. // Load all previously persisted data items from React Native's
  33. // AsyncStorage.
  34. /**
  35. * A flag to indicate that the async {@code AsyncStorage} is not
  36. * initialized yet. This is native specific but it will work
  37. * fine on web as well, as it will have no value (== false) there.
  38. * This is required to be available as we need a sync way to check
  39. * if the storage is inited or not.
  40. */
  41. this._initializing = true;
  42. this._inited = new Promise(resolve => {
  43. AsyncStorage.getAllKeys().then((...getAllKeysCallbackArgs) => {
  44. // XXX The keys argument of getAllKeys' callback may
  45. // or may not be preceded by an error argument.
  46. const keys
  47. = getAllKeysCallbackArgs[
  48. getAllKeysCallbackArgs.length - 1
  49. ].filter(key => key.startsWith(this._keyPrefix));
  50. AsyncStorage.multiGet(keys)
  51. .then((...multiGetCallbackArgs) => {
  52. // XXX The result argument of multiGet may or may not be
  53. // preceded by an errors argument.
  54. const result
  55. = multiGetCallbackArgs[
  56. multiGetCallbackArgs.length - 1
  57. ];
  58. const keyPrefixLength
  59. = this._keyPrefix && this._keyPrefix.length;
  60. // eslint-disable-next-line prefer-const
  61. for (let [ key, value ] of result) {
  62. key = key.substring(keyPrefixLength);
  63. // XXX The loading of the previously persisted data
  64. // items from AsyncStorage is asynchronous which
  65. // means that it is technically possible to invoke
  66. // setItem with a key before the key is loaded from
  67. // AsyncStorage.
  68. if (!this.hasOwnProperty(key)) {
  69. this[key] = value;
  70. }
  71. }
  72. this._initializing = false;
  73. resolve();
  74. });
  75. });
  76. });
  77. }
  78. }
  79. /**
  80. * Removes all keys from this storage.
  81. *
  82. * @returns {void}
  83. */
  84. clear() {
  85. for (const key of Object.keys(this)) {
  86. this.removeItem(key);
  87. }
  88. }
  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. * Returns the value associated with a specific key in this storage in an
  101. * async manner. This method is required for those cases where we need the
  102. * stored data but we're not sure yet whether the {@code Storage} is already
  103. * initialised or not - e.g. on app start.
  104. *
  105. * @param {string} key - The name of the key to retrieve the value of.
  106. * @private
  107. * @returns {Promise}
  108. */
  109. _getItemAsync(key) {
  110. return new Promise(
  111. resolve =>
  112. AsyncStorage.getItem(
  113. `${String(this._keyPrefix)}${key}`,
  114. (error, result) => resolve(result ? result : null)));
  115. }
  116. /**
  117. * Returns the name of the nth key in this storage.
  118. *
  119. * @param {number} n - The zero-based integer index of the key to get the
  120. * name of.
  121. * @returns {string} The name of the nth key in this storage.
  122. */
  123. key(n) {
  124. const keys = Object.keys(this);
  125. return n < keys.length ? keys[n] : null;
  126. }
  127. /**
  128. * Returns an integer representing the number of data items stored in this
  129. * storage.
  130. *
  131. * @returns {number}
  132. */
  133. get length() {
  134. return Object.keys(this).length;
  135. }
  136. /**
  137. * Removes a specific key from this storage.
  138. *
  139. * @param {string} key - The name of the key to remove.
  140. * @returns {void}
  141. */
  142. removeItem(key) {
  143. delete this[key];
  144. typeof this._keyPrefix === 'undefined'
  145. || AsyncStorage.removeItem(`${String(this._keyPrefix)}${key}`);
  146. }
  147. /**
  148. * Adds a specific key to this storage and associates it with a specific
  149. * value. If the key exists already, updates its value.
  150. *
  151. * @param {string} key - The name of the key to add/update.
  152. * @param {string} value - The value to associate with {@code key}.
  153. * @returns {void}
  154. */
  155. setItem(key, value) {
  156. value = String(value); // eslint-disable-line no-param-reassign
  157. this[key] = value;
  158. typeof this._keyPrefix === 'undefined'
  159. || AsyncStorage.setItem(`${String(this._keyPrefix)}${key}`, value);
  160. }
  161. }