@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/login.js CHANGED
@@ -2,6 +2,91 @@ 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/environment.ts
6
+ function normalizeText(value) {
7
+ return value || "";
8
+ }
9
+ function getRuntimeInput() {
10
+ if (typeof navigator === "undefined") {
11
+ return {};
12
+ }
13
+ const nav = navigator;
14
+ const coarsePointer = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : void 0;
15
+ return {
16
+ coarsePointer,
17
+ maxTouchPoints: nav.maxTouchPoints || 0,
18
+ platform: nav.platform || "",
19
+ standalone: nav.standalone,
20
+ userAgent: nav.userAgent || "",
21
+ userAgentDataMobile: nav.userAgentData?.mobile
22
+ };
23
+ }
24
+ function includesAny(value, patterns) {
25
+ return patterns.some((pattern) => pattern.test(value));
26
+ }
27
+ function detectLoginEnvironment(input = getRuntimeInput()) {
28
+ const userAgent = normalizeText(input.userAgent);
29
+ const platform = normalizeText(input.platform);
30
+ const rawMaxTouchPoints = input.maxTouchPoints ?? 0;
31
+ const maxTouchPoints = Number.isFinite(rawMaxTouchPoints) ? Math.max(0, rawMaxTouchPoints) : 0;
32
+ const isCoarsePointer = input.coarsePointer === true;
33
+ const isTouch = maxTouchPoints > 0 || isCoarsePointer;
34
+ const isWeChat = /micromessenger/i.test(userAgent);
35
+ const isAndroid = /\bandroid\b/i.test(userAgent);
36
+ const hasExplicitIOSDevice = /\b(iPad|iPhone|iPod)\b/i.test(userAgent);
37
+ const hasMacintoshUserAgent = /\bMacintosh\b/i.test(userAgent);
38
+ const hasMacPlatform = /^Mac/i.test(platform);
39
+ const isIPadOS = !hasExplicitIOSDevice && hasMacintoshUserAgent && hasMacPlatform && typeof input.standalone !== "undefined" && maxTouchPoints > 2;
40
+ const isIOS = hasExplicitIOSDevice || isIPadOS;
41
+ const isTabletByUa = includesAny(userAgent, [
42
+ /\biPad\b/i,
43
+ /\btablet\b/i,
44
+ /\bplaybook\b/i,
45
+ /\bsilk\b(?!.*\bmobile\b)/i,
46
+ /\bkindle\b/i,
47
+ /\bnexus 7\b/i,
48
+ /\bnexus 9\b/i,
49
+ /\bxoom\b/i,
50
+ /\bsm-t\d+/i,
51
+ /\bgt-p\d+/i,
52
+ /\bmi pad\b/i
53
+ ]);
54
+ const isMobileByUa = includesAny(userAgent, [
55
+ /\bMobile\b/i,
56
+ /\biPhone\b/i,
57
+ /\biPod\b/i,
58
+ /\bWindows Phone\b/i,
59
+ /\bIEMobile\b/i,
60
+ /\bBlackBerry\b/i,
61
+ /\bBB10\b/i,
62
+ /\bOpera Mini\b/i,
63
+ /\bOpera Mobi\b/i,
64
+ /\bwebOS\b/i
65
+ ]);
66
+ const isAndroidTablet = isAndroid && !/\bMobile\b/i.test(userAgent);
67
+ const isTablet = isIPadOS || isTabletByUa || isAndroidTablet;
68
+ const isMobile = input.userAgentDataMobile === true || isMobileByUa || isAndroid && !isTablet || isIOS && !isTablet;
69
+ const deviceType = isTablet ? "tablet" : isMobile ? "mobile" : userAgent || platform || isTouch ? "desktop" : "unknown";
70
+ const shouldUseRedirectLogin = deviceType === "mobile" || deviceType === "tablet" || isWeChat;
71
+ return {
72
+ deviceType,
73
+ isAndroid,
74
+ isCoarsePointer,
75
+ isDesktop: deviceType === "desktop",
76
+ isIOS,
77
+ isIPadOS,
78
+ isMobile,
79
+ isStandalone: input.standalone === true,
80
+ isTablet,
81
+ isTouch,
82
+ isWeChat,
83
+ maxTouchPoints,
84
+ platform,
85
+ shouldUseRedirectLogin,
86
+ userAgent
87
+ };
88
+ }
89
+
5
90
  // src/hosted-modal.ts
