android-emulator-webrtc 1.0.18 → 2.0.1

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.
Files changed (69) hide show
  1. package/README.md +93 -204
  2. package/dist/components/emulator/emulator.d.ts +64 -0
  3. package/dist/components/emulator/emulator.js +161 -173
  4. package/dist/components/emulator/net/emulator_status.d.ts +45 -0
  5. package/dist/components/emulator/net/emulator_status.js +35 -42
  6. package/dist/components/emulator/net/logcat.js +37 -54
  7. package/dist/components/emulator/net/logger.d.ts +7 -0
  8. package/dist/components/emulator/net/logger.js +20 -0
  9. package/dist/components/emulator/net/ws_jsep_protocol_driver.d.ts +213 -0
  10. package/dist/components/emulator/net/ws_jsep_protocol_driver.js +640 -0
  11. package/dist/components/emulator/views/event_handler.d.ts +37 -0
  12. package/dist/components/emulator/views/event_handler.js +274 -260
  13. package/dist/components/emulator/views/webrtc_view.d.ts +27 -0
  14. package/dist/components/emulator/views/webrtc_view.js +42 -20
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.js +0 -7
  17. package/dist/proto/emulator_controller_pb.d.ts +1 -0
  18. package/dist/proto/emulator_controller_pb.js +146 -287
  19. package/dist/src/components/emulator/emulator.d.ts +64 -0
  20. package/dist/src/components/emulator/net/emulator_status.d.ts +45 -0
  21. package/dist/src/components/emulator/net/logger.d.ts +7 -0
  22. package/dist/src/components/emulator/net/ws_jsep_protocol_driver.d.ts +213 -0
  23. package/dist/src/components/emulator/views/event_handler.d.ts +37 -0
  24. package/dist/src/components/emulator/views/webrtc_view.d.ts +27 -0
  25. package/dist/src/index.d.ts +8 -0
  26. package/dist/src/proto/emulator_controller_pb.d.ts +1 -0
  27. package/package.json +17 -17
  28. package/proto/emulator_controller.proto +746 -181
  29. package/.vscode/launch.json +0 -21
  30. package/.vscode/settings.json +0 -3
  31. package/CONTRIBUTING.md +0 -28
  32. package/Makefile +0 -123
  33. package/android-emulator-webrtc.code-workspace +0 -7
  34. package/api_descriptor.pb +0 -0
  35. package/babel.config.js +0 -16
  36. package/cloudbuild.yaml +0 -41
  37. package/cloudbuilders/.gcloudignore +0 -1
  38. package/cloudbuilders/Dockerfile +0 -20
  39. package/cloudbuilders/README.md +0 -29
  40. package/cloudbuilders/cloudbuild.yaml +0 -19
  41. package/dist/components/emulator/net/jsep_protocol_driver.js +0 -365
  42. package/dist/components/emulator/views/simple_png_view.js +0 -110
  43. package/dist/proto/emulator_controller_grpc_web_pb.js +0 -1601
  44. package/dist/proto/emulator_web_client.js +0 -176
  45. package/dist/proto/rtc_service_grpc_web_pb.js +0 -231
  46. package/dist/proto/rtc_service_pb.js +0 -338
  47. package/emulator/index.js +0 -1
  48. package/eslint_prefix.py +0 -37
  49. package/gen_md_doc.js +0 -40
  50. package/npmrc.enc +0 -0
  51. package/proto/rtc_service.proto +0 -117
  52. package/protoc-plugin/Makefile +0 -31
  53. package/protoc-plugin/grpc_generator.cc +0 -1755
  54. package/src/components/emulator/emulator.js +0 -217
  55. package/src/components/emulator/net/emulator_status.js +0 -94
  56. package/src/components/emulator/net/jsep_protocol_driver.js +0 -364
  57. package/src/components/emulator/net/logcat.js +0 -155
  58. package/src/components/emulator/views/event_handler.js +0 -268
  59. package/src/components/emulator/views/simple_png_view.js +0 -105
  60. package/src/components/emulator/views/webrtc_view.js +0 -142
  61. package/src/index.js +0 -20
  62. package/src/proto/emulator_web_client.js +0 -140
  63. package/test/cloudbuild.yaml +0 -41
  64. package/test/emulator.test.js +0 -125
  65. package/test/event_handler.test.js +0 -91
  66. package/test/fake_events.js +0 -50
  67. package/test/jsep_protocol_driver.test.js +0 -228
  68. package/test/simple_png_view.test.js +0 -145
  69. package/test/touch_event_handler.test.js +0 -118
