action-engine-js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -0
- package/README.md +348 -0
- package/actionengine/3rdparty/goblin/goblin.js +9609 -0
- package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
- package/actionengine/camera/actioncamera.js +90 -0
- package/actionengine/camera/cameracollisionhandler.js +69 -0
- package/actionengine/character/actioncharacter.js +360 -0
- package/actionengine/character/actioncharacter3D.js +61 -0
- package/actionengine/core/app.js +430 -0
- package/actionengine/debug/basedebugpanel.js +858 -0
- package/actionengine/display/canvasmanager.js +75 -0
- package/actionengine/display/gl/programmanager.js +570 -0
- package/actionengine/display/gl/shaders/lineshader.js +118 -0
- package/actionengine/display/gl/shaders/objectshader.js +1756 -0
- package/actionengine/display/gl/shaders/particleshader.js +43 -0
- package/actionengine/display/gl/shaders/shadowshader.js +319 -0
- package/actionengine/display/gl/shaders/spriteshader.js +100 -0
- package/actionengine/display/gl/shaders/watershader.js +67 -0
- package/actionengine/display/graphics/actionmodel3D.js +191 -0
- package/actionengine/display/graphics/actionsprite3D.js +230 -0
- package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
- package/actionengine/display/graphics/lighting/actionlight.js +211 -0
- package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
- package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
- package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
- package/actionengine/display/graphics/renderableobject.js +44 -0
- package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
- package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
- package/actionengine/display/graphics/texture/texturemanager.js +242 -0
- package/actionengine/display/graphics/texture/textureregistry.js +177 -0
- package/actionengine/input/actionscrollablearea.js +1405 -0
- package/actionengine/input/inputhandler.js +1647 -0
- package/actionengine/math/geometry/geometrybuilder.js +161 -0
- package/actionengine/math/geometry/glbexporter.js +364 -0
- package/actionengine/math/geometry/glbloader.js +722 -0
- package/actionengine/math/geometry/modelcodegenerator.js +97 -0
- package/actionengine/math/geometry/triangle.js +33 -0
- package/actionengine/math/geometry/triangleutils.js +34 -0
- package/actionengine/math/mathutils.js +25 -0
- package/actionengine/math/matrix4.js +785 -0
- package/actionengine/math/physics/actionphysics.js +108 -0
- package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
- package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
- package/actionengine/math/physics/actionraycast.js +129 -0
- package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
- package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
- package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
- package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
- package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
- package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
- package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
- package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
- package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
- package/actionengine/math/quaternion.js +61 -0
- package/actionengine/math/vector2.js +277 -0
- package/actionengine/math/vector3.js +318 -0
- package/actionengine/math/viewfrustum.js +136 -0
- package/actionengine/network/ACTIONNETREADME.md +810 -0
- package/actionengine/network/client/ActionNetManager.js +802 -0
- package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
- package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
- package/actionengine/network/client/SyncSystem.js +422 -0
- package/actionengine/network/p2p/ActionNetPeer.js +142 -0
- package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
- package/actionengine/network/p2p/DataConnection.js +282 -0
- package/actionengine/network/p2p/README.md +510 -0
- package/actionengine/network/p2p/example.html +502 -0
- package/actionengine/network/server/ActionNetServer.js +577 -0
- package/actionengine/network/server/ActionNetServerSSL.js +579 -0
- package/actionengine/network/server/ActionNetServerUtils.js +458 -0
- package/actionengine/network/server/SERVERREADME.md +314 -0
- package/actionengine/network/server/package-lock.json +35 -0
- package/actionengine/network/server/package.json +13 -0
- package/actionengine/network/server/start.bat +27 -0
- package/actionengine/network/server/start.sh +25 -0
- package/actionengine/network/server/startwss.bat +27 -0
- package/actionengine/sound/audiomanager.js +1589 -0
- package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
- package/actionengine/sound/soundfont/actionparser.js +718 -0
- package/actionengine/sound/soundfont/actionreverb.js +252 -0
- package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
- package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
- package/actionengine/sound/soundfont/soundfont.js +2 -0
- package/dist/action-engine.min.js +328 -0
- package/package.json +35 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# ActionNetP2P Library
|
|
2
|
+
|
|
3
|
+
A lightweight, zero-dependency WebRTC-based peer-to-peer networking library. Uses WebSocket trackers for peer discovery, built on BitTorrent infrastructure (infohash + tracker protocol).
|
|
4
|
+
|
|
5
|
+
<img width="3002" height="1757" alt="image" src="https://github.com/user-attachments/assets/886ed373-bb7a-482e-b74c-2d8759d6370e" />
|
|
6
|
+
|
|
7
|
+
## Architecture Overview
|
|
8
|
+
|
|
9
|
+
The library has three main layers:
|
|
10
|
+
|
|
11
|
+
1. **Tracker Layer** - Peer discovery via WebSocket tracker
|
|
12
|
+
2. **ActionNetPeer Layer** - WebRTC signaling connections between peers
|
|
13
|
+
3. **DataConnection Layer** - Application protocol through WebRTC data channels
|
|
14
|
+
|
|
15
|
+
## Library vs Application
|
|
16
|
+
|
|
17
|
+
**This library's responsibility:**
|
|
18
|
+
- Discover peers via tracker
|
|
19
|
+
- Establish WebRTC ActionNetPeer connections
|
|
20
|
+
- Emit `peer` event with connected ActionNetPeer instance
|
|
21
|
+
- Emit `connection` event with ready-to-use DataConnection instance
|
|
22
|
+
|
|
23
|
+
**Your application's responsibility:**
|
|
24
|
+
- Handle DataConnection connections
|
|
25
|
+
- Implement handshaking (validation/confirmation of peer identity)
|
|
26
|
+
- Send/receive application-specific messages
|
|
27
|
+
- Handle disconnects and reconnects
|
|
28
|
+
|
|
29
|
+
See `example.html` for a complete example of how to build on top of this library.
|
|
30
|
+
|
|
31
|
+
## Core Components
|
|
32
|
+
|
|
33
|
+
### ActionNetPeer (ActionNetPeer.js)
|
|
34
|
+
|
|
35
|
+
A thin wrapper around DataConnection that provides compatibility and delegation.
|
|
36
|
+
|
|
37
|
+
**Constructor:**
|
|
38
|
+
```javascript
|
|
39
|
+
const peer = new ActionNetPeer({
|
|
40
|
+
initiator: true, // Whether this peer creates the offer
|
|
41
|
+
trickle: false, // Wait for complete ICE before signaling (default: true)
|
|
42
|
+
localPeerId: 'peer_xyz', // This peer's ID
|
|
43
|
+
remotePeerId: 'peer_abc', // Remote peer's ID
|
|
44
|
+
iceServers: [{ urls: 'stun:...' }] // STUN/TURN servers
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Key Features:**
|
|
49
|
+
- Wraps an internal DataConnection
|
|
50
|
+
- Initiators create WebRTC offers automatically
|
|
51
|
+
- Responders handle incoming offers via `signal(data)`
|
|
52
|
+
- Exposes `peer.connection` which is the internal DataConnection
|
|
53
|
+
- Delegates all calls to the internal DataConnection
|
|
54
|
+
|
|
55
|
+
**Events:**
|
|
56
|
+
- `signal` - Emit offer/answer with complete SDP (ready to send to remote peer)
|
|
57
|
+
- `connect` - WebRTC connection established (tracker signaling complete)
|
|
58
|
+
- `data` - Received data through DataConnection
|
|
59
|
+
- `close` - Connection closed
|
|
60
|
+
- `error` - Connection error
|
|
61
|
+
|
|
62
|
+
**Methods:**
|
|
63
|
+
- `signal(data)` - Handle remote offer/answer/ICE candidate (delegates to DataConnection)
|
|
64
|
+
- `send(data)` - Send data through DataConnection
|
|
65
|
+
- `destroy()` - Clean up connection
|
|
66
|
+
|
|
67
|
+
### ActionNetTrackerClient (ActionNetTrackerClient.js)
|
|
68
|
+
|
|
69
|
+
Handles peer discovery through a WebSocket tracker server and manages ActionNetPeer lifecycle.
|
|
70
|
+
|
|
71
|
+
**Constructor:**
|
|
72
|
+
```javascript
|
|
73
|
+
const tracker = new ActionNetTrackerClient(
|
|
74
|
+
['wss://tracker.openwebtorrent.com/', 'wss://tracker.btorrent.xyz/'], // URL or array of URLs
|
|
75
|
+
infohash, // SHA-1 hash of application ID
|
|
76
|
+
peerId, // Random peer identifier
|
|
77
|
+
{
|
|
78
|
+
numwant: 50, // Max peers to request
|
|
79
|
+
announceInterval: 5000, // Milliseconds between announces
|
|
80
|
+
iceServers: [...] // Optional STUN/TURN servers
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Flow:**
|
|
86
|
+
1. `tracker.connect()` opens WebSocket connection
|
|
87
|
+
2. Creates first ActionNetPeer and waits for ICE gathering to complete (ready state)
|
|
88
|
+
3. Emits `ready` event when first peer is prepared
|
|
89
|
+
4. Every 30 seconds: generates new WebRTC offers and announces them
|
|
90
|
+
5. Each offer has 50-second timeout to receive an answer
|
|
91
|
+
6. Tracker relays other peers' offers back via `peer-offer` event
|
|
92
|
+
7. When answers arrive, signals them to stored peers
|
|
93
|
+
8. Emits `peer` event when WebRTC connection succeeds
|
|
94
|
+
9. Tracks connected peer IDs to avoid duplicate connections
|
|
95
|
+
|
|
96
|
+
**Events:**
|
|
97
|
+
- `ready` - First peer ready, starts announcing with offers
|
|
98
|
+
- `peer` - Successfully connected to peer: `{ id: peerId, peer: ActionNetPeer instance, source: 'tracker' }`
|
|
99
|
+
- `connection` - DataConnection (application protocol layer) ready: DataConnection instance with `localPeerId` and `remotePeerId` exposed
|
|
100
|
+
- `peer-disconnected` - ActionNetPeer connection closed: `{ id: peerId }`
|
|
101
|
+
- `peer-failed` - ActionNetPeer connection failed: `{ id: peerId, error: Error }`
|
|
102
|
+
- `update` - Tracker sent stats: `{ complete: seeders, incomplete: leechers }`
|
|
103
|
+
- `error` - Connection or protocol error
|
|
104
|
+
- `close` - Disconnected from all trackers
|
|
105
|
+
|
|
106
|
+
**Methods:**
|
|
107
|
+
- `connect()` - Connect to tracker and start announcing
|
|
108
|
+
- `disconnect()` - Stop announcing and close connection
|
|
109
|
+
- `sendAnswer(offerId, peerId, answer)` - Send answer back to tracker for responder flow
|
|
110
|
+
- `on(event, handler)` - Register event listener
|
|
111
|
+
|
|
112
|
+
**Peer Lifecycle:**
|
|
113
|
+
- Library manages all ActionNetPeer creation, connection, and cleanup
|
|
114
|
+
- When ActionNetPeer connects: marked as connected, emits `peer` event
|
|
115
|
+
- When ActionNetPeer disconnects: removed from tracking, emits `peer-disconnected`
|
|
116
|
+
- When ActionNetPeer errors: removed from tracking, emits `peer-failed`
|
|
117
|
+
- Developer should listen for these events and clean up their DataConnection instances
|
|
118
|
+
|
|
119
|
+
### DataConnection (DataConnection.js)
|
|
120
|
+
|
|
121
|
+
Standalone RTCPeerConnection for tracker-level signaling (offer/answer/ICE exchange).
|
|
122
|
+
|
|
123
|
+
**Purpose:**
|
|
124
|
+
Manages the RTCPeerConnection for tracker discovery and signaling. Receives offer/answer/ICE via `signal()` method. Used internally by ActionNetPeer and emitted by ActionNetTrackerClient.
|
|
125
|
+
|
|
126
|
+
**Constructor:**
|
|
127
|
+
```javascript
|
|
128
|
+
const connection = new DataConnection({
|
|
129
|
+
localPeerId: 'peer_xyz', // This peer's ID
|
|
130
|
+
remotePeerId: 'peer_abc', // Remote peer's ID
|
|
131
|
+
initiator: true, // Whether this peer creates the offer
|
|
132
|
+
iceServers: [...] // Optional STUN/TURN servers
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Negotiation Flow:**
|
|
137
|
+
1. Initiator (determined by constructor option or peer ID comparison) creates offer
|
|
138
|
+
2. Offer/answer/ICE candidates exchanged via `signal()` method
|
|
139
|
+
3. Emits `signal` events with offer/answer/ICE to be relayed (e.g., through tracker)
|
|
140
|
+
4. When ICE connection completes, emits `connect` event
|
|
141
|
+
|
|
142
|
+
**Events:**
|
|
143
|
+
- `signal` - Emit offer/answer/ICE for relay to remote peer (e.g., via tracker)
|
|
144
|
+
- `connect` - Tracker-level RTCPeerConnection established
|
|
145
|
+
- `close` - Connection closed
|
|
146
|
+
- `error` - Connection error
|
|
147
|
+
|
|
148
|
+
**Methods:**
|
|
149
|
+
- `signal(data)` - Receive offer/answer/ICE from remote peer
|
|
150
|
+
- `{ type: 'offer', sdp: '...' }` - WebRTC offer
|
|
151
|
+
- `{ type: 'answer', sdp: '...' }` - WebRTC answer
|
|
152
|
+
- `{ candidate: {...} }` - ICE candidate
|
|
153
|
+
- `send(message)` - Send JSON object through data channel (auto-stringified, returns boolean)
|
|
154
|
+
- `close()` - Clean up connection
|
|
155
|
+
- `on(event, handler)` - Register event listener
|
|
156
|
+
|
|
157
|
+
**Events:**
|
|
158
|
+
- `signal` - Emit offer/answer/ICE for relay to remote peer
|
|
159
|
+
- `connect` - Data channel established
|
|
160
|
+
- `data` - Received message on data channel (already parsed JSON)
|
|
161
|
+
- `close` - Connection closed
|
|
162
|
+
- `error` - Connection error
|
|
163
|
+
|
|
164
|
+
**Data Channel Messages:**
|
|
165
|
+
|
|
166
|
+
DataConnection automatically serializes/deserializes JSON. Send objects, receive parsed objects:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// Send (automatically JSON.stringify'd)
|
|
170
|
+
connection.send({
|
|
171
|
+
type: 'greeting',
|
|
172
|
+
message: 'Hello from peer',
|
|
173
|
+
timestamp: Date.now()
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Receive (automatically JSON.parse'd)
|
|
177
|
+
connection.on('data', (message) => {
|
|
178
|
+
console.log(message.type); // 'greeting'
|
|
179
|
+
console.log(message.message); // 'Hello from peer'
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Handshake Pattern (Recommended):**
|
|
184
|
+
|
|
185
|
+
Peers should exchange handshakes immediately upon connection to validate compatibility and ensure you're only communicating with peers running compatible versions of your application. Peers that fail validation should be rejected:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
connection.on('connect', () => {
|
|
189
|
+
// Send handshake for confirmation
|
|
190
|
+
connection.send({
|
|
191
|
+
type: 'handshake',
|
|
192
|
+
peerId: localPeerId,
|
|
193
|
+
version: '1.0',
|
|
194
|
+
metadata: { /* your app-specific data */ }
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
connection.on('data', (message) => {
|
|
199
|
+
if (message.type === 'handshake') {
|
|
200
|
+
// Validate: is peer compatible with our application?
|
|
201
|
+
if (message.version !== '1.0') {
|
|
202
|
+
// Reject this peer, close connection
|
|
203
|
+
connection.close();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Confirm: peer is valid, proceed with communication
|
|
207
|
+
registerConfirmedPeer(message.peerId, message.metadata);
|
|
208
|
+
} else {
|
|
209
|
+
// Handle other application messages
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Note:** DataConnection handles both tracker signaling (offer/answer/ICE via `signal()`) and application communication (messages via `send()`/`on('data')`). This provides a unified communication channel that works for both peer discovery and application-level messaging.
|
|
215
|
+
|
|
216
|
+
## Message Protocol
|
|
217
|
+
|
|
218
|
+
### Tracker Announce (JSON via WebSocket)
|
|
219
|
+
|
|
220
|
+
**Client announces to tracker:**
|
|
221
|
+
```javascript
|
|
222
|
+
{
|
|
223
|
+
action: "announce",
|
|
224
|
+
info_hash: "sha1_of_application_id",
|
|
225
|
+
peer_id: "peer_xyz",
|
|
226
|
+
port: 6881,
|
|
227
|
+
numwant: 50,
|
|
228
|
+
offers: [
|
|
229
|
+
{
|
|
230
|
+
offer_id: "random_20_byte_hex",
|
|
231
|
+
offer: { type: "offer", sdp: "..." }
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Tracker responds with stats:**
|
|
238
|
+
```javascript
|
|
239
|
+
{
|
|
240
|
+
action: "announce",
|
|
241
|
+
interval: 120,
|
|
242
|
+
complete: 0,
|
|
243
|
+
incomplete: 2
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Tracker relays peer offer:**
|
|
248
|
+
```javascript
|
|
249
|
+
{
|
|
250
|
+
action: "announce",
|
|
251
|
+
offer_id: "...",
|
|
252
|
+
peer_id: "peer_abc",
|
|
253
|
+
offer: { type: "offer", sdp: "..." }
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Tracker relays peer answer:**
|
|
258
|
+
```javascript
|
|
259
|
+
{
|
|
260
|
+
action: "announce",
|
|
261
|
+
offer_id: "...",
|
|
262
|
+
peer_id: "peer_abc",
|
|
263
|
+
answer: { type: "answer", sdp: "..." }
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Usage Example
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
// Create tracker client with multiple trackers for redundancy
|
|
271
|
+
const tracker = new ActionNetTrackerClient(
|
|
272
|
+
['wss://tracker.openwebtorrent.com/', 'wss://tracker.btorrent.xyz/'],
|
|
273
|
+
infohash,
|
|
274
|
+
peerId,
|
|
275
|
+
{ numwant: 50 }
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Tracker is ready (first peer prepared, will start announcing)
|
|
279
|
+
tracker.on('ready', () => {
|
|
280
|
+
console.log('Tracker ready, discovering peers...');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// DataConnection created and ready (TrackerClient creates it automatically)
|
|
284
|
+
tracker.on('connection', (connection) => {
|
|
285
|
+
const peerId = connection.remotePeerId;
|
|
286
|
+
|
|
287
|
+
connection.on('connect', () => {
|
|
288
|
+
// Send handshake to confirm peer
|
|
289
|
+
connection.send({
|
|
290
|
+
type: 'handshake',
|
|
291
|
+
peerId: myPeerId,
|
|
292
|
+
version: '1.0'
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
connection.on('data', (message) => {
|
|
297
|
+
console.log('Received from', peerId, ':', message);
|
|
298
|
+
if (message.type === 'handshake') {
|
|
299
|
+
// Validate and confirm peer
|
|
300
|
+
} else {
|
|
301
|
+
// Handle application messages
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
connection.on('close', () => {
|
|
306
|
+
console.log('DataConnection closed with peer:', peerId);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
connection.on('error', (err) => {
|
|
310
|
+
console.error('DataConnection error with peer:', peerId, err);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Peer disconnected or failed
|
|
315
|
+
tracker.on('peer-disconnected', (data) => {
|
|
316
|
+
console.log('Peer disconnected:', data.id);
|
|
317
|
+
// Clean up any DataConnection associated with this peer
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
tracker.on('peer-failed', (data) => {
|
|
321
|
+
console.log('Peer connection failed:', data.id, data.error.message);
|
|
322
|
+
// Clean up any DataConnection associated with this peer
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Connect to tracker (TrackerClient handles all peer offer/answer negotiation internally)
|
|
326
|
+
await tracker.connect();
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Connection Lifecycle
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
Initiator (Tab A) Tracker Responder (Tab B)
|
|
333
|
+
| | |
|
|
334
|
+
| 1. Connect to tracker | |
|
|
335
|
+
|--announce (with offers)--------> | |
|
|
336
|
+
| |--announce-----------------> |
|
|
337
|
+
| | 2. Relay offer to peers |
|
|
338
|
+
| | 3. Create responder Peer |
|
|
339
|
+
| | 4. Handle offer |
|
|
340
|
+
| | 5. Generate answer |
|
|
341
|
+
| | <------------------answer--|
|
|
342
|
+
| 6. Receive answer | |
|
|
343
|
+
| 7. Signal answer to Peer | |
|
|
344
|
+
| | |
|
|
345
|
+
|===== Peer WebRTC Connected ======| |
|
|
346
|
+
| | |
|
|
347
|
+
| 8. Create DataConnection | |
|
|
348
|
+
| <---------------- DataConnection negotiation ----------------> |
|
|
349
|
+
| | |
|
|
350
|
+
|====== DataConnection Ready ======| |
|
|
351
|
+
| | |
|
|
352
|
+
| 9. Send application message | |
|
|
353
|
+
|--application-data-message------> | |
|
|
354
|
+
| | 10. Receive message |
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Connection Model
|
|
358
|
+
|
|
359
|
+
The library handles peer discovery and provides a ready-to-use communication channel:
|
|
360
|
+
|
|
361
|
+
1. **Peer Discovery (ActionNetTrackerClient)**:
|
|
362
|
+
- Announces offers to tracker
|
|
363
|
+
- Tracker relays offers to other peers
|
|
364
|
+
- Manages peer lifecycle (connect, disconnect, failures)
|
|
365
|
+
|
|
366
|
+
2. **Peer-to-Peer Communication (DataConnection)**:
|
|
367
|
+
- Each peer gets a DataConnection with its own RTCPeerConnection
|
|
368
|
+
- Handles offer/answer/ICE signaling automatically
|
|
369
|
+
- Provides a data channel for sending/receiving application messages
|
|
370
|
+
- Application uses `connection.send()` and `connection.on('data')`
|
|
371
|
+
|
|
372
|
+
**Why this design?** The library handles all the complexity of peer discovery and WebRTC negotiation. Your application just listens for connections and sends/receives messages. No need to manage offers/answers yourself.
|
|
373
|
+
|
|
374
|
+
## Key Design Decisions
|
|
375
|
+
|
|
376
|
+
**One RTCPeerConnection Per Peer:**
|
|
377
|
+
- DataConnection wraps a single RTCPeerConnection for all peer-to-peer communication
|
|
378
|
+
- Handles both tracker signaling and application messaging through the same connection
|
|
379
|
+
|
|
380
|
+
**Deterministic Initiator Selection:**
|
|
381
|
+
- DataConnection initiator selected based on peer ID comparison (lexicographically highest initiates)
|
|
382
|
+
- Guarantees both sides agree on who initiates, preventing simultaneous offers
|
|
383
|
+
|
|
384
|
+
**Complete ICE Gathering:**
|
|
385
|
+
- DataConnection waits for ICE gathering to complete before emitting offer/answer
|
|
386
|
+
- Ensures SDP includes all ICE candidates
|
|
387
|
+
- Prevents trickle ICE complexity at tracker level
|
|
388
|
+
|
|
389
|
+
**Ordered Messages:**
|
|
390
|
+
- Data channels created with `ordered: true`
|
|
391
|
+
- Guarantees in-order message delivery
|
|
392
|
+
|
|
393
|
+
**Zero Dependencies:**
|
|
394
|
+
- Native WebRTC APIs only
|
|
395
|
+
- No external libraries required
|
|
396
|
+
- Works in any modern browser
|
|
397
|
+
|
|
398
|
+
## Configuration
|
|
399
|
+
|
|
400
|
+
**TrackerClient Options:**
|
|
401
|
+
- `port` - Local port number (default: 6881)
|
|
402
|
+
- `numwant` - Maximum peers to request from tracker (default: 50)
|
|
403
|
+
- `announceInterval` - Time between periodic announces in milliseconds (default: 30000)
|
|
404
|
+
- `iceServers` - Array of STUN/TURN server configurations
|
|
405
|
+
|
|
406
|
+
**ActionNetPeer Options:**
|
|
407
|
+
- `initiator` - Whether this peer creates the offer (default: false)
|
|
408
|
+
- `trickle` - Use trickle ICE (default: true)
|
|
409
|
+
- `iceServers` - Array of STUN/TURN server configurations
|
|
410
|
+
|
|
411
|
+
**ActionNetTrackerClient:**
|
|
412
|
+
- `debug` - Enable logging for debugging (default: false)
|
|
413
|
+
- `numwant` - Maximum peers to request from tracker (default: 50)
|
|
414
|
+
- `announceInterval` - Initial time between announces in milliseconds (default: 5000)
|
|
415
|
+
- `maxAnnounceInterval` - Maximum interval cap in milliseconds (default: 120000)
|
|
416
|
+
- `backoffMultiplier` - Multiply interval by this after each announce (default: 1.1)
|
|
417
|
+
- `iceServers` - Array of STUN/TURN server configurations
|
|
418
|
+
|
|
419
|
+
**Announce Backoff Strategy:**
|
|
420
|
+
|
|
421
|
+
After each announce, the interval is multiplied by `backoffMultiplier` until reaching `maxAnnounceInterval`. With default settings (`announceInterval: 5000`, `backoffMultiplier: 1.1`, `maxAnnounceInterval: 120000`):
|
|
422
|
+
|
|
423
|
+
- 5s → 5.5s → 6s → 6.6s → 7.3s → 8s → 8.8s → 9.7s → 10.7s → 11.7s → 12.9s → 14.2s → 15.6s → 17.2s → 18.9s → 20.8s → 22.9s → 25.2s → 27.7s → 30.5s ... → 120s (capped after ~36 announces)
|
|
424
|
+
|
|
425
|
+
Higher multipliers (e.g., 1.5) reach the cap faster: 5s → 7.5s → 11s → 17s → 26s → 39s → 59s → 88s → 120s (capped).
|
|
426
|
+
|
|
427
|
+
**DataConnection:**
|
|
428
|
+
- No configuration; automatically uses STUN servers for ICE
|
|
429
|
+
|
|
430
|
+
**Infohash Generation:**
|
|
431
|
+
|
|
432
|
+
The infohash is derived from the application ID using a cryptographic hash. The example uses SHA-1, but SHA-256 or any consistent hash works. **Important:** All peers must use the same hash function to generate the same infohash for discovery.
|
|
433
|
+
|
|
434
|
+
```javascript
|
|
435
|
+
// SHA-1 (example.html approach)
|
|
436
|
+
const hashBuffer = await crypto.subtle.digest('SHA-1', applicationIdBytes);
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## NAT Traversal: STUN and TURN
|
|
440
|
+
|
|
441
|
+
WebRTC uses ICE (Interactive Connectivity Establishment) to find the best network path between peers. This requires STUN and optionally TURN servers.
|
|
442
|
+
|
|
443
|
+
**STUN (Session Traversal Utilities for NAT):**
|
|
444
|
+
- Helps peers discover their public IP address
|
|
445
|
+
- Works for most NAT configurations (90%+ of cases)
|
|
446
|
+
- Free, no auth required
|
|
447
|
+
- Default: Multiple Google STUN servers for redundancy
|
|
448
|
+
|
|
449
|
+
**TURN (Traversal Using Relays around NAT):**
|
|
450
|
+
- Relays traffic when peers can't reach each other directly
|
|
451
|
+
- Needed for restrictive firewalls, double NAT, mobile networks
|
|
452
|
+
- Requires credentials and infrastructure (paid or self-hosted)
|
|
453
|
+
- Only use if STUN fails
|
|
454
|
+
|
|
455
|
+
**Default Configuration (STUN only):**
|
|
456
|
+
```javascript
|
|
457
|
+
iceServers: [
|
|
458
|
+
{ urls: 'stun:stun.l.google.com:19302' },
|
|
459
|
+
{ urls: 'stun:stun1.l.google.com:19302' },
|
|
460
|
+
{ urls: 'stun:stun2.l.google.com:19302' }
|
|
461
|
+
]
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Production Configuration (STUN + TURN):**
|
|
465
|
+
```javascript
|
|
466
|
+
iceServers: [
|
|
467
|
+
// STUN servers (free)
|
|
468
|
+
{ urls: 'stun:stun.l.google.com:19302' },
|
|
469
|
+
{ urls: 'stun:stun1.l.google.com:19302' },
|
|
470
|
+
|
|
471
|
+
// TURN server (your infrastructure or paid service)
|
|
472
|
+
{
|
|
473
|
+
urls: 'turn:your-turn-server.com',
|
|
474
|
+
username: 'user',
|
|
475
|
+
credential: 'password'
|
|
476
|
+
}
|
|
477
|
+
]
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Passing Custom ICE Servers:**
|
|
481
|
+
```javascript
|
|
482
|
+
const tracker = new ActionNetTrackerClient(trackerUrls, infohash, peerId, {
|
|
483
|
+
iceServers: [
|
|
484
|
+
{ urls: 'stun:your-stun.example.com:3478' },
|
|
485
|
+
{ urls: 'turn:your-turn.example.com:3478', username: 'user', credential: 'pass' }
|
|
486
|
+
]
|
|
487
|
+
});
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Testing
|
|
491
|
+
|
|
492
|
+
Open `example.html` in two browser tabs with the same application ID (or create a test app):
|
|
493
|
+
|
|
494
|
+
1. Both peers connect to tracker
|
|
495
|
+
2. First peer announces an offer
|
|
496
|
+
3. Second peer receives offer, creates responder Peer connection
|
|
497
|
+
4. First peer receives answer, completes WebRTC signaling connection
|
|
498
|
+
5. Both create DataConnection connections automatically for application data
|
|
499
|
+
6. Peers exchange handshakes to confirm each other
|
|
500
|
+
7. Exchange application messages through DataConnection data channels
|
|
501
|
+
|
|
502
|
+
Watch the stats and confirmed peers list update as connections establish.
|
|
503
|
+
|
|
504
|
+
## Limitations
|
|
505
|
+
|
|
506
|
+
**Current Limitations:**
|
|
507
|
+
1. Requires public WebSocket tracker (no DHT)
|
|
508
|
+
2. Peer connections not persisted across disconnects (rejoin requires new TrackerClient)
|
|
509
|
+
3. No connection pooling (single Peer per remote peer)
|
|
510
|
+
4. No message fragmentation for large payloads
|