Skip to main content
LiveCodes is built on a modular service architecture that separates concerns and enables flexible deployment options. This guide explains each service and how they interact.

Service Overview

LiveCodes consists of several core services:
┌─────────────────────────────────────────────────────┐
│                  LiveCodes App                      │
│                                                     │
│  ┌──────────┐  ┌──────────┐  ┌───────────────┐   │
│  │ Sandbox  │  │ Modules  │  │    GitHub     │   │
│  │ Service  │  │ Service  │  │    Service    │   │
│  └──────────┘  └──────────┘  └───────────────┘   │
│                                                     │
│  ┌──────────┐  ┌──────────┐  ┌───────────────┐   │
│  │  Share   │  │Broadcast │  │   Firebase    │   │
│  │ Service  │  │ Service  │  │    Service    │   │
│  └──────────┘  └──────────┘  └───────────────┘   │
│                                                     │
│  ┌──────────┐  ┌──────────┐                       │
│  │   CORS   │  │  Allowed │                       │
│  │  Proxy   │  │  Origins │                       │
│  └──────────┘  └──────────┘                       │
└─────────────────────────────────────────────────────┘

Sandbox Service

The sandbox service provides secure, isolated execution of user code.

Purpose

Executes untrusted user code in a separate origin to prevent:
  • Access to parent application cookies and storage
  • Cross-site scripting (XSS) attacks
  • Malicious code affecting the main application

Implementation

Location: src/livecodes/services/sandbox.ts
const cfPagesBaseUrl = 'https://livecodes-sandbox.pages.dev';
const ghPagesBaseUrl = 'https://live-codes.github.io/livecodes-sandbox/dist';
const selfHostedBaseUrl = `https://${process.env.SANDBOX_HOST_NAME}:${process.env.SANDBOX_PORT}`;
const localBaseUrl = 'http://127.0.0.1:8085';

const serviceBaseUrl =
  location.hostname === 'localhost' || location.hostname === '127.0.0.1'
    ? localBaseUrl
    : process.env.SELF_HOSTED === 'true'
      ? selfHostedBaseUrl
      : process.env.CI === 'true'
        ? ghPagesBaseUrl
        : cfPagesBaseUrl;

export const sandboxService = {
  getResultUrl: () => `${serviceBaseUrl}/${version}/`,
  getCompilerUrl: () => `${serviceBaseUrl}/${version}/compiler${ext}`,
  getOrigin: () => new URL(serviceBaseUrl).origin,
};

Server Implementation

Location: server/src/sandbox.ts
export const sandbox = async ({ hostname, port }) => {
  const app = express();
  
  app.use(cors());
  app.disable('x-powered-by');
  
  // Dynamically determine sandbox version
  const version = dirs
    .filter((v) => v.startsWith('v'))
    .map((v) => Number(v.slice(1)))
    .sort((a, b) => a - b)
    .map((v) => 'v' + v)
    .pop() || '';
  
  // Serve sandbox HTML files
  app.use('/', (req, res) => {
    res.set('Content-Type', 'text/html');
    res.status(200).sendFile(filePath);
  });
  
  app.listen(port);
};

Sandbox Versioning

Sandbox files are versioned (e.g., v9/) to allow:
  • Cache-busting on updates
  • Parallel version support
  • Rollback capability
The sandbox must be hosted on a different origin (scheme + domain + port) than the main app for security isolation.

Modules Service

Resolves and loads external modules from various CDNs.

Supported CDN Providers

Module CDNs (ESM):
  • esm.sh (default)
  • skypack
  • jspm
  • jsdelivr.esm
  • esm.run
Package CDNs (NPM):
  • jsdelivr (default)
  • unpkg
  • npmcdn
GitHub CDNs:
  • jsdelivr.gh
  • statically

Module Resolution

