dexie-cloud-addon 4.2.5 → 4.3.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.
Files changed (168) hide show
  1. package/TODO-SOCIALAUTH.md +545 -0
  2. package/dist/modern/DexieCloudAPI.d.ts +4 -0
  3. package/dist/modern/DexieCloudOptions.d.ts +20 -0
  4. package/dist/modern/authentication/exchangeOAuthCode.d.ts +23 -0
  5. package/dist/modern/authentication/fetchAuthProviders.d.ts +14 -0
  6. package/dist/modern/authentication/handleOAuthCallback.d.ts +57 -0
  7. package/dist/modern/authentication/interactWithUser.d.ts +19 -0
  8. package/dist/modern/authentication/oauthLogin.d.ts +37 -0
  9. package/dist/modern/default-ui/AuthProviderButton.d.ts +21 -0
  10. package/dist/modern/default-ui/LoginDialog.d.ts +5 -2
  11. package/dist/modern/default-ui/ProviderSelectionDialog.d.ts +7 -0
  12. package/dist/modern/dexie-cloud-addon.js +577 -5
  13. package/dist/modern/dexie-cloud-addon.js.map +1 -1
  14. package/dist/modern/dexie-cloud-addon.min.js +1 -1
  15. package/dist/modern/dexie-cloud-addon.min.js.gz +0 -0
  16. package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
  17. package/dist/modern/errors/OAuthError.d.ts +10 -0
  18. package/dist/modern/service-worker.js +577 -5
  19. package/dist/modern/service-worker.js.map +1 -1
  20. package/dist/modern/service-worker.min.js +1 -1
  21. package/dist/modern/service-worker.min.js.map +1 -1
  22. package/dist/modern/types/DXCUserInteraction.d.ts +24 -1
  23. package/dist/umd/DISABLE_SERVICEWORKER_STRATEGY.d.ts +1 -0
  24. package/dist/umd/DXCWebSocketStatus.d.ts +1 -0
  25. package/dist/umd/DexieCloudAPI.d.ts +75 -0
  26. package/dist/umd/DexieCloudOptions.d.ts +27 -0
  27. package/dist/umd/DexieCloudSyncOptions.d.ts +4 -0
  28. package/dist/umd/DexieCloudTable.d.ts +18 -0
  29. package/dist/umd/InvalidLicenseError.d.ts +5 -0
  30. package/dist/umd/Invite.d.ts +8 -0
  31. package/dist/umd/PermissionChecker.d.ts +15 -0
  32. package/dist/umd/TSON.d.ts +17 -0
  33. package/dist/umd/WSObservable.d.ts +72 -0
  34. package/dist/umd/associate.d.ts +1 -0
  35. package/dist/umd/authentication/AuthPersistedContext.d.ts +9 -0
  36. package/dist/umd/authentication/TokenErrorResponseError.d.ts +10 -0
  37. package/dist/umd/authentication/TokenExpiredError.d.ts +3 -0
  38. package/dist/umd/authentication/UNAUTHORIZED_USER.d.ts +2 -0
  39. package/dist/umd/authentication/authenticate.d.ts +13 -0
  40. package/dist/umd/authentication/currentUserObservable.d.ts +1 -0
  41. package/dist/umd/authentication/interactWithUser.d.ts +21 -0
  42. package/dist/umd/authentication/login.d.ts +3 -0
  43. package/dist/umd/authentication/logout.d.ts +5 -0
  44. package/dist/umd/authentication/otpFetchTokenCallback.d.ts +3 -0
  45. package/dist/umd/authentication/setCurrentUser.d.ts +14 -0
  46. package/dist/umd/authentication/waitUntil.d.ts +3 -0
  47. package/dist/umd/computeSyncState.d.ts +4 -0
  48. package/dist/umd/createSharedValueObservable.d.ts +3 -0
  49. package/dist/umd/currentUserEmitter.d.ts +3 -0
  50. package/dist/umd/db/DexieCloudDB.d.ts +61 -0
  51. package/dist/umd/db/entities/BaseRevisionMapEntry.d.ts +5 -0
  52. package/dist/umd/db/entities/EntityCommon.d.ts +5 -0
  53. package/dist/umd/db/entities/GuardedJob.d.ts +5 -0
  54. package/dist/umd/db/entities/Member.d.ts +19 -0
  55. package/dist/umd/db/entities/PersistedSyncState.d.ts +22 -0
  56. package/dist/umd/db/entities/Realm.d.ts +14 -0
  57. package/dist/umd/db/entities/Role.d.ts +11 -0
  58. package/dist/umd/db/entities/UserLogin.d.ts +23 -0
  59. package/dist/umd/default-ui/Dialog.d.ts +5 -0
  60. package/dist/umd/default-ui/LoginDialog.d.ts +3 -0
  61. package/dist/umd/default-ui/Styles.d.ts +3 -0
  62. package/dist/umd/default-ui/index.d.ts +24 -0
  63. package/dist/umd/define-ydoc-trigger.d.ts +3 -0
  64. package/dist/umd/dexie-cloud-addon.d.ts +3 -0
  65. package/dist/umd/dexie-cloud-addon.js +578 -6
  66. package/dist/umd/dexie-cloud-addon.js.gz +0 -0
  67. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  68. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  69. package/dist/umd/dexie-cloud-addon.min.js.gz +0 -0
  70. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  71. package/dist/umd/dexie-cloud-client.d.ts +23 -0
  72. package/dist/umd/errors/HttpError.d.ts +5 -0
  73. package/dist/umd/extend-dexie-interface.d.ts +23 -0
  74. package/dist/umd/getGlobalRolesObservable.d.ts +5 -0
  75. package/dist/umd/getInternalAccessControlObservable.d.ts +12 -0
  76. package/dist/umd/getInvitesObservable.d.ts +23 -0
  77. package/dist/umd/getPermissionsLookupObservable.d.ts +16 -0
  78. package/dist/umd/getTiedRealmId.d.ts +2 -0
  79. package/dist/umd/helpers/BroadcastedAndLocalEvent.d.ts +8 -0
  80. package/dist/umd/helpers/CancelToken.d.ts +4 -0
  81. package/dist/umd/helpers/IS_SERVICE_WORKER.d.ts +1 -0
  82. package/dist/umd/helpers/SWBroadcastChannel.d.ts +12 -0
  83. package/dist/umd/helpers/allSettled.d.ts +1 -0
  84. package/dist/umd/helpers/bulkUpdate.d.ts +4 -0
  85. package/dist/umd/helpers/computeRealmSetHash.d.ts +2 -0
  86. package/dist/umd/helpers/date-constants.d.ts +5 -0
  87. package/dist/umd/helpers/flatten.d.ts +1 -0
  88. package/dist/umd/helpers/getMutationTable.d.ts +1 -0
  89. package/dist/umd/helpers/getSyncableTables.d.ts +4 -0
  90. package/dist/umd/helpers/getTableFromMutationTable.d.ts +1 -0
  91. package/dist/umd/helpers/makeArray.d.ts +1 -0
  92. package/dist/umd/helpers/randomString.d.ts +1 -0
  93. package/dist/umd/helpers/resolveText.d.ts +16 -0
  94. package/dist/umd/helpers/throwVersionIncrementNeeded.d.ts +1 -0
  95. package/dist/umd/helpers/visibilityState.d.ts +1 -0
  96. package/dist/umd/isEagerSyncDisabled.d.ts +2 -0
  97. package/dist/umd/isFirefox.d.ts +1 -0
  98. package/dist/umd/isSafari.d.ts +2 -0
  99. package/dist/umd/mapValueObservable.d.ts +5 -0
  100. package/dist/umd/mergePermissions.d.ts +2 -0
  101. package/dist/umd/middleware-helpers/guardedTable.d.ts +11 -0
  102. package/dist/umd/middleware-helpers/idGenerationHelpers.d.ts +18 -0
  103. package/dist/umd/middlewares/createIdGenerationMiddleware.d.ts +3 -0
  104. package/dist/umd/middlewares/createImplicitPropSetterMiddleware.d.ts +3 -0
  105. package/dist/umd/middlewares/createMutationTrackingMiddleware.d.ts +17 -0
  106. package/dist/umd/middlewares/outstandingTransaction.d.ts +4 -0
  107. package/dist/umd/overrideParseStoresSpec.d.ts +4 -0
  108. package/dist/umd/performInitialSync.d.ts +4 -0
  109. package/dist/umd/permissions.d.ts +9 -0
  110. package/dist/umd/prodLog.d.ts +9 -0
  111. package/dist/umd/service-worker.d.ts +1 -0
  112. package/dist/umd/service-worker.js +578 -6
  113. package/dist/umd/service-worker.js.map +1 -1
  114. package/dist/umd/service-worker.min.js +1 -1
  115. package/dist/umd/service-worker.min.js.map +1 -1
  116. package/dist/umd/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
  117. package/dist/umd/sync/LocalSyncWorker.d.ts +7 -0
  118. package/dist/umd/sync/SyncRequiredError.d.ts +3 -0
  119. package/dist/umd/sync/applyServerChanges.d.ts +3 -0
  120. package/dist/umd/sync/connectWebSocket.d.ts +2 -0
  121. package/dist/umd/sync/encodeIdsForServer.d.ts +4 -0
  122. package/dist/umd/sync/extractRealm.d.ts +2 -0
  123. package/dist/umd/sync/getLatestRevisionsPerTable.d.ts +6 -0
  124. package/dist/umd/sync/getTablesToSyncify.d.ts +3 -0
  125. package/dist/umd/sync/isOnline.d.ts +1 -0
  126. package/dist/umd/sync/isSyncNeeded.d.ts +2 -0
  127. package/dist/umd/sync/listClientChanges.d.ts +9 -0
  128. package/dist/umd/sync/listSyncifiedChanges.d.ts +5 -0
  129. package/dist/umd/sync/messageConsumerIsReady.d.ts +2 -0
  130. package/dist/umd/sync/messagesFromServerQueue.d.ts +8 -0
  131. package/dist/umd/sync/modifyLocalObjectsWithNewUserId.d.ts +4 -0
  132. package/dist/umd/sync/myId.d.ts +1 -0
  133. package/dist/umd/sync/numUnsyncedMutations.d.ts +2 -0
  134. package/dist/umd/sync/old_startSyncingClientChanges.d.ts +39 -0
  135. package/dist/umd/sync/performGuardedJob.d.ts +2 -0
  136. package/dist/umd/sync/ratelimit.d.ts +3 -0
  137. package/dist/umd/sync/registerSyncEvent.d.ts +3 -0
  138. package/dist/umd/sync/sync.d.ts +15 -0
  139. package/dist/umd/sync/syncIfPossible.d.ts +5 -0
  140. package/dist/umd/sync/syncWithServer.d.ts +6 -0
  141. package/dist/umd/sync/triggerSync.d.ts +2 -0
  142. package/dist/umd/sync/updateBaseRevs.d.ts +5 -0
  143. package/dist/umd/types/DXCAlert.d.ts +25 -0
  144. package/dist/umd/types/DXCInputField.d.ts +11 -0
  145. package/dist/umd/types/DXCUserInteraction.d.ts +93 -0
  146. package/dist/umd/types/NewIdOptions.d.ts +3 -0
  147. package/dist/umd/types/SWMessageEvent.d.ts +3 -0
  148. package/dist/umd/types/SWSyncEvent.d.ts +4 -0
  149. package/dist/umd/types/SyncState.d.ts +9 -0
  150. package/dist/umd/types/TXExpandos.d.ts +11 -0
  151. package/dist/umd/updateSchemaFromOptions.d.ts +3 -0
  152. package/dist/umd/userIsActive.d.ts +7 -0
  153. package/dist/umd/verifyConfig.d.ts +2 -0
  154. package/dist/umd/verifySchema.d.ts +2 -0
  155. package/dist/umd/yjs/YDexieCloudSyncState.d.ts +3 -0
  156. package/dist/umd/yjs/YTable.d.ts +3 -0
  157. package/dist/umd/yjs/applyYMessages.d.ts +9 -0
  158. package/dist/umd/yjs/awareness.d.ts +3 -0
  159. package/dist/umd/yjs/createYClientUpdateObservable.d.ts +4 -0
  160. package/dist/umd/yjs/createYHandler.d.ts +2 -0
  161. package/dist/umd/yjs/downloadYDocsFromServer.d.ts +3 -0
  162. package/dist/umd/yjs/getUpdatesTable.d.ts +3 -0
  163. package/dist/umd/yjs/listUpdatesSince.d.ts +3 -0
  164. package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
  165. package/dist/umd/yjs/reopenDocSignal.d.ts +10 -0
  166. package/dist/umd/yjs/updateYSyncStates.d.ts +6 -0
  167. package/oauth_flow.md +299 -0
  168. package/package.json +3 -3
