Comparing version 0.5.10 to 0.5.11
@@ -180,23 +180,48 @@ [![NPM version](https://badge.fury.io/js/nogap.svg)](http://badge.fury.io/js/nogap) | ||
```js | ||
tellMeSomething: function(name) { | ||
nBytes += (message && message.length) || 0; | ||
return 'Host has received a total of ' + nBytes + ' bytes.'; | ||
} | ||
var NoGapDef = require('nogap').Def; | ||
// ... | ||
NoGapDef.component({ | ||
Host: NoGapDef.defHost(function(SharedTools, Shared, SharedContext) { | ||
var nBytes = 0; | ||
onButtonClick: function() { | ||
document.body.innerHTML +='Button was clicked.<br />'; | ||
this.host.tellMeSomething('hello!') | ||
.bind(this) // this is tricky! | ||
.then(function(hostMessage) { | ||
this.showHostMessage(hostMessage); | ||
}); | ||
}, | ||
return { | ||
Public: { | ||
tellMeSomething: function(message) { | ||
nBytes += (message && message.length) || 0; | ||
this.Tools.log('Client said: ' + message); | ||
return 'Thank you! I now received a total of ' + nBytes + ' bytes.'; | ||
} | ||
} | ||
}; | ||
}), | ||
Client: NoGapDef.defClient(function(Tools, Instance, Context) { | ||
return { | ||
initClient: function() { | ||
// bind a button to a component function (quick + dirty): | ||
window.clickMe = this.onButtonClick.bind(this); | ||
document.body.innerHTML += '<button onclick="window.clickMe();">Click Me!</button><br />'; | ||
}, | ||
onButtonClick: function() { | ||
document.body.innerHTML +='Button was clicked.<br />'; | ||
this.host.tellMeSomething('hello!') | ||
.then(function(hostMessage) { | ||
document.body.innerHTML += 'Host said: ' + hostMessage + '<br />'; | ||
}) | ||
.catch(function(err) { | ||
// this can be a connection error, a bug, a Host-side `reject` etc etc... | ||
console.error('Something went wrong: ' + (err.stack || err)); | ||
}); | ||
} | ||
}; | ||
}) | ||
}); | ||
``` | ||
**New Concepts** | ||
* `Client.initClient` will be called right after the Client connected. | ||
* Upon button click, we call `this.host.tellMeSomething(...)` which will send a request to the `Host` to invoke that method (given it is in `Host.Public`). | ||
* Calling a `Public` function on a component's `host` object returns a promise. | ||
* That promise is part of a full-stack [Promise chains](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them). A value returned by a `Host`'s `Public` function (or by a promise returned by such function), will be received by the client. | ||
* Note that [JavaScript's `this` is tricky](http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/)! | ||
* That promise is part of a full-stack [Promise chains](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them). Once finnished, we get the return value from that `Host` method in our `then` callback. | ||
@@ -247,3 +272,4 @@ | ||
* We need to perform an asynchronous request whose result is to be sent to the other side | ||
* Simply use [Promise chains](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them)! | ||
* Simply use [Promise chains](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them)! | ||
* NOTE: [JavaScript's `this` is tricky](http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/)! | ||
@@ -250,0 +276,0 @@ |
@@ -21,2 +21,4 @@ /** | ||
initHost: function(app, cfg) {}, | ||
bootstrap: function(app, cfg) {}, | ||
@@ -31,8 +33,11 @@ Private: { | ||
Client: ComponentDef.defClient(function(Tools, Instance, Context) { return { | ||
Private: { | ||
/** | ||
* This client-side function tells the host to execute the given command of the given component. | ||
*/ | ||
sendClientRequestToHost: function(clientRequest) {}, | ||
/** | ||
* This client-side function tells the host to execute the given command of the given component. | ||
*/ | ||
sendRequestToHost: function(clientRequest) {}, | ||
Public: { | ||
refresh: function() {}, | ||
redirect: function(newLocation, inOldContext) {} | ||
} | ||
@@ -181,2 +186,12 @@ }}) | ||
} | ||
}, | ||
redirect: function(newLocation, inOldContext) { | ||
var connection = this.getDefaultConnection(); | ||
if (connection.client) { | ||
connection.client.redirect(newLocation, inOldContext); | ||
} | ||
else { | ||
connection.redirect(newLocation, inOldContext); | ||
} | ||
} | ||
@@ -471,3 +486,3 @@ } | ||
.then(function() { | ||
return connection.sendCustomClientRequestToHost.apply(connection, args); | ||
return connection.sendRequestToHost.apply(connection, args); | ||
}) | ||
@@ -519,3 +534,3 @@ | ||
var clientRequest = this._requestPacket.compilePacket(); | ||
return this.getDefaultConnection().sendClientRequestToHost(clientRequest) | ||
return this.getDefaultConnection().sendRequestToHost(clientRequest) | ||
@@ -673,2 +688,6 @@ // once received, handle reply sent back by Host | ||
} | ||
}, | ||
getCurrentConnectionState: function() { | ||
return this._currentConnectionState; | ||
} | ||
@@ -745,3 +764,3 @@ } | ||
'ComponentCommunications', 'requestRefresh'); | ||
this.Def.InstanceProto.sendResponseToClient(responsePacket, res); | ||
this.Def.InstanceProto.sendRPCResponseToClient(responsePacket, res); | ||
res.end(); | ||
@@ -769,3 +788,3 @@ return null; | ||
var connection = Instance.Libs.ComponentCommunications.getDefaultConnection(); | ||
connection.sendResponseToClient(hostResponseData, res); | ||
connection.sendRPCResponseToClient(hostResponseData, res); | ||
}); | ||
@@ -827,2 +846,5 @@ }; | ||
router.get(cfg.baseUrl + "*", function(req, res, next) { | ||
this._currentConnectionState = res; | ||
this._lastReq = req; | ||
// register error handler to avoid application crash | ||
@@ -853,4 +875,5 @@ var onError = function(err) { | ||
router.post(cfg.baseUrl, function(req, res, next) { | ||
// extract body data | ||
// see: http://stackoverflow.com/a/4310087/2228771 | ||
// remember request object | ||
this._lastReq = req; | ||
var Instance = req.getInstance(); | ||
@@ -860,6 +883,9 @@ if (!Instance) { | ||
// res.end(); | ||
return; | ||
} | ||
this._currentConnectionState = res; | ||
// extract body data | ||
// see: http://stackoverflow.com/a/4310087/2228771 | ||
var body = ''; | ||
@@ -870,30 +896,9 @@ req.on('data', function (data) { | ||
req.on('end', function () { | ||
// execute RPC request and send host's response back to client | ||
var implementationInstance = Instance.Libs.ComponentCommunications.getDefaultConnection(); | ||
var clientRequest; | ||
try { | ||
clientRequest = serializer.deserialize(body); | ||
} | ||
catch (err) { | ||
// empty request is invalid | ||
var maxLen = 800; | ||
if (body.length > maxLen) { | ||
// truncate to at most maxLen characters | ||
body = body.substring(0, maxLen) + '...'; | ||
} | ||
err = new Error('Invalid data sent by client cannot be parsed: ' + | ||
body + ' -- Error: ' + err.message || err); | ||
implementationInstance._serializeAndSendError(err, res); | ||
return; | ||
} | ||
if (!clientRequest) { | ||
// empty request is invalid | ||
implementationInstance._serializeAndSendError(new Error('Empty client request'), res); | ||
return; | ||
} | ||
// handle the request | ||
implementationInstance._handleClientRPCRequest(req, res, next, clientRequest); | ||
implementationInstance.handleClientRPCRequestString(body, req.headers, res) | ||
.bind(this) | ||
.finally(function() { | ||
this._currentConnectionState = null; | ||
}); | ||
}.bind(this)); | ||
@@ -903,6 +908,2 @@ }.bind(this)); | ||
setGlobal: function(varName, varValue) { | ||
GLOBAL[varName] = varValue; | ||
}, | ||
Private: { | ||
@@ -929,16 +930,3 @@ __ctor: function() { | ||
_serializeAndSendError: function(err, res) { | ||
var hostResponseData = [this.Instance.Libs.ComponentCommunications.serializeRPCError(err)]; | ||
return this.Instance.Libs.ComponentCommunications.executeInOrder(function() { | ||
return hostResponseData; | ||
}) | ||
.bind(this) | ||
.then(function(packet) { | ||
this.sendResponseToClient(packet, res); | ||
}); | ||
}, | ||
_handleClientBootstrapRequest: function(req, res, next) { | ||
this._lastReq = req; | ||
// store some client information | ||
@@ -1023,14 +1011,51 @@ var clientRoot = req.protocol + '://' + req.get('host'); | ||
_handleClientRPCRequest: function(req, res, next, clientRequest) { | ||
// remember request object | ||
this._lastReq = req; | ||
_serializeAndSendError: function(err, connectionState) { | ||
var hostResponseData = [this.Instance.Libs.ComponentCommunications.serializeRPCError(err)]; | ||
return this.Instance.Libs.ComponentCommunications.executeInOrder(function() { | ||
return hostResponseData; | ||
}) | ||
.bind(this) | ||
.then(function(packet) { | ||
this.sendRPCResponseToClient(packet, connectionState); | ||
}); | ||
}, | ||
handleClientRPCRequestString: function(requestString, requestMetadata, connectionState) { | ||
var clientRequest; | ||
try { | ||
clientRequest = serializer.deserialize(requestString); | ||
} | ||
catch (err) { | ||
// empty request is invalid | ||
var maxLen = 800; | ||
if (requestString.length > maxLen) { | ||
// truncate to at most maxLen characters | ||
requestString = requestString.substring(0, maxLen) + '...'; | ||
} | ||
err = new Error('Invalid data sent by client cannot be parsed: ' + | ||
requestString + ' -- Error: ' + err.message || err); | ||
this._serializeAndSendError(err, connectionState); | ||
return; | ||
} | ||
if (!clientRequest) { | ||
// empty request is invalid | ||
this._serializeAndSendError(new Error('Empty client request'), connectionState); | ||
return; | ||
} | ||
// handle the request | ||
return this.handleClientRPCRequest(clientRequest, requestMetadata, connectionState); | ||
}, | ||
handleClientRPCRequest: function(clientRequest, requestMetadata, connectionState) { | ||
var Tools = this.Instance.Libs.ComponentCommunications.Tools; | ||
// Execute commands. | ||
// Once finished executing, `sendResponseToClient` will be called | ||
// Once finished executing, `sendRPCResponseToClient` will be called | ||
// with the response packet to be interpreted on the client side. | ||
return this.Instance.Libs.ComponentCommunications.handleClientRequest(req.headers, clientRequest) | ||
return this.Instance.Libs.ComponentCommunications.handleClientRequest(requestMetadata, clientRequest) | ||
.then(function(hostResponseData) { | ||
this.sendResponseToClient(hostResponseData, res); | ||
this.sendRPCResponseToClient(hostResponseData, connectionState); | ||
}.bind(this)) | ||
@@ -1045,17 +1070,18 @@ | ||
*/ | ||
sendResponseToClient: function(response, res) { | ||
console.assert(res, 'Tried to call `sendResponseToClient` without `res` connection state object.'); | ||
sendRPCResponseToClient: function(hostResponseData, connectionState) { | ||
// connectionState == HtppResponse object | ||
console.assert(connectionState, 'Tried to call `sendRPCResponseToClient` without `connectionState` connection state object.'); | ||
// if response object is given, send right away | ||
var commandStr; | ||
var hostResponseString; | ||
try { | ||
// Serialize | ||
commandStr = serializer.serialize(response); | ||
hostResponseString = serializer.serialize(hostResponseData); | ||
} | ||
catch (err) { | ||
// produce pruned string representation | ||
var commStr = squishy.objToString(response, true, 3); | ||
var commStr = squishy.objToString(hostResponseData, true, 3); | ||
res.statusCode = 500; | ||
res.end(); | ||
connectionState.statusCode = 500; | ||
connectionState.end(); | ||
@@ -1070,6 +1096,7 @@ // then report error | ||
// flush response & close connection | ||
res.contentType('application/json'); | ||
res.setHeader("Access-Control-Allow-Origin", "*"); | ||
res.write(commandStr); | ||
res.end(); | ||
connectionState.contentType('application/json'); | ||
connectionState.setHeader("Access-Control-Allow-Origin", "*"); | ||
connectionState.write(hostResponseString); | ||
connectionState.end(); | ||
this._currentConnectionState = null; | ||
}, | ||
@@ -1102,2 +1129,3 @@ } | ||
}, | ||
/** | ||
@@ -1107,11 +1135,5 @@ * This client-side function is called when a host command is called from a client component. | ||
*/ | ||
sendClientRequestToHost: function(clientRequestData) { | ||
return this.sendCustomClientRequestToHost('', clientRequestData, true); | ||
}, | ||
sendRequestToHost: function(clientRequestData, path, dontSerialize, requestMetadata) { | ||
path = path || ''; | ||
/** | ||
* This client-side function is called when a host command is called from a client component. | ||
* It will transport the commands to the host, wait for the reply, and then execute the commands that were sent back. | ||
*/ | ||
sendCustomClientRequestToHost: function(path, clientRequestData, serialize, customHeaders) { | ||
var ComponentCommunications = Instance.Libs.ComponentCommunications; | ||
@@ -1124,4 +1146,4 @@ if (ComponentCommunications.hasRefreshBeenRequested()) { | ||
// add security and other metadata, required by NoGap | ||
customHeaders = customHeaders || {}; | ||
ComponentCommunications.prepareRequestMetadata(customHeaders); | ||
requestMetadata = requestMetadata || {}; | ||
ComponentCommunications.prepareRequestMetadata(requestMetadata); | ||
@@ -1131,3 +1153,8 @@ | ||
// send Ajax POST request (without jQuery) | ||
var xhReq = (typeof(XMLHttpRequest) !== 'undefined') ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); | ||
var xhReq = (typeof(XMLHttpRequest) !== 'undefined') ? new XMLHttpRequest() : | ||
((typeof(ActiveXObject) !== 'undefined') && new ActiveXObject("Microsoft.XMLHTTP")); | ||
if (!xhReq) { | ||
throw new Error('Could not `sendRequestToHost` - `XMLHttpRequest` is not available.'); | ||
} | ||
@@ -1137,8 +1164,8 @@ // send out the command request | ||
xhReq.open('POST', cfg.remoteUrl + path, true); | ||
if (serialize) { | ||
if (!dontSerialize) { | ||
xhReq.setRequestHeader('Content-type','application/json; charset=' + (cfg.charset || 'utf-8') + ';'); | ||
clientRequestData = serializer.serialize(clientRequestData); | ||
} | ||
for (var headerName in customHeaders) { | ||
xhReq.setRequestHeader(headerName, customHeaders[headerName]); | ||
for (var headerName in requestMetadata) { | ||
xhReq.setRequestHeader(headerName, requestMetadata[headerName]); | ||
} | ||
@@ -1204,2 +1231,7 @@ | ||
window.location.reload(); | ||
}, | ||
redirect: function(newLocation, inOldContext) { | ||
// open new location (really: an URL) | ||
window.open(newLocation, inOldContext ? null : '_blank'); | ||
} | ||
@@ -1206,0 +1238,0 @@ }, |
{ | ||
"name": "nogap", | ||
"version": "0.5.10", | ||
"version": "0.5.11", | ||
"author": { | ||
@@ -5,0 +5,0 @@ "name": "Dominik Seifert", |
@@ -217,23 +217,48 @@ [![NPM version](https://badge.fury.io/js/nogap.svg)](http://badge.fury.io/js/nogap) | ||
```js | ||
tellMeSomething: function(name) { | ||
nBytes += (message && message.length) || 0; | ||
return 'Host has received a total of ' + nBytes + ' bytes.'; | ||
} | ||
var NoGapDef = require('nogap').Def; | ||
// ... | ||
NoGapDef.component({ | ||
Host: NoGapDef.defHost(function(SharedTools, Shared, SharedContext) { | ||
var nBytes = 0; | ||
onButtonClick: function() { | ||
document.body.innerHTML +='Button was clicked.<br />'; | ||
this.host.tellMeSomething('hello!') | ||
.bind(this) // this is tricky! | ||
.then(function(hostMessage) { | ||
this.showHostMessage(hostMessage); | ||
}); | ||
}, | ||
return { | ||
Public: { | ||
tellMeSomething: function(message) { | ||
nBytes += (message && message.length) || 0; | ||
this.Tools.log('Client said: ' + message); | ||
return 'Thank you! I now received a total of ' + nBytes + ' bytes.'; | ||
} | ||
} | ||
}; | ||
}), | ||
Client: NoGapDef.defClient(function(Tools, Instance, Context) { | ||
return { | ||
initClient: function() { | ||
// bind a button to a component function (quick + dirty): | ||
window.clickMe = this.onButtonClick.bind(this); | ||
document.body.innerHTML += '<button onclick="window.clickMe();">Click Me!</button><br />'; | ||
}, | ||
onButtonClick: function() { | ||
document.body.innerHTML +='Button was clicked.<br />'; | ||
this.host.tellMeSomething('hello!') | ||
.then(function(hostMessage) { | ||
document.body.innerHTML += 'Host said: ' + hostMessage + '<br />'; | ||
}) | ||
.catch(function(err) { | ||
// this can be a connection error, a bug, a Host-side `reject` etc etc... | ||
console.error('Something went wrong: ' + (err.stack || err)); | ||
}); | ||
} | ||
}; | ||
}) | ||
}); | ||
``` | ||
**New Concepts** | ||
* `Client.initClient` will be called right after the Client connected. | ||
* Upon button click, we call `this.host.tellMeSomething(...)` which will send a request to the `Host` to invoke that method (given it is in `Host.Public`). | ||
* Calling a `Public` function on a component's `host` object returns a promise. | ||
* That promise is part of a full-stack [Promise chains](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them). A value returned by a `Host`'s `Public` function (or by a promise returned by such function), will be received by the client. | ||
* Note that [JavaScript's `this` is tricky](http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/)! | ||
* That promise is part of a full-stack [Promise chains](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them). Once finnished, we get the return value from that `Host` method in our `then` callback. | ||
@@ -284,3 +309,4 @@ | ||
* We need to perform an asynchronous request whose result is to be sent to the other side | ||
* Simply use [Promise chains](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them)! | ||
* Simply use [Promise chains](https://github.com/petkaantonov/bluebird#what-are-promises-and-why-should-i-use-them)! | ||
* NOTE: [JavaScript's `this` is tricky](http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/)! | ||
@@ -287,0 +313,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
534177
9512
783