You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
8.2 KiB

const SocketController = require('flitter-socket/Controller')
const uuid = require('uuid').v4
class NormEditorGroup {
constructor(page, node) {
this.id = `${page.UUID}-${node.UUID}`
this.page = page
this.node = node
this.connections = {}
this.connection_id_x_session = {}
}
join(connection_manager) {
this.connections[connection_manager.id] = connection_manager
this.connection_id_x_session[connection_manager.id] = {}
connection_manager.on_close().then(() => {
delete this.connection_id_x_session[connection_manager.id]
delete this.connections[connection_manager.id]
this.blast_editor_group_users()
})
}
has(connection_manager) {
return !!this.connections[connection_manager.id]
}
unique_users() {
const users = {}
const conns = Object.values(this.connections)
for ( const conn of conns ) {
const session = this.session(conn)
const user = conn.request.user
if ( user && !users[user.uuid] ) {
if ( !session.user_color ) {
session.user_color = '#' + Math.floor(Math.random() * 16777215).toString(16)
}
users[user.uuid] = { user, color: session.user_color }
}
}
return Object.values(users)
}
session(conn) {
return this.connection_id_x_session[conn.id]
}
blast_editor_group_users() {
const conns = Object.values(this.connections)
const unique_users = this.unique_users()
for ( const conn of conns ) {
const users = unique_users.filter(data => data.user.uuid !== conn.request.user.uuid)
.map(data => {
const { user, color } = data
return {
uuid: user.uuid,
uid: user.uid,
display: user.uid,
color,
}
})
conn._request('setEditorGroupUsers', { users })
}
}
blast_selections() {
const conns = Object.values(this.connections)
for ( const conn of conns ) {
const selections = conns.filter(maybeConn => conn.id !== maybeConn.id)
.map(otherConn => {
const session = this.session(otherConn)
const user_data = {
uuid: otherConn.request.user.uuid,
uid: otherConn.request.user.uid,
display: otherConn.request.user.uid,
}
if ( session.user_color ) {
user_data.color = session.user_color
}
return {...(this.session(otherConn).selection || {}), user_data}
})
.filter(Boolean)
conn._request('setEditorGroupSelections', { selections })
}
}
set_member_selection(conn, selection) {
const session = this.session(conn)
if ( session ) {
session.selection = selection
this.blast_selections()
}
}
}
class NormEditorController extends SocketController {
editor_groups = {}
static get services() {
return [...super.services, 'output', 'models']
}
ping(transaction, socket) {
this.output.info('Got norm editor socket ping.')
transaction.status(200)
.message('Pong!')
.send(transaction.incoming)
}
_get_editor_group_or_fail(transaction) {
if ( !transaction.incoming.editor_group_id ) {
transaction.status(400)
.message('Missing editor_group_id.')
.send()
return
}
const editor_group = this.editor_groups[transaction.incoming.editor_group_id]
if ( !editor_group || !editor_group.has(transaction.cm) ) {
transaction.status(400)
.message('Invalid editor_group_id.')
.send()
return
}
return editor_group
}
set_member_selection(transaction, socket) {
const editor_group = this._get_editor_group_or_fail(transaction)
if ( !editor_group ) return;
if ( !transaction.incoming.selection ) {
return transaction.status(400)
.message('Missing selection.')
.send()
}
editor_group.set_member_selection(transaction.cm, transaction.incoming.selection)
transaction.status(200).send()
}
get_selections(transaction, socket) {
const editor_group = this._get_editor_group_or_fail(transaction)
if ( !editor_group ) return;
const selections = Object.values(editor_group.connections)
.filter(maybeConn => transaction.cm.id !== maybeConn.id)
.map(otherConn => {
const session = editor_group.session(otherConn)
const user_data = {
uuid: otherConn.request.user.uuid,
uid: otherConn.request.user.uid,
display: otherConn.request.user.uid,
}
if ( session.user_color ) {
user_data.color = session.user_color
}
return {...(editor_group.session(otherConn).selection || {}), user_data}
})
.filter(Boolean)
transaction.status(200)
.send({ selections })
}
get_editor_group_users(transaction, socket) {
const editor_group = this._get_editor_group_or_fail(transaction)
if ( !editor_group ) return;
transaction.status(200)
.send(
editor_group.unique_users()
.filter(data => data.user.uuid !== transaction.cm.request.user.uuid)
.map(data => {
const { user, color } = data
return {
uuid: user.uuid,
uid: user.uid,
display: user.uid,
color,
}
})
)
}
async join_editor_group(transaction, socket) {
// FIXME support versioning
const Page = this.models.get('api:Page')
const Node = this.models.get('api:Node')
if ( !transaction.incoming.PageId ) {
return transaction.status(400)
.message('Missing pageId.')
.send()
}
const page = await Page.findOne({ UUID: transaction.incoming.PageId, Active: true })
if ( !page || !(await page.is_accessible_by(transaction.cm.request.user, 'update')) ) {
return transaction.status(401)
.message('Invalid PageId.')
.send()
}
if ( !transaction.incoming.NodeId ) {
transaction.status(400)
.message('Missing NodeId.')
.send()
}
const node = await Node.findOne({ UUID: transaction.incoming.NodeId, PageId: page.UUID })
if ( !node ) {
transaction.status(400)
.message('Invalid NodeId.')
.send()
}
// Try to look up an existing editor group
const editor_group_id = `${page.UUID}-${node.UUID}`
let editor_group = this.editor_groups[editor_group_id]
if ( !editor_group ) {
// create a new editor group
editor_group = new NormEditorGroup(page, node)
this.editor_groups[editor_group_id] = editor_group
}
if ( !editor_group.has(transaction.cm) )
editor_group.join(transaction.cm)
transaction.status(200)
.message('Joined editor group.')
.send({ editor_group_id })
editor_group.blast_editor_group_users()
}
_client_connected(connection_manager) {
super._client_connected(connection_manager)
this.output.debug('New client connected to norm editor socket.')
if ( !connection_manager.request?.session?.auth?.user_id ) {
delete this.connections[connection_manager.id]
connection_manager.socket.close()
}
}
}
module.exports = exports = NormEditorController