npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

x-files.js

v0.4.7

Published

WebSocket-based file browser for Node.js - the truth is in your file system

Readme

x-files.js

    __  __      ______ _ _                _
    \ \/ /     |  ____(_) |              (_)
     \  /______| |__   _| | ___  ___      _ ___
     /  \______|  __| | | |/ _ \/ __|    | / __|
    /_/\_\     | |    | | |  __/\__ \  _ | \__ \
               |_|    |_|_|\___||___/ (_)| |___/
                                        _/ |
                                       |__/

The truth is in your file system 👽

npm version License: MIT

InstallationQuick StartUI ComponentsAPISecurityExamples


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.js

Quick 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 tabId for 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 tag

This will:

  1. ✅ Check that working directory is clean
  2. 🏗️ Build the project
  3. 🧪 Run tests
  4. 🏷️ Create and push git tag (v{version})
  5. 🚀 Trigger GitHub Actions to publish to NPM and create GitHub release

Release Workflow:

  1. Make your changes and commit them
  2. Bump version: npm run version:bump patch (or minor/major)
  3. Commit version bump: git commit -am "Bump version to v0.3.2"
  4. 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

License

MIT