feat(routing,collab,trust): manually expand sticky-session and collab modules
Hyper-P2P Module Tests / unit-all (push) Has been cancelled
Hyper-P2P Module Tests / unit-all (push) Has been cancelled
- sticky-session: unbind, listSessions, bindBatch, remote remove gossip - cursor-presence: peersInDoc, pruneStale - whiteboard-op: listRooms, opCount, pruneRoom - key-rotation: listActiveKeys, revoke, tick activation sweep Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -48,6 +48,22 @@ class HyperP2PCursorPresence extends EventEmitter {
|
|||||||
return [...this._cursors.values()].filter((c) => c.docId === docId)
|
return [...this._cursors.values()].filter((c) => c.docId === docId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peersInDoc (docId) {
|
||||||
|
return [...new Set(this.listCursors(docId).map((c) => c.peer))]
|
||||||
|
}
|
||||||
|
|
||||||
|
pruneStale (maxAgeMs = 60000) {
|
||||||
|
const cutoff = Date.now() - maxAgeMs
|
||||||
|
let n = 0
|
||||||
|
for (const [key, c] of this._cursors) {
|
||||||
|
if ((c.at || 0) < cutoff) {
|
||||||
|
this._cursors.delete(key)
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
removeCursor (docId, peerHex = this.peerHex) {
|
removeCursor (docId, peerHex = this.peerHex) {
|
||||||
const key = `${docId}:${peerHex}`
|
const key = `${docId}:${peerHex}`
|
||||||
const ok = this._cursors.delete(key)
|
const ok = this._cursors.delete(key)
|
||||||
|
|||||||
@@ -53,6 +53,24 @@ class HyperP2PWhiteboardOp extends EventEmitter {
|
|||||||
return entries.length ? entries[0].seq : -1
|
return entries.length ? entries[0].seq : -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listRooms () {
|
||||||
|
return [...new Set(this._log.map((e) => e.roomId))]
|
||||||
|
}
|
||||||
|
|
||||||
|
opCount (roomId = null) {
|
||||||
|
if (roomId) return this.history(roomId, Infinity).length
|
||||||
|
return this._log.length
|
||||||
|
}
|
||||||
|
|
||||||
|
pruneRoom (roomId, keep = 500) {
|
||||||
|
assertNonEmpty(roomId, 'roomId')
|
||||||
|
const roomOps = this._log.filter((e) => e.roomId === roomId)
|
||||||
|
if (roomOps.length <= keep) return 0
|
||||||
|
const drop = roomOps.length - keep
|
||||||
|
this._log = this._log.filter((e) => e.roomId !== roomId).concat(roomOps.slice(-keep))
|
||||||
|
return drop
|
||||||
|
}
|
||||||
|
|
||||||
mergeRemote (entry) {
|
mergeRemote (entry) {
|
||||||
if (!entry || !entry.roomId) return false
|
if (!entry || !entry.roomId) return false
|
||||||
this._stats.gossipIn++
|
this._stats.gossipIn++
|
||||||
|
|||||||
@@ -32,9 +32,40 @@ class HyperP2PStickySession extends EventEmitter {
|
|||||||
return this._bindings.get(sessionId) || null
|
return this._bindings.get(sessionId) || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unbind (sessionId) {
|
||||||
|
assertNonEmpty(sessionId, 'sessionId')
|
||||||
|
const ok = this._bindings.delete(sessionId)
|
||||||
|
if (ok) {
|
||||||
|
gossipSend(this, { type: 'sticky-session-remove', sessionId })
|
||||||
|
this._stats.gossipOut++
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSession (sessionId) {
|
||||||
|
return this._bindings.has(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
listSessions () {
|
||||||
|
return [...this._bindings.entries()].map(([sessionId, peerId]) => ({ sessionId, peerId }))
|
||||||
|
}
|
||||||
|
|
||||||
|
bindBatch (pairs) {
|
||||||
|
if (!Array.isArray(pairs)) throw new Error('pairs must be an array')
|
||||||
|
let n = 0
|
||||||
|
for (const p of pairs) {
|
||||||
|
if (p && this.bind(p.sessionId, p.peerId)) n++
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
_onGossip (d) {
|
_onGossip (d) {
|
||||||
if (!d || d.type !== 'sticky-session-sync' || !d.sessionId) return
|
if (!d || d.type !== 'sticky-session-sync' || !d.sessionId) return
|
||||||
this._stats.gossipIn++
|
this._stats.gossipIn++
|
||||||
|
if (d.type === 'sticky-session-remove') {
|
||||||
|
this._bindings.delete(d.sessionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (d.peerId) this._bindings.set(d.sessionId, d.peerId)
|
if (d.peerId) this._bindings.set(d.sessionId, d.peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,25 @@ class HyperP2PKeyRotation extends EventEmitter {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
_maybeActivate (keyId) {
|
listActiveKeys () {
|
||||||
|
return [...this._active.keys()]
|
||||||
|
}
|
||||||
|
|
||||||
|
revoke (keyId) {
|
||||||
|
assertNonEmpty(keyId, 'keyId')
|
||||||
|
const ok = this._active.delete(keyId)
|
||||||
|
this._pending.delete(keyId)
|
||||||
|
if (ok) this.emit('revoked', { keyId })
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
tick (now = Date.now()) {
|
||||||
|
for (const keyId of this._pending.keys()) this._maybeActivate(keyId, now)
|
||||||
|
return this._active.size
|
||||||
|
}
|
||||||
|
|
||||||
|
_maybeActivate (keyId, now = Date.now()) {
|
||||||
const list = this._pending.get(keyId) || []
|
const list = this._pending.get(keyId) || []
|
||||||
const now = Date.now()
|
|
||||||
for (const e of list) {
|
for (const e of list) {
|
||||||
if (e.activateAt <= now) {
|
if (e.activateAt <= now) {
|
||||||
const active = { keyId, material: e.newMaterial, activatedAt: now }
|
const active = { keyId, material: e.newMaterial, activatedAt: now }
|
||||||
|
|||||||
Reference in New Issue
Block a user