rpaschoal / ng-chat

💬 A simple facebook/linkedin lookalike chat module for Angular applications.
MIT License
155 stars 92 forks source link

First message Opens window but message is not displayed #180

Closed rcampion closed 2 years ago

rcampion commented 3 years ago

First message Opens window but message is not displayed.

Testing with Chrome and Firefox.

teocto commented 3 years ago

Same problem

rcampion commented 3 years ago

I came up with a workaround.

HTML(add #ngChatInstance) <ng-chat #ngChatInstance [adapter]="adapter" [userId]="userId" [title]="title" [isCollapsed]="isCollapsed" [browserNotificationsEnabled]="browserNotificationsEnabled">

In the component that instantiates the chat adapter ... @ViewChild('ngChatInstance', { static: true }) protected ngChatInstance: IChatController;

Place the ngChatInstance in a DataSharing service.

import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { IChatController } from 'ng-chat';

@Injectable({ providedIn: 'root' }) export class DataSharingService { public ngChatInstance: BehaviorSubject = new BehaviorSubject(null); . . . constructor() { } }

Construct the ChatAdapter with the DataSharingService.

Set the value of ngChatInstance in the DataSharingService.

this.dataSharingService.ngChatInstance.next(this.ngChatInstance);

In the ChatAdapter ... before the call to onMessageReceived, call triggerOpenChatWindow(user);

                    this.ngChatInstance = this.dataSharingService.ngChatInstance.value;

                    this.ngChatInstance.triggerOpenChatWindow(user);

                    this.onMessageReceived(user, post);
julianokunha commented 3 years ago

Same problem

julianokunha commented 3 years ago

@rcampion

this.ngChatInstance = this.dataSharingService.ngChatInstance.value;

If possible send the code because your explanation was difficult to understand.

rcampion commented 3 years ago

`import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { IChatController } from 'ng-chat';

@Injectable({ providedIn: 'root' }) export class DataSharingService { public ngChatInstance: BehaviorSubject = new BehaviorSubject(null); public isActiveContactsReady: BehaviorSubject = new BehaviorSubject(false); public isUserContactsReady: BehaviorSubject = new BehaviorSubject(false); public isLoginReady: BehaviorSubject = new BehaviorSubject(false); public isUserLoggedIn: BehaviorSubject = new BehaviorSubject(false); constructor() { } } `

rcampion commented 3 years ago

<ng-chat #ngChatInstance [adapter]="adapter" [userId]="userId" [title]="title" [isCollapsed]="isCollapsed" [browserNotificationsEnabled]="browserNotificationsEnabled" [emojisEnabled]="emojisEnabled" [fileUploadUrl]=fileUploadUrl> </ng-chat>

rcampion commented 3 years ago

`import { ChatAdapter, IChatGroupAdapter, Group, Message, ChatParticipantStatus, ParticipantResponse, ChatParticipantType, IChatParticipant, MessageType, IChatController, User } from 'ng-chat'; import { Observable, of, BehaviorSubject, Subject } from 'rxjs'; import { delay, finalize, takeUntil } from "rxjs/operators"; import { ChatService } from './chat.service'; import { Injectable } from '@angular/core'; import { AppService } from './app.service'; import { Cookie } from 'ng2-cookies'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { map, catchError } from 'rxjs/operators'; import { ErrorHandlerService } from './error-handler.service'; import { UserContact } from '../interface/user-contact.model'; import { ContactsService } from './contacts.service'; import { ChatParticipant } from '../models/chatParticipant'; import { PaginationPropertySort } from '../interface/pagination'; import { environment } from 'src/environments/environment'; import { UsersService } from './users.service'; import { DataSharingService } from './datasharing.service'; import { SocketClientSixService } from './socket-client-six.service'; import { SocketClientSevenService } from './socket-client-seven.service';

import { ActiveFriendsService } from './active-friends.service';

@Injectable({ providedIn: 'root' }) export class OpenfireAdapter extends ChatAdapter implements IChatGroupAdapter {

public ngChatInstance: IChatController;

public participants: Array<ChatParticipant> = [];

public mockedHistory: Array<Message> = [];

private contactsSubject = new BehaviorSubject<UserContact[]>([]);

private loadingSubject = new BehaviorSubject<boolean>(false);

public loading$ = this.loadingSubject.asObservable();

public total = 0;

public title = "Messaging";

public userId: number;
public filter: string;
public sortProperty: string;
public sortDirection: string;
public pageIndex: number;
public pageSize: number;

messages91: any;
mysubid91 = 'my-subscription-id-091';

messages92: any;
mysubid92 = 'my-subscription-id-092';

messages33: any;
mysubid33 = 'my-subscription-id-033';

private unsubscribeSubject: Subject<void> = new Subject<void>();

xmpp: any;
isUserLoggedIn = false;
isDataReady = false;
firstLoad = true;

constructor(private http: HttpClient,
    private wsDataServiceSix: SocketClientSixService,
    private wsDataServiceSeven: SocketClientSevenService,
    private chatService: ChatService,
    private userService: UsersService,
    private appService: AppService,
    private contactsService: ContactsService,
    private dataSharingService: DataSharingService,
    private errorService: ErrorHandlerService,
    private activeFriendsService: ActiveFriendsService) {
    super();

    this.wsDataServiceSix.connect().subscribe(res => {
        console.log(res);

        this.messages91 = this.activeFriendsService
            .onUpdate(this.mysubid91)
            .pipe(takeUntil(this.unsubscribeSubject))
            .subscribe(post => {
                const isLoggedIn = this.appService.checkCredentials();
                if ((post.message === 'Session Expired')
                    || (post.message === 'Logged In')
                    || (post.message === 'Logged Out')
                    || (post.message == 'User Contacts Changed')) {
                    if (isLoggedIn) {
                        this.userId = this.userService.getUserId();
                        this.refreshFriends();
                    }
                }
            });
    });

    this.wsDataServiceSeven.connect().subscribe(res => {
        console.log(res);
        this.messages92 = this.chatService
            .onUpdate(this.mysubid92)
            .pipe(takeUntil(this.unsubscribeSubject))
            .subscribe(post => {

                console.log(post);

                const user = this.participants.find(x => x.id === post.fromId);

                const systemUser = this.userService.getCurrentUser();

                if ((post.fromId !== systemUser.contactId)
                    && (post.toId === systemUser.contactId)) {

                    this.ngChatInstance = this.dataSharingService.ngChatInstance.value;

                    this.ngChatInstance.triggerOpenChatWindow(user);

                    this.onMessageReceived(user, post);

                }

                this.mockedHistory.push(post);

            });
    });
    /*
            this.xmpp = client({
                service: "wss://www.zdslogic.com:7443/ws/",
                domain: "zdslogic",
                resource: "anonymous",
                username: "richard.campion",
                password: "ArcyAdmin8246+",
            });

            this.xmpp.start().catch(console.error);
    */
    this.dataSharingService.isUserLoggedIn.subscribe(value => {
        this.isUserLoggedIn = value;

        if (this.isUserLoggedIn) {

            const isLoggedIn = this.appService.checkCredentials();

            if (isLoggedIn) {
                this.userId = this.userService.getUserId();
            } else {
                this.dataSharingService.isActiveContactsReady.subscribe(value => {
                    this.isDataReady = value;
                    if (this.isDataReady && this.isUserLoggedIn && this.firstLoad) {
                        this.userId = this.userService.getUserId();
                        this.firstLoad = false;
                    }
                });
            }

        }

    });

}

listFriends(): Observable<ParticipantResponse[]> {
    this.dataSharingService.isUserLoggedIn.subscribe(value => {
        this.isUserLoggedIn = value;
    });

    const isLoggedIn = this.appService.checkCredentials();

    if (isLoggedIn) {
        this.userId = this.userService.getUserId();
    } else {
        this.dataSharingService.isActiveContactsReady.subscribe(value => {
            this.isDataReady = value;
            if (this.isDataReady && this.isUserLoggedIn && this.firstLoad) {
                this.userId = this.userService.getUserId();
                this.firstLoad = false;
            }
        });
    }

    this.loadingSubject.next(true);

    if (isLoggedIn) {
        this.findAllUserFriends(this.userId).subscribe(response => {
            this.participants = [];
            response.forEach(element => {
                this.participants.push(element.participant);

            });

            const responses: ParticipantResponse[] = this.participants.map(user => {
                const participantResponse = new ParticipantResponse();
                //user.status = ChatParticipantStatus.Online;
                //user.avatar = null;
                //user.participantType = ChatParticipantType.User;
                participantResponse.participant = user;
                return participantResponse;
            });
            this.friendsListChangedHandler(responses);
        });

        return this.findAllUserFriends(this.userId);

    }
    return of([]);
}

public refreshFriends() {

    const isLoggedIn = this.appService.checkCredentials();

    if (isLoggedIn) {
        this.findAllUserFriends(this.userId).subscribe(response => {
            this.participants = [];
            response.forEach(element => {
                this.participants.push(element.participant);

            });

            const responses: ParticipantResponse[] = this.participants.map(user => {
                const participantResponse = new ParticipantResponse();
                //user.status = ChatParticipantStatus.Online;
                //user.avatar = null;
                //user.participantType = ChatParticipantType.User;
                participantResponse.participant = user;
                return participantResponse;
            });
            this.friendsListChangedHandler(responses);
        });

    }

}

getMessageHistory(destinataryId: any): Observable<Message[]> {
    let mockedHistory: Array<Message> = [];
    /*
            mockedHistory = [
                {
                    fromId: MessageType.Text,
                    toId: 999,
                    message: "Hi there, here is a sample image type message:",
                    dateSent: new Date()
                },
                {
                    fromId: 1,
                    toId: 999,
                    type: MessageType.Image,
                    message: "https://66.media.tumblr.com/avatar_9dd9bb497b75_128.pnj",
                    dateSent: new Date()
                },
                {
                    fromId: MessageType.Text,
                    toId: 999,
                    message: "Type any message bellow to test this Angular module.",
                    dateSent: new Date()
                },
            ];
    */
    return of(mockedHistory).pipe(delay(2000));
}

sendMessage(message: Message): void {
    setTimeout(() => {
        let replyMessage = new Message();

        replyMessage.message = "You have typed '" + message.message + "'";
        replyMessage.dateSent = new Date();
        if (isNaN(message.toId)) {
            let group = this.participants.find(x => x.id == message.toId) as Group;

            // Message to a group. Pick up any participant for this
            let randomParticipantIndex = Math.floor(Math.random() * group.chattingTo.length);
            replyMessage.fromId = group.chattingTo[randomParticipantIndex].id;

            let messageToId = message.toId.toString();
            replyMessage.toId = messageToId;

            this.onMessageReceived(group, replyMessage);
        }
        else {
            let messageToId = message.toId.toString();
            replyMessage.toId = messageToId;
            //replyMessage.fromId = message.toId;
            let messageFromId = message.fromId.toString();
            replyMessage.toId = messageFromId;

            let user = this.participants.find(x => x.id == message.fromId);

            //this.onMessageReceived(user, replyMessage);
            /*
                            this.xmpp.on("online", async (address) => {
                                // Makes itself available
                                await this.xmpp.send(xml("presence"));

                                // Sends a chat message to itself
                                const message = xml(
                                    "message",
                                    { type: "chat", to: address },
                                    xml("body", {}, replyMessage),
                                );
                                await this.xmpp.send(message);
                            });

            */
        }
        const systemUser = this.userService.getCurrentUser();

        message.fromId = systemUser.contactId;

        this.chatService.save({ ...message, id: '1' });

    }, 1000);
}

groupCreated(group: Group): void {
    this.participants.push(group);

    this.participants = this.participants.sort((first, second) =>
        second.displayName > first.displayName ? -1 : 1
    );

    // Trigger update of friends list
    this.listFriends().subscribe(response => {
        this.onFriendsListChanged(response);
    });
}

refresh(userId: number) {

    this.userId = userId;

    this.loadUserContacts(
        this.userId,
        this.filter,
        this.sortProperty,
        this.sortDirection,
        this.pageIndex,
        this.pageSize,
    )

}

loadUserContacts(
    userId: number,
    filter: string,
    sortProperty: string,
    sortDirection: string,
    pageIndex: number,
    pageSize: number) {

    this.loadingSubject.next(true);

    const sort = new PaginationPropertySort();
    sort.property = sortProperty;
    sort.direction = sortDirection;

    this.userId = userId;
    this.filter = filter;
    this.sortProperty = sortProperty;
    this.sortDirection = sortDirection;
    this.pageIndex = pageIndex;
    this.pageSize = pageSize;

    this.contactsService.findUserContactsWithSortAndFilter(userId, filter, sort,
        pageIndex, pageSize).pipe(
            catchError(() => of([])),
            finalize(() => this.loadingSubject.next(false))
        )
        .subscribe(response => {
            this.contactsSubject.next(response.content);
            this.total = response.totalElements;
        }
        );
}

findUserFriendsWithSortAndFilter(
    userId = 0,
    filter = '', sort: PaginationPropertySort,
    pageNumber = 0, pageSize = 3): Observable<any> {

    const id: number = userId;
    const buildApiUrl = 'user/contacts/friends/' + id;
    let apiUrl = this.createCompleteRoute(buildApiUrl, environment.api_url);
    const paramsx: any = { page: pageNumber, size: pageSize };
    if (sort != null) {
        paramsx.sort = sort.property + ',' + sort.direction;
    }

    let sortTest = sort.direction;
    if (sort.property !== '') {
        sortTest = sort.property + ',' + sort.direction;
    }
    let search: string;
    if (filter !== '') {
        apiUrl = this.createCompleteRoute('contact/search', environment.api_url);
        search = 'firstName==*' + filter + '* or ' + 'lastName==*' + filter + '* or ' + 'company==*' + filter + '*';
    }
    return this.http.get(apiUrl, {
        headers: new HttpHeaders(
            {
                'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
                'Authorization': 'Bearer ' + Cookie.get('access_token')
            }),
        params: new HttpParams()
            .set('search', search)
            .set('sort', sortTest)
            .set('page', pageNumber.toString())
            .set('size', pageSize.toString())

    })

        .pipe(
            //map(res => res['content']),
            map(res => res),
            catchError(error => { this.errorService.handleError(error); return Observable.throw(error.statusText); })
        );
}

findAllUserFriends(userId = 0,): Observable<any> {

    const id: number = userId;
    const buildApiUrl = 'user/contacts/friends/' + id;
    let apiUrl = this.createCompleteRoute(buildApiUrl, environment.api_url);

    return this.http.get(apiUrl, {
        headers: new HttpHeaders(
            {
                'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
                'Authorization': 'Bearer ' + Cookie.get('access_token')
            }),

    })

        .pipe(
            //map(res => res['content']),
            map(response => response),
            catchError(error => { this.errorService.handleError(error); return Observable.throw(error.statusText); })
        );
}

private createCompleteRoute = (route: string, envAddress: string) => {
    return `${envAddress}/${route}`;
}

private generateHeaders() {
    return {

        headers: new HttpHeaders(
            {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*',
                'Authorization': 'Bearer ' + Cookie.get('access_token')
            })
    };
}

private generateHeadersNoToken() {
    return {

        headers: new HttpHeaders(
            {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            })
    };
}

}`

rcampion commented 3 years ago

`import { Component, OnInit, Output, EventEmitter, AfterViewInit, OnDestroy, ViewChild, AfterContentInit, ChangeDetectorRef } from '@angular/core'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Router, NavigationEnd } from '@angular/router'; import { User } from '../../core/models/user'; import { ChatAdapter, IChatController } from 'ng-chat'; import { Cookie } from 'ng2-cookies';

import { AppService } from 'src/app/core/services/app.service';

import { SocketClientSixService } from 'src/app/core/services/socket-client-six.service'; import { SocketClientSevenService } from 'src/app/core/services/socket-client-seven.service';

import { ChatService } from 'src/app/core/services/chat.service'; import { UsersService } from 'src/app/core/services/users.service'; import { ContactsService } from 'src/app/core/services/contacts.service'; import { DataSharingService } from 'src/app/core/services/datasharing.service'; import { ErrorHandlerService } from 'src/app/core/services/error-handler.service'; import { OpenfireAdapter } from 'src/app/core/services/openfire-adapter'; import { ActiveFriendsService } from 'src/app/core/services/active-friends.service'; import { NgChatFriendsListComponent } from 'ng-chat/ng-chat/components/ng-chat-friends-list/ng-chat-friends-list.component'; import { NgChat } from 'ng-chat/ng-chat/ng-chat.component'; import { MyAdapter } from 'src/app/core/services/my-adapter'; import { environment } from 'src/environments/environment';

@Component({ selector: 'app-header', providers: [AppService], templateUrl: './header.component.html', styleUrls: ['./header.component.scss'] }) export class HeaderComponent implements OnInit, AfterViewInit, AfterContentInit, OnDestroy {

@Output() public sidenavToggle = new EventEmitter();
currentUser: User;

@ViewChild('ngChatInstance', { static: true }) protected ngChatInstance: IChatController;

userId = 999;
title = 'Messaging';
isCollapsed = true;
browserNotificationsEnabled = false;
theme = 'dark';
emojisEnabled = true;
fileUploadUrl = '';
mySubscription: any;

isUserLoggedIn: boolean = false;

public adapter: OpenfireAdapter = null;

constructor(
    private ref: ChangeDetectorRef,
    private router: Router,
    private http: HttpClient,
    private wsDataServiceSix: SocketClientSixService,
    private wsDataServiceSeven: SocketClientSevenService,
    private chatService: ChatService,
    public userService: UsersService,
    private appService: AppService,
    private contactsService: ContactsService,
    private dataSharingService: DataSharingService,
    private errorService: ErrorHandlerService,
    private activeFriendsService: ActiveFriendsService) {

    this.fileUploadUrl = this.createCompleteRoute('uploadChatFile', environment.api_url);

    this.adapter = new OpenfireAdapter(this.http,
        this.wsDataServiceSix,
        this.wsDataServiceSeven,
        this.chatService,
        this.userService,
        this.appService,
        this.contactsService,
        this.dataSharingService,
        this.errorService,
        this.activeFriendsService);

    this.router.routeReuseStrategy.shouldReuseRoute = function () {
        return false;
    };

    this.mySubscription = this.router.events.subscribe((event) => {
        if (event instanceof NavigationEnd) {
            // Trick the Router into believing it's last link wasn't previously loaded
            this.router.navigated = false;
        }
    });

}

ngOnInit() {
    const isLoggedIn = this.appService.checkCredentials();
    const i = window.location.href.indexOf('code');
    if (!isLoggedIn && i !== -1) {

        this.appService.retrieveToken(window.location.href.substring(i + 5));

    }

    this.userService.currentUser.subscribe(
        (userData) => {

            this.currentUser = userData;
            this.userId = +this.currentUser.contactId;

        }
    );
}

ngAfterViewInit(): void {

    // Subscribe here, this will automatically update 
    // "isUserLoggedIn" whenever a change to the subject is made.
    this.dataSharingService.isUserLoggedIn.subscribe(value => {
        this.isUserLoggedIn = value;

        if (this.isUserLoggedIn) {

            this.adapter.refreshFriends();

            this.dataSharingService.ngChatInstance.next(this.ngChatInstance);

        }
    });

}

ngAfterContentInit(): void {
    this.ref.detectChanges();
}

ngOnDestroy(): void {
    if (this.mySubscription) {
        this.mySubscription.unsubscribe();
    }
}

public onToggleSidenav = () => {
    this.sidenavToggle.emit();
}

login() {
    this.userService.purgeAuth();
    this.appService.login();
}

logout(): void {
    this.userService.logout();
    this.appService.logout();
}

public redirectToRegister = () => {
    const url = `/register`;
    this.router.navigate([url]);
}

private createCompleteRoute = (route: string, envAddress: string) => {
    return `${envAddress}/${route}`;
}

private generateHeaders() {
    return {

        headers: new HttpHeaders(
            {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*',
                'Authorization': 'Bearer ' + Cookie.get('access_token')
            })
    };
}

} `

rcampion commented 3 years ago

I posted the code on this github issue.

Basically, in my code, it is the HeaderComponent that instantiates the chat adapter.

In the header.component.html see the #ngChatInstance reference for the ViewChild:

<ng-chat #ngChatInstance ...

See the @ViewChild in HeaderComponent typescript.

I have a data sharing service See DataSharingService. It gets injected when the HeaderComponent is constructed.

In the constructor of HeaderComponent, instantiate YOUR adapter with the data sharing service.

In the HeaderComponent, ngAfterViewInit(), I set the chat instance that has just been constructed into the data sharing service.

Not sure what you are doing for a server. I am using Java/Springboot with WebSocket for browser communication.

I have a ChatService that is subscribed to in the chat adapter.

In the end all I am doing is calling triggerOpenChatWindow before the onMessageReceived.

Good luck.

On Wed, May 5, 2021 at 10:37 PM Juliano Carvalho @.***> wrote:

this.ngChatInstance = this.dataSharingService.ngChatInstance.value;

If possible send the code because your explanation was difficult to understand.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/rpaschoal/ng-chat/issues/180#issuecomment-833181938, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABBF7HVIMT4USZK42A4VNN3TMH6IBANCNFSM4WDARWWQ .

-- Richard Campion @.*** http://www.richardcampion.com http://www.linkedin.com/in/richardcampion