typescript/README
Sunday 15 March 2026

Mau TypeScript Implementation

TypeScript/JavaScript implementation of the Mau P2P social network protocol.

Browser-first P2P social networking with IndexedDB storage and WebRTC communication.

Features

  • Browser-Native: Built for modern browsers with IndexedDB storage
  • WebRTC P2P: Native browser-to-browser communication over data channels
  • PGP Authentication: Challenge-response authentication over WebRTC
  • OpenPGP: Ed25519 and RSA key generation, signing, and encryption
  • P2P Sync: HTTP-style protocol over WebRTC data channels
  • Versioning: Automatic content versioning with SHA-256 checksums
  • Type-safe: Full TypeScript definitions
  • Zero native dependencies: Pure JavaScript, runs in any modern browser

Security Note

⚠️ HTTP Client: The HTTP client does not yet implement mTLS authentication. For authenticated P2P connections, use the WebRTC client which implements PGP-based challenge-response authentication.

Architecture

This package is designed for browser environments:

  • Storage: IndexedDB for encrypted file storage
  • Communication: WebRTC data channels for P2P file synchronization
  • Peer Discovery: Static resolver or DHT resolver (HTTP-based)
  • Authentication: PGP-based challenge-response over WebRTC

Tests run in Node.js using polyfills (fake-indexeddb, @roamhq/wrtc) but the production code is browser-only.

Installation

1npm install @mau-network/mau

Quick Start

Browser Example

 1import { createAccount, File } from '@mau-network/mau';
 2
 3// Create account (uses IndexedDB automatically)
 4const account = await createAccount(
 5  'mau-data',         // Root path in IndexedDB
 6  'Alice',
 7  'alice@mau.network',
 8  'strong-passphrase'
 9);
10
11console.log('Account fingerprint:', account.getFingerprint());
12console.log('Public key:', account.getPublicKey());
13
14// Write a post
15const file = await account.createFile('hello.json');
16await file.writeJSON({
17  '@type': 'SocialMediaPosting',
18  headline: 'Hello, Mau!',
19  articleBody: 'My first post on Mau',
20  datePublished: new Date().toISOString(),
21});
22
23// List all files
24const files = await account.listFiles();
25for (const f of files) {
26  console.log('File:', f.getName());
27}
28
29// Add a friend
30const friendPublicKey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n...';
31const friendFingerprint = await account.addFriend(friendPublicKey);
32
33console.log('Friend added:', friendFingerprint);

});


### Browser P2P with WebRTC

