UNPKG

stream-chat-react-native-core

Version:

The official React Native and Expo components for Stream Chat, a service for building chat applications

118 lines (103 loc) 4.31 kB
import { useCallback, useEffect, useState } from 'react'; import type { StreamChat } from 'stream-chat'; import { useStableCallback } from '../../../hooks/useStableCallback'; import { OfflineDB } from '../../../store/OfflineDB'; import { SqliteClient, SqliteClientError } from '../../../store/SqliteClient'; export type InitializeOfflineDbOptions = { /** * Encrypts the offline database at rest with SQLCipher, using the key this resolves * to. Leaving it unset opens the database unencrypted, which is the default. See * `ChatProps.getOfflineDbEncryptionKey` for the build flag it requires, the stability * requirement, and how failures are surfaced. */ getEncryptionKey?: () => Promise<string | undefined>; /** * Optional positive cap on the number of events a single `/sync` response may * contain before the offline sync manager skips replaying those events into local * storage. `false` opts out entirely. */ maxSyncEventsLimit?: number | false; }; export type UseInitializeOfflineDbParams = { client: StreamChat; /** Whether offline support is enabled at all. */ enabled: boolean; options?: InitializeOfflineDbOptions; userID?: string; }; /** * Attaches an offline database to the client and initializes it for a user. * * **Raises** whatever prevented the database from opening, from render, so an error * boundary above the caller can decide what to do. The offline database is never * silently downgraded, because an integration that asked for encryption must not end * up with an unencrypted cache. */ export const useInitializeOfflineDb = ({ client, enabled, options, userID, }: UseInitializeOfflineDbParams) => { /** * Why this attempt could not open the offline database. * * Held per attempt rather than read from a longer-lived source: a value that outlived * the attempt would be seen during the first render after a re-mount and raised * before that mount's own attempt could run, so an error boundary that re-mounts to * retry would loop forever. */ const [initializationError, setInitializationError] = useState<SqliteClientError>(); const { getEncryptionKey, maxSyncEventsLimit } = options ?? {}; // `getEncryptionKey` is overwhelmingly likely to be an inline arrow. Stabilising it // keeps a new identity per render out of the dependencies below, while still calling // whatever the latest prop is. const resolveEncryptionKey = useStableCallback( () => getEncryptionKey?.() ?? Promise.resolve(undefined), ); const isEncryptionEnabled = !!getEncryptionKey; const initialize = useCallback(async () => { if (!(userID && enabled)) { return; } if (!client.offlineDb) { const keyGetter = isEncryptionEnabled ? resolveEncryptionKey : undefined; // Confirm the database can be opened before attaching it: the client writes // through `client.offlineDb` without checking that it initialized, so one we // cannot open turns those writes into rejections (and UI is affected directly). if (keyGetter) { SqliteClient.getEncryptionKey = keyGetter; try { await SqliteClient.preflightEncryption(); } catch (error) { if (error instanceof SqliteClientError) { setInitializationError(error); return; } throw error; } } client.setOfflineDBApi( new OfflineDB({ client, getEncryptionKey: keyGetter, maxSyncEventsLimit }), ); } const { offlineDb } = client; if (offlineDb) { await offlineDb.init(userID); // Note: Since `init()` currently swallows errors by design, we have to rely // on consuming the error later in order to be able to still rethrow without // introducing a breaking change. // TODO: The DB API should be changed in the next major to always throw upwards // and let integrators handle it if necessary. setInitializationError( offlineDb instanceof OfflineDB ? offlineDb.initializationError : undefined, ); } }, [client, enabled, isEncryptionEnabled, maxSyncEventsLimit, resolveEncryptionKey, userID]); useEffect(() => { initialize(); }, [initialize]); if (initializationError) { throw initializationError; } };