react-native-background-task-manager
Version:
Advanced React Native background task manager with foreground services, scheduling, and geolocation support for Android
294 lines (271 loc) • 7.41 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TaskManagerClass = exports.TaskManager = void 0;
var _reactNative = require("react-native");
class TaskManagerClass {
tasks = new Map();
taskRunner = null;
isRunning = false;
samplingInterval = 500; // ms
/**
* Generate a unique task ID
*/
generateTaskId() {
return "task_" + Math.random().toString(36).substring(2, 14);
}
/**
* Add a new task to the task manager
*/
addTask(taskFn, config) {
const taskId = config.taskId ?? this.generateTaskId();
const task = {
taskId,
task: taskFn,
config: {
...config,
priority: config.priority ?? 'normal',
retryCount: config.retryCount ?? 0,
timeout: config.timeout ?? 30000
},
nextExecutionTime: Date.now() + (config.delay ?? 0),
executionCount: 0,
status: 'pending',
retryAttempts: 0
};
this.tasks.set(taskId, task);
this.startTaskRunner();
return taskId;
}
/**
* Remove a task from the task manager
*/
removeTask(taskId) {
this.tasks.delete(taskId);
if (this.tasks.size === 0) {
this.stopTaskRunner();
}
}
/**
* Update an existing task
*/
updateTask(taskId, taskFn, config) {
const existingTask = this.tasks.get(taskId);
if (!existingTask) {
// If task doesn't exist, add it
this.addTask(taskFn, {
...config,
taskId
});
return;
}
const updatedTask = {
...existingTask,
task: taskFn,
config: {
...config,
priority: config.priority ?? 'normal',
retryCount: config.retryCount ?? 0,
timeout: config.timeout ?? 30000
},
nextExecutionTime: Date.now() + (config.delay ?? 0),
status: 'pending'
};
this.tasks.set(taskId, updatedTask);
}
/**
* Pause a task
*/
pauseTask(taskId) {
const task = this.tasks.get(taskId);
if (task && task.status !== 'paused') {
task.status = 'paused';
this.tasks.set(taskId, task);
}
}
/**
* Resume a paused task
*/
resumeTask(taskId) {
const task = this.tasks.get(taskId);
if (task && task.status === 'paused') {
task.status = 'pending';
task.nextExecutionTime = Date.now() + (task.config.delay ?? 0);
this.tasks.set(taskId, task);
}
}
/**
* Check if a task is currently running
*/
isTaskRunning(taskId) {
const task = this.tasks.get(taskId);
return task ? task.status === 'running' : false;
}
/**
* Get all tasks with their status
*/
getAllTasks() {
const result = {};
for (const [taskId, task] of this.tasks) {
result[taskId] = {
taskId: task.taskId,
isRunning: task.status === 'running',
executionCount: task.executionCount,
...(task.lastExecutionTime !== undefined && {
lastExecutionTime: task.lastExecutionTime
}),
...(task.nextExecutionTime !== undefined && {
nextExecutionTime: task.nextExecutionTime
}),
status: task.status
};
}
return result;
}
/**
* Get status of a specific task
*/
getTaskStatus(taskId) {
const task = this.tasks.get(taskId);
if (!task) return null;
return {
taskId: task.taskId,
isRunning: task.status === 'running',
executionCount: task.executionCount,
...(task.lastExecutionTime !== undefined && {
lastExecutionTime: task.lastExecutionTime
}),
...(task.nextExecutionTime !== undefined && {
nextExecutionTime: task.nextExecutionTime
}),
status: task.status
};
}
/**
* Remove all tasks
*/
removeAllTasks() {
this.tasks.clear();
this.stopTaskRunner();
}
/**
* Start the task runner
*/
startTaskRunner() {
if (this.isRunning) return;
this.isRunning = true;
this.taskRunner = setInterval(() => {
this.executeTasksCycle();
}, this.samplingInterval);
}
/**
* Stop the task runner
*/
stopTaskRunner() {
if (this.taskRunner) {
clearInterval(this.taskRunner);
this.taskRunner = null;
}
this.isRunning = false;
}
/**
* Execute tasks that are ready to run
*/
async executeTasksCycle() {
if (!this.isRunning) return;
const now = Date.now();
const readyTasks = Array.from(this.tasks.values()).filter(task => this.shouldExecuteTask(task, now)).sort((a, b) => this.getTaskPriority(a.config.priority) - this.getTaskPriority(b.config.priority));
const promises = readyTasks.map(task => this.executeTask(task));
await Promise.all(promises.map(p => p.catch(e => console.error('Task execution error:', e))));
}
/**
* Check if a task should be executed
*/
shouldExecuteTask(task, now) {
return task.status === 'pending' && now >= task.nextExecutionTime;
}
/**
* Get numeric priority value for sorting
*/
getTaskPriority(priority) {
switch (priority) {
case 'high':
return 1;
case 'normal':
return 2;
case 'low':
return 3;
default:
return 2;
}
}
/**
* Execute a single task
*/
async executeTask(task) {
task.status = 'running';
task.lastExecutionTime = Date.now();
task.executionCount++;
this.tasks.set(task.taskId, task);
try {
// Execute task with timeout
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Task timeout')), task.config.timeout);
});
const taskPromise = Promise.resolve(task.task());
await Promise.race([taskPromise, timeoutPromise]);
// Task succeeded
task.status = task.config.onLoop ? 'pending' : 'completed';
if (task.config.onLoop) {
task.nextExecutionTime = Date.now() + task.config.delay;
} else {
// Remove one-time task after completion
this.tasks.delete(task.taskId);
}
// Call success callback
if (task.config.onSuccess) {
task.config.onSuccess();
}
} catch (error) {
// Task failed
task.retryAttempts++;
if (task.retryAttempts <= (task.config.retryCount ?? 0)) {
// Retry the task
task.status = 'pending';
task.nextExecutionTime = Date.now() + task.config.delay;
} else {
// Max retries reached
task.status = 'failed';
// Call error callback
if (task.config.onError) {
task.config.onError(error);
}
}
}
this.tasks.set(task.taskId, task);
}
/**
* Register a headless task for background execution
*/
registerForegroundTask(taskName, task) {
_reactNative.AppRegistry.registerHeadlessTask(taskName, () => task);
}
/**
* Get task manager statistics
*/
getStats() {
const tasks = Array.from(this.tasks.values());
return {
totalTasks: tasks.length,
runningTasks: tasks.filter(t => t.status === 'running').length,
pendingTasks: tasks.filter(t => t.status === 'pending').length,
completedTasks: tasks.filter(t => t.status === 'completed').length,
failedTasks: tasks.filter(t => t.status === 'failed').length
};
}
}
// Export singleton instance
exports.TaskManagerClass = TaskManagerClass;
const TaskManager = exports.TaskManager = new TaskManagerClass();
//# sourceMappingURL=TaskManager.js.map