136 lines
4.0 KiB
JavaScript
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)
|
|
}
|
|
})
|
|
}
|