ClientServeJS

Version 0.1

Introduction

ClientServeJS is a client-side file serving library that allows you to serve files directly from the browser without requiring a server. It's perfect for distributing web applications, games, and other content that can run entirely in the browser.

Key features include:

  • Service worker-based file serving
  • Blob URL mode for browsers without service worker support
  • Support for various archive formats (ZIP, CSJ)
  • Unity WebGL game support
  • Progressive Web App (PWA) support New
  • Offline HTML file generation New
  • IndexedDB file persistence

Installation

To use ClientServeJS, simply include the script in your HTML file:

<script src="clientserve.js"></script>

The library is designed to function both as the main library and as a service worker. It detects its execution context and behaves accordingly.

Basic Usage

Loading and Serving Files

// Initialize ClientServe
const clientServe = new ClientServe({
  path: '/app',
  debug: true
});

// Load files from a URL
clientServe.loadFromUrl('https://example.com/myapp.csj', 'csj')
  .then(() => {
    // Launch the application
    clientServe.launch('index.html');
  })
  .catch(error => {
    console.error('Error loading files:', error);
  });

Creating a CSJ Archive

CSJ (ClientServeJS) is a custom archive format optimized for web applications. You can create a CSJ file programmatically:

// Create a CSJ archive
const files = {
  'index.html': {
    content: '<html><body>Hello World</body></html>',
    isBinary: false,
    type: 'text/html'
  },
  'image.png': {
    content: imageUint8Array,
    isBinary: true,
    type: 'image/png'
  }
};

const metadata = {
  title: 'My App',
  author: 'Your Name'
};

// Create and download the CSJ file
CSJFormat.createCSJ(files, metadata, 'mypassword')
  .then(csjData => {
    const blob = new Blob([csjData], { type: 'application/x-clientserve-archive' });
    const url = URL.createObjectURL(blob);
    
    const a = document.createElement('a');
    a.href = url;
    a.download = 'myapp.csj';
    a.click();
  });

Options

ClientServeJS supports various options to customize its behavior:

Option Type Default Description
path string '/app' The virtual path where files will be served from
showLoadingBar boolean true Whether to show a loading bar during file loading
launchMethod string 'same-tab' How to launch the application ('same-tab', 'new-tab', 'popup')
debug boolean false Enable debug logging
fallbackMode boolean true Whether to fall back to blob URLs if service workers are not supported
blobMode boolean false Force using blob URLs instead of service workers
unitySupport boolean true Enable Unity WebGL game support
persistFiles boolean false Store files in IndexedDB for persistence
csjPassword string 'clientserve' Default password for CSJ encryption/decryption
enablePWA boolean true Enable Progressive Web App support New
offlineSupport boolean true Enable offline support New
cacheFiles boolean true Cache files for offline access New

PWA Support New

ClientServeJS now supports Progressive Web Apps (PWAs), allowing your applications to be installed on devices and work offline.

Requirements

  • A valid manifest.json file in your application
  • Service worker mode enabled (not blob mode)
  • HTTPS connection (required for service workers)

Manifest.json Example

