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