const e = require("hypercore-id-encoding"),
t = require("hypercore-crypto"),
i = require("compact-encoding"),
a = require("@holepunchto/keet-indexing"),
{
InternalSchema: n,
KeetSchema: s,
ConfigSchema: r,
} = require("@holepunchto/legacy-keet-core-schemas"),
{ CONFIG: o } = require("@holepunchto/keet-core-api"),
{ InternalOperation: c, UpgradeOperation: l } = n.Operations,
{
contract: {
hyperschema: { getEnum: u },
},
} = require("../../../../schema/spec"),
m = u("@keet/room-type"),
d = 7,
p = 9,
[f] = t.namespace("@holepunchto/keet-core/v3-migrations", 1),
v = 19
function y(e) {
if (!e) throw new Error("Encryption key is required")
return t.hash([f, e])
}
async function h(e, t, i) {
const a = e.get({ key: t, active: !1 }),
n = await a.getUserData(i)
return (await a.close(), n)
}
async function g(e, t) {
return (await h(e, t, "autobase/local")) || t
}
async function b(e, t, n) {
const r = []
for (const t of e) {
if (i.decode(i.uint, t.value) > 1) break
;((t.value = i.decode(s.Messages.KeetOplogMessage, t.value)), r.push(t))
}
let o = await O(t.bee)
for (const e of r) {
o = Math.max(o, e.value.abi)
for (const t of e.value.ops)
t.type === c.TYPE && t.value.subtype === l.SUBTYPE && (o = Math.max(o, t.value.value.version))
}
return o > 7
? n.interrupt({ unsupportedABI: o, digest: null })
: (r.length && (await a.autobaseApply(r, t)), r.length)
}
async function k(e, t, i, a, n, s) {
const {
isPrivateProfile: r,
digest: o,
config: c,
capabilities: l,
identity: u,
primaryThread: m,
devices: d,
members: p,
avatars: f,
files: y,
messages: h,
reactions: g,
dmRooms: b,
} = await w(t, i),
k = Math.min(o.abi.version, v)
;(a.setDefinition((await s.loadBundle(k)).spec.contract.hyperdb),
await a.insert("@keet/system-digest", o),
await a.insert("@keet/room-config", c),
await a.insert("@keet/threads", m),
await a.insert("@keet/room-capabilities", l),
r && (await a.insert("@keet-private-id/identity", u)))
for (const e of d.values()) e.memberKey && (await a.insert("@keet/devices", e))
for (const [e, t] of f) {
if (!p.has(e)) continue
const i = await n.put(t),
{ member: a } = p.get(e)
a.profile.avatar = { small: { pointer: { blob: i } } }
}
for (const { member: e } of p.values()) await a.insert("@keet/members", e)
for (const e of y) await a.insert("@keet/files", e)
for (const e of h) await a.insert("@keet/chat", e)
for (const e of g) await a.insert("@keet/reactions", e)
if (r) {
await a.insert("@keet-private-id/identity", u)
for (let e = 0; e < b.length; e++) {
const { memberKey: t, roomKey: i } = b[e],
n = { memberKey: t, clock: e + 1, status: { isBlocked: !1 }, mailbox: null, roomKey: i }
await a.insert("@keet-private-id/contacts", n)
}
}
}
async function w(t, i) {
const a = i.core.download(),
[
{
isPrivateProfile: n,
digest: s,
config: r,
identity: o,
primaryThread: c,
pinnedMessages: l,
},
{ files: u, messages: m, messagesByOplogId: d },
p,
f,
v,
h,
] = await Promise.all([D(i), S(i), M(i), K(i), T(i), x(i)])
a.destroy()
const g = { seed: y(t.encryptionKey) },
b = await E(i, f)
if (l) for (const e of l) d.get(e).status.isPinned = !0
for (const { member: t, activeDevices: i, inactiveDevices: a } of f.values()) {
if (i)
for (const a of i) {
const i = e.encode(a),
n = p.get(i)
n ? (n.memberKey = t.key) : p.delete(i)
}
if (a)
for (const i of a) {
const a = {
key: i,
memberKey: t.key,
profile: { swarmKey: null, name: null, isMobile: !1 },
status: { isIndexing: !1, isActive: !1 },
}
p.set(e.encode(i), a)
}
}
return (
(s.creationTime = m.length > 0 ? m[0].timestamp : 0),
{
isPrivateProfile: n,
digest: s,
config: r,
capabilities: g,
identity: o,
primaryThread: c,
devices: p,
members: f,
avatars: b,
files: u,
messages: m,
reactions: v,
dmRooms: h,
}
)
}
async function D(t) {
const i = {
abi: { version: 9 },
settings: {
roomType: m.Default,
isExperimental: !1,
isJoiningDisabled: !1,
isAppendingDisabled: !1,
isCallingDisabled: !1,
isMembershipEventsDisabled: !1,
},
},
a = { title: null, avatar: null, description: null },
s = { publicKey: null, discoveryCoreKey: null, userRegistry: null },
c = { id: 0, count: 0, parent: null, stats: C() }
let l = null
const u = await t.bee.get(n.ExperimentalModeKey, n.UpgraderEncoding)
i.settings.isExperimental = !!u
for await (const n of t.bee.createReadStream(r.Encoding)) {
const t = r.getValue(n)
switch (n.key) {
case o.ROOM_TITLE:
a.title = t
break
case o.ROOM_DESCRIPTION:
a.description = t
break
case o.ROOM_CAN_CALL:
i.settings.isCallingDisabled = "false" === t
break
case o.ID_PUBLIC_KEY:
s.publicKey = e.decode(t)
break
case o.PINNED_MESSAGES:
l = t.map((t) => e.encode(t.key) + ":" + t.seq)
break
case o.ROOM_TYPE:
i.settings.roomType = N(Number.parseInt(t))
break
case o.DISABLE_MEMBERSHIP_EVENTS:
i.settings.isMembershipEventsDisabled = !0
break
default:
continue
}
}
return {
isPrivateProfile: !!s.publicKey,
digest: i,
config: a,
identity: s,
primaryThread: c,
pinnedMessages: l,
}
}
async function M(t) {
const i = new Map()
for await (const { value: a } of t.devices.devices.createReadStream()) {
const t = {
key: a.key,
memberKey: null,
profile: { swarmKey: a.noisePublicKey, name: a.name, isMobile: a.isMobile },
status: q(a),
}
i.set(e.encode(t.key), t)
}
return i
}
async function K(t) {
const i = new Map()
for await (const { value: a } of t.members.members.createReadStream()) {
const { member: t, activeDevices: n, inactiveDevices: s } = A(a)
i.set(e.encode(t.key), {
member: t,
activeDevices: n,
inactiveDevices: s,
hasAvatar: a.hasAvatar,
})
}
for await (const { value: a } of t.members.inactiveMembers.createReadStream()) {
const { member: t, activeDevices: n, inactiveDevices: s } = A(a)
i.set(e.encode(t.key), {
member: t,
activeDevices: n,
inactiveDevices: s,
hasAvatar: a.hasAvatar,
})
}
for await (const { _id: a } of t.members.mutedMembers.createReadStream()) {
const t = e.encode(a),
n = i.get(t)
n && (n.member.status.isMuted = !0)
}
return i
}
async function E(e, t) {
const i = []
for (const [a, { member: n, hasAvatar: s }] of t) s && i.push(P(e, a, n.key))
const a = await Promise.all(i)
return new Map(a)
}
async function P(e, t, i) {
return [t, (await e.members.avatars.get(i)).value]
}
async function S(t) {
const i = new Map(),
a = [],
n = []
let s = 0,
r = 0
for await (const { value: o } of t.chat.chat.createReadStream()) {
const t = s++,
c = e.encode(o.id.key) + ":" + o.id.seq,
l = {
id: { thread: 0, index: t },
oplog: o.id,
clock: t,
timestamp: o.timestamp,
status: { isEdited: !!o.chat?.edited, isDeleted: !!o.deleted, isPinned: !1 },
message: null,
event: null,
}
if (o.chat)
l.message = {
text: o.chat.text,
files: null,
display: o.display ? B(o.display) : null,
replyTo: o.replyTo,
parent: null,
}
else if (o.file) {
const e = I(o.file),
t = r++
;(n.push({ id: t, oplog: o.id, status: { isDeleted: !1 }, file: e }),
(l.message = { text: null, files: [t], display: null, replyTo: null, parent: null }))
} else o.event && (l.event = R(o.event))
;(i.set(c, l), a.push(l))
}
return { files: n, messages: a, messagesByOplogId: i }
}
async function T(e) {
const t = []
let i = 0
for await (const {
_id: [a, n, s, r],
} of e.reactions.reactions.createReadStream())
t.push({ id: { thread: a, index: n }, sender: s, clock: i++, reaction: r })
return t
}
async function x(e) {
const t = []
for await (const {
key: i,
value: { roomKey: a },
} of e.indexes.dmByMember.createReadStream())
t.push({ memberKey: i, roomKey: a })
return t
}
function A(e) {
const t = e.devices,
i = e.inactive
return {
member: {
key: e.key,
profile: { timestamp: 0, mailbox: null, displayName: e.displayName, avatar: null },
status: _(e),
},
activeDevices: t,
inactiveDevices: i,
}
}
function I(e) {
return {
pointer: { externalDrive: { key: e.key, path: e.path, version: e.version, size: 0 } },
metadata: { name: e.path, mimetype: e.type, dimensions: e.dimensions },
preview: e.previewPointer
? {
medium: {
metadata: { isPreview: !0 },
pointer: {
externalDrive: {
key: e.previewPointer.key,
path: e.previewPointer.path,
version: e.previewPointer.version,
size: 0,
},
},
},
}
: null,
}
}
function R(e) {
switch (e.type) {
case 1:
return { memberJoined: { memberKey: null } }
case 2:
return { callStarted: !0 }
case 3:
return { roomAvatarChanged: !0 }
case 4:
return { invitationCreated: !0 }
case 5:
return { memberLeft: { memberKey: null } }
case 6:
return { titleChanged: { from: e.value.from, to: e.value.to } }
case 7:
return { memberRemoved: { memberKey: null } }
case 8:
return { callHangup: !0 }
case 9:
return { callMissed: !0 }
default:
return null
}
}
function B(e) {
const t = {
mention: null,
httpLink: null,
pearLink: null,
bold: null,
italic: null,
strikeThrough: null,
code: null,
codeBlock: null,
emoji: null,
}
for (const { type: i, value: a } of e)
switch (i) {
case 1:
;(t.mention || (t.mention = []),
t.mention.push({ range: { start: a.start, length: a.length }, memberKey: a.memberId }))
break
case 2:
;(t.httpLink || (t.httpLink = []), t.httpLink.push(L(a)))
break
case 3:
;(t.pearLink || (t.pearLink = []), t.pearLink.push(L(a)))
break
case 4:
;(t.bold || (t.bold = []), t.bold.push({ start: a.start, length: a.length }))
break
case 5:
;(t.italic || (t.italic = []), t.italic.push({ start: a.start, length: a.length }))
break
case 6:
;(t.code || (t.code = []), t.code.push({ start: a.start, length: a.length }))
break
case 7:
;(t.emoji || (t.emoji = []),
t.emoji.push({ range: { start: a.start, length: a.length }, content: a.content }))
break
case 8:
;(t.codeBlock || (t.codeBlock = []), t.codeBlock.push({ start: a.start, length: a.length }))
break
case 9:
;(t.strikeThrough || (t.strikeThrough = []),
t.strikeThrough.push({ start: a.start, length: a.length }))
break
default:
continue
}
return t
}
function L(e) {
return {
range: { start: e.start, length: e.length },
url: e.content,
preview: e.preview
? {
title: e.preview.title,
description: e.preview.description,
file: e.preview.file
? {
pointer: {
externalDrive: {
key: e.preview.file.key,
path: e.preview.file.path,
version: e.preview.file.version,
size: 0,
},
},
metadata: { mimetype: e.preview.file.type, dimensions: e.preview.file.dimensions },
}
: null,
}
: null,
}
}
async function O(e) {
const t = await e.get(n.CodeVersionKey, n.UpgraderEncoding)
return t ? t.value : 0
}
function C() {
return {
devices: { total: 0, active: 0 },
members: { total: 0, active: 0, pending: 0 },
chat: { clock: 0, pinned: 0, threads: 0, timestamp: 0 },
files: { clock: 0 },
}
}
function _(e) {
return {
isAdmin: !!(2 & e.capabilities),
isModerator: !!(4 & e.capabilities),
isPending: !1,
isActive: !e.inactive || 0 === e.inactive.length,
isMuted: !1,
isBanned: !1,
isShadowBanned: !1,
}
}
function q(e) {
return { isIndexing: !!(2 & e.capabilities), isActive: !0 }
}
function N(e) {
switch (e) {
case 0:
return m.Default
case 1:
return m.Broadcast
case 2:
return m.DirectMessage
default:
return 0
}
}
module.exports = {
generateCompatSeed: y,
getLegacyUserData: h,
getLegacyLocalKey: g,
autobaseApply: b,
migrate: k,
}