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.
- package/TODO-SOCIALAUTH.md +274 -0
- package/dist/modern/DexieCloudAPI.d.ts +4 -0
- package/dist/modern/DexieCloudOptions.d.ts +17 -0
- package/dist/modern/authentication/exchangeOAuthCode.d.ts +23 -0
- package/dist/modern/authentication/fetchAuthProviders.d.ts +14 -0
- package/dist/modern/authentication/handleOAuthCallback.d.ts +63 -0
- package/dist/modern/authentication/interactWithUser.d.ts +22 -0
- package/dist/modern/authentication/oauthLogin.d.ts +38 -0
- package/dist/modern/default-ui/AuthProviderButton.d.ts +21 -0
- package/dist/modern/default-ui/LoginDialog.d.ts +30 -2
- package/dist/modern/default-ui/OptionButton.d.ts +21 -0
- package/dist/modern/default-ui/ProviderSelectionDialog.d.ts +7 -0
- package/dist/modern/default-ui/SelectDialog.d.ts +10 -0
- package/dist/modern/dexie-cloud-addon.js +686 -13
- package/dist/modern/dexie-cloud-addon.js.map +1 -1
- package/dist/modern/dexie-cloud-addon.min.js +1 -1
- package/dist/modern/dexie-cloud-addon.min.js.gz +0 -0
- package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
- package/dist/modern/errors/OAuthError.d.ts +10 -0
- package/dist/modern/service-worker.js +686 -13
- package/dist/modern/service-worker.js.map +1 -1
- package/dist/modern/service-worker.min.js +1 -1
- package/dist/modern/service-worker.min.js.map +1 -1
- package/dist/modern/types/DXCUserInteraction.d.ts +32 -1
- package/dist/umd/DISABLE_SERVICEWORKER_STRATEGY.d.ts +1 -0
- package/dist/umd/DXCWebSocketStatus.d.ts +1 -0
- package/dist/umd/DexieCloudAPI.d.ts +75 -0
- package/dist/umd/DexieCloudOptions.d.ts +27 -0
- package/dist/umd/DexieCloudSyncOptions.d.ts +4 -0
- package/dist/umd/DexieCloudTable.d.ts +18 -0
- package/dist/umd/InvalidLicenseError.d.ts +5 -0
- package/dist/umd/Invite.d.ts +8 -0
- package/dist/umd/PermissionChecker.d.ts +15 -0
- package/dist/umd/TSON.d.ts +17 -0
- package/dist/umd/WSObservable.d.ts +72 -0
- package/dist/umd/associate.d.ts +1 -0
- package/dist/umd/authentication/AuthPersistedContext.d.ts +9 -0
- package/dist/umd/authentication/TokenErrorResponseError.d.ts +10 -0
- package/dist/umd/authentication/TokenExpiredError.d.ts +3 -0
- package/dist/umd/authentication/UNAUTHORIZED_USER.d.ts +2 -0
- package/dist/umd/authentication/authenticate.d.ts +13 -0
- package/dist/umd/authentication/currentUserObservable.d.ts +1 -0
- package/dist/umd/authentication/interactWithUser.d.ts +21 -0
- package/dist/umd/authentication/login.d.ts +3 -0
- package/dist/umd/authentication/logout.d.ts +5 -0
- package/dist/umd/authentication/otpFetchTokenCallback.d.ts +3 -0
- package/dist/umd/authentication/setCurrentUser.d.ts +14 -0
- package/dist/umd/authentication/waitUntil.d.ts +3 -0
- package/dist/umd/computeSyncState.d.ts +4 -0
- package/dist/umd/createSharedValueObservable.d.ts +3 -0
- package/dist/umd/currentUserEmitter.d.ts +3 -0
- package/dist/umd/db/DexieCloudDB.d.ts +61 -0
- package/dist/umd/db/entities/BaseRevisionMapEntry.d.ts +5 -0
- package/dist/umd/db/entities/EntityCommon.d.ts +5 -0
- package/dist/umd/db/entities/GuardedJob.d.ts +5 -0
- package/dist/umd/db/entities/Member.d.ts +19 -0
- package/dist/umd/db/entities/PersistedSyncState.d.ts +22 -0
- package/dist/umd/db/entities/Realm.d.ts +14 -0
- package/dist/umd/db/entities/Role.d.ts +11 -0
- package/dist/umd/db/entities/UserLogin.d.ts +23 -0
- package/dist/umd/default-ui/Dialog.d.ts +5 -0
- package/dist/umd/default-ui/LoginDialog.d.ts +3 -0
- package/dist/umd/default-ui/Styles.d.ts +3 -0
- package/dist/umd/default-ui/index.d.ts +24 -0
- package/dist/umd/define-ydoc-trigger.d.ts +3 -0
- package/dist/umd/dexie-cloud-addon.d.ts +3 -0
- package/dist/umd/dexie-cloud-addon.js +687 -14
- package/dist/umd/dexie-cloud-addon.js.gz +0 -0
- package/dist/umd/dexie-cloud-addon.js.map +1 -1
- package/dist/umd/dexie-cloud-addon.min.js +1 -1
- package/dist/umd/dexie-cloud-addon.min.js.gz +0 -0
- package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
- package/dist/umd/dexie-cloud-client.d.ts +23 -0
- package/dist/umd/errors/HttpError.d.ts +5 -0
- package/dist/umd/extend-dexie-interface.d.ts +23 -0
- package/dist/umd/getGlobalRolesObservable.d.ts +5 -0
- package/dist/umd/getInternalAccessControlObservable.d.ts +12 -0
- package/dist/umd/getInvitesObservable.d.ts +23 -0
- package/dist/umd/getPermissionsLookupObservable.d.ts +16 -0
- package/dist/umd/getTiedRealmId.d.ts +2 -0
- package/dist/umd/helpers/BroadcastedAndLocalEvent.d.ts +8 -0
- package/dist/umd/helpers/CancelToken.d.ts +4 -0
- package/dist/umd/helpers/IS_SERVICE_WORKER.d.ts +1 -0
- package/dist/umd/helpers/SWBroadcastChannel.d.ts +12 -0
- package/dist/umd/helpers/allSettled.d.ts +1 -0
- package/dist/umd/helpers/bulkUpdate.d.ts +4 -0
- package/dist/umd/helpers/computeRealmSetHash.d.ts +2 -0
- package/dist/umd/helpers/date-constants.d.ts +5 -0
- package/dist/umd/helpers/flatten.d.ts +1 -0
- package/dist/umd/helpers/getMutationTable.d.ts +1 -0
- package/dist/umd/helpers/getSyncableTables.d.ts +4 -0
- package/dist/umd/helpers/getTableFromMutationTable.d.ts +1 -0
- package/dist/umd/helpers/makeArray.d.ts +1 -0
- package/dist/umd/helpers/randomString.d.ts +1 -0
- package/dist/umd/helpers/resolveText.d.ts +16 -0
- package/dist/umd/helpers/throwVersionIncrementNeeded.d.ts +1 -0
- package/dist/umd/helpers/visibilityState.d.ts +1 -0
- package/dist/umd/isEagerSyncDisabled.d.ts +2 -0
- package/dist/umd/isFirefox.d.ts +1 -0
- package/dist/umd/isSafari.d.ts +2 -0
- package/dist/umd/mapValueObservable.d.ts +5 -0
- package/dist/umd/mergePermissions.d.ts +2 -0
- package/dist/umd/middleware-helpers/guardedTable.d.ts +11 -0
- package/dist/umd/middleware-helpers/idGenerationHelpers.d.ts +18 -0
- package/dist/umd/middlewares/createIdGenerationMiddleware.d.ts +3 -0
- package/dist/umd/middlewares/createImplicitPropSetterMiddleware.d.ts +3 -0
- package/dist/umd/middlewares/createMutationTrackingMiddleware.d.ts +17 -0
- package/dist/umd/middlewares/outstandingTransaction.d.ts +4 -0
- package/dist/umd/overrideParseStoresSpec.d.ts +4 -0
- package/dist/umd/performInitialSync.d.ts +4 -0
- package/dist/umd/permissions.d.ts +9 -0
- package/dist/umd/prodLog.d.ts +9 -0
- package/dist/umd/service-worker.d.ts +1 -0
- package/dist/umd/service-worker.js +687 -14
- package/dist/umd/service-worker.js.map +1 -1
- package/dist/umd/service-worker.min.js +1 -1
- package/dist/umd/service-worker.min.js.map +1 -1
- package/dist/umd/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
- package/dist/umd/sync/LocalSyncWorker.d.ts +7 -0
- package/dist/umd/sync/SyncRequiredError.d.ts +3 -0
- package/dist/umd/sync/applyServerChanges.d.ts +3 -0
- package/dist/umd/sync/connectWebSocket.d.ts +2 -0
- package/dist/umd/sync/encodeIdsForServer.d.ts +4 -0
- package/dist/umd/sync/extractRealm.d.ts +2 -0
- package/dist/umd/sync/getLatestRevisionsPerTable.d.ts +6 -0
- package/dist/umd/sync/getTablesToSyncify.d.ts +3 -0
- package/dist/umd/sync/isOnline.d.ts +1 -0
- package/dist/umd/sync/isSyncNeeded.d.ts +2 -0
- package/dist/umd/sync/listClientChanges.d.ts +9 -0
- package/dist/umd/sync/listSyncifiedChanges.d.ts +5 -0
- package/dist/umd/sync/messageConsumerIsReady.d.ts +2 -0
- package/dist/umd/sync/messagesFromServerQueue.d.ts +8 -0
- package/dist/umd/sync/modifyLocalObjectsWithNewUserId.d.ts +4 -0
- package/dist/umd/sync/myId.d.ts +1 -0
- package/dist/umd/sync/numUnsyncedMutations.d.ts +2 -0
- package/dist/umd/sync/old_startSyncingClientChanges.d.ts +39 -0
- package/dist/umd/sync/performGuardedJob.d.ts +2 -0
- package/dist/umd/sync/ratelimit.d.ts +3 -0
- package/dist/umd/sync/registerSyncEvent.d.ts +3 -0
- package/dist/umd/sync/sync.d.ts +15 -0
- package/dist/umd/sync/syncIfPossible.d.ts +5 -0
- package/dist/umd/sync/syncWithServer.d.ts +6 -0
- package/dist/umd/sync/triggerSync.d.ts +2 -0
- package/dist/umd/sync/updateBaseRevs.d.ts +5 -0
- package/dist/umd/types/DXCAlert.d.ts +25 -0
- package/dist/umd/types/DXCInputField.d.ts +11 -0
- package/dist/umd/types/DXCUserInteraction.d.ts +93 -0
- package/dist/umd/types/NewIdOptions.d.ts +3 -0
- package/dist/umd/types/SWMessageEvent.d.ts +3 -0
- package/dist/umd/types/SWSyncEvent.d.ts +4 -0
- package/dist/umd/types/SyncState.d.ts +9 -0
- package/dist/umd/types/TXExpandos.d.ts +11 -0
- package/dist/umd/updateSchemaFromOptions.d.ts +3 -0
- package/dist/umd/userIsActive.d.ts +7 -0
- package/dist/umd/verifyConfig.d.ts +2 -0
- package/dist/umd/verifySchema.d.ts +2 -0
- package/dist/umd/yjs/YDexieCloudSyncState.d.ts +3 -0
- package/dist/umd/yjs/YTable.d.ts +3 -0
- package/dist/umd/yjs/applyYMessages.d.ts +9 -0
- package/dist/umd/yjs/awareness.d.ts +3 -0
- package/dist/umd/yjs/createYClientUpdateObservable.d.ts +4 -0
- package/dist/umd/yjs/createYHandler.d.ts +2 -0
- package/dist/umd/yjs/downloadYDocsFromServer.d.ts +3 -0
- package/dist/umd/yjs/getUpdatesTable.d.ts +3 -0
- package/dist/umd/yjs/listUpdatesSince.d.ts +3 -0
- package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
- package/dist/umd/yjs/reopenDocSignal.d.ts +10 -0
- package/dist/umd/yjs/updateYSyncStates.d.ts +6 -0
- package/oauth_flow.md +307 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.2
|
|
11
|
+
* Version 4.3.2, Thu Jan 22 2026
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -2445,6 +2445,74 @@
|
|
|
2445
2445
|
}
|
|
2446
2446
|
}
|
|
2447
2447
|
|
|
2448
|
+
/** Cache for fetched SVG content to avoid re-fetching */
|
|
2449
|
+
const svgCache = {};
|
|
2450
|
+
/** Default SVG icons for built-in OAuth providers */
|
|
2451
|
+
const ProviderIcons = {
|
|
2452
|
+
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>`,
|
|
2453
|
+
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>`,
|
|
2454
|
+
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>`,
|
|
2455
|
+
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>`,
|
|
2456
|
+
};
|
|
2457
|
+
/** Email/envelope icon for OTP option */
|
|
2458
|
+
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>`;
|
|
2459
|
+
/**
|
|
2460
|
+
* Fetches SVG content from a URL and caches it.
|
|
2461
|
+
* Returns the SVG string or null if fetch fails.
|
|
2462
|
+
*/
|
|
2463
|
+
function fetchSvgIcon(url) {
|
|
2464
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2465
|
+
if (svgCache[url]) {
|
|
2466
|
+
return svgCache[url];
|
|
2467
|
+
}
|
|
2468
|
+
try {
|
|
2469
|
+
const res = yield fetch(url);
|
|
2470
|
+
if (res.ok) {
|
|
2471
|
+
const svg = yield res.text();
|
|
2472
|
+
// Validate it looks like SVG
|
|
2473
|
+
if (svg.includes('<svg')) {
|
|
2474
|
+
svgCache[url] = svg;
|
|
2475
|
+
return svg;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
catch (_a) {
|
|
2480
|
+
// Silently fail - will show no icon
|
|
2481
|
+
}
|
|
2482
|
+
return null;
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* Converts an OAuthProviderInfo to a generic DXCOption.
|
|
2487
|
+
* Fetches SVG icons from URLs if needed.
|
|
2488
|
+
*/
|
|
2489
|
+
function providerToOption(provider) {
|
|
2490
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2491
|
+
var _a;
|
|
2492
|
+
let iconSvg;
|
|
2493
|
+
// First check for built-in icons
|
|
2494
|
+
if (ProviderIcons[provider.type]) {
|
|
2495
|
+
iconSvg = ProviderIcons[provider.type];
|
|
2496
|
+
}
|
|
2497
|
+
// If provider has iconUrl pointing to SVG, fetch and inline it
|
|
2498
|
+
else if ((_a = provider.iconUrl) === null || _a === void 0 ? void 0 : _a.toLowerCase().endsWith('.svg')) {
|
|
2499
|
+
const fetched = yield fetchSvgIcon(provider.iconUrl);
|
|
2500
|
+
if (fetched) {
|
|
2501
|
+
iconSvg = fetched;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return {
|
|
2505
|
+
name: 'provider',
|
|
2506
|
+
value: provider.name,
|
|
2507
|
+
displayName: `Continue with ${provider.displayName}`,
|
|
2508
|
+
iconSvg,
|
|
2509
|
+
// If iconUrl is not SVG, pass it through for img tag rendering
|
|
2510
|
+
iconUrl: (!iconSvg && provider.iconUrl) ? provider.iconUrl : undefined,
|
|
2511
|
+
// Use provider type as style hint for branding
|
|
2512
|
+
styleHint: provider.type,
|
|
2513
|
+
};
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2448
2516
|
function interactWithUser(userInteraction, req) {
|
|
2449
2517
|
return new Promise((resolve, reject) => {
|
|
2450
2518
|
const interactionProps = Object.assign(Object.assign({ submitLabel: 'Submit', cancelLabel: 'Cancel' }, req), { onSubmit: (res) => {
|
|
@@ -2579,6 +2647,67 @@
|
|
|
2579
2647
|
.catch(() => false);
|
|
2580
2648
|
});
|
|
2581
2649
|
}
|
|
2650
|
+
/**
|
|
2651
|
+
* Prompts the user to select an authentication method (OAuth provider or OTP).
|
|
2652
|
+
*
|
|
2653
|
+
* This function converts OAuth providers and OTP option into generic DXCOption[]
|
|
2654
|
+
* for the DXCSelect interaction, handling icon fetching and style hints.
|
|
2655
|
+
*
|
|
2656
|
+
* @param userInteraction - The user interaction BehaviorSubject
|
|
2657
|
+
* @param providers - Available OAuth providers
|
|
2658
|
+
* @param otpEnabled - Whether OTP is available
|
|
2659
|
+
* @param title - Dialog title
|
|
2660
|
+
* @param alerts - Optional alerts to display
|
|
2661
|
+
* @returns Promise resolving to the user's selection
|
|
2662
|
+
*/
|
|
2663
|
+
function promptForProvider(userInteraction_1, providers_1, otpEnabled_1) {
|
|
2664
|
+
return __awaiter(this, arguments, void 0, function* (userInteraction, providers, otpEnabled, title = 'Choose login method', alerts = []) {
|
|
2665
|
+
// Convert providers to generic options (with icon fetching)
|
|
2666
|
+
const providerOptions = yield Promise.all(providers.map(providerToOption));
|
|
2667
|
+
// Build the options array
|
|
2668
|
+
const options = [...providerOptions];
|
|
2669
|
+
// Add OTP option if enabled
|
|
2670
|
+
if (otpEnabled) {
|
|
2671
|
+
options.push({
|
|
2672
|
+
name: 'otp',
|
|
2673
|
+
value: 'email',
|
|
2674
|
+
displayName: 'Continue with email',
|
|
2675
|
+
iconSvg: EmailIcon,
|
|
2676
|
+
styleHint: 'otp',
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
return new Promise((resolve, reject) => {
|
|
2680
|
+
const interactionProps = {
|
|
2681
|
+
type: 'generic',
|
|
2682
|
+
title,
|
|
2683
|
+
alerts,
|
|
2684
|
+
options,
|
|
2685
|
+
fields: {},
|
|
2686
|
+
submitLabel: '', // No submit button - just options
|
|
2687
|
+
cancelLabel: 'Cancel',
|
|
2688
|
+
onSubmit: (params) => {
|
|
2689
|
+
userInteraction.next(undefined);
|
|
2690
|
+
// Check which option was selected
|
|
2691
|
+
if ('otp' in params) {
|
|
2692
|
+
resolve({ type: 'otp' });
|
|
2693
|
+
}
|
|
2694
|
+
else if ('provider' in params) {
|
|
2695
|
+
resolve({ type: 'provider', provider: params.provider });
|
|
2696
|
+
}
|
|
2697
|
+
else {
|
|
2698
|
+
// Unknown - default to OTP
|
|
2699
|
+
resolve({ type: 'otp' });
|
|
2700
|
+
}
|
|
2701
|
+
},
|
|
2702
|
+
onCancel: () => {
|
|
2703
|
+
userInteraction.next(undefined);
|
|
2704
|
+
reject(new Dexie.AbortError('User cancelled'));
|
|
2705
|
+
},
|
|
2706
|
+
};
|
|
2707
|
+
userInteraction.next(interactionProps);
|
|
2708
|
+
});
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2582
2711
|
|
|
2583
2712
|
function loadAccessToken(db) {
|
|
2584
2713
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -13113,7 +13242,7 @@
|
|
|
13113
13242
|
*
|
|
13114
13243
|
* ==========================================================================
|
|
13115
13244
|
*
|
|
13116
|
-
* Version 4.2.2,
|
|
13245
|
+
* Version 4.2.2, Tue Jan 20 2026
|
|
13117
13246
|
*
|
|
13118
13247
|
* https://dexie.org
|
|
13119
13248
|
*
|
|
@@ -14510,15 +14639,215 @@
|
|
|
14510
14639
|
});
|
|
14511
14640
|
}
|
|
14512
14641
|
|
|
14642
|
+
/** User-friendly messages for OAuth error codes */
|
|
14643
|
+
const ERROR_MESSAGES = {
|
|
14644
|
+
access_denied: 'Access was denied by the authentication provider.',
|
|
14645
|
+
invalid_state: 'The authentication response could not be verified. Please try again.',
|
|
14646
|
+
email_not_verified: 'Your email address must be verified before you can log in.',
|
|
14647
|
+
expired_code: 'The authentication code has expired. Please try again.',
|
|
14648
|
+
provider_error: 'An error occurred with the authentication provider.',
|
|
14649
|
+
network_error: 'A network error occurred during authentication. Please check your connection and try again.',
|
|
14650
|
+
};
|
|
14651
|
+
/** Error class for OAuth-specific errors */
|
|
14652
|
+
class OAuthError extends Error {
|
|
14653
|
+
constructor(code, provider, customMessage) {
|
|
14654
|
+
super(customMessage || ERROR_MESSAGES[code]);
|
|
14655
|
+
this.name = 'OAuthError';
|
|
14656
|
+
this.code = code;
|
|
14657
|
+
this.provider = provider;
|
|
14658
|
+
}
|
|
14659
|
+
/** Get user-friendly message for this error */
|
|
14660
|
+
get userMessage() {
|
|
14661
|
+
return ERROR_MESSAGES[this.code] || this.message;
|
|
14662
|
+
}
|
|
14663
|
+
}
|
|
14664
|
+
|
|
14665
|
+
/**
|
|
14666
|
+
* Exchanges a Dexie Cloud authorization code for access and refresh tokens.
|
|
14667
|
+
*
|
|
14668
|
+
* This is called after the OAuth callback delivers the authorization code
|
|
14669
|
+
* via postMessage (popup flow) or redirect.
|
|
14670
|
+
*
|
|
14671
|
+
* @param options - Exchange options
|
|
14672
|
+
* @returns Promise resolving to TokenFinalResponse
|
|
14673
|
+
* @throws OAuthError or TokenErrorResponseError on failure
|
|
14674
|
+
*/
|
|
14675
|
+
function exchangeOAuthCode(options) {
|
|
14676
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
14677
|
+
const { databaseUrl, code, publicKey, scopes = ['ACCESS_DB'] } = options;
|
|
14678
|
+
const tokenRequest = {
|
|
14679
|
+
grant_type: 'authorization_code',
|
|
14680
|
+
code,
|
|
14681
|
+
public_key: publicKey,
|
|
14682
|
+
scopes,
|
|
14683
|
+
};
|
|
14684
|
+
try {
|
|
14685
|
+
const res = yield fetch(`${databaseUrl}/token`, {
|
|
14686
|
+
method: 'POST',
|
|
14687
|
+
headers: { 'Content-Type': 'application/json' },
|
|
14688
|
+
body: JSON.stringify(tokenRequest),
|
|
14689
|
+
mode: 'cors',
|
|
14690
|
+
});
|
|
14691
|
+
if (!res.ok) {
|
|
14692
|
+
if (res.status === 400 || res.status === 401) {
|
|
14693
|
+
// Try to parse error response
|
|
14694
|
+
try {
|
|
14695
|
+
const errorResponse = yield res.json();
|
|
14696
|
+
if (errorResponse.type === 'error') {
|
|
14697
|
+
// Check for specific error codes
|
|
14698
|
+
if (errorResponse.messageCode === 'INVALID_OTP') {
|
|
14699
|
+
// In the context of OAuth, this likely means expired code
|
|
14700
|
+
throw new OAuthError('expired_code', undefined, errorResponse.message);
|
|
14701
|
+
}
|
|
14702
|
+
throw new TokenErrorResponseError(errorResponse);
|
|
14703
|
+
}
|
|
14704
|
+
}
|
|
14705
|
+
catch (e) {
|
|
14706
|
+
if (e instanceof OAuthError || e instanceof TokenErrorResponseError) {
|
|
14707
|
+
throw e;
|
|
14708
|
+
}
|
|
14709
|
+
// Fall through to generic error
|
|
14710
|
+
}
|
|
14711
|
+
}
|
|
14712
|
+
const errorText = yield res.text().catch(() => res.statusText);
|
|
14713
|
+
throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${errorText}`);
|
|
14714
|
+
}
|
|
14715
|
+
const response = yield res.json();
|
|
14716
|
+
if (response.type === 'error') {
|
|
14717
|
+
throw new TokenErrorResponseError(response);
|
|
14718
|
+
}
|
|
14719
|
+
if (response.type !== 'tokens') {
|
|
14720
|
+
throw new OAuthError('provider_error', undefined, `Unexpected response type: ${response.type}`);
|
|
14721
|
+
}
|
|
14722
|
+
return response;
|
|
14723
|
+
}
|
|
14724
|
+
catch (error) {
|
|
14725
|
+
if (error instanceof OAuthError || error instanceof TokenErrorResponseError) {
|
|
14726
|
+
throw error;
|
|
14727
|
+
}
|
|
14728
|
+
if (error instanceof TypeError) {
|
|
14729
|
+
// Network error
|
|
14730
|
+
throw new OAuthError('network_error');
|
|
14731
|
+
}
|
|
14732
|
+
throw error;
|
|
14733
|
+
}
|
|
14734
|
+
});
|
|
14735
|
+
}
|
|
14736
|
+
|
|
14737
|
+
/** Default response when OAuth is disabled or unavailable */
|
|
14738
|
+
const OTP_ONLY_RESPONSE = {
|
|
14739
|
+
providers: [],
|
|
14740
|
+
otpEnabled: true,
|
|
14741
|
+
};
|
|
14742
|
+
/**
|
|
14743
|
+
* Fetches available authentication providers from the Dexie Cloud server.
|
|
14744
|
+
*
|
|
14745
|
+
* @param databaseUrl - The Dexie Cloud database URL
|
|
14746
|
+
* @param socialAuthEnabled - Whether social auth is enabled in client config (default: true)
|
|
14747
|
+
* @returns Promise resolving to AuthProvidersResponse
|
|
14748
|
+
*
|
|
14749
|
+
* Handles failures gracefully:
|
|
14750
|
+
* - 404 → Returns OTP-only (old server version)
|
|
14751
|
+
* - Network error → Returns OTP-only
|
|
14752
|
+
* - socialAuthEnabled: false → Returns OTP-only without fetching
|
|
14753
|
+
*/
|
|
14754
|
+
function fetchAuthProviders(databaseUrl_1) {
|
|
14755
|
+
return __awaiter(this, arguments, void 0, function* (databaseUrl, socialAuthEnabled = true) {
|
|
14756
|
+
// If social auth is disabled, return OTP-only without fetching
|
|
14757
|
+
if (!socialAuthEnabled) {
|
|
14758
|
+
return OTP_ONLY_RESPONSE;
|
|
14759
|
+
}
|
|
14760
|
+
try {
|
|
14761
|
+
const res = yield fetch(`${databaseUrl}/auth-providers`, {
|
|
14762
|
+
method: 'GET',
|
|
14763
|
+
headers: { 'Accept': 'application/json' },
|
|
14764
|
+
mode: 'cors',
|
|
14765
|
+
});
|
|
14766
|
+
if (res.status === 404) {
|
|
14767
|
+
// Old server version without OAuth support
|
|
14768
|
+
console.debug('[dexie-cloud] Server does not support /auth-providers endpoint. Using OTP-only authentication.');
|
|
14769
|
+
return OTP_ONLY_RESPONSE;
|
|
14770
|
+
}
|
|
14771
|
+
if (!res.ok) {
|
|
14772
|
+
console.warn(`[dexie-cloud] Failed to fetch auth providers: ${res.status} ${res.statusText}`);
|
|
14773
|
+
return OTP_ONLY_RESPONSE;
|
|
14774
|
+
}
|
|
14775
|
+
return yield res.json();
|
|
14776
|
+
}
|
|
14777
|
+
catch (error) {
|
|
14778
|
+
// Network error or other failure - fall back to OTP
|
|
14779
|
+
console.debug('[dexie-cloud] Could not fetch auth providers, falling back to OTP:', error);
|
|
14780
|
+
return OTP_ONLY_RESPONSE;
|
|
14781
|
+
}
|
|
14782
|
+
});
|
|
14783
|
+
}
|
|
14784
|
+
|
|
14785
|
+
/** Build the OAuth login URL */
|
|
14786
|
+
function buildOAuthLoginUrl(options) {
|
|
14787
|
+
const url = new URL(`${options.databaseUrl}/oauth/login/${options.provider}`);
|
|
14788
|
+
// Set the redirect URI - defaults to current page URL for web SPAs
|
|
14789
|
+
const redirectUri = options.redirectUri ||
|
|
14790
|
+
(typeof window !== 'undefined' ? window.location.href : '');
|
|
14791
|
+
if (redirectUri) {
|
|
14792
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
14793
|
+
}
|
|
14794
|
+
return url.toString();
|
|
14795
|
+
}
|
|
14796
|
+
/**
|
|
14797
|
+
* Initiates OAuth login via full page redirect.
|
|
14798
|
+
*
|
|
14799
|
+
* The page will navigate to the OAuth provider. After authentication,
|
|
14800
|
+
* the user is redirected back to the app with a `dxc-auth` query parameter
|
|
14801
|
+
* containing base64url-encoded JSON with the authorization code.
|
|
14802
|
+
*
|
|
14803
|
+
* The dexie-cloud-addon automatically detects and processes this parameter
|
|
14804
|
+
* when db.cloud.configure() is called on page load.
|
|
14805
|
+
*
|
|
14806
|
+
* @param options - OAuth redirect options
|
|
14807
|
+
*
|
|
14808
|
+
* @example
|
|
14809
|
+
* ```typescript
|
|
14810
|
+
* // Initiate OAuth login
|
|
14811
|
+
* startOAuthRedirect({
|
|
14812
|
+
* databaseUrl: 'https://mydb.dexie.cloud',
|
|
14813
|
+
* provider: 'google'
|
|
14814
|
+
* });
|
|
14815
|
+
* // Page navigates away, user authenticates, then returns with auth code
|
|
14816
|
+
* ```
|
|
14817
|
+
*/
|
|
14818
|
+
function startOAuthRedirect(options) {
|
|
14819
|
+
// Store provider in sessionStorage for reference on callback
|
|
14820
|
+
if (typeof sessionStorage !== 'undefined') {
|
|
14821
|
+
sessionStorage.setItem('dexie-cloud-oauth-provider', options.provider);
|
|
14822
|
+
}
|
|
14823
|
+
const loginUrl = buildOAuthLoginUrl(options);
|
|
14824
|
+
window.location.href = loginUrl;
|
|
14825
|
+
}
|
|
14826
|
+
|
|
14513
14827
|
function otpFetchTokenCallback(db) {
|
|
14514
14828
|
const { userInteraction } = db.cloud;
|
|
14515
14829
|
return function otpAuthenticate(_a) {
|
|
14516
14830
|
return __awaiter(this, arguments, void 0, function* ({ public_key, hints }) {
|
|
14517
|
-
var _b;
|
|
14831
|
+
var _b, _c;
|
|
14518
14832
|
let tokenRequest;
|
|
14519
14833
|
const url = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl;
|
|
14520
14834
|
if (!url)
|
|
14521
14835
|
throw new Error(`No database URL given.`);
|
|
14836
|
+
// Handle OAuth code exchange (from redirect/deep link flows)
|
|
14837
|
+
if ((hints === null || hints === void 0 ? void 0 : hints.oauthCode) && hints.provider) {
|
|
14838
|
+
return yield exchangeOAuthCode({
|
|
14839
|
+
databaseUrl: url,
|
|
14840
|
+
code: hints.oauthCode,
|
|
14841
|
+
publicKey: public_key,
|
|
14842
|
+
scopes: ['ACCESS_DB'],
|
|
14843
|
+
});
|
|
14844
|
+
}
|
|
14845
|
+
// Handle OAuth provider login via redirect
|
|
14846
|
+
if (hints === null || hints === void 0 ? void 0 : hints.provider) {
|
|
14847
|
+
initiateOAuthRedirect(db, hints.provider);
|
|
14848
|
+
// This function never returns - page navigates away
|
|
14849
|
+
throw new Error('OAuth redirect initiated');
|
|
14850
|
+
}
|
|
14522
14851
|
if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
|
|
14523
14852
|
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
14853
|
tokenRequest = {
|
|
@@ -14542,6 +14871,20 @@
|
|
|
14542
14871
|
};
|
|
14543
14872
|
}
|
|
14544
14873
|
else {
|
|
14874
|
+
// Check for available auth providers (OAuth + OTP)
|
|
14875
|
+
const socialAuthEnabled = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.socialAuth) !== false;
|
|
14876
|
+
const authProviders = yield fetchAuthProviders(url, socialAuthEnabled);
|
|
14877
|
+
// If we have OAuth providers available, prompt for selection
|
|
14878
|
+
if (authProviders.providers.length > 0) {
|
|
14879
|
+
const selection = yield promptForProvider(userInteraction, authProviders.providers, authProviders.otpEnabled, 'Sign in');
|
|
14880
|
+
if (selection.type === 'provider') {
|
|
14881
|
+
// User selected an OAuth provider - initiate redirect
|
|
14882
|
+
initiateOAuthRedirect(db, selection.provider);
|
|
14883
|
+
// This function never returns - page navigates away
|
|
14884
|
+
throw new Error('OAuth redirect initiated');
|
|
14885
|
+
}
|
|
14886
|
+
// User chose OTP - continue with email prompt below
|
|
14887
|
+
}
|
|
14545
14888
|
const email = yield promptForEmail(userInteraction, 'Enter email address', hints === null || hints === void 0 ? void 0 : hints.email);
|
|
14546
14889
|
if (/@demo.local$/.test(email)) {
|
|
14547
14890
|
tokenRequest = {
|
|
@@ -14619,6 +14962,27 @@
|
|
|
14619
14962
|
});
|
|
14620
14963
|
};
|
|
14621
14964
|
}
|
|
14965
|
+
/**
|
|
14966
|
+
* Initiates OAuth login via full page redirect.
|
|
14967
|
+
*
|
|
14968
|
+
* The page will navigate away to the OAuth provider. After authentication,
|
|
14969
|
+
* the user is redirected back with a dxc-auth query parameter that is
|
|
14970
|
+
* automatically detected by db.cloud.configure().
|
|
14971
|
+
*/
|
|
14972
|
+
function initiateOAuthRedirect(db, provider) {
|
|
14973
|
+
var _a, _b;
|
|
14974
|
+
const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
|
|
14975
|
+
if (!url)
|
|
14976
|
+
throw new Error(`No database URL given.`);
|
|
14977
|
+
const redirectUri = ((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.oauthRedirectUri) ||
|
|
14978
|
+
(typeof window !== 'undefined' ? window.location.href : undefined);
|
|
14979
|
+
// Start OAuth redirect flow - page navigates away
|
|
14980
|
+
startOAuthRedirect({
|
|
14981
|
+
databaseUrl: url,
|
|
14982
|
+
provider,
|
|
14983
|
+
redirectUri,
|
|
14984
|
+
});
|
|
14985
|
+
}
|
|
14622
14986
|
|
|
14623
14987
|
/** A way to log to console in production without terser stripping out
|
|
14624
14988
|
* it from the release bundle.
|
|
@@ -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
|
-
|
|
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("
|
|
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
|
-
} },
|
|
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
|
-
//
|
|
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
|
const DEFAULT_OPTIONS = {
|
|
17269
17907
|
nameSuffix: true,
|
|
17270
17908
|
};
|
|
@@ -17276,6 +17914,8 @@
|
|
|
17276
17914
|
const currentUserEmitter = getCurrentUserEmitter(dexie);
|
|
17277
17915
|
const subscriptions = [];
|
|
17278
17916
|
let configuredProgramatically = false;
|
|
17917
|
+
// Pending OAuth auth code from dxc-auth redirect (detected in configure())
|
|
17918
|
+
let pendingOAuthCode = null;
|
|
17279
17919
|
// local sync worker - used when there's no service worker.
|
|
17280
17920
|
let localSyncWorker = null;
|
|
17281
17921
|
dexie.on('ready', (dexie) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -17305,7 +17945,7 @@
|
|
|
17305
17945
|
const syncComplete = new rxjs.Subject();
|
|
17306
17946
|
dexie.cloud = {
|
|
17307
17947
|
// @ts-ignore
|
|
17308
|
-
version: "4.2
|
|
17948
|
+
version: "4.3.2",
|
|
17309
17949
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
17310
17950
|
schema: null,
|
|
17311
17951
|
get currentUserId() {
|
|
@@ -17340,6 +17980,26 @@
|
|
|
17340
17980
|
DexieCloudDB(dexie).reconfigure(); // Update observable from new dexie.name
|
|
17341
17981
|
}
|
|
17342
17982
|
updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
|
|
17983
|
+
// Check for OAuth callback (dxc-auth query parameter)
|
|
17984
|
+
// Only check in DOM environment, not workers
|
|
17985
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
17986
|
+
try {
|
|
17987
|
+
const callback = parseOAuthCallback();
|
|
17988
|
+
if (callback) {
|
|
17989
|
+
// Clean up URL immediately (remove dxc-auth param)
|
|
17990
|
+
cleanupOAuthUrl();
|
|
17991
|
+
// Store the pending auth code for processing when db is ready
|
|
17992
|
+
pendingOAuthCode = { code: callback.code, provider: callback.provider };
|
|
17993
|
+
console.debug('[dexie-cloud] OAuth callback detected, auth code stored for processing');
|
|
17994
|
+
}
|
|
17995
|
+
}
|
|
17996
|
+
catch (error) {
|
|
17997
|
+
// parseOAuthCallback throws OAuthError on error callbacks
|
|
17998
|
+
// Store null for code but log the error
|
|
17999
|
+
console.warn('[dexie-cloud] OAuth callback error:', error);
|
|
18000
|
+
cleanupOAuthUrl();
|
|
18001
|
+
}
|
|
18002
|
+
}
|
|
17343
18003
|
},
|
|
17344
18004
|
logout() {
|
|
17345
18005
|
return __awaiter(this, arguments, void 0, function* ({ force } = {}) {
|
|
@@ -17534,6 +18194,19 @@
|
|
|
17534
18194
|
// HERE: If requireAuth, do athentication now.
|
|
17535
18195
|
let changedUser = false;
|
|
17536
18196
|
const user = yield db.getCurrentUser();
|
|
18197
|
+
// Process pending OAuth callback if present (from dxc-auth redirect)
|
|
18198
|
+
if (pendingOAuthCode && !db.cloud.isServiceWorkerDB) {
|
|
18199
|
+
const { code, provider } = pendingOAuthCode;
|
|
18200
|
+
pendingOAuthCode = null; // Clear pending code
|
|
18201
|
+
console.debug('[dexie-cloud] Processing OAuth callback, provider:', provider);
|
|
18202
|
+
try {
|
|
18203
|
+
changedUser = yield login(db, { oauthCode: code, provider });
|
|
18204
|
+
}
|
|
18205
|
+
catch (error) {
|
|
18206
|
+
console.error('[dexie-cloud] OAuth login failed:', error);
|
|
18207
|
+
// Continue with normal flow - user can try again
|
|
18208
|
+
}
|
|
18209
|
+
}
|
|
17537
18210
|
const requireAuth = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.requireAuth;
|
|
17538
18211
|
if (requireAuth) {
|
|
17539
18212
|
if (db.cloud.isServiceWorkerDB) {
|
|
@@ -17622,7 +18295,7 @@
|
|
|
17622
18295
|
}
|
|
17623
18296
|
}
|
|
17624
18297
|
// @ts-ignore
|
|
17625
|
-
dexieCloud.version = "4.2
|
|
18298
|
+
dexieCloud.version = "4.3.2";
|
|
17626
18299
|
Dexie.Cloud = dexieCloud;
|
|
17627
18300
|
|
|
17628
18301
|
// In case the SW lives for a while, let it reuse already opened connections:
|