A drop on the holocron, for moving files between machines
Drop a file on one machine, pick it up on another. Two columns, a private store of its own, and no polling.
Full-width, below the tiles. Left half takes what you drop on it, right half lists what is in there with a download and a delete. It replaces wanting an FTP account somewhere: no third party, no server to patch, and it is already behind the gate.
Its own store, and that is not a preference
Public and private is a property of the store, not of a file in it. Podcast
audio has to be public — a podcast client fetching an <enclosure> has no
cookie to send — and a file drop has to be private. They cannot be the same
store. Vercel's own guidance says the same thing under store limits: use
separate stores to isolate public and private content.
So the store SC-20 created is left alone, and this gets a private one. It also means the delete button here cannot reach the audio, whatever anyone clicks.
Why nothing refreshes by itself
list() is a billed Advanced Operation. Polling the listing every five seconds
is 720 an hour, so a tab left open overnight would run up thousands of calls for
a page nobody is looking at. The listing moves on page load, after an upload,
after a delete, and when you press refresh.
The first listing is rendered by the page rather than fetched on mount, so arriving costs one operation rather than two.
The file never touches the site
Uploads go straight from the browser to the store, with /api/drop/upload
handing out a one-shot token. Through a Function instead, a file would be
capped at 4.5 MB — the body limit — and the transfer would be paid for twice.
Downloads have to come back through /api/drop/file, because a private blob's
URL answers to nobody without a token. That route streams it with
Cache-Control: private, no-store.
Ceiling of 100 MB a file, because Vercel recommends against serving anything larger through a private store, and because 1 GB of storage does not survive many mistakes.
The gate is now carrying more than it was sized for
lib/holocron.ts says of itself:
This keeps a casual wanderer out and nothing more, which is the right size for a page with nothing in it.
The page no longer has nothing in it. The attempt counter lives in a cookie, so a private window resets it and two tries an hour is really unlimited guessing. Every route here checks the session server-side next to the call it guards, and a closed session gets a 404 that says nothing — but that is the door, not the lock. Real rate limiting needs server-side state keyed to something the visitor does not control.
Until that exists, the password is the whole of the security, and it should be long enough that unlimited guessing does not matter.
Not done until
- The private store exists and
DROP_BLOB_READ_WRITE_TOKENis set on Vercel and pulled locally. Without it every route answers 503 and the tile says so. - A file has actually made the trip between two machines. Nothing here has touched a real store yet — the gate, the listing route, the path checks and the tile were tested against a running server, the uploads were not.
Built and QA'd, September 21
The store is scofti-drop — store_d6TyYQLokipUaXAP, private, iad1 —
created separately from the audio store, which stays empty and untouched.
DROP_BLOB_READ_WRITE_TOKEN is set on production, preview and development.
Michael dropped a file in through the browser and pulled it back down. That was the one path nothing else could test: the upload goes straight from the browser to the store on a one-shot token, so it needs a real browser and a real file.
Everything else was checked against production:
- List, download, delete all work through the page. Downloads come back with
Content-Disposition: attachmentandCache-Control: private, no-store. - Without a session, the listing, the file route and delete all answer 404 — no hint that anything is there.
../../etc/passwdanswers 404. So does any pathname outside thedrop/prefix; a delete aimed outside it is refused with 400.
The thing worth knowing before touching this again
Setting it up put a test file in the wrong store. The Vercel CLI and the SDK
both ignore an explicitly passed token when OIDC credentials and a store id are
present in the environment — and .env.local had a stale BLOB_STORE_ID
pointing at the audio store. The file went there instead, and had to be deleted.
So: an explicit token is not authoritative. When two stores exist, clear
BLOB_STORE_ID and VERCEL_OIDC_TOKEN from the environment before running
anything that writes, or it will quietly write somewhere else. The site itself
was never wrong — it reads DROP_BLOB_READ_WRITE_TOKEN and nothing else.
Two related traps, recorded rather than fixed: vercel blob commands rewrite
.env.local without being asked, which cost a local-only API key on the way
through; and the default BLOB_* variables still sit in that file pointing at
the audio store.
Still true, and still soft
The gate did not change. The attempt counter is a cookie, so a private window resets it, and the password is doing all the work. That was noted when this was built and it is noted again now that the page holds files rather than a clock.
Correction, September 21
This ticket and the code both said the drop does not poll because the Hobby plan includes 2,000 Advanced Operations a month, and that exceeding it locks the store for thirty days. The project is on Pro, where those operations are billed rather than capped, so the consequence was wrong in both places.
The decision does not change — polling still costs, it just costs money instead of access. The reason now says what is actually true.
Noted because it was an assumption rather than a check: the plan was read out of Vercel's Hobby documentation and never confirmed against the account.