Fix localStorage crash
## Ticket 1572 Always check for availability before use. ## Changes - Consolidated the keys into one place for easier tracking. - It'll also be easier to code using constant autocomplete. - Cleaned up the wrapper to be as close as the original. - Updated existing code to use the wrapper (even if they already handled the availability) to encourage future code to just use the wrapper.
This commit is contained in:
parent
3ed05e62a5
commit
a73694deb4
9 changed files with 67 additions and 46 deletions
|
@ -46,8 +46,6 @@ export const GA_DIMENSIONS = {
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev');
|
const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev');
|
||||||
|
|
||||||
export const SHARE_INTERNAL = 'shareInternal';
|
|
||||||
|
|
||||||
const WATCHMAN_BACKEND_ENDPOINT = 'https://watchman.na-backend.odysee.com/reports/playback';
|
const WATCHMAN_BACKEND_ENDPOINT = 'https://watchman.na-backend.odysee.com/reports/playback';
|
||||||
const SEND_DATA_TO_WATCHMAN_INTERVAL = 10; // in seconds
|
const SEND_DATA_TO_WATCHMAN_INTERVAL = 10; // in seconds
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
// Local Storage keys
|
|
||||||
export const TUS_LOCKED_UPLOADS = 'tusLockedUploads';
|
|
||||||
export const TUS_REMOVED_UPLOADS = 'tusRemovedUploads';
|
|
||||||
export const TUS_REFRESH_LOCK = 'tusRefreshLock';
|
|
|
@ -45,8 +45,9 @@ import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { doSetPrefsReady, doPreferenceGet, doPopulateSharedUserState, syncInvalidated } from 'redux/actions/sync';
|
import { doSetPrefsReady, doPreferenceGet, doPopulateSharedUserState, syncInvalidated } from 'redux/actions/sync';
|
||||||
import { doAuthenticate } from 'redux/actions/user';
|
import { doAuthenticate } from 'redux/actions/user';
|
||||||
import { lbrySettings as config, version as appVersion } from 'package.json';
|
import { lbrySettings as config, version as appVersion } from 'package.json';
|
||||||
import analytics, { SHARE_INTERNAL } from 'analytics';
|
import analytics from 'analytics';
|
||||||
import { doSignOutCleanup } from 'util/saved-passwords';
|
import { doSignOutCleanup } from 'util/saved-passwords';
|
||||||
|
import { LocalStorage, LS } from 'util/storage';
|
||||||
import { doNotificationSocketConnect } from 'redux/actions/websocket';
|
import { doNotificationSocketConnect } from 'redux/actions/websocket';
|
||||||
import { stringifyServerParam, shouldSetSetting } from 'util/sync-settings';
|
import { stringifyServerParam, shouldSetSetting } from 'util/sync-settings';
|
||||||
|
|
||||||
|
@ -348,7 +349,7 @@ export function doDaemonReady() {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
// TODO: call doFetchDaemonSettings, then get usage data, and call doAuthenticate once they are loaded into the store
|
// TODO: call doFetchDaemonSettings, then get usage data, and call doAuthenticate once they are loaded into the store
|
||||||
const shareUsageData = IS_WEB || window.localStorage.getItem(SHARE_INTERNAL) === 'true';
|
const shareUsageData = IS_WEB || LocalStorage.getItem(LS.SHARE_INTERNAL) === 'true';
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
doAuthenticate(
|
doAuthenticate(
|
||||||
|
|
|
@ -10,11 +10,9 @@ import {
|
||||||
filterUpcomingLiveStreamClaims,
|
filterUpcomingLiveStreamClaims,
|
||||||
} from 'util/livestream';
|
} from 'util/livestream';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { isLocalStorageAvailable } from 'util/storage';
|
import { LocalStorage, LS } from 'util/storage';
|
||||||
import { isEmpty } from 'util/object';
|
import { isEmpty } from 'util/object';
|
||||||
|
|
||||||
const localStorageAvailable = isLocalStorageAvailable();
|
|
||||||
|
|
||||||
export const doFetchNoSourceClaims = (channelId: string) => async (dispatch: Dispatch, getState: GetState) => {
|
export const doFetchNoSourceClaims = (channelId: string) => async (dispatch: Dispatch, getState: GetState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.FETCH_NO_SOURCE_CLAIMS_STARTED,
|
type: ACTIONS.FETCH_NO_SOURCE_CLAIMS_STARTED,
|
||||||
|
@ -118,20 +116,20 @@ const findActiveStreams = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doFetchChannelLiveStatus = (channelId: string) => async (dispatch: Dispatch) => {
|
export const doFetchChannelLiveStatus = (channelId: string) => async (dispatch: Dispatch) => {
|
||||||
const statusForId = `channel-live-status`;
|
const statusForId = LS.CHANNEL_LIVE_STATUS;
|
||||||
const localStatus = localStorageAvailable && window.localStorage.getItem(statusForId);
|
const localStatus = LocalStorage.getItem(statusForId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { channelStatus, channelData } = await fetchLiveChannel(channelId);
|
const { channelStatus, channelData } = await fetchLiveChannel(channelId);
|
||||||
// store live state locally, and force 2 non-live statuses before returninig NOT LIVE. This allows for the stream to finish before disposing player.
|
// store live state locally, and force 2 non-live statuses before returninig NOT LIVE. This allows for the stream to finish before disposing player.
|
||||||
if (localStatus === LiveStatus.LIVE && channelStatus === LiveStatus.NOT_LIVE) {
|
if (localStatus === LiveStatus.LIVE && channelStatus === LiveStatus.NOT_LIVE) {
|
||||||
localStorageAvailable && window.localStorage.removeItem(statusForId);
|
LocalStorage.removeItem(statusForId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelStatus === LiveStatus.NOT_LIVE && !localStatus) {
|
if (channelStatus === LiveStatus.NOT_LIVE && !localStatus) {
|
||||||
dispatch({ type: ACTIONS.REMOVE_CHANNEL_FROM_ACTIVE_LIVESTREAMS, data: { channelId } });
|
dispatch({ type: ACTIONS.REMOVE_CHANNEL_FROM_ACTIVE_LIVESTREAMS, data: { channelId } });
|
||||||
localStorageAvailable && window.localStorage.removeItem(statusForId);
|
LocalStorage.removeItem(statusForId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,10 +146,10 @@ export const doFetchChannelLiveStatus = (channelId: string) => async (dispatch:
|
||||||
dispatch({ type: ACTIONS.ADD_CHANNEL_TO_ACTIVE_LIVESTREAMS, data: { ...channelData } });
|
dispatch({ type: ACTIONS.ADD_CHANNEL_TO_ACTIVE_LIVESTREAMS, data: { ...channelData } });
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorageAvailable && window.localStorage.setItem(statusForId, channelStatus);
|
LocalStorage.setItem(statusForId, channelStatus);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch({ type: ACTIONS.REMOVE_CHANNEL_FROM_ACTIVE_LIVESTREAMS, data: { channelId } });
|
dispatch({ type: ACTIONS.REMOVE_CHANNEL_FROM_ACTIVE_LIVESTREAMS, data: { channelId } });
|
||||||
localStorageAvailable && window.localStorage.removeItem(statusForId);
|
LocalStorage.removeItem(statusForId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ import rewards from 'rewards';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
import { DOMAIN, LOCALE_API } from 'config';
|
import { DOMAIN, LOCALE_API } from 'config';
|
||||||
import { getDefaultLanguage } from 'util/default-languages';
|
import { getDefaultLanguage } from 'util/default-languages';
|
||||||
|
import { LocalStorage, LS } from 'util/storage';
|
||||||
|
|
||||||
const AUTH_IN_PROGRESS = 'authInProgress';
|
|
||||||
export let sessionStorageAvailable = false;
|
export let sessionStorageAvailable = false;
|
||||||
const CHECK_INTERVAL = 200;
|
const CHECK_INTERVAL = 200;
|
||||||
const AUTH_WAIT_TIMEOUT = 10000;
|
const AUTH_WAIT_TIMEOUT = 10000;
|
||||||
|
@ -91,9 +91,9 @@ function checkAuthBusy() {
|
||||||
if (!IS_WEB || !sessionStorageAvailable) {
|
if (!IS_WEB || !sessionStorageAvailable) {
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
const inProgress = window.sessionStorage.getItem(AUTH_IN_PROGRESS);
|
const inProgress = LocalStorage.getItem(LS.AUTH_IN_PROGRESS);
|
||||||
if (!inProgress) {
|
if (!inProgress) {
|
||||||
window.sessionStorage.setItem(AUTH_IN_PROGRESS, 'true');
|
LocalStorage.setItem(LS.AUTH_IN_PROGRESS, 'true');
|
||||||
return resolve();
|
return resolve();
|
||||||
} else {
|
} else {
|
||||||
if (Date.now() - time < AUTH_WAIT_TIMEOUT) {
|
if (Date.now() - time < AUTH_WAIT_TIMEOUT) {
|
||||||
|
@ -175,7 +175,7 @@ export function doAuthenticate(
|
||||||
return Lbryio.authenticate(DOMAIN, getDefaultLanguage());
|
return Lbryio.authenticate(DOMAIN, getDefaultLanguage());
|
||||||
})
|
})
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS);
|
LocalStorage.removeItem(LS.AUTH_IN_PROGRESS);
|
||||||
Lbryio.getAuthToken().then((token) => {
|
Lbryio.getAuthToken().then((token) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.AUTHENTICATION_SUCCESS,
|
type: ACTIONS.AUTHENTICATION_SUCCESS,
|
||||||
|
@ -199,7 +199,7 @@ export function doAuthenticate(
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS);
|
LocalStorage.removeItem(LS.AUTH_IN_PROGRESS);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.AUTHENTICATION_FAILURE,
|
type: ACTIONS.AUTHENTICATION_FAILURE,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import { LocalStorage } from 'util/storage';
|
||||||
|
|
||||||
export const selectState = (state) => state.user || {};
|
export const selectState = (state) => state.user || {};
|
||||||
|
|
||||||
|
@ -126,7 +127,7 @@ export const selectOdyseeMembershipIsPremiumPlus = (state) => {
|
||||||
*/
|
*/
|
||||||
export const selectHasOdyseeMembership = (state) => {
|
export const selectHasOdyseeMembership = (state) => {
|
||||||
// @if process.env.NODE_ENV!='production'
|
// @if process.env.NODE_ENV!='production'
|
||||||
const override = window.localStorage.getItem('hasMembershipOverride');
|
const override = LocalStorage.getItem('hasMembershipOverride');
|
||||||
if (override) return override === 'true';
|
if (override) return override === 'true';
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,21 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
|
export const LS = Object.freeze({
|
||||||
|
AUTH_IN_PROGRESS: 'authInProgress',
|
||||||
|
CHANNEL_LIVE_STATUS: 'channel-live-status',
|
||||||
|
GDPR_REQUIRED: 'gdprRequired', // <-- should be using 'locale/get', right?
|
||||||
|
SHARE_INTERNAL: 'shareInternal',
|
||||||
|
TUS_LOCKED_UPLOADS: 'tusLockedUploads',
|
||||||
|
TUS_REFRESH_LOCK: 'tusRefreshLock',
|
||||||
|
TUS_REMOVED_UPLOADS: 'tusRemovedUploads',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
export function isLocalStorageAvailable() {
|
export function isLocalStorageAvailable() {
|
||||||
try {
|
try {
|
||||||
return Boolean(window.localStorage);
|
return Boolean(window.localStorage);
|
||||||
|
@ -24,14 +42,23 @@ export function getLocalStorageSummary() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// LocalStorage (wrapper for 'window.localStorage')
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
|
// This assumes that local storage availability never changes after boot.
|
||||||
const localStorageAvailable = isLocalStorageAvailable();
|
const localStorageAvailable = isLocalStorageAvailable();
|
||||||
|
|
||||||
export function getLocalStorageItem(key) {
|
export const LocalStorage = {
|
||||||
return localStorageAvailable ? window.localStorage.getItem(key) : undefined;
|
setItem: (key: string, value: string) => {
|
||||||
}
|
if (localStorageAvailable) window.localStorage.setItem(key, value);
|
||||||
|
},
|
||||||
|
|
||||||
export function setLocalStorageItem(key, value) {
|
getItem: (key: string) => {
|
||||||
if (localStorageAvailable) {
|
return localStorageAvailable ? window.localStorage.getItem(key) : undefined;
|
||||||
window.localStorage.setItem(key, value);
|
},
|
||||||
}
|
|
||||||
}
|
removeItem: (key: string) => {
|
||||||
|
if (localStorageAvailable) window.localStorage.removeItem(key);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { TUS_LOCKED_UPLOADS, TUS_REFRESH_LOCK, TUS_REMOVED_UPLOADS } from 'constants/storage';
|
import { isLocalStorageAvailable, LocalStorage, LS } from 'util/storage';
|
||||||
import { isLocalStorageAvailable } from 'util/storage';
|
|
||||||
import { doUpdateUploadRemove, doUpdateUploadProgress } from 'redux/actions/publish';
|
import { doUpdateUploadRemove, doUpdateUploadProgress } from 'redux/actions/publish';
|
||||||
|
|
||||||
const localStorageAvailable = isLocalStorageAvailable();
|
const localStorageAvailable = isLocalStorageAvailable();
|
||||||
|
@ -32,7 +31,7 @@ function getTabId() {
|
||||||
|
|
||||||
function getLockedUploads() {
|
function getLockedUploads() {
|
||||||
if (localStorageAvailable) {
|
if (localStorageAvailable) {
|
||||||
const storedValue = window.localStorage.getItem(TUS_LOCKED_UPLOADS);
|
const storedValue = LocalStorage.getItem(LS.TUS_LOCKED_UPLOADS);
|
||||||
return storedValue ? JSON.parse(storedValue) : {};
|
return storedValue ? JSON.parse(storedValue) : {};
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
@ -47,7 +46,7 @@ export function tusLockAndNotify(guid: string) {
|
||||||
const lockedUploads = getLockedUploads();
|
const lockedUploads = getLockedUploads();
|
||||||
if (!lockedUploads[guid] && localStorageAvailable) {
|
if (!lockedUploads[guid] && localStorageAvailable) {
|
||||||
lockedUploads[guid] = getTabId();
|
lockedUploads[guid] = getTabId();
|
||||||
window.localStorage.setItem(TUS_LOCKED_UPLOADS, JSON.stringify(lockedUploads));
|
LocalStorage.setItem(LS.TUS_LOCKED_UPLOADS, JSON.stringify(lockedUploads));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +74,9 @@ export function tusUnlockAndNotify(guid?: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(lockedUploads).length > 0) {
|
if (Object.keys(lockedUploads).length > 0) {
|
||||||
window.localStorage.setItem(TUS_LOCKED_UPLOADS, JSON.stringify(lockedUploads));
|
LocalStorage.setItem(LS.TUS_LOCKED_UPLOADS, JSON.stringify(lockedUploads));
|
||||||
} else {
|
} else {
|
||||||
window.localStorage.removeItem(TUS_LOCKED_UPLOADS);
|
LocalStorage.removeItem(LS.TUS_LOCKED_UPLOADS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +86,7 @@ export function tusUnlockAndNotify(guid?: string) {
|
||||||
|
|
||||||
function getRemovedUploads() {
|
function getRemovedUploads() {
|
||||||
if (localStorageAvailable) {
|
if (localStorageAvailable) {
|
||||||
const storedValue = window.localStorage.getItem(TUS_REMOVED_UPLOADS);
|
const storedValue = LocalStorage.getItem(LS.TUS_REMOVED_UPLOADS);
|
||||||
return storedValue ? storedValue.split(',') : [];
|
return storedValue ? storedValue.split(',') : [];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
@ -97,18 +96,18 @@ export function tusRemoveAndNotify(guid: string) {
|
||||||
if (!localStorageAvailable) return;
|
if (!localStorageAvailable) return;
|
||||||
const removedUploads = getRemovedUploads();
|
const removedUploads = getRemovedUploads();
|
||||||
removedUploads.push(guid);
|
removedUploads.push(guid);
|
||||||
window.localStorage.setItem(TUS_REMOVED_UPLOADS, removedUploads.join(','));
|
LocalStorage.setItem(LS.TUS_REMOVED_UPLOADS, removedUploads.join(','));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tusClearRemovedUploads() {
|
export function tusClearRemovedUploads() {
|
||||||
if (!localStorageAvailable) return;
|
if (!localStorageAvailable) return;
|
||||||
window.localStorage.removeItem(TUS_REMOVED_UPLOADS);
|
LocalStorage.removeItem(LS.TUS_REMOVED_UPLOADS);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tusClearLockedUploads() {
|
export function tusClearLockedUploads() {
|
||||||
if (!localStorageAvailable) return;
|
if (!localStorageAvailable) return;
|
||||||
window.localStorage.removeItem(TUS_LOCKED_UPLOADS);
|
LocalStorage.removeItem(LS.TUS_LOCKED_UPLOADS);
|
||||||
window.localStorage.setItem(TUS_REFRESH_LOCK, Math.random());
|
LocalStorage.setItem(LS.TUS_REFRESH_LOCK, String(Math.random()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
@ -117,17 +116,17 @@ export function tusClearLockedUploads() {
|
||||||
|
|
||||||
export function tusHandleTabUpdates(storageKey: string) {
|
export function tusHandleTabUpdates(storageKey: string) {
|
||||||
switch (storageKey) {
|
switch (storageKey) {
|
||||||
case TUS_LOCKED_UPLOADS:
|
case LS.TUS_LOCKED_UPLOADS:
|
||||||
// The locked IDs are in localStorage, but related GUI is unaware.
|
// The locked IDs are in localStorage, but related GUI is unaware.
|
||||||
// Send a redux update to force an update.
|
// Send a redux update to force an update.
|
||||||
window.store.dispatch(doUpdateUploadProgress({ guid: 'force--update' }));
|
window.store.dispatch(doUpdateUploadProgress({ guid: 'force--update' }));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TUS_REFRESH_LOCK:
|
case LS.TUS_REFRESH_LOCK:
|
||||||
window.store.dispatch(doUpdateUploadProgress({ guid: 'refresh--lock' }));
|
window.store.dispatch(doUpdateUploadProgress({ guid: 'refresh--lock' }));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TUS_REMOVED_UPLOADS:
|
case LS.TUS_REMOVED_UPLOADS:
|
||||||
// The other tab's store has removed this upload, so it's safe to do the
|
// The other tab's store has removed this upload, so it's safe to do the
|
||||||
// same without affecting rehydration.
|
// same without affecting rehydration.
|
||||||
if (localStorageAvailable) {
|
if (localStorageAvailable) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import classnames from 'classnames';
|
||||||
import { platform } from 'util/platform';
|
import { platform } from 'util/platform';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
import { LocalStorage, LS } from 'util/storage';
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const AD_CONFIGS = Object.freeze({
|
const AD_CONFIGS = Object.freeze({
|
||||||
|
@ -64,7 +65,7 @@ function Ads(props: Props) {
|
||||||
const mobileAds = platform.isAndroid() || platform.isIOS();
|
const mobileAds = platform.isAndroid() || platform.isIOS();
|
||||||
|
|
||||||
// this is populated from app based on location
|
// this is populated from app based on location
|
||||||
const isInEu = localStorage.getItem('gdprRequired') === 'true';
|
const isInEu = LocalStorage.getItem(LS.GDPR_REQUIRED) === 'true';
|
||||||
const adConfig = isInEu ? AD_CONFIGS.EU : mobileAds ? AD_CONFIGS.MOBILE : AD_CONFIGS.DEFAULT;
|
const adConfig = isInEu ? AD_CONFIGS.EU : mobileAds ? AD_CONFIGS.MOBILE : AD_CONFIGS.DEFAULT;
|
||||||
|
|
||||||
function resolveAdVisibility() {
|
function resolveAdVisibility() {
|
||||||
|
|
Loading…
Reference in a new issue