Skip to content

Commit

Permalink
#6651 fix localStorage error that causes infinite loading in embedded… (
Browse files Browse the repository at this point in the history
#6667)

* #6651 fix localStorage error that causes infinite loading in embedded map in incognito mode

* made more robust the check on userSession for browser provider

* add tests + refactor

* finalize refactor plus adding more unit tests

* fix typo error with new api for storage

* refactor for some tests
  • Loading branch information
MV88 authored Mar 22, 2021
1 parent 6c00ffa commit 152d976
Show file tree
Hide file tree
Showing 19 changed files with 350 additions and 37 deletions.
48 changes: 48 additions & 0 deletions web/client/api/userPersistedStorage/__tests__/index-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2021, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import expect from 'expect';
import { setApi, getApi } from '..';


describe('UserPersistedSession', () => {
describe('MemoryStorage', () => {
let memoryApi;
beforeEach(() => {
setApi("memoryStorage");
memoryApi = getApi();
});
afterEach(() => {
setApi("localStorage");
memoryApi.setAccessDenied(false);
});
it('tests setItem & getItem', () => {
memoryApi.setItem("key", "value");
memoryApi.setItem("key2", "value2");
expect(memoryApi.getItem("key")).toBe("value");
expect(memoryApi.getItem("key2")).toBe("value2");
});
it('tests removeItem', () => {
memoryApi.removeItem("key");
expect(memoryApi.getItem("key2")).toBe("value2");
expect(memoryApi.getItem("key")).toBeFalsy();
});
it('tests accessDenied', (done) => {
memoryApi.setAccessDenied(true);
try {
memoryApi.getItem("key");
} catch (e) {
expect(e).toBeTruthy();
const err = new Error("Cannot Access memoryStorage");
expect(e).toEqual(err);
done();
}
});
});

});
75 changes: 75 additions & 0 deletions web/client/api/userPersistedStorage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright 2018, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/


import get from 'lodash/get';
import set from 'lodash/set';
import unset from 'lodash/unset';

let UserPersistedSession = {
api: localStorage
};
let MemoryStorage = {};
const ApiProviders = {
localStorage: window.localStorage,
memoryStorage: {
accessDenied: false,
getItem: (path) => {
if (ApiProviders.memoryStorage.accessDenied) {
throw Error("Cannot Access memoryStorage");
}
return get(MemoryStorage, path);
},
setItem: (path, value) => {
if (ApiProviders.memoryStorage.accessDenied) {
throw Error("Cannot Access memoryStorage");
}
set(MemoryStorage, path, value);
},
removeItem: (path) => {
if (ApiProviders.memoryStorage.accessDenied) {
throw Error("Cannot Access memoryStorage");
}
return unset(MemoryStorage, path);
},
setAccessDenied: (status = false) => {
ApiProviders.memoryStorage.accessDenied = status;
}
}
};
export const api = "localStorage";
/**
* Add a new API implementation
* @param {string} name the key of the added api implementation
* @param {object} api the api implementation
*/
export const addApi = (name, apiName) => {
ApiProviders[name] = apiName;
};
/**
* Set the current API
* @param {string} name the key of the api implementation to be used
*/
export const setApi = (name = "localStorage") => {
UserPersistedSession.api = name;
};
/**
* Add a new api implementation
* @return {object} Current api
*/
export const getApi = () => {
return ApiProviders[UserPersistedSession.api];
};
UserPersistedSession = {
api,
addApi,
setApi,
getApi
};

export default UserPersistedSession;
3 changes: 2 additions & 1 deletion web/client/api/usersession/__tests__/serverbackup-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {getSessionName} from "../browser";
import axios from "../../../libs/ajax";
import ConfigUtils from "../../../utils/ConfigUtils";
import MockAdapter from "axios-mock-adapter";
import { getApi } from '../../userPersistedStorage';

