UNPKG

shaku

Version:

A simple and effective JavaScript game development framework that knows its place!

239 lines (209 loc) 6.34 kB
/** * Implement a storage wrapper. * * |-- copyright and license --| * @module Shaku * @file shaku\src\utils\storage.js * @author Ronen Ness (ronenness@gmail.com | http://ronenness.com) * @copyright (c) 2021 Ronen Ness * @license MIT * |-- end copyright and license --| * */ 'use strict'; const StorageAdapter = require("./storage_adapter"); /** * A thin wrapper layer around storage utility. */ class Storage { /** * Create the storage. * @param {Array<StorageAdapter>} adapters List of storage adapters to pick from. Will use the first option returning 'isValid()' = true. * @param {String} prefix Optional prefix to add to all keys under this storage instance. * @param {Boolean} valuesAsBase64 If true, will encode and decode data as base64. * @param {Boolean} keysAsBase64 If true, will encode and decode keys as base64. */ constructor(adapters, prefix, valuesAsBase64, keysAsBase64) { // default adapters adapters = adapters || Storage.defaultAdapters; // default to array if (!Array.isArray(adapters)) { adapters = [adapters]; } // choose adapter this._adapter = null; for (let adapter of adapters) { if (adapter.isValid()) { this._adapter = adapter; break; } } // set if should use base64 this.valuesAsBase64 = Boolean(valuesAsBase64); this.keysAsBase64 = Boolean(keysAsBase64); // set prefix this._keysPrefix = 'shaku_storage_' + (prefix || '') + '_'; } /** * Return if this storage adapter is persistent storage or not. * @returns {Boolean} True if this storage type is persistent. */ get persistent() { return this.isValid && this._adapter.persistent; } /** * Check if this storage instance has a valid adapter. * @returns {Boolean} True if found a valid adapter to use, false otherwise. */ get isValid() { return Boolean(this._adapter); } /** * Convert key to string and add prefix if needed. * @private * @param {String} key Key to normalize. * @returns {String} Normalized key. */ normalizeKey(key) { key = this._keysPrefix + key.toString(); if (this.keysAsBase64) { key = btoa(key); } return key; } /** * Check if a key exists. * @param {String} key Key to check. * @returns {Boolean} True if key exists in storage. */ exists(key) { if (typeof key !== 'string') { throw new Error("Key must be a string!"); } key = this.normalizeKey(key); return this._adapter.exists(key); } /** * Set value. * @private */ #_set(key, value) { // json stringify value = JSON.stringify({ data: value, timestamp: (new Date()).getTime(), src: "Shaku", sver: 1.0 }); // convert to base64 if (this.valuesAsBase64) { value = btoa(value); } // store value this._adapter.setItem(key, value); } /** * Get value. * @private */ #_get(key) { // get value var value = this._adapter.getItem(key); // not found? return null if (value === null) { return null; } // convert from base64 if (this.valuesAsBase64) { try { value = atob(value); } catch (e) { throw new Error("Failed to parse Base64 string while reading data. Did you try to read a value as Base64 that wasn't encoded as Base64 when written to storage?"); } } // parse json try { value = JSON.parse(value); } catch (e) { throw new Error("Failed to JSON-parse data from storage. Did you try to read something that wasn't written with the Storage utility?"); } // return value return value.data; } /** * Set value. * @param {String} key Key to set. * @param {String} value Value to set. */ setItem(key, value) { // sanity and normalize key if (typeof key !== 'string') { throw new Error("Key must be a string!"); } key = this.normalizeKey(key); // write value with metadata this.#_set(key, value); } /** * Get value. * @param {String} key Key to get. * @returns {String} Value or null if not set. */ getItem(key) { // sanity and normalize key if (typeof key !== 'string') { throw new Error("Key must be a string!"); } key = this.normalizeKey(key); // read value from metadata return this.#_get(key); } /** * Get value and JSON parse it. * @param {String} key Key to get. * @returns {*} Value as a dictionary object or null if not set. */ getJson(key) { return this.getItem(key) || null; } /** * Set value as JSON. * @param {String} key Key to set. * @param {*} value Value to set as a dictionary. */ setJson(key, value) { key = this.normalizeKey(key); this.#_set(key, value); } /** * Delete value. * @param {String} key Key to delete. */ deleteItem(key) { if (typeof key !== 'string') { throw new Error("Key must be a string!"); } key = this.normalizeKey(key); this._adapter.deleteItem(key); } /** * Clear all values from this storage instance, based on prefix + adapter type. */ clear() { this._adapter.clear(this._keysPrefix); } } /** * Default adapters to use when no adapters list is provided. */ Storage.defaultAdapters = [new StorageAdapter.localStorage(), new StorageAdapter.sessionStorage(), new StorageAdapter.memory()]; // export the storage class module.exports = Storage;