const e = require("autobase"),
t = require("b4a"),
{
contract: {
hyperschema: { getEnum: i, encode: a },
hyperdispatch: { encode: s },
},
} = require("../../../../schema/spec"),
{
createDeviceResponse: r,
decryptDeviceRequest: n,
verifyAttestation: o,
verifyInvitationDeviceRequest: d,
generateMailboxKeyPair: c,
} = require("../../invitations"),
m = i("@keet/room-type"),
{ deviceStats: y, memberStats: k } = require("../stats"),
{ isMemberActive: l, getMemberForDeviceKey: u } = require("../query"),
{ addEvent: w } = require("../chat"),
{ updateAvatar: v } = require("../avatars"),
{ KeetContractError: f } = require("../errors"),
b = require("../registration"),
{ isValidBuildType: p } = require("../helpers"),
A = ["isMuted", "isBanned", "isShadowBanned"],
h = { IsMuted: 1, IsBanned: 2, IsShadowBanned: 3 }
async function g(e, t, i) {
const { db: a, hostcalls: s } = i,
[r, n] = await Promise.all([
a.get("@keet-private-id/identity"),
a.get("@keet/threads", { id: 0 }),
]),
o = await a.get("@keet/members", { key: r.publicKey }),
d = { ...o, status: { ...o.status } },
c = { key: e, memberKey: r.publicKey, profile: t, status: { isActive: !0, isIndexing: !0 } }
return (
(o.status.isPending = !1),
await s.addWriter(e, { indexer: !0 }),
await a.insert("@keet/members", o),
await k(a, n, o, d),
await a.insert("@keet/devices", c),
await y(a, n, c, null),
{ added: !0, device: c }
)
}
async function R(e, t, i, a, s, { db: r, writer: n, hostcalls: d, genesis: c }) {
const [l, u, v, b, p] = await Promise.all([
r.get("@keet/devices", { key: t }),
r.get("@keet/members", { key: a.memberKey }),
r.get("@keet/member-proofs", { key: a.memberKey }),
r.get("@keet/system-digest"),
r.get("@keet/threads", { id: 0 }),
])
if (!u || !v) throw f.ABORT()
if (l) return { added: !1, device: l }
const A = { ...u, status: { ...u.status } },
h = u.status.isPending,
g = o(a, t, v.latestReceipt)
v.latestReceipt = g
const R = { isActive: !0, isIndexing: !!s?.isIndexing }
;((u.status.isActive = !0),
(u.status.isPending = !1),
h && !u.status.isModerator && s?.isModerator && (u.status.isModerator = !0),
R.isIndexing && !u.status.isAdmin && (u.status.isAdmin = !0))
const T = { key: t, memberKey: u.key, status: R, profile: i }
if (
(await r.insert("@keet/members", u),
await k(r, p, u, A),
await r.insert("@keet/devices", T),
await y(r, p, T, null),
await r.insert("@keet/member-proofs", v),
c || (await d.addWriter(t, { indexer: R.isIndexing })),
!h || b.settings.roomType === m.Broadcast)
)
return { added: !0, device: T }
if (b.abi.version >= 11 && b.settings.isMembershipEventsDisabled) return { added: !0, device: T }
const M = { memberJoined: { memberKey: u.key } }
return (await w(r, n, M, e.timestamp), { added: !0, device: T })
}
async function T(e, t, i, s, { db: r, blobs: n, writer: d, genesis: c }) {
const [m, y, l] = await Promise.all([
r.get("@keet/members", { key: i.memberKey }),
r.get("@keet/member-proofs", { key: i.memberKey }),
r.get("@keet/threads", { id: 0 }),
])
if (m) throw f.ABORT()
const u = a("@keet/member-profile", t),
w = o(i, u, y?.latestReceipt)
let b = y
;(b
? ((b.latestReceipt = w), (b.profileAttestation = i.proof))
: (b = { key: i.memberKey, latestReceipt: w, profileAttestation: i.proof }),
t.avatar ? (t.avatar = await v(n, m?.profile.avatar, t.avatar)) : (t.avatar = null))
const p = {
isAdmin: !!s.isAdmin,
isModerator: !!s.isModerator,
isPending: !0,
isActive: !0,
isMuted: !1,
isBanned: !1,
isShadowBanned: !1,
},
A = { key: i.memberKey, devices: [], status: p, profile: t }
return (
await r.insert("@keet/members", A),
await k(r, l, A),
await r.insert("@keet/member-proofs", b),
A
)
}
async function M(e, t, i, { db: s, blobs: r }) {
const [n, d] = await Promise.all([
s.get("@keet/members", { key: i.memberKey }),
s.get("@keet/member-proofs", { key: i.memberKey }),
])
if (!n) throw f.ABORT()
const c = a("@keet/member-profile", t),
m = o(i, c, d?.latestReceipt)
let y = d
;(y
? ((y.latestReceipt = m), (y.profileAttestation = i.proof))
: (y = { key: i.memberKey, latestReceipt: m, profileAttestation: i.proof }),
n.profile.timestamp >= t.timestamp ||
((n.profile = t),
t.avatar
? (n.profile.avatar = await v(r, n.profile.avatar, t.avatar))
: (n.profile.avatar = null),
await s.insert("@keet/members", n),
await s.insert("@keet/member-proofs", y)))
}
async function B(t, i, a, n, o, d) {
const { db: c, trace: m, hostcalls: y, dryRun: k } = d,
l = await c.get("@keet/room-capabilities"),
u = y.discoveryKey,
w = y.key,
v = n.metadata,
b = { isAdmin: v.canAdmin, isModerator: v.canModerate || v.canAdmin },
p = { isIndexing: v.canAdmin, isModerator: v.canModerate }
let A = null
if (o)
((A = await g(i, a.device.profile, d)),
await c.insert("@keet-private-id/device-identities", {
deviceKey: i,
privateProfileIdentity: o.privateProfileIdentity,
deviceProof: o.deviceProof,
}))
else {
const e = await c.get("@keet/members", { key: a.device.attestation.memberKey })
if (e) {
if (e.status.isRemoved) throw new f.ABORT()
await M(t, a.member.profile, a.member.attestation, d)
} else await T(t, a.member.profile, a.member.attestation, b, d)
A = await R(t, i, a.device.profile, a.device.attestation, p, d)
}
const { device: h, added: B } = A,
O = a.member.profile.mailbox,
K = l.seed,
{ blindResponse: q } = r(O, K, n),
I = s("@keet-private-id/resolve-pending-invitation", {
header: { timestamp: 0 },
blindResponse: q,
}),
P = e.encodeValue(I, { optimistic: !0, encrypted: !0 })
if (
(B &&
((n.metadata.canAdmin = !1),
(n.metadata.canModerate = !1),
(h.invitation = { id: n.id, response: P }),
await c.insert("@keet/devices", h),
await c.insert("@keet/invitations", n)),
m && !k)
) {
const e = { roomKey: w, discoveryKey: u, request: a, response: P, invitation: n }
await m({ type: "@keet/add-device", value: e })
}
}
async function O({ device: e, member: t }, i, a) {
const { db: s, hostcalls: r, writer: n } = a,
[o, d, c] = await Promise.all([
s.get("@keet/members", { key: e.attestation.memberKey }),
s.get("@keet/member-proofs", { key: e.attestation.memberKey }),
s.get("@keet/system-digest"),
])
if (!o || !d) throw f.ABORT()
const y = c.settings.roomType === m.DirectMessage
if (c.abi.version >= 12 && !o.status.isActive && !y) throw f.ABORT()
;(await R(i, n.key, e.profile, e.attestation, { isActive: !0, isIndexing: !1 }, a),
await r.ackWriter(n.key),
t && (await M(i, t.profile, t.attestation, a)))
}
async function K(e, i, a) {
const { db: s, hostcalls: r, writer: n } = a
let o = null
try {
o = d(e)
} catch (e) {
throw f.ABORT()
}
if (!o) throw f.ABORT()
const { id: c, request: m } = o
if (!t.equals(m.deviceKey, n.key)) throw f.ABORT()
const [y, k, l] = await Promise.all([
s.get("@keet/join-requests", { deviceKey: n.key }),
s.get("@keet/invitations", { id: c }),
s.get("@keet-private-id/identity"),
])
if (!k || y) throw f.ABORT()
const u = k.metadata.buildType,
w = m.device.profile.buildType
if (!p(w, u)) throw f.INVALID_BUILD_TYPE()
;(await r.ackWriter(n.key),
k.requiresConfirmation || l
? await s.insert("@keet/join-requests", { deviceKey: n.key, id: c, request: m })
: await B(i, n.key, m, k, null, a))
}
async function q({ header: e, deviceKey: t, identity: i }, a) {
const { db: s, writer: r } = a,
[n, o] = await Promise.all([u(s, r.key), s.get("@keet/join-requests", { deviceKey: t })])
if (!l(n) || !o) throw f.ABORT()
const { id: d, request: c } = o,
m = await s.get("@keet/invitations", { id: d })
if (!m) throw f.ABORT()
;(await B(e, t, c, m, i, a), await s.delete("@keet/join-requests", { deviceKey: t }))
}
async function I({ header: e, deviceKey: t }, i) {
const { db: a, writer: s } = i,
[r, n] = await Promise.all([u(a, s.key), a.get("@keet/join-requests", { deviceKey: t })])
if (!l(r) || !n) throw f.ABORT()
await a.delete("@keet/join-requests", { deviceKey: t })
}
async function P({ invitation: e }, { db: t, writer: i, hostcalls: a }) {
const [s, r, n] = await Promise.all([
u(t, i.key),
t.get("@keet/invitations", { id: e.id }),
t.get("@keet/system-digest"),
])
if (!l(s)) throw f.ABORT()
if (s.status.isMuted) return
const o = e.metadata
if (r || n.settings.roomType === m.DirectMessage) throw f.ABORT()
return (
(o.buildType = n.settings.buildType),
o.canModerate && !s.status.isModerator && (o.canModerate = !1),
o.canAdmin && !s.status.isAdmin && (o.canAdmin = !1),
(e.isActive = !0),
(e.anchor = await a.createAnchor(i.key, i.seq + 1)),
t.insert("@keet/invitations", e)
)
}
async function x({ id: e }, { db: t, writer: i }) {
const [a, s] = await Promise.all([u(t, i.key), t.get("@keet/invitations", { id: e })])
if (!l(a) || !a.status.isModerator || !s) throw f.ABORT()
;((s.isActive = !1), await t.insert("@keet/invitations", s))
}
async function E({ deviceKey: e, fromDeviceKey: i }, { db: a, writer: s, hostcalls: r }) {
const [n, o, d] = await Promise.all([u(a, s.key), u(a, e), u(a, i)])
if (!l(n)) throw f.ABORT()
if (!t.equals(n.key, o.key)) throw f.ABORT()
if (!t.equals(n.key, d.key)) throw f.ABORT()
const [c, m] = await Promise.all([
a.get("@keet/devices", { key: i }),
a.get("@keet/devices", { key: e }),
])
if (!c || !m) throw f.ABORT()
if (!c.status.isActive) throw f.ABORT()
if (!m.status.isActive) throw f.ABORT()
if (!c.status.isIndexing) throw f.ABORT()
if (m.status.isIndexing) throw f.ABORT()
if (t.equals(c.key, m.key)) throw f.ABORT()
;(await r.addWriter(m.key, { isIndexer: !0 }),
await r.addWriter(c.key, { isIndexer: !1 }),
(m.status.isIndexing = !0),
(c.status.isIndexing = !1),
await a.insert("@keet/devices", c),
await a.insert("@keet/devices", m))
}
async function D(e, { db: t, writer: i, hostcalls: a }) {
const [s, r] = await Promise.all([t.get("@keet/devices", { key: i.key }), u(t, i.key)])
if (!s.status.isActive || !l(r) || !r.status.canAdmin) throw f.ABORT()
;(await a.addWriter(i.key, { indexer: !0 }),
(s.status.isIndexing = !0),
await t.insert("@keet/devices", s))
}
async function _(e, { db: t, writer: i, hostcalls: a }) {
const [s, r] = await Promise.all([t.get("@keet/devices", { key: i.key }), u(t, i.key)])
if (!s.status.isActive || !l(r) || !r.status.canAdmin) throw f.ABORT()
;(await a.addWriter(i.key, { indexer: !1 }),
(s.status.isIndexing = !1),
await t.insert("@keet/devices", s))
}
async function N({ header: e, blindRequest: t }, i) {
const { db: a } = i,
s = await a.get("@keet/room-capabilities")
if (!s) throw f.ABORT()
let r = null
try {
const e = c(i.hostcalls.key, s.seed)
r = n(e, t)
} catch (e) {
throw f.ABORT()
}
if (!r) throw f.ABORT()
const { withInvitation: o, withMemberProof: d } = r
return o ? K(o, e, i) : d ? O(d, e, i) : void 0
}
async function S({ header: e, member: { profile: t, attestation: i } }, a) {
return M(e, t, i, a)
}
async function W({ profile: e }, { db: i, writer: a }) {
const [s, r] = await Promise.all([u(i, a.key), i.get("@keet/devices", { key: a.key })])
if (!(r.status.isActive && l(s) && r && t.equals(r.key, a.key))) throw f.ABORT()
;((r.profile = e), await i.insert("@keet/devices", r))
}
async function j({ key: e }, { db: i, writer: a, hostcalls: s }) {
const [r, n, o] = await Promise.all([
u(i, a.key),
i.get("@keet/devices", { key: e }),
i.get("@keet/threads", { id: 0 }),
])
if (!n || !l(r) || !t.equals(n.memberKey, r.key)) throw f.ABORT()
const d = { ...n, status: { ...n.status } }
;((n.status.isActive = !1),
(n.status.isIndexing = !1),
s.removeable(n.key) && (await s.removeWriter(n.key)),
await i.insert("@keet/devices", n),
await y(i, o, n, d))
}
async function L({ memberKey: e, key: t, status: i }, { db: a, writer: s }) {
const [r, n] = await Promise.all([u(a, s.key), a.get("@keet/members", { key: e })])
if (!l(r)) throw f.INACTIVE_MEMBER()
if (!n) throw f.TARGET_MEMBER_NOT_FOUND()
if (!r.status.isModerator) throw f.MODERATORS_ONLY()
if (0 === t || t > A.length) throw f.INVALID_MEMBER_STATUS()
const o = A[t - 1]
;((n.status[o] = i[o]), await a.insert("@keet/members", n))
}
async function V({ key: e, isModerator: t, isAdmin: i }, { db: a, writer: s, hostcalls: r }) {
const [n, o, d] = await Promise.all([
u(a, s.key),
a.get("@keet/members", { key: e }),
a
.find("@keet/active-devices-by-member-key", { gte: { memberKey: e }, lte: { memberKey: e } })
.toArray(),
])
if (!l(n)) throw f.INACTIVE_MEMBER()
if (!o) throw f.TARGET_MEMBER_NOT_FOUND()
const c = n.status.isAdmin,
m = n.status.isModerator,
y = o.status.isAdmin
if (!c && (y || i)) throw f.ADMINS_ONLY()
if (!m) throw f.MODERATORS_ONLY()
let k = !1
if (y && !i)
for (const e of d) r.removeable(e.key) ? await r.addWriter(e.key, { indexer: !1 }) : (k = !0)
;(k || (o.status.isAdmin = i),
(o.status.isModerator = t || i),
await a.insert("@keet/members", o))
}
async function C({ header: e, key: i }, { db: a, writer: s, hostcalls: r }) {
const [n, o, d, c, v] = await Promise.all([
u(a, s.key),
a.get("@keet/members", { key: i }),
a.get("@keet/system-digest"),
a
.find("@keet/active-devices-by-member-key", { gte: { memberKey: i }, lte: { memberKey: i } })
.toArray(),
a.get("@keet/threads", { id: 0 }),
])
if (!l(n)) throw f.INACTIVE_MEMBER()
if (!o) throw f.TARGET_MEMBER_NOT_FOUND()
const b = { ...o, status: { ...o.status } },
p = t.equals(n.key, o.key),
A = n.status.isModerator,
h = o.status.isAdmin
if (!p && h) throw f.ABORT()
if (!p && !A) throw f.MODERATORS_ONLY()
;((o.status.isActive = !1),
(o.status.isModerator = !1),
(o.status.isAdmin = !1),
(o.status.isPending = !!p),
p || ((o.status.isRemoved = !0), await a.insert("@keet/tmp-removed-members", { key: o.key })))
for (const e of c) {
const t = { ...e, status: { ...e.status } }
;(r.removeable(e.key) &&
(await r.removeWriter(e.key, { indexer: !1 }), (e.status.isIndexing = !1)),
(e.status.isActive = !1),
await a.insert("@keet/devices", e),
await y(a, v, e, t))
}
if (
(await a.insert("@keet/members", o), await k(a, v, o, b), d.settings.roomType === m.Broadcast)
)
return
if (d.abi.version >= 11 && d.settings.isMembershipEventsDisabled) return
const g = p ? { memberLeft: { memberKey: o.key } } : { memberRemoved: { memberKey: o.key } }
await w(a, s, g, e.timestamp)
}
function U(e) {
;(e.add("@keet/add-device", N),
e.add("@keet/update-device", W),
e.add("@keet/remove-device", j),
e.add("@keet/start-indexing", D),
e.add("@keet/stop-indexing", _),
e.add("@keet/accept-join-request", q),
e.add("@keet/reject-join-request", I),
e.add("@keet/update-member", S),
e.add("@keet/change-member-role", V),
e.add("@keet/change-member-status", L),
e.add("@keet/remove-member", C),
e.add("@keet/add-invitation", P),
e.add("@keet/remove-invitation", x),
e.add("@keet/move-indexer", E))
}
module.exports = { [b]: U, MemberStatus: h, addDevice: R, addMember: T }