@rynx-ai/daemon 0.1.11-beta.38 → 0.1.11-beta.39

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/plugin-channel-lark",
3
- "version": "0.1.11-beta.38",
3
+ "version": "0.1.11-beta.39",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -42,6 +42,14 @@ const NETWORK_AUTO_ATTACH_FILTER = [
42
42
  { type: "shared_worker" },
43
43
  { exclude: true },
44
44
  ];
45
+ const NETWORK_AUTO_ATTACH_PARAMS = {
46
+ autoAttach: true,
47
+ // Pause new targets until Fetch interception is installed. This is required
48
+ // for Session Headers to cover a popup's very first navigation request.
49
+ waitForDebuggerOnStart: true,
50
+ flatten: true,
51
+ filter: NETWORK_AUTO_ATTACH_FILTER,
52
+ };
45
53
  export function createHeadlessBrowserHost(options = {}) {
46
54
  return new HeadlessBrowserHost(options);
47
55
  }
@@ -235,12 +243,7 @@ class HeadlessBrowserHandle {
235
243
  }
236
244
  async initialize() {
237
245
  await this.host.connection.command("Target.setDiscoverTargets", { discover: true });
238
- await this.host.connection.command("Target.setAutoAttach", {
239
- autoAttach: true,
240
- waitForDebuggerOnStart: true,
241
- flatten: true,
242
- filter: NETWORK_AUTO_ATTACH_FILTER,
243
- });
246
+ await this.host.connection.command("Target.setAutoAttach", NETWORK_AUTO_ATTACH_PARAMS);
244
247
  await this.createFreshStartupPage();
245
248
  await this.refreshTargets();
246
249
  const requestedUrl = this.host.input.url;
@@ -433,14 +436,28 @@ class HeadlessBrowserHandle {
433
436
  this.closing = true;
434
437
  this.closePromise = (async () => {
435
438
  await Promise.allSettled([...this.surfaceSources.values()].map((source) => source.close()));
436
- this.host.connection.close();
437
439
  let terminated = false;
438
440
  try {
439
- await terminateProcessGroup(this.host.child, this.host.exit, this.host.terminationGraceMs);
441
+ let exited = false;
442
+ if (this.host.connection.connected) {
443
+ const closeCommand = this.host.connection.command("Browser.close")
444
+ .catch(() => undefined);
445
+ exited = await Promise.race([
446
+ this.host.exit.then(() => true),
447
+ delay(this.host.terminationGraceMs).then(() => false),
448
+ ]);
449
+ if (!exited)
450
+ this.host.connection.close();
451
+ await closeCommand;
452
+ }
453
+ if (!exited) {
454
+ await terminateProcessGroup(this.host.child, this.host.exit, this.host.terminationGraceMs);
455
+ }
440
456
  removeMatchingProcessLease(this.host.profileDir, this.host.ownerToken);
441
457
  terminated = true;
442
458
  }
443
459
  finally {
460
+ this.host.connection.close();
444
461
  if (terminated)
445
462
  this.host.release();
446
463
  this.eventQueue.end();
@@ -501,11 +518,19 @@ class HeadlessBrowserHandle {
501
518
  try {
502
519
  const targets = await this.readPageTargets();
503
520
  const next = new Map();
504
- for (const target of targets) {
505
- if (next.size >= RUNTIME_BROWSER_MAX_PAGES) {
506
- throw new SessionBrowserHostError("capacity", "Headless Browser has too many Pages");
521
+ const orderedTargets = [
522
+ ...targets.filter((target) => this.targets.has(target.targetId)),
523
+ ...targets.filter((target) => !this.targets.has(target.targetId)),
524
+ ];
525
+ for (const target of orderedTargets) {
526
+ if (this.closingTargetIds.has(target.targetId))
527
+ continue;
528
+ if (next.size < RUNTIME_BROWSER_MAX_PAGES) {
529
+ next.set(target.targetId, target);
530
+ continue;
507
531
  }
508
- next.set(target.targetId, target);
532
+ this.closingTargetIds.add(target.targetId);
533
+ await this.networkExclusive(() => this.closeRejectedTarget(undefined, target, false));
509
534
  }
510
535
  for (const targetId of this.targets.keys()) {
511
536
  if (!next.has(targetId))
@@ -700,6 +725,13 @@ class HeadlessBrowserHandle {
700
725
  this.targetBySession.set(sessionId, target.targetId);
701
726
  this.networkTargetTypeBySession.set(sessionId, target.type);
702
727
  if (target.type === "page") {
728
+ if (this.closingTargetIds.has(target.targetId) ||
729
+ (!this.targets.has(target.targetId) &&
730
+ this.targets.size >= RUNTIME_BROWSER_MAX_PAGES)) {
731
+ this.closingTargetIds.add(target.targetId);
732
+ void this.networkExclusive(() => this.closeRejectedTarget(sessionId, target, waitingForDebugger)).catch(() => undefined);
733
+ return;
734
+ }
703
735
  this.targets.set(target.targetId, target);
704
736
  let pending;
705
737
  pending = this.networkExclusive(() => this.initializeAttachedTarget(sessionId, target, waitingForDebugger))
@@ -744,6 +776,16 @@ class HeadlessBrowserHandle {
744
776
  const target = parseTargetInfo(event.params.targetInfo);
745
777
  if (target.type !== "page")
746
778
  return;
779
+ if (event.method === "Target.targetCreated" &&
780
+ !this.targets.has(target.targetId) &&
781
+ this.targets.size >= RUNTIME_BROWSER_MAX_PAGES) {
782
+ this.closingTargetIds.add(target.targetId);
783
+ void this.networkExclusive(() => this.closeRejectedTarget(undefined, target, false))
784
+ .catch(() => undefined);
785
+ return;
786
+ }
787
+ if (this.closingTargetIds.has(target.targetId))
788
+ return;
747
789
  this.targets.set(target.targetId, target);
748
790
  if (event.method === "Target.targetCreated")
749
791
  this.activeTargetId = target.targetId;
@@ -765,7 +807,12 @@ class HeadlessBrowserHandle {
765
807
  if (event.method === "Target.targetCrashed") {
766
808
  const targetId = optionalCdpId(event.params.targetId);
767
809
  if (targetId && this.targets.has(targetId)) {
768
- this.markUnavailable("A Headless Browser Page renderer crashed");
810
+ const target = this.targets.get(targetId);
811
+ this.closingTargetIds.add(targetId);
812
+ this.dropTarget(targetId);
813
+ void this.networkExclusive(() => this.closeRejectedTarget(undefined, target, false))
814
+ .catch(() => undefined);
815
+ this.eventQueue.changed(this.browserGeneration);
769
816
  }
770
817
  return;
771
818
  }
@@ -778,6 +825,10 @@ class HeadlessBrowserHandle {
778
825
  const targetId = event.sessionId ? this.targetBySession.get(event.sessionId) : undefined;
779
826
  if (!targetId)
780
827
  return;
828
+ if (event.method === "Page.javascriptDialogOpening") {
829
+ void this.dismissJavaScriptDialog(event.sessionId, targetId);
830
+ return;
831
+ }
781
832
  if (event.method === "Inspector.detached") {
782
833
  this.dropPageSession(event.sessionId);
783
834
  return;
@@ -856,17 +907,14 @@ class HeadlessBrowserHandle {
856
907
  }
857
908
  async initializeAttachedTarget(sessionId, target, waitingForDebugger) {
858
909
  try {
859
- await this.host.connection.command("Network.enable", {}, sessionId);
910
+ // Fetch.enable succeeds while a waitForDebugger target is paused and must
911
+ // precede its first request. Network.enable does not: current Chrome for
912
+ // Testing builds leave that command pending until the target is resumed.
860
913
  await this.host.connection.command("Fetch.enable", {
861
914
  patterns: [{ urlPattern: "*", requestStage: "Request" }],
862
915
  }, sessionId);
863
916
  if (target.type === "page" || target.type === "iframe") {
864
- await this.host.connection.command("Target.setAutoAttach", {
865
- autoAttach: true,
866
- waitForDebuggerOnStart: true,
867
- flatten: true,
868
- filter: NETWORK_AUTO_ATTACH_FILTER,
869
- }, sessionId);
917
+ await this.host.connection.command("Target.setAutoAttach", NETWORK_AUTO_ATTACH_PARAMS, sessionId);
870
918
  }
871
919
  if (target.type === "page") {
872
920
  await this.host.connection.command("Page.enable", {}, sessionId);
@@ -874,14 +922,52 @@ class HeadlessBrowserHandle {
874
922
  if (waitingForDebugger) {
875
923
  await this.host.connection.command("Runtime.runIfWaitingForDebugger", {}, sessionId);
876
924
  }
925
+ await this.host.connection.command("Network.enable", {}, sessionId);
877
926
  }
878
927
  catch (error) {
879
928
  if (isMissingTargetError(error) || isTransientPageTransitionError(error)) {
880
929
  this.dropAttachedSession(sessionId);
881
930
  throw missingPage(error);
882
931
  }
883
- this.markUnavailable("Headless Browser could not initialize a network target");
884
- throw this.mapOperationError(error);
932
+ if (!this.host.connection.connected || error instanceof CdpDisconnectedError) {
933
+ this.markUnavailable("The native CDP observer disconnected");
934
+ throw this.mapOperationError(error);
935
+ }
936
+ await this.closeRejectedTarget(sessionId, target, waitingForDebugger);
937
+ if (target.type === "page")
938
+ throw missingPage(error);
939
+ throw unavailable("Headless Browser isolated a network target that failed setup", error);
940
+ }
941
+ }
942
+ async closeRejectedTarget(sessionId, target, waitingForDebugger) {
943
+ if (target.type === "page") {
944
+ this.closingTargetIds.add(target.targetId);
945
+ this.dropTarget(target.targetId);
946
+ }
947
+ else if (sessionId) {
948
+ this.dropAttachedSession(sessionId);
949
+ }
950
+ let closed = false;
951
+ try {
952
+ if (this.host.connection.connected) {
953
+ const result = await this.host.connection.command("Target.closeTarget", {
954
+ targetId: target.targetId,
955
+ });
956
+ closed = result.success === true;
957
+ }
958
+ }
959
+ catch (error) {
960
+ if (!isMissingTargetError(error) && !isTransientPageTransitionError(error)) {
961
+ if (!this.host.connection.connected || error instanceof CdpDisconnectedError) {
962
+ this.markUnavailable("The native CDP observer disconnected");
963
+ }
964
+ }
965
+ }
966
+ if (!closed && waitingForDebugger && sessionId && this.host.connection.connected) {
967
+ await this.host.connection.command("Runtime.runIfWaitingForDebugger", {}, sessionId).catch(() => undefined);
968
+ }
969
+ if (!closed && sessionId && this.host.connection.connected) {
970
+ await this.host.connection.command("Target.detachFromTarget", { sessionId }).catch(() => undefined);
885
971
  }
886
972
  }
887
973
  async detachAuxiliaryPageSession(sessionId) {
@@ -898,6 +984,27 @@ class HeadlessBrowserHandle {
898
984
  this.dropAttachedSession(sessionId);
899
985
  }
900
986
  }
987
+ async dismissJavaScriptDialog(sessionId, targetId) {
988
+ try {
989
+ // Rynx has no native dialog presentation surface. Dismiss every modal
990
+ // deterministically so alert/confirm/prompt/beforeunload cannot block all
991
+ // later CDP commands for this Page.
992
+ await this.host.connection.command("Page.handleJavaScriptDialog", { accept: false }, sessionId);
993
+ }
994
+ catch (error) {
995
+ if (isMissingTargetError(error) || isTransientPageTransitionError(error)) {
996
+ this.dropAttachedSession(sessionId);
997
+ return;
998
+ }
999
+ if (!this.host.connection.connected || error instanceof CdpDisconnectedError) {
1000
+ this.markUnavailable("The native CDP observer disconnected");
1001
+ return;
1002
+ }
1003
+ const target = this.targets.get(targetId);
1004
+ if (target)
1005
+ await this.closeRejectedTarget(sessionId, target, false);
1006
+ }
1007
+ }
901
1008
  async continuePausedRequest(sessionId, params) {
902
1009
  const requestId = requiredCdpId(params.requestId, "Fetch.requestPaused requestId");
903
1010
  const config = this.requestHeaders;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/daemon",
3
- "version": "0.1.11-beta.38",
3
+ "version": "0.1.11-beta.39",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -51,17 +51,18 @@
51
51
  "tar": "^7.5.19",
52
52
  "undici": "^7.28.0",
53
53
  "ws": "^8.21.0",
54
- "@rynx-ai/core": "0.1.11-beta.38",
55
- "@rynx-ai/emulator": "0.1.11-beta.38",
56
- "@rynx-ai/plugin-runner": "0.1.11-beta.38",
57
- "@rynx-ai/plugin-sdk": "0.1.11-beta.38",
58
- "@rynx-ai/protocol": "0.1.11-beta.38",
59
- "@rynx-ai/remote-runtime-client": "0.1.11-beta.38",
60
- "@rynx-ai/server": "0.1.11-beta.38"
54
+ "@rynx-ai/core": "0.1.11-beta.39",
55
+ "@rynx-ai/emulator": "0.1.11-beta.39",
56
+ "@rynx-ai/plugin-runner": "0.1.11-beta.39",
57
+ "@rynx-ai/plugin-sdk": "0.1.11-beta.39",
58
+ "@rynx-ai/protocol": "0.1.11-beta.39",
59
+ "@rynx-ai/remote-runtime-client": "0.1.11-beta.39",
60
+ "@rynx-ai/server": "0.1.11-beta.39"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/ws": "^8.18.1",
64
- "@rynx-ai/plugin-channel-lark": "0.1.11-beta.38"
64
+ "@rynx-ai/browser-cdp": "0.1.11-beta.39",
65
+ "@rynx-ai/plugin-channel-lark": "0.1.11-beta.39"
65
66
  },
66
67
  "scripts": {
67
68
  "build": "rm -rf dist bundled-plugins && tsc -p tsconfig.json && chmod +x dist/index-daemon.js && node ../../scripts/stage-bundled-plugins.mjs",