@nextop-os/browser-node 0.0.26 → 0.0.28

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,11 +1,11 @@
1
+ import {
2
+ isBrowserSessionPartitionAllowed,
3
+ resolveBrowserSessionPartition
4
+ } from "../chunk-UTXZLRPE.js";
1
5
  import {
2
6
  normalizeBrowserComparableUrl,
3
7
  resolveBrowserNavigationUrl
4
8
  } from "../chunk-2ZAMKGGP.js";
5
- import {
6
- isBrowserSessionPartitionAllowed,
7
- resolveBrowserSessionPartition
8
- } from "../chunk-OTK5YBCK.js";
9
9
 
10
10
  // src/electron-main/loopbackPreviewProxy.ts
11
11
  import {
@@ -13,12 +13,8 @@ import {
13
13
  request as httpRequest
14
14
  } from "http";
15
15
  import { request as httpsRequest } from "https";
16
- import { createRequire } from "module";
17
16
  import { pipeline } from "stream";
18
- var require2 = createRequire(import.meta.url);
19
- var wsModule = require2("ws");
20
- var WebSocket = wsModule;
21
- var { WebSocketServer } = wsModule;
17
+ import WebSocket, { WebSocketServer } from "ws";
22
18
  var defaultLoopbackPreviewCacheTtlMs = 3e4;
23
19
  var loopbackHostPattern = /^(localhost|127(?:\.\d{1,3}){0,3})$/i;
24
20
  var loopbackPreviewProxyBypassRules = "*;<-loopback>;https://*;wss://*";
@@ -463,6 +459,26 @@ function normalizeWebSocketCloseCode(code) {
463
459
  var browserPreviewMaxWidth = 260;
464
460
  var browserPreviewMaxHeight = 170;
465
461
  var abortedNavigationErrorCode = -3;
462
+ function canGuestGoBack(contents) {
463
+ return contents.navigationHistory?.canGoBack() ?? contents.canGoBack();
464
+ }
465
+ function canGuestGoForward(contents) {
466
+ return contents.navigationHistory?.canGoForward() ?? contents.canGoForward();
467
+ }
468
+ function goGuestBack(contents) {
469
+ if (contents.navigationHistory) {
470
+ contents.navigationHistory.goBack();
471
+ return;
472
+ }
473
+ contents.goBack();
474
+ }
475
+ function goGuestForward(contents) {
476
+ if (contents.navigationHistory) {
477
+ contents.navigationHistory.goForward();
478
+ return;
479
+ }
480
+ contents.goForward();
481
+ }
466
482
  function resolveBrowserNodeUrlError(resolved) {
467
483
  if (resolved.errorCode === "invalid-url") {
468
484
  return { code: "invalid-url" };
@@ -500,6 +516,28 @@ function resizeBrowserPreviewImage(image) {
500
516
  function isAbortedNavigationError(input) {
501
517
  return input.errorCode === abortedNavigationErrorCode || input.errorDescription === "ERR_ABORTED";
502
518
  }
519
+ function resolveBrowserNavigationOrigin(url) {
520
+ const resolved = resolveBrowserNavigationUrl(url);
521
+ if (!resolved.url) {
522
+ return null;
523
+ }
524
+ try {
525
+ return new URL(resolved.url).origin;
526
+ } catch {
527
+ return null;
528
+ }
529
+ }
530
+ function isBrowserNavigationAllowedByPolicy(input) {
531
+ if (!input.policy) {
532
+ return true;
533
+ }
534
+ if (input.policy.mode === "same-origin") {
535
+ const policyOrigin = resolveBrowserNavigationOrigin(input.policy.originUrl);
536
+ const nextOrigin = resolveBrowserNavigationOrigin(input.url);
537
+ return policyOrigin !== null && nextOrigin !== null && policyOrigin === nextOrigin;
538
+ }
539
+ return true;
540
+ }
503
541
  async function applyPreferredColorSchemeToGuest(session, logger, syncPreferredColorScheme, scheme) {
504
542
  const contents = session.contents;
505
543
  if (!contents || contents.isDestroyed() || !syncPreferredColorScheme || scheme === null || session.appliedColorScheme === scheme) {
@@ -540,6 +578,12 @@ function createBrowserGuestManager({
540
578
  if (input?.sessionMode !== void 0) {
541
579
  existing.sessionMode = input.sessionMode;
542
580
  }
581
+ if (input?.sessionPartition !== void 0) {
582
+ existing.sessionPartition = input.sessionPartition;
583
+ }
584
+ if (input?.navigationPolicy !== void 0) {
585
+ existing.navigationPolicy = input.navigationPolicy;
586
+ }
543
587
  if (input?.url !== void 0) {
544
588
  existing.desiredUrl = input.url;
545
589
  }
@@ -551,9 +595,11 @@ function createBrowserGuestManager({
551
595
  desiredUrl: input?.url ?? "about:blank",
552
596
  lifecycle: "cold",
553
597
  listeners: [],
598
+ navigationPolicy: input?.navigationPolicy ?? null,
554
599
  nodeId,
555
600
  profileId: input?.profileId ?? null,
556
601
  sessionMode: input?.sessionMode ?? "shared",
602
+ sessionPartition: input?.sessionPartition ?? null,
557
603
  webContentsId: null
558
604
  };
559
605
  sessions.set(nodeId, session);
@@ -562,8 +608,8 @@ function createBrowserGuestManager({
562
608
  const publishState = (session) => {
563
609
  const contents = session.contents && !session.contents.isDestroyed() ? session.contents : null;
564
610
  emit({
565
- canGoBack: contents ? contents.canGoBack() : false,
566
- canGoForward: contents ? contents.canGoForward() : false,
611
+ canGoBack: contents ? canGuestGoBack(contents) : false,
612
+ canGoForward: contents ? canGuestGoForward(contents) : false,
567
613
  isAttachedToWindow: Boolean(contents),
568
614
  isLoading: contents ? contents.isLoading() : false,
569
615
  isOccluded: session.lifecycle === "cold",
@@ -613,10 +659,22 @@ function createBrowserGuestManager({
613
659
  const onFailLoad = (...args) => {
614
660
  const errorCode = typeof args[1] === "number" ? args[1] : void 0;
615
661
  const errorDescription = typeof args[2] === "string" ? args[2] : void 0;
662
+ const validatedUrl = typeof args[3] === "string" ? args[3] : void 0;
663
+ const isMainFrame = typeof args[4] === "boolean" ? args[4] : void 0;
616
664
  if (isAbortedNavigationError({ errorCode, errorDescription })) {
617
665
  publishState(session);
618
666
  return;
619
667
  }
668
+ logger?.warn?.("Browser Node guest navigation failed", {
669
+ currentUrl: contents.getURL(),
670
+ desiredUrl: session.desiredUrl,
671
+ errorCode,
672
+ errorDescription,
673
+ isMainFrame,
674
+ nodeId: session.nodeId,
675
+ validatedUrl,
676
+ webContentsId: session.webContentsId
677
+ });
620
678
  emit({
621
679
  code: "navigation-failed",
622
680
  diagnosticMessage: errorDescription,
@@ -627,12 +685,26 @@ function createBrowserGuestManager({
627
685
  publishState(session);
628
686
  };
629
687
  const onDestroyed = () => detachGuest(session);
688
+ const onWillNavigate = (...args) => {
689
+ const event = args[0] && typeof args[0] === "object" && "preventDefault" in args[0] ? args[0] : null;
690
+ const url = typeof args[1] === "string" ? args[1] : "";
691
+ if (url.length === 0 || isBrowserNavigationAllowedByPolicy({
692
+ policy: session.navigationPolicy,
693
+ url
694
+ })) {
695
+ return;
696
+ }
697
+ event?.preventDefault?.();
698
+ openExternalFromGuest(url);
699
+ publishState(session);
700
+ };
630
701
  const records = [
631
702
  { event: "did-start-loading", listener: onStateChange },
632
703
  { event: "did-stop-loading", listener: onStateChange },
633
704
  { event: "did-navigate", listener: onStateChange },
634
705
  { event: "did-navigate-in-page", listener: onStateChange },
635
706
  { event: "page-title-updated", listener: onStateChange },
707
+ { event: "will-navigate", listener: onWillNavigate },
636
708
  { event: "did-fail-load", listener: onFailLoad },
637
709
  { event: "destroyed", listener: onDestroyed }
638
710
  ];
@@ -657,14 +729,35 @@ function createBrowserGuestManager({
657
729
  publishState(session);
658
730
  return;
659
731
  }
732
+ if (!isBrowserNavigationAllowedByPolicy({
733
+ policy: session.navigationPolicy,
734
+ url: resolved.url
735
+ })) {
736
+ openExternalFromGuest(resolved.url);
737
+ publishState(session);
738
+ return;
739
+ }
660
740
  const currentComparable = normalizeBrowserComparableUrl(contents.getURL());
661
741
  const nextComparable = normalizeBrowserComparableUrl(resolved.url);
662
742
  if (currentComparable && currentComparable === nextComparable) {
663
743
  publishState(session);
664
744
  return;
665
745
  }
666
- await contents.loadURL(resolved.url);
667
- publishState(session);
746
+ try {
747
+ await contents.loadURL(resolved.url);
748
+ } catch (error) {
749
+ logger?.warn?.("Browser Node guest loadURL failed", {
750
+ currentUrl: contents.getURL(),
751
+ desiredUrl: session.desiredUrl,
752
+ error: error instanceof Error ? error.message : String(error),
753
+ nodeId: session.nodeId,
754
+ resolvedUrl: resolved.url,
755
+ webContentsId: session.webContentsId
756
+ });
757
+ throw error;
758
+ } finally {
759
+ publishState(session);
760
+ }
668
761
  };
669
762
  const openExternalFromGuest = (url) => {
670
763
  const resolved = resolveBrowserNavigationUrl(url);
@@ -686,8 +779,10 @@ function createBrowserGuestManager({
686
779
  throw new Error("Browser Node rejected navigation URL");
687
780
  }
688
781
  const session = getSession(input.nodeId, {
782
+ navigationPolicy: input.navigationPolicy,
689
783
  profileId: input.profileId,
690
784
  sessionMode: input.sessionMode,
785
+ sessionPartition: input.sessionPartition,
691
786
  url: resolved.url
692
787
  });
693
788
  session.lifecycle = "active";
@@ -721,8 +816,8 @@ function createBrowserGuestManager({
721
816
  }
722
817
  const contents = session.contents && !session.contents.isDestroyed() ? session.contents : null;
723
818
  return {
724
- canGoBack: contents ? contents.canGoBack() : false,
725
- canGoForward: contents ? contents.canGoForward() : false,
819
+ canGoBack: contents ? canGuestGoBack(contents) : false,
820
+ canGoForward: contents ? canGuestGoForward(contents) : false,
726
821
  currentUrl: contents ? contents.getURL() : null,
727
822
  desiredUrl: session.desiredUrl,
728
823
  isAttachedToWindow: Boolean(contents),
@@ -731,6 +826,7 @@ function createBrowserGuestManager({
731
826
  nodeId: session.nodeId,
732
827
  profileId: session.profileId,
733
828
  sessionMode: session.sessionMode,
829
+ sessionPartition: session.sessionPartition,
734
830
  title: contents ? contents.getTitle() : null,
735
831
  webContentsDestroyed: session.contents ? session.contents.isDestroyed() : null,
736
832
  webContentsId: session.webContentsId
@@ -738,15 +834,15 @@ function createBrowserGuestManager({
738
834
  },
739
835
  goBack(input) {
740
836
  const contents = sessions.get(input.nodeId)?.contents;
741
- if (contents && !contents.isDestroyed() && contents.canGoBack()) {
742
- contents.goBack();
837
+ if (contents && !contents.isDestroyed() && canGuestGoBack(contents)) {
838
+ goGuestBack(contents);
743
839
  }
744
840
  return Promise.resolve();
745
841
  },
746
842
  goForward(input) {
747
843
  const contents = sessions.get(input.nodeId)?.contents;
748
- if (contents && !contents.isDestroyed() && contents.canGoForward()) {
749
- contents.goForward();
844
+ if (contents && !contents.isDestroyed() && canGuestGoForward(contents)) {
845
+ goGuestForward(contents);
750
846
  }
751
847
  return Promise.resolve();
752
848
  },
@@ -755,22 +851,29 @@ function createBrowserGuestManager({
755
851
  if (!resolved.url) {
756
852
  throw new Error("Browser Node rejected navigation URL");
757
853
  }
758
- const session = getSession(input.nodeId, { url: resolved.url });
854
+ const session = getSession(input.nodeId, {
855
+ navigationPolicy: input.navigationPolicy,
856
+ url: resolved.url
857
+ });
759
858
  session.lifecycle = "active";
760
859
  await loadDesiredUrl(session);
761
860
  },
762
861
  async prepareSession(input) {
763
862
  await prepareSession?.(input);
764
863
  getSession(input.nodeId, {
864
+ navigationPolicy: input.navigationPolicy,
765
865
  profileId: input.profileId,
766
- sessionMode: input.sessionMode
866
+ sessionMode: input.sessionMode,
867
+ sessionPartition: input.sessionPartition
767
868
  });
768
869
  },
769
870
  async registerGuest(input) {
770
871
  await prepareSession?.({
771
872
  nodeId: input.nodeId,
772
873
  profileId: input.profileId,
773
- sessionMode: input.sessionMode
874
+ sessionMode: input.sessionMode,
875
+ sessionPartition: input.sessionPartition,
876
+ navigationPolicy: input.navigationPolicy
774
877
  });
775
878
  const contents = resolveWebContents(input.webContentsId);
776
879
  if (!contents || contents.isDestroyed()) {
@@ -785,8 +888,10 @@ function createBrowserGuestManager({
785
888
  );
786
889
  }
787
890
  const session = getSession(input.nodeId, {
891
+ navigationPolicy: input.navigationPolicy,
788
892
  profileId: input.profileId,
789
- sessionMode: input.sessionMode
893
+ sessionMode: input.sessionMode,
894
+ sessionPartition: input.sessionPartition
790
895
  });
791
896
  if (session.webContentsId === input.webContentsId && session.contents === contents) {
792
897
  publishState(session);
@@ -836,12 +941,17 @@ function registerBrowserNodeElectronMain(input) {
836
941
  const managersByWindow = /* @__PURE__ */ new WeakMap();
837
942
  const loopbackPreviewProxy = input.loopbackPreviewRouting !== void 0 ? createBrowserNodeLoopbackPreviewProxy({
838
943
  logger: input.logger,
839
- resolveSession: async ({ profileId, sessionMode }) => {
944
+ resolveSession: async ({
945
+ profileId,
946
+ sessionMode,
947
+ sessionPartition
948
+ }) => {
840
949
  const { session } = await import("electron");
841
950
  return session.fromPartition(
842
951
  resolveBrowserSessionPartition({
843
952
  profileId,
844
- sessionMode
953
+ sessionMode,
954
+ sessionPartition
845
955
  })
846
956
  );
847
957
  },
@@ -938,10 +1048,14 @@ function registerBrowserNodeElectronMain(input) {
938
1048
  }
939
1049
 
940
1050
  // src/electron-main/webviewSecurity.ts
941
- function isBrowserNodeWebviewAttach(params) {
942
- return params["data-browser-node-webview"] === "true" || isBrowserSessionPartitionAllowed(params.partition);
1051
+ function isBrowserNodeInitialWebviewUrl(url) {
1052
+ return (url ?? "").trim() === "about:blank";
1053
+ }
1054
+ function isBrowserNodeWebviewAttach(params, allowedSessionPartitions) {
1055
+ return params["data-browser-node-webview"] === "true" || isBrowserSessionPartitionAllowed(params.partition, allowedSessionPartitions);
943
1056
  }
944
1057
  function enforceBrowserWebviewSecurity({
1058
+ allowedSessionPartitions,
945
1059
  params,
946
1060
  resolvePreload,
947
1061
  webPreferences
@@ -955,20 +1069,24 @@ function enforceBrowserWebviewSecurity({
955
1069
  webPreferences.webSecurity = true;
956
1070
  delete webPreferences.preload;
957
1071
  const partition = params.partition;
958
- if (!partition || !isBrowserSessionPartitionAllowed(partition)) {
1072
+ if (!partition || !isBrowserSessionPartitionAllowed(partition, allowedSessionPartitions)) {
959
1073
  return {
960
1074
  allowed: false,
961
1075
  reason: "Unsupported Browser Node session partition"
962
1076
  };
963
1077
  }
964
- const resolved = resolveBrowserNavigationUrl(params.src ?? "about:blank");
965
- if (!resolved.url) {
966
- return {
967
- allowed: false,
968
- reason: "Unsupported browser URL"
969
- };
1078
+ if (isBrowserNodeInitialWebviewUrl(params.src)) {
1079
+ params.src = "about:blank";
1080
+ } else {
1081
+ const resolved = resolveBrowserNavigationUrl(params.src ?? "about:blank");
1082
+ if (!resolved.url) {
1083
+ return {
1084
+ allowed: false,
1085
+ reason: "Unsupported browser URL"
1086
+ };
1087
+ }
1088
+ params.src = resolved.url;
970
1089
  }
971
- params.src = resolved.url;
972
1090
  const preload = resolvePreload?.({ params: { ...params } });
973
1091
  const resolvedPreload = typeof preload === "string" ? preload.trim() : "";
974
1092
  if (resolvedPreload.length > 0) {
@@ -977,20 +1095,28 @@ function enforceBrowserWebviewSecurity({
977
1095
  return { allowed: true, reason: null };
978
1096
  }
979
1097
  function installBrowserWebviewSecurity({
1098
+ allowedSessionPartitions,
980
1099
  contents,
981
1100
  logger,
982
1101
  onGuestAttached,
983
1102
  openExternal,
984
1103
  resolvePreload,
985
- shouldHandleWebview = isBrowserNodeWebviewAttach
1104
+ shouldHandleWebview
986
1105
  }) {
987
1106
  let pendingBrowserAttachCount = 0;
988
1107
  const handleWillAttachWebview = (event, webPreferences, params) => {
989
- if (!shouldHandleWebview(params)) {
1108
+ const shouldHandle = shouldHandleWebview?.(params) ?? isBrowserNodeWebviewAttach(params, allowedSessionPartitions);
1109
+ logger?.debug?.("Browser Node webview will attach", {
1110
+ partition: params.partition ?? null,
1111
+ shouldHandle,
1112
+ src: params.src ?? null
1113
+ });
1114
+ if (!shouldHandle) {
990
1115
  return;
991
1116
  }
992
1117
  pendingBrowserAttachCount += 1;
993
1118
  const result = enforceBrowserWebviewSecurity({
1119
+ allowedSessionPartitions,
994
1120
  params,
995
1121
  resolvePreload,
996
1122
  webPreferences
@@ -999,14 +1125,27 @@ function installBrowserWebviewSecurity({
999
1125
  pendingBrowserAttachCount = Math.max(0, pendingBrowserAttachCount - 1);
1000
1126
  logger?.warn?.("Browser Node webview blocked", { reason: result.reason });
1001
1127
  event.preventDefault();
1128
+ return;
1002
1129
  }
1130
+ logger?.debug?.("Browser Node webview attach allowed", {
1131
+ partition: params.partition ?? null,
1132
+ src: params.src ?? null
1133
+ });
1003
1134
  };
1004
1135
  const handleDidAttachWebview = (_event, guestContents) => {
1005
1136
  if (pendingBrowserAttachCount <= 0) {
1137
+ logger?.debug?.("Browser Node webview did attach ignored", {
1138
+ guestWebContentsId: guestContents.id ?? null,
1139
+ pendingBrowserAttachCount
1140
+ });
1006
1141
  return;
1007
1142
  }
1008
1143
  pendingBrowserAttachCount -= 1;
1009
1144
  onGuestAttached?.(guestContents);
1145
+ logger?.debug?.("Browser Node webview guest attached", {
1146
+ guestWebContentsId: guestContents.id ?? null,
1147
+ pendingBrowserAttachCount
1148
+ });
1010
1149
  guestContents.setWindowOpenHandler(({ url }) => {
1011
1150
  const resolved = resolveBrowserNavigationUrl(url);
1012
1151
  if (resolved.url) {