Bidirectional WebSockets in laravel? - node.js

Anyone here familiar with bi-directional WebSockets in laravel?
Please note I am not talking about pusher+echo. pusher+echo cannot make a request to the server.
I have experience in express.js in which the socket can listen at server side.
I am looking for a similar feature.. any suggestion or help is greatly appreciated

Have you tried the laravel events method along with socketIO (as discussed in https://laracasts.com/discuss/channels/general-discussion/step-by-step-guide-to-installing-socketio-and-broadcasting-events-with-laravel-51)
In this method you could emit messages back to socket server, or you could initiate an XHR request to directly interact with the backend server (server with laravel).

I use Laravel Broadcasting with Laravel Websockets and Laravel Echo for server to client communication while using the code below for client to server communication using websockets api. Big thanks to Acadea.io. Please refer to the following:
BaseSocketHandler.php
namespace App\Websockets\SocketHandler;
use BeyondCode\LaravelWebSockets\Apps\App;
use BeyondCode\LaravelWebSockets\QueryParameters;
use BeyondCode\LaravelWebSockets\WebSockets\Exceptions\UnknownAppKey;
use Ratchet\ConnectionInterface;
use Ratchet\WebSocket\MessageComponentInterface;
abstract class BaseSocketHandler implements MessageComponentInterface
{
protected function verifyAppKey(ConnectionInterface $connection)
{
$appKey = QueryParameters::create($connection->httpRequest)->get('appKey');
if (!$app = App::findByKey($appKey)) {
throw new UnknownAppKey($appKey);
}
$connection->app = $app;
return $this;
}
protected function generateSocketId(ConnectionInterface $connection)
{
$socketId = sprintf('%d.%d', random_int(1, 1000000000), random_int(1, 1000000000));
$connection->socketId = $socketId;
return $this;
}
function onOpen(ConnectionInterface $conn)
{
dump('on opened');
$this->verifyAppKey($conn)->generateSocketId($conn);
}
function onClose(ConnectionInterface $conn)
{
dump('closed');
}
function onError(ConnectionInterface $conn, \Exception $e)
{
dump($e);
dump('onerror');
}
}
CustomSocketHandler
namespace App\Websockets\SocketHandler;
use Ratchet\ConnectionInterface;
use Ratchet\RFC6455\Messaging\MessageInterface;
use Ratchet\WebSocket\MessageComponentInterface;
class CustomSocketHandler extends BaseSocketHandler implements MessageComponentInterface
{
public function onOpen(ConnectionInterface $connection)
{
// TODO: Implement onOpen() method.
}
public function onClose(ConnectionInterface $connection)
{
// TODO: Implement onClose() method.
}
public function onError(ConnectionInterface $connection, \Exception $e)
{
// TODO: Implement onError() method.
}
public function onMessage(ConnectionInterface $connection, MessageInterface $msg)
{
$body = json_decode(collect(json_decode($msg->getPayload(), true)), true);
$access_token = $body["access_token"];
$authenticateUserByAccessToken = new AuthenticateUserByAccessToken();
$chat_user = $authenticateUserByAccessToken->getAuthenticatedUser($access_token);
// TODO: Implement onMessage() method.
$dataToBeReturned = json_encode($dataToBeReturned);
$connection->send($dataToBeReturned);
}
web
use App\Websockets\SocketHandler\CustomSocketHandler;
use BeyondCode\LaravelWebSockets\Facades\WebSocketsRouter;
WebSocketsRouter::webSocket('/socket/custom-socket-url', CustomSocketHandler::class);
JS
function functionName(access_token, id) {
const socket = new WebSocket(
`wss://${window.location.hostname}:${process.env.MIX_LARAVEL_WEBSOCKETS_PORT}/socket/custom-socket-url?appKey=${process.env.MIX_PUSHER_APP_KEY}`
);
socket.onopen = function (event) {
// console.log("on open", event);
};
socket.onclose = function (event) {
// console.log("on close", event);
};
socket.onmessage = function (event) {
// console.log("on message", event);
};
socket.send(
JSON.stringify({
access_token: access_token, //optional JWT Auth Token
payload: {
id: id,
},
})
);
socket.close();
}
Optional. If you want to authenticate your websocket connection (using jwt)
namespace App\Websockets\SocketHandler;
use App\Models\AuthenticationModelToBeUsed;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Tymon\JWTAuth\Facades\JWTAuth;
class AuthenticateUserByAccessToken
{
public function getAuthenticatedUser($access_token)
{
Config::set('auth.providers.users.model', AuthenticationModelToBeUsed::class);
try {
JWTAuth::setToken($access_token);
if (!$user = JWTAuth::toUser($access_token)) {
return response()->json(array('message' => 'user_not_found'), 404);
}
} catch (\Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
return response()->json(array('message' => 'token_expired'), 404);
} catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
return response()->json(array('message' => 'token_invalid'), 404);
} catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
return response()->json(array('message' => 'token_absent'), 404);
}
Log::info($user);
return $user;
}
}

