neoagent 2.4.1-beta.6 → 2.4.1-beta.8
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/flutter_app/lib/src/backend_client.dart +44 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +32 -3
- package/flutter_app/lib/src/desktop_companion_io.dart +83 -0
- package/flutter_app/lib/src/stream_renderer.dart +155 -0
- package/package.json +1 -1
- package/server/guest_agent.js +7 -0
- package/server/http/routes.js +1 -0
- package/server/http/socket.js +1 -1
- package/server/index.js +4 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/routes/stream.js +178 -0
- package/server/services/android/controller.js +6 -2
- package/server/services/browser/controller.js +16 -0
- package/server/services/desktop/gateway.js +28 -3
- package/server/services/desktop/protocol.js +32 -0
- package/server/services/desktop/provider.js +10 -0
- package/server/services/desktop/registry.js +41 -0
- package/server/services/manager.js +11 -0
- package/server/services/runtime/backends/local-vm.js +6 -0
- package/server/services/runtime/manager.js +4 -0
- package/server/services/streaming/android-stream.js +72 -0
- package/server/services/streaming/browser-stream.js +87 -0
- package/server/services/streaming/stream-hub.js +231 -0
- package/server/services/websocket.js +73 -0
|
@@ -615,6 +615,50 @@ class BackendClient {
|
|
|
615
615
|
return _postEmpty(baseUrl, '/api/browser/close');
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
+
Future<Map<String, dynamic>> startStream(
|
|
619
|
+
String baseUrl, {
|
|
620
|
+
required String platform,
|
|
621
|
+
required String deviceId,
|
|
622
|
+
int fps = 15,
|
|
623
|
+
int quality = 80,
|
|
624
|
+
String? displayId,
|
|
625
|
+
}) async {
|
|
626
|
+
return postMap(baseUrl, '/api/stream/start', <String, dynamic>{
|
|
627
|
+
'platform': platform,
|
|
628
|
+
'deviceId': deviceId,
|
|
629
|
+
'fps': fps,
|
|
630
|
+
'quality': quality,
|
|
631
|
+
if (displayId != null && displayId.isNotEmpty) 'displayId': displayId,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
Future<Map<String, dynamic>> stopStream(
|
|
636
|
+
String baseUrl, {
|
|
637
|
+
required String platform,
|
|
638
|
+
required String deviceId,
|
|
639
|
+
}) async {
|
|
640
|
+
return postMap(baseUrl, '/api/stream/stop', <String, dynamic>{
|
|
641
|
+
'platform': platform,
|
|
642
|
+
'deviceId': deviceId,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
Future<Map<String, dynamic>> fetchStreamStatus(
|
|
647
|
+
String baseUrl, {
|
|
648
|
+
String? platform,
|
|
649
|
+
String? deviceId,
|
|
650
|
+
}) async {
|
|
651
|
+
final query = <String>[];
|
|
652
|
+
if (platform != null && platform.isNotEmpty) {
|
|
653
|
+
query.add('platform=${Uri.encodeQueryComponent(platform)}');
|
|
654
|
+
}
|
|
655
|
+
if (deviceId != null && deviceId.isNotEmpty) {
|
|
656
|
+
query.add('deviceId=${Uri.encodeQueryComponent(deviceId)}');
|
|
657
|
+
}
|
|
658
|
+
final suffix = query.isEmpty ? '' : '?${query.join('&')}';
|
|
659
|
+
return getMap(baseUrl, '/api/stream/status$suffix');
|
|
660
|
+
}
|
|
661
|
+
|
|
618
662
|
Future<Map<String, dynamic>> fetchAndroidStatus(String baseUrl) async {
|
|
619
663
|
return getMap(baseUrl, '/api/android/status');
|
|
620
664
|
}
|
|
@@ -177,6 +177,18 @@ class DesktopCompanionActions {
|
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
Future<Uint8List> compressToJpeg(
|
|
181
|
+
DesktopCompanionSnapshot snapshot,
|
|
182
|
+
int quality,
|
|
183
|
+
) async {
|
|
184
|
+
final raw = _decodeScreenshotBytes(snapshot.screenshotBase64);
|
|
185
|
+
if (_looksLikeJpeg(raw)) return raw;
|
|
186
|
+
final decoded = img.decodeImage(raw);
|
|
187
|
+
if (decoded == null) return raw;
|
|
188
|
+
final normalizedQuality = quality.clamp(30, 95);
|
|
189
|
+
return Uint8List.fromList(img.encodeJpg(decoded, quality: normalizedQuality));
|
|
190
|
+
}
|
|
191
|
+
|
|
180
192
|
Future<Map<String, Object?>> observe({
|
|
181
193
|
bool includeTree = false,
|
|
182
194
|
String? activeDisplayId,
|
|
@@ -450,15 +462,15 @@ class DesktopCompanionActions {
|
|
|
450
462
|
await stdoutSub.cancel();
|
|
451
463
|
await stderrSub.cancel();
|
|
452
464
|
|
|
453
|
-
String
|
|
465
|
+
String trimOutput(StringBuffer buf) {
|
|
454
466
|
final s = buf.toString().trim();
|
|
455
467
|
return s.length > maxChars ? '${s.substring(0, maxChars)}\n...[truncated, ${s.length} total chars]' : s;
|
|
456
468
|
}
|
|
457
469
|
|
|
458
470
|
return <String, Object?>{
|
|
459
471
|
'exitCode': exitCode,
|
|
460
|
-
'stdout':
|
|
461
|
-
'stderr':
|
|
472
|
+
'stdout': trimOutput(stdoutBuf),
|
|
473
|
+
'stderr': trimOutput(stderrBuf),
|
|
462
474
|
'timedOut': timedOut,
|
|
463
475
|
'killed': timedOut,
|
|
464
476
|
'durationMs': DateTime.now().difference(startedAt).inMilliseconds,
|
|
@@ -588,6 +600,23 @@ class DesktopCompanionActions {
|
|
|
588
600
|
}
|
|
589
601
|
}
|
|
590
602
|
|
|
603
|
+
Uint8List _decodeScreenshotBytes(String screenshotBase64) {
|
|
604
|
+
final trimmed = screenshotBase64.trim();
|
|
605
|
+
final commaIndex = trimmed.indexOf(',');
|
|
606
|
+
final encoded = trimmed.startsWith('data:image/') && commaIndex >= 0
|
|
607
|
+
? trimmed.substring(commaIndex + 1)
|
|
608
|
+
: trimmed;
|
|
609
|
+
return Uint8List.fromList(base64Decode(encoded));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
bool _looksLikeJpeg(Uint8List bytes) {
|
|
613
|
+
return bytes.length >= 4 &&
|
|
614
|
+
bytes[0] == 0xff &&
|
|
615
|
+
bytes[1] == 0xd8 &&
|
|
616
|
+
bytes[bytes.length - 2] == 0xff &&
|
|
617
|
+
bytes[bytes.length - 1] == 0xd9;
|
|
618
|
+
}
|
|
619
|
+
|
|
591
620
|
String _normalizeMouseButton(String button) {
|
|
592
621
|
final value = button.trim().toLowerCase();
|
|
593
622
|
if (value == 'left' || value == 'right' || value == 'middle') {
|
|
@@ -25,6 +25,10 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
25
25
|
final DesktopCompanionActions _actions;
|
|
26
26
|
WebSocket? _socket;
|
|
27
27
|
Timer? _reconnectTimer;
|
|
28
|
+
Timer? _streamTimer;
|
|
29
|
+
bool _streamCaptureInFlight = false;
|
|
30
|
+
int _frameSeq = 0;
|
|
31
|
+
int _streamGeneration = 0;
|
|
28
32
|
|
|
29
33
|
String _backendUrl = '';
|
|
30
34
|
String _sessionCookie = '';
|
|
@@ -126,6 +130,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
126
130
|
Future<void> disconnect() async {
|
|
127
131
|
_reconnectTimer?.cancel();
|
|
128
132
|
_reconnectTimer = null;
|
|
133
|
+
_stopStreaming();
|
|
129
134
|
_connecting = false;
|
|
130
135
|
_connected = false;
|
|
131
136
|
final socket = _socket;
|
|
@@ -327,6 +332,10 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
327
332
|
);
|
|
328
333
|
case 'captureFrame':
|
|
329
334
|
return _actions.captureFrame(activeDisplayId: _activeDisplayId);
|
|
335
|
+
case 'startStream':
|
|
336
|
+
return _startStreaming(payload);
|
|
337
|
+
case 'stopStream':
|
|
338
|
+
return _stopStreaming();
|
|
330
339
|
case 'observe':
|
|
331
340
|
return _actions.observe(
|
|
332
341
|
includeTree: payload['includeTree'] == true,
|
|
@@ -404,6 +413,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
404
413
|
}
|
|
405
414
|
|
|
406
415
|
void _handleSocketClosed() {
|
|
416
|
+
_stopStreaming();
|
|
407
417
|
_socket = null;
|
|
408
418
|
_connecting = false;
|
|
409
419
|
_connected = false;
|
|
@@ -415,6 +425,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
415
425
|
void dispose() {
|
|
416
426
|
_reconnectTimer?.cancel();
|
|
417
427
|
_reconnectTimer = null;
|
|
428
|
+
_stopStreaming();
|
|
418
429
|
_connecting = false;
|
|
419
430
|
_connected = false;
|
|
420
431
|
_enabled = false;
|
|
@@ -448,6 +459,78 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
448
459
|
);
|
|
449
460
|
}
|
|
450
461
|
|
|
462
|
+
Future<Map<String, Object?>> _startStreaming(
|
|
463
|
+
Map<String, Object?> payload,
|
|
464
|
+
) async {
|
|
465
|
+
_streamTimer?.cancel();
|
|
466
|
+
final generation = ++_streamGeneration;
|
|
467
|
+
final fps = ((payload['fps'] as num?)?.round() ?? 15).clamp(1, 20);
|
|
468
|
+
final quality = ((payload['quality'] as num?)?.round() ?? 80).clamp(30, 95);
|
|
469
|
+
final displayId = payload['displayId']?.toString().trim();
|
|
470
|
+
if (displayId != null && displayId.isNotEmpty) {
|
|
471
|
+
_activeDisplayId = displayId;
|
|
472
|
+
}
|
|
473
|
+
final interval = Duration(milliseconds: max(1, (1000 / fps).floor()));
|
|
474
|
+
_frameSeq = 0;
|
|
475
|
+
_streamTimer = Timer.periodic(interval, (_) {
|
|
476
|
+
unawaited(_captureAndSendBinaryFrame(quality, generation));
|
|
477
|
+
});
|
|
478
|
+
unawaited(_captureAndSendBinaryFrame(quality, generation));
|
|
479
|
+
return <String, Object?>{
|
|
480
|
+
'success': true,
|
|
481
|
+
'fps': fps,
|
|
482
|
+
'quality': quality,
|
|
483
|
+
'displayId': _activeDisplayId,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
Map<String, Object?> _stopStreaming() {
|
|
488
|
+
_streamTimer?.cancel();
|
|
489
|
+
_streamTimer = null;
|
|
490
|
+
_streamGeneration++;
|
|
491
|
+
_streamCaptureInFlight = false;
|
|
492
|
+
return <String, Object?>{'success': true};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
Future<void> _captureAndSendBinaryFrame(int quality, int generation) async {
|
|
496
|
+
final socket = _socket;
|
|
497
|
+
if (socket == null ||
|
|
498
|
+
!_connected ||
|
|
499
|
+
_streamCaptureInFlight ||
|
|
500
|
+
generation != _streamGeneration) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
_streamCaptureInFlight = true;
|
|
504
|
+
try {
|
|
505
|
+
final snapshot = await _actions.captureSnapshot(
|
|
506
|
+
activeDisplayId: _activeDisplayId,
|
|
507
|
+
);
|
|
508
|
+
if (snapshot == null) return;
|
|
509
|
+
final jpeg = await _actions.compressToJpeg(snapshot, quality);
|
|
510
|
+
if (jpeg.isEmpty) return;
|
|
511
|
+
if (!_connected || generation != _streamGeneration || _socket != socket) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
final frame = Uint8List(10 + jpeg.length);
|
|
515
|
+
final header = ByteData.sublistView(frame, 0, 10);
|
|
516
|
+
header.setUint8(0, 0x01);
|
|
517
|
+
header.setUint32(1, _frameSeq++ & 0xffffffff, Endian.big);
|
|
518
|
+
header.setUint32(
|
|
519
|
+
5,
|
|
520
|
+
DateTime.now().millisecondsSinceEpoch & 0xffffffff,
|
|
521
|
+
Endian.big,
|
|
522
|
+
);
|
|
523
|
+
header.setUint8(9, 0x01);
|
|
524
|
+
frame.setRange(10, frame.length, jpeg);
|
|
525
|
+
socket.add(frame);
|
|
526
|
+
} catch (error) {
|
|
527
|
+
_errorMessage = 'Desktop stream capture failed: $error';
|
|
528
|
+
notifyListeners();
|
|
529
|
+
} finally {
|
|
530
|
+
_streamCaptureInFlight = false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
451
534
|
Future<void> _openMacPermissionSettings(String key) async {
|
|
452
535
|
final uri = switch (key) {
|
|
453
536
|
'screencapture' =>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import 'dart:typed_data';
|
|
2
|
+
|
|
3
|
+
import 'package:flutter/material.dart';
|
|
4
|
+
import 'package:socket_io_client/socket_io_client.dart' as io;
|
|
5
|
+
|
|
6
|
+
class StreamRenderer extends StatefulWidget {
|
|
7
|
+
const StreamRenderer({
|
|
8
|
+
super.key,
|
|
9
|
+
required this.socket,
|
|
10
|
+
required this.deviceId,
|
|
11
|
+
required this.platform,
|
|
12
|
+
required this.remoteResolution,
|
|
13
|
+
this.onTap,
|
|
14
|
+
this.onType,
|
|
15
|
+
this.fit = BoxFit.contain,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
final io.Socket socket;
|
|
19
|
+
final String deviceId;
|
|
20
|
+
final String platform;
|
|
21
|
+
final Size remoteResolution;
|
|
22
|
+
final void Function(double x, double y)? onTap;
|
|
23
|
+
final void Function(String text)? onType;
|
|
24
|
+
final BoxFit fit;
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
State<StreamRenderer> createState() => _StreamRendererState();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class _StreamRendererState extends State<StreamRenderer> {
|
|
31
|
+
Uint8List? _frame;
|
|
32
|
+
|
|
33
|
+
@override
|
|
34
|
+
void initState() {
|
|
35
|
+
super.initState();
|
|
36
|
+
widget.socket.on('stream:frame', _onFrame);
|
|
37
|
+
widget.socket.emit('stream:subscribe', <String, Object?>{
|
|
38
|
+
'deviceId': widget.deviceId,
|
|
39
|
+
'platform': widget.platform,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
void didUpdateWidget(StreamRenderer oldWidget) {
|
|
45
|
+
super.didUpdateWidget(oldWidget);
|
|
46
|
+
if (oldWidget.socket == widget.socket &&
|
|
47
|
+
oldWidget.deviceId == widget.deviceId &&
|
|
48
|
+
oldWidget.platform == widget.platform) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
oldWidget.socket.off('stream:frame', _onFrame);
|
|
52
|
+
oldWidget.socket.emit('stream:unsubscribe', <String, Object?>{
|
|
53
|
+
'deviceId': oldWidget.deviceId,
|
|
54
|
+
'platform': oldWidget.platform,
|
|
55
|
+
});
|
|
56
|
+
widget.socket.on('stream:frame', _onFrame);
|
|
57
|
+
widget.socket.emit('stream:subscribe', <String, Object?>{
|
|
58
|
+
'deviceId': widget.deviceId,
|
|
59
|
+
'platform': widget.platform,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void _onFrame(dynamic data) {
|
|
64
|
+
Object? meta;
|
|
65
|
+
Object? bytes;
|
|
66
|
+
if (data is List && data.length >= 2) {
|
|
67
|
+
meta = data[0];
|
|
68
|
+
bytes = data[1];
|
|
69
|
+
}
|
|
70
|
+
if (meta is Map) {
|
|
71
|
+
final frameDeviceId = meta['deviceId']?.toString() ?? '';
|
|
72
|
+
final framePlatform = meta['platform']?.toString() ?? '';
|
|
73
|
+
if (frameDeviceId.isNotEmpty && frameDeviceId != widget.deviceId) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (framePlatform.isNotEmpty && framePlatform != widget.platform) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
final frame = switch (bytes) {
|
|
81
|
+
Uint8List value => value,
|
|
82
|
+
List<int> value => Uint8List.fromList(value),
|
|
83
|
+
ByteBuffer value => Uint8List.view(value),
|
|
84
|
+
_ => null,
|
|
85
|
+
};
|
|
86
|
+
if (frame == null || frame.isEmpty || !mounted) return;
|
|
87
|
+
setState(() => _frame = frame);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@override
|
|
91
|
+
Widget build(BuildContext context) {
|
|
92
|
+
final frame = _frame;
|
|
93
|
+
if (frame == null) {
|
|
94
|
+
return const Center(child: CircularProgressIndicator());
|
|
95
|
+
}
|
|
96
|
+
return LayoutBuilder(
|
|
97
|
+
builder: (context, constraints) {
|
|
98
|
+
return GestureDetector(
|
|
99
|
+
behavior: HitTestBehavior.opaque,
|
|
100
|
+
onTapDown: widget.onTap == null
|
|
101
|
+
? null
|
|
102
|
+
: (details) => _handleTap(details, constraints.biggest),
|
|
103
|
+
child: Image.memory(
|
|
104
|
+
frame,
|
|
105
|
+
gaplessPlayback: true,
|
|
106
|
+
fit: widget.fit,
|
|
107
|
+
width: constraints.maxWidth,
|
|
108
|
+
height: constraints.maxHeight,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
void _handleTap(TapDownDetails details, Size boxSize) {
|
|
116
|
+
final remote = widget.remoteResolution;
|
|
117
|
+
if (remote.width <= 0 || remote.height <= 0 || boxSize.width <= 0 || boxSize.height <= 0) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
final imageAspect = remote.width / remote.height;
|
|
121
|
+
final boxAspect = boxSize.width / boxSize.height;
|
|
122
|
+
double renderWidth;
|
|
123
|
+
double renderHeight;
|
|
124
|
+
double offsetX = 0;
|
|
125
|
+
double offsetY = 0;
|
|
126
|
+
if (boxAspect > imageAspect) {
|
|
127
|
+
renderHeight = boxSize.height;
|
|
128
|
+
renderWidth = renderHeight * imageAspect;
|
|
129
|
+
offsetX = (boxSize.width - renderWidth) / 2;
|
|
130
|
+
} else {
|
|
131
|
+
renderWidth = boxSize.width;
|
|
132
|
+
renderHeight = renderWidth / imageAspect;
|
|
133
|
+
offsetY = (boxSize.height - renderHeight) / 2;
|
|
134
|
+
}
|
|
135
|
+
final localX = details.localPosition.dx - offsetX;
|
|
136
|
+
final localY = details.localPosition.dy - offsetY;
|
|
137
|
+
if (localX < 0 || localY < 0 || localX > renderWidth || localY > renderHeight) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
widget.onTap?.call(
|
|
141
|
+
localX * remote.width / renderWidth,
|
|
142
|
+
localY * remote.height / renderHeight,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@override
|
|
147
|
+
void dispose() {
|
|
148
|
+
widget.socket.emit('stream:unsubscribe', <String, Object?>{
|
|
149
|
+
'deviceId': widget.deviceId,
|
|
150
|
+
'platform': widget.platform,
|
|
151
|
+
});
|
|
152
|
+
widget.socket.off('stream:frame', _onFrame);
|
|
153
|
+
super.dispose();
|
|
154
|
+
}
|
|
155
|
+
}
|
package/package.json
CHANGED
package/server/guest_agent.js
CHANGED
|
@@ -195,6 +195,13 @@ function requireCapability(controller, name) {
|
|
|
195
195
|
app.post('/browser/launch', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').launch(req.body || {})));
|
|
196
196
|
app.post('/browser/navigate', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').navigate(req.body?.url, req.body || {})));
|
|
197
197
|
app.post('/browser/screenshot', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').screenshot(req.body || {})));
|
|
198
|
+
app.post('/browser/screenshot-jpeg', async (req, res) => handle(res, async () => {
|
|
199
|
+
const jpeg = await requireCapability(browserController, 'browser').screenshotJpeg(req.body?.quality, req.body || {});
|
|
200
|
+
return {
|
|
201
|
+
contentType: 'image/jpeg',
|
|
202
|
+
contentBase64: Buffer.from(jpeg).toString('base64'),
|
|
203
|
+
};
|
|
204
|
+
}));
|
|
198
205
|
app.post('/browser/click', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').click(req.body?.selector, req.body?.text, req.body?.screenshot !== false)));
|
|
199
206
|
app.post('/browser/click-point', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').clickPoint(req.body?.x, req.body?.y, req.body?.screenshot !== false)));
|
|
200
207
|
app.post('/browser/fill', async (req, res) => handle(res, () => requireCapability(browserController, 'browser').type(req.body?.selector, String(req.body?.value ?? req.body?.text ?? ''), req.body || {})));
|
package/server/http/routes.js
CHANGED
|
@@ -25,6 +25,7 @@ const routeRegistry = [
|
|
|
25
25
|
{ basePath: '/api/browser-extension', modulePath: '../routes/browser_extension' },
|
|
26
26
|
{ basePath: '/api/android', modulePath: '../routes/android' },
|
|
27
27
|
{ basePath: '/api/desktop', modulePath: '../routes/desktop' },
|
|
28
|
+
{ basePath: '/api/stream', modulePath: '../routes/stream' },
|
|
28
29
|
{ basePath: '/api/recordings', modulePath: '../routes/recordings' },
|
|
29
30
|
{ basePath: '/api/social-video', modulePath: '../routes/social_video' },
|
|
30
31
|
{ basePath: '/api/voice-assistant', modulePath: '../routes/voice_assistant' },
|
package/server/http/socket.js
CHANGED
|
@@ -8,7 +8,7 @@ function createSocketServer(httpServer, { validateOrigin }) {
|
|
|
8
8
|
pingInterval: Number(process.env.NEOAGENT_SOCKET_PING_INTERVAL_MS || 25000),
|
|
9
9
|
pingTimeout: Number(process.env.NEOAGENT_SOCKET_PING_TIMEOUT_MS || 20000),
|
|
10
10
|
connectTimeout: Number(process.env.NEOAGENT_SOCKET_CONNECT_TIMEOUT_MS || 10000),
|
|
11
|
-
maxHttpBufferSize: Number(process.env.NEOAGENT_SOCKET_MAX_HTTP_BUFFER_BYTES ||
|
|
11
|
+
maxHttpBufferSize: Number(process.env.NEOAGENT_SOCKET_MAX_HTTP_BUFFER_BYTES || 8 * 1024 * 1024),
|
|
12
12
|
cors: {
|
|
13
13
|
origin(origin, callback) {
|
|
14
14
|
return validateOrigin(origin, callback, { allowMissingOrigin: true });
|
package/server/index.js
CHANGED
|
@@ -34,6 +34,7 @@ const { startServices, stopServices } = require('./services/manager');
|
|
|
34
34
|
const { bindBrowserExtensionGateway } = require('./services/browser/extension/gateway');
|
|
35
35
|
const { bindDesktopCompanionGateway } = require('./services/desktop/gateway');
|
|
36
36
|
const { bindWearableGateway } = require('./services/wearable/gateway');
|
|
37
|
+
const { StreamHub } = require('./services/streaming/stream-hub');
|
|
37
38
|
|
|
38
39
|
function parseBooleanFlag(value, fallback = false) {
|
|
39
40
|
const normalized = String(value || '').trim().toLowerCase();
|
|
@@ -89,6 +90,8 @@ const app = express();
|
|
|
89
90
|
app.disable('x-powered-by');
|
|
90
91
|
const httpServer = createServer(app);
|
|
91
92
|
const io = createSocketServer(httpServer, { validateOrigin });
|
|
93
|
+
const streamHub = new StreamHub(io);
|
|
94
|
+
app.locals.streamHub = streamHub;
|
|
92
95
|
app.locals.httpRuntimeConfig = {
|
|
93
96
|
secureCookies: SECURE_COOKIES,
|
|
94
97
|
trustProxy: TRUST_PROXY,
|
|
@@ -112,7 +115,7 @@ registerApiRoutes(app);
|
|
|
112
115
|
registerStaticRoutes(app);
|
|
113
116
|
registerErrorHandler(app);
|
|
114
117
|
bindBrowserExtensionGateway(httpServer, app);
|
|
115
|
-
bindDesktopCompanionGateway(httpServer, app, sessionMiddleware);
|
|
118
|
+
bindDesktopCompanionGateway(httpServer, app, sessionMiddleware, streamHub);
|
|
116
119
|
bindWearableGateway(httpServer, app, sessionMiddleware);
|
|
117
120
|
|
|
118
121
|
let shuttingDown = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f09d02f54b922317d6897b53a29a5868
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"4c525dac5ebe5971c5708ef73558ed8edcf4a3
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1854193544" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -131149,7 +131149,7 @@ r===$&&A.b()
|
|
|
131149
131149
|
o.push(A.iq(p,A.j2(!1,new A.a4(B.u0,A.e_(new A.cN(B.hf,new A.a6E(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
|
|
131150
131150
|
if(!s.ay)if(!s.ch){r=s.e
|
|
131151
131151
|
r===$&&A.b()
|
|
131152
|
-
r=B.b.v("
|
|
131152
|
+
r=B.b.v("mpem9vkq-e415031").length!==0&&r.b}if(r){r=s.d
|
|
131153
131153
|
r===$&&A.b()
|
|
131154
131154
|
r=r.ab&&!r.Z?84:0
|
|
131155
131155
|
q=s.e
|
|
@@ -136201,7 +136201,7 @@ $S:338}
|
|
|
136201
136201
|
A.Z6.prototype={}
|
|
136202
136202
|
A.S_.prototype={
|
|
136203
136203
|
n4(a){var s=this
|
|
136204
|
-
if(B.b.v("
|
|
136204
|
+
if(B.b.v("mpem9vkq-e415031").length===0||s.a!=null)return
|
|
136205
136205
|
s.Aq()
|
|
136206
136206
|
s.a=A.q9(B.Qi,new A.b7t(s))},
|
|
136207
136207
|
Aq(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
|
|
@@ -136219,7 +136219,7 @@ if(!t.f.b(k)){s=1
|
|
|
136219
136219
|
break}i=J.X(k,"buildId")
|
|
136220
136220
|
h=i==null?null:B.b.v(J.q(i))
|
|
136221
136221
|
j=h==null?"":h
|
|
136222
|
-
if(J.bt(j)===0||J.c(j,"
|
|
136222
|
+
if(J.bt(j)===0||J.c(j,"mpem9vkq-e415031")){s=1
|
|
136223
136223
|
break}n.b=!0
|
|
136224
136224
|
n.F()
|
|
136225
136225
|
p=2
|
|
@@ -136236,7 +136236,7 @@ case 2:return A.i(o.at(-1),r)}})
|
|
|
136236
136236
|
return A.k($async$Aq,r)},
|
|
136237
136237
|
vl(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
|
|
136238
136238
|
var $async$vl=A.h(function(a2,a3){if(a2===1){o.push(a3)
|
|
136239
|
-
s=p}for(;;)switch(s){case 0:if(B.b.v("
|
|
136239
|
+
s=p}for(;;)switch(s){case 0:if(B.b.v("mpem9vkq-e415031").length===0||n.c){s=1
|
|
136240
136240
|
break}n.c=!0
|
|
136241
136241
|
n.F()
|
|
136242
136242
|
p=4
|