@aiscene/aiserver 2.0.4 → 2.0.5

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 (66) hide show
  1. package/dist/api/device-api.d.ts.map +1 -1
  2. package/dist/api/device-api.js +0 -2
  3. package/dist/api/device-api.js.map +1 -1
  4. package/dist/config/cli.d.ts +24 -0
  5. package/dist/config/cli.d.ts.map +1 -1
  6. package/dist/config/cli.js +96 -0
  7. package/dist/config/cli.js.map +1 -1
  8. package/dist/config/index.d.ts.map +1 -1
  9. package/dist/config/index.js +20 -0
  10. package/dist/config/index.js.map +1 -1
  11. package/dist/config/schema.d.ts +12 -0
  12. package/dist/config/schema.d.ts.map +1 -1
  13. package/dist/core/types.d.ts +0 -6
  14. package/dist/core/types.d.ts.map +1 -1
  15. package/dist/debug/websocket-server.d.ts.map +1 -1
  16. package/dist/debug/websocket-server.js +23 -35
  17. package/dist/debug/websocket-server.js.map +1 -1
  18. package/dist/device/detector.d.ts +0 -2
  19. package/dist/device/detector.d.ts.map +1 -1
  20. package/dist/device/detector.js +3 -70
  21. package/dist/device/detector.js.map +1 -1
  22. package/dist/device/heartbeat.d.ts.map +1 -1
  23. package/dist/device/heartbeat.js +2 -14
  24. package/dist/device/heartbeat.js.map +1 -1
  25. package/dist/device/types.d.ts +0 -3
  26. package/dist/device/types.d.ts.map +1 -1
  27. package/dist/executor/ios-executor.d.ts.map +1 -1
  28. package/dist/executor/ios-executor.js +15 -29
  29. package/dist/executor/ios-executor.js.map +1 -1
  30. package/dist/goose-runtime/git.d.ts +4 -0
  31. package/dist/goose-runtime/git.d.ts.map +1 -0
  32. package/dist/goose-runtime/git.js +63 -0
  33. package/dist/goose-runtime/git.js.map +1 -0
  34. package/dist/goose-runtime/runtime.d.ts +5 -0
  35. package/dist/goose-runtime/runtime.d.ts.map +1 -0
  36. package/dist/goose-runtime/runtime.js +141 -0
  37. package/dist/goose-runtime/runtime.js.map +1 -0
  38. package/dist/goose-runtime/types.d.ts +23 -0
  39. package/dist/goose-runtime/types.d.ts.map +1 -0
  40. package/dist/goose-runtime/types.js +2 -0
  41. package/dist/goose-runtime/types.js.map +1 -0
  42. package/dist/goose-runtime/workspace.d.ts +23 -0
  43. package/dist/goose-runtime/workspace.d.ts.map +1 -0
  44. package/dist/goose-runtime/workspace.js +200 -0
  45. package/dist/goose-runtime/workspace.js.map +1 -0
  46. package/dist/scrcpy/preview-status.js +2 -2
  47. package/dist/scrcpy/preview-status.js.map +1 -1
  48. package/dist/scrcpy/server.d.ts +0 -14
  49. package/dist/scrcpy/server.d.ts.map +1 -1
  50. package/dist/scrcpy/server.js +15 -472
  51. package/dist/scrcpy/server.js.map +1 -1
  52. package/dist/storage/repositories/device-repo.d.ts.map +1 -1
  53. package/dist/storage/repositories/device-repo.js +0 -1
  54. package/dist/storage/repositories/device-repo.js.map +1 -1
  55. package/dist/task/guada-online-batch-poller.d.ts +0 -10
  56. package/dist/task/guada-online-batch-poller.d.ts.map +1 -1
  57. package/dist/task/guada-online-batch-poller.js +8 -137
  58. package/dist/task/guada-online-batch-poller.js.map +1 -1
  59. package/dist/task/scheduler.d.ts +5 -0
  60. package/dist/task/scheduler.d.ts.map +1 -1
  61. package/dist/task/scheduler.js +185 -95
  62. package/dist/task/scheduler.js.map +1 -1
  63. package/dist/web/server.d.ts.map +1 -1
  64. package/dist/web/server.js +10 -23
  65. package/dist/web/server.js.map +1 -1
  66. package/package.json +1 -1
