I am able to connect to my Hub and I've hooked up OnConnected and OnDisconnected. They should add/subtract from a integer and call a client callback with the new value.
My angular application is connecting to the server successfully but my registered callback function is not being triggered.
Here is my Serverhub:
[HubName("online")]
public class OnlineHub : Hub
{
private static int userCount = 0;
public override Task OnConnected()
{
userCount++;
Clients.All.listUpdated(userCount);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
userCount--;
Clients.All.listUpdated(userCount);
return base.OnDisconnected(stopCalled);
}
}
And here's my Angular SignalRService:
import { AppSettings } from './../app.settings';
import { EventEmitter, Injectable, OnDestroy } from '#angular/core';
declare const $: any;
#Injectable()
export class SignalRService {
// Declare the variables
private onlineHub: any;
// create the Event Emitter
public messageReceived: EventEmitter<any>;
public connectionEstablished: EventEmitter<Boolean>;
public connectionExists: Boolean;
constructor(private appSettings: AppSettings) {
// Setup
this.connectionEstablished = new EventEmitter<Boolean>();
this.messageReceived = new EventEmitter<any>();
this.connectionExists = false;
}
// This method gets executed from angular controller
public initialize(proxyName: string): void {
this.onlineHub = $.connection.online;
this.onlineHub.client.listUpdated = function(list: any): void {
console.log(list);
this.messageReceived.emit(list);
};
this.startConnection();
}
private startConnection(): void {
$.connection.hub.url = this.appSettings.SIGNALR_BASE_URL + '/signalr';
$.connection.hub.start()
.done((data: any) => {
console.log('SignalR Connected with: ' + data.transport.name);
this.connectionEstablished.emit(true);
this.connectionExists = true;
})
.fail((error: any) => {
console.log('SignalR could not connect: ' + error);
this.connectionEstablished.emit(false);
});
}
private registerOnServerEvents() {
this.onlineHub.client.listUpdated = function(list: any): void {
console.log(list);
this.messageReceived.emit(list);
};
}
}
I am registering my callback "listUpdated" before I run start() as the documentation says and $.connection.hub contains client.listUpdated before start() is called so it should register. But still, the OnConnected method is not called.
I fixed this issue by surrounding the OnConnected() and OnDisconnected() code in try/catch block and created a clientside method called "error" that returns eventual exceptions to the client. That way I found out that I had a Json Serialization issue.
My Hub now looks like this:
[HubName("online")]
public class OnlineHub : Hub
{
private static int userCount = 0;
public override Task OnConnected()
{
try
{
userCount++;
Clients.All.listUpdated(userCount);
}
catch (Exception exc)
{
Clients.All.error(exc);
}
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
try
{
userCount--;
Clients.All.listUpdated(userCount);
}
catch (Exception exc)
{
Clients.All.error(exc);
}
return base.OnDisconnected(stopCalled);
}
}
And I register the error callback on the js client BEFORE calling start():
this.onlineHub.client.error = (exc: any): void => {
console.log('Error occured:', exc);
};
Related
I have a working auth. flow. There is just one strange bug. When I log in with a first user I first have to refresh the page in order to get data in my view. When I log out with this user and a next user logs in I still have the data of user1 in my view. If I log out again with user2 and log in with user3 I have the data of user2 in my view. So it seems like its always one behind. I tried to fix it with with destroying the subscription, but that didn't solve the problem. Also when I think that bug begins when I have to refresh my page in order to get my first users data, to destroy the subscription cannot be the errors solution.
This is my code:
auth.service: Where I post the users credentials, give back a token, and store it and the user_id in order to get the authenticated users data in the view.
import { Storage } from '#ionic/storage';
import { JwtHelperService } from '#auth0/angular-jwt';
export const TOKEN_KEY = 'access_token';
export const USERNAME_KEY = 'username_key';
export const USER_ID = 'user_id';
...
user = null;
refreshToken = null;
authenticationState = new BehaviorSubject(false);
constructor(private storage: Storage, private helper: JwtHelperService) {
this.checkToken();
}
checkToken() {
this.storage.get(TOKEN_KEY).then(access => {
if (access) {
this.user = this.helper.decodeToken(access);
this.authenticationState.next(true);
}
});
}
apilogin(username: string, password: string) {
return this.http.post<any>(`${this.url}`, { username, password })
.pipe(
tap(res => {
this.storage.set(TOKEN_KEY, res['access']);
this.storage.set(USERNAME_KEY, username);
this.storage.set(USER_ID, this.user['user_id']);
this.user = this.helper.decodeToken(res['access']);
console.log('my user: ', this.user);
this.authenticationState.next(true);
}));
}
apilogout() {
this.storage.remove(USER_ID);
this.storage.remove(USERNAME_KEY);
this.storage.remove(TOKEN_KEY).then(() => {
this.authenticationState.next(false);
});
}
page.ts: here I get the data that I display in my view. (user service just retrieves a single user. In the end I destroy the subscription)
import { Storage } from '#ionic/storage';
import { USER_ID } from 'src/app/services/auth.service';
import { SubscriptionLike } from 'rxjs';
information = null;
id: number;
key: string;
subscription: SubscriptionLike;
constructor(private storage: Storage, private activatedRoute: ActivatedRoute,
private userService: UserService, private authService: AuthService) { }
ngOnInit() {
// How to get just the authenticated api?
if (this.authService.authenticationState) {
console.log(this.storage);
this.storage.get(USER_ID).then(val => {
this.id = val;
this.subscription = this.userService.getUserDetails(this.id).subscribe(result => {
this.information = result;
console.log(this.information);
});
});
}
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.information = null;
}
login.ts (handles my routing to the main page)
// get return url from route parameters or default to '/'
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
...
apiSubmit() {
console.log('Hello World');
this.submitted = true;
// if form is invalid => stop
if (this.loginForm.invalid) {
return;
}
this.isLoading = true;
this.loadingEl.present();
this.authService.apilogin(
this.f.username,
this.f.password)
.pipe(tap(x => this.loadingEl.dismiss()),
)
.subscribe(
data => {
console.log('0');
this.router.navigate([this.returnUrl]);
},
error => {
console.log('1');
this.loadingEl.dismiss();
this.error = error;
this.isLoading = false;
}
);
}
authGuard
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authService: AuthService
) { }
canActivate(): boolean {
return this.authService.isAuthenticated();
}
}
Instead of if (this.authService.authenticationState) { you should actually subscribe to this observable. Also we need to remember that setting or getting or removing from ionic Storage is actually asynchronous. We need to wait that the action has been performed before doing anything else. Also I suggest to instead of calling next on your observable, just call checkToken() that can do the check and then call next on the BehaviorSubject.
This should work:
Service:
import { BehaviorSubject, of, forkJoin } from 'rxjs';
import { tap, switchMap, map } from 'rxjs/operators';
// ...
private authenticationState = new BehaviorSubject(false);
public authenticationState$ = this.authenticationState.asObservable();
checkToken() {
this.storage.get(TOKEN_KEY).then(access => {
if (access) {
this.authenticationState.next(true);
} else {
this.authenticationState.next(false);
}
});
}
apilogin(username: string, password: string) {
return this.http.post<any>(`${this.url}`, { username, password }).pipe(
// switch to inner observable
switchMap((data: any) => {
// run all in paralell
return forkJoin(
this.storage.set(TOKEN_KEY, 'access'),
this.storage.set(USERNAME_KEY, 'username'),
this.storage.set(USER_ID, 'id'),
)
}),
// now we know for sure storage values have been set,
// therefore call checkToken()
tap(() => this.checkToken()),
)
}
// seems you are not currently subscribing to this function in the
// component, so I guess you can subscribe here, but I'd subscribe in comp
apilogout() {
forkJoin(
this.storage.remove(USER_ID),
this.storage.remove(REFRESH_TOKEN_KEY),
this.storage.remove(USERNAME_KEY),
this.storage.remove(TOKEN_KEY)
).subscribe(() => this.checkToken())
}
Then the component would subscribe to your BehaviorSubject and do whatever you need to do:
ngOnInit(): void {
this.sub = this.authService.authenticationState$.pipe(
switchMap((data) => {
return data ? this.storage.get(USER_ID) : of(null)
}),
switchMap((id: any) => {
this.id = id;
return id ? this.userService.getUserDetails(id) : of(null)
})
).subscribe(val => {
// do stuff!
})
}
ngOnDestroy() {
this.sub.unsubscribe();
}
PS. DON'T use any, type your data to models :)
I have a problem in Angular 2 that function continue work and does not waiting getting data from API.
HTTPHelper Class
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import { ReturnResultViewModel } from './ReturnResultViewModel';
export class HttpHelpers {
_result: any = null;
_errormsg: any = null;
_returnResultViewModel: ReturnResultViewModel;
constructor(private http: Http) {
this._returnResultViewModel = new ReturnResultViewModel();
}
postaction(param: any, path: string) {
let body = JSON.stringify(param);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(path, body, options)
.map((m:Response) => m.json())
}
}
HomeUserService Class
import 'rxjs/add/operator/map';
import { HttpHelpers } from '../../../HttpHelpers';
import { usersHome } from "././Home.users.ViewModel";
import { ReturnResultViewModel } from "../../../ReturnResultViewModel";
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
Injectable()
export class HomeUsersService extends HttpHelpers {
private _UrlLogin: string = "Accounts/getAllUsers";
constructor( #Inject(Http) private _http: Http) {
super(_http);
}
private _usersHome: usersHome[];
getAllUsers(): usersHome[]{
alert(2);
this.postaction(null, this._UrlLogin)
.subscribe(result =>
this._returnResultViewModel = result);
if (this._returnResultViewModel == null)
{
alert("NULL");
return null;
}
else {
alert("Has DATA");
this._usersHome = this._returnResultViewModel.result;
alert(this._usersHome.length)
return this._usersHome;
}
}
}
HomeUserComponent Class
export class HomeUserComponent implements OnInit{
_usersHome: [usersHome];
constructor( private _homeUsersService: HomeUsersService, private _router: Router) {}
ngOnInit() {
alert(1);
var x = this._homeUsersService.getAllUsers();
}
}
You should avoid calling subscribe in your service, unless necessary. Let your service do all the logic manipulation, and only do a subscribe in your component.
In your service, change your .subscribe to .map:
export class HomeUsersService extends HttpHelpers {
private _UrlLogin: string = "Accounts/getAllUsers";
constructor(#Inject(Http) private _http: Http) {
super(_http);
}
private _usersHome: usersHome[];
getAllUsers(): usersHome[] {
alert(2);
this.postaction(null, this._UrlLogin)
.map(result => {
if (result === null) {
alert("NULL");
return null;
} else {
alert("HAS DATA");
this._usersHome = result.result;
alert(this._usersHome.length);
return this._usersHome;
}
});
}
}
Now, since your service returns a correctly mapped Observables, you can handle the rest in your component:
export class HomeUserComponent implements OnInit {
_usersHome: [usersHome];
constructor(private _homeUsersService: HomeUsersService, private _router: Router) {
}
ngOnInit() {
alert(1);
this._homeUsersService.getAllUsers()
.subscribe(users => {
//do something to your users.
let x = users;
});
}
}
The code within the .subscribe() is asynchronous. This means it is not executed right away, but sometime later.
If you have a code x that depends on the execution of the asynchronous bit, you have to place that x inside the .subscribe() part itself or use other mechanisms, like callbacks or promises.
Try as follows:
getAllUsers(): usersHome[]{
alert(2);
this.postaction(null, this._UrlLogin)
.subscribe((result) => { // changed here
this._returnResultViewModel = result; // changed here
if (this._returnResultViewModel == null)
{
alert("NULL");
return null;
}
else {
alert("Has DATA");
this._usersHome = this._returnResultViewModel.result;
alert(this._usersHome.length)
return this._usersHome;
}
} // added this
}
I placed the code that depends on the asynchronous part (this._returnResultViewModel = result;) inside the function that is given to .subscribe().
I have a DAO class as a separate layer for getting data from my repository. I made the class Singleton and methods static.
In another class I made other service methods for transforming the data. I would like to write tests for this code but don't succeed.
How to mock the Dao repository methods?
This is what I tried so far:
// error: TS2345: Argument of type "getAllPosts" is not assignable to paramenter of type "prototype" | "getInstance"
const dao = sinon.stub(Dao, "getAllPosts");
// TypeError: Attempted to wrap undefined property getAllPosts as function
const instance = sinon.mock(Dao);
instance.expects("getAllPosts").returns(data);
export class Dao {
private noPostFound: string = "No post found with id";
private dbSaveError: string = "Error saving to database";
public static getInstance(): Dao {
if (!Dao.instance) {
Dao.instance = new Dao();
}
return Dao.instance;
}
private static instance: Dao;
private id: number;
private posts: Post[];
private constructor() {
this.posts = posts;
this.id = this.posts.length;
}
public getPostById = (id: number): Post => {
const post: Post = this.posts.find((post: Post) => {
return post.id === id;
});
if (!post) {
throw new Error(`${this.noPostFound} ${id}`);
}
else {
return post;
}
}
public getAllPosts = (): Post[] => {
return this.posts;
}
public savePost = (post: Post): void => {
post.id = this.getId();
try {
this.posts.push(post);
}
catch(e) {
throw new Error(this.dbSaveError);
}
}
}
Solved it like this:
// create an instance of Singleton
const instance = Dao.getInstance();
// mock the instance
const mock = sinon.mock(instance);
// mock "getAllPosts" method
mock.expects("getAllPosts").returns(data);
Here, I'm making an simple example using WebSocket to understand the pattern(like oberserver) of typescript & how it should be written.
The code of example is including wrapper of WebSocket and in last line of codes, there is executable code.
When I run this with node command, like following,
#tsc test.ts
#node test.js
this.m_websocket.send("Hello World!");
^
TypeError: Cannot read property 'send' of undefined
at WebSocketClient.onConnected (/Users/tehokang/Documents/Eclipse/jee/hucast.receiver/out/receiver/utils/websocket_client.js:17:25)
at WebSocket.onOpen (/Users/tehokang/Documents/Eclipse/jee/hucast.receiver/node_modules/ws/lib/EventTarget.js:120:16)
at emitNone (events.js:86:13)
at WebSocket.emit (events.js:186:7)
at WebSocket.setSocket (/Users/tehokang/Documents/Eclipse/jee/hucast.receiver/node_modules/ws/lib/WebSocket.js:167:10)
at ClientRequest._req.on (/Users/tehokang/Documents/Eclipse/jee/hucast.receiver/node_modules/ws/lib/WebSocket.js:708:10)
at emitThree (events.js:116:13)
at ClientRequest.emit (events.js:195:7)
at Socket.socketOnData (_http_client.js:443:11)
at emitOne (events.js:96:13)
I expected the member(m_websocket) can do call 'send' of WebSocket and even the member is undefined in onConnected of WebSocketClient. I couldn't understand why it happened. What should I doubt to fix this up?
Following is full example codes what I'm trying.
var WebSocket = require('ws');
export interface IWebSocketClientListener {
onConnected(url:string) : void;
onDisconnected(url:string) : void;
onReceived(url:string, message:any) : void;
onError(url:string, error:any) : void;
}
export class WebSocketClient {
private m_server_url : string;
private m_websocket : WebSocket;
private m_listener : IWebSocketClientListener;
constructor(listener : IWebSocketClientListener) {
this.m_listener = listener;
}
public connect(server_url : string ) : void {
this.m_server_url = server_url;
this.m_websocket = new WebSocket(server_url);
this.m_websocket.onopen = this.onConnected;
this.m_websocket.onclose = this.onDisconnected;
this.m_websocket.onmessage = this.onReceived;
this.m_websocket.onerror = this.onError;
}
public onConnected() : void {
this.m_websocket.send("Hello World!");
}
public onDisconnected() : void {
this.m_listener.onDisconnected(this.m_server_url);
}
public onReceived(message:any) : void {
this.m_listener.onReceived(this.m_server_url, message);
}
public onError(error:any) : void {
this.m_listener.onError(this.m_server_url, error);
}
public send(message:any) : void {
this.m_websocket.send(message);
}
};
/**
* #note Below lines are example of websocket client.
*/
var websocket_client : WebSocketClient = new WebSocketClient({
onConnected(url:string) : void {
console.log('Connected to ' + url);
},
onDisconnected(url:string) : void {
console.log('Disconnected from ' + url);
},
onReceived(url:string, message:any) : void {
console.log('Received from ' + url);
console.log(message);
},
onError(url:string, error:any) : void {
console.log(error.data);
}
} as IWebSocketClientListener);
websocket_client.connect("ws://192.168.0.30:4434");
When I replace the callbacks with followings then I could see working out well as I expected.
public onConnected = () => {
this.m_listener.onConnected(this.m_server_url);
}
public onDisconnected = () => {
this.m_listener.onDisconnected(this.m_server_url);
}
public onReceived = (message:any) => {
this.m_listener.onReceived(this.m_server_url, message);
}
public onError = (error:any) => {
this.m_listener.onError(this.m_server_url, error);
}
I'm trying to learn websocket. I started sending simple string between peers and everything was fine. Now I'm trying to send Object to my javascript client but the onmessage function never fires. Here is the code:
Java serverside:
#ServerEndpoint(value = "/event/{id}",
encoders={PositionJSONEncoder.class},
decoders={PositionJSONDecoder.class}
)
public class SocketManager {
private static ConcurrentHashMap<String, Session> users = new ConcurrentHashMap<String, Session>();
#OnMessage
public void onMessage(Position position, #PathParam("id") String id, Session session) {
log.info("user "+id+", "+position.toString());
try {
for(Entry<String, Session> entry : users.entrySet()) {
if(!entry.getKey().equals(position.getUserID()) && entry.getValue().isOpen()) {
entry.getValue().getBasicRemote().sendObject(position);
}
}
} catch (EncodeException ee) {
log.error(ee);
} catch (IOException ioe) {
log.error(ioe);
}
}
}
The serverendpoint encoder (I'll omit the decoder, server handle data correctly):
public class PositionJSONEncoder implements Encoder.Text<Position>{
private Gson gson = new Gson();
public void destroy() {}
public void init(EndpointConfig arg0) {}
public String encode(Position arg0) throws EncodeException {
return gson.toJson(arg0);
}
}
The relevant client side (AngularJS):
app.factory('socket', function() {
var service = {};
service.ws = {};
service.connect = function(userID) {
this.ws = new WebSocket("ws://localhost:8080/event/"+userID);
};
service.disconnect = function() {
if(this.ws != undefined && this.ws != null) {
this.ws.onclose();
}
};
service.ws.onopen = function() {
// TODO
};
service.ws.onmessage = function(msg) {
try {
alert('roba: '+JSON.parse(msg.data));
} catch(err) {
alert(err.message);
}
};
service.ws.onclose = function() {
// TODO
};
service.ws.onerror = function(evt) {
alert(evt.data);
};
return service;
});
The model the server send:
public class Position {
private String userID;
private Float lat;
private Float lng;
public Position() {}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public Float getLat() {
return lat;
}
public void setLat(Float lat) {
this.lat = lat;
}
public Float getLng() {
return lng;
}
public void setLng(Float lng) {
this.lng = lng;
}
#Override
public String toString() {
return userID+"["+"lat: "+lat+", "+"lng: "+lng+"]";
}
}
My pom's dependencies:
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<!-- GSON JSON serializer -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>1.7.1</version>
</dependency>
</dependencies>
The server gets the JSON object from the client correctly, but when it sends some Position object back the client, the onmessage function won't fire. I can see the Encoder is working as it returns strings like this:
{"userID":"bob","lat":2.0,"lng":2.0}
I see the websocket carry the messages:
but my javascript onmessage function always stay silent. I also implemented an onerror function but I can't get any feedback from it too. I'm using wildfly-8.0.0.Final.
Update: I implement a java websocket client. This client receive websocket frame sent by the server. Is my AngularJS client wrong?
I found what was wrong. In my javascript client I assigned a function to an undefined object. Here:
service.ws.onmessage = function(msg) {
try {
alert('roba: '+JSON.parse(msg.data));
} catch(err) {
alert(err.message);
}
};
service.ws.onmessage was undefined, that's why onmessage function never fire. I change my angular factory in this way:
app.factory('socket', function() {
var service = {};
service.ws = {};
service.connect = function(userID) {
this.ws = new WebSocket("ws://localhost:8080/event/"+userID);
this.ws.onmessage = function(msg) {
try {
alert('roba: '+JSON.parse(msg.data).lat+' :: '+JSON.parse(msg.data).lng);
} catch(err) {
alert(err.message);
}
};
this.ws.onerror = function(evt) {
alert('error: '+evt.data);
};
};
service.disconnect = function() {
if(this.ws != undefined && this.ws != null) {
this.ws.onclose();
}
};
return service;
});