Oblien Docs

File Watcher

Monitor file system changes in real-time via WebSocket for automated workflows and live updates.

How It Works

The File Watcher automatically monitors your sandbox file system and sends notifications via WebSocket when files change.

Receiving File Change Events

// Using SDK
const ws = await sandbox.websocket.connect();

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    console.log('File changed:', message.data.path);
    console.log('Change type:', message.data.changeType);
    console.log('File size:', message.data.size);
  }
});
// Using native WebSocket
const ws = new WebSocket(`wss://sandbox.oblien.com:55872?token=${token}`);

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  
  if (message.type === 'file:changed') {
    handleFileChange(message.data);
  }
};

function handleFileChange(data) {
  console.log(`[${data.changeType}] ${data.path}`);
  
  switch (data.changeType) {
    case 'created':
      console.log('New file created');
      break;
    case 'modified':
      console.log('File was modified');
      break;
    case 'deleted':
      console.log('File was deleted');
      break;
    case 'renamed':
      console.log('File was renamed');
      break;
  }
}

Event Data

File change events include:

{
  "type": "file:changed",
  "data": {
    "path": "/opt/app/src/index.js",
    "changeType": "modified",
    "size": 1245,
    "timestamp": "2025-10-16T12:00:00Z",
    "isDirectory": false
  }
}

Event Fields:

FieldTypeDescription
pathstringAbsolute file path
changeTypestringcreated, modified, deleted, or renamed
sizenumberFile size in bytes (0 for deleted)
timestampstringWhen change occurred
isDirectorybooleanTrue if directory

Change Types

File Created

{
  "type": "file:changed",
  "data": {
    "path": "/opt/app/new-file.js",
    "changeType": "created",
    "size": 256,
    "isDirectory": false
  }
}

File Modified

{
  "type": "file:changed",
  "data": {
    "path": "/opt/app/src/index.js",
    "changeType": "modified",
    "size": 1450,
    "isDirectory": false
  }
}

File Deleted

{
  "type": "file:changed",
  "data": {
    "path": "/opt/app/old-file.js",
    "changeType": "deleted",
    "size": 0,
    "isDirectory": false
  }
}

Directory Created

{
  "type": "file:changed",
  "data": {
    "path": "/opt/app/new-folder",
    "changeType": "created",
    "size": 4096,
    "isDirectory": true
  }
}

Controlling the Watcher

Disable for Operation

Pause watcher during file operations:

// Using SDK
await sandbox.files.create({
  filePath: '/opt/app/temp.txt',
  content: 'temporary',
  withWatcher: false // Don't trigger notification
});

Batch Operations

// Process multiple files without notifications
const files = ['file1.js', 'file2.js', 'file3.js'];

for (const file of files) {
  await sandbox.files.create({
    filePath: `/opt/app/${file}`,
    content: '// Generated file',
    withWatcher: false
  });
}

// Send single notification when done
await sandbox.files.create({
  filePath: '/opt/app/.batch-complete',
  content: '',
  withWatcher: true
});

Use Cases

Auto-Reload on Changes

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    const { path, changeType } = message.data;
    
    // Reload app when source files change
    if (path.includes('/src/') && changeType === 'modified') {
      console.log('Source file changed, reloading...');
      restartApplication();
    }
  }
});

Live Compilation

ws.on('message', async (message) => {
  if (message.type === 'file:changed') {
    const { path, changeType } = message.data;
    
    // Compile TypeScript on save
    if (path.endsWith('.ts') && changeType === 'modified') {
      await sandbox.terminal.execute({
        command: `npx tsc ${path}`,
        cwd: '/opt/app'
      });
    }
  }
});

Sync Files to Remote

const changedFiles = new Set();

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    changedFiles.add(message.data.path);
  }
});

// Periodically sync changed files
setInterval(async () => {
  if (changedFiles.size > 0) {
    const files = Array.from(changedFiles);
    changedFiles.clear();
    
    // Commit and push
    await sandbox.git.add({ repoPath: '/opt/app', files });
    await sandbox.git.commit({ 
      repoPath: '/opt/app', 
      message: `Auto-sync: ${files.length} files` 
    });
    await sandbox.git.push({ repoPath: '/opt/app' });
  }
}, 30000); // Every 30 seconds

Code Analysis Trigger

ws.on('message', async (message) => {
  if (message.type === 'file:changed') {
    const { path, changeType } = message.data;
    
    if (changeType === 'modified' && path.endsWith('.js')) {
      // Run linter on changed file
      const lint = await sandbox.terminal.execute({
        command: `npx eslint ${path}`,
        cwd: '/opt/app'
      });
      
      if (lint.exitCode !== 0) {
        console.log('Linting errors:', lint.output);
      }
    }
  }
});

Filtering Events

By File Type

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    const { path } = message.data;
    
    // Only handle JavaScript files
    if (path.endsWith('.js') || path.endsWith('.ts')) {
      handleCodeChange(path);
    }
    
    // Only handle config files
    if (path.includes('config') || path.endsWith('.json')) {
      handleConfigChange(path);
    }
  }
});

By Directory

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    const { path } = message.data;
    
    // Only handle changes in src directory
    if (path.startsWith('/opt/app/src/')) {
      handleSourceChange(path);
    }
    
    // Ignore node_modules
    if (!path.includes('node_modules')) {
      handleChange(path);
    }
  }
});

By Change Type

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    const { path, changeType } = message.data;
    
    switch (changeType) {
      case 'created':
        console.log('New file:', path);
        break;
      case 'modified':
        console.log('File updated:', path);
        break;
      case 'deleted':
        console.log('File removed:', path);
        break;
    }
  }
});

Debouncing Events

Avoid processing rapid successive changes:

const debounceMap = new Map();
const debounceDelay = 500; // ms

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    const { path } = message.data;
    
    // Clear existing timeout
    if (debounceMap.has(path)) {
      clearTimeout(debounceMap.get(path));
    }
    
    // Set new timeout
    const timeout = setTimeout(() => {
      handleFileChange(path);
      debounceMap.delete(path);
    }, debounceDelay);
    
    debounceMap.set(path, timeout);
  }
});

Performance Considerations

1. Batch Processing

const changeQueue = [];
let processing = false;

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    changeQueue.push(message.data);
    processQueue();
  }
});

async function processQueue() {
  if (processing || changeQueue.length === 0) return;
  
  processing = true;
  const batch = changeQueue.splice(0, 10); // Process 10 at a time
  
  for (const change of batch) {
    await handleChange(change);
  }
  
  processing = false;
  if (changeQueue.length > 0) {
    processQueue();
  }
}

2. Ignore Patterns

const ignorePatterns = [
  /node_modules/,
  /\.git/,
  /dist/,
  /build/,
  /\.log$/
];

ws.on('message', (message) => {
  if (message.type === 'file:changed') {
    const { path } = message.data;
    
    // Ignore if matches any pattern
    if (ignorePatterns.some(pattern => pattern.test(path))) {
      return;
    }
    
    handleFileChange(path);
  }
});

Next Steps