diff --git a/app/livechat/client/views/app/tabbar/visitorHistory.html b/app/livechat/client/views/app/tabbar/visitorHistory.html
index 5d31eb9af9f..25b744f51cc 100644
--- a/app/livechat/client/views/app/tabbar/visitorHistory.html
+++ b/app/livechat/client/views/app/tabbar/visitorHistory.html
@@ -5,13 +5,17 @@
{{_ "Past_Chats"}}
-
+ {{#if isLoading}}
+ {{_ "Loading..."}}
+ {{else}}
+
+ {{/if}}
diff --git a/app/livechat/client/views/app/tabbar/visitorHistory.js b/app/livechat/client/views/app/tabbar/visitorHistory.js
index b475d85102e..84d3c00b447 100644
--- a/app/livechat/client/views/app/tabbar/visitorHistory.js
+++ b/app/livechat/client/views/app/tabbar/visitorHistory.js
@@ -1,27 +1,20 @@
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
-import { Mongo } from 'meteor/mongo';
import moment from 'moment';
+import _ from 'underscore';
-import { ChatRoom } from '../../../../../models';
import './visitorHistory.html';
+import { APIClient } from '../../../../../utils/client';
-const visitorHistory = new Mongo.Collection('visitor_history');
+const ITEMS_COUNT = 50;
Template.visitorHistory.helpers({
- historyLoaded() {
- return !Template.instance().loadHistory.ready();
+ isLoading() {
+ return Template.instance().isLoading.get();
},
previousChats() {
- return visitorHistory.find({
- _id: { $ne: this.rid },
- 'v._id': Template.instance().visitorId.get(),
- }, {
- sort: {
- ts: -1,
- },
- });
+ return Template.instance().history.get();
},
title() {
@@ -38,15 +31,40 @@ Template.visitorHistory.helpers({
Template.visitorHistory.onCreated(function() {
const currentData = Template.currentData();
this.visitorId = new ReactiveVar();
+ this.isLoading = new ReactiveVar(false);
+ this.history = new ReactiveVar([]);
+ this.offset = new ReactiveVar(0);
+ this.total = new ReactiveVar(0);
- this.autorun(() => {
- const room = ChatRoom.findOne({ _id: Template.currentData().rid });
- if (room) {
+ this.autorun(async () => {
+ const { room } = await APIClient.v1.get(`rooms.info?roomId=${ currentData.rid }`);
+ if (room && room.v) {
this.visitorId.set(room.v._id);
}
});
- if (currentData && currentData.rid) {
- this.loadHistory = this.subscribe('livechat:visitorHistory', { rid: currentData.rid });
- }
+ this.autorun(async () => {
+ if (!this.visitorId.get() || !currentData || !currentData.rid) {
+ return;
+ }
+
+ const offset = this.offset.get();
+ this.isLoading.set(true);
+ const { history, total } = await APIClient.v1.get(`livechat/visitors.chatHistory/room/${ currentData.rid }/visitor/${ this.visitorId.get() }?count=${ ITEMS_COUNT }&offset=${ offset }`);
+ this.isLoading.set(false);
+ this.total.set(total);
+ this.history.set(this.history.get().concat(history));
+ });
+});
+
+Template.visitorHistory.events({
+ 'scroll .visitor-scroll': _.throttle(function(e, instance) {
+ if (e.target.scrollTop >= (e.target.scrollHeight - e.target.clientHeight)) {
+ const history = instance.history.get();
+ if (instance.total.get() <= history.length) {
+ return;
+ }
+ return instance.offset.set(instance.offset.get() + ITEMS_COUNT);
+ }
+ }, 200),
});
diff --git a/app/livechat/imports/server/rest/visitors.js b/app/livechat/imports/server/rest/visitors.js
index 28229b334eb..42b5a20b25d 100644
--- a/app/livechat/imports/server/rest/visitors.js
+++ b/app/livechat/imports/server/rest/visitors.js
@@ -2,7 +2,7 @@
import { check } from 'meteor/check';
import { API } from '../../../../api';
-import { findVisitorInfo, findVisitedPages } from '../../../server/api/lib/visitors';
+import { findVisitorInfo, findVisitedPages, findChatHistory } from '../../../server/api/lib/visitors';
API.v1.addRoute('livechat/visitors.info', { authRequired: true }, {
get() {
@@ -38,3 +38,27 @@ API.v1.addRoute('livechat/visitors.pagesVisited/:roomId', { authRequired: true }
return API.v1.success(pages);
},
});
+
+API.v1.addRoute('livechat/visitors.chatHistory/room/:roomId/visitor/:visitorId', { authRequired: true }, {
+ get() {
+ check(this.urlParams, {
+ visitorId: String,
+ roomId: String,
+ });
+ const { offset, count } = this.getPaginationItems();
+ const { sort } = this.parseJsonQuery();
+
+ const history = Promise.await(findChatHistory({
+ userId: this.userId,
+ roomId: this.urlParams.roomId,
+ visitorId: this.urlParams.visitorId,
+ pagination: {
+ offset,
+ count,
+ sort,
+ },
+ }));
+
+ return API.v1.success(history);
+ },
+});
diff --git a/app/livechat/server/api/lib/visitors.js b/app/livechat/server/api/lib/visitors.js
index 48e946d801e..ad7cb3da93e 100644
--- a/app/livechat/server/api/lib/visitors.js
+++ b/app/livechat/server/api/lib/visitors.js
@@ -1,5 +1,6 @@
import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { LivechatVisitors, Messages, LivechatRooms } from '../../../../models/server/raw';
+import { canAccessRoomAsync } from '../../../../authorization/server/functions/canAccessRoom';
export async function findVisitorInfo({ userId, visitorId }) {
if (!await hasPermissionAsync(userId, 'view-l-room')) {
@@ -24,7 +25,7 @@ export async function findVisitedPages({ userId, roomId, pagination: { offset, c
if (!room) {
throw new Error('invalid-room');
}
- const cursor = await Messages.findByRoomIdAndType(room._id, 'livechat_navigation_history', {
+ const cursor = Messages.findByRoomIdAndType(room._id, 'livechat_navigation_history', {
sort: sort || { ts: -1 },
skip: offset,
limit: count,
@@ -41,3 +42,33 @@ export async function findVisitedPages({ userId, roomId, pagination: { offset, c
total,
};
}
+
+export async function findChatHistory({ userId, roomId, visitorId, pagination: { offset, count, sort } }) {
+ if (!await hasPermissionAsync(userId, 'view-l-room')) {
+ throw new Error('error-not-authorized');
+ }
+ const room = await LivechatRooms.findOneById(roomId);
+ if (!room) {
+ throw new Error('invalid-room');
+ }
+ if (!await canAccessRoomAsync(room, { _id: userId })) {
+ throw new Error('error-not-allowed');
+ }
+
+ const cursor = LivechatRooms.findByVisitorId(visitorId, {
+ sort: sort || { ts: -1 },
+ skip: offset,
+ limit: count,
+ });
+
+ const total = await cursor.count();
+
+ const history = await cursor.toArray();
+
+ return {
+ history,
+ count: history.length,
+ offset,
+ total,
+ };
+}
diff --git a/app/livechat/server/publications/visitorHistory.js b/app/livechat/server/publications/visitorHistory.js
index 3447983113b..9f2e9296a15 100644
--- a/app/livechat/server/publications/visitorHistory.js
+++ b/app/livechat/server/publications/visitorHistory.js
@@ -4,6 +4,7 @@ import { hasPermission } from '../../../authorization';
import { LivechatRooms, Subscriptions } from '../../../models';
Meteor.publish('livechat:visitorHistory', function({ rid: roomId }) {
+ console.warn('The publication "livechat:visitorHistory" is deprecated and will be removed after version v3.0.0');
if (!this.userId) {
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:visitorHistory' }));
}
diff --git a/app/models/server/raw/LivechatRooms.js b/app/models/server/raw/LivechatRooms.js
index 896ff0ed241..b29b854a650 100644
--- a/app/models/server/raw/LivechatRooms.js
+++ b/app/models/server/raw/LivechatRooms.js
@@ -94,4 +94,13 @@ export class LivechatRoomsRaw extends BaseRaw {
}
return this.col.aggregate(params).toArray();
}
+
+ findByVisitorId(visitorId, options) {
+ const query = {
+ t: 'l',
+ 'v._id': visitorId,
+ };
+
+ return this.find(query, options);
+ }
}
diff --git a/tests/end-to-end/api/livechat/visitors.js b/tests/end-to-end/api/livechat/visitors.js
index 1e3dc5e8997..e348f849e98 100644
--- a/tests/end-to-end/api/livechat/visitors.js
+++ b/tests/end-to-end/api/livechat/visitors.js
@@ -101,4 +101,49 @@ describe('LIVECHAT - visitors', function() {
});
});
});
+
+ describe('livechat/visitors.chatHistory/room/room-id/visitor/visitor-id', () => {
+ it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => {
+ updatePermission('view-l-room', []).then(() => {
+ request.get(api('livechat/visitors.chatHistory/room/room-id/visitor/visitor-id'))
+ .set(credentials)
+ .expect('Content-Type', 'application/json')
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body.error).to.be.equal('error-not-authorized');
+ })
+ .end(done);
+ });
+ });
+ it('should return an "error" when the roomId param is invalid', (done) => {
+ updatePermission('view-l-room', ['admin']).then(() => {
+ request.get(api('livechat/visitors.chatHistory/room/room-id/visitor/visitor-id'))
+ .set(credentials)
+ .expect('Content-Type', 'application/json')
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ })
+ .end(done);
+ });
+ });
+ it('should return an array of chat history', (done) => {
+ updatePermission('view-l-room', ['admin'])
+ .then(() => {
+ request.get(api(`livechat/visitors.chatHistory/room/GENERAL/visitor/${ visitor._id }`))
+ .set(credentials)
+ .expect('Content-Type', 'application/json')
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body.history).to.be.an('array');
+ expect(res.body).to.have.property('offset');
+ expect(res.body).to.have.property('total');
+ expect(res.body).to.have.property('count');
+ })
+ .end(done);
+ });
+ });
+ });
});