@@ -48,7 +48,7 @@
48
48
  * (视频流走二进制帧,不走 JSON)
49
49
  */
50
50
  import { createReadStream, existsSync } from 'node:fs';
51
- import { createServer, get as httpGet, } from 'node:http';
51
+ import { createServer } from 'node:http';
52
52
  import path from 'node:path';
53
53
  import { createRequire } from 'node:module';
54
54
  import { randomUUID } from 'node:crypto';
@@ -61,8 +61,6 @@ import { withTimeout } from './timeout.js';
61
61
  import { androidRecordingManager } from '../recorder/android-recording-manager.js';
62
62
  import { replayAndroidLoginScript } from '../recorder/android-login-replay.js';
63
63
  import { captureAndroidElementState, captureAndroidScreenshotState, getAndroidScreenSize, runAdb, } from '../recorder/android-ui-hierarchy.js';
64
- import { deviceDetector } from '../device/detector.js';
65
- import { ensureIOSWda } from '../executor/ios-wda-manager.js';
66
64
  const logger = createLogger('ScrcpyServer');
67
65
  const requireCjs = createRequire(import.meta.url);
68
66
  function resolveRequestedDeviceId(options, currentDeviceId) {
@@ -227,7 +225,6 @@ export class ScrcpyServer {
227
225
  id: d.serial,
228
226
  name: d.product || d.model || d.serial,
229
227
  status: d.state || 'device',
230
- platform: 'android',
231
228
  }));
232
229
  }
233
230
  catch (error) {
@@ -235,22 +232,6 @@ export class ScrcpyServer {
235
232
  return [];
236
233
  }
237
234
  }
