Search code examples
angulartypescriptsignalrsignalr.clientasp.net-core-signalr

WebSocket failed to connect


I am trying to make a live location sharing kind of feature for my app, but I have some issues with the SignalR part.

Just a short description of what I'm trying to do here: This hub should just send the people that are sharing their location live. First I'm trying to establish a connection for the sender and when the start button is pressed the connection starts, but it keeps staying in "Connecting" state and the message of a new user that is going to send their location won't be registered, and I'm wondering what I did wrong. I followed a tutorial for a chat and tried to adapt to my needs, but probably something needs to be added or edited to work as I want.

share-live.component.ts

import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { MapService } from 'src/app/services/location/map.service';
import { AccountService } from 'src/app/shared/services';
import { ShareLiveService } from '../services/share-live.service';

@Component({
  selector: 'app-share-live',
  templateUrl: './share-live.component.html',
  styleUrls: ['./share-live.component.scss']
})
export class ShareLiveComponent implements OnDestroy{
  userPosition: google.maps.LatLngLiteral = null;
  username: string;
  stopWatch = true;
  connectedToHub = false;
  isSharing = new BehaviorSubject<boolean>(false);
  id: number;
  options: google.maps.MapOptions = {
    center: {lat: 45.9432, lng: 26.5},
    zoom: 16
  };
  markerOptions: google.maps.MarkerOptions = {
    clickable: true,
    draggable: false,
  };

  markerSettings = [
    {
        visible:true,
        shape:'Circle',
        fill:'white',
        width:3,
        animationDuration:0,
        border:{width:2,color:'#333'}
   }
  ];

  constructor(
    readonly mapService: MapService,
    private readonly shareLiveService: ShareLiveService,
    private readonly accountService: AccountService
    ) {
    this.accountService.currentUserSource
      .pipe(take(1))
      .subscribe(user => this.username = user.username);
    this.positionMap();
  }

  ngOnDestroy(): void {
    if(this.connectedToHub){
      this.stopHubConnection();
    }
  }

  start(): void {
    this.stopWatch = false;
    this.isSharing.next(!this.stopWatch);
    this.connectDisconectHub();
    this.id = navigator.geolocation.watchPosition((position) => {
      console.log(position);
      this.refreshOptions(position.coords);
      this.refreshLocation(position.coords);
    });
  }

  stop(): void {
    this.stopWatch = true;
    this.connectDisconectHub();
    if(this.id){
      navigator.geolocation.clearWatch(this.id);
      this.id = null;
      this.isSharing.next(!this.stopWatch);
    }
  }

  private connectDisconectHub(): void {
    if(!this.connectedToHub){
      this.createHubConnection();
    } else {
      this.stopHubConnection();
    }
  }

  private createHubConnection(): void {
    this.shareLiveService.createHubConnection(this.username);
    this.connectedToHub = true;
  }

  private stopHubConnection(): void {
    this.shareLiveService.eraseSharer(this.username)
    .catch(error=> console.log(error));
    this.shareLiveService.stopHubConnection();
    this.connectedToHub = false;
  }

  private positionMap(): void {
    navigator.geolocation.getCurrentPosition((position) => {
      this.refreshOptions(position.coords);
    });
  }

  private refreshOptions(coordinates: GeolocationCoordinates): void {
    this.options = {
      ...this.options,
      center: {lat: coordinates.latitude, lng: coordinates.longitude}
    };
  }

  private refreshLocation(coordinates: GeolocationCoordinates): void {
    this.userPosition = {lat: coordinates.latitude, lng: coordinates.longitude};
  }
}

share-live.service.ts

