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:
Field | Type | Description |
---|---|---|
path | string | Absolute file path |
changeType | string | created , modified , deleted , or renamed |
size | number | File size in bytes (0 for deleted) |
timestamp | string | When change occurred |
isDirectory | boolean | True 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
- Set up WebSocket connection
- Learn about File Operations
- Explore Snapshots for state management