dexie-cloud-addon 4.3.4 → 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.
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.3.4, Fri Jan 23 2026
11
+ * Version 4.3.6, Wed Jan 28 2026
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -1145,73 +1145,19 @@ class TokenErrorResponseError extends Error {
1145
1145
  }
1146
1146
  }
1147
1147
 
1148
- /** Cache for fetched SVG content to avoid re-fetching */
1149
- const svgCache = {};
1150
- /** Default SVG icons for built-in OAuth providers */
1151
- const ProviderIcons = {
1152
- 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>`,
1153
- 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>`,
1154
- 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>`,
1155
- 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>`,
1156
- };
1157
- /** Email/envelope icon for OTP option */
1158
- 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>`;
1159
- /**
1160
- * Fetches SVG content from a URL and caches it.
1161
- * Returns the SVG string or null if fetch fails.
1162
- */
1163
- function fetchSvgIcon(url) {
1164
- return __awaiter(this, void 0, void 0, function* () {
1165
- if (svgCache[url]) {
1166
- return svgCache[url];
1167
- }
1168
- try {
1169
- const res = yield fetch(url);
1170
- if (res.ok) {
1171
- const svg = yield res.text();
1172
- // Validate it looks like SVG
1173
- if (svg.includes('<svg')) {
1174
- svgCache[url] = svg;
1175
- return svg;
1176
- }
1177
- }
1178
- }
1179
- catch (_a) {
1180
- // Silently fail - will show no icon
1181
- }
1182
- return null;
1183
- });
1184
- }
1148
+ /** Email/envelope icon data URL for OTP option */
1149
+ 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>')}`;
1185
1150
  /**
1186
1151
  * Converts an OAuthProviderInfo to a generic DXCOption.
1187
- * Fetches SVG icons from URLs if needed.
1188
1152
  */
1189
1153
  function providerToOption(provider) {
1190
- return __awaiter(this, void 0, void 0, function* () {
1191
- var _a;
1192
- let iconSvg;
1193
- // First check for built-in icons
1194
- if (ProviderIcons[provider.type]) {
1195
- iconSvg = ProviderIcons[provider.type];
1196
- }
1197
- // If provider has iconUrl pointing to SVG, fetch and inline it
1198
- else if ((_a = provider.iconUrl) === null || _a === void 0 ? void 0 : _a.toLowerCase().endsWith('.svg')) {
1199
- const fetched = yield fetchSvgIcon(provider.iconUrl);
1200
- if (fetched) {
1201
- iconSvg = fetched;
1202
- }
1203
- }
1204
- return {
1205
- name: 'provider',
1206
- value: provider.name,
1207
- displayName: `Continue with ${provider.displayName}`,
1208
- iconSvg,
1209
- // If iconUrl is not SVG, pass it through for img tag rendering
1210
- iconUrl: (!iconSvg && provider.iconUrl) ? provider.iconUrl : undefined,
1211
- // Use provider type as style hint for branding
1212
- styleHint: provider.type,
1213
- };
1214
- });
1154
+ return {
1155
+ name: 'provider',
1156
+ value: provider.name,
1157
+ displayName: `Continue with ${provider.displayName}`,
1158
+ iconUrl: provider.iconUrl,
1159
+ styleHint: provider.type,
1160
+ };
1215
1161
  }
