@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 +1123 -41
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +74 -4
- package/dist/client.d.ts +74 -4
- package/dist/client.js +1120 -41
- package/dist/client.js.map +1 -1
- package/dist/feedback.cjs +682 -0
- package/dist/feedback.cjs.map +1 -0
- package/dist/feedback.d.cts +38 -0
- package/dist/feedback.d.ts +38 -0
- package/dist/feedback.js +656 -0
- package/dist/feedback.js.map +1 -0
- package/dist/index.cjs +1866 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1861 -41
- package/dist/index.js.map +1 -1
- package/dist/{login-DerOcXcH.d.cts → login-Dqemys65.d.cts} +8 -4
- package/dist/{login-DerOcXcH.d.ts → login-Dqemys65.d.ts} +8 -4
- package/dist/login.cjs +334 -33
- package/dist/login.cjs.map +1 -1
- package/dist/login.d.cts +1 -1
- package/dist/login.d.ts +1 -1
- package/dist/login.js +334 -33
- package/dist/login.js.map +1 -1
- package/dist/server.cjs +87 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +146 -1
- package/dist/server.d.ts +146 -1
- package/dist/server.js +87 -0
- package/dist/server.js.map +1 -1
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -33,16 +33,236 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
33
33
|
var src_exports = {};
|
|
34
34
|
__export(src_exports, {
|
|
35
35
|
LoginUI: () => LoginUI,
|
|
36
|
+
MessageUI: () => MessageUI,
|
|
36
37
|
PaymentClient: () => PaymentClient,
|
|
37
38
|
PaymentUI: () => PaymentUI,
|
|
39
|
+
createFeedbackWidget: () => createFeedbackWidget,
|
|
38
40
|
createLoginUI: () => createLoginUI,
|
|
41
|
+
createMessageUI: () => createMessageUI,
|
|
39
42
|
createPaymentUI: () => createPaymentUI,
|
|
43
|
+
detectLoginEnvironment: () => detectLoginEnvironment,
|
|
40
44
|
getCustomAmountRechargeRule: () => getCustomAmountRechargeRule,
|
|
41
45
|
handleLoginCallbackIfPresent: () => handleLoginCallbackIfPresent,
|
|
46
|
+
mountFeedbackWidget: () => mountFeedbackWidget,
|
|
42
47
|
validateCustomAmountRecharge: () => validateCustomAmountRecharge
|
|
43
48
|
});
|
|
44
49
|
module.exports = __toCommonJS(src_exports);
|
|
45
50
|
|
|
51
|
+
// src/brand-logo.ts
|
|
52
|
+
function ensureSdkBrandLogoStyles() {
|
|
53
|
+
if (document.getElementById?.("youidian-sdk-brand-logo-style")) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const style = document.createElement("style");
|
|
57
|
+
style.id = "youidian-sdk-brand-logo-style";
|
|
58
|
+
style.textContent = `
|
|
59
|
+
.youidian-sdk-brand-logo__ring,
|
|
60
|
+
.youidian-sdk-brand-logo__dot,
|
|
61
|
+
.youidian-sdk-brand-logo__copy,
|
|
62
|
+
.youidian-sdk-brand-logo__tagline {
|
|
63
|
+
transform-box: fill-box;
|
|
64
|
+
transform-origin: center;
|
|
65
|
+
will-change: opacity, transform;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__ring {
|
|
69
|
+
animation: youidian-sdk-brand-logo-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__dot {
|
|
73
|
+
animation: youidian-sdk-brand-logo-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__copy {
|
|
77
|
+
animation: youidian-sdk-brand-logo-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__tagline {
|
|
81
|
+
animation: youidian-sdk-brand-logo-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@keyframes youidian-sdk-brand-logo-ring {
|
|
85
|
+
0%,
|
|
86
|
+
100% {
|
|
87
|
+
opacity: 0.72;
|
|
88
|
+
transform: rotate(0deg) scale(0.98);
|
|
89
|
+
}
|
|
90
|
+
50% {
|
|
91
|
+
opacity: 1;
|
|
92
|
+
transform: rotate(8deg) scale(1.02);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@keyframes youidian-sdk-brand-logo-dot {
|
|
97
|
+
0%,
|
|
98
|
+
100% {
|
|
99
|
+
opacity: 0.58;
|
|
100
|
+
transform: scale(0.9);
|
|
101
|
+
}
|
|
102
|
+
50% {
|
|
103
|
+
opacity: 1;
|
|
104
|
+
transform: scale(1.08);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@keyframes youidian-sdk-brand-logo-copy {
|
|
109
|
+
0%,
|
|
110
|
+
100% {
|
|
111
|
+
opacity: 0.86;
|
|
112
|
+
transform: translateY(0);
|
|
113
|
+
}
|
|
114
|
+
50% {
|
|
115
|
+
opacity: 1;
|
|
116
|
+
transform: translateY(-1px);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@keyframes youidian-sdk-brand-logo-tagline {
|
|
121
|
+
0%,
|
|
122
|
+
100% {
|
|
123
|
+
opacity: 0.64;
|
|
124
|
+
}
|
|
125
|
+
50% {
|
|
126
|
+
opacity: 0.92;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@media (prefers-reduced-motion: reduce) {
|
|
131
|
+
.youidian-sdk-brand-logo__ring,
|
|
132
|
+
.youidian-sdk-brand-logo__dot,
|
|
133
|
+
.youidian-sdk-brand-logo__copy,
|
|
134
|
+
.youidian-sdk-brand-logo__tagline {
|
|
135
|
+
animation: none;
|
|
136
|
+
opacity: 1;
|
|
137
|
+
transform: none;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
document.head?.appendChild(style);
|
|
142
|
+
}
|
|
143
|
+
function createSdkBrandLogoMark({
|
|
144
|
+
animated,
|
|
145
|
+
inverted,
|
|
146
|
+
size,
|
|
147
|
+
theme,
|
|
148
|
+
variant = "brand"
|
|
149
|
+
}) {
|
|
150
|
+
ensureSdkBrandLogoStyles();
|
|
151
|
+
const mark = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
152
|
+
mark.setAttribute("viewBox", "0 0 512 512");
|
|
153
|
+
mark.setAttribute("fill", "none");
|
|
154
|
+
mark.setAttribute("aria-hidden", "true");
|
|
155
|
+
Object.assign(mark.style, {
|
|
156
|
+
width: `${size}px`,
|
|
157
|
+
height: `${size}px`,
|
|
158
|
+
color: inverted ? "#ffffff" : theme.primary,
|
|
159
|
+
display: "block",
|
|
160
|
+
flex: "0 0 auto",
|
|
161
|
+
overflow: "visible"
|
|
162
|
+
});
|
|
163
|
+
const logoGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
164
|
+
logoGroup.setAttribute(
|
|
165
|
+
"transform",
|
|
166
|
+
variant === "mark" ? "matrix(0.74 0 0 0.74 -124 -114)" : "matrix(0.58 0 0 0.58 -40 -31)"
|
|
167
|
+
);
|
|
168
|
+
const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
169
|
+
ring.setAttribute("class", animated ? "youidian-sdk-brand-logo__ring" : "");
|
|
170
|
+
ring.setAttribute("fill", inverted ? "currentColor" : theme.primary);
|
|
171
|
+
ring.setAttribute(
|
|
172
|
+
"d",
|
|
173
|
+
"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"
|
|
174
|
+
);
|
|
175
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
176
|
+
dot.setAttribute("class", animated ? "youidian-sdk-brand-logo__dot" : "");
|
|
177
|
+
dot.setAttribute("fill", inverted ? "currentColor" : theme.secondary);
|
|
178
|
+
dot.setAttribute("cx", "714.5");
|
|
179
|
+
dot.setAttribute("cy", "490.5");
|
|
180
|
+
dot.setAttribute("r", "68");
|
|
181
|
+
dot.setAttribute("opacity", inverted ? "0.78" : "1");
|
|
182
|
+
logoGroup.appendChild(ring);
|
|
183
|
+
logoGroup.appendChild(dot);
|
|
184
|
+
mark.appendChild(logoGroup);
|
|
185
|
+
return mark;
|
|
186
|
+
}
|
|
187
|
+
function createSdkBrandLogo({
|
|
188
|
+
animated,
|
|
189
|
+
brand,
|
|
190
|
+
brandWidth,
|
|
191
|
+
copyWidth,
|
|
192
|
+
gap = 10,
|
|
193
|
+
markMarginRight,
|
|
194
|
+
markSize,
|
|
195
|
+
nameSize,
|
|
196
|
+
subtitleMarginTop = 6,
|
|
197
|
+
subtitleSize,
|
|
198
|
+
subtitleWeight = "800",
|
|
199
|
+
theme,
|
|
200
|
+
variant = "brand"
|
|
201
|
+
}) {
|
|
202
|
+
ensureSdkBrandLogoStyles();
|
|
203
|
+
const wrapper = document.createElement("div");
|
|
204
|
+
wrapper.className = "youidian-sdk-brand-logo";
|
|
205
|
+
wrapper.setAttribute("data-animated", animated ? "true" : "false");
|
|
206
|
+
Object.assign(wrapper.style, {
|
|
207
|
+
display: "inline-flex",
|
|
208
|
+
alignItems: "center",
|
|
209
|
+
justifyContent: "center",
|
|
210
|
+
gap: `${gap}px`,
|
|
211
|
+
width: brandWidth ? `${brandWidth}px` : void 0
|
|
212
|
+
});
|
|
213
|
+
const copy = document.createElement("div");
|
|
214
|
+
copy.className = "youidian-sdk-brand-logo__copy";
|
|
215
|
+
copy.setAttribute("data-brand-locale", brand.language);
|
|
216
|
+
Object.assign(copy.style, {
|
|
217
|
+
display: "flex",
|
|
218
|
+
flexDirection: "column",
|
|
219
|
+
alignItems: "flex-start",
|
|
220
|
+
justifyContent: "center",
|
|
221
|
+
lineHeight: "1",
|
|
222
|
+
width: copyWidth ? `${copyWidth}px` : void 0
|
|
223
|
+
});
|
|
224
|
+
const name = document.createElement("div");
|
|
225
|
+
name.className = "youidian-sdk-brand-logo__name";
|
|
226
|
+
name.textContent = brand.name;
|
|
227
|
+
Object.assign(name.style, {
|
|
228
|
+
color: theme.foreground,
|
|
229
|
+
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',
|
|
230
|
+
fontSize: `${nameSize}px`,
|
|
231
|
+
fontWeight: brand.language === "zh" ? "900" : "800",
|
|
232
|
+
letterSpacing: brand.language === "zh" ? "0.08em" : "0.01em",
|
|
233
|
+
lineHeight: "1",
|
|
234
|
+
whiteSpace: "nowrap"
|
|
235
|
+
});
|
|
236
|
+
const subtitle = document.createElement("div");
|
|
237
|
+
subtitle.className = "youidian-sdk-brand-logo__tagline";
|
|
238
|
+
subtitle.textContent = brand.subtitle;
|
|
239
|
+
Object.assign(subtitle.style, {
|
|
240
|
+
color: theme.muted,
|
|
241
|
+
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',
|
|
242
|
+
fontSize: `${subtitleSize}px`,
|
|
243
|
+
fontWeight: subtitleWeight,
|
|
244
|
+
letterSpacing: brand.language === "zh" ? "0.24em" : "0.07em",
|
|
245
|
+
lineHeight: "1",
|
|
246
|
+
marginTop: `${subtitleMarginTop}px`,
|
|
247
|
+
textTransform: brand.language === "zh" ? "none" : "uppercase",
|
|
248
|
+
whiteSpace: "nowrap"
|
|
249
|
+
});
|
|
250
|
+
copy.appendChild(name);
|
|
251
|
+
copy.appendChild(subtitle);
|
|
252
|
+
const mark = createSdkBrandLogoMark({
|
|
253
|
+
animated,
|
|
254
|
+
size: markSize,
|
|
255
|
+
theme,
|
|
256
|
+
variant
|
|
257
|
+
});
|
|
258
|
+
if (typeof markMarginRight === "number") {
|
|
259
|
+
mark.style.marginRight = `${markMarginRight}px`;
|
|
260
|
+
}
|
|
261
|
+
wrapper.appendChild(mark);
|
|
262
|
+
wrapper.appendChild(copy);
|
|
263
|
+
return wrapper;
|
|
264
|
+
}
|
|
265
|
+
|
|
46
266
|
// src/hosted-modal.ts
|
|
47
267
|
var SDK_SUPPORTED_LOCALES = [
|
|
48
268
|
"en",
|
|
@@ -166,7 +386,7 @@ var HostedFrameModal = class {
|
|
|
166
386
|
const container = document.createElement("div");
|
|
167
387
|
Object.assign(container.style, {
|
|
168
388
|
width: options.width || "450px",
|
|
169
|
-
height: options.height || "min(
|
|
389
|
+
height: options.height || "min(760px, calc(100vh - 32px))",
|
|
170
390
|
backgroundColor: "transparent",
|
|
171
391
|
borderRadius: "28px",
|
|
172
392
|
overflow: "visible",
|
|
@@ -174,8 +394,7 @@ var HostedFrameModal = class {
|
|
|
174
394
|
boxShadow: "none",
|
|
175
395
|
maxWidth: "calc(100vw - 32px)",
|
|
176
396
|
maxHeight: "calc(100vh - 32px)",
|
|
177
|
-
transition: "
|
|
178
|
-
willChange: "height"
|
|
397
|
+
transition: "none"
|
|
179
398
|
});
|
|
180
399
|
const closeBtn = document.createElement("button");
|
|
181
400
|
closeBtn.innerHTML = "\xD7";
|
|
@@ -245,6 +464,91 @@ var HostedFrameModal = class {
|
|
|
245
464
|
}
|
|
246
465
|
};
|
|
247
466
|
|
|
467
|
+
// src/environment.ts
|
|
468
|
+
function normalizeText(value) {
|
|
469
|
+
return value || "";
|
|
470
|
+
}
|
|
471
|
+
function getRuntimeInput() {
|
|
472
|
+
if (typeof navigator === "undefined") {
|
|
473
|
+
return {};
|
|
474
|
+
}
|
|
475
|
+
const nav = navigator;
|
|
476
|
+
const coarsePointer = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : void 0;
|
|
477
|
+
return {
|
|
478
|
+
coarsePointer,
|
|
479
|
+
maxTouchPoints: nav.maxTouchPoints || 0,
|
|
480
|
+
platform: nav.platform || "",
|
|
481
|
+
standalone: nav.standalone,
|
|
482
|
+
userAgent: nav.userAgent || "",
|
|
483
|
+
userAgentDataMobile: nav.userAgentData?.mobile
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function includesAny(value, patterns) {
|
|
487
|
+
return patterns.some((pattern) => pattern.test(value));
|
|
488
|
+
}
|
|
489
|
+
function detectLoginEnvironment(input = getRuntimeInput()) {
|
|
490
|
+
const userAgent = normalizeText(input.userAgent);
|
|
491
|
+
const platform = normalizeText(input.platform);
|
|
492
|
+
const rawMaxTouchPoints = input.maxTouchPoints ?? 0;
|
|
493
|
+
const maxTouchPoints = Number.isFinite(rawMaxTouchPoints) ? Math.max(0, rawMaxTouchPoints) : 0;
|
|
494
|
+
const isCoarsePointer = input.coarsePointer === true;
|
|
495
|
+
const isTouch = maxTouchPoints > 0 || isCoarsePointer;
|
|
496
|
+
const isWeChat = /micromessenger/i.test(userAgent);
|
|
497
|
+
const isAndroid = /\bandroid\b/i.test(userAgent);
|
|
498
|
+
const hasExplicitIOSDevice = /\b(iPad|iPhone|iPod)\b/i.test(userAgent);
|
|
499
|
+
const hasMacintoshUserAgent = /\bMacintosh\b/i.test(userAgent);
|
|
500
|
+
const hasMacPlatform = /^Mac/i.test(platform);
|
|
501
|
+
const isIPadOS = !hasExplicitIOSDevice && hasMacintoshUserAgent && hasMacPlatform && typeof input.standalone !== "undefined" && maxTouchPoints > 2;
|
|
502
|
+
const isIOS = hasExplicitIOSDevice || isIPadOS;
|
|
503
|
+
const isTabletByUa = includesAny(userAgent, [
|
|
504
|
+
/\biPad\b/i,
|
|
505
|
+
/\btablet\b/i,
|
|
506
|
+
/\bplaybook\b/i,
|
|
507
|
+
/\bsilk\b(?!.*\bmobile\b)/i,
|
|
508
|
+
/\bkindle\b/i,
|
|
509
|
+
/\bnexus 7\b/i,
|
|
510
|
+
/\bnexus 9\b/i,
|
|
511
|
+
/\bxoom\b/i,
|
|
512
|
+
/\bsm-t\d+/i,
|
|
513
|
+
/\bgt-p\d+/i,
|
|
514
|
+
/\bmi pad\b/i
|
|
515
|
+
]);
|
|
516
|
+
const isMobileByUa = includesAny(userAgent, [
|
|
517
|
+
/\bMobile\b/i,
|
|
518
|
+
/\biPhone\b/i,
|
|
519
|
+
/\biPod\b/i,
|
|
520
|
+
/\bWindows Phone\b/i,
|
|
521
|
+
/\bIEMobile\b/i,
|
|
522
|
+
/\bBlackBerry\b/i,
|
|
523
|
+
/\bBB10\b/i,
|
|
524
|
+
/\bOpera Mini\b/i,
|
|
525
|
+
/\bOpera Mobi\b/i,
|
|
526
|
+
/\bwebOS\b/i
|
|
527
|
+
]);
|
|
528
|
+
const isAndroidTablet = isAndroid && !/\bMobile\b/i.test(userAgent);
|
|
529
|
+
const isTablet = isIPadOS || isTabletByUa || isAndroidTablet;
|
|
530
|
+
const isMobile = input.userAgentDataMobile === true || isMobileByUa || isAndroid && !isTablet || isIOS && !isTablet;
|
|
531
|
+
const deviceType = isTablet ? "tablet" : isMobile ? "mobile" : userAgent || platform || isTouch ? "desktop" : "unknown";
|
|
532
|
+
const shouldUseRedirectLogin = deviceType === "mobile" || deviceType === "tablet" || isWeChat;
|
|
533
|
+
return {
|
|
534
|
+
deviceType,
|
|
535
|
+
isAndroid,
|
|
536
|
+
isCoarsePointer,
|
|
537
|
+
isDesktop: deviceType === "desktop",
|
|
538
|
+
isIOS,
|
|
539
|
+
isIPadOS,
|
|
540
|
+
isMobile,
|
|
541
|
+
isStandalone: input.standalone === true,
|
|
542
|
+
isTablet,
|
|
543
|
+
isTouch,
|
|
544
|
+
isWeChat,
|
|
545
|
+
maxTouchPoints,
|
|
546
|
+
platform,
|
|
547
|
+
shouldUseRedirectLogin,
|
|
548
|
+
userAgent
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
248
552
|
// src/login.ts
|
|
249
553
|
function getOrigin(value) {
|
|
250
554
|
if (!value) return null;
|
|
@@ -254,6 +558,15 @@ function getOrigin(value) {
|
|
|
254
558
|
return null;
|
|
255
559
|
}
|
|
256
560
|
}
|
|
561
|
+
function isValidLoginRedirectUrl(value) {
|
|
562
|
+
if (!value) return false;
|
|
563
|
+
try {
|
|
564
|
+
const url = new URL(value);
|
|
565
|
+
return url.protocol === "https:" || url.protocol === "http:";
|
|
566
|
+
} catch {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
257
570
|
function createLoginCallbackState() {
|
|
258
571
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
259
572
|
return crypto.randomUUID().replace(/-/g, "");
|
|
@@ -600,6 +913,24 @@ function applyLoginPanelStyle(panel) {
|
|
|
600
913
|
backdropFilter: "blur(16px)"
|
|
601
914
|
});
|
|
602
915
|
}
|
|
916
|
+
function applyLoginLoadingPanelStyle(panel) {
|
|
917
|
+
Object.assign(panel.style, {
|
|
918
|
+
position: "absolute",
|
|
919
|
+
inset: "0",
|
|
920
|
+
display: "flex",
|
|
921
|
+
alignItems: "center",
|
|
922
|
+
justifyContent: "center",
|
|
923
|
+
border: "0",
|
|
924
|
+
borderRadius: "28px",
|
|
925
|
+
background: "rgba(255,255,255,0.94)",
|
|
926
|
+
color: "#0f172a",
|
|
927
|
+
padding: "24px",
|
|
928
|
+
textAlign: "center",
|
|
929
|
+
zIndex: "1",
|
|
930
|
+
boxShadow: "none",
|
|
931
|
+
backdropFilter: "blur(16px)"
|
|
932
|
+
});
|
|
933
|
+
}
|
|
603
934
|
function createLoginPanelContent() {
|
|
604
935
|
const content = document.createElement("div");
|
|
605
936
|
Object.assign(content.style, {
|
|
@@ -624,6 +955,71 @@ function createLoginPanelContent() {
|
|
|
624
955
|
content.appendChild(badge);
|
|
625
956
|
return content;
|
|
626
957
|
}
|
|
958
|
+
function createBrandLoadingMark() {
|
|
959
|
+
const logo = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
960
|
+
logo.setAttribute("viewBox", "0 0 1024 1024");
|
|
961
|
+
logo.setAttribute("fill", "none");
|
|
962
|
+
logo.setAttribute("aria-hidden", "true");
|
|
963
|
+
Object.assign(logo.style, {
|
|
964
|
+
width: "112px",
|
|
965
|
+
height: "112px",
|
|
966
|
+
color: "#22c55e",
|
|
967
|
+
display: "block",
|
|
968
|
+
overflow: "visible"
|
|
969
|
+
});
|
|
970
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
971
|
+
group.setAttribute("fill", "currentColor");
|
|
972
|
+
const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
973
|
+
ring.setAttribute(
|
|
974
|
+
"d",
|
|
975
|
+
"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"
|
|
976
|
+
);
|
|
977
|
+
ring.setAttribute("class", "youidian-sdk-brand-loading__ring");
|
|
978
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
979
|
+
dot.setAttribute("class", "youidian-sdk-brand-loading__dot");
|
|
980
|
+
dot.setAttribute("cx", "714.5");
|
|
981
|
+
dot.setAttribute("cy", "490.5");
|
|
982
|
+
dot.setAttribute("r", "68");
|
|
983
|
+
group.appendChild(ring);
|
|
984
|
+
group.appendChild(dot);
|
|
985
|
+
logo.appendChild(group);
|
|
986
|
+
return logo;
|
|
987
|
+
}
|
|
988
|
+
function createBrandLoadingCopy() {
|
|
989
|
+
const brand = document.createElement("div");
|
|
990
|
+
brand.className = "youidian-sdk-brand-loading__copy";
|
|
991
|
+
Object.assign(brand.style, {
|
|
992
|
+
display: "flex",
|
|
993
|
+
flexDirection: "column",
|
|
994
|
+
alignItems: "flex-start",
|
|
995
|
+
justifyContent: "center",
|
|
996
|
+
lineHeight: "1"
|
|
997
|
+
});
|
|
998
|
+
const name = document.createElement("div");
|
|
999
|
+
name.className = "youidian-sdk-brand-loading__name";
|
|
1000
|
+
name.textContent = "\u4F18\u6613\u70B9";
|
|
1001
|
+
Object.assign(name.style, {
|
|
1002
|
+
color: "#0f172a",
|
|
1003
|
+
fontSize: "34px",
|
|
1004
|
+
fontWeight: "900",
|
|
1005
|
+
letterSpacing: "0",
|
|
1006
|
+
lineHeight: "1"
|
|
1007
|
+
});
|
|
1008
|
+
const tagline = document.createElement("div");
|
|
1009
|
+
tagline.className = "youidian-sdk-brand-loading__tagline";
|
|
1010
|
+
tagline.textContent = "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0";
|
|
1011
|
+
Object.assign(tagline.style, {
|
|
1012
|
+
color: "#64748b",
|
|
1013
|
+
fontSize: "16px",
|
|
1014
|
+
fontWeight: "700",
|
|
1015
|
+
letterSpacing: "0.24em",
|
|
1016
|
+
lineHeight: "1",
|
|
1017
|
+
marginTop: "6px"
|
|
1018
|
+
});
|
|
1019
|
+
brand.appendChild(name);
|
|
1020
|
+
brand.appendChild(tagline);
|
|
1021
|
+
return brand;
|
|
1022
|
+
}
|
|
627
1023
|
function createLoginPanelTitle(value) {
|
|
628
1024
|
const title = document.createElement("div");
|
|
629
1025
|
title.textContent = value;
|
|
@@ -653,32 +1049,111 @@ function ensureLoginLoadingStyles() {
|
|
|
653
1049
|
}
|
|
654
1050
|
const style = document.createElement("style");
|
|
655
1051
|
style.id = "youidian-login-loading-style";
|
|
656
|
-
style.textContent =
|
|
1052
|
+
style.textContent = `
|
|
1053
|
+
.youidian-sdk-brand-loading__ring,
|
|
1054
|
+
.youidian-sdk-brand-loading__dot {
|
|
1055
|
+
transform-box: fill-box;
|
|
1056
|
+
transform-origin: center;
|
|
1057
|
+
will-change: opacity, transform;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.youidian-sdk-brand-loading__ring {
|
|
1061
|
+
animation: youidian-sdk-brand-loading-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.youidian-sdk-brand-loading__dot {
|
|
1065
|
+
animation: youidian-sdk-brand-loading-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
.youidian-sdk-brand-loading__copy {
|
|
1069
|
+
will-change: opacity, transform;
|
|
1070
|
+
animation: youidian-sdk-brand-loading-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.youidian-sdk-brand-loading__tagline {
|
|
1074
|
+
will-change: opacity;
|
|
1075
|
+
animation: youidian-sdk-brand-loading-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
@keyframes youidian-sdk-brand-loading-ring {
|
|
1079
|
+
0%,
|
|
1080
|
+
100% {
|
|
1081
|
+
opacity: 0.72;
|
|
1082
|
+
transform: rotate(0deg) scale(0.98);
|
|
1083
|
+
}
|
|
1084
|
+
50% {
|
|
1085
|
+
opacity: 1;
|
|
1086
|
+
transform: rotate(8deg) scale(1.02);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
@keyframes youidian-sdk-brand-loading-dot {
|
|
1091
|
+
0%,
|
|
1092
|
+
100% {
|
|
1093
|
+
opacity: 0.55;
|
|
1094
|
+
transform: scale(0.9);
|
|
1095
|
+
}
|
|
1096
|
+
50% {
|
|
1097
|
+
opacity: 1;
|
|
1098
|
+
transform: scale(1.08);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
@keyframes youidian-sdk-brand-loading-copy {
|
|
1103
|
+
0%,
|
|
1104
|
+
100% {
|
|
1105
|
+
opacity: 0.86;
|
|
1106
|
+
transform: translateY(0);
|
|
1107
|
+
}
|
|
1108
|
+
50% {
|
|
1109
|
+
opacity: 1;
|
|
1110
|
+
transform: translateY(-1px);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
@keyframes youidian-sdk-brand-loading-tagline {
|
|
1115
|
+
0%,
|
|
1116
|
+
100% {
|
|
1117
|
+
opacity: 0.64;
|
|
1118
|
+
}
|
|
1119
|
+
50% {
|
|
1120
|
+
opacity: 0.92;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1125
|
+
.youidian-sdk-brand-loading__ring,
|
|
1126
|
+
.youidian-sdk-brand-loading__dot,
|
|
1127
|
+
.youidian-sdk-brand-loading__copy,
|
|
1128
|
+
.youidian-sdk-brand-loading__tagline {
|
|
1129
|
+
animation: none;
|
|
1130
|
+
opacity: 1;
|
|
1131
|
+
transform: none;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
`;
|
|
657
1135
|
document.head.appendChild(style);
|
|
658
1136
|
}
|
|
659
|
-
function createLoginLoadingPanel(
|
|
1137
|
+
function createLoginLoadingPanel() {
|
|
660
1138
|
ensureLoginLoadingStyles();
|
|
661
1139
|
const panel = document.createElement("div");
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
margin: "0 auto 18px",
|
|
672
|
-
animation: "youidian-login-spin 780ms linear infinite"
|
|
1140
|
+
applyLoginLoadingPanelStyle(panel);
|
|
1141
|
+
panel.setAttribute("role", "status");
|
|
1142
|
+
panel.setAttribute("aria-label", "\u4F18\u6613\u70B9\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0");
|
|
1143
|
+
const content = document.createElement("div");
|
|
1144
|
+
Object.assign(content.style, {
|
|
1145
|
+
display: "flex",
|
|
1146
|
+
alignItems: "center",
|
|
1147
|
+
justifyContent: "center",
|
|
1148
|
+
gap: "12px"
|
|
673
1149
|
});
|
|
674
|
-
content.appendChild(
|
|
675
|
-
content.appendChild(
|
|
676
|
-
content.appendChild(createLoginPanelDescription(options.description));
|
|
1150
|
+
content.appendChild(createBrandLoadingMark());
|
|
1151
|
+
content.appendChild(createBrandLoadingCopy());
|
|
677
1152
|
panel.appendChild(content);
|
|
678
1153
|
return panel;
|
|
679
1154
|
}
|
|
680
|
-
function createLoginRedirectingPanel(
|
|
681
|
-
return createLoginLoadingPanel(
|
|
1155
|
+
function createLoginRedirectingPanel(_options) {
|
|
1156
|
+
return createLoginLoadingPanel();
|
|
682
1157
|
}
|
|
683
1158
|
function createLoginFallbackPanel(options) {
|
|
684
1159
|
const panel = document.createElement("div");
|
|
@@ -760,6 +1235,7 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
760
1235
|
this.completed = false;
|
|
761
1236
|
}
|
|
762
1237
|
markIframeReady() {
|
|
1238
|
+
if (this.iframeReady) return;
|
|
763
1239
|
this.iframeReady = true;
|
|
764
1240
|
if (typeof window !== "undefined" && this.iframeLoadTimer) {
|
|
765
1241
|
window.clearTimeout(this.iframeLoadTimer);
|
|
@@ -778,14 +1254,11 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
778
1254
|
this.iframeFallbackPanel = null;
|
|
779
1255
|
this.iframeLoadingPanel = null;
|
|
780
1256
|
}
|
|
781
|
-
showIframeLoading(container
|
|
1257
|
+
showIframeLoading(container) {
|
|
782
1258
|
if (this.iframeLoadingPanel || this.iframeReady) {
|
|
783
1259
|
return;
|
|
784
1260
|
}
|
|
785
|
-
const panel = createLoginLoadingPanel(
|
|
786
|
-
description: options?.loadingDescription || "Please wait while the secure login page opens.",
|
|
787
|
-
title: options?.loadingTitle || "Opening login page"
|
|
788
|
-
});
|
|
1261
|
+
const panel = createLoginLoadingPanel();
|
|
789
1262
|
this.iframeLoadingPanel = panel;
|
|
790
1263
|
container.appendChild(panel);
|
|
791
1264
|
}
|
|
@@ -873,6 +1346,20 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
873
1346
|
break;
|
|
874
1347
|
case "LOGIN_RESIZE":
|
|
875
1348
|
break;
|
|
1349
|
+
case "LOGIN_REDIRECT_REQUIRED":
|
|
1350
|
+
if (!isValidLoginRedirectUrl(data.authUrl)) {
|
|
1351
|
+
logLoginWarn("Login redirect requested with invalid authUrl");
|
|
1352
|
+
options?.onError?.("Login redirect URL is invalid", data);
|
|
1353
|
+
break;
|
|
1354
|
+
}
|
|
1355
|
+
logLoginInfo("Login redirect requested by hosted page", {
|
|
1356
|
+
attemptId: data.attemptId || null,
|
|
1357
|
+
channel: data.channel || null,
|
|
1358
|
+
authUrl: data.authUrl
|
|
1359
|
+
});
|
|
1360
|
+
this.close();
|
|
1361
|
+
window.location.assign(data.authUrl);
|
|
1362
|
+
break;
|
|
876
1363
|
case "LOGIN_ERROR":
|
|
877
1364
|
if (this.completed) {
|
|
878
1365
|
break;
|
|
@@ -896,6 +1383,25 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
896
1383
|
break;
|
|
897
1384
|
}
|
|
898
1385
|
}
|
|
1386
|
+
openLoginRedirect(params, options) {
|
|
1387
|
+
if (typeof window === "undefined") return;
|
|
1388
|
+
const { callbackState, finalUrl, launchPayload } = this.buildOpenContext(
|
|
1389
|
+
params,
|
|
1390
|
+
options
|
|
1391
|
+
);
|
|
1392
|
+
logLoginInfo("Redirecting to hosted login page", {
|
|
1393
|
+
appId: params.appId,
|
|
1394
|
+
callbackState,
|
|
1395
|
+
callbackUrl: launchPayload.callbackUrl || null,
|
|
1396
|
+
hasCallbackUrl: Boolean(launchPayload.callbackUrl),
|
|
1397
|
+
loginUrl: finalUrl,
|
|
1398
|
+
autoClose: options?.autoClose ?? true,
|
|
1399
|
+
displayMode: "redirect",
|
|
1400
|
+
pageUrl: window.location.href,
|
|
1401
|
+
parentOrigin: launchPayload.origin || null
|
|
1402
|
+
});
|
|
1403
|
+
window.location.assign(finalUrl);
|
|
1404
|
+
}
|
|
899
1405
|
listenForLoginCallback(callbackState, options) {
|
|
900
1406
|
try {
|
|
901
1407
|
this.callbackChannel = new BroadcastChannel(
|
|
@@ -972,21 +1478,28 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
972
1478
|
options?.onCancel?.();
|
|
973
1479
|
options?.onClose?.();
|
|
974
1480
|
},
|
|
975
|
-
onMessage: (data,
|
|
1481
|
+
onMessage: (data, _container, event) => {
|
|
976
1482
|
const isIframeEvent = event.source === this.iframe?.contentWindow;
|
|
1483
|
+
if (!isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE")) {
|
|
1484
|
+
logLoginWarn(
|
|
1485
|
+
"Login lifecycle event ignored: event.source does not match iframe contentWindow",
|
|
1486
|
+
{
|
|
1487
|
+
type: data.type,
|
|
1488
|
+
eventOrigin: event.origin,
|
|
1489
|
+
hasIframe: Boolean(this.iframe),
|
|
1490
|
+
hasIframeContentWindow: Boolean(this.iframe?.contentWindow)
|
|
1491
|
+
}
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
977
1494
|
if (isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE") || data.type === "LOGIN_SUCCESS" || data.type === "LOGIN_ERROR") {
|
|
978
1495
|
this.markIframeReady();
|
|
979
1496
|
}
|
|
980
|
-
if (data.type === "LOGIN_RESIZE" && data.height) {
|
|
981
|
-
const maxHeight = window.innerHeight * 0.94;
|
|
982
|
-
container.style.height = `${Math.min(data.height, maxHeight)}px`;
|
|
983
|
-
}
|
|
984
1497
|
this.handleLoginEvent(data, options);
|
|
985
1498
|
},
|
|
986
1499
|
width: "min(520px, 100%)"
|
|
987
1500
|
});
|
|
988
1501
|
if (hostedFrame) {
|
|
989
|
-
this.showIframeLoading(hostedFrame.container
|
|
1502
|
+
this.showIframeLoading(hostedFrame.container);
|
|
990
1503
|
this.startIframeWatchdog({
|
|
991
1504
|
container: hostedFrame.container,
|
|
992
1505
|
finalUrl,
|
|
@@ -1090,11 +1603,19 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
1090
1603
|
}
|
|
1091
1604
|
openLogin(params, options) {
|
|
1092
1605
|
if (typeof window === "undefined") return;
|
|
1093
|
-
const displayMode = options?.displayMode ?? "
|
|
1606
|
+
const displayMode = options?.displayMode ?? "auto";
|
|
1607
|
+
if (displayMode === "redirect") {
|
|
1608
|
+
this.openLoginRedirect(params, options);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1094
1611
|
if (displayMode === "popup") {
|
|
1095
1612
|
this.openLoginPopup(params, options);
|
|
1096
1613
|
return;
|
|
1097
1614
|
}
|
|
1615
|
+
if (displayMode === "auto" && detectLoginEnvironment().shouldUseRedirectLogin) {
|
|
1616
|
+
this.openLoginRedirect(params, options);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1098
1619
|
this.openLoginModal(params, options);
|
|
1099
1620
|
}
|
|
1100
1621
|
};
|
|
@@ -1104,6 +1625,406 @@ function createLoginUI() {
|
|
|
1104
1625
|
handleLoginCallbackIfPresent({ autoClose: false });
|
|
1105
1626
|
|
|
1106
1627
|
// src/client.ts
|
|
1628
|
+
var DEFAULT_PAYMENT_IFRAME_LOAD_TIMEOUT_MS = 8e3;
|
|
1629
|
+
var PAYMENT_UI_LOG_PREFIX = "[Youidian PaymentUI]";
|
|
1630
|
+
function hexToRgb(value) {
|
|
1631
|
+
const normalized = value.trim().replace(/^#/, "");
|
|
1632
|
+
if (!/^[0-9a-f]{3}([0-9a-f]{3})?$/i.test(normalized)) return null;
|
|
1633
|
+
const hex = normalized.length === 3 ? normalized.split("").map((char) => `${char}${char}`).join("") : normalized;
|
|
1634
|
+
const number = Number.parseInt(hex, 16);
|
|
1635
|
+
return {
|
|
1636
|
+
b: number & 255,
|
|
1637
|
+
g: number >> 8 & 255,
|
|
1638
|
+
r: number >> 16 & 255
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function alphaColor(value, alpha) {
|
|
1642
|
+
const rgb = hexToRgb(value);
|
|
1643
|
+
if (!rgb)
|
|
1644
|
+
return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`;
|
|
1645
|
+
return `rgba(${rgb.r},${rgb.g},${rgb.b},${alpha})`;
|
|
1646
|
+
}
|
|
1647
|
+
function resolvePaymentTheme(options) {
|
|
1648
|
+
const primary = options?.theme?.primary || "#16a34a";
|
|
1649
|
+
const secondary = options?.theme?.secondary || "color-mix(in srgb, #16a34a 78%, white)";
|
|
1650
|
+
const foreground = options?.theme?.foreground || "#0f172a";
|
|
1651
|
+
const muted = options?.theme?.muted || "#64748b";
|
|
1652
|
+
const surface = options?.theme?.surface || "#ffffff";
|
|
1653
|
+
return {
|
|
1654
|
+
foreground,
|
|
1655
|
+
muted,
|
|
1656
|
+
primary,
|
|
1657
|
+
primary10: alphaColor(primary, 0.1),
|
|
1658
|
+
primary14: alphaColor(primary, 0.14),
|
|
1659
|
+
primary20: alphaColor(primary, 0.2),
|
|
1660
|
+
primary28: alphaColor(primary, 0.28),
|
|
1661
|
+
primary36: alphaColor(primary, 0.36),
|
|
1662
|
+
primary64: alphaColor(primary, 0.64),
|
|
1663
|
+
secondary,
|
|
1664
|
+
surface
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
function logPaymentInfo(message, details) {
|
|
1668
|
+
if (typeof console === "undefined") return;
|
|
1669
|
+
console.info(PAYMENT_UI_LOG_PREFIX, message, details || {});
|
|
1670
|
+
}
|
|
1671
|
+
function logPaymentWarn(message, details) {
|
|
1672
|
+
if (typeof console === "undefined") return;
|
|
1673
|
+
console.warn(PAYMENT_UI_LOG_PREFIX, message, details || {});
|
|
1674
|
+
}
|
|
1675
|
+
function getPaymentIframeLoadTimeoutMs(options) {
|
|
1676
|
+
const timeout = options?.iframeLoadTimeoutMs;
|
|
1677
|
+
if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
|
|
1678
|
+
return DEFAULT_PAYMENT_IFRAME_LOAD_TIMEOUT_MS;
|
|
1679
|
+
}
|
|
1680
|
+
return Math.max(0, timeout);
|
|
1681
|
+
}
|
|
1682
|
+
function isChinesePaymentLocale(locale) {
|
|
1683
|
+
return locale?.toLowerCase().startsWith("zh") || false;
|
|
1684
|
+
}
|
|
1685
|
+
function getPaymentFallbackText(options) {
|
|
1686
|
+
const isChinese = isChinesePaymentLocale(options?.locale);
|
|
1687
|
+
return {
|
|
1688
|
+
buttonText: options?.fallbackButtonText || (isChinese ? "\u5728\u65B0\u9875\u9762\u4E2D\u6253\u5F00" : "Open in new page"),
|
|
1689
|
+
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.",
|
|
1690
|
+
retryText: options?.fallbackRetryText || (isChinese ? "\u91CD\u65B0\u5C1D\u8BD5" : "Try again"),
|
|
1691
|
+
title: isChinese ? "\u652F\u4ED8\u9875\u9762\u6253\u5F00\u8F83\u6162" : "Payment page is taking longer"
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function getPaymentBrandText(options) {
|
|
1695
|
+
const isChinese = isChinesePaymentLocale(options?.locale);
|
|
1696
|
+
return {
|
|
1697
|
+
language: isChinese ? "zh" : "en",
|
|
1698
|
+
name: isChinese ? "\u4F18\u6613\u70B9" : "Youidian",
|
|
1699
|
+
subtitle: isChinese ? "\u5F00\u53D1\u8005\u670D\u52A1\u5E73\u53F0" : "Developer Service Platform"
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function getPaymentBrandLoadingText(options) {
|
|
1703
|
+
const isChinese = isChinesePaymentLocale(options?.locale);
|
|
1704
|
+
return {
|
|
1705
|
+
language: isChinese ? "zh" : "en",
|
|
1706
|
+
name: isChinese ? "\u4F18\u6613\u70B9" : "Youidian",
|
|
1707
|
+
subtitle: isChinese ? "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0" : "Developer Service"
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
function getPaymentLoadingText(options) {
|
|
1711
|
+
const isChinese = isChinesePaymentLocale(options?.locale);
|
|
1712
|
+
return options?.loadingTitle || options?.loadingDescription || (isChinese ? "\u52A0\u8F7D\u4E2D..." : "Loading...");
|
|
1713
|
+
}
|
|
1714
|
+
function applyPaymentPanelStyle(panel, theme) {
|
|
1715
|
+
Object.assign(panel.style, {
|
|
1716
|
+
position: "absolute",
|
|
1717
|
+
inset: "0",
|
|
1718
|
+
display: "flex",
|
|
1719
|
+
alignItems: "center",
|
|
1720
|
+
justifyContent: "center",
|
|
1721
|
+
borderRadius: "28px",
|
|
1722
|
+
background: `linear-gradient(180deg, ${theme.surface} 0%, rgba(248,250,252,0.98) 100%)`,
|
|
1723
|
+
color: theme.foreground,
|
|
1724
|
+
padding: "28px 24px",
|
|
1725
|
+
textAlign: "center",
|
|
1726
|
+
zIndex: "1",
|
|
1727
|
+
boxShadow: "none",
|
|
1728
|
+
backdropFilter: "blur(16px)"
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
function createPaymentIcon(type) {
|
|
1732
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1733
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
1734
|
+
svg.setAttribute("fill", "none");
|
|
1735
|
+
svg.setAttribute("aria-hidden", "true");
|
|
1736
|
+
Object.assign(svg.style, {
|
|
1737
|
+
display: "block",
|
|
1738
|
+
flex: "0 0 auto"
|
|
1739
|
+
});
|
|
1740
|
+
const paths = type === "external" ? [
|
|
1741
|
+
"M7 17L17 7",
|
|
1742
|
+
"M10 7H17V14",
|
|
1743
|
+
"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"
|
|
1744
|
+
] : type === "refresh" ? [
|
|
1745
|
+
"M20 6V11H15",
|
|
1746
|
+
"M4 18V13H9",
|
|
1747
|
+
"M18.2 9A7 7 0 0 0 6.2 6.6L4 9",
|
|
1748
|
+
"M5.8 15A7 7 0 0 0 17.8 17.4L20 15"
|
|
1749
|
+
] : ["M12 7V12L15 14", "M21 12A9 9 0 1 1 3 12A9 9 0 0 1 21 12"];
|
|
1750
|
+
for (const value of paths) {
|
|
1751
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1752
|
+
path.setAttribute("d", value);
|
|
1753
|
+
path.setAttribute("stroke", "currentColor");
|
|
1754
|
+
path.setAttribute("stroke-width", "2");
|
|
1755
|
+
path.setAttribute("stroke-linecap", "round");
|
|
1756
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
1757
|
+
svg.appendChild(path);
|
|
1758
|
+
}
|
|
1759
|
+
return svg;
|
|
1760
|
+
}
|
|
1761
|
+
function createPaymentFallbackArtwork(theme) {
|
|
1762
|
+
const artwork = document.createElement("div");
|
|
1763
|
+
Object.assign(artwork.style, {
|
|
1764
|
+
height: "164px",
|
|
1765
|
+
margin: "0 auto 34px",
|
|
1766
|
+
position: "relative",
|
|
1767
|
+
width: "184px"
|
|
1768
|
+
});
|
|
1769
|
+
const wash = document.createElement("div");
|
|
1770
|
+
Object.assign(wash.style, {
|
|
1771
|
+
position: "absolute",
|
|
1772
|
+
left: "16px",
|
|
1773
|
+
top: "20px",
|
|
1774
|
+
width: "152px",
|
|
1775
|
+
height: "126px",
|
|
1776
|
+
borderRadius: "48% 52% 43% 57% / 54% 42% 58% 46%",
|
|
1777
|
+
background: `radial-gradient(circle at 48% 42%, ${theme.primary20}, ${theme.primary10} 52%, transparent 76%)`,
|
|
1778
|
+
filter: "blur(1px)",
|
|
1779
|
+
transform: "rotate(-10deg)"
|
|
1780
|
+
});
|
|
1781
|
+
const blob = document.createElement("div");
|
|
1782
|
+
Object.assign(blob.style, {
|
|
1783
|
+
position: "absolute",
|
|
1784
|
+
left: "26px",
|
|
1785
|
+
top: "28px",
|
|
1786
|
+
width: "132px",
|
|
1787
|
+
height: "116px",
|
|
1788
|
+
borderRadius: "42% 58% 53% 47% / 45% 39% 61% 55%",
|
|
1789
|
+
background: `linear-gradient(150deg, ${theme.primary14}, ${theme.primary10})`,
|
|
1790
|
+
boxShadow: `0 30px 64px ${theme.primary14}`,
|
|
1791
|
+
transform: "rotate(8deg)"
|
|
1792
|
+
});
|
|
1793
|
+
const tile = document.createElement("div");
|
|
1794
|
+
Object.assign(tile.style, {
|
|
1795
|
+
position: "absolute",
|
|
1796
|
+
left: "50%",
|
|
1797
|
+
top: "50%",
|
|
1798
|
+
transform: "translate(-50%, -50%) rotate(-4deg)",
|
|
1799
|
+
width: "112px",
|
|
1800
|
+
height: "106px",
|
|
1801
|
+
borderRadius: "38% 62% 50% 50% / 44% 43% 57% 56%",
|
|
1802
|
+
background: `linear-gradient(145deg, ${theme.primary}, ${theme.secondary})`,
|
|
1803
|
+
border: `1px solid ${theme.primary36}`,
|
|
1804
|
+
color: "#ffffff",
|
|
1805
|
+
display: "inline-flex",
|
|
1806
|
+
alignItems: "center",
|
|
1807
|
+
justifyContent: "center",
|
|
1808
|
+
boxShadow: `0 26px 54px ${theme.primary28}`
|
|
1809
|
+
});
|
|
1810
|
+
const icon = createPaymentIcon("slow");
|
|
1811
|
+
Object.assign(icon.style, {
|
|
1812
|
+
width: "52px",
|
|
1813
|
+
height: "52px",
|
|
1814
|
+
transform: "rotate(4deg)"
|
|
1815
|
+
});
|
|
1816
|
+
tile.appendChild(icon);
|
|
1817
|
+
artwork.appendChild(wash);
|
|
1818
|
+
artwork.appendChild(blob);
|
|
1819
|
+
artwork.appendChild(tile);
|
|
1820
|
+
return artwork;
|
|
1821
|
+
}
|
|
1822
|
+
function createPaymentBrandLoading(options, theme) {
|
|
1823
|
+
const brand = getPaymentBrandLoadingText(options);
|
|
1824
|
+
const wrapper = document.createElement("div");
|
|
1825
|
+
Object.assign(wrapper.style, {
|
|
1826
|
+
display: "flex",
|
|
1827
|
+
flexDirection: "column",
|
|
1828
|
+
alignItems: "center",
|
|
1829
|
+
justifyContent: "center",
|
|
1830
|
+
margin: "0 auto 16px"
|
|
1831
|
+
});
|
|
1832
|
+
wrapper.appendChild(
|
|
1833
|
+
createSdkBrandLogo({
|
|
1834
|
+
animated: true,
|
|
1835
|
+
brand,
|
|
1836
|
+
brandWidth: 292,
|
|
1837
|
+
copyWidth: 176,
|
|
1838
|
+
gap: 6,
|
|
1839
|
+
markMarginRight: -10,
|
|
1840
|
+
markSize: 112,
|
|
1841
|
+
nameSize: 39,
|
|
1842
|
+
subtitleMarginTop: 8,
|
|
1843
|
+
subtitleSize: 14,
|
|
1844
|
+
subtitleWeight: "700",
|
|
1845
|
+
theme,
|
|
1846
|
+
variant: "brand"
|
|
1847
|
+
})
|
|
1848
|
+
);
|
|
1849
|
+
return wrapper;
|
|
1850
|
+
}
|
|
1851
|
+
function createPaymentPlatformLogo(options, theme) {
|
|
1852
|
+
const brand = getPaymentBrandText(options);
|
|
1853
|
+
const wrapper = document.createElement("div");
|
|
1854
|
+
Object.assign(wrapper.style, {
|
|
1855
|
+
display: "flex",
|
|
1856
|
+
alignItems: "center",
|
|
1857
|
+
justifyContent: "center",
|
|
1858
|
+
margin: "0 auto 10px"
|
|
1859
|
+
});
|
|
1860
|
+
wrapper.appendChild(
|
|
1861
|
+
createSdkBrandLogo({
|
|
1862
|
+
brand,
|
|
1863
|
+
markSize: 48,
|
|
1864
|
+
nameSize: brand.language === "zh" ? 22 : 22,
|
|
1865
|
+
subtitleSize: brand.language === "zh" ? 9 : 9,
|
|
1866
|
+
theme
|
|
1867
|
+
})
|
|
1868
|
+
);
|
|
1869
|
+
return wrapper;
|
|
1870
|
+
}
|
|
1871
|
+
function createPaymentPanelContent() {
|
|
1872
|
+
const content = document.createElement("div");
|
|
1873
|
+
Object.assign(content.style, {
|
|
1874
|
+
display: "flex",
|
|
1875
|
+
flexDirection: "column",
|
|
1876
|
+
justifyContent: "center",
|
|
1877
|
+
maxWidth: "364px",
|
|
1878
|
+
minHeight: "100%",
|
|
1879
|
+
width: "100%"
|
|
1880
|
+
});
|
|
1881
|
+
return content;
|
|
1882
|
+
}
|
|
1883
|
+
function createPaymentPanelTitle(value, theme) {
|
|
1884
|
+
const title = document.createElement("div");
|
|
1885
|
+
title.textContent = value;
|
|
1886
|
+
Object.assign(title.style, {
|
|
1887
|
+
color: theme?.foreground || "#0f172a",
|
|
1888
|
+
fontSize: "24px",
|
|
1889
|
+
fontWeight: "900",
|
|
1890
|
+
lineHeight: "1.22",
|
|
1891
|
+
marginBottom: "16px"
|
|
1892
|
+
});
|
|
1893
|
+
return title;
|
|
1894
|
+
}
|
|
1895
|
+
function createPaymentPanelDescription(value, theme) {
|
|
1896
|
+
const description = document.createElement("div");
|
|
1897
|
+
description.textContent = value;
|
|
1898
|
+
Object.assign(description.style, {
|
|
1899
|
+
color: theme?.muted || "#64748b",
|
|
1900
|
+
fontSize: "14px",
|
|
1901
|
+
fontWeight: "600",
|
|
1902
|
+
lineHeight: "1.62",
|
|
1903
|
+
margin: "0 auto",
|
|
1904
|
+
maxWidth: "320px"
|
|
1905
|
+
});
|
|
1906
|
+
return description;
|
|
1907
|
+
}
|
|
1908
|
+
function createPaymentLoadingDescription(value, theme) {
|
|
1909
|
+
const description = document.createElement("div");
|
|
1910
|
+
description.textContent = value;
|
|
1911
|
+
Object.assign(description.style, {
|
|
1912
|
+
color: theme?.muted || "#64748b",
|
|
1913
|
+
fontSize: "14px",
|
|
1914
|
+
fontWeight: "500",
|
|
1915
|
+
lineHeight: "1.25",
|
|
1916
|
+
margin: "12px auto 0",
|
|
1917
|
+
maxWidth: "320px"
|
|
1918
|
+
});
|
|
1919
|
+
return description;
|
|
1920
|
+
}
|
|
1921
|
+
function createPaymentFallbackMessage(options) {
|
|
1922
|
+
const message = document.createElement("div");
|
|
1923
|
+
Object.assign(message.style, {
|
|
1924
|
+
margin: "0 auto",
|
|
1925
|
+
textAlign: "center"
|
|
1926
|
+
});
|
|
1927
|
+
message.appendChild(createPaymentFallbackArtwork(options.theme));
|
|
1928
|
+
message.appendChild(createPaymentPanelTitle(options.title, options.theme));
|
|
1929
|
+
message.appendChild(
|
|
1930
|
+
createPaymentPanelDescription(options.description, options.theme)
|
|
1931
|
+
);
|
|
1932
|
+
return message;
|
|
1933
|
+
}
|
|
1934
|
+
function createPaymentLoadingPanel(options) {
|
|
1935
|
+
const panel = document.createElement("div");
|
|
1936
|
+
const theme = resolvePaymentTheme(options);
|
|
1937
|
+
applyPaymentPanelStyle(panel, theme);
|
|
1938
|
+
const content = createPaymentPanelContent();
|
|
1939
|
+
content.appendChild(createPaymentBrandLoading(options, theme));
|
|
1940
|
+
content.appendChild(
|
|
1941
|
+
createPaymentLoadingDescription(getPaymentLoadingText(options), theme)
|
|
1942
|
+
);
|
|
1943
|
+
panel.appendChild(content);
|
|
1944
|
+
return panel;
|
|
1945
|
+
}
|
|
1946
|
+
function createPaymentButton(value, options) {
|
|
1947
|
+
const primary = options?.primary || false;
|
|
1948
|
+
const theme = options?.theme || resolvePaymentTheme();
|
|
1949
|
+
const button = document.createElement("button");
|
|
1950
|
+
button.type = "button";
|
|
1951
|
+
Object.assign(button.style, {
|
|
1952
|
+
width: "100%",
|
|
1953
|
+
height: "50px",
|
|
1954
|
+
borderRadius: "14px",
|
|
1955
|
+
border: primary ? `1px solid ${theme.primary}` : `1px solid ${theme.primary64}`,
|
|
1956
|
+
background: primary ? `linear-gradient(135deg, ${theme.primary}, ${theme.secondary})` : "#ffffff",
|
|
1957
|
+
color: primary ? "#ffffff" : theme.primary,
|
|
1958
|
+
cursor: "pointer",
|
|
1959
|
+
display: "inline-flex",
|
|
1960
|
+
alignItems: "center",
|
|
1961
|
+
justifyContent: "center",
|
|
1962
|
+
gap: "10px",
|
|
1963
|
+
fontSize: "15px",
|
|
1964
|
+
fontWeight: "800",
|
|
1965
|
+
marginTop: primary ? "12px" : "0",
|
|
1966
|
+
boxShadow: primary ? `0 14px 28px ${theme.primary20}` : "none"
|
|
1967
|
+
});
|
|
1968
|
+
if (options?.icon) {
|
|
1969
|
+
const icon = createPaymentIcon(options.icon);
|
|
1970
|
+
Object.assign(icon.style, {
|
|
1971
|
+
width: "17px",
|
|
1972
|
+
height: "17px"
|
|
1973
|
+
});
|
|
1974
|
+
button.appendChild(icon);
|
|
1975
|
+
}
|
|
1976
|
+
const label = document.createElement("span");
|
|
1977
|
+
label.textContent = value;
|
|
1978
|
+
button.appendChild(label);
|
|
1979
|
+
return button;
|
|
1980
|
+
}
|
|
1981
|
+
function createPaymentFallbackPanel(options) {
|
|
1982
|
+
const panel = document.createElement("div");
|
|
1983
|
+
const theme = resolvePaymentTheme(options.paymentOptions);
|
|
1984
|
+
applyPaymentPanelStyle(panel, theme);
|
|
1985
|
+
const content = createPaymentPanelContent();
|
|
1986
|
+
Object.assign(content.style, {
|
|
1987
|
+
justifyContent: "space-between"
|
|
1988
|
+
});
|
|
1989
|
+
const main = document.createElement("div");
|
|
1990
|
+
Object.assign(main.style, {
|
|
1991
|
+
display: "flex",
|
|
1992
|
+
flexDirection: "column",
|
|
1993
|
+
alignItems: "center",
|
|
1994
|
+
marginTop: "38px",
|
|
1995
|
+
width: "100%"
|
|
1996
|
+
});
|
|
1997
|
+
main.appendChild(
|
|
1998
|
+
createPaymentFallbackMessage({
|
|
1999
|
+
description: options.description,
|
|
2000
|
+
theme,
|
|
2001
|
+
title: options.title
|
|
2002
|
+
})
|
|
2003
|
+
);
|
|
2004
|
+
const actions = document.createElement("div");
|
|
2005
|
+
Object.assign(actions.style, {
|
|
2006
|
+
marginTop: "46px",
|
|
2007
|
+
width: "100%"
|
|
2008
|
+
});
|
|
2009
|
+
const retryButton = createPaymentButton(options.retryText, {
|
|
2010
|
+
icon: "refresh",
|
|
2011
|
+
theme
|
|
2012
|
+
});
|
|
2013
|
+
retryButton.onclick = options.onRetry;
|
|
2014
|
+
actions.appendChild(retryButton);
|
|
2015
|
+
const openButton = createPaymentButton(options.buttonText, {
|
|
2016
|
+
icon: "external",
|
|
2017
|
+
primary: true,
|
|
2018
|
+
theme
|
|
2019
|
+
});
|
|
2020
|
+
openButton.onclick = options.onOpen;
|
|
2021
|
+
actions.appendChild(openButton);
|
|
2022
|
+
main.appendChild(actions);
|
|
2023
|
+
content.appendChild(main);
|
|
2024
|
+
content.appendChild(createPaymentPlatformLogo(options.paymentOptions, theme));
|
|
2025
|
+
panel.appendChild(content);
|
|
2026
|
+
return panel;
|
|
2027
|
+
}
|
|
1107
2028
|
var PaymentUI = class extends HostedFrameModal {
|
|
1108
2029
|
constructor() {
|
|
1109
2030
|
super(...arguments);
|
|
@@ -1112,6 +2033,127 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1112
2033
|
__publicField(this, "activeOrderId", null);
|
|
1113
2034
|
__publicField(this, "paymentCompleted", false);
|
|
1114
2035
|
__publicField(this, "cancelRequestedOrderId", null);
|
|
2036
|
+
__publicField(this, "iframeFallbackPanel", null);
|
|
2037
|
+
__publicField(this, "iframeLoadingPanel", null);
|
|
2038
|
+
__publicField(this, "iframeLoadTimer", null);
|
|
2039
|
+
__publicField(this, "iframeReady", false);
|
|
2040
|
+
}
|
|
2041
|
+
cleanupPaymentFrameState() {
|
|
2042
|
+
if (typeof window !== "undefined" && this.iframeLoadTimer) {
|
|
2043
|
+
window.clearTimeout(this.iframeLoadTimer);
|
|
2044
|
+
}
|
|
2045
|
+
if (this.iframeFallbackPanel?.parentNode) {
|
|
2046
|
+
this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
|
|
2047
|
+
}
|
|
2048
|
+
if (this.iframeLoadingPanel?.parentNode) {
|
|
2049
|
+
this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
|
|
2050
|
+
}
|
|
2051
|
+
this.iframeFallbackPanel = null;
|
|
2052
|
+
this.iframeLoadingPanel = null;
|
|
2053
|
+
this.iframeLoadTimer = null;
|
|
2054
|
+
this.iframeReady = false;
|
|
2055
|
+
}
|
|
2056
|
+
markPaymentIframeReady() {
|
|
2057
|
+
if (this.iframeReady) return;
|
|
2058
|
+
this.iframeReady = true;
|
|
2059
|
+
if (typeof window !== "undefined" && this.iframeLoadTimer) {
|
|
2060
|
+
window.clearTimeout(this.iframeLoadTimer);
|
|
2061
|
+
this.iframeLoadTimer = null;
|
|
2062
|
+
}
|
|
2063
|
+
if (this.iframeFallbackPanel?.parentNode) {
|
|
2064
|
+
this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
|
|
2065
|
+
}
|
|
2066
|
+
if (this.iframeLoadingPanel?.parentNode) {
|
|
2067
|
+
this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
|
|
2068
|
+
}
|
|
2069
|
+
this.iframeFallbackPanel = null;
|
|
2070
|
+
this.iframeLoadingPanel = null;
|
|
2071
|
+
}
|
|
2072
|
+
showPaymentIframeLoading(container, options) {
|
|
2073
|
+
if (this.iframeLoadingPanel || this.iframeReady) return;
|
|
2074
|
+
const panel = createPaymentLoadingPanel(options);
|
|
2075
|
+
this.iframeLoadingPanel = panel;
|
|
2076
|
+
container.appendChild(panel);
|
|
2077
|
+
}
|
|
2078
|
+
openFallbackPaymentPage(finalUrl, options) {
|
|
2079
|
+
if (typeof window === "undefined") return;
|
|
2080
|
+
const popup = window.open(
|
|
2081
|
+
finalUrl,
|
|
2082
|
+
"youidian-payment-checkout",
|
|
2083
|
+
"popup=yes,width=480,height=760,resizable=yes,scrollbars=yes"
|
|
2084
|
+
);
|
|
2085
|
+
if (popup) {
|
|
2086
|
+
logPaymentInfo("Opened hosted checkout fallback page", {
|
|
2087
|
+
checkoutUrl: finalUrl
|
|
2088
|
+
});
|
|
2089
|
+
options?.onFallbackOpen?.(finalUrl);
|
|
2090
|
+
try {
|
|
2091
|
+
popup.focus();
|
|
2092
|
+
} catch {
|
|
2093
|
+
}
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
logPaymentWarn("Hosted checkout fallback popup was blocked", {
|
|
2097
|
+
checkoutUrl: finalUrl
|
|
2098
|
+
});
|
|
2099
|
+
options?.onFallbackBlocked?.(finalUrl);
|
|
2100
|
+
window.location.assign(finalUrl);
|
|
2101
|
+
}
|
|
2102
|
+
showPaymentIframeFallback(options) {
|
|
2103
|
+
if (this.iframeReady || this.iframeFallbackPanel) return;
|
|
2104
|
+
const { container, finalUrl, paymentOptions } = options;
|
|
2105
|
+
logPaymentWarn("Hosted checkout iframe did not report ready in time", {
|
|
2106
|
+
checkoutUrl: finalUrl
|
|
2107
|
+
});
|
|
2108
|
+
if (this.iframeLoadingPanel?.parentNode) {
|
|
2109
|
+
this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
|
|
2110
|
+
}
|
|
2111
|
+
this.iframeLoadingPanel = null;
|
|
2112
|
+
const fallbackText = getPaymentFallbackText(paymentOptions);
|
|
2113
|
+
const panel = createPaymentFallbackPanel({
|
|
2114
|
+
buttonText: fallbackText.buttonText,
|
|
2115
|
+
description: fallbackText.description,
|
|
2116
|
+
onOpen: () => {
|
|
2117
|
+
this.openFallbackPaymentPage(finalUrl, paymentOptions);
|
|
2118
|
+
},
|
|
2119
|
+
onRetry: () => {
|
|
2120
|
+
this.iframeFallbackPanel = null;
|
|
2121
|
+
if (panel.parentNode) {
|
|
2122
|
+
panel.parentNode.removeChild(panel);
|
|
2123
|
+
}
|
|
2124
|
+
this.iframeReady = false;
|
|
2125
|
+
this.showPaymentIframeLoading(container, paymentOptions);
|
|
2126
|
+
if (this.iframe) {
|
|
2127
|
+
this.iframe.src = finalUrl;
|
|
2128
|
+
}
|
|
2129
|
+
this.startPaymentIframeWatchdog({
|
|
2130
|
+
container,
|
|
2131
|
+
finalUrl,
|
|
2132
|
+
paymentOptions
|
|
2133
|
+
});
|
|
2134
|
+
},
|
|
2135
|
+
paymentOptions,
|
|
2136
|
+
retryText: fallbackText.retryText,
|
|
2137
|
+
title: fallbackText.title
|
|
2138
|
+
});
|
|
2139
|
+
this.iframeFallbackPanel = panel;
|
|
2140
|
+
container.appendChild(panel);
|
|
2141
|
+
paymentOptions?.onFallbackVisible?.();
|
|
2142
|
+
}
|
|
2143
|
+
startPaymentIframeWatchdog(options) {
|
|
2144
|
+
if (typeof window === "undefined") return;
|
|
2145
|
+
if (this.iframeLoadTimer) {
|
|
2146
|
+
window.clearTimeout(this.iframeLoadTimer);
|
|
2147
|
+
}
|
|
2148
|
+
const timeoutMs = getPaymentIframeLoadTimeoutMs(options.paymentOptions);
|
|
2149
|
+
if (timeoutMs === 0) {
|
|
2150
|
+
this.showPaymentIframeFallback(options);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
this.iframeLoadTimer = window.setTimeout(() => {
|
|
2154
|
+
this.iframeLoadTimer = null;
|
|
2155
|
+
this.showPaymentIframeFallback(options);
|
|
2156
|
+
}, timeoutMs);
|
|
1115
2157
|
}
|
|
1116
2158
|
cancelHostedOrder(reason = "user_cancel", orderId) {
|
|
1117
2159
|
const targetOrderId = orderId || this.activeOrderId;
|
|
@@ -1154,6 +2196,7 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1154
2196
|
this.activeOrderId = null;
|
|
1155
2197
|
this.paymentCompleted = false;
|
|
1156
2198
|
this.cancelRequestedOrderId = null;
|
|
2199
|
+
this.cleanupPaymentFrameState();
|
|
1157
2200
|
if (typeof urlOrParams === "string") {
|
|
1158
2201
|
checkoutUrl = urlOrParams;
|
|
1159
2202
|
try {
|
|
@@ -1222,14 +2265,21 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1222
2265
|
checkoutUrl,
|
|
1223
2266
|
getAutoResolvedLocale(options?.locale)
|
|
1224
2267
|
);
|
|
1225
|
-
this.openHostedFrame(finalUrl, {
|
|
2268
|
+
const hostedFrame = this.openHostedFrame(finalUrl, {
|
|
1226
2269
|
allowedOrigin: options?.allowedOrigin,
|
|
2270
|
+
height: "min(760px, calc(100vh - 32px))",
|
|
1227
2271
|
onCloseButton: () => {
|
|
1228
2272
|
this.cancelHostedOrder("modal_close");
|
|
1229
2273
|
options?.onCancel?.(this.activeOrderId || void 0);
|
|
1230
2274
|
},
|
|
1231
|
-
onMessage: (data, container) => {
|
|
2275
|
+
onMessage: (data, container, event) => {
|
|
2276
|
+
const isIframeMessage = event.source === this.iframe?.contentWindow;
|
|
2277
|
+
if (isIframeMessage && (data.type === "PAYMENT_READY" || data.type === "PAYMENT_RESIZE" || data.type === "PAYMENT_STARTED")) {
|
|
2278
|
+
this.markPaymentIframeReady();
|
|
2279
|
+
}
|
|
1232
2280
|
switch (data.type) {
|
|
2281
|
+
case "PAYMENT_READY":
|
|
2282
|
+
break;
|
|
1233
2283
|
case "PAYMENT_STARTED":
|
|
1234
2284
|
if (data.orderId) {
|
|
1235
2285
|
this.activeOrderId = data.orderId;
|
|
@@ -1244,11 +2294,6 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1244
2294
|
options?.onCancel?.(data.orderId);
|
|
1245
2295
|
break;
|
|
1246
2296
|
case "PAYMENT_RESIZE":
|
|
1247
|
-
if (data.height) {
|
|
1248
|
-
const maxHeight = window.innerHeight * 0.9;
|
|
1249
|
-
const newHeight = Math.min(data.height, maxHeight);
|
|
1250
|
-
container.style.height = `${newHeight}px`;
|
|
1251
|
-
}
|
|
1252
2297
|
break;
|
|
1253
2298
|
case "PAYMENT_CLOSE":
|
|
1254
2299
|
this.cancelHostedOrder("payment_close", data.orderId);
|
|
@@ -1256,8 +2301,21 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1256
2301
|
options?.onClose?.();
|
|
1257
2302
|
break;
|
|
1258
2303
|
}
|
|
1259
|
-
}
|
|
2304
|
+
},
|
|
2305
|
+
width: "min(450px, 100%)"
|
|
1260
2306
|
});
|
|
2307
|
+
if (hostedFrame) {
|
|
2308
|
+
this.showPaymentIframeLoading(hostedFrame.container, options);
|
|
2309
|
+
this.startPaymentIframeWatchdog({
|
|
2310
|
+
container: hostedFrame.container,
|
|
2311
|
+
finalUrl,
|
|
2312
|
+
paymentOptions: options
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
close() {
|
|
2317
|
+
super.close();
|
|
2318
|
+
this.cleanupPaymentFrameState();
|
|
1261
2319
|
}
|
|
1262
2320
|
/**
|
|
1263
2321
|
* Poll order status from integrator's API endpoint
|
|
@@ -1303,10 +2361,685 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1303
2361
|
});
|
|
1304
2362
|
}
|
|
1305
2363
|
};
|
|
2364
|
+
var MessageUI = class {
|
|
2365
|
+
openWechatBinding(options) {
|
|
2366
|
+
const bindingUrl = options.bindingUrl?.trim();
|
|
2367
|
+
if (!bindingUrl) {
|
|
2368
|
+
throw new Error("bindingUrl is required");
|
|
2369
|
+
}
|
|
2370
|
+
if (options.displayMode === "popup") {
|
|
2371
|
+
const popup = window.open(
|
|
2372
|
+
bindingUrl,
|
|
2373
|
+
options.popupName || "youidian-wechat-message-binding",
|
|
2374
|
+
"width=480,height=720,noopener,noreferrer"
|
|
2375
|
+
);
|
|
2376
|
+
if (!popup) {
|
|
2377
|
+
window.location.href = bindingUrl;
|
|
2378
|
+
}
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
window.location.href = bindingUrl;
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
function createMessageUI() {
|
|
2385
|
+
return new MessageUI();
|
|
2386
|
+
}
|
|
1306
2387
|
function createPaymentUI() {
|
|
1307
2388
|
return new PaymentUI();
|
|
1308
2389
|
}
|
|
1309
2390
|
|
|
2391
|
+
// src/feedback.ts
|
|
2392
|
+
var DEFAULT_CATEGORIES = [
|
|
2393
|
+
{ value: "BUG", label: "\u95EE\u9898\u53CD\u9988" },
|
|
2394
|
+
{ value: "PAYMENT", label: "\u652F\u4ED8\u95EE\u9898" },
|
|
2395
|
+
{ value: "LOGIN", label: "\u767B\u5F55\u95EE\u9898" },
|
|
2396
|
+
{ value: "SUGGESTION", label: "\u529F\u80FD\u5EFA\u8BAE" },
|
|
2397
|
+
{ value: "OTHER", label: "\u5176\u4ED6" }
|
|
2398
|
+
];
|
|
2399
|
+
var STYLE_ID = "youidian-feedback-widget-style";
|
|
2400
|
+
var MAX_IMAGES_PER_MESSAGE = 4;
|
|
2401
|
+
var DEFAULT_PANEL_TITLE = "\u53CD\u9988";
|
|
2402
|
+
var DEFAULT_PANEL_SUBTITLE = "\u63D0\u4EA4\u95EE\u9898\u3001\u5EFA\u8BAE\u6216\u4F7F\u7528\u53CD\u9988\u3002";
|
|
2403
|
+
var DEFAULT_ATTACHMENTS_LABEL = "\u652F\u6301\u7C98\u8D34\u3001\u62D6\u5165\u6216\u4E0A\u4F20\u622A\u56FE\uFF0C\u6700\u591A 4 \u5F20";
|
|
2404
|
+
var FEEDBACK_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.6 8.6 0 0 1-7.7 4.7 8.5 8.5 0 0 1-3.8-.9L3 21l1.9-5.2a8.5 8.5 0 0 1-1-4.1 8.6 8.6 0 0 1 8.6-8.6 8.5 8.5 0 0 1 8.5 8.4Z" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/><path d="M8.4 10.2h7.2M8.4 13.5h4.7" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>`;
|
|
2405
|
+
var BUG_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 8.5a4 4 0 0 1 8 0v6a4 4 0 0 1-8 0v-6Z" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M5 9h3m8 0h3M5.5 15H8m8 0h2.5M7 5.5 5.5 4M17 5.5 18.5 4M12 4.5V2.8M12 20.5v-12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>`;
|
|
2406
|
+
var NOTE_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 3.5h7l3.5 3.5v13.5H7A2.5 2.5 0 0 1 4.5 18V6A2.5 2.5 0 0 1 7 3.5Z" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M14 3.8V7h3.2M8 11h8M8 15h6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>`;
|
|
2407
|
+
var MAIL_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4.5 6.5h15v11h-15v-11Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="m5 7 7 6 7-6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
2408
|
+
var IMAGE_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 5h14v14H5V5Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="m7.5 16 3.2-3.2 2.4 2.4 1.7-1.7L18 16.8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.8 8.2h.01" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"/></svg>`;
|
|
2409
|
+
var CHEVRON_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m7 10 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
2410
|
+
function ensureStyle() {
|
|
2411
|
+
if (typeof document === "undefined") return;
|
|
2412
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
2413
|
+
const style = document.createElement("style");
|
|
2414
|
+
style.id = STYLE_ID;
|
|
2415
|
+
style.textContent = `
|
|
2416
|
+
.yd-feedback-root,.yd-feedback-root *{box-sizing:border-box}
|
|
2417
|
+
.yd-feedback-root{--yd-feedback-bg:hsl(var(--background,0 0% 100%));--yd-feedback-fg:hsl(var(--foreground,240 10% 3.9%));--yd-feedback-card:hsl(var(--card,0 0% 100%));--yd-feedback-card-fg:hsl(var(--card-foreground,240 10% 3.9%));--yd-feedback-muted:hsl(var(--muted,240 4.8% 95.9%));--yd-feedback-muted-fg:hsl(var(--muted-foreground,240 3.8% 46.1%));--yd-feedback-border:hsl(var(--border,240 5.9% 90%));--yd-feedback-primary:hsl(var(--primary,142 76% 36%));--yd-feedback-primary-fg:hsl(var(--primary-foreground,0 0% 100%));--yd-feedback-ring:hsl(var(--ring,142 76% 36%));font:14px/1.5 Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:var(--yd-feedback-fg)}
|
|
2418
|
+
.yd-feedback-root button,.yd-feedback-root input,.yd-feedback-root textarea{font:inherit}
|
|
2419
|
+
.yd-feedback-launcher{position:fixed;right:22px;bottom:22px;z-index:2147483000;width:54px;height:54px;display:grid;place-items:center;border:1px solid color-mix(in srgb,var(--yd-feedback-primary) 24%,var(--yd-feedback-border));border-radius:999px;background:var(--yd-feedback-primary);color:var(--yd-feedback-primary-fg);box-shadow:0 18px 45px color-mix(in srgb,var(--yd-feedback-primary) 22%,transparent);cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,background .18s ease}
|
|
2420
|
+
.yd-feedback-launcher:hover{transform:translateY(-2px);box-shadow:0 22px 56px color-mix(in srgb,var(--yd-feedback-primary) 30%,transparent)}
|
|
2421
|
+
.yd-feedback-launcher:focus-visible{outline:3px solid color-mix(in srgb,var(--yd-feedback-ring) 32%,transparent);outline-offset:3px}
|
|
2422
|
+
.yd-feedback-launcher svg{width:27px;height:27px}
|
|
2423
|
+
.yd-feedback-panel{position:fixed;inset:auto 22px 86px auto;z-index:2147483000;width:min(760px,calc(100vw - 44px));height:min(560px,calc(100vh - 108px));display:none;grid-template-rows:auto minmax(0,1fr);overflow:hidden;border:1px solid var(--yd-feedback-border);border-radius:18px;background:var(--yd-feedback-card);color:var(--yd-feedback-card-fg);box-shadow:0 24px 70px rgba(15,23,42,.18)}
|
|
2424
|
+
.yd-feedback-panel[data-open="true"]{display:grid}
|
|
2425
|
+
.yd-feedback-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:18px 20px 16px;border-bottom:1px solid var(--yd-feedback-border);background:var(--yd-feedback-card)}
|
|
2426
|
+
.yd-feedback-title{margin:0;font-size:22px;line-height:1.15;font-weight:760;letter-spacing:0;color:var(--yd-feedback-fg)}
|
|
2427
|
+
.yd-feedback-subtitle{margin:8px 0 0;color:var(--yd-feedback-muted-fg);font-size:13px;line-height:1.45}
|
|
2428
|
+
.yd-feedback-close{display:grid;place-items:center;width:34px;height:34px;border:0;border-radius:10px;background:transparent;color:var(--yd-feedback-muted-fg);cursor:pointer;transition:background .16s ease,color .16s ease}
|
|
2429
|
+
.yd-feedback-close:hover{background:var(--yd-feedback-muted);color:var(--yd-feedback-fg)}
|
|
2430
|
+
.yd-feedback-close svg{width:24px;height:24px}
|
|
2431
|
+
.yd-feedback-shell{display:grid;min-height:0;grid-template-columns:230px minmax(0,1fr)}
|
|
2432
|
+
.yd-feedback-sidebar{min-width:0;border-right:1px solid var(--yd-feedback-border);background:var(--yd-feedback-muted)}
|
|
2433
|
+
.yd-feedback-sidebar-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px 14px;border-bottom:1px solid var(--yd-feedback-border)}
|
|
2434
|
+
.yd-feedback-sidebar-title{display:flex;align-items:center;gap:9px;color:var(--yd-feedback-fg);font-size:15px;font-weight:720}
|
|
2435
|
+
.yd-feedback-sidebar-title svg{width:20px;height:20px;color:var(--yd-feedback-primary)}
|
|
2436
|
+
.yd-feedback-icon-button{display:grid;place-items:center;width:34px;height:34px;border:1px solid var(--yd-feedback-border);border-radius:999px;background:var(--yd-feedback-card);color:var(--yd-feedback-fg);cursor:pointer;transition:background .16s ease,border-color .16s ease}
|
|
2437
|
+
.yd-feedback-icon-button:hover{background:color-mix(in srgb,var(--yd-feedback-primary) 8%,var(--yd-feedback-card));border-color:color-mix(in srgb,var(--yd-feedback-primary) 38%,var(--yd-feedback-border))}
|
|
2438
|
+
.yd-feedback-icon-button svg{width:18px;height:18px}
|
|
2439
|
+
.yd-feedback-thread-list{padding:12px}
|
|
2440
|
+
.yd-feedback-thread-card{display:none;border:1px solid var(--yd-feedback-border);border-radius:14px;background:var(--yd-feedback-card);padding:12px}
|
|
2441
|
+
.yd-feedback-thread-card[data-visible="true"]{display:block}
|
|
2442
|
+
.yd-feedback-thread-badges{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:10px}
|
|
2443
|
+
.yd-feedback-badge{display:inline-flex;align-items:center;border:1px solid var(--yd-feedback-border);border-radius:999px;background:var(--yd-feedback-muted);color:var(--yd-feedback-fg);padding:4px 9px;font-size:11px;font-weight:700;line-height:1}
|
|
2444
|
+
.yd-feedback-badge[data-status="true"]{border-color:color-mix(in srgb,var(--yd-feedback-primary) 45%,var(--yd-feedback-border));background:color-mix(in srgb,var(--yd-feedback-primary) 12%,var(--yd-feedback-card));color:var(--yd-feedback-primary)}
|
|
2445
|
+
.yd-feedback-thread-preview{margin:0;color:var(--yd-feedback-fg);font-size:13px;line-height:1.45;word-break:break-word}
|
|
2446
|
+
.yd-feedback-thread-meta{display:flex;justify-content:space-between;gap:10px;margin-top:10px;color:var(--yd-feedback-muted-fg);font-size:11px}
|
|
2447
|
+
.yd-feedback-empty-thread{border:1px dashed var(--yd-feedback-border);border-radius:14px;padding:14px;color:var(--yd-feedback-muted-fg);font-size:12px}
|
|
2448
|
+
.yd-feedback-main{min-width:0;min-height:0;background:var(--yd-feedback-card);overflow:hidden}
|
|
2449
|
+
.yd-feedback-form{height:100%;display:grid;grid-template-rows:auto auto auto minmax(0,1fr) auto auto;gap:14px;padding:16px 18px 0}
|
|
2450
|
+
.yd-feedback-row{display:grid;gap:14px;grid-template-columns:180px minmax(0,1fr)}
|
|
2451
|
+
.yd-feedback-field{display:flex;min-width:0;flex-direction:column;gap:8px}
|
|
2452
|
+
.yd-feedback-label{display:flex;align-items:center;gap:8px;color:var(--yd-feedback-fg);font-size:13px;font-weight:720}
|
|
2453
|
+
.yd-feedback-label svg{width:17px;height:17px;color:var(--yd-feedback-fg)}
|
|
2454
|
+
.yd-feedback-input,.yd-feedback-textarea{width:100%;border:1px solid var(--yd-feedback-border);border-radius:13px;background:var(--yd-feedback-card);color:var(--yd-feedback-fg);outline:none;transition:border-color .16s ease,box-shadow .16s ease,background .16s ease}
|
|
2455
|
+
.yd-feedback-input{height:40px;padding:0 13px;font-size:14px}
|
|
2456
|
+
.yd-feedback-textarea{min-height:118px;padding:13px 14px;font-size:14px;resize:none}
|
|
2457
|
+
.yd-feedback-input::placeholder,.yd-feedback-textarea::placeholder{color:var(--yd-feedback-muted-fg)}
|
|
2458
|
+
.yd-feedback-input:focus,.yd-feedback-textarea:focus{border-color:var(--yd-feedback-ring);box-shadow:0 0 0 3px color-mix(in srgb,var(--yd-feedback-ring) 18%,transparent);background:var(--yd-feedback-card)}
|
|
2459
|
+
.yd-feedback-compose-box{overflow:hidden;border:1px solid var(--yd-feedback-border);border-radius:15px;background:var(--yd-feedback-card)}
|
|
2460
|
+
.yd-feedback-compose-box .yd-feedback-textarea{border:0;border-radius:0;box-shadow:none;background:transparent}
|
|
2461
|
+
.yd-feedback-attachment-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--yd-feedback-border);padding:9px 12px;color:var(--yd-feedback-muted-fg)}
|
|
2462
|
+
.yd-feedback-file-trigger{display:inline-flex;align-items:center;gap:8px;border:0;background:transparent;color:var(--yd-feedback-fg);cursor:pointer;padding:7px 8px;border-radius:10px}
|
|
2463
|
+
.yd-feedback-file-trigger:hover{background:var(--yd-feedback-muted)}
|
|
2464
|
+
.yd-feedback-file-trigger svg{width:18px;height:18px}
|
|
2465
|
+
.yd-feedback-file-input{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;clip-path:inset(50%)}
|
|
2466
|
+
.yd-feedback-files{min-width:0;text-align:right;color:var(--yd-feedback-muted-fg);font-size:12px}
|
|
2467
|
+
.yd-feedback-select{position:relative;width:142px}
|
|
2468
|
+
.yd-feedback-select-button{width:100%;height:40px;display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid var(--yd-feedback-border);border-radius:13px;background:var(--yd-feedback-card);color:var(--yd-feedback-fg);padding:0 12px;cursor:pointer}
|
|
2469
|
+
.yd-feedback-select-value{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700}
|
|
2470
|
+
.yd-feedback-select-value svg{width:18px;height:18px;color:var(--yd-feedback-muted-fg)}
|
|
2471
|
+
.yd-feedback-select-chevron{width:17px;height:17px;color:var(--yd-feedback-muted-fg)}
|
|
2472
|
+
.yd-feedback-select-menu{position:absolute;left:0;top:calc(100% + 7px);z-index:2;display:none;min-width:176px;max-height:220px;overflow:auto;border:1px solid var(--yd-feedback-border);border-radius:13px;background:var(--yd-feedback-card);box-shadow:0 18px 45px rgba(15,23,42,.16);padding:5px}
|
|
2473
|
+
.yd-feedback-select[data-open="true"] .yd-feedback-select-menu{display:block}
|
|
2474
|
+
.yd-feedback-select-option{width:100%;min-height:38px;display:flex;align-items:center;gap:8px;border:0;border-radius:10px;background:transparent;color:var(--yd-feedback-fg);text-align:left;padding:9px 10px;cursor:pointer}
|
|
2475
|
+
.yd-feedback-select-option svg{width:18px;height:18px;flex:0 0 18px;color:var(--yd-feedback-muted-fg)}
|
|
2476
|
+
.yd-feedback-select-option span{line-height:1.2;white-space:nowrap}
|
|
2477
|
+
.yd-feedback-select-option:hover,.yd-feedback-select-option[data-selected="true"]{background:color-mix(in srgb,var(--yd-feedback-primary) 10%,var(--yd-feedback-card));color:var(--yd-feedback-fg)}
|
|
2478
|
+
.yd-feedback-messages{display:flex;min-height:0;flex-direction:column;gap:8px;overflow:auto}
|
|
2479
|
+
.yd-feedback-message{border:1px solid var(--yd-feedback-border);border-radius:12px;padding:9px 10px;background:var(--yd-feedback-muted)}
|
|
2480
|
+
.yd-feedback-message[data-admin="true"]{background:color-mix(in srgb,var(--yd-feedback-primary) 10%,var(--yd-feedback-card));border-color:color-mix(in srgb,var(--yd-feedback-primary) 24%,var(--yd-feedback-border))}
|
|
2481
|
+
.yd-feedback-message-meta{margin-bottom:4px;color:var(--yd-feedback-muted-fg);font-size:11px}
|
|
2482
|
+
.yd-feedback-message-content{color:var(--yd-feedback-fg);font-size:12px;line-height:1.45;word-break:break-word}
|
|
2483
|
+
.yd-feedback-actions{position:sticky;bottom:0;display:flex;align-items:center;justify-content:flex-end;gap:12px;margin:0 -18px;padding:12px 18px 16px;border-top:1px solid var(--yd-feedback-border);background:var(--yd-feedback-card)}
|
|
2484
|
+
.yd-feedback-submit{min-width:104px;height:44px;border:0;border-radius:13px;background:var(--yd-feedback-primary);color:var(--yd-feedback-primary-fg);padding:0 18px;font-size:14px;font-weight:800;cursor:pointer;box-shadow:0 12px 24px color-mix(in srgb,var(--yd-feedback-primary) 18%,transparent);transition:background .16s ease,transform .16s ease}
|
|
2485
|
+
.yd-feedback-submit:hover{transform:translateY(-1px)}
|
|
2486
|
+
.yd-feedback-submit:disabled{opacity:.55;cursor:not-allowed;transform:none}
|
|
2487
|
+
.yd-feedback-secondary{height:38px;border:0;border-radius:12px;background:transparent;color:var(--yd-feedback-fg);padding:0 12px;font-size:14px;font-weight:760;cursor:pointer}
|
|
2488
|
+
.yd-feedback-secondary:hover{background:var(--yd-feedback-muted)}
|
|
2489
|
+
.yd-feedback-error{min-height:18px;color:#fb7185;font-size:13px}
|
|
2490
|
+
@media (max-width:760px){
|
|
2491
|
+
.yd-feedback-panel{inset:auto 14px 78px 14px;width:auto;height:min(560px,calc(100vh - 96px));border-radius:16px}
|
|
2492
|
+
.yd-feedback-header{padding:16px 16px 14px}
|
|
2493
|
+
.yd-feedback-title{font-size:20px}
|
|
2494
|
+
.yd-feedback-shell{grid-template-columns:1fr}
|
|
2495
|
+
.yd-feedback-sidebar{display:none}
|
|
2496
|
+
.yd-feedback-form{padding:14px 14px 0;gap:12px}
|
|
2497
|
+
.yd-feedback-row{grid-template-columns:1fr}
|
|
2498
|
+
.yd-feedback-actions{justify-content:space-between}
|
|
2499
|
+
}
|
|
2500
|
+
`;
|
|
2501
|
+
document.head.appendChild(style);
|
|
2502
|
+
}
|
|
2503
|
+
function getBaseUrl(options) {
|
|
2504
|
+
return (options.apiBaseUrl || "https://pay.imgto.link").replace(/\/$/, "");
|
|
2505
|
+
}
|
|
2506
|
+
function safeJson(value) {
|
|
2507
|
+
try {
|
|
2508
|
+
return JSON.stringify(value);
|
|
2509
|
+
} catch {
|
|
2510
|
+
return "{}";
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
function escapeHtml(value) {
|
|
2514
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2515
|
+
}
|
|
2516
|
+
function getInitialContact(actor) {
|
|
2517
|
+
return actor?.contactEmail || actor?.contactPhone || "";
|
|
2518
|
+
}
|
|
2519
|
+
function formatCategoryLabel(value, categories = DEFAULT_CATEGORIES) {
|
|
2520
|
+
const matched = categories.find((item) => item.value === value);
|
|
2521
|
+
return matched?.label || value || DEFAULT_CATEGORIES[0]?.label || "\u53CD\u9988";
|
|
2522
|
+
}
|
|
2523
|
+
function formatThreadStatus(value) {
|
|
2524
|
+
const statusMap = {
|
|
2525
|
+
OPEN: "\u5DF2\u63D0\u4EA4",
|
|
2526
|
+
WAITING_ADMIN: "\u7B49\u5F85\u56DE\u590D",
|
|
2527
|
+
WAITING_USER: "\u5F85\u8865\u5145",
|
|
2528
|
+
RESOLVED: "\u5DF2\u5904\u7406",
|
|
2529
|
+
CLOSED: "\u5DF2\u5173\u95ED"
|
|
2530
|
+
};
|
|
2531
|
+
return statusMap[value || ""] || value || "\u5DF2\u63D0\u4EA4";
|
|
2532
|
+
}
|
|
2533
|
+
function formatSenderType(value) {
|
|
2534
|
+
if (value === "END_USER") return "\u7528\u6237";
|
|
2535
|
+
if (value === "ADMIN") return "\u7BA1\u7406\u5458";
|
|
2536
|
+
return "\u7CFB\u7EDF";
|
|
2537
|
+
}
|
|
2538
|
+
function formatThreadTime(value) {
|
|
2539
|
+
if (!value) return "";
|
|
2540
|
+
return new Date(value).toLocaleString();
|
|
2541
|
+
}
|
|
2542
|
+
async function requestJson(url, init) {
|
|
2543
|
+
const response = await fetch(url, {
|
|
2544
|
+
...init,
|
|
2545
|
+
headers: {
|
|
2546
|
+
Accept: "application/json",
|
|
2547
|
+
...init?.body ? { "Content-Type": "application/json" } : {},
|
|
2548
|
+
...init?.headers || {}
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2551
|
+
const payload = await response.json().catch(() => null);
|
|
2552
|
+
if (!response.ok || payload?.code !== 200) {
|
|
2553
|
+
throw new Error(payload?.message || response.statusText || "Request failed");
|
|
2554
|
+
}
|
|
2555
|
+
return payload.data;
|
|
2556
|
+
}
|
|
2557
|
+
function collectMetadata(options) {
|
|
2558
|
+
const page = typeof window === "undefined" ? {} : {
|
|
2559
|
+
pageUrl: window.location.href,
|
|
2560
|
+
pageTitle: document.title,
|
|
2561
|
+
userAgent: navigator.userAgent
|
|
2562
|
+
};
|
|
2563
|
+
return { ...page, ...options.metadata || {} };
|
|
2564
|
+
}
|
|
2565
|
+
async function compressImage(file) {
|
|
2566
|
+
if (typeof document === "undefined" || !file.type.startsWith("image/")) {
|
|
2567
|
+
return file;
|
|
2568
|
+
}
|
|
2569
|
+
const bitmap = await createImageBitmap(file);
|
|
2570
|
+
const maxSize = 1600;
|
|
2571
|
+
const scale = Math.min(1, maxSize / Math.max(bitmap.width, bitmap.height));
|
|
2572
|
+
const canvas = document.createElement("canvas");
|
|
2573
|
+
canvas.width = Math.max(1, Math.round(bitmap.width * scale));
|
|
2574
|
+
canvas.height = Math.max(1, Math.round(bitmap.height * scale));
|
|
2575
|
+
const ctx = canvas.getContext("2d");
|
|
2576
|
+
if (!ctx) return file;
|
|
2577
|
+
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
|
2578
|
+
const blob = await new Promise((resolve) => {
|
|
2579
|
+
canvas.toBlob(resolve, "image/webp", 0.82);
|
|
2580
|
+
});
|
|
2581
|
+
if (!blob) return file;
|
|
2582
|
+
return new File([blob], file.name.replace(/\.[^.]+$/, ".webp"), {
|
|
2583
|
+
type: "image/webp",
|
|
2584
|
+
lastModified: Date.now()
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
function createFeedbackWidget(container, options) {
|
|
2588
|
+
if (typeof document === "undefined") {
|
|
2589
|
+
return { open() {
|
|
2590
|
+
}, close() {
|
|
2591
|
+
}, destroy() {
|
|
2592
|
+
} };
|
|
2593
|
+
}
|
|
2594
|
+
if (!options.appId) throw new Error("appId is required");
|
|
2595
|
+
ensureStyle();
|
|
2596
|
+
let threadState = null;
|
|
2597
|
+
let pollTimer = null;
|
|
2598
|
+
let panelReady = false;
|
|
2599
|
+
let pendingFiles = [];
|
|
2600
|
+
let renderedMessages = [];
|
|
2601
|
+
let removeLoadListener = null;
|
|
2602
|
+
const root = document.createElement("div");
|
|
2603
|
+
root.className = "yd-feedback-root";
|
|
2604
|
+
const launcher = document.createElement("button");
|
|
2605
|
+
launcher.type = "button";
|
|
2606
|
+
launcher.className = "yd-feedback-launcher";
|
|
2607
|
+
launcher.setAttribute("aria-label", options.launcherText || "\u53CD\u9988");
|
|
2608
|
+
launcher.innerHTML = FEEDBACK_ICON;
|
|
2609
|
+
root.appendChild(launcher);
|
|
2610
|
+
container.appendChild(root);
|
|
2611
|
+
const panel = document.createElement("section");
|
|
2612
|
+
panel.className = "yd-feedback-panel";
|
|
2613
|
+
panel.setAttribute("aria-label", options.panelTitle || DEFAULT_PANEL_TITLE);
|
|
2614
|
+
panel.innerHTML = `
|
|
2615
|
+
<div class="yd-feedback-header">
|
|
2616
|
+
<div>
|
|
2617
|
+
<h2 class="yd-feedback-title">${escapeHtml(options.panelTitle || DEFAULT_PANEL_TITLE)}</h2>
|
|
2618
|
+
<p class="yd-feedback-subtitle">${escapeHtml(options.panelSubtitle || DEFAULT_PANEL_SUBTITLE)}</p>
|
|
2619
|
+
</div>
|
|
2620
|
+
<button type="button" class="yd-feedback-close" aria-label="\u5173\u95ED">
|
|
2621
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
|
|
2622
|
+
</button>
|
|
2623
|
+
</div>
|
|
2624
|
+
<div class="yd-feedback-shell">
|
|
2625
|
+
<aside class="yd-feedback-sidebar">
|
|
2626
|
+
<div class="yd-feedback-sidebar-toolbar">
|
|
2627
|
+
<div class="yd-feedback-sidebar-title">${FEEDBACK_ICON}<span>\u53CD\u9988\u8BB0\u5F55</span></div>
|
|
2628
|
+
<button type="button" class="yd-feedback-icon-button" data-role="clear" aria-label="\u65B0\u5EFA\u53CD\u9988">
|
|
2629
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
|
|
2630
|
+
</button>
|
|
2631
|
+
</div>
|
|
2632
|
+
<div class="yd-feedback-thread-list">
|
|
2633
|
+
<div class="yd-feedback-thread-card" data-role="thread-card">
|
|
2634
|
+
<div class="yd-feedback-thread-badges">
|
|
2635
|
+
<span class="yd-feedback-badge" data-role="thread-category">\u95EE\u9898\u53CD\u9988</span>
|
|
2636
|
+
<span class="yd-feedback-badge" data-status="true" data-role="thread-status">\u5DF2\u63D0\u4EA4</span>
|
|
2637
|
+
</div>
|
|
2638
|
+
<p class="yd-feedback-thread-preview" data-role="thread-preview"></p>
|
|
2639
|
+
<div class="yd-feedback-thread-meta">
|
|
2640
|
+
<span data-role="thread-time"></span>
|
|
2641
|
+
<span>\u6211\u7684\u53CD\u9988</span>
|
|
2642
|
+
</div>
|
|
2643
|
+
</div>
|
|
2644
|
+
<div class="yd-feedback-empty-thread" data-role="thread-empty">\u63D0\u4EA4\u53CD\u9988\u540E\uFF0C\u8BB0\u5F55\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002</div>
|
|
2645
|
+
</div>
|
|
2646
|
+
</aside>
|
|
2647
|
+
<div class="yd-feedback-main">
|
|
2648
|
+
<div class="yd-feedback-form">
|
|
2649
|
+
<div class="yd-feedback-row">
|
|
2650
|
+
<div class="yd-feedback-field">
|
|
2651
|
+
<label class="yd-feedback-label">\u53CD\u9988\u7C7B\u578B</label>
|
|
2652
|
+
<div class="yd-feedback-select" data-role="category-select">
|
|
2653
|
+
<button type="button" class="yd-feedback-select-button" data-role="category-trigger">
|
|
2654
|
+
<span class="yd-feedback-select-value">${BUG_ICON}<span data-role="category-value">\u95EE\u9898\u53CD\u9988</span></span>
|
|
2655
|
+
<span class="yd-feedback-select-chevron">${CHEVRON_ICON}</span>
|
|
2656
|
+
</button>
|
|
2657
|
+
<div class="yd-feedback-select-menu" data-role="category-menu"></div>
|
|
2658
|
+
</div>
|
|
2659
|
+
</div>
|
|
2660
|
+
</div>
|
|
2661
|
+
<div class="yd-feedback-field">
|
|
2662
|
+
<label class="yd-feedback-label">${NOTE_ICON}<span>\u53CD\u9988\u5185\u5BB9</span></label>
|
|
2663
|
+
<div class="yd-feedback-compose-box">
|
|
2664
|
+
<textarea class="yd-feedback-textarea" data-role="content" placeholder="${escapeHtml(options.placeholder || "\u8BF7\u63CF\u8FF0\u95EE\u9898\u73B0\u8C61\u3001\u64CD\u4F5C\u6B65\u9AA4\u6216\u5EFA\u8BAE\u5185\u5BB9\u3002")}"></textarea>
|
|
2665
|
+
<div class="yd-feedback-attachment-row">
|
|
2666
|
+
<button type="button" class="yd-feedback-file-trigger" data-role="files-trigger">${IMAGE_ICON}<span>\u4E0A\u4F20\u56FE\u7247</span></button>
|
|
2667
|
+
<input class="yd-feedback-file-input" type="file" accept="image/png,image/jpeg,image/webp" multiple data-role="files" />
|
|
2668
|
+
<div class="yd-feedback-files" data-role="files-label">${DEFAULT_ATTACHMENTS_LABEL}</div>
|
|
2669
|
+
</div>
|
|
2670
|
+
</div>
|
|
2671
|
+
</div>
|
|
2672
|
+
<div class="yd-feedback-field">
|
|
2673
|
+
<label class="yd-feedback-label">${MAIL_ICON}<span>\u8054\u7CFB\u65B9\u5F0F\uFF08\u9009\u586B\uFF09</span></label>
|
|
2674
|
+
<input class="yd-feedback-input" data-role="contact" value="${escapeHtml(getInitialContact(options.actor))}" placeholder="${escapeHtml(options.contactPlaceholder || "\u90AE\u7BB1\u6216\u7535\u8BDD\u53F7\u7801\uFF0C\u4EC5\u7528\u4E8E\u53CD\u9988\u8DDF\u8FDB")}" />
|
|
2675
|
+
</div>
|
|
2676
|
+
<div class="yd-feedback-messages" data-role="messages"></div>
|
|
2677
|
+
<div class="yd-feedback-error" data-role="error"></div>
|
|
2678
|
+
<div class="yd-feedback-actions">
|
|
2679
|
+
<button type="button" class="yd-feedback-secondary" data-role="clear-secondary">\u5173\u95ED</button>
|
|
2680
|
+
<button type="button" class="yd-feedback-submit" data-role="submit">\u63D0\u4EA4\u53CD\u9988</button>
|
|
2681
|
+
</div>
|
|
2682
|
+
</div>
|
|
2683
|
+
</div>
|
|
2684
|
+
</div>
|
|
2685
|
+
`;
|
|
2686
|
+
function initPanel() {
|
|
2687
|
+
if (panelReady) return;
|
|
2688
|
+
panelReady = true;
|
|
2689
|
+
root.appendChild(panel);
|
|
2690
|
+
const categorySelect = panel.querySelector(
|
|
2691
|
+
'[data-role="category-select"]'
|
|
2692
|
+
);
|
|
2693
|
+
const categoryMenu = panel.querySelector(
|
|
2694
|
+
'[data-role="category-menu"]'
|
|
2695
|
+
);
|
|
2696
|
+
const categoryValue = panel.querySelector(
|
|
2697
|
+
'[data-role="category-value"]'
|
|
2698
|
+
);
|
|
2699
|
+
if (categoryMenu && categoryValue) {
|
|
2700
|
+
const categories = options.categories || DEFAULT_CATEGORIES;
|
|
2701
|
+
categoryValue.textContent = categories[0]?.label || DEFAULT_CATEGORIES[0]?.label || "\u53CD\u9988";
|
|
2702
|
+
for (const item of categories) {
|
|
2703
|
+
const option = document.createElement("button");
|
|
2704
|
+
option.type = "button";
|
|
2705
|
+
option.className = "yd-feedback-select-option";
|
|
2706
|
+
option.dataset.value = item.value;
|
|
2707
|
+
option.dataset.selected = String(item.value === categories[0]?.value);
|
|
2708
|
+
option.innerHTML = `${BUG_ICON}<span>${escapeHtml(item.label)}</span>`;
|
|
2709
|
+
option.addEventListener("click", () => {
|
|
2710
|
+
for (const node of categoryMenu.querySelectorAll(
|
|
2711
|
+
".yd-feedback-select-option"
|
|
2712
|
+
)) {
|
|
2713
|
+
node.dataset.selected = String(node === option);
|
|
2714
|
+
}
|
|
2715
|
+
categoryValue.textContent = item.label;
|
|
2716
|
+
if (categorySelect) {
|
|
2717
|
+
categorySelect.dataset.value = item.value;
|
|
2718
|
+
categorySelect.dataset.open = "false";
|
|
2719
|
+
}
|
|
2720
|
+
});
|
|
2721
|
+
categoryMenu.appendChild(option);
|
|
2722
|
+
}
|
|
2723
|
+
categorySelect?.setAttribute("data-value", categories[0]?.value || "BUG");
|
|
2724
|
+
}
|
|
2725
|
+
panel.querySelector('[data-role="category-trigger"]')?.addEventListener("click", () => {
|
|
2726
|
+
if (!categorySelect) return;
|
|
2727
|
+
categorySelect.dataset.open = categorySelect.dataset.open === "true" ? "false" : "true";
|
|
2728
|
+
});
|
|
2729
|
+
panel.querySelector(".yd-feedback-close")?.addEventListener("click", close);
|
|
2730
|
+
panel.querySelector('[data-role="clear"]')?.addEventListener("click", resetThread);
|
|
2731
|
+
panel.querySelector('[data-role="clear-secondary"]')?.addEventListener("click", close);
|
|
2732
|
+
panel.querySelector('[data-role="files-trigger"]')?.addEventListener("click", () => {
|
|
2733
|
+
panel.querySelector('[data-role="files"]')?.click();
|
|
2734
|
+
});
|
|
2735
|
+
panel.querySelector('[data-role="files"]')?.addEventListener("change", (event) => {
|
|
2736
|
+
setPendingFiles(event.target.files);
|
|
2737
|
+
});
|
|
2738
|
+
const composeBox = panel.querySelector(
|
|
2739
|
+
".yd-feedback-compose-box"
|
|
2740
|
+
);
|
|
2741
|
+
composeBox?.addEventListener("dragover", (event) => {
|
|
2742
|
+
event.preventDefault();
|
|
2743
|
+
});
|
|
2744
|
+
composeBox?.addEventListener("drop", (event) => {
|
|
2745
|
+
event.preventDefault();
|
|
2746
|
+
setPendingFiles(event.dataTransfer?.files || null);
|
|
2747
|
+
});
|
|
2748
|
+
composeBox?.addEventListener("paste", (event) => {
|
|
2749
|
+
const files = Array.from(event.clipboardData?.files || []).filter(
|
|
2750
|
+
(file) => file.type.startsWith("image/")
|
|
2751
|
+
);
|
|
2752
|
+
if (files.length > 0) {
|
|
2753
|
+
setPendingFiles(files);
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
panel.querySelector('[data-role="submit"]')?.addEventListener("click", () => void submit());
|
|
2757
|
+
}
|
|
2758
|
+
function setPendingFiles(files) {
|
|
2759
|
+
pendingFiles = Array.from(files || []).filter((file) => file.type.startsWith("image/")).slice(0, MAX_IMAGES_PER_MESSAGE);
|
|
2760
|
+
const label = panel.querySelector('[data-role="files-label"]');
|
|
2761
|
+
if (label) {
|
|
2762
|
+
label.textContent = pendingFiles.length > 0 ? `\u5DF2\u9009\u62E9 ${pendingFiles.length} \u5F20\u56FE\u7247` : DEFAULT_ATTACHMENTS_LABEL;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
function resetThread() {
|
|
2766
|
+
threadState = null;
|
|
2767
|
+
pendingFiles = [];
|
|
2768
|
+
renderedMessages = [];
|
|
2769
|
+
renderMessages([]);
|
|
2770
|
+
updateThreadCard();
|
|
2771
|
+
setError("");
|
|
2772
|
+
const textarea = panel.querySelector(
|
|
2773
|
+
'[data-role="content"]'
|
|
2774
|
+
);
|
|
2775
|
+
if (textarea) textarea.value = "";
|
|
2776
|
+
const fileInput = panel.querySelector(
|
|
2777
|
+
'[data-role="files"]'
|
|
2778
|
+
);
|
|
2779
|
+
if (fileInput) fileInput.value = "";
|
|
2780
|
+
const filesLabel = panel.querySelector(
|
|
2781
|
+
'[data-role="files-label"]'
|
|
2782
|
+
);
|
|
2783
|
+
if (filesLabel) filesLabel.textContent = DEFAULT_ATTACHMENTS_LABEL;
|
|
2784
|
+
}
|
|
2785
|
+
function updateThreadCard() {
|
|
2786
|
+
const card = panel.querySelector('[data-role="thread-card"]');
|
|
2787
|
+
const empty = panel.querySelector('[data-role="thread-empty"]');
|
|
2788
|
+
if (!card || !empty) return;
|
|
2789
|
+
if (!threadState) {
|
|
2790
|
+
card.dataset.visible = "false";
|
|
2791
|
+
empty.style.display = "block";
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
card.dataset.visible = "true";
|
|
2795
|
+
empty.style.display = "none";
|
|
2796
|
+
const category = card.querySelector(
|
|
2797
|
+
'[data-role="thread-category"]'
|
|
2798
|
+
);
|
|
2799
|
+
const status = card.querySelector(
|
|
2800
|
+
'[data-role="thread-status"]'
|
|
2801
|
+
);
|
|
2802
|
+
const preview = card.querySelector(
|
|
2803
|
+
'[data-role="thread-preview"]'
|
|
2804
|
+
);
|
|
2805
|
+
const time = card.querySelector('[data-role="thread-time"]');
|
|
2806
|
+
if (category) {
|
|
2807
|
+
category.textContent = formatCategoryLabel(
|
|
2808
|
+
threadState.category || DEFAULT_CATEGORIES[0]?.value,
|
|
2809
|
+
options.categories || DEFAULT_CATEGORIES
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
if (status) status.textContent = formatThreadStatus(threadState.status);
|
|
2813
|
+
if (preview) preview.textContent = threadState.contentPreview || "";
|
|
2814
|
+
if (time) time.textContent = formatThreadTime(threadState.createdAt);
|
|
2815
|
+
}
|
|
2816
|
+
function setError(message) {
|
|
2817
|
+
const node = panel.querySelector('[data-role="error"]');
|
|
2818
|
+
if (node) node.textContent = message;
|
|
2819
|
+
if (message) options.onError?.(new Error(message));
|
|
2820
|
+
}
|
|
2821
|
+
function setSubmitting(value) {
|
|
2822
|
+
const button = panel.querySelector(
|
|
2823
|
+
'[data-role="submit"]'
|
|
2824
|
+
);
|
|
2825
|
+
if (button) button.disabled = value;
|
|
2826
|
+
}
|
|
2827
|
+
function renderMessages(messages) {
|
|
2828
|
+
const node = panel.querySelector('[data-role="messages"]');
|
|
2829
|
+
if (!node) return;
|
|
2830
|
+
renderedMessages = messages;
|
|
2831
|
+
node.innerHTML = "";
|
|
2832
|
+
for (const message of messages) {
|
|
2833
|
+
const item = document.createElement("div");
|
|
2834
|
+
item.className = "yd-feedback-message";
|
|
2835
|
+
item.dataset.admin = String(message.senderType !== "END_USER");
|
|
2836
|
+
item.innerHTML = `<div class="yd-feedback-message-meta">${formatSenderType(message.senderType)} \xB7 ${new Date(message.createdAt).toLocaleString()}</div><div class="yd-feedback-message-content"></div>`;
|
|
2837
|
+
const content = item.querySelector(".yd-feedback-message-content");
|
|
2838
|
+
if (content) {
|
|
2839
|
+
content.textContent = message.senderType === "END_USER" ? message.content : message.translatedContent || message.content;
|
|
2840
|
+
}
|
|
2841
|
+
node.appendChild(item);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
function appendMessages(messages) {
|
|
2845
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2846
|
+
for (const message of renderedMessages) byId.set(message.id, message);
|
|
2847
|
+
for (const message of messages) byId.set(message.id, message);
|
|
2848
|
+
renderMessages(Array.from(byId.values()));
|
|
2849
|
+
}
|
|
2850
|
+
async function uploadImages(files) {
|
|
2851
|
+
const selected = files.slice(0, MAX_IMAGES_PER_MESSAGE);
|
|
2852
|
+
const ids = [];
|
|
2853
|
+
for (const file of selected) {
|
|
2854
|
+
const compressed = await compressImage(file);
|
|
2855
|
+
const body = new FormData();
|
|
2856
|
+
body.append("file", compressed);
|
|
2857
|
+
if (threadState) body.append("threadId", threadState.threadId);
|
|
2858
|
+
const headers = {};
|
|
2859
|
+
if (threadState) headers["x-feedback-token"] = threadState.clientToken;
|
|
2860
|
+
const response = await fetch(
|
|
2861
|
+
`${getBaseUrl(options)}/api/v1/feedback/${encodeURIComponent(options.appId)}/resources/images`,
|
|
2862
|
+
{ method: "POST", body, headers }
|
|
2863
|
+
);
|
|
2864
|
+
const payload = await response.json();
|
|
2865
|
+
if (!response.ok || payload?.code !== 200) {
|
|
2866
|
+
throw new Error(payload?.message || "Image upload failed");
|
|
2867
|
+
}
|
|
2868
|
+
ids.push(payload.data.resource.id);
|
|
2869
|
+
}
|
|
2870
|
+
return ids;
|
|
2871
|
+
}
|
|
2872
|
+
async function submit() {
|
|
2873
|
+
const content = panel.querySelector('[data-role="content"]')?.value.trim();
|
|
2874
|
+
if (!content) {
|
|
2875
|
+
setError("\u8BF7\u8F93\u5165\u53CD\u9988\u5185\u5BB9\u3002");
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
setSubmitting(true);
|
|
2879
|
+
setError("");
|
|
2880
|
+
try {
|
|
2881
|
+
const category = panel.querySelector('[data-role="category-select"]')?.dataset.value || "OTHER";
|
|
2882
|
+
const contact = panel.querySelector('[data-role="contact"]')?.value || "";
|
|
2883
|
+
const fileInput = panel.querySelector(
|
|
2884
|
+
'[data-role="files"]'
|
|
2885
|
+
);
|
|
2886
|
+
const attachments = await uploadImages(pendingFiles);
|
|
2887
|
+
if (!threadState) {
|
|
2888
|
+
const result = await requestJson(
|
|
2889
|
+
`${getBaseUrl(options)}/api/v1/feedback/${encodeURIComponent(options.appId)}/threads`,
|
|
2890
|
+
{
|
|
2891
|
+
method: "POST",
|
|
2892
|
+
body: safeJson({
|
|
2893
|
+
category,
|
|
2894
|
+
content,
|
|
2895
|
+
attachments,
|
|
2896
|
+
actor: {
|
|
2897
|
+
...options.actor || {},
|
|
2898
|
+
contactEmail: contact.includes("@") ? contact : options.actor?.contactEmail,
|
|
2899
|
+
contactPhone: contact && !contact.includes("@") ? contact : options.actor?.contactPhone
|
|
2900
|
+
},
|
|
2901
|
+
metadata: collectMetadata(options)
|
|
2902
|
+
})
|
|
2903
|
+
}
|
|
2904
|
+
);
|
|
2905
|
+
threadState = {
|
|
2906
|
+
threadId: result.thread.id,
|
|
2907
|
+
clientToken: result.clientToken,
|
|
2908
|
+
lastMessageId: result.message?.id,
|
|
2909
|
+
status: result.thread.status,
|
|
2910
|
+
category,
|
|
2911
|
+
contentPreview: content,
|
|
2912
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2913
|
+
};
|
|
2914
|
+
updateThreadCard();
|
|
2915
|
+
options.onThreadCreated?.(result.thread.id);
|
|
2916
|
+
options.onMessageSent?.(result.message.id);
|
|
2917
|
+
} else {
|
|
2918
|
+
const result = await requestJson(
|
|
2919
|
+
`${getBaseUrl(options)}/api/v1/feedback/${encodeURIComponent(options.appId)}/threads/${encodeURIComponent(threadState.threadId)}/messages`,
|
|
2920
|
+
{
|
|
2921
|
+
method: "POST",
|
|
2922
|
+
headers: { "x-feedback-token": threadState.clientToken },
|
|
2923
|
+
body: safeJson({
|
|
2924
|
+
content,
|
|
2925
|
+
attachments,
|
|
2926
|
+
metadata: collectMetadata(options)
|
|
2927
|
+
})
|
|
2928
|
+
}
|
|
2929
|
+
);
|
|
2930
|
+
threadState.lastMessageId = result.message.id;
|
|
2931
|
+
threadState.contentPreview = content;
|
|
2932
|
+
options.onMessageSent?.(result.message.id);
|
|
2933
|
+
}
|
|
2934
|
+
const textarea = panel.querySelector(
|
|
2935
|
+
'[data-role="content"]'
|
|
2936
|
+
);
|
|
2937
|
+
if (textarea) textarea.value = "";
|
|
2938
|
+
if (fileInput) fileInput.value = "";
|
|
2939
|
+
pendingFiles = [];
|
|
2940
|
+
const filesLabel = panel.querySelector(
|
|
2941
|
+
'[data-role="files-label"]'
|
|
2942
|
+
);
|
|
2943
|
+
if (filesLabel) filesLabel.textContent = DEFAULT_ATTACHMENTS_LABEL;
|
|
2944
|
+
await pollOnce();
|
|
2945
|
+
startPolling();
|
|
2946
|
+
} catch (error) {
|
|
2947
|
+
setError(error instanceof Error ? error.message : "\u63D0\u4EA4\u5931\u8D25");
|
|
2948
|
+
} finally {
|
|
2949
|
+
setSubmitting(false);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
async function pollOnce() {
|
|
2953
|
+
if (!threadState) return;
|
|
2954
|
+
const currentThread = threadState;
|
|
2955
|
+
const params = new URLSearchParams();
|
|
2956
|
+
if (currentThread.lastMessageId) {
|
|
2957
|
+
params.set("afterMessageId", currentThread.lastMessageId);
|
|
2958
|
+
}
|
|
2959
|
+
params.set("feedbackToken", currentThread.clientToken);
|
|
2960
|
+
const result = await requestJson(
|
|
2961
|
+
`${getBaseUrl(options)}/api/v1/feedback/${encodeURIComponent(options.appId)}/threads/${encodeURIComponent(currentThread.threadId)}/messages?${params.toString()}`
|
|
2962
|
+
);
|
|
2963
|
+
currentThread.status = result.threadStatus;
|
|
2964
|
+
updateThreadCard();
|
|
2965
|
+
if (result.messages.length > 0) {
|
|
2966
|
+
const latestMessage = result.messages.at(-1);
|
|
2967
|
+
if (latestMessage) currentThread.lastMessageId = latestMessage.id;
|
|
2968
|
+
appendMessages(result.messages);
|
|
2969
|
+
}
|
|
2970
|
+
if (result.threadStatus !== "WAITING_ADMIN") stopPolling();
|
|
2971
|
+
else schedulePoll(result.nextPollAfterMs);
|
|
2972
|
+
}
|
|
2973
|
+
function schedulePoll(delay) {
|
|
2974
|
+
stopPolling();
|
|
2975
|
+
if (!delay || delay < 1 || document.hidden || !threadState) return;
|
|
2976
|
+
pollTimer = window.setTimeout(() => void pollOnce(), delay);
|
|
2977
|
+
}
|
|
2978
|
+
function startPolling() {
|
|
2979
|
+
if (!threadState || threadState.status !== "WAITING_ADMIN") return;
|
|
2980
|
+
schedulePoll(1e4);
|
|
2981
|
+
}
|
|
2982
|
+
function stopPolling() {
|
|
2983
|
+
if (pollTimer) window.clearTimeout(pollTimer);
|
|
2984
|
+
pollTimer = null;
|
|
2985
|
+
}
|
|
2986
|
+
function open() {
|
|
2987
|
+
initPanel();
|
|
2988
|
+
panel.dataset.open = "true";
|
|
2989
|
+
window.setTimeout(() => {
|
|
2990
|
+
panel.querySelector('[data-role="content"]')?.focus();
|
|
2991
|
+
}, 0);
|
|
2992
|
+
}
|
|
2993
|
+
function close() {
|
|
2994
|
+
panel.dataset.open = "false";
|
|
2995
|
+
stopPolling();
|
|
2996
|
+
}
|
|
2997
|
+
launcher.addEventListener("click", open);
|
|
2998
|
+
const handleVisibilityChange = () => {
|
|
2999
|
+
if (document.hidden) stopPolling();
|
|
3000
|
+
else startPolling();
|
|
3001
|
+
};
|
|
3002
|
+
const handleDocumentClick = (event) => {
|
|
3003
|
+
const categorySelect = panel.querySelector(
|
|
3004
|
+
'[data-role="category-select"]'
|
|
3005
|
+
);
|
|
3006
|
+
if (categorySelect && event.target instanceof Node && !categorySelect.contains(event.target)) {
|
|
3007
|
+
categorySelect.dataset.open = "false";
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
3011
|
+
document.addEventListener("click", handleDocumentClick);
|
|
3012
|
+
if (typeof window !== "undefined") {
|
|
3013
|
+
const preload = () => {
|
|
3014
|
+
if ("requestIdleCallback" in window) {
|
|
3015
|
+
;
|
|
3016
|
+
window.requestIdleCallback(() => initPanel());
|
|
3017
|
+
} else {
|
|
3018
|
+
globalThis.setTimeout(() => initPanel(), 1200);
|
|
3019
|
+
}
|
|
3020
|
+
};
|
|
3021
|
+
if (document.readyState === "complete") preload();
|
|
3022
|
+
else {
|
|
3023
|
+
window.addEventListener("load", preload, { once: true });
|
|
3024
|
+
removeLoadListener = () => window.removeEventListener("load", preload);
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
return {
|
|
3028
|
+
open,
|
|
3029
|
+
close,
|
|
3030
|
+
destroy() {
|
|
3031
|
+
stopPolling();
|
|
3032
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
3033
|
+
document.removeEventListener("click", handleDocumentClick);
|
|
3034
|
+
removeLoadListener?.();
|
|
3035
|
+
root.remove();
|
|
3036
|
+
}
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
function mountFeedbackWidget(container, options) {
|
|
3040
|
+
return createFeedbackWidget(container, options);
|
|
3041
|
+
}
|
|
3042
|
+
|
|
1310
3043
|
// src/server.ts
|
|
1311
3044
|
var import_crypto = __toESM(require("crypto"), 1);
|
|
1312
3045
|
function getCustomAmountRechargeRule(product, currency) {
|
|
@@ -1436,6 +3169,12 @@ var PaymentClient = class {
|
|
|
1436
3169
|
}
|
|
1437
3170
|
/**
|
|
1438
3171
|
* Decrypts the callback notification payload using AES-256-GCM.
|
|
3172
|
+
*
|
|
3173
|
+
* @deprecated This legacy encrypted-envelope protocol is NOT what the platform
|
|
3174
|
+
* actually delivers. The platform sends an HMAC-signed plaintext JSON webhook
|
|
3175
|
+
* with `X-UniPay-Signature` / `X-UniPay-Timestamp` headers; use
|
|
3176
|
+
* {@link PaymentClient.verifyWebhookSignature} to validate those instead.
|
|
3177
|
+
*
|
|
1439
3178
|
* @param notification - The encrypted notification from payment webhook
|
|
1440
3179
|
* @returns Decrypted payment callback data
|
|
1441
3180
|
*/
|
|
@@ -1458,6 +3197,39 @@ var PaymentClient = class {
|
|
|
1458
3197
|
);
|
|
1459
3198
|
}
|
|
1460
3199
|
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Verify an inbound platform webhook signature.
|
|
3202
|
+
*
|
|
3203
|
+
* The platform delivers webhooks as plaintext JSON with two headers:
|
|
3204
|
+
* `X-UniPay-Signature` (HMAC-SHA256 of `${timestamp}.${rawBodyString}`) and
|
|
3205
|
+
* `X-UniPay-Timestamp`. The body itself is NOT encrypted; verify the signature
|
|
3206
|
+
* then `JSON.parse` the body to obtain a {@link WebhookPayload}.
|
|
3207
|
+
*
|
|
3208
|
+
* @param rawBody - The raw request body string (exactly as received)
|
|
3209
|
+
* @param signature - The `X-UniPay-Signature` header value (hex)
|
|
3210
|
+
* @param timestamp - The `X-UniPay-Timestamp` header value (ms epoch string)
|
|
3211
|
+
* @param options - Optional `toleranceMs` for timestamp freshness (default 5 min)
|
|
3212
|
+
* @returns `true` if the signature is valid and the timestamp is fresh
|
|
3213
|
+
*/
|
|
3214
|
+
verifyWebhookSignature(rawBody, signature, timestamp, options) {
|
|
3215
|
+
try {
|
|
3216
|
+
const toleranceMs = options?.toleranceMs ?? 5 * 60 * 1e3;
|
|
3217
|
+
const timestampMs = Number.parseInt(timestamp, 10);
|
|
3218
|
+
if (Number.isNaN(timestampMs) || Math.abs(Date.now() - timestampMs) > toleranceMs) {
|
|
3219
|
+
return false;
|
|
3220
|
+
}
|
|
3221
|
+
const rawString = `${timestamp}.${rawBody}`;
|
|
3222
|
+
const expected = import_crypto.default.createHmac("sha256", this.appSecret).update(rawString).digest("hex");
|
|
3223
|
+
const signatureBuffer = Buffer.from(signature);
|
|
3224
|
+
const expectedBuffer = Buffer.from(expected);
|
|
3225
|
+
if (signatureBuffer.length !== expectedBuffer.length) {
|
|
3226
|
+
return false;
|
|
3227
|
+
}
|
|
3228
|
+
return import_crypto.default.timingSafeEqual(signatureBuffer, expectedBuffer);
|
|
3229
|
+
} catch {
|
|
3230
|
+
return false;
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
1461
3233
|
/**
|
|
1462
3234
|
* Fetch products for the configured app.
|
|
1463
3235
|
*/
|
|
@@ -1659,6 +3431,27 @@ var PaymentClient = class {
|
|
|
1659
3431
|
...options
|
|
1660
3432
|
});
|
|
1661
3433
|
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Consume numeric entitlements from an ordered key pool.
|
|
3436
|
+
* Useful when subscription credits and perpetual credits use separate keys.
|
|
3437
|
+
*/
|
|
3438
|
+
async consumeEntitlementPool(userId, keys, amount, options) {
|
|
3439
|
+
return this.request("POST", `/users/${userId}/entitlements/consume-pool`, {
|
|
3440
|
+
keys,
|
|
3441
|
+
amount,
|
|
3442
|
+
...options
|
|
3443
|
+
});
|
|
3444
|
+
}
|
|
3445
|
+
/**
|
|
3446
|
+
* Get active numeric entitlement buckets ordered by expiration.
|
|
3447
|
+
*/
|
|
3448
|
+
async getEntitlementCreditBuckets(userId, keys) {
|
|
3449
|
+
const query = keys && keys.length > 0 ? `?keys=${encodeURIComponent(keys.join(","))}` : "";
|
|
3450
|
+
return this.request(
|
|
3451
|
+
"GET",
|
|
3452
|
+
`/users/${userId}/entitlements/credit-buckets${query}`
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
1662
3455
|
/**
|
|
1663
3456
|
* Add numeric entitlement (e.g. refund)
|
|
1664
3457
|
* @param userId - User ID
|
|
@@ -1739,16 +3532,48 @@ var PaymentClient = class {
|
|
|
1739
3532
|
}
|
|
1740
3533
|
);
|
|
1741
3534
|
}
|
|
3535
|
+
/**
|
|
3536
|
+
* Create a hosted WeChat official account binding URL for a user.
|
|
3537
|
+
*/
|
|
3538
|
+
async createWechatMessageBinding(params) {
|
|
3539
|
+
const loginExternalUserId = params.loginExternalUserId?.trim() || params.userId?.trim();
|
|
3540
|
+
const merchantUserId = params.merchantUserId?.trim();
|
|
3541
|
+
if (!loginExternalUserId && !merchantUserId) {
|
|
3542
|
+
throw new Error("userId or merchantUserId is required");
|
|
3543
|
+
}
|
|
3544
|
+
return this.request("POST", "/messages/wechat/bindings", {
|
|
3545
|
+
...params,
|
|
3546
|
+
loginExternalUserId,
|
|
3547
|
+
merchantUserId
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
/**
|
|
3551
|
+
* Send an app-linked message template to a bound recipient.
|
|
3552
|
+
*/
|
|
3553
|
+
async sendMessage(params) {
|
|
3554
|
+
if (!params.templateId?.trim() && !params.templateCode?.trim()) {
|
|
3555
|
+
throw new Error("templateId or templateCode is required");
|
|
3556
|
+
}
|
|
3557
|
+
if (!params.data || typeof params.data !== "object") {
|
|
3558
|
+
throw new Error("data is required");
|
|
3559
|
+
}
|
|
3560
|
+
return this.request("POST", "/messages/send", params);
|
|
3561
|
+
}
|
|
1742
3562
|
};
|
|
1743
3563
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1744
3564
|
0 && (module.exports = {
|
|
1745
3565
|
LoginUI,
|
|
3566
|
+
MessageUI,
|
|
1746
3567
|
PaymentClient,
|
|
1747
3568
|
PaymentUI,
|
|
3569
|
+
createFeedbackWidget,
|
|
1748
3570
|
createLoginUI,
|
|
3571
|
+
createMessageUI,
|
|
1749
3572
|
createPaymentUI,
|
|
3573
|
+
detectLoginEnvironment,
|
|
1750
3574
|
getCustomAmountRechargeRule,
|
|
1751
3575
|
handleLoginCallbackIfPresent,
|
|
3576
|
+
mountFeedbackWidget,
|
|
1752
3577
|
validateCustomAmountRecharge
|
|
1753
3578
|
});
|
|
1754
3579
|
//# sourceMappingURL=index.cjs.map
|