1import { EventEmitter, UnavailabilityError } from 'expo-modules-core'; 2 3import ExpoTaskManager from './ExpoTaskManager'; 4 5// @needsAudit @docsMissing 6/** 7 * Error object that can be received through [`TaskManagerTaskBody`](#taskmanagertaskbody) when the 8 * task fails. 9 */ 10export interface TaskManagerError { 11 code: string | number; 12 message: string; 13} 14 15// @needsAudit 16/** 17 * Represents the object that is passed to the task executor. 18 */ 19export interface TaskManagerTaskBody<T = unknown> { 20 /** 21 * An object of data passed to the task executor. Its properties depends on the type of the task. 22 */ 23 data: T; 24 25 /** 26 * Error object if the task failed or `null` otherwise. 27 */ 28 error: TaskManagerError | null; 29 30 /** 31 * Additional details containing unique ID of task event and name of the task. 32 */ 33 executionInfo: TaskManagerTaskBodyExecutionInfo; 34} 35 36// @needsAudit 37/** 38 * Additional details about execution provided in `TaskManagerTaskBody`. 39 */ 40export interface TaskManagerTaskBodyExecutionInfo { 41 /** 42 * State of the application. 43 * @platform ios 44 */ 45 appState?: 'active' | 'background' | 'inactive'; 46 /** 47 * Unique ID of task event. 48 */ 49 eventId: string; 50 /** 51 * Name of the task. 52 */ 53 taskName: string; 54} 55 56// @needsAudit 57/** 58 * Represents an already registered task. 59 */ 60export interface TaskManagerTask { 61 /** 62 * Name that the task is registered with. 63 */ 64 taskName: string; 65 66 /** 67 * Type of the task which depends on how the task was registered. 68 */ 69 taskType: string; 70 71 /** 72 * Provides `options` that the task was registered with. 73 */ 74 options: any; 75} 76 77/** 78 * @deprecated Use `TaskManagerTask` instead. 79 * @hidden 80 */ 81export interface RegisteredTask extends TaskManagerTask {} 82 83// @needsAudit 84/** 85 * Type of task executor – a function that handles the task. 86 */ 87export type TaskManagerTaskExecutor<T = unknown> = (body: TaskManagerTaskBody<T>) => void; 88 89const tasks: Map<string, TaskManagerTaskExecutor<any>> = new Map< 90 string, 91 TaskManagerTaskExecutor<any> 92>(); 93 94function _validateTaskName(taskName) { 95 if (!taskName || typeof taskName !== 'string') { 96 throw new TypeError('`taskName` must be a non-empty string.'); 97 } 98} 99 100// @needsAudit 101/** 102 * Defines task function. It must be called in the global scope of your JavaScript bundle. 103 * In particular, it cannot be called in any of React lifecycle methods like `componentDidMount`. 104 * This limitation is due to the fact that when the application is launched in the background, 105 * we need to spin up your JavaScript app, run your task and then shut down — no views are mounted 106 * in this scenario. 107 * 108 * @param taskName Name of the task. It must be the same as the name you provided when registering the task. 109 * @param taskExecutor A function that will be invoked when the task with given `taskName` is executed. 110 */ 111export function defineTask<T = unknown>( 112 taskName: string, 113 taskExecutor: TaskManagerTaskExecutor<T> 114) { 115 if (!taskName || typeof taskName !== 'string') { 116 console.warn(`TaskManager.defineTask: 'taskName' argument must be a non-empty string.`); 117 return; 118 } 119 if (!taskExecutor || typeof taskExecutor !== 'function') { 120 console.warn(`TaskManager.defineTask: 'task' argument must be a function.`); 121 return; 122 } 123 tasks.set(taskName, taskExecutor); 124} 125 126// @needsAudit 127/** 128 * Checks whether the task is already defined. 129 * 130 * @param taskName Name of the task. 131 */ 132export function isTaskDefined(taskName: string): boolean { 133 return tasks.has(taskName); 134} 135 136// @needsAudit 137/** 138 * Determine whether the task is registered. Registered tasks are stored in a persistent storage and 139 * preserved between sessions. 140 * 141 * @param taskName Name of the task. 142 * @returns A promise which fulfills with a `boolean` value whether or not the task with given name 143 * is already registered. 144 */ 145export async function isTaskRegisteredAsync(taskName: string): Promise<boolean> { 146 if (!ExpoTaskManager.isTaskRegisteredAsync) { 147 throw new UnavailabilityError('TaskManager', 'isTaskRegisteredAsync'); 148 } 149 150 _validateTaskName(taskName); 151 return ExpoTaskManager.isTaskRegisteredAsync(taskName); 152} 153 154// @needsAudit 155/** 156 * Retrieves `options` associated with the task, that were passed to the function registering the task 157 * (eg. `Location.startLocationUpdatesAsync`). 158 * 159 * @param taskName Name of the task. 160 * @return A promise which fulfills with the `options` object that was passed while registering task 161 * with given name or `null` if task couldn't be found. 162 */ 163export async function getTaskOptionsAsync<TaskOptions>(taskName: string): Promise<TaskOptions> { 164 if (!ExpoTaskManager.getTaskOptionsAsync) { 165 throw new UnavailabilityError('TaskManager', 'getTaskOptionsAsync'); 166 } 167 168 _validateTaskName(taskName); 169 return ExpoTaskManager.getTaskOptionsAsync(taskName); 170} 171 172// @needsAudit 173/** 174 * Provides information about tasks registered in the app. 175 * 176 * @returns A promise which fulfills with an array of tasks registered in the app. Example: 177 * ```json 178 * [ 179 * { 180 * taskName: 'location-updates-task-name', 181 * taskType: 'location', 182 * options: { 183 * accuracy: Location.Accuracy.High, 184 * showsBackgroundLocationIndicator: false, 185 * }, 186 * }, 187 * { 188 * taskName: 'geofencing-task-name', 189 * taskType: 'geofencing', 190 * options: { 191 * regions: [...], 192 * }, 193 * }, 194 * ] 195 * ``` 196 */ 197export async function getRegisteredTasksAsync(): Promise<TaskManagerTask[]> { 198 if (!ExpoTaskManager.getRegisteredTasksAsync) { 199 throw new UnavailabilityError('TaskManager', 'getRegisteredTasksAsync'); 200 } 201 202 return ExpoTaskManager.getRegisteredTasksAsync(); 203} 204 205// @needsAudit 206/** 207 * Unregisters task from the app, so the app will not be receiving updates for that task anymore. 208 * _It is recommended to use methods specialized by modules that registered the task, eg. 209 * [`Location.stopLocationUpdatesAsync`](./location/#expolocationstoplocationupdatesasynctaskname)._ 210 * 211 * @param taskName Name of the task to unregister. 212 * @return A promise which fulfills as soon as the task is unregistered. 213 */ 214export async function unregisterTaskAsync(taskName: string): Promise<void> { 215 if (!ExpoTaskManager.unregisterTaskAsync) { 216 throw new UnavailabilityError('TaskManager', 'unregisterTaskAsync'); 217 } 218 219 _validateTaskName(taskName); 220 await ExpoTaskManager.unregisterTaskAsync(taskName); 221} 222 223// @needsAudit 224/** 225 * Unregisters all tasks registered for the running app. You may want to call this when the user is 226 * signing out and you no longer need to track his location or run any other background tasks. 227 * @return A promise which fulfills as soon as all tasks are completely unregistered. 228 */ 229export async function unregisterAllTasksAsync(): Promise<void> { 230 if (!ExpoTaskManager.unregisterAllTasksAsync) { 231 throw new UnavailabilityError('TaskManager', 'unregisterAllTasksAsync'); 232 } 233 234 await ExpoTaskManager.unregisterAllTasksAsync(); 235} 236 237if (ExpoTaskManager) { 238 const eventEmitter = new EventEmitter(ExpoTaskManager); 239 eventEmitter.addListener<TaskManagerTaskBody>( 240 ExpoTaskManager.EVENT_NAME, 241 async ({ data, error, executionInfo }) => { 242 const { eventId, taskName } = executionInfo; 243 const taskExecutor = tasks.get(taskName); 244 let result: any = null; 245 246 if (taskExecutor) { 247 try { 248 // Execute JS task 249 result = await taskExecutor({ data, error, executionInfo }); 250 } catch (error) { 251 console.error(`TaskManager: Task "${taskName}" failed:`, error); 252 } finally { 253 // Notify manager the task is finished. 254 await ExpoTaskManager.notifyTaskFinishedAsync(taskName, { eventId, result }); 255 } 256 } else { 257 console.warn( 258 `TaskManager: Task "${taskName}" has been executed but looks like it is not defined. Please make sure that "TaskManager.defineTask" is called during initialization phase.` 259 ); 260 // No tasks defined -> we need to notify about finish anyway. 261 await ExpoTaskManager.notifyTaskFinishedAsync(taskName, { eventId, result }); 262 // We should also unregister such tasks automatically as the task might have been removed 263 // from the app or just renamed - in that case it needs to be registered again (with the new name). 264 await ExpoTaskManager.unregisterTaskAsync(taskName); 265 } 266 } 267 ); 268} 269 270// @needsAudit 271/** 272 * Determine if the `TaskManager` API can be used in this app. 273 * @return A promise fulfills with `true` if the API can be used, and `false` otherwise. 274 * On the web it always returns `false`. 275 */ 276export async function isAvailableAsync(): Promise<boolean> { 277 return await ExpoTaskManager.isAvailableAsync(); 278} 279