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,502 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ActionNetP2P Discovery</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
}
|
|
11
|
+
body {
|
|
12
|
+
font-family: monospace;
|
|
13
|
+
background: #1e1e1e;
|
|
14
|
+
color: #d4d4d4;
|
|
15
|
+
padding: 20px;
|
|
16
|
+
margin: 0;
|
|
17
|
+
}
|
|
18
|
+
.container {
|
|
19
|
+
max-width: 1200px;
|
|
20
|
+
margin: 0 auto;
|
|
21
|
+
}
|
|
22
|
+
h1 {
|
|
23
|
+
color: #4ec9b0;
|
|
24
|
+
margin-top: 0;
|
|
25
|
+
}
|
|
26
|
+
h2 {
|
|
27
|
+
color: #569cd6;
|
|
28
|
+
margin-top: 30px;
|
|
29
|
+
border-bottom: 1px solid #333;
|
|
30
|
+
padding-bottom: 5px;
|
|
31
|
+
}
|
|
32
|
+
.status {
|
|
33
|
+
padding: 10px;
|
|
34
|
+
margin: 10px 0;
|
|
35
|
+
border-radius: 4px;
|
|
36
|
+
background: #252526;
|
|
37
|
+
border-left: 3px solid #569cd6;
|
|
38
|
+
}
|
|
39
|
+
.status.connected {
|
|
40
|
+
border-left-color: #4ec9b0;
|
|
41
|
+
}
|
|
42
|
+
.status.error {
|
|
43
|
+
border-left-color: #f48771;
|
|
44
|
+
}
|
|
45
|
+
.status.info {
|
|
46
|
+
border-left-color: #dcdcaa;
|
|
47
|
+
}
|
|
48
|
+
.controls {
|
|
49
|
+
display: flex;
|
|
50
|
+
gap: 10px;
|
|
51
|
+
margin: 20px 0;
|
|
52
|
+
flex-wrap: wrap;
|
|
53
|
+
}
|
|
54
|
+
button {
|
|
55
|
+
padding: 8px 16px;
|
|
56
|
+
background: #569cd6;
|
|
57
|
+
color: #fff;
|
|
58
|
+
border: none;
|
|
59
|
+
border-radius: 4px;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
font-family: monospace;
|
|
62
|
+
}
|
|
63
|
+
button:hover {
|
|
64
|
+
background: #4ec9b0;
|
|
65
|
+
}
|
|
66
|
+
button:disabled {
|
|
67
|
+
background: #666;
|
|
68
|
+
cursor: not-allowed;
|
|
69
|
+
}
|
|
70
|
+
input {
|
|
71
|
+
padding: 8px;
|
|
72
|
+
background: #3e3e42;
|
|
73
|
+
color: #d4d4d4;
|
|
74
|
+
border: 1px solid #555;
|
|
75
|
+
border-radius: 4px;
|
|
76
|
+
font-family: monospace;
|
|
77
|
+
}
|
|
78
|
+
.log {
|
|
79
|
+
background: #1e1e1e;
|
|
80
|
+
border: 1px solid #333;
|
|
81
|
+
border-radius: 4px;
|
|
82
|
+
padding: 10px;
|
|
83
|
+
max-height: 400px;
|
|
84
|
+
overflow-y: auto;
|
|
85
|
+
margin: 10px 0;
|
|
86
|
+
font-size: 12px;
|
|
87
|
+
}
|
|
88
|
+
.log-entry {
|
|
89
|
+
margin: 3px 0;
|
|
90
|
+
padding: 2px 5px;
|
|
91
|
+
border-radius: 2px;
|
|
92
|
+
}
|
|
93
|
+
.log-entry.info {
|
|
94
|
+
color: #858585;
|
|
95
|
+
}
|
|
96
|
+
.log-entry.peer {
|
|
97
|
+
color: #4ec9b0;
|
|
98
|
+
}
|
|
99
|
+
.log-entry.connection {
|
|
100
|
+
color: #569cd6;
|
|
101
|
+
}
|
|
102
|
+
.log-entry.error {
|
|
103
|
+
color: #f48771;
|
|
104
|
+
}
|
|
105
|
+
.peers-list {
|
|
106
|
+
display: grid;
|
|
107
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
108
|
+
gap: 10px;
|
|
109
|
+
margin: 10px 0;
|
|
110
|
+
}
|
|
111
|
+
.peer-card {
|
|
112
|
+
background: #252526;
|
|
113
|
+
border: 1px solid #333;
|
|
114
|
+
border-radius: 4px;
|
|
115
|
+
padding: 10px;
|
|
116
|
+
border-left: 3px solid #4ec9b0;
|
|
117
|
+
}
|
|
118
|
+
.peer-card h3 {
|
|
119
|
+
margin: 0 0 5px 0;
|
|
120
|
+
color: #4ec9b0;
|
|
121
|
+
font-size: 13px;
|
|
122
|
+
}
|
|
123
|
+
.peer-card p {
|
|
124
|
+
margin: 3px 0;
|
|
125
|
+
font-size: 12px;
|
|
126
|
+
}
|
|
127
|
+
.stats {
|
|
128
|
+
display: grid;
|
|
129
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
130
|
+
gap: 15px;
|
|
131
|
+
margin: 20px 0;
|
|
132
|
+
}
|
|
133
|
+
.stat-box {
|
|
134
|
+
background: #252526;
|
|
135
|
+
border: 1px solid #333;
|
|
136
|
+
border-radius: 4px;
|
|
137
|
+
padding: 15px;
|
|
138
|
+
text-align: center;
|
|
139
|
+
}
|
|
140
|
+
.stat-number {
|
|
141
|
+
font-size: 24px;
|
|
142
|
+
color: #4ec9b0;
|
|
143
|
+
font-weight: bold;
|
|
144
|
+
}
|
|
145
|
+
.stat-label {
|
|
146
|
+
font-size: 12px;
|
|
147
|
+
color: #858585;
|
|
148
|
+
margin-top: 5px;
|
|
149
|
+
}
|
|
150
|
+
</style>
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
<div class="container">
|
|
154
|
+
<h1>ActionNetP2P Discovery Test</h1>
|
|
155
|
+
|
|
156
|
+
<div class="status connected">
|
|
157
|
+
<strong>Status:</strong> <span id="status">Initializing...</span>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<h2>Configuration</h2>
|
|
161
|
+
<div class="controls">
|
|
162
|
+
<div>
|
|
163
|
+
<label>Application ID:</label>
|
|
164
|
+
<input type="text" id="applicationId" value="app-id-00000" style="width: 200px">
|
|
165
|
+
</div>
|
|
166
|
+
<div>
|
|
167
|
+
<label>Username:</label>
|
|
168
|
+
<input type="text" id="username" value="Tester" style="width: 150px">
|
|
169
|
+
</div>
|
|
170
|
+
<button id="joinBtn" onclick="joinGame()">Join Game</button>
|
|
171
|
+
<button id="leaveBtn" onclick="leaveGame()" disabled>Leave Game</button>
|
|
172
|
+
<button id="clearLogBtn" onclick="clearLog()">Clear Log</button>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<h2>Stats</h2>
|
|
176
|
+
<div class="stats">
|
|
177
|
+
<div class="stat-box">
|
|
178
|
+
<div class="stat-number" id="totalPeerCount">0</div>
|
|
179
|
+
<div class="stat-label">Total Peers (from tracker)</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="stat-box">
|
|
182
|
+
<div class="stat-number" id="connectedPeerCount">0</div>
|
|
183
|
+
<div class="stat-label">Connected Peers</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="stat-box">
|
|
186
|
+
<div class="stat-number" id="connectionCount">0</div>
|
|
187
|
+
<div class="stat-label">Data Connections</div>
|
|
188
|
+
</div>
|
|
189
|
+
<div class="stat-box">
|
|
190
|
+
<div class="stat-number" id="trackerCount">0</div>
|
|
191
|
+
<div class="stat-label">Active Trackers</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<h2>Confirmed Peers</h2>
|
|
196
|
+
<div id="confirmedPeersList" class="peers-list">
|
|
197
|
+
<div style="color: #858585; padding: 10px;">No confirmed peers yet</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<h2>Peers Discovered</h2>
|
|
201
|
+
<div id="peersList" class="peers-list">
|
|
202
|
+
<div style="color: #858585; padding: 10px;">No peers discovered yet</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<h2>Log</h2>
|
|
206
|
+
<div id="log" class="log"></div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<!-- Load P2P modules in order -->
|
|
210
|
+
<script src="ActionNetPeer.js"></script>
|
|
211
|
+
<script src="ActionNetTrackerClient.js"></script>
|
|
212
|
+
<script src="DataConnection.js"></script> <!-- Exports DataConnection class -->
|
|
213
|
+
|
|
214
|
+
<script>
|
|
215
|
+
// Test harness
|
|
216
|
+
let localPeerId = null; // Our peer ID for DataConnection initiator selection
|
|
217
|
+
let currentTracker = null;
|
|
218
|
+
let currentApplicationId = null;
|
|
219
|
+
let username = null;
|
|
220
|
+
const peers = new Map();
|
|
221
|
+
const connections = new Map();
|
|
222
|
+
const confirmedPeers = new Map(); // peerId -> {id, username, confirmed}
|
|
223
|
+
|
|
224
|
+
// Redirect console.log to UI log
|
|
225
|
+
const originalLog = console.log;
|
|
226
|
+
console.log = function(...args) {
|
|
227
|
+
const msg = args.map(arg => {
|
|
228
|
+
if (typeof arg === 'object') {
|
|
229
|
+
try {
|
|
230
|
+
return JSON.stringify(arg, null, 2);
|
|
231
|
+
} catch {
|
|
232
|
+
return String(arg);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return String(arg);
|
|
236
|
+
}).join(' ');
|
|
237
|
+
log(msg, 'info');
|
|
238
|
+
originalLog.apply(console, args);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const log = (msg, type = 'info') => {
|
|
242
|
+
const entry = document.createElement('div');
|
|
243
|
+
entry.className = `log-entry ${type}`;
|
|
244
|
+
entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
|
245
|
+
document.getElementById('log').appendChild(entry);
|
|
246
|
+
document.getElementById('log').scrollTop = document.getElementById('log').scrollHeight;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const clearLog = () => {
|
|
250
|
+
document.getElementById('log').innerHTML = '';
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const updateStats = () => {
|
|
254
|
+
document.getElementById('totalPeerCount').textContent = currentTracker ? currentTracker.getDiscoveredPeerCount() : 0;
|
|
255
|
+
document.getElementById('connectedPeerCount').textContent = peers.size;
|
|
256
|
+
document.getElementById('connectionCount').textContent = connections.size;
|
|
257
|
+
document.getElementById('trackerCount').textContent = currentTracker ? 1 : 0;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const updateConfirmedPeersList = () => {
|
|
261
|
+
const container = document.getElementById('confirmedPeersList');
|
|
262
|
+
if (confirmedPeers.size === 0) {
|
|
263
|
+
container.innerHTML = '<div style="color: #858585; padding: 10px;">No confirmed peers yet</div>';
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
container.innerHTML = Array.from(confirmedPeers.values()).map(peer => `
|
|
268
|
+
<div class="peer-card">
|
|
269
|
+
<h3>${peer.username || peer.id}</h3>
|
|
270
|
+
<p><strong>Peer ID:</strong> ${peer.id}</p>
|
|
271
|
+
<p><strong>Status:</strong> ${peer.confirmed ? 'Confirmed' : 'Confirming'}</p>
|
|
272
|
+
</div>
|
|
273
|
+
`).join('');
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const updatePeersList = () => {
|
|
277
|
+
const container = document.getElementById('peersList');
|
|
278
|
+
if (peers.size === 0) {
|
|
279
|
+
container.innerHTML = '<div style="color: #858585; padding: 10px;">No peers discovered yet</div>';
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
container.innerHTML = Array.from(peers.values()).map(peer => `
|
|
284
|
+
<div class="peer-card">
|
|
285
|
+
<h3>${peer.id}</h3>
|
|
286
|
+
<p><strong>IP:</strong> ${peer.ip || 'N/A'}</p>
|
|
287
|
+
<p><strong>Port:</strong> ${peer.port || 'N/A'}</p>
|
|
288
|
+
<p><strong>Source:</strong> ${peer.source || 'unknown'}</p>
|
|
289
|
+
<p><strong>Time:</strong> ${new Date(peer.discovered || Date.now()).toLocaleTimeString()}</p>
|
|
290
|
+
</div>
|
|
291
|
+
`).join('');
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const joinGame = async () => {
|
|
295
|
+
try {
|
|
296
|
+
currentApplicationId = document.getElementById('applicationId').value || 'app-id-00000';
|
|
297
|
+
username = document.getElementById('username').value || 'Tester';
|
|
298
|
+
|
|
299
|
+
log(`Joining network: ${currentApplicationId} as ${username}`, 'info');
|
|
300
|
+
|
|
301
|
+
document.getElementById('status').textContent = `Connecting to tracker...`;
|
|
302
|
+
document.getElementById('status').parentElement.className = 'status';
|
|
303
|
+
|
|
304
|
+
// Generate peer ID
|
|
305
|
+
localPeerId = 'peer_' + Math.random().toString(36).substr(2, 9);
|
|
306
|
+
log(`Generated peer ID: ${localPeerId}`, 'info');
|
|
307
|
+
|
|
308
|
+
// Generate infohash from application ID
|
|
309
|
+
const encoder = new TextEncoder();
|
|
310
|
+
const data = encoder.encode(currentApplicationId);
|
|
311
|
+
const hashBuffer = await crypto.subtle.digest('SHA-1', data);
|
|
312
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
313
|
+
const infohash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
314
|
+
|
|
315
|
+
log(`Application ID hash (infohash): ${infohash}`, 'info');
|
|
316
|
+
|
|
317
|
+
// Create tracker client with multiple trackers for redundancy
|
|
318
|
+
const trackerUrls = [
|
|
319
|
+
'wss://tracker.openwebtorrent.com/',
|
|
320
|
+
'wss://tracker.btorrent.xyz/',
|
|
321
|
+
'wss://tracker.fastcast.nz/'
|
|
322
|
+
];
|
|
323
|
+
const tracker = new ActionNetTrackerClient(trackerUrls, infohash, localPeerId, {
|
|
324
|
+
debug: true, // Enable logging for debugging
|
|
325
|
+
numwant: 50, // Max peers to request from tracker
|
|
326
|
+
announceInterval: 5000, // Start at 5 seconds for fast discovery
|
|
327
|
+
maxAnnounceInterval: 120000, // Back off to max 120 seconds
|
|
328
|
+
backoffMultiplier: 1.1 // Increase by 1.1x each cycle
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Handle peer connections
|
|
332
|
+
tracker.on('peer', (data) => {
|
|
333
|
+
const peerId = data.id;
|
|
334
|
+
log(`Peer connected: ${peerId}`, 'peer');
|
|
335
|
+
peers.set(peerId, {
|
|
336
|
+
id: peerId,
|
|
337
|
+
discovered: Date.now(),
|
|
338
|
+
source: data.source,
|
|
339
|
+
peer: data.peer
|
|
340
|
+
});
|
|
341
|
+
updateStats();
|
|
342
|
+
updatePeersList();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Handle data connections (already created by tracker)
|
|
346
|
+
tracker.on('connection', (connection) => {
|
|
347
|
+
const peerId = connection.remotePeerId;
|
|
348
|
+
|
|
349
|
+
log(`Data connection created with peer: ${peerId}`, 'info');
|
|
350
|
+
|
|
351
|
+
connection.on('connect', () => {
|
|
352
|
+
log(`Data connection connected with ${peerId}`, 'info');
|
|
353
|
+
connections.set(peerId, connection);
|
|
354
|
+
updateStats();
|
|
355
|
+
|
|
356
|
+
// Send handshake
|
|
357
|
+
connection.send({
|
|
358
|
+
type: 'handshake',
|
|
359
|
+
peerId: localPeerId,
|
|
360
|
+
username: username,
|
|
361
|
+
applicationId: currentApplicationId
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
connection.on('data', (message) => {
|
|
366
|
+
if (message.type === 'handshake') {
|
|
367
|
+
// Validate handshake
|
|
368
|
+
if (message.applicationId !== currentApplicationId) {
|
|
369
|
+
log(`Handshake failed: peer on different application (${message.applicationId})`, 'error');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Add to confirmed peers
|
|
374
|
+
confirmedPeers.set(peerId, {
|
|
375
|
+
id: message.peerId,
|
|
376
|
+
username: message.username,
|
|
377
|
+
confirmed: true
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
log(`Peer confirmed: ${message.username} (${message.peerId})`, 'peer');
|
|
381
|
+
updateConfirmedPeersList();
|
|
382
|
+
} else {
|
|
383
|
+
log(`DataConnection message from ${peerId}: ${JSON.stringify(message)}`, 'connection');
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
connection.on('close', () => {
|
|
388
|
+
log(`Data connection closed with ${peerId}`, 'info');
|
|
389
|
+
connections.delete(peerId);
|
|
390
|
+
confirmedPeers.delete(peerId);
|
|
391
|
+
updateStats();
|
|
392
|
+
updateConfirmedPeersList();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
connection.on('error', (err) => {
|
|
396
|
+
log(`Data connection error with ${peerId}: ${err.message}`, 'error');
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Handle tracker ready event
|
|
401
|
+
tracker.on('ready', () => {
|
|
402
|
+
log('Tracker ready, waiting for peers...', 'info');
|
|
403
|
+
document.getElementById('status').textContent = `Connected to tracker - Waiting for peers...`;
|
|
404
|
+
document.getElementById('status').parentElement.className = 'status connected';
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Handle tracker updates
|
|
408
|
+
tracker.on('update', (data) => {
|
|
409
|
+
const total = data.complete + data.incomplete;
|
|
410
|
+
log(`Tracker: ${data.complete} seeders, ${data.incomplete} leechers (${total} total)`, 'info');
|
|
411
|
+
updateStats();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Handle peer disconnection
|
|
415
|
+
tracker.on('peer-disconnected', (data) => {
|
|
416
|
+
const peerId = data.id;
|
|
417
|
+
log(`Peer disconnected: ${peerId}`, 'info');
|
|
418
|
+
|
|
419
|
+
// Clean up connection
|
|
420
|
+
if (connections.has(peerId)) {
|
|
421
|
+
connections.get(peerId).close();
|
|
422
|
+
connections.delete(peerId);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Clean up peer
|
|
426
|
+
peers.delete(peerId);
|
|
427
|
+
confirmedPeers.delete(peerId);
|
|
428
|
+
|
|
429
|
+
updateStats();
|
|
430
|
+
updateConfirmedPeersList();
|
|
431
|
+
updatePeersList();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Handle peer connection failure
|
|
435
|
+
tracker.on('peer-failed', (data) => {
|
|
436
|
+
const peerId = data.id;
|
|
437
|
+
log(`Peer failed: ${peerId} - ${data.error.message}`, 'error');
|
|
438
|
+
|
|
439
|
+
// Clean up connection
|
|
440
|
+
if (connections.has(peerId)) {
|
|
441
|
+
connections.get(peerId).close();
|
|
442
|
+
connections.delete(peerId);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Clean up peer
|
|
446
|
+
peers.delete(peerId);
|
|
447
|
+
confirmedPeers.delete(peerId);
|
|
448
|
+
|
|
449
|
+
updateStats();
|
|
450
|
+
updateConfirmedPeersList();
|
|
451
|
+
updatePeersList();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// Handle tracker errors
|
|
455
|
+
tracker.on('error', (err) => {
|
|
456
|
+
log(`Tracker error: ${err.message}`, 'error');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Connect to tracker
|
|
460
|
+
await tracker.connect();
|
|
461
|
+
log('Connected to tracker and announced', 'info');
|
|
462
|
+
document.getElementById('joinBtn').disabled = true;
|
|
463
|
+
document.getElementById('leaveBtn').disabled = false;
|
|
464
|
+
|
|
465
|
+
log('To see peers: Open this page in another window/tab and join the same game', 'info');
|
|
466
|
+
|
|
467
|
+
// Store tracker for cleanup
|
|
468
|
+
currentTracker = tracker;
|
|
469
|
+
|
|
470
|
+
} catch (error) {
|
|
471
|
+
log(`Error: ${error.message}`, 'error');
|
|
472
|
+
document.getElementById('status').textContent = `Error: ${error.message}`;
|
|
473
|
+
document.getElementById('status').parentElement.className = 'status error';
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const leaveGame = () => {
|
|
478
|
+
// Disconnect tracker
|
|
479
|
+
if (currentTracker) {
|
|
480
|
+
currentTracker.disconnect();
|
|
481
|
+
currentTracker = null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
peers.clear();
|
|
485
|
+
connections.clear();
|
|
486
|
+
confirmedPeers.clear();
|
|
487
|
+
updateStats();
|
|
488
|
+
updateConfirmedPeersList();
|
|
489
|
+
updatePeersList();
|
|
490
|
+
|
|
491
|
+
document.getElementById('status').textContent = 'Disconnected';
|
|
492
|
+
document.getElementById('status').parentElement.className = 'status';
|
|
493
|
+
document.getElementById('joinBtn').disabled = false;
|
|
494
|
+
document.getElementById('leaveBtn').disabled = true;
|
|
495
|
+
|
|
496
|
+
log('Left game', 'info');
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
log('P2P Test harness initializing...', 'info');
|
|
500
|
+
</script>
|
|
501
|
+
</body>
|
|
502
|
+
</html>
|