Skip to content

Commit

Permalink
[front] fix: check localStorage is available on app load before read/…
Browse files Browse the repository at this point in the history
…write
  • Loading branch information
amatissart committed Sep 19, 2024
1 parent 6db155a commit 326bd91
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 61 deletions.
13 changes: 13 additions & 0 deletions frontend/src/app/localStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function getLocalStorage() {
try {
const key = '__checking_local_storage__';
localStorage.setItem(key, key);
localStorage.removeItem(key);
return localStorage;
} catch (e) {
console.error(`Cannot to use localstorage: ${e}`);
return null;
}
}

export const storage = getLocalStorage();
8 changes: 2 additions & 6 deletions frontend/src/features/frame/Frame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import clsx from 'clsx';
import makeStyles from '@mui/styles/makeStyles';
import { Box } from '@mui/material';
import { storage } from 'src/app/localStorage';
import TopBar, { topBarHeight } from './components/topbar/TopBar';
import Footer from './components/footer/Footer';
import SideBar from './components/sidebar/SideBar';
Expand Down Expand Up @@ -52,12 +53,7 @@ const hasLocalStorageAccess = async () => {
if (hasStorageAccess != null) {
return hasStorageAccess;
}
try {
localStorage;
return true;
} catch (err) {
return false;
}
return storage != null;
};

