OverviewFilesCommitsIssuesDocsReleasesPeers

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

225 lines · javascript ref:
const e = require("b4a"),
  { ChatSchema: t } = require("@holepunchto/keet-core-schemas"),
  {
    FEATURES: i,
    GENERIC_EVENTS: s,
    DISPLAY_TYPES: a,
    isDisplaySupported: n,
  } = require("@holepunchto/keet-core-api"),
  { PRIMARY_THREAD_ID: r } = t.Messages,
  {
    AddChatMessageOperation: l,
    AddFileOperation: o,
    AddGenericEventOperation: d,
    RemoveChatMessageOperation: p,
    RemoveFileOperation: h,
    RemoveGenericEventOperation: y,
    UpdateChatMessageOperation: c,
  } = t.Operations,
  u = new Set([s.CALL_STARTED, s.ROOM_AVATAR_CHANGED, s.CALL_HANGUP, s.CALL_MISSED])
function v(e) {
  return e.filter((e) => e.type === a.MENTION)
}
function m(e) {
  return !!e.replyTo
}
function w(t, i) {
  return (
    (!t && !i) || (t && i && e.equals(t.key, i.key) && t.path === i.path && t.version === i.version)
  )
}
async function g(e, t) {
  const i = await e.get(t)
  return i ? i.value : null
}
module.exports = class {
  static Operations = new Set([l.TYPE, c.TYPE, p.TYPE, d.TYPE, y.TYPE, o.TYPE, h.TYPE])
  constructor(e) {
    ;((this.view = e),
      (this.members = null),
      (this.config = null),
      (this.chat = null),
      (this.mentionsByMemberId = null),
      (this.eventIdsByMemberId = null),
      (this.eventIdsByMessageid = null),
      (this.eventIdsByClock = null),
      (this.drivesByPath = null))
  }
  initialize() {
    ;((this.members = this.view.members),
      (this.config = this.view.config),
      (this.chat = this.view.collections.chat),
      (this.mentionsByMemberId = this.view.indexes.mentionsByMemberId),
      (this.eventIdsByMessageId = this.view.indexes.eventIdsByMessageId),
      (this.eventIdsByClock = this.view.indexes.eventIdsByClock),
      (this.drivesByPath = this.view.indexes.drivesByPath))
  }
  async _ensureMemberCanChat({ id: e }) {
    return !((await this.config.isBroadcastRoom()) && !(await this.members.isModerator(e.key)))
  }
  _applyAddChatMessageOperation(e, { id: t, timestamp: i }) {
    const { replyTo: s, parent: a, display: n, ...r } = e.value
    return this.addMessage({
      id: t,
      timestamp: i,
      clock: 0,
      chat: r,
      parent: a,
      display: n,
      replyTo: s,
    })
  }
  _applyAddFileOperation(e, { id: t, timestamp: i }) {
    const s = {
      id: t,
      timestamp: i,
      clock: 0,
      parent: e.value.parent,
      file: e.value.file,
      chat: e.value.description ? { text: e.value.description } : null,
    }
    return this.addMessage(s)
  }
  async _applyAddGenericEventOperation(e, { id: t, timestamp: a }) {
    if (
      u.has(e.value.type) &&
      !(
        (e.value.type === s.CALL_HANGUP &&
          (await this.view.isUnsupportedFeature(i.CALL_HANGUP_EVENT))) ||
        (e.value.type === s.CALL_MISSED &&
          (await this.view.isUnsupportedFeature(i.CALL_MISSED_EVENT)))
      )
    )
      return this.addMessage({ id: t, timestamp: a, clock: 0, event: e.value })
  }
  async _applyUpdateChatMessageOperation(t, { id: s }) {
    if (m(t.value) && (await this.view.isUnsupportedFeature(i.REPLY_TO))) return
    const a = t.value.id,
      r = await g(this.eventIdsByMessageId, [a.key, a.seq])
    if (!r) return
    const l = await g(this.chat, r)
    if (!l) return
    const [o, d, p] = await Promise.all([
      this.members.isModerator(s.key),
      this.members.getMemberForDeviceKey(s.key),
      this.members.getMemberForDeviceKey(a.key),
    ])
    if (e.equals(d.key, p.key) || o) {
      const e = t.value
      if (e.display) {
        const t = await this.view.internal.getCodeVersion()
        e.display = e.display.filter((e) => n(t, e.type))
      }
      await this.chat.update(r, {
        ...l,
        replyTo: e.replyTo,
        display: e.display,
        chat: { text: e.text, edited: !0 },
      })
    }
  }
  async _applyRemovalOperation(t, { id: i }) {
    const s = t.value.id,
      a = await g(this.eventIdsByMessageId, [s.key, s.seq])
    if (!a) return
    const n = await this.chat.get(a)
    if (!n) return
    const r = n.value,
      [l, o, d, p] = await Promise.all([
        this.members.isModerator(i.key),
        this.members.getMemberForDeviceKey(i.key),
        this.members.getMemberForDeviceKey(s.key),
        this.members.getInactiveMemberForDeviceKey(s.key),
      ]),
      h = d || p
    ;(e.equals(o.key, h.key) || l) &&
      (await this.config.deletePinnedMessageIfExists(s),
      await this.chat.update(a, { ...r, chat: null, file: null, event: null, deleted: !0 }))
  }
  _applyRemoveChatMessageOperation(e, t) {
    return this._applyRemovalOperation(e, t)
  }
  _applyRemoveFileOperation(e, t) {
    return this._applyRemovalOperation(e, t)
  }
  _applyRemoveGenericEventOperation(e, t) {
    return this._applyRemovalOperation(e, t)
  }
  async _indexClocks(e, t, i) {
    t &&
      !e &&
      (await this.eventIdsByClock.insert(t.clock, i),
      await this.eventIdsByMessageId.insert([t.id.key, t.id.seq], i))
  }
  async _indexMentions(e, t, i) {
    const s = e?.display && v(e.display),
      a = t?.display && v(t.display)
    if (s) for (const { value: e } of s) await this.mentionsByMemberId.delete([e.memberId, ...i])
    if (a) for (const { value: e } of a) await this.mentionsByMemberId.insert([e.memberId, ...i])
  }
  async _indexDrivesByPath(e, t) {
    ;(e &&
      t &&
      w(e.file, t.file) &&
      e?.file &&
      (await this.drivesByPath.delete([e.file.path, e.file.key, e.file.version])),
      t?.file && (await this.drivesByPath.insert([t.file.path, t.file.key, t.file.version])))
  }
  async getThreadLength(e) {
    const t = await this.chat.peek({ gte: [e], lte: [e], reverse: !0 })
    return t ? t._id[1] + 1 : 0
  }
  async messageExists(e) {
    const t = await this.getMessageById(e)
    return t && !0 !== t?.value?.deleted
  }
  async getMessageById(e) {
    const t = await this.getEventId(e)
    return t ? await this.chat.get(t) : null
  }
  async getEventId(e) {
    const t = await this.eventIdsByMessageId.get([e.key, e.seq])
    return t ? t.value : null
  }
  async addMessage(e) {
    const [t, s, a, l, o] = await Promise.all([
      this.view.internal.getCodeVersion(),
      this.view.isUnsupportedFeature(i.REPLY_TO),
      e.parent ? g(this.eventIdsByMessageId, e.parent) : null,
      this.eventIdsByClock.peek({ reverse: !0 }),
      this.view.members.isDeviceMuted(e.id.key),
    ])
    if ((m(e) && s) || o) return
    let d = r
    a && (d = a.clock + 1)
    const p = await this.getThreadLength(d)
    ;((e.clock = l ? l.key + 1 : 0),
      e.display && (e.display = e.display.filter((e) => n(t, e.type))),
      await this.chat.update([d, p], e, { upsert: !0 }))
  }
  async autobaseApply(e, t) {
    if (await this._ensureMemberCanChat(t))
      switch (e.type) {
        case l.TYPE:
          return this._applyAddChatMessageOperation(e, t)
        case o.TYPE:
          return this._applyAddFileOperation(e, t)
        case d.TYPE:
          return this._applyAddGenericEventOperation(e, t)
        case p.TYPE:
          return this._applyRemoveChatMessageOperation(e, t)
        case h.TYPE:
          return this._applyRemoveFileOperation(e, t)
        case y.TYPE:
          return this._applyRemoveGenericEventOperation(e, t)
        case c.TYPE:
          return this._applyUpdateChatMessageOperation(e, t)
      }
  }
  async autobaseIndex({ prev: e, next: t, id: i }) {
    ;(await this._indexClocks(e, t, i),
      await this._indexMentions(e, t, i),
      await this._indexDrivesByPath(e, t, i))
  }
}