How to send message with websocket - node.js

I'm trying to send messages in WebSocket to TypeScript. I created a Socket service with two functions: .onMessage () and .emit (message):
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Message } from '../message/message.model';
import * as socketIo from 'socket.io-client';
const SERVER_URL = 'http://localhost:8080';
#Injectable()
export class SocketService {
private socket;
public initSocket(): void {
this.socket = socketIo(SERVER_URL);
}
public send(message?: Message): void {
this.socket.emit('displayHello', { to: 'flo#NXS_DEV_FLO2', from: 'flo#NXS_DEV_FLO2', name: 'displayHello'})
}
public onMessage(): Observable<any> {
return new Observable(observer => {
this.socket.on('message', (data) => {
observer.next(data);
});
});
}
}
My .onMessage function does not display any errors. While my .emit (message) function displays this as an error:
ERROR TypeError: Cannot read property 'emit' of undefined
Is that how it should be done?
///////////////EDIT///////////////////
I call my functions like this :
addNewMessage(newMessage: Message): void {
this.socketService.initSocket();
this.socketService.onMessage();
this.socketService.send(newMessage);
}
/////////////EDIT 2 ////////////////////
When I test this in my console, it works :
public onMessage(): Observable<any> {
return new Observable(observer => {
this.socket.on('displayHello', function(data) {
$.pnotify({
title: 'Hello',
text: data.from + ' te dis bonjour ' + data.to,
type: 'info'
});
});
}
But I have this error when I tried to put in my code :
Property 'pnotify' does not exist on type '(search: string) => ElementFinder'

Related

Issue with testing middleware in a nodejs express typescript project

I have been following https://github.com/goldbergyoni/nodebestpractices to learn more about nodejs best practices.
I have the following middleware that I implemented:
import { NextFunction, Request, Response } from "express";
import { BAD_REQUEST_ERROR_TYPES } from "../constants";
import { BadRequestError } from "./../error-handling";
const isAccountActive = (req: Request, res: Response, next: NextFunction) => {
if (req.session?.user?.isEmailVerified) {
next();
} else {
next(new BadRequestError(BAD_REQUEST_ERROR_TYPES.ACCOUNT_NOT_ACTIVE));
}
};
export default isAccountActive;
This is the test that I wrote for it:
describe("isAccountActive Middleware", () => {
describe("Recieving a request", () => {
test("When the request has a userUUID set in the session, it calls the next function without throwing a Bad Request Account Not Active error", async () => {
// Arrange
const req = {
method: "GET",
url: "/user/42",
session: {
user: {
userUUID: "some-string",
},
},
} as unknown as Request;
const res = jest.fn as unknown as Response;
const next = jest.fn;
// Act
await isAccountActive(req, res, next);
// Assert
expect(next).toBeCalledTimes(1);
expect(next).toBeCalledWith(
new BadRequestError(BAD_REQUEST_ERROR_TYPES.ACCOUNT_NOT_ACTIVE)
);
});
});
});
That is implementation number 3 for that test. I also tried using sinon, and node-mocks-http.
When I run the test command, I get the following error regardless of any implementation:
My app builds and runs fine; so I am not quite sure why jest would be throwing this error when the actuall server code itself is being compiled and run without any issues.
For reference, my config.ts:
import { isFullRedisURL } from "./helpers";
import { z } from "zod";
import { REDIS_URL_ERROR } from "./constants";
import { StartupError } from "./error-handling";
const input = {
environment: process.env.NODE_ENV,
basePort: process.env.BASE_PORT,
redisUrl: process.env.REDIS_URL,
redisPassword: process.env.REDIS_PASSWORD,
databaseUrl: process.env.DATABASE_URL,
sessionSecret: process.env.SESSION_SECRET,
};
const configSchema = z.object({
environment: z.string(),
basePort: z.coerce.number().positive().int(),
redisUrl: z
.string()
.refine((val) => isFullRedisURL(val), { message: REDIS_URL_ERROR }),
redisPassword: z.string(),
databaseUrl: z.string(),
sessionSecret: z.string().min(8),
});
let parsedInput;
try {
parsedInput = configSchema.parse(input);
} catch (e) {
throw new StartupError("Config validation error", e);
}
export const config = parsedInput;
export type Config = z.infer<typeof configSchema>;
my error-handling/error-handling-middleware.ts
import { COMMON_ERRORS, STATUS_CODES } from "../constants";
import { NextFunction, Request, Response } from "express";
import errorHandler from "./errorHandler";
import { config } from "../config";
const errorHandlingMiddleware = async (
// eslint-disable-next-line #typescript-eslint/no-explicit-any
error: any,
req: Request,
res: Response,
next: NextFunction
) => {
if (error && typeof error === "object") {
if (error.isTrusted === undefined || error.isTrusted === null) {
error.isTrusted = true; // Error during a specific request is usually not fatal and should not lead to process exit
}
}
errorHandler.handleError(error);
const { environment } = config;
const result = {
status: error?.httpStatus || STATUS_CODES.InternalServerError,
name: error?.name || COMMON_ERRORS.InternalServerError,
message: error?.message || "Sorry, something went wrong.",
details: error?.details,
stacktrace: environment === "development" ? error?.stacktrace : undefined,
};
res
.status(error?.httpStatus || STATUS_CODES.InternalServerError)
.send(result);
};
export default errorHandlingMiddleware;
the StartupError class:
import { FieldError } from "__shared/types";
import {
COMMON_ERRORS,
BAD_REQUEST_ERROR_MESSAGES,
BAD_REQUEST_ERROR_TYPES,
STATUS_CODES,
} from "../constants";
export class ApplicationError extends Error {
constructor(
public name: string,
public message: string,
public httpStatus: STATUS_CODES = STATUS_CODES.InternalServerError,
public isTrusted: boolean = true,
public isOperational: boolean = true,
public details?: FieldError[],
public stacktrace?: unknown
) {
super(message); // 'Error' breaks prototype chain here
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
this.name = name;
this.httpStatus = httpStatus;
this.isOperational = isOperational;
this.isTrusted = isTrusted;
this.details = details;
this.stacktrace = stacktrace;
Error.captureStackTrace(this, this.constructor);
}
}
export class BadRequestError extends ApplicationError {
constructor(type: keyof typeof BAD_REQUEST_ERROR_TYPES) {
super(
COMMON_ERRORS.BadRequestError,
BAD_REQUEST_ERROR_MESSAGES[type],
STATUS_CODES.BadRequest,
true,
true
);
}
}
export class StartupError extends ApplicationError {
constructor(reason: string, error: unknown) {
super(
COMMON_ERRORS.StartupError,
`Start up failed: (${reason}) `,
STATUS_CODES.InternalServerError,
false,
true,
undefined,
error
);
}
}

How to add normal modules to Nuxt 3

I am writing a web application in Nuxt 3 and want to integrate a SockJs over Stomp websocket. Now I have written the component...
<template>
<div>
<h1>Test</h1>
</div>
</template>
<script lang="ts" setup>
import {useRouter, ref} from "#imports";
import SockJS from "sockjs-client";
import {Stomp} from "#stomp/stompjs";
let consoleText = ref<string>("");
interface ConsoleMessage {
messageContent: string,
user: string,
}
const props = defineProps<{
originURL:string,
publicURL:string,
privateURL:string
}>();
let stompClient: any = null;
function connect() {
const socket = new SockJS(props.originURL);
stompClient = Stomp.over(socket);
stompClient.connect({}, function () {
stompClient.subscribe(props.publicURL, function (message: { body: string; }) {
showMessage(JSON.parse(message.body));
});
stompClient.subscribe(props.privateURL, function (message: { body: string; }) {
showMessage(JSON.parse(message.body));
});
});
}
function showMessage(message: ConsoleMessage) {
consoleText.value = "\n" + message.messageContent + "\n";
}
connect();
</script>
I have imported #stomp/stompjs and sockjs-client via yarn add. What do I have to change in the nuxt.config.ts to make it so that the application loads the modules.
Edit:
Following the advice given by nur_iyad I attempted to write a plugin
import {defineNuxtPlugin} from "nuxt/app";
import SockJS from "sockjs-client";
import {CompatClient, Stomp} from "#stomp/stompjs";
import {Message} from "~/composables/display";
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.provide('sock', () => new NuxtSockJs())
})
declare module '#app' {
interface NuxtApp {
$sock (): NuxtSockJs
}
}
export class NuxtSockJs {
stompInstance: CompatClient | null;
constructor() {
this.stompInstance = null;
}
connect(originURL:string,
subscribeURLs:Array<string>,
displayMessage: (message: Message) => any): void {
const socket = new SockJS(originURL);
let stompClient: CompatClient = Stomp.over(socket);
stompClient.connect({}, function () {
for(const subscribeURL of subscribeURLs) {
stompClient.subscribe(subscribeURL, function (message: { body: string; }) {
displayMessage(JSON.parse(message.body));
});
}
});
this.stompInstance = stompClient;
}
sendMessage(sendURL: string, message: Message):void {
(this.stompInstance)!.send(sendURL, {}, JSON.stringify(message));
}
}
This does not work and just throws the following error:
Uncaught ReferenceError: global is not defined
at node_modules/sockjs-client/lib/utils/browser-crypto.js

