OverviewFilesCommitsIssuesDocsReleasesPeers

keet-deobfuscated / @holepunchto / keet-core / lib / core / users / index.js

236 lines · javascript ref:
const e = require("@holepunchto/keet-registry-client"),
  t = require("ready-resource"),
  s = require("@holepunchto/keet-registry"),
  i = require("b4a"),
  { createUsernameReservationRequest: r } = require("../invitations"),
  { Readable: n } = require("streamx"),
  a = require("@holepunchto/keet-registry/lib/errors")
class o extends t {
  constructor(e, { db: t, reserved: s, blobs: i, rpc: r, rateLimit: n = {} } = {}) {
    ;(super(),
      (this.dbKey = t),
      (this.reservedKey = s),
      (this.blobsKey = i),
      (this.rpcKeys = r),
      (this.keet = e),
      (this.store = null),
      (this.swarm = null),
      (this.swarmWorker = null),
      (this.db = null),
      (this.reserved = null),
      (this.blobs = null),
      (this._keetRegistry = null),
      (this._rpcClient = null),
      (this._registryRpcOpts = { rateLimit: n }),
      (this._subs = []),
      (this._watchingReservation = !1),
      (this._watchingRecord = !1),
      (this._boundEmit = this._emit.bind(this)),
      (this._boundCatch = this._catch.bind(this)),
      (this._boundLookupOwn = null))
  }
  async _open() {
    ;((this.store = this.keet.store.session()),
      (this.swarmWorker = this.keet.swarmWorker),
      (this.db = this.store.get({ key: this.dbKey })),
      (this.reserved = this.store.get({ key: this.reservedKey })),
      (this.blobs = this.store.get({ key: this.blobsKey })),
      (this._keetRegistry = new s(this.db, this.reserved, this.blobs, { extension: !1 })),
      (this._rpcClient = new e(this.rpcKeys, this.keet.rpcClient, this._registryRpcOpts)),
      await this._keetRegistry.ready())
    const t = await this._getIdentity()
    ;((this._boundLookupOwn = this._safeLookupOwn.bind(this, t)),
      this._lookupOwn(t).catch(this.keet.onerror),
      this._keetRegistry.reserved.core.once("append", this._boundLookupOwn))
  }
  _emit(e, t = null, s = null) {
    for (const i of this._subs) {
      if (this.keet.closing) return
      i.push({ status: h(e), reservation: t, record: s })
    }
  }
  async _catch(e) {
    this._unwatch()
    for (const t of this._subs) t.push({ status: h({ hasError: !0 }), error: e.code ?? e.message })
  }
  async _close() {
    ;(this._unwatch(), this._rpcClient.destroy())
    try {
      await this._keetRegistry.close()
    } catch (e) {
      if ("REQUEST_CANCELLED" !== e.code) throw e
    }
    await this.store.close()
  }
  async _getIdentity() {
    const e = await this.keet.local.get("@local/identity")
    if (!e) throw new Error("Identity is required")
    return e
  }
  _encode(e) {
    return e ? { ...e, contactInfo: this.keet.internalEncodeContactInfo(e) } : null
  }
  async _safeLookupOwn(e) {
    try {
      return await this._lookupOwn(e)
    } catch (e) {
      this.keet.onerror(e)
    }
  }
  async _lookupOwn(e) {
    const [t, s] = await Promise.all([
      this._keetRegistry.getByKey(e.publicKey),
      this._keetRegistry.getReservationsByKey(e.publicKey),
    ])
    if (!t) {
      if (s.length && !this._watchingRecord) {
        const e = s.pop()
        ;(this._boundEmit({ isReserved: !0 }, { username: e.username }), this._claimReservation(e))
      }
      return null
    }
    return this._encode(t)
  }
  async lookup(e) {
    if (!e) throw new Error("Username required")
    ;(this.opened || (await this.ready()),
      this.swarmWorker.startDiscoverySwarming(this.db.discoveryKey, {
        deferSwarming: !1,
        limit: 1,
      }))
    const t = await this._keetRegistry.get(e)
    return this._encode(t)
  }
  async lookupByKey(e) {
    if (!e) throw new Error("Public key required")
    ;(this.opened || (await this.ready()),
      this.swarmWorker.startDiscoverySwarming(this.db.discoveryKey, {
        deferSwarming: !1,
        limit: 1,
      }))
    const t = await this._getIdentity()
    if (i.equals(t.publicKey, e)) return this._lookupOwn(t)
    const s = await this._keetRegistry.getByKey(e)
    return this._encode(s)
  }
  async search(e, t) {
    if (!e) return []
    ;(this.opened || (await this.ready()),
      this.swarmWorker.startDiscoverySwarming(this.db.discoveryKey, {
        deferSwarming: !1,
        limit: 1,
      }))
    const s = []
    for await (const i of this._keetRegistry.search(e, t)) s.push(this._encode(i))
    return s
  }
  async isAvailable(e) {
    if (!e) throw new Error("Username required")
    return (
      this.opened || (await this.ready()),
      this.swarmWorker.startDiscoverySwarming(this.db.discoveryKey, {
        deferSwarming: !1,
        limit: 1,
      }),
      this._keetRegistry.isAvailable(e)
    )
  }
  async getReservationsByKey(e) {
    if (!e) throw new Error("Public key required")
    return (
      this.opened || (await this.ready()),
      this.swarmWorker.startDiscoverySwarming(this.db.discoveryKey, {
        deferSwarming: !1,
        limit: 1,
      }),
      this._keetRegistry.getReservationsByKey(e)
    )
  }
  async internalUpdateRegistryMember(e) {
    try {
      ;(await s.verifyPut(this._keetRegistry, e), await this._rpcClient.putRecord(e))
    } catch (e) {
      this._boundCatch(e)
    }
  }
  async _pushOwnRecord() {
    const { publicKey: e } = await this._getIdentity(),
      t = await this._keetRegistry.getByKey(e)
    t && this._boundEmit({ hasRecord: !0 }, null, t)
  }
  subscribeUsername() {
    const e = this
    return new n({
      open(t) {
        ;(e._subs.push(this), e._pushOwnRecord().catch(e.keet.onerror), t(null))
      },
      destroy(t) {
        const s = e._subs.indexOf(this)
        ;(-1 !== s && e._subs.splice(s, 1), t(null))
      },
    })
  }
  async update(e) {
    if (!e) throw new Error("Username required")
    if ((this.opened || (await this.ready()), this._watchingReservation))
      throw new Error("Registration already in progress")
    const { publicKey: t, deviceKeyPair: i, deviceProof: n } = await this._getIdentity()
    ;(this.swarmWorker.startDiscoverySwarming(this.db.discoveryKey, {
      deferSwarming: !1,
      limit: 1,
    }),
      this._watchReservation(e, t))
    try {
      this._boundEmit({ isReserving: !0 }, { username: e })
      const a = r(e, { publicKey: t, deviceKeyPair: i, deviceProof: n })
      ;(await s.verifyReservation(a), await this._rpcClient.reserve(a))
    } catch (e) {
      return (this._boundCatch(e), !1)
    }
    return !0
  }
  _unwatch() {
    ;(this._keetRegistry.unwatch(), (this._watchingRecord = !1), (this._watchingReservation = !1))
  }
  async _claimReservation({ username: e, memberKey: t }) {
    ;(this._keetRegistry.reserved.core.removeListener("append", this._boundLookupOwn),
      this._boundEmit({ isReserved: !0 }, { username: e, memberKey: t }),
      this._watchRecord(e, t),
      await this.keet
        .internalUpdateProfile({ username: e }, { forceUpdateRegistry: !0 })
        .catch(this._boundCatch))
  }
  _watchReservation(e, t) {
    const s = this
    ;((this._watchingReservation = !0),
      this._keetRegistry.watchReservation(e).then(async function (e) {
        ;((s._watchingReservation = !1),
          null !== e &&
            (i.equals(e.memberKey, t)
              ? await s._claimReservation(e)
              : s._boundCatch(a.NAME_UNAVAILABLE())))
      }, this._boundCatch))
  }
  _watchRecord(e, t) {
    const s = this,
      i = { attestation: { memberKey: t } }
    ;((this._watchingRecord = !0),
      this._keetRegistry
        .watchRecord(i, function (t) {
          return t.profile.username === e
        })
        .then(function (e) {
          ;((s._watchingRecord = !1), null !== e && s._boundEmit({ hasRecord: !0 }, null, e))
        }, this._boundCatch))
  }
}
function h(e) {
  return {
    hasRecord: !0 === e.hasRecord,
    hasError: !0 === e.hasError,
    isReserving: !0 === e.isReserving,
    isReserved: !0 === e.isReserved || !0 === e.hasRecord,
  }
}
module.exports = o