dexie-cloud-addon 4.3.0 → 4.3.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.
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.3.0, Tue Jan 20 2026
11
+ * Version 4.3.2, Thu Jan 22 2026
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -103,15 +103,6 @@
103
103
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
104
104
  };
105
105
 
106
- /** Type guard to check if a message is an OAuthResultMessage */
107
- function isOAuthResultMessage(msg) {
108
- return (typeof msg === 'object' &&
109
- msg !== null &&
110
- msg.type === 'dexie:oauthResult' &&
111
- typeof msg.provider === 'string' &&
112
- typeof msg.state === 'string');
113
- }
114
-
115
106
  function assert(b) {
116
107
  if (!b)
117
108
  throw new Error('Assertion Failed');
@@ -2070,6 +2061,74 @@
2070
2061
  }
2071
2062
  }
2072
2063
 
2064
+ /** Cache for fetched SVG content to avoid re-fetching */
2065
+ const svgCache = {};
2066
+ /** Default SVG icons for built-in OAuth providers */
2067
+ const ProviderIcons = {
2068
+ google: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>`,
2069
+ github: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>`,
2070
+ microsoft: `<svg viewBox="0 0 24 24" width="20" height="20"><rect fill="#F25022" x="1" y="1" width="10" height="10"/><rect fill="#00A4EF" x="1" y="13" width="10" height="10"/><rect fill="#7FBA00" x="13" y="1" width="10" height="10"/><rect fill="#FFB900" x="13" y="13" width="10" height="10"/></svg>`,
2071
+ apple: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>`,
2072
+ };
2073
+ /** Email/envelope icon for OTP option */
2074
+ const EmailIcon = `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M22 6L12 13 2 6"/></svg>`;
2075
+ /**
2076
+ * Fetches SVG content from a URL and caches it.
2077
+ * Returns the SVG string or null if fetch fails.
2078
+ */
2079
+ function fetchSvgIcon(url) {
2080
+ return __awaiter(this, void 0, void 0, function* () {
2081
+ if (svgCache[url]) {
2082
+ return svgCache[url];
2083
+ }
2084
+ try {
2085
+ const res = yield fetch(url);
2086
+ if (res.ok) {
2087
+ const svg = yield res.text();
2088
+ // Validate it looks like SVG
2089
+ if (svg.includes('<svg')) {
2090
+ svgCache[url] = svg;
2091
+ return svg;
2092
+ }
2093
+ }
2094
+ }
2095
+ catch (_a) {
2096
+ // Silently fail - will show no icon
2097
+ }
2098
+ return null;
2099
+ });
2100
+ }
2101
+ /**
2102
+ * Converts an OAuthProviderInfo to a generic DXCOption.
2103
+ * Fetches SVG icons from URLs if needed.
2104
+ */
2105
+ function providerToOption(provider) {
2106
+ return __awaiter(this, void 0, void 0, function* () {
2107
+ var _a;
2108
+ let iconSvg;
2109
+ // First check for built-in icons
2110
+ if (ProviderIcons[provider.type]) {
2111
+ iconSvg = ProviderIcons[provider.type];
2112
+ }
2113
+ // If provider has iconUrl pointing to SVG, fetch and inline it
2114
+ else if ((_a = provider.iconUrl) === null || _a === void 0 ? void 0 : _a.toLowerCase().endsWith('.svg')) {
2115
+ const fetched = yield fetchSvgIcon(provider.iconUrl);
2116
+ if (fetched) {
2117
+ iconSvg = fetched;
2118
+ }
2119
+ }
2120
+ return {
2121
+ name: 'provider',
2122
+ value: provider.name,
2123
+ displayName: `Continue with ${provider.displayName}`,
2124
+ iconSvg,
2125
+ // If iconUrl is not SVG, pass it through for img tag rendering
2126
+ iconUrl: (!iconSvg && provider.iconUrl) ? provider.iconUrl : undefined,
2127
+ // Use provider type as style hint for branding
2128
+ styleHint: provider.type,
2129
+ };
2130
+ });
2131
+ }
2073
2132
  function interactWithUser(userInteraction, req) {
2074
2133
  return new Promise((resolve, reject) => {
2075
2134
  const interactionProps = Object.assign(Object.assign({ submitLabel: 'Submit', cancelLabel: 'Cancel' }, req), { onSubmit: (res) => {
@@ -2207,6 +2266,9 @@
2207
2266
  /**
2208
2267
  * Prompts the user to select an authentication method (OAuth provider or OTP).
2209
2268
  *
2269
+ * This function converts OAuth providers and OTP option into generic DXCOption[]
2270
+ * for the DXCSelect interaction, handling icon fetching and style hints.
2271
+ *
2210
2272
  * @param userInteraction - The user interaction BehaviorSubject
2211
2273
  * @param providers - Available OAuth providers
2212
2274
  * @param otpEnabled - Whether OTP is available
@@ -2214,31 +2276,52 @@
2214
2276
  * @param alerts - Optional alerts to display
2215
2277
  * @returns Promise resolving to the user's selection
2216
2278
  */
2217
- function promptForProvider(userInteraction, providers, otpEnabled, title = 'Choose login method', alerts = []) {
2218
- return new Promise((resolve, reject) => {
2219
- const interactionProps = {
2220
- type: 'provider-selection',
2221
- title,
2222
- alerts,
2223
- providers,
2224
- otpEnabled,
2225
- fields: {},
2226
- submitLabel: undefined,
2227
- cancelLabel: 'Cancel',
2228
- onSelectProvider: (providerName) => {
2229
- userInteraction.next(undefined);
2230
- resolve({ type: 'provider', provider: providerName });
2231
- },
2232
- onSelectOtp: () => {
2233
- userInteraction.next(undefined);
2234
- resolve({ type: 'otp' });
2235
- },
2236
- onCancel: () => {
2237
- userInteraction.next(undefined);
2238
- reject(new Dexie.AbortError('User cancelled'));
2239
- },
2240
- };
2241
- userInteraction.next(interactionProps);
2279
+ function promptForProvider(userInteraction_1, providers_1, otpEnabled_1) {
2280
+ return __awaiter(this, arguments, void 0, function* (userInteraction, providers, otpEnabled, title = 'Choose login method', alerts = []) {
2281
+ // Convert providers to generic options (with icon fetching)
2282
+ const providerOptions = yield Promise.all(providers.map(providerToOption));
2283
+ // Build the options array
2284
+ const options = [...providerOptions];
2285
+ // Add OTP option if enabled
2286
+ if (otpEnabled) {
2287
+ options.push({
2288
+ name: 'otp',
2289
+ value: 'email',
2290
+ displayName: 'Continue with email',
2291
+ iconSvg: EmailIcon,
2292
+ styleHint: 'otp',
2293
+ });
2294
+ }
2295
+ return new Promise((resolve, reject) => {
2296
+ const interactionProps = {
2297
+ type: 'generic',
2298
+ title,
2299
+ alerts,
2300
+ options,
2301
+ fields: {},
2302
+ submitLabel: '', // No submit button - just options
2303
+ cancelLabel: 'Cancel',
2304
+ onSubmit: (params) => {
2305
+ userInteraction.next(undefined);
2306
+ // Check which option was selected
2307
+ if ('otp' in params) {
2308
+ resolve({ type: 'otp' });
2309
+ }
2310
+ else if ('provider' in params) {
2311
+ resolve({ type: 'provider', provider: params.provider });
2312
+ }
2313
+ else {
2314
+ // Unknown - default to OTP
2315
+ resolve({ type: 'otp' });
2316
+ }
2317
+ },
2318
+ onCancel: () => {
2319
+ userInteraction.next(undefined);
2320
+ reject(new Dexie.AbortError('User cancelled'));
2321
+ },
2322
+ };
2323
+ userInteraction.next(interactionProps);
2324
+ });
2242
2325
  });
2243
2326
  }
2244
2327
 
@@ -2566,8 +2649,6 @@
2566
2649
 
2567
2650
  /** User-friendly messages for OAuth error codes */
2568
2651
  const ERROR_MESSAGES = {
2569
- popup_blocked: 'The login popup was blocked by your browser. Please allow popups for this site and try again.',
2570
- popup_closed: 'The login popup was closed before completing authentication.',
2571
2652
  access_denied: 'Access was denied by the authentication provider.',
2572
2653
  invalid_state: 'The authentication response could not be verified. Please try again.',
2573
2654
  email_not_verified: 'Your email address must be verified before you can log in.',
@@ -2709,144 +2790,46 @@
2709
2790
  });
2710
2791
  }
2711
2792
 
2712
- /** Generate a random state string for CSRF protection */
2713
- function generateState() {
2714
- const array = new Uint8Array(32);
2715
- crypto.getRandomValues(array);
2716
- return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
2717
- }
2718
2793
  /** Build the OAuth login URL */
2719
- function buildOAuthLoginUrl(options, state) {
2794
+ function buildOAuthLoginUrl(options) {
2720
2795
  const url = new URL(`${options.databaseUrl}/oauth/login/${options.provider}`);
2721
- url.searchParams.set('state', state);
2722
- // Set the redirect URI for postMessage or custom scheme
2796
+ // Set the redirect URI - defaults to current page URL for web SPAs
2723
2797
  const redirectUri = options.redirectUri ||
2724
- (typeof window !== 'undefined' ? window.location.origin : '');
2798
+ (typeof window !== 'undefined' ? window.location.href : '');
2725
2799
  if (redirectUri) {
2726
2800
  url.searchParams.set('redirect_uri', redirectUri);
2727
2801
  }
2728
2802
  return url.toString();
2729
2803
  }
2730
- /** Calculate centered popup position */
2731
- function getPopupPosition(width, height) {
2732
- var _a, _b, _c, _d, _e, _f;
2733
- const screenLeft = (_a = window.screenLeft) !== null && _a !== void 0 ? _a : window.screenX;
2734
- const screenTop = (_b = window.screenTop) !== null && _b !== void 0 ? _b : window.screenY;
2735
- const screenWidth = (_d = (_c = window.innerWidth) !== null && _c !== void 0 ? _c : document.documentElement.clientWidth) !== null && _d !== void 0 ? _d : screen.width;
2736
- const screenHeight = (_f = (_e = window.innerHeight) !== null && _e !== void 0 ? _e : document.documentElement.clientHeight) !== null && _f !== void 0 ? _f : screen.height;
2737
- const left = screenLeft + (screenWidth - width) / 2;
2738
- const top = screenTop + (screenHeight - height) / 2;
2739
- return { left: Math.max(0, left), top: Math.max(0, top) };
2740
- }
2741
2804
  /**
2742
- * Initiates OAuth login flow using a popup window.
2805
+ * Initiates OAuth login via full page redirect.
2743
2806
  *
2744
- * Opens a popup to the OAuth provider, listens for postMessage with the result,
2745
- * and returns the Dexie Cloud authorization code.
2807
+ * The page will navigate to the OAuth provider. After authentication,
2808
+ * the user is redirected back to the app with a `dxc-auth` query parameter
2809
+ * containing base64url-encoded JSON with the authorization code.
2746
2810
  *
2747
- * @param options - OAuth login options
2748
- * @returns Promise resolving to OAuthLoginResult
2749
- * @throws OAuthError on failure
2811
+ * The dexie-cloud-addon automatically detects and processes this parameter
2812
+ * when db.cloud.configure() is called on page load.
2813
+ *
2814
+ * @param options - OAuth redirect options
2815
+ *
2816
+ * @example
2817
+ * ```typescript
2818
+ * // Initiate OAuth login
2819
+ * startOAuthRedirect({
2820
+ * databaseUrl: 'https://mydb.dexie.cloud',
2821
+ * provider: 'google'
2822
+ * });
2823
+ * // Page navigates away, user authenticates, then returns with auth code
2824
+ * ```
2750
2825
  */
2751
- function oauthLogin(options) {
2752
- return __awaiter(this, void 0, void 0, function* () {
2753
- const { databaseUrl, provider, usePopup = true } = options;
2754
- if (!usePopup) {
2755
- // For redirect flows, we can't return a promise - the page will navigate away
2756
- throw new Error('Non-popup OAuth flow requires handleOAuthCallback after redirect');
2757
- }
2758
- const state = generateState();
2759
- const loginUrl = buildOAuthLoginUrl(options, state);
2760
- // Calculate popup dimensions and position
2761
- const width = 500;
2762
- const height = 600;
2763
- const { left, top } = getPopupPosition(width, height);
2764
- // Open popup window
2765
- const popup = window.open(loginUrl, 'dexie-cloud-oauth', `width=${width},height=${height},left=${left},top=${top},menubar=no,toolbar=no,location=yes,status=no`);
2766
- if (!popup) {
2767
- throw new OAuthError('popup_blocked', provider);
2768
- }
2769
- return new Promise((resolve, reject) => {
2770
- let resolved = false;
2771
- // Listen for postMessage from the popup
2772
- const handleMessage = (event) => {
2773
- // Validate origin - must be from the Dexie Cloud server
2774
- const expectedOrigin = new URL(databaseUrl).origin;
2775
- if (event.origin !== expectedOrigin) {
2776
- return; // Ignore messages from other origins
2777
- }
2778
- // Check if this is our OAuth result message
2779
- if (!isOAuthResultMessage(event.data)) {
2780
- return;
2781
- }
2782
- const message = event.data;
2783
- // Validate state to prevent CSRF
2784
- if (message.state !== state) {
2785
- console.warn('[dexie-cloud] OAuth state mismatch, ignoring message');
2786
- return;
2787
- }
2788
- // Clean up
2789
- cleanup();
2790
- resolved = true;
2791
- // Handle error from OAuth flow
2792
- if (message.error) {
2793
- const errorCode = mapOAuthError(message.error);
2794
- reject(new OAuthError(errorCode, provider, message.error));
2795
- return;
2796
- }
2797
- // Success - return the authorization code
2798
- if (message.code) {
2799
- resolve({
2800
- code: message.code,
2801
- provider: message.provider,
2802
- state: message.state,
2803
- });
2804
- }
2805
- else {
2806
- reject(new OAuthError('provider_error', provider, 'No authorization code received'));
2807
- }
2808
- };
2809
- // Check if popup was closed without completing
2810
- const checkPopupClosed = setInterval(() => {
2811
- if (popup.closed && !resolved) {
2812
- cleanup();
2813
- reject(new OAuthError('popup_closed', provider));
2814
- }
2815
- }, 500);
2816
- // Cleanup function
2817
- const cleanup = () => {
2818
- window.removeEventListener('message', handleMessage);
2819
- clearInterval(checkPopupClosed);
2820
- try {
2821
- if (!popup.closed) {
2822
- popup.close();
2823
- }
2824
- }
2825
- catch (_a) {
2826
- // Ignore errors when closing popup
2827
- }
2828
- };
2829
- // Start listening for messages
2830
- window.addEventListener('message', handleMessage);
2831
- });
2832
- });
2833
- }
2834
- /** Map OAuth error strings to error codes */
2835
- function mapOAuthError(error) {
2836
- const lowerError = error.toLowerCase();
2837
- if (lowerError.includes('access_denied') || lowerError.includes('access denied')) {
2838
- return 'access_denied';
2826
+ function startOAuthRedirect(options) {
2827
+ // Store provider in sessionStorage for reference on callback
2828
+ if (typeof sessionStorage !== 'undefined') {
2829
+ sessionStorage.setItem('dexie-cloud-oauth-provider', options.provider);
2839
2830
  }
2840
- if (lowerError.includes('email') && lowerError.includes('verif')) {
2841
- return 'email_not_verified';
2842
- }
2843
- if (lowerError.includes('expired')) {
2844
- return 'expired_code';
2845
- }
2846
- if (lowerError.includes('state')) {
2847
- return 'invalid_state';
2848
- }
2849
- return 'provider_error';
2831
+ const loginUrl = buildOAuthLoginUrl(options);
2832
+ window.location.href = loginUrl;
2850
2833
  }
2851
2834
 
2852
2835
  function otpFetchTokenCallback(db) {
@@ -2867,9 +2850,11 @@
2867
2850
  scopes: ['ACCESS_DB'],
2868
2851
  });
2869
2852
  }
2870
- // Handle OAuth provider login (popup flow)
2853
+ // Handle OAuth provider login via redirect
2871
2854
  if (hints === null || hints === void 0 ? void 0 : hints.provider) {
2872
- return yield handleOAuthFlow(db, public_key, hints.provider);
2855
+ initiateOAuthRedirect(db, hints.provider);
2856
+ // This function never returns - page navigates away
2857
+ throw new Error('OAuth redirect initiated');
2873
2858
  }
2874
2859
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
2875
2860
  const demo_user = yield promptForEmail(userInteraction, 'Enter a demo user email', (hints === null || hints === void 0 ? void 0 : hints.email) || (hints === null || hints === void 0 ? void 0 : hints.userId));
@@ -2901,8 +2886,10 @@
2901
2886
  if (authProviders.providers.length > 0) {
2902
2887
  const selection = yield promptForProvider(userInteraction, authProviders.providers, authProviders.otpEnabled, 'Sign in');
2903
2888
  if (selection.type === 'provider') {
2904
- // User selected an OAuth provider
2905
- return yield handleOAuthFlow(db, public_key, selection.provider);
2889
+ // User selected an OAuth provider - initiate redirect
2890
+ initiateOAuthRedirect(db, selection.provider);
2891
+ // This function never returns - page navigates away
2892
+ throw new Error('OAuth redirect initiated');
2906
2893
  }
2907
2894
  // User chose OTP - continue with email prompt below
2908
2895
  }
@@ -2984,46 +2971,24 @@
2984
2971
  };
2985
2972
  }
2986
2973
  /**
2987
- * Handles the OAuth popup flow and token exchange.
2974
+ * Initiates OAuth login via full page redirect.
2975
+ *
2976
+ * The page will navigate away to the OAuth provider. After authentication,
2977
+ * the user is redirected back with a dxc-auth query parameter that is
2978
+ * automatically detected by db.cloud.configure().
2988
2979
  */
2989
- function handleOAuthFlow(db, publicKey, provider) {
2990
- return __awaiter(this, void 0, void 0, function* () {
2991
- var _a, _b, _c;
2992
- const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
2993
- if (!url)
2994
- throw new Error(`No database URL given.`);
2995
- const { userInteraction } = db.cloud;
2996
- const usePopup = ((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.oauthPopup) !== false;
2997
- const redirectUri = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.oauthRedirectUri) ||
2998
- (typeof window !== 'undefined' ? window.location.origin : undefined);
2999
- try {
3000
- // Start OAuth popup flow
3001
- const result = yield oauthLogin({
3002
- databaseUrl: url,
3003
- provider,
3004
- redirectUri,
3005
- usePopup,
3006
- });
3007
- // Exchange the auth code for tokens
3008
- return yield exchangeOAuthCode({
3009
- databaseUrl: url,
3010
- code: result.code,
3011
- publicKey,
3012
- scopes: ['ACCESS_DB'],
3013
- });
3014
- }
3015
- catch (error) {
3016
- if (error instanceof OAuthError) {
3017
- // Show user-friendly error message
3018
- yield alertUser(userInteraction, 'Authentication Failed', {
3019
- type: 'error',
3020
- messageCode: 'GENERIC_ERROR',
3021
- message: error.userMessage,
3022
- messageParams: {},
3023
- }).catch(() => { });
3024
- }
3025
- throw error;
3026
- }
2980
+ function initiateOAuthRedirect(db, provider) {
2981
+ var _a, _b;
2982
+ const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
2983
+ if (!url)
2984
+ throw new Error(`No database URL given.`);
2985
+ const redirectUri = ((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.oauthRedirectUri) ||
2986
+ (typeof window !== 'undefined' ? window.location.href : undefined);
2987
+ // Start OAuth redirect flow - page navigates away
2988
+ startOAuthRedirect({
2989
+ databaseUrl: url,
2990
+ provider,
2991
+ redirectUri,
3027
2992
  });
3028
2993
  }
3029
2994
 
@@ -17039,7 +17004,10 @@
17039
17004
  ProviderButtonIcon: {
17040
17005
  width: "20px",
17041
17006
  height: "20px",
17042
- flexShrink: 0
17007
+ flexShrink: 0,
17008
+ display: "flex",
17009
+ alignItems: "center",
17010
+ justifyContent: "center"
17043
17011
  },
17044
17012
  ProviderButtonText: {
17045
17013
  flex: 1,
@@ -17104,14 +17072,7 @@
17104
17072
  color: "#374151",
17105
17073
  transition: "all 0.2s ease",
17106
17074
  gap: "12px"
17107
- },
17108
- // Cancel button for provider selection
17109
- CancelButtonRow: {
17110
- display: "flex",
17111
- justifyContent: "center",
17112
- marginTop: "16px"
17113
- }
17114
- };
17075
+ }};
17115
17076
 
17116
17077
  function Dialog({ children, className }) {
17117
17078
  return (_$1("div", { className: `dexie-dialog ${className || ''}` },
@@ -17140,19 +17101,126 @@
17140
17101
  return message.replace(/\{\w+\}/ig, n => messageParams[n.substring(1, n.length - 1)]);
17141
17102
  }
17142
17103
 
17104
+ /** Get style based on styleHint (for provider branding, etc.) */
17105
+ function getOptionStyle(styleHint) {
17106
+ const baseStyle = Object.assign({}, Styles.ProviderButton);
17107
+ if (!styleHint) {
17108
+ return baseStyle;
17109
+ }
17110
+ switch (styleHint) {
17111
+ case 'google':
17112
+ return Object.assign(Object.assign({}, baseStyle), Styles.ProviderGoogle);
17113
+ case 'github':
17114
+ return Object.assign(Object.assign({}, baseStyle), Styles.ProviderGitHub);
17115
+ case 'microsoft':
17116
+ return Object.assign(Object.assign({}, baseStyle), Styles.ProviderMicrosoft);
17117
+ case 'apple':
17118
+ return Object.assign(Object.assign({}, baseStyle), Styles.ProviderApple);
17119
+ case 'otp':
17120
+ return Object.assign({}, Styles.OtpButton);
17121
+ case 'custom-oauth2':
17122
+ return Object.assign(Object.assign({}, baseStyle), Styles.ProviderCustom);
17123
+ default:
17124
+ return baseStyle;
17125
+ }
17126
+ }
17127
+ /**
17128
+ * Generic button component for selectable options.
17129
+ * Displays the option's icon and display name.
17130
+ *
17131
+ * The icon can be:
17132
+ * - Inline SVG (iconSvg) - rendered directly with dangerouslySetInnerHTML
17133
+ * - Image URL (iconUrl) - rendered as an img tag
17134
+ *
17135
+ * Style is determined by the styleHint property for branding purposes.
17136
+ */
17137
+ function OptionButton({ option, onClick }) {
17138
+ const { displayName, iconUrl, iconSvg, styleHint, value } = option;
17139
+ const style = getOptionStyle(styleHint);
17140
+ // Get the text color from the button style for SVG fill processing
17141
+ const textColor = style.color || '#000000';
17142
+ // Process SVG to replace currentColor with actual text color
17143
+ const processedSvg = iconSvg
17144
+ ? iconSvg
17145
+ .replace(/fill="currentColor"/gi, `fill="${textColor}"`)
17146
+ .replace(/fill='currentColor'/gi, `fill='${textColor}'`)
17147
+ .replace(/stroke="currentColor"/gi, `stroke="${textColor}"`)
17148
+ .replace(/stroke='currentColor'/gi, `stroke='${textColor}'`)
17149
+ : null;
17150
+ // Render the appropriate icon
17151
+ const renderIcon = () => {
17152
+ // Inline SVG
17153
+ if (processedSvg) {
17154
+ return (_$1("span", { style: Styles.ProviderButtonIcon, "aria-hidden": "true", dangerouslySetInnerHTML: { __html: processedSvg } }));
17155
+ }
17156
+ // Image URL
17157
+ if (iconUrl) {
17158
+ return (_$1("img", { src: iconUrl, alt: "", style: Styles.ProviderButtonIcon, "aria-hidden": "true" }));
17159
+ }
17160
+ return null;
17161
+ };
17162
+ return (_$1("button", { type: "button", style: style, onClick: onClick, class: `dxc-option-btn${styleHint ? ` dxc-option-${styleHint}` : ''}`, "aria-label": displayName },
17163
+ renderIcon(),
17164
+ _$1("span", { style: Styles.ProviderButtonText }, displayName)));
17165
+ }
17166
+ /**
17167
+ * Visual divider with "or" text.
17168
+ */
17169
+ function Divider() {
17170
+ return (_$1("div", { style: Styles.Divider },
17171
+ _$1("div", { style: Styles.DividerLine }),
17172
+ _$1("span", { style: Styles.DividerText }, "or"),
17173
+ _$1("div", { style: Styles.DividerLine })));
17174
+ }
17175
+
17143
17176
  const OTP_LENGTH = 8;
17144
- function LoginDialog({ title, type, alerts, fields, submitLabel, cancelLabel, onCancel, onSubmit, }) {
17177
+ /**
17178
+ * Generic dialog that can render:
17179
+ * - Form fields (text inputs)
17180
+ * - Selectable options (buttons)
17181
+ * - Or both together
17182
+ *
17183
+ * When an option is clicked, calls onSubmit({ [option.name]: option.value }).
17184
+ * This unified approach means the same callback handles both form submission
17185
+ * and option selection.
17186
+ */
17187
+ function LoginDialog({ title, alerts, fields, options, submitLabel, cancelLabel, onCancel, onSubmit, }) {
17145
17188
  const [params, setParams] = d({});
17146
17189
  const firstFieldRef = A(null);
17147
17190
  _(() => { var _a; return (_a = firstFieldRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, []);
17191
+ const fieldEntries = Object.entries(fields || {});
17192
+ const hasFields = fieldEntries.length > 0;
17193
+ const hasOptions = options && options.length > 0;
17194
+ // Group options by name to detect if we have multiple groups
17195
+ const optionGroups = new Map();
17196
+ if (options) {
17197
+ for (const option of options) {
17198
+ const group = optionGroups.get(option.name) || [];
17199
+ group.push(option);
17200
+ optionGroups.set(option.name, group);
17201
+ }
17202
+ }
17203
+ const hasMultipleGroups = optionGroups.size > 1;
17204
+ // Handler for option clicks - calls onSubmit with { [option.name]: option.value }
17205
+ const handleOptionClick = (option) => {
17206
+ onSubmit({ [option.name]: option.value });
17207
+ };
17148
17208
  return (_$1(Dialog, { className: "dxc-login-dlg" },
17149
17209
  _$1(k$1, null,
17150
17210
  _$1("h3", { style: Styles.WindowHeader }, title),
17151
- alerts.map((alert) => (_$1("p", { style: Styles.Alert[alert.type] }, resolveText(alert)))),
17152
- _$1("form", { onSubmit: (ev) => {
17211
+ alerts.map((alert, idx) => (_$1("p", { key: idx, style: Styles.Alert[alert.type] }, resolveText(alert)))),
17212
+ hasOptions && (_$1("div", { class: "dxc-options" }, hasMultipleGroups ? (
17213
+ // Render with dividers between groups
17214
+ Array.from(optionGroups.entries()).map(([groupName, groupOptions], groupIdx) => (_$1(k$1, { key: groupName },
17215
+ groupIdx > 0 && _$1(Divider, null),
17216
+ groupOptions.map((option) => (_$1(OptionButton, { key: `${option.name}-${option.value}`, option: option, onClick: () => handleOptionClick(option) }))))))) : (
17217
+ // Simple case: all options in one group
17218
+ options.map((option) => (_$1(OptionButton, { key: `${option.name}-${option.value}`, option: option, onClick: () => handleOptionClick(option) })))))),
17219
+ hasOptions && hasFields && _$1(Divider, null),
17220
+ hasFields && (_$1("form", { onSubmit: (ev) => {
17153
17221
  ev.preventDefault();
17154
17222
  onSubmit(params);
17155
- } }, Object.entries(fields).map(([fieldName, { type, label, placeholder }], idx) => (_$1("label", { style: Styles.Label, key: idx },
17223
+ } }, fieldEntries.map(([fieldName, { type, label, placeholder }], idx) => (_$1("label", { style: Styles.Label, key: idx },
17156
17224
  label ? `${label}: ` : '',
17157
17225
  _$1("input", { ref: idx === 0 ? firstFieldRef : undefined, type: type, name: fieldName, autoComplete: "on", style: Styles.Input, autoFocus: true, placeholder: placeholder, value: params[fieldName] || '', onInput: (ev) => {
17158
17226
  var _a;
@@ -17163,10 +17231,10 @@
17163
17231
  // Auto-submit when OTP is filled in.
17164
17232
  onSubmit(updatedParams);
17165
17233
  }
17166
- } })))))),
17234
+ } }))))))),
17167
17235
  _$1("div", { style: Styles.ButtonsDiv },
17168
17236
  _$1(k$1, null,
17169
- _$1("button", { type: "submit", style: Styles.PrimaryButton, onClick: () => onSubmit(params) }, submitLabel),
17237
+ hasFields && submitLabel && (_$1("button", { type: "submit", style: Styles.PrimaryButton, onClick: () => onSubmit(params) }, submitLabel)),
17170
17238
  cancelLabel && (_$1("button", { style: Styles.Button, onClick: onCancel }, cancelLabel))))));
17171
17239
  }
17172
17240
  function valueTransformer(type, value) {
@@ -17180,82 +17248,6 @@
17180
17248
  }
17181
17249
  }
17182
17250
 
17183
- /** Default SVG icons for built-in providers */
17184
- const ProviderIcons = {
17185
- google: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>`,
17186
- github: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>`,
17187
- microsoft: `<svg viewBox="0 0 24 24" width="20" height="20"><rect fill="#F25022" x="1" y="1" width="10" height="10"/><rect fill="#00A4EF" x="1" y="13" width="10" height="10"/><rect fill="#7FBA00" x="13" y="1" width="10" height="10"/><rect fill="#FFB900" x="13" y="13" width="10" height="10"/></svg>`,
17188
- apple: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>`,
17189
- };
17190
- /** Get provider-specific button styles */
17191
- function getProviderStyle(providerType) {
17192
- const baseStyle = Object.assign({}, Styles.ProviderButton);
17193
- switch (providerType) {
17194
- case 'google':
17195
- return Object.assign(Object.assign({}, baseStyle), Styles.ProviderGoogle);
17196
- case 'github':
17197
- return Object.assign(Object.assign({}, baseStyle), Styles.ProviderGitHub);
17198
- case 'microsoft':
17199
- return Object.assign(Object.assign({}, baseStyle), Styles.ProviderMicrosoft);
17200
- case 'apple':
17201
- return Object.assign(Object.assign({}, baseStyle), Styles.ProviderApple);
17202
- default:
17203
- return Object.assign(Object.assign({}, baseStyle), Styles.ProviderCustom);
17204
- }
17205
- }
17206
- /**
17207
- * Button component for OAuth provider login.
17208
- * Displays the provider's icon and name following provider branding guidelines.
17209
- */
17210
- function AuthProviderButton({ provider, onClick }) {
17211
- const { type, name, displayName, iconUrl } = provider;
17212
- const style = getProviderStyle(type);
17213
- // Determine button text
17214
- const buttonText = `Continue with ${displayName}`;
17215
- // Get icon - use custom iconUrl if provided, otherwise use built-in SVG
17216
- const iconSvg = ProviderIcons[type] || '';
17217
- return (_$1("button", { type: "button", style: style, onClick: onClick, class: `dxc-provider-btn dxc-provider-${type}`, "aria-label": buttonText },
17218
- iconUrl ? (_$1("img", { src: iconUrl, alt: "", style: Styles.ProviderButtonIcon, "aria-hidden": "true" })) : iconSvg ? (_$1("span", { style: Styles.ProviderButtonIcon, "aria-hidden": "true", dangerouslySetInnerHTML: { __html: iconSvg } })) : null,
17219
- _$1("span", { style: Styles.ProviderButtonText }, buttonText)));
17220
- }
17221
- /** Email/envelope icon for OTP button */
17222
- const EmailIcon = `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M22 6L12 13 2 6"/></svg>`;
17223
- /**
17224
- * Button for email/OTP authentication option.
17225
- */
17226
- function OtpButton({ onClick }) {
17227
- return (_$1("button", { type: "button", style: Styles.OtpButton, onClick: onClick, class: "dxc-otp-btn", "aria-label": "Continue with email" },
17228
- _$1("span", { style: Styles.ProviderButtonIcon, "aria-hidden": "true", dangerouslySetInnerHTML: { __html: EmailIcon } }),
17229
- _$1("span", { style: Styles.ProviderButtonText }, "Continue with email")));
17230
- }
17231
- /**
17232
- * Visual divider with "or" text.
17233
- */
17234
- function Divider() {
17235
- return (_$1("div", { style: Styles.Divider },
17236
- _$1("div", { style: Styles.DividerLine }),
17237
- _$1("span", { style: Styles.DividerText }, "or"),
17238
- _$1("div", { style: Styles.DividerLine })));
17239
- }
17240
-
17241
- /**
17242
- * Dialog component for OAuth provider selection.
17243
- * Displays available OAuth providers as buttons and optionally an email/OTP option.
17244
- */
17245
- function ProviderSelectionDialog({ title, alerts, providers, otpEnabled, cancelLabel, onSelectProvider, onSelectOtp, onCancel, }) {
17246
- return (_$1(Dialog, { className: "dxc-provider-selection-dlg" },
17247
- _$1(k$1, null,
17248
- _$1("h3", { style: Styles.WindowHeader }, title),
17249
- alerts.map((alert, idx) => (_$1("p", { key: idx, style: Styles.Alert[alert.type] }, resolveText(alert)))),
17250
- _$1("div", { class: "dxc-providers" }, providers.map((provider) => (_$1(AuthProviderButton, { key: provider.name, provider: provider, onClick: () => onSelectProvider(provider.name) })))),
17251
- otpEnabled && providers.length > 0 && (_$1(k$1, null,
17252
- _$1(Divider, null),
17253
- _$1(OtpButton, { onClick: onSelectOtp }))),
17254
- otpEnabled && providers.length === 0 && (_$1(OtpButton, { onClick: onSelectOtp })),
17255
- cancelLabel && (_$1("div", { style: Styles.CancelButtonRow },
17256
- _$1("button", { type: "button", style: Styles.Button, onClick: onCancel }, cancelLabel))))));
17257
- }
17258
-
17259
17251
  class LoginGui extends x {
17260
17252
  constructor(props) {
17261
17253
  super(props);
@@ -17274,11 +17266,8 @@
17274
17266
  render(props, { userInteraction }) {
17275
17267
  if (!userInteraction)
17276
17268
  return null;
17277
- // Render appropriate dialog based on interaction type
17278
- if (userInteraction.type === 'provider-selection') {
17279
- return _$1(ProviderSelectionDialog, Object.assign({}, userInteraction));
17280
- }
17281
- // Default to LoginDialog for other interaction types
17269
+ // LoginDialog handles all interaction types uniformly
17270
+ // (forms with fields, options, or both)
17282
17271
  return _$1(LoginDialog, Object.assign({}, userInteraction));
17283
17272
  }
17284
17273
  }
@@ -17837,6 +17826,83 @@
17837
17826
  return awareness;
17838
17827
  }
17839
17828
 
17829
+ /**
17830
+ * Decodes a base64url-encoded string to a regular string.
17831
+ * Base64url uses - instead of + and _ instead of /, and may omit padding.
17832
+ */
17833
+ function decodeBase64Url(encoded) {
17834
+ // Add padding if needed
17835
+ const padded = encoded + '='.repeat((4 - (encoded.length % 4)) % 4);
17836
+ // Convert base64url to base64
17837
+ const base64 = padded.replace(/-/g, '+').replace(/_/g, '/');
17838
+ return atob(base64);
17839
+ }
17840
+ /**
17841
+ * Parses OAuth callback parameters from the dxc-auth query parameter.
17842
+ *
17843
+ * The dxc-auth parameter contains base64url-encoded JSON with the following structure:
17844
+ * - On success: { "code": "...", "provider": "...", "state": "..." }
17845
+ * - On error: { "error": "...", "provider": "...", "state": "..." }
17846
+ *
17847
+ * @param url - The URL to parse (defaults to window.location.href)
17848
+ * @returns OAuthCallbackParams if valid callback, null otherwise
17849
+ * @throws OAuthError if there's an error in the callback
17850
+ */
17851
+ function parseOAuthCallback(url) {
17852
+ const targetUrl = (typeof window !== 'undefined' ? window.location.href : '');
17853
+ if (!targetUrl) {
17854
+ return null;
17855
+ }
17856
+ const parsed = new URL(targetUrl);
17857
+ const encoded = parsed.searchParams.get('dxc-auth');
17858
+ if (!encoded) {
17859
+ return null; // Not an OAuth callback URL
17860
+ }
17861
+ let payload;
17862
+ try {
17863
+ const json = decodeBase64Url(encoded);
17864
+ payload = JSON.parse(json);
17865
+ }
17866
+ catch (e) {
17867
+ console.warn('[dexie-cloud] Failed to parse dxc-auth parameter:', e);
17868
+ return null;
17869
+ }
17870
+ const { code, provider, state, error } = payload;
17871
+ // Check for error first
17872
+ if (error) {
17873
+ if (error.toLowerCase().includes('access_denied') || error.toLowerCase().includes('access denied')) {
17874
+ throw new OAuthError('access_denied', provider, error);
17875
+ }
17876
+ if (error.toLowerCase().includes('email') && error.toLowerCase().includes('verif')) {
17877
+ throw new OAuthError('email_not_verified', provider, error);
17878
+ }
17879
+ throw new OAuthError('provider_error', provider, error);
17880
+ }
17881
+ // Validate required fields for success case
17882
+ if (!code || !provider || !state) {
17883
+ console.warn('[dexie-cloud] Invalid dxc-auth payload: missing required fields');
17884
+ return null;
17885
+ }
17886
+ return { code, provider, state };
17887
+ }
17888
+ /**
17889
+ * Cleans up the dxc-auth query parameter from the URL.
17890
+ * Call this after successfully handling the callback to clean up the browser URL.
17891
+ */
17892
+ function cleanupOAuthUrl() {
17893
+ var _a;
17894
+ if (typeof window === 'undefined' || !((_a = window.history) === null || _a === void 0 ? void 0 : _a.replaceState)) {
17895
+ return;
17896
+ }
17897
+ const url = new URL(window.location.href);
17898
+ if (!url.searchParams.has('dxc-auth')) {
17899
+ return;
17900
+ }
17901
+ url.searchParams.delete('dxc-auth');
17902
+ const cleanUrl = url.pathname + (url.searchParams.toString() ? `?${url.searchParams.toString()}` : '') + url.hash;
17903
+ window.history.replaceState(null, '', cleanUrl);
17904
+ }
17905
+
17840
17906
  function getTiedRealmId(objectId) {
17841
17907
  return 'rlm~' + objectId;
17842
17908
  }
@@ -18019,6 +18085,8 @@
18019
18085
  const currentUserEmitter = getCurrentUserEmitter(dexie);
18020
18086
  const subscriptions = [];
18021
18087
  let configuredProgramatically = false;
18088
+ // Pending OAuth auth code from dxc-auth redirect (detected in configure())
18089
+ let pendingOAuthCode = null;
18022
18090
  // local sync worker - used when there's no service worker.
18023
18091
  let localSyncWorker = null;
18024
18092
  dexie.on('ready', (dexie) => __awaiter(this, void 0, void 0, function* () {
@@ -18048,7 +18116,7 @@
18048
18116
  const syncComplete = new rxjs.Subject();
18049
18117
  dexie.cloud = {
18050
18118
  // @ts-ignore
18051
- version: "4.3.0",
18119
+ version: "4.3.2",
18052
18120
  options: Object.assign({}, DEFAULT_OPTIONS),
18053
18121
  schema: null,
18054
18122
  get currentUserId() {
@@ -18083,6 +18151,26 @@
18083
18151
  DexieCloudDB(dexie).reconfigure(); // Update observable from new dexie.name
18084
18152
  }
18085
18153
  updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
18154
+ // Check for OAuth callback (dxc-auth query parameter)
18155
+ // Only check in DOM environment, not workers
18156
+ if (typeof window !== 'undefined' && window.location) {
18157
+ try {
18158
+ const callback = parseOAuthCallback();
18159
+ if (callback) {
18160
+ // Clean up URL immediately (remove dxc-auth param)
18161
+ cleanupOAuthUrl();
18162
+ // Store the pending auth code for processing when db is ready
18163
+ pendingOAuthCode = { code: callback.code, provider: callback.provider };
18164
+ console.debug('[dexie-cloud] OAuth callback detected, auth code stored for processing');
18165
+ }
18166
+ }
18167
+ catch (error) {
18168
+ // parseOAuthCallback throws OAuthError on error callbacks
18169
+ // Store null for code but log the error
18170
+ console.warn('[dexie-cloud] OAuth callback error:', error);
18171
+ cleanupOAuthUrl();
18172
+ }
18173
+ }
18086
18174
  },
18087
18175
  logout() {
18088
18176
  return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
@@ -18277,6 +18365,19 @@
18277
18365
  // HERE: If requireAuth, do athentication now.
18278
18366
  let changedUser = false;
18279
18367
  const user = yield db.getCurrentUser();
18368
+ // Process pending OAuth callback if present (from dxc-auth redirect)
18369
+ if (pendingOAuthCode && !db.cloud.isServiceWorkerDB) {
18370
+ const { code, provider } = pendingOAuthCode;
18371
+ pendingOAuthCode = null; // Clear pending code
18372
+ console.debug('[dexie-cloud] Processing OAuth callback, provider:', provider);
18373
+ try {
18374
+ changedUser = yield login(db, { oauthCode: code, provider });
18375
+ }
18376
+ catch (error) {
18377
+ console.error('[dexie-cloud] OAuth login failed:', error);
18378
+ // Continue with normal flow - user can try again
18379
+ }
18380
+ }
18280
18381
  const requireAuth = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.requireAuth;
18281
18382
  if (requireAuth) {
18282
18383
  if (db.cloud.isServiceWorkerDB) {
@@ -18365,7 +18466,7 @@
18365
18466
  }
18366
18467
  }
18367
18468
  // @ts-ignore
18368
- dexieCloud.version = "4.3.0";
18469
+ dexieCloud.version = "4.3.2";
18369
18470
  Dexie.Cloud = dexieCloud;
18370
18471
 
18371
18472
  exports.default = dexieCloud;