New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

@encody/vue

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@encody/vue

Encody client SDK — resumable upload queue with Vue composables

npmnpm
Version
0.7.0
Version published
Weekly downloads
14
133.33%
Maintainers
1
Weekly downloads
 
Created
Source

@encody/vue

TUS-based resumable upload queue with a Vue 3 composable. Handles concurrency, retries, SSE status updates, file validation, and error codes out of the box.

Install

npm install @encody/vue

Usage

import { useEncody } from '@encody/vue'

const { add, clear, files, isActive } = useEncody({
  token: () => getTokenFromBackend()
})

All options accept a raw value or a Vue ref — the composable stays reactive either way.

Live demo and playground: demo.encody.io

Options

OptionDefaultDescription
baseUrl'https://app.encody.io'API origin. Override for on-prem or enterprise installs.
endpoint{baseUrl}/media/uploadTUS upload endpoint. Derived from baseUrl if omitted.
sseUrl{baseUrl}/api/upload-eventsSSE endpoint for real-time status updates. Derived from baseUrl if omitted.
tokenasync () => string — called before each upload and on SSE reconnect. Must return a short-lived JWT. Never expose your API key to the browser — request the token from your own backend, which exchanges the API key for a JWT against the Encody API.
concurrency3Max parallel uploads.
retryDelays[0, 3000, 10000, 30000]Backoff delays in ms between TUS retries. Pass [] to disable.
maxSizenullMax file size in bytes. null = unlimited.
allowedTypes[]Allowed MIME types. Supports wildcards (image/*). Empty = all types allowed.

Token flow

The token function is called by the SDK before every upload and on SSE reconnect. It must return a short-lived JWT issued by the Encody API.

Your API key must never leave your server. The recommended flow is:

Browser  →  POST /token  →  Your backend  →  POST /api/token  →  Encody API
                                              (Authorization: Bearer ek_...)

Minimal Node.js token endpoint (no dependencies):

// token-server.mjs
import { createServer } from 'node:http'

const {
  PORT = 3000,
  ENCODY_API_KEY,
  ENCODY_BASE_URL = 'https://app.encody.io'
} = process.env

createServer(async (req, res) => {
  if (req.method !== 'POST' || req.url !== '/token') {
    res.writeHead(404).end()
    return
  }

  const { token } = await fetch(`${ENCODY_BASE_URL}/api/token`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${ENCODY_API_KEY}` }
  }).then(r => r.json())

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ token }))
}).listen(PORT)
ENCODY_API_KEY=ek_... node token-server.mjs

Then pass it to useEncody:

const { add, files } = useEncody({
  token: async () => {
    const res = await fetch('/token', { method: 'POST' })
    return (await res.json()).token
  }
})

Composable return value

const {
  add, // add files to the queue
  clear, // clear the queue
  files, // ComputedRef<FileRecord[]> — reactive queue state
  isActive, // ComputedRef<boolean> — true while any upload is in progress
  file, // (id) => { retry, cancel, pause, resume, updateMeta }
  useFile, // (id | Ref<id>) => ComputedRef<FileRecord> — reactive single file
  useBatch, // (batchId) => { files, progress, isComplete, hasFailed }
  on, // (event, handler) => void
  off, // (event, handler) => void
  initSession // () => Promise<void> — establish /media/* browser session cookie
} = useEncody(options)

add(input, options?)

// single file
add(file)

// multiple files — auto-grouped as a batch
add([file1, file2])

// with custom meta (stored to DB, accessible server-side)
add(files, { meta: { folderId: '123', tag: 'avatar' } })

// per-file meta
add([
  { file: fileA, meta: { tag: 'cover' } },
  { file: fileB, meta: { tag: 'thumb' } }
])

file(id)

file(id).retry()
file(id).cancel()
file(id).pause()
file(id).resume()
file(id).updateMeta({ tag: 'updated' })

Events

on('file:uploading', ({ fileId, filename, size, meta }) => {})
on('file:ready', ({ fileId, url, meta }) => {})
on('file:failed', ({ fileId, code, error, meta }) => {})
on('file:progress', ({ fileId, progress, meta }) => {})
on('batch:complete', ({ batchId, files }) => {})
on('batch:failed', ({ batchId, failed, succeeded }) => {})
on('sse:error', () => {})

file(id).raw()

Returns the file as a File object. Accepts an optional options object to control the source:

OptionDefaultDescription
(none)autoIn-memory File if available in this session, fetches from API otherwise
{ remote: true }Always fetch from API — bypasses the in-memory cache
{ local: true }Only return in-memory File; returns null if not in queue
// Auto — local if available, remote fallback
const f = await file(record.id).raw()

// Force remote fetch (e.g. to measure download time or get a fresh copy)
const f = await file(record.id).raw({ remote: true })

// Local only — null if the file isn't in the current session queue
const f = await file(record.id).raw({ local: true })

// Local image preview
img.src = URL.createObjectURL(f)

// Pass to another API (e.g. canvas, custom processor)
processLocally(f)

file(id).downloadUrl()

Returns a Promise resolving to the authenticated download URL. Works with queue IDs (resolves the server file ID automatically) or raw server file IDs fetched from a REST listing — the caller doesn't need to know which kind of ID it has.

// from the upload queue
const url = await file(record.id).downloadUrl()

// from a REST listing — works identically
const url = await file(upload.fileId).downloadUrl()
const url = await file(upload.id).downloadUrl()

// in a Vue template
<a :href="await file(record.id).downloadUrl()">Download</a>

file(id).download(filename?)

Fetches the file with auth and triggers a browser download dialog — no signed URLs needed. Uses the JWT from your token getter if configured, otherwise falls back to credentials: 'include' for session auth.

// from the upload queue
await file(record.id).download()
await file(record.id).download('custom-name.jpg')

// from a REST listing — works identically
await file(upload.fileId).download()

// in a template
<button @click="file(record.id).download(record.name)">Download</button>

initSession()

Exchanges the current JWT for a browser session cookie covering all /media/* routes (/media/image, /media/download, etc.). Once established, cross-origin <img src> and <video src> requests authenticate via the cookie automatically — no Authorization header needed.

Called automatically on each add() call. Invoke explicitly at startup for image-only use cases (no uploads):

const { initSession, file } = useEncody({ token: getTokenFromBackend })
await initSession()

// <img :src="file(id).imageUrl(400, 300)"> now works cross-origin

The session is valid for 15 minutes (matching the JWT TTL). The SDK tracks expiry via a companion readable cookie (encody_media_exp) set by the server, so redundant re-init calls are skipped across page reloads.

file(id).imageUrl(width?, height?, format?)

Returns an image URL string — sync, safe to use inline in templates. Passes id through as-is (accepts a fileId or any server-side id). Browser HTTP caching (1 year, public + ETag) is fully preserved since no blob fetch is involved.

// Auto-detects WebP support and scales for device pixel ratio.
// On a 2× Retina display with WebP support:
file(upload.fileId).imageUrl(400, 300) // → '/media/image/{id}/800-600/image.webp'

// On a 1× display without WebP:
file(upload.fileId).imageUrl(400, 300) // → '/media/image/{id}/400-300/image.jpg'

// Override format explicitly (DPR still applied)
file(upload.fileId).imageUrl(400, 300, 'jpeg') // → '/media/image/{id}/800-600/image.jpeg' on 2× display

// Pass 0 for either dimension to preserve aspect ratio (no letterboxing, no cropping):
file(upload.fileId).imageUrl(400, 0) // → '/media/image/{id}/800-0/image.webp'  — fit to width
file(upload.fileId).imageUrl(0, 300) // → '/media/image/{id}/0-600/image.webp'  — fit to height

// Original file, no transformation
file(upload.fileId).imageUrl() // → '/media/image/{id}'

// Directly in a Vue template — no computed or await needed
// <img :src="file(upload.fileId).imageUrl(400, 300)">

Both methods accept any server-side id — the server resolves either format automatically.

FileRecord shape

{
  id:          string,
  status:      'queued' | 'uploading' | 'processing' | 'ready' | 'failed' | 'cancelled',
  progress:    number,   // 0–99 during upload, 99 during processing, 100 when ready
  name:        string,
  size:        number,
  mimeType:    string,
  meta:        object,
  batchId:     string | null,
  error:       string | null,
  errorCode:   ErrorCode | null,
  url:          string | null,  // populated when status === 'ready'
  serverFileId: string | null,  // server-side file ID, populated when status === 'ready' — use for download()
}

Error codes

import { defaultMessages, EncodyError, ErrorCode } from '@encody/vue'

ErrorCode.TOKEN_EMPTY // token() returned falsy
ErrorCode.UPLOAD_FAILED // network or server error
ErrorCode.VIRUS_DETECTED // antivirus flagged the file
ErrorCode.SSE_ERROR // SSE connection failed repeatedly
ErrorCode.FILE_TOO_LARGE // exceeds maxSize
ErrorCode.FILE_TYPE_NOT_ALLOWED // not in allowedTypes

Use defaultMessages[code] as fallbacks, override per code for i18n:

const messages = {
  [ErrorCode.FILE_TOO_LARGE]: 'Die Datei ist zu groß'
}

function errorMessage(file) {
  return messages[file.errorCode] ?? file.error
}

Framework-agnostic core

import { createEncody } from '@encody/vue/core'

const instance = createEncody({ token, baseUrl })
instance.add(files)
instance.on('file:ready', handler)
instance.destroy()

License

MIT

Author

Marcus Spiegel spiegel@uscreen.de — published and supported by u|screen

FAQs

Package last updated on 04 May 2026

Related posts