1import { NativeModulesProxy, EventEmitter } from 'expo-core'; 2 3interface TaskError { 4 code: string | number, 5 message: string, 6} 7 8interface TaskBody { 9 data: object, 10 error: TaskError | null, 11 executionInfo: { 12 eventId: string, 13 taskName: string, 14 }, 15} 16 17interface RegisteredTask { 18 taskName: string, 19 taskType: string, 20 options: any, 21} 22 23type Task = (body: TaskBody) => void; 24 25const { ExpoTaskManager } = NativeModulesProxy; 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 _validateTaskName(taskName); 63 return ExpoTaskManager.isTaskRegisteredAsync(taskName); 64} 65 66export async function getTaskOptionsAsync<TaskOptions>(taskName: string): Promise<TaskOptions> { 67 _validateTaskName(taskName); 68 return ExpoTaskManager.getTaskOptionsAsync(taskName); 69} 70 71export async function getRegisteredTasksAsync(): Promise<RegisteredTask[]> { 72 return ExpoTaskManager.getRegisteredTasksAsync(); 73} 74 75export async function unregisterTaskAsync(taskName: string): Promise<void> { 76 _validateTaskName(taskName); 77 await ExpoTaskManager.unregisterTaskAsync(taskName); 78} 79 80export async function unregisterAllTasksAsync(): Promise<void> { 81 await ExpoTaskManager.unregisterAllTasksAsync(); 82} 83 84eventEmitter.addListener<TaskBody>(ExpoTaskManager.EVENT_NAME, async ({ data, error, executionInfo }) => { 85 const { eventId, taskName } = executionInfo; 86 const task = tasks.get(taskName); 87 let result: any = null; 88 89 if (task) { 90 try { 91 // Execute JS task 92 result = await task({ data, error, executionInfo }); 93 } catch (error) { 94 console.error(`TaskManager: Task "${taskName}" failed:`, error); 95 } finally { 96 // Notify manager the task is finished. 97 await ExpoTaskManager.notifyTaskFinishedAsync(taskName, { eventId, result }); 98 } 99 } else { 100 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.`); 101 // No tasks defined -> we need to notify about finish anyway. 102 await ExpoTaskManager.notifyTaskFinishedAsync(taskName, { eventId, result }); 103 // We should also unregister such tasks automatically as the task might have been removed 104 // from the app or just renamed - in that case it needs to be registered again (with the new name). 105 await ExpoTaskManager.unregisterTaskAsync(taskName); 106 } 107}); 108 109// @tsapeta: Turn off `defineTask` function right after the initialization phase. 110// Promise.resolve() ensures that it will be called as a microtask just after the first event loop. 111Promise.resolve().then(() => { 112 isRunningDuringInitialization = false; 113}); 114