|
1 | | -type DevtoolsOptions = { |
2 | | - maxAge?: number; |
3 | | - name?: string; |
4 | | - postTimelineUpdate?: () => void; |
5 | | - preAction?: () => void; |
6 | | - logTrace?: boolean; |
7 | | - remote?: boolean; |
8 | | -}; |
9 | | - |
10 | | -type DevtoolsSubscriber = (message: {type: string; payload: unknown; state: string}) => void; |
11 | | - |
12 | | -type DevtoolsConnection = { |
13 | | - send(data: Record<string, unknown>, state: Record<string, unknown>): void; |
14 | | - init(state: Record<string, unknown>): void; |
15 | | - unsubscribe(): void; |
16 | | - subscribe(cb: DevtoolsSubscriber): () => void; |
17 | | -}; |
18 | | - |
19 | | -const ERROR_LABEL = 'Onyx DevTools - Error: '; |
20 | | - |
21 | | -type ReduxDevtools = { |
22 | | - connect(options?: DevtoolsOptions): DevtoolsConnection; |
23 | | -}; |
24 | | - |
25 | | -class DevTools { |
26 | | - private remoteDev?: DevtoolsConnection; |
27 | | - |
28 | | - private state: Record<string, unknown>; |
29 | | - |
30 | | - private defaultState: Record<string, unknown>; |
31 | | - |
32 | | - constructor() { |
33 | | - this.remoteDev = this.connectViaExtension(); |
34 | | - this.state = {}; |
35 | | - this.defaultState = {}; |
36 | | - } |
37 | | - |
38 | | - connectViaExtension(options?: DevtoolsOptions): DevtoolsConnection | undefined { |
39 | | - try { |
40 | | - // We don't want to augment the window type in a library code, so we use type assertion instead |
41 | | - // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-explicit-any |
42 | | - const reduxDevtools: ReduxDevtools = typeof window === 'undefined' ? undefined : (window as any).__REDUX_DEVTOOLS_EXTENSION__; |
43 | | - |
44 | | - if (options?.remote || !reduxDevtools) { |
45 | | - return; |
46 | | - } |
| 1 | +import type {IDevTools, DevtoolsConnection} from './DevTools/types'; |
| 2 | +import RealDevTools from './DevTools/RealDevTools'; |
| 3 | +import NoOpDevTools from './DevTools/NoOpDevTools'; |
| 4 | + |
| 5 | +// Start with a no-op instance |
| 6 | +let devToolsInstance: IDevTools = new NoOpDevTools(); |
| 7 | + |
| 8 | +/** |
| 9 | + * Initializes DevTools with the given enabled flag |
| 10 | + */ |
| 11 | +function initDevTools(enabled: boolean): void { |
| 12 | + devToolsInstance = enabled ? new RealDevTools() : new NoOpDevTools(); |
| 13 | +} |
47 | 14 |
|
48 | | - return reduxDevtools.connect(options); |
49 | | - } catch (e) { |
50 | | - console.error(ERROR_LABEL, e); |
51 | | - } |
52 | | - } |
| 15 | +/** |
| 16 | + * Gets the current DevTools instance (for testing purposes only) |
| 17 | + * @private |
| 18 | + */ |
| 19 | +function getDevToolsInstance(): IDevTools { |
| 20 | + return devToolsInstance; |
| 21 | +} |
53 | 22 |
|
54 | | - /** |
55 | | - * Registers an action that updated the current state of the storage |
56 | | - * |
57 | | - * @param type - name of the action |
58 | | - * @param payload - data written to the storage |
59 | | - * @param stateChanges - partial state that got updated after the changes |
60 | | - */ |
| 23 | +/** |
| 24 | + * Export a default object that delegates to the current devToolsInstance |
| 25 | + * This allows the instance to be swapped out while keeping the same import signature |
| 26 | + */ |
| 27 | +const DevTools: IDevTools = { |
61 | 28 | registerAction(type: string, payload: unknown, stateChanges: Record<string, unknown> | null = {}) { |
62 | | - try { |
63 | | - if (!this.remoteDev) { |
64 | | - return; |
65 | | - } |
66 | | - const newState = { |
67 | | - ...this.state, |
68 | | - ...stateChanges, |
69 | | - }; |
70 | | - this.remoteDev.send({type, payload}, newState); |
71 | | - this.state = newState; |
72 | | - } catch (e) { |
73 | | - console.error(ERROR_LABEL, e); |
74 | | - } |
75 | | - } |
76 | | - |
| 29 | + devToolsInstance.registerAction(type, payload, stateChanges); |
| 30 | + }, |
77 | 31 | initState(initialState: Record<string, unknown> = {}) { |
78 | | - try { |
79 | | - if (!this.remoteDev) { |
80 | | - return; |
81 | | - } |
82 | | - this.remoteDev.init(initialState); |
83 | | - this.state = initialState; |
84 | | - this.defaultState = initialState; |
85 | | - } catch (e) { |
86 | | - console.error(ERROR_LABEL, e); |
87 | | - } |
88 | | - } |
89 | | - |
90 | | - /** |
91 | | - * This clears the internal state of the DevTools, preserving the keys included in `keysToPreserve` |
92 | | - */ |
93 | | - clearState(keysToPreserve: string[] = []): void { |
94 | | - const newState = Object.entries(this.state).reduce((obj: Record<string, unknown>, [key, value]) => { |
95 | | - // eslint-disable-next-line no-param-reassign |
96 | | - obj[key] = keysToPreserve.includes(key) ? value : this.defaultState[key]; |
97 | | - return obj; |
98 | | - }, {}); |
99 | | - |
100 | | - this.registerAction('CLEAR', undefined, newState); |
101 | | - } |
102 | | -} |
| 32 | + devToolsInstance.initState(initialState); |
| 33 | + }, |
| 34 | + clearState(keysToPreserve: string[] = []) { |
| 35 | + devToolsInstance.clearState(keysToPreserve); |
| 36 | + }, |
| 37 | +}; |
103 | 38 |
|
104 | | -export default new DevTools(); |
105 | | -export type {DevtoolsConnection}; |
| 39 | +export default DevTools; |
| 40 | +export {initDevTools, getDevToolsInstance}; |
| 41 | +export type {DevtoolsConnection, IDevTools}; |
| 42 | +export type {default as RealDevTools} from './DevTools/RealDevTools'; |
0 commit comments