Files
zulip/static/js/localstorage.js
Anders Kaseorg 6de39ae92d js: Clean up typeof … === "undefined" checks.
The only reason to use typeof foo === "undefined" is when foo is a
global identifier that might not have been declared at all, so it
might raise a ReferenceError if evaluated.  For a variable declared
with const or let or import, a function argument, or a complex
expression, simply foo === undefined is equivalent.

Some of these conditions have become impossible and can be removed
entirely, and some can be replaced more idiomatically with default
parameters (note that JavaScript does not share the Python misfeature
of evaluating the default parameter at function declaration time).

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-03-24 13:15:01 -07:00

182 lines
5.0 KiB
JavaScript

import * as blueslip from "./blueslip";
const ls = {
// parse JSON without throwing an error.
parseJSON(str) {
try {
return JSON.parse(str);
} catch {
return undefined;
}
},
// check if the datestamp is from before now and if so return true.
isExpired(stamp) {
return new Date(stamp) < new Date();
},
// return the localStorage key that is bound to a version of a key.
formGetter(version, name) {
return "ls__" + version + "__" + name;
},
// create a formData object to put in the data, a signature that it was
// created with this library, and when it expires (if ever).
formData(data, expires) {
return {
data,
__valid: true,
expires: Date.now() + expires,
};
},
getData(version, name) {
const key = this.formGetter(version, name);
let data = localStorage.getItem(key);
data = ls.parseJSON(data);
if (
data &&
data.__valid &&
// JSON forms of data with `Infinity` turns into `null`,
// so if null then it hasn't expired since nothing was specified.
(!ls.isExpired(data.expires) || data.expires === null)
) {
return data;
}
return undefined;
},
// set the wrapped version of the data into localStorage.
setData(version, name, data, expires) {
const key = this.formGetter(version, name);
const val = this.formData(data, expires);
localStorage.setItem(key, JSON.stringify(val));
},
// remove the key from localStorage and from memory.
removeData(version, name) {
const key = this.formGetter(version, name);
localStorage.removeItem(key);
},
// Remove keys which match a regex.
removeDataRegex(version, regex) {
const key_regex = new RegExp(this.formGetter(version, regex));
const keys = Object.keys(localStorage).filter((key) => key_regex.test(key));
for (const key of keys) {
localStorage.removeItem(key);
}
},
// migrate from an older version of a data src to a newer one with a
// specified callback function.
migrate(name, v1, v2, callback) {
const old = this.getData(v1, name);
this.removeData(v1, name);
if (old && old.__valid) {
const data = callback(old.data);
this.setData(v2, name, data, Number.POSITIVE_INFINITY);
return data;
}
return undefined;
},
};
// return a new function instance that has instance-scoped variables.
export const localstorage = function () {
const _data = {
VERSION: 1,
expires: Number.POSITIVE_INFINITY,
expiresIsGlobal: false,
};
const prototype = {
// `expires` should be a Number that represents the number of ms from
// now that this should expire in.
// this allows for it to either be set only once or permanently.
setExpiry(expires, isGlobal) {
_data.expires = expires;
_data.expiresIsGlobal = isGlobal || false;
return this;
},
get(name) {
const data = ls.getData(_data.VERSION, name);
if (data) {
return data.data;
}
return undefined;
},
set(name, data) {
if (_data.VERSION !== undefined) {
ls.setData(_data.VERSION, name, data, _data.expires);
// if the expires attribute was not set as a global, then
// make sure to return it back to Infinity to not impose
// constraints on the next key.
if (!_data.expiresIsGlobal) {
_data.expires = Number.POSITIVE_INFINITY;
}
return true;
}
return false;
},
// remove a key with a given version.
remove(name) {
ls.removeData(_data.VERSION, name);
},
// Remove keys which match the pattern given by name.
removeRegex(name) {
ls.removeDataRegex(_data.VERSION, name);
},
migrate(name, v1, v2, callback) {
return ls.migrate(name, v1, v2, callback);
},
};
// set a new master version for the LocalStorage instance.
Object.defineProperty(prototype, "version", {
get() {
return _data.VERSION;
},
set(version) {
_data.VERSION = version;
},
});
return prototype;
};
let warned_of_localstorage = false;
localstorage.supported = function supports_localstorage() {
try {
return window.localStorage !== undefined && window.localStorage !== null;
} catch {
if (!warned_of_localstorage) {
blueslip.error(
"Client browser does not support local storage, will lose socket message on reload",
);
warned_of_localstorage = true;
}
return false;
}
};