OverviewFilesCommitsIssuesDocsReleasesPeers

keet-deobfuscated / @holepunchto / keet-store / store / chat / file / chat-file.saga.js

275 lines · javascript ref:
import { channel as e, buffers as i } from "redux-saga"
import {
  all as t,
  call as o,
  put as r,
  select as n,
  getContext as l,
  race as s,
  delay as a,
  takeEvery as d,
  fork as f,
  take as m,
  join as c,
} from "redux-saga/effects"
import p from "b4a"
import { DISPLAY_TYPES as u } from "@holepunchto/keet-core/legacy-constants"
import { getCoreBackend as y } from "../../../backend"
import { apiCall as h } from "../../../api/core"
import { apiSubscribeSaga as g } from "../../lib/external-src"
import { forkChildren as w } from "../../lib/effects"
import {
  increaseWaveformVolume as v,
  isAudioMimeType as b,
  isAudioVoicePath as F,
  isMediaMimeType as I,
  isVideoMimeType as P,
  waveformToBase64 as x,
} from "../../lib/file"
import { isInfoError as B, taskErrorSaga as S } from "../../error"
import { APP_CONTEXT_KEY as k } from "../../constants"
import { CHAT_FILE_PREVIEW_BUFFER_LIMIT as A } from "./chat-file.constant"
import {
  FILE_PREVIEW_MAX_BYTES_MEDIUM as C,
  FILE_PREVIEW_EXTENSION as L,
  FILE_PREVIEW_NAME_PREFIX as M,
  fileSaga as T,
} from "../../media/file"
import { sendChatMessageSuccess as $ } from "../message"
import { setChatMessagesInView as E, getChatGroupedMessagesInView as V } from "../list-view"
import {
  addChatPendingFiles as H,
  addChatPendingFileSuccess as K,
  updateChatPendingFile as N,
  removeChatPendingFile as W,
  setChatPendingFileError as _,
  sendChatFiles as D,
} from "./chat-file.action"
import {
  getChatPendingFileError as R,
  getChatPendingFileErrorByTempId as U,
  getChatPendingFileResultByTempId as j,
} from "./chat-file.selector"
import {
  getBytes as q,
  isFilePath as z,
  generateTempId as G,
  groupFiles as J,
  getChatFileMetadata as O,
} from "./chat-file.model"
import { PEAR_LINK_PREVIEW_IMAGE_FECTH_TIMEOUT as Q } from "../chat.constant"
import { userInfoNotification as X } from "../../notification"
import { sharedCoreMessagesUpdate as Y } from "../../api/data.action"
const Z = (e) => `${M}preview.${e}`,
  ee = (e) => Z(e.split("/").pop())
