self.onmessage = async function(e) {
const { type, data } = e.data;
switch (type) {
case 'execute':
await executeCommand(data);
break;
case 'cancel':
cancelCommand(data.commandId);
break;
default:
console.error('Unknown message type:', type);
}
};
const activeCommands = new Map();
async function executeCommand(request) {
const { id: commandId, command, args, env, stdin, timeout } = request;
const startTime = Date.now();
const abortController = new AbortController();
activeCommands.set(commandId, abortController);
try {
postMessage({
type: 'event',
event: {
type: 'Started',
command_id: commandId,
command: command,
args: args,
timestamp: new Date().toISOString()
}
});
const result = await simulateCommand(command, args, env, stdin, abortController.signal);
for (const line of result.stdout) {
if (abortController.signal.aborted) break;
postMessage({
type: 'event',
event: {
type: 'Stdout',
command_id: commandId,
data: line,
timestamp: new Date().toISOString()
}
});
await delay(10);
}
for (const line of result.stderr) {
if (abortController.signal.aborted) break;
postMessage({
type: 'event',
event: {
type: 'Stderr',
command_id: commandId,
data: line,
timestamp: new Date().toISOString()
}
});
await delay(10);
}
for (const [percentage, message] of result.progress || []) {
if (abortController.signal.aborted) break;
postMessage({
type: 'event',
event: {
type: 'Progress',
command_id: commandId,
percentage: percentage,
message: message,
timestamp: new Date().toISOString()
}
});
await delay(50);
}
const duration = Date.now() - startTime;
if (!abortController.signal.aborted) {
if (result.exitCode === 0) {
postMessage({
type: 'event',
event: {
type: 'Completed',
command_id: commandId,
exit_code: result.exitCode,
duration_ms: duration,
timestamp: new Date().toISOString()
}
});
} else {
postMessage({
type: 'event',
event: {
type: 'Failed',
command_id: commandId,
error: `Command exited with code ${result.exitCode}`,
duration_ms: duration,
timestamp: new Date().toISOString()
}
});
}
}
} catch (error) {
if (error.name === 'AbortError') {
postMessage({
type: 'event',
event: {
type: 'Cancelled',
command_id: commandId,
duration_ms: Date.now() - startTime,
timestamp: new Date().toISOString()
}
});
} else {
postMessage({
type: 'event',
event: {
type: 'Failed',
command_id: commandId,
error: error.message,
duration_ms: Date.now() - startTime,
timestamp: new Date().toISOString()
}
});
}
} finally {
activeCommands.delete(commandId);
}
}
function cancelCommand(commandId) {
const controller = activeCommands.get(commandId);
if (controller) {
controller.abort();
}
}
async function simulateCommand(command, args, env, stdin, signal) {
if (signal.aborted) throw new Error('Aborted');
const commands = {
echo: async () => ({
stdout: [args.join(' ')],
stderr: [],
exitCode: 0
}),
ls: async () => ({
stdout: [
'file1.txt',
'file2.js',
'directory/',
'README.md',
'.gitignore',
'package.json'
],
stderr: [],
exitCode: 0,
progress: [[50, 'Listing files...']]
}),
cat: async () => {
if (stdin) {
return {
stdout: stdin.split('\n'),
stderr: [],
exitCode: 0
};
} else if (args.length > 0) {
return {
stdout: [`Contents of ${args[0]}`, 'Line 1', 'Line 2', 'Line 3'],
stderr: [],
exitCode: 0
};
} else {
return {
stdout: [],
stderr: ['cat: missing file operand'],
exitCode: 1
};
}
},
grep: async () => {
const pattern = args[0] || '';
const files = args.slice(1);
if (!pattern) {
return {
stdout: [],
stderr: ['grep: missing pattern'],
exitCode: 1
};
}
return {
stdout: [
`${files[0] || 'input'}:10: Found pattern "${pattern}"`,
`${files[0] || 'input'}:25: Another match for "${pattern}"`
],
stderr: [],
exitCode: 0,
progress: [[25, 'Searching...'], [75, 'Processing matches...']]
};
},
sleep: async () => {
const seconds = parseInt(args[0]) || 1;
const steps = Math.min(seconds * 2, 10);
const progress = [];
for (let i = 0; i < steps; i++) {
if (signal.aborted) throw new Error('Aborted');
const percentage = ((i + 1) / steps) * 100;
progress.push([percentage, `Sleeping... ${Math.round(percentage)}%`]);
await delay(seconds * 1000 / steps);
}
return {
stdout: [],
stderr: [],
exitCode: 0,
progress
};
},
pwd: async () => ({
stdout: [env.PWD || '/home/user'],
stderr: [],
exitCode: 0
}),
date: async () => ({
stdout: [new Date().toString()],
stderr: [],
exitCode: 0
}),
whoami: async () => ({
stdout: [env.USER || 'web-user'],
stderr: [],
exitCode: 0
}),
env: async () => ({
stdout: Object.entries(env).map(([k, v]) => `${k}=${v}`),
stderr: [],
exitCode: 0
})
};
const commandFn = commands[command];
if (commandFn) {
return await commandFn();
} else {
return {
stdout: [],
stderr: [`${command}: command not found`],
exitCode: 127
};
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
postMessage({ type: 'ready' });