UNPKG

react-native-onyx

Version:

State management for React Native

228 lines (227 loc) 12.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const bindAll_1 = __importDefault(require("lodash/bindAll")); const Logger = __importStar(require("./Logger")); const OnyxUtils_1 = __importDefault(require("./OnyxUtils")); const Str = __importStar(require("./Str")); const utils_1 = __importDefault(require("./utils")); const OnyxCache_1 = __importDefault(require("./OnyxCache")); /** * Manages Onyx connections of `Onyx.connect()`, `useOnyx()` and `withOnyx()` subscribers. */ class OnyxConnectionManager { constructor() { this.connectionsMap = new Map(); this.lastCallbackID = 0; this.sessionID = Str.guid(); // Binds all public methods to prevent problems with `this`. (0, bindAll_1.default)(this, 'generateConnectionID', 'fireCallbacks', 'connect', 'disconnect', 'disconnectAll', 'refreshSessionID', 'addToEvictionBlockList', 'removeFromEvictionBlockList'); } /** * Generates a connection ID based on the `connectOptions` object passed to the function. * * The properties used to generate the ID are handpicked for performance reasons and * according to their purpose and effect they produce in the Onyx connection. */ generateConnectionID(connectOptions) { const { key, initWithStoredValues, reuseConnection, waitForCollectionCallback } = connectOptions; // The current session ID is appended to the connection ID so we can have different connections // after an `Onyx.clear()` operation. let suffix = `,sessionID=${this.sessionID}`; // We will generate a unique ID in any of the following situations: // - `reuseConnection` is `false`. That means the subscriber explicitly wants the connection to not be reused. // - `initWithStoredValues` is `false`. This flag changes the subscription flow when set to `false`, so the connection can't be reused. // - `key` is a collection key AND `waitForCollectionCallback` is `undefined/false`. This combination needs a new connection at every subscription // in order to send all the collection entries, so the connection can't be reused. // - `withOnyxInstance` is defined inside `connectOptions`. That means the subscriber is a `withOnyx` HOC and therefore doesn't support connection reuse. if (reuseConnection === false || initWithStoredValues === false || (!utils_1.default.hasWithOnyxInstance(connectOptions) && OnyxUtils_1.default.isCollectionKey(key) && (waitForCollectionCallback === undefined || waitForCollectionCallback === false)) || utils_1.default.hasWithOnyxInstance(connectOptions)) { suffix += `,uniqueID=${Str.guid()}`; } return `onyxKey=${key},initWithStoredValues=${initWithStoredValues !== null && initWithStoredValues !== void 0 ? initWithStoredValues : true},waitForCollectionCallback=${waitForCollectionCallback !== null && waitForCollectionCallback !== void 0 ? waitForCollectionCallback : false}${suffix}`; } /** * Fires all the subscribers callbacks associated with that connection ID. */ fireCallbacks(connectionID) { const connection = this.connectionsMap.get(connectionID); connection === null || connection === void 0 ? void 0 : connection.callbacks.forEach((callback) => { if (connection.waitForCollectionCallback) { callback(connection.cachedCallbackValue, connection.cachedCallbackKey, connection.sourceValue); } else { callback(connection.cachedCallbackValue, connection.cachedCallbackKey); } }); } /** * Connects to an Onyx key given the options passed and listens to its changes. * * @param connectOptions The options object that will define the behavior of the connection. * @returns The connection object to use when calling `disconnect()`. */ connect(connectOptions) { const connectionID = this.generateConnectionID(connectOptions); let connectionMetadata = this.connectionsMap.get(connectionID); let subscriptionID; const callbackID = String(this.lastCallbackID++); // If there is no connection yet for that connection ID, we create a new one. if (!connectionMetadata) { let callback; // If the subscriber is a `withOnyx` HOC we don't define `callback` as the HOC will use // its own logic to handle the data. if (!utils_1.default.hasWithOnyxInstance(connectOptions)) { callback = (value, key, sourceValue) => { const createdConnection = this.connectionsMap.get(connectionID); if (createdConnection) { // We signal that the first connection was made and now any new subscribers // can fire their callbacks immediately with the cached value when connecting. createdConnection.isConnectionMade = true; createdConnection.cachedCallbackValue = value; createdConnection.cachedCallbackKey = key; createdConnection.sourceValue = sourceValue; this.fireCallbacks(connectionID); } }; } subscriptionID = OnyxUtils_1.default.subscribeToKey(Object.assign(Object.assign({}, connectOptions), { callback: callback })); connectionMetadata = { subscriptionID, onyxKey: connectOptions.key, isConnectionMade: false, callbacks: new Map(), waitForCollectionCallback: connectOptions.waitForCollectionCallback, }; this.connectionsMap.set(connectionID, connectionMetadata); } // We add the subscriber's callback to the list of callbacks associated with this connection. if (connectOptions.callback) { connectionMetadata.callbacks.set(callbackID, connectOptions.callback); } // If the first connection is already made we want any new subscribers to receive the cached callback value immediately. if (connectionMetadata.isConnectionMade) { // Defer the callback execution to the next tick of the event loop. // This ensures that the current execution flow completes and the result connection object is available when the callback fires. Promise.resolve().then(() => { var _a, _b; (_b = (_a = connectOptions).callback) === null || _b === void 0 ? void 0 : _b.call(_a, connectionMetadata.cachedCallbackValue, connectionMetadata.cachedCallbackKey); }); } return { id: connectionID, callbackID }; } /** * Disconnects and removes the listener from the Onyx key. * * @param connection Connection object returned by calling `connect()`. */ disconnect(connection) { if (!connection) { Logger.logInfo(`[ConnectionManager] Attempted to disconnect passing an undefined connection object.`); return; } const connectionMetadata = this.connectionsMap.get(connection.id); if (!connectionMetadata) { Logger.logInfo(`[ConnectionManager] Attempted to disconnect but no connection was found.`); return; } // Removes the callback from the connection's callbacks map. connectionMetadata.callbacks.delete(connection.callbackID); // If the connection's callbacks map is empty we can safely unsubscribe from the Onyx key. if (connectionMetadata.callbacks.size === 0) { OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID); this.removeFromEvictionBlockList(connection); this.connectionsMap.delete(connection.id); } } /** * Disconnect all subscribers from Onyx. */ disconnectAll() { this.connectionsMap.forEach((connectionMetadata, connectionID) => { OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID); connectionMetadata.callbacks.forEach((_, callbackID) => { this.removeFromEvictionBlockList({ id: connectionID, callbackID }); }); }); this.connectionsMap.clear(); } /** * Refreshes the connection manager's session ID. */ refreshSessionID() { this.sessionID = Str.guid(); } /** * Adds the connection to the eviction block list. Connections added to this list can never be evicted. * */ addToEvictionBlockList(connection) { var _a; if (!connection) { Logger.logInfo(`[ConnectionManager] Attempted to add connection to eviction block list passing an undefined connection object.`); return; } const connectionMetadata = this.connectionsMap.get(connection.id); if (!connectionMetadata) { Logger.logInfo(`[ConnectionManager] Attempted to add connection to eviction block list but no connection was found.`); return; } const evictionBlocklist = OnyxCache_1.default.getEvictionBlocklist(); if (!evictionBlocklist[connectionMetadata.onyxKey]) { evictionBlocklist[connectionMetadata.onyxKey] = []; } (_a = evictionBlocklist[connectionMetadata.onyxKey]) === null || _a === void 0 ? void 0 : _a.push(`${connection.id}_${connection.callbackID}`); } /** * Removes a connection previously added to this list * which will enable it to be evicted again. */ removeFromEvictionBlockList(connection) { var _a, _b, _c; if (!connection) { Logger.logInfo(`[ConnectionManager] Attempted to remove connection from eviction block list passing an undefined connection object.`); return; } const connectionMetadata = this.connectionsMap.get(connection.id); if (!connectionMetadata) { Logger.logInfo(`[ConnectionManager] Attempted to remove connection from eviction block list but no connection was found.`); return; } const evictionBlocklist = OnyxCache_1.default.getEvictionBlocklist(); evictionBlocklist[connectionMetadata.onyxKey] = (_b = (_a = evictionBlocklist[connectionMetadata.onyxKey]) === null || _a === void 0 ? void 0 : _a.filter((evictionKey) => evictionKey !== `${connection.id}_${connection.callbackID}`)) !== null && _b !== void 0 ? _b : []; // Remove the key if there are no more subscribers. if (((_c = evictionBlocklist[connectionMetadata.onyxKey]) === null || _c === void 0 ? void 0 : _c.length) === 0) { delete evictionBlocklist[connectionMetadata.onyxKey]; } } } const connectionManager = new OnyxConnectionManager(); exports.default = connectionManager;