import { CommonApiService, MassTalkSaveRequest } from '@ucap-webmessenger/api-common'; import { KEY_ENVIRONMENTS_INFO } from './../../../types/environment.type'; import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { NGXLogger } from 'ngx-logger'; import { of } from 'rxjs'; import { tap, switchMap, map, catchError, exhaustMap, withLatestFrom, concatMap } from 'rxjs/operators'; import { InfoData, Info, InfoResponse, EventProtocolService, SVC_TYPE_EVENT, SSVC_TYPE_EVENT_INFO_DATA, SSVC_TYPE_EVENT_INFO_RES, SendResponse, ReadResponse, DelResponse, CancelResponse, EventType, ReadNotification, SSVC_TYPE_EVENT_SEND_RES, SSVC_TYPE_EVENT_SEND_NOTI } from '@ucap-webmessenger/protocol-event'; import * as ChatStore from '@app/store/messenger/chat'; import * as RoomStore from '@app/store/messenger/room'; import * as SyncStore from '@app/store/messenger/sync'; import { info, infoSuccess, infoFailure, send, sendSuccess, sendFailure, appendInfoList, newInfo, sendNotification, readNotification, cancelNotification, delNotification, recallInfoList, read, readFailure, del, delFailure, delInfoList, cancel, cancelFailure, forward, forwardAfterRoomOpen, sendMass, sendMassFailure, infoMoreSuccess, infoIntervalClear } from './actions'; import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import { RoomInfo, RoomProtocolService, OpenResponse } from '@ucap-webmessenger/protocol-room'; import { LoginInfo, KEY_LOGIN_INFO, EnvironmentsInfo } from '@app/types'; import { Dictionary } from '@ngrx/entity'; import { openSuccess, openFailure } from '../room'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type'; import { StatusCode } from '@ucap-webmessenger/api'; import { CONST } from '@ucap-webmessenger/core'; @Injectable() export class Effects { selectedRoomForInfo$ = createEffect(() => this.actions$.pipe( ofType(ChatStore.selectedRoom), map(action => { return info({ roomSeq: action.roomSeq, baseSeq: 0, requestCount: CONST.EVENT_INFO_READ_COUNT }); }) ) ); info$ = createEffect( () => { let infoList: Info[]; return this.actions$.pipe( ofType(info), tap(() => { infoList = []; }), switchMap(req => { return this.eventProtocolService.info(req).pipe( map(res => { switch (res.SSVC_TYPE) { case SSVC_TYPE_EVENT_INFO_DATA: infoList.push(...(res as InfoData).infoList); break; case SSVC_TYPE_EVENT_INFO_RES: { if (req.baseSeq === 0) { this.store.dispatch( infoSuccess({ infoList, res: res as InfoResponse }) ); } else { this.store.dispatch( infoMoreSuccess({ infoList, res: res as InfoResponse }) ); } if (req.baseSeq === 0) { // 최초 이벤트 목록 조회시 SSVC_TYPE_EVENT_READ_REQ 수행. const maxSeq = Math.max.apply( Math, infoList.map(v => v.seq) ); this.store.dispatch( read({ roomSeq: req.roomSeq, lastReadSeq: Number(maxSeq) }) ); } } break; } }), catchError(error => of(infoFailure({ error }))) ); }) ); }, { dispatch: false } ); infoIntervalClear$ = createEffect( () => { return this.actions$.pipe( ofType(infoIntervalClear), withLatestFrom( this.store.pipe( select((state: any) => state.messenger.room.roomInfo as RoomInfo) ), this.store.pipe( select( (state: any) => state.messenger.event.infoList.entities as Dictionary ) ) ), map(([action, roomInfo, eventList]) => { if (roomInfo.isTimeRoom && roomInfo.timeRoomInterval > 0) { const delEventSeq: number[] = []; // tslint:disable-next-line: forin for (const key in eventList) { const event: Info = eventList[key]; if ( new Date().getTime() - new Date(event.sendDate).getTime() >= roomInfo.timeRoomInterval * 1000 ) { delEventSeq.push(event.seq); } } if (delEventSeq.length > 0) { this.store.dispatch(delInfoList({ eventSeqs: delEventSeq })); } } }) ); }, { dispatch: false } ); read$ = createEffect(() => this.actions$.pipe( ofType(read), exhaustMap(req => this.eventProtocolService.read(req).pipe( map((res: ReadResponse) => { return readNotification(res as ReadNotification); }), catchError(error => of(readFailure({ error }))) ) ) ) ); readNotification$ = createEffect( () => { return this.actions$.pipe( ofType(readNotification), withLatestFrom( this.store.pipe( select((state: any) => state.messenger.room.roomInfo as RoomInfo) ), this.store.pipe( select( (state: any) => state.account.authentication.loginRes as LoginResponse ) ) ), // map(([action, roomInfo]) => ([action.noti, roomInfo])), tap(([action, roomInfo, loginRes]) => { // 현재 오픈된 방에 대한 read noti/res 가 유입될 경우 roomUserList 의 lastReadSeq 를 갱신한다. if ( !!roomInfo && !!roomInfo.roomSeq && roomInfo.roomSeq === action.roomSeq ) { this.store.dispatch(RoomStore.updateRoomUserLastReadSeq(action)); } // noti/res 를 일으킨 주체가 본인이라면 현재 오픈된 방 여부에 상관없이 해당방에 대한 noReadCnt 를 초기화 한다. if (action.SENDER_SEQ === loginRes.userSeq) { this.store.dispatch( SyncStore.updateUnreadCount({ roomSeq: action.roomSeq, noReadCnt: 0 }) ); } }) ); }, { dispatch: false } ); send$ = createEffect(() => this.actions$.pipe( ofType(send), concatMap(action => this.eventProtocolService.send(action.req).pipe( map((res: SendResponse) => { return sendSuccess({ senderSeq: action.senderSeq, res }); }), catchError(error => of(sendFailure({ error }))) ) ) ) ); sendSuccess$ = createEffect( () => { return this.actions$.pipe( ofType(sendSuccess), tap(action => { const res = action.res; const appendInfo: Info = { seq: res.seq, type: res.eventType, senderSeq: action.senderSeq, sendDate: res.sendDate, sentMessage: res.message, receiverCount: res.receiverCount }; this.store.dispatch( newInfo({ roomSeq: res.roomSeq, info: appendInfo, SVC_TYPE: res.SVC_TYPE, SSVC_TYPE: res.SSVC_TYPE }) ); }) ); }, { dispatch: false } ); sendNotification$ = createEffect( () => { return this.actions$.pipe( ofType(sendNotification), map(action => action.noti), tap(noti => { const appendInfo: Info = { seq: noti.seq, type: noti.eventType, senderSeq: noti.SENDER_SEQ, sendDate: noti.sendDate, sentMessage: noti.message, receiverCount: noti.receiverCount }; this.store.dispatch( newInfo({ roomSeq: noti.roomSeq, info: appendInfo, SVC_TYPE: noti.SVC_TYPE, SSVC_TYPE: noti.SSVC_TYPE }) ); }) ); }, { dispatch: false } ); forward$ = createEffect( () => { return this.actions$.pipe( ofType(forward), map(action => { if (!!action.trgtRoomSeq) { this.store.dispatch( ChatStore.selectedRoom({ roomSeq: action.trgtRoomSeq }) ); this.store.dispatch( send({ senderSeq: action.senderSeq, req: { roomSeq: action.trgtRoomSeq, eventType: action.req.eventType, sentMessage: action.req.sentMessage } }) ); } else if (!!action.trgtUserSeqs && action.trgtUserSeqs.length > 0) { // 방오픈 후 대화전달. this.store.dispatch(forwardAfterRoomOpen(action)); } }) ); }, { dispatch: false } ); forwardAfterRoomOpen$ = createEffect(() => this.actions$.pipe( ofType(forwardAfterRoomOpen), exhaustMap(action => { return this.roomProtocolService .open({ divCd: 'forwardOpen', userSeqs: action.trgtUserSeqs }) .pipe( map((res: OpenResponse) => { return openSuccess({ res }); }), map(res => { this.store.dispatch( send({ senderSeq: action.senderSeq, req: { roomSeq: res.res.roomSeq, eventType: action.req.eventType, sentMessage: action.req.sentMessage } }) ); return res; }), catchError(error => of(openFailure({ error }))) ); }) ) ); sendMass$ = createEffect( () => { return this.actions$.pipe( ofType(sendMass), exhaustMap(action => { const loginResInfo: LoginResponse = this.sessionStorageService.get< LoginResponse >(KEY_LOGIN_RES_INFO); const environmentsInfo = this.sessionStorageService.get< EnvironmentsInfo >(KEY_ENVIRONMENTS_INFO); const req: MassTalkSaveRequest = { userSeq: loginResInfo.userSeq, deviceType: environmentsInfo.deviceType, token: loginResInfo.tokenString, content: action.req.sentMessage, roomSeq: action.req.roomSeq }; return this.commonApiService.massTalkSave(req).pipe( map(res => { if (res.statusCode === StatusCode.Success) { this.store.dispatch( send({ senderSeq: action.senderSeq, req: { roomSeq: res.roomSeq, eventType: EventType.MassText, sentMessage: res.returnJson } }) ); } else { this.store.dispatch(sendMassFailure({ error: res })); } }), catchError(error => of(sendMassFailure({ error }))) ); }) ); }, { dispatch: false } ); newInfo$ = createEffect( () => { return this.actions$.pipe( ofType(newInfo), withLatestFrom( this.store.pipe( select((state: any) => state.messenger.room.roomInfo as RoomInfo) ), this.store.pipe( select( (state: any) => state.messenger.sync.room.entities as Dictionary ) ) ), tap(([action, roomInfo, trgtRoomInfos]) => { // opened room :: event add if (!!roomInfo && roomInfo.roomSeq === action.roomSeq) { this.store.dispatch(appendInfoList({ info: action.info })); if ( action.SVC_TYPE === SVC_TYPE_EVENT && action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI ) { this.store.dispatch( read({ roomSeq: action.roomSeq, lastReadSeq: action.info.seq }) ); } } // not opened room :: unread count increased if ( action.SVC_TYPE === SVC_TYPE_EVENT && action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_RES ) { /** * 다른 디바이스에서 대화를 송신 할경우 RES 가 noti 로 유입될 수 있다. * 이때 unread count 를 중가하지 않는다. */ } else { if (!roomInfo || roomInfo.roomSeq !== action.roomSeq) { if (!!trgtRoomInfos && !!trgtRoomInfos[action.roomSeq]) { const noReadCnt = trgtRoomInfos[action.roomSeq].noReadCnt; this.store.dispatch( SyncStore.updateUnreadCount({ roomSeq: action.roomSeq, noReadCnt: noReadCnt + 1 }) ); } } } // 대화 > 리스트 :: finalEventMessage refresh this.store.dispatch(ChatStore.newEventMessage(action)); }) ); }, { dispatch: false } ); cancel$ = createEffect(() => this.actions$.pipe( ofType(cancel), exhaustMap(req => this.eventProtocolService.cancel(req).pipe( map((res: CancelResponse) => { return cancelNotification({ noti: res }); }), catchError(error => of(cancelFailure({ error }))) ) ) ) ); cancelNotification$ = createEffect( () => { return this.actions$.pipe( ofType(cancelNotification), withLatestFrom( this.store.pipe( select((state: any) => state.messenger.room.roomInfo as RoomInfo) ) ), tap(([action, roomInfo]) => { // 현재 방이 오픈되어 있으면 방내용 갱신 if (!!roomInfo && roomInfo.roomSeq === action.noti.roomSeq) { this.store.dispatch( recallInfoList({ eventSeq: action.noti.eventSeq }) ); } // 대화 > 리스트의 항목 갱신 const loginInfo = this.sessionStorageService.get( KEY_LOGIN_INFO ); this.store.dispatch( SyncStore.refreshRoom({ roomSeq: action.noti.roomSeq, isDetail: true, localeCode: loginInfo.localeCode }) ); }) ); }, { dispatch: false } ); del$ = createEffect(() => this.actions$.pipe( ofType(del), exhaustMap(req => this.eventProtocolService.del(req).pipe( map((res: DelResponse) => { return delNotification({ noti: res }); }), catchError(error => of(delFailure({ error }))) ) ) ) ); delNotification$ = createEffect( () => { return this.actions$.pipe( ofType(delNotification), map(action => action.noti), withLatestFrom( this.store.pipe( select((state: any) => state.messenger.room.roomInfo as RoomInfo) ) ), tap(([noti, roomInfo]) => { // 현재 방이 오픈되어 있으면 방내용 갱신 if (!!roomInfo && roomInfo.roomSeq === noti.roomSeq) { this.store.dispatch(delInfoList({ eventSeqs: [noti.eventSeq] })); } // 대화 > 리스트의 항목 갱신 const loginInfo = this.sessionStorageService.get( KEY_LOGIN_INFO ); this.store.dispatch( SyncStore.refreshRoom({ roomSeq: noti.roomSeq, isDetail: true, localeCode: loginInfo.localeCode }) ); }) ); }, { dispatch: false } ); constructor( private actions$: Actions, private store: Store, private commonApiService: CommonApiService, private eventProtocolService: EventProtocolService, private roomProtocolService: RoomProtocolService, private sessionStorageService: SessionStorageService, private logger: NGXLogger ) {} }