Emitting an Event in Node.js C++ Addon - javascript

I have an application that reads an array of process data from an industrial controller. I want to push that data to a web page as it changes. To that end, I wrote a node.js addon in c++ that scans the process data and attempts to fire an event when the data value changes. Everything works fine with the Addon until it tries to fire an event, at which point node.js terminates with the error:
undefined:0
TypeError: undefined is not a function
The CPP, javascript shim and test javascript are below. Any insights are greatly appreciated.
Thanks in advance.
node_corelink.cpp
typedef struct CoreLinkValue
{
// pointer to a CTS variant value
CtsVariant* value;
// copy of the last value that was broadcast
CtsVariant lastValue;
} CoreLinkValue;
//
// An event structure for pushing events to node.js
// Requires the javascript shim code in node_corelink.js
//
struct Emitter: ObjectWrap
{
static Handle<Value> New(const Arguments& args);
static Handle<Value> DataChange(const char* topic, CtsVariant* value);
};
//
// Create a message payload based on the variant type and
// initiate sending out on the topic
//
static Handle<Value>
createVariantHandle(CtsVariant* value)
{
Handle<Value> ret;
switch (value->type)
{
case CTSTYPE_BIT:
case CTSTYPE_BYTE:
ret = Integer::New(value->value.byte[0]);
break;
case CTSTYPE_WORD:
ret = Integer::New(value->value.word[0]);
break;
case CTSTYPE_DWORD:
ret = Integer::New(value->value.dword[0]);
break;
case CTSTYPE_WORD64:
ret = Number::New(value->value.word64);
break;
case CTSTYPE_REAL64:
ret = Number::New(value->value.real64);
break;
default:
ret = Undefined();
break;
}
return ret;
}
Handle<Value> Emitter::New(const Arguments& args)
{
HandleScope scope;
assert(args.IsConstructCall());
Emitter* self = new Emitter();
self->Wrap(args.This());
return scope.Close(args.This());
}
// emits DataChange Event
Handle<Value> Emitter::DataChange( const char* topic, CtsVariant* value )
{
HandleScope scope;
Handle<Value> argv[3] = {
String::New("DataChange"), // event name
String::New(topic), // topic argument
createVariantHandle(value) // value argument
};
printf ("C++ Emitting event!\n" );
MakeCallback(context_obj_, "emit", 2, argv);
return True();
}
//
// Triggered by the event loop on a regular interval.
// Scans the registered data to see if the latest value has been
// broadcast and does so if needed.
//
void
scan_task( uv_timer_t* timer, int status )
{
std::map<std::string, CoreLinkValue>::iterator it;
bool doUpdate;
for( it = pdos_.begin();
it != pdos_.end();
++it )
{
if (forceRefreshPdos_ == true)
{
//
// An update of this value was requested.
//
doUpdate = true;
}
else if ( it->second.value->type != it->second.lastValue.type )
{
//
// If the types don't match, then this variant was obviously
// updated.
//
doUpdate = true;
}
else if ( it->second.value->value.word64 != it->second.lastValue.value.word64 )
{
//
// Word64 contains all bits of the value. If this value has
// changed, then they've all changed.
//
doUpdate = true;
}
else
{
doUpdate = false;
}
if (doUpdate)
{
it->second.lastValue.value = it->second.value->value;
Emitter::DataChange( it->first.c_str(), it->second.value );
}
}
if (forceRefreshPdos_)
{
forceRefreshPdos_ = false;
printf("Completed refresh all.\n");
}
}
//
// Start the execution of the scan loop
//
int
startScanLoop( void )
{
uv_timer_init( uv_default_loop(), &scanTimer_ );
uv_timer_start(
&scanTimer_, // timer instance
&scan_task, // callback function
0, // startup delay (ms)
100 ); // repeat interval (ms)
return 1;
}
//
// Stop the execution of the scan loop
//
void
stopScanLoop( void )
{
uv_timer_stop( &scanTimer_ );
}
//
// Connects to the kernel IPC
//
Handle<Value>
connect(const Arguments& args)
{
HandleScope scope;
...
startScanLoop();
return scope.Close( True() );
}
//
// Shuts down the kernel IPC
//
Handle<Value>
close(const Arguments& args)
{
HandleScope scope;
stopScanLoop();
...
return scope.Close( True() );
}
//
// Called by node.js to initialize the library.
//
void
init(Handle<Object> target)
{
target->Set(String::NewSymbol("connect"),
FunctionTemplate::New(connect)->GetFunction());
target->Set(String::NewSymbol("close"),
FunctionTemplate::New(close)->GetFunction());
//
// Events interface
//
Local<FunctionTemplate> t = FunctionTemplate::New(Emitter::New);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(String::New("Emitter"));
target->Set(String::NewSymbol("Emitter"), t->GetFunction());
}
NODE_MODULE(node_corelink, init)
node_corelink.js
module.exports = require(__dirname + '/build/Release/node_corelink.node');
var Emitter = require(__dirname + '/build/Release/node_corelink.node').Emitter;
var events = require('events');
inherits(Emitter, events.EventEmitter);
exports.Emitter = Emitter;
// extend prototype
function inherits(target, source) {
for (var k in source.prototype)
target.prototype[k] = source.prototype[k];
}
test.js
process.stdin.resume(); //so the program will not close instantly
process.on('exit', function () {
corelink.close();
console.log('Goodbye!');
});
process.on('SIGINT', function () {
console.log('Got SIGINT.');
process.exit();
});
var corelink = require('./node_corelink');
var Emitter = require('./node_corelink').Emitter;
var e = new Emitter();
e.on('DataChange', function(s) {
console.log('DataChange');
});
corelink.connect();

