Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990// App Open, 2026.09.28// Counts a launch of one of our native apps.// POST /api/app-open {app, version, platform, install, fresh}//// `install` is a random UUID the app mints on its first launch and keeps in// its own storage, so it goes when the app is deleted. It is never tied to an// account, handle, device or address. `fresh` is true only on that first// launch, which is how new installs (and reinstalls, which look the same)// get counted. One row per app + install + day holds the day's open count,// so daily active installs are a count of rows. Rows expire like visits do.// See toolchain/analytics/VISITS.md.import { connect } from "../../backend/database.mjs";import { createHmac, randomBytes } from "node:crypto";import { respond } from "../../backend/http.mjs";import { RETENTION_DAYS } from "../../public/aesthetic.computer/lib/visit-model.mjs";
export const APP_OPEN_COLLECTION = "app-opens";export const APP_OPEN_APPS = Object.freeze([ "menuband", "macpal", "slab", "aesel", "oskiewar", "aestheticcomputer", "desktop", "tapes",]);const PLATFORMS = ["mac", "ios", "ipados", "tvos", "windows", "xbox", "linux"];const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;const VERSION = /^\d+(?:\.\d+){0,3}$/;
// Ephemeral abuse guard only, as in visit-track: nothing here reaches the DB.const rateSalt = randomBytes(32), rates = new Map();function permitted(event) { const now = Date.now(); const source = event.headers?.["cf-connecting-ip"] || event.headers?.["x-forwarded-for"] || "unknown"; const key = createHmac("sha256", rateSalt).update(source).digest("hex"); for (const [id, row] of rates) if (row.until <= now) rates.delete(id); let row = rates.get(key); if (!row) { if (rates.size >= 10000) return false; row = { count: 0, until: now + 60000 }; rates.set(key, row); } return ++row.count <= 60;}
export function validateOpen(body) { if (!body || !APP_OPEN_APPS.includes(body.app) || !PLATFORMS.includes(body.platform) || typeof body.version !== "string" || !VERSION.test(body.version) || typeof body.install !== "string" || !UUID.test(body.install) || typeof body.fresh !== "boolean") return null; return { app: body.app, platform: body.platform, version: body.version, install: body.install.toLowerCase(), fresh: body.fresh };}
let indexes;export async function handler(event) { const headers = { "Cache-Control": "no-store" }; if (event.httpMethod === "OPTIONS") return respond(204, "", headers); if (event.httpMethod !== "POST") return respond(405, { error: "POST required" }, headers); let body = event.body; if (typeof body === "string") { if (body.length > 1024) return respond(400, { error: "Invalid open" }, headers); try { body = JSON.parse(body); } catch { return respond(400, { error: "Invalid JSON" }, headers); } } const open = validateOpen(body); if (!open) return respond(400, { error: "Invalid open" }, headers); if (!permitted(event)) return respond(429, { error: "Open rate limit reached" }, headers); try { const { db } = await connect(); const collection = db.collection(APP_OPEN_COLLECTION); indexes ||= Promise.all([ collection.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }), collection.createIndex({ day: 1, app: 1 }), ]).catch(error => { indexes = null; throw error; }); await indexes; const now = new Date(), day = now.toISOString().slice(0, 10); const update = { $setOnInsert: { app: open.app, install: open.install, day, platform: open.platform, country: event.headers?.["cf-ipcountry"] || null, expiresAt: new Date(+now + RETENTION_DAYS * 86400000) }, $set: { version: open.version }, $inc: { opens: 1 }, $max: { fresh: open.fresh, lastAt: now }, $min: { firstAt: now }, }; const _id = `${open.app}:${open.install}:${day}`; try { await collection.updateOne({ _id }, update, { upsert: true }); } catch (error) { if (error.code !== 11000) throw error; // Two first opens raced the upsert. await collection.updateOne({ _id }, update); } return respond(204, "", headers); } catch { return respond(503, { error: "Open tracking unavailable" }, headers); }}