dexie-cloud-addon 4.2.5 → 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.
Files changed (170) hide show
  1. package/TODO-SOCIALAUTH.md +274 -0
  2. package/dist/modern/DexieCloudAPI.d.ts +4 -0
  3. package/dist/modern/DexieCloudOptions.d.ts +17 -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 +63 -0
  7. package/dist/modern/authentication/interactWithUser.d.ts +22 -0
  8. package/dist/modern/authentication/oauthLogin.d.ts +38 -0
  9. package/dist/modern/default-ui/AuthProviderButton.d.ts +21 -0
  10. package/dist/modern/default-ui/LoginDialog.d.ts +30 -2
  11. package/dist/modern/default-ui/OptionButton.d.ts +21 -0
  12. package/dist/modern/default-ui/ProviderSelectionDialog.d.ts +7 -0
  13. package/dist/modern/default-ui/SelectDialog.d.ts +10 -0
  14. package/dist/modern/dexie-cloud-addon.js +686 -13
  15. package/dist/modern/dexie-cloud-addon.js.map +1 -1
  16. package/dist/modern/dexie-cloud-addon.min.js +1 -1
  17. package/dist/modern/dexie-cloud-addon.min.js.gz +0 -0
  18. package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
  19. package/dist/modern/errors/OAuthError.d.ts +10 -0
  20. package/dist/modern/service-worker.js +686 -13
  21. package/dist/modern/service-worker.js.map +1 -1
  22. package/dist/modern/service-worker.min.js +1 -1
  23. package/dist/modern/service-worker.min.js.map +1 -1
  24. package/dist/modern/types/DXCUserInteraction.d.ts +32 -1
  25. package/dist/umd/DISABLE_SERVICEWORKER_STRATEGY.d.ts +1 -0
  26. package/dist/umd/DXCWebSocketStatus.d.ts +1 -0
  27. package/dist/umd/DexieCloudAPI.d.ts +75 -0
  28. package/dist/umd/DexieCloudOptions.d.ts +27 -0
  29. package/dist/umd/DexieCloudSyncOptions.d.ts +4 -0
  30. package/dist/umd/DexieCloudTable.d.ts +18 -0
  31. package/dist/umd/InvalidLicenseError.d.ts +5 -0
  32. package/dist/umd/Invite.d.ts +8 -0
  33. package/dist/umd/PermissionChecker.d.ts +15 -0
  34. package/dist/umd/TSON.d.ts +17 -0
  35. package/dist/umd/WSObservable.d.ts +72 -0
  36. package/dist/umd/associate.d.ts +1 -0
  37. package/dist/umd/authentication/AuthPersistedContext.d.ts +9 -0
  38. package/dist/umd/authentication/TokenErrorResponseError.d.ts +10 -0
  39. package/dist/umd/authentication/TokenExpiredError.d.ts +3 -0
  40. package/dist/umd/authentication/UNAUTHORIZED_USER.d.ts +2 -0
  41. package/dist/umd/authentication/authenticate.d.ts +13 -0
  42. package/dist/umd/authentication/currentUserObservable.d.ts +1 -0
  43. package/dist/umd/authentication/interactWithUser.d.ts +21 -0
  44. package/dist/umd/authentication/login.d.ts +3 -0
  45. package/dist/umd/authentication/logout.d.ts +5 -0
  46. package/dist/umd/authentication/otpFetchTokenCallback.d.ts +3 -0
  47. package/dist/umd/authentication/setCurrentUser.d.ts +14 -0
  48. package/dist/umd/authentication/waitUntil.d.ts +3 -0
  49. package/dist/umd/computeSyncState.d.ts +4 -0
  50. package/dist/umd/createSharedValueObservable.d.ts +3 -0
  51. package/dist/umd/currentUserEmitter.d.ts +3 -0
  52. package/dist/umd/db/DexieCloudDB.d.ts +61 -0
  53. package/dist/umd/db/entities/BaseRevisionMapEntry.d.ts +5 -0
  54. package/dist/umd/db/entities/EntityCommon.d.ts +5 -0
  55. package/dist/umd/db/entities/GuardedJob.d.ts +5 -0
  56. package/dist/umd/db/entities/Member.d.ts +19 -0
  57. package/dist/umd/db/entities/PersistedSyncState.d.ts +22 -0
  58. package/dist/umd/db/entities/Realm.d.ts +14 -0
  59. package/dist/umd/db/entities/Role.d.ts +11 -0
  60. package/dist/umd/db/entities/UserLogin.d.ts +23 -0
  61. package/dist/umd/default-ui/Dialog.d.ts +5 -0
  62. package/dist/umd/default-ui/LoginDialog.d.ts +3 -0
  63. package/dist/umd/default-ui/Styles.d.ts +3 -0
  64. package/dist/umd/default-ui/index.d.ts +24 -0
  65. package/dist/umd/define-ydoc-trigger.d.ts +3 -0
  66. package/dist/umd/dexie-cloud-addon.d.ts +3 -0
  67. package/dist/umd/dexie-cloud-addon.js +687 -14
  68. package/dist/umd/dexie-cloud-addon.js.gz +0 -0
  69. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  70. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  71. package/dist/umd/dexie-cloud-addon.min.js.gz +0 -0
  72. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  73. package/dist/umd/dexie-cloud-client.d.ts +23 -0
  74. package/dist/umd/errors/HttpError.d.ts +5 -0
  75. package/dist/umd/extend-dexie-interface.d.ts +23 -0
  76. package/dist/umd/getGlobalRolesObservable.d.ts +5 -0
  77. package/dist/umd/getInternalAccessControlObservable.d.ts +12 -0
  78. package/dist/umd/getInvitesObservable.d.ts +23 -0
  79. package/dist/umd/getPermissionsLookupObservable.d.ts +16 -0
  80. package/dist/umd/getTiedRealmId.d.ts +2 -0
  81. package/dist/umd/helpers/BroadcastedAndLocalEvent.d.ts +8 -0
  82. package/dist/umd/helpers/CancelToken.d.ts +4 -0
  83. package/dist/umd/helpers/IS_SERVICE_WORKER.d.ts +1 -0
  84. package/dist/umd/helpers/SWBroadcastChannel.d.ts +12 -0
  85. package/dist/umd/helpers/allSettled.d.ts +1 -0
  86. package/dist/umd/helpers/bulkUpdate.d.ts +4 -0
  87. package/dist/umd/helpers/computeRealmSetHash.d.ts +2 -0
  88. package/dist/umd/helpers/date-constants.d.ts +5 -0
  89. package/dist/umd/helpers/flatten.d.ts +1 -0
  90. package/dist/umd/helpers/getMutationTable.d.ts +1 -0
  91. package/dist/umd/helpers/getSyncableTables.d.ts +4 -0
  92. package/dist/umd/helpers/getTableFromMutationTable.d.ts +1 -0
  93. package/dist/umd/helpers/makeArray.d.ts +1 -0
  94. package/dist/umd/helpers/randomString.d.ts +1 -0
  95. package/dist/umd/helpers/resolveText.d.ts +16 -0
  96. package/dist/umd/helpers/throwVersionIncrementNeeded.d.ts +1 -0
  97. package/dist/umd/helpers/visibilityState.d.ts +1 -0
  98. package/dist/umd/isEagerSyncDisabled.d.ts +2 -0
  99. package/dist/umd/isFirefox.d.ts +1 -0
  100. package/dist/umd/isSafari.d.ts +2 -0
  101. package/dist/umd/mapValueObservable.d.ts +5 -0
  102. package/dist/umd/mergePermissions.d.ts +2 -0
  103. package/dist/umd/middleware-helpers/guardedTable.d.ts +11 -0
  104. package/dist/umd/middleware-helpers/idGenerationHelpers.d.ts +18 -0
  105. package/dist/umd/middlewares/createIdGenerationMiddleware.d.ts +3 -0
  106. package/dist/umd/middlewares/createImplicitPropSetterMiddleware.d.ts +3 -0
  107. package/dist/umd/middlewares/createMutationTrackingMiddleware.d.ts +17 -0
  108. package/dist/umd/middlewares/outstandingTransaction.d.ts +4 -0
  109. package/dist/umd/overrideParseStoresSpec.d.ts +4 -0
  110. package/dist/umd/performInitialSync.d.ts +4 -0
  111. package/dist/umd/permissions.d.ts +9 -0
  112. package/dist/umd/prodLog.d.ts +9 -0
  113. package/dist/umd/service-worker.d.ts +1 -0
  114. package/dist/umd/service-worker.js +687 -14
  115. package/dist/umd/service-worker.js.map +1 -1
  116. package/dist/umd/service-worker.min.js +1 -1
  117. package/dist/umd/service-worker.min.js.map +1 -1
  118. package/dist/umd/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
  119. package/dist/umd/sync/LocalSyncWorker.d.ts +7 -0
  120. package/dist/umd/sync/SyncRequiredError.d.ts +3 -0
  121. package/dist/umd/sync/applyServerChanges.d.ts +3 -0
  122. package/dist/umd/sync/connectWebSocket.d.ts +2 -0
  123. package/dist/umd/sync/encodeIdsForServer.d.ts +4 -0
  124. package/dist/umd/sync/extractRealm.d.ts +2 -0
  125. package/dist/umd/sync/getLatestRevisionsPerTable.d.ts +6 -0
  126. package/dist/umd/sync/getTablesToSyncify.d.ts +3 -0
  127. package/dist/umd/sync/isOnline.d.ts +1 -0
  128. package/dist/umd/sync/isSyncNeeded.d.ts +2 -0
  129. package/dist/umd/sync/listClientChanges.d.ts +9 -0
  130. package/dist/umd/sync/listSyncifiedChanges.d.ts +5 -0
  131. package/dist/umd/sync/messageConsumerIsReady.d.ts +2 -0
  132. package/dist/umd/sync/messagesFromServerQueue.d.ts +8 -0
  133. package/dist/umd/sync/modifyLocalObjectsWithNewUserId.d.ts +4 -0
  134. package/dist/umd/sync/myId.d.ts +1 -0
  135. package/dist/umd/sync/numUnsyncedMutations.d.ts +2 -0
  136. package/dist/umd/sync/old_startSyncingClientChanges.d.ts +39 -0
  137. package/dist/umd/sync/performGuardedJob.d.ts +2 -0
  138. package/dist/umd/sync/ratelimit.d.ts +3 -0
  139. package/dist/umd/sync/registerSyncEvent.d.ts +3 -0
  140. package/dist/umd/sync/sync.d.ts +15 -0
  141. package/dist/umd/sync/syncIfPossible.d.ts +5 -0
  142. package/dist/umd/sync/syncWithServer.d.ts +6 -0
  143. package/dist/umd/sync/triggerSync.d.ts +2 -0
  144. package/dist/umd/sync/updateBaseRevs.d.ts +5 -0
  145. package/dist/umd/types/DXCAlert.d.ts +25 -0
  146. package/dist/umd/types/DXCInputField.d.ts +11 -0
  147. package/dist/umd/types/DXCUserInteraction.d.ts +93 -0
  148. package/dist/umd/types/NewIdOptions.d.ts +3 -0
  149. package/dist/umd/types/SWMessageEvent.d.ts +3 -0
  150. package/dist/umd/types/SWSyncEvent.d.ts +4 -0
  151. package/dist/umd/types/SyncState.d.ts +9 -0
  152. package/dist/umd/types/TXExpandos.d.ts +11 -0
  153. package/dist/umd/updateSchemaFromOptions.d.ts +3 -0
  154. package/dist/umd/userIsActive.d.ts +7 -0
  155. package/dist/umd/verifyConfig.d.ts +2 -0
  156. package/dist/umd/verifySchema.d.ts +2 -0
  157. package/dist/umd/yjs/YDexieCloudSyncState.d.ts +3 -0
  158. package/dist/umd/yjs/YTable.d.ts +3 -0
  159. package/dist/umd/yjs/applyYMessages.d.ts +9 -0
  160. package/dist/umd/yjs/awareness.d.ts +3 -0
  161. package/dist/umd/yjs/createYClientUpdateObservable.d.ts +4 -0
  162. package/dist/umd/yjs/createYHandler.d.ts +2 -0
  163. package/dist/umd/yjs/downloadYDocsFromServer.d.ts +3 -0
  164. package/dist/umd/yjs/getUpdatesTable.d.ts +3 -0
  165. package/dist/umd/yjs/listUpdatesSince.d.ts +3 -0
  166. package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
  167. package/dist/umd/yjs/reopenDocSignal.d.ts +10 -0
  168. package/dist/umd/yjs/updateYSyncStates.d.ts +6 -0
  169. package/oauth_flow.md +307 -0
  170. package/package.json +1 -1
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.2.5, Sat Dec 20 2025
11
+ * Version 4.3.2, Thu Jan 22 2026
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -2061,6 +2061,74 @@
2061
2061
  }
