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
Related
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 = [];
}
}
I am working on chat application, using socketIO
Whenever user signed in sucessfully, user is navigated to dashboard and list of current loggedin users will be displayed.
Whenever new user is signed in, existing user list is not getting updated.
Adding the necessary code here
events: backend
let verifyClaim = require("./tokenLib");
let socketio = require("socket.io");
let tokenLibs = require('./tokenLib');
let setService = (server) => {
let onlineUsers = [];
let io = socketio.listen(server);
let myio = io.of('')
myio.on('connection', (socket) => {
console.log(' emitting verify user');
socket.emit("verifyUser", "");
socket.on('set-user', (authToken) => {
console.log(authToken);
tokenLibs.verifyTokenWithoutSecret(authToken, (user, err,) => {
if (user) {
console.log(user);
let currentUser = user;
socket.userId = currentUser._id;
let fullName = `${currentUser.name}`
console.log(`${fullName} is online`);
socket.emit(currentUser._id, `${fullName} is online`)
let userObj = { userId: currentUser._id, name: fullName }
onlineUsers.push(userObj);
console.log(onlineUsers)
socket.emit('userlist', onlineUsers)
}
else {
socket.emit('auth-error', { status: 500, error: 'Please provide valid token ' })
}
})
})
socket.on('disconnect', () => {
console.log('user is disconnected');
let removeUserId = onlineUsers.map(function (user) { return user.userId }).indexOf(socket.userId)
onlineUsers.splice(removeUserId, 1)
console.log(onlineUsers)
})
})
}
module.exports = { setService: setService }
socket service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import * as io from 'socket.io-client';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class SocketService {
public prod = 'https://todolistbe.herokuapp.com/api/v1';
public dev = 'http://localhost:3001';
public baseUrl = this.dev;
private socket;
constructor(public http: HttpClient) {
this.socket=io('http://localhost:3001')
}
public verifyUser=()=>{
return Observable.create((observer)=>{
this.socket.on('verifyUser',(data)=>{
observer.next(data);
})
})
}
public setUser=(authToken)=>{
this.socket.emit("set-user",authToken)
}
public userList=()=>{
return Observable.create((observer)=>{
this.socket.on('userlist',(data)=>{
observer.next(data);
})
})
}
public welcomeUser=(userid)=>{
return Observable.create((observer)=>{
this.socket.on(userid,(data)=>{
observer.next(data);
})
})
}
public disconnectUser = () => {
return Observable.create((observer) => {
this.socket.on('disconnect', () => {
observer.next()
})
})
}
}
dashboard:
import { Component, OnInit } from '#angular/core';
import { ThemePalette } from '#angular/material/core';
import { SocketService } from '../../socket.service';
import { ToastrService } from 'ngx-toastr';
export interface Task {
name: string;
completed: boolean;
color: ThemePalette;
subtasks?: Task[];
}
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css'],
providers: [SocketService]
})
export class DashboardComponent implements OnInit {
public authToken: any = localStorage.getItem('authToken');
public userList: any = [];
public userNotification;
allComplete: boolean = false;
ngOnInit(): void {
this.verifyUserConfirmation();
this.getOnlineUsers();
}
public verifyUserConfirmation: any = () => {
this.SocketService.verifyUser().subscribe((data) => {
console.log(this.authToken)
this.SocketService.setUser(this.authToken);
this.getOnlineUsers();
})
}
selected = 'option2';
toggleNavbar() {
console.log('toggled' + this.isMenuOpened);
this.isMenuOpened = !this.isMenuOpened;
}
getOnlineUsers() {
// this.SocketService.welcomeUser(localStorage.getItem('id')).subscribe((data)=>{
// this.userNotification=data;
// console.log("hi:"+this.userNotification)
// })
this.SocketService.userList().subscribe((user) => {
this.userList = [];
for (let x in user) {
let tmp = { 'user': x, 'name': user[x] }
this.userList.push(tmp);
}
console.log(this.userList)
})
}
}
Whenever you want to emit an event with all other users, we should use myio.emit instead of socket.io.
Issue is resolved when i made necessary changed in my backend events library
I have written a server and wired up React with Redux, also made use of container-componenent-seperation. The first action fetchSnus() is successfully dispatched, the selectors also seem to work, but for some reason all the actions that I call after the first rendering, such as fetchSnu() (that only fetches ONE single snu-object) e.g. is not accessible for me in the store, meaning: after mapping state and dispatch to props not accessible for me under this.props. I really don't understand why!
You can see the whole project here: https://github.com/julibi/shonagon
This is my container ReadSingleSnuContainer.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchSnus, fetchSnu, setToRead, createSnu, getSnusMatchingKeyword } from '../../actions/index';
import { unreadSnus, randomFirstSnu } from '../../selectors/index';
import ReadSingleSnu from './ReadSingleSnu';
class ReadSingleSnuContainer extends Component {
componentWillMount() {
this.props.fetchSnus();
}
render() {
return (
<ReadSingleSnu { ...this.props } />
);
}
}
const mapStateToProps = (state) => {
return {
snus: state.snus,
randomFirstSnu: randomFirstSnu(state),
candidate: state.candidate
};
}
const mapDispatchToProps = (dispatch) => {
return {
fetchSnus: () => dispatch(fetchSnus()),
getSnusMatchingKeyword,
fetchSnu,
setToRead,
createSnu
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ReadSingleSnuContainer);
This is my component ReadSingleSnu.js:
import React, { Component } from 'react';
import style from './ReadSingleSnu.css'
import Typist from 'react-typist';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
export default class ReadSingleSnu extends Component {
constructor(props) {
super(props);
this.state = { showTitles: false };
}
renderRandomFirstSnu() {
const { randomFirstSnu } = this.props;
const { showTitles } = this.state;
// preselect a keyword
// dispatch the action that searches for other keywords (action I)
if(randomFirstSnu) {
return (
<div>
<h3><Typist cursor={ { show: false } }>{ randomFirstSnu.title }</Typist></h3>
<ReactCSSTransitionGroup
transitionName="snu"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<p>{ randomFirstSnu.text }</p>
</ReactCSSTransitionGroup>
{ !showTitles ? (
<div>
<button>Some other</button>
<button onClick={ () => this.handleDoneReading(randomFirstSnu) }>Done reading, next</button>
</div>
) : (
<ReactCSSTransitionGroup
transitionName="keywords"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<ul>{ randomFirstSnu.keywords.map((keyword, idx) =>
<li key={ idx }>
<button onClick={ () => this.fetchNextSnu(randomFirstSnu) }>
{ keyword }
</button>
</li>) }
</ul>
</ReactCSSTransitionGroup>
)
}
</div>
);
}
return <div>Loading ...</div>
}
handleDoneReading(snu) {
const { setToRead, getSnusMatchingKeyword } = this.props;
const id = snu._id;
if (snu.keywords.length > 0 && setToRead) {
// setToRead(id, snu);
this.setState({ showTitles: true });
const randomIndex = Math.floor(Math.random() * snu.keywords.length);
const randomKeyword = snu.keywords[randomIndex];
console.log('This is the randomKeyword :', randomKeyword);
getSnusMatchingKeyword(randomKeyword);
} else {
console.log('Here will soon be the select random next snu action :)');
}
}
render() {
console.log('ReadSingleSnu, this.props: ', this.props);
return (
<div className={style.App}>
<div>{ this.renderRandomFirstSnu() }</div>
</div>
);
}
}
This is my actions file:
import axios from 'axios';
export const FETCH_SNUS = 'FETCH_SNUS';
export const FETCH_SNU = 'FETCH_SNU';
export const SET_TO_READ = 'SET_TO_READ';
export const CREATE_SNU = 'CREATE_SNU';
export function getSnusMatchingKeyword(keyword) {
const request = axios.get(`/snus/keyword/${keyword}`);
return {
type: GET_SNUS_MATCHING_KEYWORD,
payload: request
};
}
export function fetchSnus() {
const request = axios.get('/snus');
return {
type: FETCH_SNUS,
payload: request
};
}
export function fetchSnu(id) {
const request = axios.get(`/snus/${id}`);
return {
type: FETCH_SNU,
payload: request
};
}
export function setToRead(id, snu) {
const request = axios.patch(`/snus/${id}`, { title: snu.title, text: snu.text, keywords: snu.keywords, read: true });
return {
type: SET_TO_READ,
payload: request
}
}
export function createSnu(object) {
const request = axios.post('/snus', object);
return {
type: CREATE_SNU,
payload: request
};
}
A Reducer:
import { FETCH_SNUS, FETCH_SNU, SET_TO_READ, CREATE_SNU } from '../actions/index';
export default function(state = [], action) {
switch(action.type) {
case FETCH_SNUS:
return [ ...state, ...action.payload.data ];
case FETCH_SNU:
return [ ...state, ...action.payload.data ];
case SET_TO_READ:
return [ ...state, ...action.payload.data ];
case CREATE_SNU:
return [...state, ...action.payload.data ];
default:
return state;
}
}
I tested all endpoints via Postman and they work. So that should not be the problem… Please help! I cannot find a solution to this problem.
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'
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