implemented websocket provider
This commit is contained in:
parent
e1ece6dc66
commit
67bbc0a3fe
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ docs
|
|||||||
/examples/yjs-dist.js*
|
/examples/yjs-dist.js*
|
||||||
.vscode
|
.vscode
|
||||||
.yjsPersisted
|
.yjsPersisted
|
||||||
|
build
|
@ -1,25 +1,13 @@
|
|||||||
import { createYdbClient } from '../../YdbClient/index.js'
|
/* eslint-env browser */
|
||||||
import Y from '../../src/Y.dist.js'
|
import * as Y from '../../src/index.js'
|
||||||
|
import WebsocketProvider from '../../provider/websocket/WebSocketProvider.js'
|
||||||
|
|
||||||
createYdbClient('ws://localhost:8899/ws').then(ydbclient => {
|
const provider = new WebsocketProvider('ws://localhost:1234/')
|
||||||
const y = ydbclient.getY('textarea')
|
const ydocument = provider.get('textarea')
|
||||||
let type = y.define('textarea', Y.Text)
|
const type = ydocument.define('textarea', Y.Text)
|
||||||
let textarea = document.querySelector('textarea')
|
const textarea = document.querySelector('textarea')
|
||||||
window.binding = new Y.TextareaBinding(type, textarea)
|
const binding = new Y.TextareaBinding(type, textarea)
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
window.textareaExample = {
|
||||||
let y = new Y('textarea-example', {
|
provider, ydocument, type, textarea, binding
|
||||||
connector: {
|
}
|
||||||
name: 'websockets-client',
|
|
||||||
url: 'http://127.0.0.1:1234'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
window.yTextarea = y
|
|
||||||
|
|
||||||
// bind the textarea to a shared text element
|
|
||||||
let type = y.define('textarea', Y.Text)
|
|
||||||
let textarea = document.querySelector('textarea')
|
|
||||||
window.binding = new Y.TextareaBinding(type, textarea)
|
|
||||||
*/
|
|
||||||
|
17
package-lock.json
generated
17
package-lock.json
generated
@ -271,6 +271,11 @@
|
|||||||
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
|
"integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"async-limiter": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||||
|
},
|
||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@ -2597,7 +2602,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-generator": {
|
"babel-generator": {
|
||||||
"version": "6.11.4",
|
"version": "6.11.4",
|
||||||
"resolved": "http://registry.npmjs.org/babel-generator/-/babel-generator-6.11.4.tgz",
|
"resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.11.4.tgz",
|
||||||
"integrity": "sha1-FPaTOrsgxiZm0n47e59bncBxKpo=",
|
"integrity": "sha1-FPaTOrsgxiZm0n47e59bncBxKpo=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -2628,7 +2633,7 @@
|
|||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -7019,6 +7024,14 @@
|
|||||||
"mkdirp": "^0.5.1"
|
"mkdirp": "^0.5.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==",
|
||||||
|
"requires": {
|
||||||
|
"async-limiter": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"xml-name-validator": {
|
"xml-name-validator": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz",
|
||||||
|
@ -69,5 +69,8 @@
|
|||||||
"rollup-regenerator-runtime": "^6.23.1",
|
"rollup-regenerator-runtime": "^6.23.1",
|
||||||
"rollup-watch": "^3.2.2",
|
"rollup-watch": "^3.2.2",
|
||||||
"standard": "^11.0.1"
|
"standard": "^11.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^6.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
85
provider/websocket/WebSocketProvider.js
Normal file
85
provider/websocket/WebSocketProvider.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/* eslint-env browser */
|
||||||
|
|
||||||
|
import * as Y from '../../src/index.js'
|
||||||
|
export * from '../../src/index.js'
|
||||||
|
|
||||||
|
const reconnectTimeout = 100
|
||||||
|
|
||||||
|
const setupWS = (doc, url) => {
|
||||||
|
const websocket = new WebSocket(url)
|
||||||
|
websocket.binaryType = 'arraybuffer'
|
||||||
|
doc.ws = websocket
|
||||||
|
websocket.onmessage = event => {
|
||||||
|
const decoder = Y.createDecoder(event.data)
|
||||||
|
const encoder = Y.createEncoder()
|
||||||
|
doc.mux(() =>
|
||||||
|
Y.readMessage(decoder, encoder, doc)
|
||||||
|
)
|
||||||
|
if (Y.length(encoder) > 0) {
|
||||||
|
websocket.send(Y.toBuffer(encoder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
websocket.onclose = () => {
|
||||||
|
doc.ws = null
|
||||||
|
doc.wsconnected = false
|
||||||
|
doc.emit('status', {
|
||||||
|
status: 'connected'
|
||||||
|
})
|
||||||
|
setTimeout(setupWS, reconnectTimeout, doc, url)
|
||||||
|
}
|
||||||
|
websocket.onopen = () => {
|
||||||
|
doc.wsconnected = true
|
||||||
|
doc.emit('status', {
|
||||||
|
status: 'disconnected'
|
||||||
|
})
|
||||||
|
// always send sync step 1 when connected
|
||||||
|
const encoder = Y.createEncoder()
|
||||||
|
Y.writeSyncStep1(encoder, doc)
|
||||||
|
websocket.send(Y.toBuffer(encoder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const broadcastUpdate = (y, transaction) => {
|
||||||
|
if (y.wsconnected && transaction.encodedStructsLen > 0) {
|
||||||
|
y.mux(() => {
|
||||||
|
const encoder = Y.createEncoder()
|
||||||
|
Y.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
|
||||||
|
y.ws.send(Y.toBuffer(encoder))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebsocketsSharedDocument extends Y.Y {
|
||||||
|
constructor (url) {
|
||||||
|
super()
|
||||||
|
this.wsconnected = false
|
||||||
|
this.mux = Y.createMutex()
|
||||||
|
setupWS(this, url)
|
||||||
|
this.on('afterTransaction', broadcastUpdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class WebsocketProvider {
|
||||||
|
constructor (url) {
|
||||||
|
// ensure that url is always ends with /
|
||||||
|
while (url[url.length - 1] === '/') {
|
||||||
|
url = url.slice(0, url.length - 1)
|
||||||
|
}
|
||||||
|
this.url = url + '/'
|
||||||
|
/**
|
||||||
|
* @type {Map<string, WebsocketsSharedDocument>}
|
||||||
|
*/
|
||||||
|
this.docs = new Map()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @return {WebsocketsSharedDocument}
|
||||||
|
*/
|
||||||
|
get (name) {
|
||||||
|
let doc = this.docs.get(name)
|
||||||
|
if (doc === undefined) {
|
||||||
|
doc = new WebsocketsSharedDocument(this.url + name)
|
||||||
|
}
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
}
|
53
provider/websocket/server.js
Normal file
53
provider/websocket/server.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
const Y = require('../../build/node/index.js')
|
||||||
|
const WebSocket = require('ws')
|
||||||
|
const wss = new WebSocket.Server({ port: 1234 })
|
||||||
|
const docs = new Map()
|
||||||
|
|
||||||
|
const afterTransaction = (doc, transaction) => {
|
||||||
|
if (transaction.encodedStructsLen > 0) {
|
||||||
|
const encoder = Y.createEncoder()
|
||||||
|
Y.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
|
||||||
|
const message = Y.toBuffer(encoder)
|
||||||
|
doc.conns.forEach(conn => conn.send(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WSSharedDoc extends Y.Y {
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
this.mux = Y.createMutex()
|
||||||
|
this.conns = new Set()
|
||||||
|
this.on('afterTransaction', afterTransaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageListener = (conn, doc, message) => {
|
||||||
|
const encoder = Y.createEncoder()
|
||||||
|
const decoder = Y.createDecoder(message)
|
||||||
|
Y.readMessage(decoder, encoder, doc)
|
||||||
|
if (Y.length(encoder) > 0) {
|
||||||
|
conn.send(Y.toBuffer(encoder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupConnection = (conn, req) => {
|
||||||
|
conn.binaryType = 'arraybuffer'
|
||||||
|
// get doc, create if it does not exist yet
|
||||||
|
let doc = docs.get(req.url.slice(1))
|
||||||
|
if (doc === undefined) {
|
||||||
|
doc = new WSSharedDoc()
|
||||||
|
docs.set(req.url.slice(1), doc)
|
||||||
|
}
|
||||||
|
doc.conns.add(conn)
|
||||||
|
// listen and reply to events
|
||||||
|
conn.on('message', message => messageListener(conn, doc, message))
|
||||||
|
conn.on('close', () =>
|
||||||
|
doc.conns.delete(conn)
|
||||||
|
)
|
||||||
|
// send sync step 1
|
||||||
|
const encoder = Y.createEncoder()
|
||||||
|
Y.writeSyncStep1(encoder, doc)
|
||||||
|
conn.send(Y.toBuffer(encoder))
|
||||||
|
}
|
||||||
|
|
||||||
|
wss.on('connection', setupConnection)
|
@ -1,28 +1,18 @@
|
|||||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
const pkg = require('./package.json')
|
||||||
import commonjs from 'rollup-plugin-commonjs'
|
|
||||||
var pkg = require('./package.json')
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
input: 'src/Y.dist.js',
|
input: 'src/index.js',
|
||||||
nameame: 'Y',
|
|
||||||
sourcemap: true,
|
|
||||||
output: {
|
output: {
|
||||||
file: 'y.node.js',
|
name: 'Y',
|
||||||
format: 'cjs'
|
file: 'build/node/index.js',
|
||||||
},
|
format: 'cjs',
|
||||||
plugins: [
|
sourcemap: true,
|
||||||
nodeResolve({
|
banner: `
|
||||||
main: true,
|
|
||||||
module: true,
|
|
||||||
browser: true
|
|
||||||
}),
|
|
||||||
commonjs()
|
|
||||||
],
|
|
||||||
banner: `
|
|
||||||
/**
|
/**
|
||||||
* ${pkg.name} - ${pkg.description}
|
* ${pkg.name} - ${pkg.description}
|
||||||
* @version v${pkg.version}
|
* @version v${pkg.version}
|
||||||
* @license ${pkg.license}
|
* @license ${pkg.license}
|
||||||
*/
|
*/
|
||||||
`
|
`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,10 @@ export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js'
|
|||||||
|
|
||||||
export { default as domToType } from './Bindings/DomBinding/domToType.js'
|
export { default as domToType } from './Bindings/DomBinding/domToType.js'
|
||||||
export { domsToTypes, switchAssociation } from './Bindings/DomBinding/util.js'
|
export { domsToTypes, switchAssociation } from './Bindings/DomBinding/util.js'
|
||||||
|
export * from './message.js'
|
||||||
|
export * from '../lib/encoding.js'
|
||||||
|
export * from '../lib/decoding.js'
|
||||||
|
export * from '../lib/mutex.js'
|
||||||
|
|
||||||
// TODO: reorder (Item* should have low numbers)
|
// TODO: reorder (Item* should have low numbers)
|
||||||
registerStruct(0, ItemJSON)
|
registerStruct(0, ItemJSON)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user