Related

Nestjs | grpc : How to handle remote error from client side

Remote Server
#Catch(RpcException)
export class RpcExceptionHandler implements RpcExceptionFilter<RpcException> {
catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
return throwError(exception.getError());
}
}
#UseFilters(new RpcExceptionHandler())
#GrpcMethod('AppController', 'Accumulate')
async accumulate(numberArray: INumberArray, metadata: any): Promise<ISumOfNumberArray> {
throw new RpcException({
code: 5,
message: 'Data Not Found'
})
}
Client code
#Get('add')
async getSumc(#Query('data') data: number[]) {
try {
let ata = await this.grpcService.accumulate({ data });
return ata;
} catch (err) {
//logic here if error comes
return err;
}
}
Proto defination.
syntax = "proto3";
package app;
// Declare a service for each controller you have
service AppController {
// Declare an rpc for each method that is called via gRPC
rpc Accumulate (NumberArray) returns (SumOfNumberArray);
}
// Declare the types used above
message NumberArray {
repeated double data = 1;
}
message SumOfNumberArray {
double sum = 1;
}
If error comes it is not going to catch block, just showing the server error.
I want to catch the error if remote throwing any error.
Try this one:
#Get('add')
async getSumc(#Query('data') data: number[]) {
try {
let ata = await this.grpcService.accumulate({ data }).toPromise();
return ata;
} catch (e) {
throw new RpcException(e);
}
}
Example here

Can I make API calls to my Node server that is connected through Web Socket with my ReactJS?

I have front-end in ReactJS where I am integrating my backend in NodeJS through websocket(also using sharedb).
https://github.com/share/sharedb
My setup for this is below:
import sharedb from 'sharedb/lib/client';
// Using a Reconnecting Web Socket https://github.com/share/sharedb/pull/138/files
import { default as WebSocket } from 'reconnecting-websocket';
export default class ShareService {
constructor(location) {
this.socket = new WebSocket(location);
// Open WebSocket connection to ShareDB server
const connection = new sharedb.Connection(this.socket);
this.connection = connection;
this.location = location;
}
close() {
this.connection.close();
}
reconnect() {
this.socket = new WebSocket(this.location);
this.connection.bindToSocket(this.socket);
}
doc(collection, id) {
const doc = this.connection.get(collection, id);
return doc;
}
sendCommand(data) {
this.socket.send(JSON.stringify({
'a': 'command',
'deviceType': 'web',
...data
}));
}
}
And when I need to call from React I do:
this.shareService.sendCommand({
command: 'I_AM_COMMAND',
payload: {
activeAgendaId: 123,
}
});
I want to know if I can also make normal API calls to my Node from React even after WebSocket connection is there?

Is it possible to override the DEFAULT_TEARDOWN function inside exceptions-zone.ts?

I'm trying to create an application with NestJS framework and I'd like to check if a specific Service is in the application context but, the framework default behavior is exiting the process when it doesn't find the Service inside the context.
I've surrounded the get method with try...catch but it doesn't work because of the process.exit(1) execution.
private async loadConfigService(server: INestApplication): Promise<void> {
try {
this.configService = await server.get<ConfigService>(ConfigService, { strict: false });
} catch (error) {
this.logger.debug('Server does not have a config module loaded. Loading defaults...');
}
this.port = this.configService ? this.configService.port : DEFAULT_PORT;
this.environment = this.configService ? this.configService.environment : Environment[process.env.NODE_ENV] || Environment.DEVELOPMENT;
this.isProduction = this.configService ? this.configService.isProduction : false;
}
I'd like to catch the exception to manage the result instead of exiting the process.
Here's what I came up with:
import { NestFactory } from '#nestjs/core';
export class CustomNestFactory {
constructor() {}
public static create(module, serverOrOptions, options) {
const ob = NestFactory as any;
ob.__proto__.createExceptionZone = (receiver, prop) => {
return (...args) => {
const result = receiver[prop](...args);
return result;
};
};
return NestFactory.create(module, serverOrOptions, options);
}
}
Now, instead of using the default NestFactory I can use my CustomNestFactory
Dirty, but it works
Here is my code to solve same issue:
import { ExceptiinsZone } from '#nestjs/core/errors/exceptions-zone';
ExceptionsZone.run = callback => {
try {
callback();
} catch (e) {
ExceptionsZone.exceptionHandler.handle(e);
throw e;
}
};
You may need to override asyncRun() also for other method.

