@thor-commerce/app-bridge-react 0.7.2 → 0.8.0

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/dist/index.cjs CHANGED
@@ -23,11 +23,16 @@ __export(index_exports, {
23
23
  AppBridge: () => AppBridge,
24
24
  AppBridgeProvider: () => AppBridgeProvider,
25
25
  EMBEDDED_LAUNCH_PARAMS: () => EMBEDDED_LAUNCH_PARAMS,
26
+ EmbeddedAppRuntime: () => EmbeddedAppRuntime,
27
+ THOR_NAVIGATE_EVENT: () => THOR_NAVIGATE_EVENT,
26
28
  buildNavigationUpdatePayload: () => buildNavigationUpdatePayload,
27
29
  createAppBridge: () => createAppBridge,
30
+ getEmbeddedAppRuntime: () => getEmbeddedAppRuntime,
31
+ getOrCreateEmbeddedAppRuntime: () => getOrCreateEmbeddedAppRuntime,
28
32
  isBridgeMessage: () => isBridgeMessage,
29
33
  normalizeBridgeNavigationItem: () => normalizeBridgeNavigationItem,
30
34
  normalizeBridgeNavigationItems: () => normalizeBridgeNavigationItems,
35
+ preserveEmbeddedAppLaunchParams: () => preserveEmbeddedAppLaunchParams,
31
36
  resolveLocalNavigationPath: () => resolveLocalNavigationPath,
32
37
  resolveNavigationDestination: () => resolveNavigationDestination,
33
38
  sanitizeEmbeddedAppPath: () => sanitizeEmbeddedAppPath,
@@ -44,6 +49,7 @@ var EMBEDDED_LAUNCH_PARAMS = [
44
49
  "id_token",
45
50
  "link_source",
46
51
  "locale",
52
+ "project",
47
53
  "protocol",
48
54
  "session",
49
55
  "shop",
@@ -109,6 +115,37 @@ function sanitizeEmbeddedAppPath(path) {
109
115
  }
110
116
  return `${url.pathname}${url.search}${url.hash}`;
111
117
  }
118
+ function preserveEmbeddedAppLaunchParams(path, currentHref) {
119
+ if (!path || !currentHref) {
120
+ return path;
121
+ }
122
+ try {
123
+ const currentUrl = new URL(currentHref, NAVIGATION_BASE_URL);
124
+ const nextUrl = new URL(path, currentUrl.href);
125
+ if (nextUrl.origin !== currentUrl.origin) {
126
+ return path;
127
+ }
128
+ let changed = false;
129
+ for (const key of EMBEDDED_LAUNCH_PARAMS) {
130
+ if (!nextUrl.searchParams.has(key) && currentUrl.searchParams.has(key)) {
131
+ const value = currentUrl.searchParams.get(key);
132
+ if (value != null) {
133
+ nextUrl.searchParams.set(key, value);
134
+ changed = true;
135
+ }
136
+ }
137
+ }
138
+ if (!changed) {
139
+ return path;
140
+ }
141
+ if (/^https?:\/\//i.test(path)) {
142
+ return nextUrl.toString();
143
+ }
144
+ return `${nextUrl.pathname}${nextUrl.search}${nextUrl.hash}`;
145
+ } catch {
146
+ return path;
147
+ }
148
+ }
112
149
  function resolveLocalNavigationPath(href, currentOrigin) {
113
150
  if (!href || href.startsWith("#")) {
114
151
  return null;
@@ -183,13 +220,6 @@ function normalizeBridgeNavigationItems(items) {
183
220
  }
184
221
 
185
222
  // src/core.ts
186
- var PRESERVED_EMBEDDED_DOCUMENT_PARAMS = [
187
- "hmac",
188
- "id_token",
189
- "project",
190
- "tenant",
191
- "timestamp"
192
- ];
193
223
  var DEFAULT_NAMESPACE = "thorcommerce:app-bridge";
194
224
  var DEFAULT_TIMEOUT_MS = 1e4;
195
225
  var DEFAULT_REDIRECT_EVENT_TYPE = "navigation:redirect";
@@ -503,266 +533,165 @@ var AppBridge = class {
503
533
  throw new Error("AppBridge could not resolve a browser window for redirect.");
504
534
  }
505
535
  this.selfWindow.location.assign(
506
- preserveEmbeddedDocumentParams(destination, this.selfWindow.location.href)
536
+ preserveEmbeddedAppLaunchParams(destination, this.selfWindow.location.href) ?? destination
507
537
  );
508
538
  }
509
539
  };
510
- function preserveEmbeddedDocumentParams(destination, currentHref) {
511
- try {
512
- const currentUrl = new URL(currentHref);
513
- const nextUrl = new URL(destination, currentUrl.href);
514
- if (nextUrl.origin !== currentUrl.origin) {
515
- return destination;
516
- }
517
- let changed = false;
518
- for (const key of PRESERVED_EMBEDDED_DOCUMENT_PARAMS) {
519
- if (!nextUrl.searchParams.has(key) && currentUrl.searchParams.has(key)) {
520
- const value = currentUrl.searchParams.get(key);
521
- if (value != null) {
522
- nextUrl.searchParams.set(key, value);
523
- changed = true;
524
- }
525
- }
526
- }
527
- return changed ? nextUrl.toString() : destination;
528
- } catch {
529
- return destination;
530
- }
531
- }
532
540
  function createAppBridge(options = {}) {
533
541
  return new AppBridge(options);
534
542
  }
535
543
 
536
544
  // src/react.tsx
537
545
  var import_react = require("react");
538
- var import_jsx_runtime = require("react/jsx-runtime");
539
- var AppBridgeContext = (0, import_react.createContext)(null);
540
- function AppBridgeProvider({
541
- children,
542
- clientId,
543
- currentPath,
544
- navigationItems,
545
- navigationEventType = "navigation:go",
546
- navigationUpdateEventType = "navigation:update",
547
- namespace,
548
- onNavigate,
549
- readyEventType = "app:ready",
550
- readyPayload,
551
- requestTimeoutMs,
552
- selfWindow,
553
- targetOrigin,
554
- targetWindow
555
- }) {
556
- const [bridge, setBridge] = (0, import_react.useState)(null);
557
- const onNavigateRef = (0, import_react.useRef)(onNavigate);
558
- const normalizedNavigationItems = (0, import_react.useMemo)(
559
- () => normalizeBridgeNavigationItems(navigationItems),
560
- [navigationItems]
561
- );
562
- const sessionTokenCacheRef = (0, import_react.useRef)(null);
563
- const projectRef = (0, import_react.useRef)(readInitialProject(selfWindow));
564
- const pendingSessionTokenRef = (0, import_react.useRef)(null);
565
- (0, import_react.useEffect)(() => {
566
- onNavigateRef.current = onNavigate;
567
- }, [onNavigate]);
568
- (0, import_react.useEffect)(() => {
569
- if (typeof window === "undefined" && !selfWindow) {
570
- return;
546
+
547
+ // src/runtime.ts
548
+ var GLOBAL_RUNTIME_KEY = "__thorEmbeddedAppRuntime__";
549
+ var THOR_NAVIGATE_EVENT = "thor:navigate";
550
+ var EmbeddedAppRuntime = class {
551
+ constructor(config) {
552
+ this.currentPath = null;
553
+ this.navigationItems = [];
554
+ this.navigationEventType = "navigation:go";
555
+ this.navigationUpdateEventType = "navigation:update";
556
+ this.readyEventType = "app:ready";
557
+ this.sessionTokenCache = null;
558
+ this.pendingSessionToken = null;
559
+ const resolvedWindow = config.selfWindow ?? (typeof window !== "undefined" ? window : void 0);
560
+ if (!resolvedWindow) {
561
+ throw new Error("EmbeddedAppRuntime requires a browser window.");
571
562
  }
572
- const resolvedTargetOrigin = targetOrigin ?? getReferrerOrigin(selfWindow);
573
- const resolvedAllowedOrigins = resolvedTargetOrigin ? [resolvedTargetOrigin] : void 0;
574
- const nextBridge = createAppBridge({
575
- allowedOrigins: resolvedAllowedOrigins,
576
- clientId,
577
- namespace,
578
- requestTimeoutMs,
579
- selfWindow,
563
+ this.selfWindow = resolvedWindow;
564
+ this.targetOrigin = config.targetOrigin ?? getReferrerOrigin(resolvedWindow);
565
+ this.clientId = config.clientId;
566
+ this.project = readInitialProject(resolvedWindow);
567
+ this.readyPayload = config.readyPayload ?? { clientId: config.clientId };
568
+ this.bridge = createAppBridge({
569
+ allowedOrigins: this.targetOrigin ? [this.targetOrigin] : void 0,
570
+ namespace: config.namespace,
571
+ requestTimeoutMs: config.requestTimeoutMs,
572
+ selfWindow: resolvedWindow,
580
573
  source: "embedded-app",
581
574
  target: "dashboard",
582
- targetOrigin: resolvedTargetOrigin,
583
- targetWindow
575
+ targetOrigin: this.targetOrigin,
576
+ targetWindow: config.targetWindow
584
577
  });
585
- setBridge(nextBridge);
586
- return () => {
587
- setBridge((currentBridge) => currentBridge === nextBridge ? null : currentBridge);
588
- nextBridge.destroy();
589
- };
590
- }, [
591
- clientId,
592
- namespace,
593
- requestTimeoutMs,
594
- selfWindow,
595
- targetOrigin,
596
- targetWindow
597
- ]);
598
- (0, import_react.useEffect)(() => {
599
- if (!bridge || !bridge.hasTargetWindow()) {
600
- return;
601
- }
602
- bridge.send(
603
- readyEventType,
604
- readyPayload ?? {
605
- clientId
606
- }
607
- );
608
- }, [bridge, clientId, readyEventType, readyPayload]);
609
- (0, import_react.useEffect)(() => {
610
- if (!bridge || !bridge.hasTargetWindow()) {
611
- return;
612
- }
613
- bridge.send("navigation:items:update", {
614
- items: normalizedNavigationItems
615
- });
616
- }, [bridge, normalizedNavigationItems]);
617
- (0, import_react.useEffect)(() => {
618
- if (!bridge || !bridge.hasTargetWindow() || !currentPath) {
619
- return;
620
- }
621
- bridge.send(navigationUpdateEventType, buildNavigationUpdatePayload(currentPath));
622
- }, [bridge, currentPath, navigationUpdateEventType]);
623
- (0, import_react.useEffect)(() => {
624
- if (!bridge || !onNavigate) {
625
- return;
626
- }
627
- return bridge.on(navigationEventType, (message) => {
578
+ this.removeNavigationRequestHandler = this.bridge.on(this.navigationEventType, (message) => {
628
579
  const destination = resolveNavigationDestination(message.payload);
629
580
  if (!destination) {
630
581
  return;
631
582
  }
632
- onNavigateRef.current?.(destination, message);
583
+ const nextPath = preserveEmbeddedAppLaunchParams(
584
+ destination,
585
+ this.selfWindow.location.href
586
+ );
587
+ this.navigate(nextPath ?? destination, message);
633
588
  });
634
- }, [bridge, navigationEventType, onNavigate]);
635
- (0, import_react.useEffect)(() => {
636
- if (!bridge || !onNavigate || typeof document === "undefined" || typeof window === "undefined") {
637
- return;
638
- }
639
- const handleLocalNavigation = (path) => {
640
- const sanitizedPath = sanitizeEmbeddedAppPath(path);
641
- if (!sanitizedPath) {
642
- return;
643
- }
644
- onNavigateRef.current?.(sanitizedPath);
645
- };
646
- const handleDocumentClick = (event) => {
647
- if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
648
- return;
649
- }
650
- const target = event.target;
651
- if (!(target instanceof Element)) {
652
- return;
653
- }
654
- const anchor = target.closest("a[href]");
655
- if (!(anchor instanceof HTMLAnchorElement)) {
656
- return;
657
- }
658
- if (anchor.hasAttribute("download")) {
659
- return;
660
- }
661
- const targetWindow2 = anchor.target.toLowerCase();
662
- const href = anchor.getAttribute("href");
663
- if (!href) {
664
- return;
665
- }
666
- if (targetWindow2 === "_top" || targetWindow2 === "_parent") {
667
- event.preventDefault();
668
- bridge.redirectToRemote(anchor.href);
669
- return;
670
- }
671
- if (targetWindow2 && targetWindow2 !== "_self") {
672
- return;
673
- }
674
- const nextPath = resolveLocalNavigationPath(href, window.location.origin);
675
- if (!nextPath) {
676
- return;
677
- }
678
- event.preventDefault();
679
- handleLocalNavigation(nextPath);
680
- };
681
- const originalOpen = window.open.bind(window);
682
- window.open = (url, target, features) => {
683
- if (url == null) {
684
- return originalOpen(url, target, features);
685
- }
686
- const href = typeof url === "string" ? url : url.toString();
687
- const targetName = (target ?? "").toLowerCase();
688
- if (targetName === "_top" || targetName === "_parent") {
689
- bridge.redirectToRemote(new URL(href, window.location.href).toString());
690
- return null;
589
+ this.removeNavigationInterceptors = installNavigationInterceptors({
590
+ bridge: this.bridge,
591
+ selfWindow: this.selfWindow,
592
+ navigate: (path) => this.navigate(path)
593
+ });
594
+ this.restoreFetch = installFetchInterceptor({
595
+ bridge: this.bridge,
596
+ getClientId: () => this.clientId,
597
+ getProject: () => this.project,
598
+ setProject: (project) => {
599
+ this.project = project;
600
+ },
601
+ readCachedToken: () => this.sessionTokenCache,
602
+ writeCachedToken: (token) => {
603
+ this.sessionTokenCache = token;
604
+ },
605
+ readPendingToken: () => this.pendingSessionToken,
606
+ writePendingToken: (token) => {
607
+ this.pendingSessionToken = token;
691
608
  }
692
- if (!targetName || targetName === "_self") {
693
- const nextPath = resolveLocalNavigationPath(href, window.location.origin);
694
- if (nextPath) {
695
- handleLocalNavigation(nextPath);
696
- return window;
697
- }
609
+ });
610
+ this.update(config);
611
+ }
612
+ update(config) {
613
+ if (config.clientId) {
614
+ this.clientId = config.clientId;
615
+ if (!this.readyPayload || typeof this.readyPayload === "object" && this.readyPayload !== null && "clientId" in this.readyPayload) {
616
+ this.readyPayload = config.readyPayload ?? { clientId: config.clientId };
698
617
  }
699
- return originalOpen(url, target, features);
700
- };
701
- document.addEventListener("click", handleDocumentClick, true);
702
- return () => {
703
- document.removeEventListener("click", handleDocumentClick, true);
704
- window.open = originalOpen;
705
- };
706
- }, [bridge, onNavigate]);
707
- (0, import_react.useEffect)(() => {
708
- if (!bridge || typeof window === "undefined") {
618
+ }
619
+ if (config.currentPath !== void 0) {
620
+ this.currentPath = config.currentPath;
621
+ }
622
+ if (config.navigationItems !== void 0) {
623
+ this.navigationItems = normalizeBridgeNavigationItems(config.navigationItems);
624
+ }
625
+ if (config.onNavigate !== void 0) {
626
+ this.onNavigate = config.onNavigate;
627
+ }
628
+ if (config.readyEventType) {
629
+ this.readyEventType = config.readyEventType;
630
+ }
631
+ if (config.readyPayload !== void 0) {
632
+ this.readyPayload = config.readyPayload;
633
+ }
634
+ if (config.targetWindow !== void 0) {
635
+ this.bridge.setTargetWindow(config.targetWindow ?? null);
636
+ }
637
+ this.syncBridgeState();
638
+ }
639
+ clearNavigationHandler() {
640
+ this.onNavigate = void 0;
641
+ }
642
+ destroy() {
643
+ this.removeNavigationRequestHandler?.();
644
+ this.removeNavigationInterceptors();
645
+ this.restoreFetch();
646
+ this.bridge.destroy();
647
+ }
648
+ syncBridgeState() {
649
+ if (!this.bridge.hasTargetWindow()) {
709
650
  return;
710
651
  }
711
- const originalFetch = globalThis.fetch.bind(globalThis);
712
- const interceptedFetch = async (input, init) => {
713
- if (!shouldAttachSessionToken(input)) {
714
- return originalFetch(input, init);
715
- }
716
- const existingAuthorization = getExistingAuthorization(input, init);
717
- if (existingAuthorization) {
718
- return originalFetch(input, init);
719
- }
720
- const nextHeaders = new Headers(
721
- input instanceof Request ? input.headers : init?.headers
652
+ this.bridge.send(this.readyEventType, this.readyPayload);
653
+ this.bridge.send("navigation:items:update", {
654
+ items: this.navigationItems
655
+ });
656
+ if (this.currentPath) {
657
+ this.bridge.send(
658
+ this.navigationUpdateEventType,
659
+ buildNavigationUpdatePayload(this.currentPath)
722
660
  );
723
- if (projectRef.current && !nextHeaders.has("X-Thor-Project")) {
724
- nextHeaders.set("X-Thor-Project", projectRef.current);
725
- }
726
- try {
727
- const sessionToken = await getSessionToken({
728
- bridge,
729
- clientId,
730
- pendingSessionTokenRef,
731
- projectRef,
732
- sessionTokenCacheRef
733
- });
734
- nextHeaders.set("Authorization", `Bearer ${sessionToken}`);
735
- } catch {
736
- if (!projectRef.current) {
737
- throw new Error("Missing Thor embedded session token");
738
- }
739
- }
740
- if (!nextHeaders.has("X-Requested-With")) {
741
- nextHeaders.set("X-Requested-With", "XMLHttpRequest");
742
- }
743
- if (input instanceof Request) {
744
- return originalFetch(
745
- new Request(input, {
746
- headers: nextHeaders
747
- })
748
- );
749
- }
750
- return originalFetch(input, {
751
- ...init,
752
- headers: nextHeaders
753
- });
754
- };
755
- globalThis.fetch = interceptedFetch;
756
- window.fetch = interceptedFetch;
757
- return () => {
758
- globalThis.fetch = originalFetch;
759
- window.fetch = originalFetch;
760
- };
761
- }, [bridge, clientId]);
762
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppBridgeContext.Provider, { value: bridge, children });
661
+ }
662
+ }
663
+ navigate(path, message) {
664
+ if (this.onNavigate) {
665
+ this.onNavigate(path, message);
666
+ return;
667
+ }
668
+ if (dispatchNavigateEvent(this.selfWindow.document, path)) {
669
+ return;
670
+ }
671
+ this.selfWindow.location.assign(path);
672
+ }
673
+ };
674
+ function getOrCreateEmbeddedAppRuntime(config) {
675
+ const globalScope = globalThis;
676
+ const existingRuntime = globalScope[GLOBAL_RUNTIME_KEY];
677
+ if (existingRuntime) {
678
+ existingRuntime.update(config);
679
+ return existingRuntime;
680
+ }
681
+ const runtime = new EmbeddedAppRuntime(config);
682
+ globalScope[GLOBAL_RUNTIME_KEY] = runtime;
683
+ return runtime;
763
684
  }
764
- function useAppBridge() {
765
- return (0, import_react.useContext)(AppBridgeContext);
685
+ function getEmbeddedAppRuntime() {
686
+ return globalThis[GLOBAL_RUNTIME_KEY] ?? null;
687
+ }
688
+ function dispatchNavigateEvent(document, path) {
689
+ const event = new CustomEvent(THOR_NAVIGATE_EVENT, {
690
+ cancelable: true,
691
+ detail: { path }
692
+ });
693
+ document.dispatchEvent(event);
694
+ return event.defaultPrevented;
766
695
  }
767
696
  function getReferrerOrigin(explicitWindow) {
768
697
  const resolvedWindow = explicitWindow ?? (typeof window !== "undefined" ? window : void 0);
@@ -776,40 +705,190 @@ function getReferrerOrigin(explicitWindow) {
776
705
  return void 0;
777
706
  }
778
707
  }
708
+ function installNavigationInterceptors({
709
+ bridge,
710
+ selfWindow,
711
+ navigate
712
+ }) {
713
+ const document = selfWindow.document;
714
+ const handleLocalNavigation = (path) => {
715
+ const sanitizedPath = sanitizeEmbeddedAppPath(path);
716
+ if (!sanitizedPath) {
717
+ return;
718
+ }
719
+ const nextPath = preserveEmbeddedAppLaunchParams(
720
+ sanitizedPath,
721
+ selfWindow.location.href
722
+ );
723
+ navigate(nextPath ?? sanitizedPath);
724
+ };
725
+ const handleDocumentClick = (event) => {
726
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
727
+ return;
728
+ }
729
+ const target = event.target;
730
+ if (!(target instanceof Element)) {
731
+ return;
732
+ }
733
+ const anchor = target.closest("a[href]");
734
+ if (!(anchor instanceof HTMLAnchorElement)) {
735
+ return;
736
+ }
737
+ if (anchor.hasAttribute("download")) {
738
+ return;
739
+ }
740
+ const targetWindow = anchor.target.toLowerCase();
741
+ const href = anchor.getAttribute("href");
742
+ if (!href) {
743
+ return;
744
+ }
745
+ if (targetWindow === "_top" || targetWindow === "_parent") {
746
+ event.preventDefault();
747
+ bridge.redirectToRemote(anchor.href);
748
+ return;
749
+ }
750
+ if (targetWindow && targetWindow !== "_self") {
751
+ return;
752
+ }
753
+ const nextPath = resolveLocalNavigationPath(href, selfWindow.location.origin);
754
+ if (!nextPath) {
755
+ return;
756
+ }
757
+ event.preventDefault();
758
+ handleLocalNavigation(nextPath);
759
+ };
760
+ const originalOpen = selfWindow.open.bind(selfWindow);
761
+ selfWindow.open = (url, target, features) => {
762
+ if (url == null) {
763
+ return originalOpen(url, target, features);
764
+ }
765
+ const href = typeof url === "string" ? url : url.toString();
766
+ const targetName = (target ?? "").toLowerCase();
767
+ if (targetName === "_top" || targetName === "_parent") {
768
+ bridge.redirectToRemote(new URL(href, selfWindow.location.href).toString());
769
+ return null;
770
+ }
771
+ if (!targetName || targetName === "_self") {
772
+ const nextPath = resolveLocalNavigationPath(
773
+ href,
774
+ selfWindow.location.origin
775
+ );
776
+ if (nextPath) {
777
+ handleLocalNavigation(nextPath);
778
+ return selfWindow;
779
+ }
780
+ }
781
+ return originalOpen(url, target, features);
782
+ };
783
+ document.addEventListener("click", handleDocumentClick, true);
784
+ return () => {
785
+ document.removeEventListener("click", handleDocumentClick, true);
786
+ selfWindow.open = originalOpen;
787
+ };
788
+ }
789
+ function installFetchInterceptor({
790
+ bridge,
791
+ getClientId,
792
+ getProject,
793
+ setProject,
794
+ readCachedToken,
795
+ writeCachedToken,
796
+ readPendingToken,
797
+ writePendingToken
798
+ }) {
799
+ if (typeof window === "undefined") {
800
+ return () => {
801
+ };
802
+ }
803
+ const originalFetch = globalThis.fetch.bind(globalThis);
804
+ const interceptedFetch = async (input, init) => {
805
+ if (!shouldAttachSessionToken(input)) {
806
+ return originalFetch(input, init);
807
+ }
808
+ const existingAuthorization = getExistingAuthorization(input, init);
809
+ if (existingAuthorization) {
810
+ return originalFetch(input, init);
811
+ }
812
+ const nextHeaders = new Headers(
813
+ input instanceof Request ? input.headers : init?.headers
814
+ );
815
+ try {
816
+ const sessionToken = await getSessionToken({
817
+ bridge,
818
+ clientId: getClientId(),
819
+ project: getProject,
820
+ setProject,
821
+ readCachedToken,
822
+ writeCachedToken,
823
+ readPendingToken,
824
+ writePendingToken
825
+ });
826
+ nextHeaders.set("Authorization", `Bearer ${sessionToken}`);
827
+ } catch {
828
+ if (!getProject()) {
829
+ throw new Error("Missing Thor embedded session token");
830
+ }
831
+ }
832
+ if (!nextHeaders.has("X-Requested-With")) {
833
+ nextHeaders.set("X-Requested-With", "XMLHttpRequest");
834
+ }
835
+ if (input instanceof Request) {
836
+ return originalFetch(
837
+ new Request(input, {
838
+ headers: nextHeaders
839
+ })
840
+ );
841
+ }
842
+ return originalFetch(input, {
843
+ ...init,
844
+ headers: nextHeaders
845
+ });
846
+ };
847
+ globalThis.fetch = interceptedFetch;
848
+ window.fetch = interceptedFetch;
849
+ return () => {
850
+ globalThis.fetch = originalFetch;
851
+ window.fetch = originalFetch;
852
+ };
853
+ }
779
854
  async function getSessionToken({
780
855
  bridge,
781
856
  clientId,
782
- pendingSessionTokenRef,
783
- projectRef,
784
- sessionTokenCacheRef
857
+ project,
858
+ setProject,
859
+ readCachedToken,
860
+ writeCachedToken,
861
+ readPendingToken,
862
+ writePendingToken
785
863
  }) {
786
- const cachedToken = sessionTokenCacheRef.current;
864
+ const cachedToken = readCachedToken();
787
865
  if (cachedToken && !isExpired(cachedToken.expiresAt)) {
788
866
  return cachedToken.token;
789
867
  }
790
- if (pendingSessionTokenRef.current) {
791
- return pendingSessionTokenRef.current;
868
+ const pendingToken = readPendingToken();
869
+ if (pendingToken) {
870
+ return pendingToken;
792
871
  }
793
- const pendingToken = bridge.getSessionToken({ clientId }).then((response) => {
872
+ const nextPendingToken = bridge.getSessionToken({ clientId }).then((response) => {
794
873
  const token = response.sessionToken ?? response.idToken;
795
874
  if (!token) {
796
875
  throw new Error("Missing Thor embedded session token");
797
876
  }
798
877
  if (response.project) {
799
- projectRef.current = response.project;
878
+ setProject(response.project);
800
879
  }
801
- sessionTokenCacheRef.current = {
880
+ writeCachedToken({
802
881
  token,
803
882
  expiresAt: normalizeTokenExpiry(token, response.exp)
804
- };
805
- pendingSessionTokenRef.current = null;
883
+ });
884
+ writePendingToken(null);
806
885
  return token;
807
886
  }).catch((error) => {
808
- pendingSessionTokenRef.current = null;
887
+ writePendingToken(null);
809
888
  throw error;
810
889
  });
811
- pendingSessionTokenRef.current = pendingToken;
812
- return pendingToken;
890
+ writePendingToken(nextPendingToken);
891
+ return nextPendingToken;
813
892
  }
814
893
  function shouldAttachSessionToken(input) {
815
894
  if (typeof window === "undefined") {
@@ -875,16 +954,88 @@ function readInitialProject(explicitWindow) {
875
954
  return null;
876
955
  }
877
956
  }
957
+
958
+ // src/react.tsx
959
+ var import_jsx_runtime = require("react/jsx-runtime");
960
+ var AppBridgeContext = (0, import_react.createContext)(null);
961
+ function AppBridgeProvider({
962
+ children,
963
+ clientId,
964
+ currentPath,
965
+ navigationItems,
966
+ navigationEventType = "navigation:go",
967
+ navigationUpdateEventType = "navigation:update",
968
+ namespace,
969
+ onNavigate,
970
+ readyEventType = "app:ready",
971
+ readyPayload,
972
+ requestTimeoutMs,
973
+ selfWindow,
974
+ targetOrigin,
975
+ targetWindow
976
+ }) {
977
+ const [bridge, setBridge] = (0, import_react.useState)(null);
978
+ (0, import_react.useEffect)(() => {
979
+ if (typeof window === "undefined" && !selfWindow) {
980
+ return;
981
+ }
982
+ const runtime = getOrCreateEmbeddedAppRuntime({
983
+ clientId,
984
+ currentPath,
985
+ navigationEventType,
986
+ navigationItems,
987
+ navigationUpdateEventType,
988
+ namespace,
989
+ onNavigate,
990
+ readyEventType,
991
+ readyPayload,
992
+ requestTimeoutMs,
993
+ selfWindow,
994
+ targetOrigin,
995
+ targetWindow
996
+ });
997
+ setBridge(runtime.bridge);
998
+ return () => {
999
+ runtime.clearNavigationHandler();
1000
+ setBridge(
1001
+ (currentBridge) => currentBridge === runtime.bridge ? null : currentBridge
1002
+ );
1003
+ };
1004
+ }, [
1005
+ clientId,
1006
+ currentPath,
1007
+ navigationEventType,
1008
+ navigationItems,
1009
+ navigationUpdateEventType,
1010
+ namespace,
1011
+ onNavigate,
1012
+ readyEventType,
1013
+ readyPayload,
1014
+ requestTimeoutMs,
1015
+ selfWindow,
1016
+ targetOrigin,
1017
+ targetWindow
1018
+ ]);
1019
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppBridgeContext.Provider, { value: bridge, children });
1020
+ }
1021
+ function useAppBridge() {
1022
+ return (0, import_react.useContext)(AppBridgeContext);
1023
+ }
878
1024
  // Annotate the CommonJS export names for ESM import in node:
879
1025
  0 && (module.exports = {
880
1026
  AppBridge,
881
1027
  AppBridgeProvider,
882
1028
  EMBEDDED_LAUNCH_PARAMS,
1029
+ EmbeddedAppRuntime,
1030
+ THOR_NAVIGATE_EVENT,
883
1031
  buildNavigationUpdatePayload,
884
1032
  createAppBridge,
1033
+ getEmbeddedAppRuntime,
1034
+ getOrCreateEmbeddedAppRuntime,
885
1035
  isBridgeMessage,
886
1036
  normalizeBridgeNavigationItem,
887
1037
  normalizeBridgeNavigationItems,
1038
+ preserveEmbeddedAppLaunchParams,
888
1039
  resolveLocalNavigationPath,
889
1040
  resolveNavigationDestination,
890
1041
  sanitizeEmbeddedAppPath,