Unable to Stop DetectIntentStream on DialogFlow Es - node.js

I am trying to create a two-way communication voice bot using detectIntentStream Api of Dialogflow ES. But unable to stop streaming or end the session after communication.
I use example2 of this repository: https://github.com/dialogflow/selfservicekiosk-audio-streaming/tree/master/examples
here is the server code
const projectId = process.env.npm_config_PROJECT_ID;
const example = process.env.npm_config_EXAMPLE;
const port = process.env.npm_config_PORT || 3000;
const languageCode = "en-US";
let encoding = "AUDIO_ENCODING_LINEAR_16";
if (example > 3) {
// NOTE: ENCODING NAMING FOR SPEECH API IS DIFFERENT
encoding = "LINEAR16";
}
console.log(example);
if (example == 7) {
// NOTE: ENCODING NAMING FOR SPEECH API IS DIFFERENT
encoding = "linear16";
}
const singleUtterance = false;
const interimResults = false;
const sampleRateHertz = 16000;
const speechContexts = [
{
phrases: ["mail", "email"],
boost: 20.0,
},
];
console.log(example);
console.log(projectId);
// ----------------------
// load all the libraries for the server
const socketIo = require("socket.io");
const path = require("path");
const fs = require("fs");
const http = require("http");
const cors = require("cors");
const express = require("express");
const ss = require("socket.io-stream");
// load all the libraries for the Dialogflow part
const uuid = require("uuid");
const util = require("util");
const { Transform, pipeline } = require("stream");
const pump = util.promisify(pipeline);
const df = require("dialogflow").v2beta1;
// set some server variables
const app = express();
var server;
var sessionId, sessionClient, sessionPath, request;
var speechClient,
requestSTT,
ttsClient,
requestTTS,
mediaTranslationClient,
requestMedia;
// STT demo
const speech = require("#google-cloud/speech");
// TTS demo
const textToSpeech = require("#google-cloud/text-to-speech");
// Media Translation Demo
const mediatranslation = require("#google-cloud/media-translation");
/**
* Setup Express Server with CORS and SocketIO
*/
function setupServer() {
// setup Express
app.use(cors());
app.get("/", function (req, res) {
res.sendFile(path.join(__dirname + "/example" + example + ".html"));
});
server = http.createServer(app);
io = socketIo(server);
server.listen(port, () => {
console.log("Running server on port %s", port);
});
// Listener, once the client connect to the server socket
io.on("connect", (client) => {
console.log(`Client connected [id=${client.id}]`);
client.emit("server_setup", `Server connected [id=${client.id}]`);
ss(client).on("stream", function (stream, data) {
// get the name of the stream
const filename = path.basename(data.name);
// pipe the filename to the stream
stream.pipe(fs.createWriteStream(filename));
// make a detectIntStream call
detectIntentStream(stream, function (results) {
console.log(results);
console.log(results.outputAudio);
client.emit("results", results);
});
});
});
}
function setupDialogflow() {
// Dialogflow will need a session Id
sessionId = uuid.v4();
// Dialogflow will need a DF Session Client
// So each DF session is unique
sessionClient = new df.SessionsClient();
// Create a session path from the Session client,
// which is a combination of the projectId and sessionId.
sessionPath = sessionClient.sessionPath(projectId, sessionId);
// Create the initial request object
// When streaming, this is the first call you will
// make, a request without the audio stream
// which prepares Dialogflow in receiving audio
// with a certain sampleRateHerz, encoding and languageCode
// this needs to be in line with the audio settings
// that are set in the client
request = {
session: sessionPath,
queryInput: {
audioConfig: {
sampleRateHertz: sampleRateHertz,
encoding: encoding,
languageCode: languageCode,
speechContexts: speechContexts,
},
singleUtterance: singleUtterance,
},
outputAudioConfig: {
audioEncoding: "OUTPUT_AUDIO_ENCODING_LINEAR_16",
},
};
}
async function detectIntentStream(audio, cb) {
// execute the Dialogflow Call: streamingDetectIntent()
const stream = sessionClient
.streamingDetectIntent()
.on("data", function (data) {
// when data comes in
// log the intermediate transcripts
if (data.recognitionResult) {
console.log(
`Intermediate transcript:
${data.recognitionResult.transcript}`
);
} else {
// log the detected intent
console.log(`Detected intent:`);
cb(data);
}
})
.on("error", (e) => {
console.log(e);
})
.on("end", () => {
console.log("on end");
return null;
});
// Write request objects.
// Thee first message must contain StreamingDetectIntentRequest.session,
// [StreamingDetectIntentRequest.query_input] plus optionally
// [StreamingDetectIntentRequest.query_params]. If the client wants
// to receive an audio response, it should also contain
// StreamingDetectIntentRequest.output_audio_config.
// The message must not contain StreamingDetectIntentRequest.input_audio.
stream.write(request);
// pump is a small node module that pipes streams together and
// destroys all of them if one of them closes.
await pump(
audio,
// Format the audio stream into the request format.
new Transform({
objectMode: true,
transform: (obj, _, next) => {
next(null, {
inputAudio: obj,
outputAudioConfig: {
audioEncoding: `OUTPUT_AUDIO_ENCODING_LINEAR_16`,
},
});
},
}),
stream
);
}
setupDialogflow();
setupServer();
and here is client code
<!DOCTYPE html>
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<meta charset="utf-8" />
<title>RecordRTC over Socket.io</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.indigo-pink.min.css">
<script src="https://www.WebRTC-Experiment.com/RecordRTC.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io-stream/0.9.1/socket.io-stream.js"></script>
</head>
<body>
<div style="margin: 20px">
<h1 style="font-size: 18px;">Example 2: Dialogflow Speech Detection through streaming</h1>
<div>
<button id="start-recording" disabled>Start Streaming</button>
<button id="stop-recording" disabled>Stop Streaming</button>
</div>
<h2 style="font-size: 16px; margin-bottom: 10px;">Query Text</h2>
<code>data.queryResult.queryText</code><br/>
<input id="queryText" type="text" style="width: 400px;"/>
<h2 style="font-size: 16px; margin-bottom: 10px;">Intent</h2>
<code>data.queryResult.intent.displayName</code><br/>
<input id="intent" type="text" style="width: 400px;"/>
<h2 style="font-size: 16px;">Responses</h2>
<code>data.queryResult.fulfillmentText</code><br/>
<textarea id="results" style="width: 800px; height: 300px;"></textarea>
<code>data.queryResult.audio</code><br/>
<textarea id="result_audio" style="width: 800px; height: 300px;"></textarea>
</div>
<script type="text/javascript">
const startRecording = document.getElementById('start-recording');
const stopRecording = document.getElementById('stop-recording');
let recordAudio;
const socketio = io();
const socket = socketio.on('connect', function() {
startRecording.disabled = false;
});
startRecording.onclick = function() {
startRecording.disabled = true;
navigator.getUserMedia({
audio: true
}, function(stream) {
recordAudio = RecordRTC(stream, {
type: 'audio',
mimeType: 'audio/webm',
sampleRate: 44100,
desiredSampRate: 16000,
recorderType: StereoAudioRecorder,
numberOfAudioChannels: 1,
//1)
// get intervals based blobs
// value in milliseconds
// as you might not want to make detect calls every seconds
timeSlice: 4000,
//2)
// as soon as the stream is available
ondataavailable: function(blob) {
// 3
// making use of socket.io-stream for bi-directional
// streaming, create a stream
var stream = ss.createStream();
// stream directly to server
// it will be temp. stored locally
ss(socket).emit('stream', stream, {
name: 'stream.wav',
size: blob.size
});
// pipe the audio blob to the read stream
ss.createBlobReadStream(blob).pipe(stream);
}
});
recordAudio.stopRecording();
stopRecording.disabled = false;
}, function(error) {
console.error(JSON.stringify(error));
});
};
// 4)
// on stop button handler
stopRecording.onclick = function() {
// recording stopped
recordAudio.startRecording();
startRecording.disabled = false;
stopRecording.disabled = true;
// socketio.emit("close",function(){
// })
};
// const aduiopreview = document.getElementById('result_audio');
const resultpreview = document.getElementById('results');
const intentInput = document.getElementById('intent');
const textInput = document.getElementById('queryText');
socketio.on('results', function (data) {
console.log(data);
if(data.queryResult){
resultpreview.innerHTML += "" + data.queryResult.fulfillmentText;
intentInput.value = data.queryResult.intent.displayName;
textInput.value = "" + data.queryResult.queryText;
}
if(data.outputAudio){
console.log(data.outputAudio);
playOutput(data.outputAudio)
}
});
/*
* When working with Dialogflow and Dialogflow matched an intent,
* and returned an audio buffer. Play this output.
*/
function playOutput(arrayBuffer){
let audioContext = new AudioContext();
let outputSource;
try {
if(arrayBuffer.byteLength > 0){
console.log(arrayBuffer.byteLength);
audioContext.decodeAudioData(arrayBuffer,
function(buffer){
audioContext.resume();
outputSource = audioContext.createBufferSource();
outputSource.connect(audioContext.destination);
outputSource.buffer = buffer;
outputSource.start(0);
},
function(){
console.log(arguments);
});
}
} catch(e) {
console.log(e);
}
}
</script>
</body>
</html>

