So my question is as follows: I'm working on a mobile application that takes data from a vital sign sensor and sends to a telehealth server, so that a physician is able to retrieve the data from the server in real time as a plotted curve.
As I have a very weak background on this, my question is of two parts: a) how do I retrieve the data from the server in real time and b) can I use HTML5 libs or anything similar like HighCharts or Meteor charts or ZingCharts to have them plotted or is it impossible? Please be very specific as again I have a weak background on this :)
In ZingChart, you can do this in two ways:
Method 1 - Via Websockets - EX: http://www.zingchart.com/dataweek/presentation/feed.html
The websocket transport is part of the refresh/feed section, its attribute can be found here: Graph >> Refresh section of the ZingChart JSON docs. In addition, a server socket component is required and it has to follow some standard protocol in order to allow connectivity and transport with the client socket.
To get specific:
###############################
# NodeJS code
###############################
#!/usr/bin/env node
var WebSocketServer = require('websocket').server;
var http = require('http');
var server = http.createServer(function(request, response) {
response.writeHead(404);
response.end();
});
server.listen(8888, function() {
console.log((new Date()) + ' Server is listening on port 8888');
});
wsServer = new WebSocketServer({
httpServer: server,
autoAcceptConnections: false
});
function originIsAllowed(origin) {
return true;
}
wsServer.on('request', function(request) {
if (!originIsAllowed(request.origin)) {
request.reject();
console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
return;
}
var type = '',
method = '',
status = 0;
var connection = request.accept('zingchart', request.origin);
connection.on('message', function(message) {
function startFeed() {
console.log('start feed');
status = 1;
if (method == 'push') {
sendFeedData();
}
}
function stopFeed() {
console.log('stop feed');
status = 0;
}
function sendFeedData() {
if (method == 'push') {
var ts = (new Date()).getTime();
var data = {
"scale-x": ts,
"plot0": [ts, parseInt(10 + 100 * Math.random(), 10)]
};
console.log('sending feed data (push)');
connection.sendUTF(JSON.stringify(data));
if (status == 1) {
iFeedTick = setTimeout(sendFeedData, 500);
}
} else if (method == 'pull') {
var data = [];
var ts = (new Date()).getTime();
for (var i = -5; i <= 0; i++) {
data.push({
"scale-x": ts + i * 500,
"plot0": [ts + i * 500, parseInt(10 + 100 * Math.random(), 10)]
});
}
console.log('sending feed data (pull)');
connection.sendUTF(JSON.stringify(data));
}
}
function sendFullData() {
var data = {
type: "bar",
series: [{
values: [
[(new Date()).getTime(), parseInt(10 + 100 * Math.random(), 10)]
]
}]
};
console.log('sending full data');
connection.sendUTF(JSON.stringify(data));
if (status == 1) {
if (method == 'push') {
setTimeout(sendFullData, 2000);
}
}
}
if (message.type === 'utf8') {
console.log('************************ ' + message.utf8Data);
switch (message.utf8Data) {
case 'zingchart.full':
type = 'full';
break;
case 'zingchart.feed':
type = 'feed';
break;
case 'zingchart.push':
method = 'push';
break;
case 'zingchart.pull':
method = 'pull';
break;
case 'zingchart.startfeed':
startFeed();
break;
case 'zingchart.stopfeed':
stopFeed();
break;
case 'zingchart.getdata':
status = 1;
if (type == 'full') {
sendFullData();
} else if (type == 'feed') {
sendFeedData();
}
break;
}
}
});
connection.on('close', function(reasonCode, description) {
status = 0;
console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
});
});
###############################################
# Sample JSON settings for socket transport
###############################################
refresh: {
type: "feed",
transport: "websockets",
url: "ws://198.101.197.138:8888/",
method: "push",
maxTicks: 120,
resetTimeout: 2400
}
or
refresh: {
type: "feed",
transport: "websockets",
url: "ws://198.101.197.138:8888/",
method: "pull",
interval: 3000,
maxTicks: 120,
resetTimeout: 2400
}
Method 2 - Via API - EX: http://www.zingchart.com/dataweek/presentation/api.html
In the case you described, this would involve setting intervals of time at which you would like to retrieve data from your server, and then pass that data via the API. Check out the "Feed" section in API-Methods section of the ZingChart docs.
Related
I have a table that has more than 25 items and wrote a basic script to break them into sub arrays of 25 items each then loops thru that collection of sub arrays to run a batch write item command in the AWS DynamoDB Client. The issue I am getting is a returned validation error. When I run the same seed file via the aws-cli it seeds the table perfectly. This makes me think it has something to do with my script. See anything I am missing? Thanks in advance!
var { DynamoDB } = require('aws-sdk');
var db = new DynamoDB.DocumentClient({
region: 'localhost',
endpoint: 'http://localhost:8000',
});
const allItems = require('./allItems.json');
const tableName = 'some-table-name';
console.log({ tableName, allItems });
var batches = [];
var currentBatch = [];
var count = 0;
for (let i = 0; i < allItems.length; i++) {
//push item to the current batch
count++;
currentBatch.push(allItems[i]);
if (count === 25) {
batches.push(currentBatch);
currentBatch = [];
}
}
//if there are still items left in the curr batch, add to the collection of batches
if (currentBatch.length > 0 && currentBatch.length !== 25) {
batches.push(currentBatch);
}
var completedRequests = 0;
var errors = false;
//request handler for DynamoDB
function requestHandler(err, data) {
console.log('In the request handler...');
return function (err, data) {
completedRequests++;
errors = errors ? true : err;
//log error
if (errors) {
console.error('Request caused a DB error.');
console.error('ERROR: ' + err);
console.error(JSON.stringify(err, null, 2));
} else {
var res = {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify(data),
isBase64Encoded: false,
};
console.log(`Success: returned ${data}`);
return res;
}
if (completedRequests == batches.length) {
return errors;
}
};
}
//Make request
var params;
for (let j = 0; j < batches.length; j++) {
//items go in params.RequestedItems.id array
//format for the items is {PutRequest : {Item: ITEM_OBJECT}}
params = '{"RequestItems": {"' + tableName + '": []}}';
params = JSON.parse(params);
params.RequestItems[tableName] = batches[j];
console.log('before db.batchWriteItem: ', params);
try {
//send to db
db.batchWrite(params, requestHandler(params));
} catch{
console.error(err)
}
}
Here is the formatted request object and the error:
before db.batchWriteItem:
{ RequestItems:
{ 'some-table-name': [ [Object], [Object], [Object], [Object] ] }
}
In the request handler...
Request caused a DB error.
ERROR: ValidationException: Invalid attribute value type
{
"message": "Invalid attribute value type",
"code": "ValidationException",
"time": "2020-08-04T10:51:13.751Z",
"requestId": "dd49628c-6ee9-4275-9349-6edca29636fd",
"statusCode": 400,
"retryable": false,
"retryDelay": 47.94198279972915
}
You are using the DocumentClient in the nodejs code. This will automatically convert the data format used by DynamoDB to a more easily consumable format.
e.g.
{
"id": {
"S": "A string value"
}
}
would become
{
"id": "A string value"
}
The CLI does not perform this data conversion.
You can use the regular DynamoDB client to not perform this conversion in Nodejs. e.g. const db = new Dynamodb()
We are having an issue with signalR. We have an auction site that runs on signalr for real time bidding. We fixed some issues with the browser and everything seemed to be working well. Then we installed new relic on our server and noticed that every minute we are getting http error code 400 on signalr connect, reconnect and abort. Here's a screenshot:
SignalR connect and reconnect are the most time consuming operations of the site according to new relic.
Here is SignalR backend code (We use sql server as signalr backplane):
public class SignalRHub : Hub
{
public void BroadCastMessage(String msg)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SignalRHub>();
hubContext.Clients.All.receiveMessage(msg);
}
}
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
string appString=string.Empty;
//Gets the connection string.
if (System.Configuration.ConfigurationSettings.AppSettings["SignaRScaleoutConn"] != null)
{
appString = System.Configuration.ConfigurationSettings.AppSettings["SignaRScaleoutConn"].ToString();
}
GlobalHost.DependencyResolver.UseSqlServer(appString);
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromMinutes(15); //I added this timeout, but it is not required.
app.MapSignalR();
}
}
The javascript client looks like this, it's lengthy, but most of it is jQuery to affect the DOM, I include it all in case something may be wrong inside it.
$(function () {
var chatProxy = $.connection.signalRHub;
$.connection.hub.start();
chatProxy.client.receiveMessage = function (msg) {
var all = $(".soon").map(function () {
var hiddenModelId = $("#hiddenListingId");
if (msg == hiddenModelId.val()) {
$.ajax({
async: "true",
url: "/Listing/AuctionRemainingTime",
type: "POST",
dataType: 'json',
data: '{ "listingID": "' + msg + '"}',
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data != null) {
SoonSettings.HasReloadedThisTick = false;
var element = document.getElementById(msg);
var obj = JSON.parse(data)
// For Clock Counter End Date Time Interval
// Adds 2 minutes to the soon clock when bid is close to finishing.
var hdID = "hdn" + obj.ListingId;
var hdValue = $("#" + hdID);
if (obj.EndDate != hdValue.val()) {
SoonSettings.HasUpdated = false; //Allows clock to change color once it gets under two minutes.
$('#' + hdID).val(obj.EndDate);
Soon.destroy(element);
Soon.create(element, { //Recreates clock with the before 2 minute tick event.
'due': 'in ' + obj.Seconds + ' seconds',
'layout':'group label-uppercase',
'visual':'ring cap-round progressgradient-00fff6_075fff ring-width-custom gap-0',
'face':'text',
'eventTick': 'tick'
});
}
var highbid = obj.HighBidderURL;
// For Date Ends Info.
var ListingEndDate = $("#tdAuctionListingEndDate");
if (obj.EndDate != ListingEndDate.val()) {
$('#' + hdID).val(obj.EndDate);
ListingEndDate.text(obj.EndDate + " Eastern");
ListingEndDate.effect("pulsate", { times: 5 }, 5000);
}
else
{
$(".Bidding_Current_Price").stop(true, true); ///Removes the pulsating effect.
$(".Bidding_Current_Price").removeAttr("style"); //Removes unnecessary attribute from HTML.
}
//Bid div notification.
if (obj.AcceptedActionCount.replace(/[^:]+:*/, "") > 0) {
if (obj.Disposition != '' && obj.Disposition != null) {
if (obj.Disposition == "Neutral") {
$("#spanNeutralBid").show();
$("#divOutbidNotification").hide();
$("#spanPositiveBid").hide();
$("#divProxyBidNotification").hide();
}
else if (obj.Disposition == "Positive") {
$("#spanPositiveBid").show();
$("#divOutbidNotification").hide();
$("#spanNeutralBid").hide();
$("#divProxyBidNotification").hide();
}
else if (obj.Disposition == "Negative") {
$("#divOutbidNotification").show();
$("#spanNeutralBid").hide();
$("#spanPositiveBid").hide();
$("#divProxyBidNotification").hide();
}
else {
$("#divOutbidNotification").hide();
$("#spanNeutralBid").hide();
$("#divProxyBidNotification").hide();
$("#spanPositiveBid").hide();
}
}
}
// For Highlight Current Price when it is Updated
var hdCurrentPrice = $("#hdnCurrentPrice");
if (obj.CurrentPrice != hdCurrentPrice.val()) {
$(".Bidding_Current_Price").text(obj.CurrentPrice);
$(".Bidding_Current_Price").effect("pulsate", { times: 5 }, 5000);
$("#hdnCurrentPrice").val(obj.CurrentPrice);
}
else {
$(".Bidding_Current_Price").stop(true, true);
$(".Bidding_Current_Price").removeAttr("style");
}
// For ReservePrice Status
$("#spanReservePriceStatus").html(obj.ReservePriceStatus);
$("#smallReservePriceStatus").html(obj.ReservePriceStatus);
// For Bid Count
var spanBidCounter = $("#spanBidCount");
$(spanBidCounter).text(obj.AcceptedActionCount);
var stringAppend = "<tr id='trhHighBidder'><td><strong>HighBidder</strong></td>";
stringAppend += "<td>";
if (obj.isAdmin == true) {
stringAppend += "<a id='anchorHighBid' href=" + obj.HighBidderURL + ">";
stringAppend += "<span id='spanHighBidder'>" + obj.CurrentListingActionUserName + "</span>"
stringAppend += "</a>";
}
else {
stringAppend += "<span id='spanHighBidderAnonymous'>" + obj.CurrentListingActionUserName + "</span>";
}
stringAppend += "</td></tr>";
if (obj.AcceptedActionCount.replace(/[^:]+:*/, "") > 0) {
if ($("#tblAuctionDetail").find("#rowHighBidder").length > 0) {
if ($("#tblAuctionDetail").find("#trhHighBidder").length > 0) {
$("#trhHighBidder").remove();
}
}
else {
//add tr to table
if (!$("#tblAuctionDetail").find("#trhHighBidder").length > 0) {
$('#tblAuctionDetail > tbody > tr:eq(6)').after(stringAppend);
}
}
}
// For High Bidder
if (obj.isAdmin) {
var anchorElement = $("#anchorHighBid");
$(anchorElement).attr("href", obj.HighBidderURL);
var spanHighBidder = $("#spanHighBidder");
$(spanHighBidder).text(obj.CurrentListingActionUserName);
}
else {
var spanAdminHighBid = $("#spanHighBidderAnonymous");
$(spanAdminHighBid).text(obj.CurrentListingActionUserName)
}
}
},
error: function (xhr, textStatus, errorThrown) {
}
});
}
});
};
});
Is there anything wrong with the client or the server signalr code that may need to be changed to avoid these errors happening so often? The 400 code has the tendency of showing up almost every minute. I am very new to signalR and know very little of how to make effective code with it.
The real time bidding in the site does work, it's just to find a way to avoid these constant errors. Any help explaining anything of how signalR works is appreciated.
Thanks,
I'd give a try changing the transportation method of SignalR: http://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client#transport
and check if problem persists.
If possible to get UserAgent from Bad Request log, try to narrow down which browsers get 400 error. I think, maybe, some browsers are not connecting with correct transport method.
I've created a Meteor method to upload a file, it's working well but until the file is fully uploaded, I cannot move around, all subscriptions seem to wait that the upload finishes... is there a way to avoid that ?
Here is the code on the server :
Meteor.publish('product-photo', function (productId) {
return Meteor.photos.find({productId: productId}, {limit: 1});
});
Meteor.methods({
/**
* Creates an photo
* #param obj
* #return {*}
*/
createPhoto: function (obj) {
check(obj, Object);
// Filter attributes
obj = filter(obj, [
'name',
'productId',
'size',
'type',
'url'
]);
// Check user
if (!this.userId) {
throw new Meteor.Error('not-connected');
}
// Check file name
if (typeof obj.name !== 'string' || obj.name.length > 255) {
throw new Meteor.Error('invalid-file-name');
}
// Check file type
if (typeof obj.type !== 'string' || [
'image/gif',
'image/jpg',
'image/jpeg',
'image/png'
].indexOf(obj.type) === -1) {
throw new Meteor.Error('invalid-file-type');
}
// Check file url
if (typeof obj.url !== 'string' || obj.url.length < 1) {
throw new Meteor.Error('invalid-file-url');
}
// Check file size
if (typeof obj.size !== 'number' || obj.size <= 0) {
throw new Meteor.Error('invalid-file-size');
}
// Check file max size
if (obj.size > 1024 * 1024) {
throw new Meteor.Error('file-too-large');
}
// Check if product exists
if (!obj.productId || Meteor.products.find({_id: obj.productId}).count() !== 1) {
throw new Meteor.Error('product-not-found');
}
// Limit the number of photos per user
if (Meteor.photos.find({productId: obj.productId}).count() >= 3) {
throw new Meteor.Error('max-photos-reached');
}
// Resize the photo if the data is in base64
if (typeof obj.url === 'string' && obj.url.indexOf('data:') === 0) {
obj.url = resizeImage(obj.url, 400, 400);
obj.size = obj.url.length;
obj.type = 'image/png';
}
// Add info
obj.createdAt = new Date();
obj.userId = this.userId;
return Meteor.photos.insert(obj);
}
});
And the code on the client :
Template.product.events({
'change [name=photo]': function (ev) {
var self = this;
readFilesAsDataURL(ev, function (event, file) {
var photo = {
name: file.name,
productId: self._id,
size: file.size,
type: file.type,
url: event.target.result
};
Session.set('uploadingPhoto', true);
// Save the file
Meteor.call('createPhoto', photo, function (err, photoId) {
Session.set('uploadingPhoto', false);
if (err) {
displayError(err);
} else {
notify(i18n("Transfert terminé pour {{name}}", photo));
}
});
});
}
});
I finally found the solution myself.
Explication : the code I used was blocking the subscriptions because it was using only one method call to transfer all the file from the first byte to the last one, that leads to block the thread (I think, the one reserved to each users on the server) until the transfer is complete.
Solution : I splitted the file into chunks of about 8KB, and send chunk by chunk, this way the thread or whatever was blocking the subscriptions is free after each chunk transfer.
The final working solution is on that post : How to write a file from an ArrayBuffer in JS
Client Code
// data comes from file.readAsArrayBuffer();
var total = data.byteLength;
var offset = 0;
var upload = function() {
var length = 4096; // chunk size
// adjust the last chunk size
if (offset + length > total) {
length = total - offset;
}
// I am using Uint8Array to create the chunk
// because it can be passed to the Meteor.method natively
var chunk = new Uint8Array(data, offset, length);
if (offset < total) {
// Send the chunk to the server and tell it what file to append to
Meteor.call('uploadFileData', fileId, chunk, function (err, length) {
if (!err) {
offset += length;
upload();
}
}
}
};
upload();
Server code
var fs = Npm.require('fs');
var Future = Npm.require('fibers/future');
Meteor.methods({
uploadFileData: function(fileId, chunk) {
var fut = new Future();
var path = '/uploads/' + fileId;
// I tried that with no success
chunk = String.fromCharCode.apply(null, chunk);
// how to write the chunk that is an Uint8Array to the disk ?
fs.appendFile(path, new Buffer(chunk), function (err) {
if (err) {
fut.throw(err);
} else {
fut.return(chunk.length);
}
});
return fut.wait();
}
});
Improving #Karl's code:
Client
This function breaks the file into chunks and sends them to the server one by one.
function uploadFile(file) {
const reader = new FileReader();
let _offset = 0;
let _total = file.size;
return new Promise((resolve, reject) => {
function readChunk() {
var length = 10 * 1024; // chunk size
// adjust the last chunk size
if (_offset + length > _total) {
length = _total - _offset;
}
if (_offset < _total) {
const slice = file.slice(_offset, _offset + length);
reader.readAsArrayBuffer(slice);
} else {
// EOF
setProgress(100);
resolve(true);
}
}
reader.onload = function readerOnload() {
let buffer = new Uint8Array(reader.result) // convert to binary
Meteor.call('fileUpload', file.name, buffer, _offset,
(error, length) => {
if (error) {
console.log('Oops, unable to import!');
return false;
} else {
_offset += length;
readChunk();
}
}
);
};
reader.onloadend = function readerOnloadend() {
setProgress(100 * _offset / _total);
};
readChunk();
});
}
Server
The server then writes to a file when offset is zero, or appends to its end otherwise, returning a promise, as I used an asynchronous function to write/append in order to avoid blocking the client.
if (Meteor.isServer) {
var fs = require('fs');
var Future = require('fibers/future');
}
Meteor.methods({
// Upload file from client to server
fileUpload(
fileName: string,
fileData: Uint8Array,
offset: number) {
check(fileName, String);
check(fileData, Uint8Array);
check(offset, Number);
console.log(`[x] Received file ${fileName} data length: ${fileData.length}`);
if (Meteor.isServer) {
const fut = new Future();
const filePath = '/tmp/' + fileName;
const buffer = new Buffer(fileData);
const jot = offset === 0 ? fs.writeFile : fs.appendFile;
jot(filePath, buffer, 'binary', (err) => {
if (err) {
fut.throw(err);
} else {
fut.return(buffer.length);
}
});
return fut.wait();
}
}
)};
Usage
uploadFile(file)
.then(() => {
/* do your stuff */
});
I am trying to use an Asterisk Manager NPM module in Meteor, but am having difficulties with processing emitted events.
This NPM module establishes a permanent connection to Asterisk Manager and emits whatever Events it receives from Asterisk. I've managed to patch the code so that it runs in Meteor. It connects to Asterisk, emits events and I can log them to console, but once I try to do something with the data, like insert it into a collection, I receive the following error:
Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.
How do I overcome that? Thank you.
server side code:
var ami = new AsteriskManager( { username: 'meteor', password: '123456' } );
ami.on('ami_data', function(data){
console.log(data); // <- this works fine
// the following causes the error
EventsLog.insert({ timestamp: (new Date()).getTime(),
data: data});
});
ami.connect(function(){});//creates a socket connection and sends the login action
the patched npm module code:
var util = Npm.require('util');
var events = Npm.require('events').EventEmitter;
var net = Npm.require('net');
var AsteriskConstructor = function AsteriskManager(params){
params = params || {};
this.net = net;
this.CRLF = "\r\n";
this.END = "\r\n\r\n";
this.buffer = "";
this.port = params.port || 5038;
this.host = params.host || 'localhost';
this.username = params.username || 'username';
this.password = params.password || 'password';
this.enable_debug = params.debug || false;
this.reconnect = params.reconnect || false;
this.reconnect_after = params.reconnect_after || 3000;
this.events = (params.events != undefined ? params.events : true);
this.identifier = params.identifier || false;
this.ami_encoding = params.ami_encoding || 'ascii';
};
AsteriskManager = AsteriskConstructor;
util.inherits(AsteriskManager, events);
AsteriskManager.prototype.connect = function(connect_cb, data_cb){
var self = this;
self.debug('running ami connect');
self.socket = null;
self.socket = this.net.createConnection(this.port, this.host);//reopen it
self.socket.setEncoding(this.ami_encoding);
self.socket.setKeepAlive(true, 500);
self.socket.on('connect', function(){
self.debug('connected to Asterisk AMI');
//login to the manager interface
self.send({Action: 'login', Username : self.username, Secret : self.password, Events: (self.events ? 'on' : 'off')});
if(connect_cb && typeof connect_cb == 'function'){
connect_cb();
}
}).on('data', function(data){
if(data_cb && typeof data_cb == 'function'){
data_cb(data);
}
var all_events = self.processData(data);
for(var i in all_events){
var result = all_events[i];
if(result.response && result.message && /Authentication/gi.exec(result.message) == 'Authentication'){
self.emit('ami_login', ((result.response == 'Success') ? true : false) ,result);
}
self.emit('ami_data', result);
}
}).on('drain', function(){
self.debug('Asterisk Socket connection drained');
self.emit('ami_socket_drain');
}).on('error', function(error){
if(error){
self.debug('Asterisk Socket connection error, error was: ' + error);//prob lost connection to ami due to asterisk restarting so restart the connection
}
self.emit('ami_socket_error', error);
}).on('timeout',function(){
self.debug('Asterisk Socket connection has timed out');
self.emit('ami_socket_timeout');
}).on('end', function() {
self.debug('Asterisk Socket connection ran end event');
self.emit('ami_socket_end');
}).on('close', function(had_error){
self.debug('Asterisk Socket connection closed, error status - ' + had_error);
self.emit('ami_socket_close', had_error);
if(self.reconnect){
self.debug('Reconnecting to AMI in ' + self.reconnect_after);
setTimeout(function() {
self.connect(connect_cb, data_cb);
}, self.reconnect_after);
}
});
}
AsteriskManager.prototype.disconnect = function(){
this.reconnect = false;//just in case we wanted it to reconnect before, we've asked for it to be closed this time so make sure it doesnt reconnect
this.socket.end(this.generateSocketData({Action: 'Logoff'}));
}
AsteriskManager.prototype.destroy = function(){
this.socket.destroy();
}
AsteriskManager.prototype.processData = function(data, cb){
/*
Thanks to mscdex for this bit of code that takes many lots of data and sorts them out into one if needed!
https://github.com/mscdex/node-asterisk/blob/master/asterisk.js
*/
data = data.toString();
if (data.substr(0, 21) == "Asterisk Call Manager"){
data = data.substr(data.indexOf(this.CRLF)+2); // skip the server greeting when first connecting
}
this.buffer += data;
var iDelim, info, headers, kv, type, all_events = [];
while ((iDelim = this.buffer.indexOf(this.END)) > -1) {
info = this.buffer.substring(0, iDelim+2).split(this.CRLF);
this.buffer = this.buffer.substr(iDelim + 4);
result = {}; type = ""; kv = [];
for (var i=0,len=info.length; i<len; i++) {
if (info[i].indexOf(": ") == -1){
continue;
}
kv = info[i].split(": ", 2);
kv[0] = kv[0].toLowerCase().replace("-", "");
if (i==0){
type = kv[0];
}
result[kv[0]] = kv[1];
}
if(this.identifier){
result.identifier = this.identifier;
}
all_events.push(result);
}
return all_events;
}
AsteriskManager.prototype.debug = function(data){
if(this.enable_debug){
console.log(data);
}
}
AsteriskManager.prototype.generateRandom = function(){
return Math.floor(Math.random()*100000000000000000);
}
AsteriskManager.prototype.generateSocketData = function(obj){
var str = '';
for(var i in obj){
str += (i + ': ' + obj[i] + this.CRLF);
}
return str + this.CRLF;
}
AsteriskManager.prototype.send = function(obj, cb) {
//check state of connection here, if not up then bail out
if(!obj.ActionID){
obj.ActionID = this.generateRandom();
}
//maybe i should be checking if this socket is writeable
if(this.socket != null && this.socket.writable){
this.debug(obj);
this.socket.write(this.generateSocketData(obj), this.ami_encoding, cb);
}else{
this.debug('cannot write to Asterisk Socket');
this.emit('ami_socket_unwritable');
}
}
As the error message says "Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment"
ami.on('ami_data', Meteor.bindEnvironment( function(data){
console.log(data); // <- this works fine
// the following causes the error
EventsLog.insert({ timestamp: (new Date()).getTime(),
data: data});
}, function( error) { console.log( error);})
);
There are a lot of other examples around.
If the server code above is not in a Fiber you might get "Meteor code must always run within a Fiber" error.
Using the chat example, when I close the browser window, I do not ever see the #Disconnect method called.
Here is my service:-
package org.atmosphere.samples.chat;
import java.io.IOException;
import org.atmosphere.config.service.Disconnect;
import org.atmosphere.config.service.ManagedService;
import org.atmosphere.config.service.Ready;
import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.AtmosphereResourceEvent;
import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#ManagedService(path = "{room: [a-zA-Z][a-zA-Z_0-9]*}")
public class Chat
{
private static final Logger LOGGER = LoggerFactory.getLogger(Chat.class);
#Ready
public void onReady(final AtmosphereResource inAtmosphereResource)
{
LOGGER.info("Browser {} connected.", inAtmosphereResource.uuid());
}
#Disconnect
public void onDisconnect(AtmosphereResourceEvent event)
{
if (event.isCancelled())
{
LOGGER.info("Browser {} unexpectedly disconnected", event.getResource().uuid());
}
else if (event.isClosedByClient())
{
LOGGER.info("Browser {} closed the connection", event.getResource().uuid());
}
else
{
LOGGER.info("close event was called, but I know not why");
}
}
#org.atmosphere.config.service.Message(encoders = { JacksonEncoder.class }, decoders = { JacksonDecoder.class })
public Message onMessage(Message message) throws IOException
{
LOGGER.info("{} just send {}", message.getAuthor(), message.getMessage());
return message;
}
}
Here is my JS:-
$(function () {
"use strict";
var header = $('#header');
var content = $('#content');
var input = $('#input');
var status = $('#status');
console.log(document.location.hash);
var bookingNumber = document.location.hash.substring(1, document.location.hash.length);
console.log(bookingNumber);
var myName = false;
var author = null;
var logged = false;
var socket = $.atmosphere;
var subSocket;
var transport = 'websocket';
// We are now ready to cut the request
var request = { url: document.location.toString().replace(/#.*/, "") + 'chat/' + bookingNumber,
contentType : "application/json",
trackMessageLength : true,
shared : true,
logLevel : "debug",
transport : transport ,
fallbackTransport: 'long-polling'};
request.onOpen = function(response) {
content.html($('>p<', { text: 'Atmosphere connected using ' + response.transport }));
input.removeAttr('disabled').focus();
status.text('Choose name:');
transport = response.transport;
if (response.transport == "local") {
subSocket.pushLocal("Name?");
}
};
request.onTransportFailure = function(errorMsg, request) {
jQuery.atmosphere.info(errorMsg);
if (window.EventSource) {
request.fallbackTransport = "sse";
transport = "see";
}
header.html($('<h3>', { text: 'Atmosphere Chat. Default transport is WebSocket, fallback is ' + request.fallbackTransport }));
};
request.onMessage = function (response) {
// We need to be logged first.
if (!myName) return;
var message = response.responseBody;
try {
var json = jQuery.parseJSON(message);
} catch (e) {
console.log('This doesn\'t look like a valid JSON: ', message.data);
return;
}
if (!logged) {
logged = true;
status.text(myName + ': ').css('color', 'blue');
input.removeAttr('disabled').focus();
subSocket.pushLocal(myName);
} else {
input.removeAttr('disabled');
var me = json.author == author;
var date = typeof(json.time) == 'string' ? parseInt(json.time) : json.time;
addMessage(json.author, json.message, me ? 'blue' : 'black', new Date(date));
}
};
request.onClose = function(response) {
logged = false;
}
subSocket = socket.subscribe(request);
input.keydown(function(e) {
if (e.keyCode === 13) {
var msg = $(this).val();
if (author == null) {
author = msg;
}
subSocket.push(jQuery.stringifyJSON({ author: author, message: msg }));
$(this).val('');
input.attr('disabled', 'disabled');
if (myName === false) {
myName = msg;
}
}
});
function addMessage(author, message, color, datetime) {
content.append('<p><span style="color:' + color + '">' + author + '</span> # ' +
+ (datetime.getHours() < 10 ? '0' + datetime.getHours() : datetime.getHours()) + ':'
+ (datetime.getMinutes() < 10 ? '0' + datetime.getMinutes() : datetime.getMinutes())
+ ': ' + message + '</p>');
}
});
I am running on glassfish 3, with web sockets and comet enabled.
UPDATE I forgot to mention that I am using v 2.1.0RC1
tested with GF 3.1.2.2:
Blockquote
[#|2013-12-02T10:43:24.743-0500|INFO|glassfish3.1.2|javax.enterprise.system.std.com.sun.enterprise.server.logging|_ThreadID=25;_ThreadName=http-thread-pool-8080(4);|10:43:24.743 [http-thread-pool-8080(4)] INFO org.atmosphere.samples.chat.Chat - Browser 0f7f5596-bd25-4dda-a488-1357da8487f5 closed the connection
Let's havce the discussion on the Atmosphere ML in case you are still experiencing issue. I've tested with the sample BTW.
-- Jeanfrancois