Download src/lib/beta/betaAccess.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 4.3 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/beta/betaAccess.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/lib/beta/betaAccess.ts
-
curl -L -o betaAccess.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/beta/betaAccess.ts
4.3 kB
| /** | |
| * Day 5 — controlled beta access gate. | |
| * | |
| * This is a SOFT, frontend-only gate so the builder can share DocDoe with a | |
| * small, selected group of SSLC beta students. It is intentionally NOT strong | |
| * security: it is a shared access code checked entirely in the browser, with | |
| * no backend, no account system, and no payment. Do not describe this as | |
| * "secure authentication" anywhere in the UI or docs. | |
| * | |
| * Privacy: only a beta code, an optional nickname, class, board and language | |
| * preference are stored — all on this device. No email, phone, full name, | |
| * exact school, location, or parent contact is ever asked for or stored. | |
| */ | |
| export type BetaAccessState = { | |
| hasAccess: boolean; | |
| accessCodeLabel?: string; | |
| grantedAt?: string; | |
| studentNickname?: string; | |
| classLevel?: string; | |
| board?: string; | |
| languagePreference?: string; | |
| }; | |
| export type BetaAccessProfile = { | |
| studentNickname?: string; | |
| classLevel?: string; | |
| board?: string; | |
| languagePreference?: string; | |
| }; | |
| export type BetaAccessGrantResult = | |
| | { ok: true; state: BetaAccessState } | |
| | { ok: false; error: "invalid_code" | "storage_unavailable" }; | |
| export const BETA_ACCESS_STORAGE_KEY = "docdoe:beta-access"; | |
| /** | |
| * Small local allowlist for this beta. Easy to update: add/remove strings. | |
| * Codes are matched case-insensitively after trimming. | |
| */ | |
| export const BETA_ACCESS_CODES = ["DOCDOE-BETA", "SSLC-PHYSICS", "ASSESS-CLASS10"]; | |
| export const BETA_ACCESS_DEFAULTS = { | |
| classLevel: "Class 10", | |
| board: "Kerala SSLC", | |
| languagePreference: "English", | |
| }; | |
| function hasStorage(): boolean { | |
| return typeof window !== "undefined" && typeof window.localStorage !== "undefined"; | |
| } | |
| function normalizeCode(code: string): string { | |
| return code.trim().toUpperCase(); | |
| } | |
| /** True if the code matches an active beta allowlist entry (case-insensitive). */ | |
| export function validateBetaAccessCode(code: string): boolean { | |
| const normalized = normalizeCode(code ?? ""); | |
| if (!normalized) return false; | |
| return BETA_ACCESS_CODES.some((allowed) => normalizeCode(allowed) === normalized); | |
| } | |
| function isAccessStateLike(value: unknown): value is BetaAccessState { | |
| return Boolean(value) && typeof value === "object" && typeof (value as Record<string, unknown>).hasAccess === "boolean"; | |
| } | |
| /** Load access state. Corrupt/absent storage yields "no access", never throws. */ | |
| export function loadBetaAccess(): BetaAccessState { | |
| if (!hasStorage()) return { hasAccess: false }; | |
| try { | |
| const raw = window.localStorage.getItem(BETA_ACCESS_STORAGE_KEY); | |
| if (!raw) return { hasAccess: false }; | |
| const parsed = JSON.parse(raw); | |
| if (!isAccessStateLike(parsed)) return { hasAccess: false }; | |
| return parsed; | |
| } catch { | |
| return { hasAccess: false }; | |
| } | |
| } | |
| export function saveBetaAccess(state: BetaAccessState): void { | |
| if (!hasStorage()) return; | |
| try { | |
| window.localStorage.setItem(BETA_ACCESS_STORAGE_KEY, JSON.stringify(state)); | |
| } catch { | |
| // Best-effort only; the gate simply re-prompts on the next load. | |
| } | |
| } | |
| export function clearBetaAccess(): void { | |
| if (!hasStorage()) return; | |
| try { | |
| window.localStorage.removeItem(BETA_ACCESS_STORAGE_KEY); | |
| } catch { | |
| // ignore | |
| } | |
| } | |
| export function hasBetaAccess(): boolean { | |
| return loadBetaAccess().hasAccess; | |
| } | |
| /** | |
| * Validate a code and, if active, grant + persist access with the given | |
| * (fully optional, non-identifying) profile fields. Returns a friendly error | |
| * on failure without explaining exactly why the code was rejected. | |
| */ | |
| export function grantBetaAccess(code: string, profile: BetaAccessProfile = {}): BetaAccessGrantResult { | |
| if (!validateBetaAccessCode(code)) { | |
| return { ok: false, error: "invalid_code" }; | |
| } | |
| if (!hasStorage()) { | |
| return { ok: false, error: "storage_unavailable" }; | |
| } | |
| const state: BetaAccessState = { | |
| hasAccess: true, | |
| accessCodeLabel: normalizeCode(code), | |
| grantedAt: new Date().toISOString(), | |
| studentNickname: profile.studentNickname?.trim() || undefined, | |
| classLevel: profile.classLevel?.trim() || BETA_ACCESS_DEFAULTS.classLevel, | |
| board: profile.board?.trim() || BETA_ACCESS_DEFAULTS.board, | |
| languagePreference: profile.languagePreference?.trim() || BETA_ACCESS_DEFAULTS.languagePreference, | |
| }; | |
| saveBetaAccess(state); | |
| return { ok: true, state }; | |
| } | |