NAPI Call Emit inside a c++ Lambda fucnction - javascript

I'm working on a N-API addon to capture video frame using windows graphic capture API , extract frame bytes and send it back to JavaScript.
I have tried the event emitter but I can't get the data.
Here is my C++ code:
#include <napi.h>
// my other include
Napi::Value startCapture(const Napi::CallbackInfo &info){
Napi::Env env = info.Env();
Napi::Function emit = info[0].As<Napi::Function>();
emit.Call({Napi::String::New(env, "start")});
auto framePool = winrt::Direct3D11CaptureFramePool::Create(
device, //d3d device
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
2,
itemSize);
// capture session
auto session = framePool.CreateCaptureSession(item);
// Lambda Function
framePool.FrameArrived([session, d3dDevice, d3dContext, &emit, &env](auto &framePool, auto &) {
auto frame = framePool.TryGetNextFrame();
auto frameTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
// Extraction the byte and so on ...
emit.Call({Napi::String::New(env, "data"), Napi::String::New(env, "data ...")});
}
session.StartCapture();
emit.Call({Napi::String::New(env, "end")});
return Napi::String::New(env, "OK");
}
here my JavaScript code calling the start capture function
<!-- language-all: js -->
const EventEmitter = require('events').EventEmitter
const addon = require('./build/myaddon.node')
const emitter = new EventEmitter()
emitter.on('start', () => {
console.log('Start Recording ...')
})
emitter.on('data', (evt) => {
console.log(evt);
})
emitter.on('end', () => {
console.log('Stop Recording ...')
})
addon.startCapture(emitter.emit.bind(emitter))
Normally my output should be an infinite loop of data messages until I stop it
Start Recording …
data data
.
.
.
data
After looking to the lambda function framePool.FrameArrived it seems that it's running on a different thread than the startCapture function if I understand the lambda function concept correctly, I just wanna to found a way on how I can stream those messages to JavaScript using event Emitter or any other recommendation is well welcoming.

//instead of
Napi::Env env = info.Env();
Napi::Function emit = info[0].As<Napi::Function>();
emit.Call({Napi::String::New(env, "data"), Napi::String::New(env, "data ...")});
//try
Napi::Env env = info.Env();
Napi::Function fn = info[0].As<Napi::Function>();
emit = Persistent(fn);
emit.SuppressDestruct();
emit.Value().Call({Napi::String::New(env, "data"), Napi::String::New(env, "data ...")});

Related

Intercept WebSocket messages

With ajax requests it can be done with this code:
let oldXHROpen = window.XMLHttpRequest.prototype.open;
window.lastXhr = '';
window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
this.addEventListener('load', function() {
window.lastXhr = this.responseText;
});
return oldXHROpen.apply(this, arguments);
};
lastXhr variable will hold the last response.
But how can this be achieved for websockets too?
you would need to make this wrapper as soon as possible
#brunoff you're correct in that you can always use your functions before a server's by puppet window logic, or you could just hijack the data from the MessageEvent itself:
function listen(fn){
fn = fn || console.log;
let property = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
const data = property.get;
// wrapper that replaces getter
function lookAtMessage() {
let socket = this.currentTarget instanceof WebSocket;
if (!socket) {
return data.call(this);
}
let msg = data.call(this);
Object.defineProperty(this, "data", { value: msg } ); //anti-loop
fn({ data: msg, socket:this.currentTarget, event:this });
return msg;
}
property.get = lookAtMessage;
Object.defineProperty(MessageEvent.prototype, "data", property);
}
listen( ({data}) => console.log(data))
You can try putting in the code and running it in the console on this page and then running their WebSocket example.
To intercept the messages, you will have to spy on the onmessage = fn and addEventListener("message", fn) calls.
To be able to modify the onmessage we have to override the global WebSocket in the first place. The below is intercepting the incoming messages, but in a similar way you can spy on the send method to intercept the outgoing messages (the ones sent by the client to the server).
I tested this on a page using Firebase and it works nicely, but you have to initialize it before the other scripts making sure that the websocket library (it can be socket.io, ws, etc) is using the overridden WebSocket constructor.
Spy the Incoming Messages and modify the data
Eventually you can override the data before calling the real message listener – this becomes handy if you do not have control over the page functionality and want to inject your own data in the message listener.
const OriginalWebsocket = window.WebSocket
const ProxiedWebSocket = function() {
console.log("Intercepting web socket creation")
const ws = new OriginalWebsocket(...arguments)
const originalAddEventListener = ws.addEventListener
const proxiedAddEventListener = function() {
if (arguments[0] === "message") {
const cb = arguments[1]
arguments[1] = function() {
// Here you can get the actual data from the incoming messages
// Here you can even change the data before calling the real message listener
Object.defineProperty(e, "data", { value: 'your injected data' })
console.log("intercepted", arguments[0].data)
return cb.apply(this, arguments)
}
}
return originalAddEventListener.apply(this, arguments)
}
ws.addEventListener = proxiedAddEventListener
Object.defineProperty(ws, "onmessage", {
set(func) {
return proxiedAddEventListener.apply(this, [
"message",
func,
false
]);
}
});
return ws;
};
window.WebSocket = ProxiedWebSocket;
If you do not need to modify the data, you can follow the second part of the answer.
Spy the Incoming messages without modifying the data
If you want to listen for messages only, without overriding the data, things are simpler:
const OriginalWebsocket = window.WebSocket
const ProxiedWebSocket = function() {
const ws = new OriginalWebsocket(...arguments)
ws.addEventListener("message", function (e) {
// Only intercept
console.log(e.data)
})
return ws;
};
window.WebSocket = ProxiedWebSocket;
Spy the Outgoing Messages
In a very similar way, you can proxy the send method which is used to send data to the server.
const OriginalWebsocket = window.WebSocket
const ProxiedWebSocket = function() {
const ws = new OriginalWebsocket(...arguments)
const originalSend = ws.send
const proxiedSend = function() {
console.log("Intercepted outgoing ws message", arguments)
// Eventually change the sent data
// arguments[0] = ...
// arguments[1] = ...
return originalSend.apply(this, arguments)
}
ws.send = proxiedSend
return ws;
};
window.WebSocket = ProxiedWebSocket;
Feel free to ask any questions if anything is unclear.
In a solution similar to yours, where the window.XMLHttpRequest was replaced with a wrapped version that feeds window.lastXhr, we replace window.WebSockets with a wrapped version that feeds window.WebSocketMessages with all messages and timestamps received from all websockets created after this script.
window.watchedWebSockets = [];
window.WebSocketMessages = [];
function WebSocketAttachWatcher(websocket) {
websocket.addEventListener("message", (event)=>window.WebSocketMessages.push([event.data,Date.now()]));
window.watchedWebSockets.push(websocket);
}
// here we replace WebSocket with a wrapped one, that attach listeners on
window.WebSocketUnchanged = window.WebSocket;
window.WebSocket = function(...args) {
const websocket = new window.WebSocketUnchanged(...args);
WebSocketAttachWatcher(websocket);
return websocket;
}
Differently from your XMLRequest case, the websocket may already exist. If you need garanties that all websockets would be catched then you would need to make this wrapper as soon as possible. If you just can't, there's an not so good trick to capture already existing websockets once they send a message:
// here we detect existing websockets on send event... not so trustable
window.WebSocketSendUnchanged = window.WebSocketUnchanged.prototype.send;
window.WebSocket.prototype.send = function(...args) {
console.log("firstsend");
if (!(this in window.watchedWebSockets))
WebSocketAttachWatcher(this);
this.send = window.WebSocketSendUnchanged; // avoid passing here again on next send
window.WebSocketSendUnchanged.call(this, ...args);
}
It is not so trustable since if they don't send but receive they will stay unnoticed.
Intro
The question/bounty/op is specifically asking for a reputable source.
Instead of rolling a custom solution, my proposal is that a known proven library should be used - that has been used, audited, forked, and in general used by the community and that is hosted on github.
The second option is to roll your own (though not recommended) and there are many exccelent answers on how to do it involving the addEventListener
wshook
Wshook is a library (hosted on github) that allows to easily intercept and modify WebSocket requests and message events. It has been starred and forked multiple times.
Disclaimer: I don't have any relationship with the specific project.strong text
Example:
wsHook.before = function(data, url, wsObject) {
console.log("Sending message to " + url + " : " + data);
}
// Make sure your program calls `wsClient.onmessage` event handler somewhere.
wsHook.after = function(messageEvent, url, wsObject) {
console.log("Received message from " + url + " : " + messageEvent.data);
return messageEvent;
}
From the documentation, you will find:
wsHook.before - function(data, url, wsObject):
Invoked just before
calling the actual WebSocket's send() method.
This method must return data which can be modified as well.
wsHook.after - function(event, url, wsObject):
Invoked just after
receiving the MessageEvent from the WebSocket server and before
calling the WebSocket's onmessage Event Handler.
Websocket addEventListener
The WebSocket object supports .addEventListener().
Please see: Multiple Handlers for Websocket Javascript
if you are using nodejs then you can use socket.io
yarn add socket.io
after installation, you can use the middleware of socket.io
io.use(async (socket, next) => {
try {
const user = await fetchUser(socket);
socket.user = user;
} catch (e) {
next(new Error("unknown user"));
}
});