Related

Forcing Client to Stop AJAX from Node.js Server

I looked at several SO posts trying to find a way to make a Node.js server tell a client to stop uploading after a certain file size has been reached. The most promising of these is ed-ta's technique at Avoiding further processing on busyboy file upload size limit.
Per ed-ta, my Node.js server seemed to be doing what it should. The server sent the 455 status code as soon as the size limit was reached and stopped accepting any more data. Unfortunately, my client kept processing the file until it was completely done anyway. This is a less than ideal experience when the user tries to upload extremely large files since the client doesn't alert the user that the threshold is reached until the AJAX request is completely done.
How do I get the client to see the 455 status code in a timely manner?
I tried checking for the 455 status inside xhr.onreadystatechange, but I can't seem to find that information from inside onreadystatehange even if the server has already sent the 455 in the response 1. Also, the onreadystatechange event doesn't seem to trigger until after the entire file has been processed by the client anyway.
I have tried to simplify the problem by getting rid of the irrelevant details and my current demo code follows:
Server.js
// This code is based on
// https://stackoverflow.com/questions/23691194/node-express-file-upload
//
// [1] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [2] - https://stackoverflow.com/questions/18310394/
// no-access-control-allow-origin-node-apache-port-issue
//
// [3] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [4] - https://stackoverflow.com/questions/44736327/
// node-js-cors-issue-response-to-preflight-
// request-doesnt-pass-access-control-c
var express = require('express');
var busboy = require('connect-busboy');
var fs = require('fs-extra');
var cors = require('cors'); // [4]
const app = express();
// See [2][4]
app.use(
function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "null");
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST")
next();
}
);
app.options('*', cors());
app.use(
busboy({ limits: { files: 1, fileSize: 500000000 } }) // [1]
);
app.post('/uploadEndpoint', function (req, res, next) {
var fStream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldName, file, filename) {
console.log("Uploading: " + filename);
var destPath = './' + filename;
fStream = fs.createWriteStream(destPath);
file.pipe(fStream);
// ed-ta [3]
// Despite being asynchronous limit_reach
// will be seen here as true if it hits max size
// as set in file.on.limit because once it hits
// max size the stream will stop and on.finish
// will be triggered.
var limit_reach = false;
req.busboy.on('finish', function() {
if(!limit_reach){
res.send(filename + " uploaded");
}
});
file.on('limit', function() {
fs.unlink(destPath, function(){
limit_reach = true;
res.status(455).send("File too big.");
console.log('Telling client to stop...');
});
});
});
})
app.listen(8000);
test.html
<!DOCTYPE html>
<!--
This code is based off timdream and Basar at
https://stackoverflow.com/questions/6211145/
upload-file-with-ajax-xmlhttprequest
[1] - https://stackoverflow.com/questions/49692745/
express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos
-->
<html>
<head>
<meta charset="utf-8" />
<script>
function uploadFile() {
var progress = document.getElementById("output");
progress.innerText = "Starting";
var fileCtl = document.getElementById("theFile");
var file = fileCtl.files[0];
var xhr = new XMLHttpRequest();
// timdream
var formData = new FormData();
formData.append('theFile', file);
xhr.upload.onprogress = (progressEvent) => {
var percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
progress.innerText =
"Percent Uploaded: " + percentCompleted + "%";
};
xhr.onreadystatechange = function(e) {
if (this.status === 455) {
alert('Sorry, file was too big.');
}
};
xhr.open('post', 'http://localhost:8000/uploadEndpoint', true);
// Apparently this line causes issues Multipart Boundary not
// found error [1]
// xhr.setRequestHeader("Content-Type","multipart/form-data");
// timdream
xhr.send(formData);
}
</script>
</head>
<body>
<input type="file" id="theFile" name="theName" /><br />
<div id="output">Upload Progress</div>
<input type="button" id="theButton"
onclick="uploadFile();" value="Send" />
</body>
</html>
1 - I could setup another endpoint on the Node.js server and use AJAX to poll that endpoint for the current status inside the client's onprogress but that seems like a kludgy solution that would waste bandwidth.
To get around the problem described above, I wound up using a separate WebSocket channel to send a message from the server back down to the client to tell the said client to stop the upload. I then called abort on the client's XMLHttpRequest object per the Mozilla docs.
Final sample code looks like this:
Server.js
// This code is based on
// https://stackoverflow.com/questions/23691194/node-express-file-upload
//
// [1] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [2] - https://stackoverflow.com/questions/18310394/
// no-access-control-allow-origin-node-apache-port-issue
//
// [3] - https://stackoverflow.com/questions/39681966/
// avoiding-further-processing-on-busyboy-file-upload-size-limit
//
// [4] - https://stackoverflow.com/questions/44736327/
// node-js-cors-issue-response-to-preflight-
// request-doesnt-pass-access-control-c
var express = require('express');
var busboy = require('connect-busboy');
var fs = require('fs-extra');
var cors = require('cors'); // [4]
var ws = require('ws');
var WebSocketServer = ws.WebSocketServer;
var g_ws;
// BEGIN FROM: https://www.npmjs.com/package/ws
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function connection(ws) {
g_ws = ws;
});
// END FROM: https://www.npmjs.com/package/ws
const app = express();
// See [2][4]
app.use(
function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "null");
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, POST")
next();
}
);
app.options('*', cors());
app.use(
busboy({ limits: { files: 1, fileSize: 300000000 } }) // [1]
);
app.post('/uploadEndpoint', function (req, res, next) {
var fStream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldName, file, fileNameObject) {
var filename = fileNameObject.filename;
console.log("Uploading: " + filename);
var destPath = './' + filename;
fStream = fs.createWriteStream(destPath);
file.pipe(fStream);
// ed-ta [3]
// Despite being asynchronous limit_reach
// will be seen here as true if it hits max size
// as set in file.on.limit because once it hits
// max size the stream will stop and on.finish
// will be triggered.
var limit_reach = false;
req.busboy.on('finish', function() {
var message;
if(!limit_reach){
message = 'success';
res.send(filename + " uploaded");
} else {
message = 'TooBig';
}
g_ws.send(message);
});
file.on('limit', function() {
fs.unlink(destPath, function(){
limit_reach = true;
res.status(455).send("File too big.");
console.log('Telling client to stop...');
// https://www.npmjs.com/package/ws
g_ws.send("TooBig");
});
});
});
})
app.listen(8000);
test.html
<!DOCTYPE html>
<!--
This code is based off timdream and Basar at
https://stackoverflow.com/questions/6211145/
upload-file-with-ajax-xmlhttprequest
[1] - https://stackoverflow.com/questions/49692745/
express-using-multer-error-multipart-boundary-not-found-request-sent-by-pos
-->
<html>
<head>
<meta charset="utf-8" />
<script>
function uploadFile() {
var progress = document.getElementById("output");
progress.innerText = "Starting";
var fileCtl = document.getElementById("theFile");
var file = fileCtl.files[0];
var xhr = new XMLHttpRequest();
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('message', function (event) {
if (event.data === 'TooBig') {
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort
xhr.abort();
alert('Server says file was too big.');
} else if (event.data === 'success') {
alert('File uploaded sucessfully.');
} else {
alert('Unknown server error');
}
socket.close();
});
// timdream
var formData = new FormData();
formData.append('theFile', file);
xhr.upload.onprogress = (progressEvent) => {
var percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
progress.innerText =
"Percent Uploaded: " + percentCompleted + "%";
};
xhr.onreadystatechange = function(e) {
if (this.status === 455) {
alert('Sorry, file was too big.');
}
};
xhr.open('post', 'http://localhost:8000/uploadEndpoint', true);
// Apparently this line causes issues Multipart Boundary not
// found error [1]
// xhr.setRequestHeader("Content-Type","multipart/form-data");
// timdream
xhr.send(formData);
}
</script>
</head>
<body>
<input type="file" id="theFile" name="theName" /><br />
<div id="output">Upload Progress</div>
<input type="button" id="theButton"
onclick="uploadFile();" value="Send" />
</body>
</html>
This is not the exact code I used for my solution but this simplified working demo illustrates the concept.
BTW: Filesize limit was lowered to 300000000 on the server to make testing easier but that doesn't matter.

