1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 12:55:55 +00:00
FrankerFaceZ/src/settings/providers.js
2017-11-13 01:23:39 -05:00

301 lines
7 KiB
JavaScript

'use strict';
// ============================================================================
// Settings Providers
// ============================================================================
import {EventEmitter} from 'utilities/events';
import {has} from 'utilities/object';
// ============================================================================
// SettingsProvider
// ============================================================================
/**
* Base class for providers for the settings system. A provider is in charge
* of reading and writing values from storage as well as sending events to
* the {@link SettingsManager} when a value is changed remotely.
*
* @extends EventEmitter
*/
export class SettingsProvider extends EventEmitter {
/**
* Create a new SettingsProvider
* @param {SettingsManager} manager - The manager that owns this provider.
*/
constructor(manager) {
super();
this.manager = manager;
this.disabled = false;
}
awaitReady() {
if ( this.ready )
return Promise.resolve();
return Promise.reject(new Error('Not Implemented'));
}
get(key, default_value) { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this, no-unused-vars
set(key, value) { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this, no-unused-vars
delete(key) { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this, no-unused-vars
clear() { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this
has(key) { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this, no-unused-vars
keys() { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this
entries() { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this
get size() { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this
}
// ============================================================================
// LocalStorage
// ============================================================================
export class LocalStorageProvider extends SettingsProvider {
constructor(manager, prefix) {
super(manager);
this.prefix = prefix = prefix == null ? 'FFZ:setting:' : prefix;
const cache = this._cached = new Map,
len = prefix.length;
for(const key in localStorage)
if ( has(localStorage, key) && key.startsWith(prefix) ) {
const val = localStorage.getItem(key);
try {
cache.set(key.slice(len), JSON.parse(val));
} catch(err) {
this.manager.log.warn(`unable to parse value for ${key}`, val);
}
}
this.ready = true;
this._boundHandleStorage = this.handleStorage.bind(this);
window.addEventListener('storage', this._boundHandleStorage);
}
destroy() {
this.disable();
this._cached.clear();
}
disable() {
this.disabled = true;
if ( this._boundHandleStorage ) {
window.removeEventListener('storage', this._boundHandleStorage);
this._boundHandleStorage = null;
}
}
handleStorage(event) {
if ( this.disabled )
return;
this.manager.log.debug('storage event', event);
if ( event.storageArea !== localStorage )
return;
if ( event.key.startsWith(this.prefix) ) {
// If value is null, the key was deleted.
const key = event.key.slice(this.prefix.length);
let val = event.newValue;
if ( val === null ) {
this._cached.delete(key);
this.emit('changed', key, undefined, true);
} else {
val = JSON.parse(val);
this._cached.set(key, val);
this.emit('changed', key, val, false);
}
}
}
get(key, default_value) {
return this._cached.has(key) ?
this._cached.get(key) :
default_value;
}
set(key, value) {
this._cached.set(key, value);
localStorage.setItem(this.prefix + key, JSON.stringify(value));
}
delete(key) {
this._cached.delete(key);
localStorage.removeItem(this.prefix + key);
}
has(key) {
return this._cached.has(key);
}
keys() {
return this._cached.keys();
}
clear() {
for(const key of this._cached.keys())
localStorage.removeItem(this.prefix + key);
this._cached.clear();
}
entries() {
return this._cached.entries();
}
get size() {
return this._cached.size;
}
}
export class CloudStorageProvider extends SettingsProvider {
constructor(manager) {
super(manager);
this._cached = new Map;
this.ready = false;
this._ready_wait = null;
this._boundHandleStorage = this.handleStorage.bind(this);
window.addEventListener('message', this._boundHandleStorage);
this._send('get_all');
}
destroy() {
this.disable();
this._cached.clear();
}
disable() {
this.disabled = true;
if ( this._boundHandleStorage ) {
window.removeEventListener('message', this._boundHandleStorage)
this._boundHandleStorage = null;
}
}
awaitReady() {
if ( this.ready )
return Promise.resolve();
return new Promise((resolve, reject) => {
const waiters = this._ready_wait = this._ready_wait || [];
waiters.push([resolve, reject]);
})
}
// ========================================================================
// Communication
// ========================================================================
handleStorage(event) {
if ( event.source !== window || ! event.data || ! event.data.ffz )
return;
const cmd = event.data.cmd,
data = event.data.data;
if ( cmd === 'all_values' ) {
const old_keys = new Set(this._cached.keys());
for(const key in data)
if ( has(data, key) ) {
const val = data[key];
old_keys.delete(key);
this._cached.set(key, val);
if ( this.ready )
this.emit('changed', key, val);
}
for(const key of old_keys) {
this._cached.delete(key);
if ( this.ready )
this.emit('changed', key, undefined, true);
}
this.ready = true;
if ( this._ready_wait ) {
for(const resolve of this._ready_wait)
resolve();
this._ready_wait = null;
}
} else if ( cmd === 'changed' ) {
this._cached.set(data.key, data.value);
this.emit('changed', data.key, data.value);
} else if ( cmd === 'deleted' ) {
this._cached.delete(data);
this.emit('changed', data, undefined, true);
} else {
this.manager.log.info('unknown storage event', event);
}
}
_send(cmd, data) { // eslint-disable-line class-methods-use-this
window.postMessage({
ffz: true,
cmd,
data
}, location.origin);
}
// ========================================================================
// Data Access
// ========================================================================
get(key, default_value) {
return this._cached.has(key) ?
this._cached.get(key) :
default_value;
}
set(key, value) {
this._cached.set(key, value);
this._send('set', {key, value});
}
delete(key) {
this._cached.delete(key);
this._send('delete', key);
}
has(key) {
return this._cached.has(key);
}
keys() {
return this._cached.keys();
}
clear() {
this._cached.clear();
this._send('clear');
}
entries() {
return this._cached.entries();
}
get size() {
return this._cached.size;
}
}