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