Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
- js-libp2p-example-transports
- js-libp2p-example-webrtc-private-to-private
- js-libp2p-example-webrtc-private-to-public
- js-libp2p-example-yjs-libp2p
defaults:
run:
working-directory: examples/${{ matrix.project }}
Expand All @@ -47,6 +48,15 @@ jobs:
run: npm install
- name: Install Playwright
run: npx -y playwright install --with-deps
- name: Build project (if build script exists)
run: |
if npm run | grep -q "build"; then
npm run build
else
echo "No build script found, skipping build"
fi
env:
CI: true
- name: Run tests
run: npm run test
env:
Expand All @@ -64,6 +74,10 @@ jobs:
run: npm install
- name: Install Playwright
run: npx -y playwright install --with-deps
- name: Build projects
run: npm run build
env:
CI: true
- name: Run linting
run: npm run lint
env:
Expand Down Expand Up @@ -97,6 +111,7 @@ jobs:
- js-libp2p-example-transports
- js-libp2p-example-webrtc-private-to-private
- js-libp2p-example-webrtc-private-to-public
- js-libp2p-example-yjs-libp2p
steps:
- uses: convictional/trigger-workflow-and-wait@f69fa9eedd3c62a599220f4d5745230e237904be
with:
Expand Down
34 changes: 34 additions & 0 deletions examples/js-libp2p-example-yjs-libp2p/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Dependencies
node_modules/

# Build output
dist/
.vite/

# Test artifacts
test/relay-info.json
playwright-report/
test-results/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment variables
.env
.env.local

# Editor directories and files
.vscode/
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# OS files
.DS_Store
Thumbs.db
4 changes: 4 additions & 0 deletions examples/js-libp2p-example-yjs-libp2p/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This project is dual licensed under MIT and Apache-2.0.

MIT: https://www.opensource.org/licenses/mit
Apache-2.0: https://www.apache.org/licenses/license-2.0
5 changes: 5 additions & 0 deletions examples/js-libp2p-example-yjs-libp2p/LICENSE-APACHE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
19 changes: 19 additions & 0 deletions examples/js-libp2p-example-yjs-libp2p/LICENSE-MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
The MIT License (MIT)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
166 changes: 166 additions & 0 deletions examples/js-libp2p-example-yjs-libp2p/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# @libp2p/example-yjs-libp2p <!-- omit in toc -->

A collaborative text editor built with Yjs and libp2p, demonstrating real-time peer-to-peer document synchronization.

## Table of Contents <!-- omit in toc -->

