1import { EventEmitter, UnavailabilityError } from '@unimodules/core'; 2import ExpoTaskManager from './ExpoTaskManager'; 3 4interface TaskError { 5 code: string | number; 6 message: string; 7} 8 9interface TaskBody { 10 data: object; 11 error: TaskError | null; 12 executionInfo: { 13 eventId: string; 14 taskName: string; 15 }; 16} 17 18export interface RegisteredTask { 19 taskName: string; 20 taskType: string; 21 options: any; 22} 23 24type Task = (body: TaskBody) => void; 25 26const eventEmitter = new EventEmitter(ExpoTaskManager); 27const tasks: Map<string, Task> = new Map<string, Task>(); 28 29let isRunningDuringInitialization = true; 30 31function _validateTaskName(taskName) { 32 if (!taskName || typeof taskName !== 'string') { 33 throw new TypeError('`taskName` must be a non-empty string.'); 34 } 35} 36 37export function defineTask(taskName: string, task: Task) { 38 if (!isRunningDuringInitialization) { 39 console.error(`TaskManager.defineTask must be called during initialization phase!`); 40 return; 41 } 42 if (!taskName || typeof taskName !== 'string') { 43 console.warn(`TaskManager.defineTask: 'taskName' argument must be a non-empty string.`); 44 return; 45 } 46 if (!task || typeof task !== 'function') { 47 console.warn(`TaskManager.defineTask: 'task' argument must be a function.`); 48 return; 49 } 50 if (tasks.has(taskName)) { 51 console.warn(`TaskManager.defineTask: task '${taskName}' is already defined.`); 52 return; 53 } 54 tasks.set(taskName, task); 55} 56 57export function isTaskDefined(taskName: string): boolean { 58 return tasks.has(taskName); 59} 60 61export async function isTaskRegisteredAsync(taskName: string): Promise<boolean> { 62 if (!ExpoTaskManager.isTaskRegisteredAsync) { 63 throw new UnavailabilityError('TaskManager', 'isTaskRegisteredAsync'); 64 } 65 66 _validateTaskName(taskName); 67 return ExpoTaskManager.isTaskRegisteredAsync(taskName); 68} 69 70export async function getTaskOptionsAsync<TaskOptions>(taskName: string): Promise<TaskOptions> { 71 if (!ExpoTaskManager.getTaskOptionsAsync) { 72 throw new UnavailabilityError('TaskManager', 'getTaskOptionsAsync'); 73 } 74 75 _validateTaskName(taskName); 76 return ExpoTaskManager.getTaskOptionsAsync(taskName); 77} 78 79export async function getRegisteredTasksAsync(): Promise<RegisteredTask[]> { 80 if (!ExpoTaskManager.getRegisteredTasksAsync) { 81 throw new UnavailabilityError('TaskManager', 'getRegisteredTasksAsync'); 82 } 83 84 return ExpoTaskManager.getRegisteredTasksAsync(); 85} 86 87export async function unregisterTaskAsync(taskName: string): Promise<void> { 88 if (!ExpoTaskManager.unregisterTaskAsync) { 89 throw new UnavailabilityError('TaskManager', 'unregisterTaskAsync'); 90 } 91 92 _validateTaskName(taskName); 93 await ExpoTaskManager.unregisterTaskAsync(taskName); 94} 95 96export async function unregisterAllTasksAsync(): Promise<void> { 97 if (!ExpoTaskManager.unregisterAllTasksAsync) { 98 throw new UnavailabilityError('TaskManager', 'unregisterAllTasksAsync'); 99 } 100 101 await ExpoTaskManager.unregisterAllTasksAsync(); 102} 103 104eventEmitter.addListener<TaskBody>( 105 ExpoTaskManager.EVENT_NAME, 106 async ({ data, error, executionInfo }) => { 107 const { eventId, taskName } = executionInfo; 108 const task = tasks.get(taskName); 109 let result: any = null; 110 111 if (task) { 112 try { 113 // Execute JS task 114 result = await task({ data, error, executionInfo }); 115 } catch (error) { 116 console.error(`TaskManager: Task "${taskName}" failed:`, error); 117 } finally { 118 // Notify manager the task is finished. 119 await ExpoTaskManager.notifyTaskFinishedAsync(taskName, { eventId, result }); 120 } 121 } else { 122 console.warn( 123 `TaskManager: Task "${taskName}" has been executed but looks like it is not defined. Please make sure that "TaskManager.defineTask" is called during initialization phase.` 124 ); 125 // No tasks defined -> we need to notify about finish anyway. 126 await ExpoTaskManager.notifyTaskFinishedAsync(taskName, { eventId, result }); 127 // We should also unregister such tasks automatically as the task might have been removed 128 // from the app or just renamed - in that case it needs to be registered again (with the new name). 129 await ExpoTaskManager.unregisterTaskAsync(taskName); 130 } 131 } 132); 133 134// @tsapeta: Turn off `defineTask` function right after the initialization phase. 135// Promise.resolve() ensures that it will be called as a microtask just after the first event loop. 136Promise.resolve().then(() => { 137 isRunningDuringInitialization = false; 138}); 139