I have a websocket, which receives a message from the client and then sends back the message. When using two browsers, both receive their own messages. But in the server logs, I cannot see both messages.
How can I make it globalized so I can see both messages, is it through creating another websocket which will get these messages and it will send it back?
Frontend:
import { isPlatformBrowser } from '#angular/common';
import { Inject, Injectable } from '#angular/core';
import { PLATFORM_ID } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class WebsocketService {
messages: Array<any> = [];
chatWebSocket: WebSocket | undefined = undefined;
createSocket() {
if (isPlatformBrowser(this.platformId)) {
this.chatWebSocket = new WebSocket('ws://localhost:3000/ws');
}
}
send(msg: string) {
console.log(this.chatWebSocket);
(this.chatWebSocket as unknown as WebSocket).send(msg);
}
recv() {
if (!this.chatWebSocket) return;
(this.chatWebSocket as WebSocket).addEventListener('message', (e) => {
this.messages.push(e.data);
});
}
constructor(#Inject(PLATFORM_ID) public platformId: Object) {}
}
Server:
pub async fn chat_ws(
ws: WebSocketUpgrade,
user_agent: Option<TypedHeader<UserAgent>>,
) -> impl IntoResponse {
if let Some(TypedHeader(user_agent)) = user_agent {
println!("`{}` connected", user_agent.as_str());
}
ws.on_upgrade(handle_chat_socket)
}
pub async fn handle_chat_socket(mut socket: WebSocket) {
loop {
if let Some(msg) = socket.recv().await {
if let Ok(msg) = msg {
match msg {
Message::Text(a) => {
socket.send(Message::Text(String::from(a.clone()))).await;
},
_ => println!("Other"),
}
}
}
}
}
Related
i have a messaging room application that create a discussion chat foreach room between users signed in with same room ,
i m facing this error :
Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
i used 3 functions :
getChatMessages() : to get all chat messages from firestore for users
with same room
getCurrentRoom():to get the room of the connected user
getUsers(): return all users with same room
chat.services.ts
import { Injectable } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/compat/auth';
import { AngularFirestore } from '#angular/fire/compat/firestore';
import { Observable } from 'rxjs';
import { Timestamp } from 'rxjs/internal/operators/timestamp';
import { switchMap,map, timestamp, filter } from 'rxjs/operators';
import { query, orderBy, limit, where } from "firebase/firestore";
import firebase from 'firebase/compat/app';
export interface User {
uid: string;
email: string;
displayName:string;
username?:string;
room?:string
}
export interface Message {
createdAt: firebase.firestore.FieldValue;
id: string;
from: string;
msg: string;
fromName: string;
myMsg: boolean;
}
#Injectable({
providedIn: 'root'
})
export class ChatService {
currentUser: User ;
currentRoom:string="";
updatedRoom:string="";
constructor(private afAuth: AngularFireAuth, private afs: AngularFirestore) {
this.afAuth.onAuthStateChanged((user) => {
this.currentUser=user;
console.log("current email"+this.currentUser.email);
});
}
async signup({ username,email, password,room }): Promise<any> {
const credential = await this.afAuth.createUserWithEmailAndPassword(
email,
password
);
const uid = credential.user.uid;
return this.afs.doc(
`users/${uid}`
).set({
uid,
email: credential.user.email,
username:username,
room:room,
})
}
signIn({ email, password }) {
return this.afAuth.signInWithEmailAndPassword(email, password);
}
signOut(): Promise<void> {
return this.afAuth.signOut();
}
addChatMessage(msg) {
return this.afs.collection('messages').add({
createdAt:firebase.firestore.FieldValue.serverTimestamp(),//firebase.default.firestore.Timestamp,
msg: msg,
from: this.currentUser.uid
});
}
async getChatMessages() {
let users = [];
return (await this.getUsers()).pipe(
switchMap(res => {
users = res;
console.log("resssssss"+res);
return this.afs.collection('messages', ref => ref.orderBy('createdAt','asc')).valueChanges({ idField: 'id' }) as Observable<Message[]>;
}),
map(messages => {
console.log("messages"+messages);
// Get the real name for each user
for (let m of messages) {
m.fromName = this.getUserForMsg(m.from, users);
m.myMsg = this.currentUser.uid === m.from;
}
return messages
})
)
}
public async getCurrentRoom() {
await this.afs.collection('users', ref => ref.where("email", "==", this.currentUser.email)).get().toPromise()
.then(snapshot => {
snapshot.forEach(doc => {
this.currentRoom=JSON.parse(JSON.stringify(doc.data())).room;
console.log("current room" + this.currentRoom);
});
});
}
public async getUsers() {
console.log("this room" + this.currentRoom);
return this.afs.collection('users', ref => ref.where("room", "==", this.currentRoom)).valueChanges({
idField: 'uid'
}) as Observable < User[] > ;
}
private getUserForMsg(msgFromId, users: User[]): string {
for (let usr of users) {
if (usr.uid == msgFromId) {
return usr.username ?? 'undefind';
}
}
return 'Deleted';
}
}
my chat.page.ts :
import { Component, OnInit, ViewChild } from '#angular/core';
import { IonContent } from '#ionic/angular';
import { Observable } from 'rxjs';
import { ChatService } from '../chat.service';
import { Router } from '#angular/router';
import { AngularFireStorage, AngularFireUploadTask } from '#angular/fire/compat/storage';
import { AngularFirestore, AngularFirestoreCollection } from '#angular/fire/compat/firestore';
import { finalize, tap } from 'rxjs/operators';
export interface FILE {
name: string;
filepath: string;
size: number;
}
#Component({
selector: 'app-chat',
templateUrl: './chat.page.html',
styleUrls: ['./chat.page.scss'],
})
export class ChatPage implements OnInit {
ngFireUploadTask: AngularFireUploadTask;
progressNum: Observable<number>;
progressSnapshot: Observable<any>;
fileUploadedPath: Observable<string>;
room:any;
files: Observable<FILE[]>;
ImgtoSend:any;
FileName: string;
FileSize: number;
isImgUploading: boolean;
isImgUploaded: boolean;
private ngFirestoreCollection: AngularFirestoreCollection<FILE>;
#ViewChild(IonContent) content: IonContent;
messages:any=[];
newMsg = '';
constructor(private angularFirestore: AngularFirestore,
private angularFireStorage: AngularFireStorage,private chatService: ChatService, private router: Router) {
this.isImgUploading = false;
this.isImgUploaded = false;
this.ngFirestoreCollection = angularFirestore.collection<FILE>('filesCollection');
this.files = this.ngFirestoreCollection.valueChanges();
}
ngOnInit() {
this.messages= this.chatService.getChatMessages();
}
sendMessage() {
this.chatService.addChatMessage(this.newMsg).then(() => {
this.newMsg = '';
this.content.scrollToBottom();
});
}
signOut() {
this.chatService.signOut().then(() => {
this.router.navigateByUrl('/login', { replaceUrl: true });
});
}
fileUpload(event: FileList) {
const file = event.item(0)
if (file.type.split('/')[0] !== 'image') {
console.log('File type is not supported!')
return;
}
this.isImgUploading = true;
this.isImgUploaded = false;
this.FileName = file.name;
const fileStoragePath = `filesStorage/${new Date().getTime()}_${file.name}`;
console.log("filestoragepath"+fileStoragePath);
const imageRef = this.angularFireStorage.ref(fileStoragePath);
console.log("image ref"+imageRef);
this.ngFireUploadTask = this.angularFireStorage.upload(fileStoragePath, file);
this.ImgtoSend=this.FileName;
console.log("image to Send"+this.ImgtoSend);
this.progressNum = this.ngFireUploadTask.percentageChanges();
this.progressSnapshot = this.ngFireUploadTask.snapshotChanges().pipe(
finalize(() => {
this.fileUploadedPath = imageRef.getDownloadURL();
console.log("uploaded path"+this.fileUploadedPath);
this.fileUploadedPath.subscribe(resp=>{
this.fileStorage({
name: file.name,
filepath: resp,
size: this.FileSize
});
this.isImgUploading = false;
this.isImgUploaded = true;
},error => {
console.log(error);
})
}),
tap(snap => {
this.FileSize = snap.totalBytes;
})
)
}
fileStorage(image: FILE) {
const ImgId = this.angularFirestore.createId();
this.ngFirestoreCollection.doc(ImgId).set(image).then(data => {
console.log("data"+data);
}).catch(error => {
console.log(error);
});
}
}
enter code here
First, you have to eleminate all the code that is not relevant to the question to help people help you.
Second, the issue is simple:
What the compiler is telling you here is, Hey Mohammed Amir, *ngFor="" is used to loop through an Array of objects while you are passing to it an object literal.
Check the value of the property you bind to *ngFor="let msg of [YourAssumedArrayFromEndpointResponse]" in your template you will find that YourAssumedArrayFromEndpointResponse is not an array. That's why the compiler is complaining
I'm trying to call an event, but an error occurs: UnhandledPromiseRejectionWar
ning: TypeError: Cannot read property 'emit' of undefined
How it is possible to bypass it? nodejs
import { io } from 'socket.io-client';
import { EventEmitter2, OnEvent } from '#nestjs/event-emitter';
import { SynchronizeService } from './synchronize.service';
import { UserService } from '../user/user.service';
const serverAddress = 'http://localhost:3050'
const socket = io(serverAddress, { reconnection: true })
export class SocketClient {
constructor(
private readonly synchronizeService: SynchronizeService,
private eventEmitter: EventEmitter2,
private readonly userService: UserService
) {}
#OnEvent('order.created')
async handler(req):Promise<void> {
try{
socket.on('synchronize', () => {
});
socket.on('send_from_server', async ({ msg }) => {
await this.eventEmitter.emitAsync('order.came')
});
socket.emit('synchronize', req);
} catch (e) {
console.log(e);
}
}
}
I have started learning how to test angular projects. So far basic unit testing is working fine for me but for the dependency testing especially when API services are injected into the component I am facing issue for providing HttpClient. I have tried different solutions but none is working for me.
Service
// Present in HttpClientService file
getDisposition() {
return this.http.get<{ message: string, data: { dispositionList: Disposition[] } }>(`${this.URL}/disposition/get`);
}
// Present in FileProcessService file
deleteMedia(media: string) {
return this.http.delete<{ message: string }>(`${this.URL}/certificate/delete?certificate=${media}`);
}
add-edit-activity.component.ts
import { HttpEventType } from '#angular/common/http';
import { Component, ElementRef, Inject, OnInit, ViewChild } from '#angular/core';
import { FormBuilder, Validators } from '#angular/forms';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { DomSanitizer, SafeUrl } from '#angular/platform-browser';
import { AssignedPerson } from '#model/assigned-person.model';
import { Disposition } from '#model/disposition.model';
import { mimeTypes } from '#model/mime-type';
import { FileProcessService } from '#service/file-process.service';
import { HttpClientService } from '#service/http-client.service';
import { DeleteComponent } from '#shared/delete/delete.component';
import { CustomErrorStateMatcher } from '#validator/error-state-matcher';
import { ToastrService } from 'ngx-toastr';
#Component({
selector: 'app-add-edit-activity',
templateUrl: './add-edit-activity.component.html',
styleUrls: ['./add-edit-activity.component.css']
})
export class AddEditActivityComponent implements OnInit {
constructor(private fb: FormBuilder, private sanitizer: DomSanitizer, private dialogRef: MatDialogRef<AddEditActivityComponent>, #Inject(MAT_DIALOG_DATA) public data: any,
private _http: HttpClientService, private _fileService: FileProcessService, private toastr: ToastrService, private dialog: MatDialog,) { }
re = new RegExp(/^[a-zA-Z-]*/, 'g');
ISOstamp = { T: ' ', Z: '000' };
matcher = new CustomErrorStateMatcher();
dispositionList: Disposition[] = [];
assignedPersonList: AssignedPerson[] = [];
filterAssignedPersonList: AssignedPerson[] = [];
uploaded = false;
uploadProgress = false;
fileURL: SafeUrl;
activityForm = this.fb.group({
gaugeId: [this.data.gaugeId, Validators.maxLength(9)], createTimeStamp: [{ value: new Date().toISOString().replace(/[TZ]/g, m => this.ISOstamp[m]), disabled: true }],
user: [{ value: sessionStorage.getItem('username'), disabled: true }], disposition: ['', [Validators.required, Validators.maxLength(30)]],
assignedPersonName: ['', Validators.maxLength(30)], department: ['', Validators.maxLength(20)],
shift: ['', Validators.maxLength(1)], remark: ['', Validators.maxLength(50)],
calibrationDate: ['', Validators.maxLength(10)], attachment: ['', Validators.maxLength(255)]
});
#ViewChild('file') certificate: ElementRef;
ngOnInit(): void {
this.data.type.match(this.re)[0] === 'Update' && this.setFormValues();
this._http.getDisposition().subscribe(response => this.dispositionList = response.data.dispositionList);
this._http.getAssignedPerson().subscribe(response => this.assignedPersonList = this.filterAssignedPersonList = response.data.assignedToList);
}
get GaugeId() {
return this.activityForm.get('gaugeId');
}
get TimeStamp() {
return this.activityForm.get('createTimeStamp');
}
get Attachment() {
return this.activityForm.get('attachment');
}
get Disposition() {
return this.activityForm.get('disposition');
}
get DispositionValue() {
return this.dispositionList.map(e => e.dispositionType).indexOf(this.Disposition.value) < 0;
}
get AssignedTo() {
return this.activityForm.get('assignedPersonName');
}
get AssignedToValue() {
return this.assignedPersonList.map(e => `${e.firstName} ${e.lastName}`).indexOf(this.Disposition.value) < 0;
}
private async setFormValues() {
this.activityForm.patchValue({ ...this.data });
if (this.data.attachment) {
this.uploadProgress = true;
this.uploaded = true;
await this.fetchUploadedFile(this.data.attachment, mimeTypes[this.data.attachment.split('.')[1]]);
this.activityForm.markAsPristine();
}
}
searchAssignedPerson(event) {
if (event.target.value) {
this.filterAssignedPersonList = [];
for (let person of this.assignedPersonList) {
if (person.firstName.toLowerCase().startsWith(event.target.value.toLowerCase())) {
this.filterAssignedPersonList.push(person);
}
}
} else { this.filterAssignedPersonList = this.assignedPersonList }
}
upload(event) {
const file: File = event.target.files[0];
this.certificate.nativeElement.value = '';
if (file.size > (20 * 1000 * 1000)) { // Checking if File size is above 20MB
this.toastr.error('Size of ' + file.name + ' is above 20MB');
return;
}
const fd = new FormData();
fd.append('certificate', file, file.name);
this.processAttachment(fd);
}
private processAttachment(file: FormData) {
this._fileService.uploadMedia(file).subscribe(event => {
if (event.type === HttpEventType.UploadProgress) { this.uploadProgress = true }
if (event.type === HttpEventType.Response) {
let media = event.body.data.Certificate;
this.fetchUploadedFile(media.fileName, media.fileType);
this.toastr.info(event.body.message);
}
}, error => {
this.toastr.error(error.error.message);
this.uploadProgress = false;
});
}
private async fetchUploadedFile(file: string, mimeType: string) {
try {
let response = await this._fileService.getMedia(file).toPromise();
this.fileURL = this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(new Blob([response], { type: mimeType })));
this.uploaded = true;
this.uploadProgress = false;
this.activityForm.patchValue({ attachment: file });
this.Attachment.markAsDirty();
} catch (error) {
this.uploadProgress = false;
this._fileService.processError(error.error)
}
}
deleteFile() {
this.dialog.open(DeleteComponent, {
width: '350px', disableClose: true
}).afterClosed().subscribe(response => {
if (response) {
this._fileService.deleteMedia(this.Attachment.value).subscribe(response => {
this.toastr.info(response.message);
this.activityForm.patchValue({ attachment: '' });
this.Attachment.markAsDirty();
this.uploaded = false;
}, error => this.toastr.error(error.error.message));
}
});
}
async doAction() {
let message = '';
if (this.data.type.match(this.re)[0] === 'Add') {
message = await (await this._http.addActivityLog(this.activityForm.getRawValue()).toPromise()).message;
} else {
message = await (await this._http.updateActivityLog(this.GaugeId.value, this.TimeStamp.value, this.activityForm.getRawValue()).toPromise()).message;
}
this.dialogRef.close(message);
}
}
add-edit-activity.component.spec.ts
import { ComponentFixture, TestBed, tick } from '#angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { FileProcessService } from '#service/file-process.service';
import { HttpClientService } from '#service/http-client.service';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { AddEditActivityComponent } from './add-edit-activity.component';
describe('AddEditActivityComponent', () => {
let component: AddEditActivityComponent;
let fixture: ComponentFixture<AddEditActivityComponent>;
let _http: HttpClientService, _file: FileProcessService;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AddEditActivityComponent],
imports: [FormsModule, ReactiveFormsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { type: 'Add Activity Log', gaugeId: 'W-001' } },,
{ provider: HttpClientService, useValue: null },
{ provider: FileProcessService, useValue: null }
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddEditActivityComponent);
component = fixture.debugElement.componentInstance;
_http = fixture.debugElement.injector.get(HttpClientService);
_file = fixture.debugElement.injector.get(FileProcessService);
fixture.detectChanges();
});
// These 2 tests throwing error.
it('SHOULD mock http service', () => {
let spy = spyOn(_http, 'getDisposition').and.callFake(() => {
return of({
message: '',
data: { dispositionList: [] }
}).pipe(delay(100));
});
component.ngOnInit();
tick(100);
expect(component.dispositionList).toEqual([]);
});
it('SHOULD mock file service', () => {
let spy = spyOn(_file, 'deleteMedia').and.callFake((file: string) => {
return of({ message: '' }).pipe(delay(100));
});
component.deleteFile();
tick(100);
expect(component.uploaded).toBe(false);
});
});
The error that am getting for those 2 tests (I'm providing the error of 1 test case, the same is coming for the 2nd one also):
SHOULD mock http service
AddEditActivityComponent
Error: Invalid provider for the NgModule 'DynamicTestModule' - only instances of Provider and Type are allowed, got: [..., ..., ...,
..., ?undefined?, ..., ...]
at throwInvalidProviderError (node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:240:1)
at providerToFactory (node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11550:1)
at providerToRecord (node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11521:1)
at R3Injector.processProvider (node_modules/#angular/core/ivy_ngcc/fesm2015/core.js:11424:1)
Error: : could not find an object to spy upon for getDisposition() Usage: spyOn(, ) Error: :
could not find an object to spy upon for getDisposition() Usage:
spyOn(, )
at
Anyone, please help what's the exact error happening with my test cases? Stuck here for almost 2 days.
You have to avoid using a 'null' value when providing services in the TestBed, that what is causing your problem.
The best way to mock Angular services is to create SpyObj instances and then use them like what is done Angular tests documentation.
Here is how would be your spec file if you were using SpyObj:
import { ComponentFixture, TestBed, tick } from '#angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
import { FileProcessService } from '#service/file-process.service';
import { HttpClientService } from '#service/http-client.service';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { AddEditActivityComponent } from './add-edit-activity.component';
describe('AddEditActivityComponent', () => {
let component: AddEditActivityComponent;
let fixture: ComponentFixture<AddEditActivityComponent>;
let httpSpy: jasmine.SpyObj<HttpClientService>;
let fileSpy: jasmine.SpyObj<FileProcessService>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AddEditActivityComponent],
imports: [FormsModule, ReactiveFormsModule],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { type: 'Add Activity Log', gaugeId: 'W-001' } },
{
provider: HttpClientService,
useValue: jasmine.createSpyObj('HttpClientService', ['getDisposition', 'getAssignedPerson', 'addActivityLog', 'updateActivityLog'])
},
{
provider: FileProcessService,
useValue: jasmine.createSpyObj('FileProcessService', ['uploadMedia', 'getMedia', 'deleteMedia', 'processError'])
}
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddEditActivityComponent);
component = fixture.debugElement.componentInstance;
httpSpy = fixture.debugElement.injector.get(HttpClientService) as jasmine.SpyObj<HttpClientService>;
fileSpy = fixture.debugElement.injector.get(FileProcessService) as jasmine.SpyObj<FileProcessService>;
fixture.detectChanges();
});
// These 2 tests throwing error.
it('SHOULD mock http service', () => {
httpSpy.getDisposition.and.callFake(() => {
return of({
message: '',
data: { dispositionList: [] }
}).pipe(delay(100));
});
component.ngOnInit();
tick(100);
expect(component.dispositionList).toEqual([]);
});
it('SHOULD mock file service', () => {
fileSpy.deleteMedia.and.callFake((file: string) => {
return of({ message: '' }).pipe(delay(100));
});
component.deleteFile();
tick(100);
expect(component.uploaded).toBe(false);
});
});
I have a websocket service in Angular7
import { environment } from './../../../environments/environment.prod';
import { Injectable } from '#angular/core';
import * as Rx from 'rxjs';
import {map} from 'rxjs/operators';
#Injectable()
export class WsService{
public wsMessages: Rx.Subject<any>;
public connect(url): Rx.Subject<MessageEvent> {
if(!this.subject){
this.subject = this.create(url);
console.log("Websocket (Dashboard) successfully connected to : ", url);
}
return this.subject;
}
private create(url): Rx.Subject<MessageEvent> {
let ws = new WebSocket(
url,
[`Bearer`, `${this.token.substring(7)}`]
);
let observable = Rx.Observable.create(
(obs: Rx.Observer<MessageEvent>) => {
ws.onmessage = obs.next.bind(obs);
ws.onerror = obs.error.bind(obs);
ws.onclose = obs.complete.bind(obs);
return ws.close.bind(ws);
}
)
let observer = {
next: (data: Object) => {
if(ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
}
}
return Rx.Subject.create(observer, observable);
}
private token: string;
constructor(){
this.token = JSON.parse(localStorage.getItem('user')).token
this.wsMessages = <Rx.Subject<any>>
this.connect(`${environment.websocketUrl}/dashboard/ws`)
.pipe(
map((response: MessageEvent): any =>{
let data = JSON.parse(response.data);
return data;
})
)
}
private subject: Rx.Subject<MessageEvent>;
}
and i have mulltiple component that subscribe to wsMessages
this.ws.wsMessages.subscribe(msg => {
this.catchWebSocketEvents(msg)
console.log("LeftBarSocket : ", msg);
})
the events are only printed on one component only and i need many components to listen to those ws events.
Ok so after doing a bit of reading on the subject, I found out there is a share() function that allows multiple subscribers sharing a source.
the solution is here :
const observable = Rx.Observable.create(
(obs: Rx.Observer<MessageEvent>) => {
this.ws.onmessage = obs.next.bind(obs);
this.ws.onerror = obs.error.bind(obs);
this.ws.onclose = obs.complete.bind(obs);
return this.ws.close.bind(this.ws);
}
).pipe(
share()
)
just need to add the .pipe(share()) to the observable, being created.
So I tried sending an object through my websocket by translating it to json and then back when it returns. Unfortunately it gives me the below error. The console.log shows me that it is valid JSON, but somehow it gives me an error at JSON.parse in the service document. Can anyone see what I did wrong?
The error
core.js?223c:1440 ERROR SyntaxError: Unexpected token c in JSON at position 0
at JSON.parse (<anonymous>)
at WebSocket._this.ws.onmessage [as __zone_symbol__ON_PROPERTYmessage] (movie-chat.service.ts?6086:22)
at WebSocket.wrapFn (zone.js?fad3:1166)
console.log result of event.data (valid json)
{"message":"good boy","extra":"extra"}
movie-chat.service.ts
import {Injectable} from "#angular/core";
import 'rxjs/rx';
import {HttpClient} from "#angular/common/http";
import {Observable} from "rxjs/Observable";
// We need #injectable if we want to use http
#Injectable()
export class MovieChatService {
ws;
constructor(private http: HttpClient) {
}
// receive events
createObservableSocket(url:string){
this.ws = new WebSocket(url);
return new Observable(observer => {
this.ws.onmessage = (e) => {
console.log(e.data);
var object = JSON.parse(e.data);
observer.next(object);
}
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
}
);
}
// send events
sendMessage(message) {
message = JSON.stringify(message);
console.log(message);
this.ws.send(message);
}
}
Back-end handling of messages
var wss = new Websocket.Server({port:3185});
var CLIENTS = [];
wss.on('connection',
function(websocket) {
CLIENTS.push(websocket);
websocket.send('connected to socket');
websocket.on('message', function (message) {
console.log('Server received:', message);
sendAll(message)
});
websocket.on('close', function(client) {
CLIENTS.splice(CLIENTS.indexOf(client), 1);
});
websocket.on('error', function(client) {
CLIENTS.splice(CLIENTS.indexOf(client), 1);
});
});
movie-chat.component.ts
import {Component, OnInit} from "#angular/core";
import { MovieChatService} from "./movie-chat.service";
#Component({
selector: 'app-movie-chat',
templateUrl: './movie-chat.component.html',
styleUrls: ['./movie-chat.component.css']
})
export class MovieChatComponent implements OnInit{
fullName;
messageFromServer;
title = 'Websocket Demo';
url;
ws;
messages = [];
constructor(private movieChatService: MovieChatService){
}
ngOnInit(){
this.fullName = localStorage.getItem('fullName');
this.url = 'ws://localhost:3185';
this.movieChatService.createObservableSocket(this.url)
.subscribe(data => {
this.messageFromServer = data;
},
err => console.log(err),
() => console.log('The observable stream, is complete'));
}
sendMessageToServer(){
console.log('Client sending message to websocket server');
this.movieChatService.sendMessage({
message: 'good boy',
extra: 'extra'
});
}
}
It seems that you are trying to parse a Json Object,
{"message":"good boy","extra":"extra"}
JSON.parse expect string parameter and you are passing an Json Object for that the exception is rised.
We try to surround the Parse with try and catch
import {Injectable} from "#angular/core";
import 'rxjs/rx';
import {HttpClient} from "#angular/common/http";
import {Observable} from "rxjs/Observable";
// We need #injectable if we want to use http
#Injectable()
export class MovieChatService {
ws;
constructor(private http: HttpClient) {
}
// receive events
createObservableSocket(url:string){
this.ws = new WebSocket(url);
return new Observable(observer => {
this.ws.onmessage = (e) => {
console.log(e.data);
try {
var object = JSON.parse(e.data);
observer.next(object);
} catch (e) {
console.log("Cannot parse data : " + e);
}
}
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
}
);
}
// send events
sendMessage(message) {
message = JSON.stringify(message);
console.log(message);
this.ws.send(message);
}
}
So, I didn't actually solve it the way I wanted it, but I found out that it is possible to send arrays through a websocket. I did this and on the receiving end I transferred it into an object in the service file. It does the job for now. If anyone knows a better solution, let me know :)
createObservableSocket(url:string){
this.ws = new WebSocket(url);
return new Observable(observer => {
this.ws.onmessage = (e) => {
var obj = e.data.split(',');
console.log(obj);
obj = {
name: obj[0],
msg: obj[1]
};
console.log(obj);
observer.next(obj);
}
this.ws.onerror = (event) => observer.error(event);
this.ws.onclose = (event) => observer.complete();
}
);
}