x-files.js
v0.4.7
Published
WebSocket-based file browser for Node.js - the truth is in your file system
Maintainers
Readme
x-files.js
__ __ ______ _ _ _
\ \/ / | ____(_) | (_)
\ /______| |__ _| | ___ ___ _ ___
/ \______| __| | | |/ _ \/ __| | / __|
/_/\_\ | | | | | __/\__ \ _ | \__ \
|_| |_|_|\___||___/ (_)| |___/
_/ |
|__/The truth is in your file system 👽
Installation • Quick Start • UI Components • API • Security • Examples
WebSocket-based file browser for Node.js with full upload/download capabilities. Browse, read, write, upload, download, and manage remote files through a simple, secure API. Supports both text and binary files with automatic file type detection.
Why x-files.js?
- WebSocket-based - Real-time, bidirectional communication
- Binary file support - Upload and download text and binary files seamlessly
- Security-first - Path whitelisting, auth hooks, granular permissions
- Lightweight - ~10KB client bundle, minimal dependencies
- TypeScript - Full type definitions included
- Universal - Works in browsers and Node.js
- UI Components - Drop-in Web Components that work with any framework
Installation
npm install x-files.jsQuick Start
Server
import { XFilesHandler } from 'x-files.js';
import { WebSocketServer } from 'ws';
const handler = new XFilesHandler({
allowedPaths: ['/home/user/projects'],
allowWrite: true,
});
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws, req) => handler.handleConnection(ws, req));
console.log('x-files server running on ws://localhost:8080');Client (Browser)
import { XFilesClient } from 'x-files.js/client/browser';
const client = new XFilesClient({ url: 'ws://localhost:8080' });
await client.connect();
// List files
const files = await client.listDirectory('/home/user/projects');
// Read a file
const { content } = await client.readFile('/home/user/projects/README.md');
// Write a file
await client.writeFile('/home/user/projects/hello.txt', 'Hello, World!');
// Upload binary file
const imageBuffer = await fs.readFile('./image.jpg');
await client.uploadBinary('/home/user/projects/image.jpg', imageBuffer);
// Download file (auto-detects text vs binary)
const { content, isBinary } = await client.downloadFile('/home/user/projects/image.jpg');
if (isBinary) {
const buffer = Buffer.from(content, 'base64');
// Handle binary data
} else {
console.log(content); // Handle text data
}Client (Node.js)
import { XFilesClient } from 'x-files.js/client';
const client = new XFilesClient({ url: 'ws://localhost:8080' });
await client.connect();
const files = await client.listDirectory('/home/user');UI Components
x-files.js includes ready-to-use Web Components built with Lit. They work with any framework (React, Vue, Angular, Svelte, vanilla JS).
Quick Usage
<script type="module">
import 'x-files.js/ui/browser';
</script>
<x-files-browser
url="ws://localhost:8080"
path="/home/user/projects"
></x-files-browser>Available Components
| Component | Description |
|-----------|-------------|
| <x-files-browser> | Full file browser with navigation, toolbar, and context menu |
| <x-files-tabbed-browser> | Tabbed file browser with multiple independent tabs |
| <x-files-icon> | File/folder icon with type detection |
| <x-files-breadcrumb> | Breadcrumb path navigation |
Browser Component
<x-files-browser
url="ws://localhost:8080/files"
path="/home/user"
show-hidden
readonly
></x-files-browser>
<script>
const browser = document.querySelector('x-files-browser');
// Listen for file selection (triggered on every click)
browser.addEventListener('select', (e) => {
console.log('Selected:', e.detail.file);
});
// Listen for file open (double-click or context menu)
browser.addEventListener('open', (e) => {
console.log('Opened:', e.detail.file);
});
// Listen for navigation
browser.addEventListener('navigate', (e) => {
console.log('Navigated to:', e.detail.path);
});
</script>Tabbed Browser Component
The tabbed browser allows users to open multiple directories in tabs for efficient multitasking:
<x-files-tabbed-browser
url="ws://localhost:8080"
path="/home/user"
max-tabs="8"
show-hidden
></x-files-tabbed-browser>
<script>
const browser = document.querySelector('x-files-tabbed-browser');
// Listen for events (optional - include tabId to identify which tab)
browser.addEventListener('navigate', (e) => {
console.log(`Tab ${e.detail.tabId} navigated to: ${e.detail.path}`);
});
browser.addEventListener('select', (e) => {
console.log(`File selected in tab ${e.detail.tabId}:`, e.detail.file);
});
browser.addEventListener('open', (e) => {
console.log(`File opened in tab ${e.detail.tabId}:`, e.detail.file);
});
</script>Key Features:
- Multiple independent tabs with shared connection
- Double-click directories to open in new tabs
- Tab switching and closing with intuitive UI
- Configurable maximum tabs (default: 10)
- All events include
tabIdfor tab identification - Responsive design with mobile support
Event Handling
Both browser components support the same events. All event listeners are optional - you only need to listen to the events you care about.
| Event | When Triggered | Detail Properties |
|-------|----------------|-------------------|
| select | On every file/folder click (selection) | file: FileEntry, tabId?: string |
| open | On double-click or context menu "Open" | file: FileEntry, tabId?: string |
| navigate | When changing directories | path: string, tabId?: string |
Note: The tabId property is only included in events from <x-files-tabbed-browser> to identify which tab triggered the event.
Theming
Use the theme attribute for built-in themes:
<!-- Dark theme (default) -->
<x-files-browser theme="dark" url="ws://localhost:8080"></x-files-browser>
<!-- Light theme -->
<x-files-browser theme="light" url="ws://localhost:8080"></x-files-browser>
<!-- Auto - follows system preference -->
<x-files-browser theme="auto" url="ws://localhost:8080"></x-files-browser>Or customize with CSS custom properties:
x-files-browser {
/* Colors */
--x-files-bg: #1e1e1e;
--x-files-bg-hover: #2d2d2d;
--x-files-bg-selected: #094771;
--x-files-border: #3c3c3c;
--x-files-text: #cccccc;
--x-files-text-muted: #808080;
--x-files-accent: #0078d4;
--x-files-danger: #f44336;
/* Icons */
--x-files-icon-folder: #dcb67a;
--x-files-icon-file: #cccccc;
/* Sizing */
--x-files-font-size: 13px;
--x-files-row-height: 28px;
--x-files-icon-size: 16px;
--x-files-padding: 8px;
--x-files-radius: 4px;
--x-files-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}With React
import 'x-files.js/ui/browser';
function App() {
const handleSelect = (e) => {
console.log('Selected:', e.detail.file);
};
const handleTabbedSelect = (e) => {
console.log(`Tab ${e.detail.tabId} selected:`, e.detail.file);
};
const handleTabbedNavigate = (e) => {
console.log(`Tab ${e.detail.tabId} navigated to:`, e.detail.path);
};
return (
<div>
{/* Single browser */}
<x-files-browser
url="ws://localhost:8080"
path="/home/user"
onSelect={handleSelect}
/>
{/* Tabbed browser (supports all the same events + tabId) */}
<x-files-tabbed-browser
url="ws://localhost:8080"
path="/home/user"
max-tabs="8"
onSelect={handleTabbedSelect}
onNavigate={handleTabbedNavigate}
/>
</div>
);
}With Vue
<template>
<div>
<!-- Single browser -->
<x-files-browser
url="ws://localhost:8080"
path="/home/user"
@select="onSelect"
/>
<!-- Tabbed browser (supports all the same events + tabId) -->
<x-files-tabbed-browser
url="ws://localhost:8080"
path="/home/user"
max-tabs="8"
@select="onTabbedSelect"
@open="onTabbedOpen"
@navigate="onTabbedNavigate"
/>
</div>
</template>
<script setup>
import 'x-files.js/ui/browser';
const onSelect = (e) => {
console.log('Selected:', e.detail.file);
};
const onTabbedSelect = (e) => {
console.log(`Tab ${e.detail.tabId} selected:`, e.detail.file);
};
const onTabbedOpen = (e) => {
console.log(`Tab ${e.detail.tabId} opened:`, e.detail.file);
};
const onTabbedNavigate = (e) => {
console.log(`Tab ${e.detail.tabId} navigated to:`, e.detail.path);
};
</script>API
Server Configuration
const handler = new XFilesHandler({
// Directories users can access (required for security)
allowedPaths: ['/data', '/uploads'], // Default: [os.homedir()]
// Permissions
allowWrite: false, // Allow create/edit operations
allowDelete: false, // Allow delete operations
// Limits
maxFileSize: 10 * 1024 * 1024, // 10MB default
// Authentication (called on each connection)
authenticate: async (req) => {
const token = req.headers.authorization;
return await validateToken(token);
},
// Authorization (called on each operation)
authorize: async (operation, path, req) => {
// Fine-grained per-operation control
return true;
},
});Client Methods
| Method | Description |
|--------|-------------|
| connect() | Connect to server |
| disconnect() | Disconnect from server |
| isConnected() | Check connection status |
| getServerConfig() | Get server configuration |
File Operations
| Method | Description | Requires |
|--------|-------------|----------|
| listDirectory(path) | List directory contents | - |
| getStats(path) | Get file/directory info | - |
| readFile(path, encoding?) | Read file contents | - |
| writeFile(path, content, encoding?) | Write file | allowWrite |
| createDirectory(path) | Create directory | allowWrite |
| deleteItem(path) | Delete file/directory | allowDelete |
| rename(oldPath, newPath) | Rename/move | allowWrite |
| copy(source, destination) | Copy file/directory | allowWrite |
| exists(path) | Check if path exists | - |
| search(path, pattern, options?) | Search for files | - |
| uploadFile(path, content, encoding?, isBinary?) | Upload text or binary file | allowWrite |
| uploadBinary(path, buffer) | Upload binary file from Buffer | allowWrite |
| downloadFile(path, asBinary?) | Download file (auto-detects binary) | - |
| downloadBinary(path) | Download file as Buffer | - |
FileEntry Type
interface FileEntry {
name: string; // File name
path: string; // Full path
isDirectory: boolean;
isFile: boolean;
size: number; // Size in bytes
modified: string; // ISO date string
created: string; // ISO date string
}Events
client.onConnect(() => console.log('Connected!'));
client.onDisconnect(() => console.log('Disconnected'));
client.onError((err) => console.error('Error:', err));Security
x-files.js is designed with security as a priority:
| Feature | Description | |---------|-------------| | Path Whitelisting | Only explicitly allowed directories are accessible | | Traversal Protection | Paths are normalized and validated | | Read-Only Default | Write/delete must be explicitly enabled | | Authentication Hook | Custom auth logic per connection | | Authorization Hook | Per-operation permission checks | | Size Limits | Configurable max file size |
Example: Secure Setup
const handler = new XFilesHandler({
// 1. Whitelist specific directories only
allowedPaths: ['/app/user-data'],
// 2. Enable only what's needed
allowWrite: true,
allowDelete: false,
// 3. Limit file sizes
maxFileSize: 5 * 1024 * 1024, // 5MB
// 4. Authenticate connections
authenticate: async (req) => {
const token = req.headers['authorization']?.replace('Bearer ', '');
if (!token) return false;
return await verifyJWT(token);
},
// 5. Authorize operations
authorize: async (operation, path, req) => {
const user = req.user;
// Users can only access their own directory
return path.startsWith(`/app/user-data/${user.id}/`);
},
});Examples
File Upload/Download
import { XFilesClient } from 'x-files.js/client';
import { promises as fs } from 'fs';
const client = new XFilesClient({ url: 'ws://localhost:8080' });
await client.connect();
// Upload text file
await client.uploadFile('/remote/path/document.txt', 'Hello, World!');
// Upload binary file from Buffer
const imageData = await fs.readFile('./local-image.jpg');
await client.uploadBinary('/remote/path/image.jpg', imageData);
// Download and auto-detect file type
const { content, isBinary, size } = await client.downloadFile('/remote/path/image.jpg');
if (isBinary) {
// Save binary file
const buffer = Buffer.from(content, 'base64');
await fs.writeFile('./downloaded-image.jpg', buffer);
} else {
// Handle text file
console.log('Text content:', content);
}
// Download binary file directly as Buffer
const { buffer } = await client.downloadBinary('/remote/path/image.jpg');
await fs.writeFile('./downloaded-binary.jpg', buffer);With Express
import express from 'express';
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { XFilesHandler } from 'x-files.js';
const app = express();
const server = createServer(app);
// Mount on /files path
const wss = new WebSocketServer({ server, path: '/files' });
const handler = new XFilesHandler({
allowedPaths: ['/data'],
allowWrite: true,
});
wss.on('connection', (ws, req) => handler.handleConnection(ws, req));
server.listen(3000);File Browser UI
Using the built-in Web Component:
<script type="module" src="https://unpkg.com/x-files.js/dist/ui/browser-bundle.js"></script>
<x-files-browser
url="ws://localhost:8080"
path="/data"
style="height: 400px;"
></x-files-browser>Or build your own UI with the headless client:
<div id="file-list"></div>
<script type="module">
import { XFilesClient } from 'https://unpkg.com/x-files.js/dist/client/browser-bundle.js';
const client = new XFilesClient({ url: 'ws://localhost:8080' });
await client.connect();
const files = await client.listDirectory('/data');
document.getElementById('file-list').innerHTML = files
.map(f => `<div>${f.isDirectory ? '[DIR]' : '[FILE]'} ${f.name}</div>`)
.join('');
</script>Development
Building the Project
npm run build # Compile TypeScript + browser bundle
npm run build:browser # Just browser bundle
npm run watch # Watch mode for TypeScript
npm run clean # Remove dist/Version Management
npm run version # Show current version and usage
npm run version get # Get current version
npm run version:bump patch # Bump patch version (0.3.1 → 0.3.2)
npm run version:bump minor # Bump minor version (0.3.1 → 0.4.0)
npm run version:bump major # Bump major version (0.3.1 → 1.0.0)Releasing
The project uses automated CI/CD through GitHub Actions:
npm run release:create # Create and push release tagThis will:
- ✅ Check that working directory is clean
- 🏗️ Build the project
- 🧪 Run tests
- 🏷️ Create and push git tag (
v{version}) - 🚀 Trigger GitHub Actions to publish to NPM and create GitHub release
Release Workflow:
- Make your changes and commit them
- Bump version:
npm run version:bump patch(or minor/major) - Commit version bump:
git commit -am "Bump version to v0.3.2" - Create release:
npm run release:create
The GitHub Actions workflow will automatically:
- Run tests on multiple Node.js versions
- Build the project
- Publish to NPM with provenance
- Create a GitHub release with auto-generated release notes
Use Cases
- Web IDEs - Browse and edit remote code
- Admin Panels - Manage server files
- File Managers - Build custom file browsers
- Dev Tools - Remote development environments
- Media Browsers - Browse remote media libraries
Related Projects
- xterm.js - Terminal for the browser
- electron-to-web - Run Electron apps in the browser
License
MIT