{
  "name": "My PWA App",
  "short_name": "MyApp",
  "description": "A Progressive Web App example",
  "start_url": "/app/index.html",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#4CAF50",
  "icons": [
    {
      "src": "icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Enabling PWA Support

const clientServe = new ClientServe({
  path: '/app',
  enablePWA: true,
  offlineSupport: true,
  cacheFiles: true
});
Note: When a manifest.json file is detected in your application, ClientServeJS will automatically set up the necessary links and meta tags for PWA support.

Offline Support New

ClientServeJS now provides two ways to enable offline support:

1. Service Worker Caching

When using service worker mode with cacheFiles: true, files are automatically cached for offline access.

2. Standalone Offline HTML Files

You can generate standalone HTML files that work without a server using the new Offline Creator tool or the API:

// Generate an offline HTML file
const options = {
  title: 'My Offline App',
  startFile: 'index.html',
  remoteServiceWorkerUrl: 'https://example.com/clientserve.js',
  password: 'mypassword',
  customCss: '.my-custom-style { color: red; }',
  customJs: 'console.log("Custom script loaded");'
};

clientServe.createOfflineHtml(options)
  .then(blob => {
    // Download the offline HTML file
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'my-offline-app.html';
    a.click();
  });

Using the Offline Creator Tool

The Offline Creator tool (offline-creator.html) provides a user-friendly interface for creating offline HTML files from CSJ archives.

Important: Offline HTML files using service workers require a HTTPS connection to function correctly, as service workers cannot work with file:// URLs. The offline HTML file will connect to the specified remote service worker URL to enable full functionality.

Unity Support

ClientServeJS provides special support for Unity WebGL games, handling the unique requirements of Unity's file structure and loading process.

Unity Blob Mode (Beta)

The new Unity Blob Mode allows Unity games to run in blob URL mode without requiring a service worker:

const clientServe = new ClientServe({
  path: '/app',
  blobMode: true,
  unityBlobMode: true
});
Note: Unity Blob Mode is still in beta and may not work with all Unity games. If you encounter issues, you can enable unityBlobFallback: true to automatically switch to service worker mode when Unity files are detected.

API Reference

ClientServe Class

The main class for loading and serving files.

Methods

  • loadFromUrl(url, type) - Load files from a URL (type can be 'zip', 'csj', etc.)
  • launch(startFile) - Launch the application with the specified start file
  • loadFromStorage(path) - Load files from IndexedDB storage
  • deleteFromStorage(path) - Delete files from IndexedDB storage
  • listStoredPaths() - List all stored paths in IndexedDB
  • createOfflineHtml(options) - Create a standalone offline HTML file New
  • dispose() - Clean up resources

CSJFormat Class

Utilities for working with CSJ archives.

Methods

  • createCSJ(files, metadata, password) - Create a CSJ archive
  • decrypt(data, password) - Decrypt a CSJ archive
  • encrypt(data, password) - Encrypt data using the CSJ format
  • toBase64(data) - Convert CSJ data to Base64 New
  • fromBase64(base64) - Convert Base64 to CSJ data New

Examples

Basic Example

<!DOCTYPE html>
<html>
<head>
  <title>ClientServeJS Example</title>
  <script src="clientserve.js"></script>
</head>
<body>
  <h1>ClientServeJS Example</h1>
  <button id="load-button">Load Application</button>

  <script>
    document.getElementById('load-button').addEventListener('click', function() {
      const clientServe = new ClientServe({
        path: '/app',
        debug: true,
        showLoadingBar: true
      });

      clientServe.loadFromUrl('app.csj', 'csj')
        .then(() => {
          clientServe.launch('index.html');
        })
        .catch(error => {
          console.error('Error:', error);
        });
    });
  </script>
</body>
</html>

PWA Example New

<!DOCTYPE html>
<html>
<head>
  <title>ClientServeJS PWA Example</title>
  <script src="clientserve.js"></script>
</head>
<body>
  <h1>ClientServeJS PWA Example</h1>
  <button id="load-button">Load PWA</button>

  <script>
    document.getElementById('load-button').addEventListener('click', function() {
      const clientServe = new ClientServe({
        path: '/app',
        debug: true,
        enablePWA: true,
        offlineSupport: true,
        cacheFiles: true
      });

      clientServe.loadFromUrl('pwa-app.csj', 'csj')
        .then(() => {
          clientServe.launch('index.html');
        })
        .catch(error => {
          console.error('Error:', error);
        });
    });
  </script>
</body>
</html>

Offline HTML Generation Example New

<!DOCTYPE html>
<html>
<head>
  <title>ClientServeJS Offline Generator Example</title>
  <script src="clientserve.js"></script>
</head>
<body>
  <h1>Generate Offline HTML</h1>
  <input type="file" id="csj-file" accept=".csj">
  <button id="generate-button" disabled>Generate Offline HTML</button>

  <script>
    const fileInput = document.getElementById('csj-file');
    const generateButton = document.getElementById('generate-button');
    let csjFile = null;

    fileInput.addEventListener('change', function(event) {
      if (event.target.files.length > 0) {
        csjFile = event.target.files[0];
        generateButton.disabled = false;
      }
    });

    generateButton.addEventListener('click', async function() {
      if (!csjFile) return;

      try {
        // Read the CSJ file
        const buffer = await csjFile.arrayBuffer();
        
        // Create a ClientServe instance
        const clientServe = new ClientServe({
          debug: true,
          blobMode: true
        });
        
        // Extract the CSJ file
        const csjData = await CSJFormat.decrypt(new Uint8Array(buffer), 'clientserve');
        clientServe.files = csjData.files;
        
        // Generate the offline HTML file
        const options = {
          title: 'My Offline App',
          startFile: 'index.html',
          remoteServiceWorkerUrl: window.location.origin + '/clientserve.js'
        };
        
        const offlineBlob = await clientServe.createOfflineHtml(options);
        
        // Download the offline HTML file
        const url = URL.createObjectURL(offlineBlob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'offline-app.html';
        a.click();
      } catch (error) {
        console.error('Error:', error);
        alert('Error: ' + error.message);
      }
    });
  </script>
</body>
</html>