dexie-cloud-addon 4.3.5 → 4.3.6

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.
@@ -9,7 +9,7 @@ This feature adds support for OAuth 2.0 social login providers (Google, GitHub,
9
9
  ### Related Files
10
10
 
11
11
  - **Detailed flow diagram**: [oauth_flow.md](oauth_flow.md) - Sequence diagrams and detailed protocol description
12
- - **Server implementation**: `/Users/daw/repos/dexie-cloud/libs/dexie-cloud-server`
12
+ - **Server implementation**: See `dexie-cloud-server` repository
13
13
  - `src/api/oauth/registerOAuthEndpoints.ts` - OAuth endpoints
14
14
  - `src/api/oauth/oauth-helpers.ts` - Provider exchange logic
15
15
  - `src/api/registerTokenEndpoint.ts` - Token endpoint (authorization_code grant)
@@ -26,10 +26,6 @@ export declare function parseOAuthCallback(url?: string): OAuthCallbackParams |
26
26
  * @returns true if valid, false otherwise
27
27
  */
28
28
  export declare function validateOAuthState(receivedState: string): boolean;
29
- /**
30
- * Gets the OAuth provider from sessionStorage (for redirect flows).
31
- */
32
- export declare function getStoredOAuthProvider(): string | null;
33
29
  /**
34
30
  * Cleans up the dxc-auth query parameter from the URL.
35
31
  * Call this after successfully handling the callback to clean up the browser URL.
@@ -8,10 +8,6 @@ export interface OptionButtonProps {
8
8
  * Generic button component for selectable options.
9
9
  * Displays the option's icon and display name.
10
10
  *
11
- * The icon can be:
12
- * - Inline SVG (iconSvg) - rendered directly with dangerouslySetInnerHTML
13
- * - Image URL (iconUrl) - rendered as an img tag
14
- *
15
11
  * Style is determined by the styleHint property for branding purposes.
16
12
  */
17
13
  export declare function OptionButton({ option, onClick }: OptionButtonProps): h.JSX.Element;
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.3.5, Fri Jan 23 2026
11
+ * Version 4.3.6, Wed Jan 28 2026
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -761,73 +761,19 @@ class TokenErrorResponseError extends Error {
761
761
  }
762
762
  }
763
763
 
