Add EMF field mode, Armory shop/waitlist, and ambient haunting layer
Three self-contained features, verified complete and cross-wired end-to-end (audited: backend 54/54 tests, frontend 110/110 tests, tsc --noEmit clean, i18n coverage script clean): - EMF mode: DeviceMotion/DeviceOrientation-based field-meter sensing, a fifth séance channel alongside Wire/EVP/Radio/Ouija, with its own fragment prompt persona and full frontend gauge UI. - Armory (shop/waitlist): pre-order capture page for the future Ultimate Quantum Box hardware line, rate-limited public endpoint, explicitly no payment collection. - Haunting layer: ambient possession effects (dread-bed audio, title glitching, idle-paced whispers/manifests), respects prefers-reduced-motion, mounted once at the app root. Plus WebUSB robustness fixes in lib/sdr.ts (Terratec vendor ID, explicit selectConfiguration, isSecureContext gate). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013PphXq1s43DNRj1uWKGXof
This commit is contained in:
@@ -29,6 +29,9 @@ export type SweepCallbacks = {
|
||||
// USB identification for RTL2832U dongles.
|
||||
export const RTL2832U_VENDOR = 0x0bda
|
||||
export const RTL2832U_PRODUCTS = [0x2832, 0x2834, 0x2838, 0x2837]
|
||||
// Terratec (and other rebadged) dongles share the RTL2832U guts but report a
|
||||
// different vendor id — they speak the same protocol once claimed.
|
||||
export const TERRATEC_VENDOR = 0x0ccd
|
||||
|
||||
// Request types used by librtlsdr.
|
||||
const CTRL_IN = 0xc0
|
||||
@@ -38,8 +41,14 @@ const USB_EPA = 0x02
|
||||
const SYS = 0x09
|
||||
const PAGE_USB = 0x01
|
||||
|
||||
/** WebUSB only exists in secure contexts (https, or localhost). */
|
||||
export function isSecureContext(): boolean {
|
||||
return typeof window !== 'undefined' && window.isSecureContext === true
|
||||
}
|
||||
|
||||
export function isSupported(): boolean {
|
||||
return (
|
||||
isSecureContext() &&
|
||||
typeof navigator !== 'undefined' &&
|
||||
'usb' in navigator &&
|
||||
typeof navigator.usb?.requestDevice === 'function'
|
||||
@@ -62,12 +71,9 @@ export class RtlSdr {
|
||||
if (!isSupported()) {
|
||||
throw new Error('WebUSB is not available in this vessel (Chromium required)')
|
||||
}
|
||||
const filters = [
|
||||
{ vendorId: RTL2832U_VENDOR, productId: 0x2832 },
|
||||
{ vendorId: RTL2832U_VENDOR, productId: 0x2834 },
|
||||
{ vendorId: RTL2832U_VENDOR, productId: 0x2838 },
|
||||
{ vendorId: RTL2832U_VENDOR, productId: 0x2837 },
|
||||
]
|
||||
// Vendor-only filters: many dongles report product ids outside the
|
||||
// handful we know, so filtering by productId hides them from the picker.
|
||||
const filters = [{ vendorId: RTL2832U_VENDOR }, { vendorId: TERRATEC_VENDOR }]
|
||||
this.device = await navigator.usb.requestDevice({ filters })
|
||||
}
|
||||
|
||||
@@ -78,6 +84,17 @@ export class RtlSdr {
|
||||
this.sampleRate = sampleRateHz
|
||||
await dev.open()
|
||||
|
||||
// WebUSB populates `configuration` (interfaces, endpoints) only after a
|
||||
// configuration is explicitly selected — without this, most dongles
|
||||
// report a null configuration and everything downstream fails.
|
||||
try {
|
||||
await dev.selectConfiguration(1)
|
||||
} catch (err) {
|
||||
// Tolerable only if the device is already configured (some platforms
|
||||
// refuse to re-select the active configuration).
|
||||
if (!dev.configuration) throw err
|
||||
}
|
||||
|
||||
// Find the first bulk-IN endpoint.
|
||||
const iface = dev.configuration?.interfaces[0]
|
||||
const alt = iface?.alternates[0]
|
||||
|
||||
Reference in New Issue
Block a user