6
91
  var SDK_SUPPORTED_LOCALES = [
7
92
  "en",
@@ -125,7 +210,7 @@ var HostedFrameModal = class {
125
210
  const container = document.createElement("div");
126
211
  Object.assign(container.style, {
127
212
  width: options.width || "450px",
128
- height: options.height || "min(600px, 90vh)",
213
+ height: options.height || "min(760px, calc(100vh - 32px))",
129
214
  backgroundColor: "transparent",
130
215
  borderRadius: "28px",
131
216
  overflow: "visible",
@@ -133,8 +218,7 @@ var HostedFrameModal = class {
133
218
  boxShadow: "none",
134
219
  maxWidth: "calc(100vw - 32px)",
135
220
  maxHeight: "calc(100vh - 32px)",
136
- transition: "height 180ms ease",
137
- willChange: "height"
221
+ transition: "none"
138
222
  });
139
223
  const closeBtn = document.createElement("button");
140
224
  closeBtn.innerHTML = "\xD7";
@@ -213,6 +297,15 @@ function getOrigin(value) {
213
297
  return null;
214
298
  }
215
299
  }
300
+ function isValidLoginRedirectUrl(value) {
301
+ if (!value) return false;
302
+ try {
303
+ const url = new URL(value);
304
+ return url.protocol === "https:" || url.protocol === "http:";
305
+ } catch {
306
+ return false;
307
+ }
308
+ }
216
309
  function createLoginCallbackState() {
217
310
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
218
311
  return crypto.randomUUID().replace(/-/g, "");
@@ -559,6 +652,24 @@ function applyLoginPanelStyle(panel) {
559
652
  backdropFilter: "blur(16px)"
560
653
  });
561
654
  }
655
+ function applyLoginLoadingPanelStyle(panel) {
656
+ Object.assign(panel.style, {
657
+ position: "absolute",
658
+ inset: "0",
659
+ display: "flex",
660
+ alignItems: "center",
661
+ justifyContent: "center",
662
+ border: "0",
663
+ borderRadius: "28px",
664
+ background: "rgba(255,255,255,0.94)",
665
+ color: "#0f172a",
666
+ padding: "24px",
667
+ textAlign: "center",
668
+ zIndex: "1",
669
+ boxShadow: "none",
670
+ backdropFilter: "blur(16px)"
671
+ });
672
+ }
562
673
  function createLoginPanelContent() {
563
674
  const content = document.createElement("div");
564
675
  Object.assign(content.style, {
@@ -583,6 +694,71 @@ function createLoginPanelContent() {
583
694
  content.appendChild(badge);
584
695
  return content;
585
696
  }
697
+ function createBrandLoadingMark() {
698
+ const logo = document.createElementNS("http://www.w3.org/2000/svg", "svg");
699
+ logo.setAttribute("viewBox", "0 0 1024 1024");
700
+ logo.setAttribute("fill", "none");
701
+ logo.setAttribute("aria-hidden", "true");
702
+ Object.assign(logo.style, {
703
+ width: "112px",
704
+ height: "112px",
705
+ color: "#22c55e",
706
+ display: "block",
707
+ overflow: "visible"
708
+ });
709
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
710
+ group.setAttribute("fill", "currentColor");
711
+ const ring = document.createElementNS("http://www.w3.org/2000/svg", "path");
712
+ ring.setAttribute(
713
+ "d",
714
+ "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"
715
+ );
716
+ ring.setAttribute("class", "youidian-sdk-brand-loading__ring");
717
+ const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
718
+ dot.setAttribute("class", "youidian-sdk-brand-loading__dot");
719
+ dot.setAttribute("cx", "714.5");
720
+ dot.setAttribute("cy", "490.5");
721
+ dot.setAttribute("r", "68");
722
+ group.appendChild(ring);
723
+ group.appendChild(dot);
724
+ logo.appendChild(group);
725
+ return logo;
726
+ }
727
+ function createBrandLoadingCopy() {
728
+ const brand = document.createElement("div");
729
+ brand.className = "youidian-sdk-brand-loading__copy";
730
+ Object.assign(brand.style, {
731
+ display: "flex",
732
+ flexDirection: "column",
733
+ alignItems: "flex-start",
734
+ justifyContent: "center",
735
+ lineHeight: "1"
736
+ });
737
+ const name = document.createElement("div");
738
+ name.className = "youidian-sdk-brand-loading__name";
739
+ name.textContent = "\u4F18\u6613\u70B9";
740
+ Object.assign(name.style, {
741
+ color: "#0f172a",
742
+ fontSize: "34px",
743
+ fontWeight: "900",
744
+ letterSpacing: "0",
745
+ lineHeight: "1"
746
+ });
747
+ const tagline = document.createElement("div");
748
+ tagline.className = "youidian-sdk-brand-loading__tagline";
749
+ tagline.textContent = "\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0";
750
+ Object.assign(tagline.style, {
751
+ color: "#64748b",
752
+ fontSize: "16px",
753
+ fontWeight: "700",
754
+ letterSpacing: "0.24em",
755
+ lineHeight: "1",
756
+ marginTop: "6px"
757
+ });
758
+ brand.appendChild(name);
759
+ brand.appendChild(tagline);
760
+ return brand;
761
+ }
586
762
  function createLoginPanelTitle(value) {
587
763
  const title = document.createElement("div");
588
764
  title.textContent = value;
@@ -612,32 +788,111 @@ function ensureLoginLoadingStyles() {
612
788
  }
613
789
  const style = document.createElement("style");
614
790
  style.id = "youidian-login-loading-style";
615
- style.textContent = "@keyframes youidian-login-spin{to{transform:rotate(360deg)}}";
791
+ style.textContent = `
792
+ .youidian-sdk-brand-loading__ring,
793
+ .youidian-sdk-brand-loading__dot {
794
+ transform-box: fill-box;
795
+ transform-origin: center;
796
+ will-change: opacity, transform;
797
+ }
798
+
799
+ .youidian-sdk-brand-loading__ring {
800
+ animation: youidian-sdk-brand-loading-ring 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
801
+ }
802
+
803
+ .youidian-sdk-brand-loading__dot {
804
+ animation: youidian-sdk-brand-loading-dot 1400ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
805
+ }
806
+
807
+ .youidian-sdk-brand-loading__copy {
808
+ will-change: opacity, transform;
809
+ animation: youidian-sdk-brand-loading-copy 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
810
+ }
811
+
812
+ .youidian-sdk-brand-loading__tagline {
813
+ will-change: opacity;
814
+ animation: youidian-sdk-brand-loading-tagline 1600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
815
+ }
816
+
817
+ @keyframes youidian-sdk-brand-loading-ring {
818
+ 0%,
819
+ 100% {
820
+ opacity: 0.72;
821
+ transform: rotate(0deg) scale(0.98);
822
+ }
823
+ 50% {
824
+ opacity: 1;
825
+ transform: rotate(8deg) scale(1.02);
826
+ }
827
+ }
828
+
829
+ @keyframes youidian-sdk-brand-loading-dot {
830
+ 0%,
831
+ 100% {
832
+ opacity: 0.55;
833
+ transform: scale(0.9);
834
+ }
835
+ 50% {
836
+ opacity: 1;
837
+ transform: scale(1.08);
838
+ }
839
+ }
840
+
841
+ @keyframes youidian-sdk-brand-loading-copy {
842
+ 0%,
843
+ 100% {
844
+ opacity: 0.86;
845
+ transform: translateY(0);
846
+ }
847
+ 50% {
848
+ opacity: 1;
849
+ transform: translateY(-1px);
850
+ }
851
+ }
852
+
853
+ @keyframes youidian-sdk-brand-loading-tagline {
854
+ 0%,
855
+ 100% {
856
+ opacity: 0.64;
857
+ }
858
+ 50% {
859
+ opacity: 0.92;
860
+ }
861
+ }
862
+
863
+ @media (prefers-reduced-motion: reduce) {
864
+ .youidian-sdk-brand-loading__ring,
865
+ .youidian-sdk-brand-loading__dot,
866
+ .youidian-sdk-brand-loading__copy,
867
+ .youidian-sdk-brand-loading__tagline {
868
+ animation: none;
869
+ opacity: 1;
870
+ transform: none;
871
+ }
872
+ }
873
+ `;
616
874
  document.head.appendChild(style);
617
875
  }
618
- function createLoginLoadingPanel(options) {
876
+ function createLoginLoadingPanel() {
619
877
  ensureLoginLoadingStyles();
620
878
  const panel = document.createElement("div");
621
- applyLoginPanelStyle(panel);
622
- const content = createLoginPanelContent();
623
- const spinner = document.createElement("div");
624
- Object.assign(spinner.style, {
625
- width: "22px",
626
- height: "22px",
627
- borderRadius: "9999px",
628
- border: "2px solid rgba(23,23,23,0.16)",
629
- borderTopColor: "#171717",
630
- margin: "0 auto 18px",
631
- animation: "youidian-login-spin 780ms linear infinite"
879
+ applyLoginLoadingPanelStyle(panel);
880
+ panel.setAttribute("role", "status");
881
+ panel.setAttribute("aria-label", "\u4F18\u6613\u70B9\u5F00\u53D1\u8005\u670D\u52A1\u4E2D\u53F0");
882
+ const content = document.createElement("div");
883
+ Object.assign(content.style, {
884
+ display: "flex",
885
+ alignItems: "center",
886
+ justifyContent: "center",
887
+ gap: "12px"
632
888
  });
633
- content.appendChild(spinner);
634
- content.appendChild(createLoginPanelTitle(options.title));
635
- content.appendChild(createLoginPanelDescription(options.description));
889
+ content.appendChild(createBrandLoadingMark());
890
+ content.appendChild(createBrandLoadingCopy());
636
891
  panel.appendChild(content);
637
892
  return panel;
638
893
  }
639
- function createLoginRedirectingPanel(options) {
640
- return createLoginLoadingPanel(options);
894
+ function createLoginRedirectingPanel(_options) {
895
+ return createLoginLoadingPanel();
641
896
  }
642
897
  function createLoginFallbackPanel(options) {
643
898
  const panel = document.createElement("div");
@@ -719,6 +974,7 @@ var LoginUI = class extends HostedFrameModal {
719
974
  this.completed = false;
720
975
  }
721
976
  markIframeReady() {
977
+ if (this.iframeReady) return;
722
978
  this.iframeReady = true;
723
979
  if (typeof window !== "undefined" && this.iframeLoadTimer) {
724
980
  window.clearTimeout(this.iframeLoadTimer);
@@ -737,14 +993,11 @@ var LoginUI = class extends HostedFrameModal {
737
993
  this.iframeFallbackPanel = null;
738
994
  this.iframeLoadingPanel = null;
739
995
  }
740
- showIframeLoading(container, options) {
996
+ showIframeLoading(container) {
741
997
  if (this.iframeLoadingPanel || this.iframeReady) {
742
998
  return;
743
999
  }
744
- const panel = createLoginLoadingPanel({
745
- description: options?.loadingDescription || "Please wait while the secure login page opens.",
746
- title: options?.loadingTitle || "Opening login page"
747
- });
1000
+ const panel = createLoginLoadingPanel();
748
1001
  this.iframeLoadingPanel = panel;
749
1002
  container.appendChild(panel);
750
1003
  }
@@ -832,6 +1085,20 @@ var LoginUI = class extends HostedFrameModal {
832
1085
  break;
833
1086
  case "LOGIN_RESIZE":
834
1087
  break;
1088
+ case "LOGIN_REDIRECT_REQUIRED":
1089
+ if (!isValidLoginRedirectUrl(data.authUrl)) {
1090
+ logLoginWarn("Login redirect requested with invalid authUrl");
1091
+ options?.onError?.("Login redirect URL is invalid", data);
1092
+ break;
1093
+ }
1094
+ logLoginInfo("Login redirect requested by hosted page", {
1095
+ attemptId: data.attemptId || null,
1096
+ channel: data.channel || null,
1097
+ authUrl: data.authUrl
1098
+ });
1099
+ this.close();
1100
+ window.location.assign(data.authUrl);
1101
+ break;
835
1102
  case "LOGIN_ERROR":
836
1103
  if (this.completed) {
837
1104
  break;
@@ -855,6 +1122,25 @@ var LoginUI = class extends HostedFrameModal {
855
1122
  break;
856
1123
  }
857
1124
  }
1125
+ openLoginRedirect(params, options) {
1126
+ if (typeof window === "undefined") return;
1127
+ const { callbackState, finalUrl, launchPayload } = this.buildOpenContext(
1128
+ params,
1129
+ options
1130
+ );
1131
+ logLoginInfo("Redirecting to hosted login page", {
1132
+ appId: params.appId,
1133
+ callbackState,
1134
+ callbackUrl: launchPayload.callbackUrl || null,
1135
+ hasCallbackUrl: Boolean(launchPayload.callbackUrl),
1136
+ loginUrl: finalUrl,
1137
+ autoClose: options?.autoClose ?? true,
1138
+ displayMode: "redirect",
1139
+ pageUrl: window.location.href,
1140
+ parentOrigin: launchPayload.origin || null
1141
+ });
1142
+ window.location.assign(finalUrl);
1143
+ }
858
1144
  listenForLoginCallback(callbackState, options) {
859
1145
  try {
860
1146
  this.callbackChannel = new BroadcastChannel(
@@ -931,21 +1217,28 @@ var LoginUI = class extends HostedFrameModal {
931
1217
  options?.onCancel?.();
932
1218
  options?.onClose?.();
933
1219
  },
934
- onMessage: (data, container, event) => {
1220
+ onMessage: (data, _container, event) => {
935
1221
  const isIframeEvent = event.source === this.iframe?.contentWindow;
1222
+ if (!isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE")) {
1223
+ logLoginWarn(
1224
+ "Login lifecycle event ignored: event.source does not match iframe contentWindow",
1225
+ {
1226
+ type: data.type,
1227
+ eventOrigin: event.origin,
1228
+ hasIframe: Boolean(this.iframe),
1229
+ hasIframeContentWindow: Boolean(this.iframe?.contentWindow)
1230
+ }
1231
+ );
1232
+ }
936
1233
  if (isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE") || data.type === "LOGIN_SUCCESS" || data.type === "LOGIN_ERROR") {
937
1234
  this.markIframeReady();
938
1235
  }
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
1236
  this.handleLoginEvent(data, options);
944
1237
  },
945
1238
  width: "min(520px, 100%)"
946
1239
  });
947
1240
  if (hostedFrame) {
948
- this.showIframeLoading(hostedFrame.container, options);
1241
+ this.showIframeLoading(hostedFrame.container);
949
1242
  this.startIframeWatchdog({
950
1243
  container: hostedFrame.container,
951
1244
  finalUrl,
@@ -1049,11 +1342,19 @@ var LoginUI = class extends HostedFrameModal {
1049
1342
  }
1050
1343
  openLogin(params, options) {
1051
1344
  if (typeof window === "undefined") return;
1052
- const displayMode = options?.displayMode ?? "modal";
1345
+ const displayMode = options?.displayMode ?? "auto";
1346
+ if (displayMode === "redirect") {
1347
+ this.openLoginRedirect(params, options);
1348
+ return;
1349
+ }
1053
1350
  if (displayMode === "popup") {
1054
1351
  this.openLoginPopup(params, options);
1055
1352
  return;
1056
1353
  }
1354
+ if (displayMode === "auto" && detectLoginEnvironment().shouldUseRedirectLogin) {
1355
+ this.openLoginRedirect(params, options);
1356
+ return;
1357
+ }
1057
1358
  this.openLoginModal(params, options);
1058
1359
  }
1059
1360
  };