@edgespark/web 0.0.3 → 0.0.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.
@@ -96,6 +96,40 @@
96
96
  gap: 8px;
97
97
  }
98
98
 
99
+ .edgespark-auth__tabs {
100
+ display: grid;
101
+ grid-template-columns: repeat(2, minmax(0, 1fr));
102
+ gap: 4px;
103
+ min-height: 44px;
104
+ padding: 4px;
105
+ border: 1px solid var(--edgespark-auth-border);
106
+ border-radius: var(--edgespark-auth-radius-control);
107
+ background: var(--edgespark-auth-bg-muted);
108
+ }
109
+
110
+ .edgespark-auth__tab {
111
+ min-width: 0;
112
+ border: 0;
113
+ border-radius: calc(var(--edgespark-auth-radius-control) - 4px);
114
+ background: transparent;
115
+ color: var(--edgespark-auth-text-muted);
116
+ cursor: pointer;
117
+ font: inherit;
118
+ font-size: 0.92rem;
119
+ font-weight: 600;
120
+ }
121
+
122
+ .edgespark-auth__tab--active {
123
+ background: var(--edgespark-auth-bg);
124
+ color: var(--edgespark-auth-text);
125
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
126
+ }
127
+
128
+ .edgespark-auth__tab:disabled {
129
+ cursor: not-allowed;
130
+ opacity: 0.6;
131
+ }
132
+
99
133
  .edgespark-auth__label {
100
134
  font-size: 0.9rem;
101
135
  font-weight: 600;
@@ -119,7 +153,8 @@
119
153
  }
120
154
 
121
155
  .edgespark-auth__button,
122
- .edgespark-auth__link {
156
+ .edgespark-auth__link,
157
+ .edgespark-auth__text-action {
123
158
  font: inherit;
124
159
  }
125
160
 
@@ -136,7 +171,8 @@
136
171
  }
137
172
 
138
173
  .edgespark-auth__button:disabled,
139
- .edgespark-auth__link:disabled {
174
+ .edgespark-auth__link:disabled,
175
+ .edgespark-auth__text-action:disabled {
140
176
  cursor: not-allowed;
141
177
  opacity: 0.6;
142
178
  }
@@ -174,6 +210,27 @@
174
210
  cursor: pointer;
175
211
  }
176
212
 
