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
@@ -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
  *
@@ -542,6 +542,15 @@
542
542
  }
543
543
  }
544
544
 
545
+ /** Type guard to check if a message is an OAuthResultMessage */
546
+ function isOAuthResultMessage(msg) {
547
+ return (typeof msg === 'object' &&
548
+ msg !== null &&
549
+ msg.type === 'dexie:oauthResult' &&
550
+ typeof msg.provider === 'string' &&
551
+ typeof msg.state === 'string');
552
+ }
553
+
545
554
  function assert(b) {
546
555
  if (!b)
547
556
  throw new Error('Assertion Failed');
@@ -2579,6 +2588,43 @@
2579
2588
  .catch(() => false);
2580
2589
  });
2581
2590
  }
2591
+ /**
2592
+ * Prompts the user to select an authentication method (OAuth provider or OTP).
2593
+ *
2594
+ * @param userInteraction - The user interaction BehaviorSubject
2595
+ * @param providers - Available OAuth providers
2596
+ * @param otpEnabled - Whether OTP is available
2597
+ * @param title - Dialog title
2598
+ * @param alerts - Optional alerts to display
2599
+ * @returns Promise resolving to the user's selection
2600
+ */
2601
+ function promptForProvider(userInteraction, providers, otpEnabled, title = 'Choose login method', alerts = []) {
2602
+ return new Promise((resolve, reject) => {
2603
+ const interactionProps = {
2604
+ type: 'provider-selection',
2605
+ title,
2606
+ alerts,
2607
+ providers,
2608
+ otpEnabled,
2609
+ fields: {},
2610
+ submitLabel: undefined,
2611
+ cancelLabel: 'Cancel',
2612
+ onSelectProvider: (providerName) => {
2613
+ userInteraction.next(undefined);
2614
+ resolve({ type: 'provider', provider: providerName });
2615
+ },
2616
+ onSelectOtp: () => {
2617
+ userInteraction.next(undefined);
2618
+ resolve({ type: 'otp' });
2619
+ },
2620
+ onCancel: () => {
2621
+ userInteraction.next(undefined);
2622
+ reject(new Dexie.AbortError('User cancelled'));
2623
+ },
2624
+ };
2625
+ userInteraction.next(interactionProps);
2626
+ });
2627
+ }
2582
2628
 
2583
2629
  function loadAccessToken(db) {
2584
2630
  return __awaiter(this, void 0, void 0, function* () {
@@ -13113,7 +13159,7 @@
13113
13159
  *
13114
13160
  * ==========================================================================
13115
13161
  *
13116
- * Version 4.2.2, Sat Dec 20 2025
13162
+ * Version 4.2.2, Tue Jan 20 2026
13117
13163
  *
13118
13164
  * https://dexie.org
13119
13165
  *
@@ -14510,15 +14556,313 @@
14510
14556
  });
14511
14557
  }
14512
14558
 
