typescript/AGENTS
Sunday 15 March 2026

AGENTS.md - TypeScript Implementation Guide

This document provides guidance for AI agents working on the Mau TypeScript implementation.

Project Structure

typescript/
├── src/                    # Source code
│   ├── account.ts         # Account/identity management
│   ├── client.ts          # HTTP client for peer sync
│   ├── server.ts          # HTTP server for serving files
│   ├── file.ts            # File operations with encryption
│   ├── index.ts           # Public API exports
│   ├── crypto/            # PGP encryption/signing
│   ├── network/           # P2P networking (WebRTC, resolvers)
│   ├── storage/           # Storage backends (filesystem, IndexedDB)
│   └── types/             # TypeScript type definitions
├── examples/              # Usage examples
├── dist/                  # Compiled JavaScript (git-ignored)
├── coverage/              # Test coverage reports (git-ignored)
├── README.md              # User documentation
├── BROWSER.md             # Browser testing guide
└── package.json           # Dependencies and scripts

Test files: `*.test.ts` alongside source files

Core Principles

1. Browser-Only Architecture

  • Designed exclusively for modern browsers
  • Uses IndexedDB for storage (via BrowserStorage)
  • WebRTC for peer-to-peer communication
  • Tests run in Node.js with polyfills (fake-indexeddb, @roamhq/wrtc)

2. Zero Native Dependencies

  • Pure JavaScript/TypeScript only
  • No C/C++ bindings, no native addons
  • Polyfills used only for testing (not in production builds)

3. Type Safety

  • All public APIs must have TypeScript definitions
  • Use strict mode ("strict": true in tsconfig.json)
  • Export types from src/types/index.ts

4. Test Coverage

  • Target: >50% branch coverage (current: ~41%)
  • Every public method should have at least one test
  • Integration tests for critical paths (sync, encryption)
  • Focus on high-impact areas: account.ts (40%), client.ts (47%), server.ts (48%)
  • Low-coverage areas need attention: dht.ts (13%), signaling.ts (20%), webrtc-server.ts (15%)

5. Security First

  • All files are PGP-encrypted before storage
  • Signatures verified on read
  • PGP-based authentication for WebRTC connections
  • No plaintext secrets in memory longer than necessary

Development Workflow

Building

1npm run build          # TypeScript → dist/
2npm run build:browser  # Vite bundle for browsers

Testing

1npm test                      # Run all tests with coverage
2npm test -- --watch           # Watch mode
3npm test src/file.test.ts     # Run specific test file
4npm test -- --no-coverage     # Faster runs without coverage

Linting

1npm run lint                  # ESLint check
2npm run format                # Prettier format

Documentation

1npm run docs                  # Generate HTML docs → docs/
2npm run docs:serve            # Generate + serve at http://localhost:8000

Manual Testing

1# Browser testing
2npm run dev                   # Start dev server at http://localhost:5173
3npm run preview               # Preview production build
4
5# Node.js testing
6node test-integration.mjs
7
8# Automated browser testing (Playwright)
9npx playwright test

Test Files:

  • *.test.ts - Jest unit/integration tests (run in Node.js with polyfills)
  • Test environment uses fake-indexeddb and @roamhq/wrtc to simulate browser APIs

Key Implementation Details

Project Architecture

The codebase is organized into these key modules:

  • Core: account.ts, file.ts, client.ts, server.ts, index.ts
  • Cryptography: crypto/pgp.ts, crypto/index.ts - OpenPGP operations
  • Networking: network/webrtc.ts, network/webrtc-server.ts, network/resolvers.ts, network/dht.ts, network/signaling.ts
  • Storage: storage/browser.ts, storage/index.ts (filesystem storage removed)
  • Types: types/index.ts - TypeScript type definitions

Storage Abstraction

 1interface Storage {
 2  exists(path: string): Promise<boolean>;
 3  readFile(path: string): Promise<Uint8Array>;
 4  writeFile(path: string, data: Uint8Array): Promise<void>;
 5  readText(path: string): Promise<string>;
 6  writeText(path: string, text: string): Promise<void>;
 7  readDir(path: string): Promise<string[]>;
 8  mkdir(path: string): Promise<void>;
 9  remove(path: string): Promise<void>;
10  stat(path: string): Promise<{ size: number; isDirectory: boolean; modifiedTime?: number }>;
11  join(...parts: string[]): string;
12}

