diff --git a/src/app/core/chat/usecase/message/http-listen-to-message-load-history-use-case.ts b/src/app/core/chat/usecase/message/http-listen-to-message-load-history-use-case.ts new file mode 100644 index 000000000..94d970498 --- /dev/null +++ b/src/app/core/chat/usecase/message/http-listen-to-message-load-history-use-case.ts @@ -0,0 +1,34 @@ +import { HttpErrorResponse } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Result } from "neverthrow"; +import { Observable } from "rxjs"; +import { filter, map } from "rxjs/operators"; +import { HttpAdapter } from "src/app/infra/http/adapter"; +import { MessageOutPutDTO } from "src/app/module/chat/data/dto/message/messageOutputDTO"; +import { z } from "zod"; + +const HttpListenToMessageLoadHistoryUseCaseInputSchema = z.object({ + roomId: z.string() +}) + +export type HttpListenToMessageLoadHistoryUseCaseInput = z.infer + +@Injectable({ + providedIn: 'root' +}) +export class HttpListenToMessageLoadHistoryUseCase{ + + constructor( + private http: HttpAdapter + ) {} + + execute(input: HttpListenToMessageLoadHistoryUseCaseInput): Observable { + return this.http.listen().pipe( + filter((response)=> response.isOk()), + filter((response: any)=> { + return response.value.url.includes(`Room/${input.roomId}/Messages`) + }), + map((response: any) => response.value.data as MessageOutPutDTO) + ) + } +} \ No newline at end of file diff --git a/src/app/core/chat/usecase/socket/socket-on-reconnect-use-case.ts b/src/app/core/chat/usecase/socket/socket-on-reconnect-use-case.ts index 2f369f8d8..112f140c0 100644 --- a/src/app/core/chat/usecase/socket/socket-on-reconnect-use-case.ts +++ b/src/app/core/chat/usecase/socket/socket-on-reconnect-use-case.ts @@ -1,9 +1,20 @@ +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { ISignalRService } from "src/app/infra/socket/adapter"; import { z } from "zod" const e = z.object({}) -export class SocketOnConnectUseCase { +@Injectable({ + providedIn: 'root' +}) +export class SocketOnReconnectUseCase { - constructor() {} - execute() {} + constructor(private SignalRService: ISignalRService) {} + + execute(): Observable { + + const connection = this.SignalRService.onReconnect() + return connection + } } \ No newline at end of file diff --git a/src/app/infra/database/dexie/instance/chat/schema/message.ts b/src/app/infra/database/dexie/instance/chat/schema/message.ts index a856bb370..09bf14716 100644 --- a/src/app/infra/database/dexie/instance/chat/schema/message.ts +++ b/src/app/infra/database/dexie/instance/chat/schema/message.ts @@ -18,7 +18,7 @@ export const MessageTableSchema = z.object({ wxUserId: z.number(), wxFullName: z.string(), wxeMail: z.string(), - userPhoto: z.string(), + userPhoto: z.string().nullable().optional(), }), receiverId: z.number().optional(), sending: z.boolean().optional(), diff --git a/src/app/infra/http/adapter.ts b/src/app/infra/http/adapter.ts new file mode 100644 index 000000000..dea156c06 --- /dev/null +++ b/src/app/infra/http/adapter.ts @@ -0,0 +1,14 @@ +import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; +import { HttpResult } from './type'; +import { Result } from 'neverthrow'; +import { Observable } from 'rxjs'; + + +export abstract class HttpAdapter { + abstract post(url: string, body: any): Promise, HttpErrorResponse>> + abstract get(url: string, options?: Object): Promise, HttpErrorResponse>> + abstract put(url: string, body: any): Promise, HttpErrorResponse>> + abstract patch(url: string, body?: Object): Promise, HttpErrorResponse>> + abstract delete(url: string, body?: Object): Promise, HttpErrorResponse>> + abstract listen():Observable, HttpErrorResponse>> +} \ No newline at end of file diff --git a/src/app/infra/http/http.module.ts b/src/app/infra/http/http.module.ts new file mode 100644 index 000000000..169ba0117 --- /dev/null +++ b/src/app/infra/http/http.module.ts @@ -0,0 +1,17 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { HttpAdapter } from './adapter'; +import { HttpService } from './http.service'; + +@NgModule({ + imports: [], + declarations: [], + schemas: [], + providers: [ + { + provide: HttpAdapter, + useClass: HttpService, // or MockDataService + } + ], + entryComponents: [] +}) +export class HttpModule {} diff --git a/src/app/infra/http/http.service.ts b/src/app/infra/http/http.service.ts new file mode 100644 index 000000000..86eb448f0 --- /dev/null +++ b/src/app/infra/http/http.service.ts @@ -0,0 +1,123 @@ +import { HttpErrorResponse, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ok, err, Result } from 'neverthrow'; +import { HttpResult } from './type'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class HttpService { + + private responseSubject = new BehaviorSubject, HttpErrorResponse>>(null); + + constructor(private http: HttpClient) { } + + async post(url: string, body: any): Promise, HttpErrorResponse>> { + try { + const response = await this.http.post(url, body, { observe: 'response' }).toPromise(); + const data = { + data: response.body, + status: response.status, + headers: response.headers, + url: response.url || url + } + this.responseSubject.next(ok(data)) + return ok(data); + } catch (e) { + this.responseSubject.next(err(e)) + return err(e as HttpErrorResponse); + } + } + + async get(url: string, options = {}): Promise, HttpErrorResponse>> { + try { + const response = await this.http.get(url, { ...options, observe: 'response' }).toPromise(); + + const data = { + data: response.body, + status: response.status, + headers: response.headers, + url: response.url || url + } + + this.responseSubject.next(ok(data)) + return ok(data); + } catch (e) { + this.responseSubject.next(err(e)) + return err(e as HttpErrorResponse); + } + } + + async put(url: string, body: any): Promise, HttpErrorResponse>> { + try { + const response = await this.http.put(url, body, { observe: 'response' }).toPromise(); + + const data = { + data: response.body, + status: response.status, + headers: response.headers, + url: response.url || url + } + + this.responseSubject.next(ok(data)) + return ok(data); + } catch (e) { + this.responseSubject.next(err(e)) + return err(e as HttpErrorResponse); + } + } + + async patch(url: string, body: any = {}): Promise, HttpErrorResponse>> { + try { + const response = await this.http.patch(url, body, { observe: 'response' }).toPromise(); + + const data = { + data: response.body, + status: response.status, + headers: response.headers, + url: response.url || url + } + + this.responseSubject.next(ok(data)) + return ok(data); + } catch (e) { + this.responseSubject.next(err(e)) + return err(e as HttpErrorResponse); + } + } + + async delete(url: string, body = {}): Promise, HttpErrorResponse>> { + const options = { + body: body, // Pass payload as the body of the request + observe: 'response' as 'body' + }; + + try { + const response: any = await this.http.delete(url, options).toPromise(); + + const data = { + data: response?.body, + status: response?.status, + headers: response?.headers, + url: response?.url || url + } + + this.responseSubject.next(ok(data)) + return ok(data as any); + } catch (e) { + this.responseSubject.next(err(e)) + return err(e as HttpErrorResponse); + } + } + + listen() { + return this.responseSubject.asObservable() + } +} + +export function isHttpResponse(data: any): data is HttpResponse { + return typeof data.status == 'number'; +} + diff --git a/src/app/infra/http/type.ts b/src/app/infra/http/type.ts new file mode 100644 index 000000000..81d98edc4 --- /dev/null +++ b/src/app/infra/http/type.ts @@ -0,0 +1,13 @@ +import { HttpHeaders, HttpParams } from '@angular/common/http'; + +export interface Options { + headers?: HttpHeaders + params?: HttpParams +} + +export interface HttpResult { + data: T | null; + status: number; + headers: HttpHeaders; + url: string; +} diff --git a/src/app/infra/socket/adapter.ts b/src/app/infra/socket/adapter.ts index 2b30cec63..064b4d77c 100644 --- a/src/app/infra/socket/adapter.ts +++ b/src/app/infra/socket/adapter.ts @@ -9,5 +9,6 @@ export abstract class ISignalRService { abstract join(): void; abstract getData(): Observable<{ method: string; data: T }>; abstract getConnectionState(): Observable; + abstract onReconnect(): Observable; abstract newConnection(): void; } \ No newline at end of file diff --git a/src/app/infra/socket/signalR/signal-r.service.ts b/src/app/infra/socket/signalR/signal-r.service.ts index 3d51de086..55c41134e 100644 --- a/src/app/infra/socket/signalR/signal-r.service.ts +++ b/src/app/infra/socket/signalR/signal-r.service.ts @@ -3,8 +3,7 @@ import { BehaviorSubject, Observable, Subject, timer } from 'rxjs'; import { Platform } from '@ionic/angular'; import { SignalRConnection, SocketMessage } from './signalR'; import { Plugins } from '@capacitor/core'; -import { z } from 'zod'; -import { filter, map, switchMap } from 'rxjs/operators'; +import { filter, map, skip, switchMap } from 'rxjs/operators'; import { Result } from 'neverthrow'; import { HubConnection } from '@microsoft/signalr'; import { MessageOutPutDataDTO } from 'src/app/module/chat/data/dto/message/messageOutputDTO'; @@ -127,9 +126,17 @@ export class SignalRService { return this.connection.join() } + // onReconnect() { + // const connection = this.getConnectionState() + // return connection.pipe( + // skip(1) // Skip the first value + // ) + // } + getData() { return this.sendDataSubject.asObservable() as BehaviorSubject<{method: string, data: T}> } + public getConnectionState(): Observable { return this.connectingSubject.asObservable(); } diff --git a/src/app/infra/socket/signalR/signalR.ts b/src/app/infra/socket/signalR/signalR.ts index 5e61d19c3..651b4822f 100644 --- a/src/app/infra/socket/signalR/signalR.ts +++ b/src/app/infra/socket/signalR/signalR.ts @@ -58,8 +58,6 @@ export class SignalRConnection { }) .catch(error => { console.log('Error while starting connection: ' + error); - this.connectionStateSubject.next(false); - if(this.hasConnectOnce) { setTimeout(()=> { this.attempReconnect(); diff --git a/src/app/module/chat/chat.module.ts b/src/app/module/chat/chat.module.ts index b40f6ce83..b3d7246b4 100644 --- a/src/app/module/chat/chat.module.ts +++ b/src/app/module/chat/chat.module.ts @@ -7,9 +7,22 @@ import { Subject, timer } from 'rxjs'; import { UserTypingLocalRepository } from './data/repository/typing/user-typing-local-data-source.service'; import { UserTypingRemoteRepositoryService } from './data/repository/typing/user-typing-live-data-source.service'; import { RoomService } from 'src/app/module/chat/domain/service/room.service' +import { HttpListenToMessageLoadHistoryAdapter, SocketOnReconnectAdapter } from './domain/adapter'; +import { ISignalRService } from 'src/app/infra/socket/adapter'; +import { HttpModule } from 'src/app/infra/http/http.module'; +import { HttpListenToMessageLoadHistoryUseCase } from 'src/app/core/chat/usecase/message/http-listen-to-message-load-history-use-case'; @NgModule({ - imports: [], - providers: [], + imports: [HttpModule], + providers: [ + { + provide: ISignalRService, + useClass: SignalRService, // or MockDataService + }, + { + provide: HttpListenToMessageLoadHistoryAdapter, + useClass: HttpListenToMessageLoadHistoryUseCase, // or MockDataService + }, + ], declarations: [], schemas: [], entryComponents: [] diff --git a/src/app/module/chat/data/repository/message/message-remote-data-source.service.ts b/src/app/module/chat/data/repository/message/message-remote-data-source.service.ts index eb1ba0a07..351558290 100644 --- a/src/app/module/chat/data/repository/message/message-remote-data-source.service.ts +++ b/src/app/module/chat/data/repository/message/message-remote-data-source.service.ts @@ -11,6 +11,7 @@ import { SessionStore } from 'src/app/store/session.service'; import { MessageDeleteInputDTO } from '../../dto/message/messageDeleteInputDTO'; import { InstanceId } from '../../../domain/chat-service.service'; import { v4 as uuidv4 } from 'uuid' +import { HttpAdapter } from 'src/app/infra/http/adapter'; @Injectable({ providedIn: 'root' @@ -22,6 +23,7 @@ export class MessageRemoteDataSourceService { constructor( private httpService: HttpService, private socket: SignalRService, + private http: HttpAdapter ) {} @APIReturn(MessageOutPutDTOSchema, 'post/Messages') @@ -37,6 +39,13 @@ export class MessageRemoteDataSourceService { // @APIReturn(MessageOutPutDTOSchema, 'get/Messages') async getMessagesFromRoom(id: string): DataSourceReturn { + + var a = await this.http.get(`${this.baseUrl}/Room/${id}/Messages`) + + return a.map((e) => { + return e.data + }) + return await this.httpService.get(`${this.baseUrl}/Room/${id}/Messages`); } diff --git a/src/app/module/chat/domain/adapter.ts b/src/app/module/chat/domain/adapter.ts index 134316c47..a89e10c44 100644 --- a/src/app/module/chat/domain/adapter.ts +++ b/src/app/module/chat/domain/adapter.ts @@ -1,8 +1,22 @@ +import { HttpErrorResponse } from "@angular/common/http"; +import { Result } from "neverthrow"; +import { Observable } from "rxjs"; import { MessageEntity } from "src/app/core/chat/entity/message"; +import { HttpListenToMessageLoadHistoryUseCaseInput } from "src/app/core/chat/usecase/message/http-listen-to-message-load-history-use-case"; +import { HttpResult } from "src/app/infra/http/type"; import { UseCase } from "src/app/utils/use-case-interface"; +import { MessageOutPutDTO } from "../data/dto/message/messageOutputDTO"; export abstract class ITemplateCreateAdapter implements UseCase { execute(input: MessageEntity): Promise { throw new Error("Method not implemented."); } -} \ No newline at end of file +} + +export abstract class SocketOnReconnectAdapter { + abstract execute(): Observable +} + +export abstract class HttpListenToMessageLoadHistoryAdapter { + abstract execute(input: HttpListenToMessageLoadHistoryUseCaseInput): Observable +} diff --git a/src/app/module/chat/domain/chat-service.service.ts b/src/app/module/chat/domain/chat-service.service.ts index a069d2df5..61a4ec746 100644 --- a/src/app/module/chat/domain/chat-service.service.ts +++ b/src/app/module/chat/domain/chat-service.service.ts @@ -4,7 +4,7 @@ import { SessionStore } from 'src/app/store/session.service'; import { MessageReactionInput, MessageReactionUseCaseService } from 'src/app/module/chat/domain/use-case/message/message-reaction-by-id-use-case.service'; import { MessageUpdateInput, MessageUpdateUseCaseService } from 'src/app/module/chat/domain/use-case/message/message-update-by-id-use-case.service'; import { MemberAdminUseCaseService, MemberSetAdminDTO } from 'src/app/module/chat/domain/use-case/member/member-admin-use-case.service'; -import { MessageCreateUseCaseService, MessageEnum } from 'src/app/module/chat/domain/use-case/message/message-create-use-case.service'; +import { MessageCreateUseCaseService } from 'src/app/module/chat/domain/use-case/message/message-create-use-case.service'; import { SignalRService } from 'src/app/infra/socket/signalR/signal-r.service'; import { SocketMessageDeleteUseCaseService } from 'src/app/module/chat/domain/use-case/socket/socket-message-delete-use-case.service'; import { SocketMessageUpdateUseCaseService } from 'src/app/module/chat/domain/use-case/socket/socket-message-update-use-case.service'; @@ -20,7 +20,6 @@ import { CreateRoomUseCaseService } from './use-case/room/room-create-use-case.s import { RoomLeaveUseCase } from './use-case/room/room-leave-by-id-use-case.service'; import { SyncAllRoomMessagesService } from './use-case/message/sync-all-room-messages.service'; import { ListenSendMessageUseCase } from './use-case/message/listen-send-message.service' -import { MessageReadAtByIdUseCaseService } from './use-case/message/message-read-at-by-id-use-case.service' import { SendLocalMessagesUseCaseService } from './use-case/message/messages-send-offline-use-case.service' import { RemoveMemberUseCaseService } from './use-case/member/-use-case.service' import { AddMemberUseCaseService } from './use-case/member/member-add-use-case.service' @@ -40,10 +39,10 @@ import { AddMemberToRoomInputDTO } from '../data/dto/room/addMemberToRoomInputDt import { RoomUpdateInputDTO } from '../data/dto/room/roomUpdateInputDTO'; import { RoomType } from "src/app/core/chat/entity/group"; import { sendReadAt } from "src/app/module/chat/data/repository/message/message-live-signalr-data-source.service"; - +import { HttpListenToMessageLoadHistoryAdapter } from './adapter' +import { HttpListenToMessageLoadHistoryUseCaseInput } from 'src/app/core/chat/usecase/message/http-listen-to-message-load-history-use-case'; export const InstanceId = uuidv4(); - @Injectable({ providedIn: 'root' }) @@ -76,11 +75,11 @@ export class ChatServiceService { private AddMemberUseCaseService: AddMemberUseCaseService, private UpdateRoomByIdUseCaseService: UpdateRoomByIdUseCaseService, private RemoveMemberUseCaseService: RemoveMemberUseCaseService, - private MessageReadAtByIdUseCaseService: MessageReadAtByIdUseCaseService, private SendLocalMessagesUseCaseService: SendLocalMessagesUseCaseService, private MessageMarkAsReadUseCaseService: MessageMarkAsReadUseCaseService, private SocketConnectUseCaseService: SocketConnectUseCaseService, - private MessageMarkAllMessageAsReadByRoomIdService: MessageMarkAllMessageAsReadByRoomIdService + private MessageMarkAllMessageAsReadByRoomIdService: MessageMarkAllMessageAsReadByRoomIdService, + private HttpListenToMessageLoadHistory: HttpListenToMessageLoadHistoryAdapter, ) { this.messageLiveSignalRDataSourceService.getMessageDelete() .pipe() @@ -184,6 +183,9 @@ export class ChatServiceService { } + listenToMessageLoadHistory(input: HttpListenToMessageLoadHistoryUseCaseInput) { + return this.HttpListenToMessageLoadHistory.execute(input) + } getRoomList() { return this.GetRoomListUseCaseService.execute() diff --git a/src/app/services/http/adapter.ts b/src/app/services/http/adapter.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/services/http/http-service.service.spec.ts b/src/app/services/http/http-service.service.spec.ts deleted file mode 100644 index 63f333708..000000000 --- a/src/app/services/http/http-service.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { HttpServiceService } from './http-service.service'; - -describe('HttpServiceService', () => { - let service: HttpServiceService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(HttpServiceService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/app/ui/chat/component/messages/messages.page.ts b/src/app/ui/chat/component/messages/messages.page.ts index af323b013..2a0ae0b71 100644 --- a/src/app/ui/chat/component/messages/messages.page.ts +++ b/src/app/ui/chat/component/messages/messages.page.ts @@ -140,6 +140,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy messageUpdateSubject: Subscription messageSendSubject: Subscription messageTypingSubject: Subscription + messageOnReconnectSubject: Subscription messages1: {[key: string]: MessageEntity[]} = {} MessageAttachmentFileType = MessageAttachmentFileType @@ -228,6 +229,8 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy }) as any + + } messageStatus(message: MessageEntity) { @@ -268,6 +271,22 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy this.sendReadMessage() }, 1000) + this.messageOnReconnectSubject?.unsubscribe() + this.messageOnReconnectSubject = this.chatServiceService.listenToMessageLoadHistory({roomId: this.roomId}).subscribe((messages) => { + + console.log('message', messages); + + for(const message of messages.data) { + const found = this.messages1[this.roomId].find((e) => e.id == message.id) + + if(!found) { + const msg = new MessageEntity() + Object.assign(msg, message) + this.messages1[this.roomId].push(msg) + } + } + }) + } async loadAttachment() { @@ -424,6 +443,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy }); } + onDisConnect() {} toggleEmojiPicker(message: MessageEntity) { if (this.selectedMessage === message) {