UNPKG

@blockv/sdk

Version:

Allows web apps to display and interact with vatoms.

270 lines (199 loc) 7.76 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _Region = _interopRequireDefault(require("../Region")); var _Vatom = _interopRequireDefault(require("../../../model/Vatom")); var _DataObjectAnimator = _interopRequireDefault(require("../DataObjectAnimator")); var _lodash = require("lodash"); var _Delayer = _interopRequireDefault(require("../Delayer")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Intermediate class which handles updates via the BLOCKv websocket and returning Vatom objects. Regions can subclass this to automatically * get updates via WebSocket. */ class BLOCKvRegion extends _Region.default { constructor(dataPool) { super(dataPool); // Queue of pending WebSocket messages this.queuedMessages = []; this.socketPaused = false; this.socketProcessing = false; // Bind functions this.onWebSocketMessage = this.onWebSocketMessage.bind(this); // Add listeners for the WebSocket this.socket = this.dataPool.Blockv.WebSockets; this.socket.connect(); this.socket.addEventListener('websocket.raw', this.onWebSocketMessage); // Monitor for timed updates _DataObjectAnimator.default.addRegion(this); } /** Called when this region is going to be shut down */ close() { super.close(); // Remove listeners this.socket.removeEventListener('websocket.raw', this.onWebSocketMessage); _DataObjectAnimator.default.removeRegion(this); } /** * Called to pause processing of websocket messages * @private Called by subclasses. */ pauseMessages() { this.socketPaused = true; } /** * Called to resume processing of websocket messages * * @private Called by subclasses. */ resumeMessages() { // Unpause this.socketPaused = false; // Process next message if needed if (!this.socketProcessing) { this.processNextMessage(); } } /** * Called when there's a new event message via the WebSocket. * * @private * @param {Object} msg The raw JSON from the websocket event message */ onWebSocketMessage(msg) { // Add to queue this.queuedMessages.push(msg); // Process it if necessary if (!this.socketPaused && !this.socketProcessing) { this.processNextMessage(); } } /** * Called to process the next WebSocket message. */ async processNextMessage() { // Stop if socket is paused if (this.socketPaused) { return; } // Stop if already processing if (this.socketProcessing) { return; } this.socketProcessing = true; // Process message try { // Get next msg to process let msg = this.queuedMessages.shift(); if (!msg) { // No more messages! this.socketProcessing = false; return; } // Process message await this.processMessage(msg); } catch (err) { // Error! console.warn('[DataPool > BVWebSocketRegion] Error processing WebSocket message! ', err); } // Done, process next message this.socketProcessing = false; this.processNextMessage(); } /** * Processes a WebSocket message. * * @private Called by BVWebSocketRegion. * @abstract Subclasses can override to process other WebSocket messages. Always call super.processMessage(msg) though. * @param {Object} msg The raw JSON from the websocket event message */ processMessage(msg) { // Get vatom ID let vatomID = msg.payload && msg.payload.id; if (!vatomID) { return console.warn(`[DataPool > BVWebSocketRegion] Got websocket message with no vatom ID in it: `, msg); } // Ensure it's a state update message if (msg.msg_type !== 'state_update' || !msg.payload.new_object) { return; } // Update existing objects this.updateObjects([{ id: msg.payload.id, new_data: msg.payload.new_object }]); } /** Map our data objects to Vatom objects */ map(object) { // Only handle vatoms if (object.type !== 'vatom') { return null; } // Fetch all faces linked to this vatom let faces = Array.from(this.objects.values()).filter(o => o.type === 'face' && o.data.template === object.data['vAtom::vAtomType'].template).map(o => o.data); // Fetch all actions linked to this vatom let actions = Array.from(this.objects.values()).filter(o => o.type === 'action' && o.data.name.startsWith(object.data['vAtom::vAtomType'].template + '::Action::')).map(o => o.data); // Create vatom object return new _Vatom.default(object.data, faces, actions); } /** * Called when an object is about to be added. * * @private * @abstract Can be overridden by subclasses which need to get these events. * @param {DataObject} object The object which will be added. */ willAdd(object) { // Notify parent as well let parent = object.data && object.data['vAtom::vAtomType'] && object.data['vAtom::vAtomType'].parent_id; if (parent) { _Delayer.default.run(e => this.emit('object.updated', parent)); } // If our DataObjectAnimator has a scheduled update for this object, include that change now. This is to work around map objects jumping around when a new region is created. let nextUpdate = _DataObjectAnimator.default.changes.find(u => u.id === object.id); if (nextUpdate) { (0, _lodash.merge)(object.data, nextUpdate.new_data); } } /** * Called when an object is about to be updated. * * @private * @abstract Can be overridden by subclasses which need to get these events. * @param {DataObject} object The object which will be updated. * @param {Object} newData The sparse object containing the changed fields */ willUpdateFields(object, newData) { // Notify parent as well let oldParent = object.data && object.data['vAtom::vAtomType'] && object.data['vAtom::vAtomType'].parent_id; let newParent = newData && newData['vAtom::vAtomType'] && newData['vAtom::vAtomType'].parent_id; if (newParent) { _Delayer.default.run(e => this.emit('object.updated', oldParent)); } if (newParent) { _Delayer.default.run(e => this.emit('object.updated', newParent)); } } /** * Called when an object is about to be updated. * * @private * @abstract Can be overridden by subclasses which need to get these events. * @param {DataObject} object The object which will be updated. * @param {String} keyPath The field which will be changed. * @param {*} oldValue The current field value. * @param {*} newValue The new field value. */ willUpdateField(object, keyPath, oldValue, newValue) { // Only do if modifying the parent ID field if (keyPath !== 'vAtom::vAtomType.parent_id') { return; } // Notify parent _Delayer.default.run(e => this.emit('object.updated', oldValue)); _Delayer.default.run(e => this.emit('object.updated', newValue)); } /** * Called when an object is about to be removed. * * @private * @abstract Can be overridden by subclasses which need to get these events. * @param {DataObject|String} objectOrID The object (or ID) which will be updated. */ willRemove(objectOrID) { // Get object if needed let object = objectOrID; if (typeof objectOrID === 'string') { object = this.objects.get(objectOrID); } // Notify parent as well let parent = object && object.data && object.data['vAtom::vAtomType'] && object.data['vAtom::vAtomType'].parent_id; if (parent) { _Delayer.default.run(e => this.emit('object.updated', parent)); } } } exports.default = BLOCKvRegion;