@auth-link/web 0.0.5 → 2.0.0

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/styles.css CHANGED
@@ -1,4 +1,4 @@
1
- /* All variants share the same protocol, accessible controls, and theme tokens. */
1
+ /* A single responsive minimal dialog with accessible controls and theme tokens. */
2
2
  .authlink-dialog {
3
3
  --authlink-line: #e7ebe9;
4
4
  --authlink-soft: #f0f5f2;
@@ -26,7 +26,7 @@
26
26
  top: 8px; left: 1px; width: 16px; height: 1.5px; border-radius: 1px; background: currentColor; transform: rotate(45deg); }
27
27
  .authlink-close-icon::after { transform: rotate(-45deg); }
28
28
  .authlink-dialog .authlink-close:hover { background: var(--authlink-soft); color: var(--authlink-foreground, #173f35); }
29
- .authlink-dialog .authlink-close:focus-visible, .authlink-cancel:focus-visible, .authlink-button:focus-visible {
29
+ .authlink-dialog .authlink-close:focus-visible, .authlink-button:focus-visible {
30
30
  outline: 2px solid var(--authlink-accent, #277456); outline-offset: 2px; }
31
31
  .authlink-widget { box-sizing: border-box; width: 100%; min-width: 0; padding: 24px;
32
32
  color: var(--authlink-foreground, #173f35); background: var(--authlink-background, #fff);
@@ -64,63 +64,25 @@
64
64
  .authlink-widget[data-status="error"] .authlink-status, .authlink-widget[data-status="rejected"] .authlink-status { color: #a04432; }
65
65
  .authlink-widget[data-status="confirmed"] .authlink-status { color: var(--authlink-accent, #277456); }
66
66
  .authlink-footer { margin-top: 20px; }
67
- .authlink-widget[data-variant="A"] .authlink-footer .authlink-status { border-top: 1px solid var(--authlink-line, #e7ebe9); padding-top: 16px; }
67
+ .authlink-footer .authlink-status { border-top: 1px solid var(--authlink-line, #e7ebe9); padding-top: 16px; }
68
68
  .authlink-button { margin-top: 18px; border: 0; border-radius: 8px; min-height: 44px; padding: 10px 20px; font: inherit;
69
69
  cursor: pointer; color: #fff; background: var(--authlink-accent, #277456); }
70
- .authlink-cancel { display: inline-flex; align-items: center; justify-content: flex-start; min-height: 44px;
71
- min-width: 44px; padding: 8px 0; border: 0; border-radius: 4px; background: transparent;
72
- color: var(--authlink-muted, #697780); font: inherit; font-size: 12px; text-align: start; cursor: pointer; }
73
- .authlink-cancel:hover { color: var(--authlink-accent, #277456); text-decoration: underline; text-underline-offset: 3px; }
74
- /* B: wide desktop layout, vertical layout on narrow viewports. */
75
- .authlink-dialog[data-variant="B"] { width: min(650px, calc(100vw - 32px)); border-radius: 16px; }
76
- .authlink-dialog[data-variant="B"][data-has-qr="false"] { width: min(460px, calc(100vw - 32px)); }
77
- .authlink-dialog[data-variant="B"] .authlink-header { margin: 0; padding: 10px 24px; }
78
- .authlink-dialog[data-variant="B"] .authlink-close { margin-right: -6px; }
79
- .authlink-dialog[data-variant="B"] .authlink-close::before { content: ''; position: absolute; inset: 7px; border-radius: 7px; background: #f0f2f3; }
80
- .authlink-widget[data-variant="B"] { text-align: left; }
81
- .authlink-widget[data-variant="B"] .authlink-title { font-size: 24px; }
82
- .authlink-widget[data-variant="B"] .authlink-subtitle { display: none; }
83
- .authlink-widget[data-variant="B"][data-has-qr="true"] .authlink-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr); align-items: center; gap: 24px; }
84
- .authlink-widget[data-variant="B"] .authlink-qr-wrap { width: 100%; margin: 0; text-align: center; }
85
- .authlink-widget[data-variant="B"] .authlink-qr { width: min(100%, 196px); }
86
- .authlink-widget[data-variant="B"] .authlink-qr-caption { display: block; }
87
- .authlink-widget[data-variant="B"][data-has-qr="true"] .authlink-details { min-width: 0; padding-left: 24px; border-left: 1px solid var(--authlink-line, #e7ebe9); }
88
- .authlink-widget[data-variant="B"] .authlink-code-panel { display: block; padding: 0; background: transparent; }
89
- .authlink-widget[data-variant="B"] .authlink-code { margin: 10px 0 0; font-size: clamp(30px, 5vw, 40px); }
90
- .authlink-widget[data-variant="B"] .authlink-status { justify-content: flex-start; }
91
- .authlink-widget[data-variant="B"] .authlink-footer { display: flex; flex-wrap: wrap; gap: 8px 20px; align-items: center;
92
- justify-content: space-between; border-top: 1px solid var(--authlink-line, #e7ebe9); padding-top: 8px; margin-top: 24px; }
93
- /* C: compact card; becomes a bottom sheet on mobile. */
94
- .authlink-dialog[data-variant="C"] { width: min(390px, calc(100vw - 32px)); border-radius: 14px; }
95
- .authlink-dialog[data-variant="C"] .authlink-header { min-height: 58px; border: 0; padding-bottom: 0; }
96
- .authlink-widget[data-variant="C"] { padding-top: 12px; text-align: left; }
97
- .authlink-widget[data-variant="C"] .authlink-title { font-size: 22px; }
98
- .authlink-widget[data-variant="C"] .authlink-qr { width: min(100%, 180px); }
99
- .authlink-widget[data-variant="C"] .authlink-qr-wrap { margin-bottom: 18px; }
100
- .authlink-widget[data-variant="C"] .authlink-code-panel { grid-template-columns: minmax(0, 1fr); padding: 14px 16px; gap: 10px; background: #f5f7f8; }
101
- .authlink-widget[data-variant="C"] .authlink-code { font-size: clamp(22px, 6.8vw, 26px); margin: 4px 0 0; }
102
- .authlink-widget[data-variant="C"] .authlink-progress { grid-column: 1 / -1; }
103
- .authlink-widget[data-variant="C"] .authlink-help { font-size: 11px; margin-top: 12px; }
104
- .authlink-widget[data-variant="C"] .authlink-footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; border-top: 1px solid var(--authlink-line, #e7ebe9); padding-top: 16px; margin-top: 18px; }
105
- .authlink-widget[data-variant="C"] .authlink-status { justify-content: flex-start; margin: 0; }
106
70
  @media (max-width: 560px) {
107
- .authlink-dialog, .authlink-dialog[data-variant="B"], .authlink-dialog[data-variant="B"][data-has-qr="false"] { width: calc(100vw - 24px); max-height: calc(100dvh - 24px); }
71
+ .authlink-dialog { width: calc(100vw - 24px); max-height: calc(100dvh - 24px); }
108
72
  .authlink-header { margin: 0 20px; }
109
73
  .authlink-widget { padding: 20px; }
110
- .authlink-title, .authlink-widget[data-variant="B"] .authlink-title { font-size: 22px; }
74
+ .authlink-title { font-size: 22px; }
111
75
  .authlink-code-panel { padding: 14px 16px; gap: 0 10px; }
112
- .authlink-widget[data-variant="B"][data-has-qr="true"] .authlink-body { display: block; }
113
- .authlink-widget[data-variant="B"][data-has-qr="true"] .authlink-details { border: 0; padding: 22px 0 0; }
114
- .authlink-widget[data-variant="B"] .authlink-code { font-size: 36px; }
115
- .authlink-widget[data-variant="B"] .authlink-qr { width: min(100%, 180px); }
116
- .authlink-dialog[data-variant="C"] { width: 100%; max-width: none; margin: auto 0 0; max-height: calc(100dvh - 12px); border-radius: 20px 20px 0 0; border-bottom: 0; }
117
- .authlink-dialog[data-variant="C"] .authlink-widget { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
118
76
  }
119
77
  @media (max-width: 350px) {
120
78
  .authlink-widget { padding: 16px; }
121
79
  .authlink-header { margin: 0 16px; }
122
80
  .authlink-code-panel { padding: 12px; }
123
- .authlink-widget[data-variant="C"] .authlink-code-panel { grid-template-columns: minmax(0, 1fr); }
124
- .authlink-widget[data-variant="C"] .authlink-code { margin-bottom: 4px; }
125
81
  }
126
82
  @media (prefers-reduced-motion: reduce) { .authlink-dialog * { animation: none !important; transition: none !important; } }
83
+
84
+ .authlink-code-form { display: grid; gap: 12px; margin-top: 20px; text-align: left; }
85
+ .authlink-code-form input { width: 100%; box-sizing: border-box; padding: 12px; border: 1px solid #bccbc2; border-radius: 10px; background: white; color: #18352b; font: inherit; font-size: 24px; text-align: center; letter-spacing: .3em; }
86
+ .authlink-code-form label { font-size: 14px; font-weight: 600; }
87
+ .authlink-code-form button:disabled { opacity: .6; cursor: wait; }
88
+ .authlink-input-error { color: #a33128; font-size: 13px; }
@@ -24,10 +24,12 @@ export interface ClientOptions {
24
24
  authLinkOrigin?: string;
25
25
  fetch?: typeof fetch;
26
26
  }
27
+ export declare function createBackendCall(options: ClientOptions): (path: string, body: unknown, signal?: AbortSignal, key?: string) => Promise<unknown>;
27
28
  export declare function createAuthLinkClient(options: ClientOptions): AuthLinkClient;
28
29
  export declare function requestKey(): string;
29
30
  export declare function initialSnapshot(): Snapshot;
30
31
  export interface ObserveOptions extends Events {
32
+ onReady?: (submit: (confirmationCode: string) => Promise<void>) => void;
31
33
  /** Presentation-only timing from the same monotonic clock as code expiry. */
32
34
  onCodeTiming?: (remainingMs: number) => void;
33
35
  expectedMode?: Mode;
@@ -1,6 +1,6 @@
1
1
  import type { Locale } from './types.js';
2
2
  export declare const supportedLocales: readonly ["en", "zh-CN", "zh-TW", "ja", "bn", "es", "fil", "hi", "id", "ms", "pt", "th", "vi"];
3
- type Messages = Record<"cancel" | "close" | "bind" | "confirm" | "scan" | "code" | "help" | "waiting" | "loading" | "confirmed" | "rejected" | "expired" | "error" | "retry" | "refresh" | "autoUpdate" | "expiring" | "updating", string>;
3
+ type Messages = Record<"otpCode" | "otpHelp" | "otpWaiting" | "otpError" | "otpSetup" | "codeLocked" | "confirmationCode" | "submitCode" | "codeError" | "cancel" | "close" | "bind" | "confirm" | "scan" | "code" | "help" | "waiting" | "loading" | "confirmed" | "rejected" | "expired" | "error" | "retry" | "refresh" | "autoUpdate" | "expiring" | "updating", string>;
4
4
  export declare const defaultLocale: Locale;
5
5
  export declare function resolveLocale(locale?: string): Locale;
6
6
  export declare function getMessages(locale?: string): Messages;
@@ -4,3 +4,5 @@ export { supportedLocales } from './i18n.js';
4
4
  export { openAuthLink, openAuthLinkBind, openAuthLinkConfirm } from './modal.js';
5
5
  export type { AuthLinkModal, AuthLinkModalOptions } from './modal.js';
6
6
  export type { ClientOptions } from './core.js';
7
+ export { openPasscode, createPasscodeClient } from './passcode.js';
8
+ export type { OTPVerification, PasscodeClient, PasscodeOptions } from './passcode.js';
@@ -1,4 +1,5 @@
1
- import type { DialogVariant, Locale, WidgetOptions } from './types.js';
1
+ import type { ViewNode } from './view.js';
2
+ import type { Locale, WidgetOptions } from './types.js';
2
3
  export interface AuthLinkModalOptions extends WidgetOptions {
3
4
  className?: string;
4
5
  onClose?: () => void;
@@ -9,10 +10,9 @@ export interface AuthLinkModal {
9
10
  /** Component cleanup: close without delivering a user-close callback. */
10
11
  destroy(): void;
11
12
  setLocale(locale?: Locale): void;
12
- /** Change presentation without creating or restarting a request. */
13
- setVariant(variant?: DialogVariant): void;
14
13
  retry(): void;
15
14
  }
15
+ export declare function element(value: ViewNode | string, svg?: boolean): Node;
16
16
  /** Show a backend-created request. This function never creates or authorizes a request. */
17
17
  export declare function openAuthLink(options: AuthLinkModalOptions): AuthLinkModal;
18
18
  export declare const openAuthLinkBind: (options: AuthLinkModalOptions) => AuthLinkModal;
@@ -0,0 +1,28 @@
1
+ import type { ClientOptions } from './core.js';
2
+ import type { AuthLinkModal } from './modal.js';
3
+ import type { Locale } from './types.js';
4
+ export interface OTPVerification {
5
+ status: 'confirmed';
6
+ verificationId: string;
7
+ authenticationPolicy: 'device-totp.v1';
8
+ expiresAt: string;
9
+ }
10
+ export interface PasscodeClient {
11
+ verify(passcode: string, signal: AbortSignal): Promise<OTPVerification>;
12
+ }
13
+ export interface PasscodeOptions {
14
+ client: PasscodeClient;
15
+ /** The CMS's single pending action deadline, not a TOTP time step. */
16
+ expiresAt: string;
17
+ locale?: Locale;
18
+ /** First-time enrollment only. Ordinary verification has no QR or request ID. */
19
+ bindingQr?: string;
20
+ authLinkOrigin?: string;
21
+ onVerified?: (result: OTPVerification) => void | Promise<void>;
22
+ onClose?: () => void;
23
+ onExpired?: () => void | Promise<void>;
24
+ onError?: (error: Error) => void;
25
+ }
26
+ export declare function createPasscodeClient(options: ClientOptions): PasscodeClient;
27
+ /** A session-scoped passcode prompt. No request creation, request ID or polling. */
28
+ export declare function openPasscode(options: PasscodeOptions): AuthLinkModal;
@@ -1,9 +1,13 @@
1
1
  import type { Snapshot, WidgetOptions } from './types.js';
2
2
  import type { AuthLinkModalOptions } from './modal.js';
3
- export type { Ticket, Confirmation, Rejection, Snapshot, AuthLinkClient, Locale, DialogVariant } from './types.js';
3
+ import type { PasscodeOptions } from './passcode.js';
4
+ export type { PasscodeOptions, PasscodeClient, OTPVerification } from './passcode.js';
5
+ export declare function AuthLinkPasscode(props: PasscodeOptions): null;
6
+ export type { Ticket, Confirmation, Rejection, Snapshot, AuthLinkClient, Locale } from './types.js';
4
7
  export declare function useAuthLink(options: WidgetOptions): {
5
8
  snapshot: Snapshot;
6
9
  retry: () => void;
10
+ submitCode: (code: string) => Promise<void>;
7
11
  };
8
12
  export interface AuthLinkWidgetProps extends AuthLinkModalOptions {
9
13
  }
@@ -1,5 +1,4 @@
1
1
  export type Mode = 'bind' | 'confirm';
2
- export type DialogVariant = 'A' | 'B' | 'C';
3
2
  export type Status = 'idle' | 'pending' | 'confirmed' | 'rejected' | 'expired' | 'error';
4
3
  export type Locale = 'en' | 'zh-CN' | 'zh-TW' | 'ja' | 'bn' | 'es' | 'fil' | 'hi' | 'id' | 'ms' | 'pt' | 'th' | 'vi';
5
4
  /** Current display only. The generation secret must never reach the browser. */
@@ -43,10 +42,11 @@ export interface AuthLinkClient {
43
42
  idempotencyKey: string;
44
43
  signal?: AbortSignal;
45
44
  }): Promise<Ticket>;
46
- result(ticket: Ticket, signal: AbortSignal): Promise<Result>;
45
+ result(ticket: Ticket, signal: AbortSignal, confirmationCode?: string): Promise<Result>;
47
46
  }
48
47
  export interface Snapshot {
49
48
  status: Status;
49
+ submitting?: boolean;
50
50
  code: string | null;
51
51
  secondsRemaining: number;
52
52
  codeSecondsRemaining: number;
@@ -63,8 +63,6 @@ export interface WidgetOptions extends Events {
63
63
  ticket: Ticket;
64
64
  client: AuthLinkClient;
65
65
  locale?: Locale;
66
- /** A: minimal (default), B: split, C: compact / mobile bottom sheet. */
67
- variant?: DialogVariant;
68
66
  showQr?: boolean;
69
67
  intervalMs?: number;
70
68
  onRefresh?: () => void;
@@ -1,19 +1,20 @@
1
- import type { DialogVariant, Locale, Mode, Snapshot } from './types.js';
1
+ import type { Locale, Mode, Snapshot } from './types.js';
2
2
  export interface ViewNode {
3
3
  tag: string;
4
4
  props: Record<string, unknown>;
5
5
  children: (ViewNode | string)[];
6
6
  }
7
- export declare function resolveVariant(variant?: string): DialogVariant;
8
7
  export declare function widgetView(options: {
9
8
  mode: Mode;
10
9
  locale?: Locale;
11
- variant?: DialogVariant;
12
10
  snapshot: Snapshot;
11
+ passcodeMode?: boolean;
13
12
  qr?: string;
14
13
  showQr?: boolean;
14
+ submitCode?: (code: string) => Promise<void>;
15
+ inputCode?: string;
16
+ setCode?: (code: string) => void;
15
17
  retry: () => void;
16
18
  refresh?: () => void;
17
- close?: () => void;
18
19
  className?: string;
19
20
  }): ViewNode;
@@ -1,10 +1,14 @@
1
1
  import type { DefineComponent, ShallowRef } from 'vue';
2
2
  import type { Snapshot, WidgetOptions } from './types.js';
3
3
  import type { AuthLinkModalOptions } from './modal.js';
4
- export type { Ticket, Confirmation, Rejection, Snapshot, AuthLinkClient, Locale, DialogVariant } from './types.js';
4
+ import type { PasscodeOptions } from './passcode.js';
5
+ export type { PasscodeOptions, PasscodeClient, OTPVerification } from './passcode.js';
6
+ export declare const AuthLinkPasscode: DefineComponent<PasscodeOptions>;
7
+ export type { Ticket, Confirmation, Rejection, Snapshot, AuthLinkClient, Locale } from './types.js';
5
8
  export declare function useAuthLink(getOptions: () => WidgetOptions): {
6
9
  snapshot: ShallowRef<Snapshot>;
7
10
  retry: () => void;
11
+ submitCode: (code: string) => Promise<void>;
8
12
  };
9
13
  export interface AuthLinkWidgetProps extends Omit<AuthLinkModalOptions, 'mode'> {
10
14
  }