Skip to content

Moving session data from localStorage to sessionStorage #207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"axios": "^0.21.1",
"bootstrap-vue": "^2.15.0",
"core-js": "^3.6.5",
"js-cookie": "^3.0.1",
"vue": "^2.6.11",
"vue-easy-dnd": "^1.8.4",
"vue-i18n": "^8.22.3",
Expand Down
23 changes: 0 additions & 23 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,29 +113,6 @@ export default {
elem.setAttribute("role", "presentation");
});
});

// Refresh when some things have changed in another tab.
const watchKeys = ["token", "userId", "role", "communityId"];
let updateRequired;
window.addEventListener("storage", (e) => {
if (!e.key || watchKeys.includes(e.key)) {
if (this.$route.meta && !this.$route.meta.public) {
if (document.visibilityState === "hidden") {
updateRequired = true;
} else {
this.$router.go();
}
}
}
});

window.addEventListener("visibilitychange", (e) => {
if (updateRequired && document.visibilityState !== "hidden") {
updateRequired = false;
this.$router.go();
}
});

},
watch: {
bodyClasses: {
Expand Down
4 changes: 3 additions & 1 deletion src/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ if (href.host.endsWith(".morphic.dev")) {
* @property {Number} MAX_BARS Number of bars a member can have, before hiding the add bar button.
* @property {Number} MAC_VERSION Lowest macOS version supported
* @property {Number} WIN_VERSION Lowest Windows version supported
* @property {Number} SESSION_DATA_EXPIRY Time, in minutes, the session data will last for.
*/

/**
Expand All @@ -39,7 +40,8 @@ const CONF = {
DISABLE_TRIAL: true,
MAX_BARS: 5,
MAC_VERSION: 10.14,
WIN_VERSION: 10
WIN_VERSION: 10,
SESSION_DATA_EXPIRY: 20
},

// Local development (npm run dev)
Expand Down
4 changes: 2 additions & 2 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import "./styles/app.scss";

import i18n from "./i18n/i18n";

const token = localStorage.getItem("token");
const token = sessionStorage.getItem("token");
if (token) {
HTTP.defaults.headers.common.Authorization = `Bearer ${token}`;
}
Expand All @@ -26,7 +26,7 @@ HTTP.interceptors.response.use(undefined, function (err) {
return new Promise((resolve, reject) => {
if (err.message.indexOf("401") > 1) {
if (err.config.action !== "logout") {
store.dispatch("logout");
store.dispatch("logout").then(() => location.replace("/"));
}
resolve();
}
Expand Down
4 changes: 2 additions & 2 deletions src/services/communityService.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export function deleteCommunityBar(communityId, barId) {
*/
export function getCommunityMembers(communityId) {
return HTTP.get(`/v1/communities/${communityId}/members`).then((r) => {
var userId = localStorage.getItem("userId");
var userId = sessionStorage.getItem("userId");
r.data.members.forEach(member => makeMember(member, userId));
return r;
}, {action: "get members"});
Expand Down Expand Up @@ -291,7 +291,7 @@ function storeBar(bar) {
* @param {GUID} [currentUserId] The current user id
*/
function makeMember(member, currentUserId) {
var userId = currentUserId || localStorage.getItem("userId");
var userId = currentUserId || sessionStorage.getItem("userId");
const noName = "(no name)";
member.isCurrent = userId && member.userId === userId;

Expand Down
148 changes: 126 additions & 22 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,126 @@ import Vuex from "vuex";
import { HTTP } from "@/services/index";
import { login, register, resetPassword, logout } from "@/services/userService";
import { createNewCommunity, getUserCommunities } from "@/services/communityService";
import { CONFIG } from "@/config/config";
import Cookies from "js-cookie";

Vue.use(Vuex);

/**
* Implementation of a Storage, to store session data in sessionStorage and backed by session cookies so it can be
* shared with other instances.
*
* This combination is used instead of localStorage, so data can be shared between other tabs and also get cleared when
* the user leaves the site.
*
* @type {Storage}
*/
const sharedStorage = {
values: {},

getItem: function (key) {
let value = sessionStorage.getItem(key);
if (value === null) {
value = Cookies.get(`session-${key}`);
if (value !== null && value !== undefined) {
sessionStorage.setItem(key, value);
}
}
return value;
},

setItem: function (key, value) {
const changed = sessionStorage.getItem(key) !== value;
Cookies.set(`session-${key}`, value, {expires: 1 / (24 * 60) * CONFIG.SESSION_DATA_EXPIRY});
if (changed) {
this.values[key] = value;
sessionStorage.setItem(key, value);
this.changed(key);
}
},

removeItem: function (key) {
const changed = !!sessionStorage.getItem(key);
sessionStorage.removeItem(key);
Cookies.remove(`session-${key}`);
if (changed) {
this.changed(key);
}
},

clear() {
Object.keys(this.values).forEach(key => Cookies.remove(`session-${key}`));
sessionStorage.clear();
},

key(index) {
return sessionStorage.key(index);
},

get length() {
return sessionStorage.length;
},

/**
* Called when a value has been changed. For interesting keys (like the token), an entry in localStorage is set to
* other tabs can act upon the `storage` event.
* @param {String} key The name of the key that was updated.
*/
changed: function (key) {
// For important fields, which can affect what the user can access, let the other tabs know about the change.
if (["token", "userId", "role", "communityId"].includes(key)) {
localStorage.setItem("reload", key);
localStorage.removeItem("reload");
}
},

/**
* Continuously increase the expiry of the session cookies while the page is still open.
*/
keepAlive: function () {
Object.entries(this.values).forEach(([key, value]) => this.setItem(key, value));
setTimeout(() => this.keepAlive(), 200000);
}
};

sharedStorage.keepAlive();

// Refresh when some things have changed in another tab.
let updateRequired;
window.addEventListener("storage", (e) => {
if (e.key === "reload" && e.newValue) {
sessionStorage.removeItem(e.newValue);
// Reload hidden tabs when they are visible again.
if (document.visibilityState === "hidden") {
updateRequired = true;
} else {
location.reload();
}
}
});

// When the tab becomes visible, reload the page if it's required.
window.addEventListener("visibilitychange", (e) => {
if (updateRequired && document.visibilityState !== "hidden") {
updateRequired = false;
location.reload();
}
});


export default new Vuex.Store({
state: {
status: "",
token: localStorage.getItem("token") || "",
userId: localStorage.getItem("userId") || "",
communityId: localStorage.getItem("communityId") || "",
role: localStorage.getItem("role") || "",
email: localStorage.getItem("email") || "",
token: sharedStorage.getItem("token") || "",
userId: sharedStorage.getItem("userId") || "",
communityId: sharedStorage.getItem("communityId") || "",
role: sharedStorage.getItem("role") || "",
email: sharedStorage.getItem("email") || "",
user: {},
community: {},
errorMessage: {},
unsavedChanges: false,
unsavedBar: JSON.parse(localStorage.getItem("unsavedBar") || "null"),
unsavedBar: JSON.parse(sharedStorage.getItem("unsavedBar") || "null"),
resetPasswordEmail: "",
// The url the visitor used, before authenticating - redirect to this after login completes.
beforeLoginPage: undefined,
Expand Down Expand Up @@ -69,7 +173,7 @@ export default new Vuex.Store({
},
role(state, role) {
state.role = role;
localStorage.setItem("role", role);
sharedStorage.setItem("role", role);
},
unsavedChanges(state, isChanged) {
state.unsavedChanges = isChanged;
Expand Down Expand Up @@ -119,13 +223,13 @@ export default new Vuex.Store({
};
} catch (err) {
commit("auth_error", err);
localStorage.removeItem("token");
sharedStorage.removeItem("token");
throw err;
}

localStorage.setItem("token", userData.token);
localStorage.setItem("userId", userData.user.id);
localStorage.setItem("email", user.email);
sharedStorage.setItem("token", userData.token);
sharedStorage.setItem("userId", userData.user.id);
sharedStorage.setItem("email", user.email);
HTTP.defaults.headers.common.Authorization = `Bearer ${userData.token}`;

commit("auth_success", userData);
Expand All @@ -138,11 +242,11 @@ export default new Vuex.Store({
logout({ commit, state }) {
return logout(state.userId).finally(() => {
commit("logout");
localStorage.removeItem("token");
localStorage.removeItem("userId");
localStorage.removeItem("communityId");
localStorage.removeItem("role");
localStorage.removeItem("email");
sharedStorage.removeItem("token");
sharedStorage.removeItem("userId");
sharedStorage.removeItem("communityId");
sharedStorage.removeItem("role");
sharedStorage.removeItem("email");
delete HTTP.defaults.headers.common.Authorization;
});
},
Expand Down Expand Up @@ -179,7 +283,7 @@ export default new Vuex.Store({
.then(resp => {
const communities = resp.data.communities;
if (communities.length !== 0) {
localStorage.setItem("communityId", communities[0].id);
sharedStorage.setItem("communityId", communities[0].id);
commit("community", communities[0].id);
commit("role", communities[0].role);
resolve(communities);
Expand All @@ -194,7 +298,7 @@ export default new Vuex.Store({
},
activeCommunity({ commit }, communityId) {
return new Promise((resolve, reject) => {
localStorage.setItem("communityId", communityId);
sharedStorage.setItem("communityId", communityId);
commit("community", communityId);
resolve();
});
Expand All @@ -204,17 +308,17 @@ export default new Vuex.Store({
},
unsavedBar({ commit }, barDetails) {
if (barDetails) {
localStorage.setItem("unsavedBar", JSON.stringify(barDetails));
sharedStorage.setItem("unsavedBar", JSON.stringify(barDetails));
} else {
localStorage.removeItem("unsavedBar");
sharedStorage.removeItem("unsavedBar");
}
commit("unsavedBar", barDetails);
},
forceFocusMode({ commit }, flag) {
if (flag) {
localStorage.setItem("focusMode", true);
sharedStorage.setItem("focusMode", true);
} else {
localStorage.removeItem("focusMode");
sharedStorage.removeItem("focusMode");
}
commit("forceFocusMode", !!flag);
}
Expand Down