@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.cjs CHANGED
@@ -23,13 +23,231 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
23
23
  var client_exports = {};
24
24
  __export(client_exports, {
25
25
  LoginUI: () => LoginUI,
26
+ MessageUI: () => MessageUI,
26
27
  PaymentUI: () => PaymentUI,
27
28
  createLoginUI: () => createLoginUI,
29
+ createMessageUI: () => createMessageUI,
28
30
  createPaymentUI: () => createPaymentUI,
31
+ detectLoginEnvironment: () => detectLoginEnvironment,
29
32
  handleLoginCallbackIfPresent: () => handleLoginCallbackIfPresent
30
33
  });
31
34
  module.exports = __toCommonJS(client_exports);
32
35
 
36
+ // src/brand-logo.ts
37
+ function ensureSdkBrandLogoStyles() {
38
+ if (document.getElementById?.("youidian-sdk-brand-logo-style")) {
39
+ return;
40
+ }
41
+ const style = document.createElement("style");
42
+ style.id = "youidian-sdk-brand-logo-style";
43
+ style.textContent = `
44
+ .youidian-sdk-brand-logo__ring,
45
+ .youidian-sdk-brand-logo__dot,
46
+ .youidian-sdk-brand-logo__copy,
47
+ .youidian-sdk-brand-logo__tagline {
48
+ transform-box: fill-box;
49
+ transform-origin: center;
50
+ will-change: opacity, transform;
51
+ }
52
+
53
+ .youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__ring {
54
+ animation: youidian-sdk-brand-logo-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
55
+ }
56
+
57
+ .youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__dot {
58
+ animation: youidian-sdk-brand-logo-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
59
+ }
60
+
61
+ .youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__copy {
62
+ animation: youidian-sdk-brand-logo-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
63
+ }
64
+
65
+ .youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__tagline {
66
+ animation: youidian-sdk-brand-logo-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
67
+ }
68
+
69
+ @keyframes youidian-sdk-brand-logo-ring {
70
+ 0%,
71
+ 100% {
72
+ opacity: 0.72;
73
+ transform: rotate(0deg) scale(0.98);
74
+ }
75
+ 50% {
76
+ opacity: 1;
77
+ transform: rotate(8deg) scale(1.02);
78
+ }
79
+ }
80
+
81
+ @keyframes youidian-sdk-brand-logo-dot {
82
+ 0%,
83
+ 100% {
84
+ opacity: 0.58;
85
+ transform: scale(0.9);
86
+ }
87
+ 50% {
88
+ opacity: 1;
89
+ transform: scale(1.08);
90
+ }
91
+ }
92
+
93
+ @keyframes youidian-sdk-brand-logo-copy {
94
+ 0%,
95
+ 100% {
96
+ opacity: 0.86;
97
+ transform: translateY(0);
98
+ }
99
+ 50% {
100
+ opacity: 1;
101
+ transform: translateY(-1px);
102
+ }
103
+ }
104
+
105
+ @keyframes youidian-sdk-brand-logo-tagline {
106
+ 0%,
107
+ 100% {
108
+ opacity: 0.64;
109
+ }
110
+ 50% {
111
+ opacity: 0.92;
112
+ }
113
+ }
114
+
115
+ @media (prefers-reduced-motion: reduce) {
116
+ .youidian-sdk-brand-logo__ring,
117
+ .youidian-sdk-brand-logo__dot,
118
+ .youidian-sdk-brand-logo__copy,
119
+ .youidian-sdk-brand-logo__tagline {
120
+ animation: none;
121
+ opacity: 1;
122
+ transform: none;
123
+ }
124
+ }
125
+ `;
126
+ document.head?.appendChild(style);
127
+ }
128
+ function createSdkBrandLogoMark({
129
+ animated,
130
+ inverted,
131
+ size,
132
+ theme,
133
+ variant = "brand"
134
+ }) {
135
+ ensureSdkBrandLogoStyles();
136
+ const mark = document.createElementNS("http://www.w3.org/2000/svg", "svg");
137
+ mark.setAttribute("viewBox", "0 0 512 512");
138
+ mark.setAttribute("fill", "none");
139
+ mark.setAttribute("aria-hidden", "true");
140
+ Object.assign(mark.style, {
141
+ width: `${size}px`,
142
+ height: `${size}px`,
143
+ color: inverted ? "#ffffff" : theme.primary,
144
+ display: "block",
145
+ flex: "0 0 auto",
146
+ overflow: "visible"
147
+ });
148
+ const logoGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
149
+ logoGroup.setAttribute(
150
+ "transform",
151
+ variant === "mark" ? "matrix(0.74 0 0 0.74 -124 -114)" : "matrix(0.58 0 0 0.58 -40 -31)"
152
+ );
153
+ const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
154
+ ring.setAttribute("class", animated ? "youidian-sdk-brand-logo__ring" : "");
155
+ ring.setAttribute("fill", inverted ? "currentColor" : theme.primary);
156
+ ring.setAttribute(
157
+ "d",
158
+ "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"
159
+ );
160
+ const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
161
+ dot.setAttribute("class", animated ? "youidian-sdk-brand-logo__dot" : "");
162
+ dot.setAttribute("fill", inverted ? "currentColor" : theme.secondary);
163
+ dot.setAttribute("cx", "714.5");
164
+ dot.setAttribute("cy", "490.5");
165
+ dot.setAttribute("r", "68");
166
+ dot.setAttribute("opacity", inverted ? "0.78" : "1");
167
+ logoGroup.appendChild(ring);
168
+ logoGroup.appendChild(dot);
169
+ mark.appendChild(logoGroup);
170
+ return mark;
171
+ }
172
+ function createSdkBrandLogo({
173
+ animated,
174
+ brand,
175
+ brandWidth,
176
+ copyWidth,
177
+ gap = 10,
178
+ markMarginRight,
179
+ markSize,
180
+ nameSize,
181
+ subtitleMarginTop = 6,
182
+ subtitleSize,
183
+ subtitleWeight = "800",
184
+ theme,
185
+ variant = "brand"
186
+ }) {
187
+ ensureSdkBrandLogoStyles();
188
+ const wrapper = document.createElement("div");
189
+ wrapper.className = "youidian-sdk-brand-logo";
190
+ wrapper.setAttribute("data-animated", animated ? "true" : "false");
191
+ Object.assign(wrapper.style, {
192
+ display: "inline-flex",
193
+ alignItems: "center",
194
+ justifyContent: "center",
195
+ gap: `${gap}px`,
196
+ width: brandWidth ? `${brandWidth}px` : void 0
197
+ });
198
+ const copy = document.createElement("div");
199
+ copy.className = "youidian-sdk-brand-logo__copy";
200
+ copy.setAttribute("data-brand-locale", brand.language);
201
+ Object.assign(copy.style, {
202
+ display: "flex",
203
+ flexDirection: "column",
204
+ alignItems: "flex-start",
205
+ justifyContent: "center",
206
+ lineHeight: "1",
207
+ width: copyWidth ? `${copyWidth}px` : void 0
208
+ });
209
+ const name = document.createElement("div");
210
+ name.className = "youidian-sdk-brand-logo__name";
211
+ name.textContent = brand.name;
212
+ Object.assign(name.style, {
213
+ color: theme.foreground,
214
+ 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',
215
+ fontSize: `${nameSize}px`,
216
+ fontWeight: brand.language === "zh" ? "900" : "800",
217
+ letterSpacing: brand.language === "zh" ? "0.08em" : "0.01em",
218
+ lineHeight: "1",
219
+ whiteSpace: "nowrap"
220
+ });
221
+ const subtitle = document.createElement("div");
222
+ subtitle.className = "youidian-sdk-brand-logo__tagline";
223
+ subtitle.textContent = brand.subtitle;
224
+ Object.assign(subtitle.style, {
225
+ color: theme.muted,
226
+ 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',
227
+ fontSize: `${subtitleSize}px`,
228
+ fontWeight: subtitleWeight,
229
+ letterSpacing: brand.language === "zh" ? "0.24em" : "0.07em",
230
+ lineHeight: "1",
231
+ marginTop: `${subtitleMarginTop}px`,
232
+ textTransform: brand.language === "zh" ? "none" : "uppercase",
233
+ whiteSpace: "nowrap"
234
+ });
235
+ copy.appendChild(name);
236
+ copy.appendChild(subtitle);
237
+ const mark = createSdkBrandLogoMark({
238
+ animated,
239
+ size: markSize,
240
+ theme,
241
+ variant
242
+ });
243
+ if (typeof markMarginRight === "number") {
244
+ mark.style.marginRight = `${markMarginRight}px`;
245
+ }
246
+ wrapper.appendChild(mark);
247
+ wrapper.appendChild(copy);
248
+ return wrapper;
249
+ }
250
+
33
251
  // src/hosted-modal.ts
