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 }
}
}