OverviewFilesCommitsIssuesDocsReleasesPeers

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

395 lines · javascript ref:
const e = require("keet-identity-key"),
  i = require("tiny-buffer-map"),
  t = require("b4a"),
  { MemberSchema: s } = require("@holepunchto/keet-core-schemas"),
  {
    MemberCapabilities: a,
    MemberRoles: r,
    getCapabilitiesForRole: n,
    GENERIC_EVENTS: c,
    FEATURES: m,
    VALIDATION: o,
  } = require("@holepunchto/keet-core-api"),
  {
    UpdateMemberOperationV1: l,
    UpdateMemberOperationV2: y,
    RemoveMemberOperation: b,
    ChangeMemberRoleOperation: u,
    MuteMemberOperation: v,
    UnmuteMemberOperation: d,
  } = s.Operations,
  p = 0,
  h = t.alloc(0)
function M(i, t, s) {
  return e.verify(i.proof, t, { expectedIdentity: i.memberKey, receipt: s }) || null
}
function f(e, i, t, { latestReceipt: s } = {}) {
  return {
    key: e,
    hasAvatar: !!i?.hasAvatar || !!t?.hasAvatar,
    displayName: i?.displayName || t?.displayName,
    latestReceipt: t?.latestReceipt || s,
    devices: w(i, t),
    capabilities: g(i, t),
  }
}
function w(e, s) {
  if (!e && !s) return []
  if (!s) return e.devices ? e.devices : [e.key]
  if (!e) return s.devices ? s.devices : [s.key]
  const a = new i()
  if (e.devices) {
    for (const i of e.devices) a.set(i, h)
    for (const e of s.devices) a.set(e, h)
  } else {
    for (const e of s.devices) a.set(e, h)
    a.set(e.key, h)
  }
  const r = [...a.keys()]
  return (r.sort(t.compare), r)
}
function g(e, i) {
  return e || i ? (e ? (i ? e.capabilities | i.capabilities : e.capabilities) : i.capabilities) : 0
}
function K(e) {
  return 0 !== (e & a.CAN_MODERATE)
}
function E(e, i) {
  return !e || -1 === e.devices.findIndex((e) => t.equals(e, i))
}
function D(e) {
  if (!e.inactive) return !1
  if (e.inactive.length !== e.devices.length) return !1
  for (let i = 0; i < e.inactive.length; i++) if (!t.equals(e.inactive[i], e.devices[i])) return !1
  return !0
}
function _(e, i) {
  if (e.length !== i.length) return !1
  for (let s = 0; s < e.length; s++) if (!t.equals(e[s], i[s])) return !1
  return !0
}
module.exports = class {
  static Operations = new Set([l.TYPE, y.TYPE, b.TYPE, u.TYPE, v.TYPE, d.TYPE])
  constructor(e) {
    ;((this.view = e),
      (this.chat = null),
      (this.devices = null),
      (this.config = null),
      (this.members = null),
      (this.avatars = null),
      (this.pendingMembers = null),
      (this.inactiveMembers = null),
      (this.mutedMembers = null),
      (this.membersKeysByMemberName = null),
      (this.membersByDeviceKey = null),
      (this.inactiveMembersByDeviceKey = null),
      (this.capabilitiesByDeviceKey = null),
      (this.moderatorsByMemberKey = null),
      (this.membersCount = null))
  }
  initialize() {
    ;((this.chat = this.view.chat),
      (this.devices = this.view.devices),
      (this.config = this.view.config),
      (this.members = this.view.collections.members),
      (this.avatars = this.view.collections.avatars),
      (this.pendingMembers = this.view.collections.pendingMembers),
      (this.inactiveMembers = this.view.collections.inactiveMembers),
      (this.mutedMembers = this.view.collections.mutedMembers),
      (this.memberKeysByMemberName = this.view.indexes.memberKeysByMemberName),
      (this.membersByDeviceKey = this.view.indexes.membersByDeviceKey),
      (this.inactiveMembersByDeviceKey = this.view.indexes.inactiveMembersByDeviceKey),
      (this.capabilitiesByDeviceKey = this.view.indexes.capabilitiesByDeviceKey),
      (this.moderatorsByMemberKey = this.view.indexes.moderatorsByMemberKey),
      (this.membersCount = this.view.indexes.membersCount))
  }
  _migratePendingMember(e, i, s) {
    if (e.attestation)
      return s && t.equals(s.memberKey, e.attestation.memberKey)
        ? f(s.memberKey, e, i, { latestReceipt: e.attestation.receipt })
        : null
    if (s) {
      if (!s.proof) return null
      const t = M(s, e.key, i?.latestReceipt)
      return f(t ? s.memberKey : e.key, e, t ? i : null, { latestReceipt: t ? t.receipt : null })
    }
    return f(e.key, e, null)
  }
  _migrateExistingMember(e, i, s) {
    if (!s.proof || t.equals(e.key, s.memberKey)) return null
    const a = M(s, e.key, i?.latestReceipt)
    return a ? f(s.memberKey, e, i, { latestReceipt: a.receipt }) : null
  }
  async _maybeReactivateMember(e) {
    const i = await this.getInactiveMemberForDeviceKey(e, { full: !0 })
    if (i) {
      const s = i.devices.findIndex((i) => t.equals(i, e))
      ;-1 !== s &&
        (i.devices.splice(s, 1),
        i.devices.length > 0
          ? await this.inactiveMembers.update(i.key, i, { upsert: !0 })
          : await this.inactiveMembers.delete(i.key))
    }
  }
  async _applyUpdateMemberOperation(e, { id: i, timestamp: t }) {
    const { displayName: s, avatar: a, identity: r } = e.value,
      [n, l, y, b, u] = await Promise.all([
        this.getPendingMemberForDeviceKey(i.key),
        this.getMemberForDeviceKey(i.key, { full: !0 }),
        r ? this.getMember(r.memberKey) : Promise.resolve(),
        this.view.internal.getCodeVersion(),
        this.view.isFeatureSupported(m.DISPLAY_NAME_VALIDATION),
      ])
    let v = l,
      d = s
    if (
      (u &&
        s &&
        (s.length < o.MIN_USERNAME_LENGTH || s.length > o.MAX_USERNAME_LENGTH) &&
        (d = v.displayName),
      n)
    ) {
      if (
        (b >= 2 && (await this._maybeReactivateMember(i.key)),
        (v = this._migratePendingMember(n, y, r)),
        !v)
      )
        return
      await this.pendingMembers.delete(i.key)
    } else if (r && E(y, i.key)) {
      if (((v = this._migrateExistingMember(l, y, r)), !v)) return
      await this.members.delete(l.key)
    }
    a && (await this.avatars.update(v.key, a, { upsert: !0 }))
    const p = { ...v, timestamp: t, displayName: d || v.displayName, hasAvatar: !!a || v.hasAvatar }
    if ((await this.members.update(p.key, p, { upsert: !0 }), n && !y)) {
      const [e, s] = await Promise.all([
        this.config.isBroadcastRoom(),
        this.config.isDisableMembershipEvents(),
      ])
      if (!e && !s) {
        const e = { id: i, timestamp: t, event: { type: c.MEMBER_JOINED } }
        await this.chat.addMessage(e)
      }
    }
  }
  async _applyRemoveMemberOperation(e, { id: t, timestamp: s }) {
    const { memberKey: r } = e.value,
      [n, o, l, y] = await Promise.all([
        this.isModerator(t.key),
        this.getMemberForDeviceKey(t.key),
        this.getMember(r),
        this.view.isFeatureSupported(m.REMOVE_MEMBER),
      ])
    if (!o || !l || !y) return
    if (!n) return
    const b = 0 !== (o.capabilities & a.CAN_INDEX)
    if (0 !== (l.capabilities & a.CAN_INDEX) && !b) return
    const u = new i()
    for (const e of l.inactive || []) u.set(e, h)
    let v = !1
    for await (const e of l.devices) {
      if (u.get(e)) continue
      const i = await this.devices.maybeDeleteDevice(e)
      ;(i && (await this.removeMemberForDeviceKey({ key: e }, s, { sendLeftEvent: !1 })), (v = i))
    }
    v &&
      (await this.chat.addMessage({
        id: t,
        timestamp: s,
        event: { type: c.MEMBER_REMOVED, value: { memberKey: r } },
      }))
  }
  async _applyChangeMemberRoleOperation(e, { id: i, timestamp: t }) {
    const { memberKey: s, role: c } = e.value
    if (![r.MODERATOR, r.PEER].includes(c)) return
    const [o, l, y, b, u] = await Promise.all([
      this.isModerator(i.key),
      this.getMemberForDeviceKey(i.key),
      this.moderatorsByMemberKey.get(s),
      this.getMember(s),
      this.view.isFeatureSupported(m.PERMISSION_DOWNGRADE),
    ])
    if (!l || !b || !u) return
    const v = !!y
    if (!o) return
    const d = 0 !== (l.capabilities & a.CAN_INDEX),
      p = 0 !== (b.capabilities & a.CAN_INDEX)
    if (p && !d) return
    if (!p && v && r.PEER !== c) return
    if (!p && !v && c !== r.MODERATOR) return
    const h = n(c),
      M = await Promise.all(b.devices.map((e) => this.devices.getDevice(e)))
    if (!M.some((e) => 0 !== (e.capabilities & a.CAN_INDEX) && !this.view.base.removeable(e.key))) {
      for await (const e of M) {
        const { key: i } = e
        ;(0 !== (e.capabilities & a.CAN_INDEX) &&
          this.view.base.removeable(i) &&
          (await this.view.base.addWriter(i, { indexer: !1 })),
          await this.devices.updateDevice(i, { ...e, capabilities: h }))
      }
      await this.members.update(b.key, { ...b, timestamp: t, capabilities: h }, { upsert: !0 })
    }
  }
  async _applyMuteMemberOperation(e, { id: i, timestamp: t }) {
    const { memberKey: s } = e.value,
      [a, r] = await Promise.all([
        this.view.isFeatureSupported(m.MUTE_MEMBER),
        this.isModerator(i.key),
      ])
    a && r && (await this.mutedMembers.update(s, h, { upsert: !0 }))
  }
  async _applyUnmuteMemberOperation(e, { id: i, timestamp: t }) {
    const { memberKey: s } = e.value,
      [a, r] = await Promise.all([
        this.view.isFeatureSupported(m.MUTE_MEMBER),
        this.isModerator(i.key),
      ])
    a && r && (await this.mutedMembers.get(s)) && (await this.mutedMembers.delete(s))
  }
  async _indexMembersCount(e, i) {
    if (e && i) return
    const t = await this.membersCount.get(0)
    let s = t ? t.value : 0
    ;(e && (s -= 1), i && (s += 1), await this.membersCount.insert(0, s))
  }
  async _indexMemberKeysByMemberName(e, i, t) {
    ;(e && i && e.displayName === i.displayName) ||
      (e &&
        e.displayName &&
        (await this.memberKeysByMemberName.delete([e.displayName.toLowerCase(), t])),
      i &&
        i.displayName &&
        (await this.memberKeysByMemberName.insert([i.displayName.toLowerCase(), t])))
  }
  async _indexMembersByDeviceKey(e, i) {
    if (e) for (const i of e.devices) await this.membersByDeviceKey.delete(i)
    if (i) for (const e of i.devices) await this.membersByDeviceKey.insert(e, i)
  }
  async _indexInactiveMembersByDeviceKey(e, i) {
    if (e) for (const i of e.devices) await this.inactiveMembersByDeviceKey.delete(i)
    if (i) for (const e of i.devices) await this.inactiveMembersByDeviceKey.insert(e, i)
  }
  async _indexCapabilitiesByDeviceKey(e, i) {
    if (!(i && e && e.capabilities === i.capabilities && _(e.devices, i.devices))) {
      if (e) for (const i of e.devices) await this.capabilitiesByDeviceKey.delete(i)
      if (i) for (const e of i.devices) await this.capabilitiesByDeviceKey.insert(e, i.capabilities)
    }
  }
  async _indexModeratorsByMemberKey(e, i) {
    ;(e && i && K(e.capabilities) && K(i.capabilities)) ||
      (e && K(e.capabilities) && (await this.moderatorsByMemberKey.delete(e.key)),
      i && K(i.capabilities) && (await this.moderatorsByMemberKey.insert(i.key, h)))
  }
  async addPendingMember(e) {
    ;((await this.view.internal.getCodeVersion()) < 2 && (await this._maybeReactivateMember(e.key)),
      await this.pendingMembers.update(
        e.key,
        { invitation: e.invitation, capabilities: e.capabilities, attestation: e.attestation },
        { upsert: !0 },
      ))
  }
  async updateExistingMember(e) {
    const i = await this.getMemberForDeviceKey(e.key, { full: !0 })
    if (i)
      return this.members.update(
        i.key,
        { ...i, capabilities: i.capabilities | e.capabilities },
        { upsert: !0 },
      )
  }
  async getPendingMemberForDeviceKey(e) {
    const i = await this.pendingMembers.get(e)
    return i ? { key: i._id, ...i.value } : null
  }
  async getMemberForDeviceKey(e, { full: i = !1 } = {}) {
    const t = await this.membersByDeviceKey.get(e)
    if (!t) return null
    const s = t.value
    return i ? this.getMember(s.key) : { ...s, deviceKey: e }
  }
  async getInactiveMemberForDeviceKey(e, { full: i = !1 } = {}) {
    const t = await this.inactiveMembersByDeviceKey.get(e)
    if (!t) return null
    const s = t.value
    return i ? this.getInactiveMember(s.key) : { ...s, deviceKey: e }
  }
  async removeMemberForDeviceKey(e, i, { sendLeftEvent: s = !0 } = {}) {
    const a = e.key,
      [r, n, m] = await Promise.all([
        this.getMemberForDeviceKey(a, { full: !0 }),
        this.getInactiveMemberForDeviceKey(a, { full: !0 }),
        this.getPendingMemberForDeviceKey(a),
      ])
    if (n || !r) return
    if (
      (m && (await this.pendingMembers.delete(a)),
      r.inactive
        ? -1 === r.inactive.findIndex((e) => t.equals(e, a)) && r.inactive.push(a)
        : (r.inactive = [a]),
      r.inactive.sort(t.compare),
      await this.members.update(r.key, r, { upsert: !0 }),
      !D(r))
    )
      return
    ;(await this.members.delete(r.key), await this.inactiveMembers.update(r.key, r, { upsert: !0 }))
    const [o, l, y] = await Promise.all([
      this.config.isBroadcastRoom(),
      this.view.internal.getCodeVersion(),
      this.config.isDisableMembershipEvents(),
    ])
    if ((l < 2 || !o) && s && !y) {
      const t = { id: e, timestamp: i, event: { type: c.MEMBER_LEFT } }
      await this.chat.addMessage(t)
    }
  }
  async getMember(e) {
    const i = await this.members.get(e)
    return i?.value
  }
  async getInactiveMember(e) {
    const i = await this.inactiveMembers.get(e)
    return i?.value
  }
  async isDeviceMuted(e) {
    const [i, t] = await Promise.all([
      this.getMemberForDeviceKey(e),
      this.getInactiveMemberForDeviceKey(e),
    ])
    return !(!i && !t) && this.isMemberMuted(i?.key || t?.key)
  }
  async isMemberMuted(e) {
    return !!(await this.mutedMembers.get(e))
  }
  async isModerator(e) {
    if (t.equals(e, this.view.base.key)) return !0
    const i = await this.capabilitiesByDeviceKey.get(e)
    return K(i?.value)
  }
  autobaseApply(e, i) {
    switch (e.type) {
      case l.TYPE:
      case y.TYPE:
        return this._applyUpdateMemberOperation(e, i)
      case b.TYPE:
        return this._applyRemoveMemberOperation(e, i)
      case u.TYPE:
        return this._applyChangeMemberRoleOperation(e, i)
      case v.TYPE:
        return this._applyMuteMemberOperation(e, i)
      case d.TYPE:
        return this._applyUnmuteMemberOperation(e, i)
    }
  }
  async autobaseIndexMembers({ prev: e, next: i, id: t }) {
    ;(await this._indexMemberKeysByMemberName(e, i, t),
      await this._indexMembersByDeviceKey(e, i, t),
      await this._indexCapabilitiesByDeviceKey(e, i, t),
      await this._indexModeratorsByMemberKey(e, i, t),
      await this._indexMembersCount(e, i))
  }
  async autobaseIndexInactiveMembers({ prev: e, next: i, id: t }) {
    await this._indexInactiveMembersByDeviceKey(e, i, t)
  }
}