diff --git a/documents/업무/3월/5째주/0331.txt b/documents/업무/3월/5째주/0331.txt new file mode 100644 index 0000000..9475d24 --- /dev/null +++ b/documents/업무/3월/5째주/0331.txt @@ -0,0 +1,87 @@ +sender: + _eventCount: 14, + autoHideMunuBar: false, + id: 1, + webContents: + _eventCount: 21, + id: 3, + inPageIndex: 0, + currentIndex:2 + +focusBrowser,webContents.currentIndex > 1 + +윈도우 브라우저 상태 + _eventCount: 14, + id: 1, + webContents: + _eventCount: 24, + currentIndex: 0, + id: 3, + inPageIndex: -1, + pendingIndex: -1 + +기존 send notification + sendNotification 이벤트 + event/effect + newInfo$ 이벤트 + 현재 오픈방일 경우 + read$ 이벤트 + 읽음 처리 + 현재 오픈방 + updateRoomUserLastReadSeq$ 이벤트 + 액션 주체 와 로그인 사용자가 같은경우 + updateUnreadCount 이벤트 + 파일정보 조회 + 오픈방이 아닌 경우 + 대화방 상태 변경에 대한 처리 + + newEventMessage 이벤트 + +블러 정보 + roomSeq, info + +현재 대화방 번호 & send 노티 대화방 번호 일치 + 포커스 + newInfo + 블러 + blurInfo + 블러 정보 저장 + + 대화방 포커스 + 블러 정보가 있는 경우 + 블러 정보 초기화 후 처리 + 대화 이벤트 노티 클릭 + 블러 정보가 있는 경우 + 대화방 번호가 같은 경우 + 대화방 포커스 + 대화방 번호가 다른 경우 + 블러 정보 초기화 + + + +sendNotification + 윈도우 상태가 focus일때 + + 윈도우 상태가 blur 일때 +state + focus를 위한 state 생성 + ! eventSeq[] + roomSeq: number; + eventSeq: number; + info: Info; + +오픈 대화방 + blur 일 때 + 노티피케이션 클릭 후 이동 + native service에 클릭 이벤트 + focus state 초기화 + 앱 새로고침 + focus state 초기화 + + focus 일 때 + 대화 읽음 처리 + +blur + chat.component + 선택대화방 프로퍼티 +focus \ No newline at end of file diff --git a/documents/업무/3월/5째주/window-focus-backup/event/effect.ts b/documents/업무/3월/5째주/window-focus-backup/event/effect.ts new file mode 100644 index 0000000..b4ce852 --- /dev/null +++ b/documents/업무/3월/5째주/window-focus-backup/event/effect.ts @@ -0,0 +1,1189 @@ +import { + CommonApiService, + MassTalkSaveRequest, + FileTalkShareRequest, + FileTalkShareMultiRequest, + FileTalkShareMultiResponse +} from '@ucap-webmessenger/api-common'; +import { KEY_ENVIRONMENTS_INFO } from './../../../types/environment.type'; +import { Injectable, Inject } 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, + take +} from 'rxjs/operators'; +import moment from 'moment'; +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, + EventJson, + StickerEventJson, + FileEventJson, + BundleImageEventJson, + MassTextEventJson, + TranslationEventJson, + MassTranslationEventJson, + decodeFileEventJson, + decodeBundleImageEventJson +} 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 { + NativeService, + UCAP_NATIVE_SERVICE, + WindowState +} from '@ucap-webmessenger/native'; +import { ElectronBrowserWindowChannel } from '@ucap-webmessenger/electron-core'; + +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, + fileInfo, + fileInfoSuccess, + fileInfoFailure, + roomOpenAfterForward, + infoForSearch, + infoForSearchEnd, + infoAll, + forwardFailure +} 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 { + FileProtocolService, + SSVC_TYPE_FILE_INFO_DATA, + SSVC_TYPE_FILE_INFO_CHECK_DATA, + SSVC_TYPE_FILE_INFO_RES, + FileInfo, + FileDownloadInfo, + InfoData as FileInfoData, + InfoCheckData as FileInfoCheckData, + InfoResponse as FileInfoResponse, + FileType +} from '@ucap-webmessenger/protocol-file'; +import { environment } from '../../../../environments/environment'; +import { + AlertDialogComponent, + AlertDialogResult, + AlertDialogData, + DialogService +} from '@ucap-webmessenger/ui'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable() +export class Effects { + // selectedRoomForInfo$ = createEffect(() => + // this.actions$.pipe( + // ofType(ChatStore.selectedRoom), + // map(action => { + // return info({ + // roomSeq: action.roomSeq, + // baseSeq: 0, + // requestCount: environment.productConfig.CommonSetting.eventRequestDefaultCount + // }); + // }) + // ) + // ); + + selectedRoomForInfo$ = createEffect(() => + this.actions$.pipe( + ofType(RoomStore.infoSuccess), + map(action => { + const roomInfo = action.roomInfo; + let requestCount = + environment.productConfig.CommonSetting.eventRequestInitCount; + + // 여기까지 읽음 처리.. + if (!!roomInfo.finalEventSeq && !!roomInfo.lastReadEventSeq) { + requestCount = roomInfo.finalEventSeq - roomInfo.lastReadEventSeq; + + // 기존 요청개수보다 요청할 갯수가 적을 경우 기본값. + if ( + environment.productConfig.CommonSetting.eventRequestInitCount >= + requestCount + ) { + requestCount = + environment.productConfig.CommonSetting.eventRequestInitCount; + } else { + // 여기까지 읽음 처리를 위한 최대 요청 개수 제한. + if ( + environment.productConfig.CommonSetting + .readHereShowMaximumEventCount < requestCount + ) { + requestCount = + environment.productConfig.CommonSetting + .readHereEventRequestCount; + } else { + requestCount = requestCount + 5; + } + } + } + + return info({ + roomSeq: roomInfo.roomSeq, + baseSeq: 0, + requestCount + }); + }) + ) + ); + + 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, + remainInfo: + infoList.length === req.requestCount ? true : false + }) + ); + } else { + this.store.dispatch( + infoMoreSuccess({ + infoList, + res: res as InfoResponse, + remainInfo: + infoList.length === req.requestCount ? true : false + }) + ); + } + + 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) + }) + ); + + // File 정보 수집. + this.store.dispatch( + fileInfo({ + req: { + roomSeq: req.roomSeq, + // { 파일타입 } cf) I : 이미지 V: 동영상 F: 파일 "" 빈값이면 모든 타입을 내려줌 + type: FileType.All + } + }) + ); + } + } + break; + } + }), + catchError(error => of(infoFailure({ error }))) + ); + }) + ); + }, + { dispatch: false } + ); + + infoForSearch$ = createEffect( + () => { + let infoList: Info[]; + + return this.actions$.pipe( + ofType(infoForSearch), + tap(() => { + infoList = []; + }), + withLatestFrom( + this.store.pipe( + select( + (state: any) => + state.messenger.event.infoSearchListProcessing as boolean + ) + ) + ), + switchMap(([action, processing]) => { + return this.eventProtocolService.info(action.req).pipe( + map(async res => { + const req = action.req; + + 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, + remainInfo: + infoList.length === req.requestCount ? true : false + }) + ); + } else { + this.store.dispatch( + infoMoreSuccess({ + infoList, + res: res as InfoResponse, + remainInfo: + infoList.length === req.requestCount ? true : false + }) + ); + } + + // 검색어가 있을경우 조회된 이벤트 리스트 중 검색어를 찾고, 없으면 재귀한다. + if (!!action.searchText && infoList.length > 0) { + const searchList = infoList.filter(event => { + let contents = ''; + if (event.type === EventType.Character) { + contents = event.sentMessage; + } else if ( + event.type === EventType.Sticker && + !!event.sentMessageJson + ) { + contents = (event.sentMessageJson as StickerEventJson) + .chat; + } else if ( + event.type === EventType.File && + !!event.sentMessageJson + ) { + contents = (event.sentMessageJson as FileEventJson) + .fileName; + } else if ( + event.type === EventType.MassText && + !!event.sentMessageJson + ) { + contents = (event.sentMessageJson as MassTextEventJson) + .content; + } else if ( + event.type === EventType.Translation && + !!event.sentMessageJson + ) { + contents = (event.sentMessageJson as TranslationEventJson) + .original; + } else if ( + event.type === EventType.MassTranslation && + !!event.sentMessageJson + ) { + contents = (event.sentMessageJson as MassTranslationEventJson) + .original; + } + + return contents.indexOf(action.searchText) > -1; + }); + if ( + searchList.length === 0 && + infoList.length === action.req.requestCount && + processing + ) { + // 재귀 + this.store.dispatch( + infoForSearch({ + req: { + roomSeq: req.roomSeq, + baseSeq: infoList[0].seq, + requestCount: req.requestCount + }, + searchText: action.searchText + }) + ); + + // // 재귀하지 않는다. + // this.store.dispatch(infoForSearchEnd({})); + } else { + if (infoList.length < action.req.requestCount) { + this.store.dispatch(infoForSearchEnd({})); + + await this.dialogService.open< + AlertDialogComponent, + AlertDialogData, + AlertDialogResult + >(AlertDialogComponent, { + width: '360px', + disableClose: true, + data: { + title: '', + message: this.translateService.instant( + 'chat.noMoreEvents' + ) + } + }); + } + } + } + } + break; + } + }), + catchError(error => of(infoFailure({ error }))) + ); + }) + ); + }, + { dispatch: false } + ); + + infoAll$ = createEffect( + () => { + let infoList: Info[]; + + return this.actions$.pipe( + ofType(infoAll), + tap(() => { + infoList = []; + }), + withLatestFrom( + this.store.pipe( + select( + (state: any) => + state.messenger.event.infoSearchListProcessing as boolean + ) + ) + ), + switchMap(([params, processing]) => { + const req = params.req; + const mergedInfoList = params.infoList; + + return this.eventProtocolService.info(req).pipe( + map(async 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 (!!infoList && 0 < infoList.length) { + infoList = infoList.sort((a, b) => a.seq - b.seq); + } + if ( + infoList.length > 0 && + infoList.length >= req.requestCount && + processing + ) { + // 재귀 + this.store.dispatch( + infoAll({ + req: { + roomSeq: req.roomSeq, + baseSeq: infoList[0].seq, + requestCount: req.requestCount + }, + infoList: !!mergedInfoList + ? [...infoList, ...mergedInfoList] + : infoList + }) + ); + } else { + this.store.dispatch( + infoMoreSuccess({ + infoList: !!mergedInfoList + ? [...infoList, ...mergedInfoList] + : infoList, + res: res as InfoResponse, + remainInfo: + infoList.length === req.requestCount ? true : false + }) + ); + + this.store.dispatch(infoForSearchEnd({})); + } + } + break; + } + }), + catchError(error => of(infoFailure({ error }))) + ); + }) + ); + }, + { dispatch: false } + ); + + fileInfo$ = createEffect( + () => { + let fileInfoList: FileInfo[]; + let fileInfoCheckList: FileDownloadInfo[]; + + return this.actions$.pipe( + ofType(fileInfo), + tap(() => { + fileInfoList = []; + fileInfoCheckList = []; + }), + switchMap(action => { + return this.fileProtocolService.info(action.req).pipe( + map(res => { + switch (res.SSVC_TYPE) { + case SSVC_TYPE_FILE_INFO_DATA: + fileInfoList.push(...(res as FileInfoData).fileInfos); + break; + case SSVC_TYPE_FILE_INFO_CHECK_DATA: + fileInfoCheckList.push( + ...(res as FileInfoCheckData).fileDownloadInfos + ); + break; + case SSVC_TYPE_FILE_INFO_RES: + { + this.store.dispatch( + fileInfoSuccess({ + fileInfoList, + fileInfoCheckList, + res: res as FileInfoResponse + }) + ); + } + break; + } + }), + catchError(error => of(fileInfoFailure({ 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< + Info + > + ) + ) + ), + 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() - + moment(event.sendDate) + .toDate() + .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; + + this.store.dispatch( + newInfo({ + roomSeq: res.roomSeq, + info: res.info, + 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 => { + this.store.dispatch( + newInfo({ + roomSeq: noti.roomSeq, + info: noti.info, + SVC_TYPE: noti.SVC_TYPE, + SSVC_TYPE: noti.SSVC_TYPE + }) + ); + }) + ); + }, + { dispatch: false } + ); + + forward$ = createEffect( + () => { + return this.actions$.pipe( + ofType(forward), + tap(action => { + const loginResInfo: LoginResponse = this.sessionStorageService.get< + LoginResponse + >(KEY_LOGIN_RES_INFO); + + const environmentsInfo = this.sessionStorageService.get< + EnvironmentsInfo + >(KEY_ENVIRONMENTS_INFO); + if (!!action.trgtRoomSeq) { + // 대화전달 후 방오픈. Exist roomSeq. + if (action.req.eventType === EventType.File) { + const fileEventJson: FileEventJson = decodeFileEventJson( + action.req.sentMessage + ); + const req: FileTalkShareRequest = { + userSeq: loginResInfo.userSeq, + deviceType: environmentsInfo.deviceType, + token: loginResInfo.tokenString, + attachmentsSeq: fileEventJson.attachmentSeq.toString(), + roomSeq: action.trgtRoomSeq, + synapKey: '' + }; + this.commonApiService + .fileTalkShare(req) + .pipe( + take(1), + map(res => { + if (res.statusCode === StatusCode.Success) { + action = { + ...action, + req: { + ...action.req, + sentMessage: res.returnJson + } + }; + + this.store.dispatch(roomOpenAfterForward(action)); + } else { + this.store.dispatch(forwardFailure({ error: res })); + } + }), + catchError(error => of(forwardFailure({ error }))) + ) + .subscribe(); + } else if (action.req.eventType === EventType.BundleImage) { + const fileEventJson: BundleImageEventJson = decodeBundleImageEventJson( + action.req.sentMessage + ); + const req: FileTalkShareMultiRequest = { + userSeq: loginResInfo.userSeq, + deviceType: environmentsInfo.deviceType, + token: loginResInfo.tokenString, + attachmentsSeq: fileEventJson.attachmentSeq.toString(), + roomSeq: action.trgtRoomSeq + }; + + this.commonApiService + .fileTalkShareMulti(req) + .pipe( + take(1), + map(res => { + if (res.statusCode === StatusCode.Success) { + action = { + ...action, + req: { + ...action.req, + sentMessage: res.returnJson + } + }; + + this.store.dispatch(roomOpenAfterForward(action)); + } else { + this.store.dispatch(forwardFailure({ error: res })); + } + }), + catchError(error => of(forwardFailure({ error }))) + ) + .subscribe(); + } else { + this.store.dispatch(roomOpenAfterForward(action)); + } + } 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 => { + const loginResInfo: LoginResponse = this.sessionStorageService.get< + LoginResponse + >(KEY_LOGIN_RES_INFO); + + const environmentsInfo = this.sessionStorageService.get< + EnvironmentsInfo + >(KEY_ENVIRONMENTS_INFO); + + if (action.req.eventType === EventType.File) { + const fileEventJson: FileEventJson = decodeFileEventJson( + action.req.sentMessage + ); + const req: FileTalkShareRequest = { + userSeq: loginResInfo.userSeq, + deviceType: environmentsInfo.deviceType, + token: loginResInfo.tokenString, + attachmentsSeq: fileEventJson.attachmentSeq.toString(), + roomSeq: action.trgtRoomSeq, + synapKey: '' + }; + this.commonApiService + .fileTalkShare(req) + .pipe( + take(1), + map(resFileShare => { + if (resFileShare.statusCode === StatusCode.Success) { + action = { + ...action, + req: { + ...action.req, + sentMessage: resFileShare.returnJson + } + }; + + this.store.dispatch( + send({ + senderSeq: action.senderSeq, + req: { + roomSeq: res.res.roomSeq, + eventType: action.req.eventType, + sentMessage: action.req.sentMessage + } + }) + ); + } else { + this.store.dispatch(forwardFailure({ error: res })); + } + }), + catchError(error => of(forwardFailure({ error }))) + ) + .subscribe(); + } else if (action.req.eventType === EventType.BundleImage) { + const fileEventJson: BundleImageEventJson = decodeBundleImageEventJson( + action.req.sentMessage + ); + const req: FileTalkShareMultiRequest = { + userSeq: loginResInfo.userSeq, + deviceType: environmentsInfo.deviceType, + token: loginResInfo.tokenString, + attachmentsSeq: fileEventJson.attachmentSeq.toString(), + roomSeq: action.trgtRoomSeq + }; + + this.commonApiService + .fileTalkShareMulti(req) + .pipe( + take(1), + map(resMultiShare => { + if (resMultiShare.statusCode === StatusCode.Success) { + action = { + ...action, + req: { + ...action.req, + sentMessage: resMultiShare.returnJson + } + }; + + this.store.dispatch( + send({ + senderSeq: action.senderSeq, + req: { + roomSeq: res.res.roomSeq, + eventType: action.req.eventType, + sentMessage: action.req.sentMessage + } + }) + ); + } else { + this.store.dispatch( + forwardFailure({ error: resMultiShare }) + ); + } + }), + catchError(error => of(forwardFailure({ error }))) + ) + .subscribe(); + } else { + 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 }))) + ); + }) + ) + ); + + roomOpenAfterForward$ = createEffect(() => + this.actions$.pipe( + ofType(roomOpenAfterForward), + concatMap(action => { + return this.eventProtocolService + .send({ + roomSeq: action.trgtRoomSeq, + eventType: action.req.eventType, + sentMessage: action.req.sentMessage + }) + .pipe( + map((res: SendResponse) => { + this.store.dispatch( + newInfo({ + roomSeq: res.roomSeq, + info: res.info, + SVC_TYPE: res.SVC_TYPE, + SSVC_TYPE: res.SSVC_TYPE + }) + ); + return ChatStore.selectedRoom({ roomSeq: action.trgtRoomSeq }); + }), + catchError(error => of(sendFailure({ 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.replace(/"/g, '\\"'), + 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) { + if ( + action.SVC_TYPE === SVC_TYPE_EVENT && + action.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI + ) { + this.store.dispatch(appendInfoList({ info: action.info })); + + const windowState = this.nativeService.getWindowState(); + + if ( + !!windowState && + windowState.windowState !== WindowState.Minimized && + windowState.windowState !== WindowState.Hidden && + windowState.windowFocusState === + ElectronBrowserWindowChannel.Focus + ) { + this.store.dispatch( + read({ + roomSeq: action.roomSeq, + lastReadSeq: action.info.seq + }) + ); + } else { + // Blur 상태 일 때 + } + } + + if ( + action.info.type === EventType.File || + action.info.type === EventType.BundleImage + ) { + // File 정보 수집. + this.store.dispatch( + fileInfo({ + req: { + roomSeq: action.roomSeq, + // { 파일타입 } cf) I : 이미지 V: 동영상 F: 파일 "" 빈값이면 모든 타입을 내려줌 + type: FileType.All + } + }) + ); + } + } else { + // 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 ( + !!trgtRoomInfos && + !!trgtRoomInfos[action.roomSeq] && + action.info.type !== EventType.Join && + action.info.type !== EventType.Exit && + action.info.type !== EventType.ForcedExit + ) { + 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: false, + 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: false, + localeCode: loginInfo.localeCode + }) + ); + }) + ); + }, + { dispatch: false } + ); + + constructor( + private actions$: Actions, + private store: Store, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private commonApiService: CommonApiService, + private eventProtocolService: EventProtocolService, + private fileProtocolService: FileProtocolService, + private roomProtocolService: RoomProtocolService, + private sessionStorageService: SessionStorageService, + private translateService: TranslateService, + private dialogService: DialogService, + private logger: NGXLogger + ) {} +} diff --git a/documents/업무/3월/5째주/window-focus-backup/event/next-ucap-messenger-lf-2020-0331-1740.zip b/documents/업무/3월/5째주/window-focus-backup/event/next-ucap-messenger-lf-2020-0331-1740.zip new file mode 100644 index 0000000..388042d Binary files /dev/null and b/documents/업무/3월/5째주/window-focus-backup/event/next-ucap-messenger-lf-2020-0331-1740.zip differ