const e = require("keet-identity-key"),
{
DeviceSchema: i,
MemberSchema: t,
InvitationSchema: s,
} = require("@holepunchto/keet-core-schemas"),
{ MemberCapabilities: a } = require("@holepunchto/keet-core-api"),
n = require("compact-encoding"),
c = require("b4a"),
o = require("../invitations"),
{
AddDeviceOperationV1: r,
AddDeviceOperationV2: v,
UpdateDeviceOperation: l,
RemoveDeviceOperationV1: u,
RemoveDeviceOperationV2: d,
} = i.Operations,
{ UpdateMemberOperationV1: p, UpdateMemberOperationV2: y } = t.Operations,
{ InvitationUserData: h } = s.Messages,
b = 0,
m = 1
module.exports = class {
static Operations = new Set([r.TYPE, v.TYPE, l.TYPE, u.TYPE, d.TYPE, p.TYPE, y.TYPE])
constructor(e) {
;((this.view = e),
(this.members = null),
(this.invitations = null),
(this.attestations = null),
(this.devices = null),
(this.deviceCount = null),
(this.mobileDeviceCount = null),
(this.deviceKeysByNoisePublicKey = null))
}
initialize() {
;((this.members = this.view.members),
(this.invitations = this.view.invitations),
(this.attestations = this.view.attestations),
(this.devices = this.view.collections.devices),
(this.deviceCount = this.view.indexes.deviceCount),
(this.mobileDeviceCount = this.view.indexes.mobileDeviceCount),
(this.deviceKeysByNoisePublicKey = this.view.indexes.deviceKeysByNoisePublicKey))
}
_verifyInvitationReceipt(e, i) {
const t = o.verifyReceipt(i, e.receipt, { checkExpiration: !1 })
if (!t) return null
const s = n.decode(h, t)
return c.equals(s.deviceKey, e.key) && c.equals(s.noisePublicKey, e.noisePublicKey) ? s : null
}
_verifyAttestation(i, t, s, a) {
if (!s) return null
const n = e.verify(s, t, { receipt: a, expectedIdentity: i.attestsTo })
return n ? n.receipt : null
}
async _addDeviceFromInvitation(e) {
const i = e.value
if (!i.receipt || !i.invitation) return
const [t, s] = await Promise.all([
this.getDevice(i.key),
this.invitations.getInvitation(i.invitation),
])
if (!s) return
let n = null
if (s.attestsTo) {
const e = await this.members.getMember(s.attestsTo)
n = e?.latestReceipt
}
const c = this._verifyInvitationReceipt(i, s)
if (!c) return
let o = null
if (s.attestsTo && ((o = this._verifyAttestation(s, c.deviceKey, c.attestation, n)), !o)) return
let r = null
;((r = s.attestsTo
? await this.members.getMember(s.attestsTo)
: await this.members.getMemberForDeviceKey(c.deviceKey, { full: !0 })),
s.uses >= 1 && ((s.capabilities &= ~a.CAN_INDEX), (s.capabilities &= ~a.CAN_MODERATE)))
const v = (r?.capabilities || 0) & ~a.CAN_INDEX
if (
((i.capabilities = v | s.capabilities),
await this.view.base.addWriter(i.key, { indexer: 0 !== (i.capabilities & a.CAN_INDEX) }),
await this.devices.update(i.key, i, { upsert: !0 }),
t)
)
await this.members.updateExistingMember(i)
else {
const e = o ? { memberKey: s.attestsTo, receipt: n } : null
await this.members.addPendingMember({ ...i, attestation: e })
}
;(s.uses++, await this.invitations.updateInvitation(i.invitation, s))
}
async _manuallyAddDevice(e, { id: i }) {
const t = e.value
;(await this.members.isModerator(i.key)) &&
(await this.view.base.addWriter(t.key, { indexer: 0 !== (t.capabilities & a.CAN_INDEX) }),
await this.devices.update(t.key, t, { upsert: !0 }),
await this.members.addPendingMember(t))
}
_applyAddDeviceOperation(e, i) {
return e.value.invitation ? this._addDeviceFromInvitation(e, i) : this._manuallyAddDevice(e, i)
}
async _applyUpdateDeviceOperation(e, { id: i }) {
const t = await this.getDevice(i.key)
t && (await this.devices.update(t.key, { ...t, name: e.value.name || t.name }))
}
async _applyUpdateMemberOperation(e, { id: i }) {
const t = await this.getDevice(i.key)
if (!t) return
const s = e.value.pushNotifications || e.value.isMobile
!t.isMobile && s && (await this.devices.update(t.key, { ...t, isMobile: s }, { upsert: !0 }))
}
async _applyRemoveDeviceOperationV1() {}
async _applyRemoveDeviceOperationV2(e, { id: i, timestamp: t }) {
c.equals(e.value.key, i.key) &&
this.view.base.removeable(i.key) &&
(await this.members.removeMemberForDeviceKey(i, t),
await this.devices.delete(i.key),
await this.view.base.removeWriter(i.key))
}
async _indexDevicesKeysByNoisePublicKey(e, i, t) {
;(e && i && c.equals(e.noisePublicKey, i.noisePublicKey)) ||
(e?.noisePublicKey && (await this.deviceKeysByNoisePublicKey.delete(e.noisePublicKey, t)),
i?.noisePublicKey && (await this.deviceKeysByNoisePublicKey.insert(i.noisePublicKey, t)))
}
async _indexDeviceCount(e, i) {
if (e && i) return
const t = await this.deviceCount.get(0)
let s = t ? t.value : 0
;(e && (s -= 1), i && (s += 1), await this.deviceCount.insert(0, s))
}
async _indexMobileDeviceCount(e, i) {
if (e && i && e.isMobile === i.isMobile) return
const t = await this.mobileDeviceCount.get(1)
let s = t ? t.value : 0
;(e?.isMobile && (s -= 1), i?.isMobile && (s += 1), await this.mobileDeviceCount.insert(1, s))
}
async addBootstrapMember(e, i) {
const t = { key: e, noisePublicKey: i, capabilities: a.ALL_CAPABILITIES }
;(await this.view.base.addWriter(e, { indexer: !0 }),
await this.devices.update(e, t, { upsert: !0 }),
await this.members.addPendingMember(t))
}
async getDevice(e) {
const i = await this.devices.get(e)
return i?.value
}
async updateDevice(e, i) {
return this.devices.update(e, i, { upsert: !0 })
}
async maybeDeleteDevice(e) {
return (
!!this.view.base.removeable(e) &&
(await this.view.base.removeWriter(e), await this.devices.delete(e), !0)
)
}
autobaseApply(e, i) {
switch (e.type) {
case r.TYPE:
case v.TYPE:
return this._applyAddDeviceOperation(e, i)
case l.TYPE:
return this._applyUpdateDeviceOperation(e, i)
case u.TYPE:
return this._applyRemoveDeviceOperationV1(e, i)
case d.TYPE:
return this._applyRemoveDeviceOperationV2(e, i)
case p.TYPE:
case y.TYPE:
return this._applyUpdateMemberOperation(e, i)
}
}
async autobaseIndex({ prev: e, next: i, id: t }) {
;(await this._indexDevicesKeysByNoisePublicKey(e, i, t),
await this._indexDeviceCount(e, i, t),
await this._indexMobileDeviceCount(e, i, t))
}
}