@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
359 lines (274 loc) • 8.95 kB
text/typescript
import Log from './util/Log';
import IStorage from './Storage.interface';
import Options from './Options';
const PREFIX = 'jsxc2';
const SEP = ':';
const IGNORE_KEY = ['rid'];
export default class Storage implements IStorage {
public static storageNotConform: boolean = false;
public static tested: boolean = false;
private hooks: any = {};
private static backend;
public static toSNC: number;
public static clear(name?: string) {
Storage.getKeysWithPrefix(name).forEach(key => Storage.backend.removeItem(key));
}
public static getKeysWithPrefix(name?: string): string[] {
let prefix = '';
if (!name.startsWith(PREFIX)) {
prefix = PREFIX + SEP;
}
if (name) {
prefix = prefix + name + SEP;
}
let keys = [];
for (let key in Storage.backend) {
if (!Storage.backend.hasOwnProperty(key)) {
continue;
}
if (key.startsWith(prefix)) {
keys.push(key);
}
}
return keys;
}
constructor(private name: string = null) {
if (!Storage.backend) {
Storage.backend = Options.getDefault('storage');
}
if (!Storage.tested) {
Storage.tested = true;
this.testStorage();
}
window.addEventListener('storage', this.onStorageEvent, false);
}
public getName(): string {
return this.name;
}
public generateKey(...args: string[]): string {
let key = '';
args.forEach(function (arg) {
if (key !== '') {
key += SEP;
}
key += arg;
});
return key;
}
private testStorage() {
let randomNumber = Math.round(Math.random() * 1000000000) + '';
let key = this.getPrefix() + randomNumber;
let timeout;
let listenerFunction = function (ev) {
if (ev.newValue === randomNumber) {
clearTimeout(timeout);
cleanup();
Storage.storageNotConform = true;
}
};
let cleanup = function () {
window.removeEventListener('storage', listenerFunction, false);
Storage.backend.removeItem(key);
};
window.addEventListener('storage', listenerFunction, false);
timeout = setTimeout(function () {
cleanup();
}, 20);
Storage.backend.setItem(key, randomNumber);
}
public getPrefix(): string {
let prefix = PREFIX + SEP;
if (this.name) {
prefix += this.name + SEP;
}
return prefix;
}
public getBackend() {
return Storage.backend;
}
public setItem<Data = any>(type: string, key: string, value: Data): void;
public setItem<Data = any>(key: string, value: Data): void;
public setItem(): void {
let key: string;
let value: any;
if (arguments.length === 2) {
key = arguments[0];
value = arguments[1];
} else if (arguments.length === 3) {
key = arguments[0] + SEP + arguments[1];
value = arguments[2];
}
//@REVIEW why do we just stringify objects?
if (typeof value === 'object') {
// exclude jquery objects, because otherwise safari will fail
try {
value = JSON.stringify(value, function (key, val) {
if (!(val instanceof jQuery)) {
return val;
}
});
} catch (err) {
Log.warn('Could not stringify value', err);
}
}
let oldValue = Storage.backend.getItem(this.getPrefix() + key);
Storage.backend.setItem(this.getPrefix() + key, value);
if (!Storage.storageNotConform && oldValue !== value) {
this.onStorageEvent({
key: this.getPrefix() + key,
oldValue,
newValue: value,
});
}
}
public getItem<Data = any>(type: string, key: string): Data;
public getItem<Data = any>(key: string): Data;
public getItem(): any {
let key: string;
if (arguments.length === 1) {
key = arguments[0];
} else if (arguments.length === 2) {
key = arguments[0] + SEP + arguments[1];
}
key = this.getPrefix() + key;
if (!Storage.backend.hasOwnProperty(key)) {
return undefined;
}
let value = Storage.backend.getItem(key);
return this.parseValue(value);
}
public removeItem(type, key): void;
public removeItem(key): void;
public removeItem(): void {
let key;
if (arguments.length === 1) {
key = arguments[0];
} else if (arguments.length === 2) {
key = arguments[0] + SEP + arguments[1];
}
Storage.backend.removeItem(this.getPrefix() + key);
}
public updateItem(type: string, key: string, variable: string, value: any): void;
public updateItem(key: string, variable: string, value: any): void;
public updateItem(): void {
let key: string;
let variable: string;
let value: any;
if (arguments.length === 4 || (arguments.length === 3 && typeof variable === 'object')) {
key = arguments[0] + SEP + arguments[1];
variable = arguments[2];
value = arguments[3];
} else {
key = arguments[0];
variable = arguments[1];
value = arguments[2];
}
let data = this.getItem(key) || {};
if (typeof variable === 'object') {
$.each(variable, function (key, val) {
if (typeof data[key] === 'undefined') {
Log.debug(`Variable ${key} doesn't exist in ${variable}. It was created.`);
}
data[key] = val;
});
} else {
if (typeof data[variable] === 'undefined') {
Log.debug('Variable ' + variable + " doesn't exist. It was created.");
}
data[variable] = value;
}
this.setItem(key, data);
}
public increment(key: string): void {
let value = Number(this.getItem(key));
this.setItem(key, value + 1);
}
public removeElement(type, key, name): void;
public removeElement(key, name): void;
public removeElement(): void {
let key;
let name;
if (arguments.length === 2) {
key = arguments[0];
name = arguments[1];
} else if (arguments.length === 3) {
key = arguments[0] + SEP + arguments[1];
name = arguments[2];
}
let item = this.getItem(key);
if ($.isArray(item)) {
item = $.grep(item, function (e) {
return e !== name;
});
} else if (typeof item === 'object' && item !== null) {
delete item[name];
}
this.setItem(key, item);
}
public getItemsWithKeyPrefix(keyPrefix: string) {
let prefix = this.getPrefix() + keyPrefix;
let fullKeys = Storage.getKeysWithPrefix(prefix);
let items = {};
for (const fullKey of fullKeys) {
let key = fullKey.replace(new RegExp('^' + this.getPrefix()), '');
items[key] = this.getItem(key);
}
return items;
}
public registerHook(eventName: string, func: (newValue: any, oldValue: any, key: string) => void) {
if (!this.hooks[eventName]) {
this.hooks[eventName] = [];
}
this.hooks[eventName].push(func);
}
public removeHook(eventName: string, func?: (newValue: any, oldValue: any, key: string) => void) {
let eventNameList = this.hooks[eventName] || [];
if (typeof func === 'undefined') {
eventNameList = [];
} else if (eventNameList.indexOf(func) > -1) {
eventNameList = $.grep(eventNameList, function (i) {
return func !== i;
});
}
this.hooks[eventName] = eventNameList;
}
public removeAllHooks() {
this.hooks = {};
}
public destroy() {
this.removeAllHooks();
window.removeEventListener('storage', this.onStorageEvent, false);
}
private onStorageEvent = (ev: any) => {
let prefix = this.getPrefix();
if (!ev.key || ev.key.indexOf(prefix) !== 0) {
return;
}
let key = ev.key.slice(prefix.length);
if (IGNORE_KEY.indexOf(key) > -1) {
return;
}
let hooks = this.hooks;
let oldValue = this.parseValue(ev.oldValue);
let newValue = this.parseValue(ev.newValue);
let eventNames = Object.keys(hooks);
eventNames.forEach(function (eventName) {
if (eventName === '*' || key === eventName || key.indexOf(eventName + ':') === 0) {
let eventNameHooks = hooks[eventName] || [];
eventNameHooks.forEach(function (hook) {
hook(newValue, oldValue, key);
});
}
});
};
private parseValue(value: string) {
if (value === 'undefined') {
return;
}
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}
}