Skip to content

fix(billing): Guard localStorage access in navBillingStatus and trialStarter#112970

Open
dashed wants to merge 4 commits intomasterfrom
dashed/fix/localstorage-null-guard-nav-billing-status
Open

fix(billing): Guard localStorage access in navBillingStatus and trialStarter#112970
dashed wants to merge 4 commits intomasterfrom
dashed/fix/localstorage-null-guard-nav-billing-status

Conversation

@dashed
Copy link
Copy Markdown
Member

@dashed dashed commented Apr 14, 2026

Closes JAVASCRIPT-38TV

The Bug

On devices where window.localStorage is null — notably Android WebView
without DOM Storage enabled (e.g. Sony BRAVIA smart TVs) — two gsApp
components crash with:

TypeError: Cannot read property 'getItem' of null

Root cause chain:

  1. Android WebView has DOM Storage disabled by default
  2. The embedding app must call WebSettings.setDomStorageEnabled(true) to enable it
  3. When disabled, window.localStorage evaluates to null (not undefined — literally null)
  4. navBillingStatus.tsx and trialStarter.tsx call localStorage.getItem() / .setItem() / .removeItem() directly on this null value

The crash in navBillingStatus.tsx is caught by an ErrorBoundary with
customComponent={null}, so the billing status icon silently disappears.
User impact is minimal but the fix is trivial.

The Fix

Sentry already has a safe localStorage wrapper:

createStorage() → localStorageWrapper → useLocalStorageState

createStorage() tests actual read/write capability at module load. If
localStorage is null or throws, it returns a noopStorage object that
returns null for reads and no-ops for writes. Both components just
weren't using it.

Changes:

  • navBillingStatus.tsx — replace 4 raw localStorage.getItem/setItem calls with localStorageWrapper
  • trialStarter.tsx — replace 1 raw localStorage.removeItem call with localStorageWrapper

Tests

  • navBillingStatus.spec.tsx — 2 tests: component renders/auto-opens and allows dismiss when localStorage is unavailable
  • trialStarter.spec.tsx — 1 test: trial starts successfully when localStorage is unavailable
  • createStorage.spec.tsx (new file) — 3 tests: noopStorage fallback when storage is null, when setItem throws, and happy path

…navBillingStatus

Replace 4 raw localStorage.getItem/setItem calls with localStorageWrapper
from sentry/utils/localStorage. On devices where window.localStorage is
null (e.g. Android WebView without DOM Storage enabled), raw access throws
TypeError. localStorageWrapper safely falls back to a noopStorage that
returns null for reads and no-ops for writes.

Add unit and integration tests verifying the component renders, auto-opens,
and allows dismiss interaction when localStorage is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Apr 14, 2026
@sentry
Copy link
Copy Markdown
Contributor

sentry bot commented Apr 14, 2026

Sentry Snapshot Testing

Name Added Removed Modified Renamed Unchanged Status
sentry-frontend
sentry-frontend
0 0 0 0 204 ✅ Unchanged

Consolidate 3 redundant unit tests into 1 focused test that covers
render, auto-open, and setItem gracefully. Add afterEach with
jest.restoreAllMocks() to prevent mockReturnValue leaking across
describe blocks (clearMocks only resets call history, not implementations).

Move createStorage utility tests to a dedicated createStorage.spec.tsx
file — they test shared utility behavior, not component behavior.
Remove implementation-detail test that only verified call arguments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…trialStarter

Replace raw localStorage.removeItem call with localStorageWrapper.removeItem.
On devices where window.localStorage is null (e.g. Android WebView without
DOM Storage enabled), raw access throws TypeError. localStorageWrapper safely
falls back to noopStorage.

Add test verifying trial start succeeds when localStorage is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The jest.mock spread of localStorageWrapper missed prototype methods
like removeItem because native Storage objects don't have own-property
methods. The "Start Trial" button in billing status tests triggers
trialStarter which calls localStorageWrapper.removeItem(), causing
TypeError in CI. Explicitly mock all Storage methods as pass-throughs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dashed dashed changed the title fix(billing): Use localStorageWrapper instead of raw localStorage in navBillingStatus fix(billing): Guard localStorage access in navBillingStatus and trialStarter Apr 14, 2026
@dashed dashed marked this pull request as ready for review April 14, 2026 21:09
@dashed dashed requested review from a team as code owners April 14, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant