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