213
+ .edgespark-auth__text-action {
214
+ justify-self: center;
215
+ padding: 2px 4px;
216
+ border: 0;
217
+ background: transparent;
218
+ color: var(--edgespark-auth-primary);
219
+ cursor: pointer;
220
+ font-size: 0.95rem;
221
+ font-weight: 600;
222
+ line-height: 1.4;
223
+ }
224
+
225
+ .edgespark-auth__text-action:hover:not(:disabled) {
226
+ color: var(--edgespark-auth-primary-hover);
227
+ }
228
+
229
+ .edgespark-auth__text-action:disabled {
230
+ color: var(--edgespark-auth-text-muted);
231
+ opacity: 1;
232
+ }
233
+
177
234
  .edgespark-auth__divider {
178
235
  position: relative;
179
236
  text-align: center;
@@ -29,3 +29,8 @@ export declare function autoVerifyLocalDev(email: string): Promise<boolean>;
29
29
  * Returns the OTP string or null if not available.
30
30
  */
31
31
  export declare function fetchOtpLocalDev(email: string): Promise<string | null>;
32
+ /**
33
+ * Fetch the pending OTP for email sign-in in local dev.
34
+ * Returns the OTP string or null if not available.
35
+ */
36
+ export declare function fetchSignInOtpLocalDev(email: string): Promise<string | null>;
@@ -1,14 +1,18 @@
1
1
  import { BetterAuthClientInstance } from '../auth/types';
2
2
  import { NormalizedAuthUIConfig } from './config';
3
+ import { AuthUIModuleRuntimeOptions } from './mount';
3
4
  import { MountAuthUIOptions, MountedAuthUI } from './types';
4
- export interface ManagedAuthUIRuntimeOptions {
5
- syncPublicSession?: () => Promise<void> | void;
6
- }
7
5
  /**
8
6
  * Internal managed auth UI implementation.
9
7
  *
10
8
  * The public API only exposes `authUI.mount(...)`; this class owns the DOM,
11
9
  * form flows, and lifecycle.
10
+ *
11
+ * DOM ownership: the instance owns a single root element it appends to the
12
+ * mount container. It never writes to `container.innerHTML`. This keeps
13
+ * tear-down safe when the container is a DOM node a reconciling framework
14
+ * (React/Vue/Solid) reuses across conditional branches — wiping the
15
+ * container would erase sibling content the framework just rendered.
12
16
  */
13
17
  export declare class ManagedAuthUI implements MountedAuthUI {
14
18
  private readonly container;
@@ -27,7 +31,7 @@ export declare class ManagedAuthUI implements MountedAuthUI {
27
31
  private pendingFocusSnapshot;
28
32
  private focusRestoreTimer;
29
33
  private verificationCompletionAction;
30
- constructor(container: HTMLElement, auth: BetterAuthClientInstance, options: MountAuthUIOptions, loadConfig: () => Promise<NormalizedAuthUIConfig>, runtimeOptions?: ManagedAuthUIRuntimeOptions | undefined);
34
+ constructor(container: HTMLElement, auth: BetterAuthClientInstance, options: MountAuthUIOptions, loadConfig: () => Promise<NormalizedAuthUIConfig>, runtimeOptions?: AuthUIModuleRuntimeOptions | undefined);
31
35
  destroy(): void;
32
36
  private initialize;
33
37
  private redirectAfterManagedReturnLookupFailure;
@@ -48,6 +52,9 @@ export declare class ManagedAuthUI implements MountedAuthUI {
48
52
  private bindDOM;
49
53
  private switchView;
50
54
  private handleSignIn;
55
+ private handleEmailOtpSignIn;
56
+ private sendSignInOtp;
57
+ private resendSignInCode;
51
58
  private handleSignUp;
52
59
  private handleOAuthSignIn;
53
60
  private handleForgotPassword;
@@ -81,6 +88,10 @@ export declare class ManagedAuthUI implements MountedAuthUI {
81
88
  private scheduleFocusRestore;
82
89
  private clearFocusRestoreTimer;
83
90
  private passwordResetEndpoints;
91
+ private emailOtpSignInEndpoints;
92
+ private resolveInitialSignInMethod;
93
+ private setSignInMethod;
94
+ private canUseEmailOtpSignIn;
84
95
  private extractRedirectURL;
85
96
  private extractUser;
86
97
  private canUsePasswordResetEmailOtp;
@@ -1,6 +1,15 @@
1
1
  export type OAuthProviderId = "google" | "apple" | "microsoft" | "github" | "gitlab" | "discord";
2
+ interface OAuthProviderConfig {
3
+ /**
4
+ * OAuth Client ID resolved server-side. Present only when both the Client
5
+ * ID var and the corresponding secret resolve, so a non-empty value is
6
+ * the "this provider is fully wired browser-side" signal.
7
+ */
8
+ clientId?: string;
9
+ }
2
10
  interface OAuthProviderBlock {
3
11
  enabled?: boolean;
12
+ config?: OAuthProviderConfig;
4
13
  }
5
14
  interface ProviderEmailPassword {
6
15
  enabled?: boolean;
@@ -10,9 +19,13 @@ interface ProviderEmailPassword {
10
19
  requirePasswordResetEmailVerification?: boolean;
11
20
  };
12
21
  }
22
+ interface ProviderEmailOtp {
23
+ enabled?: boolean;
24
+ }
13
25
  interface AuthConfigResponse {
14
26
  disableSignUp?: boolean;
15
27
  providerEmailPassword?: ProviderEmailPassword;
28
+ providerEmailOtp?: ProviderEmailOtp;
16
29
  providerGoogle?: OAuthProviderBlock;
17
30
  providerApple?: OAuthProviderBlock;
18
31
  providerMicrosoft?: OAuthProviderBlock;
@@ -23,10 +36,17 @@ interface AuthConfigResponse {
23
36
  export interface NormalizedAuthUIConfig {
24
37
  disableSignUp: boolean;
25
38
  emailPasswordEnabled: boolean;
39
+ emailOtpSignInEnabled: boolean;
26
40
  minPasswordLength: number;
27
41
  requireEmailVerification: boolean;
28
42
  passwordResetEmailOtpEnabled: boolean;
29
43
  oauthProviders: OAuthProviderId[];
44
+ /**
45
+ * Resolved Google OAuth Client ID when Google is enabled and both
46
+ * credentials resolved server-side; `null` otherwise. Signals that
47
+ * Google client-side wiring is ready.
48
+ */
49
+ googleClientId: string | null;
30
50
  isLocalDev: boolean;
31
51
  }
32
52
  export declare function loadNormalizedAuthUIConfig(): Promise<NormalizedAuthUIConfig>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Minimal helpers for driving Google Identity Services (One Tap) from the
3
+ * managed auth UI. Loads the GIS client script on demand and exposes a tiny
4
+ * typed surface around the global `google.accounts.id` namespace.
5
+ *
6
+ * GIS is treated as a UX enhancement: any load failure falls back silently
7
+ * to the regular "Continue with Google" OAuth button.
8
+ */
9
+ interface GsiCredentialResponse {
10
+ credential: string;
11
+ select_by?: string;
12
+ }
13
+ interface GsiInitializeOptions {
14
+ client_id: string;
15
+ callback: (response: GsiCredentialResponse) => void;
16
+ auto_select?: boolean;
17
+ cancel_on_tap_outside?: boolean;
18
+ use_fedcm_for_prompt?: boolean;
19
+ context?: "signin" | "signup" | "use";
20
+ }
21
+ /**
22
+ * Subset of Google's `PromptMomentNotification`. Methods are optional because
23
+ * GIS's FedCM-era roadmap retires some of them, and EdgeSpark does not depend
24
+ * on `@types/google.accounts` for the full type surface.
25
+ */
26
+ interface GsiPromptNotification {
27
+ isNotDisplayed?: () => boolean;
28
+ isSkippedMoment?: () => boolean;
29
+ isDismissedMoment?: () => boolean;
30
+ getDismissedReason?: () => string | undefined;
31
+ }
32
+ interface GsiIdAccounts {
33
+ initialize(options: GsiInitializeOptions): void;
34
+ prompt(notificationCallback?: (notification: GsiPromptNotification) => void): void;
35
+ cancel(): void;
36
+ disableAutoSelect(): void;
37
+ }
38
+ /**
39
+ * Loads `accounts.google.com/gsi/client` once per page and resolves with
40
+ * the `google.accounts.id` namespace. Subsequent calls return the same
41
+ * promise; load failures reject and reset so a caller can retry next mount.
42
+ */
43
+ export declare function loadGoogleIdentityServices(): Promise<GsiIdAccounts>;
44
+ export type GoogleIdentityServices = GsiIdAccounts;
45
+ export type GoogleCredentialResponse = GsiCredentialResponse;
46
+ export type GooglePromptNotification = GsiPromptNotification;
47
+ export {};
@@ -1,4 +1,4 @@
1
1
  export { createAuthUIModule } from './mount';
2
2
  export { AUTH_UI_APPEARANCE_VARIABLE, AUTH_UI_APPEARANCE_VARIABLE_NAMES, AUTH_UI_THEME, AUTH_UI_DEFAULT_THEME, AUTH_UI_THEME_DARK, AUTH_UI_THEME_LIGHT, AUTH_UI_THEMES, } from './appearance';
3
3
  export type { AuthUIAppearance, AuthUIAppearanceVariableName, AuthUIAppearanceVariables, AuthUITheme, } from './appearance';
4
- export type { AuthSuccessEvent, AuthUILabelOverrides, AuthUILabels, ControlledAuthUIOptions, EdgeSparkAuthUI, MountAuthUIOptions, MountedAuthUI, RedirectAuthUIOptions, } from './types';
4
+ export type { AuthenticatedAuthSuccessEvent, AuthSuccessEvent, AuthUILabelOverrides, AuthUILabels, ControlledAuthUIOptions, EdgeSparkAuthUI, MountAuthUIOptions, MountedAuthUI, OneTapPromptHandle, PromptGoogleOneTapOptions, RedirectAuthUIOptions, } from './types';
@@ -1,4 +1,22 @@
1
1
  import { BetterAuthClientInstance } from '../auth/types';
2
- import { ManagedAuthUIRuntimeOptions } from './ManagedAuthUI';
3
2
  import { EdgeSparkAuthUI } from './types';
4
- export declare function createAuthUIModule(auth: BetterAuthClientInstance, runtimeOptions?: ManagedAuthUIRuntimeOptions): EdgeSparkAuthUI;
3
+ /**
4
+ * Internal runtime config for the auth-UI module factory. Threads cross-cutting
5
+ * auth wiring into `ManagedAuthUI` (modal/redirect flows) and
6
+ * `promptGoogleOneTap` (page-load One Tap prompt).
7
+ */
8
+ export interface AuthUIModuleRuntimeOptions {
9
+ /**
10
+ * Best-effort hook to nudge the public auth session after the managed UI
11
+ * completes a sign-in. Used by `ManagedAuthUI`'s email and OAuth flows.
12
+ */
13
+ syncPublicSession?: () => Promise<void> | void;
14
+ /**
15
+ * The public auth client used by `promptGoogleOneTap` for credential
16
+ * exchange. Routing One Tap through the same client as `signIn`/`signUp`
17
+ * keeps session updates and `onSessionChange` consistent across all
18
+ * sign-in methods.
19
+ */
20
+ publicAuthClient: BetterAuthClientInstance;
21
+ }
22
+ export declare function createAuthUIModule(auth: BetterAuthClientInstance, runtimeOptions: AuthUIModuleRuntimeOptions): EdgeSparkAuthUI;
@@ -0,0 +1,43 @@
1
+ import { BetterAuthClientInstance } from '../auth/types';
2
+ import { EdgeSparkAuthError } from '../errors';
3
+ import { NormalizedAuthUIConfig } from './config';
4
+ import { AuthenticatedAuthSuccessEvent } from './types';
5
+ export interface PromptGoogleOneTapOptions {
6
+ /**
7
+ * Fires when Google One Tap sign-in completes.
8
+ *
9
+ * **Most apps should prefer `client.auth.onSessionChange`** as the single
10
+ * source of truth — it fires for One Tap, modal sign-in, sign-out, and
11
+ * cross-tab session changes. Use `onSuccess` only for flow-local concerns
12
+ * scoped to this specific prompt (e.g., per-flow analytics).
13
+ */
14
+ onSuccess?: (event: AuthenticatedAuthSuccessEvent) => void;
15
+ /**
16
+ * Fires on credential-exchange or post-sign-in errors.
17
+ */
18
+ onError?: (error: EdgeSparkAuthError) => void;
19
+ /**
20
+ * Fires when Google declines to display the prompt — e.g., FedCM cooldown,
21
+ * no eligible accounts, or browser permission denied. Use this to render a
22
+ * fallback "Sign in with Google" button.
23
+ */
24
+ onUnavailable?: () => void;
25
+ }
26
+ export interface OneTapPromptHandle {
27
+ /**
28
+ * Dismiss the prompt and suppress any pending callbacks. Idempotent and
29
+ * safe to call after the prompt has already closed.
30
+ */
31
+ cancel(): void;
32
+ }
33
+ interface PromptRuntime {
34
+ /**
35
+ * The wrapped public auth client. Calls flow through its path-proxy so
36
+ * `oneTap.callback` triggers the same session-atom refresh and
37
+ * `onSessionChange` emit as `signIn`/`signUp`.
38
+ */
39
+ authClient: BetterAuthClientInstance;
40
+ loadConfig: () => Promise<NormalizedAuthUIConfig>;
41
+ }
42
+ export declare function createPromptGoogleOneTap(runtime: PromptRuntime): (options?: PromptGoogleOneTapOptions) => OneTapPromptHandle;
43
+ export {};
@@ -3,6 +3,8 @@ import { EdgeSparkAuthError } from '../errors';
3
3
  import { RedirectTarget } from '../redirect';
4
4
  import { DeepPartial } from '../shared/types';
5
5
  import { AuthUIAppearance } from './appearance';
6
+ import { OneTapPromptHandle, PromptGoogleOneTapOptions } from './prompt-google-one-tap';
7
+ export type { OneTapPromptHandle, PromptGoogleOneTapOptions } from './prompt-google-one-tap';
6
8
  /** Sign-in or sign-up completed with an authenticated session. */
7
9
  export type AuthenticatedAuthSuccessEvent = {
8
10
  action: "sign-in";
@@ -38,7 +40,14 @@ export interface AuthUILabels {
38
40
  subtitle: string;
39
41
  emailLabel: string;
40
42
  passwordLabel: string;
43
+ codeLabel?: string;
41
44
  submitButton: string;
45
+ sendCodeButton?: string;
46
+ verifyCodeButton?: string;
47
+ resendCodeButton?: string;
48
+ resendCountdown?: string;
49
+ passwordTab?: string;
50
+ codeTab?: string;
42
51
  forgotPassword: string;
43
52
  switchToSignUp: string;
44
53
  signUpPrompt: string;
@@ -152,5 +161,21 @@ export interface EdgeSparkAuthUI {
152
161
  * surrounding app wants to control routing/state after auth completes.
153
162
  */
154
163
  mount(container: HTMLElement, options: MountAuthUIOptions): MountedAuthUI;
164
+ /**
165
+ * Triggers a Google One Tap prompt. Calling the function is the opt-in —
166
+ * there is no separate server config or option flag.
167
+ *
168
+ * The prompt shows only when Google is fully wired server-side (the
169
+ * project's `auth-config.yaml` enables Google with resolvable credentials).
170
+ * Otherwise the call logs a clear error to the console and returns an
171
+ * inert handle.
172
+ *
173
+ * @example
174
+ * useEffect(() => {
175
+ * if (session) return;
176
+ * const prompt = client.authUI.promptGoogleOneTap();
177
+ * return () => prompt.cancel();
178
+ * }, [session]);
179
+ */
180
+ promptGoogleOneTap(options?: PromptGoogleOneTapOptions): OneTapPromptHandle;
155
181
  }
156
- export {};