Angular: How do I get data from an event inside a service to my component?

I am currently writing an angular project that opens a websocket connection with a NodeJS server. This is the service:
export class WebsocketService {
socket : any;
constructor() { }
setupSocketConnection(){
this.socket = io(environment.SOCKET_ENDPOINT);
this.socket.emit('message', 'The client wants to intruduce itself to the server');
this.socket.on('broadcast', (data: string) => {
console.log(data);
});
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
}
}
}
and this is my component:
export class AppComponent {
title = '-';
constructor(private websocket : WebsocketService) { }
ngOnInit(){
this.websocket.setupSocketConnection();
}
ngOnDestroy() {
this.websocket.disconnect();
}
}
My question is: how can I pass "data" from the broadcast event listener into the component to display it there? Another service would be a solution, but I dont think it would be a good one. I could also put the listener into a function and call it from the component, but wouldn't that violate the encapsulation concepts of services?
Thank you
You could use BehaviorSubject by following theses steps:
Imagine sending JSON object holding a "type" field: Make sure to stringify data sent using
1- Server side:
JSON.stringify({type: "message", value: "whatever"})
2- Now client side
export class WebsocketService {
// Put the right data type here
message = new BehaviorSubject<string>('');
connection = new BehaviorSubject<string>('');
socket : any;
constructor() { }
setupSocketConnection(){
this.socket = io(environment.SOCKET_ENDPOINT);
this.socket.emit('message', 'The client wants to intruduce itself to the server');
this.socket.on('broadcast', (data: string) => {
const jsonObject = JSON.parse(data);
switch (jsonObject.type) {
case "message":
this.message.next(jsonObject.value);
break;
case "connection":
this.connection.next(jsonObject.value);
break;
default:
throw new Error('Unknown message type' + jsonObject.type)
break;
}
});
}
disconnect() {
if (this.socket) {
this.socket.disconnect();
}
}
}
And on there other hand, just subscribe to your data behaviorSubject emited values.
export class AppComponent implements OnInit, OnDestroy {
title = '-';
subscriptions: Subscription[] = [];
constructor(private websocket : WebsocketService) { }
ngOnInit(){
this.websocket.setupSocketConnection();
this.websocket.message.subscribe(value => {
// Do your stuff here.
console.log(value);
})
this.websocket.connection.subscribe(value => {
// Do your stuff here.
console.log(value);
})
}
ngOnDestroy() {
this.websocket.disconnect();
this.subscriptions.forEach(s => s.unsubscribe());
this.subscription = [];
}
}

