2021-01-02 21:12:29 +00:00
|
|
|
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 })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 16:26:32 +00:00
|
|
|
blast_content_mutation(mutation, exclude_conn_id) {
|
|
|
|
const conns = Object.values(this.connections)
|
|
|
|
|
|
|
|
for ( const conn of conns ) {
|
|
|
|
if ( conn.id === exclude_conn_id ) continue;
|
|
|
|
conn._request('applyRemoteContentMutation', { mutation })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-02 21:12:29 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-01-31 16:26:32 +00:00
|
|
|
broadcast_content_mutation(transaction, socket) {
|
|
|
|
const editor_group = this._get_editor_group_or_fail(transaction)
|
|
|
|
if ( !editor_group ) return;
|
|
|
|
|
|
|
|
if ( transaction.incoming.data.path.toLowerCase().startsWith('body@') ) {
|
|
|
|
return transaction.status(200).send()
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('mutation', transaction.incoming.data)
|
|
|
|
editor_group.blast_content_mutation(transaction.incoming.data, transaction.cm.id)
|
|
|
|
|
|
|
|
transaction.status(200).send()
|
|
|
|
}
|
|
|
|
|
2021-01-02 21:12:29 +00:00
|
|
|
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
|