/** * Client for the SkateSpots push service (`push-service/`). * * The service watches relays for push-marked gift wraps and sends a CONTENT-FREE * Web Push. It never decrypts; the app fetches + decrypts the named wrap itself. * * Base URL mirrors `sasquatchAiBase()`: production (and native builds) use the * deployed host; dev points at the local service. `VITE_PUSH_SERVICE_BASE` * overrides both. */ import { isNativePlatform } from '@/lib/platform'; const PRODUCTION_PUSH_BASE = 'https://push.skate-spots.org'; export function pushServiceBase(): string { const override = (import.meta.env as Record).VITE_PUSH_SERVICE_BASE; if (override) return override.replace(/\/+$/, ''); if (import.meta.env.PROD || isNativePlatform()) return PRODUCTION_PUSH_BASE; return 'http://localhost:8788'; } /** Standard `PushSubscription.toJSON()` shape the service expects. */ export interface PushSubscriptionJSON { endpoint: string; keys: { p256dh: string; auth: string }; } async function post(path: string, body: unknown): Promise { const res = await fetch(`${pushServiceBase()}${path}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), }); const data = (await res.json().catch(() => ({}))) as T & { error?: string }; if (!res.ok) throw new Error(data.error || `Push service error (${res.status})`); return data; } export async function fetchVapidPublicKey(): Promise { const res = await fetch(`${pushServiceBase()}/vapid-public-key`); if (!res.ok) throw new Error('Could not reach the notification service.'); const data = (await res.json()) as { key?: string }; if (!data.key) throw new Error('Notification service returned no key.'); return data.key; } export async function requestPushChallenge(pubkey: string): Promise { const data = await post<{ challenge: string }>('/auth/challenge', { pubkey }); return data.challenge; } export async function subscribeToPush( subscription: PushSubscriptionJSON, proof: unknown, categories?: string[], ): Promise { await post('/subscribe', { subscription, proof, categories }); } export async function unsubscribeFromPush(endpoint: string, proof: unknown): Promise { await post('/unsubscribe', { endpoint, proof }); }