How to receive data from client using socket.io events inside express controller?

I am using Express framework with TypeScript:
I want to get client location at login using Socket IO, so on the client side, i made an emit event on login page. On the server side i tried to integrate a socket event into the login controller, which is placed before the response is sent back to the client, but the event from the server fires up just after i'm already signed in and clicked on sign out button which leads me to login page again (this is the moment when i receive the location message on server side from console.log(data)).
This is how the authentication controller file (auth.controller.ts) looks like:
import { io } from '../server/https';
....
..
public authenticate(req: Request, res: Response) {
if(req.body.email && req.body.password) {
....
..
io.on('connection', (socket: SocketIO.Socket) => {
console.log('New Socket Connected!');
socket.on('client-location', (data) => {
console.log(data);
});
socket.on('disconnect', (reason) => {
console.log(reason);
});
});
res.status(200).json({
resType: 'success',
token: 'Bearer ' + token,
userId: user.id,
message: 'Authenticated.',
loginNumber: result.session.length
});
};
....
..
}
The { io } import in the auth.controller.ts is the exported io constant from https.ts file.
Below is the https.ts file:
import socketIO from 'socket.io';
import HTTPS from 'https';
import app from '../app/index';
....
..
const options = {
cert: fs.readFileSync(path.resolve('../../../certificates/ca.crt')),
key: fs.readFileSync(path.resolve('../../../certificates/ca.key'))
}
export const server = HTTPS.createServer(options, app)
.listen(process.env.HTTP_SERVER_PORT, () => {
console.log(`Platform Server is running from
${process.env.HTTP_SERVER_HOST}, port: ${process.env.HTTP_SERVER_PORT}`);
});
export const io = socketIO.listen(server);
Something is not synchronized and i don't get it.
The client side is wrote in Angular 6 and the component from where the event is emited is below:
import { Component, OnInit } from '#angular/core';
import { LoginService } from '../../services/login.service';
import { Router } from '#angular/router';
import * as io from 'socket.io-client';
import { env } from '../../../../../environments/environment';
#Component({
selector: 'login-form',
templateUrl: './login-form.component.html',
styleUrls: ['./login-form.component.css']
})
export class LoginFormComponent implements OnInit {
public positionOptions = {
enableHighAccuracy: true,
maximumAge: 0
};
public LoginFormModel: any = {};
private URL = `${env.WS_SERVER_URL}:${env.HTTP_SERVER_PORT}`;
private socket;
constructor(private loginController: LoginService,
private router: Router) {
this.socket = io(this.URL);
}
ngOnInit() {}
public emitLocation() {
navigator.geolocation.getCurrentPosition(position => {
const { latitude: lat, longitude: lng } = position.coords;
console.log({ lat, lng });
this.socket.emit('client-location', { lat, lng });
},
err => {
console.log(err);
}, this.positionOptions);
}
loginEvent() {
this.loginController.login(this.LoginFormModel.email.value,
this.LoginFormModel.password.value)
.subscribe(
result => {
this.loginController.createSession('token', result.token);
this.emitLocation();
this.router.navigate(['user/profile']);
},
err => {
console.log(err);
});
}
}