I was able to trigger callback in a less-graceful method.
node_corelink.js
module.exports = require(__dirname + '/build/Release/node_corelink.node');
test.js
var corelink = require('./node_corelink');
function onDataChange( topic, value )
{
if ( value !== undefined )
console.log ( topic + " ::: " + value.toString() );
}
function onMessage( msg )
{
console.log ( "Message from kernel: " + msg.toString() );
}
corelink.connect(onDataChange, onMessage);
node_corelink.cpp
static void
dataChange( const char* topic, CtsVariant* value )
{
HandleScope scope;
Handle<Value> argv[2] =
{
String::New(topic), // topic argument
createVariantHandle(value) // value argument
};
MakeCallback(Context::GetCurrent()->Global(), pfOnDataChange_, 2, argv);
}
static void
onMessage( const char* message )
{
HandleScope scope;
Handle<Value> argv[1] =
{
String::New(message) // message argument
};
MakeCallback(Context::GetCurrent()->Global(), pfOnMessage_, 1, argv);
}
//
// Connects to the kernel IPC
//
Handle<Value>
connect(const Arguments& args)
{
HandleScope scope;
if ( args.Length() < 2
|| !args[0]->IsFunction()
|| !args[1]->IsFunction() )
{
return scope.Close( False() );
}
pfOnDataChange_ = Persistent<Function>::New(args[0].As<Function>());
pfOnMessage_ = Persistent<Function>::New(args[1].As<Function>());
...
return scope.Close( True() );
}

Related

command in rust does not get called via invoke, no error message