34
252
  var SDK_SUPPORTED_LOCALES = [
35
253
  "en",
@@ -153,7 +371,7 @@ var HostedFrameModal = class {
153
371
  const container = document.createElement("div");
154
372
  Object.assign(container.style, {
155
373
  width: options.width || "450px",
156
- height: options.height || "min(600px, 90vh)",
374
+ height: options.height || "min(760px, calc(100vh - 32px))",
157
375
  backgroundColor: "transparent",
158
376
  borderRadius: "28px",
159
377
  overflow: "visible",
@@ -161,8 +379,7 @@ var HostedFrameModal = class {
161
379
  boxShadow: "none",
162
380
  maxWidth: "calc(100vw - 32px)",
163
381
  maxHeight: "calc(100vh - 32px)",
164
- transition: "height 180ms ease",
165
- willChange: "height"
382
+ transition: "none"
166
383
  });
167
384
  const closeBtn = document.createElement("button");
168
385
  closeBtn.innerHTML = "\xD7";
@@ -232,6 +449,91 @@ var HostedFrameModal = class {
232
449
  }
233
450
  };
234
451
 
452
+ // src/environment.ts
453
+ function normalizeText(value) {
454
+ return value || "";
455
+ }
456
+ function getRuntimeInput() {
457
+ if (typeof navigator === "undefined") {
458
+ return {};
459
+ }
460
+ const nav = navigator;
461
+ const coarsePointer = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : void 0;
462
+ return {
463
+ coarsePointer,
464
+ maxTouchPoints: nav.maxTouchPoints || 0,
465
+ platform: nav.platform || "",
466
+ standalone: nav.standalone,
467
+ userAgent: nav.userAgent || "",
468
+ userAgentDataMobile: nav.userAgentData?.mobile
469
+ };
470
+ }
471
+ function includesAny(value, patterns) {
472
+ return patterns.some((pattern) => pattern.test(value));
473
+ }
474
+ function detectLoginEnvironment(input = getRuntimeInput()) {
475
+ const userAgent = normalizeText(input.userAgent);
476
+ const platform = normalizeText(input.platform);
477
+ const rawMaxTouchPoints = input.maxTouchPoints ?? 0;
478
+ const maxTouchPoints = Number.isFinite(rawMaxTouchPoints) ? Math.max(0, rawMaxTouchPoints) : 0;
479
+ const isCoarsePointer = input.coarsePointer === true;
480
+ const isTouch = maxTouchPoints > 0 || isCoarsePointer;
481
+ const isWeChat = /micromessenger/i.test(userAgent);
482
+ const isAndroid = /\bandroid\b/i.test(userAgent);
483
+ const hasExplicitIOSDevice = /\b(iPad|iPhone|iPod)\b/i.test(userAgent);
484
+ const hasMacintoshUserAgent = /\bMacintosh\b/i.test(userAgent);
485
+ const hasMacPlatform = /^Mac/i.test(platform);
486
+ const isIPadOS = !hasExplicitIOSDevice && hasMacintoshUserAgent && hasMacPlatform && typeof input.standalone !== "undefined" && maxTouchPoints > 2;
487
+ const isIOS = hasExplicitIOSDevice || isIPadOS;
488
+ const isTabletByUa = includesAny(userAgent, [
489
+ /\biPad\b/i,
490
+ /\btablet\b/i,
491
+ /\bplaybook\b/i,
492
+ /\bsilk\b(?!.*\bmobile\b)/i,
493
+ /\bkindle\b/i,
494
+ /\bnexus 7\b/i,
495
+ /\bnexus 9\b/i,
496
+ /\bxoom\b/i,
497
+ /\bsm-t\d+/i,
498
+ /\bgt-p\d+/i,
499
+ /\bmi pad\b/i
500
+ ]);
501
+ const isMobileByUa = includesAny(userAgent, [
502
+ /\bMobile\b/i,
503
+ /\biPhone\b/i,
504
+ /\biPod\b/i,
505
+ /\bWindows Phone\b/i,
506
+ /\bIEMobile\b/i,
507
+ /\bBlackBerry\b/i,
508
+ /\bBB10\b/i,
509
+ /\bOpera Mini\b/i,
510
+ /\bOpera Mobi\b/i,
511
+ /\bwebOS\b/i
512
+ ]);
513
+ const isAndroidTablet = isAndroid && !/\bMobile\b/i.test(userAgent);
514
+ const isTablet = isIPadOS || isTabletByUa || isAndroidTablet;
515
+ const isMobile = input.userAgentDataMobile === true || isMobileByUa || isAndroid && !isTablet || isIOS && !isTablet;
516
+ const deviceType = isTablet ? "tablet" : isMobile ? "mobile" : userAgent || platform || isTouch ? "desktop" : "unknown";
517
+ const shouldUseRedirectLogin = deviceType === "mobile" || deviceType === "tablet" || isWeChat;
518
+ return {
519
+ deviceType,
520
+ isAndroid,
521
+ isCoarsePointer,
522
+ isDesktop: deviceType === "desktop",
523
+ isIOS,
524
+ isIPadOS,
525
+ isMobile,
526
+ isStandalone: input.standalone === true,
527
+ isTablet,
528
+ isTouch,
529
+ isWeChat,
530
+ maxTouchPoints,
531
+ platform,
532
+ shouldUseRedirectLogin,
533
+ userAgent
534
+ };
535
+ }
536
+
235
537
  // src/login.ts
236
538
  function getOrigin(value) {
237
539
  if (!value) return null;
@@ -241,6 +543,15 @@ function getOrigin(value) {
241
543
  return null;
242
544
  }
243
545
  }
546
+ function isValidLoginRedirectUrl(value) {
547
+ if (!value) return false;
548
+ try {
549
+ const url = new URL(value);
550
+ return url.protocol === "https:" || url.protocol === "http:";
551
+ } catch {
552
+ return false;
553
+ }
554
+ }
244
555
  function createLoginCallbackState() {
245
556
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
246
557
  return crypto.randomUUID().replace(/-/g, "");
@@ -587,6 +898,24 @@ function applyLoginPanelStyle(panel) {
587
898
  backdropFilter: "blur(16px)"
588
899
  });
589
900
  }
901
+ function applyLoginLoadingPanelStyle(panel) {
902
+ Object.assign(panel.style, {
903
+ position: "absolute",
904
+ inset: "0",
905
+ display: "flex",
906
+ alignItems: "center",
907
+ justifyContent: "center",
908
+ border: "0",
909
+ borderRadius: "28px",
910
+ background: "rgba(255,255,255,0.94)",
911
+ color: "#0f172a",
912
+ padding: "24px",
913
+ textAlign: "center",
914
+ zIndex: "1",
915
+ boxShadow: "none",
916
+ backdropFilter: "blur(16px)"
917
+ });
918
+ }
590
919
  function createLoginPanelContent() {
591
920
  const content = document.createElement("div");
592
921
  Object.assign(content.style, {
@@ -611,6 +940,71 @@ function createLoginPanelContent() {
611
940
  content.appendChild(badge);
612
941
  return content;
613
942
  }
943
+ function createBrandLoadingMark() {
944
+ const logo = document.createElementNS("http://www.w3.org/2000/svg", "svg");
945
+ logo.setAttribute("viewBox", "0 0 1024 1024");
946
+ logo.setAttribute("fill", "none");
947
+ logo.setAttribute("aria-hidden", "true");
948
+ Object.assign(logo.style, {
949
+ width: "112px",
950
+ height: "112px",
951
+ color: "#22c55e",
952
+ display: "block",
953
+ overflow: "visible"
954
+ });
955
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
956
+ group.setAttribute("fill", "currentColor");
957
+ const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
958
+ ring.setAttribute(
959
+ "d",
960
+ "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"
961
+ );
962
+ ring.setAttribute("class", "youidian-sdk-brand-loading__ring");
963
+ const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
964
+ dot.setAttribute("class", "youidian-sdk-brand-loading__dot");
965
+ dot.setAttribute("cx", "714.5");
966
+ dot.setAttribute("cy", "490.5");
967
+ dot.setAttribute("r", "68");
968
+ group.appendChild(ring);
969
+ group.appendChild(dot);
970
+ logo.appendChild(group);
971
+ return logo;
972
+ }
973
+ function createBrandLoadingCopy() {
974
+ const brand = document.createElement("div");
975
+ brand.className = "youidian-sdk-brand-loading__copy";
976
+ Object.assign(brand.style, {
977
+ display: "flex",
978
+ flexDirection: "column",
979
+ alignItems: "flex-start",
980
+ justifyContent: "center",
981
+ lineHeight: "1"
982
+ });
983
+ const name = document.createElement("div");
984
+ name.className = "youidian-sdk-brand-loading__name";
985
+ name.textContent = "\u4F18\u6613\u70B9";
986
+ Object.assign(name.style, {
987
+ color: "#0f172a",
988
+ fontSize: "34px",
989
+ fontWeight: "900",
990
+ letterSpacing: "0",
991
+ lineHeight: "1"
992
+ });
993
+ const tagline = document.createElement("div");
994
+ tagline.className = "youidian-sdk-brand-loading__tagline";
995
+ tagline.textContent = "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0";
996
+ Object.assign(tagline.style, {
997
+ color: "#64748b",
998
+ fontSize: "16px",
999
+ fontWeight: "700",
1000
+ letterSpacing: "0.24em",
1001
+ lineHeight: "1",
1002
+ marginTop: "6px"
1003
+ });
1004
+ brand.appendChild(name);
1005
+ brand.appendChild(tagline);
1006
+ return brand;
1007
+ }
614
1008
  function createLoginPanelTitle(value) {
615
1009
  const title = document.createElement("div");
616
1010
  title.textContent = value;
@@ -640,32 +1034,111 @@ function ensureLoginLoadingStyles() {
640
1034
  }
641
1035
  const style = document.createElement("style");
642
1036
  style.id = "youidian-login-loading-style";
643
- style.textContent = "@keyframes youidian-login-spin{to{transform:rotate(360deg)}}";
1037
+ style.textContent = `
1038
+ .youidian-sdk-brand-loading__ring,
1039
+ .youidian-sdk-brand-loading__dot {
1040
+ transform-box: fill-box;
1041
+ transform-origin: center;
1042
+ will-change: opacity, transform;
1043
+ }
1044
+
1045
+ .youidian-sdk-brand-loading__ring {
1046
+ animation: youidian-sdk-brand-loading-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
1047
+ }
1048
+
1049
+ .youidian-sdk-brand-loading__dot {
1050
+ animation: youidian-sdk-brand-loading-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
1051
+ }
1052
+
1053
+ .youidian-sdk-brand-loading__copy {
1054
+ will-change: opacity, transform;
1055
+ animation: youidian-sdk-brand-loading-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
1056
+ }
1057
+
1058
+ .youidian-sdk-brand-loading__tagline {
1059
+ will-change: opacity;
1060
+ animation: youidian-sdk-brand-loading-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
1061
+ }
1062
+
1063
+ @keyframes youidian-sdk-brand-loading-ring {
1064
+ 0%,
1065
+ 100% {
1066
+ opacity: 0.72;
1067
+ transform: rotate(0deg) scale(0.98);
1068
+ }
1069
+ 50% {
1070
+ opacity: 1;
1071
+ transform: rotate(8deg) scale(1.02);
1072
+ }
1073
+ }
1074
+
1075
+ @keyframes youidian-sdk-brand-loading-dot {
1076
+ 0%,
1077
+ 100% {
1078
+ opacity: 0.55;
1079
+ transform: scale(0.9);
1080
+ }
1081
+ 50% {
1082
+ opacity: 1;
1083
+ transform: scale(1.08);
1084
+ }
1085
+ }
1086
+
1087
+ @keyframes youidian-sdk-brand-loading-copy {
1088
+ 0%,
1089
+ 100% {
1090
+ opacity: 0.86;
1091
+ transform: translateY(0);
1092
+ }
1093
+ 50% {
1094
+ opacity: 1;
1095
+ transform: translateY(-1px);
1096
+ }
1097
+ }
1098
+
1099
+ @keyframes youidian-sdk-brand-loading-tagline {
1100
+ 0%,
1101
+ 100% {
1102
+ opacity: 0.64;
1103
+ }
1104
+ 50% {
1105
+ opacity: 0.92;
1106
+ }
1107
+ }
1108
+
1109
+ @media (prefers-reduced-motion: reduce) {
1110
+ .youidian-sdk-brand-loading__ring,
1111
+ .youidian-sdk-brand-loading__dot,
1112
+ .youidian-sdk-brand-loading__copy,
1113
+ .youidian-sdk-brand-loading__tagline {
1114
+ animation: none;
1115
+ opacity: 1;
1116
+ transform: none;
1117
+ }
1118
+ }
1119
+ `;
644
1120
  document.head.appendChild(style);
645
1121
  }
646
- function createLoginLoadingPanel(options) {
1122
+ function createLoginLoadingPanel() {
647
1123
  ensureLoginLoadingStyles();
648
1124
  const panel = document.createElement("div");
649
- applyLoginPanelStyle(panel);
650
- const content = createLoginPanelContent();
651
- const spinner = document.createElement("div");
652
- Object.assign(spinner.style, {
653
- width: "22px",
654
- height: "22px",
655
- borderRadius: "9999px",
656
- border: "2px solid rgba(23,23,23,0.16)",
657
- borderTopColor: "#171717",
658
- margin: "0 auto 18px",
659
- animation: "youidian-login-spin 780ms linear infinite"
1125
+ applyLoginLoadingPanelStyle(panel);
1126
+ panel.setAttribute("role", "status");
1127
+ panel.setAttribute("aria-label", "\u4F18\u6613\u70B9\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0");
1128
+ const content = document.createElement("div");
1129
+ Object.assign(content.style, {
1130
+ display: "flex",
1131
+ alignItems: "center",
1132
+ justifyContent: "center",
1133
+ gap: "12px"
660
1134
  });
661
- content.appendChild(spinner);
662
- content.appendChild(createLoginPanelTitle(options.title));
663
- content.appendChild(createLoginPanelDescription(options.description));
1135
+ content.appendChild(createBrandLoadingMark());
1136
+ content.appendChild(createBrandLoadingCopy());
664
1137
  panel.appendChild(content);
665
1138
  return panel;
666
1139
  }
667
- function createLoginRedirectingPanel(options) {
668
- return createLoginLoadingPanel(options);
1140
+ function createLoginRedirectingPanel(_options) {
1141
+ return createLoginLoadingPanel();
669
1142
  }
670
1143
  function createLoginFallbackPanel(options) {
671
1144
  const panel = document.createElement("div");
@@ -747,6 +1220,7 @@ var LoginUI = class extends HostedFrameModal {
747
1220
  this.completed = false;
748
1221
  }
749
1222
  markIframeReady() {
1223
+ if (this.iframeReady) return;
750
1224
  this.iframeReady = true;
751
1225
  if (typeof window !== "undefined" && this.iframeLoadTimer) {
752
1226
  window.clearTimeout(this.iframeLoadTimer);
@@ -765,14 +1239,11 @@ var LoginUI = class extends HostedFrameModal {
765
1239
  this.iframeFallbackPanel = null;
766
1240
  this.iframeLoadingPanel = null;
767
1241
  }
768
- showIframeLoading(container, options) {
1242
+ showIframeLoading(container) {
769
1243
  if (this.iframeLoadingPanel || this.iframeReady) {
770
1244
  return;
771
1245
  }
772
- const panel = createLoginLoadingPanel({
773
- description: options?.loadingDescription || "Please wait while the secure login page opens.",
774
- title: options?.loadingTitle || "Opening login page"
775
- });
1246
+ const panel = createLoginLoadingPanel();
776
1247
  this.iframeLoadingPanel = panel;
777
1248
  container.appendChild(panel);
778
1249
  }
@@ -860,6 +1331,20 @@ var LoginUI = class extends HostedFrameModal {
860
1331
  break;
861
1332
  case "LOGIN_RESIZE":
862
1333
  break;
1334
+ case "LOGIN_REDIRECT_REQUIRED":
1335
+ if (!isValidLoginRedirectUrl(data.authUrl)) {
1336
+ logLoginWarn("Login redirect requested with invalid authUrl");
1337
+ options?.onError?.("Login redirect URL is invalid", data);
1338
+ break;
1339
+ }
1340
+ logLoginInfo("Login redirect requested by hosted page", {
1341
+ attemptId: data.attemptId || null,
1342
+ channel: data.channel || null,
1343
+ authUrl: data.authUrl
1344
+ });
1345
+ this.close();
1346
+ window.location.assign(data.authUrl);
1347
+ break;
863
1348
  case "LOGIN_ERROR":
864
1349
  if (this.completed) {
865
1350
  break;
@@ -883,6 +1368,25 @@ var LoginUI = class extends HostedFrameModal {
883
1368
  break;
884
1369
  }
885
1370
  }
1371
+ openLoginRedirect(params, options) {
1372
+ if (typeof window === "undefined") return;
1373
+ const { callbackState, finalUrl, launchPayload } = this.buildOpenContext(
1374
+ params,
1375
+ options
1376
+ );
1377
+ logLoginInfo("Redirecting to hosted login page", {
1378
+ appId: params.appId,
1379
+ callbackState,
1380
+ callbackUrl: launchPayload.callbackUrl || null,
1381
+ hasCallbackUrl: Boolean(launchPayload.callbackUrl),
1382
+ loginUrl: finalUrl,
1383
+ autoClose: options?.autoClose ?? true,
1384
+ displayMode: "redirect",
1385
+ pageUrl: window.location.href,
1386
+ parentOrigin: launchPayload.origin || null
1387
+ });
1388
+ window.location.assign(finalUrl);
1389
+ }
886
1390
  listenForLoginCallback(callbackState, options) {
887
1391
  try {
888
1392
  this.callbackChannel = new BroadcastChannel(
@@ -959,21 +1463,28 @@ var LoginUI = class extends HostedFrameModal {
959
1463
  options?.onCancel?.();
960
1464
  options?.onClose?.();
961
1465
  },
962
- onMessage: (data, container, event) => {
1466
+ onMessage: (data, _container, event) => {
963
1467
  const isIframeEvent = event.source === this.iframe?.contentWindow;
1468
+ if (!isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE")) {
1469
+ logLoginWarn(
1470
+ "Login lifecycle event ignored: event.source does not match iframe contentWindow",
1471
+ {
1472
+ type: data.type,
1473
+ eventOrigin: event.origin,
1474
+ hasIframe: Boolean(this.iframe),
1475
+ hasIframeContentWindow: Boolean(this.iframe?.contentWindow)
1476
+ }
1477
+ );
1478
+ }
964
1479
  if (isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE") || data.type === "LOGIN_SUCCESS" || data.type === "LOGIN_ERROR") {
965
1480
  this.markIframeReady();
966
1481
  }
967
- if (data.type === "LOGIN_RESIZE" && data.height) {
968
- const maxHeight = window.innerHeight * 0.94;
969
- container.style.height = `${Math.min(data.height, maxHeight)}px`;
970
- }
971
1482
  this.handleLoginEvent(data, options);
972
1483
  },
973
1484
  width: "min(520px, 100%)"
974
1485
  });
975
1486
  if (hostedFrame) {
976
- this.showIframeLoading(hostedFrame.container, options);
1487
+ this.showIframeLoading(hostedFrame.container);
977
1488
  this.startIframeWatchdog({
978
1489
  container: hostedFrame.container,
979
1490
  finalUrl,
@@ -1077,11 +1588,19 @@ var LoginUI = class extends HostedFrameModal {
1077
1588
  }
1078
1589
  openLogin(params, options) {
1079
1590
  if (typeof window === "undefined") return;
1080
- const displayMode = options?.displayMode ?? "modal";
1591
+ const displayMode = options?.displayMode ?? "auto";
1592
+ if (displayMode === "redirect") {
1593
+ this.openLoginRedirect(params, options);
1594
+ return;
1595
+ }
1081
1596
  if (displayMode === "popup") {
1082
1597
  this.openLoginPopup(params, options);
1083
1598
  return;
1084
1599
  }
1600
+ if (displayMode === "auto" && detectLoginEnvironment().shouldUseRedirectLogin) {
1601
+ this.openLoginRedirect(params, options);
1602
+ return;
1603
+ }
1085
1604
  this.openLoginModal(params, options);
1086
1605
  }
1087
1606
  };
@@ -1091,6 +1610,406 @@ function createLoginUI() {
1091
1610
  handleLoginCallbackIfPresent({ autoClose: false });
1092
1611
 
1093
1612
  // src/client.ts
1613
+ var DEFAULT_PAYMENT_IFRAME_LOAD_TIMEOUT_MS = 8e3;
1614
+ var PAYMENT_UI_LOG_PREFIX = "[Youidian PaymentUI]";
1615
+ function hexToRgb(value) {
1616
+ const normalized = value.trim().replace(/^#/, "");
1617
+ if (!/^[0-9a-f]{3}([0-9a-f]{3})?$/i.test(normalized)) return null;
1618
+ const hex = normalized.length === 3 ? normalized.split("").map((char) => `${char}${char}`).join("") : normalized;
1619
+ const number = Number.parseInt(hex, 16);
1620
+ return {
1621
+ b: number & 255,
1622
+ g: number >> 8 & 255,
1623
+ r: number >> 16 & 255
1624
+ };
1625
+ }
1626
+ function alphaColor(value, alpha) {
1627
+ const rgb = hexToRgb(value);
1628
+ if (!rgb)
1629
+ return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`;
1630
+ return `rgba(${rgb.r},${rgb.g},${rgb.b},${alpha})`;
1631
+ }
1632
+ function resolvePaymentTheme(options) {
1633
+ const primary = options?.theme?.primary || "#16a34a";
1634
+ const secondary = options?.theme?.secondary || "color-mix(in srgb, #16a34a 78%, white)";
1635
+ const foreground = options?.theme?.foreground || "#0f172a";
1636
+ const muted = options?.theme?.muted || "#64748b";
1637
+ const surface = options?.theme?.surface || "#ffffff";
1638
+ return {
1639
+ foreground,
1640
+ muted,
1641
+ primary,
1642
+ primary10: alphaColor(primary, 0.1),
1643
+ primary14: alphaColor(primary, 0.14),
1644
+ primary20: alphaColor(primary, 0.2),
1645
+ primary28: alphaColor(primary, 0.28),
1646
+ primary36: alphaColor(primary, 0.36),
1647
+ primary64: alphaColor(primary, 0.64),
1648
+ secondary,
1649
+ surface
1650
+ };
1651
+ }
1652
+ function logPaymentInfo(message, details) {
1653
+ if (typeof console === "undefined") return;
1654
+ console.info(PAYMENT_UI_LOG_PREFIX, message, details || {});
1655
+ }
1656
+ function logPaymentWarn(message, details) {
1657
+ if (typeof console === "undefined") return;
1658
+ console.warn(PAYMENT_UI_LOG_PREFIX, message, details || {});
1659
+ }
1660
+ function getPaymentIframeLoadTimeoutMs(options) {
1661
+ const timeout = options?.iframeLoadTimeoutMs;
1662
+ if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
1663
+ return DEFAULT_PAYMENT_IFRAME_LOAD_TIMEOUT_MS;
1664
+ }
1665
+ return Math.max(0, timeout);
1666
+ }
1667
+ function isChinesePaymentLocale(locale) {
1668
+ return locale?.toLowerCase().startsWith("zh") || false;
1669
+ }
1670
+ function getPaymentFallbackText(options) {
1671
+ const isChinese = isChinesePaymentLocale(options?.locale);
1672
+ return {
1673
+ buttonText: options?.fallbackButtonText || (isChinese ? "\u5728\u65B0\u9875\u9762\u4E2D\u6253\u5F00" : "Open in new page"),
1674
+ 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.",
1675
+ retryText: options?.fallbackRetryText || (isChinese ? "\u91CD\u65B0\u5C1D\u8BD5" : "Try again"),
1676
+ title: isChinese ? "\u652F\u4ED8\u9875\u9762\u6253\u5F00\u8F83\u6162" : "Payment page is taking longer"
1677
+ };
1678
+ }
1679
+ function getPaymentBrandText(options) {
1680
+ const isChinese = isChinesePaymentLocale(options?.locale);
1681
+ return {
1682
+ language: isChinese ? "zh" : "en",
1683
+ name: isChinese ? "\u4F18\u6613\u70B9" : "Youidian",
1684
+ subtitle: isChinese ? "\u5F00\u53D1\u8005\u670D\u52A1\u5E73\u53F0" : "Developer Service Platform"
1685
+ };
1686
+ }
1687
+ function getPaymentBrandLoadingText(options) {
1688
+ const isChinese = isChinesePaymentLocale(options?.locale);
1689
+ return {
1690
+ language: isChinese ? "zh" : "en",
1691
+ name: isChinese ? "\u4F18\u6613\u70B9" : "Youidian",
1692
+ subtitle: isChinese ? "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0" : "Developer Service"
1693
+ };
1694
+ }
1695
+ function getPaymentLoadingText(options) {
1696
+ const isChinese = isChinesePaymentLocale(options?.locale);
1697
+ return options?.loadingTitle || options?.loadingDescription || (isChinese ? "\u52A0\u8F7D\u4E2D..." : "Loading...");
1698
+ }
1699
+ function applyPaymentPanelStyle(panel, theme) {
1700
+ Object.assign(panel.style, {
1701
+ position: "absolute",
1702
+ inset: "0",
1703
+ display: "flex",
1704
+ alignItems: "center",
1705
+ justifyContent: "center",
1706
+ borderRadius: "28px",
1707
+ background: `linear-gradient(180deg, ${theme.surface} 0%, rgba(248,250,252,0.98) 100%)`,
1708
+ color: theme.foreground,
1709
+ padding: "28px 24px",
1710
+ textAlign: "center",
1711
+ zIndex: "1",
1712
+ boxShadow: "none",
1713
+ backdropFilter: "blur(16px)"
1714
+ });
1715
+ }
1716
+ function createPaymentIcon(type) {
1717
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1718
+ svg.setAttribute("viewBox", "0 0 24 24");
1719
+ svg.setAttribute("fill", "none");
1720
+ svg.setAttribute("aria-hidden", "true");
1721
+ Object.assign(svg.style, {
1722
+ display: "block",
1723
+ flex: "0 0 auto"
1724
+ });
1725
+ const paths = type === "external" ? [
1726
+ "M7 17L17 7",
1727
+ "M10 7H17V14",
1728
+ "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"
1729
+ ] : type === "refresh" ? [
1730
+ "M20 6V11H15",
1731
+ "M4 18V13H9",
1732
+ "M18.2 9A7 7 0 0 0 6.2 6.6L4 9",
1733
+ "M5.8 15A7 7 0 0 0 17.8 17.4L20 15"
1734
+ ] : ["M12 7V12L15 14", "M21 12A9 9 0 1 1 3 12A9 9 0 0 1 21 12"];
1735
+ for (const value of paths) {
1736
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
1737
+ path.setAttribute("d", value);
1738
+ path.setAttribute("stroke", "currentColor");
1739
+ path.setAttribute("stroke-width", "2");
1740
+ path.setAttribute("stroke-linecap", "round");
1741
+ path.setAttribute("stroke-linejoin", "round");
1742
+ svg.appendChild(path);
1743
+ }
1744
+ return svg;
1745
+ }
1746
+ function createPaymentFallbackArtwork(theme) {
1747
+ const artwork = document.createElement("div");
1748
+ Object.assign(artwork.style, {
1749
+ height: "164px",
1750
+ margin: "0 auto 34px",
1751
+ position: "relative",
1752
+ width: "184px"
1753
+ });
1754
+ const wash = document.createElement("div");
1755
+ Object.assign(wash.style, {
1756
+ position: "absolute",
1757
+ left: "16px",
1758
+ top: "20px",
1759
+ width: "152px",
1760
+ height: "126px",
1761
+ borderRadius: "48% 52% 43% 57% / 54% 42% 58% 46%",
1762
+ background: `radial-gradient(circle at 48% 42%, ${theme.primary20}, ${theme.primary10} 52%, transparent 76%)`,
1763
+ filter: "blur(1px)",
1764
+ transform: "rotate(-10deg)"
1765
+ });
1766
+ const blob = document.createElement("div");
1767
+ Object.assign(blob.style, {
1768
+ position: "absolute",
1769
+ left: "26px",
1770
+ top: "28px",
1771
+ width: "132px",
1772
+ height: "116px",
1773
+ borderRadius: "42% 58% 53% 47% / 45% 39% 61% 55%",
1774
+ background: `linear-gradient(150deg, ${theme.primary14}, ${theme.primary10})`,
1775
+ boxShadow: `0 30px 64px ${theme.primary14}`,
1776
+ transform: "rotate(8deg)"
1777
+ });
1778
+ const tile = document.createElement("div");
1779
+ Object.assign(tile.style, {
1780
+ position: "absolute",
1781
+ left: "50%",
1782
+ top: "50%",
1783
+ transform: "translate(-50%, -50%) rotate(-4deg)",
1784
+ width: "112px",
1785
+ height: "106px",
1786
+ borderRadius: "38% 62% 50% 50% / 44% 43% 57% 56%",
1787
+ background: `linear-gradient(145deg, ${theme.primary}, ${theme.secondary})`,
1788
+ border: `1px solid ${theme.primary36}`,
1789
+ color: "#ffffff",
1790
+ display: "inline-flex",
1791
+ alignItems: "center",
1792
+ justifyContent: "center",
1793
+ boxShadow: `0 26px 54px ${theme.primary28}`
1794
+ });
1795
+ const icon = createPaymentIcon("slow");
1796
+ Object.assign(icon.style, {
1797
+ width: "52px",
1798
+ height: "52px",
1799
+ transform: "rotate(4deg)"
1800
+ });
1801
+ tile.appendChild(icon);
1802
+ artwork.appendChild(wash);
1803
+ artwork.appendChild(blob);
1804
+ artwork.appendChild(tile);
1805
+ return artwork;
1806
+ }
1807
+ function createPaymentBrandLoading(options, theme) {
1808
+ const brand = getPaymentBrandLoadingText(options);
1809
+ const wrapper = document.createElement("div");
1810
+ Object.assign(wrapper.style, {
1811
+ display: "flex",
1812
+ flexDirection: "column",
1813
+ alignItems: "center",
1814
+ justifyContent: "center",
1815
+ margin: "0 auto 16px"
1816
+ });
1817
+ wrapper.appendChild(
1818
+ createSdkBrandLogo({
1819
+ animated: true,
1820
+ brand,
1821
+ brandWidth: 292,
1822
+ copyWidth: 176,
1823
+ gap: 6,
1824
+ markMarginRight: -10,
1825
+ markSize: 112,
1826
+ nameSize: 39,
1827
+ subtitleMarginTop: 8,
1828
+ subtitleSize: 14,
1829
+ subtitleWeight: "700",
1830
+ theme,
1831
+ variant: "brand"
1832
+ })
1833
+ );
1834
+ return wrapper;
1835
+ }
1836
+ function createPaymentPlatformLogo(options, theme) {
1837
+ const brand = getPaymentBrandText(options);
1838
+ const wrapper = document.createElement("div");
1839
+ Object.assign(wrapper.style, {
1840
+ display: "flex",
1841
+ alignItems: "center",
1842
+ justifyContent: "center",
1843
+ margin: "0 auto 10px"
1844
+ });
1845
+ wrapper.appendChild(
1846
+ createSdkBrandLogo({
1847
+ brand,
1848
+ markSize: 48,
1849
+ nameSize: brand.language === "zh" ? 22 : 22,
1850
+ subtitleSize: brand.language === "zh" ? 9 : 9,
1851
+ theme
1852
+ })
1853
+ );
1854
+ return wrapper;
1855
+ }
1856
+ function createPaymentPanelContent() {
1857
+ const content = document.createElement("div");
1858
+ Object.assign(content.style, {
1859
+ display: "flex",
1860
+ flexDirection: "column",
1861
+ justifyContent: "center",
1862
+ maxWidth: "364px",
1863
+ minHeight: "100%",
1864
+ width: "100%"
1865
+ });
1866
+ return content;
1867
+ }
1868
+ function createPaymentPanelTitle(value, theme) {
1869
+ const title = document.createElement("div");
1870
+ title.textContent = value;
1871
+ Object.assign(title.style, {
1872
+ color: theme?.foreground || "#0f172a",
1873
+ fontSize: "24px",
1874
+ fontWeight: "900",
1875
+ lineHeight: "1.22",
1876
+ marginBottom: "16px"
1877
+ });
1878
+ return title;
1879
+ }
1880
+ function createPaymentPanelDescription(value, theme) {
1881
+ const description = document.createElement("div");
1882
+ description.textContent = value;
1883
+ Object.assign(description.style, {
1884
+ color: theme?.muted || "#64748b",
1885
+ fontSize: "14px",
1886
+ fontWeight: "600",
1887
+ lineHeight: "1.62",
1888
+ margin: "0 auto",
1889
+ maxWidth: "320px"
1890
+ });
1891
+ return description;
1892
+ }
1893
+ function createPaymentLoadingDescription(value, theme) {
1894
+ const description = document.createElement("div");
1895
+ description.textContent = value;
1896
+ Object.assign(description.style, {
1897
+ color: theme?.muted || "#64748b",
1898
+ fontSize: "14px",
1899
+ fontWeight: "500",
1900
+ lineHeight: "1.25",
1901
+ margin: "12px auto 0",
1902
+ maxWidth: "320px"
1903
+ });
1904
+ return description;
1905
+ }
1906
+ function createPaymentFallbackMessage(options) {
1907
+ const message = document.createElement("div");
1908
+ Object.assign(message.style, {
1909
+ margin: "0 auto",
1910
+ textAlign: "center"
1911
+ });
1912
+ message.appendChild(createPaymentFallbackArtwork(options.theme));
1913
+ message.appendChild(createPaymentPanelTitle(options.title, options.theme));
1914
+ message.appendChild(
1915
+ createPaymentPanelDescription(options.description, options.theme)
1916
+ );
1917
+ return message;
1918
+ }
1919
+ function createPaymentLoadingPanel(options) {
1920
+ const panel = document.createElement("div");
1921
+ const theme = resolvePaymentTheme(options);
1922
+ applyPaymentPanelStyle(panel, theme);
1923
+ const content = createPaymentPanelContent();
1924
+ content.appendChild(createPaymentBrandLoading(options, theme));
1925
+ content.appendChild(
1926
+ createPaymentLoadingDescription(getPaymentLoadingText(options), theme)
1927
+ );
1928
+ panel.appendChild(content);
1929
+ return panel;
1930
+ }
1931
+ function createPaymentButton(value, options) {
1932
+ const primary = options?.primary || false;
1933
+ const theme = options?.theme || resolvePaymentTheme();
1934
+ const button = document.createElement("button");
1935
+ button.type = "button";
1936
+ Object.assign(button.style, {
1937
+ width: "100%",
1938
+ height: "50px",
1939
+ borderRadius: "14px",
1940
+ border: primary ? `1px solid ${theme.primary}` : `1px solid ${theme.primary64}`,
1941
+ background: primary ? `linear-gradient(135deg, ${theme.primary}, ${theme.secondary})` : "#ffffff",
1942
+ color: primary ? "#ffffff" : theme.primary,
1943
+ cursor: "pointer",
1944
+ display: "inline-flex",
1945
+ alignItems: "center",
1946
+ justifyContent: "center",
1947
+ gap: "10px",
1948
+ fontSize: "15px",
1949
+ fontWeight: "800",
1950
+ marginTop: primary ? "12px" : "0",
1951
+ boxShadow: primary ? `0 14px 28px ${theme.primary20}` : "none"
1952
+ });
1953
+ if (options?.icon) {
1954
+ const icon = createPaymentIcon(options.icon);
1955
+ Object.assign(icon.style, {
1956
+ width: "17px",
1957
+ height: "17px"
1958
+ });
1959
+ button.appendChild(icon);
1960
+ }
1961
+ const label = document.createElement("span");
1962
+ label.textContent = value;
1963
+ button.appendChild(label);
1964
+ return button;
1965
+ }
1966
+ function createPaymentFallbackPanel(options) {
1967
+ const panel = document.createElement("div");
1968
+ const theme = resolvePaymentTheme(options.paymentOptions);
1969
+ applyPaymentPanelStyle(panel, theme);
1970
+ const content = createPaymentPanelContent();
1971
+ Object.assign(content.style, {
1972
+ justifyContent: "space-between"
1973
+ });
1974
+ const main = document.createElement("div");
1975
+ Object.assign(main.style, {
1976
+ display: "flex",
1977
+ flexDirection: "column",
1978
+ alignItems: "center",
1979
+ marginTop: "38px",
1980
+ width: "100%"
1981
+ });
1982
+ main.appendChild(
1983
+ createPaymentFallbackMessage({
1984
+ description: options.description,
1985
+ theme,
1986
+ title: options.title
1987
+ })
1988
+ );
1989
+ const actions = document.createElement("div");
1990
+ Object.assign(actions.style, {
1991
+ marginTop: "46px",
1992
+ width: "100%"
1993
+ });
1994
+ const retryButton = createPaymentButton(options.retryText, {
1995
+ icon: "refresh",
1996
+ theme
1997
+ });
1998
+ retryButton.onclick = options.onRetry;
1999
+ actions.appendChild(retryButton);
2000
+ const openButton = createPaymentButton(options.buttonText, {
2001
+ icon: "external",
2002
+ primary: true,
2003
+ theme
2004
+ });
2005
+ openButton.onclick = options.onOpen;
2006
+ actions.appendChild(openButton);
2007
+ main.appendChild(actions);
2008
+ content.appendChild(main);
2009
+ content.appendChild(createPaymentPlatformLogo(options.paymentOptions, theme));
2010
+ panel.appendChild(content);
2011
+ return panel;
2012
+ }
1094
2013
  var PaymentUI = class extends HostedFrameModal {
1095
2014
  constructor() {
1096
2015
  super(...arguments);
@@ -1099,6 +2018,127 @@ var PaymentUI = class extends HostedFrameModal {
1099
2018
  __publicField(this, "activeOrderId", null);
1100
2019
  __publicField(this, "paymentCompleted", false);
1101
2020
  __publicField(this, "cancelRequestedOrderId", null);
2021
+ __publicField(this, "iframeFallbackPanel", null);
2022
+ __publicField(this, "iframeLoadingPanel", null);
2023
+ __publicField(this, "iframeLoadTimer", null);
2024
+ __publicField(this, "iframeReady", false);
2025
+ }
2026
+ cleanupPaymentFrameState() {
2027
+ if (typeof window !== "undefined" && this.iframeLoadTimer) {
2028
+ window.clearTimeout(this.iframeLoadTimer);
2029
+ }
2030
+ if (this.iframeFallbackPanel?.parentNode) {
2031
+ this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
2032
+ }
2033
+ if (this.iframeLoadingPanel?.parentNode) {
2034
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
2035
+ }
2036
+ this.iframeFallbackPanel = null;
2037
+ this.iframeLoadingPanel = null;
2038
+ this.iframeLoadTimer = null;
2039
+ this.iframeReady = false;
2040
+ }
2041
+ markPaymentIframeReady() {
2042
+ if (this.iframeReady) return;
2043
+ this.iframeReady = true;
2044
+ if (typeof window !== "undefined" && this.iframeLoadTimer) {
2045
+ window.clearTimeout(this.iframeLoadTimer);
2046
+ this.iframeLoadTimer = null;
2047
+ }
2048
+ if (this.iframeFallbackPanel?.parentNode) {
2049
+ this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
2050
+ }
2051
+ if (this.iframeLoadingPanel?.parentNode) {
2052
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
2053
+ }
2054
+ this.iframeFallbackPanel = null;
2055
+ this.iframeLoadingPanel = null;
2056
+ }
2057
+ showPaymentIframeLoading(container, options) {
2058
+ if (this.iframeLoadingPanel || this.iframeReady) return;
2059
+ const panel = createPaymentLoadingPanel(options);
2060
+ this.iframeLoadingPanel = panel;
2061
+ container.appendChild(panel);
2062
+ }
2063
+ openFallbackPaymentPage(finalUrl, options) {
2064
+ if (typeof window === "undefined") return;
2065
+ const popup = window.open(
2066
+ finalUrl,
2067
+ "youidian-payment-checkout",
2068
+ "popup=yes,width=480,height=760,resizable=yes,scrollbars=yes"
2069
+ );
2070
+ if (popup) {
2071
+ logPaymentInfo("Opened hosted checkout fallback page", {
2072
+ checkoutUrl: finalUrl
2073
+ });
2074
+ options?.onFallbackOpen?.(finalUrl);
2075
+ try {
2076
+ popup.focus();
2077
+ } catch {
2078
+ }
2079
+ return;
2080
+ }
2081
+ logPaymentWarn("Hosted checkout fallback popup was blocked", {
2082
+ checkoutUrl: finalUrl
2083
+ });
2084
+ options?.onFallbackBlocked?.(finalUrl);
2085
+ window.location.assign(finalUrl);
2086
+ }
2087
+ showPaymentIframeFallback(options) {
2088
+ if (this.iframeReady || this.iframeFallbackPanel) return;
2089
+ const { container, finalUrl, paymentOptions } = options;
2090
+ logPaymentWarn("Hosted checkout iframe did not report ready in time", {
2091
+ checkoutUrl: finalUrl
2092
+ });
2093
+ if (this.iframeLoadingPanel?.parentNode) {
2094
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
2095
+ }
2096
+ this.iframeLoadingPanel = null;
2097
+ const fallbackText = getPaymentFallbackText(paymentOptions);
2098
+ const panel = createPaymentFallbackPanel({
2099
+ buttonText: fallbackText.buttonText,
2100
+ description: fallbackText.description,
2101
+ onOpen: () => {
2102
+ this.openFallbackPaymentPage(finalUrl, paymentOptions);
2103
+ },
2104
+ onRetry: () => {
2105
+ this.iframeFallbackPanel = null;
2106
+ if (panel.parentNode) {
2107
+ panel.parentNode.removeChild(panel);
2108
+ }
2109
+ this.iframeReady = false;
2110
+ this.showPaymentIframeLoading(container, paymentOptions);
2111
+ if (this.iframe) {
2112
+ this.iframe.src = finalUrl;
2113
+ }
2114
+ this.startPaymentIframeWatchdog({
2115
+ container,
2116
+ finalUrl,
2117
+ paymentOptions
2118
+ });
2119
+ },
2120
+ paymentOptions,
2121
+ retryText: fallbackText.retryText,
2122
+ title: fallbackText.title
2123
+ });
2124
+ this.iframeFallbackPanel = panel;
2125
+ container.appendChild(panel);
2126
+ paymentOptions?.onFallbackVisible?.();
2127
+ }
2128
+ startPaymentIframeWatchdog(options) {
2129
+ if (typeof window === "undefined") return;
2130
+ if (this.iframeLoadTimer) {
2131
+ window.clearTimeout(this.iframeLoadTimer);
2132
+ }
2133
+ const timeoutMs = getPaymentIframeLoadTimeoutMs(options.paymentOptions);
2134
+ if (timeoutMs === 0) {
2135
+ this.showPaymentIframeFallback(options);
2136
+ return;
2137
+ }
2138
+ this.iframeLoadTimer = window.setTimeout(() => {
2139
+ this.iframeLoadTimer = null;
2140
+ this.showPaymentIframeFallback(options);
2141
+ }, timeoutMs);
1102
2142
  }
1103
2143
  cancelHostedOrder(reason = "user_cancel", orderId) {
1104
2144
  const targetOrderId = orderId || this.activeOrderId;
@@ -1141,6 +2181,7 @@ var PaymentUI = class extends HostedFrameModal {
1141
2181
  this.activeOrderId = null;
1142
2182
  this.paymentCompleted = false;
1143
2183
  this.cancelRequestedOrderId = null;
2184
+ this.cleanupPaymentFrameState();
1144
2185
  if (typeof urlOrParams === "string") {
1145
2186
  checkoutUrl = urlOrParams;
1146
2187
  try {
@@ -1209,14 +2250,21 @@ var PaymentUI = class extends HostedFrameModal {
1209
2250
  checkoutUrl,
1210
2251
  getAutoResolvedLocale(options?.locale)
1211
2252
  );
1212
- this.openHostedFrame(finalUrl, {
2253
+ const hostedFrame = this.openHostedFrame(finalUrl, {
1213
2254
  allowedOrigin: options?.allowedOrigin,
2255
+ height: "min(760px, calc(100vh - 32px))",
1214
2256
  onCloseButton: () => {
1215
2257
  this.cancelHostedOrder("modal_close");
1216
2258
  options?.onCancel?.(this.activeOrderId || void 0);
1217
2259
  },
1218
- onMessage: (data, container) => {
2260
+ onMessage: (data, container, event) => {
2261
+ const isIframeMessage = event.source === this.iframe?.contentWindow;
2262
+ if (isIframeMessage && (data.type === "PAYMENT_READY" || data.type === "PAYMENT_RESIZE" || data.type === "PAYMENT_STARTED")) {
2263
+ this.markPaymentIframeReady();
2264
+ }
1219
2265
  switch (data.type) {
2266
+ case "PAYMENT_READY":
2267
+ break;
1220
2268
  case "PAYMENT_STARTED":
1221
2269
  if (data.orderId) {
1222
2270
  this.activeOrderId = data.orderId;
@@ -1231,11 +2279,6 @@ var PaymentUI = class extends HostedFrameModal {
1231
2279
  options?.onCancel?.(data.orderId);
1232
2280
  break;
1233
2281
  case "PAYMENT_RESIZE":
1234
- if (data.height) {
1235
- const maxHeight = window.innerHeight * 0.9;
1236
- const newHeight = Math.min(data.height, maxHeight);
1237
- container.style.height = `${newHeight}px`;
1238
- }
1239
2282
  break;
1240
2283
  case "PAYMENT_CLOSE":
1241
2284
  this.cancelHostedOrder("payment_close", data.orderId);
@@ -1243,8 +2286,21 @@ var PaymentUI = class extends HostedFrameModal {
1243
2286
  options?.onClose?.();
1244
2287
  break;
1245
2288
  }
1246
- }
2289
+ },
2290
+ width: "min(450px, 100%)"
1247
2291
  });
2292
+ if (hostedFrame) {
2293
+ this.showPaymentIframeLoading(hostedFrame.container, options);
2294
+ this.startPaymentIframeWatchdog({
2295
+ container: hostedFrame.container,
2296
+ finalUrl,
2297
+ paymentOptions: options
2298
+ });
2299
+ }
2300
+ }
2301
+ close() {
2302
+ super.close();
2303
+ this.cleanupPaymentFrameState();
1248
2304
  }
1249
2305
  /**
1250
2306
  * Poll order status from integrator's API endpoint
@@ -1290,15 +2346,41 @@ var PaymentUI = class extends HostedFrameModal {
1290
2346
  });
1291
2347
  }
1292
2348
  };
2349
+ var MessageUI = class {
2350
+ openWechatBinding(options) {
2351
+ const bindingUrl = options.bindingUrl?.trim();
2352
+ if (!bindingUrl) {
2353
+ throw new Error("bindingUrl is required");
2354
+ }
2355
+ if (options.displayMode === "popup") {
2356
+ const popup = window.open(
2357
+ bindingUrl,
2358
+ options.popupName || "youidian-wechat-message-binding",
2359
+ "width=480,height=720,noopener,noreferrer"
2360
+ );
2361
+ if (!popup) {
2362
+ window.location.href = bindingUrl;
2363
+ }
2364
+ return;
2365
+ }
2366
+ window.location.href = bindingUrl;
2367
+ }
2368
+ };
2369
+ function createMessageUI() {
2370
+ return new MessageUI();
2371
+ }
1293
2372
  function createPaymentUI() {
1294
2373
  return new PaymentUI();
1295
2374
  }
1296
2375
  // Annotate the CommonJS export names for ESM import in node:
1297
2376
  0 && (module.exports = {
1298
2377
  LoginUI,
2378
+ MessageUI,
1299
2379
  PaymentUI,
1300
2380
  createLoginUI,
2381
+ createMessageUI,
1301
2382
  createPaymentUI,
2383
+ detectLoginEnvironment,
1302
2384
  handleLoginCallbackIfPresent
1303
2385
  });
1304
2386
  //# sourceMappingURL=client.cjs.map