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.
Related
I am not getting any clue how to mock a method. I have to write a unit test for this function:
index.ts
export async function getTenantExemptionNotes(platform: string) {
return Promise.all([(await getCosmosDbInstance()).getNotes(platform)])
.then(([notes]) => {
return notes;
})
.catch((error) => {
return Promise.reject(error);
});
}
api/CosmosDBAccess.ts
import { Container, CosmosClient, SqlQuerySpec } from "#azure/cosmos";
import { cosmosdbConfig } from "config/Config";
import { Workload } from "config/PlatformConfig";
import { fetchSecret } from "./FetchSecrets";
export class CosmoDbAccess {
private static instance: CosmoDbAccess;
private container: Container;
private constructor(client: CosmosClient) {
this.container = client
.database(cosmosdbConfig.database)
.container(cosmosdbConfig.container);
}
static async getInstance() {
if (!CosmoDbAccess.instance) {
try {
const connectionString = await fetchSecret(
"CosmosDbConnectionString"
);
const client: CosmosClient = new CosmosClient(connectionString);
// Deleting to avoid error: Refused to set unsafe header "user-agent"
delete client["clientContext"].globalEndpointManager.options
.defaultHeaders["User-Agent"];
CosmoDbAccess.instance = new CosmoDbAccess(client);
return CosmoDbAccess.instance;
} catch (error) {
// todo - send to app insights
}
}
return CosmoDbAccess.instance;
}
public async getAllNotesForLastSixMonths() {
const querySpec: SqlQuerySpec = {
// Getting data from past 6 months
query: `SELECT * FROM c
WHERE (udf.convertToDate(c["Date"]) > DateTimeAdd("MM", -6, GetCurrentDateTime()))
AND c.IsArchived != true
ORDER BY c.Date DESC`,
parameters: [],
};
const query = this.container.items.query(querySpec);
const response = await query.fetchAll();
return response.resources;
}
}
export const getCosmosDbInstance = async () => {
const cosmosdb = await CosmoDbAccess.getInstance();
return cosmosdb;
};
index.test.ts
describe("getExemptionNotes()", () => {
beforeEach(() => {
jest.resetAllMocks();
});
it("makes a network call to getKustoResponse which posts to axios and returns what axios returns", async () => {
const mockNotes = [
{
},
];
const cosmosDBInstance = jest
.spyOn(CosmoDbAccess, "getInstance")
.mockReturnValue(Promise.resolve(CosmoDbAccess.instance));
const kustoResponseSpy = jest
.spyOn(CosmoDbAccess.prototype, "getAllNotesForLastSixMonths")
.mockReturnValue(Promise.resolve([mockNotes]));
const actual = await getExemptionNotes();
expect(kustoResponseSpy).toHaveBeenCalledTimes(1);
expect(actual).toEqual(mockNotes);
});
});
I am not able to get instance of CosmosDB or spyOn just the getAllNotesForLastSixMonths method. Please help me code it or give hints. The complexity is because the class is singleton or the methods are static and private
I am trying to display data in my Angular frontend that is called against an Oracle DB linked to my Node backend. Currently if I go to the phsyiscal API link I will see the data and it will display in my backend console.log.
Am I missing something easy here? I am a newbie so apologize in advance if this is easy.
Angular Page Component
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
import { DBService, Group} from '../db.service';
import { HttpClient } from '#angular/common/http';
import { NgIf } from '#angular/common/src/directives/ng_if';
import { element } from 'protractor';
import { ActivatedRoute } from '#angular/router';
import { Router } from '#angular/router';
#Component({
selector: 'app-flow',
templateUrl: './flow.component.html',
styleUrls: ['../css.css']
})
export class FlowComponent implements OnInit {
groups: Group[];
constructor(
private auth: AuthenticationService,
private dbService: DBService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.dbService.getGroup2().subscribe(groups => this.groups = groups);
}
}
DB Service Code
getGroup2(): Observable<Group[]> {
const url = 'http://localhost:4200/api/' + 'employees';
const data = ({
});
return this._http.post(url, data)
.pipe(
map((res) => {
console.log(res);
return <Group[]> res;
})
);
}
Backend
services/router.js
const express = require('express');
const router = new express.Router();
const employees = require('../controllers/employees.js');
router.route('/employees')
.get(employees.get);
module.exports = router;
services/database.js
const oracledb = require('oracledb');
const dbConfig = require('../config/database.js');
async function initialize() {
const pool = await oracledb.createPool(dbConfig.hrPool);
}
module.exports.initialize = initialize;
// close the function
async function close() {
await oracledb.getPool().close();
}
module.exports.close = close;
// simple executre function
function simpleExecute(statement, binds = [], opts = {}) {
return new Promise(async (resolve, reject) => {
let conn;
opts.outFormat = oracledb.OBJECT;
opts.autoCommit = true;
try {
conn = await oracledb.getConnection();
const result = await conn.execute(statement, binds, opts);
resolve(result);
} catch (err) {
reject(err);
} finally {
if (conn) { // conn assignment worked, need to close
try {
await conn.close();
} catch (err) {
console.log(err);
}
}
}
});
}
module.exports.simpleExecute = simpleExecute;
controllers/employees.js
const employees = require('../db_apis/employees.js');
async function get(req, res, next) {
try {
const context = {};
// context.id = parseInt(req.params.id, 10);
const rows = await employees.find(11);
if (req.params.id) {
if (rows.length === 1) {
res.status(200).json(rows[0]);
} else {
res.status(404).end();
}
} else {
res.status(200).json(rows);
}
} catch (err) {
next(err);
}
}
module.exports.get = get;
db_apis/employees.js
const database = require('../services/database.js');
const baseQuery =
`SELECT * FROM DB.EMPLOYEES`;
async function find(context) {
// let query = baseQuery;
// const binds = {};
// if (context.id) {
// binds.employee_id = context.id;
// query += `\nwhere employee_id = :employee_id`;
// }
const result = await database.simpleExecute(baseQuery);
console.log(result.rows);
return result.rows;
}
module.exports.find = find;
You have router.route('/employees').get(employees.get); but in your services you are doing this._http.post(url, data)
You should be doing a get request this._http.get(url)....
In your getGroup2, where is the headers part?
getGroup2(): Observable<Group[]> {
const url = 'http://localhost:4200/api/' + 'employees';
const data = ({
});
return this._http.post(url, data)
.pipe(
map((res) => {
console.log(res);
return <Group[]> res;
})
);
}
you must include the options
const headers = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `Bearer fs4df4sdfsd4fs`
});
const options = {headers: _headers}
return this._http.post(url, data, options)
Hope this helps 👍
when trying to cancel upload by unsubscribing what actually happen that i unsubscribe to upload progress but that actual upload is not cancelled and keep uploading to the server.
upload.components.ts
import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '#angular/core';
import { Subject, Subscription, Observable } from 'rxjs';
import { HttpEventType } from '#angular/common/http';
import { UploadService } from '../../../services';
import { takeUntil } from 'rxjs/operators';
#Component({
selector: 'app-image-upload-item',
templateUrl: './image-upload-item.component.html',
styleUrls: ['./image-upload-item.component.scss']
})
export class ImageUploadItemComponent implements OnInit, OnDestroy {
#Input() index: any;
#Output() uploadSuccess: EventEmitter<any>;
#Output() uploadCanceled: EventEmitter<any>;
public localimageURL: string;
public uploadProgress: number;
public isUploadCompleted: boolean;
public uploadImageObservable: Subscription;
public isReadyForUpload: boolean;
public isUploading: boolean;
public progressMode: string;
public readonly unique: string = Math.floor((Math.random() *
100)).toString();
public readonly imagePreviewID = 'imagePreview' + this.unique;
_file: any;
#Input() public set file(value: any) {
const reader = new FileReader();
reader.onload = (e: any) => {
this.localimageURL = e.target.result;
};
this._file = value;
reader.readAsDataURL(this._file);
console.log(this._file);
}
constructor(private uploadService: UploadService) {
this.uploadProgress = 0;
this.isUploading = false;
this.localimageURL = '';
this.isUploadCompleted = false;
this.uploadSuccess = new EventEmitter<any>();
this.uploadCanceled = new EventEmitter<any>();
this.progressMode = 'indeterminate';
}
ngOnInit() {
this.uploadImageToServer(this._file);
// setTimeout(() => {
// console.log('im in set time out unsubscripting',
this.uploadImageObservable);
// this.uploadImageObservable.forEach(subscription => {
// subscription.unsubscribe();
// });
// }, 100);
}
ngOnDestroy() {
console.log('component destroyed');
this.uploadImageObservable.unsubscribe();
}
public clearUploadButtonClicked() {
// if (this.uploadImageObservable !== undefined) {
// console.log('image observable is defined');
// this.uploadImageObservable.unsubscribe();
// console.log(this.uploadImageObservable.closed);
// }
// this.uploadImageObservable.unsubscribe();
this._file = '';
this.uploadCanceled.emit({ index: this.index, uploaded: false });
}
public get showUploadProgress(): boolean {
return this.uploadProgress !== 0;
}
public uploadImageToServer(file) {
this.isUploading = true;
const progress = new Subject<number>();
progress.subscribe(value => {
this.uploadProgress = value;
});
this.uploadImageObservable = this.uploadService.uploadImage(file)
.subscribe(result => {
const type = result.type;
const data = result.data;
console.log(result);
if (type === HttpEventType.UploadProgress) {
const percentDone = Math.round(100 * data.loaded / data.total);
progress.next(percentDone);
if (percentDone === 100) {
this.progressMode = 'indeterminate';
}
} else if (type === HttpEventType.Response) {
if (data) {
progress.complete();
this.progressMode = 'determinate';
this.isReadyForUpload = false;
this.isUploadCompleted = true;
this.isUploading = false;
this.uploadSuccess.emit({ index: this.index, mediaItem: data });
}
}
}, errorEvent => {
});
}
}
upload.service.ts
public uploadImage(imageFile: File): Observable<any> {
const formData: FormData = new FormData();
if (imageFile !== undefined) {
formData.append('image', imageFile, imageFile.name);
const req = new HttpRequest('POST', environment.uploadImageEndPoint,
formData, {
reportProgress: true,
});
return new Observable<any>(observer => {
this.httpClient.request<any>(req).subscribe(event => {
if (event.type === HttpEventType.Response) {
const responseBody = event.body;
if (responseBody) {
this.alertService.success(responseBody.message);
observer.next({ type: event.type, data: new
MediaItem(responseBody.mediaItem) });
}
} else if (event.type === HttpEventType.UploadProgress) {
observer.next({ type: event.type, data: { loaded: event.loaded, total:
event.total } });
} else {
observer.next(event);
}
}, errorEvent => {
if (errorEvent.status === 400) {
this.alertService.error(errorEvent.error['image']);
} else {
this.alertService.error('Server Error, Please try again later!');
}
observer.next(null);
});
});
}
}
how can i cancel upload request properly with observable unsubscribe
note i already tried pipe takeuntil() and nothing changed
What you'll want to do is return the result from the pipe function on the http request return observable. Right now you have multiple streams and the component's unsubscribe is only unsubscribing to the observable wrapping the http request observable (not connected).
You'll want to do something like:
return this.httpClient.request<any>(req).pipe(
// use rxjs operators here
);
You'll then use rxjs operators (I've been doing this for a while, but I still highly reference this site) to perform any logic needed and reflect things like your errors and upload progress to the component calling the service. On the component side, you'll keep your subscribe/unsubscribe logic.
For instance, you can use the switchMap operator to transform what is returning to the component from the http request observable and specify the value to return to the component, and catchError to react to any errors accordingly.
return this.httpClient.request<any>(req).pipe(
switchMap(event => {
if (event.type === HttpEventType.Response) {
const responseBody = event.body;
if (responseBody) {
this.alertService.success(responseBody.message);
return { type: event.type, data: new MediaItem(responseBody.mediaItem) };
}
} else if (event.type === HttpEventType.UploadProgress) {
return { type: event.type, data: { loaded: event.loaded, total: event.total } };
}
return event;
}),
catchError(errorEvent => {
if (errorEvent.status === 400) {
this.alertService.error(errorEvent.error['image']);
} else {
this.alertService.error('Server Error, Please try again later!');
}
return of(<falsy or error value>);
}),
);
Alternatively you could model it a little more after this example by just returning the http function call from the service to the component and handling things in the subscribe there.
actually i found a way as follows
public uploadImage(imageFile: File): Observable<any> {
const formData: FormData = new FormData();
if (imageFile !== undefined) {
formData.append('image', imageFile, imageFile.name);
const req = new HttpRequest('POST', environment.uploadImageEndPoint, formData, {
reportProgress: true,
});
return this.httpClient.request<any>(req).pipe(
map((res: any) => {
return res;
}),
catchError(errorEvent => {
if (errorEvent.status === 400) {
this.alertService.error(errorEvent.error['image']);
} else {
this.alertService.error('Server Error, Please try again later!');
return Observable.throw(errorEvent);
}
return Observable.throw(errorEvent);
}));
}
}
...It does in the documentation P:
I import * as firebase from "firebase"; at the top of the file. The uid-fetching function works. It doesn't like the syntax of the for-loop for some reason...
I have also tried the syntax: for (DataSnapshot child : parent.getChildren()) { } and then the compiler tells me a semicolon is expected in the line where the for-loop starts.
getMessages() {
return new Promise(function (resolve) {
return firebase.auth().onAuthStateChanged(function (user) {
if (user) {
resolve(user.uid);
}
});
}).then((result) => {
return firebase.database().ref('mailboxes/' + result).once('value').then((snapshot) => {
let messageArray;
for (let snap of snapshot.getChildren()) {
messageArray.push(snap.val());
console.log('snapshot key:' + snap.key);
console.log('snapshot val:' + snap.val());
};
return messageArray;
});
});
}
You need to use a subscription to watch for the changes. Use AngularFire to watch for when they are logged in and get the UID (assuming you are using the Authentication login in Firebase so that all data is saved using the UID as the tree path
import { AngularFirestore } from 'angularfire2/firestore';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';
import { switchMap, map } from 'rxjs/operators';
import { Observable, pipe } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import firebase as firebase from 'firebase/app';
private myOAuthSubscription: Subscription;
private myDatasubscription: Subscription;
public userloggedin:boolean = false;
public uid:string = '';
public this.items:any = [];
constructor(
public _DB: AngularFireDatabase,
public _afAuth: AngularFireAuth,
) {
try {
this.myOAuthSubscription = this._afAuth.authState.subscribe(user => {
if (user && user.uid) {
console.log('loggedin = true');
this.userloggedin = true;
this.uid = String(user.uid);
this.funDoDB():
} else {
console.log('loggedin = false');
this.userloggedin = true;
this.uid = '';
}
});
} catch (e) {
console.error("fbData_subscription", e);
}
}
ngOnDestroy() {
this.myOAuthSubscription.unsubscribe();
this.myDatasubscription.unsubscribe();
}
private funDoDB(){
if(this.userloggedin == true){
try {
//subscription using AngulaFire
this.myDatasubscription = this._DB.list('mailboxes/' + this.uid).snapshotChanges().pipe(map(actions => {
return actions.map(action => ({ key: action.key, val: action.payload.val() }));
}))
.subscribe(items => {
this.items = [];
this.items = items.map(item => item);
console.log("db results",this.items);
var icount=0;
for (let i in this.items) {
console.log("key",this.items[i].key);
console.log("val",this.items[i].val);
console.log("----------------------------------);
//checking if something exists
if (this.items[i].key == 'SomeNodePath') {
var log = this.items[i].val;
}
}
} catch (e) {
console.error(e);
}
});
}
}
npm install --save angularfire2 firebase
npm install -D rxjs#6.2.2 rxjs-compat#6.2.2
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();
}
);
}