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