2062
2062
  }
2063
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
+ }
2064
2132
  function interactWithUser(userInteraction, req) {
2065
2133
  return new Promise((resolve, reject) => {
2066
2134
  const interactionProps = Object.assign(Object.assign({ submitLabel: 'Submit', cancelLabel: 'Cancel' }, req), { onSubmit: (res) => {
@@ -2195,6 +2263,67 @@
2195
2263
  .catch(() => false);
2196
2264
  });
2197
2265
  }
2266
+ /**
2267
+ * Prompts the user to select an authentication method (OAuth provider or OTP).
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
+ *
2272
+ * @param userInteraction - The user interaction BehaviorSubject
2273
+ * @param providers - Available OAuth providers
2274
+ * @param otpEnabled - Whether OTP is available
2275
+ * @param title - Dialog title
2276
+ * @param alerts - Optional alerts to display
2277
+ * @returns Promise resolving to the user's selection
2278
+ */
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
+ });
2325
+ });
2326
+ }
2198
2327
 
2199
2328
  function loadAccessToken(db) {
2200
2329
  return __awaiter(this, void 0, void 0, function* () {
@@ -2518,15 +2647,215 @@
2518
2647
  }
2519
2648
  }
2520
2649
 
2650
+ /** User-friendly messages for OAuth error codes */
2651
+ const ERROR_MESSAGES = {
2652
+ access_denied: 'Access was denied by the authentication provider.',
2653
+ invalid_state: 'The authentication response could not be verified. Please try again.',
2654
+ email_not_verified: 'Your email address must be verified before you can log in.',
2655
+ expired_code: 'The authentication code has expired. Please try again.',
2656
+ provider_error: 'An error occurred with the authentication provider.',
2657
+ network_error: 'A network error occurred during authentication. Please check your connection and try again.',
2658
+ };
2659
+ /** Error class for OAuth-specific errors */
2660
+ class OAuthError extends Error {
2661
+ constructor(code, provider, customMessage) {
2662
+ super(customMessage || ERROR_MESSAGES[code]);
2663
+ this.name = 'OAuthError';
2664
+ this.code = code;
2665
+ this.provider = provider;
2666
+ }
2667
+ /** Get user-friendly message for this error */
2668
+ get userMessage() {
2669
+ return ERROR_MESSAGES[this.code] || this.message;
2670
+ }
2671
+ }
2672
+
2673
+ /**
2674
+ * Exchanges a Dexie Cloud authorization code for access and refresh tokens.
2675
+ *
2676
+ * This is called after the OAuth callback delivers the authorization code
2677
+ * via postMessage (popup flow) or redirect.
2678
+ *
2679
+ * @param options - Exchange options
2680
+ * @returns Promise resolving to TokenFinalResponse
2681
+ * @throws OAuthError or TokenErrorResponseError on failure
2682
+ */
2683
+ function exchangeOAuthCode(options) {
2684
+ return __awaiter(this, void 0, void 0, function* () {
2685
+ const { databaseUrl, code, publicKey, scopes = ['ACCESS_DB'] } = options;
2686
+ const tokenRequest = {
2687
+ grant_type: 'authorization_code',
2688
+ code,
2689
+ public_key: publicKey,
2690
+ scopes,
2691
+ };
2692
+ try {
2693
+ const res = yield fetch(`${databaseUrl}/token`, {
2694
+ method: 'POST',
2695
+ headers: { 'Content-Type': 'application/json' },
2696
+ body: JSON.stringify(tokenRequest),
2697
+ mode: 'cors',
2698
+ });
2699
+ if (!res.ok) {
2700
+ if (res.status === 400 || res.status === 401) {
2701
+ // Try to parse error response
2702
+ try {
2703
+ const errorResponse = yield res.json();
2704
+ if (errorResponse.type === 'error') {
2705
+ // Check for specific error codes
2706
+ if (errorResponse.messageCode === 'INVALID_OTP') {
2707
+ // In the context of OAuth, this likely means expired code
2708
+ throw new OAuthError('expired_code', undefined, errorResponse.message);
2709
+ }
2710
+ throw new TokenErrorResponseError(errorResponse);
2711
+ }
2712
+ }
2713
+ catch (e) {
2714
+ if (e instanceof OAuthError || e instanceof TokenErrorResponseError) {
2715
+ throw e;
2716
+ }
2717
+ // Fall through to generic error
2718
+ }
2719
+ }
2720
+ const errorText = yield res.text().catch(() => res.statusText);
2721
+ throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${errorText}`);
2722
+ }
2723
+ const response = yield res.json();
2724
+ if (response.type === 'error') {
2725
+ throw new TokenErrorResponseError(response);
2726
+ }
2727
+ if (response.type !== 'tokens') {
2728
+ throw new OAuthError('provider_error', undefined, `Unexpected response type: ${response.type}`);
2729
+ }
2730
+ return response;
2731
+ }
2732
+ catch (error) {
2733
+ if (error instanceof OAuthError || error instanceof TokenErrorResponseError) {
2734
+ throw error;
2735
+ }
2736
+ if (error instanceof TypeError) {
2737
+ // Network error
2738
+ throw new OAuthError('network_error');
2739
+ }
2740
+ throw error;
2741
+ }
2742
+ });
2743
+ }
2744
+
2745
+ /** Default response when OAuth is disabled or unavailable */
2746
+ const OTP_ONLY_RESPONSE = {
2747
+ providers: [],
2748
+ otpEnabled: true,
2749
+ };
2750
+ /**
2751
+ * Fetches available authentication providers from the Dexie Cloud server.
2752
+ *
2753
+ * @param databaseUrl - The Dexie Cloud database URL
2754
+ * @param socialAuthEnabled - Whether social auth is enabled in client config (default: true)
2755
+ * @returns Promise resolving to AuthProvidersResponse
2756
+ *
2757
+ * Handles failures gracefully:
2758
+ * - 404 → Returns OTP-only (old server version)
2759
+ * - Network error → Returns OTP-only
2760
+ * - socialAuthEnabled: false → Returns OTP-only without fetching
2761
+ */
2762
+ function fetchAuthProviders(databaseUrl_1) {
2763
+ return __awaiter(this, arguments, void 0, function* (databaseUrl, socialAuthEnabled = true) {
2764
+ // If social auth is disabled, return OTP-only without fetching
2765
+ if (!socialAuthEnabled) {
2766
+ return OTP_ONLY_RESPONSE;
2767
+ }
2768
+ try {
2769
+ const res = yield fetch(`${databaseUrl}/auth-providers`, {
2770
+ method: 'GET',
2771
+ headers: { 'Accept': 'application/json' },
2772
+ mode: 'cors',
2773
+ });
2774
+ if (res.status === 404) {
2775
+ // Old server version without OAuth support
2776
+ console.debug('[dexie-cloud] Server does not support /auth-providers endpoint. Using OTP-only authentication.');
2777
+ return OTP_ONLY_RESPONSE;
2778
+ }
2779
+ if (!res.ok) {
2780
+ console.warn(`[dexie-cloud] Failed to fetch auth providers: ${res.status} ${res.statusText}`);
2781
+ return OTP_ONLY_RESPONSE;
2782
+ }
2783
+ return yield res.json();
2784
+ }
2785
+ catch (error) {
2786
+ // Network error or other failure - fall back to OTP
2787
+ console.debug('[dexie-cloud] Could not fetch auth providers, falling back to OTP:', error);
2788
+ return OTP_ONLY_RESPONSE;
2789
+ }
2790
+ });
2791
+ }
2792
+
2793
+ /** Build the OAuth login URL */
2794
+ function buildOAuthLoginUrl(options) {
2795
+ const url = new URL(`${options.databaseUrl}/oauth/login/${options.provider}`);
2796
+ // Set the redirect URI - defaults to current page URL for web SPAs
2797
+ const redirectUri = options.redirectUri ||
2798
+ (typeof window !== 'undefined' ? window.location.href : '');
2799
+ if (redirectUri) {
2800
+ url.searchParams.set('redirect_uri', redirectUri);
2801
+ }
2802
+ return url.toString();
2803
+ }
2804
+ /**
2805
+ * Initiates OAuth login via full page redirect.
2806
+ *
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.
2810
+ *
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
+ * ```
2825
+ */
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);
2830
+ }
2831
+ const loginUrl = buildOAuthLoginUrl(options);
2832
+ window.location.href = loginUrl;
2833
+ }
2834
+
2521
2835
  function otpFetchTokenCallback(db) {
2522
2836
  const { userInteraction } = db.cloud;
2523
2837
  return function otpAuthenticate(_a) {
2524
2838
  return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
2525
- var _b;
2839
+ var _b, _c;
2526
2840
  let tokenRequest;
2527
2841
  const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
2528
2842
  if (!url)
2529
2843
  throw new Error(`No database URL given.`);
2844
+ // Handle OAuth code exchange (from redirect/deep link flows)
2845
+ if ((hints === null || hints === void 0 ? void 0 : hints.oauthCode) && hints.provider) {
2846
+ return yield exchangeOAuthCode({
2847
+ databaseUrl: url,
2848
+ code: hints.oauthCode,
2849
+ publicKey: public_key,
2850
+ scopes: ['ACCESS_DB'],
2851
+ });
2852
+ }
2853
+ // Handle OAuth provider login via redirect
2854
+ if (hints === null || hints === void 0 ? void 0 : hints.provider) {
2855
+ initiateOAuthRedirect(db, hints.provider);
2856
+ // This function never returns - page navigates away
2857
+ throw new Error('OAuth redirect initiated');
2858
+ }
2530
2859
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
2531
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));
2532
2861
  tokenRequest = {
@@ -2550,6 +2879,20 @@
2550
2879
  };
2551
2880
  }
2552
2881
  else {
2882
+ // Check for available auth providers (OAuth + OTP)
2883
+ const socialAuthEnabled = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.socialAuth) !== false;
2884
+ const authProviders = yield fetchAuthProviders(url, socialAuthEnabled);
2885
+ // If we have OAuth providers available, prompt for selection
2886
+ if (authProviders.providers.length > 0) {
2887
+ const selection = yield promptForProvider(userInteraction, authProviders.providers, authProviders.otpEnabled, 'Sign in');
2888
+ if (selection.type === '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');
2893
+ }
2894
+ // User chose OTP - continue with email prompt below
2895
+ }
2553
2896
  const email = yield promptForEmail(userInteraction, 'Enter email address', hints === null || hints === void 0 ? void 0 : hints.email);
2554
2897
  if (/@demo.local$/.test(email)) {
2555
2898
  tokenRequest = {
@@ -2627,6 +2970,27 @@
2627
2970
  });
2628
2971
  };
