Protect local storage calls (#3043)

This PR provides some safe wrappers for local storage calls. Local
storage is not available in all environments (for example, a React
Native web view). The PR also adds an eslint rule preventing direct
calls to local / session storage.

### Change Type

- [x] `patch` — Bug fix

### Release Notes

- Fixes a bug that could cause crashes in React Native webviews.
This commit is contained in:
Steve Ruiz 2024-03-04 13:37:09 +00:00 committed by GitHub
parent 3161e5cb4f
commit 2f28d7c6f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 625 additions and 52 deletions

View file

@ -1,17 +1,15 @@
/**
* What is going on in this file?
*
* We had some bad early assumptions about how we would store documents.
* Which ended up with us generating random persistenceKey strings for the
* 'scratch' document for each user (i.e. each browser context), and storing it in localStorage.
*
* Many users still have that random string in their localStorage so we need to load it. But for new
* users it does not need to be unique and we can just use a constant.
*/
// What is going on in this file?
// We had some bad early assumptions about how we would store documents.
// Which ended up with us generating random persistenceKey strings for the
// 'scratch' document for each user (i.e. each browser context), and storing it in localStorage.
// Many users still have that random string in their localStorage so we need to load it. But for new
// users it does not need to be unique and we can just use a constant.
import { getFromLocalStorage, setInLocalStorage } from 'tldraw'
// DO NOT CHANGE THESE WITHOUT ADDING MIGRATION LOGIC. DOING SO WOULD WIPE ALL EXISTING LOCAL DATA.
const defaultDocumentKey = 'TLDRAW_DEFAULT_DOCUMENT_NAME_v2'
const w = typeof window === 'undefined' ? undefined : window
export const SCRATCH_PERSISTENCE_KEY =
(w?.localStorage.getItem(defaultDocumentKey) as any) ?? 'tldraw_document_v3'
w?.localStorage.setItem(defaultDocumentKey, SCRATCH_PERSISTENCE_KEY)
(getFromLocalStorage(defaultDocumentKey) as any) ?? 'tldraw_document_v3'
setInLocalStorage(defaultDocumentKey, SCRATCH_PERSISTENCE_KEY)

View file

@ -1,4 +1,4 @@
import { T, atom } from 'tldraw'
import { T, atom, getFromLocalStorage, setInLocalStorage } from 'tldraw'
const channel =
typeof BroadcastChannel !== 'undefined' ? new BroadcastChannel('tldrawUserPreferences') : null
@ -37,9 +37,7 @@ function createPreference<Type>(key: string, validator: T.Validator<Type>, defau
}
function loadItemFromStorage<Type>(key: string, validator: T.Validator<Type>): Type | null {
if (typeof localStorage === 'undefined' || !localStorage) return null
const item = localStorage.getItem(`tldrawUserPreferences.${key}`)
const item = getFromLocalStorage(`tldrawUserPreferences.${key}`, null)
if (item == null) return null
try {
return validator.validate(JSON.parse(item))
@ -49,11 +47,5 @@ function loadItemFromStorage<Type>(key: string, validator: T.Validator<Type>): T
}
function saveItemToStorage(key: string, value: unknown): void {
if (typeof localStorage === 'undefined' || !localStorage) return
try {
localStorage.setItem(`tldrawUserPreferences.${key}`, JSON.stringify(value))
} catch (e) {
// not a big deal
}
setInLocalStorage(`tldrawUserPreferences.${key}`, JSON.stringify(value))
}