Files
Fredrik Johansson 4eee44f675 Initial scaffold: blob store server, CLI scripts, and PWA share target
Clipboard/screenshot sync across own devices via a single DEVICE_TOKEN-gated
blob store, plus a small installable web app so a phone can push/pull
clips and share screenshots via the OS share sheet.
2026-08-20 15:28:42 +02:00

50 lines
1.8 KiB
TypeScript

import { Router } from 'express'
import fs from 'node:fs'
import multer from 'multer'
import { config } from './config.js'
import { blobPath } from './db.js'
import { newBlobId } from './ids.js'
import { insertBlobRecord } from './blobs.js'
import { timingSafeStringEqual } from './auth.js'
// Separate from routes.ts: the OS share sheet POSTs here directly (no
// custom headers possible), so auth rides in the query string instead of
// X-Device-Token — see manifest.ts for where that URL gets the token baked
// in. Same trust level as the token in ~/.config/scrot/env: not a secret
// from the machine it's on, just from the network.
export const shareRouter = Router()
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: config.maxUploadBytes } })
shareRouter.post('/share', upload.any(), (req, res) => {
const token = typeof req.query.t === 'string' ? req.query.t : ''
if (!timingSafeStringEqual(token, config.deviceToken)) {
res.status(401).send('bad or missing device token — re-add scrot to your home screen to re-onboard')
return
}
const files = (req.files as Express.Multer.File[] | undefined) ?? []
const image = files.find((f) => f.mimetype.startsWith('image/'))
if (image) {
const id = newBlobId()
fs.writeFileSync(blobPath(id), image.buffer)
insertBlobRecord(id, 'shot', image.mimetype, image.buffer.length)
res.redirect(303, `/?shared=shot&id=${id}`)
return
}
const body = req.body as Record<string, string>
const text = (body.text || body.url || body.title || '').trim()
if (text) {
const id = newBlobId()
const buf = Buffer.from(text, 'utf8')
fs.writeFileSync(blobPath(id), buf)
insertBlobRecord(id, 'clip', 'text/plain; charset=utf-8', buf.length)
res.redirect(303, '/?shared=clip')
return
}
res.redirect(303, '/?shared=empty')
})