const applyEmbeddedStyle = () => {
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/features/frame/components/topbar/PwaBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { useState } from 'react';
import { storage } from 'src/app/localStorage';
import { BeforeInstallPromptEvent } from '../../pwaPrompt';
import { Avatar, Button, Grid, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';

const pwaBannerIgnoredKey = 'pwaBannerIgnoredAt';

const hasPwaBannerBeenIgnored = () => {
try {
const value = localStorage.getItem(pwaBannerIgnoredKey);
return value != null;
} catch {
if (!storage) {
// Avoid showing the banner when ignore action can't be persisted
return true;
}
return storage.getItem(pwaBannerIgnoredKey) != null;
};

interface Props {
Expand Down Expand Up @@ -53,7 +53,7 @@ const PwaBanner = ({ beforeInstallPromptEvent }: Props) => {
color="inherit"
onClick={() => {
setPwaBannerVisible(false);
localStorage.setItem(pwaBannerIgnoredKey, new Date().toISOString());
storage?.setItem(pwaBannerIgnoredKey, new Date().toISOString());
}}
>
{t('pwaBanner.ignore')}
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/hooks/useCurrentPoll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React, {
useMemo,
} from 'react';
import { useTranslation } from 'react-i18next';
import { storage } from 'src/app/localStorage';
import { PollsService, Poll } from 'src/services/openapi';
import { LAST_POLL_NAME_STORAGE_KEY, polls } from 'src/utils/constants';
import { YOUTUBE_POLL_NAME } from 'src/utils/constants';
Expand Down Expand Up @@ -71,9 +72,7 @@ export const PollProvider = ({ children }: { children: React.ReactNode }) => {

useEffect(() => {
// Persist the last poll in localStorage for future sessions (after signup, etc.)
if (localStorage) {
localStorage.setItem(LAST_POLL_NAME_STORAGE_KEY, contextValue.name);
}
storage?.setItem(LAST_POLL_NAME_STORAGE_KEY, contextValue.name);
}, [contextValue.name]);

useEffect(() => {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/hooks/useLastPoll.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useEffect } from 'react';
import { storage } from 'src/app/localStorage';
import { LAST_POLL_NAME_STORAGE_KEY, polls } from 'src/utils/constants';
import { useCurrentPoll } from './useCurrentPoll';

const lastSessionPollName = localStorage?.getItem(LAST_POLL_NAME_STORAGE_KEY);
const lastSessionPollName = storage?.getItem(LAST_POLL_NAME_STORAGE_KEY);

const useLastPoll = () => {
// Hook that will activate the last poll that was persisted
Expand Down
49 changes: 28 additions & 21 deletions frontend/src/utils/comparison/pending.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { storage } from 'src/app/localStorage';
import { PollCriteria } from 'src/services/openapi';
import { CriteriaValuesType } from 'src/utils/types';

Expand Down Expand Up @@ -53,20 +54,20 @@ export const setPendingRating = (
criterion: string,
rating: number
) => {
let pending = localStorage.getItem(PENDING_NS);

if (!storage) {
return;
}
let pending = storage.getItem(PENDING_NS);
if (pending == null) {
pending = initPending();
}

if (keyIsInvalid(poll, uidA, uidB, criterion)) {
return null;
return;
}

const pendingJSON = JSON.parse(pending);
pendingJSON[makePendingKey(poll, uidA, uidB, criterion)] = rating;

localStorage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
storage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
};

/**
Expand All @@ -83,10 +84,12 @@ export const getPendingRating = (
uidA: string,
uidB: string,
criterion: string,
pop?: boolean
pop = false
): number | null => {
const pending = localStorage.getItem(PENDING_NS) ?? initPending();

if (!storage) {
return null;
}
const pending = storage.getItem(PENDING_NS) ?? initPending();
if (pendingIsEmpty(pending)) {
return null;
}
Expand All @@ -97,12 +100,11 @@ export const getPendingRating = (

const pendingJSON = JSON.parse(pending);
const pendingKey = makePendingKey(poll, uidA, uidB, criterion);

const rating = pendingJSON[pendingKey];

if (pop) {
delete pendingJSON[pendingKey];
localStorage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
storage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
}

return rating ?? null;
Expand All @@ -121,7 +123,10 @@ export const clearPendingRating = (
uidB: string,
criterion: string
) => {
const pending = localStorage.getItem(PENDING_NS) ?? initPending();
if (!storage) {
return;
}
const pending = storage.getItem(PENDING_NS) ?? initPending();

if (pendingIsEmpty(pending)) {
return;
Expand All @@ -133,7 +138,7 @@ export const clearPendingRating = (

const pendingJSON = JSON.parse(pending);
delete pendingJSON[makePendingKey(poll, uidA, uidB, criterion)];
localStorage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
storage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
};

/**
Expand All @@ -150,11 +155,10 @@ export const getAllPendingRatings = (
uidA: string,
uidB: string,
criterias: PollCriteria[],
pop?: boolean
pop = false
): CriteriaValuesType => {
const pendingCriteria: CriteriaValuesType = {};

const pending = localStorage.getItem(PENDING_NS) ?? initPending();
const pending = storage?.getItem(PENDING_NS) ?? initPending();

if (pendingIsEmpty(pending)) {
return {};
Expand Down Expand Up @@ -183,7 +187,7 @@ export const getAllPendingRatings = (
}
});

localStorage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
storage?.setItem(PENDING_NS, JSON.stringify(pendingJSON));
return pendingCriteria;
};

Expand All @@ -200,7 +204,10 @@ export const clearAllPendingRatings = (
uidB: string,
criterias: PollCriteria[]
) => {
const pending = localStorage.getItem(PENDING_NS) ?? initPending();
if (!storage) {
return;
}
const pending = storage.getItem(PENDING_NS) ?? initPending();

if (pendingIsEmpty(pending)) {
return;
Expand All @@ -216,13 +223,13 @@ export const clearAllPendingRatings = (

const pendingJSON = JSON.parse(pending);

criterias.map((criterion) => {
criterias.forEach((criterion) => {
delete pendingJSON[makePendingKey(poll, uidA, uidB, criterion.name)];
});

localStorage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
storage.setItem(PENDING_NS, JSON.stringify(pendingJSON));
};

export const resetPendingRatings = () => {
localStorage.setItem(PENDING_NS, initPending());
storage?.setItem(PENDING_NS, initPending());
};
17 changes: 7 additions & 10 deletions frontend/src/utils/comparisonSeries/skip.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { storage } from 'src/app/localStorage';

/**
* The day we will have different components using the skippedBy state, we
* should consider using Redux instead of our custom localstorage accessors.
Expand All @@ -16,9 +18,9 @@ const skippedByIsEmpty = (skippedBy: string) => {
* @param username The username of the user.
*/
export const setSkippedBy = (skipKey: string, username: string) => {
const skippedBy = localStorage.getItem(skipKey) ?? INITAL_SKIPPED_BY;
let users: Array<string>;
const skippedBy = storage?.getItem(skipKey) ?? INITAL_SKIPPED_BY;

let users: Array<string>;
if (skippedByIsEmpty(skippedBy)) {
users = [];
} else {
Expand All @@ -29,25 +31,20 @@ export const setSkippedBy = (skipKey: string, username: string) => {
users.push(username);
}

localStorage.setItem(skipKey, users.join(','));
storage?.setItem(skipKey, users.join(','));
};

/**
* Return true if the comparison series identified by `skipKey` has been
* skipped by the user.
*/
export const getSkippedBy = (skipKey: string, username: string): boolean => {
const skippedBy = localStorage.getItem(skipKey) ?? INITAL_SKIPPED_BY;
const skippedBy = storage?.getItem(skipKey) ?? INITAL_SKIPPED_BY;

if (skippedByIsEmpty(skippedBy)) {
return false;
}

const users = skippedBy.split(',');

if (users.includes(username)) {
return true;
}

return false;
return users.includes(username);
};
18 changes: 6 additions & 12 deletions frontend/src/utils/entityContexts/collapsed.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import { storage } from 'src/app/localStorage';

// Name of the key used in the browser's local storage.
const COLLAPSED_NS = 'entityContextsCollapsed';

const initCollapsed = () => '{}';

const collapsedIsEmpty = (collapsed: string) => {
return collapsed == null || collapsed === initCollapsed();
};

export const setCollapsedState = (uid: string, state: boolean) => {
let collapsed = localStorage.getItem(COLLAPSED_NS);

let collapsed = storage?.getItem(COLLAPSED_NS);
if (collapsed == null) {
collapsed = initCollapsed();
}

const collapsedJSON = JSON.parse(collapsed);
collapsedJSON[uid] = state;

localStorage.setItem(COLLAPSED_NS, JSON.stringify(collapsedJSON));
storage?.setItem(COLLAPSED_NS, JSON.stringify(collapsedJSON));
};

export const getCollapsedState = (uid: string): boolean | null => {
const collapsed = localStorage.getItem(COLLAPSED_NS) ?? initCollapsed();

if (collapsedIsEmpty(collapsed)) {
const collapsed = storage?.getItem(COLLAPSED_NS) ?? initCollapsed();
if (!collapsed) {
return null;
}

const pendingJSON = JSON.parse(collapsed);
const state = pendingJSON[uid];
return state ?? null;
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/utils/recommendationsLanguages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TFunction } from 'react-i18next';
import { storage } from 'src/app/localStorage';
import { uniq } from 'src/utils/array';

export const recommendationsLanguages: {
Expand Down Expand Up @@ -79,7 +80,7 @@ export const getLanguageName = (t: TFunction, language: string) => {
};

export const saveRecommendationsLanguages = (value: string) => {
localStorage.setItem('recommendationsLanguages', value);
storage?.setItem('recommendationsLanguages', value);
const event = new CustomEvent('tournesol:recommendationsLanguagesChange', {
detail: { recommendationsLanguages: value },
});
Expand All @@ -96,8 +97,9 @@ export const initRecommendationsLanguages = (): string => {
return languages;
};

export const loadRecommendationsLanguages = (): string | null =>
localStorage.getItem('recommendationsLanguages');
export const loadRecommendationsLanguages = (): string | null => {
return storage?.getItem('recommendationsLanguages') ?? null;
};

export const recommendationsLanguagesFromNavigator = (): string =>
// This function also exists in the browser extension so it should be updated there too if it changes here.
Expand Down

0 comments on commit 326bd91

Please sign in to comment.