UNPKG

react-native-onyx

Version:

State management for React Native

190 lines (189 loc) 8.79 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __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 OnyxKeys_1 = __importDefault(require("./OnyxKeys")); const Str = __importStar(require("./Str")); const OnyxSnapshotCache_1 = __importDefault(require("./OnyxSnapshotCache")); /** * Manages Onyx connections of `Onyx.connect()` and `useOnyx()` 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'); } /** * 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, reuseConnection } = 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 when `reuseConnection` is `false`, which means the subscriber // explicitly wants the connection to not be reused. Collection-root subscriptions are now always // snapshot mode, so they can be reused like any other connection. if (reuseConnection === false) { suffix += `,uniqueID=${Str.guid()}`; } return `onyxKey=${key}${suffix}`; } /** * Fires all the subscribers callbacks associated with that connection ID. */ fireCallbacks(connectionID) { const connection = this.connectionsMap.get(connectionID); if (!connection) { return; } for (const callback of connection.callbacks.values()) { try { if (OnyxKeys_1.default.isCollectionKey(connection.onyxKey)) { callback(connection.cachedCallbackValue, connection.cachedCallbackKey); } else { callback(connection.cachedCallbackValue, connection.cachedCallbackKey); } } catch (error) { Logger.logAlert(`[ConnectionManager] Subscriber callback threw an error for key '${connection.onyxKey}': ${error}`); } } } /** * 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) { const callback = (value, key) => { 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; this.fireCallbacks(connectionID); } }; subscriptionID = OnyxUtils_1.default.subscribeToKey(Object.assign(Object.assign({}, connectOptions), { callback })); connectionMetadata = { subscriptionID, onyxKey: connectOptions.key, isConnectionMade: false, callbacks: new Map(), }; 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; (_a = connectOptions.callback) === null || _a === void 0 ? void 0 : _a.call(connectOptions, 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.connectionsMap.delete(connection.id); } } /** * Disconnect all subscribers from Onyx. */ disconnectAll() { for (const connectionMetadata of this.connectionsMap.values()) { OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID); } this.connectionsMap.clear(); // Clear snapshot cache when all connections are disconnected OnyxSnapshotCache_1.default.clear(); } /** * Refreshes the connection manager's session ID. */ refreshSessionID() { this.sessionID = Str.guid(); // Clear snapshot cache when session refreshes to avoid stale cache issues OnyxSnapshotCache_1.default.clear(); } } const connectionManager = new OnyxConnectionManager(); exports.default = connectionManager;