14559
+ /** User-friendly messages for OAuth error codes */
14560
+ const ERROR_MESSAGES = {
14561
+ popup_blocked: 'The login popup was blocked by your browser. Please allow popups for this site and try again.',
14562
+ popup_closed: 'The login popup was closed before completing authentication.',
14563
+ access_denied: 'Access was denied by the authentication provider.',
14564
+ invalid_state: 'The authentication response could not be verified. Please try again.',
14565
+ email_not_verified: 'Your email address must be verified before you can log in.',
14566
+ expired_code: 'The authentication code has expired. Please try again.',
14567
+ provider_error: 'An error occurred with the authentication provider.',
14568
+ network_error: 'A network error occurred during authentication. Please check your connection and try again.',
14569
+ };
14570
+ /** Error class for OAuth-specific errors */
14571
+ class OAuthError extends Error {
14572
+ constructor(code, provider, customMessage) {
14573
+ super(customMessage || ERROR_MESSAGES[code]);
14574
+ this.name = 'OAuthError';
14575
+ this.code = code;
14576
+ this.provider = provider;
14577
+ }
14578
+ /** Get user-friendly message for this error */
14579
+ get userMessage() {
14580
+ return ERROR_MESSAGES[this.code] || this.message;
14581
+ }
14582
+ }
14583
+
14584
+ /**
14585
+ * Exchanges a Dexie Cloud authorization code for access and refresh tokens.
14586
+ *
14587
+ * This is called after the OAuth callback delivers the authorization code
14588
+ * via postMessage (popup flow) or redirect.
14589
+ *
14590
+ * @param options - Exchange options
14591
+ * @returns Promise resolving to TokenFinalResponse
14592
+ * @throws OAuthError or TokenErrorResponseError on failure
14593
+ */
14594
+ function exchangeOAuthCode(options) {
14595
+ return __awaiter(this, void 0, void 0, function* () {
14596
+ const { databaseUrl, code, publicKey, scopes = ['ACCESS_DB'] } = options;
14597
+ const tokenRequest = {
14598
+ grant_type: 'authorization_code',
14599
+ code,
14600
+ public_key: publicKey,
14601
+ scopes,
14602
+ };
14603
+ try {
14604
+ const res = yield fetch(`${databaseUrl}/token`, {
14605
+ method: 'POST',
14606
+ headers: { 'Content-Type': 'application/json' },
14607
+ body: JSON.stringify(tokenRequest),
14608
+ mode: 'cors',
14609
+ });
14610
+ if (!res.ok) {
14611
+ if (res.status === 400 || res.status === 401) {
14612
+ // Try to parse error response
14613
+ try {
14614
+ const errorResponse = yield res.json();
14615
+ if (errorResponse.type === 'error') {
14616
+ // Check for specific error codes
14617
+ if (errorResponse.messageCode === 'INVALID_OTP') {
14618
+ // In the context of OAuth, this likely means expired code
14619
+ throw new OAuthError('expired_code', undefined, errorResponse.message);
14620
+ }
14621
+ throw new TokenErrorResponseError(errorResponse);
14622
+ }
14623
+ }
14624
+ catch (e) {
14625
+ if (e instanceof OAuthError || e instanceof TokenErrorResponseError) {
14626
+ throw e;
14627
+ }
14628
+ // Fall through to generic error
14629
+ }
14630
+ }
14631
+ const errorText = yield res.text().catch(() => res.statusText);
14632
+ throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${errorText}`);
14633
+ }
14634
+ const response = yield res.json();
14635
+ if (response.type === 'error') {
14636
+ throw new TokenErrorResponseError(response);
14637
+ }
14638
+ if (response.type !== 'tokens') {
14639
+ throw new OAuthError('provider_error', undefined, `Unexpected response type: ${response.type}`);
14640
+ }
14641
+ return response;
14642
+ }
14643
+ catch (error) {
14644
+ if (error instanceof OAuthError || error instanceof TokenErrorResponseError) {
14645
+ throw error;
14646
+ }
14647
+ if (error instanceof TypeError) {
14648
+ // Network error
14649
+ throw new OAuthError('network_error');
14650
+ }
14651
+ throw error;
14652
+ }
14653
+ });
14654
+ }
14655
+
14656
+ /** Default response when OAuth is disabled or unavailable */
14657
+ const OTP_ONLY_RESPONSE = {
14658
+ providers: [],
14659
+ otpEnabled: true,
14660
+ };
14661
+ /**
14662
+ * Fetches available authentication providers from the Dexie Cloud server.
14663
+ *
14664
+ * @param databaseUrl - The Dexie Cloud database URL
14665
+ * @param socialAuthEnabled - Whether social auth is enabled in client config (default: true)
14666
+ * @returns Promise resolving to AuthProvidersResponse
14667
+ *
14668
+ * Handles failures gracefully:
14669
+ * - 404 → Returns OTP-only (old server version)
14670
+ * - Network error → Returns OTP-only
14671
+ * - socialAuthEnabled: false → Returns OTP-only without fetching
14672
+ */
14673
+ function fetchAuthProviders(databaseUrl_1) {
14674
+ return __awaiter(this, arguments, void 0, function* (databaseUrl, socialAuthEnabled = true) {
14675
+ // If social auth is disabled, return OTP-only without fetching
14676
+ if (!socialAuthEnabled) {
14677
+ return OTP_ONLY_RESPONSE;
14678
+ }
14679
+ try {
14680
+ const res = yield fetch(`${databaseUrl}/auth-providers`, {
14681
+ method: 'GET',
14682
+ headers: { 'Accept': 'application/json' },
14683
+ mode: 'cors',
14684
+ });
14685
+ if (res.status === 404) {
14686
+ // Old server version without OAuth support
14687
+ console.debug('[dexie-cloud] Server does not support /auth-providers endpoint. Using OTP-only authentication.');
14688
+ return OTP_ONLY_RESPONSE;
14689
+ }
14690
+ if (!res.ok) {
14691
+ console.warn(`[dexie-cloud] Failed to fetch auth providers: ${res.status} ${res.statusText}`);
14692
+ return OTP_ONLY_RESPONSE;
14693
+ }
14694
+ return yield res.json();
14695
+ }
14696
+ catch (error) {
14697
+ // Network error or other failure - fall back to OTP
14698
+ console.debug('[dexie-cloud] Could not fetch auth providers, falling back to OTP:', error);
14699
+ return OTP_ONLY_RESPONSE;
14700
+ }
14701
+ });
14702
+ }
14703
+
14704
+ /** Generate a random state string for CSRF protection */
14705
+ function generateState() {
14706
+ const array = new Uint8Array(32);
14707
+ crypto.getRandomValues(array);
14708
+ return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
14709
+ }
14710
+ /** Build the OAuth login URL */
14711
+ function buildOAuthLoginUrl(options, state) {
14712
+ const url = new URL(`${options.databaseUrl}/oauth/login/${options.provider}`);
14713
+ url.searchParams.set('state', state);
14714
+ // Set the redirect URI for postMessage or custom scheme
14715
+ const redirectUri = options.redirectUri ||
14716
+ (typeof window !== 'undefined' ? window.location.origin : '');
14717
+ if (redirectUri) {
14718
+ url.searchParams.set('redirect_uri', redirectUri);
14719
+ }
14720
+ return url.toString();
14721
+ }
14722
+ /** Calculate centered popup position */
14723
+ function getPopupPosition(width, height) {
14724
+ var _a, _b, _c, _d, _e, _f;
14725
+ const screenLeft = (_a = window.screenLeft) !== null && _a !== void 0 ? _a : window.screenX;
14726
+ const screenTop = (_b = window.screenTop) !== null && _b !== void 0 ? _b : window.screenY;
14727
+ const screenWidth = (_d = (_c = window.innerWidth) !== null && _c !== void 0 ? _c : document.documentElement.clientWidth) !== null && _d !== void 0 ? _d : screen.width;
14728
+ const screenHeight = (_f = (_e = window.innerHeight) !== null && _e !== void 0 ? _e : document.documentElement.clientHeight) !== null && _f !== void 0 ? _f : screen.height;
14729
+ const left = screenLeft + (screenWidth - width) / 2;
14730
+ const top = screenTop + (screenHeight - height) / 2;
14731
+ return { left: Math.max(0, left), top: Math.max(0, top) };
14732
+ }
14733
+ /**
14734
+ * Initiates OAuth login flow using a popup window.
14735
+ *
14736
+ * Opens a popup to the OAuth provider, listens for postMessage with the result,
14737
+ * and returns the Dexie Cloud authorization code.
14738
+ *
14739
+ * @param options - OAuth login options
14740
+ * @returns Promise resolving to OAuthLoginResult
14741
+ * @throws OAuthError on failure
14742
+ */
14743
+ function oauthLogin(options) {
14744
+ return __awaiter(this, void 0, void 0, function* () {
14745
+ const { databaseUrl, provider, usePopup = true } = options;
14746
+ if (!usePopup) {
14747
+ // For redirect flows, we can't return a promise - the page will navigate away
14748
+ throw new Error('Non-popup OAuth flow requires handleOAuthCallback after redirect');
14749
+ }
14750
+ const state = generateState();
14751
+ const loginUrl = buildOAuthLoginUrl(options, state);
14752
+ // Calculate popup dimensions and position
14753
+ const width = 500;
14754
+ const height = 600;
14755
+ const { left, top } = getPopupPosition(width, height);
14756
+ // Open popup window
14757
+ const popup = window.open(loginUrl, 'dexie-cloud-oauth', `width=${width},height=${height},left=${left},top=${top},menubar=no,toolbar=no,location=yes,status=no`);
14758
+ if (!popup) {
14759
+ throw new OAuthError('popup_blocked', provider);
14760
+ }
14761
+ return new Promise((resolve, reject) => {
14762
+ let resolved = false;
14763
+ // Listen for postMessage from the popup
14764
+ const handleMessage = (event) => {
14765
+ // Validate origin - must be from the Dexie Cloud server
14766
+ const expectedOrigin = new URL(databaseUrl).origin;
14767
+ if (event.origin !== expectedOrigin) {
14768
+ return; // Ignore messages from other origins
14769
+ }
14770
+ // Check if this is our OAuth result message
14771
+ if (!isOAuthResultMessage(event.data)) {
14772
+ return;
14773
+ }
14774
+ const message = event.data;
14775
+ // Validate state to prevent CSRF
14776
+ if (message.state !== state) {
14777
+ console.warn('[dexie-cloud] OAuth state mismatch, ignoring message');
14778
+ return;
14779
+ }
14780
+ // Clean up
14781
+ cleanup();
14782
+ resolved = true;
14783
+ // Handle error from OAuth flow
14784
+ if (message.error) {
14785
+ const errorCode = mapOAuthError(message.error);
14786
+ reject(new OAuthError(errorCode, provider, message.error));
14787
+ return;
14788
+ }
14789
+ // Success - return the authorization code
14790
+ if (message.code) {
14791
+ resolve({
14792
+ code: message.code,
14793
+ provider: message.provider,
14794
+ state: message.state,
14795
+ });
14796
+ }
14797
+ else {
14798
+ reject(new OAuthError('provider_error', provider, 'No authorization code received'));
14799
+ }
14800
+ };
14801
+ // Check if popup was closed without completing
14802
+ const checkPopupClosed = setInterval(() => {
14803
+ if (popup.closed && !resolved) {
14804
+ cleanup();
14805
+ reject(new OAuthError('popup_closed', provider));
14806
+ }
14807
+ }, 500);
14808
+ // Cleanup function
14809
+ const cleanup = () => {
14810
+ window.removeEventListener('message', handleMessage);
14811
+ clearInterval(checkPopupClosed);
14812
+ try {
14813
+ if (!popup.closed) {
14814
+ popup.close();
14815
+ }
14816
+ }
14817
+ catch (_a) {
14818
+ // Ignore errors when closing popup
14819
+ }
14820
+ };
14821
+ // Start listening for messages
14822
+ window.addEventListener('message', handleMessage);
14823
+ });
14824
+ });
14825
+ }
14826
+ /** Map OAuth error strings to error codes */
14827
+ function mapOAuthError(error) {
14828
+ const lowerError = error.toLowerCase();
14829
+ if (lowerError.includes('access_denied') || lowerError.includes('access denied')) {
14830
+ return 'access_denied';
14831
+ }
14832
+ if (lowerError.includes('email') && lowerError.includes('verif')) {
14833
+ return 'email_not_verified';
14834
+ }
14835
+ if (lowerError.includes('expired')) {
14836
+ return 'expired_code';
14837
+ }
14838
+ if (lowerError.includes('state')) {
14839
+ return 'invalid_state';
14840
+ }
14841
+ return 'provider_error';
14842
+ }
14843
+
14513
14844
  function otpFetchTokenCallback(db) {
14514
14845
  const { userInteraction } = db.cloud;
14515
14846
  return function otpAuthenticate(_a) {
14516
14847
  return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
14517
- var _b;
14848
+ var _b, _c;
14518
14849
  let tokenRequest;
14519
14850
  const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
14520
14851
  if (!url)
14521
14852
  throw new Error(`No database URL given.`);
14853
+ // Handle OAuth code exchange (from redirect/deep link flows)
14854
+ if ((hints === null || hints === void 0 ? void 0 : hints.oauthCode) && hints.provider) {
14855
+ return yield exchangeOAuthCode({
14856
+ databaseUrl: url,
14857
+ code: hints.oauthCode,
14858
+ publicKey: public_key,
14859
+ scopes: ['ACCESS_DB'],
14860
+ });
14861
+ }
14862
+ // Handle OAuth provider login (popup flow)
14863
+ if (hints === null || hints === void 0 ? void 0 : hints.provider) {
14864
+ return yield handleOAuthFlow(db, public_key, hints.provider);
14865
+ }
14522
14866
  if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
14523
14867
  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));
14524
14868
  tokenRequest = {
@@ -14542,6 +14886,18 @@
14542
14886
  };
14543
14887
  }
14544
14888
  else {
14889
+ // Check for available auth providers (OAuth + OTP)
14890
+ const socialAuthEnabled = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.socialAuth) !== false;
14891
+ const authProviders = yield fetchAuthProviders(url, socialAuthEnabled);
14892
+ // If we have OAuth providers available, prompt for selection
14893
+ if (authProviders.providers.length > 0) {
14894
+ const selection = yield promptForProvider(userInteraction, authProviders.providers, authProviders.otpEnabled, 'Sign in');
14895
+ if (selection.type === 'provider') {
14896
+ // User selected an OAuth provider
14897
+ return yield handleOAuthFlow(db, public_key, selection.provider);
14898
+ }
14899
+ // User chose OTP - continue with email prompt below
14900
+ }
14545
14901
  const email = yield promptForEmail(userInteraction, 'Enter email address', hints === null || hints === void 0 ? void 0 : hints.email);
14546
14902
  if (/@demo.local$/.test(email)) {
14547
14903
  tokenRequest = {
@@ -14619,6 +14975,49 @@
14619
14975
  });
14620
14976
  };
14621
14977
  }
14978
+ /**
14979
+ * Handles the OAuth popup flow and token exchange.
14980
+ */
14981
+ function handleOAuthFlow(db, publicKey, provider) {
14982
+ return __awaiter(this, void 0, void 0, function* () {
14983
+ var _a, _b, _c;
14984
+ const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
14985
+ if (!url)
14986
+ throw new Error(`No database URL given.`);
14987
+ const { userInteraction } = db.cloud;
14988
+ const usePopup = ((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.oauthPopup) !== false;
14989
+ const redirectUri = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.oauthRedirectUri) ||
14990
+ (typeof window !== 'undefined' ? window.location.origin : undefined);
14991
+ try {
14992
+ // Start OAuth popup flow
14993
+ const result = yield oauthLogin({
14994
+ databaseUrl: url,
14995
+ provider,
14996
+ redirectUri,
14997
+ usePopup,
14998
+ });
14999
+ // Exchange the auth code for tokens
15000
+ return yield exchangeOAuthCode({
15001
+ databaseUrl: url,
15002
+ code: result.code,
15003
+ publicKey,
15004
+ scopes: ['ACCESS_DB'],
15005
+ });
15006
+ }
15007
+ catch (error) {
15008
+ if (error instanceof OAuthError) {
15009
+ // Show user-friendly error message
15010
+ yield alertUser(userInteraction, 'Authentication Failed', {
15011
+ type: 'error',
15012
+ messageCode: 'GENERIC_ERROR',
15013
+ message: error.userMessage,
15014
+ messageParams: {},
15015
+ }).catch(() => { });
15016
+ }
15017
+ throw error;
15018
+ }
15019
+ });
15020
+ }
14622
15021
 
14623
15022
  /** A way to log to console in production without terser stripping out
14624
15023
  * it from the release bundle.
@@ -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
  }
@@ -17305,7 +17877,7 @@
17305
17877
  const syncComplete = new rxjs.Subject();
17306
17878
  dexie.cloud = {
17307
17879
  // @ts-ignore
17308
- version: "4.2.5",
17880
+ version: "4.3.0",
17309
17881
  options: Object.assign({}, DEFAULT_OPTIONS),
17310
17882
  schema: null,
17311
17883
  get currentUserId() {
@@ -17622,7 +18194,7 @@
17622
18194
  }
17623
18195
  }
17624
18196
  // @ts-ignore
17625
- dexieCloud.version = "4.2.5";
18197
+ dexieCloud.version = "4.3.0";
17626
18198
  Dexie.Cloud = dexieCloud;
17627
18199
 
17628
18200
  // In case the SW lives for a while, let it reuse already opened connections: