@phystack/hub-client 4.5.19-dev → 4.5.20-dev
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/dist/index.d.ts +22 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -378
- package/dist/index.js.map +1 -1
- package/dist/peripheral-twin.d.ts +34 -0
- package/dist/peripheral-twin.d.ts.map +1 -0
- package/dist/peripheral-twin.js +234 -0
- package/dist/peripheral-twin.js.map +1 -0
- package/dist/services/phyhub-connection.service.d.ts +1 -0
- package/dist/services/phyhub-connection.service.d.ts.map +1 -1
- package/dist/services/phyhub-connection.service.js +17 -0
- package/dist/services/phyhub-connection.service.js.map +1 -1
- package/dist/services/phyhub-direct-connection.service.d.ts +21 -0
- package/dist/services/phyhub-direct-connection.service.d.ts.map +1 -0
- package/dist/services/phyhub-direct-connection.service.js +101 -0
- package/dist/services/phyhub-direct-connection.service.js.map +1 -0
- package/dist/services/webrtc/data-channel-handler.d.ts +45 -0
- package/dist/services/webrtc/data-channel-handler.d.ts.map +1 -0
- package/dist/services/webrtc/data-channel-handler.js +260 -0
- package/dist/services/webrtc/data-channel-handler.js.map +1 -0
- package/dist/services/webrtc/index.d.ts +8 -0
- package/dist/services/webrtc/index.d.ts.map +1 -0
- package/dist/services/webrtc/index.js +18 -0
- package/dist/services/webrtc/index.js.map +1 -0
- package/dist/services/webrtc/media-stream-handler.d.ts +57 -0
- package/dist/services/webrtc/media-stream-handler.d.ts.map +1 -0
- package/dist/services/webrtc/media-stream-handler.js +383 -0
- package/dist/services/webrtc/media-stream-handler.js.map +1 -0
- package/dist/services/webrtc/peer-connection-manager.d.ts +40 -0
- package/dist/services/webrtc/peer-connection-manager.d.ts.map +1 -0
- package/dist/services/webrtc/peer-connection-manager.js +336 -0
- package/dist/services/webrtc/peer-connection-manager.js.map +1 -0
- package/dist/services/webrtc/types.d.ts +134 -0
- package/dist/services/webrtc/types.d.ts.map +1 -0
- package/dist/services/webrtc/types.js +12 -0
- package/dist/services/webrtc/types.js.map +1 -0
- package/dist/services/webrtc/webrtc-globals.d.ts +4 -0
- package/dist/services/webrtc/webrtc-globals.d.ts.map +1 -0
- package/dist/services/webrtc/webrtc-globals.js +72 -0
- package/dist/services/webrtc/webrtc-globals.js.map +1 -0
- package/dist/services/webrtc/webrtc-manager.d.ts +35 -0
- package/dist/services/webrtc/webrtc-manager.d.ts.map +1 -0
- package/dist/services/webrtc/webrtc-manager.js +274 -0
- package/dist/services/webrtc/webrtc-manager.js.map +1 -0
- package/dist/test/communication-comprehensive-test.d.ts +8 -0
- package/dist/test/communication-comprehensive-test.d.ts.map +1 -0
- package/dist/test/communication-comprehensive-test.js +356 -0
- package/dist/test/communication-comprehensive-test.js.map +1 -0
- package/dist/test/webrtc-channel-names-test.d.ts +2 -0
- package/dist/test/webrtc-channel-names-test.d.ts.map +1 -0
- package/dist/test/webrtc-channel-names-test.js +177 -0
- package/dist/test/webrtc-channel-names-test.js.map +1 -0
- package/dist/test/webrtc-comprehensive-test.d.ts +2 -0
- package/dist/test/webrtc-comprehensive-test.d.ts.map +1 -0
- package/dist/test/webrtc-comprehensive-test.js +328 -0
- package/dist/test/webrtc-comprehensive-test.js.map +1 -0
- package/dist/test/webrtc-reconnect-test.d.ts +4 -0
- package/dist/test/webrtc-reconnect-test.d.ts.map +1 -0
- package/dist/test/webrtc-reconnect-test.js +244 -0
- package/dist/test/webrtc-reconnect-test.js.map +1 -0
- package/dist/test/webrtc-test-harness.d.ts +4 -0
- package/dist/test/webrtc-test-harness.d.ts.map +1 -0
- package/dist/test/webrtc-test-harness.js +169 -0
- package/dist/test/webrtc-test-harness.js.map +1 -0
- package/dist/twin-messaging.d.ts +20 -0
- package/dist/twin-messaging.d.ts.map +1 -0
- package/dist/twin-messaging.js +94 -0
- package/dist/twin-messaging.js.map +1 -0
- package/dist/twin-registry.d.ts +9 -0
- package/dist/twin-registry.d.ts.map +1 -0
- package/dist/twin-registry.js +26 -0
- package/dist/twin-registry.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +20 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/twin.types.d.ts +62 -14
- package/dist/types/twin.types.d.ts.map +1 -1
- package/dist/types/twin.types.js +8 -1
- package/dist/types/twin.types.js.map +1 -1
- package/docs/webrtc-howto.md +398 -0
- package/docs/webrtc-test.md +330 -0
- package/package.json +3 -3
- package/scripts/webrtc-test.sh +401 -0
- package/src/index.ts +378 -568
- package/src/peripheral-twin.ts +337 -0
- package/src/services/phyhub-connection.service.ts +24 -0
- package/src/services/phyhub-direct-connection.service.ts +159 -0
- package/src/services/webrtc/data-channel-handler.ts +362 -0
- package/src/services/webrtc/index.ts +36 -0
- package/src/services/webrtc/media-stream-handler.ts +536 -0
- package/src/services/webrtc/peer-connection-manager.ts +467 -0
- package/src/services/webrtc/types.ts +273 -0
- package/src/services/webrtc/webrtc-globals.ts +108 -0
- package/src/services/webrtc/webrtc-manager.ts +490 -0
- package/src/test/communication-comprehensive-test.ts +533 -0
- package/src/test/webrtc-channel-names-test.ts +266 -0
- package/src/test/webrtc-comprehensive-test.ts +494 -0
- package/src/test/webrtc-reconnect-test.ts +345 -0
- package/src/test/webrtc-test-harness.ts +254 -0
- package/src/twin-messaging.ts +184 -0
- package/src/twin-registry.ts +39 -0
- package/src/types/index.ts +3 -0
- package/src/types/twin.types.ts +80 -14
- package/dist/services/webrtc/datachannel.d.ts +0 -10
- package/dist/services/webrtc/datachannel.d.ts.map +0 -1
- package/dist/services/webrtc/datachannel.js +0 -290
- package/dist/services/webrtc/datachannel.js.map +0 -1
- package/dist/services/webrtc/mediastream.d.ts +0 -10
- package/dist/services/webrtc/mediastream.d.ts.map +0 -1
- package/dist/services/webrtc/mediastream.js +0 -396
- package/dist/services/webrtc/mediastream.js.map +0 -1
- package/dist/services/webrtc/peer-connection-ice.d.ts +0 -32
- package/dist/services/webrtc/peer-connection-ice.d.ts.map +0 -1
- package/dist/services/webrtc/peer-connection-ice.js +0 -483
- package/dist/services/webrtc/peer-connection-ice.js.map +0 -1
- package/src/services/webrtc/datachannel.ts +0 -421
- package/src/services/webrtc/mediastream.ts +0 -602
- package/src/services/webrtc/peer-connection-ice.ts +0 -689
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebRTC Reconnection Test
|
|
3
|
+
*
|
|
4
|
+
* Tests that WebRTC DataChannel handles disconnection and reconnection seamlessly:
|
|
5
|
+
* - Messages continue flowing after reconnection
|
|
6
|
+
* - Handlers persist across reconnections (no need to reattach)
|
|
7
|
+
* - Messages sent during disconnection are buffered and delivered
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* Terminal 1 (Responder - start first):
|
|
11
|
+
* PHYHUB_DIRECT=true DEVICE_ID=<id> ACCESS_KEY=<key> PEER_TWIN_ID=<peer> ROLE=responder \
|
|
12
|
+
* node dist/test/webrtc-reconnect-test.js
|
|
13
|
+
*
|
|
14
|
+
* Terminal 2 (Initiator):
|
|
15
|
+
* PHYHUB_DIRECT=true DEVICE_ID=<id> ACCESS_KEY=<key> PEER_TWIN_ID=<peer> ROLE=initiator \
|
|
16
|
+
* node dist/test/webrtc-reconnect-test.js
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { PhyHubClient } from '../index';
|
|
20
|
+
import { PhygridDataChannel } from '../services/webrtc/types';
|
|
21
|
+
|
|
22
|
+
const TEST_DURATION_MS = 120000; // 2 minutes
|
|
23
|
+
const MESSAGE_INTERVAL_MS = 2000;
|
|
24
|
+
const DISCONNECT_AFTER_MESSAGES = 5;
|
|
25
|
+
|
|
26
|
+
interface TestMessage {
|
|
27
|
+
seq: number;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
phase: 'initial' | 'after-reconnect';
|
|
30
|
+
from: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface TestStats {
|
|
34
|
+
messagesSent: number;
|
|
35
|
+
messagesReceived: number;
|
|
36
|
+
reconnectCount: number;
|
|
37
|
+
errors: string[];
|
|
38
|
+
phases: Set<string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sleep(ms: number): Promise<void> {
|
|
42
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function runInitiatorTest(peerTwinId: string): Promise<void> {
|
|
46
|
+
console.log('\n=== INITIATOR: Reconnection Test ===\n');
|
|
47
|
+
|
|
48
|
+
const stats: TestStats = {
|
|
49
|
+
messagesSent: 0,
|
|
50
|
+
messagesReceived: 0,
|
|
51
|
+
reconnectCount: 0,
|
|
52
|
+
errors: [],
|
|
53
|
+
phases: new Set(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Connect to PhyHub
|
|
57
|
+
console.log('[INIT] Connecting to PhyHub...');
|
|
58
|
+
const client = await PhyHubClient.connect();
|
|
59
|
+
console.log('[INIT] Connected!\n');
|
|
60
|
+
|
|
61
|
+
const manager = await client.getWebRTCManager({ verbose: true });
|
|
62
|
+
|
|
63
|
+
// Track reconnection events
|
|
64
|
+
manager.on('connected', (data) => {
|
|
65
|
+
console.log(`[EVENT] Connected to ${data.targetTwinId}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
manager.on('disconnected', (data) => {
|
|
69
|
+
console.log(`[EVENT] Disconnected from ${data.targetTwinId}`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
manager.on('reconnecting', (data) => {
|
|
73
|
+
stats.reconnectCount++;
|
|
74
|
+
console.log(`[EVENT] Reconnecting to ${data.targetTwinId} (attempt ${data.attempt})`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
manager.on('reconnected', (data) => {
|
|
78
|
+
console.log(`[EVENT] Reconnected to ${data.targetTwinId}`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
manager.on('error', (data) => {
|
|
82
|
+
stats.errors.push(data.error.message);
|
|
83
|
+
console.error(`[EVENT] Error:`, data.error.message);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Create data channel
|
|
87
|
+
console.log(`[INIT] Creating DataChannel to: ${peerTwinId}`);
|
|
88
|
+
const channel = await client.getDataChannel(peerTwinId);
|
|
89
|
+
console.log('[INIT] DataChannel created!\n');
|
|
90
|
+
|
|
91
|
+
// Setup message handler ONCE - it should persist across reconnections
|
|
92
|
+
channel.onMessage((data: TestMessage) => {
|
|
93
|
+
stats.messagesReceived++;
|
|
94
|
+
stats.phases.add(data.phase);
|
|
95
|
+
console.log(`[RECV] seq=${data.seq} phase=${data.phase} from=${data.from}`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Test phases
|
|
99
|
+
let phase: 'initial' | 'after-reconnect' = 'initial';
|
|
100
|
+
let seq = 0;
|
|
101
|
+
|
|
102
|
+
console.log('[TEST] Starting message loop...\n');
|
|
103
|
+
console.log('[INFO] The peer will simulate a disconnect after receiving messages.');
|
|
104
|
+
console.log('[INFO] Messages should continue after reconnection automatically.\n');
|
|
105
|
+
|
|
106
|
+
const startTime = Date.now();
|
|
107
|
+
|
|
108
|
+
while (Date.now() - startTime < TEST_DURATION_MS) {
|
|
109
|
+
seq++;
|
|
110
|
+
const msg: TestMessage = {
|
|
111
|
+
seq,
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
phase,
|
|
114
|
+
from: 'initiator',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Use channel.send - it should buffer if disconnected
|
|
118
|
+
channel.send(msg);
|
|
119
|
+
stats.messagesSent++;
|
|
120
|
+
console.log(`[SEND] seq=${seq} phase=${phase}`);
|
|
121
|
+
|
|
122
|
+
// After reconnect events, switch phase
|
|
123
|
+
if (stats.reconnectCount > 0 && phase === 'initial') {
|
|
124
|
+
phase = 'after-reconnect';
|
|
125
|
+
console.log('\n[PHASE] Switched to "after-reconnect" phase\n');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await sleep(MESSAGE_INTERVAL_MS);
|
|
129
|
+
|
|
130
|
+
// Check if we've received messages from both phases (success condition)
|
|
131
|
+
if (stats.phases.has('after-reconnect') && stats.messagesReceived > 10) {
|
|
132
|
+
console.log('\n[SUCCESS] Received messages from both phases!');
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Print results
|
|
138
|
+
console.log('\n' + '='.repeat(50));
|
|
139
|
+
console.log('Test Results (Initiator)');
|
|
140
|
+
console.log('='.repeat(50));
|
|
141
|
+
console.log(`Messages sent: ${stats.messagesSent}`);
|
|
142
|
+
console.log(`Messages received: ${stats.messagesReceived}`);
|
|
143
|
+
console.log(`Reconnect events: ${stats.reconnectCount}`);
|
|
144
|
+
console.log(`Phases seen: ${Array.from(stats.phases).join(', ')}`);
|
|
145
|
+
console.log(`Errors: ${stats.errors.length > 0 ? stats.errors.join(', ') : 'None'}`);
|
|
146
|
+
console.log('='.repeat(50));
|
|
147
|
+
|
|
148
|
+
if (stats.phases.has('after-reconnect') && stats.messagesReceived > 5) {
|
|
149
|
+
console.log('\n[PASS] Reconnection test passed!');
|
|
150
|
+
} else {
|
|
151
|
+
console.log('\n[FAIL] Reconnection test failed - did not receive messages after reconnect');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
channel.close();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function runResponderTest(peerTwinId: string): Promise<void> {
|
|
158
|
+
console.log('\n=== RESPONDER: Reconnection Test ===\n');
|
|
159
|
+
|
|
160
|
+
const stats: TestStats = {
|
|
161
|
+
messagesSent: 0,
|
|
162
|
+
messagesReceived: 0,
|
|
163
|
+
reconnectCount: 0,
|
|
164
|
+
errors: [],
|
|
165
|
+
phases: new Set(),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Connect to PhyHub
|
|
169
|
+
console.log('[INIT] Connecting to PhyHub...');
|
|
170
|
+
const client = await PhyHubClient.connect();
|
|
171
|
+
console.log('[INIT] Connected!\n');
|
|
172
|
+
|
|
173
|
+
const manager = await client.getWebRTCManager({ verbose: true });
|
|
174
|
+
|
|
175
|
+
// Track reconnection events
|
|
176
|
+
manager.on('connected', (data) => {
|
|
177
|
+
console.log(`[EVENT] Connected to ${data.targetTwinId}`);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
manager.on('disconnected', (data) => {
|
|
181
|
+
console.log(`[EVENT] Disconnected from ${data.targetTwinId}`);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
manager.on('reconnecting', (data) => {
|
|
185
|
+
stats.reconnectCount++;
|
|
186
|
+
console.log(`[EVENT] Reconnecting to ${data.targetTwinId} (attempt ${data.attempt})`);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
manager.on('reconnected', (data) => {
|
|
190
|
+
console.log(`[EVENT] Reconnected to ${data.targetTwinId}`);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
manager.on('error', (data) => {
|
|
194
|
+
stats.errors.push(data.error.message);
|
|
195
|
+
console.error(`[EVENT] Error:`, data.error.message);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
console.log(`[INIT] Waiting for DataChannel from: ${peerTwinId}\n`);
|
|
199
|
+
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const timeout = setTimeout(() => {
|
|
202
|
+
reject(new Error('Timeout waiting for connection'));
|
|
203
|
+
}, TEST_DURATION_MS);
|
|
204
|
+
|
|
205
|
+
let phase: 'initial' | 'after-reconnect' = 'initial';
|
|
206
|
+
let disconnectSimulated = false;
|
|
207
|
+
|
|
208
|
+
// Setup handler ONCE - this is the key test:
|
|
209
|
+
// The handler should persist across any reconnections
|
|
210
|
+
client.onDataChannel(peerTwinId, (ch: PhygridDataChannel) => {
|
|
211
|
+
console.log('[INIT] DataChannel received!\n');
|
|
212
|
+
|
|
213
|
+
// Setup message handler - should only need to do this ONCE
|
|
214
|
+
ch.onMessage((data: TestMessage) => {
|
|
215
|
+
stats.messagesReceived++;
|
|
216
|
+
stats.phases.add(data.phase);
|
|
217
|
+
console.log(`[RECV] seq=${data.seq} phase=${data.phase} from=${data.from}`);
|
|
218
|
+
|
|
219
|
+
// Echo back
|
|
220
|
+
const response: TestMessage = {
|
|
221
|
+
seq: stats.messagesSent + 1,
|
|
222
|
+
timestamp: Date.now(),
|
|
223
|
+
phase,
|
|
224
|
+
from: 'responder',
|
|
225
|
+
};
|
|
226
|
+
ch.send(response);
|
|
227
|
+
stats.messagesSent++;
|
|
228
|
+
console.log(`[SEND] seq=${response.seq} phase=${phase}`);
|
|
229
|
+
|
|
230
|
+
// Simulate disconnect after receiving some messages
|
|
231
|
+
if (stats.messagesReceived === DISCONNECT_AFTER_MESSAGES && !disconnectSimulated) {
|
|
232
|
+
disconnectSimulated = true;
|
|
233
|
+
console.log('\n[TEST] Simulating network disconnect...');
|
|
234
|
+
console.log('[TEST] Closing peer connection to trigger reconnection...\n');
|
|
235
|
+
|
|
236
|
+
// Access the internal peer connection and close it to simulate disconnect
|
|
237
|
+
// This should trigger automatic reconnection
|
|
238
|
+
try {
|
|
239
|
+
const webrtcManager = (client as any).webrtcManager;
|
|
240
|
+
if (webrtcManager) {
|
|
241
|
+
const handler = webrtcManager.dataChannelHandlers?.get(peerTwinId);
|
|
242
|
+
if (handler) {
|
|
243
|
+
const pcManager = (handler as any).pcManager;
|
|
244
|
+
if (pcManager) {
|
|
245
|
+
const pc = pcManager.getPeerConnection();
|
|
246
|
+
if (pc) {
|
|
247
|
+
console.log('[TEST] Closing RTCPeerConnection...');
|
|
248
|
+
pc.close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error('[TEST] Error simulating disconnect:', err);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// After reconnect, update phase
|
|
259
|
+
if (stats.reconnectCount > 0 && phase === 'initial') {
|
|
260
|
+
phase = 'after-reconnect';
|
|
261
|
+
console.log('\n[PHASE] Switched to "after-reconnect" phase\n');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Success condition: received messages in both phases
|
|
265
|
+
if (stats.phases.has('after-reconnect') && stats.messagesReceived > 10) {
|
|
266
|
+
clearTimeout(timeout);
|
|
267
|
+
|
|
268
|
+
// Print results
|
|
269
|
+
console.log('\n' + '='.repeat(50));
|
|
270
|
+
console.log('Test Results (Responder)');
|
|
271
|
+
console.log('='.repeat(50));
|
|
272
|
+
console.log(`Messages sent: ${stats.messagesSent}`);
|
|
273
|
+
console.log(`Messages received: ${stats.messagesReceived}`);
|
|
274
|
+
console.log(`Reconnect events: ${stats.reconnectCount}`);
|
|
275
|
+
console.log(`Phases seen: ${Array.from(stats.phases).join(', ')}`);
|
|
276
|
+
console.log(`Errors: ${stats.errors.length > 0 ? stats.errors.join(', ') : 'None'}`);
|
|
277
|
+
console.log('='.repeat(50));
|
|
278
|
+
|
|
279
|
+
if (stats.phases.has('after-reconnect')) {
|
|
280
|
+
console.log('\n[PASS] Reconnection test passed!');
|
|
281
|
+
} else {
|
|
282
|
+
console.log('\n[FAIL] Reconnection test failed');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
resolve();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
ch.onClose(() => {
|
|
290
|
+
console.log('[EVENT] Channel closed');
|
|
291
|
+
});
|
|
292
|
+
}).catch(reject);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function main(): Promise<void> {
|
|
297
|
+
console.log('='.repeat(60));
|
|
298
|
+
console.log('WebRTC Reconnection Test');
|
|
299
|
+
console.log('='.repeat(60));
|
|
300
|
+
|
|
301
|
+
// Validate environment
|
|
302
|
+
if (process.env.PHYHUB_DIRECT !== 'true') {
|
|
303
|
+
console.error('ERROR: Set PHYHUB_DIRECT=true');
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const deviceId = process.env.DEVICE_ID || process.env.PHYGRID_DEVICE_ID;
|
|
308
|
+
const accessKey = process.env.ACCESS_KEY || process.env.PHYGRID_DEVICE_KEY;
|
|
309
|
+
const peerTwinId = process.env.PEER_TWIN_ID;
|
|
310
|
+
const role = process.env.ROLE?.toLowerCase();
|
|
311
|
+
|
|
312
|
+
if (!deviceId || !accessKey || !peerTwinId) {
|
|
313
|
+
console.error('ERROR: Missing DEVICE_ID, ACCESS_KEY, or PEER_TWIN_ID');
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (role !== 'initiator' && role !== 'responder') {
|
|
318
|
+
console.error('ERROR: ROLE must be "initiator" or "responder"');
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log('\nConfiguration:');
|
|
323
|
+
console.log(` Device ID: ${deviceId}`);
|
|
324
|
+
console.log(` Peer Twin ID: ${peerTwinId}`);
|
|
325
|
+
console.log(` Role: ${role}`);
|
|
326
|
+
console.log('');
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
if (role === 'initiator') {
|
|
330
|
+
await runInitiatorTest(peerTwinId);
|
|
331
|
+
} else {
|
|
332
|
+
await runResponderTest(peerTwinId);
|
|
333
|
+
}
|
|
334
|
+
process.exit(0);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error('\n[FAILED]', error);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (require.main === module) {
|
|
342
|
+
main();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export { runInitiatorTest, runResponderTest };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebRTC Test Harness
|
|
3
|
+
*
|
|
4
|
+
* A utility for testing WebRTC DataChannel and MediaStream connections
|
|
5
|
+
* between two twins using direct PhyHub connection (bypassing phyos).
|
|
6
|
+
*
|
|
7
|
+
* Prerequisites:
|
|
8
|
+
* 1. Get device credentials from CLI:
|
|
9
|
+
* phy dev list
|
|
10
|
+
* phy dev select # Select a device
|
|
11
|
+
* phy dev develop --export # Get PHYGRID_DEVICE_ID and PHYGRID_DEVICE_KEY
|
|
12
|
+
*
|
|
13
|
+
* 2. You need TWO devices for testing WebRTC between them
|
|
14
|
+
*
|
|
15
|
+
* Usage (two terminals):
|
|
16
|
+
*
|
|
17
|
+
* Terminal 1 (Device A - initiator):
|
|
18
|
+
* ```
|
|
19
|
+
* PHYHUB_DIRECT=true \
|
|
20
|
+
* DEVICE_ID=<device-a-id> \
|
|
21
|
+
* ACCESS_KEY=<device-a-key> \
|
|
22
|
+
* PEER_TWIN_ID=<device-b-id> \
|
|
23
|
+
* ROLE=initiator \
|
|
24
|
+
* npx ts-node src/test/webrtc-test-harness.ts
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Terminal 2 (Device B - responder):
|
|
28
|
+
* ```
|
|
29
|
+
* PHYHUB_DIRECT=true \
|
|
30
|
+
* DEVICE_ID=<device-b-id> \
|
|
31
|
+
* ACCESS_KEY=<device-b-key> \
|
|
32
|
+
* PEER_TWIN_ID=<device-a-id> \
|
|
33
|
+
* ROLE=responder \
|
|
34
|
+
* npx ts-node src/test/webrtc-test-harness.ts
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Environment variables:
|
|
38
|
+
* PHYHUB_DIRECT - Must be 'true' to enable direct connection
|
|
39
|
+
* DEVICE_ID - Your device ID (also used as twin ID)
|
|
40
|
+
* ACCESS_KEY - Your device access key
|
|
41
|
+
* PEER_TWIN_ID - The twin ID of the peer to connect to
|
|
42
|
+
* ROLE - 'initiator' or 'responder'
|
|
43
|
+
* PHYHUB_URL - PhyHub URL (optional, defaults to EU region)
|
|
44
|
+
* PHYHUB_REGION - 'eu', 'us', or 'local' (optional)
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { PhyHubClient } from '../index';
|
|
48
|
+
import { PhygridDataChannel } from '../services/webrtc/types';
|
|
49
|
+
|
|
50
|
+
async function runInitiator(peerTwinId: string): Promise<void> {
|
|
51
|
+
console.log('\n=== Running as INITIATOR ===\n');
|
|
52
|
+
|
|
53
|
+
// Connect to PhyHub
|
|
54
|
+
console.log('Connecting to PhyHub...');
|
|
55
|
+
const client = await PhyHubClient.connect();
|
|
56
|
+
console.log('Connected!\n');
|
|
57
|
+
|
|
58
|
+
// Get WebRTC manager with verbose mode
|
|
59
|
+
const manager = await client.getWebRTCManager({ verbose: true });
|
|
60
|
+
|
|
61
|
+
// Setup event listeners
|
|
62
|
+
manager.on('connected', (data) => {
|
|
63
|
+
console.log(`[EVENT] Connected to ${data.targetTwinId} (${data.connectionType})`);
|
|
64
|
+
});
|
|
65
|
+
manager.on('disconnected', (data) => {
|
|
66
|
+
console.log(`[EVENT] Disconnected from ${data.targetTwinId}`);
|
|
67
|
+
});
|
|
68
|
+
manager.on('error', (data) => {
|
|
69
|
+
console.error(`[EVENT] Error:`, data.error);
|
|
70
|
+
});
|
|
71
|
+
manager.on('reconnecting', (data) => {
|
|
72
|
+
console.log(`[EVENT] Reconnecting to ${data.targetTwinId} (attempt ${data.attempt})`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(`Creating DataChannel to peer: ${peerTwinId}`);
|
|
76
|
+
console.log('Waiting for responder to be ready...\n');
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const channel = await client.getDataChannel(peerTwinId);
|
|
80
|
+
console.log('DataChannel created successfully!\n');
|
|
81
|
+
|
|
82
|
+
// Setup message handler
|
|
83
|
+
channel.onMessage((data) => {
|
|
84
|
+
console.log('[RECEIVED]', data);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Send test messages
|
|
88
|
+
console.log('Sending test messages...\n');
|
|
89
|
+
|
|
90
|
+
for (let i = 1; i <= 5; i++) {
|
|
91
|
+
const msg = { type: 'test', count: i, timestamp: Date.now() };
|
|
92
|
+
console.log('[SENDING]', msg);
|
|
93
|
+
channel.send(msg);
|
|
94
|
+
await sleep(1000);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Wait for responses
|
|
98
|
+
console.log('\nWaiting for responses (10 seconds)...');
|
|
99
|
+
await sleep(10000);
|
|
100
|
+
|
|
101
|
+
console.log('\nTest complete. Closing channel...');
|
|
102
|
+
channel.close();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Failed to create DataChannel:', error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function runResponder(peerTwinId: string): Promise<void> {
|
|
110
|
+
console.log('\n=== Running as RESPONDER ===\n');
|
|
111
|
+
|
|
112
|
+
// Connect to PhyHub
|
|
113
|
+
console.log('Connecting to PhyHub...');
|
|
114
|
+
const client = await PhyHubClient.connect();
|
|
115
|
+
console.log('Connected!\n');
|
|
116
|
+
|
|
117
|
+
// Get WebRTC manager with verbose mode
|
|
118
|
+
const manager = await client.getWebRTCManager({ verbose: true });
|
|
119
|
+
|
|
120
|
+
// Setup event listeners
|
|
121
|
+
manager.on('connected', (data) => {
|
|
122
|
+
console.log(`[EVENT] Connected to ${data.targetTwinId} (${data.connectionType})`);
|
|
123
|
+
});
|
|
124
|
+
manager.on('disconnected', (data) => {
|
|
125
|
+
console.log(`[EVENT] Disconnected from ${data.targetTwinId}`);
|
|
126
|
+
});
|
|
127
|
+
manager.on('error', (data) => {
|
|
128
|
+
console.error(`[EVENT] Error:`, data.error);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
console.log(`Waiting for DataChannel from peer: ${peerTwinId}`);
|
|
132
|
+
console.log('Ready to accept connection...\n');
|
|
133
|
+
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const timeout = setTimeout(() => {
|
|
136
|
+
reject(new Error('Timeout waiting for incoming connection (60s)'));
|
|
137
|
+
}, 60000);
|
|
138
|
+
|
|
139
|
+
client.onDataChannel(peerTwinId, (channel: PhygridDataChannel) => {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
console.log('DataChannel received!\n');
|
|
142
|
+
|
|
143
|
+
let messageCount = 0;
|
|
144
|
+
|
|
145
|
+
channel.onMessage((data) => {
|
|
146
|
+
console.log('[RECEIVED]', data);
|
|
147
|
+
messageCount++;
|
|
148
|
+
|
|
149
|
+
// Echo back with modification
|
|
150
|
+
const response = {
|
|
151
|
+
type: 'echo',
|
|
152
|
+
original: data,
|
|
153
|
+
respondedAt: Date.now(),
|
|
154
|
+
};
|
|
155
|
+
console.log('[SENDING]', response);
|
|
156
|
+
channel.send(response);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
channel.onClose(() => {
|
|
160
|
+
console.log(`\nChannel closed. Received ${messageCount} messages.`);
|
|
161
|
+
resolve();
|
|
162
|
+
});
|
|
163
|
+
}).catch(reject);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function sleep(ms: number): Promise<void> {
|
|
168
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function main(): Promise<void> {
|
|
172
|
+
console.log('='.repeat(60));
|
|
173
|
+
console.log('WebRTC Test Harness');
|
|
174
|
+
console.log('='.repeat(60));
|
|
175
|
+
|
|
176
|
+
// Validate environment
|
|
177
|
+
if (process.env.PHYHUB_DIRECT !== 'true') {
|
|
178
|
+
console.error(`
|
|
179
|
+
ERROR: Direct connection mode not enabled.
|
|
180
|
+
|
|
181
|
+
Set PHYHUB_DIRECT=true to enable direct PhyHub connection.
|
|
182
|
+
`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const deviceId = process.env.DEVICE_ID || process.env.PHYGRID_DEVICE_ID;
|
|
187
|
+
const accessKey = process.env.ACCESS_KEY || process.env.PHYGRID_DEVICE_KEY;
|
|
188
|
+
const peerTwinId = process.env.PEER_TWIN_ID;
|
|
189
|
+
const role = process.env.ROLE?.toLowerCase();
|
|
190
|
+
|
|
191
|
+
if (!deviceId || !accessKey) {
|
|
192
|
+
console.error(`
|
|
193
|
+
ERROR: Missing device credentials.
|
|
194
|
+
|
|
195
|
+
Required environment variables:
|
|
196
|
+
DEVICE_ID (or PHYGRID_DEVICE_ID) - Your device ID
|
|
197
|
+
ACCESS_KEY (or PHYGRID_DEVICE_KEY) - Your device access key
|
|
198
|
+
|
|
199
|
+
To get these, run:
|
|
200
|
+
phy dev list
|
|
201
|
+
phy dev select
|
|
202
|
+
phy dev develop --export
|
|
203
|
+
`);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!peerTwinId) {
|
|
208
|
+
console.error(`
|
|
209
|
+
ERROR: Missing peer twin ID.
|
|
210
|
+
|
|
211
|
+
Set PEER_TWIN_ID to the twin ID of the device you want to connect to.
|
|
212
|
+
`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (role !== 'initiator' && role !== 'responder') {
|
|
217
|
+
console.error(`
|
|
218
|
+
ERROR: Invalid or missing ROLE.
|
|
219
|
+
|
|
220
|
+
Set ROLE to either:
|
|
221
|
+
- 'initiator' - The side that creates the DataChannel
|
|
222
|
+
- 'responder' - The side that waits for the DataChannel
|
|
223
|
+
`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log('\nConfiguration:');
|
|
228
|
+
console.log(` Device ID: ${deviceId}`);
|
|
229
|
+
console.log(` Peer Twin ID: ${peerTwinId}`);
|
|
230
|
+
console.log(` Role: ${role}`);
|
|
231
|
+
console.log(` PhyHub URL: ${process.env.PHYHUB_URL || '(default EU)'}`);
|
|
232
|
+
console.log('');
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
if (role === 'initiator') {
|
|
236
|
+
await runInitiator(peerTwinId);
|
|
237
|
+
} else {
|
|
238
|
+
await runResponder(peerTwinId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log('\n[SUCCESS] Test completed successfully!');
|
|
242
|
+
process.exit(0);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error('\n[FAILED] Test failed:', error);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Run if executed directly
|
|
250
|
+
if (require.main === module) {
|
|
251
|
+
main();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export { runInitiator, runResponder };
|