534 lines
14 KiB
TypeScript
534 lines
14 KiB
TypeScript
|
import { of } from 'rxjs';
|
||
|
import {
|
||
|
tap,
|
||
|
switchMap,
|
||
|
map,
|
||
|
catchError,
|
||
|
exhaustMap,
|
||
|
concatMap,
|
||
|
withLatestFrom,
|
||
|
debounceTime,
|
||
|
mergeMap
|
||
|
} from 'rxjs/operators';
|
||
|
|
||
|
import { Injectable, Inject } from '@angular/core';
|
||
|
|
||
|
import { Store, select } from '@ngrx/store';
|
||
|
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||
|
import { Dictionary } from '@ngrx/entity';
|
||
|
|
||
|
import { EventProtocolService } from '@ucap/ng-protocol-event';
|
||
|
import {
|
||
|
RoomType,
|
||
|
OpenResponse as CreateResponse,
|
||
|
Open3Response as CreateTimerResponse,
|
||
|
ExitResponse as DeleteResponse,
|
||
|
ExitAllResponse as DeleteMultiResponse
|
||
|
} from '@ucap/protocol-room';
|
||
|
import { RoomProtocolService } from '@ucap/ng-protocol-room';
|
||
|
import { FileProtocolService } from '@ucap/ng-protocol-file';
|
||
|
|
||
|
import * as RoomActions from '../room/actions';
|
||
|
import {
|
||
|
events,
|
||
|
eventsFailure,
|
||
|
eventsSuccess,
|
||
|
read,
|
||
|
readFailure,
|
||
|
readSuccess,
|
||
|
fileInfos,
|
||
|
fileInfosFailure,
|
||
|
fileInfosSuccess,
|
||
|
send,
|
||
|
sendSuccess,
|
||
|
sendFailure,
|
||
|
addEvent,
|
||
|
addEventSuccess,
|
||
|
del,
|
||
|
delNotification,
|
||
|
delFailure,
|
||
|
delEventList,
|
||
|
moreEvents,
|
||
|
forward,
|
||
|
forwardFailure,
|
||
|
forwardAfterRoomOpen,
|
||
|
roomOpenAfterForward,
|
||
|
cancel,
|
||
|
cancelFailure,
|
||
|
cancelNotification,
|
||
|
updateEventList
|
||
|
} from './actions';
|
||
|
|
||
|
import {
|
||
|
InfoRequest,
|
||
|
ReadResponse,
|
||
|
FileType,
|
||
|
EventType,
|
||
|
DelResponse,
|
||
|
SendResponse,
|
||
|
CancelResponse
|
||
|
} from '@ucap/protocol-event';
|
||
|
|
||
|
import { ModuleConfig } from '../../config/module-config';
|
||
|
import { _MODULE_CONFIG } from '../../config/token';
|
||
|
import { Chatting } from './state';
|
||
|
import { ChattingSelector, RoomSelector } from '../state';
|
||
|
import { Router } from '@angular/router';
|
||
|
import { LocaleCode } from '@ucap/core';
|
||
|
import { LoginSelector } from '@ucap/ng-store-authentication';
|
||
|
import { I18nService } from '@ucap/ng-i18n';
|
||
|
|
||
|
@Injectable()
|
||
|
export class Effects {
|
||
|
events$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(events),
|
||
|
map((action) => action.req),
|
||
|
switchMap((req) => {
|
||
|
return this.eventProtocolService.info(req).pipe(
|
||
|
map((res) => {
|
||
|
if (!!res && !!res.res) {
|
||
|
const infoList = res.infoList;
|
||
|
|
||
|
this.store.dispatch(
|
||
|
eventsSuccess({
|
||
|
eventInfoList: infoList,
|
||
|
res: res.res,
|
||
|
remainEvent:
|
||
|
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({
|
||
|
roomId: req.roomId,
|
||
|
lastReadSeq: Number(maxSeq)
|
||
|
})
|
||
|
);
|
||
|
|
||
|
// File 정보 수집.
|
||
|
this.store.dispatch(
|
||
|
fileInfos({
|
||
|
req: {
|
||
|
roomId: res.res.roomId,
|
||
|
// { 파일타입 } cf) I : 이미지 V: 동영상 F: 파일 "" 빈값이면 모든 타입을 내려줌
|
||
|
type: FileType.All
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
} else {
|
||
|
}
|
||
|
}
|
||
|
}),
|
||
|
catchError((error) =>
|
||
|
of(eventsFailure({ roomId: req.roomId, error }))
|
||
|
)
|
||
|
);
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
moreEvents$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(moreEvents),
|
||
|
mergeMap(
|
||
|
(action) =>
|
||
|
of(action).pipe(
|
||
|
withLatestFrom(
|
||
|
this.store.pipe(
|
||
|
select(ChattingSelector.eventList, action.roomId)
|
||
|
)
|
||
|
)
|
||
|
),
|
||
|
(action, latestStoreData) => latestStoreData
|
||
|
),
|
||
|
tap(([req, eventList]) => {
|
||
|
if (!!eventList && eventList.length > 0) {
|
||
|
this.store.dispatch(
|
||
|
events({
|
||
|
req: {
|
||
|
roomId: req.roomId,
|
||
|
baseSeq: eventList.sort((a, b) => a.seq - b.seq)[0].seq,
|
||
|
requestCount:
|
||
|
this.moduleConfig?.eventRequestDefaultCount || 50
|
||
|
} as InfoRequest
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
read$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(read),
|
||
|
exhaustMap((req) =>
|
||
|
this.eventProtocolService.read(req).pipe(
|
||
|
map((res: ReadResponse) => {
|
||
|
// room user lastReadEventSeq reset.
|
||
|
this.store.dispatch(readSuccess(res));
|
||
|
// room noReadCount reset.
|
||
|
this.store.dispatch(
|
||
|
RoomActions.updateUnreadCount({
|
||
|
roomId: res.roomId
|
||
|
})
|
||
|
);
|
||
|
}),
|
||
|
catchError((error) => of(readFailure({ error })))
|
||
|
)
|
||
|
)
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
fileInfos$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(fileInfos),
|
||
|
switchMap((action) => {
|
||
|
return this.fileProtocolService.info(action.req).pipe(
|
||
|
map((res) => {
|
||
|
this.store.dispatch(
|
||
|
fileInfosSuccess({
|
||
|
fileInfoList: res.fileInfos,
|
||
|
fileInfoCheckList: res.fileInfoChecks,
|
||
|
res: res.res
|
||
|
})
|
||
|
);
|
||
|
}),
|
||
|
catchError((error) =>
|
||
|
of(fileInfosFailure({ roomId: action.req.roomId, error }))
|
||
|
)
|
||
|
);
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
addEvent$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(addEvent),
|
||
|
withLatestFrom(
|
||
|
this.store.pipe(
|
||
|
select(
|
||
|
(state: any) =>
|
||
|
state.chat.chatting.chattings.entities as Dictionary<Chatting>
|
||
|
)
|
||
|
),
|
||
|
this.store.pipe(
|
||
|
select((state: any) => state.chat.chatting.activeRoomId)
|
||
|
)
|
||
|
),
|
||
|
tap(([action, chattings, activeRoomId]) => {
|
||
|
// Room in chattings state >> event add
|
||
|
if (!!chattings && !!chattings[action.roomId]) {
|
||
|
if (action.info.type === EventType.File) {
|
||
|
// Retrieve event of FileType in rooms
|
||
|
this.store.dispatch(
|
||
|
fileInfos({
|
||
|
req: {
|
||
|
roomId: action.roomId,
|
||
|
type: FileType.All
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// room > rooms :: finalEvent-Infos refresh
|
||
|
this.store.dispatch(
|
||
|
addEventSuccess({
|
||
|
roomId: action.roomId,
|
||
|
info: action.info
|
||
|
})
|
||
|
);
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
send$ = createEffect(() =>
|
||
|
this.actions$.pipe(
|
||
|
ofType(send),
|
||
|
concatMap((action) =>
|
||
|
this.eventProtocolService.send(action.req).pipe(
|
||
|
map((res) => {
|
||
|
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(
|
||
|
addEvent({
|
||
|
roomId: res.roomId,
|
||
|
info: res.info,
|
||
|
SVC_TYPE: res.SVC_TYPE,
|
||
|
SSVC_TYPE: res.SSVC_TYPE
|
||
|
})
|
||
|
);
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
/*******************************************************************
|
||
|
* [Room Action watching.]
|
||
|
*******************************************************************/
|
||
|
selectedRoomSuccess$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(RoomActions.selectedRoomSuccess),
|
||
|
debounceTime(300),
|
||
|
tap((action) => {
|
||
|
const requestCount = this.moduleConfig?.eventRequestInitCount || 50;
|
||
|
const req: InfoRequest = {
|
||
|
roomId: action.roomId,
|
||
|
baseSeq: 0,
|
||
|
requestCount
|
||
|
};
|
||
|
|
||
|
this.store.dispatch(events({ req }));
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ 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(ChattingSelector.activeRoomId))),
|
||
|
tap(([noti, activeRoomId]) => {
|
||
|
// 현재 방이 오픈되어 있으면 방내용 갱신
|
||
|
const roomId = noti.roomId;
|
||
|
|
||
|
if (!!activeRoomId && activeRoomId === roomId) {
|
||
|
this.store.dispatch(
|
||
|
delEventList({ roomId, eventSeqs: [noti.eventSeq] })
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// 대화 > 리스트의 항목 갱신
|
||
|
this.store.dispatch(
|
||
|
RoomActions.room({
|
||
|
req: {
|
||
|
roomId: noti.roomId,
|
||
|
isDetail: false,
|
||
|
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
forward$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(forward),
|
||
|
tap((action) => {
|
||
|
if (!!action.trgtRoomSeq) {
|
||
|
// 대화전달 후 방오픈. Exist roomSeq.
|
||
|
if (action.req.eventType === EventType.File) {
|
||
|
// file share request action
|
||
|
} else {
|
||
|
this.store.dispatch(roomOpenAfterForward(action));
|
||
|
}
|
||
|
} else if (!!action.trgtUserSeqs && action.trgtUserSeqs.length > 0) {
|
||
|
// 방오픈 후 대화전달.
|
||
|
this.store.dispatch(forwardAfterRoomOpen(action));
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
forwardAfterRoomOpen$ = createEffect(
|
||
|
() => {
|
||
|
let createRes: CreateResponse;
|
||
|
return this.actions$.pipe(
|
||
|
ofType(forwardAfterRoomOpen),
|
||
|
exhaustMap((actionReq) => {
|
||
|
return this.roomProtocolService
|
||
|
.open({ divCd: 'forwardOpen', userSeqs: actionReq.trgtUserSeqs })
|
||
|
.pipe(
|
||
|
map((res: CreateResponse) => {
|
||
|
createRes = res;
|
||
|
this.store.dispatch(RoomActions.createSuccess({ res }));
|
||
|
}),
|
||
|
map(() => {
|
||
|
if (actionReq.req.eventType === EventType.File) {
|
||
|
// file share request action
|
||
|
} else {
|
||
|
this.store.dispatch(
|
||
|
send({
|
||
|
senderSeq: actionReq.senderSeq,
|
||
|
req: {
|
||
|
roomId: createRes.roomId,
|
||
|
eventType: actionReq.req.eventType,
|
||
|
sentMessage: actionReq.req.sentMessage
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
}),
|
||
|
catchError((error) => of(RoomActions.createFailure({ error })))
|
||
|
);
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
roomOpenAfterForward$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(roomOpenAfterForward),
|
||
|
exhaustMap((action) => {
|
||
|
return this.eventProtocolService
|
||
|
.send({
|
||
|
roomId: action.trgtRoomSeq,
|
||
|
eventType: action.req.eventType,
|
||
|
sentMessage: action.req.sentMessage
|
||
|
})
|
||
|
.pipe(
|
||
|
map((res: SendResponse) => {
|
||
|
this.store.dispatch(
|
||
|
addEvent({
|
||
|
roomId: res.roomId,
|
||
|
info: res.info,
|
||
|
SVC_TYPE: res.SVC_TYPE,
|
||
|
SSVC_TYPE: res.SSVC_TYPE
|
||
|
})
|
||
|
);
|
||
|
this.store.dispatch(
|
||
|
RoomActions.selectedRoom({
|
||
|
roomId: res.roomId,
|
||
|
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
|
||
|
})
|
||
|
);
|
||
|
}),
|
||
|
catchError((error) => of(RoomActions.createFailure({ error })))
|
||
|
);
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
cancel$ = createEffect(() =>
|
||
|
this.actions$.pipe(
|
||
|
ofType(cancel),
|
||
|
exhaustMap((req) =>
|
||
|
this.eventProtocolService.cancel(req).pipe(
|
||
|
map((res: CancelResponse) => {
|
||
|
return delNotification({ noti: res });
|
||
|
}),
|
||
|
catchError((error) => of(delFailure({ error })))
|
||
|
)
|
||
|
)
|
||
|
)
|
||
|
);
|
||
|
|
||
|
cancelNotification$ = createEffect(
|
||
|
() => {
|
||
|
return this.actions$.pipe(
|
||
|
ofType(cancelNotification),
|
||
|
withLatestFrom(this.store.pipe(select(ChattingSelector.activeRoomId))),
|
||
|
tap(([action, activeRoomId]) => {
|
||
|
// 현재 방이 오픈되어 있으면 방내용 갱신
|
||
|
if (
|
||
|
!!activeRoomId &&
|
||
|
activeRoomId.localeCompare(action.noti.roomId) === 0
|
||
|
) {
|
||
|
this.store.dispatch(
|
||
|
updateEventList({
|
||
|
roomId: action.noti.roomId,
|
||
|
eventSeq: action.noti.eventSeq,
|
||
|
sentMessage: this.i18nService.t('event.recalled')
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// 대화 > 리스트의 항목 갱신
|
||
|
this.store.dispatch(
|
||
|
RoomActions.room({
|
||
|
req: {
|
||
|
roomId: action.noti.roomId,
|
||
|
isDetail: false,
|
||
|
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
})
|
||
|
);
|
||
|
},
|
||
|
{ dispatch: false }
|
||
|
);
|
||
|
|
||
|
constructor(
|
||
|
private actions$: Actions,
|
||
|
private store: Store<any>,
|
||
|
private router: Router,
|
||
|
@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig,
|
||
|
private roomProtocolService: RoomProtocolService,
|
||
|
private eventProtocolService: EventProtocolService,
|
||
|
private fileProtocolService: FileProtocolService,
|
||
|
private i18nService: I18nService
|
||
|
) {
|
||
|
this.i18nService.setDefaultNamespace('chat');
|
||
|
}
|
||
|
}
|