cowrite / server /util.js
lvwerra's picture
lvwerra HF Staff
Upload folder using huggingface_hub
e5e233a verified
Raw
History Blame Contribute Delete
3.17 kB
import crypto from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
const ID_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
export function newId(len = 10) {
const bytes = crypto.randomBytes(len)
let out = ''
for (let i = 0; i < len; i++) out += ID_ALPHABET[bytes[i] % ID_ALPHABET.length]
return out
}
export function atomicWriteFile(filePath, data) {
fs.mkdirSync(path.dirname(filePath), { recursive: true })
const tmp = path.join(path.dirname(filePath), `.tmp-${newId(6)}`)
fs.writeFileSync(tmp, data)
fs.renameSync(tmp, filePath)
}
export function readJSON(filePath, fallback) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
} catch {
return fallback
}
}
export function writeJSON(filePath, value) {
atomicWriteFile(filePath, JSON.stringify(value, null, 2))
}
export function b64encode(uint8) {
return Buffer.from(uint8).toString('base64url')
}
export function b64decode(str) {
return new Uint8Array(Buffer.from(str, 'base64url'))
}
// --- signed session cookies ---
export function signSession(payload, secret) {
const body = Buffer.from(JSON.stringify(payload)).toString('base64url')
const mac = crypto.createHmac('sha256', secret).update(body).digest('base64url')
return `${body}.${mac}`
}
export function verifySession(value, secret) {
if (!value || !value.includes('.')) return null
const [body, mac] = value.split('.')
const expected = crypto.createHmac('sha256', secret).update(body).digest('base64url')
const a = Buffer.from(mac)
const b = Buffer.from(expected)
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return null
try {
return JSON.parse(Buffer.from(body, 'base64url').toString('utf8'))
} catch {
return null
}
}
export function parseCookies(header) {
const out = {}
if (!header) return out
for (const part of header.split(';')) {
const idx = part.indexOf('=')
if (idx === -1) continue
out[part.slice(0, idx).trim()] = decodeURIComponent(part.slice(idx + 1).trim())
}
return out
}
export const HANDLE_RE = /^[a-z0-9][a-z0-9_.-]{1,38}$/
export const DOC_ID_RE = /^[A-Za-z0-9_-]{1,64}$/
// --- multi-page projects ---
// A project (former "doc") holds pages. Each page is its own Yjs document.
// Collab document names: the bare project id for the "home" page (legacy
// compatible), or `${projectId}::${slug}` for other pages. `_structure` is a
// reserved page whose YAML code block defines the sidebar tree.
export const PAGE_SLUG_RE = /^(_structure|[a-z0-9][a-z0-9-]{0,39})$/
export const DOC_NAME_RE = /^[A-Za-z0-9_-]{1,64}(::(_structure|[a-z0-9][a-z0-9-]{0,39}))?$/
export function projectIdOf(docName) {
return String(docName).split('::')[0]
}
export function pageSlugOf(docName) {
const parts = String(docName).split('::')
return parts[1] || 'home'
}
export function docNameFor(projectId, slug) {
return !slug || slug === 'home' ? projectId : `${projectId}::${slug}`
}
export function slugify(title) {
const s = String(title || '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 40)
return s || 'page'
}