Implementation:

  • BrowserStorage: Uses IndexedDB for persistent storage in browsers

Peer Discovery Resolvers

1type FingerprintResolver = (
2  fingerprint: Fingerprint,
3  timeout?: number
4) => Promise<string | null>;

Available resolvers:

  • staticResolver: Hardcoded address map
  • dhtResolver: Kademlia DHT via /kad/find_peer endpoint (HTTP-based)
  • combinedResolver: Try multiple resolvers in parallel

Note: DNS and mDNS resolvers were removed as they require Node.js-specific UDP socket access.

File Encryption Flow

Write:
  data → sign (with private key) → encrypt (to public keys) → PGP armor → storage

Read:
  storage → PGP armor → decrypt (with private key) → verify (with public keys) → data

WebRTC Architecture

  • WebRTCClient (network/webrtc.ts): Initiates connections, creates offers
  • WebRTCServer (network/webrtc-server.ts): Accepts connections, handles answers
  • Signaling (network/signaling.ts): Coordinates peer connection establishment
  • mTLS: PGP-based challenge-response authentication after data channel opens
  • HTTP-over-datachannel: Text-based HTTP/1.1 protocol for file synchronization

Note: WebRTC implementation uses native browser APIs. In Node.js tests, @roamhq/wrtc provides polyfill.

Common Patterns

Creating Storage-aware Objects

1// Use factory methods that accept storage
2const account = await Account.create(storage, rootDir, options);
3const file = File.create(account, storage, 'filename.json');
4const client = Client.create(account, storage, peer);

Async/Await Everywhere

  • All I/O operations are async
  • Use Promise.all() for parallel operations
  • Use Promise.race() for timeouts

Error Handling

 1// Custom error classes with codes
 2throw new PeerNotFoundError();  // code: 'PEER_NOT_FOUND'
 3throw new InvalidFileNameError('reason');  // code: 'INVALID_FILE_NAME'
 4
 5// Catch specific errors
 6try {
 7  await client.sync();
 8} catch (err) {
 9  if (err instanceof PeerNotFoundError) {
10    // Handle peer not found
11  }
12}

Testing Patterns

Unit Tests

 1describe('FeatureName', () => {
 2  let storage: BrowserStorage;
 3  let account: Account;
 4
 5  beforeEach(async () => {
 6    storage = await BrowserStorage.create();
 7    account = await Account.create(storage, TEST_DIR, options);
 8  });
 9
10  afterEach(async () => {
11    try {
12      await storage.remove(TEST_DIR);
13    } catch {
14      // Ignore cleanup errors
15    }
16  });
17
18  it('should do something', async () => {
19    // Test implementation
20  });
21});

Integration Tests

  • Use BrowserStorage (backed by fake-indexeddb in tests)
  • Clean up in afterEach using storage.remove()
  • Test full workflows (create account → write file → sync → verify)

Mocking

1// Use Jest mocks sparingly
2const mockResolver = jest.fn().mockResolvedValue('peer:8080');

TypeScript Configuration

  • Target: ES2022 (modern JavaScript features)
  • Module System: ES2022 modules (.js extensions in imports)
  • Strict Mode: Enabled (all strict type checks)
  • Output: dist/ directory with declaration files (.d.ts)
  • Source Maps: Enabled for debugging

Important: All imports must use .js extensions even for .ts files:

1// ✅ Correct
2import { Account } from './account.js';
3
4// ❌ Wrong
5import { Account } from './account';

API Export Policy

Public API (Exported from src/index.ts)

Core Classes:

  • Account, Client, Server, File - Main user-facing classes
  • BrowserStorage, createStorage - Storage backend

Networking:

  • WebRTCClient, WebRTCServer - WebRTC P2P communication
  • LocalSignalingServer, WebSocketSignaling, HTTPSignaling, SignaledConnection - Signaling mechanisms
  • staticResolver, dhtResolver, combinedResolver, retryResolver - Peer discovery
  • KademliaDHT - Distributed hash table for peer discovery

