UNPKG

bkendz

Version:

Admin page for viewing database entries and user requests in realtime

278 lines (222 loc) 8.23 kB
/** * Created by anthony on 02/02/2018. */ class Deferred { constructor() { this.id = `${this.constructor._ID++}` this.promise = new Promise((res, rej) => { this.resolve = res this.reject = rej }) } cancel() { this.reject('Cancelled!') } then(...args) { this._promise = (this._promise || this.promise).then(...args) return this } catch(...args){ this._promise = (this._promise || this.promise).catch(...args) return this } } Deferred._ID = 1 _.merge(WebSocket.prototype, Object.create(EventEmitter.prototype)) class LazyObject { constructor(getters){ this[Symbol.for('getters')] = getters let properties = _.transform(getters, function(result, func, key) { result[key] = { get: func } }, {}) Object.defineProperties(this, properties) } } class Bkendz extends EventEmitter { constructor() { super() this.retryCount = {api: 0, server: 0} this.deferreds = {} this.apiLocation = null this._templates = {} this._schema = null this._accessToken = null this.debugMode = false } connect(url, options = {}) { let ws = new WebSocket(url, 'echo-protocol') EventEmitter.call(ws) this.retryCount[options.retryCount]++ let name = options.retryCount this[name] = ws let subscriptions = ws.subscriptons = {} ws.on = function (eventName, callback) { let topic = `/${eventName}` console.log(`${name}: event=${eventName}, topic=${topic}`) let subscribe = `/subscribe?subject=${eventName}` ws.json(subscribe) .then((resp) => { subscriptions[resp.data.subscribed] = 0 console.log('subscriptions:', subscriptions) EventEmitter.prototype.on.call(ws, eventName, callback) }) } ws.json = (topic, data) => { let deferred = new Deferred() this.deferreds[deferred.id] = deferred if (!_.isPlainObject(data)) data = {} data.ACCESS_TOKEN = this.accessToken let req = {topic: `${topic}${_.includes(topic, '?') ? '&' : '?'}uid=${deferred.id}`, data: data} ws.send(JSON.stringify(req)) return deferred } ws.onmessage = (message) => { let msg = JSON.parse(message.data) let parts = _.split(msg.topic, '?', 2) let parsed = _.chain(_.last(parts)) .replace('?', '') // a=b454&c=dhjjh&f=g6hksdfjlksd .split('&') // ["a=b454","c=dhjjh","f=g6hksdfjlksd"] .map(_.partial(_.split, _, '=', 2)) // [["a","b454"],["c","dhjjh"],["f","g6hksdfjlksd"]] .fromPairs() // {"a":"b454","c":"dhjjh","f":"g6hksdfjlksd"} .value() let deferredId = parsed.uid let deferred = this.deferreds[deferredId] let topic = _.first(parts) if (_.isObject(deferred)) { delete this.deferreds[deferredId] if(msg.error) deferred.reject(new Error(`[${name}] ${topic}: ${msg.error}`)) else deferred.resolve(msg) } else if (topic === '/subscribe' && parsed.subject in subscriptions) { subscriptions[parsed.subject]++ ws.emit(parsed.subject, msg) } else { console.error('no pending promise', message, topic, parsed) } } return new Promise((resolve, reject) => { ws.onopen = () => { this.retryCount[options.retryCount] = 0 this.emit(options.connected || 'server_connected') resolve(ws) } ws.onclose = () => { this.emit(options.disconnected || 'server_disconnected') reject() } ws.onerror = (err) => { console.error(err) reject(err) } }) } connectToServer() { let url = `${location.protocol === 'https:' ? 'wss://' : 'ws://'}${location.host}` return this.connect(url, { connected: 'server_connected', disconnected: 'server_disconnected', retryCount: 'server' }) .then((server) => { this.server = server }) } connectToApi() { if (!this.apiLocation) Promise.reject(Error('No API location provided!')) let url = this.apiLocation[location.protocol === 'http:' ? 'ws' : 'wss'] console.log('[connectToApi] url=', url, this.apiLocation) return this.connect(url, {connected: 'api_connected', disconnected: 'api_disconnected', retryCount: 'api'}) .then((api) => { this.api = api }) } init() { const emitWrap = (event) => { let emit = event.target.getAttribute(`data-emit-${event.type}`) emit = `${event.type}_${emit}` if (this.debugMode) console.log('[Directive] emitting:', emit) this.emit(emit, event) } ['keyup', 'click', 'change'].forEach((eventType) => { $(document).on(eventType, `[data-emit-${eventType}]`, emitWrap) }) } main() { return new Promise((resolve, reject) => { let start = () => { try { this.init() this.connectToServer().then(resolve) }catch (error){ reject(error) } } document.addEventListener('DOMContentLoaded', start) }) } resolveAccess(email, password) { return this.api.json('/authenticate', {email: email, username: email, password}) .then((resp) => { this.accessToken = resp.data.tokens.access return resp.data.tokens }) } get accessToken() { return this._accessToken } set accessToken(token) { let currToken = this._accessToken this._accessToken = token this.emit('changed_accesstoken', token, currToken) } getTemplate(name, opts) { if (!_.isString(name)) throw new Error('template name must be string, found %s', name) opts = opts || {} let reload = _.isUndefined(opts.reload) ? true : opts.reload let rendered = opts.rendered || {} let cached = this._templates[name] if (reload || !_.isString(cached)) { return this.server.json(`/template?name=${name}`, {rendered}).then((res) => { this._templates[name] = res.data return res.data }) } else { return Promise.resolve(cached) } } repeat(opts) { opts = opts || {} let name = opts.template let targetSelector = opts.target let inputList = opts.templateArgs || [] let wrapperFn = opts.wrapFn || null return this.getTemplate(name) .then((rawStr) => { let compiled = _.template(rawStr) let target = $(targetSelector) _.each(inputList, (inputData, ...args) => { let html = compiled(inputData) if (wrapperFn) { let r = wrapperFn(inputData, ...args) if ('append' in r) html += r.append if ('prepend' in r) html = r.prepend + html } let el = $(html) target.append(el) }) }) } fetch(modelName, options) { return this.api.json(`/fetch?model=${modelName}`, options || {}) } set dbSchema(schema) { let oldAs = this._schema this._schema = schema this.emit('changed_dbschema', schema, oldAs) } get dbSchema() { return this._schema } } Bkendz.VERSION = Bkendz.version = '<%= package.version %>'