export function* fetchToB4ABuffer(e) {
  try {
    const [i, t] = yield s([o(fetch, e), a(Q)])
    if (t) throw new Error("Pear link preview load timed out")
    if (!i.ok) throw new Error("Failed to fetch pear link preview")
    const r = yield o([i, i.blob]),
      n = yield o([r, r.arrayBuffer])
    return p.from(n, 0, n.byteLength)
  } catch (e) {
    return (yield S("fetchToB4ABuffer", e), null)
  }
}
export function* getPreviewFile({ type: e, name: i, preview: t, dimensions: o, isLocalUri: r }) {
  if (!t) return null
  if (r) return { buffer: t, preview: t, dimensions: o, type: e || `image/${L}`, name: i || "" }
  const n = yield fetchToB4ABuffer(t)
  return n
    ? { buffer: n, preview: t, dimensions: o, type: e || `image/${L}`, name: i || Z(L) }
    : null
}
export function getLinkPreview({ file: e, preview: i }) {
  const t = { url: i.url, title: i.title, description: i.description }
  if (!e) return t
  const o = { mimetype: e.type || `image/${L}`, dimensions: e.dimensions, name: "", isPreview: !0 }
  return (
    e.duration && (o.duration = e.duration),
    e.buffer && e.buffer.length <= C
      ? {
          ...t,
          file: {
            preview: { medium: { pointer: { inlined: e.buffer }, metadata: o } },
            metadata: o,
          },
        }
      : t
  )
}
export function* updateDisplayFiles(e, i) {
  const { uploads: t } = yield l(k),
    o = []
  for (const r of e)
    if (r.type === u.HTTP_LINK) {
      const e = i[r.content]
      if (!e) continue
      const n = t.get(r.content)
      if (!n && !e.description) continue
      const l = getLinkPreview({ file: n, preview: e })
      if (!l || o.includes(l.url)) continue
      ;((r.preview = l), o.push(l.url))
    } else if (r.type === u.PEAR_LINK) {
      const e = i[r.content]
      if (!e) continue
      const t = getLinkPreview({
        file: yield getPreviewFile({ ...e, preview: e.preview || e.previews?.large }),
        preview: e,
      })
      if (!t || o.includes(t?.url)) continue
      ;((r.preview = t), o.push(t.url))
    }
}
function* ie({ roomId: e, file: i }) {
  const { fileManager: t, uploads: n } = yield l(k),
    s = y(),
    a = { name: i.name, mimetype: i.type, dimensions: i.dimensions }
  if (b(i.type) && F(i.path) && t.createAudioPreviewByFile)
    try {
      a.duration = i.duration
      const e = yield o([t, t.createAudioPreviewByFile], i),
        r = v(e)
      a.userdata = x(r)
    } catch (e) {
      yield S("sendChatMessageSubmit", e)
    }
  i.duration && (a.duration = i.duration)
  const d = {}
  if (i.previews?.small) {
    const { base64: e, mimetype: t, dimensions: o } = i.previews.small,
      r = ee(t)
    d.small = {
      pointer: { inlined: e },
      metadata: { name: r, mimetype: t, dimensions: o, isPreview: !0 },
    }
  }
  if (i.previews?.medium) {
    const { base64: e, mimetype: t, dimensions: o } = i.previews.medium,
      r = ee(t)
    d.medium = {
      pointer: { inlined: e },
      metadata: { name: r, mimetype: t, dimensions: o, isPreview: !0 },
    }
  }
  if (i.previews?.large) {
    const { base64: t, buffer: o, mimetype: r, dimensions: n } = i.previews.large,
      l = { name: ee(r), mimetype: r, dimensions: n, isPreview: !0 }
    d.large = yield h(s.saveFileBlob, e, o || p.from(t, "base64"), l)
  }
  if (z(i.path))
    yield g(
      [y().subscribeAddFile, e, i.path, a],
      function* ({ bytesAdded: e, byteLength: t, result: o }) {
        const n = !!o
        ;(yield r(N({ file: i, bytesAdded: e, byteLength: t, completed: n })),
          o && (yield r(K({ result: { ...o, preview: d }, file: i }))))
      },
      { onError: () => r(_({ file: i, error: !0 })) },
    )
  else
    try {
      let t
      t = n.getBytes ? yield o(n.getBytes, i) : q(i)
      const l = yield h(s.saveFileBlob, e, t, a)
      yield r(K({ result: { ...l, preview: d }, file: i }))
    } catch {
      yield r(_({ file: i, error: !0 }))
    }
}
export function* groupFilesSaga(e) {
  const i = yield n(j),
    t = yield n(U),
    o = e.map((e) => i[G(e)]).filter(Boolean),
    r = e.map((e) => t[G(e)]).filter(Boolean)
  return { groups: J(o), errorFiles: r }
}
function* te(e, i) {
  const { mediaWorker: t } = yield l(k)
  for (;;) {
    const r = yield m(e)
    try {
      let e = r
      if (I(r.type) && !r.previews && t) {
        const i = P(r.type) ? t.createChatVideoPreview : t.createChatImagePreview,
          n = yield o(i, { path: r.path })
        e = { ...r, ...O(n) }
      }
      yield o(ie, { roomId: i, file: e })
    } catch (e) {
      yield S("chatFilePreviewWorker", e)
    }
  }
}
function* oe(e, i) {
  try {
    const { uploads: t } = yield l(k),
      o = i.get(e)
    ;((yield n(R, o)) || (yield r(W(o))), t.del(e))
  } catch (e) {
    yield S("chatFilePendingSaga:", e)
  }
}
function* re({ files: e, roomId: i, text: t, opts: o, isMultiFileSupported: l }) {
  const s = y()
  if (l) {
    const { groups: r } = yield groupFilesSaga(e)
    for (let e = 0; e < r.length; e += 1)
      yield h(s.addChatMessage, i, 0 === e ? t : "", { ...o, files: r[e] })
  } else {
    const t = yield n(j)
    for (const o of e) {
      const e = t[G(o)]
      e && (yield h(s.sendFile, i, e))
    }
  }
  yield r($({ roomId: i }))
}
export function* chatFilePendingSaga({ roomId: n }) {
  const { uploads: s } = yield l(k)
  yield d(D, function* (l) {
    const { files: a, text: d, opts: m, isMultiFileSupported: p } = l.payload || {},
      u = new Map(),
      y = a.map((e) => {
        const i = s.get(e),
          t = G(i)
        return (u.set(e, t), i)
      })
    try {
      yield r(H(y.map((e) => ({ file: e, tempId: G(e) }))))
      const t = yield o(e, i.expanding(A)),
        l = yield f(te, t, n)
      for (const e of y) yield r(t, e)
      ;(t.close(),
        yield c(l),
        yield o(re, { files: y, roomId: n, text: d, opts: m, isMultiFileSupported: p }))
    } catch (e) {
      B(e)
        ? yield r(X({ message: e.message }))
        : yield S("chatFilePendingSaga: sendChatFilesHandler", e)
    } finally {
      yield t(a.map((e) => o(oe, e, u)))
    }
  })
}
export function* chatMessageFileTask({ roomId: e, fileIds: i, fileStatsChannel: o }) {
  try {
    yield t(i.map((i) => T({ roomId: e, fileId: i, fileStatsChannel: o })))
  } catch (e) {
    yield S("chatMessageFileTask", e)
  }
}
export function* chatFileInViewSaga({ roomId: e, fileStatsChannel: i }) {
  yield w(
    [Y, E],
    function* () {
      return (yield n(V)).reduce((e, i) => (i.fileIds.length > 0 && e.push(...i.fileIds), e), [])
    },
    function* ({ taskId: t }) {
      yield T({ roomId: e, fileId: t, fileStatsChannel: i })
    },
    (e) => S("chatFileInViewSaga", e),
  )
}