Cannot read property 'messages' of undefined

I am getting the following error when returning from a http service and attempting to push to response onto an array :
Cannot read property 'messages' of undefined
This is my chat.component.ts file :
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ChatService } from './chat.service';
#Component({
selector: 'chat-component',
template: `
<div *ngIf="messages">
<div *ngFor="let message of messages">
{{message.text}}
</div>
</div>
<input [(ngModel)]="message" /><button (click)="sendMessage()">Send</button>
`,
providers: [ChatService]
})
export class ChatComponent implements OnInit, OnDestroy {
messages = [];
connection;
message;
loading;
constructor(private chatService: ChatService) { }
sendMessage() {
this.chatService.sendMessage(this.message);
this.message = '';
}
ngOnInit() {
this.chatService.initPlaylist().subscribe(tracks => {
tracks.forEach(function(item) {
this.messages.push({
message: item.trackID,
type: "new-message"
});
});
})
this.connection = this.chatService.getMessages().subscribe(message => {
this.messages.push(message);
})
}
ngOnDestroy() {
this.connection.unsubscribe();
}
}
This is my chat.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Rx';
import * as io from 'socket.io-client';
#Injectable()
export class ChatService {
private url = 'http://localhost:1337';
private socket;
constructor(private http: Http) {
}
sendMessage(message) {
this.socket.emit('add-message', message);
}
initPlaylist() {
return this.http.get(this.url + '/playlist')
.map(this.extratData)
.catch(this.handleError);
}
getMessages() {
let observable = new Observable(observer => {
this.socket = io(this.url);
this.socket.on('message', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
})
return observable;
}
private extratData(res: Response) {
let body = res.json();
return body || {};
}
private handleError(error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
I currently have a form on the front end, in which users can add a message, this is then pushed onto this.messages and through socket.io sent out to all connected sockets.
What I am now doing is storing messages in a mongodb via an express app using mongoose.
On page load, I would like to retrieve these messages from the document store, and push them onto this.messages - so the view is updated with previous messages, then socket.io should take over on new messages, adding them to the array.
As this is an initial call, once on load, I am not using socket.io to grab these, instead I have an api route setup through express, returning json that looks as follows :
[
{
"_id": "58109b3e868f7a1dc8346105",
"trackID": "This is my message...",
"__v": 0,
"status": 0,
"meta": {
"played": null,
"requested": "2016-10-26T12:02:06.979Z"
}
}
]
However when I get to this section of code within chat.component.ts, everything breaks down with the previously mentioned error..
this.chatService.initPlaylist().subscribe(tracks => {
tracks.forEach(function(item) {
this.messages.push({
message: item.trackID,
type: "new-message"
});
});
})
I using Angular 2, Socket.io, ExpressJS and MongoDB.
don't use function () use instead () => (arrow function) for this.... to keep pointing to the local class instance
tracks.forEach((item) => {
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Resources