FTP over HTTP using a Microsoft Azure function

I have to set up an FTP over HTTP using a Microsoft Azure function, but when trying to connect to the FTP server the client timeout and throw an "ERR_GENERIC_CLIENT" error and quit. I'm sure the problem comes from the FTP client configs, but I can't find what to change.
the azure function :
let Client = require('ssh2-sftp-client');
let client = new Client();
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
const boundary = req.headers['content-type'].split('=')[1];
const splitedBody = req.body.toString().split(boundary);
let parsedBody = {};
let errors = [];
splitedBody.forEach(field => {
if (field.includes('name=')) {
if (!field.includes('filename')) {
let regex = /name="(.*?)"\r\n\r\n(.*)/g;
let m = regex.exec(field);
parsedBody[m[1]] = m[2];
}
else {
let regex = /Content-Type:\s(.*?)\s\s(((.*)\s)*)--/;
let m = regex.exec(field);
let file = field.split(m[1])[1]
file = file.substring(4, file.length - 4);
fileBuffer = Buffer.from(file);
let fileNameRegex = /filename="(.*?)"/g;
let fileName = fileNameRegex.exec(field)[1];
parsedBody.file = file;
parsedBody.buffer = fileBuffer;
parsedBody.fileName = fileName;
}
}
});
const config = {
host: parsedBody.host,
port: parseInt(parsedBody.port),
username: parsedBody.user,
password: parsedBody.password,
tryKeyboard: true,
readyTimeout: 5000
};
try {
await client.connect(config);
try {
let result = await client.put(
parsedBody.buffer,
parsedBody.path,
{
flags: 'w', // w - write and a - append
encoding: null, // use null for binary files
mode: 0o666, // mode to use for created file (rwx)
autoClose: true // automatically close the write stream when finished
})
}
catch (e) {
errors.push({e, msg: 'put'});
context.log(`main error: ${e.message}`);
}
}
catch (e) {
errors.push({e, msg: 'connect'});
context.log(e);
}
finally {
client.end();
}
context.res = {
body: {
config,
errors
}
};
};
For testing, I use a small dummy HTML form
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ftp over http</title>
</head>
<body>
<form action="azure function URL" enctype="multipart/form-data" method="post">
<input type="hidden" name="host" value="ftp.server.address">
<input type="hidden" name="port" value="21">
<input type="hidden" name="path" value="/test/">
<input type="hidden" name="user" value="username">
<input type="hidden" name="password" value="password">
<div>
Text field title: <input type="text" name="title" /></div>
<div>
File: <input type="file" name="file" /></div>
<input type="submit" value="Upload" />
</form>
</body>
</html>```
When I submit the form all I receive is the unhelpfull and undocumented "ERR_GENERIC_CLIENT" code.
According to your code you provide, your use the sdk ssh2-sftp-client to connect ftp server. It is wrong. we just can use it to connect sftp server. if you want to connect ftp server, I suggest you use the sdk basic-ftp. regarding how to use the sdk, please refer to https://github.com/patrickjuchli/basic-ftp.
For example
Code
const ftp = require("basic-ftp")
const client = new ftp.Client()
module.exports = async function (context, req) {
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
const boundary = req.headers['content-type'].split('=')[1];
const splitedBody = req.body.toString().split(boundary);
let parsedBody = {};
let errors = [];
splitedBody.forEach(field => {
if (field.includes('name=')) {
if (!field.includes('filename')) {
let regex = /name="(.*?)"\r\n\r\n(.*)/g;
let m = regex.exec(field);
parsedBody[m[1]] = m[2];
}
else {
let regex = /Content-Type:\s(.*?)\s\s(((.*)\s)*)--/;
let m = regex.exec(field);
let file = field.split(m[1])[1]
file = file.substring(4, file.length - 4);
fileBuffer = Buffer.from(file);
let fileNameRegex = /filename="(.*?)"/g;
let fileName = fileNameRegex.exec(field)[1];
parsedBody.file = file;
parsedBody.buffer = fileBuffer;
parsedBody.fileName = fileName;
}
}
});
client.ftp.verbose = true
try {
await client.access({
host: parsedBody.host,
port: parseInt(parsedBody.port),
user: parsedBody.user,
password: parsedBody.password
})
var stream =getStream(parsedBody.buffer)
const result =await client.upload(stream,parsedBody.path)
context.log(result)
}
catch(err) {
context.log(err)
errors += err
}
client.close()
context.res = {
body: {
errors
}
};
};
Test(I use the postman to do test)
Connect to FTP server to check it(I use FileZilla)

Convert midi to mp3 in node

So im developing an app in node where i use scribbletune
To create a midi-file:
const clip = scribble.clip({
notes: cMajor,
pattern: 'xxxxxxx'
});
scribble.midi(clip, 'c-major.mid');
I want to be able to play this file in the browser.
From what i gather there is no way to play midi in the browser using audio-tags:
<audio controls>
<source src="horse.ogg" type="audio/ogg">
</audio>
So im thinking that i should first convert the midi-file into an mp3-file. I've been searching the web for a node-package that could do this but have not found anything.
It sounds like something that should be possible.
Any tips on how to achieve this in node?
If you only want to play the generated MIDI in the browser and converting the file to ogg first and playing it using an audio tag is not a requirement, MidiConvert and Tone.js (as briefly explained in Scribbletune docs) can do the job.
I've hacked together an example:
const http = require("http");
const MidiConvert = require("midiconvert"); // npm install midiconvert
const scribble = require("scribbletune"); // npm install scribbletune
const createMidiForToneJs = (notes, pattern) => {
let clip = scribble.clip({
notes,
pattern
});
let midiData;
// The callback is called synchronously (https://github.com/scribbletune/scribbletune/blob/ebda52d7a2835f28b3ddab15488c22bc1d425e7b/src/midi.js#L37)
scribble.midi(
clip,
null, // setting filename to null will cause this method to return bytes (via the callback)
(err, bytes) => {
// err is always null
midiData = bytes;
}
);
return MidiConvert.parse(midiData);
};
const server = http.createServer((req, res) => {
// Don't mind the naivety of the following URL router
switch (req.url.toLowerCase()) {
case "/":
res.end(indexHTMLContents);
break;
case "/midi.json":
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
const jsonString = JSON.stringify(createMidiForToneJs("CM", "xxxxxxx"));
res.end(jsonString);
break;
default:
res.statusCode = 404;
res.end();
}
});
server.listen(3000); // Open http://localhost:3000 in browser
// -- The HTML and Javascript for the "/" route. Obviously, don't do this in production!
const jsToBeExecutedInBrowser = () => {
document.querySelector("button").addEventListener(
"click",
async () => {
Tone.Transport.clear();
Tone.Transport.stop();
const synth = new Tone.Synth().toMaster();
const response = await fetch("/midi.json");
const midiJson = await response.json();
const midi = MidiConvert.fromJSON(midiJson);
Tone.Transport.bpm.value = midi.bpm;
Tone.Transport.timeSignature = midi.timeSignature;
midi.tracks.forEach(track => {
new Tone.Part((time, event) => {
synth.triggerAttackRelease(
event.name,
event.duration,
time,
event.velocity
);
}, track.notes).start(midi.startTime);
});
Tone.Transport.start();
},
false
);
};
const indexHTMLContents = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Midi Player</title>
</head>
<body>
<button>Play!</button>
<script src="https://unpkg.com/tone"></script>
<script src="https://unpkg.com/midiconvert"></script>
<script>
(${jsToBeExecutedInBrowser.toString()})();
</script>
</body>
</html>`;

