OverviewFilesCommitsIssuesDocsReleasesPeers

keet-deobfuscated / @holepunchto / keet-core / lib / core / sync / loader.js

379 lines · javascript ref:
const t = require("ready-resource"),
  e = require("hypercore-id-encoding"),
  i = require("safety-catch"),
  a = require("b4a"),
  { KeetContractError: s } = require("../contract/errors"),
  {
    contract: {
      hyperschema: { getEnum: n },
    },
  } = require("../../../schema/spec"),
  o = n("@keet/room-type"),
  { restoreAvatar: l, restoreFile: r } = require("../contract/avatars"),
  { getMemberForDeviceKey: h } = require("../contract/query"),
  { getCall: d } = require("../../api/v1/helpers"),
  g = 20
module.exports = class extends t {
  constructor(t, e, a) {
    ;(super(),
      (this.local = t),
      (this.view = e),
      (this.base = a),
      (this.shared = 0),
      (this.syncTime = null),
      (this.output = null),
      (this.input = null),
      (this.inputBlobs = null),
      (this.core = null),
      (this._loadingLocalPrivateProfileInfo = null),
      (this._loadingLocalIdentityInfo = null),
      (this._loadingLocalRoomInfo = null),
      (this._loadingLatestMessages = null),
      (this._loadingLatestMembers = null),
      (this._loadingDigest = null),
      (this._loadingConfig = null),
      (this._loadingCapabilities = null),
      (this._loadingIdentity = null),
      (this._loadingPrimaryThread = null),
      (this._loadingLastMessage = null),
      (this._loadingDMMembers = null),
      (this._loadingReadLengths = null),
      (this._loadingChatChanges = null),
      (this._loadingContactChanges = null),
      (this._loadingSharedRoomChanges = null),
      (this._loadingPendingInvitations = null),
      this.ready().catch(i))
  }
  _close() {
    return Promise.all([this.input.close(), this.output.close(), this.inputBlobs.close()])
  }
  async _open() {
    ;(this.view.db.update(),
      (this.output = this.local.snapshot()),
      (this.input = this.view.db.snapshot()),
      (this.inputBlobs = this.view.blobs.snapshot()),
      (this.core = this.input.engine.db.core),
      (this.syncTime = Date.now()))
  }
  async _getChangeRange(t) {
    const e = await this.input.findOne(t, { reverse: !0, checkout: this.shared })
    return e ? { gt: { clock: e.clock } } : {}
  }
  async _runLoadConfig() {
    const [t, e, i, s] = await Promise.all([
      this.loadLocalRoomInfo(),
      this.loadDigest(),
      this.loadSelfMember(),
      this.input.get("@keet/room-config"),
    ])
    let n = s?.title || null,
      r = s?.avatar || null
    if (i && e?.settings.roomType === o.DirectMessage) {
      const t = await this.input.find("@keet/members").toArray()
      for (const e of t)
        if (!a.equals(i.key, e.key)) {
          ;((n = e.profile.displayName), (r = e.profile.avatar))
          break
        }
    }
    if (r) {
      const e = t?.config?.avatar?.small?.hash,
        i = r?.small?.hash
      e && i && a.equals(e, i) ? (s.avatar = t.config.avatar) : await l(this.inputBlobs, r)
    }
    return { ...s, title: n, avatar: r }
  }
  async _runLoadSelfMember() {
    const t = await h(this.input, this.base.local.key)
    if (!t) return null
    const e = await this.input.get("@keet/member-proofs", { key: t.key }),
      i = { ...t, proofs: e }
    return i?.profile.avatar ? (await l(this.inputBlobs, i.profile.avatar), i) : i
  }
  async _runLoadLastMessage() {
    const t = await this.loadPrimaryThread()
    if (!t || 0 === t.count) return null
    const e = { thread: 0, index: t.count - 1 }
    return this.loadMessage(e)
  }
  async _runLoadDMMembers() {
    const [t, e] = await Promise.all([this.loadDigest(), this.loadSelfMember()])
    if (!t || !e || t.settings.roomType !== o.DirectMessage) return []
    const i = await this.input.find("@keet/members").toArray(),
      s = []
    for (const t of i) a.equals(t.key, e.key) || s.push(t.key)
    return s
  }
  async _runLoadChatChanges() {
    const [t, e] = await Promise.all([this.loadLocalRoomInfo(), this.loadLastMessage()]),
      i = t?.lastMessage?.oplog.key,
      s = t?.lastMessage?.oplog.seq,
      n = e?.oplog.key,
      o = e?.oplog.seq
    let l = !1
    return ((l = !i || !(a.equals(i, n) && s === o)), { lastMessage: e, changed: l })
  }
  async _runLoadLatestMessages() {
    const t = await this.loadLocalIdentityInfo()
    return t && a.equals(this.base.key, t.privateProfileKey)
      ? []
      : this.input
          .find("@keet/chat", {
            gte: { id: { thread: 0 } },
            lte: { id: { thread: 0 } },
            limit: g,
            reverse: !0,
          })
          .toArray()
  }
  async _loadFiles(t) {
    const e = await Promise.all(t.map((t) => this.input.get("@keet/files", { id: t })))
    return await Promise.all(e.map(async (t) => (await r(this.inputBlobs, t.file.preview), t.file)))
  }
  async _loadMemberForDeviceKey(t, i, a) {
    const s = await h(this.input, a)
    if (!s) return null
    const n = e.encode(s.key)
    t.has(n) || (t.add(n), s.avatar && (await l(this.inputBlobs, s.avatar)), i.push(s))
  }
  async _runLoadLatestMembers() {
    const t = await this.loadLatestMessages()
    if (this.closing || !t.length) return []
    const i = [],
      a = [],
      s = new Set(),
      n = new Set()
    for (const o of t) {
      const t = e.encode(o.oplog.key)
      s.has(t) || (s.add(t), a.push(this._loadMemberForDeviceKey(n, i, o.oplog.key)))
    }
    return (await Promise.all(a), i)
  }
  async _runLoadSharedRoomChanges() {
    const t = await this._getChangeRange("@keet-private-id/rooms-by-clock"),
      e = []
    for await (const i of this.input.find("@keet-private-id/rooms-by-clock", t)) e.push(i)
    return e
  }
  async _runLoadContactChanges() {
    const t = await this._getChangeRange("@keet-private-id/contacts-by-clock"),
      e = []
    for await (const i of this.input.find("@keet-private-id/contacts-by-clock", t)) e.push(i)
    return e
  }
  _runLoadPendingInvitations() {
    return this.input.find("@keet-private-id/pending-invitations").toArray()
  }
  async _runLoadLocalPrivateProfileInfo() {
    const t = await this.loadLocalIdentityInfo()
    return this.closing || !t
      ? null
      : this.output.get("@local/rooms", { roomKey: t.privateProfileKey })
  }
  _runLoadReadLengths() {
    return this.input.find("@keet/read-lengths").toArray()
  }
  _runLoadActiveCall() {
    return d(this.input, "@keet/active-calls-by-timestamp")
  }
  _runLoadActiveCallDevice(t, e) {
    return this.input.get("@keet/call-devices", { callKey: t, deviceKey: e })
  }
  loadLocalRoomInfo() {
    return (
      this._loadingLocalRoomInfo ||
        ((this._loadingLocalRoomInfo = this.output.get("@local/rooms", { roomKey: this.base.key })),
        this._loadingLocalRoomInfo.catch(i)),
      this._loadingLocalRoomInfo
    )
  }
  loadLocalPrivateProfileInfo() {
    return (
      this._loadingLocalPrivateProfileInfo ||
        ((this._loadingLocalPrivateProfileInfo = this._runLoadLocalPrivateProfileInfo()),
        this._loadingLocalPrivateProfileInfo.catch(i)),
      this._loadingLocalPrivateProfileInfo
    )
  }
  loadLocalIdentityInfo() {
    return (
      this._loadingLocalIdentityInfo ||
        ((this._loadingLocalIdentityInfo = this.output.get("@local/identity")),
        this._loadingLocalIdentityInfo.catch(i)),
      this._loadingLocalIdentityInfo
    )
  }
  loadDigest() {
    return (
      this._loadingDigest ||
        ((this._loadingDigest = this.input.get("@keet/system-digest")),
        this._loadingDigest.catch(i)),
      this._loadingDigest
    )
  }
  loadConfig() {
    return (
      this._loadingConfig ||
        ((this._loadingConfig = this._runLoadConfig()), this._loadingConfig.catch(i)),
      this._loadingConfig
    )
  }
  loadCapabilities() {
    return (
      this._loadingCapabilities ||
        ((this._loadingCapabilities = this.input.get("@keet/room-capabilities")),
        this._loadingCapabilities.catch(i)),
      this._loadingCapabilities
    )
  }
  loadPrimaryThread() {
    return (
      this._loadingPrimaryThread ||
        ((this._loadingPrimaryThread = this.input.get("@keet/threads", { id: 0 })),
        this._loadingPrimaryThread.catch(i)),
      this._loadingPrimaryThread
    )
  }
  loadSelfDevice() {
    return (
      this._loadingSelfDevice ||
        ((this._loadingSelfDevice = this.input.get("@keet/devices", { key: this.base.local.key })),
        this._loadingSelfDevice.catch(i)),
      this._loadingSelfDevice
    )
  }
  loadSelfMember() {
    return (
      this._loadingSelfMember ||
        ((this._loadingSelfMember = this._runLoadSelfMember()), this._loadingSelfMember.catch(i)),
      this._loadingSelfMember
    )
  }
  loadLastMessage() {
    return (
      this._loadingLastMessage ||
        ((this._loadingLastMessage = this._runLoadLastMessage()),
        this._loadingLastMessage.catch(i)),
      this._loadingLastMessage
    )
  }
  loadDMMembers() {
    return (
      this._loadingDMMembers ||
        ((this._loadingDMMembers = this._runLoadDMMembers()), this._loadingDMMembers.catch(i)),
      this._loadingDMMembers
    )
  }
  loadIdentity() {
    return (
      this._loadingIdentity ||
        ((this._loadingIdentity = this.input.get("@keet-private-id/identity")),
        this._loadingIdentity.catch(i)),
      this._loadingIdentity
    )
  }
  loadChatChanges() {
    return (
      this._loadingChatChanges ||
        ((this._loadingChatChanges = this._runLoadChatChanges()),
        this._loadingChatChanges.catch(i)),
      this._loadingChatChanges
    )
  }
  loadContactChanges() {
    return (
      this._loadingContactChanges ||
        ((this._loadingContactChanges = this._runLoadContactChanges()),
        this._loadingContactChanges.catch(i)),
      this._loadingContactChanges
    )
  }
  loadSharedRoomChanges() {
    return (
      this._loadingSharedRoomChanges ||
        ((this._loadingSharedRoomChanges = this._runLoadSharedRoomChanges()),
        this._loadingSharedRoomChanges.catch(i)),
      this._loadingSharedRoomChanges
    )
  }
  loadPendingInvitations() {
    return (
      this._loadingPendingInvitations ||
        ((this._loadingPendingInvitations = this._runLoadPendingInvitations()),
        this._loadingPendingInvitations.catch(i)),
      this._loadingPendingInvitations
    )
  }
  loadLatestMessages() {
    return (
      this._loadingLatestMessages ||
        ((this._loadingLatestMessages = this._runLoadLatestMessages()),
        this._loadingLatestMessages.catch(i)),
      this._loadingLatestMessages
    )
  }
  loadLatestMembers() {
    return (
      this._loadingLatestMembers ||
        ((this._loadingLatestMembers = this._runLoadLatestMembers()),
        this._loadingLatestMembers.catch(i)),
      this._loadingLatestMembers
    )
  }
  loadReadLengths() {
    return (
      this._loadingReadLengths ||
        ((this._loadingReadLengths = this._runLoadReadLengths()),
        this._loadingReadLengths.catch(i)),
      this._loadingReadLengths
    )
  }
  loadActiveCall() {
    return (
      this._loadingActiveCall ||
        ((this._loadingActiveCall = this._runLoadActiveCall()), this._loadingActiveCall.catch(i)),
      this._loadingActiveCall
    )
  }
  loadActiveCallDevice(t, e) {
    return (
      this._loadingActiveCallDevice ||
        ((this._loadingActiveCallDevice = this._runLoadActiveCallDevice(t, e)),
        this._loadingActiveCallDevice.catch(i)),
      this._loadingActiveCallDevice
    )
  }
  loadPreferences() {
    return (
      this._loadingPreferences ||
        ((this._loadingPreferences = this.local.get("@local/room-preferences", {
          roomKey: this.base.key,
        })),
        this._loadingPreferences.catch(i)),
      this._loadingPreferences
    )
  }
  async loadMessage(t) {
    const [e, i] = await Promise.all([
      this.input.get("@keet/chat", { id: t }),
      this.input.get("@keet/reaction-digests", { id: t }),
    ])
    if (!e) return null
    const a = e.message?.files,
      n = !!a && a.length > 0,
      [o, r, d] = await Promise.all([
        this.input.get("@keet/devices", { key: e.oplog.key }),
        h(this.input, e.oplog.key),
        n ? this._loadFiles(a) : null,
      ])
    if (!r) throw s.CORRUPT("Last chat message does not have a member")
    ;(r.profile.avatar && (await l(this.inputBlobs, r.profile.avatar)), d && (e.message.files = d))
    const g = []
    if (e.event?.memberRemoved?.memberKey) {
      const t = await this.input.get("@keet/members", { key: e.event.memberRemoved.memberKey })
      t && (t.profile.avatar && (await l(this.inputBlobs, t.profile.avatar)), g.push(t))
    }
    return { message: e, reactions: i, device: o, member: r, otherMembers: g }
  }
}