@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/README.md +48 -0
- package/dist/browser.cjs +971 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +16 -0
- package/dist/browser.d.ts +16 -0
- package/dist/browser.js +37 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunk-GX5V7AK7.js +82 -0
- package/dist/chunk-GX5V7AK7.js.map +1 -0
- package/dist/{chunk-XPUL72AN.js → chunk-IRTDLWEQ.js} +337 -270
- package/dist/chunk-IRTDLWEQ.js.map +1 -0
- package/dist/index.cjs +408 -257
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -127
- package/dist/index.d.ts +4 -127
- package/dist/index.js +16 -4
- package/dist/next.cjs +392 -256
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +1 -0
- package/dist/next.d.ts +1 -0
- package/dist/next.js +4 -2
- package/dist/next.js.map +1 -1
- package/dist/react-router.cjs +392 -256
- package/dist/react-router.cjs.map +1 -1
- package/dist/react-router.d.cts +1 -0
- package/dist/react-router.d.ts +1 -0
- package/dist/react-router.js +4 -2
- package/dist/react-router.js.map +1 -1
- package/dist/runtime-mEVvh6Rv.d.cts +158 -0
- package/dist/runtime-mEVvh6Rv.d.ts +158 -0
- package/dist/thor-app-bridge.js +2 -0
- package/dist/thor-app-bridge.js.map +1 -0
- package/package.json +9 -1
- package/dist/chunk-XPUL72AN.js.map +0 -1
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
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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:
|
|
583
|
-
targetWindow
|
|
575
|
+
targetOrigin: this.targetOrigin,
|
|
576
|
+
targetWindow: config.targetWindow
|
|
584
577
|
});
|
|
585
|
-
|
|
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
|
-
|
|
583
|
+
const nextPath = preserveEmbeddedAppLaunchParams(
|
|
584
|
+
destination,
|
|
585
|
+
this.selfWindow.location.href
|
|
586
|
+
);
|
|
587
|
+
this.navigate(nextPath ?? destination, message);
|
|
633
588
|
});
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
|
765
|
-
return
|
|
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
|
-
|
|
783
|
-
|
|
784
|
-
|
|
857
|
+
project,
|
|
858
|
+
setProject,
|
|
859
|
+
readCachedToken,
|
|
860
|
+
writeCachedToken,
|
|
861
|
+
readPendingToken,
|
|
862
|
+
writePendingToken
|
|
785
863
|
}) {
|
|
786
|
-
const cachedToken =
|
|
864
|
+
const cachedToken = readCachedToken();
|
|
787
865
|
if (cachedToken && !isExpired(cachedToken.expiresAt)) {
|
|
788
866
|
return cachedToken.token;
|
|
789
867
|
}
|
|
790
|
-
|
|
791
|
-
|
|
868
|
+
const pendingToken = readPendingToken();
|
|
869
|
+
if (pendingToken) {
|
|
870
|
+
return pendingToken;
|
|
792
871
|
}
|
|
793
|
-
const
|
|
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
|
-
|
|
878
|
+
setProject(response.project);
|
|
800
879
|
}
|
|
801
|
-
|
|
880
|
+
writeCachedToken({
|
|
802
881
|
token,
|
|
803
882
|
expiresAt: normalizeTokenExpiry(token, response.exp)
|
|
804
|
-
};
|
|
805
|
-
|
|
883
|
+
});
|
|
884
|
+
writePendingToken(null);
|
|
806
885
|
return token;
|
|
807
886
|
}).catch((error) => {
|
|
808
|
-
|
|
887
|
+
writePendingToken(null);
|
|
809
888
|
throw error;
|
|
810
889
|
});
|
|
811
|
-
|
|
812
|
-
return
|
|
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,
|