764
- /** Cache for fetched SVG content to avoid re-fetching */
765
- const svgCache = {};
766
- /** Default SVG icons for built-in OAuth providers */
767
- const ProviderIcons = {
768
- 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>`,
769
- 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>`,
770
- 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>`,
771
- 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>`,
772
- };
773
- /** Email/envelope icon for OTP option */
774
- 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>`;
775
- /**
776
- * Fetches SVG content from a URL and caches it.
777
- * Returns the SVG string or null if fetch fails.
778
- */
779
- function fetchSvgIcon(url) {
780
- return __awaiter(this, void 0, void 0, function* () {
781
- if (svgCache[url]) {
782
- return svgCache[url];
783
- }
784
- try {
785
- const res = yield fetch(url);
786
- if (res.ok) {
787
- const svg = yield res.text();
788
- // Validate it looks like SVG
789
- if (svg.includes('<svg')) {
790
- svgCache[url] = svg;
791
- return svg;
792
- }
793
- }
794
- }
795
- catch (_a) {
796
- // Silently fail - will show no icon
797
- }
798
- return null;
799
- });
800
- }
764
+ /** Email/envelope icon data URL for OTP option */
765
+ const EmailIcon = `data:image/svg+xml;base64,${btoa('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="#666666" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M22 6L12 13 2 6"/></svg>')}`;
801
766
  /**
802
767
  * Converts an OAuthProviderInfo to a generic DXCOption.
803
- * Fetches SVG icons from URLs if needed.
804
768
  */
805
769
  function providerToOption(provider) {
806
- return __awaiter(this, void 0, void 0, function* () {
807
- var _a;
808
- let iconSvg;
809
- // First check for built-in icons
810
- if (ProviderIcons[provider.type]) {
811
- iconSvg = ProviderIcons[provider.type];
812
- }
813
- // If provider has iconUrl pointing to SVG, fetch and inline it
814
- else if ((_a = provider.iconUrl) === null || _a === void 0 ? void 0 : _a.toLowerCase().endsWith('.svg')) {
815
- const fetched = yield fetchSvgIcon(provider.iconUrl);
816
- if (fetched) {
817
- iconSvg = fetched;
818
- }
819
- }
820
- return {
821
- name: 'provider',
822
- value: provider.name,
823
- displayName: `Continue with ${provider.displayName}`,
824
- iconSvg,
825
- // If iconUrl is not SVG, pass it through for img tag rendering
826
- iconUrl: (!iconSvg && provider.iconUrl) ? provider.iconUrl : undefined,
827
- // Use provider type as style hint for branding
828
- styleHint: provider.type,
829
- };
830
- });
770
+ return {
771
+ name: 'provider',
772
+ value: provider.name,
773
+ displayName: `Continue with ${provider.displayName}`,
774
+ iconUrl: provider.iconUrl,
775
+ styleHint: provider.type,
776
+ };
831
777
  }
832
778
  function interactWithUser(userInteraction, req) {
833
779
  return new Promise((resolve, reject) => {
@@ -978,8 +924,8 @@ function confirmLogout(userInteraction, currentUserId, numUnsyncedChanges) {
978
924
  */
979
925
  function promptForProvider(userInteraction_1, providers_1, otpEnabled_1) {
980
926
  return __awaiter(this, arguments, void 0, function* (userInteraction, providers, otpEnabled, title = 'Choose login method', alerts = []) {
981
- // Convert providers to generic options (with icon fetching)
982
- const providerOptions = yield Promise.all(providers.map(providerToOption));
927
+ // Convert providers to generic options
928
+ const providerOptions = providers.map(providerToOption);
983
929
  // Build the options array
984
930
  const options = [...providerOptions];
985
931
  // Add OTP option if enabled
@@ -988,7 +934,7 @@ function promptForProvider(userInteraction_1, providers_1, otpEnabled_1) {
988
934
  name: 'otp',
989
935
  value: 'email',
990
936
  displayName: 'Continue with email',
991
- iconSvg: EmailIcon,
937
+ iconUrl: EmailIcon,
992
938
  styleHint: 'otp',
993
939
  });
994
940
  }
@@ -1416,10 +1362,12 @@ function exchangeOAuthCode(options) {
1416
1362
  mode: 'cors',
1417
1363
  });
1418
1364
  if (!res.ok) {
1365
+ // Read body once as text to avoid stream consumption issues
1366
+ const bodyText = yield res.text().catch(() => res.statusText);
1419
1367
  if (res.status === 400 || res.status === 401) {
1420
- // Try to parse error response
1368
+ // Try to parse error response as JSON
1421
1369
  try {
1422
- const errorResponse = yield res.json();
1370
+ const errorResponse = JSON.parse(bodyText);
1423
1371
  if (errorResponse.type === 'error') {
1424
1372
  // Check for specific error codes
1425
1373
  if (errorResponse.messageCode === 'INVALID_OTP') {
@@ -1436,8 +1384,7 @@ function exchangeOAuthCode(options) {
1436
1384
  // Fall through to generic error
1437
1385
  }
1438
1386
  }
1439
- const errorText = yield res.text().catch(() => res.statusText);
1440
- throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${errorText}`);
1387
+ throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${bodyText}`);
1441
1388
  }
1442
1389
  const response = yield res.json();
1443
1390
  if (response.type === 'error') {
@@ -1543,9 +1490,8 @@ function buildOAuthLoginUrl(options) {
1543
1490
  * ```
1544
1491
  */
