
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
@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. A project's allowed types (configured in the Encody dashboard) are inherited automatically and override this — see Allowed file types. |
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
}
})
A project can define which MIME types it accepts in the Encody dashboard. The SDK inherits that list automatically — you don't have to re-declare it in the browser:
allowedTypes return value.allowedTypes you pass to
useEncody(...); if the project defines none, your init allowedTypes applies; if that is
also empty, all types are allowed.Every add() is validated against the effective list — disallowed files are rejected with
ErrorCode.FILE_TYPE_NOT_ALLOWED (see Error codes).
<input type="file">The effective list maps directly onto the input's accept attribute — MIME wildcards like
image/* are valid accept tokens, so a comma-joined string works as-is. This filters the
operating-system file dialog so users only see allowed types:
<script setup>
import { useEncody } from '@encody/vue'
import { computed } from 'vue'
const { add, allowedTypes } = useEncody({ token: getTokenFromBackend })
// undefined when the list is empty → attribute omitted → all types allowed
const accept = computed(() => allowedTypes.value.length ? allowedTypes.value.join(',') : undefined)
</script>
<template>
<input type="file" multiple :accept="accept" @change="e => add([...e.target.files])">
</template>
allowedTypes is reactive, so the accept filter updates live when the project's types
change in the dashboard. Note that accept is a UX hint only — it doesn't enforce
anything; the SDK's add() validation (and the Encody server) still reject disallowed files.
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
allowedTypes, // Ref<string[]> — effective allowed MIME types (project override, else init), reactive
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' })
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', () => {})
on('allowedTypes:change', ({ allowedTypes }) => {}) // project's allowed types changed (JWT claim or live SSE)
file(id).raw()Returns the file as a File object. Accepts an optional options object to control the source:
| Option | Default | Description |
|---|---|---|
| (none) | auto | In-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()
}
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 3 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
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

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.