- [Overview](#overview)
- [Architecture](#architecture)
- [Setup](#setup)
- [Usage](#usage)
- [How It Works](#how-it-works)
- [Key Features](#key-features)
- [License](#license)

## Overview

This example demonstrates how to create a Yjs connection provider using libp2p instead of the standard y-webrtc connector. It showcases:

- **Custom Yjs Provider**: A libp2p-based connection provider for Yjs
- **WebRTC Support**: Direct peer-to-peer connections using WebRTC
- **Circuit Relay**: NAT traversal via relay servers
- **DCUTR**: Direct Connection Upgrade through Relay (hole punching)
- **AutoNAT**: Automatic NAT detection
- **PubSub**: GossipSub for document synchronization
- **Peer Discovery**: Automatic connection to discovered peers

## Architecture

```
┌─────────────┐ ┌─────────────┐
│ Browser 1 │ │ Browser 2 │
│ │ │ │
│ Yjs Doc ←──┼─────────┼──→ Yjs Doc │
│ ↕ │ WebRTC │ ↕ │
│ libp2p │ or │ libp2p │
│ (pubsub) │ Relay │ (pubsub) │
└──────┬──────┘ └──────┬──────┘
│ │
│ ┌─────────────┐ │
└────┤ Relay Node │────┘
│ (relay.js) │
└─────────────┘
```

## Setup

1. Install dependencies:
```bash
npm install
```

2. Start the relay server:
```bash
npm run relay
```

The relay will output its multiaddr, which looks like:
```
/ip4/127.0.0.1/tcp/53472/ws/p2p/12D3KooWABC123...
```

3. Start the development server:
```bash
npm start
```

4. Open http://localhost:5173 in multiple browser tabs or windows

## Usage

1. Copy the relay multiaddr from the terminal output
2. Paste it into the "Relay multiaddr" field in the browser
3. Keep the default topic or enter a custom one
4. Click "Connect"
5. Start typing in the text area
6. Open another browser tab/window, connect to the same relay and topic
7. Changes will sync automatically between all connected peers

### Debug Mode

To enable verbose logging:

**Relay server:**
```bash
npm run relay:debug
```

**Browser client:**
Add `?debug=true` to the URL:
```
http://localhost:5173/?debug=true
```

## How It Works

### Libp2p Configuration

The browser clients are configured with:

- **Transports**: WebSockets (for relay), WebRTC (for direct P2P), Circuit Relay
- **Security**: Noise protocol for encryption
- **Stream Muxing**: Yamux
- **Services**:
- `identify`: Peer identification
- `autoNAT`: NAT detection
- `dcutr`: Hole punching for direct connections
- `pubsub`: GossipSub for broadcasting document updates

### Yjs Integration

The custom `Libp2pProvider` class:

1. **Subscribes** to a pubsub topic for the Yjs document
2. **Listens** for Yjs document updates and broadcasts them via pubsub
3. **Receives** updates from other peers and applies them to the local document
4. **Discovers** peers subscribing to the same topic
5. **Connects** directly to discovered peers (using WebRTC when possible)
6. **Syncs** initial state using Yjs's state vector protocol

### Message Types

The provider uses three message types:

- `update`: Broadcasts document changes to all peers
- `sync-request`: Requests the current document state (sent on join)
- `sync-response`: Sends the current state to a requesting peer

### Peer Discovery Flow

1. Client connects to relay server via WebSocket
2. Client subscribes to the pubsub topic
3. Relay forwards pubsub messages between peers
4. When a peer subscribes to the same topic, both peers discover each other
5. Peers attempt direct WebRTC connections (using DCUTR for NAT traversal)
6. If direct connection fails, communication continues through the relay

## Key Features

### 🔗 Decentralized Architecture
No central server required - peers communicate directly when possible

### 🌐 NAT Traversal
Automatic hole punching via DCUTR for direct connections behind NATs

### 🔄 Real-time Sync
Changes propagate instantly to all connected peers

### 📡 Efficient Messaging
Uses Yjs's state-based CRDT for minimal bandwidth usage

### 🔌 Relay Fallback
Falls back to relay when direct connections aren't possible

### 🤝 Auto-discovery
Peers automatically discover and connect to each other

## License

Licensed under either of

- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
48 changes: 48 additions & 0 deletions examples/js-libp2p-example-yjs-libp2p/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Configuration constants for the Yjs + libp2p application
*/

// Debug mode - set via environment variable or query parameter
export const DEBUG = new URLSearchParams(window?.location?.search).get('debug') === 'true' || false

// Network timeouts (milliseconds)
export const TIMEOUTS = {
RELAY_CONNECTION: 20000,
PROTOCOL_NEGOTIATION_INBOUND: 10000,
PROTOCOL_NEGOTIATION_OUTBOUND: 10000,
UPGRADE_INBOUND: 10000,
UPGRADE_OUTBOUND: 10000,
EDITOR_READY: 10000,
PEER_DISCOVERY: 15000
}

// Pubsub intervals (milliseconds)
export const INTERVALS = {
PUBSUB_PEER_DISCOVERY: 10000,
GOSSIPSUB_HEARTBEAT: 1000,
INITIAL_SYNC_REQUEST: 1000,
PEER_CHECK: 2000
}

// Relay server configuration
export const RELAY_CONFIG = {
HOP_TIMEOUT: 30000,
MAX_RESERVATIONS: 1000,
RESERVATION_TTL: 2 * 60 * 60 * 1000, // 2 hours
DEFAULT_DATA_LIMIT: BigInt(1024 * 1024 * 1024), // 1 GB
DEFAULT_DURATION_LIMIT: 2 * 60 * 1000, // 2 minutes
MAX_CONNECTIONS: 1000,
MAX_INCOMING_PENDING: 100,
MAX_PEER_ADDRS_TO_DIAL: 100,
DIAL_TIMEOUT: 30000
}

// Pubsub discovery configuration
export const PUBSUB_DISCOVERY = {
TOPICS: ['_peer-discovery._p2p._pubsub']
}

// Default values
export const DEFAULTS = {
TOPIC: 'yjs-doc-1'
}
37 changes: 37 additions & 0 deletions examples/js-libp2p-example-yjs-libp2p/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Yjs + libp2p Example</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; margin: 2rem; }
#status { margin-bottom: 1rem; }
textarea { width: 100%; height: 300px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
.row { display: flex; gap: 1rem; margin-bottom: 1rem; }
.row > * { flex: 1; }
#peers { background: #f5f5f5; padding: 1rem; border-radius: 4px; margin-bottom: 1rem; }
#peers h3 { margin-top: 0; }
.peer { margin: 0.5rem 0; padding: 0.5rem; background: white; border-radius: 4px; }
.peer-id { font-family: ui-monospace, monospace; font-size: 0.9em; }
.transport { display: inline-block; background: #e0e0e0; padding: 0.2rem 0.5rem; border-radius: 3px; margin: 0.2rem; font-size: 0.85em; }
</style>
</head>
<body>
<h1>Yjs + libp2p</h1>
<div id="status">
<label>Relay multiaddr: <input id="relay" placeholder="/ip4/127.0.0.1/tcp/PORT/ws/p2p/RELAY_PEER_ID" size="80" /></label>
<label>Topic: <input id="topic" value="yjs-doc-1" /></label>
<button id="connect">Connect</button>
</div>
<div id="peers" style="display: none;">
<h3>Connected Peers (<span id="peer-count">0</span>)</h3>
<div id="peer-list"></div>
</div>
<div class="row">
<textarea id="editor" placeholder="Start typing..." disabled></textarea>
</div>
<pre id="log"></pre>
<script type="module" src="./index.js"></script>
</body>
</html>
Loading