Utilities:

  • validateFileName, normalizeFingerprint, formatFingerprint - Crypto utilities
  • createAccount, loadAccount - Convenience functions

Types & Errors:

  • All type definitions (interfaces, type aliases)
  • All error classes (MauError, PeerNotFoundError, etc.)

NOT Exported (Internal Constants):

  • MAU_DIR_NAME, ACCOUNT_KEY_FILENAME, SYNC_STATE_FILENAME - Internal file structure
  • FILE_PERM, DIR_PERM - Internal permissions
  • HTTP_TIMEOUT_MS, SERVER_RESULT_LIMIT - Configure via ClientConfig/ServerConfig instead
  • DHT_B, DHT_K, DHT_ALPHA, etc. - Internal DHT implementation details

Internal API (Not Exported)

Implementation Details:

  • PGP operations (generateKeyPair, signAndEncrypt, decryptAndVerify) - wrapped by Account class
  • Internal helper functions in crypto/pgp.ts
  • Low-level HTTP protocol handlers
  • Storage implementation internals

When Adding New Features

  1. Default to internal - Only export what users need
  2. Use explicit exports - Avoid export * wildcards in src/index.ts
  3. Document public APIs - Use JSDoc for exported symbols
  4. Mark internal symbols - Use JSDoc @internal tag or leading underscore for private methods

Example:

 1// src/my-feature.ts
 2
 3/**
 4 * @internal
 5 * Internal helper function - not exported from index.ts
 6 */
 7export function _internalHelper() { }
 8
 9/**
10 * Public API function - exported from index.ts
11 * @param data Input data to process
12 */
13export function publicFeature(data: string) {
14  return _internalHelper();
15}

Code Quality Guidelines

Naming Conventions

  • Classes: PascalCase (Account, File, WebRTCClient)
  • Functions/methods: camelCase (createAccount, writeJSON)
  • Constants: UPPER_SNAKE_CASE (SERVER_RESULT_LIMIT, DHT_K)
  • Private methods: prefix with _ (underscore) or use TypeScript private

File Organization

 1// 1. Imports (grouped: types, libraries, internal)
 2import type { Storage } from './types/index.js';
 3import * as fs from 'fs/promises';
 4import { Account } from './account.js';
 5
 6// 2. Type definitions
 7interface InternalType {
 8  // ...
 9}
10
11// 3. Constants
12const DEFAULT_TIMEOUT = 30000;
13
14// 4. Main class/functions
15export class Thing {
16  // ...
17}
18
19// 5. Helper functions (private)
20function internalHelper() {
21  // ...
22}

Comments

  • Use JSDoc for public APIs
  • Inline comments for complex logic only
  • TODO comments must include issue number or context

Async Best Practices

 1// ✅ Good: Parallel operations
 2const [file1, file2] = await Promise.all([
 3  storage.readFile('file1.txt'),
 4  storage.readFile('file2.txt'),
 5]);
 6
 7// ❌ Bad: Sequential when parallel is possible
 8const file1 = await storage.readFile('file1.txt');
 9const file2 = await storage.readFile('file2.txt');
10
11// ✅ Good: Timeout handling
12await Promise.race([
13  operation(),
14  new Promise((_, reject) => 
15    setTimeout(() => reject(new Error('Timeout')), 5000)
16  ),
17]);

Dependencies

Production (Browser)

  • openpgp: PGP encryption/signing (RFC 4880)
  • @peculiar/x509: X.509 certificate handling for WebRTC authentication
  • idb: IndexedDB wrapper for browser storage
  • p-retry: Robust retry mechanism for network operations
  • k-bucket: Kademlia routing table for DHT

Development

  • typescript: TypeScript compiler (v5.3.3)
  • jest: Test framework with ts-jest for TypeScript support
  • @roamhq/wrtc: WebRTC polyfill for Node.js testing
  • fake-indexeddb: IndexedDB mock for Node.js tests
  • vite: Browser bundler and dev server
  • typedoc: API documentation generator
  • eslint + prettier: Code quality and formatting

When Making Changes