Koajs and Socket.io integration

Please, need help with integration of sockets with this koajs skeleton
Here is the code from server side file (/src/index.js)
'use strict';
// 3rd party
require('dotenv').config(); // Load env vars from .env, always run this early
const koa = require('koa');
const bouncer = require('koa-bouncer');
const nunjucksRender = require('koa-nunjucks-render');
const debug = require('debug')('app:index');
// 1st party
const config = require('./config');
const mw = require('./middleware');
const belt = require('./belt');
const cancan = require('./cancan');
////////////////////////////////////////////////////////////
const app = koa();
app.poweredBy = false;
app.proxy = config.TRUST_PROXY;
////////////////////////////////////////////////////////////
// Configure view-layer (nunjucks)
//
// We can override options send directly to nunjucks.
// https://mozilla.github.io/nunjucks/api.html#configure
////////////////////////////////////////////////////////////
const nunjucksOptions = {
// `yield this.render('show_user')` will assume that a show_user.html exists
ext: '.html',
noCache: config.NODE_ENV === 'development',
// don't throw template errors in development if we try to render
// a null/undefined like {{ x }}. in theory, setting it to true prevents
// bugs and forces you to be explicit about {{ x or '' }}, but in reality,
// often more annoying than it's worth.
throwOnUndefined: false,
// globals are bindings we want to expose to all templates
globals: {
// let us use `can(USER, ACTION, TARGET)` authorization-checks in templates
can: cancan.can,
},
// filters are functions that we can pipe values to from nunjucks templates.
// e.g. {{ user.uname | md5 | toAvatarUrl }}
filters: {
json: x => JSON.stringify(x, null, ' '),
formatDate: belt.formatDate,
nl2br: belt.nl2br,
md5: belt.md5,
toAvatarUrl: belt.toAvatarUrl,
autolink: belt.autolink,
},
};
////////////////////////////////////////////////////////////
// Middleware
////////////////////////////////////////////////////////////
app.use(mw.ensureReferer());
app.use(require('koa-helmet')());
app.use(require('koa-compress')());
app.use(require('koa-static')('public'));
// Don't show logger in test mode
if (config.NODE_ENV !== 'test') {
app.use(require('koa-logger')());
}
app.use(require('koa-body')({ multipart: true }));
app.use(mw.methodOverride()); // Must come after body parser
app.use(mw.removeTrailingSlash());
app.use(mw.wrapCurrUser());
app.use(mw.wrapFlash('flash'));
app.use(bouncer.middleware());
app.use(mw.handleBouncerValidationError()); // Must come after bouncer.middleware()
app.use(nunjucksRender('views', nunjucksOptions));
// Provide a convience function for protecting our routes behind
// our authorization rules. If authorization check fails, 404 response.
//
// Usage:
//
// router.get('/topics/:id', function*() {
// const topic = yield db.getTopicById(this.params.id);
// this.assertAuthorized(this.currUser, 'READ_TOPIC', topic);
// ...
// });
app.use(function*(next) {
this.assertAuthorized = (user, action, target) => {
const isAuthorized = cancan.can(user, action, target);
const uname = (user && user.uname) || '<Guest>';
debug('[assertAuthorized] Can %s %s: %s', uname, action, isAuthorized);
this.assert(isAuthorized, 404);
};
yield* next;
});
////////////////////////////////////////////////////////////
// Routes
////////////////////////////////////////////////////////////
app.use(require('./routes').routes());
app.use(require('./routes/authentication').routes());
app.use(require('./routes/admin').routes());
////////////////////////////////////////////////////////////
// If we run this file directly (npm start, npm run start-dev, node src/index.js)
// then start the server. Else, if we require() this file (like from
// our tests), then don't start the server and instead just export the app.
if (require.main === module) {
app.listen(config.PORT, function() {
console.log('Listening on port', config.PORT);
});
} else {
module.exports = app;
}
On client side (/views/master.js) :
....
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
<script>
var socket = io();
socket.on('news', function (data) {
console.log('received news with data: ');
console.log(data);
});
function myclick () {
console.log("click");
socket.emit('click', { clickdata: 'someone clicked on the button' });
}
</script>
<button type="button" onclick="myclick();">Click Me and watch console at server and in browser.</button>
....
Please, can someone explain to me hot to integrate socket.io with koas server? I've installed ["socket.io": "^1.4.5"]. What are the next steps?
PS: Sory about my ugly english.
I've founded a solution here. Bellow is a general code sample
const Koa = require( 'koa' )
const IO = require( 'koa-socket' )
const app = new Koa()
const io = new IO()
app.use( ... )
io.attach( app )
io.on( 'join', ( ctx, data ) => {
console.log( 'join event fired', data )
})
app.listen( process.env.PORT || 3000 )

Send PDF file from AngularJS to NodeJS

i need to send a PDF file from angularjs client to NodeJS service.
I did the angularjs service, and when i receive the file its a string like this:
%PDF-1.3
3 0 obj
<</Type /Page
/Parent 1 0 R
/Reso
How can i reconvert this string to PDF in NodeJS?
This is the client code:
var sendByEmail = function () {
$scope.generatingPdf = true;
$('#budget').show();
var pdf = new JsPDF('p', 'pt', 'letter');
var source = $('#budget')[0];
pdf.addHTML(source, 0, 0, function () {
var resultPdf = pdf.output();
BillService.sendByEmail("rbrlnx#gmail.com", resultPdf).then(function () {
});
$('#budget').hide();
});
};
var sendByEmail = function (email, file) {
var deferred = $q.defer();
var data = {
email: email,
file: file
};
BillService.sendByEmail(data, function (result) {
deferred.resolve(result);
}, function () {
deferred.reject();
});
return deferred.promise;
};
The server code controller its empty:
var sendByEmail = function (req, res, next) {
var file = req.body.file;
};
I experimented with this a while ago, and I came up with this. It's not production ready by a long shot maybe you find it useful. It's free of front end libraries (except Angular ofcourse), but assumes you're using Express 4x and body-parser.
The result:
In the browser:
On the server:
What you're seeing:
You're seeing a tiny node server, serving static index.html and angular files, and a POST route receiving a PDF in base64 as delivered by the HTML FileReader API, and saves it to disk.
Instead of saving to disk, you can send it as an email attachment. See for instance here or here for some info on that.
The example below assumes uploading a PDF by a user through a file input, but the idea is the same for all other ways of sending a document to your back end system. The most important thing is to send the pdf data as BASE64, because this is the format that most file writers and email packages use (as opposed to straight up binary for instance..). This also goes for images, documents etc.
How did I do that:
In your HTML:
<div pdfs>Your browser doesn't support File API.</div>
A directive called pdfs:
myApp.directive('pdfs', ['upload', function(upload) {
return {
replace: true,
scope: function() {
files = null;
},
template: '<input id="files" type="file">',
link: function(scope,element) {
element.bind('change', function(evt) {
scope.$apply(function() {
scope.files = evt.target.files;
});
});
},
controller: function($scope, $attrs) {
$scope.$watch('files', function(files) {
//upload.put(files)
if(typeof files !== 'undefined' && files.length > 0) {
for(var i = 0; i<files.length;i++) {
readFile(files[i])
}
}
}, true);
function readFile(file) {
var reader = new FileReader();
reader.addEventListener("loadend", function(evt) {
upload.post({name: file.name, data: reader.result})
})
if(reader.type = 'application/pdf') {
reader.readAsDataURL(file);
}
}
}
}
}]);
A tiny service:
myApp.service('upload', function($http) {
this.post = function(file) {
$http.post('/pdf', file);
}
});
And a node server:
var express = require('express');
var bodyParser = require('body-parser')
var fs = require("fs");
var app = express();
app.use(express.static('.'));
app.use( bodyParser.json({limit: '1mb'}) );
app.post('/pdf', function(req, res){
var name = req.body.name;
var pdf = req.body.data;
var pdf = pdf.replace('data:application/pdf;base64,', '');
res.send('received');
fs.writeFile(name, pdf, 'base64', function(err) {
console.log(err);
});
});
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});

Resources