@youidian/sdk 3.3.3 → 3.3.5

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.
@@ -6,19 +6,24 @@ type HostedFrameOptions<TData extends HostedFrameMessage> = {
6
6
  allowedOrigin?: string;
7
7
  height?: string;
8
8
  onCloseButton?: () => void;
9
- onMessage: (data: TData, container: HTMLDivElement) => void;
9
+ onMessage: (data: TData, container: HTMLDivElement, event: MessageEvent) => void;
10
10
  width?: string;
11
11
  };
12
+ type HostedFrameInstance = {
13
+ container: HTMLDivElement;
14
+ iframe: HTMLIFrameElement;
15
+ };
12
16
  declare class HostedFrameModal<TData extends HostedFrameMessage> {
13
17
  protected iframe: HTMLIFrameElement | null;
14
18
  protected modal: HTMLDivElement | null;
15
19
  protected messageHandler: ((event: MessageEvent) => void) | null;
16
- protected openHostedFrame(url: string, options: HostedFrameOptions<TData>): void;
20
+ protected openHostedFrame(url: string, options: HostedFrameOptions<TData>): HostedFrameInstance | null;
17
21
  close(): void;
18
22
  }
19
23
 
20
- type LoginEventType = "LOGIN_SUCCESS" | "LOGIN_CANCELLED" | "LOGIN_CLOSE" | "LOGIN_RESIZE" | "LOGIN_ERROR";
24
+ type LoginEventType = "LOGIN_READY" | "LOGIN_SUCCESS" | "LOGIN_CANCELLED" | "LOGIN_CLOSE" | "LOGIN_RESIZE" | "LOGIN_ERROR";
21
25
  type LoginDisplayMode = "modal" | "popup";