1545
1492
  function startOAuthRedirect(options) {
1546
- // Store provider in sessionStorage for reference on callback
1547
- if (typeof sessionStorage !== 'undefined') {
1548
- sessionStorage.setItem('dexie-cloud-oauth-provider', options.provider);
1493
+ if (typeof window === 'undefined') {
1494
+ throw new Error('OAuth redirect requires a browser environment');
1549
1495
  }
1550
1496
  const loginUrl = buildOAuthLoginUrl(options);
1551
1497
  window.location.href = loginUrl;
@@ -1651,7 +1597,8 @@ function otpFetchTokenCallback(db) {
1651
1597
  const res1 = yield fetch(`${url}/token`, {
1652
1598
  body: JSON.stringify(tokenRequest),
1653
1599
  method: 'post',
1654
- headers: { 'Content-Type': 'application/json', mode: 'cors' },
1600
+ headers: { 'Content-Type': 'application/json' },
1601
+ mode: 'cors',
1655
1602
  });
1656
1603
  if (res1.status !== 200) {
1657
1604
  const errMsg = yield res1.text();
@@ -1721,7 +1668,11 @@ function initiateOAuthRedirect(db, provider) {
1721
1668
  if (!url)
1722
1669
  throw new Error(`No database URL given.`);
1723
1670
  const redirectUri = ((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.oauthRedirectUri) ||
1724
- (typeof window !== 'undefined' ? window.location.href : undefined);
1671
+ (typeof location !== 'undefined' ? location.href : undefined);
1672
+ // CodeRabbit suggested to fail fast here, but the only situation where
1673
+ // redirectUri would be undefined is in non-browser environments, and
1674
+ // in those environments OAuth redirect does not make sense anyway
1675
+ // and will fail fast in startOAuthRedirect().
1725
1676
  // Start OAuth redirect flow - page navigates away
1726
1677
  startOAuthRedirect({
1727
1678
  databaseUrl: url,
@@ -5836,13 +5787,13 @@ const Styles = {
5836
5787
  color: "#3c4043"
5837
5788
  },
5838
5789
  ProviderGitHub: {
5839
- backgroundColor: "#24292e",
5840
- border: "1px solid #24292e",
5841
- color: "#ffffff"
5790
+ backgroundColor: "#ffffff",
5791
+ border: "1px solid #dadce0",
5792
+ color: "#181717"
5842
5793
  },
5843
5794
  ProviderMicrosoft: {
5844
5795
  backgroundColor: "#ffffff",
5845
- border: "1px solid #8c8c8c",
5796
+ border: "1px solid #dadce0",
5846
5797
  color: "#5e5e5e"
5847
5798
  },
5848
5799
  ProviderApple: {
@@ -5851,9 +5802,9 @@ const Styles = {
5851
5802
  color: "#ffffff"
5852
5803
  },
5853
5804
  ProviderCustom: {
5854
- backgroundColor: "#4f46e5",
5855
- border: "1px solid #4f46e5",
5856
- color: "#ffffff"
5805
+ backgroundColor: "#ffffff",
5806
+ border: "1px solid #dadce0",
5807
+ color: "#181717"
5857
5808
  },
5858
5809
  // Divider styles
5859
5810
  Divider: {
@@ -5944,39 +5895,13 @@ function getOptionStyle(styleHint) {
5944
5895
  * Generic button component for selectable options.
5945
5896
  * Displays the option's icon and display name.
5946
5897
  *
5947
- * The icon can be:
5948
- * - Inline SVG (iconSvg) - rendered directly with dangerouslySetInnerHTML
5949
- * - Image URL (iconUrl) - rendered as an img tag
5950
- *
5951
5898
  * Style is determined by the styleHint property for branding purposes.
5952
5899
  */
5953
5900
  function OptionButton({ option, onClick }) {
5954
- const { displayName, iconUrl, iconSvg, styleHint, value } = option;
5901
+ const { displayName, iconUrl, styleHint } = option;
5955
5902
  const style = getOptionStyle(styleHint);
5956
- // Get the text color from the button style for SVG fill processing
5957
- const textColor = style.color || '#000000';
5958
- // Process SVG to replace currentColor with actual text color
5959
- const processedSvg = iconSvg
5960
- ? iconSvg
5961
- .replace(/fill="currentColor"/gi, `fill="${textColor}"`)
5962
- .replace(/fill='currentColor'/gi, `fill='${textColor}'`)
5963
- .replace(/stroke="currentColor"/gi, `stroke="${textColor}"`)
5964
- .replace(/stroke='currentColor'/gi, `stroke='${textColor}'`)
5965
- : null;
5966
- // Render the appropriate icon
5967
- const renderIcon = () => {
5968
- // Inline SVG
5969
- if (processedSvg) {
5970
- return (_$1("span", { style: Styles.ProviderButtonIcon, "aria-hidden": "true", dangerouslySetInnerHTML: { __html: processedSvg } }));
5971
- }
5972
- // Image URL
5973
- if (iconUrl) {
5974
- return (_$1("img", { src: iconUrl, alt: "", style: Styles.ProviderButtonIcon, "aria-hidden": "true" }));
5975
- }
5976
- return null;
5977
- };
5978
5903
  return (_$1("button", { type: "button", style: style, onClick: onClick, class: `dxc-option-btn${styleHint ? ` dxc-option-${styleHint}` : ''}`, "aria-label": displayName },
5979
- renderIcon(),
5904
+ iconUrl && (_$1("img", { src: iconUrl, alt: "", style: Styles.ProviderButtonIcon, "aria-hidden": "true" })),
5980
5905
  _$1("span", { style: Styles.ProviderButtonText }, displayName)));
5981
5906
  }
5982
5907
  /**
@@ -6934,7 +6859,7 @@ function dexieCloud(dexie) {
6934
6859
  const syncComplete = new Subject();
6935
6860
  dexie.cloud = {
6936
6861
  // @ts-ignore
6937
- version: "4.3.5",
6862
+ version: "4.3.6",
6938
6863
  options: Object.assign({}, DEFAULT_OPTIONS),
6939
6864
  schema: null,
6940
6865
  get currentUserId() {
@@ -7204,12 +7129,18 @@ function dexieCloud(dexie) {
7204
7129
  pendingOAuthError = null; // Clear pending error
7205
7130
  console.debug('[dexie-cloud] Showing OAuth error:', error.message);
7206
7131
  // Show alert to user about the OAuth error
7207
- yield alertUser(db.cloud.userInteraction, 'Authentication Error', {
7208
- type: 'error',
7209
- messageCode: 'GENERIC_ERROR',
7210
- message: error.message,
7211
- messageParams: { provider: error.provider || 'unknown' }
7212
- });
7132
+ // Guard so UI errors don't abort initialization
7133
+ try {
7134
+ yield alertUser(db.cloud.userInteraction, 'Authentication Error', {
7135
+ type: 'error',
7136
+ messageCode: 'GENERIC_ERROR',
7137
+ message: error.message,
7138
+ messageParams: { provider: error.provider || 'unknown' }
7139
+ });
7140
+ }
7141
+ catch (uiError) {
7142
+ console.error('[dexie-cloud] Failed to show OAuth error alert:', uiError);
7143
+ }
7213
7144
  }
7214
7145
  // Process pending OAuth callback if present (from dxc-auth redirect)
7215
7146
  if (pendingOAuthCode && !db.cloud.isServiceWorkerDB) {
@@ -7313,7 +7244,7 @@ function dexieCloud(dexie) {
7313
7244
  }
7314
7245
  }
7315
7246
  // @ts-ignore
7316
- dexieCloud.version = "4.3.5";
7247
+ dexieCloud.version = "4.3.6";
7317
7248
  Dexie.Cloud = dexieCloud;
7318
7249
 
7319
7250
  export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };