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