Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
# EditorConfig is awesome: http://EditorConfig.org

root = true

[*]
end_of_line = lf
charset = utf-8
insert_final_newline = true
max_line_length = 100

[*.{js}]
trim_trailing_whitespace = true

[*.{js,md}]
indent_style = space
indent_size = 2
49 changes: 49 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module.exports = {
ignorePatterns: ['**/legacy/*'],
parser: '@babel/eslint-parser',
parserOptions: {
sourceType: 'module',
},
env: {
es6: true,
node: true,
},
overrides: [
{
files: [
'./**/*.js',
],
globals: {
document: false,
window: false,
},
extends: [
'airbnb-base',
],
rules: {
'no-shadow': 'off',
'no-alert': 'off',
'no-console': ['warn', { allow: ['warn', 'error'] }],
semi: ['error', 'never'],
'import/extensions': 'off',
'import/no-extraneous-dependencies': 'off',
'import/no-unresolved': 'off',
'import/no-dynamic-require': 'off',
'arrow-parens': ['error', 'as-needed'],
'padded-blocks': 'off',
'class-methods-use-this': 'off',
'global-require': 'off',
'func-names': ['error', 'never'],
'arrow-body-style': 'off',
'max-len': 'off',
'no-param-reassign': 'off',
'import/prefer-default-export': 'off',
'consistent-return': 'off',
'no-redeclare': 'off',
'no-unused-vars': 'off',
'no-use-before-define': 'off',
'no-dupe-class-members': 'off',
},
},
],
}
3 changes: 3 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module.exports = {
presets: [
'@babel/preset-env',
],
plugins: [
'@babel/plugin-proposal-class-properties',
],
}
16 changes: 11 additions & 5 deletions demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,35 @@ const server = CollaborationServer.create({
// persistence: new PersistenceRedis('redis://:authpassword@127.0.0.1:6380/4'),

onConnect(data, resolve, reject) {
const {documentName, clientID, requestHeaders} = data
const { documentName, clientID, requestHeaders } = data

resolve()
},

onJoinDocument(data, resolve, reject) {
const {documentName, clientID, requestHeaders, clientsCount, document} = data
const {
documentName, clientID, requestHeaders, clientsCount, document,
} = data

resolve()
},

onChange(data) {
const {documentName, clientID, requestHeaders, clientsCount, document} = data
const {
documentName, clientID, requestHeaders, clientsCount, document,
} = data

},

onLeaveDocument(data) {
const {documentName, clientID, requestHeaders, clientsCount, document} = data
const {
documentName, clientID, requestHeaders, clientsCount, document,
} = data

},

onDisconnect(data) {
const {documentName, clientID, requestHeaders} = data
const { documentName, clientID, requestHeaders } = data

},

Expand Down
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
],
"scripts": {
"start": "yarn --cwd ./demo start",
"start:development": "yarn --cwd ./demo start:development"
"start:development": "yarn --cwd ./demo start:development",
"lint": "eslint --quiet --no-error-on-unmatched-pattern ./",
"lint:fix": "eslint --fix --quiet --no-error-on-unmatched-pattern ./"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"eslint": "^7.14.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.22.1"
}
}
18 changes: 11 additions & 7 deletions packages/collaboration-server/src/Connection.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import {messageListener} from './legacy/utils.js'
import { messageListener } from './legacy/utils.js'
import {
WS_READY_STATE_CLOSING,
WS_READY_STATE_CLOSED
WS_READY_STATE_CLOSED,
} from './utils/readyStates.js'

