@phystack/hub-client 4.5.19-dev → 4.5.21-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,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Communication Comprehensive Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Tests ALL communication methods between two twins:
|
|
5
|
+
*
|
|
6
|
+
* Hub-based (stateless):
|
|
7
|
+
* 1. Events - fire-and-forget messaging via instance.emit/on
|
|
8
|
+
* 2. Actions - request-response with callbacks
|
|
9
|
+
*
|
|
10
|
+
* WebRTC P2P (stateful):
|
|
11
|
+
* 3. DataChannel - default channel
|
|
12
|
+
* 4. DataChannel - named channels
|
|
13
|
+
* 5. MediaStream - connection establishment
|
|
14
|
+
*
|
|
15
|
+
* Usage (two terminals or two hosts via webrtc-test.sh):
|
|
16
|
+
*
|
|
17
|
+
* Terminal 1 (initiator):
|
|
18
|
+
* ```
|
|
19
|
+
* PHYHUB_DIRECT=true \
|
|
20
|
+
* DEVICE_ID=<device-a-id> \
|
|
21
|
+
* ACCESS_KEY=<device-a-key> \
|
|
22
|
+
* PEER_TWIN_ID=<device-b-twin-id> \
|
|
23
|
+
* ROLE=initiator \
|
|
24
|
+
* node dist/test/communication-comprehensive-test.js
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Terminal 2 (responder):
|
|
28
|
+
* ```
|
|
29
|
+
* PHYHUB_DIRECT=true \
|
|
30
|
+
* DEVICE_ID=<device-b-id> \
|
|
31
|
+
* ACCESS_KEY=<device-b-key> \
|
|
32
|
+
* PEER_TWIN_ID=<device-a-twin-id> \
|
|
33
|
+
* ROLE=responder \
|
|
34
|
+
* node dist/test/communication-comprehensive-test.js
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { PhyHubClient, Instance } from '../index';
|
|
39
|
+
import { PhygridDataChannel } from '../services/webrtc/types';
|
|
40
|
+
|
|
41
|
+
interface TestResult {
|
|
42
|
+
name: string;
|
|
43
|
+
passed: boolean;
|
|
44
|
+
details: string;
|
|
45
|
+
duration: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const MESSAGES_PER_TEST = 3;
|
|
49
|
+
const results: TestResult[] = [];
|
|
50
|
+
|
|
51
|
+
function log(msg: string): void {
|
|
52
|
+
console.log(`[${new Date().toISOString().split('T')[1].slice(0, 8)}] ${msg}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sleep(ms: number): Promise<void> {
|
|
56
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function addResult(name: string, passed: boolean, details: string, startTime: number): void {
|
|
60
|
+
const duration = Date.now() - startTime;
|
|
61
|
+
results.push({ name, passed, details, duration });
|
|
62
|
+
const status = passed ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
|
|
63
|
+
log(`[${status}] ${name}: ${details} (${duration}ms)`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// Test 1: Events (Fire-and-Forget via Hub)
|
|
68
|
+
// =============================================================================
|
|
69
|
+
async function testEvents(
|
|
70
|
+
client: PhyHubClient,
|
|
71
|
+
instance: Instance,
|
|
72
|
+
peerTwinId: string,
|
|
73
|
+
isInitiator: boolean
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
const testName = 'Events (Hub-based)';
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
log(`\n=== Test 1: ${testName} ===`);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
let receivedCount = 0;
|
|
81
|
+
const receivedMessages: any[] = [];
|
|
82
|
+
|
|
83
|
+
// Subscribe to peer's messages
|
|
84
|
+
await client.subscribeTwin(peerTwinId);
|
|
85
|
+
log(`Subscribed to peer twin: ${peerTwinId}`);
|
|
86
|
+
|
|
87
|
+
// Register event listener
|
|
88
|
+
instance.on('test-event', (data: any) => {
|
|
89
|
+
receivedCount++;
|
|
90
|
+
receivedMessages.push(data);
|
|
91
|
+
log(`[EVENT RECEIVED] ${JSON.stringify(data)}`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (isInitiator) {
|
|
95
|
+
// Wait for responder to be ready
|
|
96
|
+
log('Waiting for responder to set up listeners...');
|
|
97
|
+
await sleep(3000);
|
|
98
|
+
|
|
99
|
+
// Send test events to peer
|
|
100
|
+
log('Sending test events...');
|
|
101
|
+
for (let i = 0; i < MESSAGES_PER_TEST; i++) {
|
|
102
|
+
const eventData = { type: 'test', count: i + 1, timestamp: Date.now() };
|
|
103
|
+
log(`[SENDING EVENT] ${JSON.stringify(eventData)}`);
|
|
104
|
+
// Fire-and-forget: no response expected, no promise to catch
|
|
105
|
+
instance.to(peerTwinId).emit('test-event', eventData);
|
|
106
|
+
await sleep(500);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Wait for responses/echoes
|
|
110
|
+
await sleep(5000);
|
|
111
|
+
|
|
112
|
+
// Check if we received echo events from responder
|
|
113
|
+
const passed = receivedCount >= MESSAGES_PER_TEST;
|
|
114
|
+
addResult(testName, passed,
|
|
115
|
+
`Sent ${MESSAGES_PER_TEST}, received echoes: ${receivedCount}`, startTime);
|
|
116
|
+
} else {
|
|
117
|
+
// Responder: Listen for events and echo them back
|
|
118
|
+
log('Waiting for events from initiator...');
|
|
119
|
+
|
|
120
|
+
// Set up echo listener
|
|
121
|
+
instance.on('test-event', (data: any) => {
|
|
122
|
+
// Echo back to initiator (fire-and-forget: no promise to catch)
|
|
123
|
+
const echoData = { ...data, echo: true, echoedAt: Date.now() };
|
|
124
|
+
log(`[ECHOING EVENT] ${JSON.stringify(echoData)}`);
|
|
125
|
+
instance.to(peerTwinId).emit('test-event', echoData);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Wait for test to complete
|
|
129
|
+
await sleep(10000);
|
|
130
|
+
|
|
131
|
+
const passed = receivedCount >= MESSAGES_PER_TEST;
|
|
132
|
+
addResult(testName, passed,
|
|
133
|
+
`Received ${receivedCount} events from initiator`, startTime);
|
|
134
|
+
}
|
|
135
|
+
} catch (error: any) {
|
|
136
|
+
addResult(testName, false, error.message, startTime);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// =============================================================================
|
|
141
|
+
// Test 2: Actions (Request-Response via Hub)
|
|
142
|
+
// =============================================================================
|
|
143
|
+
async function testActions(
|
|
144
|
+
_client: PhyHubClient,
|
|
145
|
+
instance: Instance,
|
|
146
|
+
peerTwinId: string,
|
|
147
|
+
isInitiator: boolean
|
|
148
|
+
): Promise<void> {
|
|
149
|
+
const testName = 'Actions (Hub-based)';
|
|
150
|
+
const startTime = Date.now();
|
|
151
|
+
log(`\n=== Test 2: ${testName} ===`);
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
let successfulActions = 0;
|
|
155
|
+
let failedActions = 0;
|
|
156
|
+
|
|
157
|
+
if (isInitiator) {
|
|
158
|
+
// Wait for responder to set up action handlers
|
|
159
|
+
log('Waiting for responder to set up action handlers...');
|
|
160
|
+
await sleep(3000);
|
|
161
|
+
|
|
162
|
+
// Test: instance.to(peerTwinId).emit() with callback (action pattern)
|
|
163
|
+
log('Testing instance.to(peerTwinId).emit() with callback (action pattern)...');
|
|
164
|
+
for (let i = 0; i < MESSAGES_PER_TEST; i++) {
|
|
165
|
+
const actionType = 'test-action';
|
|
166
|
+
const actionPayload = {
|
|
167
|
+
command: 'process',
|
|
168
|
+
value: i * 10,
|
|
169
|
+
timestamp: Date.now()
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
log(`[SENDING ACTION ${i + 1}] ${actionType}: ${JSON.stringify(actionPayload)}`);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// emit with callback returns a Promise (action pattern)
|
|
176
|
+
const result = await instance.to(peerTwinId).emit(actionType, actionPayload, () => {});
|
|
177
|
+
log(`[ACTION RESPONSE] ${JSON.stringify(result)}`);
|
|
178
|
+
if (result?.status === 'success') {
|
|
179
|
+
successfulActions++;
|
|
180
|
+
} else {
|
|
181
|
+
failedActions++;
|
|
182
|
+
}
|
|
183
|
+
} catch (err: any) {
|
|
184
|
+
log(`[ACTION ERROR] ${err.message || JSON.stringify(err)}`);
|
|
185
|
+
failedActions++;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await sleep(500);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const passed = successfulActions >= MESSAGES_PER_TEST;
|
|
192
|
+
addResult(testName, passed,
|
|
193
|
+
`Success: ${successfulActions}, Failed: ${failedActions}`, startTime);
|
|
194
|
+
} else {
|
|
195
|
+
// Responder: Handle requests using instance.on()
|
|
196
|
+
log('Setting up request handlers via instance.on()...');
|
|
197
|
+
|
|
198
|
+
// Register request handler - the respond callback allows sending a response
|
|
199
|
+
instance.on('test-action', (message: any, respond?: (result: any) => void) => {
|
|
200
|
+
log(`[REQUEST RECEIVED] ${JSON.stringify(message)}`);
|
|
201
|
+
// Send success response
|
|
202
|
+
if (respond) {
|
|
203
|
+
respond({ status: 'success', message: 'Request processed successfully' });
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Wait for test to complete
|
|
208
|
+
await sleep(15000);
|
|
209
|
+
|
|
210
|
+
addResult(testName, true, 'Request handler registered and responded', startTime);
|
|
211
|
+
}
|
|
212
|
+
} catch (error: any) {
|
|
213
|
+
addResult(testName, false, error.message, startTime);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// =============================================================================
|
|
218
|
+
// Test 3: DataChannel Default (Unnamed)
|
|
219
|
+
// =============================================================================
|
|
220
|
+
async function testDataChannelDefault(
|
|
221
|
+
client: PhyHubClient,
|
|
222
|
+
peerTwinId: string,
|
|
223
|
+
isInitiator: boolean
|
|
224
|
+
): Promise<void> {
|
|
225
|
+
const testName = 'DataChannel Default (P2P)';
|
|
226
|
+
const startTime = Date.now();
|
|
227
|
+
log(`\n=== Test 3: ${testName} ===`);
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
let channel: PhygridDataChannel;
|
|
231
|
+
let receivedCount = 0;
|
|
232
|
+
|
|
233
|
+
if (isInitiator) {
|
|
234
|
+
channel = await client.getDataChannel(peerTwinId);
|
|
235
|
+
log(`Created default channel. Name: ${channel.getChannelName()}`);
|
|
236
|
+
|
|
237
|
+
if (channel.getChannelName() !== 'default') {
|
|
238
|
+
throw new Error(`Expected channel name 'default', got '${channel.getChannelName()}'`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
channel.onMessage((data) => {
|
|
242
|
+
receivedCount++;
|
|
243
|
+
log(`[DC RECEIVED] ${JSON.stringify(data)}`);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await sleep(2000);
|
|
247
|
+
|
|
248
|
+
for (let i = 0; i < MESSAGES_PER_TEST; i++) {
|
|
249
|
+
const msg = { test: 'default', num: i, timestamp: Date.now() };
|
|
250
|
+
log(`[DC SENDING] ${JSON.stringify(msg)}`);
|
|
251
|
+
channel.send(msg);
|
|
252
|
+
await sleep(300);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await sleep(3000);
|
|
256
|
+
|
|
257
|
+
addResult(testName, receivedCount >= MESSAGES_PER_TEST,
|
|
258
|
+
`Sent ${MESSAGES_PER_TEST}, received echoes: ${receivedCount}`, startTime);
|
|
259
|
+
|
|
260
|
+
channel.close();
|
|
261
|
+
} else {
|
|
262
|
+
await new Promise<void>((resolve, reject) => {
|
|
263
|
+
const timeout = setTimeout(() => reject(new Error('Timeout')), 30000);
|
|
264
|
+
|
|
265
|
+
client.onDataChannel(peerTwinId, (ch) => {
|
|
266
|
+
clearTimeout(timeout);
|
|
267
|
+
channel = ch;
|
|
268
|
+
log(`Received default channel. Name: ${ch.getChannelName()}`);
|
|
269
|
+
|
|
270
|
+
ch.onMessage((data) => {
|
|
271
|
+
receivedCount++;
|
|
272
|
+
log(`[DC RECEIVED] ${JSON.stringify(data)}`);
|
|
273
|
+
ch.send({ echo: data });
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
addResult(testName, receivedCount >= MESSAGES_PER_TEST,
|
|
278
|
+
`Received ${receivedCount} messages`, startTime);
|
|
279
|
+
resolve();
|
|
280
|
+
}, 8000);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
} catch (error: any) {
|
|
285
|
+
addResult(testName, false, error.message, startTime);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// =============================================================================
|
|
290
|
+
// Test 4: DataChannel Named Channels
|
|
291
|
+
// =============================================================================
|
|
292
|
+
async function testDataChannelNamed(
|
|
293
|
+
client: PhyHubClient,
|
|
294
|
+
peerTwinId: string,
|
|
295
|
+
isInitiator: boolean
|
|
296
|
+
): Promise<void> {
|
|
297
|
+
const testName = 'DataChannel Named (P2P)';
|
|
298
|
+
const startTime = Date.now();
|
|
299
|
+
log(`\n=== Test 4: ${testName} ===`);
|
|
300
|
+
|
|
301
|
+
const channelNames = ['control', 'data'];
|
|
302
|
+
const receivedByChannel: Map<string, number> = new Map();
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
if (isInitiator) {
|
|
306
|
+
const channels: PhygridDataChannel[] = [];
|
|
307
|
+
|
|
308
|
+
for (const name of channelNames) {
|
|
309
|
+
const ch = await client.getDataChannel(peerTwinId, name);
|
|
310
|
+
log(`Created channel '${name}'. Reported name: ${ch.getChannelName()}`);
|
|
311
|
+
|
|
312
|
+
if (ch.getChannelName() !== name) {
|
|
313
|
+
throw new Error(`Expected channel name '${name}', got '${ch.getChannelName()}'`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
receivedByChannel.set(name, 0);
|
|
317
|
+
ch.onMessage(() => {
|
|
318
|
+
receivedByChannel.set(name, (receivedByChannel.get(name) || 0) + 1);
|
|
319
|
+
});
|
|
320
|
+
channels.push(ch);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
await sleep(3000);
|
|
324
|
+
|
|
325
|
+
for (let i = 0; i < channels.length; i++) {
|
|
326
|
+
const ch = channels[i];
|
|
327
|
+
for (let j = 0; j < MESSAGES_PER_TEST; j++) {
|
|
328
|
+
ch.send({ channel: channelNames[i], num: j });
|
|
329
|
+
await sleep(100);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
await sleep(3000);
|
|
334
|
+
|
|
335
|
+
let allPassed = true;
|
|
336
|
+
for (const name of channelNames) {
|
|
337
|
+
const count = receivedByChannel.get(name) || 0;
|
|
338
|
+
if (count < MESSAGES_PER_TEST) allPassed = false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const details = channelNames.map(n => `${n}:${receivedByChannel.get(n)}`).join(', ');
|
|
342
|
+
addResult(testName, allPassed, details, startTime);
|
|
343
|
+
|
|
344
|
+
channels.forEach(ch => ch.close());
|
|
345
|
+
} else {
|
|
346
|
+
const promises = channelNames.map(name => new Promise<void>((resolve, reject) => {
|
|
347
|
+
const timeout = setTimeout(() => reject(new Error(`Timeout for ${name}`)), 30000);
|
|
348
|
+
receivedByChannel.set(name, 0);
|
|
349
|
+
|
|
350
|
+
client.onDataChannel(peerTwinId, (ch) => {
|
|
351
|
+
clearTimeout(timeout);
|
|
352
|
+
log(`Received channel '${name}'. Reported: ${ch.getChannelName()}`);
|
|
353
|
+
|
|
354
|
+
ch.onMessage((data) => {
|
|
355
|
+
receivedByChannel.set(name, (receivedByChannel.get(name) || 0) + 1);
|
|
356
|
+
ch.send({ echo: data });
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
setTimeout(resolve, 10000);
|
|
360
|
+
}, name);
|
|
361
|
+
}));
|
|
362
|
+
|
|
363
|
+
await Promise.all(promises);
|
|
364
|
+
|
|
365
|
+
const details = channelNames.map(n => `${n}:${receivedByChannel.get(n)}`).join(', ');
|
|
366
|
+
const allPassed = channelNames.every(n => (receivedByChannel.get(n) || 0) >= MESSAGES_PER_TEST);
|
|
367
|
+
addResult(testName, allPassed, details, startTime);
|
|
368
|
+
}
|
|
369
|
+
} catch (error: any) {
|
|
370
|
+
addResult(testName, false, error.message, startTime);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// =============================================================================
|
|
375
|
+
// Test 5: MediaStream Connection
|
|
376
|
+
// =============================================================================
|
|
377
|
+
async function testMediaStreamConnection(
|
|
378
|
+
client: PhyHubClient,
|
|
379
|
+
peerTwinId: string,
|
|
380
|
+
isInitiator: boolean
|
|
381
|
+
): Promise<void> {
|
|
382
|
+
const testName = 'MediaStream (P2P)';
|
|
383
|
+
const startTime = Date.now();
|
|
384
|
+
log(`\n=== Test 5: ${testName} ===`);
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
if (isInitiator) {
|
|
388
|
+
const { stream, close } = await client.getMediaStream(peerTwinId);
|
|
389
|
+
log(`MediaStream created. Target: ${stream.getTargetTwinId()}, Channel: ${stream.getChannelName()}`);
|
|
390
|
+
|
|
391
|
+
if (stream.getChannelName() !== 'default') {
|
|
392
|
+
throw new Error(`Expected channel name 'default', got '${stream.getChannelName()}'`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
stream.onTrack((track) => {
|
|
396
|
+
log(`Received track: ${track.kind}`);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
await sleep(5000);
|
|
400
|
+
|
|
401
|
+
addResult(testName, true,
|
|
402
|
+
`Connected to ${stream.getTargetTwinId()}`, startTime);
|
|
403
|
+
|
|
404
|
+
close();
|
|
405
|
+
} else {
|
|
406
|
+
await new Promise<void>((resolve, reject) => {
|
|
407
|
+
const timeout = setTimeout(() => reject(new Error('Timeout')), 30000);
|
|
408
|
+
|
|
409
|
+
client.onMediaStream(peerTwinId, (stream) => {
|
|
410
|
+
clearTimeout(timeout);
|
|
411
|
+
log(`MediaStream received. Channel: ${stream.getChannelName()}`);
|
|
412
|
+
|
|
413
|
+
stream.onTrack((track) => {
|
|
414
|
+
log(`Track received: ${track.kind}`);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
setTimeout(() => {
|
|
418
|
+
addResult(testName, true,
|
|
419
|
+
`Connected from ${stream.getTargetTwinId()}`, startTime);
|
|
420
|
+
resolve();
|
|
421
|
+
}, 5000);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
} catch (error: any) {
|
|
426
|
+
addResult(testName, false, error.message, startTime);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// =============================================================================
|
|
431
|
+
// Main
|
|
432
|
+
// =============================================================================
|
|
433
|
+
async function main(): Promise<void> {
|
|
434
|
+
console.log('='.repeat(65));
|
|
435
|
+
console.log(' Communication Comprehensive Test Suite');
|
|
436
|
+
console.log(' Hub-based: Events, Actions | P2P: DataChannels, MediaStreams');
|
|
437
|
+
console.log('='.repeat(65));
|
|
438
|
+
|
|
439
|
+
if (process.env.PHYHUB_DIRECT !== 'true') {
|
|
440
|
+
console.error('ERROR: Set PHYHUB_DIRECT=true');
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const peerTwinId = process.env.PEER_TWIN_ID;
|
|
445
|
+
const role = process.env.ROLE?.toLowerCase();
|
|
446
|
+
const testFilter = process.env.TEST;
|
|
447
|
+
|
|
448
|
+
if (!peerTwinId) {
|
|
449
|
+
console.error('ERROR: Set PEER_TWIN_ID');
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (role !== 'initiator' && role !== 'responder') {
|
|
454
|
+
console.error('ERROR: Set ROLE to initiator or responder');
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const isInitiator = role === 'initiator';
|
|
459
|
+
log(`Role: ${role}`);
|
|
460
|
+
log(`Peer Twin ID: ${peerTwinId}`);
|
|
461
|
+
if (testFilter) log(`Running only: ${testFilter}`);
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
log('\nConnecting to PhyHub...');
|
|
465
|
+
const client = await PhyHubClient.connect();
|
|
466
|
+
log('Connected!');
|
|
467
|
+
|
|
468
|
+
const instance = await client.getInstance();
|
|
469
|
+
log(`Instance ID: ${instance.id}\n`);
|
|
470
|
+
|
|
471
|
+
// Define tests
|
|
472
|
+
type TestFunction = (
|
|
473
|
+
client: PhyHubClient,
|
|
474
|
+
instanceOrPeer: any,
|
|
475
|
+
peerOrBool: any,
|
|
476
|
+
isInitiator?: boolean
|
|
477
|
+
) => Promise<void>;
|
|
478
|
+
|
|
479
|
+
const tests: Array<{ name: string; fn: TestFunction; needsInstance: boolean }> = [
|
|
480
|
+
{ name: 'events', fn: testEvents as TestFunction, needsInstance: true },
|
|
481
|
+
{ name: 'actions', fn: testActions as TestFunction, needsInstance: true },
|
|
482
|
+
{ name: 'datachannel', fn: testDataChannelDefault as TestFunction, needsInstance: false },
|
|
483
|
+
{ name: 'datachannel-named', fn: testDataChannelNamed as TestFunction, needsInstance: false },
|
|
484
|
+
{ name: 'mediastream', fn: testMediaStreamConnection as TestFunction, needsInstance: false },
|
|
485
|
+
];
|
|
486
|
+
|
|
487
|
+
for (const test of tests) {
|
|
488
|
+
if (!testFilter || test.name === testFilter) {
|
|
489
|
+
if (test.needsInstance) {
|
|
490
|
+
await test.fn(client, instance, peerTwinId, isInitiator);
|
|
491
|
+
} else {
|
|
492
|
+
await test.fn(client, peerTwinId, isInitiator, undefined);
|
|
493
|
+
}
|
|
494
|
+
await sleep(2000);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Print summary
|
|
499
|
+
console.log('\n' + '='.repeat(65));
|
|
500
|
+
console.log('TEST SUMMARY');
|
|
501
|
+
console.log('='.repeat(65));
|
|
502
|
+
console.log('Test'.padEnd(40) + 'Status'.padEnd(10) + 'Time');
|
|
503
|
+
console.log('-'.repeat(65));
|
|
504
|
+
|
|
505
|
+
let passed = 0;
|
|
506
|
+
let failed = 0;
|
|
507
|
+
for (const r of results) {
|
|
508
|
+
const status = r.passed ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
|
|
509
|
+
console.log(r.name.padEnd(40) + status.padEnd(15) + `${r.duration}ms`);
|
|
510
|
+
if (r.passed) passed++;
|
|
511
|
+
else failed++;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
console.log('-'.repeat(65));
|
|
515
|
+
const totalTime = results.reduce((sum, r) => sum + r.duration, 0);
|
|
516
|
+
console.log(`Total: ${passed} passed, ${failed} failed (${totalTime}ms)`);
|
|
517
|
+
console.log('='.repeat(65));
|
|
518
|
+
|
|
519
|
+
// Give time for final cleanup
|
|
520
|
+
await sleep(1000);
|
|
521
|
+
|
|
522
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.error('\n[FATAL]', error);
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (require.main === module) {
|
|
530
|
+
main();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export { testEvents, testActions, testDataChannelDefault, testDataChannelNamed, testMediaStreamConnection };
|