
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
@encody/vue
Advanced tools
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.
npm install @encody/vue
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
| Option | Default | Description |
|---|---|---|
baseUrl | 'https://app.encody.io' | API origin. Override for on-prem or enterprise installs. |
endpoint | {baseUrl}/media/upload | TUS upload endpoint. Derived from baseUrl if omitted. |
sseUrl | {baseUrl}/api/upload-events | SSE endpoint for real-time status updates. Derived from baseUrl if omitted. |
token | — | async () => 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. |
concurrency | 3 | Max parallel uploads. |
retryDelays | [0, 3000, 10000, 30000] | Backoff delays in ms between TUS retries. Pass [] to disable. |
maxSize | null | Max file size in bytes. null = unlimited. |
allowedTypes | [] | Allowed MIME types. Supports wildcards (image/*). Empty = all types allowed. |
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
}
})
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
} = 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' })
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', () => {})
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'
}
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
}
import { createEncody } from '@encody/vue/core'
const instance = createEncody({ token, baseUrl })
instance.add(files)
instance.on('file:ready', handler)
instance.destroy()
MIT
Marcus Spiegel spiegel@uscreen.de — published and supported by u|screen
FAQs
Encody client SDK — resumable upload queue with Vue composables
The npm package @encody/vue receives a total of 6 weekly downloads. As such, @encody/vue popularity was classified as not popular.
We found that @encody/vue demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.