/** * Pro board-gate registry. * * STM32 and the QEMU-backed Raspberry Pi family are Pro-only emulation * features. The OSS app knows *which* boards are Pro (public information — * `isProBoardKind`) and renders a PRO badge for them, but it does NOT know * the current user's entitlement. The pro overlay installs the real gate via * `installBoardGateImpl`, deciding whether a given add/run is allowed for the * signed-in user on the web. * * Mirrors the other OSS->Pro seams (`proSaveAction.ts`, `proSession.ts`, * `proRoutes.ts`): the OSS app defines a stable doorbell; the overlay plugs in. * * - OSS without an overlay -> default impl returns 'allow'. Self-hosted * deployments don't block in the UI; the missing emulation binary plus a * Pro-framed backend message handle availability (see stm32_lib_manager / * the Pi boot-image provider). * - With the pro overlay -> installBoardGateImpl() returns 'block' for a * non-paid user on the web, and the caller fires the upgrade prompt. * - Desktop (Tauri) -> overlay returns 'allow'; the per-board QEMU * download prompt (Stm32QemuPrompt / RaspberryPiQemuPrompt) handles it. */ import type { BoardKind } from '../types/board'; import { isPiBoardKind, isStm32BoardKind } from '../types/board'; export type BoardGateDecision = 'allow' | 'block'; /** * Static, public predicate: which board kinds are Pro emulation features. * Used for the PRO badge (always shown for these) and as the precondition the * overlay's gate checks. STM32 (libqemu-arm) + every QEMU Raspberry Pi * (Zero/1/2/3/4/5; excludes the Pico, which is browser emulation). */ export function isProBoardKind(kind: BoardKind | string): boolean { return isStm32BoardKind(kind) || isPiBoardKind(kind); } type BoardGateImpl = (kind: BoardKind) => BoardGateDecision; let _impl: BoardGateImpl | null = null; /** Installed by the pro overlay (mountPro). Pass null to clear (hot reload). */ export function installBoardGateImpl(impl: BoardGateImpl | null): void { _impl = impl; } /** Whether the pro overlay has installed a gate (else OSS default applies). */ export function hasBoardGateImpl(): boolean { return _impl !== null; } /** * Decide whether adding/running a board of `kind` is allowed for the current * user. Non-Pro boards are always allowed. For Pro boards, the overlay's impl * decides; with no overlay the OSS default is 'allow'. */ export function boardGateDecision(kind: BoardKind): BoardGateDecision { if (!isProBoardKind(kind)) return 'allow'; if (!_impl) return 'allow'; try { return _impl(kind); } catch (err) { // eslint-disable-next-line no-console console.warn('[oss] board-gate impl threw:', err); return 'allow'; } } /** * Fire the Pro upgrade prompt. Dispatches the same CustomEvent the pro * overlay's UpgradeGate listens for (`PRO_UPGRADE_EVENT` in * proComponentInjector.ts). The event name is the stable contract — the OSS * app does not import from the overlay. */ const PRO_UPGRADE_EVENT = 'velxio-pro-upgrade-prompt'; export function triggerProUpgradePrompt(featureName: string): void { if (typeof window === 'undefined') return; window.dispatchEvent( new CustomEvent(PRO_UPGRADE_EVENT, { detail: { componentName: featureName } }), ); } /** Human label for the upgrade prompt, per board family. */ export function proBoardFeatureName(kind: BoardKind | string): string { if (isStm32BoardKind(kind)) return 'STM32 emulation'; if (isPiBoardKind(kind)) return 'Raspberry Pi emulation'; return 'this board'; }