1190 lines
38 KiB
TypeScript
1190 lines
38 KiB
TypeScript
|
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<EventJson>[];
|
||
|
|
||
|
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<EventJson>[];
|
||
|
|
||
|
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<EventJson>[];
|
||
|
|
||
|
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<EventJson>
|
||
|
>
|
||
|
)
|
||
|
)
|
||
|
),
|
||
|
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<EventJson> = 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<RoomInfo>
|
||
|
)
|
||
|
)
|
||
|
),
|
||
|
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<LoginInfo>(
|
||
|
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<LoginInfo>(
|
||
|
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<any>,
|
||
|
@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
|
||
|
) {}
|
||
|
}
|