@thor-commerce/app-bridge-react 0.7.3 → 0.9.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,8 +23,12 @@ __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,
@@ -539,242 +543,218 @@ function createAppBridge(options = {}) {
539
543
 
540
544
  // src/react.tsx
541
545
  var import_react = require("react");
542
- var import_jsx_runtime = require("react/jsx-runtime");
543
- var AppBridgeContext = (0, import_react.createContext)(null);
544
- function AppBridgeProvider({
545
- children,
546
- clientId,
547
- currentPath,
548
- navigationItems,
549
- navigationEventType = "navigation:go",
550
- navigationUpdateEventType = "navigation:update",
551
- namespace,
552
- onNavigate,
553
- readyEventType = "app:ready",
554
- readyPayload,
555
- requestTimeoutMs,
556
- selfWindow,
557
- targetOrigin,
558
- targetWindow
559
- }) {
560
- const [bridge, setBridge] = (0, import_react.useState)(null);
561
- const onNavigateRef = (0, import_react.useRef)(onNavigate);
562
- const normalizedNavigationItems = (0, import_react.useMemo)(
563
- () => normalizeBridgeNavigationItems(navigationItems),
564
- [navigationItems]
565
- );
566
- const sessionTokenCacheRef = (0, import_react.useRef)(null);
567
- const projectRef = (0, import_react.useRef)(readInitialProject(selfWindow));
568
- const pendingSessionTokenRef = (0, import_react.useRef)(null);
569
- (0, import_react.useEffect)(() => {
570
- onNavigateRef.current = onNavigate;
571
- }, [onNavigate]);
572
- (0, import_react.useEffect)(() => {
573
- if (typeof window === "undefined" && !selfWindow) {
574
- 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.hasSentReady = false;
558
+ this.lastSentNavigationItemsKey = null;
559
+ this.lastReportedPath = null;
560
+ this.pendingHostNavigationPath = null;
561
+ this.sessionTokenCache = null;
562
+ this.pendingSessionToken = null;
563
+ const resolvedWindow = config.selfWindow ?? (typeof window !== "undefined" ? window : void 0);
564
+ if (!resolvedWindow) {
565
+ throw new Error("EmbeddedAppRuntime requires a browser window.");
575
566
  }
576
- const resolvedTargetOrigin = targetOrigin ?? getReferrerOrigin(selfWindow);
577
- const resolvedAllowedOrigins = resolvedTargetOrigin ? [resolvedTargetOrigin] : void 0;
578
- const nextBridge = createAppBridge({
579
- allowedOrigins: resolvedAllowedOrigins,
580
- clientId,
581
- namespace,
582
- requestTimeoutMs,
583
- selfWindow,
567
+ this.selfWindow = resolvedWindow;
568
+ this.targetOrigin = config.targetOrigin ?? getReferrerOrigin(resolvedWindow);
569
+ this.clientId = config.clientId;
570
+ this.currentPath = readCurrentPath(resolvedWindow);
571
+ this.project = readInitialProject(resolvedWindow);
572
+ this.readyPayload = config.readyPayload ?? { clientId: config.clientId };
573
+ this.bridge = createAppBridge({
574
+ allowedOrigins: this.targetOrigin ? [this.targetOrigin] : void 0,
575
+ namespace: config.namespace,
576
+ requestTimeoutMs: config.requestTimeoutMs,
577
+ selfWindow: resolvedWindow,
584
578
  source: "embedded-app",
585
579
  target: "dashboard",
586
- targetOrigin: resolvedTargetOrigin,
587
- targetWindow
580
+ targetOrigin: this.targetOrigin,
581
+ targetWindow: config.targetWindow
588
582
  });
589
- setBridge(nextBridge);
590
- return () => {
591
- setBridge((currentBridge) => currentBridge === nextBridge ? null : currentBridge);
592
- nextBridge.destroy();
593
- };
594
- }, [
595
- clientId,
596
- namespace,
597
- requestTimeoutMs,
598
- selfWindow,
599
- targetOrigin,
600
- targetWindow
601
- ]);
602
- (0, import_react.useEffect)(() => {
603
- if (!bridge || !bridge.hasTargetWindow()) {
604
- return;
605
- }
606
- bridge.send(
607
- readyEventType,
608
- readyPayload ?? {
609
- clientId
610
- }
611
- );
612
- }, [bridge, clientId, readyEventType, readyPayload]);
613
- (0, import_react.useEffect)(() => {
614
- if (!bridge || !bridge.hasTargetWindow()) {
615
- return;
616
- }
617
- bridge.send("navigation:items:update", {
618
- items: normalizedNavigationItems
619
- });
620
- }, [bridge, normalizedNavigationItems]);
621
- (0, import_react.useEffect)(() => {
622
- if (!bridge || !bridge.hasTargetWindow() || !currentPath) {
623
- return;
624
- }
625
- bridge.send(navigationUpdateEventType, buildNavigationUpdatePayload(currentPath));
626
- }, [bridge, currentPath, navigationUpdateEventType]);
627
- (0, import_react.useEffect)(() => {
628
- if (!bridge || !onNavigate) {
629
- return;
630
- }
631
- return bridge.on(navigationEventType, (message) => {
583
+ this.removeNavigationRequestHandler = this.bridge.on(this.navigationEventType, (message) => {
632
584
  const destination = resolveNavigationDestination(message.payload);
633
585
  if (!destination) {
634
586
  return;
635
587
  }
588
+ this.pendingHostNavigationPath = sanitizeEmbeddedAppPath(destination) ?? destination;
636
589
  const nextPath = preserveEmbeddedAppLaunchParams(
637
590
  destination,
638
- typeof window !== "undefined" ? window.location.href : void 0
591
+ this.selfWindow.location.href
639
592
  );
640
- onNavigateRef.current?.(nextPath ?? destination, message);
593
+ this.navigate(nextPath ?? destination, message);
641
594
  });
642
- }, [bridge, navigationEventType, onNavigate]);
643
- (0, import_react.useEffect)(() => {
644
- if (!bridge || !onNavigate || typeof document === "undefined" || typeof window === "undefined") {
645
- return;
646
- }
647
- const handleLocalNavigation = (path) => {
648
- const sanitizedPath = sanitizeEmbeddedAppPath(path);
649
- if (!sanitizedPath) {
650
- return;
651
- }
652
- const nextPath = preserveEmbeddedAppLaunchParams(
653
- sanitizedPath,
654
- window.location.href
655
- );
656
- onNavigateRef.current?.(nextPath ?? sanitizedPath);
657
- };
658
- const handleDocumentClick = (event) => {
659
- if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
660
- return;
661
- }
662
- const target = event.target;
663
- if (!(target instanceof Element)) {
664
- return;
665
- }
666
- const anchor = target.closest("a[href]");
667
- if (!(anchor instanceof HTMLAnchorElement)) {
668
- return;
669
- }
670
- if (anchor.hasAttribute("download")) {
671
- return;
595
+ this.removeNavigationInterceptors = installNavigationInterceptors({
596
+ bridge: this.bridge,
597
+ selfWindow: this.selfWindow,
598
+ navigate: (path) => this.navigate(path)
599
+ });
600
+ this.removeHistoryObserver = installHistoryObserver({
601
+ selfWindow: this.selfWindow,
602
+ onChange: (path) => {
603
+ this.currentPath = path;
604
+ this.syncBridgeState();
672
605
  }
673
- const targetWindow2 = anchor.target.toLowerCase();
674
- const href = anchor.getAttribute("href");
675
- if (!href) {
676
- return;
606
+ });
607
+ this.restoreFetch = installFetchInterceptor({
608
+ bridge: this.bridge,
609
+ getClientId: () => this.clientId,
610
+ getProject: () => this.project,
611
+ setProject: (project) => {
612
+ this.project = project;
613
+ },
614
+ readCachedToken: () => this.sessionTokenCache,
615
+ writeCachedToken: (token) => {
616
+ this.sessionTokenCache = token;
617
+ },
618
+ readPendingToken: () => this.pendingSessionToken,
619
+ writePendingToken: (token) => {
620
+ this.pendingSessionToken = token;
677
621
  }
678
- if (targetWindow2 === "_top" || targetWindow2 === "_parent") {
679
- event.preventDefault();
680
- bridge.redirectToRemote(anchor.href);
681
- return;
622
+ });
623
+ this.update(config);
624
+ }
625
+ update(config) {
626
+ if (config.clientId) {
627
+ this.clientId = config.clientId;
628
+ if (!this.readyPayload || typeof this.readyPayload === "object" && this.readyPayload !== null && "clientId" in this.readyPayload) {
629
+ this.readyPayload = config.readyPayload ?? { clientId: config.clientId };
682
630
  }
683
- if (targetWindow2 && targetWindow2 !== "_self") {
631
+ }
632
+ if (config.currentPath !== void 0) {
633
+ this.currentPath = config.currentPath;
634
+ }
635
+ if (config.navigationItems !== void 0) {
636
+ this.navigationItems = normalizeBridgeNavigationItems(config.navigationItems);
637
+ }
638
+ if (config.onNavigate !== void 0) {
639
+ this.onNavigate = config.onNavigate;
640
+ }
641
+ if (config.readyEventType) {
642
+ this.readyEventType = config.readyEventType;
643
+ }
644
+ if (config.readyPayload !== void 0) {
645
+ this.readyPayload = config.readyPayload;
646
+ }
647
+ if (config.targetWindow !== void 0) {
648
+ this.bridge.setTargetWindow(config.targetWindow ?? null);
649
+ }
650
+ this.syncBridgeState();
651
+ }
652
+ clearNavigationHandler() {
653
+ this.onNavigate = void 0;
654
+ }
655
+ destroy() {
656
+ this.removeNavigationRequestHandler?.();
657
+ this.removeNavigationInterceptors();
658
+ this.removeHistoryObserver();
659
+ this.restoreFetch();
660
+ this.bridge.destroy();
661
+ }
662
+ syncBridgeState() {
663
+ if (!this.bridge.hasTargetWindow()) {
664
+ return;
665
+ }
666
+ if (!this.hasSentReady) {
667
+ this.bridge.send(this.readyEventType, this.readyPayload);
668
+ this.hasSentReady = true;
669
+ }
670
+ const navigationItemsKey = JSON.stringify(this.navigationItems);
671
+ if (navigationItemsKey !== this.lastSentNavigationItemsKey) {
672
+ this.bridge.send("navigation:items:update", {
673
+ items: this.navigationItems
674
+ });
675
+ this.lastSentNavigationItemsKey = navigationItemsKey;
676
+ }
677
+ if (this.currentPath) {
678
+ if (this.currentPath === this.pendingHostNavigationPath) {
679
+ this.pendingHostNavigationPath = null;
680
+ this.lastReportedPath = this.currentPath;
684
681
  return;
685
682
  }
686
- const nextPath = resolveLocalNavigationPath(href, window.location.origin);
687
- if (!nextPath) {
683
+ if (this.currentPath === this.lastReportedPath) {
688
684
  return;
689
685
  }
690
- event.preventDefault();
691
- handleLocalNavigation(nextPath);
692
- };
693
- const originalOpen = window.open.bind(window);
694
- window.open = (url, target, features) => {
695
- if (url == null) {
696
- return originalOpen(url, target, features);
697
- }
698
- const href = typeof url === "string" ? url : url.toString();
699
- const targetName = (target ?? "").toLowerCase();
700
- if (targetName === "_top" || targetName === "_parent") {
701
- bridge.redirectToRemote(new URL(href, window.location.href).toString());
702
- return null;
703
- }
704
- if (!targetName || targetName === "_self") {
705
- const nextPath = resolveLocalNavigationPath(href, window.location.origin);
706
- if (nextPath) {
707
- handleLocalNavigation(nextPath);
708
- return window;
709
- }
710
- }
711
- return originalOpen(url, target, features);
712
- };
713
- document.addEventListener("click", handleDocumentClick, true);
714
- return () => {
715
- document.removeEventListener("click", handleDocumentClick, true);
716
- window.open = originalOpen;
717
- };
718
- }, [bridge, onNavigate]);
719
- (0, import_react.useEffect)(() => {
720
- if (!bridge || typeof window === "undefined") {
686
+ this.bridge.send(
687
+ this.navigationUpdateEventType,
688
+ buildNavigationUpdatePayload(this.currentPath)
689
+ );
690
+ this.lastReportedPath = this.currentPath;
691
+ }
692
+ }
693
+ navigate(path, message) {
694
+ if (this.onNavigate) {
695
+ this.onNavigate(path, message);
721
696
  return;
722
697
  }
723
- const originalFetch = globalThis.fetch.bind(globalThis);
724
- const interceptedFetch = async (input, init) => {
725
- if (!shouldAttachSessionToken(input)) {
726
- return originalFetch(input, init);
727
- }
728
- const existingAuthorization = getExistingAuthorization(input, init);
729
- if (existingAuthorization) {
730
- return originalFetch(input, init);
731
- }
732
- const nextHeaders = new Headers(
733
- input instanceof Request ? input.headers : init?.headers
734
- );
735
- if (projectRef.current && !nextHeaders.has("X-Thor-Project")) {
736
- nextHeaders.set("X-Thor-Project", projectRef.current);
737
- }
738
- try {
739
- const sessionToken = await getSessionToken({
740
- bridge,
741
- clientId,
742
- pendingSessionTokenRef,
743
- projectRef,
744
- sessionTokenCacheRef
745
- });
746
- nextHeaders.set("Authorization", `Bearer ${sessionToken}`);
747
- } catch {
748
- if (!projectRef.current) {
749
- throw new Error("Missing Thor embedded session token");
750
- }
751
- }
752
- if (!nextHeaders.has("X-Requested-With")) {
753
- nextHeaders.set("X-Requested-With", "XMLHttpRequest");
754
- }
755
- if (input instanceof Request) {
756
- return originalFetch(
757
- new Request(input, {
758
- headers: nextHeaders
759
- })
760
- );
761
- }
762
- return originalFetch(input, {
763
- ...init,
764
- headers: nextHeaders
765
- });
766
- };
767
- globalThis.fetch = interceptedFetch;
768
- window.fetch = interceptedFetch;
769
- return () => {
770
- globalThis.fetch = originalFetch;
771
- window.fetch = originalFetch;
772
- };
773
- }, [bridge, clientId]);
774
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppBridgeContext.Provider, { value: bridge, children });
698
+ if (dispatchNavigateEvent(this.selfWindow.document, path)) {
699
+ return;
700
+ }
701
+ this.selfWindow.location.assign(path);
702
+ }
703
+ };
704
+ function getOrCreateEmbeddedAppRuntime(config) {
705
+ const globalScope = globalThis;
706
+ const existingRuntime = globalScope[GLOBAL_RUNTIME_KEY];
707
+ if (existingRuntime) {
708
+ existingRuntime.update(config);
709
+ return existingRuntime;
710
+ }
711
+ const runtime = new EmbeddedAppRuntime(config);
712
+ globalScope[GLOBAL_RUNTIME_KEY] = runtime;
713
+ return runtime;
775
714
  }
776
- function useAppBridge() {
777
- return (0, import_react.useContext)(AppBridgeContext);
715
+ function getEmbeddedAppRuntime() {
716
+ return globalThis[GLOBAL_RUNTIME_KEY] ?? null;
717
+ }
718
+ function dispatchNavigateEvent(document, path) {
719
+ const event = new CustomEvent(THOR_NAVIGATE_EVENT, {
720
+ cancelable: true,
721
+ detail: { path }
722
+ });
723
+ document.dispatchEvent(event);
724
+ return event.defaultPrevented;
725
+ }
726
+ function readCurrentPath(selfWindow) {
727
+ return sanitizeEmbeddedAppPath(
728
+ `${selfWindow.location.pathname}${selfWindow.location.search}${selfWindow.location.hash}`
729
+ ) ?? null;
730
+ }
731
+ function installHistoryObserver({
732
+ selfWindow,
733
+ onChange
734
+ }) {
735
+ const history = selfWindow.history;
736
+ const originalPushState = history.pushState;
737
+ const originalReplaceState = history.replaceState;
738
+ const notify = () => {
739
+ onChange(readCurrentPath(selfWindow));
740
+ };
741
+ history.pushState = function(...args) {
742
+ originalPushState.apply(history, args);
743
+ notify();
744
+ };
745
+ history.replaceState = function(...args) {
746
+ originalReplaceState.apply(history, args);
747
+ notify();
748
+ };
749
+ selfWindow.addEventListener("popstate", notify);
750
+ selfWindow.addEventListener("hashchange", notify);
751
+ notify();
752
+ return () => {
753
+ history.pushState = originalPushState;
754
+ history.replaceState = originalReplaceState;
755
+ selfWindow.removeEventListener("popstate", notify);
756
+ selfWindow.removeEventListener("hashchange", notify);
757
+ };
778
758
  }
779
759
  function getReferrerOrigin(explicitWindow) {
780
760
  const resolvedWindow = explicitWindow ?? (typeof window !== "undefined" ? window : void 0);
@@ -788,40 +768,190 @@ function getReferrerOrigin(explicitWindow) {
788
768
  return void 0;
789
769
  }
790
770
  }
771
+ function installNavigationInterceptors({
772
+ bridge,
773
+ selfWindow,
774
+ navigate
775
+ }) {
776
+ const document = selfWindow.document;
777
+ const handleLocalNavigation = (path) => {
778
+ const sanitizedPath = sanitizeEmbeddedAppPath(path);
779
+ if (!sanitizedPath) {
780
+ return;
781
+ }
782
+ const nextPath = preserveEmbeddedAppLaunchParams(
783
+ sanitizedPath,
784
+ selfWindow.location.href
785
+ );
786
+ navigate(nextPath ?? sanitizedPath);
787
+ };
788
+ const handleDocumentClick = (event) => {
789
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
790
+ return;
791
+ }
792
+ const target = event.target;
793
+ if (!(target instanceof Element)) {
794
+ return;
795
+ }
796
+ const anchor = target.closest("a[href]");
797
+ if (!(anchor instanceof HTMLAnchorElement)) {
798
+ return;
799
+ }
800
+ if (anchor.hasAttribute("download")) {
801
+ return;
802
+ }
803
+ const targetWindow = anchor.target.toLowerCase();
804
+ const href = anchor.getAttribute("href");
805
+ if (!href) {
806
+ return;
807
+ }
808
+ if (targetWindow === "_top" || targetWindow === "_parent") {
809
+ event.preventDefault();
810
+ bridge.redirectToRemote(anchor.href);
811
+ return;
812
+ }
813
+ if (targetWindow && targetWindow !== "_self") {
814
+ return;
815
+ }
816
+ const nextPath = resolveLocalNavigationPath(href, selfWindow.location.origin);
817
+ if (!nextPath) {
818
+ return;
819
+ }
820
+ event.preventDefault();
821
+ handleLocalNavigation(nextPath);
822
+ };
823
+ const originalOpen = selfWindow.open.bind(selfWindow);
824
+ selfWindow.open = (url, target, features) => {
825
+ if (url == null) {
826
+ return originalOpen(url, target, features);
827
+ }
828
+ const href = typeof url === "string" ? url : url.toString();
829
+ const targetName = (target ?? "").toLowerCase();
830
+ if (targetName === "_top" || targetName === "_parent") {
831
+ bridge.redirectToRemote(new URL(href, selfWindow.location.href).toString());
832
+ return null;
833
+ }
834
+ if (!targetName || targetName === "_self") {
835
+ const nextPath = resolveLocalNavigationPath(
836
+ href,
837
+ selfWindow.location.origin
838
+ );
839
+ if (nextPath) {
840
+ handleLocalNavigation(nextPath);
841
+ return selfWindow;
842
+ }
843
+ }
844
+ return originalOpen(url, target, features);
845
+ };
846
+ document.addEventListener("click", handleDocumentClick, true);
847
+ return () => {
848
+ document.removeEventListener("click", handleDocumentClick, true);
849
+ selfWindow.open = originalOpen;
850
+ };
851
+ }
852
+ function installFetchInterceptor({
853
+ bridge,
854
+ getClientId,
855
+ getProject,
856
+ setProject,
857
+ readCachedToken,
858
+ writeCachedToken,
859
+ readPendingToken,
860
+ writePendingToken
861
+ }) {
862
+ if (typeof window === "undefined") {
863
+ return () => {
864
+ };
865
+ }
866
+ const originalFetch = globalThis.fetch.bind(globalThis);
867
+ const interceptedFetch = async (input, init) => {
868
+ if (!shouldAttachSessionToken(input)) {
869
+ return originalFetch(input, init);
870
+ }
871
+ const existingAuthorization = getExistingAuthorization(input, init);
872
+ if (existingAuthorization) {
873
+ return originalFetch(input, init);
874
+ }
875
+ const nextHeaders = new Headers(
876
+ input instanceof Request ? input.headers : init?.headers
877
+ );
878
+ try {
879
+ const sessionToken = await getSessionToken({
880
+ bridge,
881
+ clientId: getClientId(),
882
+ project: getProject,
883
+ setProject,
884
+ readCachedToken,
885
+ writeCachedToken,
886
+ readPendingToken,
887
+ writePendingToken
888
+ });
889
+ nextHeaders.set("Authorization", `Bearer ${sessionToken}`);
890
+ } catch {
891
+ if (!getProject()) {
892
+ throw new Error("Missing Thor embedded session token");
893
+ }
894
+ }
895
+ if (!nextHeaders.has("X-Requested-With")) {
896
+ nextHeaders.set("X-Requested-With", "XMLHttpRequest");
897
+ }
898
+ if (input instanceof Request) {
899
+ return originalFetch(
900
+ new Request(input, {
901
+ headers: nextHeaders
902
+ })
903
+ );
904
+ }
905
+ return originalFetch(input, {
906
+ ...init,
907
+ headers: nextHeaders
908
+ });
909
+ };
910
+ globalThis.fetch = interceptedFetch;
911
+ window.fetch = interceptedFetch;
912
+ return () => {
913
+ globalThis.fetch = originalFetch;
914
+ window.fetch = originalFetch;
915
+ };
916
+ }
791
917
  async function getSessionToken({
792
918
  bridge,
793
919
  clientId,
794
- pendingSessionTokenRef,
795
- projectRef,
796
- sessionTokenCacheRef
920
+ project,
921
+ setProject,
922
+ readCachedToken,
923
+ writeCachedToken,
924
+ readPendingToken,
925
+ writePendingToken
797
926
  }) {
798
- const cachedToken = sessionTokenCacheRef.current;
927
+ const cachedToken = readCachedToken();
799
928
  if (cachedToken && !isExpired(cachedToken.expiresAt)) {
800
929
  return cachedToken.token;
801
930
  }
802
- if (pendingSessionTokenRef.current) {
803
- return pendingSessionTokenRef.current;
931
+ const pendingToken = readPendingToken();
932
+ if (pendingToken) {
933
+ return pendingToken;
804
934
  }
805
- const pendingToken = bridge.getSessionToken({ clientId }).then((response) => {
935
+ const nextPendingToken = bridge.getSessionToken({ clientId }).then((response) => {
806
936
  const token = response.sessionToken ?? response.idToken;
807
937
  if (!token) {
808
938
  throw new Error("Missing Thor embedded session token");
809
939
  }
810
940
  if (response.project) {
811
- projectRef.current = response.project;
941
+ setProject(response.project);
812
942
  }
813
- sessionTokenCacheRef.current = {
943
+ writeCachedToken({
814
944
  token,
815
945
  expiresAt: normalizeTokenExpiry(token, response.exp)
816
- };
817
- pendingSessionTokenRef.current = null;
946
+ });
947
+ writePendingToken(null);
818
948
  return token;
819
949
  }).catch((error) => {
820
- pendingSessionTokenRef.current = null;
950
+ writePendingToken(null);
821
951
  throw error;
822
952
  });
823
- pendingSessionTokenRef.current = pendingToken;
824
- return pendingToken;
953
+ writePendingToken(nextPendingToken);
954
+ return nextPendingToken;
825
955
  }
826
956
  function shouldAttachSessionToken(input) {
827
957
  if (typeof window === "undefined") {
@@ -887,13 +1017,84 @@ function readInitialProject(explicitWindow) {
887
1017
  return null;
888
1018
  }
889
1019
  }
1020
+
1021
+ // src/react.tsx
1022
+ var import_jsx_runtime = require("react/jsx-runtime");
1023
+ var AppBridgeContext = (0, import_react.createContext)(null);
1024
+ function AppBridgeProvider({
1025
+ children,
1026
+ clientId,
1027
+ currentPath,
1028
+ navigationItems,
1029
+ navigationEventType = "navigation:go",
1030
+ navigationUpdateEventType = "navigation:update",
1031
+ namespace,
1032
+ onNavigate,
1033
+ readyEventType = "app:ready",
1034
+ readyPayload,
1035
+ requestTimeoutMs,
1036
+ selfWindow,
1037
+ targetOrigin,
1038
+ targetWindow
1039
+ }) {
1040
+ const [bridge, setBridge] = (0, import_react.useState)(null);
1041
+ (0, import_react.useEffect)(() => {
1042
+ if (typeof window === "undefined" && !selfWindow) {
1043
+ return;
1044
+ }
1045
+ const runtime = getOrCreateEmbeddedAppRuntime({
1046
+ clientId,
1047
+ currentPath,
1048
+ navigationEventType,
1049
+ navigationItems,
1050
+ navigationUpdateEventType,
1051
+ namespace,
1052
+ onNavigate,
1053
+ readyEventType,
1054
+ readyPayload,
1055
+ requestTimeoutMs,
1056
+ selfWindow,
1057
+ targetOrigin,
1058
+ targetWindow
1059
+ });
1060
+ setBridge(runtime.bridge);
1061
+ return () => {
1062
+ runtime.clearNavigationHandler();
1063
+ setBridge(
1064
+ (currentBridge) => currentBridge === runtime.bridge ? null : currentBridge
1065
+ );
1066
+ };
1067
+ }, [
1068
+ clientId,
1069
+ currentPath,
1070
+ navigationEventType,
1071
+ navigationItems,
1072
+ navigationUpdateEventType,
1073
+ namespace,
1074
+ onNavigate,
1075
+ readyEventType,
1076
+ readyPayload,
1077
+ requestTimeoutMs,
1078
+ selfWindow,
1079
+ targetOrigin,
1080
+ targetWindow
1081
+ ]);
1082
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppBridgeContext.Provider, { value: bridge, children });
1083
+ }
1084
+ function useAppBridge() {
1085
+ return (0, import_react.useContext)(AppBridgeContext);
1086
+ }
890
1087
  // Annotate the CommonJS export names for ESM import in node:
891
1088
  0 && (module.exports = {
892
1089
  AppBridge,
893
1090
  AppBridgeProvider,
894
1091
  EMBEDDED_LAUNCH_PARAMS,
1092
+ EmbeddedAppRuntime,
1093
+ THOR_NAVIGATE_EVENT,
895
1094
  buildNavigationUpdatePayload,
896
1095
  createAppBridge,
1096
+ getEmbeddedAppRuntime,
1097
+ getOrCreateEmbeddedAppRuntime,
897
1098
  isBridgeMessage,
898
1099
  normalizeBridgeNavigationItem,
899
1100
  normalizeBridgeNavigationItems,