Are you still using localStorage directly? It's time to elevate

When using localStorage or sessionStorage, many people like to use it directly, store it in plain text, and directly expose the information; in the browser, although it can be handled in general scenarios and is simple and rude, in special needs, such as setting the timing function, it is necessary to cannot be achieved. It needs to be encapsulated twice. In order to increase a sense of security in use, encryption must be indispensable. For the convenience of project use, routine operations are encapsulated. The imperfections will be further updated... (Updated at: 2022.06.02 16:30)
design
Before encapsulation, sort out the required functions, and what kind of specifications should be used. Some main code snippets take localStorage as an example, and the complete code will be posted at the end. It can be optimized by itself in combination with the project, or it can be used directly.
// Distinguish storage type type
// custom name prefix prefix
// Support setting expiration time expire
// Support encryption is optional, and encryption can be turned off if it is not convenient for debugging in the development environment

// Support data encryption here using crypto-js encryption, other methods can also be used

// Determine whether to support Storage isSupportStorage

// set setStorage

// get getStorage

// Does hasStorage exist?

// Get all keys getStorageKeys

// Get the key according to the index getStorageForIndex

// Get the length of localStorage getStorageLength

// Get all getAllStorage

// delete removeStorage

// clear clearStorage

//Define parameter type window.localStorage,window.sessionStorage,
const config = {
type: 'localStorage', // local storage type localStorage/sessionStorage
prefix: 'SDF_0.0.1', // name prefix suggestion: project name + project version
expire: 1, //Expire time unit: seconds
isEncrypt: true // Default encryption For the convenience of debugging, it can be unencrypted during development
}

set setStorage
Storage itself does not support expiration time setting. To support setting expiration time, you can follow the practice of Cookie. The setStorage(key,value,expire) method accepts three parameters. The third parameter is to set the expiration time, using relative time , in seconds, to perform type checking on the passed parameters. A unified expiration time can be set, or a single value expiration time can be configured individually. There are two ways to configure as needed.
Code:
// set setStorage
export const setStorage = (key,value,expire=0) => {
if (value === '' || value === null || value === undefined) {
value = null;
}

if (isNaN(expire) || expire < 1) throw new Error("Expire must be a number");

expire = (expire?expire:config.expire) * 60000;
let data = {
value: value, // store the value
time: Date.now(), //Storage timestamp
expire: expire // expiration time
}

window[config.type].setItem(key, JSON.stringify(data));
}

get getStorage
First of all, it is necessary to judge whether the key exists to prevent an error from getting a value that does not exist. The acquisition method is further extended, as long as the Storage value is acquired within the validity period, the expiration time will be renewed, and if it expires, the value will be deleted directly. and returns null
// get getStorage
export const getStorage = (key) => {
// key does not exist judgment
if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null'){
return null;
}

// Optimized for continuous use and renewal
const storage = JSON.parse(window[config.type].getItem(key));
console.log(storage)
let nowTime = Date.now();
console.log(config.expire*6000 ,(nowTime - storage.time))
// expired delete
if (storage.expire && config.expire*6000 < (nowTime - storage.time)) {
removeStorage(key);
return null;
} else {
// If it is called during the unexpired period, it will automatically renew and keep it alive
setStorage(key,storage.value);
return storage.value;
}
}

get all values
// Get all getAllStorage
export const getAllStorage = () => {
let len ​​= window[config.type].length // get the length
let arr = new Array() // define the dataset
for (let i = 0; i < len; i++) {
// Get key index starting from 0
let getKey = window[config.type].key(i)
// Get the value corresponding to the key
let getVal = window[config.type].getItem(getKey)
// put into array
arr[i] = { 'key': getKey, 'val': getVal, }
}
return arr
}

remove removeStorage
// Automatically add a prefix to the name
const autoAddPrefix = (key) => {
const prefix = config.prefix ? config.prefix + '_' : '';
return prefix + key;
}

// delete removeStorage
export const removeStorage = (key) => {
window[config.type].removeItem(autoAddPrefix(key));
}

clear clearStorage
// clear clearStorage
export const clearStorage = () => {
window[config.type].clear();
}

encrypt and decode
Encryption is using crypto-js
// install crypto-js
npm install crypto-js

// There are two ways to introduce crypto-js
import CryptoJS from "crypto-js";
// or
const CryptoJS = require("crypto-js");

To set the key and key offset for crypto-js, it can be obtained by encrypting a private key with MD5 to generate a 16-bit key.
// hexadecimal number as key
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161");
// hexadecimal number as key offset
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a");

Encapsulate the encryption method
/**
* encryption method
* @param data
* @returns {string}
*/
export function encrypt(data) {
if (typeof data === "object") {
try {
data = JSON.stringify(data);
} catch (error) {
console.log("encrypt error:", error);
}
}
const dataHex = CryptoJS.enc.Utf8.parse(data);
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString();
}

Encapsulate the decryption method
/**
* Decryption method
* @param data
* @returns {string}
*/
export function decrypt(data) {
const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
}

Use in storing and retrieving data:
Here we mainly look at the encryption and decryption part, some methods are not shown in the following code segment, please note that it cannot be run directly.
const config = {
type: 'localStorage', // local storage type sessionStorage
prefix: 'SDF_0.0.1', // name prefix suggestion: project name + project version
expire: 1, //Expire time unit: seconds
isEncrypt: true // Default encryption For the convenience of debugging, it can be unencrypted during development
}

// set setStorage
export const setStorage = (key, value, expire = 0) => {
if (value === '' || value === null || value === undefined) {
value = null;
}

if (isNaN(expire) || expire < 0) throw new Error("Expire must be a number");

expire = (expire ? expire : config.expire) * 1000;
let data = {
value: value, // store the value
time: Date.now(), //Storage timestamp
expire: expire // expiration time
}
// Encrypt the stored data Encryption is optional
const encryptString = config.isEncrypt ? encrypt(JSON.stringify(data)): JSON.stringify(data);
window[config.type].setItem(autoAddPrefix(key), encryptString);
}

