@youidian/sdk 3.3.10 → 3.4.2

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/client.js CHANGED
@@ -2,6 +2,221 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
+ // src/brand-logo.ts
6
+ function ensureSdkBrandLogoStyles() {
7
+ if (document.getElementById?.("youidian-sdk-brand-logo-style")) {
8
+ return;
9
+ }
10
+ const style = document.createElement("style");
11
+ style.id = "youidian-sdk-brand-logo-style";
12
+ style.textContent = `
13
+ .youidian-sdk-brand-logo__ring,
14
+ .youidian-sdk-brand-logo__dot,
15
+ .youidian-sdk-brand-logo__copy,
16
+ .youidian-sdk-brand-logo__tagline {
17
+ transform-box: fill-box;
18
+ transform-origin: center;
19
+ will-change: opacity, transform;
20
+ }
21
+
22
+ .youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__ring {
23
+ animation: youidian-sdk-brand-logo-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
24
+ }
25
+
26
+ .youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__dot {
27
+ animation: youidian-sdk-brand-logo-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
28
+ }
29
+
30
+ .youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__copy {
31
+ animation: youidian-sdk-brand-logo-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
32
+ }
33
+
34
+ .youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__tagline {
35
+ animation: youidian-sdk-brand-logo-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
36
+ }
37
+
38
+ @keyframes youidian-sdk-brand-logo-ring {
39
+ 0%,
40
+ 100% {
41
+ opacity: 0.72;
42
+ transform: rotate(0deg) scale(0.98);
43
+ }
44
+ 50% {
45
+ opacity: 1;
46
+ transform: rotate(8deg) scale(1.02);
47
+ }
48
+ }
49
+
50
+ @keyframes youidian-sdk-brand-logo-dot {
51
+ 0%,
52
+ 100% {
53
+ opacity: 0.58;
54
+ transform: scale(0.9);
55
+ }
56
+ 50% {
57
+ opacity: 1;
58
+ transform: scale(1.08);
59
+ }
60
+ }
61
+
62
+ @keyframes youidian-sdk-brand-logo-copy {
63
+ 0%,
64
+ 100% {
65
+ opacity: 0.86;
66
+ transform: translateY(0);
67
+ }
68
+ 50% {
69
+ opacity: 1;
70
+ transform: translateY(-1px);
71
+ }
72
+ }
73
+
74
+ @keyframes youidian-sdk-brand-logo-tagline {
75
+ 0%,
76
+ 100% {
77
+ opacity: 0.64;
78
+ }
79
+ 50% {
80
+ opacity: 0.92;
81
+ }
82
+ }
83
+
84
+ @media (prefers-reduced-motion: reduce) {
85
+ .youidian-sdk-brand-logo__ring,
86
+ .youidian-sdk-brand-logo__dot,
87
+ .youidian-sdk-brand-logo__copy,
88
+ .youidian-sdk-brand-logo__tagline {
89
+ animation: none;
90
+ opacity: 1;
91
+ transform: none;
92
+ }
93
+ }
94
+ `;
95
+ document.head?.appendChild(style);
96
+ }
97
+ function createSdkBrandLogoMark({
98
+ animated,
99
+ inverted,
100
+ size,
101
+ theme,
102
+ variant = "brand"
103
+ }) {
104
+ ensureSdkBrandLogoStyles();
105
+ const mark = document.createElementNS("http://www.w3.org/2000/svg", "svg");
106
+ mark.setAttribute("viewBox", "0 0 512 512");
107
+ mark.setAttribute("fill", "none");
108
+ mark.setAttribute("aria-hidden", "true");
109
+ Object.assign(mark.style, {
110
+ width: `${size}px`,
111
+ height: `${size}px`,
112
+ color: inverted ? "#ffffff" : theme.primary,
113
+ display: "block",
114
+ flex: "0 0 auto",
115
+ overflow: "visible"
116
+ });
117
+ const logoGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
118
+ logoGroup.setAttribute(
119
+ "transform",
120
+ variant === "mark" ? "matrix(0.74 0 0 0.74 -124 -114)" : "matrix(0.58 0 0 0.58 -40 -31)"
121
+ );
122
+ const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
123
+ ring.setAttribute("class", animated ? "youidian-sdk-brand-logo__ring" : "");
124
+ ring.setAttribute("fill", inverted ? "currentColor" : theme.primary);
125
+ ring.setAttribute(
126
+ "d",
127
+ "M704.298 679.54A264 264 0 1 1 704.298 309.46A49 49 0 0 1 634.4 378.149A166 166 0 1 0 634.4 610.851A49 49 0 0 1 704.298 679.54Z"
128
+ );
129
+ const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
130
+ dot.setAttribute("class", animated ? "youidian-sdk-brand-logo__dot" : "");
131
+ dot.setAttribute("fill", inverted ? "currentColor" : theme.secondary);
132
+ dot.setAttribute("cx", "714.5");
133
+ dot.setAttribute("cy", "490.5");
134
+ dot.setAttribute("r", "68");
135
+ dot.setAttribute("opacity", inverted ? "0.78" : "1");
136
+ logoGroup.appendChild(ring);
137
+ logoGroup.appendChild(dot);
138
+ mark.appendChild(logoGroup);
139
+ return mark;
140
+ }
141
+ function createSdkBrandLogo({
142
+ animated,
143
+ brand,
144
+ brandWidth,
145
+ copyWidth,
146
+ gap = 10,
147
+ markMarginRight,
148
+ markSize,
149
+ nameSize,
150
+ subtitleMarginTop = 6,
151
+ subtitleSize,
152
+ subtitleWeight = "800",
153
+ theme,
154
+ variant = "brand"
155
+ }) {
156
+ ensureSdkBrandLogoStyles();
157
+ const wrapper = document.createElement("div");
158
+ wrapper.className = "youidian-sdk-brand-logo";
159
+ wrapper.setAttribute("data-animated", animated ? "true" : "false");
160
+ Object.assign(wrapper.style, {
161
+ display: "inline-flex",
162
+ alignItems: "center",
163
+ justifyContent: "center",
164
+ gap: `${gap}px`,
165
+ width: brandWidth ? `${brandWidth}px` : void 0
166
+ });
167
+ const copy = document.createElement("div");
168
+ copy.className = "youidian-sdk-brand-logo__copy";
169
+ copy.setAttribute("data-brand-locale", brand.language);
170
+ Object.assign(copy.style, {
171
+ display: "flex",
172
+ flexDirection: "column",
173
+ alignItems: "flex-start",
174
+ justifyContent: "center",
175
+ lineHeight: "1",
176
+ width: copyWidth ? `${copyWidth}px` : void 0
177
+ });
178
+ const name = document.createElement("div");
179
+ name.className = "youidian-sdk-brand-logo__name";
180
+ name.textContent = brand.name;
181
+ Object.assign(name.style, {
182
+ color: theme.foreground,
183
+ fontFamily: brand.language === "zh" ? '"Noto Sans TC", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' : '"Unbounded", "Space Grotesk", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
184
+ fontSize: `${nameSize}px`,
185
+ fontWeight: brand.language === "zh" ? "900" : "800",
186
+ letterSpacing: brand.language === "zh" ? "0.08em" : "0.01em",
187
+ lineHeight: "1",
188
+ whiteSpace: "nowrap"
189
+ });
190
+ const subtitle = document.createElement("div");
191
+ subtitle.className = "youidian-sdk-brand-logo__tagline";
192
+ subtitle.textContent = brand.subtitle;
193
+ Object.assign(subtitle.style, {
194
+ color: theme.muted,
195
+ fontFamily: brand.language === "zh" ? '"Noto Sans TC", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' : '"Space Grotesk", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
196
+ fontSize: `${subtitleSize}px`,
197
+ fontWeight: subtitleWeight,
198
+ letterSpacing: brand.language === "zh" ? "0.24em" : "0.07em",
199
+ lineHeight: "1",
200
+ marginTop: `${subtitleMarginTop}px`,
201
+ textTransform: brand.language === "zh" ? "none" : "uppercase",
202
+ whiteSpace: "nowrap"
203
+ });
204
+ copy.appendChild(name);
205
+ copy.appendChild(subtitle);
206
+ const mark = createSdkBrandLogoMark({
207
+ animated,
208
+ size: markSize,
209
+ theme,
210
+ variant
211
+ });
212
+ if (typeof markMarginRight === "number") {
213
+ mark.style.marginRight = `${markMarginRight}px`;
214
+ }
215
+ wrapper.appendChild(mark);
216
+ wrapper.appendChild(copy);
217
+ return wrapper;
218
+ }
219
+
5
220
  // src/hosted-modal.ts
