UNPKG

diffusion

Version:

Diffusion JavaScript client

209 lines (167 loc) 5.54 kB
var ConversationId = require('conversation/conversation-id'); var logger = require('util/logger'); var Long = require('long'); var NEXT_CID = (function() { var id = new Long(0); return function() { id = id.add(1); return new ConversationId(id); }; }()); var Result = { ALREADY_FINISHED : 0, HANDLED_AND_ACTIVE : 1, HANDLED_AND_FINISHED : 2 }; var LOG = logger.create('Conversation Set'); /** * Implementation of ConversationSet. * <P> * See the Java ConversationSet implementation for more in-depth documentation on the design motivations behind this * class. * * @param {Function} idGenerator function to return new conversation ids */ function ConversationSet(idGenerator) { var nextCID = idGenerator; var conversations = {}; var discardReason; var self = this; function completedExceptionally(cid, conversation, e) { LOG.debug("Application handler threw exception [cid=" + cid + "]", e.stack); conversation.close(); delete conversations[cid]; } this.newConversation = function(responseHandler) { var conversation = new Conversation(responseHandler); var cid = nextCID(); conversations[cid.toString()] = conversation; try { conversation.open(cid); } catch (e) { completedExceptionally(cid, conversation, e); throw e; } if (discardReason) { self.discard(cid, discardReason); } return cid; }; this.respond = function(cid, response) { var conversation = conversations[cid]; if (conversation === undefined) { LOG.debug("No conversation for cid: " + cid + ", response: ", response); throw new Error("No such conversation"); } var result = conversation.respond(cid, response); switch (result) { case Result.ALREADY_FINISHED : throw new Error("No such conversation"); case Result.HANDLED_AND_ACTIVE: break; default: delete conversations[cid]; } }; this.respondIfPresent = function(cid, response) { try { self.respond(cid, response); } catch (e) { // No-op } }; this.discard = function(cid, reason) { var conversation = conversations[cid]; delete conversations[cid]; if (conversation) { conversation.discard(cid, reason); } }; this.discardAll = function(reason) { if (discardReason !== undefined) { return; } discardReason = reason; for (var cid in conversations) { self.discard(ConversationId.fromString(cid), reason); } }; this.size = function() { return Object.keys(conversations).length; }; this.toString = function() { return "ConversationSet cids=[" + Object.keys(conversations) + "]"; }; /** * Internal Conversation implementation. Maintains state for a provided conversation handler to ensure recursive * operations remain in-order. * * @param {Object} handler the conversation handler to dispatch events to. * @constructor */ function Conversation(handler) { var pendingDiscard; var reserved = false; var closed = false; this.open = function(cid) { handler.onOpen(cid); }; this.respond = function(cid, response) { if (closed) { return Result.ALREADY_FINISHED; } // Set as reserved so as to prevent recursive discards var previous = reserved; reserved = true; var close; try { close = handler.onResponse(cid, response); } catch (e) { completedExceptionally(cid, this, e); throw e; } if (closed) { LOG.debug("Conversation already closed", cid); return Result.HANDLED_AND_FINISHED; } else if (close) { LOG.debug("Response handler closed conversation", cid); closed = true; return Result.HANDLED_AND_FINISHED; } else if (pendingDiscard) { LOG.debug("Conversation closed with pending reason", cid); notifyDiscard(cid, pendingDiscard); return Result.HANDLED_AND_FINISHED; } else { reserved = previous; return Result.HANDLED_AND_ACTIVE; } }; this.discard = function(cid, reason) { if (reserved) { pendingDiscard = reason; } else { notifyDiscard(cid, reason); } }; this.close = function() { closed = true; }; function notifyDiscard(cid, reason) { if (!closed) { closed = true; try { handler.onDiscard(cid, reason); } catch (e) { LOG.error("Application handler threw exception [cid=" + cid + "]", e.stack); throw e; } } } } } module.exports = ConversationSet; module.exports.create = function() { return new ConversationSet(NEXT_CID); }; module.exports.Result = Result; module.exports.ConversationIdGenerator = NEXT_CID;