2018-08-16 22:30:59 +00:00
|
|
|
|
/*
|
2021-02-04 22:04:35 +00:00
|
|
|
|
* Copyright (c) 2010-2021 Tim Düsterhus.
|
2018-08-16 22:30:59 +00:00
|
|
|
|
*
|
|
|
|
|
* Use of this software is governed by the Business Source License
|
|
|
|
|
* included in the LICENSE file.
|
|
|
|
|
*
|
2022-09-17 14:23:02 +00:00
|
|
|
|
* Change Date: 2026-09-17
|
2018-08-16 22:30:59 +00:00
|
|
|
|
*
|
|
|
|
|
* On the date above, in accordance with the Business Source
|
|
|
|
|
* License, use of this software will be governed by version 2
|
|
|
|
|
* or later of the General Public License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
define([
|
|
|
|
|
'./Chat/console',
|
|
|
|
|
'Bastelstu.be/bottle',
|
|
|
|
|
'Bastelstu.be/_Push',
|
|
|
|
|
'WoltLabSuite/Core/Core',
|
|
|
|
|
'WoltLabSuite/Core/Language',
|
|
|
|
|
'WoltLabSuite/Core/Timer/Repeating',
|
|
|
|
|
'WoltLabSuite/Core/User',
|
|
|
|
|
'./Chat/Autocompleter',
|
|
|
|
|
'./Chat/CommandHandler',
|
|
|
|
|
'./Chat/DataStructure/Throttle',
|
|
|
|
|
'./Chat/Message',
|
|
|
|
|
'./Chat/Messenger',
|
|
|
|
|
'./Chat/ParseError',
|
|
|
|
|
'./Chat/ProfileStore',
|
|
|
|
|
'./Chat/Room',
|
|
|
|
|
'./Chat/Template',
|
2020-10-31 23:42:28 +00:00
|
|
|
|
'./Chat/Ui/Attachment/Upload',
|
2018-08-16 22:30:59 +00:00
|
|
|
|
'./Chat/Ui/AutoAway',
|
|
|
|
|
'./Chat/Ui/Chat',
|
|
|
|
|
'./Chat/Ui/ConnectionWarning',
|
|
|
|
|
'./Chat/Ui/ErrorDialog',
|
|
|
|
|
'./Chat/Ui/Input',
|
|
|
|
|
'./Chat/Ui/Input/Autocompleter',
|
|
|
|
|
'./Chat/Ui/MessageStream',
|
|
|
|
|
'./Chat/Ui/MessageActions/Delete',
|
|
|
|
|
'./Chat/Ui/Mobile',
|
|
|
|
|
'./Chat/Ui/Notification',
|
|
|
|
|
'./Chat/Ui/ReadMarker',
|
|
|
|
|
'./Chat/Ui/Settings',
|
|
|
|
|
'./Chat/Ui/Topic',
|
|
|
|
|
'./Chat/Ui/UserActionDropdownHandler',
|
|
|
|
|
'./Chat/Ui/UserList',
|
|
|
|
|
], function (
|
2020-11-01 16:41:19 +00:00
|
|
|
|
console,
|
|
|
|
|
Bottle,
|
|
|
|
|
Push,
|
2018-08-16 22:30:59 +00:00
|
|
|
|
Core,
|
|
|
|
|
Language,
|
|
|
|
|
RepeatingTimer,
|
|
|
|
|
CoreUser,
|
|
|
|
|
Autocompleter,
|
2020-10-31 23:42:28 +00:00
|
|
|
|
CommandHandler,
|
|
|
|
|
Throttle,
|
|
|
|
|
Message,
|
|
|
|
|
Messenger,
|
|
|
|
|
ParseError,
|
|
|
|
|
ProfileStore,
|
|
|
|
|
Room,
|
|
|
|
|
Template,
|
|
|
|
|
UiAttachmentUpload,
|
|
|
|
|
UiAutoAway,
|
|
|
|
|
Ui,
|
2018-08-16 22:30:59 +00:00
|
|
|
|
UiConnectionWarning,
|
|
|
|
|
ErrorDialog,
|
|
|
|
|
UiInput,
|
|
|
|
|
UiInputAutocompleter,
|
|
|
|
|
UiMessageStream,
|
|
|
|
|
UiMessageActionDelete,
|
|
|
|
|
UiMobile,
|
|
|
|
|
UiNotification,
|
|
|
|
|
UiReadMarker,
|
|
|
|
|
UiSettings,
|
|
|
|
|
UiTopic,
|
|
|
|
|
UiUserActionDropdownHandler,
|
|
|
|
|
UiUserList
|
|
|
|
|
) {
|
|
|
|
|
'use strict'
|
|
|
|
|
|
|
|
|
|
class Chat {
|
|
|
|
|
constructor(roomID, config) {
|
|
|
|
|
console.debug('Chat.constructor', 'Constructing …')
|
|
|
|
|
|
|
|
|
|
this.config = config
|
|
|
|
|
|
|
|
|
|
this.sessionID = Core.getUuid()
|
|
|
|
|
|
|
|
|
|
// Setup Bottle containers
|
|
|
|
|
this.bottle = new Bottle()
|
|
|
|
|
this.bottle.value('bottle', this.bottle)
|
|
|
|
|
this.bottle.value('config', config)
|
|
|
|
|
this.bottle.constant('sessionID', this.sessionID)
|
|
|
|
|
this.bottle.constant('roomID', roomID)
|
|
|
|
|
|
|
|
|
|
// Register chat components
|
|
|
|
|
this.service('Autocompleter', Autocompleter)
|
|
|
|
|
this.service('CommandHandler', CommandHandler)
|
|
|
|
|
this.service('Messenger', Messenger)
|
|
|
|
|
this.service('ProfileStore', ProfileStore)
|
|
|
|
|
this.service('Room', Room)
|
|
|
|
|
|
|
|
|
|
// Register UI components
|
|
|
|
|
this.service('Ui', Ui)
|
|
|
|
|
this.service('UiAutoAway', UiAutoAway)
|
|
|
|
|
this.service('UiConnectionWarning', UiConnectionWarning)
|
|
|
|
|
this.service('UiInput', UiInput)
|
|
|
|
|
this.service('UiInputAutocompleter', UiInputAutocompleter)
|
|
|
|
|
this.service('UiMessageActionDelete', UiMessageActionDelete)
|
|
|
|
|
this.service('UiMessageStream', UiMessageStream)
|
|
|
|
|
this.service('UiMobile', UiMobile)
|
|
|
|
|
this.service('UiNotification', UiNotification)
|
|
|
|
|
this.service('UiReadMarker', UiReadMarker)
|
|
|
|
|
this.service('UiSettings', UiSettings)
|
|
|
|
|
this.service('UiTopic', UiTopic)
|
|
|
|
|
this.service('UiUserActionDropdownHandler', UiUserActionDropdownHandler)
|
|
|
|
|
this.service('UiUserList', UiUserList)
|
2020-10-31 23:42:28 +00:00
|
|
|
|
this.service('UiAttachmentUpload', UiAttachmentUpload)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
|
|
|
|
|
// Register Models
|
|
|
|
|
this.bottle.instanceFactory('Message', (container, m) => {
|
|
|
|
|
return new Message(container.MessageType, m)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Register Templates
|
|
|
|
|
const selector = [
|
|
|
|
|
'[type="x-text/template"]',
|
|
|
|
|
'[data-application="be.bastelstu.chat"]',
|
|
|
|
|
'[data-template-name]',
|
|
|
|
|
].join('')
|
|
|
|
|
|
|
|
|
|
const templates = elBySelAll(selector)
|
|
|
|
|
|
|
|
|
|
Array.prototype.forEach.call(
|
|
|
|
|
templates,
|
|
|
|
|
function (template) {
|
|
|
|
|
this.bottle.factory(
|
|
|
|
|
`Template.${template.dataset.templateName}`,
|
|
|
|
|
function (container) {
|
|
|
|
|
const includeNames = (template.dataset.templateIncludes || '')
|
|
|
|
|
.split(/ /)
|
|
|
|
|
.filter((item) => item !== '')
|
|
|
|
|
const includes = {}
|
|
|
|
|
includeNames.forEach((item) => (includes[item] = container[item]))
|
2020-11-01 16:41:19 +00:00
|
|
|
|
|
2018-08-16 22:30:59 +00:00
|
|
|
|
return new Template(template.textContent, includes)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}.bind(this)
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
|
|
|
|
|
// Register MessageTypes
|
|
|
|
|
Object.entries(this.config.messageTypes).forEach(
|
|
|
|
|
([objectType, messageType]) => {
|
|
|
|
|
const MessageType = require(messageType.module)
|
2020-11-01 16:41:19 +00:00
|
|
|
|
|
2018-08-16 22:30:59 +00:00
|
|
|
|
this.bottle.factory(
|
|
|
|
|
`MessageType.${objectType.replace(/\./g, '-')}`,
|
|
|
|
|
(_) => {
|
|
|
|
|
const deps = this.bottle.digest(MessageType.DEPENDENCIES || [])
|
2020-11-01 16:41:19 +00:00
|
|
|
|
|
2018-08-16 22:30:59 +00:00
|
|
|
|
return new MessageType(...deps, objectType)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Register Commands
|
|
|
|
|
Object.values(this.config.commands).forEach((command) => {
|
|
|
|
|
const Command = require(command.module)
|
|
|
|
|
|
|
|
|
|
this.bottle.factory(
|
|
|
|
|
`Command.${command.package.replace(/\./g, '-')}:${
|
|
|
|
|
command.identifier
|
|
|
|
|
}`,
|
|
|
|
|
(_) => {
|
|
|
|
|
const deps = this.bottle.digest(Command.DEPENDENCIES || [])
|
|
|
|
|
|
|
|
|
|
return new Command(...deps, command)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
this.bottle.constant(
|
|
|
|
|
'Trigger',
|
|
|
|
|
new Map(
|
|
|
|
|
Object.entries(this.config.triggers).map(([trigger, commandID]) => {
|
|
|
|
|
const command = this.config.commands[commandID]
|
|
|
|
|
const key = [command.package, command.identifier]
|
|
|
|
|
return [trigger, key]
|
|
|
|
|
})
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
|
|
|
|
|
// Register Settings
|
|
|
|
|
Array.from(
|
|
|
|
|
elBySelAll('#chatQuickSettingsNavigation .button[data-module]')
|
|
|
|
|
).forEach((item) => {
|
|
|
|
|
const Button = require(item.dataset.module)
|
|
|
|
|
|
|
|
|
|
this.bottle.instanceFactory(
|
|
|
|
|
`UiSettingsButton.${item.dataset.module.replace(/\./g, '-')}`,
|
|
|
|
|
(_, element) => {
|
|
|
|
|
const deps = this.bottle.digest(Button.DEPENDENCIES || [])
|
|
|
|
|
return new Button(element, ...deps)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.knows = { from: undefined, to: undefined }
|
|
|
|
|
|
|
|
|
|
this.processMessagesThrottled = Throttle(this.processMessages.bind(this))
|
|
|
|
|
this.queuedMessages = []
|
|
|
|
|
this.messageSinks = new Set()
|
|
|
|
|
|
|
|
|
|
this.pullTimer = undefined
|
|
|
|
|
this.pullUserListTimer = undefined
|
|
|
|
|
this.pushConnected = false
|
|
|
|
|
|
|
|
|
|
this.firstFailure = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
service(name, _constructor, args = []) {
|
|
|
|
|
this.bottle.factory(name, (_) => {
|
|
|
|
|
const deps = this.bottle.digest(_constructor.DEPENDENCIES || [])
|
|
|
|
|
|
|
|
|
|
return new _constructor(...deps, ...args)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async bootstrap() {
|
|
|
|
|
console.debug('Chat.bootstrap', 'Initializing …')
|
|
|
|
|
|
|
|
|
|
this.ui = this.bottle.container.Ui
|
|
|
|
|
this.ui.bootstrap()
|
|
|
|
|
|
2020-11-01 11:03:04 +00:00
|
|
|
|
this.ui.input.on('submit', this.onSubmit.bind(this))
|
|
|
|
|
this.ui.input.on('autocomplete', this.onAutocomplete.bind(this))
|
|
|
|
|
this.ui.attachmentUpload.on('send', (event) => {
|
|
|
|
|
event.detail.promise = this.onSendAttachment(event)
|
|
|
|
|
})
|
2018-08-16 22:30:59 +00:00
|
|
|
|
|
|
|
|
|
await this.bottle.container.Room.join()
|
|
|
|
|
|
|
|
|
|
// Bind unload event to leave the Chat
|
|
|
|
|
window.addEventListener(
|
|
|
|
|
'unload',
|
|
|
|
|
this.bottle.container.Room.leave.bind(this.bottle.container.Room, true)
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
document.addEventListener('visibilitychange', (_) => {
|
|
|
|
|
this.processMessagesThrottled.setDelay(document.hidden ? 10000 : 125)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.pullTimer = new RepeatingTimer(
|
|
|
|
|
Throttle(this.pullMessages.bind(this)),
|
|
|
|
|
this.config.reloadTime * 1e3
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Push.onConnect((_) => {
|
|
|
|
|
console.debug('Chat.bootstrap', 'Push connected')
|
|
|
|
|
this.pushConnected = true
|
|
|
|
|
this.pullTimer.setDelta(30e3)
|
|
|
|
|
}).catch((error) => {
|
|
|
|
|
console.debug(error)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Push.onDisconnect((_) => {
|
|
|
|
|
console.debug('Chat.bootstrap', 'Push disconnected')
|
|
|
|
|
this.pushConnected = false
|
|
|
|
|
this.pullTimer.setDelta(this.config.reloadTime * 1e3)
|
|
|
|
|
}).catch((error) => {
|
|
|
|
|
console.debug(error)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
Push.onMessage(
|
|
|
|
|
'be.bastelstu.chat.message',
|
|
|
|
|
this.pullMessages.bind(this)
|
|
|
|
|
).catch((error) => {
|
|
|
|
|
console.debug(error)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Fetch user list every 60 seconds
|
|
|
|
|
// This acts as a safety net: It should be kept current by messages whenever possible.
|
|
|
|
|
this.pullUserListTimer = new RepeatingTimer(
|
|
|
|
|
this.updateUsers.bind(this),
|
|
|
|
|
60e3
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
this.registerMessageSink(this.bottle.container.UiMessageStream)
|
|
|
|
|
this.registerMessageSink(this.bottle.container.UiNotification)
|
|
|
|
|
this.registerMessageSink(this.bottle.container.UiAutoAway)
|
|
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
|
this.pullMessages(),
|
|
|
|
|
this.updateUsers(),
|
|
|
|
|
this.bottle.container.ProfileStore.ensureUsersByIDs([CoreUser.userId]),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
registerMessageSink(sink) {
|
|
|
|
|
if (typeof sink.ingest !== 'function') {
|
|
|
|
|
throw new Error('The given sink does not provide a .ingest function.')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.messageSinks.add(sink)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unregisterMessageSink(sink) {
|
|
|
|
|
this.messageSinks.delete(sink)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hcf(err = undefined) {
|
|
|
|
|
console.debug(
|
|
|
|
|
'Chat.hcf',
|
|
|
|
|
'Gotcha! FIRE was caught! FIRE’s data was newly added to the POKéDEX.',
|
|
|
|
|
err
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
this.pullTimer.stop()
|
|
|
|
|
this.pullUserListTimer.stop()
|
|
|
|
|
|
|
|
|
|
new ErrorDialog(Language.get('chat.error.hcf', { err }))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async onSubmit(event) {
|
|
|
|
|
const input = event.target
|
|
|
|
|
const value = input.getText()
|
|
|
|
|
|
|
|
|
|
console.debug('Chat.onSubmit', `Pushing message: ${value}`)
|
|
|
|
|
|
|
|
|
|
// Clear message input
|
|
|
|
|
input.insertText('', { append: false })
|
|
|
|
|
|
|
|
|
|
this.markAsBack()
|
|
|
|
|
|
2021-09-17 13:14:39 +00:00
|
|
|
|
let [trigger, parameterString] =
|
|
|
|
|
this.bottle.container.CommandHandler.splitCommand(value)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
let command = null
|
|
|
|
|
if (trigger === null) {
|
|
|
|
|
command = this.bottle.container.CommandHandler.getCommandByIdentifier(
|
|
|
|
|
'be.bastelstu.chat',
|
|
|
|
|
'plain'
|
|
|
|
|
)
|
|
|
|
|
} else {
|
2021-09-17 13:14:39 +00:00
|
|
|
|
command =
|
|
|
|
|
this.bottle.container.CommandHandler.getCommandByTrigger(trigger)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command === null) {
|
|
|
|
|
this.ui.input.inputError(
|
|
|
|
|
Language.get('chat.error.triggerNotFound', { trigger })
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let parameters
|
|
|
|
|
try {
|
|
|
|
|
parameters = this.bottle.container.CommandHandler.applyCommand(
|
|
|
|
|
command,
|
|
|
|
|
parameterString
|
|
|
|
|
)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (e instanceof ParseError) {
|
|
|
|
|
e = new Error(
|
|
|
|
|
Language.get('chat.error.invalidParameters', { data: e.data })
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
}
|
|
|
|
|
throw e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const payload = { commandID: command.id, parameters }
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await this.bottle.container.Messenger.push(payload)
|
|
|
|
|
this.ui.input.hideInputError()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
let seriousError = true
|
|
|
|
|
if (
|
|
|
|
|
error.returnValues &&
|
|
|
|
|
error.returnValues.fieldName === 'message' &&
|
|
|
|
|
(error.returnValues.realErrorMessage ||
|
|
|
|
|
error.returnValues.errorType)
|
2020-11-01 16:41:19 +00:00
|
|
|
|
) {
|
2018-08-16 22:30:59 +00:00
|
|
|
|
this.ui.input.inputError(
|
|
|
|
|
error.returnValues.realErrorMessage ||
|
|
|
|
|
error.returnValues.errorType
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
seriousError = false
|
|
|
|
|
} else {
|
|
|
|
|
this.ui.input.inputError(error.message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (seriousError) {
|
|
|
|
|
this.handleError(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We assume that a running push server will push us our own message
|
|
|
|
|
if (!this.pushConnected) {
|
|
|
|
|
this.pullMessages()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.debug('Chat.onSubmit', `Done`)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.ui.input.inputError(e.message)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async markAsBack() {
|
|
|
|
|
try {
|
|
|
|
|
if (this.bottle.container.ProfileStore.getSelf().away == null) return
|
|
|
|
|
console.debug('Chat.markAsBack', `Marking as back`)
|
|
|
|
|
|
2021-09-17 13:14:39 +00:00
|
|
|
|
const command =
|
|
|
|
|
this.bottle.container.CommandHandler.getCommandByIdentifier(
|
|
|
|
|
'be.bastelstu.chat',
|
|
|
|
|
'back'
|
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
return this.bottle.container.Messenger.push({
|
|
|
|
|
commandID: command.id,
|
|
|
|
|
parameters: {},
|
|
|
|
|
})
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Chat.markAsBack', err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-01 11:03:04 +00:00
|
|
|
|
async onSendAttachment(event) {
|
2021-02-04 21:44:45 +00:00
|
|
|
|
await this.bottle.container.Messenger.pushAttachment(event.detail.tmpHash)
|
2020-11-07 19:00:43 +00:00
|
|
|
|
|
|
|
|
|
this.markAsBack()
|
2020-11-01 11:03:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-16 22:30:59 +00:00
|
|
|
|
onAutocomplete(event) {
|
|
|
|
|
const input = event.target
|
|
|
|
|
const value = input.getText(true)
|
|
|
|
|
|
|
|
|
|
console.debug('Chat.onAutocomplete', `Autocompleting message: ${value}`)
|
|
|
|
|
|
|
|
|
|
const result = this.bottle.container.Autocompleter.autocomplete(value)
|
2020-10-31 16:05:30 +00:00
|
|
|
|
const completions = []
|
2018-08-16 22:30:59 +00:00
|
|
|
|
for (const item of result) {
|
2020-10-31 16:05:30 +00:00
|
|
|
|
completions.push(item)
|
|
|
|
|
if (completions.length == 5) break
|
2018-08-16 22:30:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-31 16:05:30 +00:00
|
|
|
|
this.ui.autocompleter.sendCompletions(completions)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async pullMessages() {
|
|
|
|
|
console.debug(
|
|
|
|
|
'Chat.pullMessages',
|
|
|
|
|
`Pulling new messages, starting at ${
|
|
|
|
|
this.knows.to ? this.knows.to + 1 : ''
|
|
|
|
|
}`
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let payload
|
|
|
|
|
try {
|
|
|
|
|
if (this.knows.to === undefined) {
|
|
|
|
|
payload = await this.bottle.container.Messenger.pull()
|
2020-11-01 16:41:19 +00:00
|
|
|
|
} else {
|
2018-08-16 22:30:59 +00:00
|
|
|
|
payload = await this.bottle.container.Messenger.pull(
|
|
|
|
|
this.knows.to + 1
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.handleError(e)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.debug('Chat.pullMessages', `Handling result: `, payload)
|
|
|
|
|
const start = (performance ? performance : Date).now()
|
|
|
|
|
this.ui.connectionWarning.hide()
|
|
|
|
|
this.firstFailure = null
|
|
|
|
|
|
|
|
|
|
// Null range: No messages satisfy the constraints
|
|
|
|
|
if (payload.from > payload.to) {
|
|
|
|
|
const end = (performance ? performance : Date).now()
|
|
|
|
|
console.debug('Chat.pullMessages', `took ${(end - start) / 1000}s`)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let messages = payload.messages
|
|
|
|
|
|
|
|
|
|
if (this.knows.from !== undefined && this.knows.to !== undefined) {
|
|
|
|
|
messages = messages.filter((message) => {
|
|
|
|
|
return !(
|
|
|
|
|
this.knows.from <= message.messageID &&
|
|
|
|
|
message.messageID <= this.knows.to
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.knows.from === undefined || payload.from < this.knows.from)
|
|
|
|
|
this.knows.from = payload.from
|
|
|
|
|
if (this.knows.to === undefined || payload.to > this.knows.to)
|
|
|
|
|
this.knows.to = payload.to
|
|
|
|
|
|
|
|
|
|
this.queuedMessages.push(messages)
|
|
|
|
|
const end = (performance ? performance : Date).now()
|
|
|
|
|
console.debug('Chat.pullMessages', `took ${(end - start) / 1000}s`)
|
|
|
|
|
|
|
|
|
|
this.processMessagesThrottled()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleError(error) {
|
|
|
|
|
if (this.firstFailure === null) {
|
|
|
|
|
console.error(
|
|
|
|
|
'Chat.handleError',
|
|
|
|
|
`Request failed, 30 seconds until shutdown`
|
|
|
|
|
)
|
|
|
|
|
this.firstFailure = Date.now()
|
|
|
|
|
this.ui.connectionWarning.show()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.debugException(error)
|
|
|
|
|
|
|
|
|
|
if (Date.now() - this.firstFailure >= 30e3) {
|
|
|
|
|
console.error('Chat.handleError', ' Failures for 30 seconds, aborting')
|
|
|
|
|
|
|
|
|
|
this.hcf(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async processMessages() {
|
|
|
|
|
console.debug('Chat.processMessages', 'Processing messages')
|
|
|
|
|
const start = (performance ? performance : Date).now()
|
|
|
|
|
const messages = [].concat(...this.queuedMessages)
|
|
|
|
|
this.queuedMessages = []
|
|
|
|
|
|
|
|
|
|
if (messages.length === 0) return
|
|
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
messages.map(async (message) => {
|
|
|
|
|
this.bottle.container.ProfileStore.pushLastActivity(message.userID)
|
|
|
|
|
|
|
|
|
|
return message.getMessageType().preProcess(message)
|
|
|
|
|
})
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
|
|
|
|
|
const updateUserList = messages.some((message) => {
|
|
|
|
|
return message.getMessageType().shouldUpdateUserList(message)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (updateUserList) {
|
|
|
|
|
this.updateUsers()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.bottle.container.ProfileStore.ensureUsersByIDs(
|
|
|
|
|
[].concat(
|
|
|
|
|
...messages.map((message) =>
|
|
|
|
|
message.getMessageType().getReferencedUsers(message)
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
|
|
|
|
|
messages.forEach((message) => {
|
|
|
|
|
message.getMessageType().preRender(message)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.messageSinks.forEach((sink) => sink.ingest(messages))
|
|
|
|
|
const end = (performance ? performance : Date).now()
|
|
|
|
|
console.debug('Chat.processMessages', `took ${(end - start) / 1000}s`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async updateUsers() {
|
|
|
|
|
console.debug('Chat.updateUsers')
|
|
|
|
|
|
|
|
|
|
const users = await this.bottle.container.Room.getUsers()
|
|
|
|
|
await this.bottle.container.ProfileStore.ensureUsersByIDs(
|
|
|
|
|
users.map((user) => user.userID)
|
2020-11-01 16:41:19 +00:00
|
|
|
|
)
|
2018-08-16 22:30:59 +00:00
|
|
|
|
this.ui.userList.render(users)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Chat
|
|
|
|
|
})
|