6
221
  var SDK_SUPPORTED_LOCALES = [
7
222
  "en",
@@ -125,7 +340,7 @@ var HostedFrameModal = class {
125
340
  const container = document.createElement("div");
126
341
  Object.assign(container.style, {
127
342
  width: options.width || "450px",
128
- height: options.height || "min(600px, 90vh)",
343
+ height: options.height || "min(760px, calc(100vh - 32px))",
129
344
  backgroundColor: "transparent",
130
345
  borderRadius: "28px",
131
346
  overflow: "visible",
@@ -133,8 +348,7 @@ var HostedFrameModal = class {
133
348
  boxShadow: "none",
134
349
  maxWidth: "calc(100vw - 32px)",
135
350
  maxHeight: "calc(100vh - 32px)",
136
- transition: "height 180ms ease",
137
- willChange: "height"
351
+ transition: "none"
138
352
  });
139
353
  const closeBtn = document.createElement("button");
140
354
  closeBtn.innerHTML = "\xD7";
@@ -204,6 +418,91 @@ var HostedFrameModal = class {
204
418
  }
205
419
  };
206
420
 
421
+ // src/environment.ts
422
+ function normalizeText(value) {
423
+ return value || "";
424
+ }
425
+ function getRuntimeInput() {
426
+ if (typeof navigator === "undefined") {
427
+ return {};
428
+ }
429
+ const nav = navigator;
430
+ const coarsePointer = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : void 0;
431
+ return {
432
+ coarsePointer,
433
+ maxTouchPoints: nav.maxTouchPoints || 0,
434
+ platform: nav.platform || "",
435
+ standalone: nav.standalone,
436
+ userAgent: nav.userAgent || "",
437
+ userAgentDataMobile: nav.userAgentData?.mobile
438
+ };
439
+ }
440
+ function includesAny(value, patterns) {
441
+ return patterns.some((pattern) => pattern.test(value));
442
+ }
443
+ function detectLoginEnvironment(input = getRuntimeInput()) {
444
+ const userAgent = normalizeText(input.userAgent);
445
+ const platform = normalizeText(input.platform);
446
+ const rawMaxTouchPoints = input.maxTouchPoints ?? 0;
447
+ const maxTouchPoints = Number.isFinite(rawMaxTouchPoints) ? Math.max(0, rawMaxTouchPoints) : 0;
448
+ const isCoarsePointer = input.coarsePointer === true;
449
+ const isTouch = maxTouchPoints > 0 || isCoarsePointer;
450
+ const isWeChat = /micromessenger/i.test(userAgent);
451
+ const isAndroid = /\bandroid\b/i.test(userAgent);
452
+ const hasExplicitIOSDevice = /\b(iPad|iPhone|iPod)\b/i.test(userAgent);
453
+ const hasMacintoshUserAgent = /\bMacintosh\b/i.test(userAgent);
454
+ const hasMacPlatform = /^Mac/i.test(platform);
455
+ const isIPadOS = !hasExplicitIOSDevice && hasMacintoshUserAgent && hasMacPlatform && typeof input.standalone !== "undefined" && maxTouchPoints > 2;
456
+ const isIOS = hasExplicitIOSDevice || isIPadOS;
457
+ const isTabletByUa = includesAny(userAgent, [
458
+ /\biPad\b/i,
459
+ /\btablet\b/i,
460
+ /\bplaybook\b/i,
461
+ /\bsilk\b(?!.*\bmobile\b)/i,
462
+ /\bkindle\b/i,
463
+ /\bnexus 7\b/i,
464
+ /\bnexus 9\b/i,
465
+ /\bxoom\b/i,
466
+ /\bsm-t\d+/i,
467
+ /\bgt-p\d+/i,
468
+ /\bmi pad\b/i
469
+ ]);
470
+ const isMobileByUa = includesAny(userAgent, [
471
+ /\bMobile\b/i,
472
+ /\biPhone\b/i,
473
+ /\biPod\b/i,
474
+ /\bWindows Phone\b/i,
475
+ /\bIEMobile\b/i,
476
+ /\bBlackBerry\b/i,
477
+ /\bBB10\b/i,
478
+ /\bOpera Mini\b/i,
479
+ /\bOpera Mobi\b/i,
480
+ /\bwebOS\b/i
481
+ ]);
482
+ const isAndroidTablet = isAndroid && !/\bMobile\b/i.test(userAgent);
483
+ const isTablet = isIPadOS || isTabletByUa || isAndroidTablet;
484
+ const isMobile = input.userAgentDataMobile === true || isMobileByUa || isAndroid && !isTablet || isIOS && !isTablet;
485
+ const deviceType = isTablet ? "tablet" : isMobile ? "mobile" : userAgent || platform || isTouch ? "desktop" : "unknown";
486
+ const shouldUseRedirectLogin = deviceType === "mobile" || deviceType === "tablet" || isWeChat;
487
+ return {
488
+ deviceType,
489
+ isAndroid,
490
+ isCoarsePointer,
491
+ isDesktop: deviceType === "desktop",
492
+ isIOS,
493
+ isIPadOS,
494
+ isMobile,
495
+ isStandalone: input.standalone === true,
496
+ isTablet,
497
+ isTouch,
498
+ isWeChat,
499
+ maxTouchPoints,
500
+ platform,
501
+ shouldUseRedirectLogin,
502
+ userAgent
503
+ };
504
+ }
505
+
207
506
  // src/login.ts
208
507
  function getOrigin(value) {
209
508
  if (!value) return null;
@@ -213,6 +512,15 @@ function getOrigin(value) {
213
512
  return null;
214
513
  }
215
514
  }
515
+ function isValidLoginRedirectUrl(value) {
516
+ if (!value) return false;
517
+ try {
518
+ const url = new URL(value);
519
+ return url.protocol === "https:" || url.protocol === "http:";
520
+ } catch {
521
+ return false;
522
+ }
523
+ }
216
524
  function createLoginCallbackState() {
217
525
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
218
526
  return crypto.randomUUID().replace(/-/g, "");
@@ -559,6 +867,24 @@ function applyLoginPanelStyle(panel) {
559
867
  backdropFilter: "blur(16px)"
560
868
  });
561
869
  }
870
+ function applyLoginLoadingPanelStyle(panel) {
871
+ Object.assign(panel.style, {
872
+ position: "absolute",
873
+ inset: "0",
874
+ display: "flex",
875
+ alignItems: "center",
876
+ justifyContent: "center",
877
+ border: "0",
878
+ borderRadius: "28px",
879
+ background: "rgba(255,255,255,0.94)",
880
+ color: "#0f172a",
881
+ padding: "24px",
882
+ textAlign: "center",
883
+ zIndex: "1",
884
+ boxShadow: "none",
885
+ backdropFilter: "blur(16px)"
886
+ });
887
+ }
562
888
  function createLoginPanelContent() {
563
889
  const content = document.createElement("div");
564
890
  Object.assign(content.style, {
@@ -583,6 +909,71 @@ function createLoginPanelContent() {
583
909
  content.appendChild(badge);
584
910
  return content;
585
911
  }
912
+ function createBrandLoadingMark() {
913
+ const logo = document.createElementNS("http://www.w3.org/2000/svg", "svg");
914
+ logo.setAttribute("viewBox", "0 0 1024 1024");
915
+ logo.setAttribute("fill", "none");
916
+ logo.setAttribute("aria-hidden", "true");
917
+ Object.assign(logo.style, {
918
+ width: "112px",
919
+ height: "112px",
920
+ color: "#22c55e",
921
+ display: "block",
922
+ overflow: "visible"
923
+ });
924
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
925
+ group.setAttribute("fill", "currentColor");
926
+ const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
927
+ ring.setAttribute(
928
+ "d",
929
+ "M704.298 679.54A264 264 0 1 1 704.298 309.46A49 49 0 0 1 634.4 378.149A166 166 0 1 0 634.4 610.851A49 49 0 0 1 704.298 679.54Z"
930
+ );
931
+ ring.setAttribute("class", "youidian-sdk-brand-loading__ring");
932
+ const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
933
+ dot.setAttribute("class", "youidian-sdk-brand-loading__dot");
934
+ dot.setAttribute("cx", "714.5");
935
+ dot.setAttribute("cy", "490.5");
936
+ dot.setAttribute("r", "68");
937
+ group.appendChild(ring);
938
+ group.appendChild(dot);
939
+ logo.appendChild(group);
940
+ return logo;
941
+ }
942
+ function createBrandLoadingCopy() {
943
+ const brand = document.createElement("div");
944
+ brand.className = "youidian-sdk-brand-loading__copy";
945
+ Object.assign(brand.style, {
946
+ display: "flex",
947
+ flexDirection: "column",
948
+ alignItems: "flex-start",
949
+ justifyContent: "center",
950
+ lineHeight: "1"
951
+ });
952
+ const name = document.createElement("div");
953
+ name.className = "youidian-sdk-brand-loading__name";
954
+ name.textContent = "\u4F18\u6613\u70B9";
955
+ Object.assign(name.style, {
956
+ color: "#0f172a",
957
+ fontSize: "34px",
958
+ fontWeight: "900",
959
+ letterSpacing: "0",
960
+ lineHeight: "1"
961
+ });
962
+ const tagline = document.createElement("div");
963
+ tagline.className = "youidian-sdk-brand-loading__tagline";
964
+ tagline.textContent = "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0";
965
+ Object.assign(tagline.style, {
966
+ color: "#64748b",
967
+ fontSize: "16px",
968
+ fontWeight: "700",
969
+ letterSpacing: "0.24em",
970
+ lineHeight: "1",
971
+ marginTop: "6px"
972
+ });
973
+ brand.appendChild(name);
974
+ brand.appendChild(tagline);
975
+ return brand;
976
+ }
586
977
  function createLoginPanelTitle(value) {
587
978
  const title = document.createElement("div");
588
979
  title.textContent = value;
@@ -612,32 +1003,111 @@ function ensureLoginLoadingStyles() {
612
1003
  }
613
1004
  const style = document.createElement("style");
614
1005
  style.id = "youidian-login-loading-style";
615
- style.textContent = "@keyframes youidian-login-spin{to{transform:rotate(360deg)}}";
1006
+ style.textContent = `
1007
+ .youidian-sdk-brand-loading__ring,
1008
+ .youidian-sdk-brand-loading__dot {
1009
+ transform-box: fill-box;
1010
+ transform-origin: center;
1011
+ will-change: opacity, transform;
1012
+ }
1013
+
1014
+ .youidian-sdk-brand-loading__ring {
1015
+ animation: youidian-sdk-brand-loading-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
1016
+ }
1017
+
1018
+ .youidian-sdk-brand-loading__dot {
1019
+ animation: youidian-sdk-brand-loading-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
1020
+ }
1021
+
1022
+ .youidian-sdk-brand-loading__copy {
1023
+ will-change: opacity, transform;
1024
+ animation: youidian-sdk-brand-loading-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
1025
+ }
1026
+
1027
+ .youidian-sdk-brand-loading__tagline {
1028
+ will-change: opacity;
1029
+ animation: youidian-sdk-brand-loading-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
1030
+ }
1031
+
1032
+ @keyframes youidian-sdk-brand-loading-ring {
1033
+ 0%,
1034
+ 100% {
1035
+ opacity: 0.72;
1036
+ transform: rotate(0deg) scale(0.98);
1037
+ }
1038
+ 50% {
1039
+ opacity: 1;
1040
+ transform: rotate(8deg) scale(1.02);
1041
+ }
1042
+ }
1043
+
1044
+ @keyframes youidian-sdk-brand-loading-dot {
1045
+ 0%,
1046
+ 100% {
1047
+ opacity: 0.55;
1048
+ transform: scale(0.9);
1049
+ }
1050
+ 50% {
1051
+ opacity: 1;
1052
+ transform: scale(1.08);
1053
+ }
1054
+ }
1055
+
1056
+ @keyframes youidian-sdk-brand-loading-copy {
1057
+ 0%,
1058
+ 100% {
1059
+ opacity: 0.86;
1060
+ transform: translateY(0);
1061
+ }
1062
+ 50% {
1063
+ opacity: 1;
1064
+ transform: translateY(-1px);
1065
+ }
1066
+ }
1067
+
1068
+ @keyframes youidian-sdk-brand-loading-tagline {
1069
+ 0%,
1070
+ 100% {
1071
+ opacity: 0.64;
1072
+ }
1073
+ 50% {
1074
+ opacity: 0.92;
1075
+ }
1076
+ }
1077
+
1078
+ @media (prefers-reduced-motion: reduce) {
1079
+ .youidian-sdk-brand-loading__ring,
1080
+ .youidian-sdk-brand-loading__dot,
1081
+ .youidian-sdk-brand-loading__copy,
1082
+ .youidian-sdk-brand-loading__tagline {
1083
+ animation: none;
1084
+ opacity: 1;
1085
+ transform: none;
1086
+ }
1087
+ }
1088
+ `;
616
1089
  document.head.appendChild(style);
617
1090
  }
618
- function createLoginLoadingPanel(options) {
1091
+ function createLoginLoadingPanel() {
619
1092
  ensureLoginLoadingStyles();
620
1093
  const panel = document.createElement("div");
621
- applyLoginPanelStyle(panel);
622
- const content = createLoginPanelContent();
623
- const spinner = document.createElement("div");
624
- Object.assign(spinner.style, {
625
- width: "22px",
626
- height: "22px",
627
- borderRadius: "9999px",
628
- border: "2px solid rgba(23,23,23,0.16)",
629
- borderTopColor: "#171717",
630
- margin: "0 auto 18px",
631
- animation: "youidian-login-spin 780ms linear infinite"
1094
+ applyLoginLoadingPanelStyle(panel);
1095
+ panel.setAttribute("role", "status");
1096
+ panel.setAttribute("aria-label", "\u4F18\u6613\u70B9\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0");
1097
+ const content = document.createElement("div");
1098
+ Object.assign(content.style, {
1099
+ display: "flex",
1100
+ alignItems: "center",
1101
+ justifyContent: "center",
1102
+ gap: "12px"
632
1103
  });
633
- content.appendChild(spinner);
634
- content.appendChild(createLoginPanelTitle(options.title));
635
- content.appendChild(createLoginPanelDescription(options.description));
1104
+ content.appendChild(createBrandLoadingMark());
1105
+ content.appendChild(createBrandLoadingCopy());
636
1106
  panel.appendChild(content);
637
1107
  return panel;
638
1108
  }
639
- function createLoginRedirectingPanel(options) {
640
- return createLoginLoadingPanel(options);
1109
+ function createLoginRedirectingPanel(_options) {
1110
+ return createLoginLoadingPanel();
641
1111
  }
642
1112
  function createLoginFallbackPanel(options) {
643
1113
  const panel = document.createElement("div");
@@ -719,6 +1189,7 @@ var LoginUI = class extends HostedFrameModal {
719
1189
  this.completed = false;
720
1190
  }
721
1191
  markIframeReady() {
1192
+ if (this.iframeReady) return;
722
1193
  this.iframeReady = true;
723
1194
  if (typeof window !== "undefined" && this.iframeLoadTimer) {
724
1195
  window.clearTimeout(this.iframeLoadTimer);
@@ -737,14 +1208,11 @@ var LoginUI = class extends HostedFrameModal {
737
1208
  this.iframeFallbackPanel = null;
738
1209
  this.iframeLoadingPanel = null;
739
1210
  }
740
- showIframeLoading(container, options) {
1211
+ showIframeLoading(container) {
741
1212
  if (this.iframeLoadingPanel || this.iframeReady) {
742
1213
  return;
743
1214
  }
744
- const panel = createLoginLoadingPanel({
745
- description: options?.loadingDescription || "Please wait while the secure login page opens.",
746
- title: options?.loadingTitle || "Opening login page"
747
- });
1215
+ const panel = createLoginLoadingPanel();
748
1216
  this.iframeLoadingPanel = panel;
749
1217
  container.appendChild(panel);
750
1218
  }
@@ -832,6 +1300,20 @@ var LoginUI = class extends HostedFrameModal {
832
1300
  break;
833
1301
  case "LOGIN_RESIZE":
834
1302
  break;
1303
+ case "LOGIN_REDIRECT_REQUIRED":
1304
+ if (!isValidLoginRedirectUrl(data.authUrl)) {
1305
+ logLoginWarn("Login redirect requested with invalid authUrl");
1306
+ options?.onError?.("Login redirect URL is invalid", data);
1307
+ break;
1308
+ }
1309
+ logLoginInfo("Login redirect requested by hosted page", {
1310
+ attemptId: data.attemptId || null,
1311
+ channel: data.channel || null,
1312
+ authUrl: data.authUrl
1313
+ });
1314
+ this.close();
1315
+ window.location.assign(data.authUrl);
1316
+ break;
835
1317
  case "LOGIN_ERROR":
836
1318
  if (this.completed) {
837
1319
  break;
@@ -855,6 +1337,25 @@ var LoginUI = class extends HostedFrameModal {
855
1337
  break;
856
1338
  }
857
1339
  }
1340
+ openLoginRedirect(params, options) {
1341
+ if (typeof window === "undefined") return;
1342
+ const { callbackState, finalUrl, launchPayload } = this.buildOpenContext(
1343
+ params,
1344
+ options
1345
+ );
1346
+ logLoginInfo("Redirecting to hosted login page", {
1347
+ appId: params.appId,
1348
+ callbackState,
1349
+ callbackUrl: launchPayload.callbackUrl || null,
1350
+ hasCallbackUrl: Boolean(launchPayload.callbackUrl),
1351
+ loginUrl: finalUrl,
1352
+ autoClose: options?.autoClose ?? true,
1353
+ displayMode: "redirect",
1354
+ pageUrl: window.location.href,
1355
+ parentOrigin: launchPayload.origin || null
1356
+ });
1357
+ window.location.assign(finalUrl);
1358
+ }
858
1359
  listenForLoginCallback(callbackState, options) {
859
1360
  try {
860
1361
  this.callbackChannel = new BroadcastChannel(
@@ -931,21 +1432,28 @@ var LoginUI = class extends HostedFrameModal {
931
1432
  options?.onCancel?.();
932
1433
  options?.onClose?.();
933
1434
  },
934
- onMessage: (data, container, event) => {
1435
+ onMessage: (data, _container, event) => {
935
1436
  const isIframeEvent = event.source === this.iframe?.contentWindow;
1437
+ if (!isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE")) {
1438
+ logLoginWarn(
1439
+ "Login lifecycle event ignored: event.source does not match iframe contentWindow",
1440
+ {
1441
+ type: data.type,
1442
+ eventOrigin: event.origin,
1443
+ hasIframe: Boolean(this.iframe),
1444
+ hasIframeContentWindow: Boolean(this.iframe?.contentWindow)
1445
+ }
1446
+ );
1447
+ }
936
1448
  if (isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE") || data.type === "LOGIN_SUCCESS" || data.type === "LOGIN_ERROR") {
937
1449
  this.markIframeReady();
938
1450
  }
939
- if (data.type === "LOGIN_RESIZE" && data.height) {
940
- const maxHeight = window.innerHeight * 0.94;
941
- container.style.height = `${Math.min(data.height, maxHeight)}px`;
942
- }
943
1451
  this.handleLoginEvent(data, options);
944
1452
  },
945
1453
  width: "min(520px, 100%)"
946
1454
  });
947
1455
  if (hostedFrame) {
948
- this.showIframeLoading(hostedFrame.container, options);
1456
+ this.showIframeLoading(hostedFrame.container);
949
1457
  this.startIframeWatchdog({
950
1458
  container: hostedFrame.container,
951
1459
  finalUrl,
@@ -1049,11 +1557,19 @@ var LoginUI = class extends HostedFrameModal {
1049
1557
  }
1050
1558
  openLogin(params, options) {
1051
1559
  if (typeof window === "undefined") return;
1052
- const displayMode = options?.displayMode ?? "modal";
1560
+ const displayMode = options?.displayMode ?? "auto";
1561
+ if (displayMode === "redirect") {
1562
+ this.openLoginRedirect(params, options);
1563
+ return;
1564
+ }
1053
1565
  if (displayMode === "popup") {
1054
1566
  this.openLoginPopup(params, options);
1055
1567
  return;
1056
1568
  }
1569
+ if (displayMode === "auto" && detectLoginEnvironment().shouldUseRedirectLogin) {
1570
+ this.openLoginRedirect(params, options);
1571
+ return;
1572
+ }
1057
1573
  this.openLoginModal(params, options);
1058
1574
  }
1059
1575
  };
@@ -1063,6 +1579,406 @@ function createLoginUI() {
1063
1579
  handleLoginCallbackIfPresent({ autoClose: false });
1064
1580
 
1065
1581
  // src/client.ts
1582
+ var DEFAULT_PAYMENT_IFRAME_LOAD_TIMEOUT_MS = 8e3;
1583
+ var PAYMENT_UI_LOG_PREFIX = "[Youidian PaymentUI]";
1584
+ function hexToRgb(value) {
1585
+ const normalized = value.trim().replace(/^#/, "");
1586
+ if (!/^[0-9a-f]{3}([0-9a-f]{3})?$/i.test(normalized)) return null;
1587
+ const hex = normalized.length === 3 ? normalized.split("").map((char) => `${char}${char}`).join("") : normalized;
1588
+ const number = Number.parseInt(hex, 16);
1589
+ return {
1590
+ b: number & 255,
1591
+ g: number >> 8 & 255,
1592
+ r: number >> 16 & 255
1593
+ };
1594
+ }
1595
+ function alphaColor(value, alpha) {
1596
+ const rgb = hexToRgb(value);
1597
+ if (!rgb)
1598
+ return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`;
1599
+ return `rgba(${rgb.r},${rgb.g},${rgb.b},${alpha})`;
1600
+ }
1601
+ function resolvePaymentTheme(options) {
1602
+ const primary = options?.theme?.primary || "#16a34a";
1603
+ const secondary = options?.theme?.secondary || "color-mix(in srgb, #16a34a 78%, white)";
1604
+ const foreground = options?.theme?.foreground || "#0f172a";
1605
+ const muted = options?.theme?.muted || "#64748b";
1606
+ const surface = options?.theme?.surface || "#ffffff";
1607
+ return {
1608
+ foreground,
1609
+ muted,
1610
+ primary,
1611
+ primary10: alphaColor(primary, 0.1),
1612
+ primary14: alphaColor(primary, 0.14),
1613
+ primary20: alphaColor(primary, 0.2),
1614
+ primary28: alphaColor(primary, 0.28),
1615
+ primary36: alphaColor(primary, 0.36),
1616
+ primary64: alphaColor(primary, 0.64),
1617
+ secondary,
1618
+ surface
1619
+ };
1620
+ }
1621
+ function logPaymentInfo(message, details) {
1622
+ if (typeof console === "undefined") return;
1623
+ console.info(PAYMENT_UI_LOG_PREFIX, message, details || {});
1624
+ }
1625
+ function logPaymentWarn(message, details) {
1626
+ if (typeof console === "undefined") return;
1627
+ console.warn(PAYMENT_UI_LOG_PREFIX, message, details || {});
1628
+ }
1629
+ function getPaymentIframeLoadTimeoutMs(options) {
1630
+ const timeout = options?.iframeLoadTimeoutMs;
1631
+ if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
1632
+ return DEFAULT_PAYMENT_IFRAME_LOAD_TIMEOUT_MS;
1633
+ }
1634
+ return Math.max(0, timeout);
1635
+ }
1636
+ function isChinesePaymentLocale(locale) {
1637
+ return locale?.toLowerCase().startsWith("zh") || false;
1638
+ }
1639
+ function getPaymentFallbackText(options) {
1640
+ const isChinese = isChinesePaymentLocale(options?.locale);
1641
+ return {
1642
+ buttonText: options?.fallbackButtonText || (isChinese ? "\u5728\u65B0\u9875\u9762\u4E2D\u6253\u5F00" : "Open in new page"),
1643
+ description: isChinese ? "\u5F53\u524D\u7F51\u7EDC\u53EF\u80FD\u8F83\u6162\uFF0C\u5EFA\u8BAE\u91CD\u65B0\u5C1D\u8BD5\u6216\u5728\u65B0\u9875\u9762\u4E2D\u6253\u5F00\uFF0C\u4EE5\u83B7\u5F97\u66F4\u6D41\u7545\u7684\u652F\u4ED8\u4F53\u9A8C\u3002" : "Your network may be slow. Try again or open the payment page in a new page to continue.",
1644
+ retryText: options?.fallbackRetryText || (isChinese ? "\u91CD\u65B0\u5C1D\u8BD5" : "Try again"),
1645
+ title: isChinese ? "\u652F\u4ED8\u9875\u9762\u6253\u5F00\u8F83\u6162" : "Payment page is taking longer"
1646
+ };
1647
+ }
1648
+ function getPaymentBrandText(options) {
1649
+ const isChinese = isChinesePaymentLocale(options?.locale);
1650
+ return {
1651
+ language: isChinese ? "zh" : "en",
1652
+ name: isChinese ? "\u4F18\u6613\u70B9" : "Youidian",
1653
+ subtitle: isChinese ? "\u5F00\u53D1\u8005\u670D\u52A1\u5E73\u53F0" : "Developer Service Platform"
1654
+ };
1655
+ }
1656
+ function getPaymentBrandLoadingText(options) {
1657
+ const isChinese = isChinesePaymentLocale(options?.locale);
1658
+ return {
1659
+ language: isChinese ? "zh" : "en",
1660
+ name: isChinese ? "\u4F18\u6613\u70B9" : "Youidian",
1661
+ subtitle: isChinese ? "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0" : "Developer Service"
1662
+ };
1663
+ }
1664
+ function getPaymentLoadingText(options) {
1665
+ const isChinese = isChinesePaymentLocale(options?.locale);
1666
+ return options?.loadingTitle || options?.loadingDescription || (isChinese ? "\u52A0\u8F7D\u4E2D..." : "Loading...");
1667
+ }
1668
+ function applyPaymentPanelStyle(panel, theme) {
1669
+ Object.assign(panel.style, {
1670
+ position: "absolute",
1671
+ inset: "0",
1672
+ display: "flex",
1673
+ alignItems: "center",
1674
+ justifyContent: "center",
1675
+ borderRadius: "28px",
1676
+ background: `linear-gradient(180deg, ${theme.surface} 0%, rgba(248,250,252,0.98) 100%)`,
1677
+ color: theme.foreground,
1678
+ padding: "28px 24px",
1679
+ textAlign: "center",
1680
+ zIndex: "1",
1681
+ boxShadow: "none",
1682
+ backdropFilter: "blur(16px)"
1683
+ });
1684
+ }
1685
+ function createPaymentIcon(type) {
1686
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1687
+ svg.setAttribute("viewBox", "0 0 24 24");
1688
+ svg.setAttribute("fill", "none");
1689
+ svg.setAttribute("aria-hidden", "true");
1690
+ Object.assign(svg.style, {
1691
+ display: "block",
1692
+ flex: "0 0 auto"
1693
+ });
1694
+ const paths = type === "external" ? [
1695
+ "M7 17L17 7",
1696
+ "M10 7H17V14",
1697
+ "M6.5 6.5H5.5A2.5 2.5 0 0 0 3 9V18.5A2.5 2.5 0 0 0 5.5 21H15A2.5 2.5 0 0 0 17.5 18.5V17.5"
1698
+ ] : type === "refresh" ? [
1699
+ "M20 6V11H15",
1700
+ "M4 18V13H9",
1701
+ "M18.2 9A7 7 0 0 0 6.2 6.6L4 9",
1702
+ "M5.8 15A7 7 0 0 0 17.8 17.4L20 15"
1703
+ ] : ["M12 7V12L15 14", "M21 12A9 9 0 1 1 3 12A9 9 0 0 1 21 12"];
1704
+ for (const value of paths) {
1705
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
1706
+ path.setAttribute("d", value);
1707
+ path.setAttribute("stroke", "currentColor");
1708
+ path.setAttribute("stroke-width", "2");
1709
+ path.setAttribute("stroke-linecap", "round");
1710
+ path.setAttribute("stroke-linejoin", "round");
1711
+ svg.appendChild(path);
1712
+ }
1713
+ return svg;
1714
+ }
1715
+ function createPaymentFallbackArtwork(theme) {
1716
+ const artwork = document.createElement("div");
1717
+ Object.assign(artwork.style, {
1718
+ height: "164px",
1719
+ margin: "0 auto 34px",
1720
+ position: "relative",
1721
+ width: "184px"
1722
+ });
1723
+ const wash = document.createElement("div");
1724
+ Object.assign(wash.style, {
1725
+ position: "absolute",
1726
+ left: "16px",
1727
+ top: "20px",
1728
+ width: "152px",
1729
+ height: "126px",
1730
+ borderRadius: "48% 52% 43% 57% / 54% 42% 58% 46%",
1731
+ background: `radial-gradient(circle at 48% 42%, ${theme.primary20}, ${theme.primary10} 52%, transparent 76%)`,
1732
+ filter: "blur(1px)",
1733
+ transform: "rotate(-10deg)"
1734
+ });
1735
+ const blob = document.createElement("div");
1736
+ Object.assign(blob.style, {
1737
+ position: "absolute",
1738
+ left: "26px",
1739
+ top: "28px",
1740
+ width: "132px",
1741
+ height: "116px",
1742
+ borderRadius: "42% 58% 53% 47% / 45% 39% 61% 55%",
1743
+ background: `linear-gradient(150deg, ${theme.primary14}, ${theme.primary10})`,
1744
+ boxShadow: `0 30px 64px ${theme.primary14}`,
1745
+ transform: "rotate(8deg)"
1746
+ });
1747
+ const tile = document.createElement("div");
1748
+ Object.assign(tile.style, {
1749
+ position: "absolute",
1750
+ left: "50%",
1751
+ top: "50%",
1752
+ transform: "translate(-50%, -50%) rotate(-4deg)",
1753
+ width: "112px",
1754
+ height: "106px",
1755
+ borderRadius: "38% 62% 50% 50% / 44% 43% 57% 56%",
1756
+ background: `linear-gradient(145deg, ${theme.primary}, ${theme.secondary})`,
1757
+ border: `1px solid ${theme.primary36}`,
1758
+ color: "#ffffff",
1759
+ display: "inline-flex",
1760
+ alignItems: "center",
1761
+ justifyContent: "center",
1762
+ boxShadow: `0 26px 54px ${theme.primary28}`
1763
+ });
1764
+ const icon = createPaymentIcon("slow");
1765
+ Object.assign(icon.style, {
1766
+ width: "52px",
1767
+ height: "52px",
1768
+ transform: "rotate(4deg)"
1769
+ });
1770
+ tile.appendChild(icon);
1771
+ artwork.appendChild(wash);
1772
+ artwork.appendChild(blob);
1773
+ artwork.appendChild(tile);
1774
+ return artwork;
1775
+ }
1776
+ function createPaymentBrandLoading(options, theme) {
1777
+ const brand = getPaymentBrandLoadingText(options);
1778
+ const wrapper = document.createElement("div");
1779
+ Object.assign(wrapper.style, {
1780
+ display: "flex",
1781
+ flexDirection: "column",
1782
+ alignItems: "center",
1783
+ justifyContent: "center",
1784
+ margin: "0 auto 16px"
1785
+ });
1786
+ wrapper.appendChild(
1787
+ createSdkBrandLogo({
1788
+ animated: true,
1789
+ brand,
1790
+ brandWidth: 292,
1791
+ copyWidth: 176,
1792
+ gap: 6,
1793
+ markMarginRight: -10,
1794
+ markSize: 112,
1795
+ nameSize: 39,
1796
+ subtitleMarginTop: 8,
1797
+ subtitleSize: 14,
1798
+ subtitleWeight: "700",
1799
+ theme,
1800
+ variant: "brand"
1801
+ })
1802
+ );
1803
+ return wrapper;
1804
+ }
1805
+ function createPaymentPlatformLogo(options, theme) {
1806
+ const brand = getPaymentBrandText(options);
1807
+ const wrapper = document.createElement("div");
1808
+ Object.assign(wrapper.style, {
1809
+ display: "flex",
1810
+ alignItems: "center",
1811
+ justifyContent: "center",
1812
+ margin: "0 auto 10px"
1813
+ });
1814
+ wrapper.appendChild(
1815
+ createSdkBrandLogo({
1816
+ brand,
1817
+ markSize: 48,
1818
+ nameSize: brand.language === "zh" ? 22 : 22,
1819
+ subtitleSize: brand.language === "zh" ? 9 : 9,
1820
+ theme
1821
+ })
1822
+ );
1823
+ return wrapper;
1824
+ }
1825
+ function createPaymentPanelContent() {
1826
+ const content = document.createElement("div");
1827
+ Object.assign(content.style, {
1828
+ display: "flex",
1829
+ flexDirection: "column",
1830
+ justifyContent: "center",
1831
+ maxWidth: "364px",
1832
+ minHeight: "100%",
1833
+ width: "100%"
1834
+ });
1835
+ return content;
1836
+ }
1837
+ function createPaymentPanelTitle(value, theme) {
1838
+ const title = document.createElement("div");
1839
+ title.textContent = value;
1840
+ Object.assign(title.style, {
1841
+ color: theme?.foreground || "#0f172a",
1842
+ fontSize: "24px",
1843
+ fontWeight: "900",
1844
+ lineHeight: "1.22",
1845
+ marginBottom: "16px"
1846
+ });
1847
+ return title;
1848
+ }
1849
+ function createPaymentPanelDescription(value, theme) {
1850
+ const description = document.createElement("div");
1851
+ description.textContent = value;
1852
+ Object.assign(description.style, {
1853
+ color: theme?.muted || "#64748b",
1854
+ fontSize: "14px",
1855
+ fontWeight: "600",
1856
+ lineHeight: "1.62",
1857
+ margin: "0 auto",
1858
+ maxWidth: "320px"
1859
+ });
1860
+ return description;
1861
+ }
1862
+ function createPaymentLoadingDescription(value, theme) {
1863
+ const description = document.createElement("div");
1864
+ description.textContent = value;
1865
+ Object.assign(description.style, {
1866
+ color: theme?.muted || "#64748b",
1867
+ fontSize: "14px",
1868
+ fontWeight: "500",
1869
+ lineHeight: "1.25",
1870
+ margin: "12px auto 0",
1871
+ maxWidth: "320px"
1872
+ });
1873
+ return description;
1874
+ }
1875
+ function createPaymentFallbackMessage(options) {
1876
+ const message = document.createElement("div");
1877
+ Object.assign(message.style, {
1878
+ margin: "0 auto",
1879
+ textAlign: "center"
1880
+ });
1881
+ message.appendChild(createPaymentFallbackArtwork(options.theme));
1882
+ message.appendChild(createPaymentPanelTitle(options.title, options.theme));
1883
+ message.appendChild(
1884
+ createPaymentPanelDescription(options.description, options.theme)
1885
+ );
1886
+ return message;
1887
+ }
1888
+ function createPaymentLoadingPanel(options) {
1889
+ const panel = document.createElement("div");
1890
+ const theme = resolvePaymentTheme(options);
1891
+ applyPaymentPanelStyle(panel, theme);
1892
+ const content = createPaymentPanelContent();
1893
+ content.appendChild(createPaymentBrandLoading(options, theme));
1894
+ content.appendChild(
1895
+ createPaymentLoadingDescription(getPaymentLoadingText(options), theme)
1896
+ );
1897
+ panel.appendChild(content);
1898
+ return panel;
1899
+ }
1900
+ function createPaymentButton(value, options) {
1901
+ const primary = options?.primary || false;
1902
+ const theme = options?.theme || resolvePaymentTheme();
1903
+ const button = document.createElement("button");
1904
+ button.type = "button";
1905
+ Object.assign(button.style, {
1906
+ width: "100%",
1907
+ height: "50px",
1908
+ borderRadius: "14px",
1909
+ border: primary ? `1px solid ${theme.primary}` : `1px solid ${theme.primary64}`,
1910
+ background: primary ? `linear-gradient(135deg, ${theme.primary}, ${theme.secondary})` : "#ffffff",
1911
+ color: primary ? "#ffffff" : theme.primary,
1912
+ cursor: "pointer",
1913
+ display: "inline-flex",
1914
+ alignItems: "center",
1915
+ justifyContent: "center",
1916
+ gap: "10px",
1917
+ fontSize: "15px",
1918
+ fontWeight: "800",
1919
+ marginTop: primary ? "12px" : "0",
1920
+ boxShadow: primary ? `0 14px 28px ${theme.primary20}` : "none"
1921
+ });
1922
+ if (options?.icon) {
1923
+ const icon = createPaymentIcon(options.icon);
1924
+ Object.assign(icon.style, {
1925
+ width: "17px",
1926
+ height: "17px"
1927
+ });
1928
+ button.appendChild(icon);
1929
+ }
1930
+ const label = document.createElement("span");
1931
+ label.textContent = value;
1932
+ button.appendChild(label);
1933
+ return button;
1934
+ }
1935
+ function createPaymentFallbackPanel(options) {
1936
+ const panel = document.createElement("div");
1937
+ const theme = resolvePaymentTheme(options.paymentOptions);
1938
+ applyPaymentPanelStyle(panel, theme);
1939
+ const content = createPaymentPanelContent();
1940
+ Object.assign(content.style, {
1941
+ justifyContent: "space-between"
1942
+ });
1943
+ const main = document.createElement("div");
1944
+ Object.assign(main.style, {
1945
+ display: "flex",
1946
+ flexDirection: "column",
1947
+ alignItems: "center",
1948
+ marginTop: "38px",
1949
+ width: "100%"
1950
+ });
1951
+ main.appendChild(
1952
+ createPaymentFallbackMessage({
1953
+ description: options.description,
1954
+ theme,
1955
+ title: options.title
1956
+ })
1957
+ );
1958
+ const actions = document.createElement("div");
1959
+ Object.assign(actions.style, {
1960
+ marginTop: "46px",
1961
+ width: "100%"
1962
+ });
1963
+ const retryButton = createPaymentButton(options.retryText, {
1964
+ icon: "refresh",
1965
+ theme
1966
+ });
1967
+ retryButton.onclick = options.onRetry;
1968
+ actions.appendChild(retryButton);
1969
+ const openButton = createPaymentButton(options.buttonText, {
1970
+ icon: "external",
1971
+ primary: true,
1972
+ theme
1973
+ });
1974
+ openButton.onclick = options.onOpen;
1975
+ actions.appendChild(openButton);
1976
+ main.appendChild(actions);
1977
+ content.appendChild(main);
1978
+ content.appendChild(createPaymentPlatformLogo(options.paymentOptions, theme));
1979
+ panel.appendChild(content);
1980
+ return panel;
1981
+ }
1066
1982
  var PaymentUI = class extends HostedFrameModal {
1067
1983
  constructor() {
1068
1984
  super(...arguments);
@@ -1071,6 +1987,127 @@ var PaymentUI = class extends HostedFrameModal {
1071
1987
  __publicField(this, "activeOrderId", null);
1072
1988
  __publicField(this, "paymentCompleted", false);
1073
1989
  __publicField(this, "cancelRequestedOrderId", null);
1990
+ __publicField(this, "iframeFallbackPanel", null);
1991
+ __publicField(this, "iframeLoadingPanel", null);
1992
+ __publicField(this, "iframeLoadTimer", null);
1993
+ __publicField(this, "iframeReady", false);
1994
+ }
1995
+ cleanupPaymentFrameState() {
1996
+ if (typeof window !== "undefined" && this.iframeLoadTimer) {
1997
+ window.clearTimeout(this.iframeLoadTimer);
1998
+ }
1999
+ if (this.iframeFallbackPanel?.parentNode) {
2000
+ this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
2001
+ }
2002
+ if (this.iframeLoadingPanel?.parentNode) {
2003
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
2004
+ }
2005
+ this.iframeFallbackPanel = null;
2006
+ this.iframeLoadingPanel = null;
2007
+ this.iframeLoadTimer = null;
2008
+ this.iframeReady = false;
2009
+ }
2010
+ markPaymentIframeReady() {
2011
+ if (this.iframeReady) return;
2012
+ this.iframeReady = true;
2013
+ if (typeof window !== "undefined" && this.iframeLoadTimer) {
2014
+ window.clearTimeout(this.iframeLoadTimer);
2015
+ this.iframeLoadTimer = null;
2016
+ }
2017
+ if (this.iframeFallbackPanel?.parentNode) {
2018
+ this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
2019
+ }
2020
+ if (this.iframeLoadingPanel?.parentNode) {
2021
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
2022
+ }
2023
+ this.iframeFallbackPanel = null;
2024
+ this.iframeLoadingPanel = null;
2025
+ }
2026
+ showPaymentIframeLoading(container, options) {
2027
+ if (this.iframeLoadingPanel || this.iframeReady) return;
2028
+ const panel = createPaymentLoadingPanel(options);
2029
+ this.iframeLoadingPanel = panel;
2030
+ container.appendChild(panel);
2031
+ }
2032
+ openFallbackPaymentPage(finalUrl, options) {
2033
+ if (typeof window === "undefined") return;
2034
+ const popup = window.open(
2035
+ finalUrl,
2036
+ "youidian-payment-checkout",
2037
+ "popup=yes,width=480,height=760,resizable=yes,scrollbars=yes"
2038
+ );
2039
+ if (popup) {
2040
+ logPaymentInfo("Opened hosted checkout fallback page", {
2041
+ checkoutUrl: finalUrl
2042
+ });
2043
+ options?.onFallbackOpen?.(finalUrl);
2044
+ try {
2045
+ popup.focus();
2046
+ } catch {
2047
+ }
2048
+ return;
2049
+ }
2050
+ logPaymentWarn("Hosted checkout fallback popup was blocked", {
2051
+ checkoutUrl: finalUrl
2052
+ });
2053
+ options?.onFallbackBlocked?.(finalUrl);
2054
+ window.location.assign(finalUrl);
2055
+ }
2056
+ showPaymentIframeFallback(options) {
2057
+ if (this.iframeReady || this.iframeFallbackPanel) return;
2058
+ const { container, finalUrl, paymentOptions } = options;
2059
+ logPaymentWarn("Hosted checkout iframe did not report ready in time", {
2060
+ checkoutUrl: finalUrl
2061
+ });
2062
+ if (this.iframeLoadingPanel?.parentNode) {
2063
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
2064
+ }
2065
+ this.iframeLoadingPanel = null;
2066
+ const fallbackText = getPaymentFallbackText(paymentOptions);
2067
+ const panel = createPaymentFallbackPanel({
2068
+ buttonText: fallbackText.buttonText,
2069
+ description: fallbackText.description,
2070
+ onOpen: () => {
2071
+ this.openFallbackPaymentPage(finalUrl, paymentOptions);
2072
+ },
2073
+ onRetry: () => {
2074
+ this.iframeFallbackPanel = null;
2075
+ if (panel.parentNode) {
2076
+ panel.parentNode.removeChild(panel);
2077
+ }
2078
+ this.iframeReady = false;
2079
+ this.showPaymentIframeLoading(container, paymentOptions);
2080
+ if (this.iframe) {
2081
+ this.iframe.src = finalUrl;
2082
+ }
2083
+ this.startPaymentIframeWatchdog({
2084
+ container,
2085
+ finalUrl,
2086
+ paymentOptions
2087
+ });
2088
+ },
2089
+ paymentOptions,
2090
+ retryText: fallbackText.retryText,
2091
+ title: fallbackText.title
2092
+ });
2093
+ this.iframeFallbackPanel = panel;
2094
+ container.appendChild(panel);
2095
+ paymentOptions?.onFallbackVisible?.();
2096
+ }
2097
+ startPaymentIframeWatchdog(options) {
2098
+ if (typeof window === "undefined") return;
2099
+ if (this.iframeLoadTimer) {
2100
+ window.clearTimeout(this.iframeLoadTimer);
2101
+ }
2102
+ const timeoutMs = getPaymentIframeLoadTimeoutMs(options.paymentOptions);
2103
+ if (timeoutMs === 0) {
2104
+ this.showPaymentIframeFallback(options);
2105
+ return;
2106
+ }
2107
+ this.iframeLoadTimer = window.setTimeout(() => {
2108
+ this.iframeLoadTimer = null;
2109
+ this.showPaymentIframeFallback(options);
2110
+ }, timeoutMs);
1074
2111
  }
1075
2112
  cancelHostedOrder(reason = "user_cancel", orderId) {
1076
2113
  const targetOrderId = orderId || this.activeOrderId;
@@ -1113,6 +2150,7 @@ var PaymentUI = class extends HostedFrameModal {
1113
2150
  this.activeOrderId = null;
1114
2151
  this.paymentCompleted = false;
1115
2152
  this.cancelRequestedOrderId = null;
2153
+ this.cleanupPaymentFrameState();
1116
2154
  if (typeof urlOrParams === "string") {
1117
2155
  checkoutUrl = urlOrParams;
1118
2156
  try {
@@ -1181,14 +2219,21 @@ var PaymentUI = class extends HostedFrameModal {
1181
2219
  checkoutUrl,
1182
2220
  getAutoResolvedLocale(options?.locale)
1183
2221
  );
1184
- this.openHostedFrame(finalUrl, {
2222
+ const hostedFrame = this.openHostedFrame(finalUrl, {
1185
2223
  allowedOrigin: options?.allowedOrigin,
2224
+ height: "min(760px, calc(100vh - 32px))",
1186
2225
  onCloseButton: () => {
1187
2226
  this.cancelHostedOrder("modal_close");
1188
2227
  options?.onCancel?.(this.activeOrderId || void 0);
1189
2228
  },
1190
- onMessage: (data, container) => {
2229
+ onMessage: (data, container, event) => {
2230
+ const isIframeMessage = event.source === this.iframe?.contentWindow;
2231
+ if (isIframeMessage && (data.type === "PAYMENT_READY" || data.type === "PAYMENT_RESIZE" || data.type === "PAYMENT_STARTED")) {
2232
+ this.markPaymentIframeReady();
2233
+ }
1191
2234
  switch (data.type) {
2235
+ case "PAYMENT_READY":
2236
+ break;
1192
2237
  case "PAYMENT_STARTED":
1193
2238
  if (data.orderId) {
1194
2239
  this.activeOrderId = data.orderId;
@@ -1203,11 +2248,6 @@ var PaymentUI = class extends HostedFrameModal {
1203
2248
  options?.onCancel?.(data.orderId);
1204
2249
  break;
1205
2250
  case "PAYMENT_RESIZE":
1206
- if (data.height) {
1207
- const maxHeight = window.innerHeight * 0.9;
1208
- const newHeight = Math.min(data.height, maxHeight);
1209
- container.style.height = `${newHeight}px`;
1210
- }
1211
2251
  break;
1212
2252
  case "PAYMENT_CLOSE":
1213
2253
  this.cancelHostedOrder("payment_close", data.orderId);
@@ -1215,8 +2255,21 @@ var PaymentUI = class extends HostedFrameModal {
1215
2255
  options?.onClose?.();
1216
2256
  break;
1217
2257
  }
1218
- }
2258
+ },
2259
+ width: "min(450px, 100%)"
1219
2260
  });
2261
+ if (hostedFrame) {
2262
+ this.showPaymentIframeLoading(hostedFrame.container, options);
2263
+ this.startPaymentIframeWatchdog({
2264
+ container: hostedFrame.container,
2265
+ finalUrl,
2266
+ paymentOptions: options
2267
+ });
2268
+ }
2269
+ }
2270
+ close() {
2271
+ super.close();
2272
+ this.cleanupPaymentFrameState();
1220
2273
  }
1221
2274
  /**
1222
2275
  * Poll order status from integrator's API endpoint
@@ -1262,14 +2315,40 @@ var PaymentUI = class extends HostedFrameModal {
1262
2315
  });
1263
2316
  }
1264
2317
  };
2318
+ var MessageUI = class {
2319
+ openWechatBinding(options) {
2320
+ const bindingUrl = options.bindingUrl?.trim();
2321
+ if (!bindingUrl) {
2322
+ throw new Error("bindingUrl is required");
2323
+ }
2324
+ if (options.displayMode === "popup") {
2325
+ const popup = window.open(
2326
+ bindingUrl,
2327
+ options.popupName || "youidian-wechat-message-binding",
2328
+ "width=480,height=720,noopener,noreferrer"
2329
+ );
2330
+ if (!popup) {
2331
+ window.location.href = bindingUrl;
2332
+ }
2333
+ return;
2334
+ }
2335
+ window.location.href = bindingUrl;
2336
+ }
2337
+ };
2338
+ function createMessageUI() {
2339
+ return new MessageUI();
2340
+ }
1265
2341
  function createPaymentUI() {
1266
2342
  return new PaymentUI();
1267
2343
  }
1268
2344
  export {
1269
2345
  LoginUI,
2346
+ MessageUI,
1270
2347
  PaymentUI,
1271
2348
  createLoginUI,
2349
+ createMessageUI,
1272
2350
  createPaymentUI,
2351
+ detectLoginEnvironment,
1273
2352
  handleLoginCallbackIfPresent
1274
2353
  };
1275
2354
  //# sourceMappingURL=client.js.map