Reading from an NFC reader directly into a desktop web application?

I have LibNFC working from the Linux terminal recognising my ACR122U Reader, and I wanted to know if there was a method for it to work through Chrome on a Linux Desktop as it is really similar to Android Support, with all the NFC device handling done by libnfc and the browser just has to know about this library instead of every type usb or other device than can do NFC.
I have tried using the WebNFC API to connect it :
document.getElementById("scanButton").addEventListener("click", async () => {
log.innerHTML = "NFC Register started...";
try {
const ndef = new NDEFReader();
await ndef.scan();
log.innerHTML = ("> Scan started");
ndef.addEventListener("readingerror", () => {
log.innerHTML = ("Argh! Cannot read data from the NFC tag. Try another one?");
});
ndef.addEventListener("reading", ({ message, serialNumber }) => {
log.innerHTML = ("ID ${serialNumber} logged #" + dt.toLocaleTimeString()); });
} catch (error) {
log.innerHTML = (error);
}
});
document.getElementById("stopButton").onclick = function(){
log.innerHTML = "NFC Register stopped # " + new Date().toLocaleTimeString();
};
but I'm met with Error: NFC Permission Request denied
and the WebUSB API to connect it:
var usbd = {};
let device;
let deviceEndpoint = 0x02;
let powerUpDevice = new Uint8Array([0x62,0x00, 0x00, 0x00, 0x00,0x00,0x00,0x01,0x00, 0x00]).buffer;
let getCardUID = new Uint8Array([0xff,0xca,0x00,0x00,0x04]).buffer;
(function() {
'use strict';
usbd.authorize = function(){
navigator.usb.requestDevice({ filters: [{ vendorId: 0x072f }] })
.then(selectedDevice => {
device = selectedDevice;
console.log(device.configuration.interfaces[0].interfaceNumber);
console.log(device.manufacturerName);
console.log(device.productName);
console.log(device);
return device.open()
.then(() => {
if (device.configuration === null) {
return device.selectConfiguration(1);
}
});
})
.then(() => device.claimInterface(0))
but I'm met with Error: ...blocked because it implements a protected interface class i.e. not supported, so that's a no go.
Is there a way to incorporate the libusb/libnfc libraries or any other method to directly connect an NFC reader to read into a web browser/application?
Web NFC is supported on Android only as of February 2021. See https://web.dev/nfc/
The WebUSB error suggests you're requesting an interface that implements a protected class (among those below):
// USB Class Codes are defined by the USB-IF:
// https://www.usb.org/defined-class-codes
const uint8_t kProtectedClasses[] = {
0x01, // Audio
0x03, // HID
0x08, // Mass Storage
0x0B, // Smart Card
0x0E, // Video
0x10, // Audio/Video
0xE0, // Wireless Controller (Bluetooth and Wireless USB)
};
I wonder if that's a linux thing though as I was able to communicate with the ACR122U and SCL3711 NFC reader USB devices through WebUSB. See https://github.com/beaufortfrancois/chrome-nfc
Did you give a try to WebHID by any chance first? See https://web.dev/hid

How to end Google Speech-to-Text streamingRecognize gracefully and get back the pending text results?

I'd like to be able to end a Google speech-to-text stream (created with streamingRecognize), and get back the pending SR (speech recognition) results.
In a nutshell, the relevant Node.js code:
// create SR stream
const stream = speechClient.streamingRecognize(request);
// observe data event
const dataPromise = new Promise(resolve => stream.on('data', resolve));
// observe error event
const errorPromise = new Promise((resolve, reject) => stream.on('error', reject));
// observe finish event
const finishPromise = new Promise(resolve => stream.on('finish', resolve));
// send the audio
stream.write(audioChunk);
// for testing purposes only, give the SR stream 2 seconds to absorb the audio
await new Promise(resolve => setTimeout(resolve, 2000));
// end the SR stream gracefully, by observing the completion callback
const endPromise = util.promisify(callback => stream.end(callback))();
// a 5 seconds test timeout
const timeoutPromise = new Promise(resolve => setTimeout(resolve, 5000));
// finishPromise wins the race here
await Promise.race([
dataPromise, errorPromise, finishPromise, endPromise, timeoutPromise]);
// endPromise wins the race here
await Promise.race([
dataPromise, errorPromise, endPromise, timeoutPromise]);
// timeoutPromise wins the race here
await Promise.race([dataPromise, errorPromise, timeoutPromise]);
// I don't see any data or error events, dataPromise and errorPromise don't get settled
What I experience is that the SR stream ends successfully, but I don't get any data events or error events. Neither dataPromise nor errorPromise gets resolved or rejected.
How can I signal the end of my audio, close the SR stream and still get the pending SR results?
I need to stick with streamingRecognize API because the audio I'm streaming is real-time, even though it may stop suddenly.
To clarify, it works as long as I keep streaming the audio, I do receive the real-time SR results. However, when I send the final audio chunk and end the stream like above, I don't get the final results I'd expect otherwise.
To get the final results, I actually have to keep streaming silence for several more seconds, which may increase the ST bill. I feel like there must be a better way to get them.
Updated: so it appears, the only proper time to end a streamingRecognize stream is upon data event where StreamingRecognitionResult.is_final is true. As well, it appears we're expected to keep streaming audio until data event is fired, to get any result at all, final or interim.
This looks like a bug to me, filing an issue.
Updated: it now seems to have been confirmed as a bug. Until it's fixed, I'm looking for a potential workaround.
Updated: for future references, here is the list of the current and previously tracked issues involving streamingRecognize.
I'd expect this to be a common problem for those who use streamingRecognize, surprised it hasn't been reported before. Submitting it as a bug to issuetracker.google.com, as well.
My bad — unsurprisingly, this turned to be an obscure race condition in my code.
I've put together a self-contained sample that works as expected (gist). It helped me tracking down the issue. Hopefully, it may help others and my future self:
// A simple streamingRecognize workflow,
// tested with Node v15.0.1, by #noseratio
import fs from 'fs';
import path from "path";
import url from 'url';
import util from "util";
import timers from 'timers/promises';
import speech from '#google-cloud/speech';
export {}
// need a 16-bit, 16KHz raw PCM audio
const filename = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "sample.raw");
const encoding = 'LINEAR16';
const sampleRateHertz = 16000;
const languageCode = 'en-US';
const request = {
config: {
encoding: encoding,
sampleRateHertz: sampleRateHertz,
languageCode: languageCode,
},
interimResults: false // If you want interim results, set this to true
};
// init SpeechClient
const client = new speech.v1p1beta1.SpeechClient();
await client.initialize();
// Stream the audio to the Google Cloud Speech API
const stream = client.streamingRecognize(request);
// log all data
stream.on('data', data => {
const result = data.results[0];
console.log(`SR results, final: ${result.isFinal}, text: ${result.alternatives[0].transcript}`);
});
// log all errors
stream.on('error', error => {
console.warn(`SR error: ${error.message}`);
});
// observe data event
const dataPromise = new Promise(resolve => stream.once('data', resolve));
// observe error event
const errorPromise = new Promise((resolve, reject) => stream.once('error', reject));
// observe finish event
const finishPromise = new Promise(resolve => stream.once('finish', resolve));
// observe close event
const closePromise = new Promise(resolve => stream.once('close', resolve));
// we could just pipe it:
// fs.createReadStream(filename).pipe(stream);
// but we want to simulate the web socket data
// read RAW audio as Buffer
const data = await fs.promises.readFile(filename, null);
// simulate multiple audio chunks
console.log("Writting...");
const chunkSize = 4096;
for (let i = 0; i < data.length; i += chunkSize) {
stream.write(data.slice(i, i + chunkSize));
await timers.setTimeout(50);
}
console.log("Done writing.");
console.log("Before ending...");
await util.promisify(c => stream.end(c))();
console.log("After ending.");
// race for events
await Promise.race([
errorPromise.catch(() => console.log("error")),
dataPromise.then(() => console.log("data")),
closePromise.then(() => console.log("close")),
finishPromise.then(() => console.log("finish"))
]);
console.log("Destroying...");
stream.destroy();
console.log("Final timeout...");
await timers.setTimeout(1000);
console.log("Exiting.");
The output:
Writting...
Done writing.
Before ending...
SR results, final: true, text: this is a test I'm testing voice recognition This Is the End
After ending.
data
finish
Destroying...
Final timeout...
close
Exiting.
To test it, a 16-bit/16KHz raw PCM audio file is required. An arbitrary WAV file wouldn't work as is because it contains a header with metadata.
This: "I'm looking for a potential workaround." - have you considered extending from SpeechClient as a base class? I don't have credential to test, but you can extend from SpeechClient with your own class and then call the internal close() method as needed. The close() method shuts down the SpeechClient and resolves the outstanding Promise.
Alternatively you could also Proxy the SpeechClient() and intercept/respond as needed. But since your intent is to shut it down, the below option might be your workaround.
const speech = require('#google-cloud/speech');
class ClientProxy extends speech.SpeechClient {
constructor() {
super();
}
myCustomFunction() {
this.close();
}
}
const clientProxy = new ClientProxy();
try {
clientProxy.myCustomFunction();
} catch (err) {
console.log("myCustomFunction generated error: ", err);
}
Since it's a bug, I don't know if this is suitable for you but I have used this.recognizeStream.end(); several times in different situations and it worked. However, my code was a bit different...
This feed may be something for you:
https://groups.google.com/g/cloud-speech-discuss/c/lPaTGmEcZQk/m/Kl4fbHK2BQAJ

Calling callback inside callback in native Node

I'm a node & C++ newbie so be clement.
I'm writing a native node addon.
My addon start a webcam streaming (using UVC lib) and I want every frame to be available to node.
My CC addon do something like
uvc_start_streaming(devh, &ctrl, frameProcess, (void *) &args, 0)
Where:
devh: is the UVC device
ctrl: are device config
frameProcess: is my function callback to be called at every new frame
args: are arguments from javascript function.
The c++ callback is called every new frame and I want to simple print something like "new frame received" so my C++ is like:
void frameProcess(uvc_frame_t *frame, void *ptr) {
const FunctionCallbackInfo<Value> args = *((const FunctionCallbackInfo<Value>*)(ptr));
Isolate* isolate = args.GetIsolate();
Local<Function> cb = Local<Function>::Cast(args[0]);
const unsigned argc = 1;
Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "new frame received") };
cb->Call(Null(isolate), argc, argv);
}
void testStreaming (const FunctionCallbackInfo<Value>& args) {
...
res = uvc_start_streaming(devh, &ctrl, frameProcess, (void *) &args, 0);
puts("Streaming...");
Sleep(10000); /* stream for 10 seconds */
uvc_stop_streaming(devh);
puts("Done streaming.");
...
}
...
NODE_SET_METHOD(exports, "testStreaming", testDevice);
My js is something like:
'use strict';
var uvc = require('../build/Release/binding')
uvc.testStreaming(
function (x) {
console.log(x)
}
)
The problem is that node exit without any message or error when program reach cb->Call.
If I comment cb->Call row the program run for 10 seconds (continuosly calling the ) as programmed and then exit.
But if I uncomment cb->Call the program exit immediatly.
Your frameProcess() function should call v8::Function callback in the Node.js thread, see https://stackoverflow.com/a/28116160/1355844

