1import { DeviceEventEmitter } from 'expo-modules-core';
2import { EventEmitter, EventSubscription } from 'fbemitter';
3
4import { transformNativeStateMachineContext } from './Updates';
5import type { UpdateEvent, UpdatesNativeStateChangeEvent } from './Updates.types';
6
7let _emitter: EventEmitter | null;
8
9function _getEmitter(): EventEmitter {
10  if (!_emitter) {
11    _emitter = new EventEmitter();
12    DeviceEventEmitter.addListener('Expo.nativeUpdatesEvent', _emitEvent);
13    DeviceEventEmitter.addListener(
14      'Expo.nativeUpdatesStateChangeEvent',
15      _emitNativeStateChangeEvent
16    );
17  }
18  return _emitter;
19}
20
21// Reemits native UpdateEvents sent during the startup update check
22function _emitEvent(params): void {
23  if (!_emitter) {
24    throw new Error(`EventEmitter must be initialized to use from its listener`);
25  }
26  let newParams = { ...params };
27  if (typeof params === 'string') {
28    newParams = JSON.parse(params);
29  }
30  if (newParams.manifestString) {
31    newParams.manifest = JSON.parse(newParams.manifestString);
32    delete newParams.manifestString;
33  }
34  _emitter.emit('Expo.updatesEvent', newParams);
35}
36
37// Reemits native state change events
38function _emitNativeStateChangeEvent(params: any) {
39  if (!_emitter) {
40    throw new Error(`EventEmitter must be initialized to use from its listener`);
41  }
42  let newParams = { ...params };
43  if (typeof params === 'string') {
44    newParams = JSON.parse(params);
45  }
46  newParams.context = transformNativeStateMachineContext(newParams.context);
47  _emitter.emit('Expo.updatesStateChangeEvent', newParams);
48}
49
50/**
51 * Adds a callback to be invoked when updates-related events occur (such as upon the initial app
52 * load) due to auto-update settings chosen at build-time. See also the
53 * [`useUpdateEvents`](#useupdateeventslistener) React hook.
54 *
55 * @param listener A function that will be invoked with an [`UpdateEvent`](#updateevent) instance
56 * and should not return any value.
57 * @return An `EventSubscription` object on which you can call `remove()` to unsubscribe the
58 * listener.
59 */
60export function addListener(listener: (event: UpdateEvent) => void): EventSubscription {
61  const emitter = _getEmitter();
62  return emitter.addListener('Expo.updatesEvent', listener);
63}
64
65// Internal methods
66
67/**
68 * @hidden
69 */
70export const addUpdatesStateChangeListener = (
71  listener: (event: UpdatesNativeStateChangeEvent) => void
72) => {
73  // Add listener for state change events
74  const emitter = _getEmitter();
75  return emitter.addListener('Expo.updatesStateChangeEvent', listener);
76};
77
78/**
79 * @hidden
80 */
81export const emitStateChangeEvent = (event: UpdatesNativeStateChangeEvent) => {
82  // Allows JS to emit a state change event (used in testing)
83  _emitNativeStateChangeEvent(event);
84};
85