Skip to content

Commit 0b7bbad

Browse files
authored
fix(dexie-cloud): allow anonymous blob download + fix SW Dexie.ignoreTransaction crash (#2287)
* fix(dexie-cloud): allow anonymous blob download and fix SW Dexie.ignoreTransaction crash Two bugs fixed: 1. BlobDownloadTracker: Remove hard requirement for access token. Public realm (rlm-public) blobs must be downloadable without auth. downloadBlob() now omits the Authorization header when token is null, letting the server decide whether to allow anonymous access. 2. loadCachedAccessToken: Skip Dexie.ignoreTransaction() when user is not logged in. Previously this always fell through to a DB lookup via Dexie.ignoreTransaction() even for unauthenticated users who clearly have no token to find. This caused a crash in service worker context where PSD.transless.env is undefined when called from within an active rw transaction (e.g. during applyServerChanges after sync). The fix: if currentUser.isLoggedIn is falsy, return null immediately without touching Dexie.ignoreTransaction. * fix(dexie): guard Dexie.ignoreTransaction against undefined PSD.transless PSD.transless can be undefined even when PSD.trans is truthy (e.g. in service worker context). This caused a crash in switchToZone when accessing targetZone.env on the undefined transless PSD. Fix: check both PSD.trans AND PSD.transless before calling usePSD. If transless is undefined, fall through to calling scopeFunc() directly (same as no-transaction case). * fix(dexie): use PSD.transless || globalPSD in ignoreTransaction Better approach: instead of skipping usePSD when transless is undefined, fall back to globalPSD. This ensures the zone switch always happens with a valid PSD that has proper env properties for promise patching.
1 parent 7d3535e commit 0b7bbad

3 files changed

Lines changed: 23 additions & 11 deletions

File tree

addons/dexie-cloud/src/sync/BlobDownloadTracker.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ export class BlobDownloadTracker {
3131
if (!promise) {
3232
promise = loadCachedAccessToken(this.db)
3333
.then((accessToken) => {
34-
if (!accessToken)
35-
throw new Error('No access token available for blob download');
34+
// accessToken may be null for anonymous/unauthenticated users.
35+
// Public realm blobs (rlm-public) are accessible without auth.
36+
// downloadBlob will omit the Authorization header when token is null.
3637
return downloadBlob(blobRef, dbUrl, accessToken);
3738
})
3839
.finally(() => this.inFlight.delete(blobRef.ref));
@@ -45,23 +46,26 @@ export class BlobDownloadTracker {
4546
/**
4647
* Download blob data from server via proxy endpoint.
4748
* Uses auth header for authentication (same as sync).
49+
* When accessToken is null, the request is made without Authorization header —
50+
* this allows downloading blobs from public realms (rlm-public) for
51+
* unauthenticated users.
4852
*
4953
* @param blobRef - The BlobRef to download
5054
* @param dbUrl - Base URL for the database (e.g., 'https://mydb.dexie.cloud')
51-
* @param accessToken - Access token for authentication
55+
* @param accessToken - Access token for authentication, or null for anonymous access
5256
*/
5357

5458
export async function downloadBlob(
5559
blobRef: BlobRef,
5660
dbUrl: string,
57-
accessToken: string
61+
accessToken: string | null
5862
): Promise<Uint8Array> {
5963
const downloadUrl = `${dbUrl}/blob/${blobRef.ref}`;
60-
const response = await fetch(downloadUrl, {
61-
headers: {
62-
Authorization: `Bearer ${accessToken}`,
63-
},
64-
});
64+
const headers: HeadersInit = {};
65+
if (accessToken) {
66+
headers['Authorization'] = `Bearer ${accessToken}`;
67+
}
68+
const response = await fetch(downloadUrl, { headers });
6569

6670
if (!response.ok) {
6771
throw new Error(

addons/dexie-cloud/src/sync/loadCachedAccessToken.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ export function loadCachedAccessToken(
2727
});
2828
return Promise.resolve(currentUser.accessToken);
2929
}
30+
// If the current user is not logged in (no isLoggedIn flag), there's no
31+
// token to load from the database — skip the Dexie.ignoreTransaction() call.
32+
// This avoids a crash in service worker context where Dexie's Promise zone
33+
// (PSD.transless.env) may be undefined when called from within an active
34+
// rw transaction (e.g. during applyServerChanges).
35+
if (!currentUser?.isLoggedIn) {
36+
return Promise.resolve(null);
37+
}
3038
return Dexie.ignoreTransaction(() =>
3139
loadAccessToken(db).then((user) => {
3240
if (user?.accessToken) {

src/classes/dexie/dexie-static-props.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { getObjectDiff } from '../../functions/get-object-diff';
1616
import { fullNameExceptions } from '../../errors';
1717
import { DexieConstructor } from '../../public/types/dexie-constructor';
1818
import { getDatabaseNames } from '../../helpers/database-enumerator';
19-
import { PSD } from '../../helpers/promise';
19+
import { PSD, globalPSD } from '../../helpers/promise';
2020
import { usePSD } from '../../helpers/promise';
2121
import { newScope } from '../../helpers/promise';
2222
import { rejection } from '../../helpers/promise';
@@ -125,7 +125,7 @@ props(Dexie, {
125125
// 3) setImmediate() is not supported in the ES standard.
126126
// 4) You might want to keep other PSD state that was set in a parent PSD, such as PSD.letThrough.
127127
return PSD.trans
128-
? usePSD(PSD.transless, scopeFunc) // Use the closest parent that was non-transactional.
128+
? usePSD(PSD.transless || globalPSD, scopeFunc) // Use the closest parent that was non-transactional.
129129
: scopeFunc(); // No need to change scope because there is no ongoing transaction.
130130
},
131131

0 commit comments

Comments
 (0)