OverviewFilesCommitsIssuesDocsReleasesPeers

keet-deobfuscated / @holepunchto / keet-indexing / lib / devices / index.js

176 lines · javascript ref:
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))
  }
}