Skip to content

Instantly share code, notes, and snippets.

@yairEO
Created August 28, 2023 20:47
Show Gist options
  • Save yairEO/89d0b42b67e708832a878f259eecb172 to your computer and use it in GitHub Desktop.
Save yairEO/89d0b42b67e708832a878f259eecb172 to your computer and use it in GitHub Desktop.

Revisions

  1. yairEO created this gist Aug 28, 2023.
    98 changes: 98 additions & 0 deletions browserStorage.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,98 @@
    import {isString} from 'utility/types';

    export const VERSION = 1; // current version of persisted data. if code change breaks persisted data, verison number should be bumped.
    export const STORAGE_KEY_PREFIX = 'amdocs/catalog';

    /**
    * checks arguments are as expected
    * @param {string} keyName
    * @param {string} type
    * @returns boolean
    */
    const isValid = (keyName, type) => {
    if (!window[`${type}Storage`]) {
    console.warn('wrong storage type: ', type);

    return false;
    }

    if (!keyName || !isString(keyName)) {
    console.warn('missing keyName or invalid type');

    return false;
    }

    return true;
    };

    /**
    *
    * @param {string} keyName
    * @returns full storage path name, conststing of pre-defined, base path prefix and a custom dynamic path suffix
    */
    export const getPathString = (keyName, version) => `${STORAGE_KEY_PREFIX}/${keyName}/v${version || VERSION}`;

    /**
    * extracts the storage path's version, which is at the end of the key
    * @param {string} key
    * @returns number
    */
    const getKeyVersion = key => +key.split('/v').pop();

    export function removeBrowserStorage(keyName, {type = 'local', version = VERSION} = {}) {
    if (!isValid(keyName, type)) return;

    window[`${type}Storage`].removeItem(getPathString(keyName, version));
    }

    export function getBrowserStorage(keyName, {type = 'local'} = {}) {
    if (!isValid(keyName, type)) return;

    let value;

    Object.keys(window[`${type}Storage`])
    .filter(key => key.includes('/' + keyName))
    .forEach(key => {
    const keyVersion = getKeyVersion(key);

    if (keyVersion === VERSION) {
    try { value = JSON.parse(window[`${type}Storage`][key]); }
    catch (err){ console.warn(err); }
    }
    // remove any previous version(s)
    else
    removeBrowserStorage(keyName, {version: keyVersion});
    });

    return value;
    }

    /**
    * Sets a browser local/session storage key/value
    * @param {string} keyName storage key name which gets concatenated to the base STORAGE_KEY_PREFIX
    * @param {*} data any type of data which can be represented as string
    * @param {string} type local/session storage type
    * @returns
    */
    export function setBrowserStorage(keyName, data, {type = 'local'} = {}) {
    if (!isValid(keyName, type)) return;

    if (!data) {
    console.warn('setBrowserStorage: no data (forgot?)');

    return false;
    }

    window[`${type}Storage`].setItem(getPathString(keyName), JSON.stringify(data));

    // go over all existing storage keys and find the one matching the keyName
    const foundStorageKey = Object.keys(window[`${type}Storage`]).find(key => key.includes('/' + keyName));
    // gets the key's version, which is suffixed at the end
    const storageKeyVersion = getKeyVersion(foundStorageKey);

    // if found key has a deprecated version, remove that key
    if (storageKeyVersion !== VERSION)
    removeBrowserStorage(keyName, {version: storageKeyVersion});

    window[`${type}Storage`].setItem(getPathString(keyName), JSON.stringify(data));
    }
    62 changes: 62 additions & 0 deletions browserStorage.test.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    import sinon from 'sinon';
    import {expect} from 'chai';
    import {getPathString, getBrowserStorage, setBrowserStorage} from './browserStorage';

    describe('Browser storage', () => {
    const sandbox = sinon.createSandbox();
    const data = {name: 'John Doe', age: 30};

    afterEach(() => {
    sandbox.restore();
    });

    describe('setBrowserStorage', () => {
    it('should call window.localStorage.setItem with the correct arguments', () => {
    const setItemStub = sandbox.stub(window.localStorage, 'setItem');

    setBrowserStorage('user', data);

    expect(setItemStub.calledOnceWith(getPathString('user'), JSON.stringify(data))).to.be.true;
    });

    it('should not call window.localStorage.setItem when keyName is not a string', () => {
    const setItemStub = sandbox.stub(window.localStorage, 'setItem');

    setBrowserStorage(null, data);

    expect(setItemStub.notCalled).to.be.true;
    });

    it('should not call window.localStorage.setItem when type is not "local" or "session"', () => {
    const setItemStub = sandbox.stub(window.localStorage, 'setItem');

    setBrowserStorage('user', data, {type: 'invalid'});

    expect(setItemStub.notCalled).to.be.true;
    });
    });

    describe('getBrowserStorage', () => {
    it('should call window.localStorage.getItem with the correct arguments', () => {
    const getItemStub = sandbox.stub(window.localStorage, 'getItem');
    const path = getPathString('user');

    getBrowserStorage('user');
    expect(getItemStub.calledWith(path)).to.be.true;
    });

    it('should not call window.localStorage.getItem when keyName is not a string', () => {
    const getItemStub = sandbox.stub(window.localStorage, 'getItem');

    expect(getBrowserStorage(null)).to.be.undefined;
    expect(getItemStub.notCalled).to.be.true;
    });

    it('should not call window.localStorage.getItem when type is not "local" or "session"', () => {
    const getItemStub = sandbox.stub(window.localStorage, 'getItem');

    expect(getBrowserStorage('user', {type: 'invalid'})).to.be.undefined;
    expect(getItemStub.notCalled).to.be.true;
    });
    });
    });
    1 change: 1 addition & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    export * from './browserStorage';