Adding New Features

  1. Update type definitions in src/types/index.ts
  2. Implement in appropriate module
  3. Export from src/index.ts if public API
  4. Write tests (unit + integration if needed)
  5. Update 5/6 README.md with usage example
  6. Update BROWSER.md if browser-specific

Fixing Bugs

  1. Write a failing test that reproduces the bug
  2. Fix the bug
  3. Verify test passes
  4. Check for similar issues in related code

Refactoring

  1. Ensure tests pass before starting
  2. Make small, incremental changes
  3. Run tests after each change
  4. Avoid changing behavior and structure simultaneously

Performance Optimization

  1. Profile first (don’t guess)
  2. Document the optimization with benchmarks
  3. Avoid premature optimization
  4. Test that behavior hasn’t changed

Performance Considerations

Parallel Operations

Always parallelize independent async operations:

1// ✅ Fast: Parallel execution
2const [account, config] = await Promise.all([
3  loadAccount(dir, passphrase),
4  loadConfig(configPath),
5]);
6
7// ❌ Slow: Sequential execution
8const account = await loadAccount(dir, passphrase);
9const config = await loadConfig(configPath);  // Waits unnecessarily

Retry Strategies

Network operations use p-retry for resilience:

 1import pRetry from 'p-retry';
 2
 3await pRetry(
 4  async () => {
 5    const response = await fetch(url);
 6    if (!response.ok) throw new Error('HTTP error');
 7    return response;
 8  },
 9  { retries: 3, minTimeout: 1000 }
10);

Memory Management

  • Dispose of large buffers (Uint8Array) after processing
  • Close WebRTC connections explicitly to free resources
  • Clear PGP keys from memory after use when possible

Common Gotchas

Storage Usage

1// ✅ Correct: Always use BrowserStorage
2const storage = await createStorage();  // Creates BrowserStorage with IndexedDB
3
4// ✅ Or create directly
5const storage = await BrowserStorage.create();

Browser-Only Architecture:

This package is designed exclusively for browsers:

1// ✅ Available: Browser-compatible peer discovery
2const staticRes = staticResolver(knownPeers);
3const dhtRes = dhtResolver(['bootstrap1:443']);  // Uses fetch()
4
5// ✅ Combined resolver works in browsers
6const resolver = combinedResolver([
7  staticResolver(knownPeers),
8  dhtResolver(['bootstrap1:443']),
9]);

Why this is browser-only:

  • Uses IndexedDB for storage (not filesystem)
  • WebRTC for P2P communication
  • No Node.js-specific APIs (fs, http, net, dgram)
  • Tests use polyfills to simulate browser environment

Async Constructor

 1// ❌ Wrong: Async constructor
 2class Thing {
 3  constructor() {
 4    this.init();  // Can't await in constructor
 5  }
 6}
 7
 8// ✅ Right: Factory method
 9class Thing {
10  private constructor() {}
11  
12  static async create(): Promise<Thing> {
13    const instance = new Thing();
14    await instance.init();
15    return instance;
16  }
17}

PGP Key Handling

1// ❌ Wrong: Storing unencrypted private keys
2localStorage.setItem('privateKey', privateKeyArmor);
3
4// ✅ Right: Always encrypted with passphrase
5await account.save(passphrase);  // Encrypts before storage

File Paths

1// ❌ Wrong: Hardcoded separators
2const path = rootDir + '/' + filename;
3
4// ✅ Right: Use storage.join()
5const path = storage.join(rootDir, filename);

WebRTC Cleanup

 1// ❌ Wrong: Forget to close connections
 2const client = new WebRTCClient(...);
 3await client.connect();
 4// ... use client ...
 5
 6// ✅ Right: Always close
 7try {
 8  const client = new WebRTCClient(...);
 9  await client.connect();
10  // ... use client ...
11} finally {
12  client.close();
13}

Debugging Tips

Enable Debug Logging

1// Set environment variable
2DEBUG=mau:* npm test
3
4// Or in code
5localStorage.setItem('debug', 'mau:*');  // Browser
6process.env.DEBUG = 'mau:*';             // Node.js

Inspect PGP Operations