26
+ type LoginFallbackRedirectMode = "auto" | "manual";
22
27
  interface LoginResult {
23
28
  avatar?: string | null;
24
29
  channel?: string;
@@ -53,6 +58,27 @@ interface LoginUIOptions {
53
58
  * login page in an iframe modal. Use "popup" to keep the legacy popup flow.
54
59
  */
55
60
  displayMode?: LoginDisplayMode;
61
+ /** Text for the fallback button shown when the iframe does not report readiness. */
62
+ fallbackButtonText?: string;
63
+ /** Description for the fallback panel shown when the iframe does not report readiness. */
64
+ fallbackDescription?: string;
65
+ /**
66
+ * Whether the SDK should redirect automatically when iframe loading times out.
67
+ * Defaults to "auto". Use "manual" to show a button instead.
68
+ */
69
+ fallbackRedirectMode?: LoginFallbackRedirectMode;
70
+ /** Title for the fallback panel shown when the iframe does not report readiness. */
71
+ fallbackTitle?: string;
72
+ /**
73
+ * How long the SDK waits for LOGIN_READY or LOGIN_RESIZE before showing the
74
+ * fallback redirect button. Only used when displayMode is "modal".
75
+ * Defaults to 1500ms.
76
+ */
77
+ iframeLoadTimeoutMs?: number;
78
+ /** Description shown while the hosted login iframe is loading. */
79
+ loadingDescription?: string;
80
+ /** Title shown while the hosted login iframe is loading. */
81
+ loadingTitle?: string;
56
82
  /**
57
83
  * Whether to poll popup.closed to detect manual popup closure.
58
84
  * Only used when displayMode is "popup". Defaults to false because COOP can
@@ -62,6 +88,7 @@ interface LoginUIOptions {
62
88
  onCancel?: () => void;
63
89
  onClose?: () => void;
64
90
  onError?: (message?: string, data?: LoginEventData) => void;
91
+ onFallbackVisible?: () => void;
65
92
  onSuccess?: (result: LoginResult) => void;
66
93
  }
67
94
  interface LoginCallbackOptions {
@@ -99,8 +126,17 @@ declare class LoginUI extends HostedFrameModal<LoginEventData> {
99
126
  private callbackStorageHandler;
100
127
  private popupMessageHandler;
101
128
  private closeMonitor;
129
+ private iframeFallbackPanel;
130
+ private iframeLoadingPanel;
131
+ private iframeLoadTimer;
132
+ private iframeRedirectTimer;
133
+ private iframeReady;
102
134
  private completed;
103
135
  private cleanup;
136
+ private markIframeReady;
137
+ private showIframeLoading;
138
+ private showIframeFallback;
139
+ private startIframeWatchdog;
104
140
  private handleLoginEvent;
105
141
  private listenForLoginCallback;
106
142
  close(): void;
@@ -111,4 +147,4 @@ declare class LoginUI extends HostedFrameModal<LoginEventData> {
111
147
  }
112
148
  declare function createLoginUI(): LoginUI;
113
149
 
114
- export { HostedFrameModal as H, type LoginCallbackOptions as L, type LoginDisplayMode as a, type LoginEventData as b, type LoginEventType as c, type LoginParams as d, type LoginResult as e, LoginUI as f, type LoginUIOptions as g, createLoginUI as h, handleLoginCallbackIfPresent as i };
150
+ export { HostedFrameModal as H, type LoginCallbackOptions as L, type LoginDisplayMode as a, type LoginEventData as b, type LoginEventType as c, type LoginFallbackRedirectMode as d, type LoginParams as e, type LoginResult as f, LoginUI as g, type LoginUIOptions as h, createLoginUI as i, handleLoginCallbackIfPresent as j };
package/dist/login.cjs CHANGED
@@ -130,8 +130,8 @@ var HostedFrameModal = class {
130
130
  __publicField(this, "messageHandler", null);
131
131
  }
132
132
  openHostedFrame(url, options) {
133
- if (typeof document === "undefined") return;
134
- if (this.modal) return;
133
+ if (typeof document === "undefined") return null;
134
+ if (this.modal) return null;
135
135
  this.modal = document.createElement("div");
136
136
  Object.assign(this.modal.style, {
137
137
  position: "fixed",
@@ -208,9 +208,13 @@ var HostedFrameModal = class {
208
208
  if (!data || typeof data !== "object" || !data.type) {
209
209
  return;
210
210
  }
211
- options.onMessage(data, container);
211
+ options.onMessage(data, container, event);
212
212
  };
213
213
  window.addEventListener("message", this.messageHandler);
214
+ return {
215
+ container,
216
+ iframe: this.iframe
217
+ };
214
218
  }
215
219
  close() {
216
220
  if (typeof window === "undefined") return;
@@ -376,6 +380,7 @@ var LOGIN_CALLBACK_RESULT_PARAM = "yd_login_result";
376
380
  var LOGIN_CALLBACK_RETURN_HASH_PARAM = "yd_login_return_hash";
377
381
  var LOGIN_CALLBACK_STATE_PARAM = "yd_login_state";
378
382
  var LOGIN_CALLBACK_STORAGE_PREFIX = "yd-login-callback:";
383
+ var DEFAULT_IFRAME_LOAD_TIMEOUT_MS = 1500;
379
384
  function decodeCallbackPayload(value) {
380
385
  try {
381
386
  const padded = value.padEnd(
@@ -503,6 +508,138 @@ function logLoginWarn(message, details) {
503
508
  if (typeof console === "undefined") return;
504
509
  console.warn(LOGIN_UI_LOG_PREFIX, message, details || {});
505
510
  }
511
+ function getIframeLoadTimeoutMs(options) {
512
+ const timeout = options?.iframeLoadTimeoutMs;
513
+ if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
514
+ return DEFAULT_IFRAME_LOAD_TIMEOUT_MS;
515
+ }
516
+ return Math.max(0, timeout);
517
+ }
518
+ function applyLoginPanelStyle(panel) {
519
+ Object.assign(panel.style, {
520
+ position: "absolute",
521
+ inset: "0",
522
+ display: "flex",
523
+ alignItems: "center",
524
+ justifyContent: "center",
525
+ border: "1px solid rgba(229,229,229,0.92)",
526
+ borderRadius: "28px",
527
+ background: "rgba(255,255,255,0.94)",
528
+ color: "#0f172a",
529
+ padding: "24px",
530
+ textAlign: "center",
531
+ zIndex: "1",
532
+ boxShadow: "0 18px 50px rgba(15,15,15,0.06)",
533
+ backdropFilter: "blur(16px)"
534
+ });
535
+ }
536
+ function createLoginPanelContent() {
537
+ const content = document.createElement("div");
538
+ Object.assign(content.style, {
539
+ maxWidth: "360px"
540
+ });
541
+ const badge = document.createElement("div");
542
+ badge.textContent = "YOUiDIAN";
543
+ Object.assign(badge.style, {
544
+ display: "inline-flex",
545
+ alignItems: "center",
546
+ border: "1px solid rgba(229,229,229,0.92)",
547
+ borderRadius: "9999px",
548
+ background: "#fafafa",
549
+ color: "#171717",
550
+ fontSize: "11px",
551
+ fontWeight: "700",
552
+ letterSpacing: "0.22em",
553
+ lineHeight: "1",
554
+ marginBottom: "18px",
555
+ padding: "8px 12px"
556
+ });
557
+ content.appendChild(badge);
558
+ return content;
559
+ }
560
+ function createLoginPanelTitle(value) {
561
+ const title = document.createElement("div");
562
+ title.textContent = value;
563
+ Object.assign(title.style, {
564
+ color: "#171717",
565
+ fontSize: "18px",
566
+ fontWeight: "700",
567
+ lineHeight: "1.35",
568
+ marginBottom: "10px"
569
+ });
570
+ return title;
571
+ }
572
+ function createLoginPanelDescription(value) {
573
+ const description = document.createElement("div");
574
+ description.textContent = value;
575
+ Object.assign(description.style, {
576
+ color: "#525252",
577
+ fontSize: "14px",
578
+ lineHeight: "1.6",
579
+ marginBottom: "20px"
580
+ });
581
+ return description;
582
+ }
583
+ function ensureLoginLoadingStyles() {
584
+ if (document.getElementById("youidian-login-loading-style")) {
585
+ return;
586
+ }
587
+ const style = document.createElement("style");
588
+ style.id = "youidian-login-loading-style";
589
+ style.textContent = "@keyframes youidian-login-spin{to{transform:rotate(360deg)}}";
590
+ document.head.appendChild(style);
591
+ }
592
+ function createLoginLoadingPanel(options) {
593
+ ensureLoginLoadingStyles();
594
+ const panel = document.createElement("div");
595
+ applyLoginPanelStyle(panel);
596
+ const content = createLoginPanelContent();
597
+ const spinner = document.createElement("div");
598
+ Object.assign(spinner.style, {
599
+ width: "22px",
600
+ height: "22px",
601
+ borderRadius: "9999px",
602
+ border: "2px solid rgba(23,23,23,0.16)",
603
+ borderTopColor: "#171717",
604
+ margin: "0 auto 18px",
605
+ animation: "youidian-login-spin 780ms linear infinite"
606
+ });
607
+ content.appendChild(spinner);
608
+ content.appendChild(createLoginPanelTitle(options.title));
609
+ content.appendChild(createLoginPanelDescription(options.description));
610
+ panel.appendChild(content);
611
+ return panel;
612
+ }
613
+ function createLoginRedirectingPanel(options) {
614
+ return createLoginLoadingPanel(options);
615
+ }
616
+ function createLoginFallbackPanel(options) {
617
+ const panel = document.createElement("div");
618
+ applyLoginPanelStyle(panel);
619
+ const content = createLoginPanelContent();
620
+ const button = document.createElement("button");
621
+ button.type = "button";
622
+ button.textContent = options.buttonText;
623
+ Object.assign(button.style, {
624
+ width: "100%",
625
+ border: "0",
626
+ borderRadius: "12px",
627
+ background: "#0a0a0a",
628
+ color: "#ffffff",
629
+ cursor: "pointer",
630
+ fontSize: "14px",
631
+ fontWeight: "700",
632
+ minHeight: "44px",
633
+ padding: "12px 16px",
634
+ boxShadow: "0 12px 28px rgba(17,17,17,0.14)"
635
+ });
636
+ button.onclick = options.onRedirect;
637
+ content.appendChild(createLoginPanelTitle(options.title));
638
+ content.appendChild(createLoginPanelDescription(options.description));
639
+ content.appendChild(button);
640
+ panel.appendChild(content);
641
+ return panel;
642
+ }
506
643
  var LoginUI = class extends HostedFrameModal {
507
644
  constructor() {
508
645
  super(...arguments);
@@ -511,6 +648,11 @@ var LoginUI = class extends HostedFrameModal {
511
648
  __publicField(this, "callbackStorageHandler", null);
512
649
  __publicField(this, "popupMessageHandler", null);
513
650
  __publicField(this, "closeMonitor", null);
651
+ __publicField(this, "iframeFallbackPanel", null);
652
+ __publicField(this, "iframeLoadingPanel", null);
653
+ __publicField(this, "iframeLoadTimer", null);
654
+ __publicField(this, "iframeRedirectTimer", null);
655
+ __publicField(this, "iframeReady", false);
514
656
  __publicField(this, "completed", false);
515
657
  }
516
658
  cleanup() {
@@ -528,11 +670,114 @@ var LoginUI = class extends HostedFrameModal {
528
670
  if (typeof window !== "undefined" && this.closeMonitor) {
529
671
  window.clearInterval(this.closeMonitor);
530
672
  }
673
+ if (typeof window !== "undefined" && this.iframeLoadTimer) {
674
+ window.clearTimeout(this.iframeLoadTimer);
675
+ }
676
+ if (typeof window !== "undefined" && this.iframeRedirectTimer) {
677
+ window.clearTimeout(this.iframeRedirectTimer);
678
+ }
679
+ if (this.iframeFallbackPanel?.parentNode) {
680
+ this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
681
+ }
682
+ if (this.iframeLoadingPanel?.parentNode) {
683
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
684
+ }
531
685
  this.popupMessageHandler = null;
532
686
  this.closeMonitor = null;
687
+ this.iframeFallbackPanel = null;
688
+ this.iframeLoadingPanel = null;
689
+ this.iframeLoadTimer = null;
690
+ this.iframeRedirectTimer = null;
691
+ this.iframeReady = false;
533
692
  this.popup = null;
534
693
  this.completed = false;
535
694
  }
695
+ markIframeReady() {
696
+ this.iframeReady = true;
697
+ if (typeof window !== "undefined" && this.iframeLoadTimer) {
698
+ window.clearTimeout(this.iframeLoadTimer);
699
+ this.iframeLoadTimer = null;
700
+ }
701
+ if (typeof window !== "undefined" && this.iframeRedirectTimer) {
702
+ window.clearTimeout(this.iframeRedirectTimer);
703
+ this.iframeRedirectTimer = null;
704
+ }
705
+ if (this.iframeFallbackPanel?.parentNode) {
706
+ this.iframeFallbackPanel.parentNode.removeChild(this.iframeFallbackPanel);
707
+ }
708
+ if (this.iframeLoadingPanel?.parentNode) {
709
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
710
+ }
711
+ this.iframeFallbackPanel = null;
712
+ this.iframeLoadingPanel = null;
713
+ }
714
+ showIframeLoading(container, options) {
715
+ if (this.iframeLoadingPanel || this.iframeReady) {
716
+ return;
717
+ }
718
+ const panel = createLoginLoadingPanel({
719
+ description: options?.loadingDescription || "Please wait while the secure login page opens.",
720
+ title: options?.loadingTitle || "Opening login page"
721
+ });
722
+ this.iframeLoadingPanel = panel;
723
+ container.appendChild(panel);
724
+ }
725
+ showIframeFallback(options) {
726
+ if (this.iframeReady || this.iframeFallbackPanel) {
727
+ return;
728
+ }
729
+ const { container, finalUrl, loginOptions } = options;
730
+ logLoginWarn("Hosted login iframe did not report ready in time", {
731
+ loginUrl: finalUrl
732
+ });
733
+ if (this.iframeLoadingPanel?.parentNode) {
734
+ this.iframeLoadingPanel.parentNode.removeChild(this.iframeLoadingPanel);
735
+ }
736
+ this.iframeLoadingPanel = null;
737
+ if (loginOptions?.fallbackRedirectMode !== "manual") {
738
+ const panel2 = createLoginRedirectingPanel({
739
+ description: loginOptions?.fallbackDescription || "The embedded login page is taking longer than expected. Redirecting you to the login page now.",
740
+ title: loginOptions?.fallbackTitle || "Redirecting to login page"
741
+ });
742
+ this.iframeFallbackPanel = panel2;
743
+ container.appendChild(panel2);
744
+ loginOptions?.onFallbackVisible?.();
745
+ this.iframeRedirectTimer = window.setTimeout(() => {
746
+ this.iframeRedirectTimer = null;
747
+ logLoginInfo("Redirecting to hosted login fallback page", {
748
+ loginUrl: finalUrl
749
+ });
750
+ window.location.assign(finalUrl);
751
+ }, 350);
752
+ return;
753
+ }
754
+ const panel = createLoginFallbackPanel({
755
+ buttonText: loginOptions?.fallbackButtonText || "Continue to login",
756
+ description: loginOptions?.fallbackDescription || "The embedded login page is taking longer than expected. Continue on the login page to finish signing in.",
757
+ title: loginOptions?.fallbackTitle || "Login page is still loading",
758
+ onRedirect: () => {
759
+ logLoginInfo("Redirecting to hosted login fallback page", {
760
+ loginUrl: finalUrl
761
+ });
762
+ window.location.assign(finalUrl);
763
+ }
764
+ });
765
+ this.iframeFallbackPanel = panel;
766
+ container.appendChild(panel);
767
+ loginOptions?.onFallbackVisible?.();
768
+ }
769
+ startIframeWatchdog(options) {
770
+ if (typeof window === "undefined") return;
771
+ const timeoutMs = getIframeLoadTimeoutMs(options.loginOptions);
772
+ if (timeoutMs === 0) {
773
+ this.showIframeFallback(options);
774
+ return;
775
+ }
776
+ this.iframeLoadTimer = window.setTimeout(() => {
777
+ this.iframeLoadTimer = null;
778
+ this.showIframeFallback(options);
779
+ }, timeoutMs);
780
+ }
536
781
  handleLoginEvent(data, options) {
537
782
  if (!data || typeof data !== "object" || !data.type) {
538
783
  logLoginDebug("Ignored non-login event payload");
@@ -542,6 +787,8 @@ var LoginUI = class extends HostedFrameModal {
542
787
  type: data.type
543
788
  });
544
789
  switch (data.type) {
790
+ case "LOGIN_READY":
791
+ break;
545
792
  case "LOGIN_SUCCESS":
546
793
  if (this.completed) {
547
794
  break;
@@ -649,15 +896,20 @@ var LoginUI = class extends HostedFrameModal {
649
896
  parentOrigin: launchPayload.origin || null
650
897
  });
651
898
  this.completed = false;
899
+ this.iframeReady = false;
652
900
  this.listenForLoginCallback(callbackState, options);
653
- this.openHostedFrame(finalUrl, {
901
+ const hostedFrame = this.openHostedFrame(finalUrl, {
654
902
  allowedOrigin: options?.allowedOrigin || loginOrigin || void 0,
655
903
  height: "min(760px, 94vh)",
656
904
  onCloseButton: () => {
657
905
  options?.onCancel?.();
658
906
  options?.onClose?.();
659
907
  },
660
- onMessage: (data, container) => {
908
+ onMessage: (data, container, event) => {
909
+ const isIframeEvent = event.source === this.iframe?.contentWindow;
910
+ if (isIframeEvent && (data.type === "LOGIN_READY" || data.type === "LOGIN_RESIZE") || data.type === "LOGIN_SUCCESS" || data.type === "LOGIN_ERROR") {
911
+ this.markIframeReady();
912
+ }
661
913
  if (data.type === "LOGIN_RESIZE" && data.height) {
662
914
  const maxHeight = window.innerHeight * 0.94;
663
915
  container.style.height = `${Math.min(data.height, maxHeight)}px`;
@@ -666,6 +918,15 @@ var LoginUI = class extends HostedFrameModal {
666
918
  },
667
919
  width: "min(520px, 100%)"
668
920
  });
921
+ if (hostedFrame) {
922
+ this.showIframeLoading(hostedFrame.container, options);
923
+ this.startIframeWatchdog({
924
+ container: hostedFrame.container,
925
+ finalUrl,
926
+ launchPayload,
927
+ loginOptions: options
928
+ });
929
+ }
669
930
  }
670
931
  openLoginPopup(params, options) {
671
932
  if (typeof window === "undefined") return;