I've got 3 commands i am calling from the front end, 2 of them work perfectly, the third does not.
The issue lies with the function tournament_search
main.rs:
fn main() {
tauri::Builder::default()
.manage(ApiKey {key: Default::default()})
.invoke_handler(tauri::generate_handler![set_api_key, check_connection, tournament_search])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn set_api_key(key: String , state: State<ApiKey>){
let mut api_key = state.key.lock().unwrap();
*api_key = key;
}
#[tauri::command]
async fn check_connection(api_key: State<'_, ApiKey>) -> Result<bool, ()> {
let key = api_key.key.lock().unwrap().clone();
let res = Client::new().get(API_URL).bearer_auth(key).send().await.unwrap().text().await.unwrap();
let json: Value = serde_json::from_str(res.as_str()).unwrap();
match json["success"].as_bool() {
Some(_x) => Ok(false),
None => Ok(true)
}
}
#[tauri::command]
async fn tournament_search(search_string: String, api_key: State<'_, ApiKey>) -> Result<&str, ()> {
println!("test: {}", search_string);
let key = api_key.key.lock().unwrap().clone();
let mut query: String = String::new();
query.push_str("query($name:String){tournaments(query:{filter:{name:$name}}){nodes{name,slug,id}}},{$name:");
query.push_str(search_string.as_str());
query.push_str("}");
let res = Client::new().get(API_URL).bearer_auth(key).body(query).send().await.unwrap().text().await.unwrap();
println!("{}", res);
Ok("")
}
index.js:
const { invoke } = window.__TAURI__.tauri
window.addEventListener("load", (ev) => {
let test = document.getElementById("test");
let apiKey = document.getElementById("apiKey");
let tournamentSearch = document.getElementById("tournamentSearch");
let tourneyList = document.getElementById("tourneyList");
apiKey.addEventListener("input", (ev) => {
invoke("set_api_key", {key: apiKey.value});
invoke("check_connection").then((res) => {
if(res){
tournamentSearch.disabled = false;
}else{
tournamentSearch.disabled = true;
}
});
});
tournamentSearch.addEventListener("input", (ev) => {
test.innerText = "e";
invoke('tournament_search', {search_string: tournamentSearch.value}).then((res) => {
test.innerText = res;
});
});
});
Already looked for zero width characters, whether the event get's called in js etc. The issue is just that the function is not called.
You'd only see an error message by adding a .catch() to the invoke call.
Anyawy, the issue here is that Tauri converts command arguments to camelCase on the rust side (to match the JS default) so it would be { searchString: tournamentSearch.value } instead.
If you'd prefer snake_case instead, you can tell Tauri to use that for arguments by changing the command like this:
#[tauri::command(rename_all = "snake_case")]

WebRTC datachannel wont send?

Lately I've been trying to implement WebRTC datachannels in Haxe, but come across a great deal of difficulty. When I use
dataChannel.send();
there appears to be no effect, despite the data channel supposedly being successfully opened.
The (extremely inefficient and messy) code I'm using looks like this:
package arm;
import haxe.Json;
import js.html.rtc.*;
import js.html.Document;
import js.html.WebSocket;
import js.html.DataElement;
#:expose
class DataChannelManager extends iron.Trait {
var user = "gobbledygook";
var first = false;
var initiator = true;
public function new() {
super();
var document = new Document();
var ws:js.html.WebSocket;
var config = {"iceServers":[{"url":"stun:stun.l.google.com:19302"}]};//temporary arrangement
//var optional:Array<Dynamic> = [{'DtlsSrtpKeyAgreement': true}, {'RtcDataChannels': true }];
// var connection:Dynamic = {
// 'optional'://try changing this to mandatory some time
// optional
// };
var peerConnection = new PeerConnection(config);
var dataChannel:js.html.rtc.DataChannel;
var ready = false;
function sendNegotiation(type, sdp) {
var json = {user:user/*, theloc:myloc*/, action: type, data: sdp};
ws.send(Json.stringify(json));
trace("Negotiation of type "+json.action);
}
var sdpConstraints = {
offerToReceiveAudio: false,
offerToReceiveVideo: false
};
var dcOpen=false;
notifyOnInit(function() {
var optionalStruct:Dynamic = {reliable: true}
dataChannel = peerConnection.createDataChannel("datachannel", optionalStruct);
dataChannel.onmessage = function(e){trace("DC message:" +e.data);};
dataChannel.onopen = function(){trace("-DC OPENED");dcOpen=true;};
dataChannel.onclose = function(){trace("-DC closed!");};
dataChannel.onerror = function(){trace("DC ERROR");};
trace("intialization!");
});
var firstfirst=true;
notifyOnUpdate(function() {
if (dcOpen) {
dcOpen=false;
trace("sending...");
dataChannel.send("stuff!");
}
if (firstfirst&&object.properties['go']) {
user=object.properties['string'];
first=true;
firstfirst=false;
// if (initiator) {
// peerConnection.createOffer(sdpConstraints).then(function (sdp) {
// peerConnection.setLocalDescription(sdp);
// sendNegotiation("offer", sdp);
// trace("SEND OFFER");
// }, function (data) {
// trace("Offer creation failure,", data);
// });
// } else {
// peerConnection.createAnswer(sdpConstraints).then(function (sdp) {
// trace("Answer made.");
// peerConnection.setLocalDescription(sdp);
// sendNegotiation("answer", sdp);
// });
// }
}
if (first) {
first=false;
ws = new WebSocket("ws://----------/*yes, there's an ip here*/:8080");
ws.onopen = function() {
trace("ws opened!");
peerConnection.onicecandidate = function(event) {
trace("ICE offer ready");
if (peerConnection==null || event ==null || event.candidate == null) return;
sendNegotiation("candidate", event.candidate);
}
if (initiator) {
trace("initiating");
// var optionalStruct:Dynamic = {reliable: true}
// dataChannel = peerConnection.createDataChannel("datachannel", optionalStruct);
// dataChannel.onmessage = function(e){trace("DC message:" +e.data);};
// dataChannel.onopen = function(){trace("-DC OPENED");dcOpen=true;};
// dataChannel.onclose = function(){trace("-DC closed!");};
// dataChannel.onerror = function(){trace("DC ERROR");};
peerConnection.createOffer(/*sdpConstraints*/).then(function (sdp) {
peerConnection.setLocalDescription(sdp);
sendNegotiation("offer", sdp);
trace("SEND OFFER");
}, function (data) {
trace("Offer creation failure,", data);
});
}
ws.onmessage = function (data) {
//var info=data.data.split()
if (data.data=="connected!") {return;}
var adata = Json.parse(data.data.substring(5));
if (adata.action=="offer") {
trace("Offer recieved.");
// var optionalStruct:Dynamic = {reliable: true}
// dataChannel = peerConnection.createDataChannel("datachannel", optionalStruct);
// dataChannel.onmessage = function(e){trace("DC message:" +e.data);};
// dataChannel.onopen = function(){trace("DC OPENED");dcOpen=true;};
// dataChannel.onclose = function(){trace("DC CLOSED");};
// dataChannel.onerror = function(){trace("DC ERROR");};
peerConnection.setRemoteDescription(/*try variations here*/ adata.data);
peerConnection.createAnswer(sdpConstraints).then(function (sdp) {
trace("Answer made.");
peerConnection.setLocalDescription(sdp);
sendNegotiation("answer", sdp);
});
}
if (adata.action=="answer") {
trace("Answer recieved.");
peerConnection.setRemoteDescription(/*try variations here*/ adata.data);
}
if (adata.action=="candidate") {
trace("ICE candidate recieved, looks like:",adata);
var soItDoesntComplain:Dynamic = adata.data;
peerConnection.addIceCandidate(soItDoesntComplain);
}
}
}
}
if (ready) {
trace("connected to net");
}
});
// notifyOnRemove(function() {
// });
}
}
You will notice a great deal of code is commented out -- I was expirementing with moving the dataChannel creation around.
For a better idea of what the issue is, here is the console output for the recieving and initiating clients, respectively:
In case you are wondering, notifyOnInit gets a function that is executed once at the beginning, and notifyOnUpdate gets a function called at a regular interval. object.properties['go'] is set by a different class when the username is given.
The JS api is basically the same (as far as I can tell, I haven't used WebRTC at all in the past), I haven't noticed any differences yet and I'm very sure that my issue is my fault and not Haxe's.
Thank you to those who answer.
this is not answer.
Anxious point.
peerCoonection.createOffer()
peerCoonection.createAnswer()
peerCoonection.setLocalDescription()
peerCoonection.setRemoteDescription()
peerCoonection.addIceCandidate()
are await is required.

Decoding a string to valid JSON

I am writing an add-on for Firefox. I'm catching a JSON file which should contain general user data which I need. To clarify this, the data is accessible without any security boundaries. Sometimes the caught string is a valid JSON (without any encoding at all).
And sometimes it looks like this:
Does anyone have an idea how to decode this with JavaScript or at least an idea which kind of encoding this is?
EDIT:
The correct data would be something like:
{"type":"success","message":"OK","data":{"myPersonas":[{"picture":"","personaId":"382046730","games":{"1":"3078"},"personaName":"Feirell","updatedAt":1423573314,"userId":"2832659177456727338","clanTag":"","originalPersona":{"picture":"","userId":"2832659177456727338","user":null,"updatedAt":1423573314,"firstPartyId":"","personaId":"382046730","personaName":"Feirell","gamesLegacy":"0","namespace":"cem_ea_id","gamesJson":"{\"1\":\"3078\"}","games":{"1":"3078"},"clanTag":""},"namespace":"cem_ea_id"}],"currentRankNeeded":{"name":"WARSAW_ID_P_RANK127_NAME","level":127,"pointsNeeded":20260000,"texture":"UI\/Art\/Persistence\/Ranks\/Rank127","iconImageConfig":{"category":"rank_icon","slug":"r127","texture":"UI\/Art\/Persistence\/ranks\/IconsScoreboard\/r127","versions":{"small":{"path":"warsaw\/gamedata\/rank_icon\/small.png","isSprite":true,"height":23,"name":"small","width":29},"smallns":{"path":"warsaw\/gamedata\/rank_icon\/smallns\/r127.png","isSprite":false,"height":23,"name":"smallns","width":29}}},"guid":"384D858D-8288-4B60-9C35-234B65157873","imageConfig":{"category":"rank","slug":"r127","texture":"UI\/Art\/Persistence\/Ranks\/Rank127","versions":{"large":{"path":"warsaw\/gamedata\/rank\/large\/r127.png","isSprite":false,"height":256,"name":"large","width":256},"smallinv":{"path":"warsaw\/gamedata\/rank\/smallinv.png","isSprite":true,"height":64,"name":"smallinv","width":64},"medium":{"path":"warsaw\/gamedata\/rank\/medium.png","isSprite":true,"height":128,"name":"medium","width":128},"tiny":{"path":"warsaw\/gamedata\/rank\/tiny.png","isSprite":true,"height":29,"name":"tiny","width":29},"tinyinv":{"path":"warsaw\/gamedata\/rank\/tinyinv.png","isSprite":true,"height":29,"name":"tinyinv","width":29},"small":{"path":"warsaw\/gamedata\/rank\/small.png","isSprite":true,"height":64,"name":"small","width":64},"mediumns":{"path":"warsaw\/gamedata\/rank\/mediumns\/r127.png","isSprite":false,"height":128,"name":"mediumns","width":128},"smallns":{"path":"warsaw\/gamedata\/rank\/smallns\/r127.png","isSprite":false,"height":64,"name":"smallns","width":64}}}},"generalPersonaStats":{"PDWHeadshots":0.0,"serviceStarsProgress":{"8":9.1730769231,"1":81.6516129032,"2":13.7603053435,"2048":38.81,"32":93.2388059701},"vehiclesDestroyed":2229,"mortarUser":null,"cs_kills":null,"rsScore":0,"PDWKills":0.0,"gameModesScore":{"32":"32800","1":"5274720","2":"184170","67108864":"12635","64":"5274720","68719476736":"5274720","524288":"21675","34359738368":"21410","2097152":"21675","8":"32800","8388608":"5790","512":0,"134217728":"12635","1024":"33840","33554432":"12635","16777216":"17000","137438953472":"21675"},"sc_hotwire":null,"gameModeServiceStarsGold":{},"rank":127,"rushWins":null,"sc_enforcer":null,"sc_vehicle":3337250,"cp_kills":null,"kitMaxMeleeKillsInRound":{},"cp_deaths":null,"motionSensorSpots":null,"cp_wins":null,"sniperRifleAccuracy":0.282155948,"sc_heist":null,"sc_team":532411,"skill":248,"nemesisKills":429,"heals":13978,"sc_operator":null,"kitMeleeKills":{},"longestHeadshot":694.02,"kills_assault":12436,"conquestWlr":null,"elo":0,"longestWinStreak":null,"sc_unlock":109000,"mcomDefendKills":9,"wlRatio":0.0,"sc_award":7289700,"cp_skill":null,"vehiclesDestroyedAssists":null,"revives":3866,"kitLosses":{},"squadDMWins":null,"rushWlr":null,"LMGKills":1494,"cashEarned":null,"kitKills":{},"rocketLauncherAccuracy":0.0,"kitMaxScoreInRound":{},"kills":23207,"sc_capturetheflag":null,"kdRatio":1.5,"sc_bonus":344311,"assaultRifleAccuracy":0.1521077163,"kitDeaths":{},"squadRushLosses":null,"PDWAccuracy":0.0,"flagDefend":3535,"LMGHeadshots":332,"kitWins":{},"nemesisStreak":18,"numWins":726,"flagrunner":null,"conquestWins":null,"scorePerMinuteDelta":0,"kitHeadshots":{},"rsKills":0,"sc_objective":0,"kitDeployments":{},"rsTimePlayed":0,"vehicleScores":{"32":"206936","1":0,"2":"1957700","4":"153005","8":"20374","16":"727908"},"antiGroundSoldier":162.0,"timeDead":null,"sc_bloodmoney":null,"kdRatioDelta":null,"quitPercentage":23.0852211435,"rsNumLosses":0,"sc_squad":1855100,"vehicleDamage":2523,"rsShotsHit":0,"sc_bountyhunter":null,"spm_engineer":null,"flagCaptures":5521,"kitKillStreak":{},"serviceStars":{"8":10,"1":42,"2":6,"2048":10,"32":7},"dogtagsTaken":185,"sc_turfwar":null,"scoreMultiplier":0,"kitLongestHeadshot":{},"deaths":15470,"reDeploys":null,"sc_professional":null,"clubRepution":null,"sniperRifleHeadshots":855,"killAssists":2259,"LMGAccuracy":0.120160849,"rsDeaths":0,"maxMeleeKillsInRound":null,"tdmWins":null,"gameModeServiceStarsSilver":{},"cashPerMinute":null,"rushLosses":null,"resupplies":2476,"totalScore":20500085,"sc_hacker":null,"headshots":4532,"repairs":618,"shotsFired":1099510,"avengerKills":2075,"squadRushWlr":null,"conquestLosses":null,"cs_deaths":null,"rsNumWins":0,"gameModeServiceStarsBronze":{},"squadDmWlr":null,"spm_support":null,"maxHeadshotsInRound":null,"score":20500085,"kills_recon":2451,"kitTimes":{"8":"210746","1":"708950","2":"118946","2048":"46278","32":"171331"},"timePlayedDelta":0,"squadRushWins":null,"sc_hit":null,"kitTimesInPercentage":{"8":16.7757876412,"1":56.4337859234,"2":9.4683307715,"2048":3.6838179631,"32":13.6382777009},"rankScore":20495300,"kitMaxKillsInRound":{},"accuracy":15.6488799556,"timePlayed":2171290,"sc_squadheist":null,"assaultRifleHeadshots":2450,"kitScores":{"8":"1049540","1":"6636560","2":"804026","2048":"207762","32":"1062940"},"suppressionAssists":1660,"scoreDelta":0,"kills_support":2848,"kitMaxHeadshotsInRound":{},"kills_engineer":1562,"cs_wins":null,"killsPerMinute":0.64,"sniperRifleKills":1880,"scorePerMinute":566,"timePlayedSinceLastReset":0,"winPercentage":null,"tdmWlr":null,"rsScorePerMinute":0.0,"combatScore":13098078,"tdmLosses":null,"sc_mechanic":null,"numRounds":1854,"antiAirSoldier":10.0,"sc_hostage":null,"sc_commander":null,"maxKillsInRound":null,"killStreakBonus":58,"lastReset":0,"sc_deathmatch":null,"cs_skill":null,"spm_recon":null,"rsShotsFired":0,"mcomDestroy":0.0,"shotsHit":172061,"serviceStarsGameModes":[{"serviceStars":251,"serviceStarsProgress":17.7142857143,"progressCodeNeeded":null,"actualValue":5274720,"codeNeeded":"sc_conquest","tier":null,"valueNeeded":5292000,"name":null},{"serviceStars":12,"serviceStarsProgress":27.8,"progressCodeNeeded":null,"actualValue":184170,"codeNeeded":"sc_rush","tier":null,"valueNeeded":195000,"name":null},{"serviceStars":5,"serviceStarsProgress":46.6666666667,"progressCodeNeeded":null,"actualValue":32800,"codeNeeded":"sc_deathmatch","tier":null,"valueNeeded":36000,"name":null},{"serviceStars":3,"serviceStarsProgress":77.7777777778,"progressCodeNeeded":null,"actualValue":17000,"codeNeeded":"sc_elimination","tier":null,"valueNeeded":18000,"name":null}],"numLosses":700,"spm_assault":null,"cp_loses":null,"sc_general":4577110,"saviorKills":1813,"cs_loses":null,"squadDMLosses":null,"maxScoreInRound":null,"meleeKills":null,"assaultRifleKills":11309},"rankNeeded":{"name":"WARSAW_ID_P_RANK128_NAME","level":128,"pointsNeeded":21030000,"texture":"UI\/Art\/Persistence\/Ranks\/Rank128","iconImageConfig":{"category":"rank_icon","slug":"r128","texture":"UI\/Art\/Persistence\/ranks\/IconsScoreboard\/r128","versions":{"small":{"path":"warsaw\/gamedata\/rank_icon\/small.png","isSprite":true,"height":23,"name":"small","width":29},"smallns":{"path":"warsaw\/gamedata\/rank_icon\/smallns\/r128.png","isSprite":false,"height":23,"name":"smallns","width":29}}},"guid":"9AC7B2DF-C736-4721-BE14-61F75A4AB816","imageConfig":{"category":"rank","slug":"r128","texture":"UI\/Art\/Persistence\/Ranks\/Rank128","versions":{"large":{"path":"warsaw\/gamedata\/rank\/large\/r128.png","isSprite":false,"height":256,"name":"large","width":256},"smallinv":{"path":"warsaw\/gamedata\/rank\/smallinv.png","isSprite":true,"height":64,"name":"smallinv","width":64},"medium":{"path":"warsaw\/gamedata\/rank\/medium.png","isSprite":true,"height":128,"name":"medium","width":128},"tiny":{"path":"warsaw\/gamedata\/rank\/tiny.png","isSprite":true,"height":29,"name":"tiny","width":29},"tinyinv":{"path":"warsaw\/gamedata\/rank\/tinyinv.png","isSprite":true,"height":29,"name":"tinyinv","width":29},"small":{"path":"warsaw\/gamedata\/rank\/small.png","isSprite":true,"height":64,"name":"small","width":64},"mediumns":{"path":"warsaw\/gamedata\/rank\/mediumns\/r128.png","isSprite":false,"height":128,"name":"mediumns","width":128},"smallns":{"path":"warsaw\/gamedata\/rank\/smallns\/r128.png","isSprite":false,"height":64,"name":"smallns","width":64}}}}}}
the code for the catching:
//CODE FROM: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/NsITraceableChannel#Example
let { CC, Ci, Cu } = require("chrome");
Cu.import('resource://gre/modules/Services.jsm');
var BinaryInputStream = CC('#mozilla.org/binaryinputstream;1', 'nsIBinaryInputStream', 'setInputStream');
var BinaryOutputStream = CC('#mozilla.org/binaryoutputstream;1', 'nsIBinaryOutputStream', 'setOutputStream');
var StorageStream = CC('#mozilla.org/storagestream;1', 'nsIStorageStream', 'init');
function TracingListener() {
this.receivedChunks = []; // array for incoming data. holds chunks as they come, onStopRequest we join these junks to get the full source
this.responseBody; // we'll set this to the
this.responseStatusCode;
this.deferredDone = {
promise: null,
resolve: null,
reject: null
};
this.deferredDone.promise = new Promise(function(resolve, reject) {
this.resolve = resolve;
this.reject = reject;
}.bind(this.deferredDone));
Object.freeze(this.deferredDone);
this.promiseDone = this.deferredDone.promise;
}
TracingListener.prototype = {
onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
var iStream = new BinaryInputStream(aInputStream) // binaryaInputStream
var sStream = new StorageStream(8192, aCount, null); // storageStream // not sure why its 8192 but thats how eveyrone is doing it, we should ask why
var oStream = new BinaryOutputStream(sStream.getOutputStream(0)); // binaryOutputStream
// Copy received data as they come.
var data = iStream.readBytes(aCount);
this.receivedChunks.push(data);
oStream.writeBytes(data, aCount);
this.originalListener.onDataAvailable(aRequest, aContext, sStream.newInputStream(0), aOffset, aCount);
},
onStartRequest: function(aRequest, aContext) {
this.originalListener.onStartRequest(aRequest, aContext);
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
this.responseBody = this.receivedChunks.join("");
delete this.receivedChunks;
this.responseStatus = aStatusCode;
this.originalListener.onStopRequest(aRequest, aContext, aStatusCode);
this.deferredDone.resolve();
},
QueryInterface: function(aIID) {
if (aIID.equals(Ci.nsIStreamListener) || aIID.equals(Ci.nsISupports)) {
return this;
}
throw Cr.NS_NOINTERFACE;
}
};
//my Battlereport Listener
function battlereportListener(callback){
got_battlereport = callback
}
battlereportListener.prototype.httpResponseObserver = {
observe: function(aSubject, aTopic, aData) {
aSubject.QueryInterface(Ci.nsIHttpChannel);
let url = aSubject.URI.spec;
if(url.indexOf("http://battlelog.battlefield.com/bf4/indexstats") != -1){
//console.log('Found a battlereport !!')
var newListener = new TracingListener();
aSubject.QueryInterface(Ci.nsITraceableChannel);
newListener.originalListener = aSubject.setNewListener(newListener);
newListener.promiseDone.then(
function() {
// no error happened
got_battlereport(newListener.responseBody)
},
function(aReason){
// promise was rejected, right now i didnt set up rejection, but i should listen to on abort or bad status code then reject maybe
}
).catch(
function(aCatch) {
console.error('something went wrong, a typo by dev probably:', aCatch);
}
);
}
}
};
battlereportListener.prototype.state = false
battlereportListener.prototype.start = function(){
if(this.state == false){
Services.obs.addObserver(this.httpResponseObserver, 'http-on-examine-response', false);
this.state = true;
}
};
battlereportListener.prototype.stop = function(){
if(this.state == true){
Services.obs.removeObserver(this.httpResponseObserver, 'http-on-examine-response');
this.state = false;
}
};
exports.battlereportListener = battlereportListener
I just tested it with Firefox and it works fine. However, with Firefox Developer Edition it comes to this binary code.

nodejs write file frequently

I have a listener to listen for the change of content, once the content modified, it will emit the handler function:
$('#editor').on('onchange', () => changeHandler('...','...'));
function changeHandler(filePath, content){
var ws = fs.createWriteStream(filePath, 'utf8');
ws.write(content);
}
My problem is that the 'onchange' occurs too often, so 'write file' too often handles, it may lost data during the period.
Can someone give any suggestion?
Update
Now I've changed code according the answers below looks like:
this.buffer = null; //used to cache
// once content changed, maybe too often
changeHandler() {
if (this.editor.curOp && this.editor.curOp.command.name) {
var id = $('.nav-items li.active .lk-hosts').attr('data-hosts-id');
var content = this.editor.getValue();
// cache data, not immediately write to file
this.buffer = {id: id, content: content};
}
}
setInterval(()=> {
// means there's data in cache
if (this.buffer !== null) {
let id = this.buffer.id;
let content = this.buffer.content;
// reset cache to null
this.buffer = null;
// write file
this.writeContent(id, content, (err)=> {
})
}
}, 800);
Thanks all answers!
Why not simply build a buffer to collect written text then write to file only when you have a certain number of writes:
$('#editor').on('onchange', () => changeHandler('...','...'));
var writeBuffer = ''; // can also make this an array
var writeBufferSize = 0;
var filePath = 'path_to_file';
var ws = fs.createWriteStream(filePath, 'utf8');
function changeHandler(content){
if (writeBufferSize == SOME_THRESHOLD) {
ws.write(writeBuffer);
writeBuffer = '';
writeBufferSize = 0;
} else {
writeBuffer += content + '\n';
writeBufferSize++;
}
}
If you choose a write buffer threshold that's too big, you might want to delegate the write to some worker thread to be done in parallel, and in this case you can create another temporary write buffer to fill out while the original is being written, then switch the two.
This sample below shows how to make debounced event handling, although it's not node.js code it's same in concept.
// eventEmitter variable to use
var emitter = new EventEmitter();
// dom element change event
$('#editor').on('input', function(event) {
emitter.emit('changeEvent', event.target.value);
});
// event listener, which debounces change event of input
emitter.on('changeEvent', debounce(function(data) {
writeFile('li', data);
}, 1000)); // <== debounce for 1second
// sample emitter, for demo
// we don't have access to nodejs EventEmitter class in Stackoverflow
// don't use in production
function EventEmitter() {
var callbacks = [];
return {
on: function(eventName, fn) {
callbacks.push({
eventName: eventName,
callback: fn
})
},
emit: function(eventName, payload) {
var fn = callbacks.find(function(item) {
return item.eventName === eventName;
});
if (fn) {
fn.callback(payload);
}
}
}
}
// simple logger for demo purpose
// emulates write file
function writeFile(name, content) {
var $elem = $(document.createElement(name));
$elem.text(content);
$('#logger').append($elem);
}
// throttle function - reduces fn call with timeout
// credits: https://remysharp.com/2010/07/21/throttling-function-calls
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="editor" placeholder="Enter text, this will emit change event"></textarea>
<p>
Notice the 1sec throttle (write something, pause for 1sec, write again)
</p>
<ul id="logger"></ul>
The debounce function can be also used on textarea change event
// debounce emitting
$('#editor').on('input', debounce(function(event) {
emitter.emit('changeEvent', event.target.value);
}, 1000));
// write file when received event without debounce
emitter.on('changeEvent', function(data){
logElement('li', data);
});
The Underscore library has _.throttle() and _.debounce() functions.

RxJS: Producer-consumer with abort

I've got a special producer consumer problem in RxJS: The producer slowly produces elements. A consumer is requesting elements and often has to wait for the producer. This can be achieved by zipping the producer and the request stream:
var produce = getProduceStream();
var request = getRequestStream();
var consume = Rx.Observable.zipArray(produce, request).pluck(0);
Sometimes a request gets aborted. A produced element should only consumed after a not aborted request:
produce: -------------p1-------------------------p2--------->
request: --r1--------------r2---------------r3-------------->
abort: ------a(r1)------------------a(?)------------------>
consume: ------------------c(p1, r2)-------------c(p2, r3)-->
The first request r1 would consume the first produced element p1, but r1 gets aborted by a(r1) before it can consume p1. p1 is produced and gets consumed c(p1, r2) on second request r2. The second abort a(?) is ignored, because no unanswered request happened before. The third request r3 has to wait on the next produced element p2 and is not aborted till p2 is produced. Thus, p2 is consumed c(p2, r3) immediately after it got produced.
How can I achieve this in RxJS?
Edit:
I created an example with a QUnit test on jsbin. You can edit the function createConsume(produce, request, abort) to try/test your solution.
The example contains the function definition of the previously accepted answer.
This (core idea minus details) passes your JSBin test:
var consume = request
.zip(abort.merge(produce), (r,x) => [r,x])
.filter(([r,x]) => isNotAbort(x))
.map(([r,p]) => p);
And the JSBin code.
I can't quite wrap my brain around how to do it with existing operators. Here's how to do it with Observable.create():
return Rx.Observable.create(function (observer) {
var rsub = new Rx.SingleAssignmentDisposable();
var asub = new Rx.SingleAssignmentDisposable();
var psub = new Rx.SingleAssignmentDisposable();
var sub = new Rx.CompositeDisposable(rsub, asub, psub);
var rq = [];
var pq = [];
var completeCount = 0;
var complete = function () {
if (++completeCount === 2) {
observer.onCompleted();
}
};
var consume = function () {
if (pq.length && rq.length) {
var p = pq.shift();
var r = rq.shift();
observer.onNext('p' + p);
}
};
rsub.setDisposable(request.subscribe(
function (r) {
rq.push(r);
consume();
},
function (e) { observer.onError(e); },
complete));
asub.setDisposable(abort.subscribe(
function (a) {
rq.shift();
},
function (e) { observer.onError(e); }
));
psub.setDisposable(produce.subscribe(
function (p) {
pq.push(p);
consume();
},
function (e) { observer.onError(e); },
complete));
return sub;
});
http://jsbin.com/zurepesijo/1/
This solution ignores aborts that don't follow an unanswered request:
const {merge} = Rx.Observable;
Rx.Observable.prototype.wrapValue = function(wrapper) {
wrapper = (wrapper || {});
return this.map(function (value) {
wrapper.value = value;
return wrapper;
});
};
function createConsume(produce, request, abort) {
return merge(
produce.wrapValue({type: 'produce'}),
request.wrapValue({type: 'request'}),
abort.wrapValue({type: 'abort'})
)
.scan(
[false, []],
([isRequest, products], e) => {
// if last time the request was answered
if (isRequest && products.length) {
// remove consumed product
products.shift();
// mark request as answered
isRequest = false;
}
if (e.type === 'produce') {
// save product to consume later
products.push(e.value);
} else {
// if evaluated to false, e.type === 'abort'
isRequest = (e.type === 'request');
}
return [isRequest, products];
}
)
.filter( ([isRequest, products]) => (isRequest && products.length) )
.map( ([isRequest, products]) => products[0] ); // consume
}
Code in newest test on JSBin.

Categories