1import { EventEmitter, UnavailabilityError } from '@unimodules/core'; 2 3import ExpoTaskManager from './ExpoTaskManager'; 4 5/** 6 * Error object that can be received through TaskManagerTaskBody when the task fails. 7 */ 8export interface TaskManagerError { 9 code: string | number; 10 message: string; 11} 12 13/** 14 * Represents the object that is passed to the task executor. 15 */ 16export interface TaskManagerTaskBody<T = object> { 17 /** 18 * An object of data passed to the task executor. Its properties depends on the type of the task. 19 */ 20 data: T; 21 22 /** 23 * Error object if the task failed or `null` otherwise. 24 */ 25 error: TaskManagerError | null; 26 27 /** 28 * Additional details containing unique ID of task event and name of the task. 29 */ 30 executionInfo: { 31 // iOS only 32 appState?: 'active' | 'background' | 'inactive'; 33 34 eventId: string; 35 taskName: string; 36 }; 37} 38 39/** 40 * Represents an already registered task. 41 */ 42export interface TaskManagerTask { 43 /** 44 * Name that the task is registered with. 45 */ 46 taskName: string; 47 48 /** 49 * Type of the task which depends on how the task was registered. 50 */ 51 taskType: string; 52 53 /** 54 * Provides `options` that the task was registered with. 55 */ 56 options: any; 57} 58 59/** 60 * @deprecated Use `TaskManagerTask` instead. 61 */ 62export interface RegisteredTask extends TaskManagerTask {} 63 64/** 65 * Type of task executor – a function that handles the task. 66 */ 67export type TaskManagerTaskExecutor = (body: TaskManagerTaskBody) => void; 68 69const tasks: Map<string, TaskManagerTaskExecutor> = new Map<string, TaskManagerTaskExecutor>(); 70 71function _validateTaskName(taskName) { 72 if (!taskName || typeof taskName !== 'string') { 73 throw new TypeError('`taskName` must be a non-empty string.'); 74 } 75} 76 77/** 78 * Method that you use to define a task – it saves given task executor under given task name 79 * which must be correlated with the task name used when registering the task. 80 * 81 * @param taskName Name of the task. It must be the same as the name you provided when registering the task. 82 * @param taskExecutor A function that handles the task. 83 */ 84export function defineTask(taskName: string, taskExecutor: TaskManagerTaskExecutor) { 85 if (!taskName || typeof taskName !== 'string') { 86 console.warn(`TaskManager.defineTask: 'taskName' argument must be a non-empty string.`); 87 return; 88 } 89 if (!taskExecutor || typeof taskExecutor !== 'function') { 90 console.warn(`TaskManager.defineTask: 'task' argument must be a function.`); 91 return; 92 } 93 tasks.set(taskName, taskExecutor); 94} 95 96/** 97 * Checks whether the task is already defined. 98 * 99 * @param taskName Name of the task. 100 */ 101export function isTaskDefined(taskName: string): boolean { 102 return tasks.has(taskName); 103} 104 105/** 106 * Checks whether the task has been registered. 107 * 108 * @param taskName Name of the task. 109 * @returns A promise resolving to boolean value. If `false` then even if the task is defined, it won't be called because it's not registered. 110 */ 111export async function isTaskRegisteredAsync(taskName: string): Promise<boolean> { 112 if (!ExpoTaskManager.isTaskRegisteredAsync) { 113 throw new UnavailabilityError('TaskManager', 'isTaskRegisteredAsync'); 114 } 115 116 _validateTaskName(taskName); 117 return ExpoTaskManager.isTaskRegisteredAsync(taskName); 118} 119 120/** 121 * Retrieves an `options` object for provided `taskName`. 122 * 123 * @param taskName Name of the task. 124 */ 125export async function getTaskOptionsAsync<TaskOptions>(taskName: string): Promise<TaskOptions> { 126 if (!ExpoTaskManager.getTaskOptionsAsync) { 127 throw new UnavailabilityError('TaskManager', 'getTaskOptionsAsync'); 128 } 129 130 _validateTaskName(taskName); 131 return ExpoTaskManager.getTaskOptionsAsync(taskName); 132} 133 134/** 135 * Provides informations about registered tasks. 136 * 137 * @returns Returns a promise resolving to an array containing all tasks registered by the app. 138 */ 139export async function getRegisteredTasksAsync(): Promise<TaskManagerTask[]> { 140 if (!ExpoTaskManager.getRegisteredTasksAsync) { 141 throw new UnavailabilityError('TaskManager', 'getRegisteredTasksAsync'); 142 } 143 144 return ExpoTaskManager.getRegisteredTasksAsync(); 145} 146 147/** 148 * Unregisters the task. Tasks are usually registered by other modules (e.g. expo-location). 149 * 150 * @param taskName Name of the task. 151 */ 152export async function unregisterTaskAsync(taskName: string): Promise<void> { 153 if (!ExpoTaskManager.unregisterTaskAsync) { 154 throw new UnavailabilityError('TaskManager', 'unregisterTaskAsync'); 155 } 156 157 _validateTaskName(taskName); 158 await ExpoTaskManager.unregisterTaskAsync(taskName); 159} 160 161/** 162 * Unregisters all tasks registered by the app. You may want to call this when the user is 163 * signing out and you no longer need to track his location or run any other background tasks. 164 */ 165export async function unregisterAllTasksAsync(): Promise<void> { 166 if (!ExpoTaskManager.unregisterAllTasksAsync) { 167 throw new UnavailabilityError('TaskManager', 'unregisterAllTasksAsync'); 168 } 169 170 await ExpoTaskManager.unregisterAllTasksAsync(); 171} 172 173if (ExpoTaskManager) { 174 const eventEmitter = new EventEmitter(ExpoTaskManager); 175 eventEmitter.addListener<TaskManagerTaskBody>( 176 ExpoTaskManager.EVENT_NAME, 177 async ({ data, error, executionInfo }) => { 178 const { eventId, taskName } = executionInfo; 179 const taskExecutor = tasks.get(taskName); 180 let result: any = null; 181 182 if (taskExecutor) { 183 try { 184 // Execute JS task 185 result = await taskExecutor({ data, error, executionInfo }); 186 } catch (error) { 187 console.error(`TaskManager: Task "${taskName}" failed:`, error); 188 } finally { 189 // Notify manager the task is finished. 190 await ExpoTaskManager.notifyTaskFinishedAsync(taskName, { eventId, result }); 191 } 192 } else { 193 console.warn( 194 `TaskManager: Task "${taskName}" has been executed but looks like it is not defined. Please make sure that "TaskManager.defineTask" is called during initialization phase.` 195 ); 196 // No tasks defined -> we need to notify about finish anyway. 197 await ExpoTaskManager.notifyTaskFinishedAsync(taskName, { eventId, result }); 198 // We should also unregister such tasks automatically as the task might have been removed 199 // from the app or just renamed - in that case it needs to be registered again (with the new name). 200 await ExpoTaskManager.unregisterTaskAsync(taskName); 201 } 202 } 203 ); 204} 205 206export async function isAvailableAsync(): Promise<boolean> { 207 return await ExpoTaskManager.isAvailableAsync(); 208} 209