// get getStorage
export const getStorage = (key) => {
key = autoAddPrefix(key);
// key does not exist judgment
if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null') {
return null;
}

// Decrypt the stored data
const storage = config.isEncrypt ? JSON.parse(decrypt(window[config.type].getItem(key))) : JSON.parse(window[config.type].getItem(key));
let nowTime = Date.now();

// expired delete
if (storage.expire && config.expire * 6000 < (nowTime - storage.time)) {
removeStorage(key);
return null;
} else {
// Automatically renew when used continuously
setStorage(autoRemovePrefix(key), storage.value);
return storage.value;
}
}

use
When using it, you can import it as needed through import, or you can mount it globally for use. Generally, it is recommended to use less global methods or global variables, so as to leave a convenient way for those who will continue to develop and maintain the project later to trace the code! Don't encapsulate for the sake of encapsulation, as much as possible based on project requirements and subsequent generality, as well as convenience in use. For example, to get all the storage variables, if you have never used them in your project, you might as well delete them, and keep them for the Chinese New Year.
import {isSupportStorage, hasStorage, setStorage,getStorage,getStorageKeys,getStorageForIndex,getStorageLength,removeStorage,getStorageAll,clearStorage} from '@/utils/storage'

full code
The code has been further improved, and you can directly further optimize it if you need it, or you can leave a message for suggestions that can be optimized or expandable, and I will iterate further. You can delete some unused methods according to your own needs to reduce the file size.
/***
* title: storage.js
* Author: Gaby
* Email: xxx@126.com
* Time: 2022/6/1 17:30
* last: 2022/6/2 17:30
* Desc: Simple encapsulation of storage
*/

import CryptoJS from 'crypto-js';

// hexadecimal number as key
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161");
// hexadecimal number as key offset
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a");

// type window.localStorage,window.sessionStorage,
const config = {
type: 'localStorage', // local storage type sessionStorage
prefix: 'SDF_0.0.1', // name prefix suggestion: project name + project version
expire: 1, //Expire time unit: seconds
isEncrypt: true // Default encryption For the convenience of debugging, it can be unencrypted during development
}

// Determine if Storage is supported
export const isSupportStorage = () => {
return (typeof (Storage) !== "undefined") ? true : false
}

// set setStorage
export const setStorage = (key, value, expire = 0) => {
if (value === '' || value === null || value === undefined) {
value = null;
}

if (isNaN(expire) || expire < 0) throw new Error("Expire must be a number");

expire = (expire ? expire : config.expire) * 1000;
let data = {
value: value, // store the value
time: Date.now(), //Storage timestamp
expire: expire // expiration time
}

const encryptString = config.isEncrypt
? encrypt(JSON.stringify(data))
: JSON.stringify(data);

window[config.type].setItem(autoAddPrefix(key), encryptString);
}

// get getStorage
export const getStorage = (key) => {
key = autoAddPrefix(key);
// key does not exist judgment
if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null') {
return null;
}

// Optimized for continuous use and renewal
const storage = config.isEncrypt
? JSON.parse(decrypt(window[config.type].getItem(key)))
: JSON.parse(window[config.type].getItem(key));

let nowTime = Date.now();

// expired delete
if (storage.expire && config.expire * 6000 < (nowTime - storage.time)) {
removeStorage(key);
return null;
} else {
// If it is called during the unexpired period, it will automatically renew and keep it alive
setStorage(autoRemovePrefix(key), storage.value);
return storage.value;
}
}

// Does hasStorage exist?
export const hasStorage = (key) => {
key = autoAddPrefix(key);
let arr = getStorageAll().filter((item)=>{
return item.key === key;
})
return arr.length ? true : false;
}

// get all keys
export const getStorageKeys = () => {
let items = getStorageAll()
let keys = []
for (let index = 0; index < items.length; index++) {
keys.push(items[index].key)
}
return keys
}

// get key by index
export const getStorageForIndex = (index) => {
return window[config.type].key(index)
}

// Get the length of localStorage
export const getStorageLength = () => {
return window[config.type].length
}

// Get all getAllStorage
export const getStorageAll = () => {
let len ​​= window[config.type].length // get the length
let arr = new Array() // define the dataset
for (let i = 0; i < len; i++) {
// Get key index starting from 0
let getKey = window[config.type].key(i)
// Get the value corresponding to the key
let getVal = window[config.type].getItem(getKey)
// put into array
arr[i] = {'key': getKey, 'val': getVal,}
}
return arr
}

// delete removeStorage
export const removeStorage = (key) => {
window[config.type].removeItem(autoAddPrefix(key));
}

// clear clearStorage
export const clearStorage = () => {
window[config.type].clear();
}

// Automatically add a prefix to the name
const autoAddPrefix = (key) => {
const prefix = config.prefix ? config.prefix + '_' : '';
return prefix + key;
}

// remove added prefix
const autoRemovePrefix = (key) => {
const len ​​= config.prefix?config.prefix.length+1 : '';
return key.substr(len)
// const prefix = config.prefix ? config.prefix + '_' : '';
// return prefix + key;
}

/**
* encryption method
* @param data
* @returns {string}
*/
const encrypt = (data) => {
if (typeof data === "object") {
try {
data = JSON.stringify(data);
} catch (error) {
console.log("encrypt error:", error);
}
}
const dataHex = CryptoJS.enc.Utf8.parse(data);
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString();
}

/**
* Decryption method
* @param data
* @returns {string}
*/
const decrypt = (data) => {
const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
}

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00