1// Verify signatures are working
2const file = File.create(account, storage, 'test.json');
3await file.writeJSON({ test: true });
4const data = await file.read();  // Should not throw
5
6// Check signature manually
7const publicKeys = account.getAllPublicKeys();
8const { verified } = await decryptAndVerify(armor, privateKey, publicKeys);
9console.log('Signature verified:', verified);

WebRTC Connection Issues

1// Log ICE candidates
2client.on('icecandidate', (candidate) => {
3  console.log('ICE candidate:', candidate);
4});
5
6// Check connection state
7console.log('Connection state:', client.connectionState);
8console.log('Data channel state:', client.dataChannelState);

Storage Inspection

1// Browser: Check IndexedDB
2// Open DevTools → Application → IndexedDB → mau-storage → files
3
4// Node.js: Check filesystem
5console.log(await storage.readDir(rootDir));

Troubleshooting

Common Issues

“Cannot find module” errors:

  • Ensure all imports use .js extensions (ES modules requirement)
  • Check that type: "module" is set in package.json

WebRTC connection failures:

  • Verify signaling server is reachable
  • Check firewall/NAT configuration for peer-to-peer connectivity
  • Enable debug logging: DEBUG=mau:* npm test

Test timeouts:

  • Increase Jest timeout: jest.setTimeout(30000)
  • Check for unclosed connections (WebRTC, HTTP servers)
  • Use --detectOpenHandles to find leaks

Browser IndexedDB errors:

  • Clear browser storage: DevTools → Application → Clear Storage
  • Check browser compatibility (IndexedDB API required)
  • Verify HTTPS context (required for some browser features)

Release Checklist

Before submitting PR:

  • All tests pass (npm test)
  • No linting errors (npm run lint)
  • Code formatted (npm run format)
  • Coverage maintained or improved (target: >50% branches)
  • 5/6 README.md updated with new features
  • Examples work in both Node.js and browser
  • TypeScript compiles without errors (npm run build)
  • Bundle size is reasonable (npm run build:browser)
  • API documentation updated (npm run docs)
  • No console warnings in browser tests

Security Considerations

PGP Key Management

  • Private keys are always encrypted at rest with a passphrase
  • Never log or transmit unencrypted private keys
  • Use secure random sources for key generation (crypto.getRandomValues)

Network Security

  • WebRTC uses PGP-based challenge-response authentication (not traditional X.509 mTLS)
  • HTTP client does not yet implement certificate verification (use WebRTC for authenticated connections)
  • All file contents are encrypted with OpenPGP before storage

Input Validation

  • Filename sanitization prevents directory traversal attacks
  • PGP signature verification prevents tampered content
  • Fingerprint validation ensures peer identity

CI/CD Integration

GitHub Actions workflows handle:

  • Tests: Automated test suite with coverage reporting
  • Linting: ESLint checks on every push
  • Browser Tests: Playwright-based browser automation
  • Documentation: Auto-generated API docs with TypeDoc

Local CI simulation:

1npm run lint && npm test && npm run build && npm run build:browser

Resources

  • Mau Specification: ../docs/ directory
  • Go Implementation: ../ (reference implementation)
  • OpenPGP Spec: RFC 4880
  • WebRTC Spec: W3C WebRTC 1.0
  • Kademlia Paper: Original DHT paper by Maymounkov & Mazières
  • TypeDoc: https://typedoc.org
  • Jest: https://jestjs.io
  • Vite: https://vitejs.dev

Getting Help

  • Issues: Check existing issues on GitHub
  • Discussions: Use GitHub Discussions for questions
  • Code Review: Tag @emad-elsaid for review
  • Testing: Run npm test -- --verbose for detailed output
  • Debug Mode: Set DEBUG=mau:* environment variable

Remember: This implementation must work in both browser and Node.js. Test both environments before submitting changes.

Quick Reference

Task Command
Install dependencies npm install
Run tests npm test
Run tests (watch) npm test -- --watch
Check coverage npm test -- --coverage
Build for Node.js npm run build
Build for browser npm run build:browser
Start dev server npm run dev
Lint code npm run lint
Format code npm run format
Generate docs npm run docs
Serve docs npm run docs:serve