Node.js Serialport synchronous write-read

Does anyone have any example code to use the node.js serialport module in a blocking/synchronous way?
What I am trying to do is send a command to a micro-controller and wait for the response before sending the next command.
I have the sending/receiving working but the data just comes in with the listener
serial.on( "data", function( data) {
console.log(data);
});
Is there a way to wait for the returned data after doing a
serial.write("Send Command");
Should I be setting a global flag or something?
I am still new to the async programming style of node.js
Thanks
There is no such option and it's actually not necessary. One way of doing this is to maintain a queue of commands. Something like this:
function Device (serial) {
this._serial = serial;
this._queue = queue;
this._busy = false;
this._current = null;
var device = this;
serial.on('data', function (data) {
if (!device._current) return;
device._current[1](null, data);
device.processQueue();
});
}
Device.prototype.send = function (data, callback) {
this._queue.push([data, callback]);
if (this._busy) return;
this._busy = true;
this.processQueue();
};
Device.prototype.processQueue = function () {
var next = this._queue.shift();
if (!next) {
this._busy = false;
return;
}
this._current = next;
this._serial.write(next[0]);
};
This can now be done using the serialport-synchronous library in npm.
Consider the following serial port flow:
1. << READY
2. >> getTemp
3. << Received: getTemp
4. << Temp: 23.11
We can get the temperature value with the following code:
import { SerialPortController } from 'serialport-synchronous'
const TEMP_REGEX = /^Temp: (\d+\.\d+)$/
const ERROR_REGEX = /^ERROR$/
const READY_REGEX = /^READY$/
const controller = new SerialPortController({
path: '/dev/ttyUSB0',
baudRate: 19200,
handlers: [{
pattern: READY_REGEX,
callback: main // call the main() function when READY_REGEX has matched.
}]
})
// push the log events from the library to the console
controller.on('log', (log) => console[log.level.toLowerCase()](`${log.datetime.toISOString()} [${log.level.toUpperCase()}] ${log.message}`))
// open the serial port connection
controller.open()
async function main () {
try {
// send the getTemp text to the serialport
const result = await controller.execute({
description: 'Querying current temperature', // optional, used for logging purposes
text: 'getTemp', // mandatory, the text to send
successRegex: TEMP_REGEX, // mandatory, the regex required to resolve the promise
bufferRegex: TEMP_REGEX, // optional, the regex match required to buffer the response
errorRegex: ERROR_REGEX, // optional, the regex match required to reject the promise
timeoutMs: 1000 // mandatory, the maximum time to wait before rejecting the promise
})
// parse the response to extract the temp value
const temp = result.match(TEMP_REGEX)[1]
console.log(`\nThe temperature reading was ${temp}c`)
} catch (error) {
console.error('Error occured querying temperature')
console.error(error)
}
}
Output looks something like this:
2022-07-20T01:33:56.855Z [INFO] Connection to serial port '/dev/ttyUSB0' has been opened
2022-07-20T01:33:58.391Z [INFO] << READY
2022-07-20T01:33:58.392Z [INFO] Inbound message matched unsolicited handler pattern: /^READY$/. Calling custom handler function
2022-07-20T01:33:58.396Z [INFO] Querying current temperature
2022-07-20T01:33:58.397Z [INFO] >> [TEXT] getTemp
2022-07-20T01:33:58.415Z [INFO] << Received: getTemp
2022-07-20T01:33:58.423Z [INFO] << Temp: 23.11
2022-07-20T01:33:58.423Z [DEBUG] Received expected response, calling resolve handler
The temperature reading was 23.11c

Categories