@scion-scxml/scxml
Version:
An implementation of SCXML in JavaScript.
128 lines (116 loc) • 4.52 kB
JavaScript
const resolveUri = require('./url-resolver'),
starToModel = require('./star-to-model'),
documentStringToModel = require('./document-string-to-model').documentStringToModel,
core = require('./core'),
xmlserializer = require('xmlserializer'),
urlM = require('url');
const invokeTypes = {
"http://www.w3.org/TR/scxml/" : function(invokingSession, invokeObj, invokerExecutionContext, cb){
//we are now going to introduce a runtime dependency on scxml module into the generated code...
//should consider how to expose this to both code paths.
//assume we introduce a global variable 'scxml', so that we do not need to use require
//put invoke logic here:
let method, arg;
if(invokeObj.constructorFunction){
method = startSession;
arg = invokeObj.constructorFunction;
}else if(invokeObj.content){
method = invokeContent;
arg = typeof invokeObj.content === 'string' ?
invokeObj.content :
xmlserializer.serializeToString(invokeObj.content); //FIXME: use browser-native data structure in browser environment
} else if(invokeObj.src){
//TODO: handle attribute lookup: srcexpr
method = invoke;
arg = resolveUri(invokeObj.docUrl, invokeObj.src);
} else{
throw new Error('Unable to find one of the following required attributes or children on <invoke> element: <content>, @src, @srcexpr' );
}
return method(arg,
cb,
{
invokeid : invokeObj.id,
params : invokeObj.params,
parentSession : invokingSession,
docUrl : invokeObj.docUrl,
//sessionid : //TODO: construct or generate a sessionid for invoked session
},
invokerExecutionContext);
}
//TODO: implement other invoke types
//http://www.w3.org/TR/ccxml/
//http://www.w3.org/TR/voicexml30/
//http://www.w3.org/TR/voicexml21/
};
invokeTypes['scxml'] = invokeTypes["http://www.w3.org/TR/scxml/"];
invokeTypes['http://www.w3.org/TR/scxml'] = invokeTypes["http://www.w3.org/TR/scxml/"];
const invokeCache = {};
/**
* New front-end for starting a session.
* options:
* parentSession
* params
*/
function invoke(scxmlUrl, cb, options, invokerExecutionContext){
let cached = invokeCache[scxmlUrl];
if(cached){
let fnModel = cached[0], model = cached[1];
return startSession(fnModel, cb, options, model);
}
let urlObj = urlM.parse(scxmlUrl);
switch(urlObj.protocol){
case 'http:':
case 'https:':
doInvoke(starToModel.urlToModel.bind(this, scxmlUrl), cb, options, scxmlUrl, invokerExecutionContext);
break;
//case 'data' //TODO
case 'file:':
default:
doInvoke(starToModel.pathToModel.bind(this, urlObj.pathname), cb, options, scxmlUrl, invokerExecutionContext);
break;
}
}
/**
*
* Options:
* docUrl
* docOffsetLine
* docOffsetColumn
*/
function invokeContent(scxmlString, cb, options, invokerExecutionContext){
let cached = invokeCache[scxmlString];
if(cached){
let fnModel = cached[0], model = cached[1];
return startSession(fnModel, cb, options, model);
}
doInvoke(documentStringToModel.bind(this, options.docUrl, scxmlString), cb, options, scxmlString, invokerExecutionContext);
}
function doInvoke(handler, cb, options, stringOrUrl, invokerExecutionContext){
handler(function(err, model){
//FIXME: how to handle errors? expose as error.communications, external event?
if(err) return cb(err);
model.prepare(function(err, fnModel){
if(err) return cb(err);
invokeCache[stringOrUrl] = [fnModel, model];
startSession(fnModel, cb, options, model)
}, invokerExecutionContext || {console : console});
});
}
function startSession(fnModel, cb, options, model){
var interpreter;
if( options.parentSession instanceof core.Statechart ){
interpreter = new core.Statechart(fnModel, options);
} else if (options.parentSession instanceof core.SCInterpreter) {
interpreter = new core.SCInterpreter(fnModel, options);
} else {
throw new Error('Unknown parent session type');
}
cb(null, interpreter, fnModel, model);
//we introduce a delay here before starting the interpreter to give clients that are subscribed to onInvokedSessionInitialized event a chance to subscribe to events on the newly instantiated interpreter
setImmediate( () => interpreter.start() );
}
module.exports = {
invokeTypes : invokeTypes,
invoke : invoke,
invokeContent : invokeContent
};