class Connection {

connection

request

document

timeout

pingInterval

pongReceived = true

callbacks = {
Expand All @@ -38,7 +42,7 @@ class Connection {
// TODO: Move messageListener here and refactor it
this.connection.on('message', message => messageListener(this.connection, this.document, new Uint8Array(message)))

this.pingInterval = setInterval(this._check.bind(this), this.timeout)
this.pingInterval = setInterval(this.check.bind(this), this.timeout)

this.connection.on('pong', () => {
this.pongReceived = true
Expand All @@ -48,7 +52,7 @@ class Connection {
this.close()
})

this._sendFirstSyncStep()
this.sendFirstSyncStep()
}

/**
Expand Down Expand Up @@ -111,7 +115,7 @@ class Connection {
* @returns {undefined}
* @private
*/
_check() {
check() {
if (!this.pongReceived) {
return this.close()
}
Expand All @@ -131,13 +135,13 @@ class Connection {
* Send first sync step
* @private
*/
_sendFirstSyncStep() {
sendFirstSyncStep() {
const message = this.document.writeFirstSyncStep()
this.send(message.encode())

if (this.document.getAwarenessStates().size > 0) {
this.send(
this.document.getAwarenessUpdateMessage().encode()
this.document.getAwarenessUpdateMessage().encode(),
)
}
}
Expand Down
23 changes: 12 additions & 11 deletions packages/collaboration-server/src/SharedDocument.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import awarenessProtocol from 'y-protocols/dist/awareness.cjs'
import Encoder from "./Encoder.js"
import mutex from 'lib0/dist/mutex.cjs'
import syncProtocol from 'y-protocols/dist/sync.cjs'
import Y from 'yjs'
import Encoder from './Encoder.js'
import {
MESSAGE_AWARENESS,
MESSAGE_SYNC,
Expand All @@ -11,22 +11,23 @@ import {
class SharedDocument extends Y.Doc {

connections = new Map()

mutex = mutex.createMutex()

/**
* Constructor.
* @param name
*/
constructor(name) {
super({gc: true})
super({ gc: true })

this.name = name

this.awareness = new awarenessProtocol.Awareness(this)
this.awareness.setLocalState(null)

this.awareness.on('update', this._handleAwarenessUpdate.bind(this))
this.on('update', this._handleUpdate.bind(this))
this.awareness.on('update', this.handleAwarenessUpdate.bind(this))
this.on('update', this.handleUpdate.bind(this))

// if (isCallbackSet) {
// this.on('update', debounce(
Expand Down Expand Up @@ -62,7 +63,7 @@ class SharedDocument extends Y.Doc {
awarenessProtocol.removeAwarenessStates(
this.awareness,
Array.from(this.connections.get(connection)),
null
null,
)

this.connections.delete(connection)
Expand All @@ -84,7 +85,7 @@ class SharedDocument extends Y.Doc {
getAwarenessUpdateMessage(changedClients = null) {
const message = awarenessProtocol.encodeAwarenessUpdate(
this.awareness,
changedClients ? changedClients : Array.from(this.getAwarenessStates().keys())
changedClients || Array.from(this.getAwarenessStates().keys()),
)

return new Encoder().int(MESSAGE_AWARENESS).int8(message)
Expand Down Expand Up @@ -126,9 +127,9 @@ class SharedDocument extends Y.Doc {
* @param connection
* @private
*/
_handleAwarenessUpdate(clients, connection) {
handleAwarenessUpdate(clients, connection) {

const {added, updated, removed} = clients
const { added, updated, removed } = clients
const changedClients = added.concat(updated, removed)

if (connection !== null) {
Expand All @@ -146,7 +147,7 @@ class SharedDocument extends Y.Doc {
}

this.connections.forEach((set, connection) => connection.send(
this.getAwarenessUpdateMessage(changedClients).encode()
this.getAwarenessUpdateMessage(changedClients).encode(),
))
}

Expand All @@ -155,11 +156,11 @@ class SharedDocument extends Y.Doc {
* @param update
* @private
*/
_handleUpdate(update) {
handleUpdate(update) {
const message = this.writeUpdate(update)

this.connections.forEach((set, connection) => connection.send(
message.encode()
message.encode(),
))
}
}
Expand Down
25 changes: 13 additions & 12 deletions packages/collaboration-server/src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Connection from './Connection.js'
import SharedDocument from './SharedDocument.js'
import map from 'lib0/dist/map.cjs'
import WebSocket from 'ws'
import {createServer} from 'http'
import { createServer } from 'http'
import SharedDocument from './SharedDocument.js'
import Connection from './Connection.js'

class Server {

Expand All @@ -13,26 +13,28 @@ class Server {
}

httpServer

websocketServer

documents = new Map()

/**
* Initialize
*/
constructor() {
this.httpServer = createServer((request, response) => {
response.writeHead(200, {'Content-Type': 'text/plain'})
response.writeHead(200, { 'Content-Type': 'text/plain' })
response.end('OK')
})

this.websocketServer = new WebSocket.Server({
server: this.httpServer
server: this.httpServer,
})

this.websocketServer.on('connection', (connection, request) => {
console.log(`New connection to ${request.url}`)

return this._createConnection(connection, request, this._createDocument(request))
return this.createConnection(connection, request, this.createDocument(request))
})
}

Expand All @@ -44,7 +46,7 @@ class Server {
create(configuration) {
this.configuration = {
...this.configuration,
...configuration
...configuration,
}

return this
Expand All @@ -64,10 +66,9 @@ class Server {
* @param request
* @private
*/
_createDocument(request) {
createDocument(request) {
const documentName = request.url.slice(1).split('?')[0]


return map.setIfUndefined(this.documents, documentName, () => {
const document = new SharedDocument(documentName)

Expand All @@ -90,9 +91,9 @@ class Server {
* @returns {Connection}
* @private
*/
_createConnection(connection, request, document) {
createConnection(connection, request, document) {
return new Connection(connection, request, document, this.configuration.timeout)
.onClose((document) => {
.onClose(document => {
// TODO: Document should only be deleted, when it’s persisted
if (document.connections.size === 0) {
this.documents.delete(document.name)
Expand All @@ -101,4 +102,4 @@ class Server {
}
}

export const CollaborationServer = new Server
export const CollaborationServer = new Server()
Loading