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