OverviewFilesCommitsIssuesDocsReleasesPeers

keet-cli / keet-core-4.14.4-pretty / lib / core / migrations / 3 / apply.js

486 lines · javascript ref:
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,
}