Browse Source

join room, check-in, check-out, socket disconnect enforced, I wrote code.

develop
Rob Colbert 1 year ago
parent
commit
a2cd338d68
  1. 17
      app/controllers/chat.js
  2. 7
      app/models/chat-room.js
  3. 139
      app/services/chat.js
  4. 2
      app/views/chat/components/member-list-item-standalone.pug
  5. 99
      app/views/chat/components/member-list-item.pug
  6. 92
      app/views/chat/room/view.pug
  7. 1
      client/css/dtp-site.less
  8. 23
      client/css/site/button.less
  9. 78
      client/css/site/stage.less
  10. BIN
      client/img/default-poster.png
  11. 171
      client/js/chat-client.js
  12. 2
      lib/client/js/dtp-log.js
  13. 14
      lib/client/js/dtp-socket.js
  14. 7
      lib/site-ioserver.js
  15. 4
      webpack.config.js

17
app/controllers/chat.js

@ -50,6 +50,12 @@ export default class ChatController extends SiteController {
this.getRoomCreateView.bind(this), this.getRoomCreateView.bind(this),
); );
router.get(
'/room/:roomId/join',
// limiterService.create(limiterService.config.chat.getRoomView),
this.getRoomJoin.bind(this),
);
router.get( router.get(
'/room/:roomId', '/room/:roomId',
// limiterService.create(limiterService.config.chat.getRoomView), // limiterService.create(limiterService.config.chat.getRoomView),
@ -86,6 +92,17 @@ export default class ChatController extends SiteController {
res.render('chat/room/create'); res.render('chat/room/create');
} }
async getRoomJoin (req, res, next) {
const { chat: chatService } = this.dtp.services;
try {
await chatService.joinRoom(res.locals.room, req.user);
res.status(200).json({ success: true, room: res.locals.room });
} catch (error) {
this.log.error('failed to join chat room', { error });
return next(error);
}
}
async getRoomView (req, res) { async getRoomView (req, res) {
res.locals.currentView = 'chat-room'; res.locals.currentView = 'chat-room';
res.render('chat/room/view'); res.render('chat/room/view');

7
app/models/chat-room.js

@ -10,7 +10,7 @@ import mongoose from 'mongoose';
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const ChatRoomSchema = new Schema({ const ChatRoomSchema = new Schema({
created: { type: Date, default: Date.now, required: true, index: 1, expires: '7d' }, created: { type: Date, default: Date.now, required: true, index: 1 },
lastActivity: { type: Date, index: -1 }, lastActivity: { type: Date, index: -1 },
owner: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, owner: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' },
name: { type: String, required: true }, name: { type: String, required: true },
@ -18,7 +18,12 @@ const ChatRoomSchema = new Schema({
capacity: { type: Number, required: true, min: MIN_ROOM_CAPACITY, max: MAX_ROOM_CAPACITY }, capacity: { type: Number, required: true, min: MIN_ROOM_CAPACITY, max: MAX_ROOM_CAPACITY },
invites: { type: [Schema.ObjectId], select: false }, invites: { type: [Schema.ObjectId], select: false },
members: { type: [Schema.ObjectId], select: false }, members: { type: [Schema.ObjectId], select: false },
present: { type: [Schema.ObjectId], select: false },
banned: { type: [Schema.ObjectId], select: false }, banned: { type: [Schema.ObjectId], select: false },
stats: {
memberCount: { type: Number, default: 0, min: 0, max: MAX_ROOM_CAPACITY, required: true },
presentCount: { type: Number, default: 0, min: 0, max: MAX_ROOM_CAPACITY, required: true },
},
}); });
export default mongoose.model('ChatRoom', ChatRoomSchema); export default mongoose.model('ChatRoom', ChatRoomSchema);

139
app/services/chat.js

@ -10,6 +10,8 @@ const ChatRoom = mongoose.model('ChatRoom');
const ChatMessage = mongoose.model('ChatMessage'); const ChatMessage = mongoose.model('ChatMessage');
// const ChatRoomInvite = mongoose.model('ChatRoomInvite'); // const ChatRoomInvite = mongoose.model('ChatRoomInvite');
import numeral from 'numeral';
import { SiteService, SiteError } from '../../lib/site-lib.js'; import { SiteService, SiteError } from '../../lib/site-lib.js';
import { MAX_ROOM_CAPACITY } from '../models/lib/constants.js'; import { MAX_ROOM_CAPACITY } from '../models/lib/constants.js';
@ -24,6 +26,11 @@ export default class ChatService extends SiteService {
async start ( ) { async start ( ) {
const { user: userService } = this.dtp.services; const { user: userService } = this.dtp.services;
this.templates = {
memberListItem: this.loadViewTemplate('chat/components/member-list-item-standalone.pug'),
};
this.populateChatRoom = [ this.populateChatRoom = [
{ {
path: 'owner', path: 'owner',
@ -45,6 +52,7 @@ export default class ChatService extends SiteService {
} }
room.capacity = MAX_ROOM_CAPACITY; room.capacity = MAX_ROOM_CAPACITY;
room.members = [owner._id]; room.members = [owner._id];
room.stats.memberCount = 1;
await room.save(); await room.save();
@ -60,12 +68,141 @@ export default class ChatService extends SiteService {
} }
async joinRoom (room, user) { async joinRoom (room, user) {
await ChatRoom.updateOne( const roomData = await ChatRoom.findOne({ _id: room._id, banned: user._id }).lean();
if (roomData) {
throw new SiteError(401, 'You are banned from this chat room');
}
const response = await ChatRoom.updateOne(
{ _id: room._id }, { _id: room._id },
{ {
$push: { members: user._id }, $push: { members: user._id },
}, },
); );
this.log.debug('joinRoom complete', { response });
return response;
}
async chatRoomCheckIn (room, member) {
const NOW = new Date();
// indicate presence in the chat room's Mongo document
const roomData = await ChatRoom.findOneAndUpdate(
{ _id: room._id },
{
$addToSet: { present: member._id },
$inc: { 'stats.presentCount': 1 },
},
{
new: true,
},
);
this.log.debug('member checking into chat room', {
room: {
_id: room._id,
name: room.name,
presentCount: roomData.stats.presentCount,
},
member: {
_id: member._id,
username: member.username,
},
});
/*
* Broadcast a control message to all room members that a new member has
* joined the room.
*/
const displayList = this.createDisplayList('chat-control');
displayList.removeElement(`ul#present-members li[data-member-id="${member._id}"]`);
displayList.addElement(
`ul#chat-active-members[data-room-id="${room._id}"]`,
'afterBegin',
this.templates.memberListItem({ room, member }),
);
displayList.setTextContent(
`.chat-present-count`,
numeral(roomData.stats.presentCount).format('0,0'),
);
const systemMessage = {
created: NOW.toISOString(),
content: `<a href="/member/${member.username}", uk-tooltip="Visit ${member.username}">@${member.username}</a> has connected to the room.`,
};
this.dtp.emitter.to(room._id.toString()).emit('chat-control', { displayList, systemMessages: [systemMessage] });
}
async chatRoomCheckOut (room, member) {
const NOW = new Date();
const roomData = await ChatRoom.findOneAndUpdate(
{ _id: room._id },
{
$pull: { present: member._id },
$inc: { 'stats.presentCount': -1 },
},
{
new: true,
},
);
this.log.debug('member checking out of chat room', {
room: {
_id: room._id,
name: room.name,
presentCount: roomData.stats.presentCount,
},
member: {
_id: member._id,
username: member.username,
},
});
/*
* Broadcast a control message to all room members that a new member has
* joined the room.
*/
const displayList = this.createDisplayList('chat-control');
displayList.removeElement(`ul#chat-active-members li[data-member-id="${member._id}"]`);
displayList.setTextContent(
`.chat-present-count`,
numeral(roomData.stats.presentCount).format('0,0'),
);
const systemMessage = {
created: NOW.toISOString(),
content: `<a href="/member/${member.username}", uk-tooltip="Visit ${member.username}">@${member.username}</a> has connected to the room.`,
};
this.dtp.emitter.to(room._id.toString()).emit('chat-control', { displayList, systemMessages: [systemMessage] });
}
async checkRoomMember (room, member) {
if (room.owner._id.equals(member._id)) {
return true;
}
const search = { _id: room._id, members: member._id };
const checkRoom = await ChatRoom.findOne(search).select('name').lean();
if (!checkRoom) {
throw new SiteError(403, `You are not a member of ${checkRoom.name}`);
}
return true;
}
async isRoomMember (room, member) {
if (room.owner._id.equals(member._id)) {
return true;
}
const search = { _id: room._id, members: member._id };
const checkRoom = await ChatRoom.findOne(search).select('name').lean();
return !!checkRoom;
} }
async leaveRoom (room, user) { async leaveRoom (room, user) {

2
app/views/chat/components/member-list-item-standalone.pug

@ -0,0 +1,2 @@
include member-list-item
+renderChatMemberListItem (room, member, { isHost, isGuest })

99
app/views/chat/components/member-list-item.pug

@ -0,0 +1,99 @@
include ../../user/components/profile-picture
mixin renderChatMemberListItem (room, member, options)
-
options = Object.assign({
isHost: false,
isGuest: false,
}, options);
var isRoomOwner = room.owner._id.equals(member._id);
var isSystemMod = user && user.flags && (user.flags.isAdmin || user.flags.isModerator);
var memberName = member.displayName || member.username;
// var isChannelMod = user && Array.isArray(message.channel.moderators) && !!message.channel.moderators.find((moderator) => moderator._id.equals(user._id));
li(data-member-id= member._id, data-member-username= member.username)
div(uk-grid).uk-grid-collapse.uk-flex-middle.member-list-item
.uk-width-auto
img(src='/img/default-member.png').member-profile-icon
.uk-width-expand.uk-text-small
a(href=`/member/${member.username}`, uk-tooltip={ title: `Visit ${member.username}`}).uk-link-reset
.member-display-name= member.displayName || member.username
.member-username @#{member.username}
.uk-width-auto.chat-user-menu
button(type="button").dtp-button-dropdown
i.fas.fa-ellipsis-v
div(
data-room-id= room._id,
uk-dropdown={ animation: "uk-animation-scale-up", duration: 250 },
data-mode="click",
).dtp-chatmsg-menu
ul.uk-nav.uk-dropdown-nav
li.uk-nav-header= member.username
li
a(
href="",
data-username= member.username,
onclick="return dtp.app.mentionChatUser(event);",
) Mention
li
a(
href="",
data-room-id= room._id,
data-room-name= room.name,
data-user-id= member._id,
data-username= member.username,
onclick="return dtp.app.muteChatUser(event);",
) Mute
if (options.isHost || options.isGuest) && !isRoomOwner
li.uk-nav-divider
if options.isHost
li
a(
href,
data-environment="ChatRoom",
data-room-id= room._id,
data-room-name= room.name,
data-user-id= member._id,
data-username= member.username,
data-display-name= member.displayName,
onclick="return dtp.app.removeRoomHost(event);",
) Remove host
if options.isGuest
li
a(
href,
data-environment="ChatRoom",
data-room-id= room._id,
data-room-name= room.name,
data-user-id= member._id,
data-username= member.username,
data-display-name= member.displayName,
onclick="return dtp.app.removeRoomGuest(event);",
) Remove guest
if isSystemMod || isChannelMod
li.uk-nav-divider
li
a(
href="",
data-environment="ChatRoom",
data-room-id= room._id,
data-room-name= room.name,
data-user-id= member._id,
data-username= member.username,
data-display-name= member.displayName,
onclick="return dtp.app.confirmBanUserFromEnvironment(event);",
) Ban from room
if isSystemMod
li
a(
href="",
data-room-id= room._id,
data-room-name= room.name,
data-user-id= member._id,
data-username= member.username,
data-display-name= member.displayName,
onclick="return dtp.adminApp.confirmBanUser(event);",
) Ban from #{site.name}

92
app/views/chat/room/view.pug

@ -27,7 +27,7 @@ block view-content
mixin renderLiveMember (member) mixin renderLiveMember (member)
div(data-user-id= member._id, data-username= member.username).stage-live-member div(data-user-id= member._id, data-username= member.username).stage-live-member
video(src="/static/video/gdl-crush.mp4", autoplay, muted, loop, disablepictureinpicture, disableremoteplayback) video(poster="/img/default-poster.png", disablepictureinpicture, disableremoteplayback)
.uk-flex.live-meta.no-select .uk-flex.live-meta.no-select
.live-username.uk-width-expand .live-username.uk-width-expand
.uk-text-truncate= member.displayName || member.username .uk-text-truncate= member.displayName || member.username
@ -38,30 +38,20 @@ block view-content
i.fas.fa-cog i.fas.fa-cog
.dtp-chat-stage .dtp-chat-stage
.chat-sidebar #room-member-panel.chat-sidebar
.chat-stage-header Active Members .chat-stage-header
div(uk-grid).uk-grid-small.uk-grid-middle
.uk-width-expand
.uk-text-truncate Active Members
.uk-width-auto
.chat-present-count.uk-text-small ---
.sidebar-panel .sidebar-panel
ul(id="chat-active-members").uk-list.uk-list-collapse ul(id="chat-active-members", data-room-id= room._id).uk-list.uk-list-collapse
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
+renderMemberListEntry(user)
.chat-stage-header Idle Members .chat-stage-header Idle Members
.sidebar-panel .sidebar-panel
ul(id="chat-idle-members").uk-list.uk-list-collapse .uk-text-italic.uk-text-muted There are no idle members.
+renderMemberListEntry(user, { idle: true }) ul(id="chat-idle-members", data-room-id= room._id, hidden).uk-list.uk-list-collapse
+renderMemberListEntry(user, { idle: true })
+renderMemberListEntry(user, { idle: true })
+renderMemberListEntry(user, { idle: true })
.chat-container .chat-container
.chat-stage-header .chat-stage-header
@ -69,32 +59,40 @@ block view-content
.uk-width-expand= room.name .uk-width-expand= room.name
.chat-content-panel .chat-content-panel
div(uk-grid).uk-grid-collapse.live-content .live-content
.uk-width-expand .chat-media
.chat-media div(uk-grid)
div(uk-grid) div(class="uk-width-1-1 uk-width-1-2@m uk-width-1-3@l uk-width-1-4@xl")
div(class="uk-width-1-1 uk-width-1-2@m uk-width-1-3@l uk-width-1-4@xl") +renderLiveMember(user)
+renderLiveMember(user)
.uk-width-auto div(id="chat-message-list").chat-messages
.chat-messages -
- var testMessage = {
var testMessage = { created: new Date(),
created: new Date(), author: user,
author: user, content: "This is the chat content panel. It should word-wrap and scroll correctly, and will be where individual chat messages will render as they arrive and are sent.",
content: "This is the chat content panel. It should word-wrap and scroll correctly, and will be where individual chat messages will render as they arrive and are sent.", };
};
+renderChatMessage(testMessage)
+renderChatMessage(testMessage)
+renderChatMessage(testMessage)
+renderChatMessage(testMessage) .chat-input-panel
+renderChatMessage(testMessage) form(
+renderChatMessage(testMessage) id="chat-input-form",
+renderChatMessage(testMessage) data-room-id= room._id,
onsubmit="return window.dtp.app.sendUserChat(event);",
hidden= user && user.flags && user.flags.isCloaked,
).uk-form
textarea(id="chat-input-text", name="chatInput", rows=2).uk-textarea.uk-resize-none.uk-border-rounded
.uk-margin-small
.uk-flex
.uk-width-expand
.uk-width-auto
button(id="chat-send-btn", type="submit", uk-tooltip={ title: 'Send message' }).uk-button.uk-button-default.uk-button-small.uk-border-rounded
i.fas.fa-paper-plane
.chat-input-panel block viewjs
textarea(id="chat-input", name="chatInput", rows=2).uk-textarea.uk-resize-none.uk-border-rounded script.
.uk-margin-small window.dtp = window.dtp || { };
.uk-flex window.dtp.room = !{JSON.stringify(room)};
.uk-width-expand
.uk-width-auto
button(type="submit", uk-tooltip={ title: 'Send message' }).uk-button.uk-button-default.uk-button-small.uk-border-rounded
i.fas.fa-paper-plane

1
client/css/dtp-site.less

@ -1,4 +1,5 @@
@import "site/main.less"; @import "site/main.less";
@import "site/button.less";
@import "site/navbar.less"; @import "site/navbar.less";
@import "site/stage.less"; @import "site/stage.less";
@import "site/image.less"; @import "site/image.less";

23
client/css/site/button.less

@ -0,0 +1,23 @@
button.dtp-button-dropdown {
/*
* This is a series of settings that improve the "clickable area" of the
* button without making it take up more space in the layout.
*/
display: inline-block;
position: relative;
padding: 1em;
margin: -1em;
&.always-on-top {
z-index: 1;
}
/*
* The actual "style" of the thing
*/
background: none;
border: none;
outline: none;
color: #e8e8e8;
cursor: pointer;
}

78
client/css/site/stage.less

@ -7,11 +7,6 @@
display: flex; display: flex;
.flex-row-break {
flex-basis: 100%;
height: 0;
}
.chat-stage-header { .chat-stage-header {
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
@ -42,37 +37,52 @@
} }
} }
img.member-profile-icon {
width: 32px;
height: auto;
border-radius: 5px;
margin-right: 5px;
}
.member-list-item {
line-height: 1.1em;
.member-display-name {
line-height: 1.1em;
}
.member-username {
line-height: 1em;
}
}
.chat-container { .chat-container {
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-grow: 1;
flex-direction: column; flex-direction: column;
align-items: stretch;
background-color: #a8a8a8; background-color: #a8a8a8;
color: #1a1a1a; color: #1a1a1a;
.chat-content-panel { .chat-content-panel {
box-sizing: border-box; box-sizing: border-box;
position: relative;
display: flex; display: flex;
flex-wrap: wrap; flex: 1;
height: 100%;
.live-content { .live-content {
flex-grow: 1; display: flex;
flex: 1;
.chat-media { .chat-media {
box-sizing: border-box; box-sizing: border-box;
flex-basis: 0;
flex-grow: 1; flex-grow: 1;
height: 100%;
padding: @stage-panel-padding; padding: @stage-panel-padding;
background-color: #4a4a4a; background-color: #4a4a4a;
overflow-y: auto;
.stage-live-member { .stage-live-member {
padding: 4px 4px 0 4px; padding: 4px 4px 0 4px;
@ -84,6 +94,11 @@
video { video {
display: block; display: block;
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
border-radius: 3px; border-radius: 3px;
} }
@ -104,26 +119,23 @@
flex-shrink: 0; flex-shrink: 0;
width: 320px; width: 320px;
height: 100%;
padding: @stage-panel-padding; padding: @stage-panel-padding;
overflow-y: scroll; overflow-y: scroll;
.chat-message { .chat-message {
line-height: 1; padding: 5px;
margin-bottom: 10px; margin-bottom: 10px;
border-bottom: solid 1px #4a4a4a;
&:last-child { line-height: 1;
border-bottom: none; border-radius: 4px;
}
img.member-profile-icon { background-color: #e8e8e8;
width: 32px; color: #1a1a1a;
height: auto;
border-radius: 5px; &.system-message {
margin-right: 5px; background-color: #d1f3db;
border-radius: 4px;
} }
.message-attribution { .message-attribution {
@ -154,15 +166,15 @@
} }
} }
.chat-input-panel { }
flex-basis: 100%;
flex-grow: 0; .chat-input-panel {
flex-shrink: 0; flex-grow: 0;
flex-shrink: 0;
padding: @stage-panel-padding; padding: @stage-panel-padding;
background-color: #1a1a1a; background-color: #1a1a1a;
color: #e8e8e8; color: #e8e8e8;
}
} }
} }

BIN
client/img/default-poster.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

171
client/js/chat-client.js

@ -5,13 +5,15 @@
'use strict'; 'use strict';
const DTP_COMPONENT_NAME = 'DtpChatApp'; const DTP_COMPONENT_NAME = 'DtpChatApp';
window.dtp = window.dtp || { }; const dtp = window.dtp = window.dtp || { };
import DtpApp from 'lib/dtp-app.js'; import DtpApp from 'lib/dtp-app.js';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
import Cropper from 'cropperjs'; import Cropper from 'cropperjs';
import dayjs from 'dayjs';
export class ChatApp extends DtpApp { export class ChatApp extends DtpApp {
constructor (user) { constructor (user) {
@ -19,12 +21,134 @@ export class ChatApp extends DtpApp {
this.loadSettings(); this.loadSettings();
this.log.info('DTP app client online'); this.log.info('DTP app client online');
this.chat = {
form: document.querySelector('#chat-input-form'),
messageList: document.querySelector('#chat-message-list'),
messages: [ ],
messageMenu: document.querySelector('.chat-message-menu'),
input: document.querySelector('#chat-input-text'),
sendButton: document.querySelector('#chat-send-btn'),
isAtBottom: true,
};
window.addEventListener('dtp-load', this.onDtpLoad.bind(this)); window.addEventListener('dtp-load', this.onDtpLoad.bind(this));
window.addEventListener('unload', this.onDtpUnload.bind(this));
} }
async onDtpLoad ( ) { async onDtpLoad ( ) {
this.log.info('dtp-load event received. Connecting to platform.'); this.log.info('dtp-load event received. Connecting to platform.');
await this.connect(); await this.connect({
mode: 'User',
onSocketConnect: this.onChatSocketConnect.bind(this),
onSocketDisconnect: this.onChatSocketDisconnect.bind(this),
});
}
async onDtpUnload ( ) {
await this.socket.disconnect();
}
async onChatSocketConnect (socket) {
this.log.debug('onSocketConnect', 'attaching socket events');
socket.on('chat-control', this.onChatControl.bind(this));
socket.on('system-message', this.onSystemMessage.bind(this));
if (dtp.room) {
await this.joinChatChannel(dtp.room);
}
}
async onChatSocketDisconnect (socket) {
this.log.debug('onSocketDisconnect', 'detaching socket events');
socket.off('chat-control', this.onChatControl.bind(this));
socket.off('system-message', this.onSystemMessage.bind(this));
}
async onChatControl (message) {
const isAtBottom = this.chat.isAtBottom;
if (message.displayList) {
this.displayEngine.executeDisplayList(message.displayList);
}
if (Array.isArray(message.systemMessages) && (message.systemMessages.length > 0)) {
for await (const sm of message.systemMessages) {
await this.onSystemMessage(sm);
}
}
if (message.cmd) {
switch (message.cmd) {
case 'call-start':
if (message.mediaServer && !this.call) {
dtp.mediaServer = message.mediaServer;
setTimeout(this.joinWebCall.bind(this), Math.floor(Math.random() * 3000));
}
break;
case 'call-end':
if (this.chat) {
this.chat.closeCall();
}
break;
}
}
this.scrollChatToBottom(isAtBottom);
}
async onSystemMessage (message) {
if (message.displayList) {
this.displayEngine.executeDisplayList(message.displayList);
}
if (!message.created || !message.content) {
return;
}
if (!this.chat || !this.chat.messageList) {
return;
}
const systemMessage = document.createElement('div');
systemMessage.classList.add('chat-message');
systemMessage.classList.add('system-message');
const chatContent = document.createElement('div');
chatContent.classList.add('chat-content');
chatContent.classList.add('uk-text-break');
chatContent.innerHTML = message.content;
systemMessage.appendChild(chatContent);
const chatTimestamp = document.createElement('div');
chatTimestamp.classList.add('chat-timestamp');
chatTimestamp.classList.add('uk-text-small');
chatTimestamp.setAttribute('data-dtp-timestamp', message.created);
chatTimestamp.innerHTML = dayjs(message.created).format('hh:mm:ss a');
systemMessage.appendChild(chatTimestamp);
this.chat.messageList.appendChild(systemMessage);
this.chat.messages.push(systemMessage);
while (this.chat.messages.length > 50) {
const message = this.chat.messages.shift();
this.chat.messageList.removeChild(message);
}
if (this.chat.isAtBottom) {
this.chat.messageList.scrollTo(0, this.chat.messageList.scrollHeight + 50000);
}
}
async joinChatChannel (room) {
try {
const response = await fetch(`/chat/room/${dtp.room._id}/join`);
await this.processResponse(response);
await this.socket.joinChannel(dtp.room._id, 'ChatRoom');
} catch (error) {
this.log.error('failed to join chat room', { room, error });
UIkit.modal.alert(`Failed to join chat room: ${error.message}`);
}
} }
async confirmNavigation (event) { async confirmNavigation (event) {
@ -295,4 +419,47 @@ export class ChatApp extends DtpApp {
this.log.info("createImageCropper", "Creating image cropper", { img }); this.log.info("createImageCropper", "Creating image cropper", { img });
this.cropper = new Cropper(img, options); this.cropper = new Cropper(img, options);
} }
scrollChatToBottom (isAtBottom = true) {
if (this.chat && this.chat.messageList && isAtBottom) {
this.chat.messageList.scrollTo(0, this.chat.messageList.scrollHeight);
setTimeout(( ) => {
this.chat.isAtBottom = true;
this.chat.messageList.scrollTo(0, this.chat.messageList.scrollHeight);
this.chat.isModifying = false;
}, 25);
}
}
async onChatMessageListScroll (event) {
const scrollPos = this.chat.messageList.scrollTop + this.chat.messageList.clientHeight;
if (!this.chat.isModifying) {
this.chat.isAtBottom = (scrollPos >= (this.chat.messageList.scrollHeight - 10));
this.chat.isAtTop = (scrollPos <= 0);
}
if (event && (this.chat.isAtBottom || this.chat.isAtTop)) {
event.preventDefault();
event.stopPropagation();
}
if (this.chat.isAtBottom) {
this.chat.messageMenu.classList.remove('chat-menu-visible');
} else {
this.chat.messageMenu.classList.add('chat-menu-visible');
}
}
async resumeChatScroll ( ) {
this.chat.messageList.scrollTo(0, this.chat.messageList.scrollHeight + 50000);
this.chat.isAtBottom = true;
this.chat.messageMenu.classList.remove('chat-menu-visible');
}
async onWindowResize ( ) {
if (this.chat.messageList && this.chat.isAtBottom) {
this.chat.messageList.scrollTo(0, this.chat.messageList.scrollHeight + 50000);
}
}
} }

2
lib/client/js/dtp-log.js

@ -75,7 +75,7 @@ export default class DtpWebLog {
}; };
const env = document.querySelector('body').getAttribute('data-dtp-env'); const env = document.querySelector('body').getAttribute('data-dtp-env');
if (env === 'local') { if (env === 'development') {
this.enable(); this.enable();
} }
} }

14
lib/client/js/dtp-socket.js

@ -89,6 +89,12 @@ export default class DtpWebSocket {
} }
} }
disconnect ( ) {
// remove disconnect handler since we're being deliberate
this.socket.off('disconnect', this.onSocketDisconnect.bind(this));
this.socket.disconnect();
}
async onSocketConnect ( ) { async onSocketConnect ( ) {
this.log.info('onSocketConnect', 'WebSocket connected'); this.log.info('onSocketConnect', 'WebSocket connected');
this.isConnected = true; this.isConnected = true;
@ -164,7 +170,8 @@ export default class DtpWebSocket {
} }
this.joinChannel(message.user._id, 'User'); this.joinChannel(message.user._id, 'User');
document.dispatchEvent(new Event('socketConnected')); this.log.info('onSocketUserAuthenticated', 'dispatching dtp-socket-connected');
document.dispatchEvent(new Event('dtp-socket-connected'));
} }
async onSocketWidgetAuthenticated (message) { async onSocketWidgetAuthenticated (message) {
@ -175,8 +182,7 @@ export default class DtpWebSocket {
this.options.onSocketConnect(this.socket); this.options.onSocketConnect(this.socket);
} }
// this.joinChannel(message.channel._id, 'Channel'); document.dispatchEvent(new Event('dtp-socket-connected'));
document.dispatchEvent(new Event('socketConnected'));
} }
async joinChannel (channelId, channelType, passcode) { async joinChannel (channelId, channelType, passcode) {
@ -195,7 +201,7 @@ export default class DtpWebSocket {
} }
const event = new CustomEvent('dtp-channel-joined', { detail: message }); const event = new CustomEvent('dtp-channel-joined', { detail: message });
window.dispatchEvent(event); document.dispatchEvent(event);
} }
async leaveChannel (channelId) { async leaveChannel (channelId) {

7
lib/site-ioserver.js

@ -259,7 +259,12 @@ export class SiteIoServer extends SiteCommon {
async onSocketDisconnect (session, reason) { async onSocketDisconnect (session, reason) {
const { chat: chatService } = this.dtp.services; const { chat: chatService } = this.dtp.services;
this.log.debug('socket disconnect', { sid: session.socket.id, consumerId: (session.user || session.channel)._id, reason }); this.log.debug('socket disconnect', {
sid: session.socket.id,
consumerId: (session.user || session.channel)._id,
joinedRooms: session.joinedRooms.size,
reason,
});
if (session.user && session.joinedRooms.size > 0) { if (session.user && session.joinedRooms.size > 0) {
for await (const room of session.joinedRooms) { for await (const room of session.joinedRooms) {

4
webpack.config.js

@ -68,8 +68,8 @@ export default {
}, },
performance: { performance: {
hints: 'warning', hints: 'warning',
maxEntrypointSize: 512 * 1024, maxEntrypointSize: 2 * 102412 * 1024,
maxAssetSize: 512 * 1024, maxAssetSize: 2 * 102412 * 1024,
}, },
plugins, plugins,
module: { module: {

Loading…
Cancel
Save