import { Injectable } from '@angular/core';
import { HttpTransportType, HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
import { BehaviorSubject, Observable } from 'rxjs';
import { LocationCoords } from 'src/app/shared/types';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class ShareLiveService {
  hubUrl = environment.hubUrl;
  livesharerSource$: Observable<{username: string}[]>;
  private hubConnection: HubConnection;
  private livesharer = new BehaviorSubject<{username: string}[]>([]);

  constructor() {
    this.livesharerSource$ = this.livesharer.asObservable();
  }

  createHubConnection(username: string): void {
    this.hubConnection = new HubConnectionBuilder()
      .withUrl(this.hubUrl + 'locationsharers',{
        skipNegotiation: true,
        transport: HttpTransportType.WebSockets
      })
      .build();
    this.hubConnection.start()
      .then(() => this.sendNewSharer(username))
      .catch(error => console.log(error));
  }

  stopHubConnection(): void {
    if(this.hubConnection){
      this.hubConnection.stop()
      .then(() => console.log('Connection stopped!'))
      .catch(error => console.log(error));
    }
  }

  async sendNewSharer(username: string): Promise<any> {
    return this.hubConnection.invoke('AddNewSharer', username)
    .catch(error => console.log(error));
  }

  async eraseSharer(username: string): Promise<any> {
    return this.hubConnection.invoke('RemoveSharer', username)
    .catch(error => console.log(error));
  }
}

enviroment.ts

export const environment = {
  production: false,
  url:'https://localhost:5001/api',
  hubUrl: 'http://localhost:5001/hubs/',
  mapbox: {
    accessToken: 'token'
  }
};

LiveLocationSharersHub.cs

using DataAccessAPI.DTOs;
using DataAccessAPI.Entities;
using DataAccessAPI.Interfaces;
using Microsoft.AspNetCore.SignalR;

namespace DataAccessAPI.SignalR
{
    public class LiveLocationSharersHub : Hub
    {
        private readonly ILiveLocationSharersRepository _liveLocationSharersRepository;
        private readonly ILiveLocationRepository _liveLocationRepository;

        public LiveLocationSharersHub(ILiveLocationSharersRepository liveLocationSharersRepository, ILiveLocationRepository liveLocationRepository)
        {
            _liveLocationSharersRepository = liveLocationSharersRepository;
            _liveLocationRepository = liveLocationRepository;
        }

        public override async Task OnConnectedAsync()
        {
            var httpContext = Context.GetHttpContext();
            await Groups.AddToGroupAsync(Context.ConnectionId, "people-sharing");

            var peopleSharing = _liveLocationSharersRepository.GetAll();

            await Clients.Group("people-sharing").SendAsync("PeopleSharingLocation", peopleSharing);
        }

        public override async Task OnDisconnectedAsync(Exception exception)
        {
            await base.OnDisconnectedAsync(exception);
        }

        public async Task AddNewSharer(string username)
        {
            if (!await _liveLocationRepository.UserExistsAsync(username))
            {
                throw new HubException("This user doesn't exist! Something is wrong.");
            }

            var newSharer = new LiveLocationSharer
            {
                Username = username
            };

            _liveLocationSharersRepository.AddSharer(newSharer);
            if (await _liveLocationRepository.SaveAllAsync())
            {
                await Clients.Group("people-sharing").SendAsync("NewSharer", username);
            }
            else
            {
                throw new HubException("Something went wrong.");
            }
        }

        public async Task RemoveSharer(string username)
        {
            if (!await _liveLocationRepository.UserExistsAsync(username))
            {
                throw new HubException("This user doesn't exist! Something is wrong.");
            }

            var sharer = await _liveLocationSharersRepository.GetLiveLocationSharersAsync(username);

            _liveLocationSharersRepository.Delete(sharer);
            if (await _liveLocationRepository.SaveAllAsync())
            {
                await Clients.Group("people-sharing").SendAsync("UserDeleted", username);
            }
            else
            {
                throw new HubException("Something went wrong.");
            }
        }

    }
}

configuration method

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebAPIv5 v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:8100"));

            app.UseAuthentication();
            app.UseAuthorization();
            app.UseSession();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHub<LiveLocationHub>("hubs/livelocation");
                endpoints.MapHub<LiveLocationSharersHub>("hubs/locationsharers");
            });
        }

Solution

  • Solved the problem, it was actually a small problem that could be solved with just a bit of a attention.

    in the enviroments.ts file the url for hubs was missing an 's', so instead of 'http' it is 'https'

    export const environment = {
      production: false,
      url:'https://localhost:5001/api',
      hubUrl: 'https://localhost:5001/hubs/',
      mapbox: {
        accessToken: 'token'
      }
    };