1216
1162
  function interactWithUser(userInteraction, req) {
1217
1163
  return new Promise((resolve, reject) => {
@@ -1362,8 +1308,8 @@ function confirmLogout(userInteraction, currentUserId, numUnsyncedChanges) {
1362
1308
  */
1363
1309
  function promptForProvider(userInteraction_1, providers_1, otpEnabled_1) {
1364
1310
  return __awaiter(this, arguments, void 0, function* (userInteraction, providers, otpEnabled, title = 'Choose login method', alerts = []) {
1365
- // Convert providers to generic options (with icon fetching)
1366
- const providerOptions = yield Promise.all(providers.map(providerToOption));
1311
+ // Convert providers to generic options
1312
+ const providerOptions = providers.map(providerToOption);
1367
1313
  // Build the options array
1368
1314
  const options = [...providerOptions];
1369
1315
  // Add OTP option if enabled
@@ -1372,7 +1318,7 @@ function promptForProvider(userInteraction_1, providers_1, otpEnabled_1) {
1372
1318
  name: 'otp',
1373
1319
  value: 'email',
1374
1320
  displayName: 'Continue with email',
1375
- iconSvg: EmailIcon,
1321
+ iconUrl: EmailIcon,
1376
1322
  styleHint: 'otp',
1377
1323
  });
1378
1324
  }
@@ -3740,10 +3686,12 @@ function exchangeOAuthCode(options) {
3740
3686
  mode: 'cors',
3741
3687
  });
3742
3688
  if (!res.ok) {
3689
+ // Read body once as text to avoid stream consumption issues
3690
+ const bodyText = yield res.text().catch(() => res.statusText);
3743
3691
  if (res.status === 400 || res.status === 401) {
3744
- // Try to parse error response
3692
+ // Try to parse error response as JSON
3745
3693
  try {
3746
- const errorResponse = yield res.json();
3694
+ const errorResponse = JSON.parse(bodyText);
3747
3695
  if (errorResponse.type === 'error') {
3748
3696
  // Check for specific error codes
3749
3697
  if (errorResponse.messageCode === 'INVALID_OTP') {
@@ -3760,8 +3708,7 @@ function exchangeOAuthCode(options) {
3760
3708
  // Fall through to generic error
3761
3709
  }
3762
3710
  }
3763
- const errorText = yield res.text().catch(() => res.statusText);
3764
- throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${errorText}`);
3711
+ throw new OAuthError('provider_error', undefined, `Token exchange failed: ${res.status} ${bodyText}`);
3765
3712
  }
3766
3713
  const response = yield res.json();
3767
3714
  if (response.type === 'error') {
@@ -3867,9 +3814,8 @@ function buildOAuthLoginUrl(options) {
3867
3814
  * ```
3868
3815
  */
3869
3816
  function startOAuthRedirect(options) {
3870
- // Store provider in sessionStorage for reference on callback
3871
- if (typeof sessionStorage !== 'undefined') {
3872
- sessionStorage.setItem('dexie-cloud-oauth-provider', options.provider);
3817
+ if (typeof window === 'undefined') {
3818
+ throw new Error('OAuth redirect requires a browser environment');
3873
3819
  }
3874
3820
  const loginUrl = buildOAuthLoginUrl(options);
3875
3821
  window.location.href = loginUrl;
@@ -3921,6 +3867,25 @@ function otpFetchTokenCallback(db) {
3921
3867
  public_key,
3922
3868
  };
3923
3869
  }
3870
+ else if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'otp' || (hints === null || hints === void 0 ? void 0 : hints.email)) {
3871
+ // User explicitly requested OTP flow - skip provider selection
3872
+ const email = (hints === null || hints === void 0 ? void 0 : hints.email) || (yield promptForEmail(userInteraction, 'Enter email address'));
3873
+ if (/@demo.local$/.test(email)) {
3874
+ tokenRequest = {
3875
+ demo_user: email,
3876
+ grant_type: 'demo',
3877
+ scopes: ['ACCESS_DB'],
3878
+ public_key
3879
+ };
3880
+ }
3881
+ else {
3882
+ tokenRequest = {
3883
+ email,
3884
+ grant_type: 'otp',
3885
+ scopes: ['ACCESS_DB'],
3886
+ };
3887
+ }
3888
+ }
3924
3889
  else {
3925
3890
  // Check for available auth providers (OAuth + OTP)
3926
3891
  const socialAuthEnabled = ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.socialAuth) !== false;
@@ -3956,7 +3921,8 @@ function otpFetchTokenCallback(db) {
3956
3921
  const res1 = yield fetch(`${url}/token`, {
3957
3922
  body: JSON.stringify(tokenRequest),
3958
3923
  method: 'post',
3959
- headers: { 'Content-Type': 'application/json', mode: 'cors' },
3924
+ headers: { 'Content-Type': 'application/json' },
3925
+ mode: 'cors',
3960
3926
  });
3961
3927
  if (res1.status !== 200) {
3962
3928
  const errMsg = yield res1.text();
@@ -4026,7 +3992,11 @@ function initiateOAuthRedirect(db, provider) {
4026
3992
  if (!url)
4027
3993
  throw new Error(`No database URL given.`);
4028
3994
  const redirectUri = ((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.oauthRedirectUri) ||
4029
- (typeof window !== 'undefined' ? window.location.href : undefined);
3995
+ (typeof location !== 'undefined' ? location.href : undefined);
3996
+ // CodeRabbit suggested to fail fast here, but the only situation where
3997
+ // redirectUri would be undefined is in non-browser environments, and
3998
+ // in those environments OAuth redirect does not make sense anyway
3999
+ // and will fail fast in startOAuthRedirect().
4030
4000
  // Start OAuth redirect flow - page navigates away
4031
4001
  startOAuthRedirect({
4032
4002
  databaseUrl: url,
@@ -5817,13 +5787,13 @@ const Styles = {
5817
5787
  color: "#3c4043"
5818
5788
  },
5819
5789
  ProviderGitHub: {
5820
- backgroundColor: "#24292e",
5821
- border: "1px solid #24292e",
5822
- color: "#ffffff"
5790
+ backgroundColor: "#ffffff",
5791
+ border: "1px solid #dadce0",
5792
+ color: "#181717"
5823
5793
  },
5824
5794
  ProviderMicrosoft: {
5825
5795
  backgroundColor: "#ffffff",
5826
- border: "1px solid #8c8c8c",
5796
+ border: "1px solid #dadce0",
5827
5797
  color: "#5e5e5e"
5828
5798
  },
5829
5799
  ProviderApple: {
@@ -5832,9 +5802,9 @@ const Styles = {
5832
5802
  color: "#ffffff"
5833
5803
  },
5834
5804
  ProviderCustom: {
5835
- backgroundColor: "#4f46e5",
5836
- border: "1px solid #4f46e5",
5837
- color: "#ffffff"
5805
+ backgroundColor: "#ffffff",
5806
+ border: "1px solid #dadce0",
5807
+ color: "#181717"
5838
5808
  },
5839
5809
  // Divider styles
5840
5810
  Divider: {
@@ -5925,39 +5895,13 @@ function getOptionStyle(styleHint) {
5925
5895
  * Generic button component for selectable options.
5926
5896
  * Displays the option's icon and display name.
5927
5897
  *
5928
- * The icon can be:
5929
- * - Inline SVG (iconSvg) - rendered directly with dangerouslySetInnerHTML
5930
- * - Image URL (iconUrl) - rendered as an img tag
5931
- *
5932
5898
  * Style is determined by the styleHint property for branding purposes.
5933
5899
  */
5934
5900
  function OptionButton({ option, onClick }) {
5935
- const { displayName, iconUrl, iconSvg, styleHint, value } = option;
5901
+ const { displayName, iconUrl, styleHint } = option;
5936
5902
  const style = getOptionStyle(styleHint);
5937
- // Get the text color from the button style for SVG fill processing
5938
- const textColor = style.color || '#000000';
5939
- // Process SVG to replace currentColor with actual text color
5940
- const processedSvg = iconSvg
5941
- ? iconSvg
5942
- .replace(/fill="currentColor"/gi, `fill="${textColor}"`)
5943
- .replace(/fill='currentColor'/gi, `fill='${textColor}'`)
5944
- .replace(/stroke="currentColor"/gi, `stroke="${textColor}"`)
5945
- .replace(/stroke='currentColor'/gi, `stroke='${textColor}'`)
5946
- : null;
5947
- // Render the appropriate icon
5948
- const renderIcon = () => {
5949
- // Inline SVG
5950
- if (processedSvg) {
5951
- return (_$1("span", { style: Styles.ProviderButtonIcon, "aria-hidden": "true", dangerouslySetInnerHTML: { __html: processedSvg } }));
5952
- }
5953
- // Image URL
5954
- if (iconUrl) {
5955
- return (_$1("img", { src: iconUrl, alt: "", style: Styles.ProviderButtonIcon, "aria-hidden": "true" }));
5956
- }
5957
- return null;
5958
- };
5959
5903
  return (_$1("button", { type: "button", style: style, onClick: onClick, class: `dxc-option-btn${styleHint ? ` dxc-option-${styleHint}` : ''}`, "aria-label": displayName },
5960
- renderIcon(),
5904
+ iconUrl && (_$1("img", { src: iconUrl, alt: "", style: Styles.ProviderButtonIcon, "aria-hidden": "true" })),
5961
5905
  _$1("span", { style: Styles.ProviderButtonText }, displayName)));
5962
5906
  }
5963
5907
  /**
@@ -6744,7 +6688,7 @@ function dexieCloud(dexie) {
6744
6688
  const syncComplete = new Subject();
6745
6689
  dexie.cloud = {
6746
6690
  // @ts-ignore
6747
- version: "4.3.4",
6691
+ version: "4.3.6",
6748
6692
  options: Object.assign({}, DEFAULT_OPTIONS),
6749
6693
  schema: null,
6750
6694
  get currentUserId() {
@@ -7014,12 +6958,18 @@ function dexieCloud(dexie) {
7014
6958
  pendingOAuthError = null; // Clear pending error
7015
6959
  console.debug('[dexie-cloud] Showing OAuth error:', error.message);
7016
6960
  // Show alert to user about the OAuth error
7017
- yield alertUser(db.cloud.userInteraction, 'Authentication Error', {
7018
- type: 'error',
7019
- messageCode: 'GENERIC_ERROR',
7020
- message: error.message,
7021
- messageParams: { provider: error.provider || 'unknown' }
7022
- });
6961
+ // Guard so UI errors don't abort initialization
6962
+ try {
6963
+ yield alertUser(db.cloud.userInteraction, 'Authentication Error', {
6964
+ type: 'error',
6965
+ messageCode: 'GENERIC_ERROR',
6966
+ message: error.message,
6967
+ messageParams: { provider: error.provider || 'unknown' }
6968
+ });
6969
+ }
6970
+ catch (uiError) {
6971
+ console.error('[dexie-cloud] Failed to show OAuth error alert:', uiError);
6972
+ }
7023
6973
  }
7024
6974
  // Process pending OAuth callback if present (from dxc-auth redirect)
7025
6975
  if (pendingOAuthCode && !db.cloud.isServiceWorkerDB) {
@@ -7123,7 +7073,7 @@ function dexieCloud(dexie) {
7123
7073
  }
7124
7074
  }
7125
7075
  // @ts-ignore
7126
- dexieCloud.version = "4.3.4";
7076
+ dexieCloud.version = "4.3.6";
7127
7077
  Dexie.Cloud = dexieCloud;
7128
7078
 
7129
7079
  // In case the SW lives for a while, let it reuse already opened connections: