Implementing server sent events in FeatherJS - node.js

How do we implement server sent events in FeathersJS, normally for express based applications we have
app.get('/sse', (req, res, next) => {
res.status(200).set({
"Connection": "keep-alive",
"cache-control" : "no-cache",
"Content-Type" : "text/event-stream"
});
how to do the same in feathers JS any help would be appreciated

I got SSE running in a FeathersJS app - it works great, except that the app seems to hang while a SSE request is pending (listening on events):
import { createSession, createChannel } from 'better-sse';
import { IncomingMessage, ServerResponse } from 'http';
export default function (app: any): void {
const debugSseSource = require('debug')('sseSource');
const sseChannel = createChannel();
// Order creations
const orders: any = app.service('orders'); // use endpoint URL
app.get('/v1/admin/orders', async (req: IncomingMessage, res: ServerResponse) => {
// on new connections, add SSE session to SSE channel
const sseSession = await createSession(req, res);
sseChannel.register(sseSession);
debugSseSource('Client (orders) has joined.');
//res.end(); // #TODO/PROBLEM without the whole FeathersJS app hangs - but it stops the request...
});
orders.on('created', (order: any) => {
// broadcast to all clients
debugSseSource('order created');
sseChannel.broadcast(order, 'created');
});
};
Related FeathersJS GitHub issue: https://github.com/feathersjs/feathers/issues/369

Related

PayFast integration in NodeJS / ReactJS

I am trying to integrate PayFast into my React / NodeJS app. Using Express, my NodeJS successfully retrieves a payment uuid from the PayFast endpoint (I see this uuid in my console log) -
app.get("/api", async (req, res) => {
paymentData["signature"] = generateSignature(paymentData, phrase);
console.log(paymentData["signature"])
const str = dataToString(paymentData)
const id = await getPaymentId(str)
res.json({uuid: id})
})
However, in my front end (ReactJS) I am getting an undefined response & possible CORS issue from my backend API end point when trying to retrieve this uuid -
My custom fetch hook:
export default function useFetch(baseUrl) {
const [loading, setLoading] = useState(true);
function get() {
return new Promise((resolve, reject) => {
fetch(baseUrl)
.then(res => {
console.log(res)
res.json()
})
.then(data => {
console.log(data);
if (!data) {
setLoading(false);
return reject(data);
}
setLoading(false);
resolve(data);
})
.catch(error => {
setLoading(false);
reject(error);
});
});
}
return { get, loading };
};
The error:
Response {type: 'cors', url: 'http://localhost:3001/api', redirected: false, status: 200, ok: true, …}
undefined
If I test my NodeJS end point from my browser, it successfully comes back with my payment uuid. Any one have any ideas why my React app is acting up?
Update your CORS config to accept connections from the React app host.
app.use(cors({
origin: 'http://localhost:3000',
}));
Open package.json of your react app and add a line on the bottom of the json file:
"proxy":"http://localhost:3001"
3001 is the PORT that your Node http server is running on locally, if it's another PORT just change it accordingly.
This will redirect all http traffic from your webpack dev server running on PORT 3000, to your Node server running on 3001.
For those others who might encounter a similar type of an issue, I have attached a blog post with the method that I have used to solve the CORS issue, as well as integrate with the PayFast API.
https://codersconcepts.blogspot.com/2022/04/nodejs-payfast-integration.html

Axios post request renders entire file contents as response instead of executing serverside file

I am trying to send gmail using nodemmailer and axios in react application. But the response i get is the entire file contents. Any help is greatly appreciated. Thank you.
Here is my frontend axios post request. THis request is working fine its returing 200 status code. The problem is with the response.
const axios = require('axios');
axios({
method: "POST",
url:"https://localhost/mail",
data: {
recepients: recepients,
msgbody: msgbody,
msgsubject: msgsubject
}
}).then((response)=>{
if (response.data.msg === 'success'){
console.log("Message Sent.");
// this.resetForm()
}else if(response.data.msg === 'fail'){
console.log("Message failed to send.")
}
});
This is my mail.js file.
const express = require('express')
const app = express();
var nodemailer = require('nodemailer');
var transport = {
host: 'smtp.gmail.com',
port: 587,
domain:'gmail.com',
secure:false,
auth: {
user: 'info#gmail.com',
pass: 'password'
}
}
var transporter = nodemailer.createTransport(transport)
transporter.verify((error, success) => {
if (error) {
console.log(error);
} else {
console.log('Server is ready to take messages');
}
});
app.post('/mail', (req, res, next) => {
var name = "sender";
var email = req.body.email;
var message = req.body.message;
var mail = {
from: name,
to: 'sender#gmail.com', //Change to email address that you want to receive messages on
subject: 'New Message from Contact Form',
text: 'hi'
}
transporter.sendMail(mail, (err, data) => {
if (err) {
res.json({msg: 'fail'});
} else {
res.json({msg: 'success'});
}
}
)
});
This file does not get executed. This same file content i get as response. Anyone please help with this problem. Thanks in advance.
This is my app.js file.
/* application specific logic */
import 'jquery';
import 'jquery-contextmenu';
import 'jQuery-Impromptu';
import conference from './conference';
import API from './modules/API';
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
import remoteControl from './modules/remotecontrol/RemoteControl';
import translation from './modules/translation/translation';
import UI from './modules/UI/UI';
import axios from 'axios';
window.APP = {
API,
conference,
// Used by do_external_connect.js if we receive the attach data after
// connect was already executed. status property can be 'initialized',
// 'ready', or 'connecting'. We are interested in 'ready' status only which
// means that connect was executed but we have to wait for the attach data.
// In status 'ready' handler property will be set to a function that will
// finish the connect process when the attach data or error is received.
connect: {
handler: null,
status: 'initialized'
},
// Used for automated performance tests.
connectionTimes: {
'index.loaded': window.indexLoadedTime
},
keyboardshortcut,
remoteControl,
translation,
UI
};
// TODO The execution of the mobile app starts from react/index.native.js.
// Similarly, the execution of the Web app should start from react/index.web.js
// for the sake of consistency and ease of understanding. Temporarily though
// because we are at the beginning of introducing React into the Web app, allow
// the execution of the Web app to start from app.js in order to reduce the
// complexity of the beginning step.
import './react'
;
The issue is you have added mail.js file to public directory.
app.use('/static', express.static(path.join(__dirname, 'public')))
So what is happening is when ever you hit https://localhost/mail it is resolving any file matching in public and serving content.
Make sure you don't add it in public directory. or anyother directory which is used to add client side code.

Issue with sending SSE from express

We are using microservices architecture for our project. Our project is similar to a blog. There is an activities service, which logs all the activities done by the user, like, adding post, comment, replying to a comment and so on.
Now for each activity, we need to send an SSE notification to the involved users. For this, we are using another service called notifications. So whenever an activity occurs, an HTTP request will be sent to notifications service which handles sending various SSE events.
However, we are facing some issues in sending SSE. The two major issue we are facing are memory leak and an error Error: write after end
Route
router
.route("/")
.get(controller.eventStream)
.post(controller.sendNotification);
Controller
import axios from "axios";
import eventEmitter from '../services';
const controller = {
eventStream: (req, res, next) => {
console.log("Inside event stream");
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
});
eventEmitter.on("sse", (event, data) => {
console.log('Event Triggered');
res.write(`event: ${event}\ndata: ${JSON.stringify(data)} \n\n`);
});
req.on("close", () => {
console.log("Inside Close");
res.end();
});
},
sendNotification: (req, res, next) => {
try {
const {
userId,
action,
type,
item_id,
} = req.body;
// First check the type of activity that has been performed
switch (type) {
case "topic":
// Then check the type of action that has been done
switch (action) {
case "edit":
console.log("Topic edited");
const data= 'John Doe has edited a topic';
eventEmitter.emit("sse", `edit-topic`, data);
break;
}
break;
}
res.send('sse successfully send');
} catch (error) {
res.status(500).json({error: 'SSE failed'});
}
}
};
export default controller;
Service
export default new events.EventEmitter();
Initally client side will send a GET request which executes the eventStream controller.
Now for each activity, an SSE needs to be sent through this stream
I believe Error: write after end is because the event is triggered after sending a respose. This can be fixed by removing res.send('sse successfully send'); However, node will throw the timeout error.
I am not sure how to send an SSE from sendNotification controller. Moreover, I am not sure whether this is the right approach. Any guidence would be much appreciated.

How do you handle CORS in an electron app?

I'm building an electron app and need to call APIs where the API provider has not enabled CORS. The typically proposed solution is to use a reverse proxy which is trivial to do when running locally by using node and cors-anywhere like this:
let port = (process.argv.length > 2) ? parseInt (process.argv[2]) : 8080;
require ('cors-anywhere').createServer ().listen (port, 'localhost');
The app can then be configured to proxy all requests through the reverse proxy on localhost:8080.
So, my questions are:
Is it possible to use node and cors-anywhere in an electron app to create a reverse proxy? I don't want to force the app to make calls to a remote server.
Is there a better or standard way of doing this in an Electron app? I'm assuming I'm not the first to run into CORS issues. :)
Just overide header before send request using webRequest.onBeforeSendHeaders
const filter = {
urls: ['*://*.google.com/*']
};
const session = electron.remote.session
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
details.requestHeaders['Origin'] = null;
details.headers['Origin'] = null;
callback({ requestHeaders: details.requestHeaders })
});
put these codes in renderer process
In my application, it wasn't sufficient to remove the Origin header (by setting it to null) in the request. The server I was passing the request to always provided the Access-Control-Allow-Origin header in the response, regardless of it the Origin header is present in the request. So the embedded instance of Chrome did not like that the ACAO header did not match its understanding of the origin.
Instead, I had to change the Origin header on the request and then restore it on the Access-Control-Allow-Origin header on the response.
app.on('ready', () => {
// Modify the origin for all requests to the following urls.
const filter = {
urls: ['http://example.com/*']
};
session.defaultSession.webRequest.onBeforeSendHeaders(
filter,
(details, callback) => {
console.log(details);
details.requestHeaders['Origin'] = 'http://example.com';
callback({ requestHeaders: details.requestHeaders });
}
);
session.defaultSession.webRequest.onHeadersReceived(
filter,
(details, callback) => {
console.log(details);
details.responseHeaders['Access-Control-Allow-Origin'] = [
'capacitor-electron://-'
];
callback({ responseHeaders: details.responseHeaders });
}
);
myCapacitorApp.init();
});
Try this if you are running web apps in localhost
const filter = {
urls: ['http://example.com/*'] // Remote API URS for which you are getting CORS error
}
browserWindow.webContents.session.webRequest.onBeforeSendHeaders(
filter,
(details, callback) => {
details.requestHeaders.Origin = `http://example.com/*`
callback({ requestHeaders: details.requestHeaders })
}
)
browserWindow.webContents.session.webRequest.onHeadersReceived(
filter,
(details, callback) => {
details.responseHeaders['access-control-allow-origin'] = [
'capacitor-electron://-',
'http://localhost:3000' // URL your local electron app hosted
]
callback({ responseHeaders: details.responseHeaders })
}
)
Just had this issue today API calls with axios inside a React app bundled in Electron is returning 400
From what I can see Electron calls act as normal calls to the API urls meaning they are not affected by CORS.
Now when you wrap your calls with a CORS proxy and make a regular call to the proxy, it should error with a 400 error because it's not a CORS call.
This thread explains why cors-anywhere responds like that => https://github.com/Rob--W/cors-anywhere/issues/39
I actually removed my CORS proxies from the app before the Electron build. I still need the CORS proxy for development since I'm testing in the browser.
Hope this helps.
You can have the main process, the NodeJS server running Electron, send the request. This avoids CORS because this is a server-to-server request. You can send an event from the frontend (the render process) to the main process using IPC. In the main process you can listen to this event, send the HTTP request, and return a promise to the frontend.
In main.js (the script where the Electron window is created):
import { app, protocol, BrowserWindow, ipcMain } from ‘electron’
import axios from 'axios'
ipcMain.handle('auth', async (event, ...args) => {
console.log('main: auth', event, args) const result = await axios.post(
'https://api.com/auth',
{
username: args[0].username,
password: args[0].password,
auth_type: args[1],
},
) console.log('main: auth result', result)
console.log('main: auth result.data', result.data) return result.data
})
In your frontend JS:
import { ipcRenderer } from 'electron'
sendAuthRequestUsingIpc() {
return ipcRenderer.invoke('auth',
{
username: AuthService.username,
password: AuthService.password,
},
'password',
).then((data) => {
AuthService.AUTH_TOKEN = data['access_token']
return true
}).catch((resp) => console.warn(resp))
}
I wrote an article that goes into more depth here.
While I have struggled a while with the existing answers I will provide here the solution that finally worked for me, assuming that you are on the main process.
Here are the steps involved:
You need to have access to the session object which can be obtained by one of two ways:
A) via the global session.defaultSession which is available after the app is ready.
const { session } = require('electron');
const curSession = session.defaultSession;
B) The other method is via the session on the BrowserWindow, this assumes that the windnows is already created.
win = new BrowserWindow({});
const curSession = win.webContents.session;
Once you have the session object you set the response header to the site you are sending the request from.
For example, let's say your electron BrowserWindow is loaded from http://localhost:3000 and you are making a request to example.com, here would be some sample code:
const { app, BrowserWindow, session } = require('electron');
app.whenReady().then(_ => {
// If using method B for the session you should first construct the BrowserWindow
const filter = { urls: ['*://*.example.com/*'] };
session.defaultSession.webRequest.onHeadersReceived(filter, (details, callback) => {
details.responseHeaders['Access-Control-Allow-Origin'] = [ 'http://localhost:3000' ];
callback({ responseHeaders: details.responseHeaders });
}
// Construct the BrowserWindow if haven't done so yet...
});
Have you tried using fetch()
Check how to use fetch to make a no-cors request here
https://developers.google.com/web/updates/2015/03/introduction-to-fetch?hl=en

How to subscribe to stream in Angular2?

I'm having my streaming web-service running on localhost:8080/stream, which response when any new message added to subscribed mqtt stream. I want to consume this web-service in my Angular2 app. I'm using RxJS to consume NodeJS APIs in Angular2. I tried following code which calls localhost:8080/stream once and ends response. I want my observable to listen continuously to web-service.
var headers = new Headers();
headers.append('Access-Control-Allow-Origin', '*');
headers.append('Content-Type', 'application/json');
let options = new RequestOptions({ headers: headers }); // Create a request option
return this.http.get("http://localhost:8080/stream", options) // ...using post request
.map((res: Response) => res.json()) // ...and calling .json() on the response to return data
.catch((error: any) => Observable.throw(error.json().error));
If I understand your question right, you want to consume data from stream where new messages arrive at some period of time.
To achieve this You need add subscribe to the service.
return this.http.get("http://localhost:8080/stream", options) // ...using post request
.map((res: Response) => res.json()) // ...and calling .json() on the response to return data
.catch((error: any) => Observable.throw(error.json().error)
.subscribe(result => this.result =result));
Result will be updated as new data arrives, and you can use it the way want.
Note: It is best practice to make http calls separate in services and subscribe the service in your component.
For your reference I am adding an example I have worked on for demo purpose.
Create a service for http calls
#Injectable()
export class JsonPlaceHolderService{
constructor(private http:Http){}
getAllPosts():Observable<Post[]>{
return this.http.get("https://jsonplaceholder.typicode.com/posts")
.map(res=>res.json())
}
}
From your component call service and listen to changes continuously.
export class PostsComponent implements OnInit{
constructor(private _service:JsonPlaceHolderService){}
jphPosts:Post[];
title:string="JsonPlaceHolder's Post data";
ngOnInit():void{
this._service.getAllPosts().subscribe(
(data) =>this.jphPosts = data,
(err) => console.log(err),
()=>console.log("service call completed")
);
}
}
You should use websocket on angular and make it listen to your service URL after that you should listen to its events (open, close, message) and then create your own Subject stream using Rxjs to push the new data to the subscribers.
Please check the URL below:
https://medium.com/#lwojciechowski/websockets-with-angular2-and-rxjs-8b6c5be02fac
Streaming data from nodejs to angular with socket.io
This is something that would have been of great use when I was trying to do this. Following contains code from a socket.io package for angular credit goes to original author. This is taken from a working solution and may need some tweaking.
Server side
var app = express(),
http = require('http'),
ioServer = require('socket.io');
var httpServer = http.createServer(app);
var io = new ioServer();
httpServer.listen(1337, function(){
console.log('httpServer listening on port 1337');
});
io.attach(httpServer);
io.on('connection', function (socket){
console.log(Connected socket ' + socket.id);
});
//MQTT subscription
client.on('connect', function () {
client.subscribe(topic, function () {
console.log("subscribed to " + topic)
client.on('message', function (topic, msg, pkt) {
io.sockets.emit("message", {topic: topic, msg: msg, pkt: pkt});
});
});
});
Client Side
Create a customService in angular with following
import * as io from 'socket.io-client';
declare
private ioSocket: any;
private subscribersCounter = 0;
inside service class
constructor() {
this.ioSocket = io('socketUrl', {});
}
on(eventName: string, callback: Function) {
this.ioSocket.on(eventName, callback);
}
removeListener(eventName: string, callback?: Function) {
return this.ioSocket.removeListener.apply(this.ioSocket, arguments);
}
fromEvent<T>(eventName: string): Observable<T> {
this.subscribersCounter++;
return Observable.create((observer: any) => {
this.ioSocket.on(eventName, (data: T) => {
observer.next(data);
});
return () => {
if (this.subscribersCounter === 1) {
this.ioSocket.removeListener(eventName);
}
};
}).share();
}
In your component, Import customService as service
service.on("message", dat => {
console.log(dat);
});

Resources