@@ -0,0 +1,640 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports["default"] = void 0;
8
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
11
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
12
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
13
+ var _logger = _interopRequireDefault(require("./logger"));
14
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
15
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } /*
16
+ * Copyright 2026 The Android Open Source Project
17
+ *
18
+ * Licensed under the Apache License, Version 2.0 (the "License");
19
+ * you may not use this file except in compliance with the License.
20
+ * You may obtain a copy of the License at
21
+ *
22
+ * http://www.apache.org/licenses/LICENSE-2.0
23
+ *
24
+ * Unless required by applicable law or agreed to in writing, software
25
+ * distributed under the License is distributed on an "AS IS" BASIS,
26
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27
+ * See the License for the specific language governing permissions and
28
+ * limitations under the License.
29
+ */ /*
30
+ * Copyright 2026 The Android Open Source Project
31
+ *
32
+ * Licensed under the Apache License, Version 2.0 (the "License");
33
+ * you may not use this file except in compliance with the License.
34
+ * You may obtain a copy of the License at
35
+ *
36
+ * http://www.apache.org/licenses/LICENSE-2.0
37
+ *
38
+ * Unless required by applicable law or agreed to in writing, software
39
+ * distributed under the License is distributed on an "AS IS" BASIS,
40
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
41
+ * See the License for the specific language governing permissions and
42
+ * limitations under the License.
43
+ */
44
+ /**
45
+ * A JSEP protocol driver that uses WebSockets for signaling.
46
+ *
47
+ * @export
48
+ * @class WsJsepProtocol
49
+ */
50
+ var WsJsepProtocol = exports["default"] = /*#__PURE__*/function () {
51
+ /**
52
+ * Creates an instance of WsJsepProtocol.
53
+ *
54
+ * @param {string} wsUrl The WebSocket JSEP signaling URL.
55
+ * @param {Object} [emulator=null] Fallback emulator controller for sending events when WebRTC is unavailable.
56
+ * @param {Object} [config={}] Configuration options.
57
+ * @param {boolean} [config.enableLogging=false] Whether verbose signaling logging is enabled.
58
+ * @param {function} [config.onError] Callback invoked when an error occurs.
59
+ * @param {number} [config.maxReconnectAttempts=5] Maximum number of reconnection attempts.
60
+ * @param {number} [config.reconnectDelay=1000] Initial delay for reconnection in milliseconds.
61
+ * @param {number} [config.reconnectBackoffFactor=2] Exponential backoff factor.
62
+ */
63
+ function WsJsepProtocol(wsUrl) {
64
+ var _this = this,
65
+ _this$config$maxRecon,
66
+ _this$config$reconnec,
67
+ _this$config$reconnec2;
68
+ var emulator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
69
+ var _config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
70
+ (0, _classCallCheck2["default"])(this, WsJsepProtocol);
71
+ /**
72
+ * Establishes the WebSocket connection and starts the signaling process.
73
+ * Cleans up any existing connection beforehand.
74
+ *
75
+ * @param {Object} [callbacks] Callbacks for stream lifecycle events.
76
+ * @param {function(MediaStreamTrack): void} [callbacks.onConnected] Called when a media track is received.
77
+ * @param {function(): void} [callbacks.onDisconnected] Called when the stream is disconnected.
78
+ */
79
+ (0, _defineProperty2["default"])(this, "startStream", function () {
80
+ var callbacks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
81
+ _this.cleanup();
82
+ _this.reconnectAttempts = 0;
83
+ _this.onConnected = callbacks.onConnected || _this.onConnected;
84
+ _this.onDisconnected = callbacks.onDisconnected || _this.onDisconnected;
85
+ _this.connected = true;
86
+ _this._connect();
87
+ });
88
+ /**
89
+ * Internal method to establish WebSocket connection.
90
+ *
91
+ * @private
92
+ */
93
+ (0, _defineProperty2["default"])(this, "_connect", function () {
94
+ if (_this.reconnectTimeoutId) {
95
+ clearTimeout(_this.reconnectTimeoutId);
96
+ _this.reconnectTimeoutId = null;
97
+ }
98
+ _this.ws = new WebSocket(_this.wsUrl);
99
+ _this.ws.onmessage = _this._handleWsMessage;
100
+ _this.ws.onclose = _this._handleWsClose;
101
+ _this.ws.onerror = _this._handleWsError;
102
+ });
103
+ /**
104
+ * Queues a reconnection attempt with exponential backoff.
105
+ *
106
+ * @private
107
+ */
108
+ (0, _defineProperty2["default"])(this, "_queueReconnect", function () {
109
+ if (_this.reconnectTimeoutId) return;
110
+ _this.reconnectAttempts++;
111
+ if (_this.reconnectAttempts > _this.maxReconnectAttempts) {
112
+ _logger["default"].error("Max reconnect attempts (".concat(_this.maxReconnectAttempts, ") reached. Giving up."));
113
+ if (_this.onError) {
114
+ _this.onError(new Error("Connection failed: Max reconnect attempts reached."));
115
+ }
116
+ _this.disconnect();
117
+ return;
118
+ }
119
+ var delay = _this.reconnectDelay * Math.pow(_this.reconnectBackoffFactor, _this.reconnectAttempts - 1);
120
+ _logger["default"].info("Queueing reconnect attempt ".concat(_this.reconnectAttempts, " in ").concat(delay, "ms"));
121
+ _this.reconnectTimeoutId = setTimeout(function () {
122
+ _this.reconnectTimeoutId = null;
123
+ // Partially disconnect to clean up the failed peer connection/websocket,
124
+ // but keep this.connected = true so we know we want to reconnect.
125
+ _this._disconnectState();
126
+ _this._connect();
127
+ }, delay);
128
+ });
129
+ /**
130
+ * Internal handler for incoming WebSocket messages. Parses the signal
131
+ * and queues it for sequential processing.
132
+ *
133
+ * @private
134
+ * @param {MessageEvent} event The WebSocket message event.
135
+ */
136
+ (0, _defineProperty2["default"])(this, "_handleWsMessage", function (event) {
137
+ try {
138
+ var signal = JSON.parse(event.data);
139
+ // Push to queue instead of handling immediately
140
+ _this.signalQueue.push(signal);
141
+ _this._processSignalQueue();
142
+ } catch (e) {
143
+ _logger["default"].error("Failed to handle WS message:", e, "Raw payload:", event.data);
144
+ }
145
+ });
146
+ /**
147
+ * Sequentially processes JSEP signals from the queue.
148
+ *
149
+ * @private
150
+ */
151
+ (0, _defineProperty2["default"])(this, "_processSignalQueue", /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee() {
152
+ var signal;
153
+ return _regenerator["default"].wrap(function _callee$(_context) {
154
+ while (1) switch (_context.prev = _context.next) {
155
+ case 0:
156
+ if (!_this.isProcessingSignal) {
157
+ _context.next = 2;
158
+ break;
159
+ }
160
+ return _context.abrupt("return");
161
+ case 2:
162
+ _this.isProcessingSignal = true;
163
+ case 3:
164
+ if (!(_this.signalQueue.length > 0)) {
165
+ _context.next = 9;
166
+ break;
167
+ }
168
+ signal = _this.signalQueue.shift();
169
+ _context.next = 7;
170
+ return _this._handleSignal(signal);
171
+ case 7:
172
+ _context.next = 3;
173
+ break;
174
+ case 9:
175
+ _this.isProcessingSignal = false;
176
+ case 10:
177
+ case "end":
178
+ return _context.stop();
179
+ }
180
+ }, _callee);
181
+ })));
182
+ /**
183
+ * Handles WebSocket connection close events.
184
+ *
185
+ * @private
186
+ * @param {CloseEvent} event The WebSocket close event.
187
+ */
188
+ (0, _defineProperty2["default"])(this, "_handleWsClose", function (event) {
189
+ _logger["default"].debug("WebSocket closed:", event);
190
+ if (_this.connected) {
191
+ _this._queueReconnect();
192
+ } else {
193
+ _this.disconnect();
194
+ }
195
+ });
196
+ /**
197
+ * Handles WebSocket error events.
198
+ *
199
+ * @private
200
+ * @param {Event} error The WebSocket error event.
201
+ */
202
+ (0, _defineProperty2["default"])(this, "_handleWsError", function (error) {
203
+ _logger["default"].error("WebSocket error:", error);
204
+ if (_this.connected) {
205
+ _this._queueReconnect();
206
+ } else {
207
+ if (_this.onError) {
208
+ _this.onError(error);
209
+ }
210
+ _this.disconnect();
211
+ }
212
+ });
213
+ /**
214
+ * Processes a single JSEP signal (e.g., start, offer, answer, candidate, bye).
215
+ *
216
+ * @private
217
+ * @param {Object} signal The JSEP signaling message.
218
+ */
219
+ (0, _defineProperty2["default"])(this, "_handleSignal", /*#__PURE__*/function () {
220
+ var _ref2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(signal) {
221
+ return _regenerator["default"].wrap(function _callee2$(_context2) {
222
+ while (1) switch (_context2.prev = _context2.next) {
223
+ case 0:
224
+ _logger["default"].debug("JSEP << [Received from Server]:", JSON.stringify(signal, null, 2));
225
+ _context2.prev = 1;
226
+ if (!signal.start) {
227
+ _context2.next = 5;
228
+ break;
229
+ }
230
+ _context2.next = 5;
231
+ return _this._handleStart(signal.start);
232
+ case 5:
233
+ if (!(signal.type === "offer" || signal.type === "answer")) {
234
+ _context2.next = 10;
235
+ break;
236
+ }
237
+ _context2.next = 8;
238
+ return _this._handleSDP(signal);
239
+ case 8:
240
+ _context2.next = 13;
241
+ break;
242
+ case 10:
243
+ if (!(signal.sdp && signal.sdp.type)) {
244
+ _context2.next = 13;
245
+ break;
246
+ }
247
+ _context2.next = 13;
248
+ return _this._handleSDP(signal.sdp);
249
+ case 13:
250
+ if (signal.candidate) {
251
+ _this._handleCandidate(signal.candidate);
252
+ }
253
+ if (signal.bye) {
254
+ _this._handleBye();
255
+ }
256
+ _context2.next = 20;
257
+ break;
258
+ case 17:
259
+ _context2.prev = 17;
260
+ _context2.t0 = _context2["catch"](1);
261
+ _logger["default"].error("Error processing signal:", _context2.t0);
262
+ case 20:
263
+ case "end":
264
+ return _context2.stop();
265
+ }
266
+ }, _callee2, null, [[1, 17]]);
267
+ }));
268
+ return function (_x) {
269
+ return _ref2.apply(this, arguments);
270
+ };
271
+ }());
272
+ /**
273
+ * Initializes the RTCPeerConnection and local data channels based on the start configuration.
274
+ *
275
+ * @private
276
+ * @param {Object} config The signaling start configuration.
277
+ */
278
+ (0, _defineProperty2["default"])(this, "_handleStart", /*#__PURE__*/function () {
279
+ var _ref3 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee3(config) {
280
+ var localOnlyConfig, mouse, keyboard, touch, offer;
281
+ return _regenerator["default"].wrap(function _callee3$(_context3) {
282
+ while (1) switch (_context3.prev = _context3.next) {
283
+ case 0:
284
+ localOnlyConfig = _objectSpread(_objectSpread({}, config), {}, {
285
+ iceServers: []
286
+ });
287
+ _this.peerConnection = new RTCPeerConnection(localOnlyConfig);
288
+ _this.peerConnection.ontrack = _this._handlePeerConnectionTrack;
289
+ _this.peerConnection.onicecandidate = _this._handlePeerIceCandidate;
290
+ _this.peerConnection.onconnectionstatechange = _this._handlePeerConnectionStateChange;
291
+ _this.peerConnection.ondatachannel = _this._handleDataChannel;
292
+ _this.peerConnection.addTransceiver("video", {
293
+ direction: "recvonly"
294
+ });
295
+ _this.peerConnection.addTransceiver("audio", {
296
+ direction: "recvonly"
297
+ });
298
+ mouse = _this.peerConnection.createDataChannel("mouse");
299
+ keyboard = _this.peerConnection.createDataChannel("keyboard");
300
+ touch = _this.peerConnection.createDataChannel("touch");
301
+ _this._setupDataChannel(mouse);
302
+ _this._setupDataChannel(keyboard);
303
+ _this._setupDataChannel(touch);
304
+ _this.connected = true;
305
+ _context3.prev = 15;
306
+ _context3.next = 18;
307
+ return _this.peerConnection.createOffer();
308
+ case 18:
309
+ offer = _context3.sent;
310
+ if (_this.peerConnection) {
311
+ _context3.next = 21;
312
+ break;
313
+ }
314
+ return _context3.abrupt("return");
315
+ case 21:
316
+ _context3.next = 23;
317
+ return _this.peerConnection.setLocalDescription(offer);
318
+ case 23:
319
+ if (_this.peerConnection) {
320
+ _context3.next = 25;
321
+ break;
322
+ }
323
+ return _context3.abrupt("return");
324
+ case 25:
325
+ _this._sendJsep({
326
+ sdp: offer
327
+ });
328
+ _context3.next = 31;
329
+ break;
330
+ case 28:
331
+ _context3.prev = 28;
332
+ _context3.t0 = _context3["catch"](15);
333
+ _logger["default"].error("Failed to create or set local offer:", _context3.t0);
334
+ case 31:
335
+ case "end":
336
+ return _context3.stop();
337
+ }
338
+ }, _callee3, null, [[15, 28]]);
339
+ }));
340
+ return function (_x2) {
341
+ return _ref3.apply(this, arguments);
342
+ };
343
+ }());
344
+ /**
345
+ * Handles incoming media track events from the RTCPeerConnection.
346
+ *
347
+ * @private
348
+ * @param {RTCTrackEvent} e The track event.
349
+ */
350
+ (0, _defineProperty2["default"])(this, "_handlePeerConnectionTrack", function (e) {
351
+ if (_this.onConnected) {
352
+ _this.onConnected(e.track);
353
+ }
354
+ });
355
+ /**
356
+ * Handles ICE candidate generation from the local RTCPeerConnection.
357
+ *
358
+ * @private
359
+ * @param {RTCPeerConnectionIceEvent} e The ICE candidate event.
360
+ */
361
+ (0, _defineProperty2["default"])(this, "_handlePeerIceCandidate", function (e) {
362
+ if (e.candidate === null) return;
363
+ _this._sendJsep({
364
+ candidate: e.candidate
365
+ });
366
+ });
367
+ /**
368
+ * Monitors connection state changes on the RTCPeerConnection to trigger disconnection.
369
+ *
370
+ * @private
371
+ * @param {Event} e The state change event.
372
+ */
373
+ (0, _defineProperty2["default"])(this, "_handlePeerConnectionStateChange", function (e) {
374
+ if (!_this.peerConnection) return;
375
+ switch (_this.peerConnection.connectionState) {
376
+ case "disconnected":
377
+ case "failed":
378
+ case "closed":
379
+ _this.disconnect();
380
+ }
381
+ });
382
+ /**
383
+ * Registers a data channel for event forwarding.
384
+ *
385
+ * @private
386
+ * @param {RTCDataChannel} channel The data channel.
387
+ */
388
+ (0, _defineProperty2["default"])(this, "_setupDataChannel", function (channel) {
389
+ _this.event_forwarders[channel.label] = channel;
390
+ });
391
+ /**
392
+ * Handles remote data channel creation.
393
+ *
394
+ * @private
395
+ * @param {RTCDataChannelEvent} e The data channel event.
396
+ */
397
+ (0, _defineProperty2["default"])(this, "_handleDataChannel", function (e) {
398
+ _this._setupDataChannel(e.channel);
399
+ });
400
+ /**
401
+ * Processes a remote SDP offer or answer, applying it to the RTCPeerConnection.
402
+ *
403
+ * @private
404
+ * @param {RTCSessionDescriptionInit} sdp The session description.
405
+ */
406
+ (0, _defineProperty2["default"])(this, "_handleSDP", /*#__PURE__*/function () {
407
+ var _ref4 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee4(sdp) {
408
+ var candidate, answer;
409
+ return _regenerator["default"].wrap(function _callee4$(_context4) {
410
+ while (1) switch (_context4.prev = _context4.next) {
411
+ case 0:
412
+ if (_this.peerConnection) {
413
+ _context4.next = 2;
414
+ break;
415
+ }
416
+ return _context4.abrupt("return");
417
+ case 2:
418
+ _context4.prev = 2;
419
+ _context4.next = 5;
420
+ return _this.peerConnection.setRemoteDescription(new RTCSessionDescription(sdp));
421
+ case 5:
422
+ if (_this.peerConnection) {
423
+ _context4.next = 7;
424
+ break;
425
+ }
426
+ return _context4.abrupt("return");
427
+ case 7:
428
+ _this.remoteDescriptionSet = true;
429
+ _logger["default"].debug("Processing ".concat(_this.pendingCandidates.length, " queued ICE candidates."));
430
+ while (_this.pendingCandidates.length > 0) {
431
+ candidate = _this.pendingCandidates.shift();
432
+ _this._addIceCandidate(candidate);
433
+ }
434
+ if (!(sdp.type === "offer")) {
435
+ _context4.next = 21;
436
+ break;
437
+ }
438
+ _context4.next = 13;
439
+ return _this.peerConnection.createAnswer();
440
+ case 13:
441
+ answer = _context4.sent;
442
+ if (_this.peerConnection) {
443
+ _context4.next = 16;
444
+ break;
445
+ }
446
+ return _context4.abrupt("return");
447
+ case 16:
448
+ _context4.next = 18;
449
+ return _this.peerConnection.setLocalDescription(answer);
450
+ case 18:
451
+ if (_this.peerConnection) {
452
+ _context4.next = 20;
453
+ break;
454
+ }
455
+ return _context4.abrupt("return");
456
+ case 20:
457
+ _this._sendJsep({
458
+ sdp: answer
459
+ });
460
+ case 21:
461
+ _context4.next = 26;
462
+ break;
463
+ case 23:
464
+ _context4.prev = 23;
465
+ _context4.t0 = _context4["catch"](2);
466
+ _logger["default"].error("Failed to process remote SDP:", _context4.t0);
467
+ case 26:
468
+ case "end":
469
+ return _context4.stop();
470
+ }
471
+ }, _callee4, null, [[2, 23]]);
472
+ }));
473
+ return function (_x3) {
474
+ return _ref4.apply(this, arguments);
475
+ };
476
+ }());
477
+ /**
478
+ * Adds a remote ICE candidate to the RTCPeerConnection.
479
+ *
480
+ * @private
481
+ * @param {RTCIceCandidateInit|string} candidate The ICE candidate object or string.
482
+ */
483
+ (0, _defineProperty2["default"])(this, "_addIceCandidate", function (candidate) {
484
+ try {
485
+ var candidateInit = typeof candidate === 'string' ? {
486
+ candidate: candidate,
487
+ sdpMid: "0",
488
+ sdpMLineIndex: 0
489
+ } : candidate;
490
+ _this.peerConnection.addIceCandidate(new RTCIceCandidate(candidateInit));
491
+ } catch (e) {
492
+ _logger["default"].warn("Failed to add ICE candidate:", e, candidate);
493
+ }
494
+ });
495
+ /**
496
+ * Handles an incoming remote ICE candidate, queueing it if the remote description is not yet set.
497
+ *
498
+ * @private
499
+ * @param {RTCIceCandidateInit} candidate The remote ICE candidate.
500
+ */
501
+ (0, _defineProperty2["default"])(this, "_handleCandidate", function (candidate) {
502
+ if (!_this.peerConnection) return;
503
+ if (!_this.remoteDescriptionSet) {
504
+ _logger["default"].debug("Queueing ICE candidate until remote description is set:", candidate);
505
+ _this.pendingCandidates.push(candidate);
506
+ } else {
507
+ _this._addIceCandidate(candidate);
508
+ }
509
+ });
510
+ /**
511
+ * Handles the 'bye' signal from the remote side, triggering disconnection.
512
+ *
513
+ * @private
514
+ */
515
+ (0, _defineProperty2["default"])(this, "_handleBye", function () {
516
+ _this.disconnect();
517
+ });
518
+ /**
519
+ * Serializes and sends a JSEP JSON message over the WebSocket.
520
+ *
521
+ * @private
522
+ * @param {Object} jsonObject The JSON payload.
523
+ */
524
+ (0, _defineProperty2["default"])(this, "_sendJsep", function (jsonObject) {
525
+ _logger["default"].debug("JSEP >> [Sending to Server]:", JSON.stringify(jsonObject, null, 2));
526
+ if (_this.ws && _this.ws.readyState === WebSocket.OPEN) {
527
+ _this.ws.send(JSON.stringify(jsonObject));
528
+ }
529
+ });
530
+ /**
531
+ * Cleans up the current connection's WebSocket and PeerConnection state,
532
+ * but does not mark the driver as permanently disconnected or trigger
533
+ * the onDisconnected callback. Used during reconnection.
534
+ *
535
+ * @private
536
+ */
537
+ (0, _defineProperty2["default"])(this, "_disconnectState", function () {
538
+ _this.signalQueue = [];
539
+ _this.isProcessingSignal = false;
540
+ if (_this.ws) {
541
+ _this.ws.onclose = null;
542
+ _this.ws.onerror = null;
543
+ _this.ws.onmessage = null;
544
+ _this.ws.close();
545
+ _this.ws = null;
546
+ }
547
+ if (_this.peerConnection) {
548
+ _this.peerConnection.close();
549
+ _this.peerConnection = null;
550
+ }
551
+ _this.pendingCandidates = [];
552
+ _this.remoteDescriptionSet = false;
553
+ _this.event_forwarders = {};
554
+ });
555
+ /**
556
+ * Disconnects both the WebSocket signaling connection and the WebRTC PeerConnection.
557
+ */
558
+ (0, _defineProperty2["default"])(this, "disconnect", function () {
559
+ _this.connected = false;
560
+ if (_this.reconnectTimeoutId) {
561
+ clearTimeout(_this.reconnectTimeoutId);
562
+ _this.reconnectTimeoutId = null;
563
+ }
564
+ _this.reconnectAttempts = 0;
565
+ _this._disconnectState();
566
+ if (_this.onDisconnected) {
567
+ _this.onDisconnected(_this);
568
+ }
569
+ });
570
+ /**
571
+ * Fully cleans up signaling and WebRTC state.
572
+ */
573
+ (0, _defineProperty2["default"])(this, "cleanup", function () {
574
+ _this.disconnect();
575
+ _this.event_forwarders = {};
576
+ });
577
+ this.wsUrl = wsUrl;
578
+ this.emulator = emulator;
579
+ this.config = _objectSpread({
580
+ enableLogging: false
581
+ }, _config);
582
+ this.onError = this.config.onError;
583
+ this.maxReconnectAttempts = (_this$config$maxRecon = this.config.maxReconnectAttempts) !== null && _this$config$maxRecon !== void 0 ? _this$config$maxRecon : 5;
584
+ this.reconnectDelay = (_this$config$reconnec = this.config.reconnectDelay) !== null && _this$config$reconnec !== void 0 ? _this$config$reconnec : 1000;
585
+ this.reconnectBackoffFactor = (_this$config$reconnec2 = this.config.reconnectBackoffFactor) !== null && _this$config$reconnec2 !== void 0 ? _this$config$reconnec2 : 2;
586
+ this.connected = false;
587
+ this.event_forwarders = {};
588
+ this.peerConnection = null;
589
+ this.ws = null;
590
+
591
+ // WebRTC signaling state
592
+ this.pendingCandidates = [];
593
+ this.remoteDescriptionSet = false;
594
+
595
+ // Signaling message queue to prevent concurrent mutations
596
+ this.signalQueue = [];
597
+ this.isProcessingSignal = false;
598
+
599
+ // Callbacks set during startStream
600
+ this.onConnected = null;
601
+ this.onDisconnected = null;
602
+
603
+ // Reconnection state
604
+ this.reconnectAttempts = 0;
605
+ this.reconnectTimeoutId = null;
606
+ }
607
+ (0, _createClass2["default"])(WsJsepProtocol, [{
608
+ key: "send",
609
+ value:
610
+ /**
611
+ * Sends a control message (mouse, keyboard, touch) over either the corresponding
612
+ * WebRTC DataChannel or via the fallback emulator controller.
613
+ *
614
+ * @param {string} label The channel label ("mouse", "keyboard", "touch").
615
+ * @param {Object} msg The protobuf message instance.
616
+ */
617
+ function send(label, msg) {
618
+ var bytes = msg.serializeBinary();
619
+ var forwarder = this.event_forwarders[label];
620
+ if (this.connected && forwarder && forwarder.readyState === "open") {
621
+ forwarder.send(bytes);
622
+ } else if (this.emulator) {
623
+ switch (label) {
624
+ case "mouse":
625
+ this.emulator.sendMouse(msg);
626
+ break;
627
+ case "keyboard":
628
+ this.emulator.sendKey(msg);
629
+ break;
630
+ case "touch":
631
+ this.emulator.sendTouch(msg);
632
+ break;
633
+ }
634
+ } else {
635
+ _logger["default"].warn("Data channel '".concat(label, "' is not open. Event was dropped."));
636
+ }
637
+ }
638
+ }]);
639
+ return WsJsepProtocol;
640
+ }();
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import WsJsepProtocol from "../net/ws_jsep_protocol_driver";
3
+ export interface MouseKeyHandlerProps {
4
+ /** The REST endpoint to retrieve status. */
5
+ statusUrl: string;
6
+ /** Jsep protocol driver, used to send mouse & touch events. */
7
+ jsep: WsJsepProtocol;
8
+ /** The authentication service to use. */
9
+ auth?: any;
10
+ /** The width of the component. */
11
+ width?: number;
12
+ /** The height of the component. */
13
+ height?: number;
14
+ [key: string]: any;
15
+ }
16
+ export interface MouseKeyHandlerRef {
17
+ scaleCoordinates(xp: number, yp: number): {
18
+ x: number;
19
+ y: number;
20
+ scaleX: number;
21
+ scaleY: number;
22
+ };
23
+ setDeviceWidth: React.Dispatch<React.SetStateAction<number>>;
24
+ setDeviceHeight: React.Dispatch<React.SetStateAction<number>>;
25
+ handlerRef: React.RefObject<HTMLDivElement>;
26
+ }
27
+ /**
28
+ * A handler that extends a view to send key/mouse events to the emulator.
29
+ * It wraps the inner component in a div, and will use the jsep handler
30
+ * to send key/mouse/touch events over the proper channel.
31
+ *
32
+ * It will translate the mouse events based upon the returned display size of
33
+ * the emulator.
34
+ *
35
+ * You usually want to wrap a EmulatorRtcview, or EmulatorPngView in it.
36
+ */
37
+ export default function withMouseKeyHandler<P extends object>(WrappedComponent: React.ComponentType<P>): React.ForwardRefExoticComponent<React.PropsWithoutRef<MouseKeyHandlerProps & P> & React.RefAttributes<MouseKeyHandlerRef>>;