@@ -0,0 +1,3 @@
1
+ import dexieCloudAddon from './dexie-cloud-client';
2
+ export * from './dexie-cloud-client';
3
+ export default dexieCloudAddon;
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.2.5, Sat Dec 20 2025
11
+ * Version 4.3.0, Tue Jan 20 2026
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -103,6 +103,15 @@
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
+
106
115
  function assert(b) {
107
116
  if (!b)
108
117
  throw new Error('Assertion Failed');
@@ -2195,6 +2204,43 @@
2195
2204
  .catch(() => false);
2196
2205
  });
2197
2206
  }
2207
+ /**
2208
+ * Prompts the user to select an authentication method (OAuth provider or OTP).
2209
+ *
2210
+ * @param userInteraction - The user interaction BehaviorSubject
2211
+ * @param providers - Available OAuth providers
2212
+ * @param otpEnabled - Whether OTP is available
2213
+ * @param title - Dialog title
2214
+ * @param alerts - Optional alerts to display
2215
+ * @returns Promise resolving to the user's selection
2216
+ */
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);
2242
+ });
2243
+ }
2198
2244
 
2199
2245
  function loadAccessToken(db) {
2200
2246
  return __awaiter(this, void 0, void 0, function* () {
@@ -2518,15 +2564,313 @@
2518
2564
  }
2519
2565
  }
2520
2566
 
2567
+ /** User-friendly messages for OAuth error codes */
2568
+ 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
+ access_denied: 'Access was denied by the authentication provider.',
2572
+ invalid_state: 'The authentication response could not be verified. Please try again.',
2573
+ email_not_verified: 'Your email address must be verified before you can log in.',
2574
+ expired_code: 'The authentication code has expired. Please try again.',
2575
+ provider_error: 'An error occurred with the authentication provider.',
2576
+ network_error: 'A network error occurred during authentication. Please check your connection and try again.',
2577
+ };
2578
+ /** Error class for OAuth-specific errors */
2579
+ class OAuthError extends Error {
2580
+ constructor(code, provider, customMessage) {
2581
+ super(customMessage || ERROR_MESSAGES[code]);
2582
+ this.name = 'OAuthError';
2583
+ this.code = code;
2584
+ this.provider = provider;
2585
+ }
2586
+ /** Get user-friendly message for this error */
2587
+ get userMessage() {
2588
+ return ERROR_MESSAGES[this.code] || this.message;
2589
+ }
2590
+ }
2591
+
2592
+ /**
2593
+ * Exchanges a Dexie Cloud authorization code for access and refresh tokens.
2594
+ *
2595
+ * This is called after the OAuth callback delivers the authorization code
2596
+ * via postMessage (popup flow) or redirect.
2597
+ *
2598
+ * @param options - Exchange options
2599
+ * @returns Promise resolving to TokenFinalResponse
2600
+ * @throws OAuthError or TokenErrorResponseError on failure
2601
+ */
2602
+ function exchangeOAuthCode(options) {
2603
+ return __awaiter(this, void 0, void 0, function* () {
2604
+ const { databaseUrl, code, publicKey, scopes = ['ACCESS_DB'] } = options;
2605
+ const tokenRequest = {
2606
+ grant_type: 'authorization_code',
2607
+ code,
2608
+ public_key: publicKey,
2609
+ scopes,
2610
+ };
2611
+ try {
2612
+ const res = yield fetch(`${databaseUrl}/token`, {
2613
+ method: 'POST',
2614
+ headers: { 'Content-Type': 'application/json' },
2615
+ body: JSON.stringify(tokenRequest),
2616
+ mode: 'cors',
2617
+ });
2618
+ if (!res.ok) {
2619
+ if (res.status === 400 || res.status === 401) {
2620
+ // Try to parse error response
2621
+ try {
2622
+ const errorResponse = yield res.json();
2623
+ if (errorResponse.type === 'error') {
2624
+ // Check for specific error codes
2625
+ if (errorResponse.messageCode === 'INVALID_OTP') {
2626
+ // In the context of OAuth, this likely means expired code
2627
+ throw new OAuthError('expired_code', undefined, errorResponse.message);
2628
+ }
2629
+ throw new TokenErrorResponseError(errorResponse);
2630
+ }
2631
+ }
2632
+ catch (e) {
2633
+ if (e instanceof OAuthError || e instanceof TokenErrorResponseError) {
2634
+ throw e;
2635
+ }
2636
+ // Fall through to generic error
2637
+ }
2638
+ }
2639
+ const errorText = yield res.text().catch(() => res.statusText);
2640
+ throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${errorText}`);
2641
+ }
2642
+ const response = yield res.json();
2643
+ if (response.type === 'error') {
2644
+ throw new TokenErrorResponseError(response);
2645
+ }
2646
+ if (response.type !== 'tokens') {
2647
+ throw new OAuthError('provider_error', undefined, `Unexpected response type: ${response.type}`);
2648
+ }
2649
+ return response;
2650
+ }
2651
+ catch (error) {
2652
+ if (error instanceof OAuthError || error instanceof TokenErrorResponseError) {
2653
+ throw error;
2654
+ }
2655
+ if (error instanceof TypeError) {
2656
+ // Network error
2657
+ throw new OAuthError('network_error');
2658
+ }
2659
+ throw error;
2660
+ }
2661
+ });
2662
+ }
2663
+
2664
+ /** Default response when OAuth is disabled or unavailable */
2665
+ const OTP_ONLY_RESPONSE = {
2666
+ providers: [],
2667
+ otpEnabled: true,
2668
+ };
2669
+ /**
2670
+ * Fetches available authentication providers from the Dexie Cloud server.
2671
+ *
2672
+ * @param databaseUrl - The Dexie Cloud database URL
2673
+ * @param socialAuthEnabled - Whether social auth is enabled in client config (default: true)
2674
+ * @returns Promise resolving to AuthProvidersResponse
2675
+ *
2676
+ * Handles failures gracefully:
2677
+ * - 404 → Returns OTP-only (old server version)
2678
+ * - Network error → Returns OTP-only
2679
+ * - socialAuthEnabled: false → Returns OTP-only without fetching
2680
+ */
2681
+ function fetchAuthProviders(databaseUrl_1) {
2682
+ return __awaiter(this, arguments, void 0, function* (databaseUrl, socialAuthEnabled = true) {
2683
+ // If social auth is disabled, return OTP-only without fetching
2684
+ if (!socialAuthEnabled) {
2685
+ return OTP_ONLY_RESPONSE;
2686
+ }
2687
+ try {
2688
+ const res = yield fetch(`${databaseUrl}/auth-providers`, {
2689
+ method: 'GET',
2690
+ headers: { 'Accept': 'application/json' },
2691
+ mode: 'cors',
2692
+ });
2693
+ if (res.status === 404) {
2694
+ // Old server version without OAuth support
2695
+ console.debug('[dexie-cloud] Server does not support /auth-providers endpoint. Using OTP-only authentication.');
2696
+ return OTP_ONLY_RESPONSE;
2697
+ }
2698
+ if (!res.ok) {
2699
+ console.warn(`[dexie-cloud] Failed to fetch auth providers: ${res.status} ${res.statusText}`);
2700
+ return OTP_ONLY_RESPONSE;
2701
+ }
2702
+ return yield res.json();
2703
+ }
2704
+ catch (error) {
2705
+ // Network error or other failure - fall back to OTP
2706
+ console.debug('[dexie-cloud] Could not fetch auth providers, falling back to OTP:', error);
2707
+ return OTP_ONLY_RESPONSE;
2708
+ }
2709
+ });
2710
+ }
2711
+
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
+ /** Build the OAuth login URL */
2719
+ function buildOAuthLoginUrl(options, state) {
2720
+ 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
2723
+ const redirectUri = options.redirectUri ||
2724
+ (typeof window !== 'undefined' ? window.location.origin : '');
2725
+ if (redirectUri) {
2726
+ url.searchParams.set('redirect_uri', redirectUri);
2727
+ }
2728
+ return url.toString();
2729
+ }
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
+ /**
2742
+ * Initiates OAuth login flow using a popup window.
2743
+ *
2744
+ * Opens a popup to the OAuth provider, listens for postMessage with the result,
2745
+ * and returns the Dexie Cloud authorization code.
2746
+ *
2747
+ * @param options - OAuth login options
2748
+ * @returns Promise resolving to OAuthLoginResult
2749
+ * @throws OAuthError on failure
2750
+ */
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';
2839
+ }
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';
2850
+ }
2851
+
2521
2852
  function otpFetchTokenCallback(db) {
2522
2853
  const { userInteraction } = db.cloud;
2523
2854
  return function otpAuthenticate(_a) {
2524
2855
  return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
2525
- var _b;
2856
+ var _b, _c;
2526
2857
  let tokenRequest;
2527
2858
  const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
2528
2859
  if (!url)
2529
2860
  throw new Error(`No database URL given.`);
2861
+ // Handle OAuth code exchange (from redirect/deep link flows)
2862
+ if ((hints === null || hints === void 0 ? void 0 : hints.oauthCode) && hints.provider) {
2863
+ return yield exchangeOAuthCode({
2864
+ databaseUrl: url,
2865
+ code: hints.oauthCode,
2866
+ publicKey: public_key,
2867
+ scopes: ['ACCESS_DB'],
2868
+ });
2869
+ }
2870
+ // Handle OAuth provider login (popup flow)
2871
+ if (hints === null || hints === void 0 ? void 0 : hints.provider) {
2872
+ return yield handleOAuthFlow(db, public_key, hints.provider);
2873
+ }
2530
2874
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
2531
2875
  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));
2532
2876
  tokenRequest = {
@@ -2550,6 +2894,18 @@
2550
2894
  };
2551
2895
  }
2552
2896
  else {
2897
+ // Check for available auth providers (OAuth + OTP)
2898
+ const socialAuthEnabled = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.socialAuth) !== false;
2899
+ const authProviders = yield fetchAuthProviders(url, socialAuthEnabled);
2900
+ // If we have OAuth providers available, prompt for selection
2901
+ if (authProviders.providers.length > 0) {
2902
+ const selection = yield promptForProvider(userInteraction, authProviders.providers, authProviders.otpEnabled, 'Sign in');
2903
+ if (selection.type === 'provider') {
2904
+ // User selected an OAuth provider
2905
+ return yield handleOAuthFlow(db, public_key, selection.provider);
2906
+ }
2907
+ // User chose OTP - continue with email prompt below
2908
+ }
2553
2909
  const email = yield promptForEmail(userInteraction, 'Enter email address', hints === null || hints === void 0 ? void 0 : hints.email);
2554
2910
  if (/@demo.local$/.test(email)) {
2555
2911
  tokenRequest = {
@@ -2627,6 +2983,49 @@
2627
2983
  });
2628
2984
  };
2629
2985
  }
2986
+ /**
2987
+ * Handles the OAuth popup flow and token exchange.
2988
+ */
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
+ }
3027
+ });
3028
+ }
2630
3029
 
2631
3030
  /** A way to log to console in production without terser stripping out
2632
3031
  * it from the release bundle.
@@ -13417,7 +13816,7 @@
13417
13816
  *
13418
13817
  * ==========================================================================
13419
13818
  *
13420
- * Version 4.2.2, Sat Dec 20 2025
13819
+ * Version 4.2.2, Tue Jan 20 2026
13421
13820
  *
13422
13821
  * https://dexie.org
13423
13822
  *
@@ -16618,6 +17017,99 @@
16618
17017
  color: "#333",
16619
17018
  borderBottom: "1px solid #eee",
16620
17019
  paddingBottom: "10px"
17020
+ },
17021
+ // OAuth Provider Button Styles
17022
+ ProviderButton: {
17023
+ display: "flex",
17024
+ alignItems: "center",
17025
+ justifyContent: "center",
17026
+ width: "100%",
17027
+ padding: "12px 16px",
17028
+ marginBottom: "10px",
17029
+ border: "1px solid #d1d5db",
17030
+ borderRadius: "6px",
17031
+ backgroundColor: "#ffffff",
17032
+ cursor: "pointer",
17033
+ fontSize: "14px",
17034
+ fontWeight: "500",
17035
+ color: "#374151",
17036
+ transition: "all 0.2s ease",
17037
+ gap: "12px"
17038
+ },
17039
+ ProviderButtonIcon: {
17040
+ width: "20px",
17041
+ height: "20px",
17042
+ flexShrink: 0
17043
+ },
17044
+ ProviderButtonText: {
17045
+ flex: 1,
17046
+ textAlign: "left"
17047
+ },
17048
+ // Provider-specific colors
17049
+ ProviderGoogle: {
17050
+ backgroundColor: "#ffffff",
17051
+ border: "1px solid #dadce0",
17052
+ color: "#3c4043"
17053
+ },
17054
+ ProviderGitHub: {
17055
+ backgroundColor: "#24292e",
17056
+ border: "1px solid #24292e",
17057
+ color: "#ffffff"
17058
+ },
17059
+ ProviderMicrosoft: {
17060
+ backgroundColor: "#ffffff",
17061
+ border: "1px solid #8c8c8c",
17062
+ color: "#5e5e5e"
17063
+ },
17064
+ ProviderApple: {
17065
+ backgroundColor: "#000000",
17066
+ border: "1px solid #000000",
17067
+ color: "#ffffff"
17068
+ },
17069
+ ProviderCustom: {
17070
+ backgroundColor: "#4f46e5",
17071
+ border: "1px solid #4f46e5",
17072
+ color: "#ffffff"
17073
+ },
17074
+ // Divider styles
17075
+ Divider: {
17076
+ display: "flex",
17077
+ alignItems: "center",
17078
+ margin: "20px 0",
17079
+ color: "#6b7280",
17080
+ fontSize: "13px"
17081
+ },
17082
+ DividerLine: {
17083
+ flex: 1,
17084
+ height: "1px",
17085
+ backgroundColor: "#e5e7eb"
17086
+ },
17087
+ DividerText: {
17088
+ padding: "0 12px",
17089
+ color: "#9ca3af"
17090
+ },
17091
+ // OTP Button (Continue with email)
17092
+ OtpButton: {
17093
+ display: "flex",
17094
+ alignItems: "center",
17095
+ justifyContent: "center",
17096
+ width: "100%",
17097
+ padding: "12px 16px",
17098
+ border: "1px solid #d1d5db",
17099
+ borderRadius: "6px",
17100
+ backgroundColor: "#f9fafb",
17101
+ cursor: "pointer",
17102
+ fontSize: "14px",
17103
+ fontWeight: "500",
17104
+ color: "#374151",
17105
+ transition: "all 0.2s ease",
17106
+ gap: "12px"
17107
+ },
17108
+ // Cancel button for provider selection
17109
+ CancelButtonRow: {
17110
+ display: "flex",
17111
+ justifyContent: "center",
17112
+ marginTop: "16px"
16621
17113
  }
16622
17114
  };
16623
17115
 
@@ -16688,6 +17180,82 @@
16688
17180
  }
16689
17181
  }
16690
17182
 
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
+
16691
17259
  class LoginGui extends x {
16692
17260
  constructor(props) {
16693
17261
  super(props);
@@ -16706,7 +17274,11 @@
16706
17274
  render(props, { userInteraction }) {
16707
17275
  if (!userInteraction)
16708
17276
  return null;
16709
- //if (props.db.cloud.userInteraction.observers.length > 1) return null; // Someone else subscribes.
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
16710
17282
  return _$1(LoginDialog, Object.assign({}, userInteraction));
16711
17283
  }
16712
17284
  }
@@ -17476,7 +18048,7 @@
17476
18048
  const syncComplete = new rxjs.Subject();
17477
18049
  dexie.cloud = {
17478
18050
  // @ts-ignore
17479
- version: "4.2.5",
18051
+ version: "4.3.0",
17480
18052
  options: Object.assign({}, DEFAULT_OPTIONS),
17481
18053
  schema: null,
17482
18054
  get currentUserId() {
@@ -17793,7 +18365,7 @@
17793
18365
  }
17794
18366
  }
17795
18367
  // @ts-ignore
17796
- dexieCloud.version = "4.2.5";
18368
+ dexieCloud.version = "4.3.0";
17797
18369
  Dexie.Cloud = dexieCloud;
17798
18370
 
17799
18371
  exports.default = dexieCloud;
Binary file