@blockv/sdk
Version:
Allows web apps to display and interact with vatoms.
270 lines (199 loc) • 7.76 kB
JavaScript
"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;