OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / lib / replication.js

113 lines · javascript ref:
/**
 * ReplicationManager — Hyperswarm P2P replication
 *
 * Announces local notebook drives on the swarm, joins remote drives.
 * Uses store.replicate(conn) so only cores the remote requests by
 * discovery key are served — the registry core's key is never shared.
 */

import Hyperswarm from 'hyperswarm'

export class ReplicationManager {
  constructor (store, driveManager) {
    this.store = store
    this.driveManager = driveManager
    this.swarm = null
    this._discoveries = new Map() // slug → discovery handle
    this._connections = new Set()
  }

  /**
   * Create swarm and wire store.replicate on every connection.
   */
  async start () {
    this.swarm = new Hyperswarm()

    this.swarm.on('connection', (conn, info) => {
      this.store.replicate(conn)
      this._connections.add(conn)
      conn.on('close', () => { this._connections.delete(conn) })
      conn.on('error', () => {}) // error is followed by close, which handles cleanup
    })
  }

  /**
   * Destroy swarm cleanly.
   */
  async stop () {
    if (!this.swarm) return
    await this.swarm.destroy()
    this.swarm = null
    this._discoveries.clear()
    this._connections.clear()
  }

  /**
   * Announce a local notebook on the swarm (server + client mode).
   */
  async announce (slug) {
    if (!this.swarm) return

    const info = await this.driveManager.getDriveInfo(slug)
    if (!info) return

    const discoveryKey = Buffer.from(info.discoveryKey, 'hex')
    const discovery = this.swarm.join(discoveryKey, { server: true, client: true })
    this._discoveries.set(slug, discovery)

    await this.driveManager.setShared(slug, true)
  }

  /**
   * Stop announcing a notebook.
   */
  async unannounce (slug) {
    const discovery = this._discoveries.get(slug)
    if (discovery) {
      await discovery.destroy()
      this._discoveries.delete(slug)
    }

    await this.driveManager.setShared(slug, false)
  }

  /**
   * Announce all notebooks that have isShared === true.
   */
  async announceAll () {
    const drives = await this.driveManager.listDrives()
    for (const entry of drives) {
      if (entry.isShared && entry.isLocal) {
        await this.announce(entry.slug)
      }
    }
  }

  /**
   * Join a remote notebook topic (client-only mode).
   */
  async joinRemote (slug) {
    if (!this.swarm) return

    const info = await this.driveManager.getDriveInfo(slug)
    if (!info) return

    const discoveryKey = Buffer.from(info.discoveryKey, 'hex')
    const discovery = this.swarm.join(discoveryKey, { server: false, client: true })
    this._discoveries.set(slug, discovery)
  }

  /**
   * Get current sync status.
   */
  getStatus () {
    return {
      swarmActive: this.swarm !== null,
      peersConnected: this._connections.size,
      announced: [...this._discoveries.keys()]
    }
  }
}

export default ReplicationManager