diff --git a/README.md b/README.md
index df23729bf7da..c31a423233e3 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
# Philosophy
This application is built with the following principles.
1. **Data Flow** - Ideally, this is how data flows through the app:
- 1. Server pushes data to the disk of any client (Server -> Pusher event -> Action listening to pusher event -> Ion). Currently the code only does this with report comments. Until we make more server changes, this steps is actually done by the client requesting data from the server via XHR and then storing the response in Ion.
- 1. Disk pushes data to the UI (Ion -> withIon()/connect() -> React component).
+ 1. Server pushes data to the disk of any client (Server -> Pusher event -> Action listening to pusher event -> Onyx). Currently the code only does this with report comments. Until we make more server changes, this steps is actually done by the client requesting data from the server via XHR and then storing the response in Onyx.
+ 1. Disk pushes data to the UI (Onyx -> withOnyx()/connect() -> React component).
1. UI pushes data to people's brains (React component -> device screen).
1. Brain pushes data into UI inputs (Device input -> React component).
1. UI inputs push data to the server (React component -> Action -> XHR to server).
@@ -13,22 +13,22 @@ This application is built with the following principles.
- All data that is brought into the app and is necessary to display the app when offline should be stored on disk in persistent storage (eg. localStorage on browser platforms). [AsyncStorage](https://react-native-community.github.io/async-storage/) is a cross-platform abstraction layer that is used to access persistent storage.
- All data that is displayed, comes from persistent storage.
1. **UI Binds to data on disk**
- - Ion is a Pub/Sub library to connect the application to the data stored on disk.
- - UI components subscribe to Ion (using `withIon()`) and any change to the Ion data is published to the component by calling `setState()` with the changed data.
- - Libraries subscribe to Ion (with `Ion.connect()`) and any change to the Ion data is published to the callback with the changed data.
- - The UI should never call any Ion methods except for `Ion.connect()`. That is the job of Actions (see next section).
+ - Onyx is a Pub/Sub library to connect the application to the data stored on disk.
+ - UI components subscribe to Onyx (using `withOnyx()`) and any change to the Onyx data is published to the component by calling `setState()` with the changed data.
+ - Libraries subscribe to Onyx (with `Onyx.connect()`) and any change to the Onyx data is published to the callback with the changed data.
+ - The UI should never call any Onyx methods except for `Onyx.connect()`. That is the job of Actions (see next section).
- The UI always triggers an Action when something needs to happen (eg. a person inputs data, the UI triggers an Action with this data).
- The UI should be as flexible as possible when it comes to:
- - Incomplete or missing data. Always assume data is incomplete or not there. For example, when a comment is pushed to the client from a pusher event, it's possible that Ion does not have data for that report yet. That's OK. A partial report object is added to Ion for the report key `report_1234 = {reportID: 1234, isUnread: true}`. Then there is code that monitors Ion for reports with incomplete data, and calls `fetchChatReportsByIDs(1234)` to get the full data for that report. The UI should be able to gracefully handle the report object not being complete. In this example, the sidebar wouldn't display any report that doesn't have a report name.
+ - Incomplete or missing data. Always assume data is incomplete or not there. For example, when a comment is pushed to the client from a pusher event, it's possible that Onyx does not have data for that report yet. That's OK. A partial report object is added to Onyx for the report key `report_1234 = {reportID: 1234, isUnread: true}`. Then there is code that monitors Onyx for reports with incomplete data, and calls `fetchChatReportsByIDs(1234)` to get the full data for that report. The UI should be able to gracefully handle the report object not being complete. In this example, the sidebar wouldn't display any report that doesn't have a report name.
- The order that actions are done in. All actions should be done in parallel instead of sequence.
- Parallel actions are asynchronous methods that don't return promises. Any number of these actions can be called at one time and it doesn't matter what order they happen in or when they complete.
- In-Sequence actions are asynchronous methods that return promises. This is necessary when one asynchronous method depends on the results from a previous asynchronous method. Example: Making an XHR to `command=CreateChatReport` which returns a reportID which is used to call `command=Get&rvl=reportStuff`.
-1. **Actions manage Ion Data**
+1. **Actions manage Onyx Data**
- When data needs to be written to or read from the server, this is done through Actions only.
- Public action methods should never return anything (not data or a promise). This is done to ensure that action methods can be called in parallel with no dependency on other methods (see discussion above).
- - Actions should favor using `Ion.merge()` over `Ion.set()` so that other values in an object aren't completely overwritten.
- - In general, the operations that happen inside an action should be done in parallel and not in sequence (eg. don't use the promise of one Ion method to trigger a second Ion method). Ion is built so that every operation is done in parallel and it doesn't matter what order they finish in. XHRs on the other hand need to be handled in sequence with promise chains in order to access and act upon the response.
- - If an Action needs to access data stored on disk, use a local variable and `Ion.connect()`
+ - Actions should favor using `Onyx.merge()` over `Onyx.set()` so that other values in an object aren't completely overwritten.
+ - In general, the operations that happen inside an action should be done in parallel and not in sequence (eg. don't use the promise of one Onyx method to trigger a second Onyx method). Onyx is built so that every operation is done in parallel and it doesn't matter what order they finish in. XHRs on the other hand need to be handled in sequence with promise chains in order to access and act upon the response.
+ - If an Action needs to access data stored on disk, use a local variable and `Onyx.connect()`
- Data should be optimistically stored on disk whenever possible without waiting for a server response. Example of creating a new optimistic comment:
1. user adds a comment
2. comment is shown in the UI (by mocking the expected response from the server)
@@ -107,35 +107,35 @@ Our React Native Android app now uses the `Hermes` JS engine which requires your
## Things to know or brush up on before jumping into the code
1. The major difference between React-Native and React are the [components](https://reactnative.dev/docs/components-and-apis) that are used in the `render()` method. Everything else is exactly the same. If you learn React, you've already learned 98% of React-Native.
1. The application uses [React-Router](https://reactrouter.com/native/guides/quick-start) for navigating between parts of the app.
-1. [Higher Order Components](https://reactjs.org/docs/higher-order-components.html) are used to connect React components to persistent storage via Ion.
+1. [Higher Order Components](https://reactjs.org/docs/higher-order-components.html) are used to connect React components to persistent storage via Onyx.
## Structure of the app
These are the main pieces of the application.
-### Ion
+### Onyx
This is a persistent storage solution wrapped in a Pub/Sub library. In general that means:
-- Ion stores and retrieves data from persistent storage
+- Onyx stores and retrieves data from persistent storage
- Data is stored as key/value pairs, where the value can be anything from a single piece of data to a complex object
- Collections of data are usually not stored as a single key (eg. an array with multiple objects), but as individual keys+ID (eg. `report_1234`, `report_4567`, etc.). Store collections as individual keys when a component will bind directly to one of those keys. For example: reports are stored as individual keys because `SidebarLink.js` binds to the individual report keys for each link. However, report actions are stored as an array of objects because nothing binds directly to a single report action.
-- Ion allows other code to subscribe to changes in data, and then publishes change events whenever data is changed
-- Anything needing to read Ion data needs to:
+- Onyx allows other code to subscribe to changes in data, and then publishes change events whenever data is changed
+- Anything needing to read Onyx data needs to:
1. Know what key the data is stored in (for web, you can find this by looking in the JS console > Application > local storage)
- 2. Subscribe to changes of the data for a particular key or set of keys. React components use `withIon()` and non-React libs use `Ion.connect()`.
- 3. Get initialized with the current value of that key from persistent storage (Ion does this by calling `setState()` or triggering the `callback` with the values currently on disk as part of the connection process)
-- Subscribing to Ion keys is done using a constant defined in `IONKEYS`. Each Ion key represents either a collection of items or a specific entry in storage. For example, since all reports are stored as individual keys like `report_1234`, if code needs to know about all the reports (eg. display a list of them in the nav menu), then it would subscribe to the key `IONKEYS.COLLECTION.REPORT`.
+ 2. Subscribe to changes of the data for a particular key or set of keys. React components use `withOnyx()` and non-React libs use `Onyx.connect()`.
+ 3. Get initialized with the current value of that key from persistent storage (Onyx does this by calling `setState()` or triggering the `callback` with the values currently on disk as part of the connection process)
+- Subscribing to Onyx keys is done using a constant defined in `ONYXKEYS`. Each Onyx key represents either a collection of items or a specific entry in storage. For example, since all reports are stored as individual keys like `report_1234`, if code needs to know about all the reports (eg. display a list of them in the nav menu), then it would subscribe to the key `ONYXKEYS.COLLECTION.REPORT`.
### Actions
Actions are responsible for managing what is on disk. This is usually:
-- Subscribing to Pusher events to receive data from the server that will get put immediately into Ion
-- Making XHRs to request necessary data from the server and then immediately putting that data into Ion
+- Subscribing to Pusher events to receive data from the server that will get put immediately into Onyx
+- Making XHRs to request necessary data from the server and then immediately putting that data into Onyx
- Handling any business logic with input coming from the UI layer
### The UI layer
This layer is solely responsible for:
-- Reflecting exactly the data that is in persistent storage by using `withIon()` to bind to Ion data.
+- Reflecting exactly the data that is in persistent storage by using `withOnyx()` to bind to Onyx data.
- Taking user input and passing it to an action
### Directory structure
@@ -157,7 +157,7 @@ Files should be named after the component/function/constants they export, respec
- If you export a component named `Text` the file/directory should be named `Text`
- If you export a function named `guid` the file/directory should be named `guid`.
- For files that are utilities that export several functions/classes use the UpperCamelCase version ie: `DateUtils`.
-- HOCs should be named in camelCase like withIon.
+- HOCs should be named in camelCase like withOnyx.
- All React components should be PascalCase (a.k.a. UpperCamelCase 🐫).
## Platform-Specific File Extensions
@@ -177,28 +177,28 @@ we should prefer making `CreateTransaction` return the data it just created inst
### Storage Eviction
-Different platforms come with varying storage capacities and Ion has a way to gracefully fail when those storage limits are encountered. When Ion fails to set or modify a key the following steps are taken:
-1. Ion looks at a list of recently accessed keys (access is defined as subscribed to or modified) and locates the key that was least recently accessed
+Different platforms come with varying storage capacities and Onyx has a way to gracefully fail when those storage limits are encountered. When Onyx fails to set or modify a key the following steps are taken:
+1. Onyx looks at a list of recently accessed keys (access is defined as subscribed to or modified) and locates the key that was least recently accessed
2. It then deletes this key and retries the original operation
-By default, Ion will not evict anything from storage and will presume all keys are "unsafe" to remove unless explicitly told otherwise.
+By default, Onyx will not evict anything from storage and will presume all keys are "unsafe" to remove unless explicitly told otherwise.
**To flag a key as safe for removal:**
-- Add the key to the `safeEvictionKeys` option in `Ion.init(options)`
-- Implement `canEvict` in the Ion config for each component subscribing to a key
+- Add the key to the `safeEvictionKeys` option in `Onyx.init(options)`
+- Implement `canEvict` in the Onyx config for each component subscribing to a key
- The key will only be deleted when all subscribers return `true` for `canEvict`
e.g.
```js
-Ion.init({
- safeEvictionKeys: [IONKEYS.COLLECTION.REPORT_ACTIONS],
+Onyx.init({
+ safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
});
```
```js
-export default withIon({
+export default withOnyx({
reportActions: {
- key: ({reportID}) => `${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
canEvict: props => !props.isActiveReport,
},
})(ReportActionsView);
diff --git a/package-lock.json b/package-lock.json
index 90e82a79de47..ad32ef4cd3b1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15364,11 +15364,12 @@
}
},
"js-libs": {
- "version": "git+ssh://git@github.com/Expensify/JS-Libs.git#e768c69492b50f77cba9e94b107c7c0e708dd1cd",
- "from": "git+ssh://git@github.com/Expensify/JS-Libs.git#e768c69492b50f77cba9e94b107c7c0e708dd1cd",
+ "version": "git+ssh://git@github.com/Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4",
+ "from": "git+ssh://git@github.com/Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4",
"requires": {
"classnames": "2.2.5",
"clipboard": "2.0.4",
+ "html-entities": "^1.3.1",
"jquery": "3.3.1",
"lodash.get": "4.4.2",
"lodash.has": "4.5.2",
@@ -19243,6 +19244,28 @@
"resolved": "https://registry.npmjs.org/react-native-keyboard-spacer/-/react-native-keyboard-spacer-0.4.1.tgz",
"integrity": "sha1-RvGKMgQyCYol6p+on1FD3SVNMy0="
},
+ "react-native-onyx": {
+ "version": "git+ssh://git@github.com/Expensify/react-native-onyx.git#0cc1d0e18cf15d0d6fd3d3e05a18fcbd917abefd",
+ "from": "git+ssh://git@github.com/Expensify/react-native-onyx.git#0cc1d0e18cf15d0d6fd3d3e05a18fcbd917abefd",
+ "requires": {
+ "@react-native-community/async-storage": "^1.12.1",
+ "js-libs": "git+ssh://git@github.com/Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4",
+ "lodash.merge": "^4.6.2",
+ "react": "^17.0.1",
+ "underscore": "^1.11.0"
+ },
+ "dependencies": {
+ "react": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
+ "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ }
+ }
+ },
"react-native-render-html": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-4.2.4.tgz",
diff --git a/package.json b/package.json
index 3655a76f4849..1aaa78237356 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,8 @@
"electron-updater": "^4.3.4",
"file-loader": "^6.0.0",
"html-entities": "^1.3.1",
- "js-libs": "git+https://git@github.com:Expensify/JS-Libs.git#e768c69492b50f77cba9e94b107c7c0e708dd1cd",
+ "js-libs": "git+https://git@github.com:Expensify/JS-Libs.git#92b874eed3640e7635f7342f8169ddf8f28ca7e4",
+ "react-native-onyx": "git+https://git@github.com:Expensify/react-native-onyx.git#0cc1d0e18cf15d0d6fd3d3e05a18fcbd917abefd",
"lodash.get": "^4.4.2",
"lodash.has": "^4.5.2",
"lodash.merge": "^4.6.2",
diff --git a/src/Expensify.js b/src/Expensify.js
index 56623e012e1a..9a3006dcab7f 100644
--- a/src/Expensify.js
+++ b/src/Expensify.js
@@ -1,13 +1,14 @@
import React, {Component} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
+import Onyx, {withOnyx} from 'react-native-onyx';
import {recordCurrentlyViewedReportID, recordCurrentRoute} from './libs/actions/App';
import SignInPage from './pages/SignInPage';
import HomePage from './pages/home/HomePage';
-import Ion from './libs/Ion';
+import listenToStorageEvents from './libs/listenToStorageEvents';
import * as ActiveClientManager from './libs/ActiveClientManager';
-import IONKEYS from './IONKEYS';
-import withIon from './components/withIon';
+import ONYXKEYS from './ONYXKEYS';
+
import styles from './styles/StyleSheet';
import Log from './libs/Log';
@@ -20,16 +21,19 @@ import {
import ROUTES from './ROUTES';
// Initialize the store when the app loads for the first time
-Ion.init({
- keys: IONKEYS,
- safeEvictionKeys: [IONKEYS.COLLECTION.REPORT_ACTIONS],
+Onyx.init({
+ keys: ONYXKEYS,
+ safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
initialKeyStates: {
// Clear any loading and error messages so they do not appear on app startup
- [IONKEYS.SESSION]: {loading: false, error: ''},
- }
+ [ONYXKEYS.SESSION]: {loading: false, error: ''},
+ },
+ registerStorageEventListener: (onStorageEvent) => {
+ listenToStorageEvents(onStorageEvent);
+ },
});
-Ion.registerLogger(({level, message}) => {
+Onyx.registerLogger(({level, message}) => {
if (level === 'alert') {
Log.alert(message, 0, {}, false);
} else {
@@ -38,9 +42,9 @@ Ion.registerLogger(({level, message}) => {
});
const propTypes = {
- /* Ion Props */
+ /* Onyx Props */
- // A route set by Ion that we will redirect to if present. Always empty on app init.
+ // A route set by Onyx that we will redirect to if present. Always empty on app init.
redirectTo: PropTypes.string,
};
@@ -64,8 +68,8 @@ class Expensify extends Component {
}
componentDidMount() {
- Ion.connect({
- key: IONKEYS.SESSION,
+ Onyx.connect({
+ key: ONYXKEYS.SESSION,
callback: this.removeLoadingState,
});
}
@@ -84,7 +88,7 @@ class Expensify extends Component {
}
render() {
- // Until the authToken has been initialized from Ion, display a blank page
+ // Until the authToken has been initialized from Onyx, display a blank page
if (this.state.isLoading) {
return (
@@ -120,11 +124,11 @@ class Expensify extends Component {
Expensify.propTypes = propTypes;
Expensify.defaultProps = defaultProps;
-export default withIon({
+export default withOnyx({
redirectTo: {
- key: IONKEYS.APP_REDIRECT_TO,
+ key: ONYXKEYS.APP_REDIRECT_TO,
- // Prevent the prefilling of Ion data or else the app will always redirect to what the last value was set to.
+ // Prevent the prefilling of Onyx data or else the app will always redirect to what the last value was set to.
// This ends up in a situation where you go to a report, refresh the page, and then rather than seeing the
// report you are brought back to the root of the site (ie. "/").
initWithStoredValues: false,
diff --git a/src/IONKEYS.js b/src/ONYXKEYS.js
similarity index 100%
rename from src/IONKEYS.js
rename to src/ONYXKEYS.js
diff --git a/src/components/withIon.js b/src/components/withIon.js
deleted file mode 100644
index c9ace914cb46..000000000000
--- a/src/components/withIon.js
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * This is a higher order component that provides the ability to map a state property directly to
- * something in Ion (a key/value store). That way, as soon as data in Ion changes, the state will be set and the view
- * will automatically change to reflect the new data.
- */
-import React from 'react';
-import _ from 'underscore';
-import Ion from '../libs/Ion';
-import Str from '../libs/Str';
-
-/**
- * Returns the display name of a component
- *
- * @param {object} component
- * @returns {string}
- */
-function getDisplayName(component) {
- return component.displayName || component.name || 'Component';
-}
-
-export default function (mapIonToState) {
- return (WrappedComponent) => {
- class withIon extends React.Component {
- constructor(props) {
- super(props);
-
- // This stores all the Ion connection IDs to be used when the component unmounts so everything can be
- // disconnected. It is a key value store with the format {[mapping.key]: connectionID}.
- this.activeConnectionIDs = {};
-
- this.state = {
- loading: true,
- };
- }
-
- componentDidMount() {
- // Subscribe each of the state properties to the proper Ion key
- _.each(mapIonToState, (mapping, propertyName) => {
- this.connectMappingToIon(mapping, propertyName);
- });
- this.checkAndUpdateLoading();
- }
-
- componentDidUpdate(prevProps) {
- // If any of the mappings use data from the props, then when the props change, all the
- // connections need to be reconnected with the new props
- _.each(mapIonToState, (mapping, propertyName) => {
- const previousKey = Str.result(mapping.key, prevProps);
- const newKey = Str.result(mapping.key, this.props);
-
- if (previousKey !== newKey) {
- Ion.disconnect(this.activeConnectionIDs[previousKey], previousKey);
- delete this.activeConnectionIDs[previousKey];
- this.connectMappingToIon(mapping, propertyName);
- }
- });
- this.checkAndUpdateLoading();
- }
-
- componentWillUnmount() {
- // Disconnect everything from Ion
- _.each(mapIonToState, (mapping) => {
- const key = Str.result(mapping.key, this.props);
- const connectionID = this.activeConnectionIDs[key];
- Ion.disconnect(connectionID, key);
- });
- }
-
- /**
- * Makes sure each Ion key we requested has been set to state with a value of some kind.
- * We are doing this so that the wrapped component will only render when all the data
- * it needs is available to it.
- */
- checkAndUpdateLoading() {
- // We will add this key to our list of recently accessed keys
- // if the canEvict function returns true. This is necessary criteria
- // we MUST use to specify if a key can be removed or not.
- _.each(mapIonToState, (mapping) => {
- if (_.isUndefined(mapping.canEvict)) {
- return;
- }
-
- const canEvict = Str.result(mapping.canEvict, this.props);
- const key = Str.result(mapping.key, this.props);
-
- if (!Ion.isSafeEvictionKey(key)) {
- // eslint-disable-next-line max-len
- throw new Error(`canEvict cannot be used on key '${key}'. This key must explicitly be flagged as safe for removal by adding it to Ion.init({safeEvictionKeys: []}).`);
- }
-
- if (canEvict) {
- Ion.removeFromEvictionBlockList(key, mapping.connectionID);
- } else {
- Ion.addToEvictionBlockList(key, mapping.connectionID);
- }
- });
-
- if (!this.state.loading) {
- return;
- }
-
- // Filter all keys by those which we do want to init with stored values
- // since keys that are configured to not init with stored values will
- // never appear on state when the component mounts - only after they update
- // organically.
- const requiredKeysForInit = _.chain(mapIonToState)
- .omit(config => config.initWithStoredValues === false)
- .keys()
- .value();
-
- // All state keys should exist and at least have a value of null
- if (_.every(requiredKeysForInit, key => !_.isUndefined(this.state[key]))) {
- this.setState({loading: false});
- }
- }
-
- /**
- * Takes a single mapping and binds the state of the component to the store
- *
- * @param {object} mapping
- * @param {string|function} mapping.key key to connect to. can be a string or a
- * function that takes this.props as an argument and returns a string
- * @param {string} statePropertyName the name of the state property that Ion will add the data to
- * @param {boolean} [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
- * component
- */
- connectMappingToIon(mapping, statePropertyName) {
- const key = Str.result(mapping.key, this.props);
- const connectionID = Ion.connect({
- ...mapping,
- key,
- statePropertyName,
- withIonInstance: this,
- });
-
- this.activeConnectionIDs[key] = connectionID;
- }
-
- render() {
- if (this.state.loading) {
- return null;
- }
-
- // Remove any internal state properties used by withIon
- // that should not be passed to a wrapped component
- const stateToPass = _.omit(this.state, 'loading');
-
- // Spreading props and state is necessary in an HOC where the data cannot be predicted
- return (
-
- );
- }
- }
-
- withIon.displayName = `withIon(${getDisplayName(WrappedComponent)})`;
- return withIon;
- };
-}
diff --git a/src/libs/API.js b/src/libs/API.js
index dd75538b501c..1a94ab96f937 100644
--- a/src/libs/API.js
+++ b/src/libs/API.js
@@ -1,13 +1,12 @@
import _ from 'underscore';
-import Ion from './Ion';
-import IONKEYS from '../IONKEYS';
+import Onyx from 'react-native-onyx';
+import Str from 'js-libs/lib/str';
+import ONYXKEYS from '../ONYXKEYS';
import HttpUtils from './HttpUtils';
import NetworkConnection from './NetworkConnection';
import CONFIG from '../CONFIG';
import * as Pusher from './Pusher/pusher';
import ROUTES from '../ROUTES';
-import Str from './Str';
-import guid from './guid';
import redirectToSignIn from './actions/SignInRedirect';
import PushNotification from './Notification/PushNotification';
@@ -20,32 +19,32 @@ let networkRequestQueue = [];
let reauthenticating = false;
let authToken;
-Ion.connect({
- key: IONKEYS.SESSION,
+Onyx.connect({
+ key: ONYXKEYS.SESSION,
callback: val => authToken = val ? val.authToken : null,
});
// We subscribe to changes to the online/offline status of the network to determine when we should fire off API calls
// vs queueing them for later.
let isOffline;
-Ion.connect({
- key: IONKEYS.NETWORK,
+Onyx.connect({
+ key: ONYXKEYS.NETWORK,
callback: val => isOffline = val && val.isOffline,
});
-// When the user authenticates for the first time we create a login and store credentials in Ion.
+// When the user authenticates for the first time we create a login and store credentials in Onyx.
// When the user's authToken expires we use this login to re-authenticate and get a new authToken
// and use that new authToken in subsequent API calls
let credentials;
-Ion.connect({
- key: IONKEYS.CREDENTIALS,
+Onyx.connect({
+ key: ONYXKEYS.CREDENTIALS,
callback: ionCredentials => credentials = ionCredentials,
});
// If we are ever being redirected to the sign in page, the user is currently unauthenticated, so we should clear the
// network request queue, to prevent DDoSing our own API
-Ion.connect({
- key: IONKEYS.APP_REDIRECT_TO,
+Onyx.connect({
+ key: ONYXKEYS.APP_REDIRECT_TO,
callback: (redirectTo) => {
if (redirectTo && redirectTo.startsWith(ROUTES.SIGNIN)) {
networkRequestQueue = [];
@@ -99,7 +98,7 @@ function deleteLogin(parameters) {
partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
doNotRetry: true,
})
- .catch(error => Ion.merge(IONKEYS.SESSION, {error: error.message}));
+ .catch(error => Onyx.merge(ONYXKEYS.SESSION, {error: error.message}));
}
/**
@@ -132,7 +131,7 @@ function createLogin(login, password) {
deleteLogin({partnerUserID: credentials.login});
}
- Ion.merge(IONKEYS.CREDENTIALS, {login, password});
+ Onyx.merge(ONYXKEYS.CREDENTIALS, {login, password});
});
}
@@ -147,9 +146,9 @@ function setSuccessfulSignInData(data, exitTo) {
PushNotification.register(data.accountID);
const redirectTo = exitTo ? Str.normalizeUrl(exitTo) : ROUTES.ROOT;
- Ion.multiSet({
- [IONKEYS.SESSION]: _.pick(data, 'authToken', 'accountID', 'email'),
- [IONKEYS.APP_REDIRECT_TO]: redirectTo
+ Onyx.multiSet({
+ [ONYXKEYS.SESSION]: _.pick(data, 'authToken', 'accountID', 'email'),
+ [ONYXKEYS.APP_REDIRECT_TO]: redirectTo
});
}
@@ -219,8 +218,8 @@ function request(command, parameters, type = 'post') {
// Update the authToken that will be used to retry the command since the one we have is expired
parametersWithAuthToken.authToken = response.authToken;
- // Update authToken in Ion store otherwise subsequent API calls will use the expired one
- Ion.merge(IONKEYS.SESSION, _.pick(response, 'authToken'));
+ // Update authToken in Onyx store otherwise subsequent API calls will use the expired one
+ Onyx.merge(ONYXKEYS.SESSION, _.pick(response, 'authToken'));
return response;
})
.then(() => HttpUtils.xhr(command, parametersWithAuthToken, type))
@@ -323,7 +322,7 @@ Pusher.registerCustomAuthorizer((channel, {authEndpoint}) => ({
/**
* Events that happen on the pusher socket are used to determine if the app is online or offline. The offline setting
- * is stored in Ion so the rest of the app has access to it.
+ * is stored in Onyx so the rest of the app has access to it.
*
* @params {string} eventName
*/
@@ -354,7 +353,7 @@ function getAuthToken() {
* @returns {Promise}
*/
function authenticate(parameters) {
- Ion.merge(IONKEYS.SESSION, {loading: true, error: ''});
+ Onyx.merge(ONYXKEYS.SESSION, {loading: true, error: ''});
// We treat Authenticate in a special way because unlike other commands, this one can't fail
// with 407 authToken expired. When other api commands fail with this error we call Authenticate
@@ -386,15 +385,15 @@ function authenticate(parameters) {
// After the user authenticates, create a new login for the user so that we can reauthenticate when the
// authtoken expires
.then(response => (
- createLogin(Str.generateDeviceLoginID(), guid())
+ createLogin(Str.guid('react-native-chat-'), Str.guid())
.then(() => setSuccessfulSignInData(response, parameters.exitTo))
))
.catch((error) => {
console.error(error);
console.debug('[SIGNIN] Request error');
- Ion.merge(IONKEYS.SESSION, {error: error.message});
+ Onyx.merge(ONYXKEYS.SESSION, {error: error.message});
})
- .finally(() => Ion.merge(IONKEYS.SESSION, {loading: false}));
+ .finally(() => Onyx.merge(ONYXKEYS.SESSION, {loading: false}));
}
/**
diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js
index 426b4883c9b3..9de0339998cf 100644
--- a/src/libs/ActiveClientManager/index.js
+++ b/src/libs/ActiveClientManager/index.js
@@ -1,19 +1,19 @@
import _ from 'underscore';
-import guid from '../guid';
-import Ion from '../Ion';
-import IONKEYS from '../../IONKEYS';
+import Onyx from 'react-native-onyx';
+import Str from 'js-libs/lib/str';
+import ONYXKEYS from '../../ONYXKEYS';
-const clientID = guid();
+const clientID = Str.guid();
const maxClients = 20;
let activeClients;
-Ion.connect({
- key: IONKEYS.ACTIVE_CLIENTS,
+Onyx.connect({
+ key: ONYXKEYS.ACTIVE_CLIENTS,
callback: (val) => {
activeClients = _.isNull(val) ? [] : val;
if (activeClients.length >= maxClients) {
activeClients.shift();
- Ion.set(IONKEYS.ACTIVE_CLIENTS, activeClients);
+ Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients);
}
},
});
@@ -22,7 +22,7 @@ Ion.connect({
* Add our client ID to the list of active IDs
*/
function init() {
- Ion.merge(IONKEYS.ACTIVE_CLIENTS, [clientID]);
+ Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]);
}
/**
diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js
index 072caf4a79ed..4c5d7fc673a9 100644
--- a/src/libs/DateUtils.js
+++ b/src/libs/DateUtils.js
@@ -1,12 +1,12 @@
import moment from 'moment';
import 'moment-timezone';
-import Str from './Str';
-import Ion from './Ion';
-import IONKEYS from '../IONKEYS';
+import Onyx from 'react-native-onyx';
+import Str from 'js-libs/lib/str';
+import ONYXKEYS from '../ONYXKEYS';
let timezone;
-Ion.connect({
- key: IONKEYS.MY_PERSONAL_DETAILS,
+Onyx.connect({
+ key: ONYXKEYS.MY_PERSONAL_DETAILS,
callback: val => timezone = val ? val.timezone : 'America/Los_Angeles',
});
diff --git a/src/libs/HttpUtils.js b/src/libs/HttpUtils.js
index 064996114adf..130713dea731 100644
--- a/src/libs/HttpUtils.js
+++ b/src/libs/HttpUtils.js
@@ -1,7 +1,7 @@
import _ from 'underscore';
-import Ion from './Ion';
+import Onyx from 'react-native-onyx';
import CONFIG from '../CONFIG';
-import IONKEYS from '../IONKEYS';
+import ONYXKEYS from '../ONYXKEYS';
import NetworkConnection from './NetworkConnection';
/**
@@ -26,7 +26,7 @@ function processHTTPRequest(url, method = 'get', body = null) {
NetworkConnection.setOfflineStatus(true);
// Set an error state and signify we are done loading
- Ion.merge(IONKEYS.SESSION, {loading: false, error: 'Cannot connect to server'});
+ Onyx.merge(ONYXKEYS.SESSION, {loading: false, error: 'Cannot connect to server'});
// Throw a new error to prevent any other `then()` in the promise chain from being triggered (until another
// catch() happens
diff --git a/src/libs/Ion/Logger.js b/src/libs/Ion/Logger.js
deleted file mode 100644
index eda165918a87..000000000000
--- a/src/libs/Ion/Logger.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// Logging callback
-let logger;
-
-/**
- * Register the logging callback
- *
- * @param {Function} callback
- */
-function registerLogger(callback) {
- logger = callback;
-}
-
-/**
- * Send an alert message to the logger
- *
- * @param {String} message
- */
-function logAlert(message) {
- logger({message: `[Ion] ${message}`, level: 'alert'});
-}
-
-/**
- * Send an info message to the logger
- *
- * @param {String} message
- */
-function logInfo(message) {
- logger({message: `[Ion] ${message}`, level: 'info'});
-}
-
-export {
- registerLogger,
- logInfo,
- logAlert,
-};
diff --git a/src/libs/Ion/index.js b/src/libs/Ion/index.js
deleted file mode 100644
index 4c8d21e0bb7c..000000000000
--- a/src/libs/Ion/index.js
+++ /dev/null
@@ -1,498 +0,0 @@
-import _ from 'underscore';
-import AsyncStorage from '@react-native-community/async-storage';
-import lodashMerge from 'lodash.merge';
-import addStorageEventHandler from './addStorageEventHandler';
-import Str from '../Str';
-import {registerLogger, logInfo, logAlert} from './Logger';
-
-// Keeps track of the last connectionID that was used so we can keep incrementing it
-let lastConnectionID = 0;
-
-// Holds a mapping of all the react components that want their state subscribed to a store key
-const callbackToStateMapping = {};
-
-// Stores all of the keys that Ion can use. Must be defined in init().
-let ionKeys;
-
-// Holds a list of keys that have been directly subscribed to or recently modified from least to most recent
-let recentlyAccessedKeys = [];
-
-// Holds a list of keys that are safe to remove when we reach max storage. If a key does not match with
-// whatever appears in this list it will NEVER be a candidate for eviction.
-let evictionAllowList = [];
-
-// Holds a map of keys and connectionID arrays whose keys will never be automatically evicted as
-// long as we have at least one subscriber that returns false for the canEvict property.
-const evictionBlocklist = {};
-
-/**
- * When a key change happens, search for any callbacks matching the regex pattern and trigger those callbacks
- * Get some data from the store
- *
- * @param {string} key
- * @returns {Promise<*>}
- */
-function get(key) {
- return AsyncStorage.getItem(key)
- .then(val => JSON.parse(val))
- .catch(err => logInfo(`Unable to get item from persistent storage. Key: ${key} Error: ${err}`));
-}
-
-/**
- * Checks to see if the a subscriber's supplied key
- * is associated with a collection of keys.
- *
- * @param {String} key
- * @returns {Boolean}
- */
-function isCollectionKey(key) {
- return _.contains(_.values(ionKeys.COLLECTION), key);
-}
-
-/**
- * Checks to see if a given key matches with the
- * configured key of our connected subscriber
- *
- * @param {String} configKey
- * @param {String} key
- * @return {Boolean}
- */
-function isKeyMatch(configKey, key) {
- return isCollectionKey(configKey)
- ? Str.startsWith(key, configKey)
- : configKey === key;
-}
-
-/**
- * Checks to see if this key has been flagged as
- * safe for removal.
- *
- * @param {String} testKey
- * @returns {Boolean}
- */
-function isSafeEvictionKey(testKey) {
- return _.some(evictionAllowList, key => isKeyMatch(key, testKey));
-}
-
-/**
- * Remove a key from the recently accessed key list.
- *
- * @param {String} key
- */
-function removeLastAccessedKey(key) {
- recentlyAccessedKeys = _.without(recentlyAccessedKeys, key);
-}
-
-/**
- * Add a key to the list of recently accessed keys. The least
- * recently accessed key should be at the head and the most
- * recently accessed key at the tail.
- *
- * @param {String} key
- */
-function addLastAccessedKey(key) {
- // Only specific keys belong in this list since we cannot remove an entire collection.
- if (isCollectionKey(key) || !isSafeEvictionKey(key)) {
- return;
- }
-
- removeLastAccessedKey(key);
- recentlyAccessedKeys.push(key);
-}
-
-/**
- * Removes a key previously added to this list
- * which will enable it to be deleted again.
- *
- * @param {String} key
- * @param {Number} connectionID
- */
-function removeFromEvictionBlockList(key, connectionID) {
- evictionBlocklist[key] = _.without(evictionBlocklist[key] || [], connectionID);
-
- // Remove the key if there are no more subscribers
- if (evictionBlocklist[key].length === 0) {
- delete evictionBlocklist[key];
- }
-}
-
-/**
- * Keys added to this list can never be deleted.
- *
- * @param {String} key
- * @param {Number} connectionID
- */
-function addToEvictionBlockList(key, connectionID) {
- removeFromEvictionBlockList(key, connectionID);
-
- if (!evictionBlocklist[key]) {
- evictionBlocklist[key] = [];
- }
-
- evictionBlocklist[key].push(connectionID);
-}
-
-/**
- * Take all the keys that are safe to evict and add them to
- * the recently accessed list when initializing the app. This
- * enables keys that have not recently been accessed to be
- * removed.
- */
-function addAllSafeEvictionKeysToRecentlyAccessedList() {
- AsyncStorage.getAllKeys()
- .then((keys) => {
- _.each(evictionAllowList, (safeEvictionKey) => {
- _.each(keys, (key) => {
- if (isKeyMatch(safeEvictionKey, key)) {
- addLastAccessedKey(key);
- }
- });
- });
- });
-}
-
-/**
- * When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
- *
- * @param {string} key
- * @param {mixed} data
- */
-function keyChanged(key, data) {
- // Add or remove this key from the recentlyAccessedKeys lists
- if (!_.isNull(data)) {
- addLastAccessedKey(key);
- } else {
- removeLastAccessedKey(key);
- }
-
- // Find all subscribers that were added with connect() and trigger the callback or setState() with the new data
- _.each(callbackToStateMapping, (subscriber) => {
- if (subscriber && isKeyMatch(subscriber.key, key)) {
- if (_.isFunction(subscriber.callback)) {
- subscriber.callback(data, key);
- }
-
- if (!subscriber.withIonInstance) {
- return;
- }
-
- // Check if we are subscribing to a collection key and add this item as a collection
- if (isCollectionKey(subscriber.key)) {
- subscriber.withIonInstance.setState((prevState) => {
- const collection = prevState[subscriber.statePropertyName] || {};
- collection[key] = data;
- return {
- [subscriber.statePropertyName]: collection,
- };
- });
- } else {
- subscriber.withIonInstance.setState({
- [subscriber.statePropertyName]: data,
- });
- }
- }
- });
-}
-
-/**
- * Sends the data obtained from the keys to the connection. It either:
- * - sets state on the withIonInstances
- * - triggers the callback function
- *
- * @param {object} config
- * @param {object} [config.withIonInstance]
- * @param {string} [config.statePropertyName]
- * @param {function} [config.callback]
- * @param {*|null} val
- */
-function sendDataToConnection(config, val) {
- if (config.withIonInstance) {
- config.withIonInstance.setState({
- [config.statePropertyName]: val,
- });
- } else if (_.isFunction(config.callback)) {
- config.callback(val);
- }
-}
-
-/**
- * Subscribes a react component's state directly to a store key
- *
- * @param {object} mapping the mapping information to connect Ion to the components state
- * @param {string} mapping.key
- * @param {string} mapping.statePropertyName the name of the property in the state to connect the data to
- * @param {object} [mapping.withIonInstance] whose setState() method will be called with any changed data
- * This is used by React components to connect to Ion
- * @param {object} [mapping.callback] a method that will be called with changed data
- * This is used by any non-React code to connect to Ion
- * @param {boolean} [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
- * component
- * @returns {number} an ID to use when calling disconnect
- */
-function connect(mapping) {
- const connectionID = lastConnectionID++;
- callbackToStateMapping[connectionID] = mapping;
-
- if (mapping.initWithStoredValues === false) {
- return connectionID;
- }
-
- // Check to see if this key is flagged as a safe eviction key and add it to the recentlyAccessedKeys list
- if (mapping.withIonInstance && !isCollectionKey(mapping.key) && isSafeEvictionKey(mapping.key)) {
- // All React components subscribing to a key flagged as a safe eviction
- // key must implement the canEvict property.
- if (_.isUndefined(mapping.canEvict)) {
- // eslint-disable-next-line max-len
- throw new Error(`Cannot subscribe to safe eviction key '${mapping.key}' without providing a canEvict value.`);
- }
- addLastAccessedKey(mapping.key);
- }
-
- AsyncStorage.getAllKeys()
- .then((keys) => {
- // Find all the keys matched by the config key
- const matchingKeys = _.filter(keys, key => isKeyMatch(mapping.key, key));
-
- // If the key being connected to does not exist, initialize the value with null
- if (matchingKeys.length === 0) {
- sendDataToConnection(mapping, null);
- return;
- }
-
- // When using a callback subscriber we will trigger the callback
- // for each key we find. It's up to the subscriber to know whether
- // to expect a single key or multiple keys in the case of a collection.
- // React components are an exception since we'll want to send their
- // initial data as a single object when using collection keys.
- if (mapping.withIonInstance && isCollectionKey(mapping.key)) {
- Promise.all(_.map(matchingKeys, key => get(key)))
- .then(values => _.reduce(values, (finalObject, value, i) => ({
- ...finalObject,
- [matchingKeys[i]]: value,
- }), {}))
- .then(val => sendDataToConnection(mapping, val));
- } else {
- _.each(matchingKeys, (key) => {
- get(key).then(val => sendDataToConnection(mapping, val));
- });
- }
- });
-
- return connectionID;
-}
-
-/**
- * Remove the listener for a react component
- *
- * @param {Number} connectionID
- * @param {String} [keyToRemoveFromEvictionBlocklist]
- */
-function disconnect(connectionID, keyToRemoveFromEvictionBlocklist) {
- if (!callbackToStateMapping[connectionID]) {
- return;
- }
-
- // Remove this key from the eviction block list as we are no longer
- // subscribing to it and it should be safe to delete again
- if (keyToRemoveFromEvictionBlocklist) {
- removeFromEvictionBlockList(keyToRemoveFromEvictionBlocklist, connectionID);
- }
-
- delete callbackToStateMapping[connectionID];
-}
-
-/**
- * Remove a key from Ion and update the subscribers
- *
- * @param {String} key
- * @return {Promise}
- */
-function remove(key) {
- return AsyncStorage.removeItem(key)
- .then(() => keyChanged(key, null));
-}
-
-/**
- * If we fail to set or merge we must handle this by
- * evicting some data from Ion and then retrying to do
- * whatever it is we attempted to do.
- *
- * @param {Error} error
- * @param {Function} ionMethod
- * @param {...any} args
- * @return {Promise}
- */
-function evictStorageAndRetry(error, ionMethod, ...args) {
- // Find the first key that we can remove that has no subscribers in our blocklist
- const keyForRemoval = _.find(recentlyAccessedKeys, key => !evictionBlocklist[key]);
-
- if (!keyForRemoval) {
- logAlert('Out of storage. But found no acceptable keys to remove.');
- throw error;
- }
-
- // Remove the least recently viewed key that is not currently being accessed and retry.
- logInfo(`Out of storage. Evicting least recently accessed key (${keyForRemoval}) and retrying.`);
- return remove(keyForRemoval)
- .then(() => ionMethod(...args));
-}
-
-/**
- * Write a value to our store with the given key
- *
- * @param {string} key
- * @param {mixed} val
- * @returns {Promise}
- */
-function set(key, val) {
- // Write the thing to persistent storage, which will trigger a storage event for any other tabs open on this domain
- return AsyncStorage.setItem(key, JSON.stringify(val))
- .then(() => keyChanged(key, val))
- .catch(error => evictStorageAndRetry(error, set, key, val));
-}
-
-/**
- * Sets multiple keys and values. Example
- * Ion.multiSet({'key1': 'a', 'key2': 'b'});
- *
- * @param {object} data
- * @returns {Promise}
- */
-function multiSet(data) {
- // AsyncStorage expenses the data in an array like:
- // [["@MyApp_user", "value_1"], ["@MyApp_key", "value_2"]]
- // This method will transform the params from a better JSON format like:
- // {'@MyApp_user': 'myUserValue', '@MyApp_key': 'myKeyValue'}
- const keyValuePairs = _.reduce(data, (finalArray, val, key) => ([
- ...finalArray,
- [key, JSON.stringify(val)],
- ]), []);
-
- return AsyncStorage.multiSet(keyValuePairs)
- .then(() => _.each(data, (val, key) => keyChanged(key, val)))
- .catch(error => evictStorageAndRetry(error, multiSet, data));
-}
-
-/**
- * Clear out all the data in the store
- *
- * @returns {Promise}
- */
-function clear() {
- let allKeys;
- return AsyncStorage.getAllKeys()
- .then(keys => allKeys = keys)
- .then(() => AsyncStorage.clear())
- .then(() => {
- _.each(allKeys, (key) => {
- keyChanged(key, null);
- });
- });
-}
-
-// Key/value store of Ion key and arrays of values to merge
-const mergeQueue = {};
-
-/**
- * Given an Ion key and value this method will combine all queued
- * value updates and return a single value. Merge attempts are
- * batched. They must occur after a single call to get() so we
- * can avoid race conditions.
- *
- * @param {String} key
- * @param {*} data
- *
- * @returns {*}
- */
-function applyMerge(key, data) {
- const mergeValues = mergeQueue[key];
-
- if (_.isArray(data)) {
- // Array values will always just concatenate
- // more items onto the end of the array
- return _.reduce(mergeValues, (modifiedData, mergeValue) => [
- ...modifiedData,
- ...mergeValue,
- ], data);
- }
-
- if (_.isObject(data)) {
- // Object values are merged one after the other
- return _.reduce(mergeValues, (modifiedData, mergeValue) => {
- const newData = lodashMerge({}, modifiedData, mergeValue);
-
- // We will also delete any object keys that are undefined or null.
- // Deleting keys is not supported by AsyncStorage so we do it this way.
- // Remove all first level keys that are explicitly set to null.
- return _.omit(newData, (value, finalObjectKey) => _.isNull(mergeValue[finalObjectKey]));
- }, data);
- }
-
- // If we have anything else we can't merge it so we'll
- // simply return the last value that was queued
- return _.last(mergeValues);
-}
-
-/**
- * Merge a new value into an existing value at a key
- *
- * @param {string} key
- * @param {*} val
- */
-function merge(key, val) {
- if (mergeQueue[key]) {
- mergeQueue[key].push(val);
- return;
- }
-
- mergeQueue[key] = [val];
- get(key)
- .then((data) => {
- const modifiedData = applyMerge(key, data);
-
- // Clean up the write queue so we
- // don't apply these changes again
- delete mergeQueue[key];
- set(key, modifiedData);
- });
-}
-
-/**
- * Initialize the store with actions and listening for storage events
- *
- * @param {Object} [options]
- * @param {String[]} [options.safeEvictionKeys] This is an array of IONKEYS
- * (individual or collection patterns) that when provided to Ion are flagged
- * as "safe" for removal. Any components subscribing to these keys must also
- * implement a canEvict option. See the README for more info.
- */
-function init({keys, initialKeyStates, safeEvictionKeys}) {
- // Let Ion know about all of our keys
- ionKeys = keys;
-
- // Let Ion know about which keys are safe to evict
- evictionAllowList = safeEvictionKeys;
- addAllSafeEvictionKeysToRecentlyAccessedList();
-
- // Initialize all of our keys with data provided
- _.each(initialKeyStates, (state, key) => merge(key, state));
-
- // Update any key whose value changes in storage
- addStorageEventHandler((key, newValue) => keyChanged(key, newValue));
-}
-
-const Ion = {
- connect,
- disconnect,
- set,
- multiSet,
- merge,
- clear,
- init,
- registerLogger,
- addToEvictionBlockList,
- removeFromEvictionBlockList,
- isSafeEvictionKey,
-};
-
-export default Ion;
diff --git a/src/libs/Log.js b/src/libs/Log.js
index 4e05d9d64aad..1557a4d56d98 100644
--- a/src/libs/Log.js
+++ b/src/libs/Log.js
@@ -1,14 +1,14 @@
import Logger from 'js-libs/lib/Logger';
+import Onyx from 'react-native-onyx';
import {logToServer} from './API';
import CONFIG from '../CONFIG';
import getPlatform from './getPlatform';
import {version} from '../../package.json';
-import Ion from './Ion';
-import IONKEYS from '../IONKEYS';
+import ONYXKEYS from '../ONYXKEYS';
let email;
-Ion.connect({
- key: IONKEYS.SESSION,
+Onyx.connect({
+ key: ONYXKEYS.SESSION,
callback: val => email = val ? val.email : null,
});
diff --git a/src/libs/NetworkConnection.js b/src/libs/NetworkConnection.js
index 2808be40ae9b..5aa78cb390df 100644
--- a/src/libs/NetworkConnection.js
+++ b/src/libs/NetworkConnection.js
@@ -1,8 +1,8 @@
import _ from 'underscore';
import {AppState} from 'react-native';
import NetInfo from '@react-native-community/netinfo';
-import Ion from './Ion';
-import IONKEYS from '../IONKEYS';
+import Onyx from 'react-native-onyx';
+import ONYXKEYS from '../ONYXKEYS';
// NetInfo.addEventListener() returns a function used to unsubscribe the
// listener so we must create a reference to it and call it in stopListeningForReconnect()
@@ -29,7 +29,7 @@ const triggerReconnectionCallbacks = _.throttle(() => {
* @param {boolean} isCurrentlyOffline
*/
function setOfflineStatus(isCurrentlyOffline) {
- Ion.merge(IONKEYS.NETWORK, {isOffline: isCurrentlyOffline});
+ Onyx.merge(ONYXKEYS.NETWORK, {isOffline: isCurrentlyOffline});
// When reconnecting, ie, going from offline to online, all the reconnection callbacks
// are triggered (this is usually Actions that need to re-download data from the server)
diff --git a/src/libs/Notification/LocalNotification/BrowserNotifications.js b/src/libs/Notification/LocalNotification/BrowserNotifications.js
index 554bdd9090ad..491d1d8e9b3b 100644
--- a/src/libs/Notification/LocalNotification/BrowserNotifications.js
+++ b/src/libs/Notification/LocalNotification/BrowserNotifications.js
@@ -1,5 +1,5 @@
// Web and desktop implementation only. Do not import for direct use. Use LocalNotification.
-import Str from '../../Str';
+import Str from 'js-libs/lib/str';
import CONST from '../../../CONST';
import focusApp from './focusApp';
diff --git a/src/libs/Str.js b/src/libs/Str.js
deleted file mode 100644
index 98a487a93c31..000000000000
--- a/src/libs/Str.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import _ from 'underscore';
-import {AllHtmlEntities} from 'html-entities';
-import guid from './guid';
-
-
-const Str = {
- /**
- * Returns the proper phrase depending on the count that is passed.
- * Example:
- * console.log(Str.pluralize('puppy', 'puppies', 1)); // puppy
- * console.log(Str.pluralize('puppy', 'puppies', 3)); // puppies
- *
- * @param {String} singular form of the phrase
- * @param {String} plural form of the phrase
- * @param {Number} n the count which determines the plurality
- *
- * @return {String}
- */
- pluralize(singular, plural, n) {
- if (!n || n > 1) {
- return plural;
- }
- return singular;
- },
-
- /**
- * Escape text while preventing any sort of double escape, so 'X & Y' -> 'X & Y' and 'X & Y' -> 'X & Y'
- *
- * @param {String} s the string to escape
- * @return {String} the escaped string
- */
- safeEscape(s) {
- return _.escape(_.unescape(s));
- },
-
- /**
- * Decodes the given HTML encoded string.
- *
- * @param {String} s The string to decode.
- * @return {String} The decoded string.
- */
- htmlDecode(s) {
- return AllHtmlEntities.decode(s);
- },
-
- /**
- * Convert new line to
- *
- * @param {String} str
- * @returns {string}
- */
- nl2br(str) {
- return str.replace(/\n/g, '
');
- },
-
- /**
- * Generates a random device login using Guid
- *
- * @returns {string}
- */
- generateDeviceLoginID() {
- return `react-native-chat-${guid()}`;
- },
-
- /**
- * Escapes all special RegExp characters from a string
- *
- * @param {String} string The subject
- *
- * @returns {String} The escaped string
- */
- escapeForRegExp(string) {
- return string.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
- },
-
- /**
- * Returns true if the haystack begins with the needle
- *
- * @param {String} haystack The full string to be searched
- * @param {String} needle The case-sensitive string to search for
- * @returns {Boolean} Returns true if the haystack starts with the needle.
- */
- startsWith(haystack, needle) {
- return _.isString(haystack)
- && _.isString(needle)
- && haystack.substring(0, needle.length) === needle;
- },
-
- /**
- * Takes in a URL and returns it with a leading '/'
- *
- * @param {mixed} url The URL to be formatted
- * @returns {String} The formatted URL
- */
- normalizeUrl(url) {
- return (typeof url === 'string' && url.startsWith('/')) ? url : `/${url}`;
- },
-
- /**
- * Checks if parameter is a string or function
- * if it is a function then we will call it with
- * any additional arguments.
- *
- * @param {String|Function} parameter
- * @returns {String}
- */
- result(parameter, ...args) {
- return _.isFunction(parameter)
- ? parameter(...args)
- : parameter;
- },
-};
-
-export default Str;
diff --git a/src/libs/UnreadIndicatorUpdater/index.js b/src/libs/UnreadIndicatorUpdater/index.js
index 6a461f804ab3..15eb976f0f24 100644
--- a/src/libs/UnreadIndicatorUpdater/index.js
+++ b/src/libs/UnreadIndicatorUpdater/index.js
@@ -1,6 +1,6 @@
import _ from 'underscore';
-import Ion from '../Ion';
-import IONKEYS from '../../IONKEYS';
+import Onyx from 'react-native-onyx';
+import ONYXKEYS from '../../ONYXKEYS';
import updateUnread from './updateUnread';
// Stash the unread action counts for each report
@@ -22,8 +22,8 @@ let connectionID;
* the title and unread count indicators
*/
function listenForReportChanges() {
- connectionID = Ion.connect({
- key: IONKEYS.COLLECTION.REPORT,
+ connectionID = Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
callback: (report) => {
if (!report || !report.reportID) {
return;
@@ -43,7 +43,7 @@ function stopListeningForReportChanges() {
return;
}
- Ion.disconnect(connectionID);
+ Onyx.disconnect(connectionID);
}
export default {
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index 01cd382dd111..a07405fc769e 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -1,35 +1,35 @@
-import Ion from '../Ion';
-import IONKEYS from '../../IONKEYS';
-import Str from '../Str';
+import Onyx from 'react-native-onyx';
+import Str from 'js-libs/lib/str';
+import ONYXKEYS from '../../ONYXKEYS';
let currentRedirectTo;
-Ion.connect({
- key: IONKEYS.APP_REDIRECT_TO,
+Onyx.connect({
+ key: ONYXKEYS.APP_REDIRECT_TO,
callback: val => currentRedirectTo = val,
});
/**
- * Redirect the app to a new page by updating the state in Ion
+ * Redirect the app to a new page by updating the state in Onyx
*
* @param {mixed} url
*/
function redirect(url) {
const formattedURL = Str.normalizeUrl(url);
- Ion.merge(IONKEYS.APP_REDIRECT_TO, formattedURL);
+ Onyx.merge(ONYXKEYS.APP_REDIRECT_TO, formattedURL);
}
/**
- * Keep the current route match stored in Ion so other libs can access it
- * Also reset the app_redirect_to in Ion so that if we go back to the current url the state will update
+ * Keep the current route match stored in Onyx so other libs can access it
+ * Also reset the app_redirect_to in Onyx so that if we go back to the current url the state will update
*
* @param {object} match
* @param {string} match.url
*/
function recordCurrentRoute({match}) {
- Ion.merge(IONKEYS.CURRENT_URL, match.url);
+ Onyx.merge(ONYXKEYS.CURRENT_URL, match.url);
if (match.url === currentRedirectTo) {
- Ion.merge(IONKEYS.APP_REDIRECT_TO, null);
+ Onyx.merge(ONYXKEYS.APP_REDIRECT_TO, null);
}
}
@@ -42,7 +42,7 @@ function recordCurrentRoute({match}) {
* @param {string} match.params.reportID
*/
function recordCurrentlyViewedReportID({match}) {
- Ion.merge(IONKEYS.CURRENTLY_VIEWED_REPORTID, match.params.reportID);
+ Onyx.merge(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, match.params.reportID);
}
export {
diff --git a/src/libs/actions/ChatSwitcher.js b/src/libs/actions/ChatSwitcher.js
index ebba6114f73f..647a0e09fbe2 100644
--- a/src/libs/actions/ChatSwitcher.js
+++ b/src/libs/actions/ChatSwitcher.js
@@ -1,18 +1,18 @@
-import Ion from '../Ion';
-import IONKEYS from '../../IONKEYS';
+import Onyx from 'react-native-onyx';
+import ONYXKEYS from '../../ONYXKEYS';
/**
* Hide the Chat Switcher
*/
function hide() {
- Ion.set(IONKEYS.IS_CHAT_SWITCHER_ACTIVE, false);
+ Onyx.set(ONYXKEYS.IS_CHAT_SWITCHER_ACTIVE, false);
}
/**
* Show the Chat Switcher
*/
function show() {
- Ion.set(IONKEYS.IS_CHAT_SWITCHER_ACTIVE, true);
+ Onyx.set(ONYXKEYS.IS_CHAT_SWITCHER_ACTIVE, true);
}
export {
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index 2d0620db8b25..83e87975c0b6 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -1,21 +1,21 @@
import _ from 'underscore';
import lodashGet from 'lodash.get';
-import Ion from '../Ion';
+import Onyx from 'react-native-onyx';
import * as API from '../API';
-import IONKEYS from '../../IONKEYS';
+import ONYXKEYS from '../../ONYXKEYS';
import md5 from '../md5';
import CONST from '../../CONST';
import NetworkConnection from '../NetworkConnection';
let currentUserEmail;
-Ion.connect({
- key: IONKEYS.SESSION,
+Onyx.connect({
+ key: ONYXKEYS.SESSION,
callback: val => currentUserEmail = val ? val.email : null,
});
let personalDetails;
-Ion.connect({
- key: IONKEYS.PERSONAL_DETAILS,
+Onyx.connect({
+ key: ONYXKEYS.PERSONAL_DETAILS,
callback: val => personalDetails = val,
});
@@ -93,7 +93,7 @@ function fetchTimezone() {
})
.then((data) => {
const timezone = lodashGet(data, 'nameValuePairs.timeZone.selected', 'America/Los_Angeles');
- Ion.merge(IONKEYS.MY_PERSONAL_DETAILS, {timezone});
+ Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, {timezone});
});
// Refresh the timezone every 30 minutes
@@ -109,15 +109,15 @@ function fetch() {
})
.then((data) => {
const allPersonalDetails = formatPersonalDetails(data.personalDetailsList);
- Ion.merge(IONKEYS.PERSONAL_DETAILS, allPersonalDetails);
+ Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, allPersonalDetails);
const myPersonalDetails = allPersonalDetails[currentUserEmail]
|| {avatarURL: getAvatar(undefined, currentUserEmail)};
// Set my personal details so they can be easily accessed and subscribed to on their own key
- Ion.merge(IONKEYS.MY_PERSONAL_DETAILS, myPersonalDetails);
+ Onyx.merge(ONYXKEYS.MY_PERSONAL_DETAILS, myPersonalDetails);
- // Get the timezone and put it in Ion
+ // Get the timezone and put it in Onyx
fetchTimezone();
})
.catch(error => console.error('Error fetching personal details', error));
@@ -137,7 +137,7 @@ function getForEmails(emailList) {
API.getPersonalDetails(emailList)
.then((data) => {
const details = _.pick(data, emailList.split(','));
- Ion.merge(IONKEYS.PERSONAL_DETAILS, formatPersonalDetails(details));
+ Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, formatPersonalDetails(details));
});
}
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index b581d38a5a6b..9e6b135c2162 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -2,9 +2,9 @@ import moment from 'moment';
import _ from 'underscore';
import lodashGet from 'lodash.get';
import ExpensiMark from 'js-libs/lib/ExpensiMark';
-import Ion from '../Ion';
+import Onyx from 'react-native-onyx';
import * as API from '../API';
-import IONKEYS from '../../IONKEYS';
+import ONYXKEYS from '../../ONYXKEYS';
import * as Pusher from '../Pusher/pusher';
import LocalNotification from '../Notification/LocalNotification';
import PushNotification from '../Notification/PushNotification';
@@ -18,8 +18,8 @@ import {hide as hideSidebar} from './Sidebar';
let currentUserEmail;
let currentUserAccountID;
-Ion.connect({
- key: IONKEYS.SESSION,
+Onyx.connect({
+ key: ONYXKEYS.SESSION,
callback: (val) => {
// When signed out, val is undefined
if (val) {
@@ -30,20 +30,20 @@ Ion.connect({
});
let currentURL;
-Ion.connect({
- key: IONKEYS.CURRENT_URL,
+Onyx.connect({
+ key: ONYXKEYS.CURRENT_URL,
callback: val => currentURL = val,
});
let lastViewedReportID;
-Ion.connect({
- key: IONKEYS.CURRENTLY_VIEWED_REPORTID,
+Onyx.connect({
+ key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID,
callback: val => lastViewedReportID = val ? Number(val) : null,
});
let myPersonalDetails;
-Ion.connect({
- key: IONKEYS.MY_PERSONAL_DETAILS,
+Onyx.connect({
+ key: ONYXKEYS.MY_PERSONAL_DETAILS,
callback: val => myPersonalDetails = val,
});
@@ -94,7 +94,7 @@ function getParticipantEmailsFromReport({sharedReportList}) {
}
/**
- * Only store the minimal amount of data in Ion that needs to be stored
+ * Only store the minimal amount of data in Onyx that needs to be stored
* because space is limited
*
* @param {object} report
@@ -152,7 +152,7 @@ function fetchChatReportsByIDs(chatList) {
// get the personal details.
let participantEmails = [];
- // Process the reports and store them in Ion
+ // Process the reports and store them in Onyx
_.each(fetchedReports, (report) => {
const newReport = getSimplifiedReportObject(report);
@@ -162,8 +162,8 @@ function fetchChatReportsByIDs(chatList) {
newReport.reportName = getChatReportName(report.sharedReportList);
}
- // Merge the data into Ion
- Ion.merge(`${IONKEYS.COLLECTION.REPORT}${report.reportID}`, newReport);
+ // Merge the data into Onyx
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, newReport);
});
// Fetch the person details if there are any
@@ -177,7 +177,7 @@ function fetchChatReportsByIDs(chatList) {
}
/**
- * Update the lastReadActionID in Ion and local memory.
+ * Update the lastReadActionID in Onyx and local memory.
*
* @param {Number} reportID
* @param {Number} sequenceNumber
@@ -186,7 +186,7 @@ function setLocalLastReadActionID(reportID, sequenceNumber) {
lastReadActionIDs[reportID] = sequenceNumber;
// Update the lastReadActionID on the report optimistically
- Ion.merge(`${IONKEYS.COLLECTION.REPORT}${reportID}`, {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
unreadActionCount: 0,
reportNameValuePairs: {
[`lastReadActionID_${currentUserAccountID}`]: sequenceNumber,
@@ -211,18 +211,18 @@ function updateReportWithNewAction(reportID, reportAction) {
setLocalLastReadActionID(reportID, newMaxSequenceNumber);
}
- // Always merge the reportID into Ion
- // If the report doesn't exist in Ion yet, then all the rest of the data will be filled out
+ // Always merge the reportID into Onyx
+ // If the report doesn't exist in Onyx yet, then all the rest of the data will be filled out
// by handleReportChanged
- Ion.merge(`${IONKEYS.COLLECTION.REPORT}${reportID}`, {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
reportID,
unreadActionCount: newMaxSequenceNumber - (lastReadActionIDs[reportID] || 0),
maxSequenceNumber: reportAction.sequenceNumber,
});
- // Add the action into Ion
+ // Add the action into Onyx
const messageText = lodashGet(reportAction, ['message', 0, 'text'], '');
- Ion.merge(`${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {
[reportAction.sequenceNumber]: {
...reportAction,
isAttachment: messageText === '[Attachment]',
@@ -323,13 +323,13 @@ function subscribeToReportTypingEvents(reportID) {
// Use a combo of the reportID and the login as a key for holding our timers.
const reportUserIdentifier = `${reportID}-${login}`;
clearTimeout(typingWatchTimers[reportUserIdentifier]);
- Ion.merge(`${IONKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, typingStatus);
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, typingStatus);
// Wait for 1.5s of no additional typing events before setting the status back to false.
typingWatchTimers[reportUserIdentifier] = setTimeout(() => {
const typingStoppedStatus = {};
typingStoppedStatus[login] = false;
- Ion.merge(`${IONKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, typingStoppedStatus);
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, typingStoppedStatus);
delete typingWatchTimers[reportUserIdentifier];
}, 1500);
});
@@ -346,7 +346,7 @@ function unsubscribeFromReportChannel(reportID) {
}
const pusherChannelName = getReportChannelName(reportID);
- Ion.set(`${IONKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, {});
+ Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, {});
Pusher.unsubscribe(pusherChannelName);
}
@@ -378,8 +378,8 @@ function fetchActions(reportID) {
.pluck('sequenceNumber')
.max()
.value();
- Ion.merge(`${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, indexedData);
- Ion.merge(`${IONKEYS.COLLECTION.REPORT}${reportID}`, {maxSequenceNumber});
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, indexedData);
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {maxSequenceNumber});
});
}
@@ -440,17 +440,17 @@ function fetchOrCreateChatReport(participants) {
});
})
- // Put the report object into Ion
+ // Put the report object into Onyx
.then((data) => {
const report = data.reports[reportID];
- // Store only the absolute bare minimum of data in Ion because space is limited
+ // Store only the absolute bare minimum of data in Onyx because space is limited
const newReport = getSimplifiedReportObject(report);
newReport.reportName = getChatReportName(report.sharedReportList);
- // Merge the data into Ion. Don't use set() here or multiSet() because then that would
+ // Merge the data into Onyx. Don't use set() here or multiSet() because then that would
// overwrite any existing data (like if they have unread messages)
- Ion.merge(`${IONKEYS.COLLECTION.REPORT}${reportID}`, newReport);
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, newReport);
// Redirect the logged in person to the new report
redirect(ROUTES.getReportRoute(reportID));
@@ -465,7 +465,7 @@ function fetchOrCreateChatReport(participants) {
* @param {object} file
*/
function addAction(reportID, text, file) {
- const actionKey = `${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`;
+ const actionKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`;
// Convert the comment from MD into HTML because that's how it is stored in the database
const parser = new ExpensiMark();
@@ -476,13 +476,13 @@ function addAction(reportID, text, file) {
const highestSequenceNumber = reportMaxSequenceNumbers[reportID] || 0;
const newSequenceNumber = highestSequenceNumber + 1;
- // Update the report in Ion to have the new sequence number
- Ion.merge(`${IONKEYS.COLLECTION.REPORT}${reportID}`, {
+ // Update the report in Onyx to have the new sequence number
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
maxSequenceNumber: newSequenceNumber,
});
// Optimistically add the new comment to the store before waiting to save it to the server
- Ion.merge(actionKey, {
+ Onyx.merge(actionKey, {
[newSequenceNumber]: {
actionName: 'ADDCOMMENT',
actorEmail: currentUserEmail,
@@ -550,7 +550,7 @@ function updateLastReadActionID(reportID, sequenceNumber) {
*/
function togglePinnedState(report) {
const pinnedValue = !report.isPinned;
- Ion.merge(`${IONKEYS.COLLECTION.REPORT}${report.reportID}`, {isPinned: pinnedValue});
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, {isPinned: pinnedValue});
API.togglePinnedReport({
reportID: report.reportID,
pinnedValue,
@@ -565,7 +565,7 @@ function togglePinnedState(report) {
* @param {string} comment
*/
function saveReportComment(reportID, comment) {
- Ion.merge(`${IONKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, comment);
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, comment);
}
/**
@@ -581,7 +581,7 @@ function broadcastUserIsTyping(reportID) {
}
/**
- * When a report changes in Ion, this fetches the report from the API if the report doesn't have a name
+ * When a report changes in Onyx, this fetches the report from the API if the report doesn't have a name
* and it keeps track of the max sequence number on the report actions.
*
* @param {object} report
@@ -592,7 +592,7 @@ function handleReportChanged(report) {
}
// A report can be missing a name if a comment is received via pusher event
- // and the report does not yet exist in Ion (eg. a new DM created with the logged in person)
+ // and the report does not yet exist in Onyx (eg. a new DM created with the logged in person)
if (report.reportName === undefined) {
fetchChatReportsByIDs([report.reportID]);
}
@@ -601,8 +601,8 @@ function handleReportChanged(report) {
reportMaxSequenceNumbers[report.reportID] = report.maxSequenceNumber;
}
-Ion.connect({
- key: IONKEYS.COLLECTION.REPORT,
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.REPORT,
callback: handleReportChanged
});
diff --git a/src/libs/actions/Session.js b/src/libs/actions/Session.js
index 542b9fb83fbd..97b4c275073e 100644
--- a/src/libs/actions/Session.js
+++ b/src/libs/actions/Session.js
@@ -1,11 +1,11 @@
-import Ion from '../Ion';
+import Onyx from 'react-native-onyx';
import * as API from '../API';
-import IONKEYS from '../../IONKEYS';
+import ONYXKEYS from '../../ONYXKEYS';
import redirectToSignIn from './SignInRedirect';
let credentials;
-Ion.connect({
- key: IONKEYS.CREDENTIALS,
+Onyx.connect({
+ key: ONYXKEYS.CREDENTIALS,
callback: val => credentials = val,
});
@@ -27,7 +27,7 @@ function signIn(partnerUserID, partnerUserSecret, twoFactorAuthCode = '', exitTo
}
/**
- * Clears the Ion store and redirects user to the sign in page
+ * Clears the Onyx store and redirects user to the sign in page
*/
function signOut() {
redirectToSignIn();
diff --git a/src/libs/actions/Sidebar.js b/src/libs/actions/Sidebar.js
index b4a893d27ee6..4939cb1a0602 100644
--- a/src/libs/actions/Sidebar.js
+++ b/src/libs/actions/Sidebar.js
@@ -1,18 +1,18 @@
-import Ion from '../Ion';
-import IONKEYS from '../../IONKEYS';
+import Onyx from 'react-native-onyx';
+import ONYXKEYS from '../../ONYXKEYS';
/**
* Hide the sidebar, if it is shown.
*/
function hide() {
- Ion.set(IONKEYS.IS_SIDEBAR_SHOWN, false);
+ Onyx.set(ONYXKEYS.IS_SIDEBAR_SHOWN, false);
}
/**
* Show the sidebar, if it is hidden.
*/
function show() {
- Ion.set(IONKEYS.IS_SIDEBAR_SHOWN, true);
+ Onyx.set(ONYXKEYS.IS_SIDEBAR_SHOWN, true);
}
/**
@@ -21,7 +21,7 @@ function show() {
* @param {Boolean} isAnimating
*/
function setIsAnimating(isAnimating) {
- Ion.set(IONKEYS.IS_SIDEBAR_ANIMATING, isAnimating);
+ Onyx.set(ONYXKEYS.IS_SIDEBAR_ANIMATING, isAnimating);
}
export {
diff --git a/src/libs/actions/SignInRedirect.js b/src/libs/actions/SignInRedirect.js
index b2f19f94bf12..b82049ce78ab 100644
--- a/src/libs/actions/SignInRedirect.js
+++ b/src/libs/actions/SignInRedirect.js
@@ -1,5 +1,5 @@
-import Ion from '../Ion';
-import IONKEYS from '../../IONKEYS';
+import Onyx from 'react-native-onyx';
+import ONYXKEYS from '../../ONYXKEYS';
import ROUTES from '../../ROUTES';
import {redirect} from './App';
import * as Pusher from '../Pusher/pusher';
@@ -8,18 +8,18 @@ import UnreadIndicatorUpdater from '../UnreadIndicatorUpdater';
import PushNotification from '../Notification/PushNotification';
let currentURL;
-Ion.connect({
- key: IONKEYS.CURRENT_URL,
+Onyx.connect({
+ key: ONYXKEYS.CURRENT_URL,
callback: val => currentURL = val,
});
let currentlyViewedReportID;
-Ion.connect({
- key: IONKEYS.CURRENTLY_VIEWED_REPORTID,
+Onyx.connect({
+ key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID,
callback: val => currentlyViewedReportID = val,
});
/**
- * Clears the Ion store, redirects to the sign in page and handles adding any exitTo params to the URL.
+ * Clears the Onyx store, redirects to the sign in page and handles adding any exitTo params to the URL.
* Normally this method would live in Session.js, but that would cause a circular dependency with Network.js.
*
* @param {String} [errorMessage] error message to be displayed on the sign in page
@@ -48,12 +48,12 @@ function redirectToSignIn(errorMessage) {
? ROUTES.SIGNIN
: ROUTES.getSigninWithExitToRoute(currentURL);
redirect(urlWithExitTo);
- Ion.clear().then(() => {
+ Onyx.clear().then(() => {
if (errorMessage) {
- Ion.set(IONKEYS.SESSION, {error: errorMessage});
+ Onyx.set(ONYXKEYS.SESSION, {error: errorMessage});
}
if (reportID) {
- Ion.set(IONKEYS.CURRENTLY_VIEWED_REPORTID, reportID);
+ Onyx.set(ONYXKEYS.CURRENTLY_VIEWED_REPORTID, reportID);
}
});
}
diff --git a/src/libs/guid.js b/src/libs/guid.js
deleted file mode 100644
index 27dd71654e58..000000000000
--- a/src/libs/guid.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * A simple GUID generator taken from https://stackoverflow.com/a/32760401/9114791
- * @returns {String}
- */
-export default function guid() {
- function s4() {
- return Math.floor((1 + Math.random()) * 0x10000)
- .toString(16)
- .substring(1);
- }
- return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
-}
diff --git a/src/libs/Ion/addStorageEventHandler/index.js b/src/libs/listenToStorageEvents/index.js
similarity index 85%
rename from src/libs/Ion/addStorageEventHandler/index.js
rename to src/libs/listenToStorageEvents/index.js
index c4ed501b355a..875495aca55b 100644
--- a/src/libs/Ion/addStorageEventHandler/index.js
+++ b/src/libs/listenToStorageEvents/index.js
@@ -4,7 +4,7 @@
*
* @param {function} callback
*/
-function addStorageEventHandler(callback) {
+function listenToStorageEvents(callback) {
window.addEventListener('storage', (e) => {
let newValue;
try {
@@ -18,4 +18,4 @@ function addStorageEventHandler(callback) {
});
}
-export default addStorageEventHandler;
+export default listenToStorageEvents;
diff --git a/src/libs/Ion/addStorageEventHandler/index.native.js b/src/libs/listenToStorageEvents/index.native.js
similarity index 67%
rename from src/libs/Ion/addStorageEventHandler/index.native.js
rename to src/libs/listenToStorageEvents/index.native.js
index de059553bc49..c1221db97d64 100644
--- a/src/libs/Ion/addStorageEventHandler/index.native.js
+++ b/src/libs/listenToStorageEvents/index.native.js
@@ -3,6 +3,6 @@
* you can't have multiple native clients open at the same time on the same
* device
*/
-function addStorageEventHandler() {}
+function listenToStorageEvents() {}
-export default addStorageEventHandler;
+export default listenToStorageEvents;
diff --git a/src/pages/SignInPage.js b/src/pages/SignInPage.js
index a06d1ddcfa3e..8b5684606ddf 100644
--- a/src/pages/SignInPage.js
+++ b/src/pages/SignInPage.js
@@ -11,13 +11,13 @@ import {
} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
+import {withOnyx} from 'react-native-onyx';
import CONFIG from '../CONFIG';
import compose from '../libs/compose';
import {withRouter, Redirect} from '../libs/Router';
import ROUTES from '../ROUTES';
import {signIn} from '../libs/actions/Session';
-import IONKEYS from '../IONKEYS';
-import withIon from '../components/withIon';
+import ONYXKEYS from '../ONYXKEYS';
import styles, {colors} from '../styles/StyleSheet';
import logo from '../../assets/images/expensify-logo_reversed.png';
@@ -26,7 +26,7 @@ const propTypes = {
// eslint-disable-next-line react/forbid-prop-types
match: PropTypes.object.isRequired,
- /* Ion Props */
+ /* Onyx Props */
// The session of the logged in person
session: PropTypes.shape({
@@ -159,7 +159,7 @@ App.defaultProps = defaultProps;
export default compose(
withRouter,
- withIon({
- session: {key: IONKEYS.SESSION},
+ withOnyx({
+ session: {key: ONYXKEYS.SESSION},
})
)(App);
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index a5a8ce53e7b2..cef37c980352 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -1,10 +1,10 @@
import React from 'react';
import {View, Image, TouchableOpacity} from 'react-native';
import PropTypes from 'prop-types';
+import {withOnyx} from 'react-native-onyx';
import Text from '../../components/Text';
import styles from '../../styles/StyleSheet';
-import IONKEYS from '../../IONKEYS';
-import withIon from '../../components/withIon';
+import ONYXKEYS from '../../ONYXKEYS';
import {withRouter} from '../../libs/Router';
import LHNToggle from '../../../assets/images/icon-menu-toggle.png';
import pinEnabled from '../../../assets/images/pin-enabled.png';
@@ -19,7 +19,7 @@ const propTypes = {
// Decides whether we should show the hamburger menu button
shouldShowHamburgerButton: PropTypes.bool.isRequired,
- /* Ion Props */
+ /* Onyx Props */
// The report currently being looked at
report: PropTypes.shape({
// Name of the report
@@ -90,9 +90,9 @@ HeaderView.defaultProps = defaultProps;
export default compose(
withRouter,
- withIon({
+ withOnyx({
report: {
- key: ({match}) => `${IONKEYS.COLLECTION.REPORT}${match.params.reportID}`,
+ key: ({match}) => `${ONYXKEYS.COLLECTION.REPORT}${match.params.reportID}`,
},
}),
)(HeaderView);
diff --git a/src/pages/home/HomePage.js b/src/pages/home/HomePage.js
index e62c7afbaa79..6b6272b67029 100644
--- a/src/pages/home/HomePage.js
+++ b/src/pages/home/HomePage.js
@@ -11,6 +11,7 @@ import {
} from 'react-native';
import _ from 'underscore';
import {SafeAreaInsetsContext, SafeAreaProvider} from 'react-native-safe-area-context';
+import {withOnyx} from 'react-native-onyx';
import {Route} from '../../libs/Router';
import styles, {getSafeAreaPadding} from '../../styles/StyleSheet';
import Header from './HeaderView';
@@ -29,8 +30,7 @@ import {fetch as fetchPersonalDetails} from '../../libs/actions/PersonalDetails'
import * as Pusher from '../../libs/Pusher/pusher';
import UnreadIndicatorUpdater from '../../libs/UnreadIndicatorUpdater';
import ROUTES from '../../ROUTES';
-import IONKEYS from '../../IONKEYS';
-import withIon from '../../components/withIon';
+import ONYXKEYS from '../../ONYXKEYS';
import NetworkConnection from '../../libs/NetworkConnection';
const windowSize = Dimensions.get('window');
@@ -58,8 +58,8 @@ class App extends React.Component {
this.showHamburger = this.showHamburger.bind(this);
this.toggleHamburgerBasedOnDimensions = this.toggleHamburgerBasedOnDimensions.bind(this);
- // Note: This null check is only necessary because withIon passes null for bound props
- // that are null-initialized initialized in Ion, and defaultProps only replaces for `undefined` values
+ // Note: This null check is only necessary because withOnyx passes null for bound props
+ // that are null-initialized initialized in Onyx, and defaultProps only replaces for `undefined` values
this.animationTranslateX = new Animated.Value(
!_.isNull(props.isSidebarShown) && !props.isSidebarShown ? -300 : 0
);
@@ -242,13 +242,13 @@ class App extends React.Component {
App.propTypes = propTypes;
App.defaultProps = defaultProps;
-export default withIon(
+export default withOnyx(
{
isSidebarShown: {
- key: IONKEYS.IS_SIDEBAR_SHOWN
+ key: ONYXKEYS.IS_SIDEBAR_SHOWN
},
isChatSwitcherActive: {
- key: IONKEYS.IS_CHAT_SWITCHER_ACTIVE,
+ key: ONYXKEYS.IS_CHAT_SWITCHER_ACTIVE,
initWithStoredValues: false,
},
},
diff --git a/src/pages/home/MainView.js b/src/pages/home/MainView.js
index e1d70f186256..336e785f6e62 100644
--- a/src/pages/home/MainView.js
+++ b/src/pages/home/MainView.js
@@ -2,9 +2,9 @@ import React, {Component} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
+import {withOnyx} from 'react-native-onyx';
import ReportView from './report/ReportView';
-import withIon from '../../components/withIon';
-import IONKEYS from '../../IONKEYS';
+import ONYXKEYS from '../../ONYXKEYS';
import styles from '../../styles/StyleSheet';
import {withRouter} from '../../libs/Router';
import compose from '../../libs/compose';
@@ -14,7 +14,7 @@ const propTypes = {
// eslint-disable-next-line react/forbid-prop-types
match: PropTypes.object.isRequired,
- /* Ion Props */
+ /* Onyx Props */
// List of reports to display
reports: PropTypes.objectOf(PropTypes.shape({
@@ -77,9 +77,9 @@ MainView.defaultProps = defaultProps;
export default compose(
withRouter,
- withIon({
+ withOnyx({
reports: {
- key: IONKEYS.COLLECTION.REPORT,
+ key: ONYXKEYS.COLLECTION.REPORT,
},
}),
)(MainView);
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index f963003d4c0a..b739ccb6a58a 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -2,13 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import {View, Image, TouchableOpacity} from 'react-native';
import _ from 'underscore';
+import {withOnyx} from 'react-native-onyx';
import styles, {colors} from '../../../styles/StyleSheet';
import TextInputFocusable from '../../../components/TextInputFocusable';
import sendIcon from '../../../../assets/images/icon-send.png';
-import IONKEYS from '../../../IONKEYS';
+import ONYXKEYS from '../../../ONYXKEYS';
import paperClipIcon from '../../../../assets/images/icon-paper-clip.png';
import AttachmentPicker from '../../../components/AttachmentPicker';
-import withIon from '../../../components/withIon';
import {addAction, saveReportComment, broadcastUserIsTyping} from '../../../libs/actions/Report';
import ReportTypingIndicator from './ReportTypingIndicator';
@@ -72,8 +72,8 @@ class ReportActionCompose extends React.Component {
}
/**
- * Save our report comment in Ion. We debounce this method in the constructor so that it's not called too often
- * to update Ion and re-render this component.
+ * Save our report comment in Onyx. We debounce this method in the constructor so that it's not called too often
+ * to update Onyx and re-render this component.
*
* @param {string} comment
*/
@@ -82,7 +82,7 @@ class ReportActionCompose extends React.Component {
}
/**
- * Update the value of the comment in Ion
+ * Update the value of the comment in Onyx
*
* @param {string} newComment
*/
@@ -196,8 +196,8 @@ class ReportActionCompose extends React.Component {
ReportActionCompose.propTypes = propTypes;
ReportActionCompose.defaultProps = defaultProps;
-export default withIon({
+export default withOnyx({
comment: {
- key: ({reportID}) => `${IONKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
+ key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
},
})(ReportActionCompose);
diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js
index 1c301df81347..22356acc8a48 100644
--- a/src/pages/home/report/ReportActionItemFragment.js
+++ b/src/pages/home/report/ReportActionItemFragment.js
@@ -4,7 +4,7 @@ import {
Linking, ActivityIndicator, View, Dimensions
} from 'react-native';
import PropTypes from 'prop-types';
-import Str from '../../../libs/Str';
+import Str from 'js-libs/lib/str';
import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes';
import styles, {webViewStyles, colors} from '../../../styles/StyleSheet';
import Text from '../../../components/Text';
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js
index cb2e4b0ba8b6..1d2fc377411c 100644
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.js
@@ -3,10 +3,10 @@ import {View, Keyboard, AppState} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import lodashGet from 'lodash.get';
+import {withOnyx} from 'react-native-onyx';
import Text from '../../../components/Text';
-import withIon from '../../../components/withIon';
import {fetchActions, updateLastReadActionID} from '../../../libs/actions/Report';
-import IONKEYS from '../../../IONKEYS';
+import ONYXKEYS from '../../../ONYXKEYS';
import ReportActionItem from './ReportActionItem';
import styles from '../../../styles/StyleSheet';
import ReportActionPropTypes from './ReportActionPropTypes';
@@ -21,7 +21,7 @@ const propTypes = {
// Is this report currently in view?
isActiveReport: PropTypes.bool.isRequired,
- /* Ion Props */
+ /* Onyx Props */
// Array of report actions for this report
reportActions: PropTypes.objectOf(PropTypes.shape(ReportActionPropTypes)),
@@ -252,12 +252,12 @@ class ReportActionsView extends React.Component {
ReportActionsView.propTypes = propTypes;
ReportActionsView.defaultProps = defaultProps;
-export default withIon({
+export default withOnyx({
reportActions: {
- key: ({reportID}) => `${IONKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
canEvict: props => !props.isActiveReport,
},
session: {
- key: IONKEYS.SESSION,
+ key: ONYXKEYS.SESSION,
},
})(ReportActionsView);
diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.js
index c486f984daab..7311f25adce1 100644
--- a/src/pages/home/report/ReportTypingIndicator.js
+++ b/src/pages/home/report/ReportTypingIndicator.js
@@ -2,9 +2,9 @@ import React from 'react';
import {View, Text} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
+import {withOnyx} from 'react-native-onyx';
import compose from '../../../libs/compose';
-import withIon from '../../../components/withIon';
-import IONKEYS from '../../../IONKEYS';
+import ONYXKEYS from '../../../ONYXKEYS';
import styles from '../../../styles/StyleSheet';
import {getDisplayName} from '../../../libs/actions/PersonalDetails';
@@ -83,9 +83,9 @@ ReportTypingIndicator.defaultProps = defaultProps;
ReportTypingIndicator.displayName = 'ReportTypingIndicator';
export default compose(
- withIon({
+ withOnyx({
userTypingStatuses: {
- key: ({reportID}) => `${IONKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`,
+ key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`,
}
}),
)(ReportTypingIndicator);
diff --git a/src/pages/home/sidebar/ChatSwitcherView.js b/src/pages/home/sidebar/ChatSwitcherView.js
index abd3999fd70d..9fefc3ce4a95 100644
--- a/src/pages/home/sidebar/ChatSwitcherView.js
+++ b/src/pages/home/sidebar/ChatSwitcherView.js
@@ -2,9 +2,9 @@ import React from 'react';
import {View, Text} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
-import withIon from '../../../components/withIon';
-import IONKEYS from '../../../IONKEYS';
-import Str from '../../../libs/Str';
+import {withOnyx} from 'react-native-onyx';
+import Str from 'js-libs/lib/str';
+import ONYXKEYS from '../../../ONYXKEYS';
import KeyboardShortcut from '../../../libs/KeyboardShortcut';
import ChatSwitcherList from './ChatSwitcherList';
import ChatSwitcherSearchForm from './ChatSwitcherSearchForm';
@@ -37,7 +37,7 @@ const propTypes = {
// Toggles the hamburger menu open and closed
onLinkClick: PropTypes.func.isRequired,
- /* Ion Props */
+ /* Onyx Props */
// All of the personal details for everyone
// The keys of this object are the logins of the users, and the values are an object
@@ -459,18 +459,18 @@ class ChatSwitcherView extends React.Component {
ChatSwitcherView.propTypes = propTypes;
ChatSwitcherView.defaultProps = defaultProps;
-export default withIon({
+export default withOnyx({
personalDetails: {
- key: IONKEYS.PERSONAL_DETAILS,
+ key: ONYXKEYS.PERSONAL_DETAILS,
},
reports: {
- key: IONKEYS.COLLECTION.REPORT
+ key: ONYXKEYS.COLLECTION.REPORT
},
session: {
- key: IONKEYS.SESSION,
+ key: ONYXKEYS.SESSION,
},
isSidebarAnimating: {
- key: IONKEYS.IS_SIDEBAR_ANIMATING,
+ key: ONYXKEYS.IS_SIDEBAR_ANIMATING,
initFromStoredValues: false,
},
})(ChatSwitcherView);
diff --git a/src/pages/home/sidebar/SidebarBottom.js b/src/pages/home/sidebar/SidebarBottom.js
index aa18e9472bdf..68af4cc87749 100644
--- a/src/pages/home/sidebar/SidebarBottom.js
+++ b/src/pages/home/sidebar/SidebarBottom.js
@@ -2,19 +2,19 @@ import React from 'react';
import {Image, View, StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
+import {withOnyx} from 'react-native-onyx';
import styles, {getSafeAreaMargins} from '../../../styles/StyleSheet';
import Text from '../../../components/Text';
import AppLinks from './AppLinks';
import {signOut} from '../../../libs/actions/Session';
-import IONKEYS from '../../../IONKEYS';
-import withIon from '../../../components/withIon';
+import ONYXKEYS from '../../../ONYXKEYS';
import SafeAreaInsetPropTypes from '../../SafeAreaInsetPropTypes';
const propTypes = {
// Safe area insets required for mobile devices margins
insets: SafeAreaInsetPropTypes.isRequired,
- /* Ion Props */
+ /* Onyx Props */
// The personal details of the person who is logged in
myPersonalDetails: PropTypes.shape({
@@ -78,9 +78,9 @@ SidebarBottom.propTypes = propTypes;
SidebarBottom.defaultProps = defaultProps;
SidebarBottom.displayName = 'SidebarBottom';
-export default withIon({
+export default withOnyx({
myPersonalDetails: {
- key: IONKEYS.MY_PERSONAL_DETAILS,
+ key: ONYXKEYS.MY_PERSONAL_DETAILS,
},
- network: {key: IONKEYS.NETWORK},
+ network: {key: ONYXKEYS.NETWORK},
})(SidebarBottom);
diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js
index 0fef983705a3..81bad1bc7aeb 100644
--- a/src/pages/home/sidebar/SidebarLinks.js
+++ b/src/pages/home/sidebar/SidebarLinks.js
@@ -3,11 +3,11 @@ import {View, ScrollView} from 'react-native';
import _ from 'underscore';
import PropTypes from 'prop-types';
import lodashOrderby from 'lodash.orderby';
+import {withOnyx} from 'react-native-onyx';
import styles from '../../../styles/StyleSheet';
import Text from '../../../components/Text';
import SidebarLink from './SidebarLink';
-import withIon from '../../../components/withIon';
-import IONKEYS from '../../../IONKEYS';
+import ONYXKEYS from '../../../ONYXKEYS';
import ChatSwitcherView from './ChatSwitcherView';
import SafeAreaInsetPropTypes from '../../SafeAreaInsetPropTypes';
import compose from '../../../libs/compose';
@@ -24,7 +24,7 @@ const propTypes = {
// Safe area insets required for mobile devices margins
insets: SafeAreaInsetPropTypes.isRequired,
- /* Ion Props */
+ /* Onyx Props */
// List of reports
reports: PropTypes.objectOf(PropTypes.shape({
@@ -102,9 +102,9 @@ SidebarLinks.displayName = 'SidebarLinks';
export default compose(
withRouter,
- withIon({
+ withOnyx({
reports: {
- key: IONKEYS.COLLECTION.REPORT,
+ key: ONYXKEYS.COLLECTION.REPORT,
},
}),
)(SidebarLinks);
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
index ef63c34f6281..98d77c113aad 100644
--- a/webpack/webpack.common.js
+++ b/webpack/webpack.common.js
@@ -51,7 +51,7 @@ module.exports = {
* use JSX/JS that needs to be transformed by babel.
*/
exclude: [
- /node_modules\/(?!(react-native-render-html|react-native-webview)\/).*|\.native\.js$/,
+ /node_modules\/(?!(react-native-render-html|react-native-webview|react-native-onyx)\/).*|\.native\.js$/,
platformExclude
],
},