Puppeteer mock page request object

import { Page } from 'puppeteer/lib/Page';
export class MonitorRequestHelper {
public static monitorRequests(page: Page, on = false) {
if(on) {
page.on('request', req => {
if (['image', 'font', 'stylesheet'].includes(req.resourceType())) {
// Abort requests for images, fonts & stylesheets to increase page load speed.
req.abort();
} else {
req.continue();
}
});
} else {
return true;
}
}
}
I am trying to mock and spy the function to check if it got called at least once.
Also, it would be helpful if some explain me how to mock and spy event-emitter object.
The source code is available on https://github.com/Mukesh23singh/puppeteer-unit-testing
If you want to test that your logic in monitorRequests works, you need to pass in a fake Page object with an event emitter interface that produces a fake request that you can test on.
Something like:
import {spy} from 'sinon';
// Arrange
const fakePage = { on(type, cb) { this[type] = cb; } }; // "event emitter"
const fakeRequest = {
abort: sinon.spy(),
resourceType() { return 'image'; }
};
monitorRequests( fakePage, true );
// Act
// trigger fake request
fakePage['request'](fakeRequest);
// Assert
assert(fakeRequest.abort.called);

Angular 5 socket.io not recieving update in other component

Context: I have an angular application with a backend in nodejs. I have a feed that will update when I recieve a message from the server. When new data is inserted the server is notified, but my other component does not recieve anything. I have implemented the socket in a service that is injected into both components.
My server is build like this:
const port = 3000;
const server = require('http').Server(app);
const io = require('socket.io')(server);
io.on('connection', (socket) => {
console.log('New Connection..')
socket.on('action', (data) => {
switch(data) {
case 'new_odds':
socket.emit('refresh_odds', 'UPDATE FEED! (FROM SERVER)')
break;
case 'new_results':
break;
}
});
});
//listen on port omitted
My service in angular:
const SERVER_URL = 'http://localhost:3000';
#Injectable()
export class SocketService {
constructor() {
}
private socket;
public initSocket(): void {
this.socket = socketIo(SERVER_URL);
}
public disconnectSocket(): void {
this.socket.disconnect();
}
public send(action: Action): void {
this.socket.emit('action', action);
}
public onOddsMessage(): Observable<string> {
return new Observable<string>(observer => {
this.socket.on('refresh_odds', (data:string) => {
observer.next(data)
});
});
}
public onEvent(event: Event): Observable<any> {
return new Observable<Event>(observer => {
this.socket.on(event, () => observer.next());
});
}
}
My feed component uses the socket service to listen for emits:
constructor(private _socket : SocketService) {
}
ngOnInit() {
this.initIoConnection();
}
private initIoConnection(): void {
this._socket.initSocket();
this.ioConnection = this._socket.onOddsMessage()
.subscribe((data: string) => {
console.log('Recieved data from oddsMessage')
//this.loadBetFeed();
});
}
In a different component also using the service I'm trying to emit to the socket on the server. It does recieve the message on the server and emit a new message but my feed component does NOT pick up on this
testSocket() {
//NOTIFY SERVER THAT IT SHOULD TELL CLIENTS TO REFRESH
console.log('Test Socket Clicked')
this._socket.initSocket();
this._socket.send(Action.ODDS);
}
I don't understand what I'm doing wrong - I am using a shared service. Even if the components use different socket connections it shouldn't matter since they're listening for the same emits? I've tested in 2 browser tabs and also in incognito. Any help is appreciated!
The issue was I was emitting to the socket instead of the server which means only the current connection could see it.
New server:
io.on('connect', (socket) => {
console.log('Connected client on port %s.', port);
socket.on('action', (data) => {
io.emit('refresh_odds', 'UPDATE FEED! (FROM SERVER)') <-- changed socket to io
});
}

Resources