1import 'abort-controller/polyfill';
2import { UnavailabilityError } from 'expo-modules-core';
3
4import ServerRegistrationModule from './ServerRegistrationModule';
5import { addPushTokenListener } from './TokenEmitter';
6import { DevicePushToken } from './Tokens.types';
7import getDevicePushTokenAsync from './getDevicePushTokenAsync';
8import { updateDevicePushTokenAsync as updateDevicePushTokenAsyncWithSignal } from './utils/updateDevicePushTokenAsync';
9
10let lastAbortController: AbortController | null = null;
11async function updatePushTokenAsync(token: DevicePushToken) {
12  // Abort current update process
13  lastAbortController?.abort();
14  lastAbortController = new AbortController();
15  return await updateDevicePushTokenAsyncWithSignal(lastAbortController.signal, token);
16}
17
18/**
19 * Encapsulates device server registration data
20 */
21export type DevicePushTokenRegistration = {
22  isEnabled: boolean;
23};
24
25/**
26 * Sets the registration information so that the device push token gets pushed
27 * to the given registration endpoint
28 * @param enabled
29 */
30export async function setAutoServerRegistrationEnabledAsync(enabled: boolean) {
31  // We are overwriting registration, so we shouldn't let
32  // any pending request complete.
33  lastAbortController?.abort();
34
35  if (!ServerRegistrationModule.setRegistrationInfoAsync) {
36    throw new UnavailabilityError('ServerRegistrationModule', 'setRegistrationInfoAsync');
37  }
38
39  await ServerRegistrationModule.setRegistrationInfoAsync(
40    enabled ? JSON.stringify({ isEnabled: enabled }) : null
41  );
42}
43
44// note(Chmiela): This function is exported only for testing purposes.
45export async function __handlePersistedRegistrationInfoAsync(
46  registrationInfo: string | null | undefined
47) {
48  if (!registrationInfo) {
49    // No registration info, nothing to do
50    return;
51  }
52
53  let registration: DevicePushTokenRegistration | null = null;
54  try {
55    registration = JSON.parse(registrationInfo);
56  } catch (e) {
57    console.warn(
58      '[expo-notifications] Error encountered while fetching registration information for auto token updates.',
59      e
60    );
61  }
62
63  if (!registration?.isEnabled) {
64    // Registration is invalid or not enabled, nothing more to do
65    return;
66  }
67
68  try {
69    // Since the registration is enabled, fetching a "new" device token
70    // shouldn't be a problem.
71    const latestDevicePushToken = await getDevicePushTokenAsync();
72    await updatePushTokenAsync(latestDevicePushToken);
73  } catch (e) {
74    console.warn(
75      '[expo-notifications] Error encountered while updating server registration with latest device push token.',
76      e
77    );
78  }
79}
80
81if (ServerRegistrationModule.getRegistrationInfoAsync) {
82  // A global scope (to get all the updates) device push token
83  // subscription, never cleared.
84  addPushTokenListener(async (token) => {
85    try {
86      // Before updating the push token on server we always check if we should
87      // Since modules can't change their method availability while running, we
88      // can assert it's defined.
89      const registrationInfo = await ServerRegistrationModule.getRegistrationInfoAsync!();
90
91      if (!registrationInfo) {
92        // Registration is not enabled
93        return;
94      }
95
96      const registration: DevicePushTokenRegistration | null = JSON.parse(registrationInfo);
97      if (registration?.isEnabled) {
98        // Dispatch an abortable task to update
99        // registration with new token.
100        await updatePushTokenAsync(token);
101      }
102    } catch (e) {
103      console.warn(
104        '[expo-notifications] Error encountered while updating server registration with latest device push token.',
105        e
106      );
107    }
108  });
109
110  // Verify if persisted registration
111  // has successfully uploaded last known
112  // device push token. If not, retry.
113  ServerRegistrationModule.getRegistrationInfoAsync().then(__handlePersistedRegistrationInfoAsync);
114} else {
115  console.warn(
116    `[expo-notifications] Error encountered while fetching auto-registration state, new tokens will not be automatically registered on server.`,
117    new UnavailabilityError('ServerRegistrationModule', 'getRegistrationInfoAsync')
118  );
119}
120