yjs/ydb/ydb-client.js
2018-10-08 16:09:50 +02:00

136 lines
4.0 KiB
JavaScript

/* eslint-env browser */
import * as idbactions from './idbactions.js'
import * as globals from './globals.js'
import * as message from './message.js'
import * as bc from './broadcastchannel.js'
import * as encoding from './encoding.js'
import * as logging from './logging.js'
import * as idb from './idb.js'
import Y from '../src/Y.js'
export class YdbClient {
constructor (url, db) {
this.url = url
this.ws = new WebSocket(url)
this.rooms = new Map()
this.db = db
this.connected = false
initWS(this, this.ws)
}
/**
* Open a Yjs instance that connects to `roomname`.
* @param {string} roomname
* @return {Y}
*/
getY (roomname) {
const y = new Y(roomname)
y.on('afterTransaction', function () {
debugger
})
return y
}
}
/**
* Initialize WebSocket connection. Try to reconnect on error/disconnect.
* @param {YdbClient} ydb
* @param {WebSocket} ws
*/
const initWS = (ydb, ws) => {
ws.binaryType = 'arraybuffer'
ws.onclose = () => {
ydb.connected = false
logging.log('Disconnected from ydb. Reconnecting..')
ydb.ws = new WebSocket(ydb.url)
initWS(ydb, ws)
}
ws.onopen = () => {
const t = idbactions.createTransaction(ydb.db)
globals.pall([idbactions.getRoomMetas(t), idbactions.getUnconfirmedSubscriptions(t), idbactions.getUnconfirmedUpdates(t)]).then(([metas, us, unconfirmedUpdates]) => {
const subs = []
metas.forEach(meta => {
subs.push({
room: meta.room,
offset: meta.offset
})
})
us.forEach(room => {
subs.push({
room, offset: 0
})
})
ydb.connected = true
const encoder = encoding.createEncoder()
encoding.writeArrayBuffer(encoder, message.createSub(subs))
encoding.writeArrayBuffer(encoder, unconfirmedUpdates)
send(ydb, encoding.toBuffer(encoder))
})
}
ws.onmessage = event => message.readMessage(ydb, event.data)
}
// maps from dbNamespace to db
const dbPromises = new Map()
/**
* Factory function. Get a ydb instance that connects to url, and uses dbNamespace as indexeddb namespace.
* Create if it does not exist yet.
*
* @param {string} url
* @param {string} dbNamespace
* @return {Promise<YdbClient>}
*/
export const get = (url, dbNamespace = 'ydb') => {
if (!dbPromises.has(dbNamespace)) {
dbPromises.set(dbNamespace, idbactions.openDB(dbNamespace))
}
return dbPromises.get(dbNamespace).then(db => globals.presolve(new YdbClient(url, db)))
}
/**
* Remove a db namespace. Call this to remove any persisted data. Make sure to close active sessions.
* TODO: destroy active ydbClient sessions / throw if a session is still active
* @param {string} dbNamespace
* @return {Promise}
*/
export const clear = (dbNamespace = 'ydb') => idb.deleteDB(dbNamespace)
/**
* @param {YdbClient} ydb
* @param {ArrayBuffer} m
*/
export const send = (ydb, m) => ydb.connected && ydb.ws.send(m)
/**
* @param {YdbClient} ydb
* @param {string} room
* @param {ArrayBuffer} update
*/
export const update = (ydb, room, update) => {
bc.publish(room, update)
const t = idbactions.createTransaction(ydb.db)
logging.log(`Write Unconfirmed Update. room "${room}", ${JSON.stringify(update)}`)
return idbactions.writeClientUnconfirmed(t, room, update).then(clientConf => {
logging.log(`Send Unconfirmed Update. connected ${ydb.connected} room "${room}", clientConf ${clientConf}, ${logging.arrayBufferToString(update)}`)
send(ydb, message.createUpdate(room, update, clientConf))
})
}
export const subscribe = (ydb, room, f) => {
bc.subscribe(room, f)
const t = idbactions.createTransaction(ydb.db)
idbactions.getRoomData(t, room).then(data => {
if (data.byteLength > 0) {
f(data)
}
})
idbactions.getRoomMeta(t, room).then(meta => {
if (meta === undefined) {
logging.log(`Send Subscribe. room "${room}", offset ${0}`)
// TODO: maybe set prelim meta value so we don't sub twice
send(ydb, message.createSub([{ room, offset: 0 }]))
idbactions.writeUnconfirmedSubscription(t, room)
}
})
}