@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.js
CHANGED
|
@@ -2,6 +2,221 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
|
+
// src/brand-logo.ts
|
|
6
|
+
function ensureSdkBrandLogoStyles() {
|
|
7
|
+
if (document.getElementById?.("youidian-sdk-brand-logo-style")) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const style = document.createElement("style");
|
|
11
|
+
style.id = "youidian-sdk-brand-logo-style";
|
|
12
|
+
style.textContent = `
|
|
13
|
+
.youidian-sdk-brand-logo__ring,
|
|
14
|
+
.youidian-sdk-brand-logo__dot,
|
|
15
|
+
.youidian-sdk-brand-logo__copy,
|
|
16
|
+
.youidian-sdk-brand-logo__tagline {
|
|
17
|
+
transform-box: fill-box;
|
|
18
|
+
transform-origin: center;
|
|
19
|
+
will-change: opacity, transform;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__ring {
|
|
23
|
+
animation: youidian-sdk-brand-logo-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__dot {
|
|
27
|
+
animation: youidian-sdk-brand-logo-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__copy {
|
|
31
|
+
animation: youidian-sdk-brand-logo-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.youidian-sdk-brand-logo[data-animated="true"] .youidian-sdk-brand-logo__tagline {
|
|
35
|
+
animation: youidian-sdk-brand-logo-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@keyframes youidian-sdk-brand-logo-ring {
|
|
39
|
+
0%,
|
|
40
|
+
100% {
|
|
41
|
+
opacity: 0.72;
|
|
42
|
+
transform: rotate(0deg) scale(0.98);
|
|
43
|
+
}
|
|
44
|
+
50% {
|
|
45
|
+
opacity: 1;
|
|
46
|
+
transform: rotate(8deg) scale(1.02);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@keyframes youidian-sdk-brand-logo-dot {
|
|
51
|
+
0%,
|
|
52
|
+
100% {
|
|
53
|
+
opacity: 0.58;
|
|
54
|
+
transform: scale(0.9);
|
|
55
|
+
}
|
|
56
|
+
50% {
|
|
57
|
+
opacity: 1;
|
|
58
|
+
transform: scale(1.08);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@keyframes youidian-sdk-brand-logo-copy {
|
|
63
|
+
0%,
|
|
64
|
+
100% {
|
|
65
|
+
opacity: 0.86;
|
|
66
|
+
transform: translateY(0);
|
|
67
|
+
}
|
|
68
|
+
50% {
|
|
69
|
+
opacity: 1;
|
|
70
|
+
transform: translateY(-1px);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes youidian-sdk-brand-logo-tagline {
|
|
75
|
+
0%,
|
|
76
|
+
100% {
|
|
77
|
+
opacity: 0.64;
|
|
78
|
+
}
|
|
79
|
+
50% {
|
|
80
|
+
opacity: 0.92;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@media (prefers-reduced-motion: reduce) {
|
|
85
|
+
.youidian-sdk-brand-logo__ring,
|
|
86
|
+
.youidian-sdk-brand-logo__dot,
|
|
87
|
+
.youidian-sdk-brand-logo__copy,
|
|
88
|
+
.youidian-sdk-brand-logo__tagline {
|
|
89
|
+
animation: none;
|
|
90
|
+
opacity: 1;
|
|
91
|
+
transform: none;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
document.head?.appendChild(style);
|
|
96
|
+
}
|
|
97
|
+
function createSdkBrandLogoMark({
|
|
98
|
+
animated,
|
|
99
|
+
inverted,
|
|
100
|
+
size,
|
|
101
|
+
theme,
|
|
102
|
+
variant = "brand"
|
|
103
|
+
}) {
|
|
104
|
+
ensureSdkBrandLogoStyles();
|
|
105
|
+
const mark = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
106
|
+
mark.setAttribute("viewBox", "0 0 512 512");
|
|
107
|
+
mark.setAttribute("fill", "none");
|
|
108
|
+
mark.setAttribute("aria-hidden", "true");
|
|
109
|
+
Object.assign(mark.style, {
|
|
110
|
+
width: `${size}px`,
|
|
111
|
+
height: `${size}px`,
|
|
112
|
+
color: inverted ? "#ffffff" : theme.primary,
|
|
113
|
+
display: "block",
|
|
114
|
+
flex: "0 0 auto",
|
|
115
|
+
overflow: "visible"
|
|
116
|
+
});
|
|
117
|
+
const logoGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
118
|
+
logoGroup.setAttribute(
|
|
119
|
+
"transform",
|
|
120
|
+
variant === "mark" ? "matrix(0.74 0 0 0.74 -124 -114)" : "matrix(0.58 0 0 0.58 -40 -31)"
|
|
121
|
+
);
|
|
122
|
+
const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
123
|
+
ring.setAttribute("class", animated ? "youidian-sdk-brand-logo__ring" : "");
|
|
124
|
+
ring.setAttribute("fill", inverted ? "currentColor" : theme.primary);
|
|
125
|
+
ring.setAttribute(
|
|
126
|
+
"d",
|
|
127
|
+
"M704.298 679.54A264 264 0 1 1 704.298 309.46A49 49 0 0 1 634.4 378.149A166 166 0 1 0 634.4 610.851A49 49 0 0 1 704.298 679.54Z"
|
|
128
|
+
);
|
|
129
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
130
|
+
dot.setAttribute("class", animated ? "youidian-sdk-brand-logo__dot" : "");
|
|
131
|
+
dot.setAttribute("fill", inverted ? "currentColor" : theme.secondary);
|
|
132
|
+
dot.setAttribute("cx", "714.5");
|
|
133
|
+
dot.setAttribute("cy", "490.5");
|
|
134
|
+
dot.setAttribute("r", "68");
|
|
135
|
+
dot.setAttribute("opacity", inverted ? "0.78" : "1");
|
|
136
|
+
logoGroup.appendChild(ring);
|
|
137
|
+
logoGroup.appendChild(dot);
|
|
138
|
+
mark.appendChild(logoGroup);
|
|
139
|
+
return mark;
|
|
140
|
+
}
|
|
141
|
+
function createSdkBrandLogo({
|
|
142
|
+
animated,
|
|
143
|
+
brand,
|
|
144
|
+
brandWidth,
|
|
145
|
+
copyWidth,
|
|
146
|
+
gap = 10,
|
|
147
|
+
markMarginRight,
|
|
148
|
+
markSize,
|
|
149
|
+
nameSize,
|
|
150
|
+
subtitleMarginTop = 6,
|
|
151
|
+
subtitleSize,
|
|
152
|
+
subtitleWeight = "800",
|
|
153
|
+
theme,
|
|
154
|
+
variant = "brand"
|
|
155
|
+
}) {
|
|
156
|
+
ensureSdkBrandLogoStyles();
|
|
157
|
+
const wrapper = document.createElement("div");
|
|
158
|
+
wrapper.className = "youidian-sdk-brand-logo";
|
|
159
|
+
wrapper.setAttribute("data-animated", animated ? "true" : "false");
|
|
160
|
+
Object.assign(wrapper.style, {
|
|
161
|
+
display: "inline-flex",
|
|
162
|
+
alignItems: "center",
|
|
163
|
+
justifyContent: "center",
|
|
164
|
+
gap: `${gap}px`,
|
|
165
|
+
width: brandWidth ? `${brandWidth}px` : void 0
|
|
166
|
+
});
|
|
167
|
+
const copy = document.createElement("div");
|
|
168
|
+
copy.className = "youidian-sdk-brand-logo__copy";
|
|
169
|
+
copy.setAttribute("data-brand-locale", brand.language);
|
|
170
|
+
Object.assign(copy.style, {
|
|
171
|
+
display: "flex",
|
|
172
|
+
flexDirection: "column",
|
|
173
|
+
alignItems: "flex-start",
|
|
174
|
+
justifyContent: "center",
|
|
175
|
+
lineHeight: "1",
|
|
176
|
+
width: copyWidth ? `${copyWidth}px` : void 0
|
|
177
|
+
});
|
|
178
|
+
const name = document.createElement("div");
|
|
179
|
+
name.className = "youidian-sdk-brand-logo__name";
|
|
180
|
+
name.textContent = brand.name;
|
|
181
|
+
Object.assign(name.style, {
|
|
182
|
+
color: theme.foreground,
|
|
183
|
+
fontFamily: brand.language === "zh" ? '"Noto Sans TC", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' : '"Unbounded", "Space Grotesk", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
184
|
+
fontSize: `${nameSize}px`,
|
|
185
|
+
fontWeight: brand.language === "zh" ? "900" : "800",
|
|
186
|
+
letterSpacing: brand.language === "zh" ? "0.08em" : "0.01em",
|
|
187
|
+
lineHeight: "1",
|
|
188
|
+
whiteSpace: "nowrap"
|
|
189
|
+
});
|
|
190
|
+
const subtitle = document.createElement("div");
|
|
191
|
+
subtitle.className = "youidian-sdk-brand-logo__tagline";
|
|
192
|
+
subtitle.textContent = brand.subtitle;
|
|
193
|
+
Object.assign(subtitle.style, {
|
|
194
|
+
color: theme.muted,
|
|
195
|
+
fontFamily: brand.language === "zh" ? '"Noto Sans TC", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' : '"Space Grotesk", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
196
|
+
fontSize: `${subtitleSize}px`,
|
|
197
|
+
fontWeight: subtitleWeight,
|
|
198
|
+
letterSpacing: brand.language === "zh" ? "0.24em" : "0.07em",
|
|
199
|
+
lineHeight: "1",
|
|
200
|
+
marginTop: `${subtitleMarginTop}px`,
|
|
201
|
+
textTransform: brand.language === "zh" ? "none" : "uppercase",
|
|
202
|
+
whiteSpace: "nowrap"
|
|
203
|
+
});
|
|
204
|
+
copy.appendChild(name);
|
|
205
|
+
copy.appendChild(subtitle);
|
|
206
|
+
const mark = createSdkBrandLogoMark({
|
|
207
|
+
animated,
|
|
208
|
+
size: markSize,
|
|
209
|
+
theme,
|
|
210
|
+
variant
|
|
211
|
+
});
|
|
212
|
+
if (typeof markMarginRight === "number") {
|
|
213
|
+
mark.style.marginRight = `${markMarginRight}px`;
|
|
214
|
+
}
|
|
215
|
+
wrapper.appendChild(mark);
|
|
216
|
+
wrapper.appendChild(copy);
|
|
217
|
+
return wrapper;
|
|
218
|
+
}
|
|
219
|
+
|
|
5
220
|
// src/hosted-modal.ts
|
|
6
221
|
var SDK_SUPPORTED_LOCALES = [
|
|
7
222
|
"en",
|
|
@@ -125,7 +340,7 @@ var HostedFrameModal = class {
|
|
|
125
340
|
const container = document.createElement("div");
|
|
126
341
|
Object.assign(container.style, {
|
|
127
342
|
width: options.width || "450px",
|
|
128
|
-
height: options.height || "min(
|
|
343
|
+
height: options.height || "min(760px, calc(100vh - 32px))",
|
|
129
344
|
backgroundColor: "transparent",
|
|
130
345
|
borderRadius: "28px",
|
|
131
346
|
overflow: "visible",
|
|
@@ -133,8 +348,7 @@ var HostedFrameModal = class {
|
|
|
133
348
|
boxShadow: "none",
|
|
134
349
|
maxWidth: "calc(100vw - 32px)",
|
|
135
350
|
maxHeight: "calc(100vh - 32px)",
|
|
136
|
-
transition: "
|
|
137
|
-
willChange: "height"
|
|
351
|
+
transition: "none"
|
|
138
352
|
});
|
|
139
353
|
const closeBtn = document.createElement("button");
|
|
140
354
|
closeBtn.innerHTML = "\xD7";
|
|
@@ -204,6 +418,91 @@ var HostedFrameModal = class {
|
|
|
204
418
|
}
|
|
205
419
|
};
|
|
206
420
|
|
|
421
|
+
// src/environment.ts
|
|
422
|
+
function normalizeText(value) {
|
|
423
|
+
return value || "";
|
|
424
|
+
}
|
|
425
|
+
function getRuntimeInput() {
|
|
426
|
+
if (typeof navigator === "undefined") {
|
|
427
|
+
return {};
|
|
428
|
+
}
|
|
429
|
+
const nav = navigator;
|
|
430
|
+
const coarsePointer = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : void 0;
|
|
431
|
+
return {
|
|
432
|
+
coarsePointer,
|
|
433
|
+
maxTouchPoints: nav.maxTouchPoints || 0,
|
|
434
|
+
platform: nav.platform || "",
|
|
435
|
+
standalone: nav.standalone,
|
|
436
|
+
userAgent: nav.userAgent || "",
|
|
437
|
+
userAgentDataMobile: nav.userAgentData?.mobile
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function includesAny(value, patterns) {
|
|
441
|
+
return patterns.some((pattern) => pattern.test(value));
|
|
442
|
+
}
|
|
443
|
+
function detectLoginEnvironment(input = getRuntimeInput()) {
|
|
444
|
+
const userAgent = normalizeText(input.userAgent);
|
|
445
|
+
const platform = normalizeText(input.platform);
|
|
446
|
+
const rawMaxTouchPoints = input.maxTouchPoints ?? 0;
|
|
447
|
+
const maxTouchPoints = Number.isFinite(rawMaxTouchPoints) ? Math.max(0, rawMaxTouchPoints) : 0;
|
|
448
|
+
const isCoarsePointer = input.coarsePointer === true;
|
|
449
|
+
const isTouch = maxTouchPoints > 0 || isCoarsePointer;
|
|
450
|
+
const isWeChat = /micromessenger/i.test(userAgent);
|
|
451
|
+
const isAndroid = /\bandroid\b/i.test(userAgent);
|
|
452
|
+
const hasExplicitIOSDevice = /\b(iPad|iPhone|iPod)\b/i.test(userAgent);
|
|
453
|
+
const hasMacintoshUserAgent = /\bMacintosh\b/i.test(userAgent);
|
|
454
|
+
const hasMacPlatform = /^Mac/i.test(platform);
|
|
455
|
+
const isIPadOS = !hasExplicitIOSDevice && hasMacintoshUserAgent && hasMacPlatform && typeof input.standalone !== "undefined" && maxTouchPoints > 2;
|
|
456
|
+
const isIOS = hasExplicitIOSDevice || isIPadOS;
|
|
457
|
+
const isTabletByUa = includesAny(userAgent, [
|
|
458
|
+
/\biPad\b/i,
|
|
459
|
+
/\btablet\b/i,
|
|
460
|
+
/\bplaybook\b/i,
|
|
461
|
+
/\bsilk\b(?!.*\bmobile\b)/i,
|
|
462
|
+
/\bkindle\b/i,
|
|
463
|
+
/\bnexus 7\b/i,
|
|
464
|
+
/\bnexus 9\b/i,
|
|
465
|
+
/\bxoom\b/i,
|
|
466
|
+
/\bsm-t\d+/i,
|
|
467
|
+
/\bgt-p\d+/i,
|
|
468
|
+
/\bmi pad\b/i
|
|
469
|
+
]);
|
|
470
|
+
const isMobileByUa = includesAny(userAgent, [
|
|
471
|
+
/\bMobile\b/i,
|
|
472
|
+
/\biPhone\b/i,
|
|
473
|
+
/\biPod\b/i,
|
|
474
|
+
/\bWindows Phone\b/i,
|
|
475
|
+
/\bIEMobile\b/i,
|
|
476
|
+
/\bBlackBerry\b/i,
|
|
477
|
+
/\bBB10\b/i,
|
|
478
|
+
/\bOpera Mini\b/i,
|
|
479
|
+
/\bOpera Mobi\b/i,
|
|
480
|
+
/\bwebOS\b/i
|
|
481
|
+
]);
|
|
482
|
+
const isAndroidTablet = isAndroid && !/\bMobile\b/i.test(userAgent);
|
|
483
|
+
const isTablet = isIPadOS || isTabletByUa || isAndroidTablet;
|
|
484
|
+
const isMobile = input.userAgentDataMobile === true || isMobileByUa || isAndroid && !isTablet || isIOS && !isTablet;
|
|
485
|
+
const deviceType = isTablet ? "tablet" : isMobile ? "mobile" : userAgent || platform || isTouch ? "desktop" : "unknown";
|
|
486
|
+
const shouldUseRedirectLogin = deviceType === "mobile" || deviceType === "tablet" || isWeChat;
|
|
487
|
+
return {
|
|
488
|
+
deviceType,
|
|
489
|
+
isAndroid,
|
|
490
|
+
isCoarsePointer,
|
|
491
|
+
isDesktop: deviceType === "desktop",
|
|
492
|
+
isIOS,
|
|
493
|
+
isIPadOS,
|
|
494
|
+
isMobile,
|
|
495
|
+
isStandalone: input.standalone === true,
|
|
496
|
+
isTablet,
|
|
497
|
+
isTouch,
|
|
498
|
+
isWeChat,
|
|
499
|
+
maxTouchPoints,
|
|
500
|
+
platform,
|
|
501
|
+
shouldUseRedirectLogin,
|
|
502
|
+
userAgent
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
207
506
|
// src/login.ts
|
|
208
507
|
function getOrigin(value) {
|
|
209
508
|
if (!value) return null;
|
|
@@ -213,6 +512,15 @@ function getOrigin(value) {
|
|
|
213
512
|
return null;
|
|
214
513
|
}
|
|
215
514
|
}
|
|
515
|
+
function isValidLoginRedirectUrl(value) {
|
|
516
|
+
if (!value) return false;
|
|
517
|
+
try {
|
|
518
|
+
const url = new URL(value);
|
|
519
|
+
return url.protocol === "https:" || url.protocol === "http:";
|
|
520
|
+
} catch {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
216
524
|
function createLoginCallbackState() {
|
|
217
525
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
218
526
|
return crypto.randomUUID().replace(/-/g, "");
|
|
@@ -559,6 +867,24 @@ function applyLoginPanelStyle(panel) {
|
|
|
559
867
|
backdropFilter: "blur(16px)"
|
|
560
868
|
});
|
|
561
869
|
}
|
|
870
|
+
function applyLoginLoadingPanelStyle(panel) {
|
|
871
|
+
Object.assign(panel.style, {
|
|
872
|
+
position: "absolute",
|
|
873
|
+
inset: "0",
|
|
874
|
+
display: "flex",
|
|
875
|
+
alignItems: "center",
|
|
876
|
+
justifyContent: "center",
|
|
877
|
+
border: "0",
|
|
878
|
+
borderRadius: "28px",
|
|
879
|
+
background: "rgba(255,255,255,0.94)",
|
|
880
|
+
color: "#0f172a",
|
|
881
|
+
padding: "24px",
|
|
882
|
+
textAlign: "center",
|
|
883
|
+
zIndex: "1",
|
|
884
|
+
boxShadow: "none",
|
|
885
|
+
backdropFilter: "blur(16px)"
|
|
886
|
+
});
|
|
887
|
+
}
|
|
562
888
|
function createLoginPanelContent() {
|
|
563
889
|
const content = document.createElement("div");
|
|
564
890
|
Object.assign(content.style, {
|
|
@@ -583,6 +909,71 @@ function createLoginPanelContent() {
|
|
|
583
909
|
content.appendChild(badge);
|
|
584
910
|
return content;
|
|
585
911
|
}
|
|
912
|
+
function createBrandLoadingMark() {
|
|
913
|
+
const logo = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
914
|
+
logo.setAttribute("viewBox", "0 0 1024 1024");
|
|
915
|
+
logo.setAttribute("fill", "none");
|
|
916
|
+
logo.setAttribute("aria-hidden", "true");
|
|
917
|
+
Object.assign(logo.style, {
|
|
918
|
+
width: "112px",
|
|
919
|
+
height: "112px",
|
|
920
|
+
color: "#22c55e",
|
|
921
|
+
display: "block",
|
|
922
|
+
overflow: "visible"
|
|
923
|
+
});
|
|
924
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
925
|
+
group.setAttribute("fill", "currentColor");
|
|
926
|
+
const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
927
|
+
ring.setAttribute(
|
|
928
|
+
"d",
|
|
929
|
+
"M704.298 679.54A264 264 0 1 1 704.298 309.46A49 49 0 0 1 634.4 378.149A166 166 0 1 0 634.4 610.851A49 49 0 0 1 704.298 679.54Z"
|
|
930
|
+
);
|
|
931
|
+
ring.setAttribute("class", "youidian-sdk-brand-loading__ring");
|
|
932
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
933
|
+
dot.setAttribute("class", "youidian-sdk-brand-loading__dot");
|
|
934
|
+
dot.setAttribute("cx", "714.5");
|
|
935
|
+
dot.setAttribute("cy", "490.5");
|
|
936
|
+
dot.setAttribute("r", "68");
|
|
937
|
+
group.appendChild(ring);
|
|
938
|
+
group.appendChild(dot);
|
|
939
|
+
logo.appendChild(group);
|
|
940
|
+
return logo;
|
|
941
|
+
}
|
|
942
|
+
function createBrandLoadingCopy() {
|
|
943
|
+
const brand = document.createElement("div");
|
|
944
|
+
brand.className = "youidian-sdk-brand-loading__copy";
|
|
945
|
+
Object.assign(brand.style, {
|
|
946
|
+
display: "flex",
|
|
947
|
+
flexDirection: "column",
|
|
948
|
+
alignItems: "flex-start",
|
|
949
|
+
justifyContent: "center",
|
|
950
|
+
lineHeight: "1"
|
|
951
|
+
});
|
|
952
|
+
const name = document.createElement("div");
|
|
953
|
+
name.className = "youidian-sdk-brand-loading__name";
|
|
954
|
+
name.textContent = "\u4F18\u6613\u70B9";
|
|
955
|
+
Object.assign(name.style, {
|
|
956
|
+
color: "#0f172a",
|
|
957
|
+
fontSize: "34px",
|
|
958
|
+
fontWeight: "900",
|
|
959
|
+
letterSpacing: "0",
|
|
960
|
+
lineHeight: "1"
|
|
961
|
+
});
|
|
962
|
+
const tagline = document.createElement("div");
|
|
963
|
+
tagline.className = "youidian-sdk-brand-loading__tagline";
|
|
964
|
+
tagline.textContent = "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0";
|
|
965
|
+
Object.assign(tagline.style, {
|
|
966
|
+
color: "#64748b",
|
|
967
|
+
fontSize: "16px",
|
|
968
|
+
fontWeight: "700",
|
|
969
|
+
letterSpacing: "0.24em",
|
|
970
|
+
lineHeight: "1",
|
|
971
|
+
marginTop: "6px"
|
|
972
|
+
});
|
|
973
|
+
brand.appendChild(name);
|
|
974
|
+
brand.appendChild(tagline);
|
|
975
|
+
return brand;
|
|
976
|
+
}
|
|
586
977
|
function createLoginPanelTitle(value) {
|
|
587
978
|
const title = document.createElement("div");
|
|
588
979
|
title.textContent = value;
|
|
@@ -612,32 +1003,111 @@ function ensureLoginLoadingStyles() {
|
|
|
612
1003
|
}
|
|
613
1004
|
const style = document.createElement("style");
|
|
614
1005
|
style.id = "youidian-login-loading-style";
|
|
615
|
-
style.textContent =
|
|
1006
|
+
style.textContent = `
|
|
1007
|
+
.youidian-sdk-brand-loading__ring,
|
|
1008
|
+
.youidian-sdk-brand-loading__dot {
|
|
1009
|
+
transform-box: fill-box;
|
|
1010
|
+
transform-origin: center;
|
|
1011
|
+
will-change: opacity, transform;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
.youidian-sdk-brand-loading__ring {
|
|
1015
|
+
animation: youidian-sdk-brand-loading-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
.youidian-sdk-brand-loading__dot {
|
|
1019
|
+
animation: youidian-sdk-brand-loading-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.youidian-sdk-brand-loading__copy {
|
|
1023
|
+
will-change: opacity, transform;
|
|
1024
|
+
animation: youidian-sdk-brand-loading-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.youidian-sdk-brand-loading__tagline {
|
|
1028
|
+
will-change: opacity;
|
|
1029
|
+
animation: youidian-sdk-brand-loading-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
@keyframes youidian-sdk-brand-loading-ring {
|
|
1033
|
+
0%,
|
|
1034
|
+
100% {
|
|
1035
|
+
opacity: 0.72;
|
|
1036
|
+
transform: rotate(0deg) scale(0.98);
|
|
1037
|
+
}
|
|
1038
|
+
50% {
|
|
1039
|
+
opacity: 1;
|
|
1040
|
+
transform: rotate(8deg) scale(1.02);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
@keyframes youidian-sdk-brand-loading-dot {
|
|
1045
|
+
0%,
|
|
1046
|
+
100% {
|
|
1047
|
+
opacity: 0.55;
|
|
1048
|
+
transform: scale(0.9);
|
|
1049
|
+
}
|
|
1050
|
+
50% {
|
|
1051
|
+
opacity: 1;
|
|
1052
|
+
transform: scale(1.08);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
@keyframes youidian-sdk-brand-loading-copy {
|
|
1057
|
+
0%,
|
|
1058
|
+
100% {
|
|
1059
|
+
opacity: 0.86;
|
|
1060
|
+
transform: translateY(0);
|
|
1061
|
+
}
|
|
1062
|
+
50% {
|
|
1063
|
+
opacity: 1;
|
|
1064
|
+
transform: translateY(-1px);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
@keyframes youidian-sdk-brand-loading-tagline {
|
|
1069
|
+
0%,
|
|
1070
|
+
100% {
|
|
1071
|
+
opacity: 0.64;
|
|
1072
|
+
}
|
|
1073
|
+
50% {
|
|
1074
|
+
opacity: 0.92;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1079
|
+
.youidian-sdk-brand-loading__ring,
|
|
1080
|
+
.youidian-sdk-brand-loading__dot,
|
|
1081
|
+
.youidian-sdk-brand-loading__copy,
|
|
1082
|
+
.youidian-sdk-brand-loading__tagline {
|
|
1083
|
+
animation: none;
|
|
1084
|
+
opacity: 1;
|
|
1085
|
+
transform: none;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
`;
|
|
616
1089
|
document.head.appendChild(style);
|
|
617
1090
|
}
|
|
618
|
-
function createLoginLoadingPanel(
|
|
1091
|
+
function createLoginLoadingPanel() {
|
|
619
1092
|
ensureLoginLoadingStyles();
|
|
620
1093
|
const panel = document.createElement("div");
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
margin: "0 auto 18px",
|
|
631
|
-
animation: "youidian-login-spin 780ms linear infinite"
|
|
1094
|
+
applyLoginLoadingPanelStyle(panel);
|
|
1095
|
+
panel.setAttribute("role", "status");
|
|
1096
|
+
panel.setAttribute("aria-label", "\u4F18\u6613\u70B9\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0");
|
|
1097
|
+
const content = document.createElement("div");
|
|
1098
|
+
Object.assign(content.style, {
|
|
1099
|
+
display: "flex",
|
|
1100
|
+
alignItems: "center",
|
|
1101
|
+
justifyContent: "center",
|
|
1102
|
+
gap: "12px"
|
|
632
1103
|
});
|
|
633
|
-
content.appendChild(
|
|
634
|
-
content.appendChild(
|
|
635
|
-
content.appendChild(createLoginPanelDescription(options.description));
|
|
1104
|
+
content.appendChild(createBrandLoadingMark());
|
|
1105
|
+
content.appendChild(createBrandLoadingCopy());
|
|
636
1106
|
panel.appendChild(content);
|
|
637
1107
|
return panel;
|
|
638
1108
|
}
|
|
639
|
-
function createLoginRedirectingPanel(
|
|
640
|
-
return createLoginLoadingPanel(
|
|
1109
|
+
function createLoginRedirectingPanel(_options) {
|
|
1110
|
+
return createLoginLoadingPanel();
|
|
641
1111
|
}
|
|
642
1112
|
function createLoginFallbackPanel(options) {
|
|
643
1113
|
const panel = document.createElement("div");
|
|
@@ -719,6 +1189,7 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
719
1189
|
this.completed = false;
|
|
720
1190
|
}
|
|
721
1191
|
markIframeReady() {
|
|
1192
|
+
if (this.iframeReady) return;
|
|
722
1193
|
this.iframeReady = true;
|
|
723
1194
|
if (typeof window !== "undefined" && this.iframeLoadTimer) {
|
|
724
1195
|
window.clearTimeout(this.iframeLoadTimer);
|
|
@@ -737,14 +1208,11 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
737
1208
|
this.iframeFallbackPanel = null;
|
|
738
1209
|
this.iframeLoadingPanel = null;
|
|
739
1210
|
}
|
|
740
|
-
showIframeLoading(container
|
|
1211
|
+
showIframeLoading(container) {
|
|
741
1212
|
if (this.iframeLoadingPanel || this.iframeReady) {
|
|
742
1213
|
return;
|
|
743
1214
|
}
|
|
744
|
-
const panel = createLoginLoadingPanel(
|
|
745
|
-
description: options?.loadingDescription || "Please wait while the secure login page opens.",
|
|
746
|
-
title: options?.loadingTitle || "Opening login page"
|
|
747
|
-
});
|
|
1215
|
+
const panel = createLoginLoadingPanel();
|
|
748
1216
|
this.iframeLoadingPanel = panel;
|
|
749
1217
|
container.appendChild(panel);
|
|
750
1218
|
}
|
|
@@ -832,6 +1300,20 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
832
1300
|
break;
|
|
833
1301
|
case "LOGIN_RESIZE":
|
|
834
1302
|
break;
|
|
1303
|
+
case "LOGIN_REDIRECT_REQUIRED":
|
|
1304
|
+
if (!isValidLoginRedirectUrl(data.authUrl)) {
|
|
1305
|
+
logLoginWarn("Login redirect requested with invalid authUrl");
|
|
1306
|
+
options?.onError?.("Login redirect URL is invalid", data);
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
logLoginInfo("Login redirect requested by hosted page", {
|
|
1310
|
+
attemptId: data.attemptId || null,
|
|
1311
|
+
channel: data.channel || null,
|
|
1312
|
+
authUrl: data.authUrl
|
|
1313
|
+
});
|
|
1314
|
+
this.close();
|
|
1315
|
+
window.location.assign(data.authUrl);
|
|
1316
|
+
break;
|
|
835
1317
|
case "LOGIN_ERROR":
|
|
836
1318
|
if (this.completed) {
|
|
837
1319
|
break;
|
|
@@ -855,6 +1337,25 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
855
1337
|
break;
|
|
856
1338
|
}
|
|
857
1339
|
}
|
|
1340
|
+
openLoginRedirect(params, options) {
|
|
1341
|
+
if (typeof window === "undefined") return;
|
|
1342
|
+
const { callbackState, finalUrl, launchPayload } = this.buildOpenContext(
|
|
1343
|
+
params,
|
|
1344
|
+
options
|
|
1345
|
+
);
|
|
1346
|
+
logLoginInfo("Redirecting to hosted login page", {
|
|
1347
|
+
appId: params.appId,
|
|
1348
|
+
callbackState,
|
|
1349
|
+
callbackUrl: launchPayload.callbackUrl || null,
|
|
1350
|
+
hasCallbackUrl: Boolean(launchPayload.callbackUrl),
|
|
1351
|
+
loginUrl: finalUrl,
|
|
1352
|
+
autoClose: options?.autoClose ?? true,
|
|
1353
|
+
displayMode: "redirect",
|
|
1354
|
+
pageUrl: window.location.href,
|
|
1355
|
+
parentOrigin: launchPayload.origin || null
|
|
1356
|
+
});
|
|
1357
|
+
window.location.assign(finalUrl);
|
|
1358
|
+
}
|
|
858
1359
|
listenForLoginCallback(callbackState, options) {
|
|
859
1360
|
try {
|
|
860
1361
|
this.callbackChannel = new BroadcastChannel(
|
|
@@ -931,21 +1432,28 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
931
1432
|
options?.onCancel?.();
|
|
932
1433
|
options?.onClose?.();
|
|
933
1434
|
},
|
|
934
|
-
onMessage: (data,
|
|
1435
|
+
onMessage: (data, _container, event) => {
|
|
935
1436
|
const isIframeEvent = event.source === this.iframe?.contentWindow;
|
|
1437
|
+
if (!isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE")) {
|
|
1438
|
+
logLoginWarn(
|
|
1439
|
+
"Login lifecycle event ignored: event.source does not match iframe contentWindow",
|
|
1440
|
+
{
|
|
1441
|
+
type: data.type,
|
|
1442
|
+
eventOrigin: event.origin,
|
|
1443
|
+
hasIframe: Boolean(this.iframe),
|
|
1444
|
+
hasIframeContentWindow: Boolean(this.iframe?.contentWindow)
|
|
1445
|
+
}
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
936
1448
|
if (isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE") || data.type === "LOGIN_SUCCESS" || data.type === "LOGIN_ERROR") {
|
|
937
1449
|
this.markIframeReady();
|
|
938
1450
|
}
|
|
939
|
-
if (data.type === "LOGIN_RESIZE" && data.height) {
|
|
940
|
-
const maxHeight = window.innerHeight * 0.94;
|
|
941
|
-
container.style.height = `${Math.min(data.height, maxHeight)}px`;
|
|
942
|
-
}
|
|
943
1451
|
this.handleLoginEvent(data, options);
|
|
944
1452
|
},
|
|
945
1453
|
width: "min(520px, 100%)"
|
|
946
1454
|
});
|
|
947
1455
|
if (hostedFrame) {
|
|
948
|
-
this.showIframeLoading(hostedFrame.container
|
|
1456
|
+
this.showIframeLoading(hostedFrame.container);
|
|
949
1457
|
this.startIframeWatchdog({
|
|
950
1458
|
container: hostedFrame.container,
|
|
951
1459
|
finalUrl,
|
|
@@ -1049,11 +1557,19 @@ var LoginUI = class extends HostedFrameModal {
|
|
|
1049
1557
|
}
|
|
1050
1558
|
openLogin(params, options) {
|
|
1051
1559
|
if (typeof window === "undefined") return;
|
|
1052
|
-
const displayMode = options?.displayMode ?? "
|
|
1560
|
+
const displayMode = options?.displayMode ?? "auto";
|
|
1561
|
+
if (displayMode === "redirect") {
|
|
1562
|
+
this.openLoginRedirect(params, options);
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1053
1565
|
if (displayMode === "popup") {
|
|
1054
1566
|
this.openLoginPopup(params, options);
|
|
1055
1567
|
return;
|
|
1056
1568
|
}
|
|
1569
|
+
if (displayMode === "auto" && detectLoginEnvironment().shouldUseRedirectLogin) {
|
|
1570
|
+
this.openLoginRedirect(params, options);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1057
1573
|
this.openLoginModal(params, options);
|
|
1058
1574
|
}
|
|
1059
1575
|
};
|
|
@@ -1063,6 +1579,406 @@ function createLoginUI() {
|
|
|
1063
1579
|
handleLoginCallbackIfPresent({ autoClose: false });
|
|
1064
1580
|
|
|
1065
1581
|
// src/client.ts
|
|
1582
|
+
var DEFAULT_PAYMENT_IFRAME_LOAD_TIMEOUT_MS = 8e3;
|
|
1583
|
+
var PAYMENT_UI_LOG_PREFIX = "[Youidian PaymentUI]";
|
|
1584
|
+
function hexToRgb(value) {
|
|
1585
|
+
const normalized = value.trim().replace(/^#/, "");
|
|
1586
|
+
if (!/^[0-9a-f]{3}([0-9a-f]{3})?$/i.test(normalized)) return null;
|
|
1587
|
+
const hex = normalized.length === 3 ? normalized.split("").map((char) => `${char}${char}`).join("") : normalized;
|
|
1588
|
+
const number = Number.parseInt(hex, 16);
|
|
1589
|
+
return {
|
|
1590
|
+
b: number & 255,
|
|
1591
|
+
g: number >> 8 & 255,
|
|
1592
|
+
r: number >> 16 & 255
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function alphaColor(value, alpha) {
|
|
1596
|
+
const rgb = hexToRgb(value);
|
|
1597
|
+
if (!rgb)
|
|
1598
|
+
return `color-mix(in srgb, ${value} ${Math.round(alpha * 100)}%, transparent)`;
|
|
1599
|
+
return `rgba(${rgb.r},${rgb.g},${rgb.b},${alpha})`;
|
|
1600
|
+
}
|
|
1601
|
+
function resolvePaymentTheme(options) {
|
|
1602
|
+
const primary = options?.theme?.primary || "#16a34a";
|
|
1603
|
+
const secondary = options?.theme?.secondary || "color-mix(in srgb, #16a34a 78%, white)";
|
|
1604
|
+
const foreground = options?.theme?.foreground || "#0f172a";
|
|
1605
|
+
const muted = options?.theme?.muted || "#64748b";
|
|
1606
|
+
const surface = options?.theme?.surface || "#ffffff";
|
|
1607
|
+
return {
|
|
1608
|
+
foreground,
|
|
1609
|
+
muted,
|
|
1610
|
+
primary,
|
|
1611
|
+
primary10: alphaColor(primary, 0.1),
|
|
1612
|
+
primary14: alphaColor(primary, 0.14),
|
|
1613
|
+
primary20: alphaColor(primary, 0.2),
|
|
1614
|
+
primary28: alphaColor(primary, 0.28),
|
|
1615
|
+
primary36: alphaColor(primary, 0.36),
|
|
1616
|
+
primary64: alphaColor(primary, 0.64),
|
|
1617
|
+
secondary,
|
|
1618
|
+
surface
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
function logPaymentInfo(message, details) {
|
|
1622
|
+
if (typeof console === "undefined") return;
|
|
1623
|
+
console.info(PAYMENT_UI_LOG_PREFIX, message, details || {});
|
|
1624
|
+
}
|
|
1625
|
+
function logPaymentWarn(message, details) {
|
|
1626
|
+
if (typeof console === "undefined") return;
|
|
1627
|
+
console.warn(PAYMENT_UI_LOG_PREFIX, message, details || {});
|
|
1628
|
+
}
|
|
1629
|
+
function getPaymentIframeLoadTimeoutMs(options) {
|
|
1630
|
+
const timeout = options?.iframeLoadTimeoutMs;
|
|
1631
|
+
if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
|
|
1632
|
+
return DEFAULT_PAYMENT_IFRAME_LOAD_TIMEOUT_MS;
|
|
1633
|
+
}
|
|
1634
|
+
return Math.max(0, timeout);
|
|
1635
|
+
}
|
|
1636
|
+
function isChinesePaymentLocale(locale) {
|
|
1637
|
+
return locale?.toLowerCase().startsWith("zh") || false;
|
|
1638
|
+
}
|
|
1639
|
+
function getPaymentFallbackText(options) {
|
|
1640
|
+
const isChinese = isChinesePaymentLocale(options?.locale);
|
|
1641
|
+
return {
|
|
1642
|
+
buttonText: options?.fallbackButtonText || (isChinese ? "\u5728\u65B0\u9875\u9762\u4E2D\u6253\u5F00" : "Open in new page"),
|
|
1643
|
+
description: isChinese ? "\u5F53\u524D\u7F51\u7EDC\u53EF\u80FD\u8F83\u6162\uFF0C\u5EFA\u8BAE\u91CD\u65B0\u5C1D\u8BD5\u6216\u5728\u65B0\u9875\u9762\u4E2D\u6253\u5F00\uFF0C\u4EE5\u83B7\u5F97\u66F4\u6D41\u7545\u7684\u652F\u4ED8\u4F53\u9A8C\u3002" : "Your network may be slow. Try again or open the payment page in a new page to continue.",
|
|
1644
|
+
retryText: options?.fallbackRetryText || (isChinese ? "\u91CD\u65B0\u5C1D\u8BD5" : "Try again"),
|
|
1645
|
+
title: isChinese ? "\u652F\u4ED8\u9875\u9762\u6253\u5F00\u8F83\u6162" : "Payment page is taking longer"
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
function getPaymentBrandText(options) {
|
|
1649
|
+
const isChinese = isChinesePaymentLocale(options?.locale);
|
|
1650
|
+
return {
|
|
1651
|
+
language: isChinese ? "zh" : "en",
|
|
1652
|
+
name: isChinese ? "\u4F18\u6613\u70B9" : "Youidian",
|
|
1653
|
+
subtitle: isChinese ? "\u5F00\u53D1\u8005\u670D\u52A1\u5E73\u53F0" : "Developer Service Platform"
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
function getPaymentBrandLoadingText(options) {
|
|
1657
|
+
const isChinese = isChinesePaymentLocale(options?.locale);
|
|
1658
|
+
return {
|
|
1659
|
+
language: isChinese ? "zh" : "en",
|
|
1660
|
+
name: isChinese ? "\u4F18\u6613\u70B9" : "Youidian",
|
|
1661
|
+
subtitle: isChinese ? "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0" : "Developer Service"
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
function getPaymentLoadingText(options) {
|
|
1665
|
+
const isChinese = isChinesePaymentLocale(options?.locale);
|
|
1666
|
+
return options?.loadingTitle || options?.loadingDescription || (isChinese ? "\u52A0\u8F7D\u4E2D..." : "Loading...");
|
|
1667
|
+
}
|
|
1668
|
+
function applyPaymentPanelStyle(panel, theme) {
|
|
1669
|
+
Object.assign(panel.style, {
|
|
1670
|
+
position: "absolute",
|
|
1671
|
+
inset: "0",
|
|
1672
|
+
display: "flex",
|
|
1673
|
+
alignItems: "center",
|
|
1674
|
+
justifyContent: "center",
|
|
1675
|
+
borderRadius: "28px",
|
|
1676
|
+
background: `linear-gradient(180deg, ${theme.surface} 0%, rgba(248,250,252,0.98) 100%)`,
|
|
1677
|
+
color: theme.foreground,
|
|
1678
|
+
padding: "28px 24px",
|
|
1679
|
+
textAlign: "center",
|
|
1680
|
+
zIndex: "1",
|
|
1681
|
+
boxShadow: "none",
|
|
1682
|
+
backdropFilter: "blur(16px)"
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
function createPaymentIcon(type) {
|
|
1686
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1687
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
1688
|
+
svg.setAttribute("fill", "none");
|
|
1689
|
+
svg.setAttribute("aria-hidden", "true");
|
|
1690
|
+
Object.assign(svg.style, {
|
|
1691
|
+
display: "block",
|
|
1692
|
+
flex: "0 0 auto"
|
|
1693
|
+
});
|
|
1694
|
+
const paths = type === "external" ? [
|
|
1695
|
+
"M7 17L17 7",
|
|
1696
|
+
"M10 7H17V14",
|
|
1697
|
+
"M6.5 6.5H5.5A2.5 2.5 0 0 0 3 9V18.5A2.5 2.5 0 0 0 5.5 21H15A2.5 2.5 0 0 0 17.5 18.5V17.5"
|
|
1698
|
+
] : type === "refresh" ? [
|
|
1699
|
+
"M20 6V11H15",
|
|
1700
|
+
"M4 18V13H9",
|
|
1701
|
+
"M18.2 9A7 7 0 0 0 6.2 6.6L4 9",
|
|
1702
|
+
"M5.8 15A7 7 0 0 0 17.8 17.4L20 15"
|
|
1703
|
+
] : ["M12 7V12L15 14", "M21 12A9 9 0 1 1 3 12A9 9 0 0 1 21 12"];
|
|
1704
|
+
for (const value of paths) {
|
|
1705
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1706
|
+
path.setAttribute("d", value);
|
|
1707
|
+
path.setAttribute("stroke", "currentColor");
|
|
1708
|
+
path.setAttribute("stroke-width", "2");
|
|
1709
|
+
path.setAttribute("stroke-linecap", "round");
|
|
1710
|
+
path.setAttribute("stroke-linejoin", "round");
|
|
1711
|
+
svg.appendChild(path);
|
|
1712
|
+
}
|
|
1713
|
+
return svg;
|
|
1714
|
+
}
|
|
1715
|
+
function createPaymentFallbackArtwork(theme) {
|
|
1716
|
+
const artwork = document.createElement("div");
|
|
1717
|
+
Object.assign(artwork.style, {
|
|
1718
|
+
height: "164px",
|
|
1719
|
+
margin: "0 auto 34px",
|
|
1720
|
+
position: "relative",
|
|
1721
|
+
width: "184px"
|
|
1722
|
+
});
|
|
1723
|
+
const wash = document.createElement("div");
|
|
1724
|
+
Object.assign(wash.style, {
|
|
1725
|
+
position: "absolute",
|
|
1726
|
+
left: "16px",
|
|
1727
|
+
top: "20px",
|
|
1728
|
+
width: "152px",
|
|
1729
|
+
height: "126px",
|
|
1730
|
+
borderRadius: "48% 52% 43% 57% / 54% 42% 58% 46%",
|
|
1731
|
+
background: `radial-gradient(circle at 48% 42%, ${theme.primary20}, ${theme.primary10} 52%, transparent 76%)`,
|
|
1732
|
+
filter: "blur(1px)",
|
|
1733
|
+
transform: "rotate(-10deg)"
|
|
1734
|
+
});
|
|
1735
|
+
const blob = document.createElement("div");
|
|
1736
|
+
Object.assign(blob.style, {
|
|
1737
|
+
position: "absolute",
|
|
1738
|
+
left: "26px",
|
|
1739
|
+
top: "28px",
|
|
1740
|
+
width: "132px",
|
|
1741
|
+
height: "116px",
|
|
1742
|
+
borderRadius: "42% 58% 53% 47% / 45% 39% 61% 55%",
|
|
1743
|
+
background: `linear-gradient(150deg, ${theme.primary14}, ${theme.primary10})`,
|
|
1744
|
+
boxShadow: `0 30px 64px ${theme.primary14}`,
|
|
1745
|
+
transform: "rotate(8deg)"
|
|
1746
|
+
});
|
|
1747
|
+
const tile = document.createElement("div");
|
|
1748
|
+
Object.assign(tile.style, {
|
|
1749
|
+
position: "absolute",
|
|
1750
|
+
left: "50%",
|
|
1751
|
+
top: "50%",
|
|
1752
|
+
transform: "translate(-50%, -50%) rotate(-4deg)",
|
|
1753
|
+
width: "112px",
|
|
1754
|
+
height: "106px",
|
|
1755
|
+
borderRadius: "38% 62% 50% 50% / 44% 43% 57% 56%",
|
|
1756
|
+
background: `linear-gradient(145deg, ${theme.primary}, ${theme.secondary})`,
|
|
1757
|
+
border: `1px solid ${theme.primary36}`,
|
|
1758
|
+
color: "#ffffff",
|
|
1759
|
+
display: "inline-flex",
|
|
1760
|
+
alignItems: "center",
|
|
1761
|
+
justifyContent: "center",
|
|
1762
|
+
boxShadow: `0 26px 54px ${theme.primary28}`
|
|
1763
|
+
});
|
|
1764
|
+
const icon = createPaymentIcon("slow");
|
|
1765
|
+
Object.assign(icon.style, {
|
|
1766
|
+
width: "52px",
|
|
1767
|
+
height: "52px",
|
|
1768
|
+
transform: "rotate(4deg)"
|
|
1769
|
+
});
|
|
1770
|
+
tile.appendChild(icon);
|
|
1771
|
+
artwork.appendChild(wash);
|
|
1772
|
+
artwork.appendChild(blob);
|
|
1773
|
+
artwork.appendChild(tile);
|
|
1774
|
+
return artwork;
|
|
1775
|
+
}
|
|
1776
|
+
function createPaymentBrandLoading(options, theme) {
|
|
1777
|
+
const brand = getPaymentBrandLoadingText(options);
|
|
1778
|
+
const wrapper = document.createElement("div");
|
|
1779
|
+
Object.assign(wrapper.style, {
|
|
1780
|
+
display: "flex",
|
|
1781
|
+
flexDirection: "column",
|
|
1782
|
+
alignItems: "center",
|
|
1783
|
+
justifyContent: "center",
|
|
1784
|
+
margin: "0 auto 16px"
|
|
1785
|
+
});
|
|
1786
|
+
wrapper.appendChild(
|
|
1787
|
+
createSdkBrandLogo({
|
|
1788
|
+
animated: true,
|
|
1789
|
+
brand,
|
|
1790
|
+
brandWidth: 292,
|
|
1791
|
+
copyWidth: 176,
|
|
1792
|
+
gap: 6,
|
|
1793
|
+
markMarginRight: -10,
|
|
1794
|
+
markSize: 112,
|
|
1795
|
+
nameSize: 39,
|
|
1796
|
+
subtitleMarginTop: 8,
|
|
1797
|
+
subtitleSize: 14,
|
|
1798
|
+
subtitleWeight: "700",
|
|
1799
|
+
theme,
|
|
1800
|
+
variant: "brand"
|
|
1801
|
+
})
|
|
1802
|
+
);
|
|
1803
|
+
return wrapper;
|
|
1804
|
+
}
|
|
1805
|
+
function createPaymentPlatformLogo(options, theme) {
|
|
1806
|
+
const brand = getPaymentBrandText(options);
|
|
1807
|
+
const wrapper = document.createElement("div");
|
|
1808
|
+
Object.assign(wrapper.style, {
|
|
1809
|
+
display: "flex",
|
|
1810
|
+
alignItems: "center",
|
|
1811
|
+
justifyContent: "center",
|
|
1812
|
+
margin: "0 auto 10px"
|
|
1813
|
+
});
|
|
1814
|
+
wrapper.appendChild(
|
|
1815
|
+
createSdkBrandLogo({
|
|
1816
|
+
brand,
|
|
1817
|
+
markSize: 48,
|
|
1818
|
+
nameSize: brand.language === "zh" ? 22 : 22,
|
|
1819
|
+
subtitleSize: brand.language === "zh" ? 9 : 9,
|
|
1820
|
+
theme
|
|
1821
|
+
})
|
|
1822
|
+
);
|
|
1823
|
+
return wrapper;
|
|
1824
|
+
}
|
|
1825
|
+
function createPaymentPanelContent() {
|
|
1826
|
+
const content = document.createElement("div");
|
|
1827
|
+
Object.assign(content.style, {
|
|
1828
|
+
display: "flex",
|
|
1829
|
+
flexDirection: "column",
|
|
1830
|
+
justifyContent: "center",
|
|
1831
|
+
maxWidth: "364px",
|
|
1832
|
+
minHeight: "100%",
|
|
1833
|
+
width: "100%"
|
|
1834
|
+
});
|
|
1835
|
+
return content;
|
|
1836
|
+
}
|
|
1837
|
+
function createPaymentPanelTitle(value, theme) {
|
|
1838
|
+
const title = document.createElement("div");
|
|
1839
|
+
title.textContent = value;
|
|
1840
|
+
Object.assign(title.style, {
|
|
1841
|
+
color: theme?.foreground || "#0f172a",
|
|
1842
|
+
fontSize: "24px",
|
|
1843
|
+
fontWeight: "900",
|
|
1844
|
+
lineHeight: "1.22",
|
|
1845
|
+
marginBottom: "16px"
|
|
1846
|
+
});
|
|
1847
|
+
return title;
|
|
1848
|
+
}
|
|
1849
|
+
function createPaymentPanelDescription(value, theme) {
|
|
1850
|
+
const description = document.createElement("div");
|
|
1851
|
+
description.textContent = value;
|
|
1852
|
+
Object.assign(description.style, {
|
|
1853
|
+
color: theme?.muted || "#64748b",
|
|
1854
|
+
fontSize: "14px",
|
|
1855
|
+
fontWeight: "600",
|
|
1856
|
+
lineHeight: "1.62",
|
|
1857
|
+
margin: "0 auto",
|
|
1858
|
+
maxWidth: "320px"
|
|
1859
|
+
});
|
|
1860
|
+
return description;
|
|
1861
|
+
}
|
|
1862
|
+
function createPaymentLoadingDescription(value, theme) {
|
|
1863
|
+
const description = document.createElement("div");
|
|
1864
|
+
description.textContent = value;
|
|
1865
|
+
Object.assign(description.style, {
|
|
1866
|
+
color: theme?.muted || "#64748b",
|
|
1867
|
+
fontSize: "14px",
|
|
1868
|
+
fontWeight: "500",
|
|
1869
|
+
lineHeight: "1.25",
|
|
1870
|
+
margin: "12px auto 0",
|
|
1871
|
+
maxWidth: "320px"
|
|
1872
|
+
});
|
|
1873
|
+
return description;
|
|
1874
|
+
}
|
|
1875
|
+
function createPaymentFallbackMessage(options) {
|
|
1876
|
+
const message = document.createElement("div");
|
|
1877
|
+
Object.assign(message.style, {
|
|
1878
|
+
margin: "0 auto",
|
|
1879
|
+
textAlign: "center"
|
|
1880
|
+
});
|
|
1881
|
+
message.appendChild(createPaymentFallbackArtwork(options.theme));
|
|
1882
|
+
message.appendChild(createPaymentPanelTitle(options.title, options.theme));
|
|
1883
|
+
message.appendChild(
|
|
1884
|
+
createPaymentPanelDescription(options.description, options.theme)
|
|
1885
|
+
);
|
|
1886
|
+
return message;
|
|
1887
|
+
}
|
|
1888
|
+
function createPaymentLoadingPanel(options) {
|
|
1889
|
+
const panel = document.createElement("div");
|
|
1890
|
+
const theme = resolvePaymentTheme(options);
|
|
1891
|
+
applyPaymentPanelStyle(panel, theme);
|
|
1892
|
+
const content = createPaymentPanelContent();
|
|
1893
|
+
content.appendChild(createPaymentBrandLoading(options, theme));
|
|
1894
|
+
content.appendChild(
|
|
1895
|
+
createPaymentLoadingDescription(getPaymentLoadingText(options), theme)
|
|
1896
|
+
);
|
|
1897
|
+
panel.appendChild(content);
|
|
1898
|
+
return panel;
|
|
1899
|
+
}
|
|
1900
|
+
function createPaymentButton(value, options) {
|
|
1901
|
+
const primary = options?.primary || false;
|
|
1902
|
+
const theme = options?.theme || resolvePaymentTheme();
|
|
1903
|
+
const button = document.createElement("button");
|
|
1904
|
+
button.type = "button";
|
|
1905
|
+
Object.assign(button.style, {
|
|
1906
|
+
width: "100%",
|
|
1907
|
+
height: "50px",
|
|
1908
|
+
borderRadius: "14px",
|
|
1909
|
+
border: primary ? `1px solid ${theme.primary}` : `1px solid ${theme.primary64}`,
|
|
1910
|
+
background: primary ? `linear-gradient(135deg, ${theme.primary}, ${theme.secondary})` : "#ffffff",
|
|
1911
|
+
color: primary ? "#ffffff" : theme.primary,
|
|
1912
|
+
cursor: "pointer",
|
|
1913
|
+
display: "inline-flex",
|
|
1914
|
+
alignItems: "center",
|
|
1915
|
+
justifyContent: "center",
|
|
1916
|
+
gap: "10px",
|
|
1917
|
+
fontSize: "15px",
|
|
1918
|
+
fontWeight: "800",
|
|
1919
|
+
marginTop: primary ? "12px" : "0",
|
|
1920
|
+
boxShadow: primary ? `0 14px 28px ${theme.primary20}` : "none"
|
|
1921
|
+
});
|
|
1922
|
+
if (options?.icon) {
|
|
1923
|
+
const icon = createPaymentIcon(options.icon);
|
|
1924
|
+
Object.assign(icon.style, {
|
|
1925
|
+
width: "17px",
|
|
1926
|
+
height: "17px"
|
|
1927
|
+
});
|
|
1928
|
+
button.appendChild(icon);
|
|
1929
|
+
}
|
|
1930
|
+
const label = document.createElement("span");
|
|
1931
|
+
label.textContent = value;
|
|
1932
|
+
button.appendChild(label);
|
|
1933
|
+
return button;
|
|
1934
|
+
}
|
|
1935
|
+
function createPaymentFallbackPanel(options) {
|
|
1936
|
+
const panel = document.createElement("div");
|
|
1937
|
+
const theme = resolvePaymentTheme(options.paymentOptions);
|
|
1938
|
+
applyPaymentPanelStyle(panel, theme);
|
|
1939
|
+
const content = createPaymentPanelContent();
|
|
1940
|
+
Object.assign(content.style, {
|
|
1941
|
+
justifyContent: "space-between"
|
|
1942
|
+
});
|
|
1943
|
+
const main = document.createElement("div");
|
|
1944
|
+
Object.assign(main.style, {
|
|
1945
|
+
display: "flex",
|
|
1946
|
+
flexDirection: "column",
|
|
1947
|
+
alignItems: "center",
|
|
1948
|
+
marginTop: "38px",
|
|
1949
|
+
width: "100%"
|
|
1950
|
+
});
|
|
1951
|
+
main.appendChild(
|
|
1952
|
+
createPaymentFallbackMessage({
|
|
1953
|
+
description: options.description,
|
|
1954
|
+
theme,
|
|
1955
|
+
title: options.title
|
|
1956
|
+
})
|
|
1957
|
+
);
|
|
1958
|
+
const actions = document.createElement("div");
|
|
1959
|
+
Object.assign(actions.style, {
|
|
1960
|
+
marginTop: "46px",
|
|
1961
|
+
width: "100%"
|
|
1962
|
+
});
|
|
1963
|
+
const retryButton = createPaymentButton(options.retryText, {
|
|
1964
|
+
icon: "refresh",
|
|
1965
|
+
theme
|
|
1966
|
+
});
|
|
1967
|
+
retryButton.onclick = options.onRetry;
|
|
1968
|
+
actions.appendChild(retryButton);
|
|
1969
|
+
const openButton = createPaymentButton(options.buttonText, {
|
|
1970
|
+
icon: "external",
|
|
1971
|
+
primary: true,
|
|
1972
|
+
theme
|
|
1973
|
+
});
|
|
1974
|
+
openButton.onclick = options.onOpen;
|
|
1975
|
+
actions.appendChild(openButton);
|
|
1976
|
+
main.appendChild(actions);
|
|
1977
|
+
content.appendChild(main);
|
|
1978
|
+
content.appendChild(createPaymentPlatformLogo(options.paymentOptions, theme));
|
|
1979
|
+
panel.appendChild(content);
|
|
1980
|
+
return panel;
|
|
1981
|
+
}
|
|
1066
1982
|
var PaymentUI = class extends HostedFrameModal {
|
|
1067
1983
|
constructor() {
|
|
1068
1984
|
super(...arguments);
|
|
@@ -1071,6 +1987,127 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1071
1987
|
__publicField(this, "activeOrderId", null);
|
|
1072
1988
|
__publicField(this, "paymentCompleted", false);
|
|
1073
1989
|
__publicField(this, "cancelRequestedOrderId", null);
|
|
1990
|
+
__publicField(this, "iframeFallbackPanel", null);
|
|
1991
|
+
__publicField(this, "iframeLoadingPanel", null);
|
|
1992
|
+
__publicField(this, "iframeLoadTimer", null);
|
|
1993
|
+
__publicField(this, "iframeReady", false);
|
|
1994
|
+
}
|
|
1995
|
+
cleanupPaymentFrameState() {
|
|
1996
|
+
if (typeof window !== "undefined" && this.iframeLoadTimer) {
|
|
1997
|
+
window.clearTimeout(this.iframeLoadTimer);
|
|
1998
|
+
}
|
|
1999
|
+
if (this.iframeFallbackPanel?.parentNode) {
|
|
2000
|
+
this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
|
|
2001
|
+
}
|
|
2002
|
+
if (this.iframeLoadingPanel?.parentNode) {
|
|
2003
|
+
this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
|
|
2004
|
+
}
|
|
2005
|
+
this.iframeFallbackPanel = null;
|
|
2006
|
+
this.iframeLoadingPanel = null;
|
|
2007
|
+
this.iframeLoadTimer = null;
|
|
2008
|
+
this.iframeReady = false;
|
|
2009
|
+
}
|
|
2010
|
+
markPaymentIframeReady() {
|
|
2011
|
+
if (this.iframeReady) return;
|
|
2012
|
+
this.iframeReady = true;
|
|
2013
|
+
if (typeof window !== "undefined" && this.iframeLoadTimer) {
|
|
2014
|
+
window.clearTimeout(this.iframeLoadTimer);
|
|
2015
|
+
this.iframeLoadTimer = null;
|
|
2016
|
+
}
|
|
2017
|
+
if (this.iframeFallbackPanel?.parentNode) {
|
|
2018
|
+
this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
|
|
2019
|
+
}
|
|
2020
|
+
if (this.iframeLoadingPanel?.parentNode) {
|
|
2021
|
+
this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
|
|
2022
|
+
}
|
|
2023
|
+
this.iframeFallbackPanel = null;
|
|
2024
|
+
this.iframeLoadingPanel = null;
|
|
2025
|
+
}
|
|
2026
|
+
showPaymentIframeLoading(container, options) {
|
|
2027
|
+
if (this.iframeLoadingPanel || this.iframeReady) return;
|
|
2028
|
+
const panel = createPaymentLoadingPanel(options);
|
|
2029
|
+
this.iframeLoadingPanel = panel;
|
|
2030
|
+
container.appendChild(panel);
|
|
2031
|
+
}
|
|
2032
|
+
openFallbackPaymentPage(finalUrl, options) {
|
|
2033
|
+
if (typeof window === "undefined") return;
|
|
2034
|
+
const popup = window.open(
|
|
2035
|
+
finalUrl,
|
|
2036
|
+
"youidian-payment-checkout",
|
|
2037
|
+
"popup=yes,width=480,height=760,resizable=yes,scrollbars=yes"
|
|
2038
|
+
);
|
|
2039
|
+
if (popup) {
|
|
2040
|
+
logPaymentInfo("Opened hosted checkout fallback page", {
|
|
2041
|
+
checkoutUrl: finalUrl
|
|
2042
|
+
});
|
|
2043
|
+
options?.onFallbackOpen?.(finalUrl);
|
|
2044
|
+
try {
|
|
2045
|
+
popup.focus();
|
|
2046
|
+
} catch {
|
|
2047
|
+
}
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
logPaymentWarn("Hosted checkout fallback popup was blocked", {
|
|
2051
|
+
checkoutUrl: finalUrl
|
|
2052
|
+
});
|
|
2053
|
+
options?.onFallbackBlocked?.(finalUrl);
|
|
2054
|
+
window.location.assign(finalUrl);
|
|
2055
|
+
}
|
|
2056
|
+
showPaymentIframeFallback(options) {
|
|
2057
|
+
if (this.iframeReady || this.iframeFallbackPanel) return;
|
|
2058
|
+
const { container, finalUrl, paymentOptions } = options;
|
|
2059
|
+
logPaymentWarn("Hosted checkout iframe did not report ready in time", {
|
|
2060
|
+
checkoutUrl: finalUrl
|
|
2061
|
+
});
|
|
2062
|
+
if (this.iframeLoadingPanel?.parentNode) {
|
|
2063
|
+
this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
|
|
2064
|
+
}
|
|
2065
|
+
this.iframeLoadingPanel = null;
|
|
2066
|
+
const fallbackText = getPaymentFallbackText(paymentOptions);
|
|
2067
|
+
const panel = createPaymentFallbackPanel({
|
|
2068
|
+
buttonText: fallbackText.buttonText,
|
|
2069
|
+
description: fallbackText.description,
|
|
2070
|
+
onOpen: () => {
|
|
2071
|
+
this.openFallbackPaymentPage(finalUrl, paymentOptions);
|
|
2072
|
+
},
|
|
2073
|
+
onRetry: () => {
|
|
2074
|
+
this.iframeFallbackPanel = null;
|
|
2075
|
+
if (panel.parentNode) {
|
|
2076
|
+
panel.parentNode.removeChild(panel);
|
|
2077
|
+
}
|
|
2078
|
+
this.iframeReady = false;
|
|
2079
|
+
this.showPaymentIframeLoading(container, paymentOptions);
|
|
2080
|
+
if (this.iframe) {
|
|
2081
|
+
this.iframe.src = finalUrl;
|
|
2082
|
+
}
|
|
2083
|
+
this.startPaymentIframeWatchdog({
|
|
2084
|
+
container,
|
|
2085
|
+
finalUrl,
|
|
2086
|
+
paymentOptions
|
|
2087
|
+
});
|
|
2088
|
+
},
|
|
2089
|
+
paymentOptions,
|
|
2090
|
+
retryText: fallbackText.retryText,
|
|
2091
|
+
title: fallbackText.title
|
|
2092
|
+
});
|
|
2093
|
+
this.iframeFallbackPanel = panel;
|
|
2094
|
+
container.appendChild(panel);
|
|
2095
|
+
paymentOptions?.onFallbackVisible?.();
|
|
2096
|
+
}
|
|
2097
|
+
startPaymentIframeWatchdog(options) {
|
|
2098
|
+
if (typeof window === "undefined") return;
|
|
2099
|
+
if (this.iframeLoadTimer) {
|
|
2100
|
+
window.clearTimeout(this.iframeLoadTimer);
|
|
2101
|
+
}
|
|
2102
|
+
const timeoutMs = getPaymentIframeLoadTimeoutMs(options.paymentOptions);
|
|
2103
|
+
if (timeoutMs === 0) {
|
|
2104
|
+
this.showPaymentIframeFallback(options);
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
this.iframeLoadTimer = window.setTimeout(() => {
|
|
2108
|
+
this.iframeLoadTimer = null;
|
|
2109
|
+
this.showPaymentIframeFallback(options);
|
|
2110
|
+
}, timeoutMs);
|
|
1074
2111
|
}
|
|
1075
2112
|
cancelHostedOrder(reason = "user_cancel", orderId) {
|
|
1076
2113
|
const targetOrderId = orderId || this.activeOrderId;
|
|
@@ -1113,6 +2150,7 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1113
2150
|
this.activeOrderId = null;
|
|
1114
2151
|
this.paymentCompleted = false;
|
|
1115
2152
|
this.cancelRequestedOrderId = null;
|
|
2153
|
+
this.cleanupPaymentFrameState();
|
|
1116
2154
|
if (typeof urlOrParams === "string") {
|
|
1117
2155
|
checkoutUrl = urlOrParams;
|
|
1118
2156
|
try {
|
|
@@ -1181,14 +2219,21 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1181
2219
|
checkoutUrl,
|
|
1182
2220
|
getAutoResolvedLocale(options?.locale)
|
|
1183
2221
|
);
|
|
1184
|
-
this.openHostedFrame(finalUrl, {
|
|
2222
|
+
const hostedFrame = this.openHostedFrame(finalUrl, {
|
|
1185
2223
|
allowedOrigin: options?.allowedOrigin,
|
|
2224
|
+
height: "min(760px, calc(100vh - 32px))",
|
|
1186
2225
|
onCloseButton: () => {
|
|
1187
2226
|
this.cancelHostedOrder("modal_close");
|
|
1188
2227
|
options?.onCancel?.(this.activeOrderId || void 0);
|
|
1189
2228
|
},
|
|
1190
|
-
onMessage: (data, container) => {
|
|
2229
|
+
onMessage: (data, container, event) => {
|
|
2230
|
+
const isIframeMessage = event.source === this.iframe?.contentWindow;
|
|
2231
|
+
if (isIframeMessage && (data.type === "PAYMENT_READY" || data.type === "PAYMENT_RESIZE" || data.type === "PAYMENT_STARTED")) {
|
|
2232
|
+
this.markPaymentIframeReady();
|
|
2233
|
+
}
|
|
1191
2234
|
switch (data.type) {
|
|
2235
|
+
case "PAYMENT_READY":
|
|
2236
|
+
break;
|
|
1192
2237
|
case "PAYMENT_STARTED":
|
|
1193
2238
|
if (data.orderId) {
|
|
1194
2239
|
this.activeOrderId = data.orderId;
|
|
@@ -1203,11 +2248,6 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1203
2248
|
options?.onCancel?.(data.orderId);
|
|
1204
2249
|
break;
|
|
1205
2250
|
case "PAYMENT_RESIZE":
|
|
1206
|
-
if (data.height) {
|
|
1207
|
-
const maxHeight = window.innerHeight * 0.9;
|
|
1208
|
-
const newHeight = Math.min(data.height, maxHeight);
|
|
1209
|
-
container.style.height = `${newHeight}px`;
|
|
1210
|
-
}
|
|
1211
2251
|
break;
|
|
1212
2252
|
case "PAYMENT_CLOSE":
|
|
1213
2253
|
this.cancelHostedOrder("payment_close", data.orderId);
|
|
@@ -1215,8 +2255,21 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1215
2255
|
options?.onClose?.();
|
|
1216
2256
|
break;
|
|
1217
2257
|
}
|
|
1218
|
-
}
|
|
2258
|
+
},
|
|
2259
|
+
width: "min(450px, 100%)"
|
|
1219
2260
|
});
|
|
2261
|
+
if (hostedFrame) {
|
|
2262
|
+
this.showPaymentIframeLoading(hostedFrame.container, options);
|
|
2263
|
+
this.startPaymentIframeWatchdog({
|
|
2264
|
+
container: hostedFrame.container,
|
|
2265
|
+
finalUrl,
|
|
2266
|
+
paymentOptions: options
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
close() {
|
|
2271
|
+
super.close();
|
|
2272
|
+
this.cleanupPaymentFrameState();
|
|
1220
2273
|
}
|
|
1221
2274
|
/**
|
|
1222
2275
|
* Poll order status from integrator's API endpoint
|
|
@@ -1262,10 +2315,685 @@ var PaymentUI = class extends HostedFrameModal {
|
|
|
1262
2315
|
});
|
|
1263
2316
|
}
|
|
1264
2317
|
};
|
|
2318
|
+
var MessageUI = class {
|
|
2319
|
+
openWechatBinding(options) {
|
|
2320
|
+
const bindingUrl = options.bindingUrl?.trim();
|
|
2321
|
+
if (!bindingUrl) {
|
|
2322
|
+
throw new Error("bindingUrl is required");
|
|
2323
|
+
}
|
|
2324
|
+
if (options.displayMode === "popup") {
|
|
2325
|
+
const popup = window.open(
|
|
2326
|
+
bindingUrl,
|
|
2327
|
+
options.popupName || "youidian-wechat-message-binding",
|
|
2328
|
+
"width=480,height=720,noopener,noreferrer"
|
|
2329
|
+
);
|
|
2330
|
+
if (!popup) {
|
|
2331
|
+
window.location.href = bindingUrl;
|
|
2332
|
+
}
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
window.location.href = bindingUrl;
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
function createMessageUI() {
|
|
2339
|
+
return new MessageUI();
|
|
2340
|
+
}
|
|
1265
2341
|
function createPaymentUI() {
|
|
1266
2342
|
return new PaymentUI();
|
|
1267
2343
|
}
|
|
1268
2344
|
|
|
2345
|
+
// src/feedback.ts
|
|
2346
|
+
var DEFAULT_CATEGORIES = [
|
|
2347
|
+
{ value: "BUG", label: "\u95EE\u9898\u53CD\u9988" },
|
|
2348
|
+
{ value: "PAYMENT", label: "\u652F\u4ED8\u95EE\u9898" },
|
|
2349
|
+
{ value: "LOGIN", label: "\u767B\u5F55\u95EE\u9898" },
|
|
2350
|
+
{ value: "SUGGESTION", label: "\u529F\u80FD\u5EFA\u8BAE" },
|
|
2351
|
+
{ value: "OTHER", label: "\u5176\u4ED6" }
|
|
2352
|
+
];
|
|
2353
|
+
var STYLE_ID = "youidian-feedback-widget-style";
|
|
2354
|
+
var MAX_IMAGES_PER_MESSAGE = 4;
|
|
2355
|
+
var DEFAULT_PANEL_TITLE = "\u53CD\u9988";
|
|
2356
|
+
var DEFAULT_PANEL_SUBTITLE = "\u63D0\u4EA4\u95EE\u9898\u3001\u5EFA\u8BAE\u6216\u4F7F\u7528\u53CD\u9988\u3002";
|
|
2357
|
+
var DEFAULT_ATTACHMENTS_LABEL = "\u652F\u6301\u7C98\u8D34\u3001\u62D6\u5165\u6216\u4E0A\u4F20\u622A\u56FE\uFF0C\u6700\u591A 4 \u5F20";
|
|
2358
|
+
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>`;
|
|
2359
|
+
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>`;
|
|
2360
|
+
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>`;
|
|
2361
|
+
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>`;
|
|
2362
|
+
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>`;
|
|
2363
|
+
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>`;
|
|
2364
|
+
function ensureStyle() {
|
|
2365
|
+
if (typeof document === "undefined") return;
|
|
2366
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
2367
|
+
const style = document.createElement("style");
|
|
2368
|
+
style.id = STYLE_ID;
|
|
2369
|
+
style.textContent = `
|
|
2370
|
+
.yd-feedback-root,.yd-feedback-root *{box-sizing:border-box}
|
|
2371
|
+
.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)}
|
|
2372
|
+
.yd-feedback-root button,.yd-feedback-root input,.yd-feedback-root textarea{font:inherit}
|
|
2373
|
+
.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}
|
|
2374
|
+
.yd-feedback-launcher:hover{transform:translateY(-2px);box-shadow:0 22px 56px color-mix(in srgb,var(--yd-feedback-primary) 30%,transparent)}
|
|
2375
|
+
.yd-feedback-launcher:focus-visible{outline:3px solid color-mix(in srgb,var(--yd-feedback-ring) 32%,transparent);outline-offset:3px}
|
|
2376
|
+
.yd-feedback-launcher svg{width:27px;height:27px}
|
|
2377
|
+
.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)}
|
|
2378
|
+
.yd-feedback-panel[data-open="true"]{display:grid}
|
|
2379
|
+
.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)}
|
|
2380
|
+
.yd-feedback-title{margin:0;font-size:22px;line-height:1.15;font-weight:760;letter-spacing:0;color:var(--yd-feedback-fg)}
|
|
2381
|
+
.yd-feedback-subtitle{margin:8px 0 0;color:var(--yd-feedback-muted-fg);font-size:13px;line-height:1.45}
|
|
2382
|
+
.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}
|
|
2383
|
+
.yd-feedback-close:hover{background:var(--yd-feedback-muted);color:var(--yd-feedback-fg)}
|
|
2384
|
+
.yd-feedback-close svg{width:24px;height:24px}
|
|
2385
|
+
.yd-feedback-shell{display:grid;min-height:0;grid-template-columns:230px minmax(0,1fr)}
|
|
2386
|
+
.yd-feedback-sidebar{min-width:0;border-right:1px solid var(--yd-feedback-border);background:var(--yd-feedback-muted)}
|
|
2387
|
+
.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)}
|
|
2388
|
+
.yd-feedback-sidebar-title{display:flex;align-items:center;gap:9px;color:var(--yd-feedback-fg);font-size:15px;font-weight:720}
|
|
2389
|
+
.yd-feedback-sidebar-title svg{width:20px;height:20px;color:var(--yd-feedback-primary)}
|
|
2390
|
+
.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}
|
|
2391
|
+
.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))}
|
|
2392
|
+
.yd-feedback-icon-button svg{width:18px;height:18px}
|
|
2393
|
+
.yd-feedback-thread-list{padding:12px}
|
|
2394
|
+
.yd-feedback-thread-card{display:none;border:1px solid var(--yd-feedback-border);border-radius:14px;background:var(--yd-feedback-card);padding:12px}
|
|
2395
|
+
.yd-feedback-thread-card[data-visible="true"]{display:block}
|
|
2396
|
+
.yd-feedback-thread-badges{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:10px}
|
|
2397
|
+
.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}
|
|
2398
|
+
.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)}
|
|
2399
|
+
.yd-feedback-thread-preview{margin:0;color:var(--yd-feedback-fg);font-size:13px;line-height:1.45;word-break:break-word}
|
|
2400
|
+
.yd-feedback-thread-meta{display:flex;justify-content:space-between;gap:10px;margin-top:10px;color:var(--yd-feedback-muted-fg);font-size:11px}
|
|
2401
|
+
.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}
|
|
2402
|
+
.yd-feedback-main{min-width:0;min-height:0;background:var(--yd-feedback-card);overflow:hidden}
|
|
2403
|
+
.yd-feedback-form{height:100%;display:grid;grid-template-rows:auto auto auto minmax(0,1fr) auto auto;gap:14px;padding:16px 18px 0}
|
|
2404
|
+
.yd-feedback-row{display:grid;gap:14px;grid-template-columns:180px minmax(0,1fr)}
|
|
2405
|
+
.yd-feedback-field{display:flex;min-width:0;flex-direction:column;gap:8px}
|
|
2406
|
+
.yd-feedback-label{display:flex;align-items:center;gap:8px;color:var(--yd-feedback-fg);font-size:13px;font-weight:720}
|
|
2407
|
+
.yd-feedback-label svg{width:17px;height:17px;color:var(--yd-feedback-fg)}
|
|
2408
|
+
.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}
|
|
2409
|
+
.yd-feedback-input{height:40px;padding:0 13px;font-size:14px}
|
|
2410
|
+
.yd-feedback-textarea{min-height:118px;padding:13px 14px;font-size:14px;resize:none}
|
|
2411
|
+
.yd-feedback-input::placeholder,.yd-feedback-textarea::placeholder{color:var(--yd-feedback-muted-fg)}
|
|
2412
|
+
.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)}
|
|
2413
|
+
.yd-feedback-compose-box{overflow:hidden;border:1px solid var(--yd-feedback-border);border-radius:15px;background:var(--yd-feedback-card)}
|
|
2414
|
+
.yd-feedback-compose-box .yd-feedback-textarea{border:0;border-radius:0;box-shadow:none;background:transparent}
|
|
2415
|
+
.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)}
|
|
2416
|
+
.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}
|
|
2417
|
+
.yd-feedback-file-trigger:hover{background:var(--yd-feedback-muted)}
|
|
2418
|
+
.yd-feedback-file-trigger svg{width:18px;height:18px}
|
|
2419
|
+
.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%)}
|
|
2420
|
+
.yd-feedback-files{min-width:0;text-align:right;color:var(--yd-feedback-muted-fg);font-size:12px}
|
|
2421
|
+
.yd-feedback-select{position:relative;width:142px}
|
|
2422
|
+
.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}
|
|
2423
|
+
.yd-feedback-select-value{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700}
|
|
2424
|
+
.yd-feedback-select-value svg{width:18px;height:18px;color:var(--yd-feedback-muted-fg)}
|
|
2425
|
+
.yd-feedback-select-chevron{width:17px;height:17px;color:var(--yd-feedback-muted-fg)}
|
|
2426
|
+
.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}
|
|
2427
|
+
.yd-feedback-select[data-open="true"] .yd-feedback-select-menu{display:block}
|
|
2428
|
+
.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}
|
|
2429
|
+
.yd-feedback-select-option svg{width:18px;height:18px;flex:0 0 18px;color:var(--yd-feedback-muted-fg)}
|
|
2430
|
+
.yd-feedback-select-option span{line-height:1.2;white-space:nowrap}
|
|
2431
|
+
.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)}
|
|
2432
|
+
.yd-feedback-messages{display:flex;min-height:0;flex-direction:column;gap:8px;overflow:auto}
|
|
2433
|
+
.yd-feedback-message{border:1px solid var(--yd-feedback-border);border-radius:12px;padding:9px 10px;background:var(--yd-feedback-muted)}
|
|
2434
|
+
.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))}
|
|
2435
|
+
.yd-feedback-message-meta{margin-bottom:4px;color:var(--yd-feedback-muted-fg);font-size:11px}
|
|
2436
|
+
.yd-feedback-message-content{color:var(--yd-feedback-fg);font-size:12px;line-height:1.45;word-break:break-word}
|
|
2437
|
+
.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)}
|
|
2438
|
+
.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}
|
|
2439
|
+
.yd-feedback-submit:hover{transform:translateY(-1px)}
|
|
2440
|
+
.yd-feedback-submit:disabled{opacity:.55;cursor:not-allowed;transform:none}
|
|
2441
|
+
.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}
|
|
2442
|
+
.yd-feedback-secondary:hover{background:var(--yd-feedback-muted)}
|
|
2443
|
+
.yd-feedback-error{min-height:18px;color:#fb7185;font-size:13px}
|
|
2444
|
+
@media (max-width:760px){
|
|
2445
|
+
.yd-feedback-panel{inset:auto 14px 78px 14px;width:auto;height:min(560px,calc(100vh - 96px));border-radius:16px}
|
|
2446
|
+
.yd-feedback-header{padding:16px 16px 14px}
|
|
2447
|
+
.yd-feedback-title{font-size:20px}
|
|
2448
|
+
.yd-feedback-shell{grid-template-columns:1fr}
|
|
2449
|
+
.yd-feedback-sidebar{display:none}
|
|
2450
|
+
.yd-feedback-form{padding:14px 14px 0;gap:12px}
|
|
2451
|
+
.yd-feedback-row{grid-template-columns:1fr}
|
|
2452
|
+
.yd-feedback-actions{justify-content:space-between}
|
|
2453
|
+
}
|
|
2454
|
+
`;
|
|
2455
|
+
document.head.appendChild(style);
|
|
2456
|
+
}
|
|
2457
|
+
function getBaseUrl(options) {
|
|
2458
|
+
return (options.apiBaseUrl || "https://pay.imgto.link").replace(/\/$/, "");
|
|
2459
|
+
}
|
|
2460
|
+
function safeJson(value) {
|
|
2461
|
+
try {
|
|
2462
|
+
return JSON.stringify(value);
|
|
2463
|
+
} catch {
|
|
2464
|
+
return "{}";
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function escapeHtml(value) {
|
|
2468
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2469
|
+
}
|
|
2470
|
+
function getInitialContact(actor) {
|
|
2471
|
+
return actor?.contactEmail || actor?.contactPhone || "";
|
|
2472
|
+
}
|
|
2473
|
+
function formatCategoryLabel(value, categories = DEFAULT_CATEGORIES) {
|
|
2474
|
+
const matched = categories.find((item) => item.value === value);
|
|
2475
|
+
return matched?.label || value || DEFAULT_CATEGORIES[0]?.label || "\u53CD\u9988";
|
|
2476
|
+
}
|
|
2477
|
+
function formatThreadStatus(value) {
|
|
2478
|
+
const statusMap = {
|
|
2479
|
+
OPEN: "\u5DF2\u63D0\u4EA4",
|
|
2480
|
+
WAITING_ADMIN: "\u7B49\u5F85\u56DE\u590D",
|
|
2481
|
+
WAITING_USER: "\u5F85\u8865\u5145",
|
|
2482
|
+
RESOLVED: "\u5DF2\u5904\u7406",
|
|
2483
|
+
CLOSED: "\u5DF2\u5173\u95ED"
|
|
2484
|
+
};
|
|
2485
|
+
return statusMap[value || ""] || value || "\u5DF2\u63D0\u4EA4";
|
|
2486
|
+
}
|
|
2487
|
+
function formatSenderType(value) {
|
|
2488
|
+
if (value === "END_USER") return "\u7528\u6237";
|
|
2489
|
+
if (value === "ADMIN") return "\u7BA1\u7406\u5458";
|
|
2490
|
+
return "\u7CFB\u7EDF";
|
|
2491
|
+
}
|
|
2492
|
+
function formatThreadTime(value) {
|
|
2493
|
+
if (!value) return "";
|
|
2494
|
+
return new Date(value).toLocaleString();
|
|
2495
|
+
}
|
|
2496
|
+
async function requestJson(url, init) {
|
|
2497
|
+
const response = await fetch(url, {
|
|
2498
|
+
...init,
|
|
2499
|
+
headers: {
|
|
2500
|
+
Accept: "application/json",
|
|
2501
|
+
...init?.body ? { "Content-Type": "application/json" } : {},
|
|
2502
|
+
...init?.headers || {}
|
|
2503
|
+
}
|
|
2504
|
+
});
|
|
2505
|
+
const payload = await response.json().catch(() => null);
|
|
2506
|
+
if (!response.ok || payload?.code !== 200) {
|
|
2507
|
+
throw new Error(payload?.message || response.statusText || "Request failed");
|
|
2508
|
+
}
|
|
2509
|
+
return payload.data;
|
|
2510
|
+
}
|
|
2511
|
+
function collectMetadata(options) {
|
|
2512
|
+
const page = typeof window === "undefined" ? {} : {
|
|
2513
|
+
pageUrl: window.location.href,
|
|
2514
|
+
pageTitle: document.title,
|
|
2515
|
+
userAgent: navigator.userAgent
|
|
2516
|
+
};
|
|
2517
|
+
return { ...page, ...options.metadata || {} };
|
|
2518
|
+
}
|
|
2519
|
+
async function compressImage(file) {
|
|
2520
|
+
if (typeof document === "undefined" || !file.type.startsWith("image/")) {
|
|
2521
|
+
return file;
|
|
2522
|
+
}
|
|
2523
|
+
const bitmap = await createImageBitmap(file);
|
|
2524
|
+
const maxSize = 1600;
|
|
2525
|
+
const scale = Math.min(1, maxSize / Math.max(bitmap.width, bitmap.height));
|
|
2526
|
+
const canvas = document.createElement("canvas");
|
|
2527
|
+
canvas.width = Math.max(1, Math.round(bitmap.width * scale));
|
|
2528
|
+
canvas.height = Math.max(1, Math.round(bitmap.height * scale));
|
|
2529
|
+
const ctx = canvas.getContext("2d");
|
|
2530
|
+
if (!ctx) return file;
|
|
2531
|
+
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
|
2532
|
+
const blob = await new Promise((resolve) => {
|
|
2533
|
+
canvas.toBlob(resolve, "image/webp", 0.82);
|
|
2534
|
+
});
|
|
2535
|
+
if (!blob) return file;
|
|
2536
|
+
return new File([blob], file.name.replace(/\.[^.]+$/, ".webp"), {
|
|
2537
|
+
type: "image/webp",
|
|
2538
|
+
lastModified: Date.now()
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
function createFeedbackWidget(container, options) {
|
|
2542
|
+
if (typeof document === "undefined") {
|
|
2543
|
+
return { open() {
|
|
2544
|
+
}, close() {
|
|
2545
|
+
}, destroy() {
|
|
2546
|
+
} };
|
|
2547
|
+
}
|
|
2548
|
+
if (!options.appId) throw new Error("appId is required");
|
|
2549
|
+
ensureStyle();
|
|
2550
|
+
let threadState = null;
|
|
2551
|
+
let pollTimer = null;
|
|
2552
|
+
let panelReady = false;
|
|
2553
|
+
let pendingFiles = [];
|
|
2554
|
+
let renderedMessages = [];
|
|
2555
|
+
let removeLoadListener = null;
|
|
2556
|
+
const root = document.createElement("div");
|
|
2557
|
+
root.className = "yd-feedback-root";
|
|
2558
|
+
const launcher = document.createElement("button");
|
|
2559
|
+
launcher.type = "button";
|
|
2560
|
+
launcher.className = "yd-feedback-launcher";
|
|
2561
|
+
launcher.setAttribute("aria-label", options.launcherText || "\u53CD\u9988");
|
|
2562
|
+
launcher.innerHTML = FEEDBACK_ICON;
|
|
2563
|
+
root.appendChild(launcher);
|
|
2564
|
+
container.appendChild(root);
|
|
2565
|
+
const panel = document.createElement("section");
|
|
2566
|
+
panel.className = "yd-feedback-panel";
|
|
2567
|
+
panel.setAttribute("aria-label", options.panelTitle || DEFAULT_PANEL_TITLE);
|
|
2568
|
+
panel.innerHTML = `
|
|
2569
|
+
<div class="yd-feedback-header">
|
|
2570
|
+
<div>
|
|
2571
|
+
<h2 class="yd-feedback-title">${escapeHtml(options.panelTitle || DEFAULT_PANEL_TITLE)}</h2>
|
|
2572
|
+
<p class="yd-feedback-subtitle">${escapeHtml(options.panelSubtitle || DEFAULT_PANEL_SUBTITLE)}</p>
|
|
2573
|
+
</div>
|
|
2574
|
+
<button type="button" class="yd-feedback-close" aria-label="\u5173\u95ED">
|
|
2575
|
+
<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>
|
|
2576
|
+
</button>
|
|
2577
|
+
</div>
|
|
2578
|
+
<div class="yd-feedback-shell">
|
|
2579
|
+
<aside class="yd-feedback-sidebar">
|
|
2580
|
+
<div class="yd-feedback-sidebar-toolbar">
|
|
2581
|
+
<div class="yd-feedback-sidebar-title">${FEEDBACK_ICON}<span>\u53CD\u9988\u8BB0\u5F55</span></div>
|
|
2582
|
+
<button type="button" class="yd-feedback-icon-button" data-role="clear" aria-label="\u65B0\u5EFA\u53CD\u9988">
|
|
2583
|
+
<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>
|
|
2584
|
+
</button>
|
|
2585
|
+
</div>
|
|
2586
|
+
<div class="yd-feedback-thread-list">
|
|
2587
|
+
<div class="yd-feedback-thread-card" data-role="thread-card">
|
|
2588
|
+
<div class="yd-feedback-thread-badges">
|
|
2589
|
+
<span class="yd-feedback-badge" data-role="thread-category">\u95EE\u9898\u53CD\u9988</span>
|
|
2590
|
+
<span class="yd-feedback-badge" data-status="true" data-role="thread-status">\u5DF2\u63D0\u4EA4</span>
|
|
2591
|
+
</div>
|
|
2592
|
+
<p class="yd-feedback-thread-preview" data-role="thread-preview"></p>
|
|
2593
|
+
<div class="yd-feedback-thread-meta">
|
|
2594
|
+
<span data-role="thread-time"></span>
|
|
2595
|
+
<span>\u6211\u7684\u53CD\u9988</span>
|
|
2596
|
+
</div>
|
|
2597
|
+
</div>
|
|
2598
|
+
<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>
|
|
2599
|
+
</div>
|
|
2600
|
+
</aside>
|
|
2601
|
+
<div class="yd-feedback-main">
|
|
2602
|
+
<div class="yd-feedback-form">
|
|
2603
|
+
<div class="yd-feedback-row">
|
|
2604
|
+
<div class="yd-feedback-field">
|
|
2605
|
+
<label class="yd-feedback-label">\u53CD\u9988\u7C7B\u578B</label>
|
|
2606
|
+
<div class="yd-feedback-select" data-role="category-select">
|
|
2607
|
+
<button type="button" class="yd-feedback-select-button" data-role="category-trigger">
|
|
2608
|
+
<span class="yd-feedback-select-value">${BUG_ICON}<span data-role="category-value">\u95EE\u9898\u53CD\u9988</span></span>
|
|
2609
|
+
<span class="yd-feedback-select-chevron">${CHEVRON_ICON}</span>
|
|
2610
|
+
</button>
|
|
2611
|
+
<div class="yd-feedback-select-menu" data-role="category-menu"></div>
|
|
2612
|
+
</div>
|
|
2613
|
+
</div>
|
|
2614
|
+
</div>
|
|
2615
|
+
<div class="yd-feedback-field">
|
|
2616
|
+
<label class="yd-feedback-label">${NOTE_ICON}<span>\u53CD\u9988\u5185\u5BB9</span></label>
|
|
2617
|
+
<div class="yd-feedback-compose-box">
|
|
2618
|
+
<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>
|
|
2619
|
+
<div class="yd-feedback-attachment-row">
|
|
2620
|
+
<button type="button" class="yd-feedback-file-trigger" data-role="files-trigger">${IMAGE_ICON}<span>\u4E0A\u4F20\u56FE\u7247</span></button>
|
|
2621
|
+
<input class="yd-feedback-file-input" type="file" accept="image/png,image/jpeg,image/webp" multiple data-role="files" />
|
|
2622
|
+
<div class="yd-feedback-files" data-role="files-label">${DEFAULT_ATTACHMENTS_LABEL}</div>
|
|
2623
|
+
</div>
|
|
2624
|
+
</div>
|
|
2625
|
+
</div>
|
|
2626
|
+
<div class="yd-feedback-field">
|
|
2627
|
+
<label class="yd-feedback-label">${MAIL_ICON}<span>\u8054\u7CFB\u65B9\u5F0F\uFF08\u9009\u586B\uFF09</span></label>
|
|
2628
|
+
<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")}" />
|
|
2629
|
+
</div>
|
|
2630
|
+
<div class="yd-feedback-messages" data-role="messages"></div>
|
|
2631
|
+
<div class="yd-feedback-error" data-role="error"></div>
|
|
2632
|
+
<div class="yd-feedback-actions">
|
|
2633
|
+
<button type="button" class="yd-feedback-secondary" data-role="clear-secondary">\u5173\u95ED</button>
|
|
2634
|
+
<button type="button" class="yd-feedback-submit" data-role="submit">\u63D0\u4EA4\u53CD\u9988</button>
|
|
2635
|
+
</div>
|
|
2636
|
+
</div>
|
|
2637
|
+
</div>
|
|
2638
|
+
</div>
|
|
2639
|
+
`;
|
|
2640
|
+
function initPanel() {
|
|
2641
|
+
if (panelReady) return;
|
|
2642
|
+
panelReady = true;
|
|
2643
|
+
root.appendChild(panel);
|
|
2644
|
+
const categorySelect = panel.querySelector(
|
|
2645
|
+
'[data-role="category-select"]'
|
|
2646
|
+
);
|
|
2647
|
+
const categoryMenu = panel.querySelector(
|
|
2648
|
+
'[data-role="category-menu"]'
|
|
2649
|
+
);
|
|
2650
|
+
const categoryValue = panel.querySelector(
|
|
2651
|
+
'[data-role="category-value"]'
|
|
2652
|
+
);
|
|
2653
|
+
if (categoryMenu && categoryValue) {
|
|
2654
|
+
const categories = options.categories || DEFAULT_CATEGORIES;
|
|
2655
|
+
categoryValue.textContent = categories[0]?.label || DEFAULT_CATEGORIES[0]?.label || "\u53CD\u9988";
|
|
2656
|
+
for (const item of categories) {
|
|
2657
|
+
const option = document.createElement("button");
|
|
2658
|
+
option.type = "button";
|
|
2659
|
+
option.className = "yd-feedback-select-option";
|
|
2660
|
+
option.dataset.value = item.value;
|
|
2661
|
+
option.dataset.selected = String(item.value === categories[0]?.value);
|
|
2662
|
+
option.innerHTML = `${BUG_ICON}<span>${escapeHtml(item.label)}</span>`;
|
|
2663
|
+
option.addEventListener("click", () => {
|
|
2664
|
+
for (const node of categoryMenu.querySelectorAll(
|
|
2665
|
+
".yd-feedback-select-option"
|
|
2666
|
+
)) {
|
|
2667
|
+
node.dataset.selected = String(node === option);
|
|
2668
|
+
}
|
|
2669
|
+
categoryValue.textContent = item.label;
|
|
2670
|
+
if (categorySelect) {
|
|
2671
|
+
categorySelect.dataset.value = item.value;
|
|
2672
|
+
categorySelect.dataset.open = "false";
|
|
2673
|
+
}
|
|
2674
|
+
});
|
|
2675
|
+
categoryMenu.appendChild(option);
|
|
2676
|
+
}
|
|
2677
|
+
categorySelect?.setAttribute("data-value", categories[0]?.value || "BUG");
|
|
2678
|
+
}
|
|
2679
|
+
panel.querySelector('[data-role="category-trigger"]')?.addEventListener("click", () => {
|
|
2680
|
+
if (!categorySelect) return;
|
|
2681
|
+
categorySelect.dataset.open = categorySelect.dataset.open === "true" ? "false" : "true";
|
|
2682
|
+
});
|
|
2683
|
+
panel.querySelector(".yd-feedback-close")?.addEventListener("click", close);
|
|
2684
|
+
panel.querySelector('[data-role="clear"]')?.addEventListener("click", resetThread);
|
|
2685
|
+
panel.querySelector('[data-role="clear-secondary"]')?.addEventListener("click", close);
|
|
2686
|
+
panel.querySelector('[data-role="files-trigger"]')?.addEventListener("click", () => {
|
|
2687
|
+
panel.querySelector('[data-role="files"]')?.click();
|
|
2688
|
+
});
|
|
2689
|
+
panel.querySelector('[data-role="files"]')?.addEventListener("change", (event) => {
|
|
2690
|
+
setPendingFiles(event.target.files);
|
|
2691
|
+
});
|
|
2692
|
+
const composeBox = panel.querySelector(
|
|
2693
|
+
".yd-feedback-compose-box"
|
|
2694
|
+
);
|
|
2695
|
+
composeBox?.addEventListener("dragover", (event) => {
|
|
2696
|
+
event.preventDefault();
|
|
2697
|
+
});
|
|
2698
|
+
composeBox?.addEventListener("drop", (event) => {
|
|
2699
|
+
event.preventDefault();
|
|
2700
|
+
setPendingFiles(event.dataTransfer?.files || null);
|
|
2701
|
+
});
|
|
2702
|
+
composeBox?.addEventListener("paste", (event) => {
|
|
2703
|
+
const files = Array.from(event.clipboardData?.files || []).filter(
|
|
2704
|
+
(file) => file.type.startsWith("image/")
|
|
2705
|
+
);
|
|
2706
|
+
if (files.length > 0) {
|
|
2707
|
+
setPendingFiles(files);
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
panel.querySelector('[data-role="submit"]')?.addEventListener("click", () => void submit());
|
|
2711
|
+
}
|
|
2712
|
+
function setPendingFiles(files) {
|
|
2713
|
+
pendingFiles = Array.from(files || []).filter((file) => file.type.startsWith("image/")).slice(0, MAX_IMAGES_PER_MESSAGE);
|
|
2714
|
+
const label = panel.querySelector('[data-role="files-label"]');
|
|
2715
|
+
if (label) {
|
|
2716
|
+
label.textContent = pendingFiles.length > 0 ? `\u5DF2\u9009\u62E9 ${pendingFiles.length} \u5F20\u56FE\u7247` : DEFAULT_ATTACHMENTS_LABEL;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
function resetThread() {
|
|
2720
|
+
threadState = null;
|
|
2721
|
+
pendingFiles = [];
|
|
2722
|
+
renderedMessages = [];
|
|
2723
|
+
renderMessages([]);
|
|
2724
|
+
updateThreadCard();
|
|
2725
|
+
setError("");
|
|
2726
|
+
const textarea = panel.querySelector(
|
|
2727
|
+
'[data-role="content"]'
|
|
2728
|
+
);
|
|
2729
|
+
if (textarea) textarea.value = "";
|
|
2730
|
+
const fileInput = panel.querySelector(
|
|
2731
|
+
'[data-role="files"]'
|
|
2732
|
+
);
|
|
2733
|
+
if (fileInput) fileInput.value = "";
|
|
2734
|
+
const filesLabel = panel.querySelector(
|
|
2735
|
+
'[data-role="files-label"]'
|
|
2736
|
+
);
|
|
2737
|
+
if (filesLabel) filesLabel.textContent = DEFAULT_ATTACHMENTS_LABEL;
|
|
2738
|
+
}
|
|
2739
|
+
function updateThreadCard() {
|
|
2740
|
+
const card = panel.querySelector('[data-role="thread-card"]');
|
|
2741
|
+
const empty = panel.querySelector('[data-role="thread-empty"]');
|
|
2742
|
+
if (!card || !empty) return;
|
|
2743
|
+
if (!threadState) {
|
|
2744
|
+
card.dataset.visible = "false";
|
|
2745
|
+
empty.style.display = "block";
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
card.dataset.visible = "true";
|
|
2749
|
+
empty.style.display = "none";
|
|
2750
|
+
const category = card.querySelector(
|
|
2751
|
+
'[data-role="thread-category"]'
|
|
2752
|
+
);
|
|
2753
|
+
const status = card.querySelector(
|
|
2754
|
+
'[data-role="thread-status"]'
|
|
2755
|
+
);
|
|
2756
|
+
const preview = card.querySelector(
|
|
2757
|
+
'[data-role="thread-preview"]'
|
|
2758
|
+
);
|
|
2759
|
+
const time = card.querySelector('[data-role="thread-time"]');
|
|
2760
|
+
if (category) {
|
|
2761
|
+
category.textContent = formatCategoryLabel(
|
|
2762
|
+
threadState.category || DEFAULT_CATEGORIES[0]?.value,
|
|
2763
|
+
options.categories || DEFAULT_CATEGORIES
|
|
2764
|
+
);
|
|
2765
|
+
}
|
|
2766
|
+
if (status) status.textContent = formatThreadStatus(threadState.status);
|
|
2767
|
+
if (preview) preview.textContent = threadState.contentPreview || "";
|
|
2768
|
+
if (time) time.textContent = formatThreadTime(threadState.createdAt);
|
|
2769
|
+
}
|
|
2770
|
+
function setError(message) {
|
|
2771
|
+
const node = panel.querySelector('[data-role="error"]');
|
|
2772
|
+
if (node) node.textContent = message;
|
|
2773
|
+
if (message) options.onError?.(new Error(message));
|
|
2774
|
+
}
|
|
2775
|
+
function setSubmitting(value) {
|
|
2776
|
+
const button = panel.querySelector(
|
|
2777
|
+
'[data-role="submit"]'
|
|
2778
|
+
);
|
|
2779
|
+
if (button) button.disabled = value;
|
|
2780
|
+
}
|
|
2781
|
+
function renderMessages(messages) {
|
|
2782
|
+
const node = panel.querySelector('[data-role="messages"]');
|
|
2783
|
+
if (!node) return;
|
|
2784
|
+
renderedMessages = messages;
|
|
2785
|
+
node.innerHTML = "";
|
|
2786
|
+
for (const message of messages) {
|
|
2787
|
+
const item = document.createElement("div");
|
|
2788
|
+
item.className = "yd-feedback-message";
|
|
2789
|
+
item.dataset.admin = String(message.senderType !== "END_USER");
|
|
2790
|
+
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>`;
|
|
2791
|
+
const content = item.querySelector(".yd-feedback-message-content");
|
|
2792
|
+
if (content) {
|
|
2793
|
+
content.textContent = message.senderType === "END_USER" ? message.content : message.translatedContent || message.content;
|
|
2794
|
+
}
|
|
2795
|
+
node.appendChild(item);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
function appendMessages(messages) {
|
|
2799
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2800
|
+
for (const message of renderedMessages) byId.set(message.id, message);
|
|
2801
|
+
for (const message of messages) byId.set(message.id, message);
|
|
2802
|
+
renderMessages(Array.from(byId.values()));
|
|
2803
|
+
}
|
|
2804
|
+
async function uploadImages(files) {
|
|
2805
|
+
const selected = files.slice(0, MAX_IMAGES_PER_MESSAGE);
|
|
2806
|
+
const ids = [];
|
|
2807
|
+
for (const file of selected) {
|
|
2808
|
+
const compressed = await compressImage(file);
|
|
2809
|
+
const body = new FormData();
|
|
2810
|
+
body.append("file", compressed);
|
|
2811
|
+
if (threadState) body.append("threadId", threadState.threadId);
|
|
2812
|
+
const headers = {};
|
|
2813
|
+
if (threadState) headers["x-feedback-token"] = threadState.clientToken;
|
|
2814
|
+
const response = await fetch(
|
|
2815
|
+
`${getBaseUrl(options)}/api/v1/feedback/${encodeURIComponent(options.appId)}/resources/images`,
|
|
2816
|
+
{ method: "POST", body, headers }
|
|
2817
|
+
);
|
|
2818
|
+
const payload = await response.json();
|
|
2819
|
+
if (!response.ok || payload?.code !== 200) {
|
|
2820
|
+
throw new Error(payload?.message || "Image upload failed");
|
|
2821
|
+
}
|
|
2822
|
+
ids.push(payload.data.resource.id);
|
|
2823
|
+
}
|
|
2824
|
+
return ids;
|
|
2825
|
+
}
|
|
2826
|
+
async function submit() {
|
|
2827
|
+
const content = panel.querySelector('[data-role="content"]')?.value.trim();
|
|
2828
|
+
if (!content) {
|
|
2829
|
+
setError("\u8BF7\u8F93\u5165\u53CD\u9988\u5185\u5BB9\u3002");
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
setSubmitting(true);
|
|
2833
|
+
setError("");
|
|
2834
|
+
try {
|
|
2835
|
+
const category = panel.querySelector('[data-role="category-select"]')?.dataset.value || "OTHER";
|
|
2836
|
+
const contact = panel.querySelector('[data-role="contact"]')?.value || "";
|
|
2837
|
+
const fileInput = panel.querySelector(
|
|
2838
|
+
'[data-role="files"]'
|
|
2839
|
+
);
|
|
2840
|
+
const attachments = await uploadImages(pendingFiles);
|
|
2841
|
+
if (!threadState) {
|
|
2842
|
+
const result = await requestJson(
|
|
2843
|
+
`${getBaseUrl(options)}/api/v1/feedback/${encodeURIComponent(options.appId)}/threads`,
|
|
2844
|
+
{
|
|
2845
|
+
method: "POST",
|
|
2846
|
+
body: safeJson({
|
|
2847
|
+
category,
|
|
2848
|
+
content,
|
|
2849
|
+
attachments,
|
|
2850
|
+
actor: {
|
|
2851
|
+
...options.actor || {},
|
|
2852
|
+
contactEmail: contact.includes("@") ? contact : options.actor?.contactEmail,
|
|
2853
|
+
contactPhone: contact && !contact.includes("@") ? contact : options.actor?.contactPhone
|
|
2854
|
+
},
|
|
2855
|
+
metadata: collectMetadata(options)
|
|
2856
|
+
})
|
|
2857
|
+
}
|
|
2858
|
+
);
|
|
2859
|
+
threadState = {
|
|
2860
|
+
threadId: result.thread.id,
|
|
2861
|
+
clientToken: result.clientToken,
|
|
2862
|
+
lastMessageId: result.message?.id,
|
|
2863
|
+
status: result.thread.status,
|
|
2864
|
+
category,
|
|
2865
|
+
contentPreview: content,
|
|
2866
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2867
|
+
};
|
|
2868
|
+
updateThreadCard();
|
|
2869
|
+
options.onThreadCreated?.(result.thread.id);
|
|
2870
|
+
options.onMessageSent?.(result.message.id);
|
|
2871
|
+
} else {
|
|
2872
|
+
const result = await requestJson(
|
|
2873
|
+
`${getBaseUrl(options)}/api/v1/feedback/${encodeURIComponent(options.appId)}/threads/${encodeURIComponent(threadState.threadId)}/messages`,
|
|
2874
|
+
{
|
|
2875
|
+
method: "POST",
|
|
2876
|
+
headers: { "x-feedback-token": threadState.clientToken },
|
|
2877
|
+
body: safeJson({
|
|
2878
|
+
content,
|
|
2879
|
+
attachments,
|
|
2880
|
+
metadata: collectMetadata(options)
|
|
2881
|
+
})
|
|
2882
|
+
}
|
|
2883
|
+
);
|
|
2884
|
+
threadState.lastMessageId = result.message.id;
|
|
2885
|
+
threadState.contentPreview = content;
|
|
2886
|
+
options.onMessageSent?.(result.message.id);
|
|
2887
|
+
}
|
|
2888
|
+
const textarea = panel.querySelector(
|
|
2889
|
+
'[data-role="content"]'
|
|
2890
|
+
);
|
|
2891
|
+
if (textarea) textarea.value = "";
|
|
2892
|
+
if (fileInput) fileInput.value = "";
|
|
2893
|
+
pendingFiles = [];
|
|
2894
|
+
const filesLabel = panel.querySelector(
|
|
2895
|
+
'[data-role="files-label"]'
|
|
2896
|
+
);
|
|
2897
|
+
if (filesLabel) filesLabel.textContent = DEFAULT_ATTACHMENTS_LABEL;
|
|
2898
|
+
await pollOnce();
|
|
2899
|
+
startPolling();
|
|
2900
|
+
} catch (error) {
|
|
2901
|
+
setError(error instanceof Error ? error.message : "\u63D0\u4EA4\u5931\u8D25");
|
|
2902
|
+
} finally {
|
|
2903
|
+
setSubmitting(false);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
async function pollOnce() {
|
|
2907
|
+
if (!threadState) return;
|
|
2908
|
+
const currentThread = threadState;
|
|
2909
|
+
const params = new URLSearchParams();
|
|
2910
|
+
if (currentThread.lastMessageId) {
|
|
2911
|
+
params.set("afterMessageId", currentThread.lastMessageId);
|
|
2912
|
+
}
|
|
2913
|
+
params.set("feedbackToken", currentThread.clientToken);
|
|
2914
|
+
const result = await requestJson(
|
|
2915
|
+
`${getBaseUrl(options)}/api/v1/feedback/${encodeURIComponent(options.appId)}/threads/${encodeURIComponent(currentThread.threadId)}/messages?${params.toString()}`
|
|
2916
|
+
);
|
|
2917
|
+
currentThread.status = result.threadStatus;
|
|
2918
|
+
updateThreadCard();
|
|
2919
|
+
if (result.messages.length > 0) {
|
|
2920
|
+
const latestMessage = result.messages.at(-1);
|
|
2921
|
+
if (latestMessage) currentThread.lastMessageId = latestMessage.id;
|
|
2922
|
+
appendMessages(result.messages);
|
|
2923
|
+
}
|
|
2924
|
+
if (result.threadStatus !== "WAITING_ADMIN") stopPolling();
|
|
2925
|
+
else schedulePoll(result.nextPollAfterMs);
|
|
2926
|
+
}
|
|
2927
|
+
function schedulePoll(delay) {
|
|
2928
|
+
stopPolling();
|
|
2929
|
+
if (!delay || delay < 1 || document.hidden || !threadState) return;
|
|
2930
|
+
pollTimer = window.setTimeout(() => void pollOnce(), delay);
|
|
2931
|
+
}
|
|
2932
|
+
function startPolling() {
|
|
2933
|
+
if (!threadState || threadState.status !== "WAITING_ADMIN") return;
|
|
2934
|
+
schedulePoll(1e4);
|
|
2935
|
+
}
|
|
2936
|
+
function stopPolling() {
|
|
2937
|
+
if (pollTimer) window.clearTimeout(pollTimer);
|
|
2938
|
+
pollTimer = null;
|
|
2939
|
+
}
|
|
2940
|
+
function open() {
|
|
2941
|
+
initPanel();
|
|
2942
|
+
panel.dataset.open = "true";
|
|
2943
|
+
window.setTimeout(() => {
|
|
2944
|
+
panel.querySelector('[data-role="content"]')?.focus();
|
|
2945
|
+
}, 0);
|
|
2946
|
+
}
|
|
2947
|
+
function close() {
|
|
2948
|
+
panel.dataset.open = "false";
|
|
2949
|
+
stopPolling();
|
|
2950
|
+
}
|
|
2951
|
+
launcher.addEventListener("click", open);
|
|
2952
|
+
const handleVisibilityChange = () => {
|
|
2953
|
+
if (document.hidden) stopPolling();
|
|
2954
|
+
else startPolling();
|
|
2955
|
+
};
|
|
2956
|
+
const handleDocumentClick = (event) => {
|
|
2957
|
+
const categorySelect = panel.querySelector(
|
|
2958
|
+
'[data-role="category-select"]'
|
|
2959
|
+
);
|
|
2960
|
+
if (categorySelect && event.target instanceof Node && !categorySelect.contains(event.target)) {
|
|
2961
|
+
categorySelect.dataset.open = "false";
|
|
2962
|
+
}
|
|
2963
|
+
};
|
|
2964
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
2965
|
+
document.addEventListener("click", handleDocumentClick);
|
|
2966
|
+
if (typeof window !== "undefined") {
|
|
2967
|
+
const preload = () => {
|
|
2968
|
+
if ("requestIdleCallback" in window) {
|
|
2969
|
+
;
|
|
2970
|
+
window.requestIdleCallback(() => initPanel());
|
|
2971
|
+
} else {
|
|
2972
|
+
globalThis.setTimeout(() => initPanel(), 1200);
|
|
2973
|
+
}
|
|
2974
|
+
};
|
|
2975
|
+
if (document.readyState === "complete") preload();
|
|
2976
|
+
else {
|
|
2977
|
+
window.addEventListener("load", preload, { once: true });
|
|
2978
|
+
removeLoadListener = () => window.removeEventListener("load", preload);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
return {
|
|
2982
|
+
open,
|
|
2983
|
+
close,
|
|
2984
|
+
destroy() {
|
|
2985
|
+
stopPolling();
|
|
2986
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
2987
|
+
document.removeEventListener("click", handleDocumentClick);
|
|
2988
|
+
removeLoadListener?.();
|
|
2989
|
+
root.remove();
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
function mountFeedbackWidget(container, options) {
|
|
2994
|
+
return createFeedbackWidget(container, options);
|
|
2995
|
+
}
|
|
2996
|
+
|
|
1269
2997
|
// src/server.ts
|
|
1270
2998
|
import crypto2 from "crypto";
|
|
1271
2999
|
function getCustomAmountRechargeRule(product, currency) {
|
|
@@ -1395,6 +3123,12 @@ var PaymentClient = class {
|
|
|
1395
3123
|
}
|
|
1396
3124
|
/**
|
|
1397
3125
|
* Decrypts the callback notification payload using AES-256-GCM.
|
|
3126
|
+
*
|
|
3127
|
+
* @deprecated This legacy encrypted-envelope protocol is NOT what the platform
|
|
3128
|
+
* actually delivers. The platform sends an HMAC-signed plaintext JSON webhook
|
|
3129
|
+
* with `X-UniPay-Signature` / `X-UniPay-Timestamp` headers; use
|
|
3130
|
+
* {@link PaymentClient.verifyWebhookSignature} to validate those instead.
|
|
3131
|
+
*
|
|
1398
3132
|
* @param notification - The encrypted notification from payment webhook
|
|
1399
3133
|
* @returns Decrypted payment callback data
|
|
1400
3134
|
*/
|
|
@@ -1417,6 +3151,39 @@ var PaymentClient = class {
|
|
|
1417
3151
|
);
|
|
1418
3152
|
}
|
|
1419
3153
|
}
|
|
3154
|
+
/**
|
|
3155
|
+
* Verify an inbound platform webhook signature.
|
|
3156
|
+
*
|
|
3157
|
+
* The platform delivers webhooks as plaintext JSON with two headers:
|
|
3158
|
+
* `X-UniPay-Signature` (HMAC-SHA256 of `${timestamp}.${rawBodyString}`) and
|
|
3159
|
+
* `X-UniPay-Timestamp`. The body itself is NOT encrypted; verify the signature
|
|
3160
|
+
* then `JSON.parse` the body to obtain a {@link WebhookPayload}.
|
|
3161
|
+
*
|
|
3162
|
+
* @param rawBody - The raw request body string (exactly as received)
|
|
3163
|
+
* @param signature - The `X-UniPay-Signature` header value (hex)
|
|
3164
|
+
* @param timestamp - The `X-UniPay-Timestamp` header value (ms epoch string)
|
|
3165
|
+
* @param options - Optional `toleranceMs` for timestamp freshness (default 5 min)
|
|
3166
|
+
* @returns `true` if the signature is valid and the timestamp is fresh
|
|
3167
|
+
*/
|
|
3168
|
+
verifyWebhookSignature(rawBody, signature, timestamp, options) {
|
|
3169
|
+
try {
|
|
3170
|
+
const toleranceMs = options?.toleranceMs ?? 5 * 60 * 1e3;
|
|
3171
|
+
const timestampMs = Number.parseInt(timestamp, 10);
|
|
3172
|
+
if (Number.isNaN(timestampMs) || Math.abs(Date.now() - timestampMs) > toleranceMs) {
|
|
3173
|
+
return false;
|
|
3174
|
+
}
|
|
3175
|
+
const rawString = `${timestamp}.${rawBody}`;
|
|
3176
|
+
const expected = crypto2.createHmac("sha256", this.appSecret).update(rawString).digest("hex");
|
|
3177
|
+
const signatureBuffer = Buffer.from(signature);
|
|
3178
|
+
const expectedBuffer = Buffer.from(expected);
|
|
3179
|
+
if (signatureBuffer.length !== expectedBuffer.length) {
|
|
3180
|
+
return false;
|
|
3181
|
+
}
|
|
3182
|
+
return crypto2.timingSafeEqual(signatureBuffer, expectedBuffer);
|
|
3183
|
+
} catch {
|
|
3184
|
+
return false;
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
1420
3187
|
/**
|
|
1421
3188
|
* Fetch products for the configured app.
|
|
1422
3189
|
*/
|
|
@@ -1618,6 +3385,27 @@ var PaymentClient = class {
|
|
|
1618
3385
|
...options
|
|
1619
3386
|
});
|
|
1620
3387
|
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Consume numeric entitlements from an ordered key pool.
|
|
3390
|
+
* Useful when subscription credits and perpetual credits use separate keys.
|
|
3391
|
+
*/
|
|
3392
|
+
async consumeEntitlementPool(userId, keys, amount, options) {
|
|
3393
|
+
return this.request("POST", `/users/${userId}/entitlements/consume-pool`, {
|
|
3394
|
+
keys,
|
|
3395
|
+
amount,
|
|
3396
|
+
...options
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
/**
|
|
3400
|
+
* Get active numeric entitlement buckets ordered by expiration.
|
|
3401
|
+
*/
|
|
3402
|
+
async getEntitlementCreditBuckets(userId, keys) {
|
|
3403
|
+
const query = keys && keys.length > 0 ? `?keys=${encodeURIComponent(keys.join(","))}` : "";
|
|
3404
|
+
return this.request(
|
|
3405
|
+
"GET",
|
|
3406
|
+
`/users/${userId}/entitlements/credit-buckets${query}`
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
1621
3409
|
/**
|
|
1622
3410
|
* Add numeric entitlement (e.g. refund)
|
|
1623
3411
|
* @param userId - User ID
|
|
@@ -1698,15 +3486,47 @@ var PaymentClient = class {
|
|
|
1698
3486
|
}
|
|
1699
3487
|
);
|
|
1700
3488
|
}
|
|
3489
|
+
/**
|
|
3490
|
+
* Create a hosted WeChat official account binding URL for a user.
|
|
3491
|
+
*/
|
|
3492
|
+
async createWechatMessageBinding(params) {
|
|
3493
|
+
const loginExternalUserId = params.loginExternalUserId?.trim() || params.userId?.trim();
|
|
3494
|
+
const merchantUserId = params.merchantUserId?.trim();
|
|
3495
|
+
if (!loginExternalUserId && !merchantUserId) {
|
|
3496
|
+
throw new Error("userId or merchantUserId is required");
|
|
3497
|
+
}
|
|
3498
|
+
return this.request("POST", "/messages/wechat/bindings", {
|
|
3499
|
+
...params,
|
|
3500
|
+
loginExternalUserId,
|
|
3501
|
+
merchantUserId
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
/**
|
|
3505
|
+
* Send an app-linked message template to a bound recipient.
|
|
3506
|
+
*/
|
|
3507
|
+
async sendMessage(params) {
|
|
3508
|
+
if (!params.templateId?.trim() && !params.templateCode?.trim()) {
|
|
3509
|
+
throw new Error("templateId or templateCode is required");
|
|
3510
|
+
}
|
|
3511
|
+
if (!params.data || typeof params.data !== "object") {
|
|
3512
|
+
throw new Error("data is required");
|
|
3513
|
+
}
|
|
3514
|
+
return this.request("POST", "/messages/send", params);
|
|
3515
|
+
}
|
|
1701
3516
|
};
|
|
1702
3517
|
export {
|
|
1703
3518
|
LoginUI,
|
|
3519
|
+
MessageUI,
|
|
1704
3520
|
PaymentClient,
|
|
1705
3521
|
PaymentUI,
|
|
3522
|
+
createFeedbackWidget,
|
|
1706
3523
|
createLoginUI,
|
|
3524
|
+
createMessageUI,
|
|
1707
3525
|
createPaymentUI,
|
|
3526
|
+
detectLoginEnvironment,
|
|
1708
3527
|
getCustomAmountRechargeRule,
|
|
1709
3528
|
handleLoginCallbackIfPresent,
|
|
3529
|
+
mountFeedbackWidget,
|
|
1710
3530
|
validateCustomAmountRecharge
|
|
1711
3531
|
};
|
|
1712
3532
|
//# sourceMappingURL=index.js.map
|