Skip to content

Instantly share code, notes, and snippets.

@garth
Last active January 18, 2024 21:58
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save garth/4d4d66ed9090a36affb665c098885ba7 to your computer and use it in GitHub Desktop.
Save garth/4d4d66ed9090a36affb665c098885ba7 to your computer and use it in GitHub Desktop.
A Yjs provider for socketsupply

You can use it the same as you do with other yjs providers, but you have to pass it a socket which can be created with someting like this:

// set the peer id
const peerId = window.localStorage.getItem('peerId') ?? (await Encryption.createId())
window.localStorage.setItem('peerId', peerId)

// set the signing keys
const keySeed = window.localStorage.getItem('keySeed') ?? createId()
window.localStorage.setItem('keySeed', keySeed)
const signingKeys = await Encryption.createKeyPair(keySeed)

// set the cluster id
const clusterId = await Encryption.createClusterId(CLUSTER_ID_SEED)

// create the network socket
const socket = await network({ peerId, clusterId, signingKeys })
import * as Y from 'yjs'
import { Observable } from 'lib0/observable'
import { Encryption } from 'socket:network'
import { applyAwarenessUpdate, Awareness, encodeAwarenessUpdate, removeAwarenessStates } from 'y-protocols/awareness.js'
import { Buffer } from 'buffer'
const uint8ArrayToBuffer = (uint8array: Uint8Array): Buffer => {
return Buffer.from(uint8array)
}
const bufferToUint8Array = (buffer: Buffer): Uint8Array => {
return Uint8Array.from(buffer)
}
export class SocketProvider extends Observable<string> {
doc: Y.Doc
awareness: Awareness
cluster: any
_verifyTimer: NodeJS.Timeout | null = null
constructor(socket: any, roomname: string, doc: Y.Doc) {
super()
this.doc = doc
this.awareness = new Awareness(this.doc)
void Encryption.createSharedKey(roomname).then((sharedKey) => {
socket.subcluster({ sharedKey }).then((cluster: any) => {
this.cluster = cluster
this.cluster.on('verify', this._verifyHandler)
this.cluster.on('update', this._clusterUpdateHandler)
this.doc.on('update', this._docUpdateHandler)
this.cluster.on('awareness', this._clusterAwarenessHandler)
this.awareness.on('update', this._awarenessUpdateHandler)
this._verifyTimer = setInterval(() => {
this.cluster.emit('verify', uint8ArrayToBuffer(Y.encodeStateVector(this.doc)))
}, 30_000)
})
})
}
_verifyHandler = (vector?: Buffer) => {
if (vector != null) {
try {
const update = Y.encodeStateAsUpdate(this.doc, bufferToUint8Array(vector))
this.cluster.emit('update', uint8ArrayToBuffer(update))
} catch (e) {
console.error('encodeStateAsUpdate', e)
}
}
}
_clusterUpdateHandler = (update?: Buffer) => {
if (update != null) {
try {
Y.applyUpdate(this.doc, bufferToUint8Array(update), 'socket')
} catch (e) {
console.error('applyUpdate failed', e)
}
}
}
_docUpdateHandler = (update: Uint8Array, origin: any) => {
if (origin !== 'socket') {
this.cluster.emit('update', uint8ArrayToBuffer(update))
}
}
_clusterAwarenessHandler = (update: Buffer) => {
if (update != null) {
try {
applyAwarenessUpdate(this.awareness, bufferToUint8Array(update), 'socket')
} catch (e) {
console.error('applyAwarenessUpdate failed', e)
}
}
}
_awarenessUpdateHandler = ({ added, updated, removed }: { added: []; updated: []; removed: [] }, origin: any) => {
if (origin === 'local') {
const update = encodeAwarenessUpdate(this.awareness, [...added, ...updated, ...removed])
this.cluster.emit('awareness', uint8ArrayToBuffer(update))
}
}
destroy() {
if (this._verifyTimer != null) {
clearInterval(this._verifyTimer)
}
this.doc.off('update', this._docUpdateHandler)
removeAwarenessStates(
this.awareness,
Array.from(this.awareness.getStates().keys()).filter((client) => client !== this.doc.clientID),
'provider',
)
this.awareness.destroy()
// this.cluster.destroy()
super.destroy()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment