Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 2x 2x 2x 2x 2x 2x 1x 2x 2x 2x 2x 2x 2x 2x 208x 208x 1248x 208x 2x 6x 6x 4x 1x 3x 8x 8x 8x 2x 2x 2x 1x | import { randomInt } from "node:crypto";
import { getApps, initializeApp } from "firebase-admin/app";
import { FieldValue, getFirestore, Timestamp } from "firebase-admin/firestore";
import { HttpsError, onCall } from "firebase-functions/https";
import { isAnonymous } from "./authGuard";
if (getApps().length === 0) {
initializeApp();
}
const db = getFirestore();
const REGION = "europe-west2";
// Crockford-style alphabet — no I, L, O, U, 0 or 1, so a code is unambiguous to read aloud.
const ALPHABET = "23456789ABCDEFGHJKMNPQRSTVWXYZ";
const CODE_LENGTH = 6;
const INVITE_TTL_MS = 14 * 24 * 60 * 60 * 1000; // 14 days
const MAX_ATTEMPTS = 5;
/** A short, human-friendly, single-use invite code: `GLM-XXXXXX`. */
export function generateCode(): string {
let body = "";
for (let i = 0; i < CODE_LENGTH; i++) {
body += ALPHABET[randomInt(ALPHABET.length)];
}
return `GLM-${body}`;
}
/**
* Mint an invite code the caller can share with their partner. The code maps to
* the caller's uid (`invites/{code}` → { inviterId }); `pairWithCode` consumes it.
* Codes are single-use + expiring, and `invites/*` is locked to functions only.
* Anonymous callers are rejected — you must connect a provider before pairing.
*/
export const createInvite = onCall({ region: REGION }, async (request) => {
const uid = request.auth?.uid;
if (!uid) throw new HttpsError("unauthenticated", "Sign in first.");
if (isAnonymous(request.auth)) {
throw new HttpsError(
"failed-precondition",
"Connect your account before inviting your partner.",
);
}
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
const code = generateCode();
const ref = db.doc(`invites/${code}`);
if ((await ref.get()).exists) continue;
const expiresAt = Timestamp.fromMillis(Date.now() + INVITE_TTL_MS);
await ref.set({
inviterId: uid,
createdAt: FieldValue.serverTimestamp(),
expiresAt,
used: false,
});
return { code, expiresAt: expiresAt.toMillis() };
}
throw new HttpsError("resource-exhausted", "Could not mint a unique code. Please try again.");
});
|