describe('usersession API serverbackup implementation', () => {
let mockAxios;
Expand All @@ -24,7 +25,7 @@ describe('usersession API serverbackup implementation', () => {
});
});
it('get session by name', (done) => {
localStorage.setItem(getSessionName("myname"), JSON.stringify(saved));
getApi().setItem(getSessionName("myname"), JSON.stringify(saved));
mockAxios.onGet("misc/category/name/USERSESSION/resource/name/myname/data").reply(200, saved);
mockAxios.onGet().reply(200, {
Resource: {
Expand Down
22 changes: 16 additions & 6 deletions web/client/api/usersession/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {Observable} from "rxjs";
import { getApi } from '../userPersistedStorage';

export const getSessionName = (name) => "mapstore.usersession." +
(window.location.host + window.location.pathname).replace(/[^\w]/g, "_") + "." + name;
Expand All @@ -15,17 +16,26 @@ export const getSessionName = (name) => "mapstore.usersession." +
*/
export default {
getSession: name => Observable.defer(() => {
const serialized = localStorage.getItem(getSessionName(name));
const session = serialized && JSON.parse(serialized);
const id = session && name;
return Promise.resolve([id, session]);
try {
const serialized = getApi().getItem(getSessionName(name));
const session = serialized && JSON.parse(serialized);
const id = session && name;
return Promise.resolve([id, session]);
} catch (e) {
console.error(e);
return Promise.resolve([0, null]);
}
}),
writeSession: (id, name, user, session) => Observable.defer(() => {
localStorage.setItem(getSessionName(id || name), JSON.stringify(session));
try {
getApi().setItem(getSessionName(id || name), JSON.stringify(session));
} catch (e) {
console.error(e);
}
return Promise.resolve(id || name);
}),
removeSession: id => Observable.defer(() => {
localStorage.removeItem(getSessionName(id));
getApi().removeItem(getSessionName(id));
return Promise.resolve(id);
})
};
8 changes: 7 additions & 1 deletion web/client/components/cookie/Cookie.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { Glyphicon, Col } from 'react-bootstrap';
import Button from '../misc/Button';
import Message from '../../components/I18N/Message';
import MoreDetails from './MoreDetails';
import { getApi } from '../../api/userPersistedStorage';

/**
* Component used to show a panel with the information about cookies
* @class Cookies
Expand Down Expand Up @@ -128,7 +130,11 @@ class Cookie extends React.Component {
this.props.onMoreDetails(!this.props.seeMore);
}
accept = () => {
localStorage.setItem("cookies-policy-approved", true);
try {
getApi().setItem("cookies-policy-approved", true);
} catch (e) {
console.error(e);
}
this.props.onSetCookieVisibility(false);
}
}
Expand Down
8 changes: 7 additions & 1 deletion web/client/components/data/featuregrid/toolbars/Toolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ButtonGroup, Checkbox, Glyphicon } from 'react-bootstrap';
import Message from '../../../I18N/Message';
import withHint from '../enhancers/withHint';
import TButtonComp from "./TButton";
import { getApi } from '../../../../api/userPersistedStorage';

const TButton = withHint(TButtonComp);
const getDrawFeatureTooltip = (isDrawing, isSimpleGeom) => {
if (isDrawing) {
Expand Down Expand Up @@ -196,7 +198,11 @@ export default ({
<Message msgId="featuregrid.toolbar.synchPopoverTitle"/>
<button onClick={() => {
if (syncPopover.showAgain) {
localStorage.setItem("showPopoverSync", false);
try {
getApi().setItem("showPopoverSync", false);
} catch (e) {
console.error(e);
}
}
events.hideSyncPopover();
}} className="close">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import Manager from '../../style/vector/Manager';
import defaultConfig from './AnnotationsConfig';
import FeaturesList from './FeaturesList';
import GeometryEditor from './GeometryEditor';
import { getApi } from '../../../api/userPersistedStorage';

/**
* (Default) Viewer / Editor for Annotations.
Expand Down Expand Up @@ -867,7 +868,11 @@ class AnnotationsEditor extends React.Component {

hideWarning = () => {
if (this.props.showAgain) {
localStorage.setItem("showPopupWarning", false);
try {
getApi().setItem("showPopupWarning", false);
} catch (e) {
console.error(e);
}
this.props.onHideMeasureWarning();
}
this.setPopupWarning(false);
Expand Down
41 changes: 41 additions & 0 deletions web/client/epics/__tests__/cookies-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2017, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import expect from 'expect';
import { testEpic, TEST_TIMEOUT, addTimeoutEpic} from './epicTestUtils';
import { LOCATION_CHANGE } from 'connected-react-router';
import { cookiePolicyChecker } from '../cookies';
import { SET_COOKIE_VISIBILITY } from '../../actions/cookie';
import { setApi, getApi } from '../../api/userPersistedStorage';


describe('Cookies epics', () => {
it('test cookiePolicyChecker and enable cookie msg', done => {
const NUM_ACTIONS = 1;
testEpic(cookiePolicyChecker, NUM_ACTIONS, {type: LOCATION_CHANGE}, (actions) => {
expect(actions.length).toBe(NUM_ACTIONS);
const [action] = actions;
expect(action.type).toBe(SET_COOKIE_VISIBILITY);
expect(action.status).toBe(true);
done();
}, {});
});
it('test cookiePolicyChecker and trigger error for accessing localStorage', done => {
setApi("memoryStorage");
getApi().setAccessDenied(true);
const NUM_ACTIONS = 1;
testEpic(addTimeoutEpic(cookiePolicyChecker, 40), NUM_ACTIONS, {type: LOCATION_CHANGE}, (actions) => {
expect(actions.length).toBe(NUM_ACTIONS);
const [action] = actions;
expect(action.type).toBe(TEST_TIMEOUT);
getApi().setAccessDenied(false);
setApi("localStorage");
done();
}, {});
});
});
38 changes: 35 additions & 3 deletions web/client/epics/__tests__/tutorial-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SETUP_TUTORIAL, updateTutorial, initTutorial } from '../../actions/tuto
import { geostoryLoaded, setEditing } from '../../actions/geostory';
import { testEpic, addTimeoutEpic, TEST_TIMEOUT } from './epicTestUtils';
import { onLocationChanged } from 'connected-react-router';
import { setApi, getApi } from '../../api/userPersistedStorage';

describe('tutorial Epics', () => {
const GEOSTORY_EDIT_STEPS = [{
Expand Down Expand Up @@ -381,7 +382,7 @@ describe('tutorial Epics', () => {

describe("tests for switchGeostoryTutorialEpic", () => {
beforeEach(() => {
localStorage.setItem("mapstore.plugin.tutorial.geostory.disabled", "false");
getApi().setItem("mapstore.plugin.tutorial.geostory.disabled", "false");
});
const GEOSTORY_TUTORIAL_ID = "geostory";
const ID_STORY = "1";
Expand Down Expand Up @@ -442,6 +443,37 @@ describe('tutorial Epics', () => {
}
});
});
it('tests the correct tutorial setup when passing from edit to view, intercepting throw', (done) => {
const IS_GOING_TO_EDIT_MODE = true;
setApi("memoryStorage");
getApi().setAccessDenied(true);
testEpic(switchGeostoryTutorialEpic, NUM_ACTIONS, [
setEditing(IS_GOING_TO_EDIT_MODE)
], (actions) => {
expect(actions.length).toBe(NUM_ACTIONS);
actions.map((action) => {
switch (action.type) {
case SETUP_TUTORIAL:
expect(action.steps).toEqual(GEOSTORY_EDIT_STEPS);
expect(action.stop).toEqual(false);
expect(action.id).toBe(GEOSTORY_TUTORIAL_ID);
break;
default:
expect(true).toBe(false);
}
});
done();
getApi().setAccessDenied(false);
setApi("localStorage");
}, {
tutorial: {
presetList: {
'geostory_edit_tutorial': GEOSTORY_EDIT_STEPS,
'geostory_view_tutorial': GEOSTORY_VIEW_STEPS
}
}
});
});
it('tests when steps are not correctly configured, back off to default (switchGeostoryTutorialEpic)', (done) => {
const IS_GOING_TO_EDIT_MODE = false;

Expand Down Expand Up @@ -472,7 +504,7 @@ describe('tutorial Epics', () => {
});
it('does not setup tutorial if it has been disabled (switchGeostoryTutorialEpic)', (done) => {
const IS_GOING_TO_EDIT_MODE = false;
localStorage.setItem("mapstore.plugin.tutorial.geostory.disabled", "true");
getApi().setItem("mapstore.plugin.tutorial.geostory.disabled", "true");

testEpic(addTimeoutEpic(switchGeostoryTutorialEpic, 50), NUM_ACTIONS, [
geostoryLoaded(ID_STORY),
Expand All @@ -487,7 +519,7 @@ describe('tutorial Epics', () => {
expect(true).toBe(false);
}
});
localStorage.setItem("mapstore.plugin.tutorial.geostory.disabled", "false");
getApi().setItem("mapstore.plugin.tutorial.geostory.disabled", "false");
done();
}, {
tutorial: {
Expand Down
12 changes: 10 additions & 2 deletions web/client/epics/cookies.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Rx from 'rxjs';
import { SET_MORE_DETAILS_VISIBILITY, setCookieVisibility, setDetailsCookieHtml } from '../actions/cookie';
import { CHANGE_LOCALE } from '../actions/locale';
import axios from '../libs/ajax';
import { getApi } from '../api/userPersistedStorage';

/**
* Show the cookie policy notification
Expand All @@ -24,14 +25,21 @@ import axios from '../libs/ajax';
export const cookiePolicyChecker = (action$) =>
action$.ofType(LOCATION_CHANGE )
.take(1)
.filter( () => !localStorage.getItem("cookies-policy-approved"))
.filter( () => {
try {
return !getApi().getItem("cookies-policy-approved");
} catch (e) {
console.error(e);
return false;
}
})
.switchMap(() =>
Rx.Observable.of(setCookieVisibility(true))
);

export const loadCookieDetailsPage = (action$, store) =>
action$.ofType(SET_MORE_DETAILS_VISIBILITY, CHANGE_LOCALE )
.filter( () => !localStorage.getItem("cookies-policy-approved") && store.getState().cookie.seeMore && !store.getState().cookie.html[store.getState().locale.current])
.filter( () => !getApi().getItem("cookies-policy-approved") && store.getState().cookie.seeMore && !store.getState().cookie.html[store.getState().locale.current])
.switchMap(() => Rx.Observable.fromPromise(
axios.get("translations/fragments/cookie/cookieDetails-" + store.getState().locale.current + ".html", null, {
timeout: 60000,
Expand Down
Loading

0 comments on commit 152d976

Please sign in to comment.