├── Procfile ├── .gitignore ├── Dockerfile ├── package.json ├── src ├── y-websockets-server.js └── server.js └── README.md /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.json 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:5-onbuild 2 | # replace this with your application's default port 3 | EXPOSE 1234 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "y-websockets-server", 3 | "version": "10.0.0-3", 4 | "description": "Websockets Connector for Yjs (node server)", 5 | "main": "./src/y-websockets-server.js", 6 | "bin": "./src/server.js", 7 | "scripts": { 8 | "test": "npm run lint", 9 | "lint": "standard", 10 | "start": "DEBUG=y*,-y:connector-message node ./src/server.js" 11 | }, 12 | "engines": { 13 | "node": "^8.0.0" 14 | }, 15 | "standard": {}, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/y-js/y-websockets-server.git" 19 | }, 20 | "keywords": [ 21 | "Yjs", 22 | "OT", 23 | "Collaboration", 24 | "Synchronization", 25 | "ShareJS", 26 | "Coweb", 27 | "Concurrency" 28 | ], 29 | "author": "Kevin Jahns ", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/y-js/y-websockets-server/issues" 33 | }, 34 | "homepage": "http://y-js.org", 35 | "dependencies": { 36 | "minimist": "^1.2.0", 37 | "redis": "^2.8.0", 38 | "socket.io": "^2.0.3", 39 | "y-redis": "0.0.4", 40 | "yjs": "^13.0.0-51" 41 | }, 42 | "devDependencies": { 43 | "standard": "^10.0.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/y-websockets-server.js: -------------------------------------------------------------------------------- 1 | /* global Y */ 2 | 'use strict' 3 | 4 | function extend (Y) { 5 | class Connector extends Y.AbstractConnector { 6 | constructor (y, options) { 7 | if (options === undefined) { 8 | throw new Error('Options must not be undefined!') 9 | } 10 | if (options.room == null) { 11 | throw new Error('You must define a room name!') 12 | } 13 | if (options.io == null) { 14 | throw new Error('You must define the socketio serve!') 15 | } 16 | options.role = 'master' 17 | options.forwardAppliedOperations = true 18 | options.generateUserId = true 19 | super(y, options) 20 | this.options = options 21 | this.io = options.io 22 | } 23 | disconnect () { 24 | // throw new Error('You must not disconnect with this connector!') 25 | } 26 | reconnect () { 27 | // throw new Error('You must not disconnect with this connector!') 28 | } 29 | destroy () { 30 | this.io = null 31 | this.options = null 32 | } 33 | send (uid, message) { 34 | this.io.to(uid).emit('yjsEvent', message) 35 | super.send(uid, message) 36 | } 37 | broadcast (message) { 38 | this.connections.forEach((userConn, uid) => { 39 | if (userConn.receivedSyncStep2) { 40 | this.io.to(uid).emit('yjsEvent', message) 41 | } 42 | }) 43 | super.broadcast(message) 44 | } 45 | isDisconnected () { 46 | return false 47 | } 48 | } 49 | Y['websockets-server'] = Connector 50 | } 51 | 52 | module.exports = extend 53 | if (typeof Y !== 'undefined') { 54 | extend(Y) 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # (LEGACY) Websockets Connector for [Yjs](https://github.com/y-js/yjs) (Server) 2 | > Note: this is an outdated repository which is not compatible with y-websocket or Yjs>=v13 3 | 4 | *y-websockets-server* is the connection point for *y-websockets-client*. It saves the shared data (using the [memory](https://github.com/y-js/y-memory) or the [leveldb](https://github.com/y-js/y-leveldb) database adapter), and distributes it efficiently to all connected clients. 5 | 6 | #### Instructions for [y-leveldb](https://github.com/y-js/y-leveldb) 7 | 8 | The memory database adapter is installed by default. If you intend to use the [y-leveldb](https://github.com/y-js/y-leveldb) database adapter, make sure to install it first. 9 | 10 | ```sh 11 | npm install --save y-leveldb [-g] 12 | ``` 13 | 14 | ### Global installation (easy) 15 | * Install package `npm install -g y-websockets-server` 16 | * Execute binary `y-websockets-server [--port port] [--db db]` (defaults: port = 1234, db = `memory` (choose either `leveldb` or `memory`)). 17 | 18 | Yjs uses [debug](https://github.com/visionmedia/debug) for logging. In order to 19 | turn on logging set the environment variable `DEBUG` to `y*,-y:connector-message`: I.e 20 | 21 | ```sh 22 | DEBUG=y*,-y:connector-message y-websockets-server --port 1234 23 | ``` 24 | 25 | This is how you redirect the output to a file: 26 | 27 | ```sh 28 | DEBUG_COLORS=0 DEBUG=y*,-y:connector-message y-websockets-server --port 1234 > log.txt 29 | ``` 30 | 31 | ### Local installation (recommended if you intend to modify y-websockets-server) 32 | 33 | * Set up a new project 34 | 35 | mkdir my-y-websockets-server && cd $_ && git init && npm init && echo "node_modules" > .gitignore 36 | 37 | * Install `npm i --save y-websockets-server` 38 | * Copy executable `cp node_modules/y-websockets-server/src/server.js .` 39 | * Start server `node server.js` 40 | 41 | ### Setup with Docker 42 | 43 | * Clone this repository and navigate to it. 44 | * Build the image: `docker build -t y-websockets-server .` 45 | * Run it: `docker run -it --rm -p 1234:1234 --name y-websockets-server y-websockets-server` 46 | * Feel free to modify the port argument, e.g. to `-p 1773:1234` to run it at port 1773. 47 | 48 | ### Setup with Heroku 49 | Heroku is really easy to set up, and you get a free *y-websockets-server* with https! 50 | Preliminarily you have to set up heroku - see this great [getting started guide](https://devcenter.heroku.com/articles/getting-started-with-nodejs#introduction) 51 | 52 | * Perform the steps from the local installation 53 | * Create Procfile `echo "web: DEBUG=y*,-y:connector-message server.js" > Procfile` 54 | * Specify a node environment. Add this to your package.json: 55 | 56 | "engines": { 57 | "node": "6.9.1" 58 | } 59 | 60 | * Add heroku app `heroku create my-websockets-server` 61 | * Commit & Push to heroku `git add -A && git commit -am 'init' && git push heroku master` 62 | * Start app `heroku ps:scale web=1` 63 | * Get the url for your websockes-server instance `heroku info` (see *Web Url*). 64 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* global process, global */ 3 | 'use strict' 4 | 5 | const Y = require('../../yjs/y.node.js') 6 | const debug = require('debug') 7 | const log = debug('y-websockets-server') 8 | const fs = require('fs') 9 | 10 | var config = {} 11 | try { 12 | config = JSON.parse(fs.readFileSync('config.json', 'utf8')) 13 | log('Using provided config.json', config) 14 | } catch (e) {} 15 | 16 | debug.log = console.log.bind(console) 17 | 18 | var minimist = require('minimist') 19 | 20 | try { 21 | // try to require local y-websockets-server 22 | require('./y-websockets-server.js')(Y) 23 | } catch (err) { 24 | console.error(err) 25 | // otherwise require global y-websockets-server 26 | require('y-websockets-server')(Y) 27 | } 28 | 29 | var options = minimist(process.argv.slice(2), { 30 | string: ['port', 'debug', 'db', 'persistence'], 31 | default: { 32 | port: process.env.PORT || config.port || '1234', 33 | debug: false, 34 | db: config.db || 'memory', 35 | redis: process.env.REDIS || config.redis || null 36 | } 37 | }) 38 | 39 | // if Y_RANDOM_PROCESS_KILL is set, the node app is randomly killed (within 8 seconds) 40 | if (process.env.Y_RANDOM_PROCESS_KILL != null) { 41 | console.error('You set Y_RANDOM_PROCESS_KILL environment variable. The process is killed within 8 seconds!') 42 | setInterval(() => { 43 | process.exit(0) 44 | }, Math.floor(Math.random() * 20000)) 45 | } 46 | 47 | var port = Number.parseInt(options.port, 10) 48 | var io = require('socket.io')(port) 49 | 50 | let persistence = null 51 | if (options.redis != null) { 52 | const YRedisPersistence = require('y-redis')(Y) 53 | const checkContentChange = function checkContentChange (y, transaction) { 54 | return transaction.changedParentTypes.has(y.define('xml', Y.XmlFragment)) 55 | } 56 | persistence = new YRedisPersistence(options.redis, checkContentChange) 57 | } 58 | console.log('Running y-websockets-server: port: %s, redis: %s ', port, options.redis) 59 | 60 | global.yInstances = {} 61 | 62 | function getInstanceOfY (room) { 63 | if (global.yInstances[room] == null) { 64 | let yConfig = { 65 | connector: { 66 | name: 'websockets-server', 67 | room: room, 68 | io: io, 69 | debug: !!options.debug 70 | } 71 | } 72 | global.yInstances[room] = new Promise(function (resolve) { 73 | const y = new Y(room, yConfig, persistence) 74 | y.when('connectorReady').then(function () { 75 | resolve(y) 76 | }) 77 | }) 78 | } 79 | return global.yInstances[room] 80 | } 81 | 82 | io.on('connection', function (socket) { 83 | var rooms = [] 84 | socket.on('joinRoom', function (room) { 85 | log('User "%s" joins room "%s"', socket.id, room) 86 | socket.join(room) 87 | getInstanceOfY(room).then(function (y) { 88 | global.y = y // TODO: remove !!! 89 | if (rooms.indexOf(room) === -1) { 90 | y.connector.userJoined(socket.id, 'slave') 91 | rooms.push(room) 92 | } 93 | }) 94 | }) 95 | socket.on('yjsResponsive', function (room, callback) { 96 | log('User "%s" checks responsiveness of room "%s"', socket.id, room) 97 | getInstanceOfY(room).then(function (y) { 98 | y.db.whenTransactionsFinished().then(callback) 99 | }) 100 | }) 101 | socket.on('yjsEvent', function (buffer) { 102 | let decoder = new Y.utils.BinaryDecoder(buffer) 103 | let roomname = decoder.readVarString() 104 | getInstanceOfY(roomname).then(function (y) { 105 | y.connector.receiveMessage(socket.id, buffer) 106 | }) 107 | }) 108 | socket.on('disconnect', function () { 109 | for (var i = 0; i < rooms.length; i++) { 110 | let room = rooms[i] 111 | getInstanceOfY(room).then(function (y) { 112 | var i = rooms.indexOf(room) 113 | if (i >= 0) { 114 | y.connector.userLeft(socket.id) 115 | rooms.splice(i, 1) 116 | } 117 | }) 118 | } 119 | }) 120 | socket.on('leaveRoom', function (room) { 121 | getInstanceOfY(room).then(function (y) { 122 | var i = rooms.indexOf(room) 123 | if (i >= 0) { 124 | y.connector.userLeft(socket.id) 125 | rooms.splice(i, 1) 126 | } 127 | }) 128 | }) 129 | }) 130 | --------------------------------------------------------------------------------