```typescript
import { createAccount, WebRTCServer, WebRTCClient, WebSocketSignaling } from '@mau-network/mau';

// ============== Peer A: Start Server ==============
const aliceAccount = await createAccount('mau-alice', 'Alice', 'alice@mau.network', 'secret');
const aliceFpr = aliceAccount.getFingerprint();

// Start WebRTC server (accepts incoming connections)
const server = new WebRTCServer(aliceAccount, aliceAccount.storage);

// Connect to signaling server
const signaling = new WebSocketSignaling('wss://signaling.mau.network', aliceFpr);

// Handle incoming connection offers
signaling.onMessage(async (message) => {
  if (message.type === 'offer') {
    const connectionId = `conn-${Date.now()}`;
    const answer = await server.acceptConnection(connectionId, message.data);
    
    // Send answer back through signaling
    await signaling.send({
      from: aliceFpr,
      to: message.from,
      type: 'answer',
      data: answer
    });
  }
});

console.log('Alice is ready:', aliceFpr);

// ============== Peer B: Connect to Alice ==============
const bobAccount = await createAccount('mau-bob', 'Bob', 'bob@mau.network', 'secret');
const bobFpr = bobAccount.getFingerprint();

// Follow Alice
await bobAccount.addFriend(aliceAccount.getPublicKey());

// Create WebRTC client
const client = new WebRTCClient(bobAccount, bobAccount.storage, aliceFpr);

// Create offer and send via signaling
const offer = await client.createOffer();
await signaling.send({
  from: bobFpr,
  to: aliceFpr,
  type: 'offer',
  data: offer
});

// Wait for answer
signaling.onMessage(async (message) => {
  if (message.type === 'answer' && message.from === aliceFpr) {
    await client.completeConnection(message.data);
    
    // Perform mTLS handshake
    const authenticated = await client.performMTLS();
    if (!authenticated) throw new Error('Authentication failed');
    
    // Now fetch files over WebRTC!
    const fileList = await client.fetchFileList();
    console.log('Files from Alice:', fileList);
    
    // Download a file
    const data = await client.downloadFile('hello.json');
    console.log('Downloaded:', data.length, 'bytes');
  }
});

Signaling Server

Run a simple HTTP signaling server for WebRTC coordination:

1# Start signaling server
2npx tsx examples/signaling-server.ts 8080

For full examples, see:

API Reference

Account

 1// Create new account
 2const account = await Account.create(storage, rootPath, {
 3  name: 'Alice',
 4  email: 'alice@mau.network',
 5  passphrase: 'strong-passphrase',
 6  algorithm: 'ed25519', // or 'rsa'
 7});
 8
 9// Load existing account
10const account = await Account.load(storage, rootPath, 'passphrase');
11
12// Get account info
13account.getFingerprint();  // => '3a7b2c...'
14account.getName();          // => 'Alice'
15account.getEmail();         // => 'alice@mau.network'
16account.getPublicKey();     // => '-----BEGIN PGP PUBLIC KEY BLOCK-----...'
17
18// Manage friends
19await account.addFriend(armoredPublicKey);
20await account.removeFriend(fingerprint);
21account.getFriends();       // => ['fpr1', 'fpr2', ...]
22account.isFriend(fpr);      // => true/false

File

 1// Create file reference
 2const file = File.create(account, storage, 'my-post.json');
 3
 4// Write content (encrypted + signed automatically)
 5await file.writeText('Hello, world!');
 6await file.writeJSON({ message: 'Hello' });
 7await file.write(new Uint8Array([1, 2, 3]));
 8
 9// Read content (decrypts + verifies automatically)
10const text = await file.readText();
11const json = await file.readJSON();
12const data = await file.read();
13
14// File operations
15await file.delete();
16const size = await file.getSize();
17const checksum = await file.getChecksum();
18
19// Versioning
20const versions = await file.getVersions();
21for (const version of versions) {
22  console.log(await version.readText());
23}
24
25// List files
26const files = await File.list(account, storage);
27const friendFiles = await File.listFriend(account, storage, friendFingerprint);

Client

 1// Create client
 2const client = new Client(account, storage, peerFingerprint, {
 3  timeout: 30000,
 4  resolvers: [/* custom resolvers */],
 5});
 6
 7// Or use convenience method
 8const client = Client.create(account, account.storage, {
 9  fingerprint: 'peer-fingerprint',
10  address: '192.168.1.100:8080',
11}, [staticResolver(knownPeers)]);
12
13// Sync files
14const stats = await client.sync();
15// => { downloaded: 5, updated: 2, errors: 0, failedFiles: [] }
16
17// Fetch file list
18const files = await client.fetchFileList();
19const filesAfter = await client.fetchFileList(new Date('2024-01-01'));
20
21// Download specific file
22const data = await client.downloadFile('post.json');
23const version = await client.downloadFileVersion('post.json', 'abc123...');

Server

The Server class provides a framework-agnostic HTTP request handler. It’s primarily used internally by WebRTCServer for HTTP-over-datachannel communication.

 1// Create server
 2const server = new Server(account, storage, {
 3  resultsLimit: 20,
 4});
 5
 6// Handle requests manually (used internally by WebRTCServer)
 7const response = await server.handleRequest({
 8  method: 'GET',
 9  url: '/p2p/3a7b2c.../post.json',
10  path: '/p2p/3a7b2c.../post.json',
11  query: {},
12  headers: {},
13});

For browser-to-browser communication, use WebRTCServer which wraps the Server class.

WebRTCServer

 1// Create WebRTC server (for browser)
 2const server = new WebRTCServer(account, storage, {
 3  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
 4  allowedPeers: ['allowed-fingerprint-1', 'allowed-fingerprint-2'], // optional whitelist
 5});
 6
 7// Accept incoming connection
 8const answer = await server.acceptConnection(connectionId, offer);
 9
10// Add ICE candidate
11await server.addIceCandidate(connectionId, candidate);
12
13// Listen for signaling events (ICE candidates)
14server.onSignaling(connectionId, (signal) => {
15  // Send via signaling channel
16  signalingChannel.send(signal);
17});
18
19// Get active connections
20const connections = server.getConnections();
21
22// Close connection
23server.closeConnection(connectionId);
24
25// Stop server
26server.stop();

WebRTCClient

 1// Create WebRTC client
 2const client = new WebRTCClient(account, storage, peerFingerprint, {
 3  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
 4  timeout: 30000,
 5});
 6
 7// Create offer
 8const offer = await client.createOffer();
 9
10// Or accept offer and create answer
11const answer = await client.acceptOffer(offer);
12
13// Complete connection with answer
14await client.completeConnection(answer);
15
16// Add ICE candidate
17await client.addIceCandidate(candidate);
18
19// Perform mTLS authentication with retry attempts
20const authenticated = await client.performMTLS({ maxRetries: 3 });
21if (!authenticated) throw new Error('Authentication failed');
22
23// HTTP-style requests over data channel
24const fileList = await client.fetchFileList();
25const fileData = await client.downloadFile('post.json');
26const versionData = await client.downloadFileVersion('post.json', 'hash123');
27
28// Close connection
29client.close();

Signaling

 1// WebSocket signaling (browser)
 2import { WebSocketSignaling } from '@mau-network/mau';
 3
 4const signaling = new WebSocketSignaling('wss://signaling.example.com', fingerprint);
 5
 6// Send signaling message
 7await signaling.send({
 8  from: myFingerprint,
 9  to: peerFingerprint,
10  type: 'offer',
11  data: offerData,
12});
13
14// Receive messages
15signaling.onMessage((message) => {
16  console.log('Received:', message.type, 'from', message.from);
17});
18
19// Close connection
20signaling.close();
21
22// HTTP signaling (polling-based)
23import { HTTPSignaling } from '@mau-network/mau';
24
25const signaling = new HTTPSignaling('https://signaling.example.com', fingerprint);
26
27// Start polling for messages
28signaling.startPolling();
29
30// Send and receive messages (same interface as WebSocket)
31signaling.onMessage((message) => { /* ... */ });
32await signaling.send(message);
33
34// Stop polling
35signaling.stopPolling();
36
37// Signaled connection helper
38import { SignaledConnection } from '@mau-network/mau';
39
40const connection = new SignaledConnection(signaling, myFingerprint, peerFingerprint);
41
42// Callbacks for connection events
43connection.onOffer(async (offer) => { /* handle offer */ });
44connection.onAnswer(async (answer) => { /* handle answer */ });
45connection.onICECandidate(async (candidate) => { /* handle ICE */ });
46
47// Send connection events
48await connection.sendOffer(offer);
49await connection.sendAnswer(answer);
50await connection.sendICECandidate(candidate);

Storage

 1// Auto-detect storage backend (uses BrowserStorage with IndexedDB)
 2const storage = await createStorage();
 3
 4// Or create BrowserStorage directly
 5import { BrowserStorage } from '@mau-network/mau';
 6
 7const browserStorage = await BrowserStorage.create();
 8
 9// Storage interface
10await storage.readFile(path);         // => Uint8Array
11await storage.writeFile(path, data);  // => void
12await storage.readText(path);         // => string
13await storage.writeText(path, text);  // => void
14await storage.exists(path);           // => boolean
15await storage.mkdir(path);            // => void
16await storage.readDir(path);          // => string[]
17await storage.remove(path);           // => void
18await storage.stat(path);             // => { size, isDirectory }
19storage.join('a', 'b', 'c');          // => 'a/b/c'

Architecture

Storage Abstraction

Mau uses IndexedDB for persistent storage in the browser. The storage abstraction layer provides a file-like interface over IndexedDB’s key-value store.

Browser P2P with WebRTC

In browser environments, Mau uses WebRTC data channels instead of traditional HTTP:

┌─────────────┐                    ┌─────────────┐
│  Browser A  │◄──WebRTC Channel──►│  Browser B  │
│             │                    │             │
│ WebRTCServer│                    │WebRTCClient │
│   (Alice)   │                    │   (Bob)     │
└─────────────┘                    └─────────────┘
       │                                  │
       └────── Signaling Server ─────────┘
              (offer/answer/ICE)

How it works:

  1. Signaling Phase: Peers exchange WebRTC offers/answers via a signaling server (WebSocket or HTTP)
  2. Connection: WebRTC establishes a direct peer-to-peer connection with data channel
  3. mTLS Handshake: Peers authenticate each other using PGP keys over the data channel
  4. HTTP Protocol: HTTP-style requests/responses flow over the authenticated channel

Key Components:

  • WebRTCServer: Accepts incoming connections in the browser
  • WebRTCClient: Initiates connections to peers
  • Signaling: Coordinates WebRTC connection setup (offers, answers, ICE candidates)
  • mTLS over Data Channel: Authenticates peers using PGP signatures

Benefits:

  • No server infrastructure needed (except lightweight signaling)
  • True peer-to-peer communication
  • End-to-end encrypted (WebRTC + PGP)
  • Works across NATs/firewalls (with STUN/TURN)
  • Same HTTP-style protocol as traditional client/server

Encryption Flow

  1. Write: Content → Sign with private key → Encrypt for recipients → Save
  2. Read: Load → Decrypt with private key → Verify signature → Return content

All files are encrypted by default to:

  • Your own public key (so you can read them)
  • All friends’ public keys (so they can read them)

File Versioning

When a file is modified, the old version is automatically archived:

hello.json              ← Latest version
hello.json.versions/
  ├── abc123...         ← Version 1 (SHA-256 checksum)
  └── def456...         ← Version 2

Protocol Compatibility

This implementation follows the Mau specification:

  • ✅ PGP/OpenPGP for identity and encryption
  • ✅ HTTP endpoints: /p2p/<fpr>, /p2p/<fpr>/<file>, /p2p/<fpr>/<file>.versions/<hash>
  • ✅ JSON-LD / Schema.org for content structure
  • ✅ SHA-256 checksums for content verification
  • ✅ Kademlia DHT peer discovery (HTTP-based)
  • ⚠️ mTLS authentication (PGP-based, implemented for WebRTC only)

Peer Discovery Resolvers

 1import { staticResolver, dhtResolver, combinedResolver } from '@mau-network/mau';
 2
 3// Static resolver - hardcoded peer addresses
 4const resolver1 = staticResolver({
 5  'alice-fingerprint': '192.168.1.100:8080',
 6  'bob-fingerprint': 'bob.example.com:8080',
 7});
 8
 9// DHT resolver - discover peers via Kademlia DHT
10const resolver2 = dhtResolver(['bootstrap.mau.social:443']);
11
12// Combined resolver - try multiple strategies
13const resolver3 = combinedResolver([
14  staticResolver(knownPeers),
15  dhtResolver(['bootstrap1:443', 'bootstrap2:443']),
16]);
17
18// Use with client
19const client = Client.create(account, storage, peer, [resolver3]);

Limitations

  • Certificate Pinning: TLS certificate pinning for HTTP clients is not implemented (use WebRTC for authenticated connections)
  • Browser Environment: This package is designed for browsers only; it will not work in Node.js or other JavaScript runtimes
  • WebRTC Dependency: Peer-to-peer file synchronization requires WebRTC support

These limitations are by design for this browser-focused implementation.

Development

 1# Install dependencies
 2npm install
 3
 4# Build
 5npm run build
 6
 7# Run tests
 8npm test
 9
10# Lint
11npm run lint
12
13# Format
14npm run format

License

GPL-3.0 - Same as the main Mau project

Contributing

Contributions welcome! Please open issues and PRs on the main Mau repository.

Resources