2629
2972
  }
2973
+ /**
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().
2979
+ */
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,
2992
+ });
2993
+ }
2630
2994
 
2631
2995
  /** A way to log to console in production without terser stripping out
2632
2996
  * it from the release bundle.
@@ -13417,7 +13781,7 @@
13417
13781
  *
13418
13782
  * ==========================================================================
13419
13783
  *
13420
- * Version 4.2.2, Sat Dec 20 2025
13784
+ * Version 4.2.2, Tue Jan 20 2026
13421
13785
  *
13422
13786
  * https://dexie.org
13423
13787
  *
@@ -16618,8 +16982,97 @@
16618
16982
  color: "#333",
16619
16983
  borderBottom: "1px solid #eee",
16620
16984
  paddingBottom: "10px"
16621
- }
16622
- };
16985
+ },
16986
+ // OAuth Provider Button Styles
16987
+ ProviderButton: {
16988
+ display: "flex",
16989
+ alignItems: "center",
16990
+ justifyContent: "center",
16991
+ width: "100%",
16992
+ padding: "12px 16px",
16993
+ marginBottom: "10px",
16994
+ border: "1px solid #d1d5db",
16995
+ borderRadius: "6px",
16996
+ backgroundColor: "#ffffff",
16997
+ cursor: "pointer",
16998
+ fontSize: "14px",
16999
+ fontWeight: "500",
17000
+ color: "#374151",
17001
+ transition: "all 0.2s ease",
17002
+ gap: "12px"
17003
+ },
17004
+ ProviderButtonIcon: {
17005
+ width: "20px",
17006
+ height: "20px",
17007
+ flexShrink: 0,
17008
+ display: "flex",
17009
+ alignItems: "center",
17010
+ justifyContent: "center"
17011
+ },
17012
+ ProviderButtonText: {
17013
+ flex: 1,
17014
+ textAlign: "left"
17015
+ },
17016
+ // Provider-specific colors
17017
+ ProviderGoogle: {
17018
+ backgroundColor: "#ffffff",
17019
+ border: "1px solid #dadce0",
17020
+ color: "#3c4043"
17021
+ },
17022
+ ProviderGitHub: {
17023
+ backgroundColor: "#24292e",
17024
+ border: "1px solid #24292e",
17025
+ color: "#ffffff"
17026
+ },
17027
+ ProviderMicrosoft: {
17028
+ backgroundColor: "#ffffff",
17029
+ border: "1px solid #8c8c8c",
17030
+ color: "#5e5e5e"
17031
+ },
17032
+ ProviderApple: {
17033
+ backgroundColor: "#000000",
17034
+ border: "1px solid #000000",
17035
+ color: "#ffffff"
17036
+ },
17037
+ ProviderCustom: {
17038
+ backgroundColor: "#4f46e5",
17039
+ border: "1px solid #4f46e5",
17040
+ color: "#ffffff"
17041
+ },
17042
+ // Divider styles
17043
+ Divider: {
17044
+ display: "flex",
17045
+ alignItems: "center",
17046
+ margin: "20px 0",
17047
+ color: "#6b7280",
17048
+ fontSize: "13px"
17049
+ },
17050
+ DividerLine: {
17051
+ flex: 1,
17052
+ height: "1px",
17053
+ backgroundColor: "#e5e7eb"
17054
+ },
17055
+ DividerText: {
17056
+ padding: "0 12px",
17057
+ color: "#9ca3af"
17058
+ },
17059
+ // OTP Button (Continue with email)
17060
+ OtpButton: {
17061
+ display: "flex",
17062
+ alignItems: "center",
17063
+ justifyContent: "center",
17064
+ width: "100%",
17065
+ padding: "12px 16px",
17066
+ border: "1px solid #d1d5db",
17067
+ borderRadius: "6px",
17068
+ backgroundColor: "#f9fafb",
17069
+ cursor: "pointer",
17070
+ fontSize: "14px",
17071
+ fontWeight: "500",
17072
+ color: "#374151",
17073
+ transition: "all 0.2s ease",
17074
+ gap: "12px"
17075
+ }};
16623
17076
 
16624
17077
  function Dialog({ children, className }) {
16625
17078
  return (_$1("div", { className: `dexie-dialog ${className || ''}` },
@@ -16648,19 +17101,126 @@
16648
17101
  return message.replace(/\{\w+\}/ig, n => messageParams[n.substring(1, n.length - 1)]);
16649
17102
  }
16650
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
+
16651
17176
  const OTP_LENGTH = 8;
16652
- 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, }) {
16653
17188
  const [params, setParams] = d({});
16654
17189
  const firstFieldRef = A(null);
16655
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
+ };
16656
17208
  return (_$1(Dialog, { className: "dxc-login-dlg" },
16657
17209
  _$1(k$1, null,
16658
17210
  _$1("h3", { style: Styles.WindowHeader }, title),
16659
- alerts.map((alert) => (_$1("p", { style: Styles.Alert[alert.type] }, resolveText(alert)))),
16660
- _$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) => {
16661
17221
  ev.preventDefault();
16662
17222
  onSubmit(params);
16663
- } }, 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 },
16664
17224
  label ? `${label}: ` : '',
16665
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) => {
16666
17226
  var _a;
@@ -16671,10 +17231,10 @@
16671
17231
  // Auto-submit when OTP is filled in.
16672
17232
  onSubmit(updatedParams);
16673
17233
  }
16674
- } })))))),
17234
+ } }))))))),
16675
17235
  _$1("div", { style: Styles.ButtonsDiv },
16676
17236
  _$1(k$1, null,
16677
- _$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)),
16678
17238
  cancelLabel && (_$1("button", { style: Styles.Button, onClick: onCancel }, cancelLabel))))));
16679
17239
  }
16680
17240
  function valueTransformer(type, value) {
@@ -16706,7 +17266,8 @@
16706
17266
  render(props, { userInteraction }) {
16707
17267
  if (!userInteraction)
16708
17268
  return null;
16709
- //if (props.db.cloud.userInteraction.observers.length > 1) return null; // Someone else subscribes.
17269
+ // LoginDialog handles all interaction types uniformly
17270
+ // (forms with fields, options, or both)
16710
17271
  return _$1(LoginDialog, Object.assign({}, userInteraction));
16711
17272
  }
16712
17273
  }
@@ -17265,6 +17826,83 @@
17265
17826
  return awareness;
17266
17827
  }
17267
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
+
17268
17906
  function getTiedRealmId(objectId) {
17269
17907
  return 'rlm~' + objectId;
17270
17908
  }
@@ -17447,6 +18085,8 @@
17447
18085
  const currentUserEmitter = getCurrentUserEmitter(dexie);
17448
18086
  const subscriptions = [];
17449
18087
  let configuredProgramatically = false;
18088
+ // Pending OAuth auth code from dxc-auth redirect (detected in configure())
18089
+ let pendingOAuthCode = null;
17450
18090
  // local sync worker - used when there's no service worker.
17451
18091
  let localSyncWorker = null;
17452
18092
  dexie.on('ready', (dexie) => __awaiter(this, void 0, void 0, function* () {
@@ -17476,7 +18116,7 @@
17476
18116
  const syncComplete = new rxjs.Subject();
17477
18117
  dexie.cloud = {
17478
18118
  // @ts-ignore
17479
- version: "4.2.5",
18119
+ version: "4.3.2",
17480
18120
  options: Object.assign({}, DEFAULT_OPTIONS),
17481
18121
  schema: null,
17482
18122
  get currentUserId() {
@@ -17511,6 +18151,26 @@
17511
18151
  DexieCloudDB(dexie).reconfigure(); // Update observable from new dexie.name
17512
18152
  }
17513
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
+ }
17514
18174
  },
17515
18175
  logout() {
17516
18176
  return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
@@ -17705,6 +18365,19 @@
17705
18365
  // HERE: If requireAuth, do athentication now.
17706
18366
  let changedUser = false;
17707
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
+ }
17708
18381
  const requireAuth = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.requireAuth;
17709
18382
  if (requireAuth) {
17710
18383
  if (db.cloud.isServiceWorkerDB) {
@@ -17793,7 +18466,7 @@
17793
18466
  }
17794
18467
  }
17795
18468
  // @ts-ignore
17796
- dexieCloud.version = "4.2.5";
18469
+ dexieCloud.version = "4.3.2";
17797
18470
  Dexie.Cloud = dexieCloud;
17798
18471
 
17799
18472
  exports.default = dexieCloud;