@electric-sql/client throws SecurityError on import when the browser blocks site storage (typeof localStorage outside try)
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- web-dev
Research direction
The guard is in src/expired-shapes-cache.ts (save and load) and src/up-to-date-tracker.ts (save and load), where the typeof localStorage check sits before the try block. Reproduce by defining a throwing getter on globalThis.localStorage and importing the client. Done when the four methods put the availability check inside try and the import no longer throws, with working storage behaving as before.
Written by the indexing model from the issue text.
Description
Bug
When a browser blocks storage for a site (for example Chromium with cookies and site data not allowed for that site), reading window.localStorage throws:
SecurityError: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
ExpiredShapesCache and UpToDateTracker guard their storage access with if (typeof localStorage === 'undefined') return, placed before the try. typeof on a global accessor property still calls the getter, so the guard itself throws. Both classes are instantiated as module-level singletons (expiredShapesCache, upToDateTracker), so importing @electric-sql/client throws. The importing app's module graph fails before any of its own code runs, which leaves a blank page with no chance for an error boundary.
Seen in 1.5.24. The same code is in 1.5.28: src/expired-shapes-cache.ts (save and load) and src/up-to-date-tracker.ts (save and load).
Reproduction
Node (any runner):
Object.defineProperty(globalThis, 'localStorage', {
configurable: true,
get() {
throw new DOMException('Access is denied for this document.', 'SecurityError')
},
})
await import('@electric-sql/client') // throws from ExpiredShapesCache.load
Browser: Chromium with profile.default_content_setting_values.cookies = 2 in the profile's Preferences, then load any page that imports the client.
Suggested fix
Move the availability check inside the existing try in all four methods:
private load(): void {
try {
if (typeof localStorage === `undefined`) return
const stored = localStorage.getItem(this.storageKey)
if (stored) {
this.data = JSON.parse(stored)
}
} catch {
this.data = {}
}
}
The catch blocks already treat storage errors as "no cache", so behavior with working storage stays the same. We currently carry this change as a pnpm patch over 1.5.24.
Possibly related: #3890 (custom localStorage implementation).
- Dominant language
- TypeScript
- Stars
- 10.4k
- Forks
- 391
- Avg merge
- 22h 11m
- Merged PRs (30d)
- 5
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from electric-sql/electric
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
electric-sql/electric#4830 ·
Maintainers usually reply within 1 day
-
agents-chat-starter: spawn PUT goes to the durable-streams proxy, so no entity is created and agents never wakePossibly taken @adityavkk claimed this 100 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
electric-sql/electric#4687 ·
Maintainers usually reply within 1 day
-
Listen IP addressOpenfeature request triage
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
electric-sql/electric#4150 · 1 comment ·
Maintainers usually reply within 1 day
-
ArrayDecoder doesn't handle escaped backslashes in quoted PostgreSQL array elementsPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
electric-sql/electric#3708 ·
Maintainers usually reply within 1 day
-
triage
Difficulty 3/5 1-2 days Newbie friendliness 55/100
electric-sql/electric#4828 ·
Maintainers usually reply within 1 day
All issues in electric-sql/electric
Similar issues
-
ui(ListToolbar): search takes the full row on a phone instead of sharing it with the result countOpen
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
Maintainers usually reply within 1 day
-
priority:high
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
immich-app/immich#32426 ·
Maintainers usually reply within 1 day
-
documentation good first issue hacktoberfest help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
libredb/libredb-studio#1756 ·
Maintainers usually reply within 1 day