Location: src/livecodes/services/modules.ts
export const modulesService = {
  getModuleUrl: (moduleName, { isModule = true, defaultCDN = 'esm.sh' }) => {
    // Remove bundling hints
    moduleName = moduleName.replace(/#nobundle/g, '');
    
    // Get CDN URL based on prefix
    const moduleUrl = getCdnUrl(moduleName, isModule, defaultCDN);
    
    return isModule
      ? 'https://esm.sh/' + moduleName
      : 'https://cdn.jsdelivr.net/npm/' + moduleName;
  },
};

URL Transformation Patterns

The service transforms various input formats:
// npm: packages
'npm:react''https://esm.sh/react'

// GitHub repos
'github:user/repo/file.js''https://deno.bundlejs.com/?file&q=https://cdn.jsdelivr.net/gh/user/[email protected]'

// JSR packages
'jsr:@scope/package''https://esm.sh/jsr/@scope/package'

// Deno modules
'deno:module/mod.ts''https://deno.bundlejs.com/?file&q=https://deno.land/x/module/mod.ts'

// Direct URLs
'https://example.com/file.js' → (unchanged)

CDN Health Checking

checkCDNs: async (testModule, preferredCDN) => {
  const cdns = [preferredCDN, ...modulesService.cdnLists.npm];
  for (const cdn of cdns) {
    try {
      const res = await fetch(modulesService.getUrl(testModule, cdn), {
        method: 'HEAD',
      });
      if (res.ok) return cdn;
    } catch {
      // Try next CDN
    }
  }
  return modulesService.cdnLists.npm[0]; // Fallback
}

Share Service

Enables saving and loading projects via short URLs.

Architecture

Storage: Valkey/Redis key-value store
ID Length: 14 characters (reduces collision probability)

Implementation

Location: server/src/share.ts
const valkey = new Valkey(
  Number(process.env.VALKEY_PORT || 6379),
  process.env.VALKEY_HOST || 'valkey'
);

const saveProject = async (req, res) => {
  const value = JSON.stringify(req.body);
  
  // Generate unique 14-character ID
  let id = generateId(14);
  
  // Avoid collision
  while (await valkey.get(id)) {
    id = generateId(14);
  }
  
  await valkey.set(id, value);
  res.status(200).send(id);
};

const getProject = async (req, res) => {
  const id = req.query.id;
  const value = await valkey.get(String(id).toLowerCase());
  
  if (!value) {
    res.status(404).send('Not Found!');
    return;
  }
  
  res.status(200).json(JSON.parse(value));
};

Data Persistence

Configured in docker-compose.yml:
valkey:
  command:
    - valkey-server
    - --save 60 1  # Save if ≥1 change in 60 seconds
    - --loglevel warning
  volumes:
    - valkey-data:/data

ID Comparison

ServiceID LengthExample
dpaste9 charsxY3kL9pQm
API11 charsxY3kL9pQmR2
Self-hosted14 charsxY3kL9pQmR2v5A
Longer IDs reduce collision probability: 14 chars = 62^14 ≈ 1.1 × 10^25 combinations

Broadcast Service

Enables real-time collaborative coding and live broadcasts.

Features

  • Real-time code streaming
  • WebSocket-based communication
  • Channel-based isolation
  • Optional token authentication
  • Automatic channel cleanup (20-minute timeout)

Implementation

Location: server/src/broadcast/index.ts
export const broadcast = ({ hostname, port, appUrl, userTokens }) => {
  const app = express();
  const httpserver = http.createServer(app);
  const io = new socketio.Server(httpserver);
  
  const channels = {}; // In-memory channel storage
  
  // Create/update broadcast
  app.post('/', (req, res) => {
    const channel = req.body.channel || generateId();
    const channelToken = channels[channel]?.channelToken || generateId();
    
    // Emit to all subscribers
    io.in(channel).emit('receive', result, data);
    
    // Store reduced data (limit sizes)
    channels[channel] = {
      channelToken,
      result: result.length < 300000 ? result : '',
      data: JSON.stringify(data).length < 500000 ? data : {},
      lastAccessed: Date.now(),
    };
    
    res.json({ channel, channelUrl: `${broadcastUrl}/channels/${channel}` });
  });
  
  // WebSocket connections
  io.on('connection', (socket) => {
    socket.on('join', (channel) => {
      if (!channels[channel]) return;
      socket.join(channel);
      const { result, data } = channels[channel];
      socket.emit('receive', result, data);
    });
  });
};

Authentication

const hasPermission = (req) => {
  if (!userTokens.trim()) return true; // No auth required
  
  const userToken = req.body.userToken || req.query.userToken;
  const validTokens = userTokens.split(',').map(t => t.trim());
  
  return validTokens.includes(userToken);
};

Channel Lifecycle

// Automatic cleanup on response finish
res.on('finish', () => {
  Object.keys(channels).forEach((key) => {
    const timeout = 1000 * 60 * 20; // 20 minutes
    if (Date.now() - channels[key].lastAccessed > timeout) {
      delete channels[key];
    }
  });
});

Data Size Limits

  • Result HTML: 300KB max
  • Project data: 500KB max (JSON stringified)
  • Compiled code: Stripped from stored data

GitHub Service

Integrates with GitHub API for repository operations.

Features

  • Create/update repositories
  • Commit files (single or batch)
  • Read repository contents
  • Deploy to GitHub Pages
  • List user repositories

Authentication

Location: src/livecodes/services/github.ts
export const getGithubHeaders = (user, mediaType) => ({
  Accept: `application/vnd.github.v3${mediaType ? '.' + mediaType : ''}+json`,
  'Content-Type': 'application/json',
  Authorization: 'token ' + user.token,
});

Batch Commits

Uses Git Tree API for atomic multi-file commits:
export const commitFiles = async ({
  files, user, repo, branch, message
}) => {
  // 1. Get last commit SHA
  const lastCommit = await getLastCommit(user, repo, branch);
  
  // 2. Get current tree
  const baseTree = await getTree(user, repo, lastCommit);
  
  // 3. Create new tree with all files
  const tree = await createTree(user, repo, files, baseTree);
  
  // 4. Create commit pointing to new tree
  const commit = await createCommit(user, repo, message, tree, lastCommit);
  
  // 5. Update branch to point to new commit
  await updateBranch(user, repo, branch, commit);
  
  return { tree, commit };
};

Content API Size Limits

GitHub limits file content to 1MB via the Contents API:
// For files >1MB, use raw API
if (result.content === '' && result.encoding === 'none') {
  const rawRes = await fetch(url, {
    headers: getGithubHeaders(user, 'raw'),
  });
  result.content = encode(await rawRes.text());
  result.encoding = 'base64';
}

Firebase Service

Handles user authentication via GitHub OAuth.

Configuration

Location: src/livecodes/services/firebase.ts
export const firebaseConfig = selfHostedConfig || {
  apiKey: 'AIzaSyB352dJ_NKCZ43G5kv9Lt-sb5nMXTJRONQ',
  authDomain: 'livecodes-io.firebaseapp.com',
  projectId: 'livecodes-io',
  // ...
};

Self-Hosted Override

Provide custom Firebase config via environment variable:
FIREBASE_CONFIG='{"apiKey":"...","authDomain":"..."}'

CORS Proxy Service

Proxies external requests to avoid CORS restrictions.

Security

Location: server/src/cors.ts
export const corsProxy = (req, res) => {
  const origin = req.get('Origin');
  const hostname = process.env.HOST_NAME || 'localhost';
  
  // Only allow requests from configured hostname
  if ((origin && !origin.includes(hostname)) || 
      !['GET', 'POST'].includes(req.method)) {
    res.status(403).send('Forbidden!');
    return;
  }
  
  const url = req.body?.url || req.query?.url;
  
  fetch(url, { method: req.method, ... })
    .then(r => res.send(body))
    .catch(err => res.send(err));
};

Content Type Handling

const contentType = r.headers.get('Content-Type') || '';

if (contentType === 'application/json') {
  return r.json();
}
if (['application/zip', 'application/octet-stream'].includes(contentType) ||
    contentType.startsWith('image/')) {
  return r.arrayBuffer();
}
return r.text();

Allowed Origins Service

Validates origins for security-sensitive operations. Location: src/livecodes/services/allowed-origin.ts
export const allowedOrigin = (origin = location.origin) =>
  Boolean(
    origin &&
      (origin.endsWith('livecodes.io') ||
       origin.endsWith('livecodes.pages.dev') ||
       origin.includes('127.0.0.1') ||
       origin.includes('localhost:') ||
       origin.endsWith('localhost') ||
       origin.endsWith('.test')),
  );

Whitelist Targets

Allows specific external domains:
export const whitelistTarget = (url) =>
  /^(?:(?:http|https):\/\/(?:\w+.)?)(githubusercontent.com|jsbin.com)\/(.*)/g
    .test(url);

Service Communication

Message Passing

Services communicate via postMessage for sandbox isolation:
// App → Sandbox
const message = {
  type: 'compile',
  payload: { content, language, config },
};
compilerSandbox.postMessage(message, sandboxOrigin);

// Sandbox → App
window.addEventListener('message', (event) => {
  if (event.origin === sandboxOrigin && 
      event.data.type === 'compiled') {
    resolve(event.data.payload.compiled);
  }
});

HTTP Endpoints

EndpointMethodPurpose
/api/shareGETRetrieve shared project
/api/sharePOSTSave new project
/api/corsGET/POSTCORS proxy
/oembedGEToEmbed metadata
/GETServe main app

Next Steps

Security Model

Learn about security implementation

Custom Compilers

Create custom language compilers

Performance

Optimize service performance

Docker Setup

Configure services in Docker

Build docs developers (and LLMs) love