238
- async loadDevicesList() {
239
- const [androidDevices, iosDevices] = await Promise.all([
240
- this.loadDevicesListFromAdb(),
241
- deviceDetector.detectIOSDevices().catch((error) => {
242
- logger.debug(`Failed to detect iOS devices for projection: ${error.message}`);
243
- return [];
244
- }),
245
- ]);
246
- const listedIOSDevices = iosDevices.map((device) => ({
247
- id: device.udid,
248
- name: device.name || device.model || device.udid,
249
- status: device.status || 'device',
250
- platform: 'ios',
251
- }));
252
- return [...androidDevices, ...listedIOSDevices];
253
- }
254
235
  async getDevicesList(forceRefresh = false) {
255
236
  const now = Date.now();
256
237
  const cacheTtlMs = 2000;
@@ -262,7 +243,7 @@ export class ScrcpyServer {
262
243
  if (this.deviceListInFlight) {
263
244
  return this.deviceListInFlight;
264
245
  }
265
- this.deviceListInFlight = this.loadDevicesList()
246
+ this.deviceListInFlight = this.loadDevicesListFromAdb()
266
247
  .then((devices) => {
267
248
  this.lastDeviceList = devices;
268
249
  this.lastDeviceListAt = Date.now();
@@ -462,8 +443,6 @@ export class ScrcpyServer {
462
443
  videoWidth: 0,
463
444
  videoHeight: 0,
464
445
  streamReader: null,
465
- platform: null,
466
- iosProjection: null,
467
446
  };
468
447
  this.sessions.set(ws, state);
469
448
  this.attachSocketHandlers(state);
@@ -511,324 +490,8 @@ export class ScrcpyServer {
511
490
  }
512
491
  state.scrcpyClient = null;
513
492
  }
514
- if (state.iosProjection) {
515
- const projection = state.iosProjection;
516
- projection.stopped = true;
517
- try {
518
- projection.cleanup?.();
519
- }
520
- catch {
521
- // ignore
522
- }
523
- await this.deleteIOSWdaSession(projection);
524
- state.iosProjection = null;
525
- }
526
493
  state.videoWidth = 0;
527
494
  state.videoHeight = 0;
528
- state.platform = null;
529
- }
530
- async connectIOSDeviceForSession(state, deviceId, emitStatus) {
531
- const { ws } = state;
532
- emitStatus('starting-service');
533
- const wda = await ensureIOSWda({
534
- udid: deviceId,
535
- useXcodebuild: false,
536
- log: (message) => logger.info(`[iOSProjection] ${message}`),
537
- });
538
- if (!this.isSessionOpen(state))
539
- return;
540
- const wdaSession = await this.createIOSWdaSession(wda.host, wda.port);
541
- const projection = {
542
- stopped: false,
543
- wdaHost: wda.host,
544
- wdaPort: wda.port,
545
- sessionId: wdaSession.sessionId,
546
- controlWidth: wdaSession.width,
547
- controlHeight: wdaSession.height,
548
- };
549
- state.iosProjection = projection;
550
- state.platform = 'ios';
551
- state.deviceId = deviceId;
552
- this.currentDeviceId = deviceId;
553
- emitStatus('waiting-for-video');
554
- logger.info(`iOS projection started for device=${deviceId},wda=http://${wda.host}:${wda.port},mjpeg=http://${wda.host}:${wda.mjpegPort},client=${state.id}`);
555
- const initialFrame = await this.captureIOSWdaScreenshot(wda.host, wda.port);
556
- if (!this.isSessionOpen(state) || state.iosProjection !== projection || projection.stopped)
557
- return;
558
- state.videoWidth = initialFrame.width;
559
- state.videoHeight = initialFrame.height;
560
- wsSend(ws, 'video-metadata', {
561
- codec: 'mjpeg',
562
- width: initialFrame.width,
563
- height: initialFrame.height,
564
- platform: 'ios',
565
- });
566
- this.sendIOSImageFrame(ws, {
567
- ...initialFrame,
568
- mime: 'image/png',
569
- timestamp: Date.now(),
570
- source: 'screenshot',
571
- });
572
- const startScreenshotFallback = () => {
573
- void this.startIOSWdaScreenshotLoop(state, projection, wda.host, wda.port);
574
- };
575
- const nativeStarted = await this.startIOSNativeMjpegStream(state, projection, wda.host, wda.mjpegPort, startScreenshotFallback);
576
- if (!nativeStarted) {
577
- logger.info(`iOS native MJPEG unavailable, falling back to WDA screenshots: device=${deviceId}`);
578
- startScreenshotFallback();
579
- }
580
- }
581
- sendIOSImageFrame(ws, frame) {
582
- wsSend(ws, 'ios-screenshot-frame', frame);
583
- }
584
- async createIOSWdaSession(host, port) {
585
- let sessionId = null;
586
- try {
587
- const response = await this.iosWdaRequest(host, port, 'POST', '/session', {
588
- capabilities: {
589
- alwaysMatch: {
590
- platformName: 'iOS',
591
- automationName: 'XCUITest',
592
- shouldUseSingletonTestManager: false,
593
- shouldUseTestManagerForVisibilityDetection: false,
594
- },
595
- },
596
- });
597
- sessionId = this.extractIOSWdaSessionId(response);
598
- if (sessionId) {
599
- logger.info(`iOS WDA session created for projection: ${sessionId}`);
600
- await this.configureIOSWdaSession(host, port, sessionId);
601
- }
602
- else {
603
- logger.warn('iOS WDA session response did not include sessionId; touch control will be disabled');
604
- }
605
- }
606
- catch (error) {
607
- logger.warn(`iOS WDA session creation failed, projection will be view-only: ${error.message}`);
608
- }
609
- const size = sessionId
610
- ? await this.getIOSWdaWindowSize(host, port, sessionId).catch((error) => {
611
- logger.warn(`iOS WDA window size failed, using screenshot size for touch mapping: ${error.message}`);
612
- return { width: 0, height: 0 };
613
- })
614
- : { width: 0, height: 0 };
615
- return { sessionId, width: size.width, height: size.height };
616
- }
617
- async configureIOSWdaSession(host, port, sessionId) {
618
- try {
619
- await this.iosWdaRequest(host, port, 'POST', `/session/${sessionId}/appium/settings`, {
620
- snapshotMaxDepth: 50,
621
- elementResponseAttributes: 'type,label,name,value,rect,enabled,visible',
622
- mjpegServerScreenshotQuality: 50,
623
- mjpegServerFramerate: 30,
624
- mjpegScalingFactor: 50,
625
- });
626
- }
627
- catch (error) {
628
- logger.debug(`iOS WDA settings update skipped: ${error.message}`);
629
- }
630
- }
631
- async getIOSWdaWindowSize(host, port, sessionId) {
632
- const response = await this.iosWdaRequest(host, port, 'GET', `/session/${sessionId}/window/size`);
633
- const value = response?.value || response;
634
- const width = Number(value?.width);
635
- const height = Number(value?.height);
636
- if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
637
- throw new Error(`invalid WDA window size: ${JSON.stringify(response)}`);
638
- }
639
- return { width, height };
640
- }
641
- extractIOSWdaSessionId(response) {
642
- const candidates = [
643
- response?.sessionId,
644
- response?.value?.sessionId,
645
- response?.value?.session_id,
646
- ];
647
- const matched = candidates.find((item) => typeof item === 'string' && item.trim());
648
- return matched ? matched.trim() : null;
649
- }
650
- async iosWdaRequest(host, port, method, endpoint, body) {
651
- const controller = new AbortController();
652
- const timer = setTimeout(() => controller.abort(), 8000);
653
- try {
654
- const response = await fetch(`http://${host}:${port}${endpoint}`, {
655
- method,
656
- signal: controller.signal,
657
- headers: body === undefined ? undefined : { 'Content-Type': 'application/json' },
658
- body: body === undefined ? undefined : JSON.stringify(body),
659
- });
660
- const text = await response.text();
661
- const data = text ? JSON.parse(text) : {};
662
- if (!response.ok) {
663
- throw new Error(`WDA ${method} ${endpoint} HTTP ${response.status}: ${text}`);
664
- }
665
- return data;
666
- }
667
- finally {
668
- clearTimeout(timer);
669
- }
670
- }
671
- async deleteIOSWdaSession(projection) {
672
- if (!projection.sessionId)
673
- return;
674
- try {
675
- await this.iosWdaRequest(projection.wdaHost, projection.wdaPort, 'DELETE', `/session/${projection.sessionId}`);
676
- }
677
- catch (error) {
678
- logger.debug(`delete iOS WDA session skipped: ${error.message}`);
679
- }
680
- }
681
- async startIOSWdaScreenshotLoop(state, projection, host, port) {
682
- const { ws } = state;
683
- try {
684
- while (this.isSessionOpen(state) && state.iosProjection === projection && !projection.stopped) {
685
- try {
686
- const frame = await this.captureIOSWdaScreenshot(host, port);
687
- this.sendIOSImageFrame(ws, {
688
- ...frame,
689
- mime: 'image/png',
690
- timestamp: Date.now(),
691
- source: 'screenshot',
692
- });
693
- }
694
- catch (error) {
695
- logger.warn(`iOS screenshot frame failed: ${error.message}`);
696
- wsSend(ws, 'error', { message: `iOS投屏截图失败:${error.message}` });
697
- break;
698
- }
699
- await new Promise((resolve) => setTimeout(resolve, 500));
700
- }
701
- }
702
- finally {
703
- if (state.iosProjection === projection) {
704
- state.iosProjection = null;
705
- }
706
- }
707
- }
708
- startIOSNativeMjpegStream(state, projection, host, port, onFallback) {
709
- const url = `http://${host}:${port}`;
710
- const { ws } = state;
711
- return new Promise((resolve) => {
712
- let settled = false;
713
- let sentFrames = 0;
714
- let buffer = Buffer.alloc(0);
715
- let startupTimer = null;
716
- const settle = (started) => {
717
- if (settled)
718
- return;
719
- settled = true;
720
- if (startupTimer)
721
- clearTimeout(startupTimer);
722
- resolve(started);
723
- };
724
- const req = httpGet(url, (res) => {
725
- const statusCode = res.statusCode || 0;
726
- if (statusCode >= 400) {
727
- res.resume();
728
- settle(false);
729
- return;
730
- }
731
- res.on('data', (chunk) => {
732
- if (!this.isSessionOpen(state) || state.iosProjection !== projection || projection.stopped) {
733
- req.destroy();
734
- return;
735
- }
736
- buffer = Buffer.concat([buffer, chunk]);
737
- if (buffer.length > 8 * 1024 * 1024) {
738
- const lastStart = buffer.lastIndexOf(Buffer.from([0xff, 0xd8]));
739
- buffer = lastStart >= 0 ? buffer.subarray(lastStart) : Buffer.alloc(0);
740
- }
741
- while (true) {
742
- const start = buffer.indexOf(Buffer.from([0xff, 0xd8]));
743
- if (start < 0) {
744
- if (buffer.length > 1024 * 1024)
745
- buffer = Buffer.alloc(0);
746
- break;
747
- }
748
- const end = buffer.indexOf(Buffer.from([0xff, 0xd9]), start + 2);
749
- if (end < 0) {
750
- if (start > 0)
751
- buffer = buffer.subarray(start);
752
- break;
753
- }
754
- const jpeg = buffer.subarray(start, end + 2);
755
- buffer = buffer.subarray(end + 2);
756
- sentFrames += 1;
757
- if (sentFrames === 1) {
758
- logger.info(`iOS projection streaming via native WDA MJPEG: ${url}`);
759
- settle(true);
760
- }
761
- this.sendIOSImageFrame(ws, {
762
- mime: 'image/jpeg',
763
- base64: jpeg.toString('base64'),
764
- width: state.videoWidth,
765
- height: state.videoHeight,
766
- timestamp: Date.now(),
767
- source: 'mjpeg',
768
- });
769
- }
770
- });
771
- res.on('end', () => {
772
- if (sentFrames === 0) {
773
- settle(false);
774
- return;
775
- }
776
- if (this.isSessionOpen(state) && state.iosProjection === projection && !projection.stopped) {
777
- logger.warn(`iOS native MJPEG ended, falling back to WDA screenshots: ${url}`);
778
- onFallback();
779
- }
780
- });
781
- });
782
- startupTimer = setTimeout(() => {
783
- req.destroy();
784
- settle(false);
785
- }, 1500);
786
- projection.cleanup = () => req.destroy();
787
- req.on('error', (error) => {
788
- if (sentFrames === 0) {
789
- logger.debug(`iOS native MJPEG unavailable: ${error.message}`);
790
- settle(false);
791
- return;
792
- }
793
- if (this.isSessionOpen(state) && state.iosProjection === projection && !projection.stopped) {
794
- logger.warn(`iOS native MJPEG failed, falling back to WDA screenshots: ${error.message}`);
795
- onFallback();
796
- }
797
- });
798
- });
799
- }
800
- async captureIOSWdaScreenshot(host, port) {
801
- const controller = new AbortController();
802
- const timer = setTimeout(() => controller.abort(), 5000);
803
- try {
804
- const response = await fetch(`http://${host}:${port}/screenshot`, { signal: controller.signal });
805
- if (!response.ok) {
806
- throw new Error(`WDA screenshot HTTP ${response.status}`);
807
- }
808
- const body = await response.json();
809
- const raw = String(body.value || '').replace(/^data:image\/png;base64,/, '');
810
- if (!raw)
811
- throw new Error('empty WDA screenshot');
812
- const buffer = Buffer.from(raw, 'base64');
813
- const size = this.readPngSize(buffer);
814
- return { base64: raw, ...size };
815
- }
816
- finally {
817
- clearTimeout(timer);
818
- }
819
- }
820
- readPngSize(buffer) {
821
- const isPng = buffer.length >= 24 &&
822
- buffer[0] === 0x89 &&
823
- buffer[1] === 0x50 &&
824
- buffer[2] === 0x4e &&
825
- buffer[3] === 0x47;
826
- if (!isPng)
827
- return { width: 0, height: 0 };
828
- return {
829
- width: buffer.readUInt32BE(16),
830
- height: buffer.readUInt32BE(20),
831
- };
832
495
  }
833
496
  async connectDeviceForSession(state, options = {}, emitStatus) {
834
497
  const { ws } = state;
@@ -837,16 +500,16 @@ export class ScrcpyServer {
837
500
  try {
838
501
  emitStatus('connecting-device');
839
502
  // 重复 connect-device 时,先释放旧资源避免 scrcpy 进程残留。
840
- if (state.scrcpyClient || state.iosProjection) {
503
+ if (state.scrcpyClient) {
841
504
  await this.releaseSession(state);
842
505
  }
843
- if (!this.isSessionOpen(state))
844
- return;
845
- let devices = await this.getDevicesList();
846
506
  if (!this.isSessionOpen(state))
847
507
  return;
848
508
  let targetDeviceId = resolveRequestedDeviceId(safeOptions, this.currentDeviceId);
849
509
  if (!targetDeviceId) {
510
+ const devices = await this.getDevicesList();
511
+ if (!this.isSessionOpen(state))
512
+ return;
850
513
  targetDeviceId = this.selectAvailableDeviceId(devices) || undefined;
851
514
  }
852
515
  if (!targetDeviceId) {
@@ -870,13 +533,6 @@ export class ScrcpyServer {
870
533
  this.deviceConnectLocks.set(connectLockKey, state.id);
871
534
  this.currentDeviceId = targetDeviceId;
872
535
  state.deviceId = targetDeviceId;
873
- const targetDevice = devices.find((device) => device.id === targetDeviceId);
874
- const targetPlatform = targetDevice?.platform === 'ios' ? 'ios' : 'android';
875
- state.platform = targetPlatform;
876
- if (targetPlatform === 'ios') {
877
- await this.connectIOSDeviceForSession(state, targetDeviceId, emitStatus);
878
- return;
879
- }
880
536
  state.adb = await this.getAdb(targetDeviceId);
881
537
  if (!this.isSessionOpen(state)) {
882
538
  await this.releaseSession(state);
@@ -1084,89 +740,6 @@ export class ScrcpyServer {
1084
740
  metaState: 0,
1085
741
  });
1086
742
  };
1087
- const normalizeControlPoint = (raw, width, height) => {
1088
- let x = Number(raw.x);
1089
- let y = Number(raw.y);
1090
- if (!Number.isFinite(x) || !Number.isFinite(y))
1091
- return null;
1092
- if (raw.normalized === true) {
1093
- x = Math.max(0, Math.min(1, x)) * width;
1094
- y = Math.max(0, Math.min(1, y)) * height;
1095
- }
1096
- return {
1097
- x: Math.round(Math.max(0, Math.min(width - 1, x))),
1098
- y: Math.round(Math.max(0, Math.min(height - 1, y))),
1099
- };
1100
- };
1101
- const ensureIOSProjection = () => {
1102
- const projection = state.iosProjection;
1103
- if (!projection || !projection.sessionId) {
1104
- logger.debug('iOS WDA session not ready, ignoring control event');
1105
- return null;
1106
- }
1107
- return projection;
1108
- };
1109
- const handleIOSTouch = async (raw = {}) => {
1110
- const projection = ensureIOSProjection();
1111
- if (!projection)
1112
- return;
1113
- const width = projection.controlWidth || state.videoWidth || 390;
1114
- const height = projection.controlHeight || state.videoHeight || 844;
1115
- const point = normalizeControlPoint(raw, width, height);
1116
- if (!point)
1117
- return;
1118
- const actionStr = String(raw.action || 'down').toLowerCase();
1119
- if (actionStr === 'down') {
1120
- projection.touchStart = { ...point, at: Date.now() };
1121
- return;
1122
- }
1123
- if (actionStr !== 'up' && actionStr !== 'cancel') {
1124
- return;
1125
- }
1126
- const start = projection.touchStart || { ...point, at: Date.now() };
1127
- projection.touchStart = undefined;
1128
- const distance = Math.hypot(point.x - start.x, point.y - start.y);
1129
- if (actionStr === 'cancel')
1130
- return;
1131
- if (distance < 8) {
1132
- try {
1133
- await this.iosWdaRequest(projection.wdaHost, projection.wdaPort, 'POST', `/session/${projection.sessionId}/wda/tap`, { x: point.x, y: point.y });
1134
- }
1135
- catch {
1136
- await this.iosWdaRequest(projection.wdaHost, projection.wdaPort, 'POST', `/session/${projection.sessionId}/wda/tap/0`, { x: point.x, y: point.y });
1137
- }
1138
- return;
1139
- }
1140
- const duration = Math.max(100, Math.min(1500, Date.now() - start.at));
1141
- await this.iosWdaRequest(projection.wdaHost, projection.wdaPort, 'POST', `/session/${projection.sessionId}/actions`, {
1142
- actions: [
1143
- {
1144
- type: 'pointer',
1145
- id: 'finger1',
1146
- parameters: { pointerType: 'touch' },
1147
- actions: [
1148
- { type: 'pointerMove', duration: 0, x: start.x, y: start.y },
1149
- { type: 'pointerDown', button: 0 },
1150
- { type: 'pause', duration: 100 },
1151
- { type: 'pointerMove', duration, x: point.x, y: point.y },
1152
- { type: 'pointerUp', button: 0 },
1153
- ],
1154
- },
1155
- ],
1156
- });
1157
- };
1158
- const pressIOSButton = async (name) => {
1159
- const projection = ensureIOSProjection();
1160
- if (!projection)
1161
- return;
1162
- await this.iosWdaRequest(projection.wdaHost, projection.wdaPort, 'POST', `/session/${projection.sessionId}/wda/pressButton`, { name });
1163
- };
1164
- const typeIOSText = async (text) => {
1165
- const projection = ensureIOSProjection();
1166
- if (!projection || !text)
1167
- return;
1168
- await this.iosWdaRequest(projection.wdaHost, projection.wdaPort, 'POST', `/session/${projection.sessionId}/wda/keys`, { value: text.split('') });
1169
- };
1170
743
  const handlers = {
1171
744
  ping: () => {
1172
745
  wsSend(ws, 'pong');
@@ -1223,20 +796,22 @@ export class ScrcpyServer {
1223
796
  },
1224
797
  'control-touch': async (raw = {}) => {
1225
798
  try {
1226
- if (state.platform === 'ios') {
1227
- await handleIOSTouch(raw);
1228
- return;
1229
- }
1230
799
  const controller = ensureController();
1231
800
  if (!controller)
1232
801
  return;
1233
802
  const videoWidth = state.videoWidth || 1080;
1234
803
  const videoHeight = state.videoHeight || 1920;
1235
804
  const normalized = raw.normalized === true;
1236
- const point = normalizeControlPoint({ ...raw, normalized }, videoWidth, videoHeight);
1237
- if (!point)
805
+ let x = Number(raw.x);
806
+ let y = Number(raw.y);
807
+ if (!Number.isFinite(x) || !Number.isFinite(y))
1238
808
  return;
1239
- const { x, y } = point;
809
+ if (normalized) {
810
+ x = Math.max(0, Math.min(1, x)) * videoWidth;
811
+ y = Math.max(0, Math.min(1, y)) * videoHeight;
812
+ }
813
+ x = Math.round(Math.max(0, Math.min(videoWidth - 1, x)));
814
+ y = Math.round(Math.max(0, Math.min(videoHeight - 1, y)));
1240
815
  const actionStr = String(raw.action || 'down').toLowerCase();
1241
816
  const action = ACTION_MAP[actionStr] ?? 0;
1242
817
  const isUp = actionStr === 'up' || actionStr === 'cancel';
@@ -1306,11 +881,6 @@ export class ScrcpyServer {
1306
881
  },
1307
882
  'control-text': async (raw = {}) => {
1308
883
  try {
1309
- if (state.platform === 'ios') {
1310
- const text = typeof raw === 'string' ? raw : String(raw.text ?? '');
1311
- await typeIOSText(text);
1312
- return;
1313
- }
1314
884
  const controller = ensureController();
1315
885
  if (!controller)
1316
886
  return;
@@ -1342,10 +912,6 @@ export class ScrcpyServer {
1342
912
  },
1343
913
  'control-home': async () => {
1344
914
  try {
1345
- if (state.platform === 'ios') {
1346
- await pressIOSButton('home');
1347
- return;
1348
- }
1349
915
  await pressKey(3);
1350
916
  }
1351
917
  catch (e) {
@@ -1380,29 +946,6 @@ export class ScrcpyServer {
1380
946
  },
1381
947
  'control-scroll': async (raw = {}) => {
1382
948
  try {
1383
- if (state.platform === 'ios') {
1384
- const projection = ensureIOSProjection();
1385
- if (!projection)
1386
- return;
1387
- const width = projection.controlWidth || state.videoWidth || 390;
1388
- const height = projection.controlHeight || state.videoHeight || 844;
1389
- const center = normalizeControlPoint({
1390
- x: raw.x ?? 0.5,
1391
- y: raw.y ?? 0.5,
1392
- normalized: raw.normalized !== false,
1393
- }, width, height);
1394
- if (!center)
1395
- return;
1396
- const dx = Number(raw.scrollX ?? 0) * 120;
1397
- const dy = Number(raw.scrollY ?? -1) * 160;
1398
- await handleIOSTouch({ action: 'down', x: center.x, y: center.y });
1399
- await handleIOSTouch({
1400
- action: 'up',
1401
- x: Math.max(0, Math.min(width - 1, center.x - dx)),
1402
- y: Math.max(0, Math.min(height - 1, center.y - dy)),
1403
- });
1404
- return;
1405
- }
1406
949
  const controller = ensureController();
1407
950
  if (!controller)
1408
951
  return;