New:Socket for Asana Is Now Available.Learn more
Sign In

@beacio/core

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@beacio/core - npm Package Compare versions

Comparing version
1.0.0
to
1.2.0
+2
dist/banner-DUJ4XQID.mjs
export{d as SETUP_STEPS,e as buildOnboardingUrl,g as removeInstallBanner,f as showInstallBanner}from'./chunk-5NAVIZD7.mjs';import'./chunk-TZAX4UTD.mjs';import'./chunk-L7SIDO2A.mjs';import'./chunk-3BDZNBBD.mjs';//# sourceMappingURL=banner-DUJ4XQID.mjs.map
//# sourceMappingURL=banner-DUJ4XQID.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"banner-DUJ4XQID.mjs"}
import { c as BeacioDevice, _ as WriteFragmentedOptions, $ as WriteFragmentedResult, a4 as WriteOptions, a2 as WriteLimits, J as NotificationCallback, N as NativeOverflowEvent } from './device-B5NsJWvh.js';
type AnyCharacteristicDefinition = CharacteristicDefinition<unknown, never>;
declare function parseRawBytes(value: BufferSource): DataView;
type UUIDLike = string;
type Capability = 'read' | 'write' | 'writeWithoutResponse' | 'notify';
type CharacteristicReadConfig<T> = {
capabilities: readonly ['read'] | readonly ['read', ...Capability[]];
parse: (dv: DataView) => T;
};
type CharacteristicWriteConfig<W> = {
capabilities: readonly ['write'] | readonly ['writeWithoutResponse'] | readonly ['write', ...Capability[]] | readonly ['writeWithoutResponse', ...Capability[]];
serialize: (value: W) => BufferSource;
};
type CharacteristicReadWriteConfig<T, W> = {
capabilities: readonly ['read', 'write'] | readonly ['read', 'writeWithoutResponse'] | readonly ['write', 'read'] | readonly ['writeWithoutResponse', 'read'] | readonly ['read', 'write', ...Capability[]] | readonly ['read', 'writeWithoutResponse', ...Capability[]] | readonly ['write', 'read', ...Capability[]] | readonly ['writeWithoutResponse', 'read', ...Capability[]];
parse: (dv: DataView) => T;
serialize: (value: W) => BufferSource;
};
type CharacteristicDefinition<TRead = never, TWrite = never> = {
uuid: UUIDLike;
} & (CharacteristicReadConfig<TRead> | CharacteristicWriteConfig<TWrite> | CharacteristicReadWriteConfig<TRead, TWrite>);
interface ProfileConfig<C extends Record<string, AnyCharacteristicDefinition>> {
name: string;
service: UUIDLike;
characteristics: C;
}
type CapabilityOf<T extends AnyCharacteristicDefinition> = T['capabilities'][number];
type ReadableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {
[K in keyof C]: 'read' extends CapabilityOf<C[K]> ? K : never;
}[keyof C] & string;
type WritableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {
[K in keyof C]: 'write' extends CapabilityOf<C[K]> ? K : 'writeWithoutResponse' extends CapabilityOf<C[K]> ? K : never;
}[keyof C] & string;
type NotifiableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {
[K in keyof C]: 'notify' extends CapabilityOf<C[K]> ? K : never;
}[keyof C] & string;
type ReadValue<T> = T extends {
parse: (dv: DataView) => infer TResult;
} ? TResult : never;
type WriteValue<T> = T extends {
serialize: (value: infer TValue) => BufferSource;
} ? TValue : never;
declare abstract class BaseProfile {
protected device: BeacioDevice;
protected abstract readonly service: string;
private cleanups;
constructor(device: BeacioDevice);
connect(): Promise<void>;
stop(): void;
dispose(): void;
protected read(characteristic: string): Promise<DataView>;
protected write(characteristic: string, value: BufferSource): Promise<void>;
protected writeWithoutResponse(characteristic: string, value: BufferSource): Promise<void>;
/**
* Send a payload of any size to `characteristic`, fragmenting it into
* MTU-sized chunks. This is a thin passthrough to {@link BeacioDevice.writeFragmented},
* which owns the (already-clamped) chunk-size derivation via the branded
* `ChunkSize` smart-constructors in the core write-chunker — so the stride is
* guaranteed `>= 1` and a zero-stride infinite loop is unrepresentable.
*
* Profiles MUST use this instead of hand-rolling a `for (offset += step)` /
* `subarray()` / `writeWithoutResponse()` chunk loop (enforced by the
* `no-restricted-syntax` guard scoped to `packages/profiles/src`). Defaults to
* `mode: 'without-response'` — the serial-pipe convention (Nordic UART, HM-10).
*
* @param characteristic - Target characteristic UUID or alias on this profile's service.
* @param value - Bytes to send. Accepts any {@link BufferSource}.
* @param options - Fragmentation/retry overrides; `mode` defaults to `'without-response'`.
* @returns The {@link WriteFragmentedResult} (bytes written, chunk size/count, retries).
*/
protected sendChunked(characteristic: string, value: BufferSource, options?: WriteFragmentedOptions): Promise<WriteFragmentedResult>;
protected writeValue(characteristic: string, value: BufferSource, options?: WriteOptions): Promise<void>;
protected getWriteLimits(): Promise<WriteLimits>;
protected getMtu(): Promise<number | null>;
protected subscribe(characteristic: string, callback: NotificationCallback): () => void;
/**
* Observe NATIVE notification-queue overflows for `characteristic` on this
* profile's service. The bounded Swift `EventQueue` evicts notifications under
* sustained high-frequency load and the polyfill surfaces each eviction as a
* `beacio:overflow` `CustomEvent` on the characteristic; this decodes that
* event's `detail` into a typed {@link NativeOverflowEvent} and forwards it to
* `callback`.
*
* Lifecycle parity with {@link subscribe}: the returned unsubscribe is also
* registered into the profile's cleanup set, so {@link stop}/{@link dispose}
* detach the listener too. A staleness `callback` should typically re-read the
* affected characteristic to resynchronise any UI tracking the last notified
* value rather than trusting that (now-stale) value.
*
* @param characteristic - Characteristic UUID or alias on this profile's service.
* @param callback - Called with the decoded eviction metadata on each overflow.
* @returns Unsubscribe function.
*/
protected onOverflow(characteristic: string, callback: (event: NativeOverflowEvent) => void): () => void;
}
type DefinedProfileInstance<C extends Record<string, AnyCharacteristicDefinition>> = BaseProfile & {
readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>>;
subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void;
writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void>;
getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability>;
getCharacteristicUUID<K extends keyof C & string>(name: K): string;
getServiceUUID(): string;
getWriteLimits(): Promise<WriteLimits>;
getMtu(): Promise<number | null>;
};
interface DefinedProfile<C extends Record<string, AnyCharacteristicDefinition>> {
new (device: BeacioDevice): DefinedProfileInstance<C>;
readonly profileName: string;
readonly serviceUUID: string;
readonly characteristics: {
[K in keyof C]: Omit<C[K], 'uuid'> & {
uuid: string;
};
};
}
declare function defineProfile<C extends Record<string, AnyCharacteristicDefinition>>(config: ProfileConfig<C>): DefinedProfile<C>;
export { BaseProfile as B, defineProfile as d, parseRawBytes as p };
import { c as BeacioDevice, _ as WriteFragmentedOptions, $ as WriteFragmentedResult, a4 as WriteOptions, a2 as WriteLimits, J as NotificationCallback, N as NativeOverflowEvent } from './device-B5NsJWvh.mjs';
type AnyCharacteristicDefinition = CharacteristicDefinition<unknown, never>;
declare function parseRawBytes(value: BufferSource): DataView;
type UUIDLike = string;
type Capability = 'read' | 'write' | 'writeWithoutResponse' | 'notify';
type CharacteristicReadConfig<T> = {
capabilities: readonly ['read'] | readonly ['read', ...Capability[]];
parse: (dv: DataView) => T;
};
type CharacteristicWriteConfig<W> = {
capabilities: readonly ['write'] | readonly ['writeWithoutResponse'] | readonly ['write', ...Capability[]] | readonly ['writeWithoutResponse', ...Capability[]];
serialize: (value: W) => BufferSource;
};
type CharacteristicReadWriteConfig<T, W> = {
capabilities: readonly ['read', 'write'] | readonly ['read', 'writeWithoutResponse'] | readonly ['write', 'read'] | readonly ['writeWithoutResponse', 'read'] | readonly ['read', 'write', ...Capability[]] | readonly ['read', 'writeWithoutResponse', ...Capability[]] | readonly ['write', 'read', ...Capability[]] | readonly ['writeWithoutResponse', 'read', ...Capability[]];
parse: (dv: DataView) => T;
serialize: (value: W) => BufferSource;
};
type CharacteristicDefinition<TRead = never, TWrite = never> = {
uuid: UUIDLike;
} & (CharacteristicReadConfig<TRead> | CharacteristicWriteConfig<TWrite> | CharacteristicReadWriteConfig<TRead, TWrite>);
interface ProfileConfig<C extends Record<string, AnyCharacteristicDefinition>> {
name: string;
service: UUIDLike;
characteristics: C;
}
type CapabilityOf<T extends AnyCharacteristicDefinition> = T['capabilities'][number];
type ReadableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {
[K in keyof C]: 'read' extends CapabilityOf<C[K]> ? K : never;
}[keyof C] & string;
type WritableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {
[K in keyof C]: 'write' extends CapabilityOf<C[K]> ? K : 'writeWithoutResponse' extends CapabilityOf<C[K]> ? K : never;
}[keyof C] & string;
type NotifiableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {
[K in keyof C]: 'notify' extends CapabilityOf<C[K]> ? K : never;
}[keyof C] & string;
type ReadValue<T> = T extends {
parse: (dv: DataView) => infer TResult;
} ? TResult : never;
type WriteValue<T> = T extends {
serialize: (value: infer TValue) => BufferSource;
} ? TValue : never;
declare abstract class BaseProfile {
protected device: BeacioDevice;
protected abstract readonly service: string;
private cleanups;
constructor(device: BeacioDevice);
connect(): Promise<void>;
stop(): void;
dispose(): void;
protected read(characteristic: string): Promise<DataView>;
protected write(characteristic: string, value: BufferSource): Promise<void>;
protected writeWithoutResponse(characteristic: string, value: BufferSource): Promise<void>;
/**
* Send a payload of any size to `characteristic`, fragmenting it into
* MTU-sized chunks. This is a thin passthrough to {@link BeacioDevice.writeFragmented},
* which owns the (already-clamped) chunk-size derivation via the branded
* `ChunkSize` smart-constructors in the core write-chunker — so the stride is
* guaranteed `>= 1` and a zero-stride infinite loop is unrepresentable.
*
* Profiles MUST use this instead of hand-rolling a `for (offset += step)` /
* `subarray()` / `writeWithoutResponse()` chunk loop (enforced by the
* `no-restricted-syntax` guard scoped to `packages/profiles/src`). Defaults to
* `mode: 'without-response'` — the serial-pipe convention (Nordic UART, HM-10).
*
* @param characteristic - Target characteristic UUID or alias on this profile's service.
* @param value - Bytes to send. Accepts any {@link BufferSource}.
* @param options - Fragmentation/retry overrides; `mode` defaults to `'without-response'`.
* @returns The {@link WriteFragmentedResult} (bytes written, chunk size/count, retries).
*/
protected sendChunked(characteristic: string, value: BufferSource, options?: WriteFragmentedOptions): Promise<WriteFragmentedResult>;
protected writeValue(characteristic: string, value: BufferSource, options?: WriteOptions): Promise<void>;
protected getWriteLimits(): Promise<WriteLimits>;
protected getMtu(): Promise<number | null>;
protected subscribe(characteristic: string, callback: NotificationCallback): () => void;
/**
* Observe NATIVE notification-queue overflows for `characteristic` on this
* profile's service. The bounded Swift `EventQueue` evicts notifications under
* sustained high-frequency load and the polyfill surfaces each eviction as a
* `beacio:overflow` `CustomEvent` on the characteristic; this decodes that
* event's `detail` into a typed {@link NativeOverflowEvent} and forwards it to
* `callback`.
*
* Lifecycle parity with {@link subscribe}: the returned unsubscribe is also
* registered into the profile's cleanup set, so {@link stop}/{@link dispose}
* detach the listener too. A staleness `callback` should typically re-read the
* affected characteristic to resynchronise any UI tracking the last notified
* value rather than trusting that (now-stale) value.
*
* @param characteristic - Characteristic UUID or alias on this profile's service.
* @param callback - Called with the decoded eviction metadata on each overflow.
* @returns Unsubscribe function.
*/
protected onOverflow(characteristic: string, callback: (event: NativeOverflowEvent) => void): () => void;
}
type DefinedProfileInstance<C extends Record<string, AnyCharacteristicDefinition>> = BaseProfile & {
readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>>;
subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void;
writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void>;
getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability>;
getCharacteristicUUID<K extends keyof C & string>(name: K): string;
getServiceUUID(): string;
getWriteLimits(): Promise<WriteLimits>;
getMtu(): Promise<number | null>;
};
interface DefinedProfile<C extends Record<string, AnyCharacteristicDefinition>> {
new (device: BeacioDevice): DefinedProfileInstance<C>;
readonly profileName: string;
readonly serviceUUID: string;
readonly characteristics: {
[K in keyof C]: Omit<C[K], 'uuid'> & {
uuid: string;
};
};
}
declare function defineProfile<C extends Record<string, AnyCharacteristicDefinition>>(config: ProfileConfig<C>): DefinedProfile<C>;
export { BaseProfile as B, defineProfile as d, parseRawBytes as p };
var beacioDetect=(function(exports){'use strict';var ft=Object.defineProperty;var g=(e,t)=>()=>(e&&(t=e(e=0)),t);var j=(e,t)=>{for(var r in t)ft(e,r,{get:t[r],enumerable:true});};var Ne={};j(Ne,{CDN_STUB_MARKER:()=>y,detectPlatform:()=>re,getBluetoothAPI:()=>z});function re(){if(typeof navigator>"u")return "unsupported";let e=navigator;return e.beacio?.__beacio===true?"safari-extension":e.bluetooth&&!e.bluetooth[y]?"native":"unsupported"}function z(){if(typeof navigator>"u")return null;let e=navigator;return e.beacio?.__beacio===true?e.beacio:e.bluetooth&&!e.bluetooth[y]?e.bluetooth:null}var y,G=g(()=>{y="__beacioCDNStub";});var l,R=g(()=>{l={STATE_CHANGE:"beacio:statechange",READY:"beacio:ready",INSTALLED_INACTIVE:"beacio:installedinactive",NOT_INSTALLED:"beacio:notinstalled",EXTENSION_READY:"beacio:extension:ready",EXTENSION_PING:"beacio:extension:ping",EXTENSION_PONG:"beacio:extension:pong",EXTENSION_ACTIVATE_REQUEST:"beacio:extension:activate-request",EXTENSION_ACTIVATE_RESULT:"beacio:extension:activate-result",EXTENSION_INSTALLED:"beacio:extension:installed",EXTENSION_STATUS_CHANGE:"beacio:extension:statuschange"};});function mt(){return typeof window<"u"&&window.__beacio?.status==="installed"}function _t(){if(typeof navigator>"u")return false;let e=navigator;return !!(e.beacio&&e.beacio.__beacio)}function ht(){return typeof document<"u"&&document.documentElement.dataset.beacioInstalled==="true"}function xt(){return typeof document<"u"&&document.documentElement.dataset.beacioExtension==="true"}function E(){return _t()||xt()?"active":mt()||ht()?"installed-inactive":"not-installed"}function H(){return E()==="active"}function Fe(e=3e3){let t=E();return t==="active"||typeof window>"u"?Promise.resolve(t):new Promise(r=>{let n=false,i=c=>{n||(n=true,window.removeEventListener(Ue,o),clearTimeout(a),r(c));},o=()=>i("active");window.addEventListener(Ue,o);let a=setTimeout(()=>i(E()),e);})}function V(){try{let e=localStorage.getItem(Pe);return e?Date.now()<parseInt(e,10):!1}catch{return false}}function B(e=oe){try{localStorage.setItem(Pe,String(Date.now()+e*864e5));}catch{}}function I(){B(ae);}function se(){let e=typeof window<"u"?window.location.href:"https://beacio.com",t=new URL(e),r=new URL(`https://${bt}/return`);return r.searchParams.set("url",t.toString()),r.toString()}function L(){if(typeof window>"u")return;let e=new URL(window.location.href),t=se();try{localStorage.setItem(Me,JSON.stringify({url:e.toString(),returnLink:t,timestamp:Date.now()})),navigator.storage?.persist?.();}catch{}try{navigator.clipboard?.writeText(t);}catch{}}function k(){let e=typeof window<"u"?window.location.href:"";try{let t=localStorage.getItem(Me);if(t){let r=JSON.parse(t),n=r.url||e;return {url:n,returnLink:r.returnLink||n}}}catch{}return {url:e,returnLink:e}}var Ue,Pe,Me,bt,w,oe,ae,U=g(()=>{R();Ue=l.EXTENSION_READY,Pe="beacio_dismiss_until",Me="beacio_return",bt="link.beacio.com",w="https://apps.apple.com/app/id6761301368";oe=14,ae=1;});var de={};j(de,{getExtensionInstallState:()=>W,isExtensionInstalled:()=>le,isIOSSafari:()=>ce});function ce(){if(typeof navigator>"u")return false;let e=navigator.userAgent,t=/iPad|iPhone|iPod/.test(e)||navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1,r=/^((?!chrome|android|crios|fxios).)*safari/i.test(e);return t&&r}async function W(){try{let{detectPlatform:e}=await Promise.resolve().then(()=>(G(),Ne));if(e()==="safari-extension")return "active"}catch{}return new Promise(e=>{let t=E();if(t!=="not-installed"){e(t);return}let r=0,n=setInterval(()=>{r++;let i=E();i!=="not-installed"&&(clearInterval(n),e(i)),r>20&&(clearInterval(n),e("not-installed"));},100);})}async function le(){return await W()!=="not-installed"}var K=g(()=>{U();});var q,ue=g(()=>{q="https://beacio.com/setup";});function ze(e){return !e||typeof e!="string"?"":e.split("-",1)[0].trim().toLowerCase()}function Et(){if(!(typeof navigator>"u"))return navigator.language}function Ge(e,t){if(t==null)return e;if(Array.isArray(e)||typeof e!="object"||e===null)return t;let r={...e};for(let n of Object.keys(t)){let i=t[n];i!==void 0&&(r[n]=Ge(e[n],i));}return r}function A(e={}){let t=ze(e.lang),r=t&&je[t]||je[ze(Et())]||b;return e.strings?Ge(r,e.strings):r}var b,pe,je,Y=g(()=>{b={buttonText:"Start Setup",dismiss:"Not now",dontShowAgain:"Don't show again",states:{"not-installed":{title:"Set Up Bluetooth in Safari",body:"Follow the steps below to enable Bluetooth and return to {operator}."},"installed-inactive":{title:"Enable beacio in Safari",body:"beacio is installed but the Safari extension is off. Open Settings \u2192 Apps \u2192 Safari \u2192 Extensions \u2192 beacio and turn on Allow Extension, then return here."},denied:{title:"Allow beacio on this site",body:"beacio is enabled but not yet allowed here. Tap the aA button in the address bar \u2192 Manage Extensions \u2192 beacio \u2192 Allow Every Website, then reload this page."},"private-browsing":{title:"Private Browsing blocks extensions",body:"Private Browsing disables Safari extensions, so beacio cannot run here \u2014 even if it is installed. Open this page in a normal tab to connect your device."}},steps:[{label:"Install beacio",why:"A free one-time companion app from the App Store."},{label:"Open the app once",why:"This registers the Safari extension with iOS."},{label:"Enable in Safari Settings",why:"Settings \u2192 Apps \u2192 Safari \u2192 Extensions \u2192 beacio \u2192 turn on Allow Extension."},{label:"Allow website access",why:"On the site, tap the aA button in the address bar \u2192 Manage Extensions \u2192 beacio \u2192 Allow Every Website."},{label:"Allow Bluetooth on first scan",why:"The first time you connect, Safari will ask to allow this site \u2014 tap Allow."},{label:"Return and reload",why:"Come back to this page, reload, and tap Connect."}],returnCta:"Return to {operator}",clipboardHint:"Link also copied \u2014 paste it into Safari if this button does not reopen {operator}.",reload:"Reload page to re-check",howSummary:"How does setup work?",howBody:"beacio uses a one-time iPhone app to enable the Safari extension. After enabling it and allowing access on this site (aA button \u2192 Manage Extensions \u2192 Allow Every Website), Bluetooth works in Safari.",howLink:"See the full setup guide",privacySummary:"Privacy: No data collected",privacyBody:"beacio processes all Bluetooth data locally on your device. No browsing data, device data, or personal information is ever collected or transmitted.",stillStuck:"Still stuck? Open the setup guide",barTitle:"Enable Bluetooth",barText:"Install Beacio, open the app, enable the Safari extension, then return here.",readyToast:"beacio is ready \u2014 tap Connect to pair your device with {operator}.",error:{dismiss:"Dismiss",retry:"Try again",titles:{INVALID_PARAMETER:"Something went wrong",BLUETOOTH_UNAVAILABLE:"Bluetooth is unavailable",EXTENSION_NOT_INSTALLED:"Finish Bluetooth setup",PERMISSION_DENIED:"Allow Bluetooth to continue",DEVICE_NOT_FOUND:"No device found",DEVICE_DISCONNECTED:"Device disconnected",CONNECTION_TIMEOUT:"Connection timed out",SERVICE_NOT_FOUND:"Device feature unavailable",CHARACTERISTIC_NOT_FOUND:"Device feature unavailable",CHARACTERISTIC_NOT_READABLE:"Cannot read from device",CHARACTERISTIC_NOT_WRITABLE:"Cannot send to device",CHARACTERISTIC_NOT_NOTIFIABLE:"Live updates unavailable",GATT_OPERATION_FAILED:"Connection interrupted",SCAN_ALREADY_IN_PROGRESS:"Already searching",CONNECTION_LIMIT_REACHED:"Too many devices connected",USER_CANCELLED:"Connection cancelled",TIMEOUT:"Operation timed out",WRITE_INCOMPLETE:"Send incomplete"},messages:{INVALID_PARAMETER:"The request could not be completed. Please reload the page and try again.",BLUETOOTH_UNAVAILABLE:"Turn Bluetooth on, then try again.",EXTENSION_NOT_INSTALLED:"Bluetooth is not enabled for this site yet. Finish setup, then try connecting again.",PERMISSION_DENIED:"Bluetooth access was not granted. Tap Connect yourself (Bluetooth needs a tap), then allow access when asked.",DEVICE_NOT_FOUND:"No matching device was found. Switch your device on, keep it close, then try again.",DEVICE_DISCONNECTED:"The connection to your device was lost. Reconnect to continue.",CONNECTION_TIMEOUT:"Your device did not respond in time. Keep it close and powered on, then try again.",SERVICE_NOT_FOUND:"A required feature was not found on this device. Check that it is the right device and try again.",CHARACTERISTIC_NOT_FOUND:"A required feature was not found on this device. Check that it is the right device and try again.",CHARACTERISTIC_NOT_READABLE:"This value cannot be read from your device. No action is needed for this control.",CHARACTERISTIC_NOT_WRITABLE:"This value cannot be sent to your device. No action is needed for this control.",CHARACTERISTIC_NOT_NOTIFIABLE:"This value does not support live updates on your device.",GATT_OPERATION_FAILED:"Something interrupted the connection. Switch your device off and on, then try again.",SCAN_ALREADY_IN_PROGRESS:"A device search is already running. Wait a moment, then try again.",CONNECTION_LIMIT_REACHED:"Disconnect another device before connecting a new one.",USER_CANCELLED:"No device was selected. Tap Connect to try again whenever you are ready.",TIMEOUT:"That took too long. Check your device is close and powered on, then try again.",WRITE_INCOMPLETE:"Only part of the data reached your device. Try again to resend it."},generic:{title:"Something went wrong",body:"Something interrupted the connection. Please try again."}}},pe={buttonText:"Einrichtung starten",dismiss:"Jetzt nicht",dontShowAgain:"Nicht mehr anzeigen",states:{"not-installed":{title:"Bluetooth in Safari einrichten",body:"Folge den Schritten unten, um Bluetooth zu aktivieren und zu {operator} zur\xFCckzukehren."},"installed-inactive":{title:"beacio in Safari aktivieren",body:"beacio ist installiert, aber die Safari-Erweiterung ist deaktiviert. \xD6ffne Einstellungen \u2192 Apps \u2192 Safari \u2192 Erweiterungen \u2192 beacio und aktiviere \u201EErweiterung erlauben\u201C, kehre dann hierher zur\xFCck."},denied:{title:"beacio f\xFCr diese Seite erlauben",body:"beacio ist aktiviert, aber f\xFCr diese Seite noch nicht erlaubt. Tippe auf die Schaltfl\xE4che \u201EaA\u201C in der Adressleiste \u2192 Erweiterungen verwalten \u2192 beacio \u2192 \u201EAuf allen Websites erlauben\u201C und lade diese Seite dann neu."},"private-browsing":{title:"Privates Surfen blockiert Erweiterungen",body:"Im privaten Surfmodus sind Safari-Erweiterungen deaktiviert, daher kann beacio hier nicht laufen \u2014 auch wenn es installiert ist. \xD6ffne diese Seite in einem normalen Tab, um dein Ger\xE4t zu verbinden."}},steps:[{label:"beacio installieren",why:"Eine kostenlose, einmalige Begleit-App aus dem App Store."},{label:"App einmal \xF6ffnen",why:"Damit wird die Safari-Erweiterung bei iOS registriert."},{label:"In den Safari-Einstellungen aktivieren",why:"Einstellungen \u2192 Apps \u2192 Safari \u2192 Erweiterungen \u2192 beacio \u2192 \u201EErweiterung erlauben\u201C aktivieren."},{label:"Website-Zugriff erlauben",why:"Tippe auf der Seite auf die Schaltfl\xE4che \u201EaA\u201C in der Adressleiste \u2192 Erweiterungen verwalten \u2192 beacio \u2192 \u201EAuf allen Websites erlauben\u201C."},{label:"Bluetooth beim ersten Scan erlauben",why:"Beim ersten Verbinden fragt Safari, ob diese Seite zugreifen darf \u2014 tippe auf \u201EErlauben\u201C."},{label:"Zur\xFCckkehren und neu laden",why:"Komm zu dieser Seite zur\xFCck, lade sie neu und tippe auf \u201EVerbinden\u201C."}],returnCta:"Zur\xFCck zu {operator}",clipboardHint:"Link wurde au\xDFerdem kopiert \u2014 f\xFCge ihn in Safari ein, falls diese Schaltfl\xE4che {operator} nicht erneut \xF6ffnet.",reload:"Seite neu laden und erneut pr\xFCfen",howSummary:"Wie funktioniert die Einrichtung?",howBody:"beacio nutzt eine einmalige iPhone-App, um die Safari-Erweiterung zu aktivieren. Sobald sie aktiviert und der Zugriff auf dieser Seite erlaubt ist (Schaltfl\xE4che \u201EaA\u201C \u2192 Erweiterungen verwalten \u2192 \u201EAuf allen Websites erlauben\u201C), funktioniert Bluetooth in Safari.",howLink:"Zur vollst\xE4ndigen Einrichtungsanleitung",privacySummary:"Datenschutz: Keine Datenerfassung",privacyBody:"beacio verarbeitet alle Bluetooth-Daten lokal auf deinem Ger\xE4t. Es werden niemals Browserdaten, Ger\xE4tedaten oder pers\xF6nliche Informationen erfasst oder \xFCbertragen.",stillStuck:"Kommst du nicht weiter? Einrichtungsanleitung \xF6ffnen",barTitle:"Bluetooth aktivieren",barText:"Installiere beacio, \xF6ffne die App, aktiviere die Safari-Erweiterung und kehre dann hierher zur\xFCck.",readyToast:"beacio ist bereit \u2014 tippe auf \u201EVerbinden\u201C, um dein Ger\xE4t mit {operator} zu koppeln.",error:{dismiss:"Schlie\xDFen",retry:"Erneut versuchen",titles:{INVALID_PARAMETER:"Etwas ist schiefgelaufen",BLUETOOTH_UNAVAILABLE:"Bluetooth ist nicht verf\xFCgbar",EXTENSION_NOT_INSTALLED:"Bluetooth-Einrichtung abschlie\xDFen",PERMISSION_DENIED:"Bluetooth erlauben, um fortzufahren",DEVICE_NOT_FOUND:"Kein Ger\xE4t gefunden",DEVICE_DISCONNECTED:"Ger\xE4t getrennt",CONNECTION_TIMEOUT:"Zeit\xFCberschreitung der Verbindung",SERVICE_NOT_FOUND:"Ger\xE4tefunktion nicht verf\xFCgbar",CHARACTERISTIC_NOT_FOUND:"Ger\xE4tefunktion nicht verf\xFCgbar",CHARACTERISTIC_NOT_READABLE:"Lesen vom Ger\xE4t nicht m\xF6glich",CHARACTERISTIC_NOT_WRITABLE:"Senden an das Ger\xE4t nicht m\xF6glich",CHARACTERISTIC_NOT_NOTIFIABLE:"Live-Aktualisierungen nicht verf\xFCgbar",GATT_OPERATION_FAILED:"Verbindung unterbrochen",SCAN_ALREADY_IN_PROGRESS:"Suche l\xE4uft bereits",CONNECTION_LIMIT_REACHED:"Zu viele Ger\xE4te verbunden",USER_CANCELLED:"Verbindung abgebrochen",TIMEOUT:"Zeit\xFCberschreitung des Vorgangs",WRITE_INCOMPLETE:"Senden unvollst\xE4ndig"},messages:{INVALID_PARAMETER:"Die Anfrage konnte nicht abgeschlossen werden. Lade die Seite neu und versuche es erneut.",BLUETOOTH_UNAVAILABLE:"Schalte Bluetooth ein und versuche es erneut.",EXTENSION_NOT_INSTALLED:"Bluetooth ist f\xFCr diese Seite noch nicht aktiviert. Schlie\xDFe die Einrichtung ab und versuche dann erneut, dich zu verbinden.",PERMISSION_DENIED:"Der Bluetooth-Zugriff wurde nicht gew\xE4hrt. Tippe selbst auf \u201EVerbinden\u201C (Bluetooth erfordert eine Ber\xFChrung) und erlaube den Zugriff, wenn du gefragt wirst.",DEVICE_NOT_FOUND:"Es wurde kein passendes Ger\xE4t gefunden. Schalte dein Ger\xE4t ein, halte es in der N\xE4he und versuche es erneut.",DEVICE_DISCONNECTED:"Die Verbindung zu deinem Ger\xE4t wurde unterbrochen. Verbinde dich erneut, um fortzufahren.",CONNECTION_TIMEOUT:"Dein Ger\xE4t hat nicht rechtzeitig geantwortet. Halte es in der N\xE4he und eingeschaltet und versuche es erneut.",SERVICE_NOT_FOUND:"Eine erforderliche Funktion wurde auf diesem Ger\xE4t nicht gefunden. Pr\xFCfe, ob es das richtige Ger\xE4t ist, und versuche es erneut.",CHARACTERISTIC_NOT_FOUND:"Eine erforderliche Funktion wurde auf diesem Ger\xE4t nicht gefunden. Pr\xFCfe, ob es das richtige Ger\xE4t ist, und versuche es erneut.",CHARACTERISTIC_NOT_READABLE:"Dieser Wert kann nicht von deinem Ger\xE4t gelesen werden. F\xFCr dieses Element ist keine Aktion erforderlich.",CHARACTERISTIC_NOT_WRITABLE:"Dieser Wert kann nicht an dein Ger\xE4t gesendet werden. F\xFCr dieses Element ist keine Aktion erforderlich.",CHARACTERISTIC_NOT_NOTIFIABLE:"Dieser Wert unterst\xFCtzt auf deinem Ger\xE4t keine Live-Aktualisierungen.",GATT_OPERATION_FAILED:"Etwas hat die Verbindung unterbrochen. Schalte dein Ger\xE4t aus und wieder ein und versuche es erneut.",SCAN_ALREADY_IN_PROGRESS:"Es l\xE4uft bereits eine Ger\xE4tesuche. Warte einen Moment und versuche es erneut.",CONNECTION_LIMIT_REACHED:"Trenne ein anderes Ger\xE4t, bevor du ein neues verbindest.",USER_CANCELLED:"Es wurde kein Ger\xE4t ausgew\xE4hlt. Tippe auf \u201EVerbinden\u201C, um es erneut zu versuchen, wann immer du bereit bist.",TIMEOUT:"Das hat zu lange gedauert. Pr\xFCfe, ob dein Ger\xE4t in der N\xE4he und eingeschaltet ist, und versuche es erneut.",WRITE_INCOMPLETE:"Nur ein Teil der Daten hat dein Ger\xE4t erreicht. Versuche es erneut, um sie noch einmal zu senden."},generic:{title:"Etwas ist schiefgelaufen",body:"Etwas hat die Verbindung unterbrochen. Bitte versuche es erneut."}}},je={en:b,de:pe};});var ge={};j(ge,{SETUP_STEPS:()=>fe,buildOnboardingUrl:()=>Ke,removeInstallBanner:()=>J,showInstallBanner:()=>Z});function We(e){return e.startOnboardingUrl??e.appStoreUrl??vt}function Ke(e,t={}){let r=typeof window<"u"?window.location.href:void 0,n=new URL(e,r);if(n.hostname==="apps.apple.com"){let o=n.pathname.match(/id\d+/)?.[0];return n.pathname=o?`/app/${o}`:new URL(w).pathname,t.apiKey&&!n.searchParams.has("ct")&&(n.searchParams.set("ct",t.apiKey),n.searchParams.set("mt","8")),n.toString()}return t.operatorName&&!n.searchParams.has("operatorName")&&n.searchParams.set("operatorName",t.operatorName),t.returnUrl&&!n.searchParams.has("return")&&n.searchParams.set("return",t.returnUrl),n.toString()}function qe(e,t,r){L();let n=k().url;window.location.href=Ke(e,{apiKey:t,operatorName:r,returnUrl:n});}function s(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function X(e,t,r=""){return e.replace(/\{operator\}/g,t).replace(/\{device\}/g,r)}function Ye(e){if(!e||typeof e!="string")return null;let t=typeof window<"u"?window.location.href:"https://beacio.com";try{let r=new URL(e,t);return r.protocol==="http:"||r.protocol==="https:"?r.href:null}catch{return null}}function Tt(e){let{operatorName:t=document.title||window.location.hostname,apiKey:r,dismissDays:n=14,state:i="not-installed"}=e,o=We(e),a=A({lang:e.lang,strings:e.strings}),c=a.buttonText,d=i==="active"?"not-installed":i,{title:_}=a.states[d],v=e.body??a.states[d].body,p=e.setupUrl??o,f=k(),D=e.accentColor??"#007aff",x=Ye(e.brandLogoUrl),O=e.deviceName??"",te=!!(e.accentColor||x||e.deviceName),Ae=e.privacyBody??a.privacyBody,pt=(d==="not-installed"?a.steps:At[d].map(h=>a.steps[h])).map(h=>`<li class="bc-step"><span class="bc-step-l">${s(h.label)}</span><span class="bc-step-w">${s(h.why)}</span></li>`).join(""),u=document.createElement("div");u.id="beacio-banner",u.dataset.beacioState=d,u.innerHTML=`
<style>
/* SB-SDK-11: the partner accent is exposed as a single CSS custom property on the
sheet root; every accent rule below reads var(--bc-accent). When unthemed the
value defaults to the beacio Apple-blue, so the rendered sheet is unchanged. */
#bc-s{--bc-accent:${s(D)}}
#beacio-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-end;
justify-content:center;background:rgba(0,0,0,.4);font-family:-apple-system,BlinkMacSystemFont,
'SF Pro Text',system-ui,sans-serif;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
animation:bc-fi .25s ease-out}
@keyframes bc-fi{from{opacity:0}to{opacity:1}}
@keyframes bc-su{from{transform:translateY(100%)}to{transform:translateY(0)}}
#bc-s{background:#fff;border-radius:16px 16px 0 0;padding:12px 20px 28px;max-width:420px;
width:100%;animation:bc-su .3s ease-out;max-height:90vh;overflow-y:auto;
-webkit-overflow-scrolling:touch}
#bc-s *{box-sizing:border-box;margin:0;padding:0}
.bc-h{width:36px;height:5px;border-radius:3px;background:#d1d1d6;margin:0 auto 10px}
.bc-hdr{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.bc-ic{width:36px;height:36px;border-radius:9px;background:var(--bc-accent);display:flex;
align-items:center;justify-content:center;flex-shrink:0;overflow:hidden}
.bc-ic svg{width:20px;height:20px;fill:#fff}
.bc-ic img{width:100%;height:100%;object-fit:contain}
.bc-tt{font-size:16px;font-weight:600;color:#000}
.bc-bd{font-size:13px;line-height:1.35;color:#8e8e93;margin-bottom:12px}
.bc-steps{list-style:none;margin:0 0 14px;padding:0;counter-reset:bc-step}
.bc-step{position:relative;padding:0 0 8px 28px;font-size:13px;line-height:1.35}
.bc-step::before{counter-increment:bc-step;content:counter(bc-step);position:absolute;left:0;top:0;
width:18px;height:18px;border-radius:50%;background:var(--bc-accent);color:#fff;font-size:11px;
font-weight:600;display:flex;align-items:center;justify-content:center}
.bc-step-l{display:block;font-weight:600;color:#1c1c1e}
.bc-step-w{display:block;color:#8e8e93;margin-top:1px;font-size:12px}
.bc-btn{display:block;width:100%;padding:12px;background:var(--bc-accent);color:#fff;border:none;
border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;text-align:center;
text-decoration:none;-webkit-tap-highlight-color:transparent}
.bc-btn:active{opacity:.85}
.bc-ret{display:block;width:100%;padding:12px;margin-top:8px;background:#34c759;color:#fff;border:none;
border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;text-align:center;
text-decoration:none;-webkit-tap-highlight-color:transparent}
.bc-ret:active{opacity:.85}
.bc-cb{font-size:11px;color:#8e8e93;text-align:center;margin-top:6px}
/* SB-SDK-11 AC3: VISIBLE trust surfaces (not the collapsed <details>) \u2014 the
medical-market "No data collected" reassurance + the no-affiliation microcopy. */
.bc-privacy{font-size:12px;color:#8e8e93;line-height:1.4;margin-top:12px}
.bc-noaff{font-size:11px;color:#8e8e93;line-height:1.3;margin-top:6px;text-align:center}
.bc-det{margin-top:10px}
.bc-det summary{font-size:13px;color:var(--bc-accent);cursor:pointer;list-style:none;padding:2px 0}
.bc-det summary::before{content:'\\25B8 '}
.bc-det[open] summary::before{content:'\\25BE '}
.bc-det p{font-size:12px;color:#8e8e93;line-height:1.4;padding:6px 0 2px}
.bc-det a{color:var(--bc-accent)}
.bc-stuck{display:block;font-size:12px;color:var(--bc-accent);text-align:center;margin-top:10px;
text-decoration:none}
.bc-reload{display:block;width:100%;padding:11px;margin-top:8px;background:none;
border:1px solid var(--bc-accent);border-radius:12px;font-size:15px;font-weight:600;color:var(--bc-accent);
cursor:pointer;text-align:center;-webkit-tap-highlight-color:transparent}
.bc-reload:active{opacity:.7}
.bc-dis{display:block;width:100%;padding:8px;background:none;border:none;font-size:14px;
color:#8e8e93;cursor:pointer;text-align:center;margin-top:4px;
-webkit-tap-highlight-color:transparent}
/* SB-PRD-08: the explicit long opt-out (#bc-dont-show) is visually quieter than the
soft dismiss (#bc-dismiss) above it \u2014 smaller, less padding \u2014 so the soft dismiss
stays the default gesture and the long opt-out is a deliberate secondary choice.
NB keep this comment free of literal UI copy: the <style> block is part of the
banner innerHTML, so any English token here would leak into the localized DOM
(i18n.test.ts no-English-leak guard). */
.bc-dont{font-size:12px;padding:4px 12px;margin-top:0}
@media(prefers-color-scheme:dark){
#bc-s{background:#1c1c1e}
.bc-tt,.bc-step-l{color:#fff}
.bc-bd,.bc-step-w,.bc-cb,.bc-det p,.bc-privacy,.bc-noaff{color:#98989f}
.bc-dis{color:#98989f}
.bc-reload{color:#0a84ff;border-color:#0a84ff}
.bc-h{background:#48484a}
}
</style>
<div id="beacio-overlay">
<div id="bc-s" role="dialog" aria-label="${s(_)}">
<div class="bc-h"></div>
<div class="bc-hdr">
<div class="bc-ic">${x?`<img src="${s(x)}" alt="" aria-hidden="true">`:'<svg viewBox="0 0 24 24"><path d="M12 2L7 7l5 5-5 5 5 5V2zm0 6.83L10.83 7 12 5.83v2.34zm0 8.34L10.83 17 12 15.83v1.34zM17 7l-5 5 5 5-2.12 2.12L12 17l-2.88 2.12L7 17l5-5-5-5 2.12-2.12L12 7l2.88-2.12L17 7z"/></svg>'}</div>
<div class="bc-tt">${s(_)}</div>
</div>
<div class="bc-bd">${s(X(v,t,O))}</div>
<ol class="bc-steps">${pt}</ol>
${d==="not-installed"?`<button class="bc-btn" id="bc-install">${s(c)}</button>`:""}
<a class="bc-ret" id="bc-return" href="${s(f.returnLink)}">${s(X(a.returnCta,t))}</a>
<p class="bc-cb">${s(X(a.clipboardHint,t))}</p>
<button class="bc-reload" id="bc-reload">${s(a.reload)}</button>
<details class="bc-det"><summary>${s(a.howSummary)}</summary><p>${s(a.howBody)} <a href="${s(p)}" target="_blank" rel="noopener">${s(a.howLink)}</a>.</p></details>
<details class="bc-det"><summary>${s(a.privacySummary)}</summary><p>${s(Ae)}</p></details>
${te?`<p class="bc-privacy" id="bc-privacy">${s(a.privacySummary)} \u2014 ${s(Ae)}</p><p class="bc-noaff" id="bc-noaff">beacio is an independent Safari extension and is not affiliated with the device maker.</p>`:""}
<a class="bc-stuck" id="bc-stuck" href="${s(p)}" target="_blank" rel="noopener">${s(a.stillStuck)}</a>
<button class="bc-dis" id="bc-dismiss">${s(a.dismiss)}</button>
<button class="bc-dis bc-dont" id="bc-dont-show">${s(a.dontShowAgain)}</button>
</div>
</div>`,L();let ve=e.forceShow===true,S=null,ne=false;function F(){ne=true,S!==null&&(clearTimeout(S),S=null),window.removeEventListener($e,ye),window.removeEventListener(He,Se),document.removeEventListener("visibilitychange",we);}function T(){return H()?(F(),u.remove(),true):false}function ye(){T();}function Se(){T()||Te();}function Te(){if(ne||S!==null)return;let h=0,Ie=()=>{S=null,!ne&&(T()||(h+=1,!(h>=yt)&&(S=setTimeout(Ie,St))));};Ie();}function we(){document.visibilityState==="visible"&&(T()||Te());}return ve||(window.addEventListener($e,ye),window.addEventListener(He,Se),document.addEventListener("visibilitychange",we)),requestAnimationFrame(()=>{u.querySelector("#bc-install")?.addEventListener("click",()=>{qe(o,r,t);}),u.querySelector("#bc-reload")?.addEventListener("click",()=>{T()||window.location.reload();}),u.querySelector("#bc-dismiss")?.addEventListener("click",()=>{F(),u.remove(),I();}),u.querySelector("#bc-dont-show")?.addEventListener("click",()=>{F(),u.remove(),B(n);}),u.querySelector("#beacio-overlay")?.addEventListener("click",h=>{h.target.id==="beacio-overlay"&&(F(),u.remove(),I());});}),document.body.appendChild(u),ve||T(),u}function wt(e){let{position:t="bottom",style:r={},apiKey:n,operatorName:i}=e,o=A({lang:e.lang,strings:e.strings}),a=o.barText,c=o.buttonText,d=We(e),_=e.accentColor??"#007AFF",v=Ye(e.brandLogoUrl),p=document.createElement("div");p.id="beacio-banner";let f=t==="top"?"top:0;border-bottom:1px solid #e5e7eb;":"bottom:0;border-top:1px solid #e5e7eb;",D=Object.entries(r).map(([x,O])=>`${x}:${O}`).join(";");return p.innerHTML=`
<div style="position:fixed;${f}left:0;right:0;z-index:2147483646;
background:#fff;padding:16px;
display:flex;align-items:center;gap:12px;font-family:system-ui,-apple-system,sans-serif;
box-shadow:0 ${t==="top"?"2px":"-2px"} 10px rgba(0,0,0,0.1);${D}">
${v?`<img src="${s(v)}" alt="" aria-hidden="true" width="24" height="24" style="object-fit:contain;flex-shrink:0">`:`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="${s(_)}"/>
<path d="M12 7a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V8a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" fill="white"/>
</svg>`}
<div style="flex:1">
<div style="font-size:14px;font-weight:600;color:#1f2937">${s(o.barTitle)}</div>
<div style="font-size:12px;color:#6b7280;margin-top:2px">${s(a)}</div>
</div>
<button id="beacio-banner-install"
style="background:${s(_)};color:white;padding:8px 16px;border-radius:8px;
border:none;font-size:14px;font-weight:500;white-space:nowrap;cursor:pointer">
${s(c)}</button>
<button id="beacio-banner-close"
style="background:none;border:none;color:#9ca3af;font-size:20px;
cursor:pointer;padding:4px;line-height:1"
aria-label="Close">&times;</button>
</div>`,p.querySelector("#beacio-banner-install")?.addEventListener("click",()=>{qe(d,n,i);}),p.querySelector("#beacio-banner-close")?.addEventListener("click",()=>{p.remove(),I();}),document.body.appendChild(p),p}function It(){try{return localStorage.getItem(Ve)==="1"}catch{return false}}function Nt(){try{localStorage.setItem(Ve,"1");}catch{}}function Ct(e){if(It())return null;Nt();let t=e.operatorName||document.title||window.location.hostname,r=A({lang:e.lang,strings:e.strings}),n=document.createElement("div");return n.id="beacio-banner",n.dataset.beacioState="active",n.innerHTML=`
<style>
#bc-toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483647;
max-width:420px;width:calc(100% - 32px);background:#34c759;color:#fff;border-radius:14px;
padding:14px 16px;display:flex;align-items:center;gap:12px;
font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;
box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bc-tu .3s ease-out}
@keyframes bc-tu{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
#bc-toast svg{width:22px;height:22px;flex-shrink:0;fill:#fff}
.bc-toast-tx{flex:1;font-size:15px;font-weight:600;line-height:1.3}
#bc-toast-x{background:none;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0 4px;
line-height:1;-webkit-tap-highlight-color:transparent}
</style>
<div id="bc-toast" role="status">
<svg viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
<span class="bc-toast-tx">${s(X(r.readyToast,t))}</span>
<button id="bc-toast-x" aria-label="Dismiss">&times;</button>
</div>`,requestAnimationFrame(()=>{n.querySelector("#bc-toast-x")?.addEventListener("click",()=>n.remove());}),document.body.appendChild(n),n}function Z(e={}){return e.state==="active"?Ct(e):!e.forceShow&&V()?null:e.mode==="banner"?wt(e):Tt(e)}function J(){let e=document.getElementById("beacio-banner");e&&e.remove();}var fe,At,vt,Ve,$e,He,yt,St,Q=g(()=>{ue();R();Y();U();fe=b.steps,At={"installed-inactive":[2,4,5],denied:[3,4,5],"private-browsing":[]},vt=q,Ve="beacio_ready_shown",$e=l.READY,He=l.EXTENSION_READY,yt=5,St=300;});function Rt(e){let t=e.split(`
`,1)[0]??"";return t=t.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),t=t.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),t=t.replace(Ot,""),t=t.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),t=t.replace(/[\s.,;:]+$/g,"").trim(),t}function M(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function Lt(e){return typeof e=="object"&&e!==null&&"code"in e&&typeof e.code=="string"&&e.code in Dt}function kt(e,t){let r=t.toLowerCase();switch(e){case "NotFoundError":return "DEVICE_NOT_FOUND";case "NotAllowedError":case "SecurityError":return "PERMISSION_DENIED";case "NetworkError":return "DEVICE_DISCONNECTED";case "TimeoutError":return "TIMEOUT";case "InvalidStateError":return r.includes("disconnect")?"DEVICE_DISCONNECTED":"GATT_OPERATION_FAILED";}return r.includes("user cancelled")||r.includes("user canceled")?"USER_CANCELLED":r.includes("disconnect")?"DEVICE_DISCONNECTED":r.includes("timeout")?"TIMEOUT":"GATT_OPERATION_FAILED"}function Ut(e,t,r){let n=o=>t.titles[o],i=o=>r?.messages?.[o]??t.messages[o];if(typeof e=="string"){let a=Rt(e)||t.generic.body;return {code:null,title:t.generic.title,body:a,isRetriable:false,signature:`str:${a}`}}if(Lt(e)){let o=e.code;return {code:o,title:n(o),body:i(o),isRetriable:typeof e.isRetriable=="boolean"?e.isRetriable:Xe.has(o),signature:`code:${o}`}}if(typeof e=="object"&&e!==null){let o="name"in e&&typeof e.name=="string"?e.name:"",a=e instanceof Error?e.message:String(e.message??""),c=kt(o,a);return {code:c,title:n(c),body:i(c),isRetriable:Xe.has(c),signature:`dom:${c}`}}return {code:null,title:t.generic.title,body:t.generic.body,isRetriable:false,signature:"generic"}}function me(e,t={}){if(typeof document>"u")return null;let{strings:r}=t,n=A({lang:t.lang}).error,i=Ut(e,n,r),o=Date.now(),a=document.getElementById(P);if(a&&Ze===i.signature&&o-be<Bt)return null;a&&a.remove(),Ze=i.signature,be=o;let c=t.operatorName,d=t.dismissText??r?.dismiss??n.dismiss,_=t.retryText??r?.retry??n.retry,v=i.isRetriable,p=Object.entries(t.style??{}).map(([O,te])=>`${O}:${te}`).join(";"),f=document.createElement("div");f.id=P,f.dataset.beacioErrorCode=i.code??"unknown",p&&(f.style.cssText=p);let D=c?`${c} \u2014 ${i.title}`:i.title;f.innerHTML=`
<style>
#${P}{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483646;
max-width:420px;width:calc(100% - 32px);background:#fff;color:#1c1c1e;border-radius:14px;
padding:16px 18px;display:flex;flex-direction:column;gap:10px;
font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;
box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bce-u .3s ease-out}
@keyframes bce-u{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
#${P} *{box-sizing:border-box;margin:0;padding:0}
.bce-row{display:flex;align-items:flex-start;gap:12px}
.bce-ic{width:28px;height:28px;border-radius:8px;background:#ff3b30;flex-shrink:0;display:flex;
align-items:center;justify-content:center}
.bce-ic svg{width:18px;height:18px;fill:#fff}
.bce-tx{flex:1;min-width:0}
.bce-tt{font-size:15px;font-weight:600;line-height:1.3}
.bce-bd{font-size:14px;line-height:1.4;color:#3a3a3c;margin-top:3px}
.bce-x{background:none;border:none;color:#8e8e93;font-size:20px;cursor:pointer;line-height:1;
padding:0 2px;align-self:flex-start}
/* SB-SDK-07: visually-hidden text label on the icon-only dismiss control. The
glyph stays the only visible mark; the label surfaces in the accessibility
tree + DOM text so the LOCALIZED dismiss copy is present (German when lang
selects it), not just an aria-label attribute. */
.bce-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
clip:rect(0,0,0,0);white-space:nowrap;border:0}
.bce-act{display:flex;gap:8px;justify-content:flex-end}
.bce-retry{padding:9px 16px;background:#007aff;color:#fff;border:none;border-radius:10px;
font-size:15px;font-weight:600;cursor:pointer}
.bce-retry:active{opacity:.85}
@media(prefers-color-scheme:dark){
#${P}{background:#1c1c1e;color:#fff}
.bce-bd{color:#aeaeb2}
}
</style>
<div class="bce-row">
<div class="bce-ic"><svg viewBox="0 0 24 24"><path d="M12 2 1 21h22L12 2zm0 5 7.5 13h-15L12 7zm-1 4v4h2v-4h-2zm0 6v2h2v-2h-2z"/></svg></div>
<div class="bce-tx">
<p class="bce-tt">${M(D)}</p>
<p class="bce-bd">${M(i.body)}</p>
</div>
<button class="bce-x" aria-label="${M(d)}">&times;<span class="bce-sr">${M(d)}</span></button>
</div>
${v?`<div class="bce-act"><button class="bce-retry" type="button">${M(_)}</button></div>`:""}`;function x(){f.remove(),be=0;}return f.querySelector(".bce-x")?.addEventListener("click",x),v&&f.querySelector(".bce-retry")?.addEventListener("click",()=>{x(),t.onRetry?.();}),document.body.appendChild(f),f}var Xe,Dt,Ot,P,Bt,Ze,be,Je=g(()=>{Y();Xe=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),Dt=Object.fromEntries(Object.keys(b.error.titles).map(e=>[e,{title:b.error.titles[e],body:b.error.messages[e]}])),Ot=/\b(bluefy|web ble browser|webble browser)\b/gi;P="beacio-error",Bt=1500,Ze=null,be=0;});function Pt(){return {origin:location.hostname,ua:navigator.userAgent}}function m(e,t,r){if(e)try{fetch(`${Qe}/v1/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({events:[{event:t,data:Pt(),timestamp:Date.now()}]}),keepalive:!0}).catch(()=>{});}catch{}}async function et(e){try{let t=await fetch(`${Qe}/v1/config`,{headers:{Authorization:`Bearer ${e}`}});return t.ok?await t.json():null}catch{return null}}var Qe,_e=g(()=>{Qe="https://api.beacio.com";});var nt={};j(nt,{APP_STORE_URL:()=>w,DEFAULT_DISMISS_DAYS:()=>oe,DE_STRINGS:()=>pe,EN_STRINGS:()=>b,SETUP_STEPS:()=>fe,SHORT_DISMISS_DAYS:()=>ae,dismiss:()=>B,dismissShort:()=>I,getExtensionInstallState:()=>W,getInstallState:()=>E,getReturnContext:()=>k,initBeacio:()=>he,isDismissed:()=>V,isExtensionActive:()=>H,isExtensionInstalled:()=>le,isIOSSafari:()=>ce,observeInstallState:()=>Fe,presentError:()=>me,removeInstallBanner:()=>J,reportEvent:()=>m,resolveOnboardingState:()=>jt,resolveStrings:()=>A,saveReturnContext:()=>L,showInstallBanner:()=>Z,validateApiKey:()=>et});function Mt(e){typeof window>"u"||window.dispatchEvent(new CustomEvent(l.STATE_CHANGE,{detail:{state:e}}));}async function N(e,t,r){if(e.banner===false)return;let{showInstallBanner:n}=await Promise.resolve().then(()=>(Q(),ge)),i=typeof e.banner=="object"?e.banner:{},o={...i,apiKey:e.key??"",operatorName:e.operatorName,lang:i.lang??e.lang,state:r??t};n(o);}async function ee(){if(typeof navigator>"u")return false;let e=navigator.bluetooth;if(!e||typeof e.getAvailability!="function")return false;try{return await e.getAvailability()===!1}catch{return false}}function tt(){if(typeof window>"u")return false;try{let e=window.localStorage;if(!e)return !1;let t="__beacio_pb_probe__";return e.setItem(t,"1"),e.removeItem(t),!1}catch{return true}}async function he(e){let{getExtensionInstallState:t,isIOSSafari:r}=await Promise.resolve().then(()=>(K(),de));if(!r())return;let n=await t();if(Mt(n),n==="active"){if(await ee()){m(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await N(e,n,"denied");return}m(e.key??"","extension_active"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.READY)),e.onReady?.(),await N(e,n);return}if(tt()){m(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.NOT_INSTALLED)),e.onNotInstalled?.(),await N(e,n,"private-browsing");return}if(await ee()){m(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await N(e,n,"denied");return}if(n==="installed-inactive"){m(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await N(e,n);return}m(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.NOT_INSTALLED)),e.onNotInstalled?.(),await N(e,n),e.banner!==false&&m(e.key??"","install_prompted");}function Ft(e){let t=typeof window<"u"?window.location.href:"";return `${q}?operatorName=${encodeURIComponent(e.operatorName)}&return=${encodeURIComponent(t)}`}async function jt(e){let{getExtensionInstallState:t,isIOSSafari:r}=await Promise.resolve().then(()=>(K(),de));if(!r())return {kind:"unsupported"};let n=se(),i=Ft(e),o=await t();if(o==="active")return await ee()?{kind:"denied",setupUrl:i,returnLink:n}:{kind:"ready"};if(tt())return {kind:"private-browsing",returnLink:n};if(await ee())return {kind:"denied",setupUrl:i,returnLink:n};if(o==="installed-inactive")return {kind:"installed-inactive",setupUrl:i,returnLink:n};let{buildOnboardingUrl:a}=await Promise.resolve().then(()=>(Q(),ge));return {kind:"not-installed",installUrl:a(w,{apiKey:e.apiKey,operatorName:e.operatorName}),returnLink:n}}var xe=g(()=>{K();Q();U();Je();Y();_e();_e();R();U();ue();});G();var Ce="-0000-1000-8000-00805f9b34fb",De=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,Oe={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},Re={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,local_east_coordinate:10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989},Be={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function Le(e){return e.toString(16).padStart(8,"0")+Ce}function ie(e){let t=Number(e);if(!Number.isFinite(t))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);let r=Math.trunc(t);if(r<0||r>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);return Le(r+0)}function $(e,t,r){if(typeof e=="number")return ie(e);let n=String(e);if(De.test(n))return n;let i=t[n.toLowerCase()];if(i!==void 0)return Le(i);throw new TypeError(`Failed to execute '${r}' on 'BluetoothUUID': Invalid UUID or registry name: "${n}"`)}function gt(e){return $(e,Be,"getDescriptor")}var ke={canonicalUUID:ie,getService:e=>$(e,Oe,"getService"),getCharacteristic:e=>$(e,Re,"getCharacteristic"),getDescriptor:gt};R();function rt(e){if(typeof navigator>"u"||!navigator.permissions)return;let t=navigator.permissions.query.bind(navigator.permissions);navigator.permissions.query=async function(r){if(r.name!=="bluetooth")return t(r);let n=r.deviceId,i=[];if(typeof e.getDevices=="function")try{let d=await e.getDevices();i=n===void 0?[...d]:d.filter(_=>_.id===n);}catch{i=[];}let o=Object.freeze(i),a=new EventTarget;return Object.create(a,{state:{get:()=>"prompt",enumerable:true},name:{get:()=>"bluetooth",enumerable:true},onchange:{value:null,writable:true,enumerable:true},devices:{get:()=>o,enumerable:true}})};}var lt=new Set(["requestDevice","getAvailability","getDevices","referringDevice","onavailabilitychanged","onadvertisementreceived","ongattserverdisconnected","oncharacteristicvaluechanged","onserviceadded","onservicechanged","onserviceremoved","addEventListener","removeEventListener","dispatchEvent"]);function dt(e){return e.startsWith("on")}function it(e){class t extends EventTarget{}let r=new t,n=e;for(let i of lt){if(dt(i)){Object.defineProperty(r,i,{get:()=>n[i]??null,set:a=>{n[i]=a;},enumerable:true,configurable:true});continue}if(i==="referringDevice"){Object.defineProperty(r,i,{get:()=>n[i]??null,enumerable:true,configurable:true});continue}let o=n[i];typeof o=="function"&&Object.defineProperty(r,i,{value:o.bind(e),writable:true,enumerable:true,configurable:true});}return r}function zt(){let e=(typeof window<"u"?window.beacioAutoReconnect:void 0)??{};return {enabled:e.enabled!==false,backoff:{maxAttempts:e.maxAttempts??1/0,initialDelayMs:e.initialDelayMs??1e3,maxDelayMs:e.maxDelayMs??3e4,backoffMultiplier:e.backoffMultiplier??2}}}function C(e,t){let r=new Map;return new Proxy(e,{get(n,i,o){if(typeof i=="string"&&Object.prototype.hasOwnProperty.call(t,i))return t[i];let a=Reflect.get(n,i,o);if(typeof a!="function"||Object.prototype.hasOwnProperty.call(n,i))return a;let c=r.get(i);return c||(c=a.bind(n),r.set(i,c)),c}})}function ot(e,t,r){let n=`${t}|${e.uuid}`;return C(e,{startNotifications:async()=>{let i=await e.startNotifications();return r.subscriptions.set(n,{service:t,characteristic:e.uuid}),i},stopNotifications:async()=>{let i=await e.stopNotifications();return r.subscriptions.delete(n),i}})}function at(e,t){return C(e,{getCharacteristic:async r=>{let n=await e.getCharacteristic(r);return ot(n,e.uuid,t)},getCharacteristics:async r=>(await e.getCharacteristics(r)).map(i=>ot(i,e.uuid,t))})}function Gt(e,t){return t.server=e,C(e,{getPrimaryService:async r=>{let n=await e.getPrimaryService(r);return at(n,t)},getPrimaryServices:async r=>(await e.getPrimaryServices(r)).map(i=>at(i,t))})}function $t(e,t){return C(e,{connect:async()=>{t.intentional=false;let r=await e.connect();return Gt(r,t)},disconnect:()=>{t.intentional=true,t.subscriptions.clear(),t.server=null,e.disconnect();}})}async function Ht(e,t){for(let{service:r,characteristic:n}of [...t.subscriptions.values()])try{await(await(await e.getPrimaryService(r)).getCharacteristic(n)).startNotifications();}catch{t.subscriptions.delete(`${r}|${n}`);}}function Vt(e,t){if(e.reconnecting)return;let r=e.server;if(!r)return;e.reconnecting=true;let n=[...new Set([...e.subscriptions.values()].map(i=>i.service))];(async()=>{let i=t.initialDelayMs;for(let o=1;o<=t.maxAttempts&&!(e.intentional||(await new Promise(a=>setTimeout(a,i)),e.intentional));o+=1)try{let a=r.connectAndDiscover;typeof a=="function"&&n.length>0?await a.call(r,n):await r.connect(),await Ht(r,e),e.reconnecting=!1;return}catch{i=Math.min(i*t.backoffMultiplier,t.maxDelayMs);}e.reconnecting=false;})();}function Wt(e,t){if(!e||typeof e.addEventListener!="function")return e;let r={server:null,intentional:false,reconnecting:false,subscriptions:new Map};e.addEventListener("gattserverdisconnected",()=>{if(r.intentional){r.intentional=false;return}Vt(r,t);});let n;return C(e,{get gatt(){let i=e.gatt;if(i)return n||(n=$t(i,r)),n}})}function st(e){let t=zt(),r=e;if(!t.enabled||typeof r.requestDevice!="function")return e;let n=r.requestDevice.bind(e);return C(e,{requestDevice:async(...i)=>{let o=await n(...i);return Wt(o,t.backoff)}})}function Kt(){class e extends EventTarget{}let t=new e;Object.defineProperty(t,"requestDevice",{value:async(...r)=>{try{let n=await Promise.resolve().then(()=>(xe(),nt));typeof n.showInstallBanner=="function"&&n.showInstallBanner();}catch{}throw new DOMException("Web Bluetooth is not supported on this platform. On iOS Safari, install the Beacio extension. See: https://beacio.com","NotFoundError")},writable:true,enumerable:true,configurable:true}),Object.defineProperty(t,"getAvailability",{value:async()=>false,writable:true,enumerable:true,configurable:true}),Object.defineProperty(t,"getDevices",{value:async()=>[],writable:true,enumerable:true,configurable:true}),Object.defineProperty(t,"referringDevice",{get:()=>null,enumerable:true,configurable:true});for(let r of lt){if(!dt(r))continue;let n=r.slice(2),i=null;Object.defineProperty(t,r,{get:()=>i,set:o=>{i!==null&&t.removeEventListener(n,i),i=typeof o=="function"?o:null,i!==null&&t.addEventListener(n,i);},enumerable:true,configurable:true});}return Object.defineProperty(t,y,{value:true,writable:false,enumerable:false,configurable:true}),t}var ct=false;function Ee(){if(ct||typeof navigator>"u")return;ct=true;let e=navigator;if(typeof window<"u"&&!window.BluetoothUUID&&(window.BluetoothUUID=ke),typeof window<"u"&&window.isSecureContext===false)return;let t=re();if(t!=="native"){if(t==="safari-extension"){let r=z();if(r&&!e.bluetooth){let n=it(st(r));Object.defineProperty(navigator,"bluetooth",{get:()=>n,configurable:true});}if(typeof window<"u"&&!window.beacioIOS){let n=r,i=n?.peripheral||n?.backgroundSync?{peripheral:n.peripheral,backgroundSync:n.backgroundSync,getCapabilities:()=>n?.getCapabilities?.()}:void 0;i&&Object.defineProperty(window,"beacioIOS",{value:Object.freeze(i),writable:false,enumerable:true,configurable:false});}r&&rt(r);return}if(!e.bluetooth){let r=Kt();Object.defineProperty(navigator,"bluetooth",{get:()=>r,configurable:true}),typeof window<"u"&&window.addEventListener(l.EXTENSION_READY,()=>{let n=z();if(!n||n===r)return;let i=navigator.bluetooth;if(i!==void 0&&i!==r)return;let o=it(st(n));Object.defineProperty(navigator,"bluetooth",{get:()=>o,configurable:true}),rt(n);},{once:true});}}}Ee();G();xe();Ee();function qt(){if(typeof document>"u")return;let e=document.currentScript;return (e?.dataset?.operatorName??e?.getAttribute?.("data-operator-name")??void 0)||document.title||void 0}var Yt=qt();function ut(){if(typeof navigator<"u"&&navigator.bluetooth){let e=navigator.bluetooth;if(e&&!e[y])return}he({operatorName:Yt});}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",ut,{once:true}):ut());
exports.initBeacio=he;exports.presentError=me;exports.removeInstallBanner=J;exports.showInstallBanner=Z;return exports;})({});//# sourceMappingURL=browser-auto.global.js.map
//# sourceMappingURL=browser-auto.global.js.map

Sorry, the diff of this file is too big to display

import {b}from'./chunk-FANWIUKA.mjs';var f="0000ffe0-0000-1000-8000-00805f9b34fb",o="0000ffe1-0000-1000-8000-00805f9b34fb",t=class extends b{constructor(){super(...arguments);this.service=f;}onReceive(e){return this.subscribe(o,e)}async send(e){await this.sendChunked(o,e);}};export{t as a};//# sourceMappingURL=chunk-2PX7ZYHS.mjs.map
//# sourceMappingURL=chunk-2PX7ZYHS.mjs.map
{"version":3,"sources":["../src/profiles/serial-ffe0.ts"],"names":["FFE0_SERVICE","FFE1_CHAR","HM10SerialProfile","BaseProfile","callback","data"],"mappings":"qCAGA,IAAMA,CAAAA,CAAe,sCAAA,CAEfC,CAAAA,CAAY,sCAAA,CAkCLC,EAAN,cAAgCC,CAAY,CAA5C,WAAA,EAAA,CAAA,KAAA,CAAA,GAAA,SAAA,CAAA,CACL,IAAA,CAAmB,OAAA,CAAUH,EAAAA,CAS7B,SAAA,CAAUI,EAAiD,CACzD,OAAO,IAAA,CAAK,SAAA,CAAUH,CAAAA,CAAWG,CAAQ,CAC3C,CAUA,MAAM,IAAA,CAAKC,CAAAA,CAAmC,CAI5C,MAAM,IAAA,CAAK,WAAA,CAAYJ,CAAAA,CAAWI,CAAI,EACxC,CACF","file":"chunk-2PX7ZYHS.mjs","sourcesContent":["import { BaseProfile } from './base';\n\n/** HM-10 / CC2541 \"transparent serial\" service UUID. */\nconst FFE0_SERVICE = '0000ffe0-0000-1000-8000-00805f9b34fb';\n/** Single bidirectional characteristic: write AND notify share this handle. */\nconst FFE1_CHAR = '0000ffe1-0000-1000-8000-00805f9b34fb';\n\n/**\n * HM-10 (and compatible CC2540/CC2541 modules: HM-11, AT-09, JDY-08, …)\n * transparent-serial profile.\n *\n * Unlike Nordic UART's two-characteristic design, the HM-10 multiplexes both\n * directions onto a *single* characteristic `0000ffe1-…` on service\n * `0000ffe0-…`: the host writes to it (write-without-response) and the device\n * pushes inbound bytes back via notifications on the very same handle.\n *\n * Strictly W3C `navigator.bluetooth` GATT: notifications are enabled through\n * {@link BaseProfile.subscribe} (`startNotifications()`); this profile never\n * reads or writes a CCCD/SCCD descriptor itself.\n *\n * @example\n * ```ts\n * import { HM10SerialProfile } from '@beacio/core/profiles';\n *\n * // requestDevice({ filters: [{ services: ['0000ffe0-0000-1000-8000-00805f9b34fb'] }] })\n * const serial = new HM10SerialProfile(device);\n * await serial.connect();\n *\n * const decoder = new TextDecoder();\n * const unsubscribe = serial.onReceive((chunk) => {\n * console.log(decoder.decode(chunk));\n * });\n *\n * await serial.send(new TextEncoder().encode('AT+NAME?\\r\\n'));\n *\n * unsubscribe();\n * serial.stop();\n * ```\n */\nexport class HM10SerialProfile extends BaseProfile {\n protected readonly service = FFE0_SERVICE;\n\n /**\n * Subscribe to inbound data from the module (FFE1 notify).\n * Each notification is delivered as a raw {@link DataView} chunk.\n *\n * @param callback - Invoked with every inbound chunk.\n * @returns Unsubscribe function. Also cleaned up by {@link BaseProfile.stop}.\n */\n onReceive(callback: (chunk: DataView) => void): () => void {\n return this.subscribe(FFE1_CHAR, callback);\n }\n\n /**\n * Send data to the module (FFE1 write-without-response — the same handle\n * used for inbound notifications). Payloads larger than the negotiated\n * write-without-response limit are split into MTU-sized chunks and written\n * sequentially.\n *\n * @param data - Bytes to send. Accepts any {@link BufferSource}.\n */\n async send(data: BufferSource): Promise<void> {\n // Delegate fragmentation to the core write-chunker (via BaseProfile.sendChunked),\n // which derives a branded, always-positive ChunkSize from the negotiated\n // write-without-response limit / MTU. No hand-rolled offset loop here.\n await this.sendChunked(FFE1_CHAR, data);\n }\n}\n"]}
var u="-0000-1000-8000-00805f9b34fb",g=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,c={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},x={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,local_east_coordinate:10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989},y={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function D(e){return e.toString(16).padStart(8,"0")+u}function f(e){let r=Number(e);if(!Number.isFinite(r))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);let t=Math.trunc(r);if(t<0||t>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);return D(t+0)}function m(e,r,t){if(typeof e=="number")return f(e);let i=String(e);if(g.test(i))return i;let n=r[i.toLowerCase()];if(n!==void 0)return D(n);throw new TypeError(`Failed to execute '${t}' on 'BluetoothUUID': Invalid UUID or registry name: "${i}"`)}function A(e){return e.toString(16).padStart(8,"0")+u}var p,l;function C(){if(!p){p=new Map;for(let[e,r]of Object.entries(c)){let t=A(r);p.has(t)||p.set(t,e);}}return p}function E(){if(!l){l=new Map;for(let[e,r]of Object.entries(x)){let t=A(r);l.has(t)||l.set(t,e);}}return l}var S=/^[0-9a-f]{4}$/,B=/^[0-9a-f]{8}$/;function I(e,r){let t=e.length,i=r.length,n=Array.from({length:i+1},(_,a)=>a);for(let _=1;_<=t;_++){let a=_-1;n[0]=_;for(let o=1;o<=i;o++){let s=n[o];n[o]=e[_-1]===r[o-1]?a:1+Math.min(a,n[o],n[o-1]),a=s;}}return n[i]}function U(e){return e.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function w(e,r){let t=r[e];if(t!==void 0)return t;let i=e.replace(/[._-]/g,"");if(i){for(let[n,_]of Object.entries(r))if(n.replace(/[._-]/g,"")===i)return _}}function R(e){if(typeof e=="number"){if(!Number.isInteger(e)||e<0||e>4294967295)throw new TypeError(`Invalid UUID integer: ${e}. Must be a 16-bit or 32-bit unsigned integer.`);return A(e)}let r=e.trim(),t=r.toLowerCase();if(g.test(t))return t;if(S.test(t))return "0000"+t+u;if(B.test(t))return t+u;let i=c[t]??x[t];if(i!==void 0)return A(i);let n=U(r),_=w(n,c);if(_!==void 0)return A(_);let a=w(n,x);if(a!==void 0)return A(a);let o=Object.keys(c).concat(Object.keys(x)),s,h=4;for(let d of o){let b=I(n,d);b<h&&(h=b,s=d);}!s&&n.length>=4&&(s=o.find(d=>d.startsWith(n)));let v=s?` Did you mean "${s}"?`:"";throw new TypeError(`Invalid UUID: "${e}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${v}`)}function j(e){return C().get(e.toLowerCase())}function $(e){return E().get(e.toLowerCase())}function M(e){let r=e.replace(/^(gap|gatt)\./,"");return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(r)?r.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" "):e}function F(e){return m(e,y,"getDescriptor")}var z={canonicalUUID:f,getService:e=>m(e,c,"getService"),getCharacteristic:e=>m(e,x,"getCharacteristic"),getDescriptor:F};export{f as a,R as b,j as c,$ as d,M as e,F as f,z as g};//# sourceMappingURL=chunk-33IHM3NV.mjs.map
//# sourceMappingURL=chunk-33IHM3NV.mjs.map
{"version":3,"sources":["../src/gatt-registry.generated.ts","../src/uuid.ts"],"names":["BLUETOOTH_BASE_UUID_SUFFIX","UUID_RE","GATT_ASSIGNED_SERVICES","GATT_ASSIGNED_CHARACTERISTICS","GATT_ASSIGNED_DESCRIPTORS","hexToUUID","hex","canonicalUUID","alias","converted","truncated","resolveUUIDName","name","table","getter","value","serviceNameMap","charNameMap","getServiceNameMap","uuid","getCharNameMap","HEX4_RE","HEX8_RE","levenshtein","a","b","m","n","row","i","prev","j","tmp","normalizeBluetoothName","input","lookupNamedUUID","directMatch","compactName","candidateName","candidateHex","resolveUUID","nameOrUUID","raw","lower","exactAlias","normalizedName","serviceHex","charHex","allNames","closest","bestDist","d","hint","getServiceName","getCharacteristicName","getDisplayName","bare","word","getDescriptor","BluetoothUUID"],"mappings":"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","file":"chunk-33IHM3NV.mjs","sourcesContent":["/**\n * THE single source of the Web Bluetooth §7 page-realm UUID machinery for every\n * TypeScript bundle: the npm core, the extension-injected surface\n * (`src/beacio/api/*`) and the standalone CDN polyfill (`src/cdn/beacio.ts`)\n * all import the three GATT assigned-numbers tables and the §7.1 resolver\n * (`canonicalUUID` + `resolveUUIDName`) FROM HERE. Because each shipped artifact\n * is webpack/tsup-bundled, this source module is inlined into each build, so\n * every artifact stays dependency-free while the tables + resolver exist ONCE.\n *\n * The three `registries:begin/end` table blocks are GENERATED by\n * `scripts/registries/generate.mjs` from the vendored WebBluetoothCG registry\n * files in `registries/` — the same single source of truth as\n * `Shared (Extension)/UUIDResolver.swift` (Swift cannot import TS, so it stays\n * a separate generated target). Regenerate with:\n * node scripts/registries/generate.mjs\n *\n * The resolver semantics are the STRICT §7.1 form (spec: \"A valid UUID is a\n * lower-case 128-bit string\"): a string argument must be a valid lowercase\n * 128-bit UUID or a registered name (looked up case-folded because the tables\n * key the registry's mixed-case published spellings — e.g.\n * `magnetic_flux_density_2D` — in lowercase). The bare 4/8-hex and uppercase\n * leniency of the wire helpers (`src/beacio/api/uuid-normalization.ts`) is\n * intentionally NOT offered here.\n *\n * ARCHITECTURE BOUNDARY: This file holds ONLY the three GATT assigned-numbers\n * tables (services/characteristics/descriptors) + the Web Bluetooth §7.1\n * resolver functions (canonicalUUID, resolveUUIDName, hexToUUID). No other\n * logic. No new exports. Every consumer bundles this inline, so accumulation\n * here is paid N times across consumers.\n */\n\n/** §7 base UUID suffix; a 16/32-bit alias replaces the top 32 bits. */\nexport const BLUETOOTH_BASE_UUID_SUFFIX = \"-0000-1000-8000-00805f9b34fb\";\n\n/** §7 \"A valid UUID is a string that matches [this] regexp\" (lowercase only). */\nexport const UUID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\n// registries:begin gatt-services\n// GENERATED from registries/gatt_assigned_services.txt by scripts/registries/generate.mjs — do not edit by hand.\nexport const GATT_ASSIGNED_SERVICES: Record<string, number> = {\n 'generic_access': 0x1800,\n 'generic_attribute': 0x1801,\n 'immediate_alert': 0x1802,\n 'link_loss': 0x1803,\n 'tx_power': 0x1804,\n 'current_time': 0x1805,\n 'reference_time_update': 0x1806,\n 'next_dst_change': 0x1807,\n 'glucose': 0x1808,\n 'health_thermometer': 0x1809,\n 'device_information': 0x180A,\n 'heart_rate': 0x180D,\n 'phone_alert_status': 0x180E,\n 'battery_service': 0x180F,\n 'blood_pressure': 0x1810,\n 'alert_notification': 0x1811,\n 'human_interface_device': 0x1812,\n 'scan_parameters': 0x1813,\n 'running_speed_and_cadence': 0x1814,\n 'automation_io': 0x1815,\n 'cycling_speed_and_cadence': 0x1816,\n 'cycling_power': 0x1818,\n 'location_and_navigation': 0x1819,\n 'environmental_sensing': 0x181A,\n 'body_composition': 0x181B,\n 'user_data': 0x181C,\n 'weight_scale': 0x181D,\n 'bond_management': 0x181E,\n 'continuous_glucose_monitoring': 0x181F,\n 'internet_protocol_support': 0x1820,\n 'indoor_positioning': 0x1821,\n 'pulse_oximeter': 0x1822,\n 'http_proxy': 0x1823,\n 'transport_discovery': 0x1824,\n 'object_transfer': 0x1825,\n 'fitness_machine': 0x1826,\n 'mesh_provisioning': 0x1827,\n 'mesh_proxy': 0x1828,\n 'reconnection_configuration': 0x1829,\n};\n// registries:end gatt-services\n\n// registries:begin gatt-characteristics\n// GENERATED from registries/gatt_assigned_characteristics.txt by scripts/registries/generate.mjs — do not edit by hand.\nexport const GATT_ASSIGNED_CHARACTERISTICS: Record<string, number> = {\n 'gap.device_name': 0x2A00,\n 'gap.appearance': 0x2A01,\n 'gap.peripheral_privacy_flag': 0x2A02,\n 'gap.reconnection_address': 0x2A03,\n 'gap.peripheral_preferred_connection_parameters': 0x2A04,\n 'gatt.service_changed': 0x2A05,\n 'alert_level': 0x2A06,\n 'tx_power_level': 0x2A07,\n 'date_time': 0x2A08,\n 'day_of_week': 0x2A09,\n 'day_date_time': 0x2A0A,\n 'exact_time_100': 0x2A0B,\n 'exact_time_256': 0x2A0C,\n 'dst_offset': 0x2A0D,\n 'time_zone': 0x2A0E,\n 'local_time_information': 0x2A0F,\n 'secondary_time_zone': 0x2A10,\n 'time_with_dst': 0x2A11,\n 'time_accuracy': 0x2A12,\n 'time_source': 0x2A13,\n 'reference_time_information': 0x2A14,\n 'time_broadcast': 0x2A15,\n 'time_update_control_point': 0x2A16,\n 'time_update_state': 0x2A17,\n 'glucose_measurement': 0x2A18,\n 'battery_level': 0x2A19,\n 'battery_power_state': 0x2A1A,\n 'battery_level_state': 0x2A1B,\n 'temperature_measurement': 0x2A1C,\n 'temperature_type': 0x2A1D,\n 'intermediate_temperature': 0x2A1E,\n 'temperature_celsius': 0x2A1F,\n 'temperature_fahrenheit': 0x2A20,\n 'measurement_interval': 0x2A21,\n 'boot_keyboard_input_report': 0x2A22,\n 'system_id': 0x2A23,\n 'model_number_string': 0x2A24,\n 'serial_number_string': 0x2A25,\n 'firmware_revision_string': 0x2A26,\n 'hardware_revision_string': 0x2A27,\n 'software_revision_string': 0x2A28,\n 'manufacturer_name_string': 0x2A29,\n 'ieee_11073-20601_regulatory_certification_data_list': 0x2A2A,\n 'current_time': 0x2A2B,\n 'magnetic_declination': 0x2A2C,\n 'position_2d': 0x2A2F,\n 'position_3d': 0x2A30,\n 'scan_refresh': 0x2A31,\n 'boot_keyboard_output_report': 0x2A32,\n 'boot_mouse_input_report': 0x2A33,\n 'glucose_measurement_context': 0x2A34,\n 'blood_pressure_measurement': 0x2A35,\n 'intermediate_cuff_pressure': 0x2A36,\n 'heart_rate_measurement': 0x2A37,\n 'body_sensor_location': 0x2A38,\n 'heart_rate_control_point': 0x2A39,\n 'removable': 0x2A3A,\n 'service_required': 0x2A3B,\n 'scientific_temperature_celsius': 0x2A3C,\n 'string': 0x2A3D,\n 'network_availability': 0x2A3E,\n 'alert_status': 0x2A3F,\n 'ringer_control_point': 0x2A40,\n 'ringer_setting': 0x2A41,\n 'alert_category_id_bit_mask': 0x2A42,\n 'alert_category_id': 0x2A43,\n 'alert_notification_control_point': 0x2A44,\n 'unread_alert_status': 0x2A45,\n 'new_alert': 0x2A46,\n 'supported_new_alert_category': 0x2A47,\n 'supported_unread_alert_category': 0x2A48,\n 'blood_pressure_feature': 0x2A49,\n 'hid_information': 0x2A4A,\n 'report_map': 0x2A4B,\n 'hid_control_point': 0x2A4C,\n 'report': 0x2A4D,\n 'protocol_mode': 0x2A4E,\n 'scan_interval_window': 0x2A4F,\n 'pnp_id': 0x2A50,\n 'glucose_feature': 0x2A51,\n 'record_access_control_point': 0x2A52,\n 'rsc_measurement': 0x2A53,\n 'rsc_feature': 0x2A54,\n 'sc_control_point': 0x2A55,\n 'digital': 0x2A56,\n 'digital_output': 0x2A57,\n 'analog': 0x2A58,\n 'analog_output': 0x2A59,\n 'aggregate': 0x2A5A,\n 'csc_measurement': 0x2A5B,\n 'csc_feature': 0x2A5C,\n 'sensor_location': 0x2A5D,\n 'plx_spot_check_measurement': 0x2A5E,\n 'plx_continuous_measurement': 0x2A5F,\n 'plx_features': 0x2A60,\n 'pulse_oximetry_control_point': 0x2A62,\n 'cycling_power_measurement': 0x2A63,\n 'cycling_power_vector': 0x2A64,\n 'cycling_power_feature': 0x2A65,\n 'cycling_power_control_point': 0x2A66,\n 'location_and_speed': 0x2A67,\n 'navigation': 0x2A68,\n 'position_quality': 0x2A69,\n 'ln_feature': 0x2A6A,\n 'ln_control_point': 0x2A6B,\n 'elevation': 0x2A6C,\n 'pressure': 0x2A6D,\n 'temperature': 0x2A6E,\n 'humidity': 0x2A6F,\n 'true_wind_speed': 0x2A70,\n 'true_wind_direction': 0x2A71,\n 'apparent_wind_speed': 0x2A72,\n 'apparent_wind_direction': 0x2A73,\n 'gust_factor': 0x2A74,\n 'pollen_concentration': 0x2A75,\n 'uv_index': 0x2A76,\n 'irradiance': 0x2A77,\n 'rainfall': 0x2A78,\n 'wind_chill': 0x2A79,\n 'heat_index': 0x2A7A,\n 'dew_point': 0x2A7B,\n 'descriptor_value_changed': 0x2A7D,\n 'aerobic_heart_rate_lower_limit': 0x2A7E,\n 'aerobic_threshold': 0x2A7F,\n 'age': 0x2A80,\n 'anaerobic_heart_rate_lower_limit': 0x2A81,\n 'anaerobic_heart_rate_upper_limit': 0x2A82,\n 'anaerobic_threshold': 0x2A83,\n 'aerobic_heart_rate_upper_limit': 0x2A84,\n 'date_of_birth': 0x2A85,\n 'date_of_threshold_assessment': 0x2A86,\n 'email_address': 0x2A87,\n 'fat_burn_heart_rate_lower_limit': 0x2A88,\n 'fat_burn_heart_rate_upper_limit': 0x2A89,\n 'first_name': 0x2A8A,\n 'five_zone_heart_rate_limits': 0x2A8B,\n 'gender': 0x2A8C,\n 'heart_rate_max': 0x2A8D,\n 'height': 0x2A8E,\n 'hip_circumference': 0x2A8F,\n 'last_name': 0x2A90,\n 'maximum_recommended_heart_rate': 0x2A91,\n 'resting_heart_rate': 0x2A92,\n 'sport_type_for_aerobic_and_anaerobic_thresholds': 0x2A93,\n 'three_zone_heart_rate_limits': 0x2A94,\n 'two_zone_heart_rate_limit': 0x2A95,\n 'vo2_max': 0x2A96,\n 'waist_circumference': 0x2A97,\n 'weight': 0x2A98,\n 'database_change_increment': 0x2A99,\n 'user_index': 0x2A9A,\n 'body_composition_feature': 0x2A9B,\n 'body_composition_measurement': 0x2A9C,\n 'weight_measurement': 0x2A9D,\n 'weight_scale_feature': 0x2A9E,\n 'user_control_point': 0x2A9F,\n 'magnetic_flux_density_2d': 0x2AA0,\n 'magnetic_flux_density_3d': 0x2AA1,\n 'language': 0x2AA2,\n 'barometric_pressure_trend': 0x2AA3,\n 'bond_management_control_point': 0x2AA4,\n 'bond_management_feature': 0x2AA5,\n 'gap.central_address_resolution_support': 0x2AA6,\n 'cgm_measurement': 0x2AA7,\n 'cgm_feature': 0x2AA8,\n 'cgm_status': 0x2AA9,\n 'cgm_session_start_time': 0x2AAA,\n 'cgm_session_run_time': 0x2AAB,\n 'cgm_specific_ops_control_point': 0x2AAC,\n 'indoor_positioning_configuration': 0x2AAD,\n 'latitude': 0x2AAE,\n 'longitude': 0x2AAF,\n 'local_north_coordinate': 0x2AB0,\n 'local_east_coordinate': 0x2AB1,\n 'floor_number': 0x2AB2,\n 'altitude': 0x2AB3,\n 'uncertainty': 0x2AB4,\n 'location_name': 0x2AB5,\n 'uri': 0x2AB6,\n 'http_headers': 0x2AB7,\n 'http_status_code': 0x2AB8,\n 'http_entity_body': 0x2AB9,\n 'http_control_point': 0x2ABA,\n 'https_security': 0x2ABB,\n 'tds_control_point': 0x2ABC,\n 'ots_feature': 0x2ABD,\n 'object_name': 0x2ABE,\n 'object_type': 0x2ABF,\n 'object_size': 0x2AC0,\n 'object_first_created': 0x2AC1,\n 'object_last_modified': 0x2AC2,\n 'object_id': 0x2AC3,\n 'object_properties': 0x2AC4,\n 'object_action_control_point': 0x2AC5,\n 'object_list_control_point': 0x2AC6,\n 'object_list_filter': 0x2AC7,\n 'object_changed': 0x2AC8,\n 'resolvable_private_address_only': 0x2AC9,\n 'fitness_machine_feature': 0x2ACC,\n 'treadmill_data': 0x2ACD,\n 'cross_trainer_data': 0x2ACE,\n 'step_climber_data': 0x2ACF,\n 'stair_climber_data': 0x2AD0,\n 'rower_data': 0x2AD1,\n 'indoor_bike_data': 0x2AD2,\n 'training_status': 0x2AD3,\n 'supported_speed_range': 0x2AD4,\n 'supported_inclination_range': 0x2AD5,\n 'supported_resistance_level_range': 0x2AD6,\n 'supported_heart_rate_range': 0x2AD7,\n 'supported_power_range': 0x2AD8,\n 'fitness_machine_control_point': 0x2AD9,\n 'fitness_machine_status': 0x2ADA,\n 'date_utc': 0x2AED,\n};\n// registries:end gatt-characteristics\n\n// registries:begin gatt-descriptors\n// GENERATED from registries/gatt_assigned_descriptors.txt by scripts/registries/generate.mjs — do not edit by hand.\nexport const GATT_ASSIGNED_DESCRIPTORS: Record<string, number> = {\n 'gatt.characteristic_extended_properties': 0x2900,\n 'gatt.characteristic_user_description': 0x2901,\n 'gatt.client_characteristic_configuration': 0x2902,\n 'gatt.server_characteristic_configuration': 0x2903,\n 'gatt.characteristic_presentation_format': 0x2904,\n 'gatt.characteristic_aggregate_format': 0x2905,\n 'valid_range': 0x2906,\n 'external_report_reference': 0x2907,\n 'report_reference': 0x2908,\n 'number_of_digitals': 0x2909,\n 'value_trigger_setting': 0x290A,\n 'es_configuration': 0x290B,\n 'es_measurement': 0x290C,\n 'es_trigger_setting': 0x290D,\n 'time_trigger_setting': 0x290E,\n};\n// registries:end gatt-descriptors\n\n/** Expand a validated 16/32-bit alias into the canonical lowercase 128-bit UUID. */\nfunction hexToUUID(hex: number): string {\n return hex.toString(16).padStart(8, \"0\") + BLUETOOTH_BASE_UUID_SUFFIX;\n}\n\n/**\n * `BluetoothUUID.canonicalUUID(alias)` (§7): expand a 16/32-bit alias into the\n * canonical lowercase 128-bit UUID string. Applies the WebIDL `[EnforceRange]\n * unsigned long` conversion: ToNumber, reject non-finite, truncate toward zero,\n * reject outside [0, 2^32 − 1]. Fractional/numeric-string inputs CONVERT.\n */\nexport function canonicalUUID(alias: number): string {\n const converted = Number(alias);\n if (!Number.isFinite(converted)) {\n throw new TypeError(\n `Failed to execute 'canonicalUUID' on 'BluetoothUUID': ` +\n `Value is not a valid unsigned long: ${alias}`,\n );\n }\n const truncated = Math.trunc(converted);\n if (truncated < 0 || truncated > 0xffffffff) {\n throw new TypeError(\n `Failed to execute 'canonicalUUID' on 'BluetoothUUID': ` +\n `Value is not a valid unsigned long: ${alias}`,\n );\n }\n // `+ 0` normalizes -0 (from truncating e.g. -0.5) to 0.\n return hexToUUID(truncated + 0);\n}\n\n/**\n * §7.1 ResolveUUIDName, scoped to a single GATT assigned-numbers table.\n * Names are table-scoped because the registries reuse names across categories\n * (`current_time` is service 0x1805 AND characteristic 0x2A2B), so\n * `getService` / `getCharacteristic` / `getDescriptor` each consult only their\n * own table.\n *\n * 1. unsigned long → canonicalUUID(name)\n * 2. valid lowercase UUID → pass through\n * 3. registered name → canonicalUUID(alias) (case-folded lookup)\n * 4. otherwise → throw a TypeError\n *\n * `String(name)` coerces page-realm non-string garbage so it stringifies (and\n * misses) rather than dying on `.toLowerCase`.\n */\nexport function resolveUUIDName(\n name: string | number,\n table: Record<string, number>,\n getter: string,\n): string {\n if (typeof name === \"number\") return canonicalUUID(name);\n const value = String(name);\n if (UUID_RE.test(value)) return value;\n const alias = table[value.toLowerCase()];\n if (alias !== undefined) return hexToUUID(alias);\n throw new TypeError(\n `Failed to execute '${getter}' on 'BluetoothUUID': Invalid UUID or registry name: \"${value}\"`,\n );\n}\n","// GATT assigned-numbers tables + the §7.1 resolver (`canonicalUUID` /\n// `resolveUUIDName`) live in ONE generated module (DR-06): the npm core, the\n// extension-injected surface and the CDN polyfill all import them from there,\n// so the registry data + §7.1 semantics exist exactly once. The lenient,\n// fuzzy-matching SDK-level `resolveUUID` below is a separate, core-only helper\n// built on top of those shared tables. Regenerate the tables with:\n// node scripts/registries/generate.mjs\nimport {\n BLUETOOTH_BASE_UUID_SUFFIX as BASE_SUFFIX,\n UUID_RE,\n GATT_ASSIGNED_SERVICES as SERVICES,\n GATT_ASSIGNED_CHARACTERISTICS as CHARACTERISTICS,\n GATT_ASSIGNED_DESCRIPTORS as DESCRIPTORS,\n canonicalUUID,\n resolveUUIDName,\n} from './gatt-registry.generated';\n\nexport { canonicalUUID };\n\n/** Expand a validated 16/32-bit alias into the canonical lowercase 128-bit UUID. */\nfunction hexToUUID(hex: number): string {\n return hex.toString(16).padStart(8, '0') + BASE_SUFFIX;\n}\n\n\n// Reverse maps for name lookups (built lazily)\nlet serviceNameMap: Map<string, string> | undefined;\nlet charNameMap: Map<string, string> | undefined;\n\nfunction getServiceNameMap(): Map<string, string> {\n if (!serviceNameMap) {\n serviceNameMap = new Map();\n // First definition wins, so a canonical name (e.g. generic_access) beats its\n // SIG abbreviation (gap) when multiple names map to the same UUID.\n for (const [name, hex] of Object.entries(SERVICES)) {\n const uuid = hexToUUID(hex);\n if (!serviceNameMap.has(uuid)) serviceNameMap.set(uuid, name);\n }\n }\n return serviceNameMap;\n}\n\nfunction getCharNameMap(): Map<string, string> {\n if (!charNameMap) {\n charNameMap = new Map();\n // First definition wins (canonical name beats any SIG abbreviation alias).\n for (const [name, hex] of Object.entries(CHARACTERISTICS)) {\n const uuid = hexToUUID(hex);\n if (!charNameMap.has(uuid)) charNameMap.set(uuid, name);\n }\n }\n return charNameMap;\n}\n\nconst HEX4_RE = /^[0-9a-f]{4}$/;\nconst HEX8_RE = /^[0-9a-f]{8}$/;\n\n/** Single-row Levenshtein distance — O(m·n) time, O(n) space. */\nfunction levenshtein(a: string, b: string): number {\n const m = a.length, n = b.length;\n const row = Array.from({ length: n + 1 }, (_, i) => i);\n for (let i = 1; i <= m; i++) {\n let prev = i - 1;\n row[0] = i;\n for (let j = 1; j <= n; j++) {\n const tmp = row[j];\n row[j] = a[i - 1] === b[j - 1]\n ? prev\n : 1 + Math.min(prev, row[j], row[j - 1]);\n prev = tmp;\n }\n }\n return row[n];\n}\n\nfunction normalizeBluetoothName(input: string): string {\n return input\n .trim()\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')\n .replace(/[-.\\s]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase();\n}\n\nfunction lookupNamedUUID(name: string, table: Record<string, number>): number | undefined {\n const directMatch = table[name];\n if (directMatch !== undefined) return directMatch;\n\n // Registry names may contain dots and hyphens ('gap.device_name',\n // 'ieee_11073-20601_…'); compare with all separators stripped so normalized\n // camelCase/kebab-case inputs still match.\n const compactName = name.replace(/[._-]/g, '');\n if (!compactName) return undefined;\n\n for (const [candidateName, candidateHex] of Object.entries(table)) {\n if (candidateName.replace(/[._-]/g, '') === compactName) {\n return candidateHex;\n }\n }\n\n return undefined;\n}\n\n/**\n * Resolve a service/characteristic name, number, or short UUID to a full 128-bit UUID string.\n *\n * **Supported input formats:**\n * 1. **Named alias** -- Bluetooth SIG service or characteristic name (e.g. `'heart_rate'`, `'battery_level'`)\n * 2. **16-bit integer** -- Numeric service/characteristic ID (e.g. `0x180D`)\n * 3. **4-hex string** -- Short 16-bit hex (e.g. `'180d'`)\n * 4. **8-hex string** -- 32-bit hex (e.g. `'0000180d'`)\n * 5. **Full 128-bit UUID** -- Passed through unchanged (e.g. `'0000180d-0000-1000-8000-00805f9b34fb'`)\n *\n * **Fuzzy matching:** If the input looks like a name but does not match any known alias,\n * Levenshtein edit distance (threshold <= 3) is used to suggest corrections. Name\n * normalization converts camelCase/PascalCase to snake_case and replaces hyphens/dots/spaces\n * with underscores before matching.\n *\n * @param nameOrUUID - Service/characteristic name, hex string, numeric ID, or full UUID.\n * @returns Canonical lowercase 128-bit UUID string.\n *\n * @throws {TypeError} If a numeric input is out of the 32-bit unsigned range.\n * @throws {TypeError} If a string input is not a valid UUID format or known name (includes \"Did you mean?\" hint).\n *\n * @example\n * ```typescript\n * resolveUUID('heart_rate') // '0000180d-0000-1000-8000-00805f9b34fb'\n * resolveUUID('180d') // '0000180d-0000-1000-8000-00805f9b34fb'\n * resolveUUID(0x180D) // '0000180d-0000-1000-8000-00805f9b34fb'\n * resolveUUID('battery_level') // '00002a19-0000-1000-8000-00805f9b34fb'\n * resolveUUID('HeartRate') // '0000180d-...' (camelCase normalized)\n * resolveUUID('heart_rat') // throws Error: Did you mean \"heart_rate\"?\n * ```\n *\n * @see {@link getServiceName} for reverse lookup (UUID to name)\n * @see {@link getCharacteristicName} for reverse lookup (UUID to name)\n */\nexport function resolveUUID(nameOrUUID: string | number): string {\n // Numeric input: 16-bit or 32-bit Bluetooth UUID integer\n if (typeof nameOrUUID === 'number') {\n if (!Number.isInteger(nameOrUUID) || nameOrUUID < 0 || nameOrUUID > 0xFFFFFFFF) {\n throw new TypeError(`Invalid UUID integer: ${nameOrUUID}. Must be a 16-bit or 32-bit unsigned integer.`);\n }\n return hexToUUID(nameOrUUID);\n }\n\n const raw = nameOrUUID.trim();\n const lower = raw.toLowerCase();\n\n // Full 128-bit UUID\n if (UUID_RE.test(lower)) return lower;\n\n // 4-digit hex shorthand\n if (HEX4_RE.test(lower)) return '0000' + lower + BASE_SUFFIX;\n\n // 8-digit hex shorthand\n if (HEX8_RE.test(lower)) return lower + BASE_SUFFIX;\n\n // Exact registry-name match first — registry names may contain dots\n // ('gap.device_name') that name normalization would otherwise destroy.\n const exactAlias = SERVICES[lower] ?? CHARACTERISTICS[lower];\n if (exactAlias !== undefined) return hexToUUID(exactAlias);\n\n const normalizedName = normalizeBluetoothName(raw);\n\n // Named service\n const serviceHex = lookupNamedUUID(normalizedName, SERVICES);\n if (serviceHex !== undefined) return hexToUUID(serviceHex);\n\n // Named characteristic\n const charHex = lookupNamedUUID(normalizedName, CHARACTERISTICS);\n if (charHex !== undefined) return hexToUUID(charHex);\n\n // Reject strings that don't look like valid UUIDs or hex shorthand.\n // Likely a typo of a Bluetooth SIG name (e.g. \"heart_rat\" instead of \"heart_rate\").\n // AIDEV-NOTE: Uses Levenshtein distance (≤3) with prefix fallback to catch typos\n // beyond simple character-position mismatches (e.g. \"heartrate\" → \"heart_rate\").\n const allNames = Object.keys(SERVICES).concat(Object.keys(CHARACTERISTICS));\n\n // Levenshtein match — find the closest name within edit distance 3\n let closest: string | undefined;\n let bestDist = 4; // threshold + 1\n for (const name of allNames) {\n const d = levenshtein(normalizedName, name);\n if (d < bestDist) {\n bestDist = d;\n closest = name;\n }\n }\n\n // Prefix fallback — if no close Levenshtein match, check if input is a prefix\n // of a known name (minimum 4 chars to avoid overly broad matches).\n if (!closest && normalizedName.length >= 4) {\n closest = allNames.find((name) => name.startsWith(normalizedName));\n }\n\n const hint = closest ? ` Did you mean \"${closest}\"?` : '';\n // §7.1 ResolveUUIDName: \"Otherwise, throw a TypeError.\" (a real TypeError,\n // so `err instanceof TypeError` holds for spec-conformant callers).\n throw new TypeError(`Invalid UUID: \"${nameOrUUID}\". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${hint}`);\n}\n\n/**\n * Get the human-readable Bluetooth SIG service name for a UUID, if known.\n *\n * @param uuid - Full 128-bit UUID string (case-insensitive).\n * @returns Service name (e.g. `'heart_rate'`), or `undefined` if not a known SIG service.\n *\n * @see {@link resolveUUID} for the reverse operation (name to UUID)\n */\nexport function getServiceName(uuid: string): string | undefined {\n return getServiceNameMap().get(uuid.toLowerCase());\n}\n\n/**\n * Get the human-readable Bluetooth SIG characteristic name for a UUID, if known.\n *\n * @param uuid - Full 128-bit UUID string (case-insensitive).\n * @returns Characteristic name (e.g. `'heart_rate_measurement'`), or `undefined` if not a known SIG characteristic.\n *\n * @see {@link resolveUUID} for the reverse operation (name to UUID)\n */\nexport function getCharacteristicName(uuid: string): string | undefined {\n return getCharNameMap().get(uuid.toLowerCase());\n}\n\n/**\n * Format a Bluetooth SIG snake_case name (e.g. `'heart_rate'`) as Title Case\n * (e.g. `'Heart Rate'`) for display in a UI.\n *\n * Unknown inputs — anything that does not look like a snake_case SIG name, such\n * as a raw UUID string or hex shorthand — are returned unchanged so callers can\n * use the raw value as a fallback label.\n *\n * @param name - A snake_case SIG name, or a raw UUID/hex string.\n * @returns Title-cased name, or the input unchanged when it is not a SIG name.\n *\n * @example\n * ```typescript\n * getDisplayName('heart_rate') // 'Heart Rate'\n * getDisplayName('heart_rate_measurement') // 'Heart Rate Measurement'\n * getDisplayName('gap.device_name') // 'Device Name'\n * getDisplayName('0000180d-0000-1000-8000-00805f9b34fb') // (unchanged)\n * ```\n */\nexport function getDisplayName(name: string): string {\n // Registry names in the GAP/GATT namespaces are dot-prefixed\n // ('gap.device_name', 'gatt.client_characteristic_configuration'). The\n // prefix is registry plumbing, not part of the SIG human-readable name —\n // strip it before formatting so 0x2A00 displays as 'Device Name'.\n const bare = name.replace(/^(gap|gatt)\\./, '');\n // Raw UUIDs / hex shorthand are not SIG names — return them unchanged so the\n // caller can show the raw identifier as a fallback label.\n if (!/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(bare)) return name;\n return bare\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n// ---------------------------------------------------------------------------\n// BluetoothUUID — Web Bluetooth spec §4\n// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothuuid\n//\n// Static methods to resolve service, characteristic, and descriptor\n// names/aliases to canonical 128-bit UUID strings.\n// ---------------------------------------------------------------------------\n\n// Descriptor name → 16-bit alias map (descriptors are not part of\n// SERVICES/CHARACTERISTICS). Keys use the registry dot form, e.g.\n// 'gatt.client_characteristic_configuration'.\n\n\n// `canonicalUUID` and the §7.1 `resolveUUIDName` (used by `getDescriptor` and\n// the `BluetoothUUID` namespace below) are imported from\n// `./gatt-registry.generated` — the single source shared with the extension +\n// CDN surfaces (DR-06). `canonicalUUID` is re-exported at the top of this file.\n\n/**\n * Resolve a descriptor name or UUID alias to a canonical 128-bit UUID.\n * Implements `BluetoothUUID.getDescriptor()` from the Web Bluetooth spec\n * (§7.1 ResolveUUIDName against GATT assigned descriptors only): accepts a\n * registry descriptor name in its registry dot form\n * (e.g. `'gatt.client_characteristic_configuration'`), an integer alias, or\n * a valid lowercase 128-bit UUID. Anything else throws a TypeError.\n *\n * @param name - Descriptor name, 16/32-bit integer alias, or full UUID string.\n * @returns Canonical 128-bit UUID string.\n * @throws {TypeError} For unknown names, bare hex shorthand, or uppercase UUIDs.\n *\n * @example\n * ```typescript\n * getDescriptor('gatt.client_characteristic_configuration') // '00002902-...'\n * getDescriptor(0x2902) // '00002902-...'\n * ```\n *\n * @see {@link resolveUUID} for the lenient SDK-level resolver\n */\nexport function getDescriptor(name: string | number): string {\n return resolveUUIDName(name, DESCRIPTORS, 'getDescriptor');\n}\n\n/**\n * BluetoothUUID namespace object conforming to the Web Bluetooth spec.\n * Can be assigned to `window.BluetoothUUID` for spec compliance.\n * Each getter is scoped to its own GATT assigned-numbers table (§7.1).\n */\nexport const BluetoothUUID = {\n canonicalUUID,\n getService: (name: string | number) => resolveUUIDName(name, SERVICES, 'getService'),\n getCharacteristic: (name: string | number) => resolveUUIDName(name, CHARACTERISTICS, 'getCharacteristic'),\n getDescriptor,\n} as const;\n"]}
var e={STATE_CHANGE:"beacio:statechange",READY:"beacio:ready",INSTALLED_INACTIVE:"beacio:installedinactive",NOT_INSTALLED:"beacio:notinstalled",EXTENSION_READY:"beacio:extension:ready",EXTENSION_PING:"beacio:extension:ping",EXTENSION_PONG:"beacio:extension:pong",EXTENSION_ACTIVATE_REQUEST:"beacio:extension:activate-request",EXTENSION_ACTIVATE_RESULT:"beacio:extension:activate-result",EXTENSION_INSTALLED:"beacio:extension:installed",EXTENSION_STATUS_CHANGE:"beacio:extension:statuschange"};export{e as a};//# sourceMappingURL=chunk-3BDZNBBD.mjs.map
//# sourceMappingURL=chunk-3BDZNBBD.mjs.map
{"version":3,"sources":["../src/events.ts"],"names":["BEACIO_EVENTS"],"mappings":"AAiBO,IAAMA,CAAAA,CAAgB,CAG3B,YAAA,CAAc,oBAAA,CAEd,MAAO,cAAA,CAEP,kBAAA,CAAoB,0BAAA,CAEpB,aAAA,CAAe,qBAAA,CAIf,eAAA,CAAiB,yBAEjB,cAAA,CAAgB,uBAAA,CAEhB,cAAA,CAAgB,uBAAA,CAEhB,0BAAA,CAA4B,mCAAA,CAE5B,0BAA2B,kCAAA,CAE3B,mBAAA,CAAqB,4BAAA,CAErB,uBAAA,CAAyB,+BAC3B","file":"chunk-3BDZNBBD.mjs","sourcesContent":["/**\n * Canonical beacio CustomEvent names — the single source of truth shared by the\n * detect dispatcher (@beacio/detect), the react-sdk listeners (@beacio/react),\n * the extension in-page handshake, and the CDN bundle.\n *\n * Lives in @beacio/core for the same reason as `urls.ts`: core depends on\n * nothing, while @beacio/detect (the dispatcher) and @beacio/react (a listener)\n * both peer-depend on core — so importing the event-name constants FROM core\n * introduces no dependency cycle. Both the dispatch side and every listen side\n * reference these literals, so a diverged or typo'd event name becomes a\n * compile error rather than a silent half-rebrand break (a listener registered\n * on a name nobody dispatches).\n *\n * `as const` pins each value to its string-literal type (not widened to\n * `string`); dispatch/listen sites typed against {@link BeacioEventName} reject\n * any non-member string at compile time.\n */\nexport const BEACIO_EVENTS = {\n // ── @beacio/detect package lifecycle (public) ────────────────────────────\n /** Fired on every initBeacio() run with the resolved install state. */\n STATE_CHANGE: 'beacio:statechange',\n /** Fired when the extension is detected and active/ready. */\n READY: 'beacio:ready',\n /** Fired when the extension is installed but Safari still needs activation. */\n INSTALLED_INACTIVE: 'beacio:installedinactive',\n /** Fired when the extension is not installed. */\n NOT_INSTALLED: 'beacio:notinstalled',\n\n // ── In-page extension handshake (extension ⇄ page / cdn / react-sdk) ──────\n /** The extension's injected script announces it is live and active. */\n EXTENSION_READY: 'beacio:extension:ready',\n /** Page → extension liveness probe. */\n EXTENSION_PING: 'beacio:extension:ping',\n /** Extension → page liveness response. */\n EXTENSION_PONG: 'beacio:extension:pong',\n /** Page → extension request to activate the API. */\n EXTENSION_ACTIVATE_REQUEST: 'beacio:extension:activate-request',\n /** Extension → page result of an activate request. */\n EXTENSION_ACTIVATE_RESULT: 'beacio:extension:activate-result',\n /** Extension announces it is installed (present, not yet active). */\n EXTENSION_INSTALLED: 'beacio:extension:installed',\n /** Extension → page: the injected script's __beacio status transitioned. */\n EXTENSION_STATUS_CHANGE: 'beacio:extension:statuschange',\n} as const;\n\n/**\n * The union of every canonical beacio CustomEvent name. A value typed as this\n * cannot be any string other than a {@link BEACIO_EVENTS} member, so a typo at a\n * dispatch or listen site fails to compile.\n */\nexport type BeacioEventName = (typeof BEACIO_EVENTS)[keyof typeof BEACIO_EVENTS];\n"]}
import {a as a$1,g,i,l,k,h,c}from'./chunk-TZAX4UTD.mjs';import {a as a$2}from'./chunk-L7SIDO2A.mjs';import {a}from'./chunk-3BDZNBBD.mjs';var T={buttonText:"Start Setup",dismiss:"Not now",dontShowAgain:"Don't show again",states:{"not-installed":{title:"Set Up Bluetooth in Safari",body:"Follow the steps below to enable Bluetooth and return to {operator}."},"installed-inactive":{title:"Enable beacio in Safari",body:"beacio is installed but the Safari extension is off. Open Settings \u2192 Apps \u2192 Safari \u2192 Extensions \u2192 beacio and turn on Allow Extension, then return here."},denied:{title:"Allow beacio on this site",body:"beacio is enabled but not yet allowed here. Tap the aA button in the address bar \u2192 Manage Extensions \u2192 beacio \u2192 Allow Every Website, then reload this page."},"private-browsing":{title:"Private Browsing blocks extensions",body:"Private Browsing disables Safari extensions, so beacio cannot run here \u2014 even if it is installed. Open this page in a normal tab to connect your device."}},steps:[{label:"Install beacio",why:"A free one-time companion app from the App Store."},{label:"Open the app once",why:"This registers the Safari extension with iOS."},{label:"Enable in Safari Settings",why:"Settings \u2192 Apps \u2192 Safari \u2192 Extensions \u2192 beacio \u2192 turn on Allow Extension."},{label:"Allow website access",why:"On the site, tap the aA button in the address bar \u2192 Manage Extensions \u2192 beacio \u2192 Allow Every Website."},{label:"Allow Bluetooth on first scan",why:"The first time you connect, Safari will ask to allow this site \u2014 tap Allow."},{label:"Return and reload",why:"Come back to this page, reload, and tap Connect."}],returnCta:"Return to {operator}",clipboardHint:"Link also copied \u2014 paste it into Safari if this button does not reopen {operator}.",reload:"Reload page to re-check",howSummary:"How does setup work?",howBody:"beacio uses a one-time iPhone app to enable the Safari extension. After enabling it and allowing access on this site (aA button \u2192 Manage Extensions \u2192 Allow Every Website), Bluetooth works in Safari.",howLink:"See the full setup guide",privacySummary:"Privacy: No data collected",privacyBody:"beacio processes all Bluetooth data locally on your device. No browsing data, device data, or personal information is ever collected or transmitted.",stillStuck:"Still stuck? Open the setup guide",barTitle:"Enable Bluetooth",barText:"Install Beacio, open the app, enable the Safari extension, then return here.",readyToast:"beacio is ready \u2014 tap Connect to pair your device with {operator}.",error:{dismiss:"Dismiss",retry:"Try again",titles:{INVALID_PARAMETER:"Something went wrong",BLUETOOTH_UNAVAILABLE:"Bluetooth is unavailable",EXTENSION_NOT_INSTALLED:"Finish Bluetooth setup",PERMISSION_DENIED:"Allow Bluetooth to continue",DEVICE_NOT_FOUND:"No device found",DEVICE_DISCONNECTED:"Device disconnected",CONNECTION_TIMEOUT:"Connection timed out",SERVICE_NOT_FOUND:"Device feature unavailable",CHARACTERISTIC_NOT_FOUND:"Device feature unavailable",CHARACTERISTIC_NOT_READABLE:"Cannot read from device",CHARACTERISTIC_NOT_WRITABLE:"Cannot send to device",CHARACTERISTIC_NOT_NOTIFIABLE:"Live updates unavailable",GATT_OPERATION_FAILED:"Connection interrupted",SCAN_ALREADY_IN_PROGRESS:"Already searching",CONNECTION_LIMIT_REACHED:"Too many devices connected",USER_CANCELLED:"Connection cancelled",TIMEOUT:"Operation timed out",WRITE_INCOMPLETE:"Send incomplete"},messages:{INVALID_PARAMETER:"The request could not be completed. Please reload the page and try again.",BLUETOOTH_UNAVAILABLE:"Turn Bluetooth on, then try again.",EXTENSION_NOT_INSTALLED:"Bluetooth is not enabled for this site yet. Finish setup, then try connecting again.",PERMISSION_DENIED:"Bluetooth access was not granted. Tap Connect yourself (Bluetooth needs a tap), then allow access when asked.",DEVICE_NOT_FOUND:"No matching device was found. Switch your device on, keep it close, then try again.",DEVICE_DISCONNECTED:"The connection to your device was lost. Reconnect to continue.",CONNECTION_TIMEOUT:"Your device did not respond in time. Keep it close and powered on, then try again.",SERVICE_NOT_FOUND:"A required feature was not found on this device. Check that it is the right device and try again.",CHARACTERISTIC_NOT_FOUND:"A required feature was not found on this device. Check that it is the right device and try again.",CHARACTERISTIC_NOT_READABLE:"This value cannot be read from your device. No action is needed for this control.",CHARACTERISTIC_NOT_WRITABLE:"This value cannot be sent to your device. No action is needed for this control.",CHARACTERISTIC_NOT_NOTIFIABLE:"This value does not support live updates on your device.",GATT_OPERATION_FAILED:"Something interrupted the connection. Switch your device off and on, then try again.",SCAN_ALREADY_IN_PROGRESS:"A device search is already running. Wait a moment, then try again.",CONNECTION_LIMIT_REACHED:"Disconnect another device before connecting a new one.",USER_CANCELLED:"No device was selected. Tap Connect to try again whenever you are ready.",TIMEOUT:"That took too long. Check your device is close and powered on, then try again.",WRITE_INCOMPLETE:"Only part of the data reached your device. Try again to resend it."},generic:{title:"Something went wrong",body:"Something interrupted the connection. Please try again."}}},Q={buttonText:"Einrichtung starten",dismiss:"Jetzt nicht",dontShowAgain:"Nicht mehr anzeigen",states:{"not-installed":{title:"Bluetooth in Safari einrichten",body:"Folge den Schritten unten, um Bluetooth zu aktivieren und zu {operator} zur\xFCckzukehren."},"installed-inactive":{title:"beacio in Safari aktivieren",body:"beacio ist installiert, aber die Safari-Erweiterung ist deaktiviert. \xD6ffne Einstellungen \u2192 Apps \u2192 Safari \u2192 Erweiterungen \u2192 beacio und aktiviere \u201EErweiterung erlauben\u201C, kehre dann hierher zur\xFCck."},denied:{title:"beacio f\xFCr diese Seite erlauben",body:"beacio ist aktiviert, aber f\xFCr diese Seite noch nicht erlaubt. Tippe auf die Schaltfl\xE4che \u201EaA\u201C in der Adressleiste \u2192 Erweiterungen verwalten \u2192 beacio \u2192 \u201EAuf allen Websites erlauben\u201C und lade diese Seite dann neu."},"private-browsing":{title:"Privates Surfen blockiert Erweiterungen",body:"Im privaten Surfmodus sind Safari-Erweiterungen deaktiviert, daher kann beacio hier nicht laufen \u2014 auch wenn es installiert ist. \xD6ffne diese Seite in einem normalen Tab, um dein Ger\xE4t zu verbinden."}},steps:[{label:"beacio installieren",why:"Eine kostenlose, einmalige Begleit-App aus dem App Store."},{label:"App einmal \xF6ffnen",why:"Damit wird die Safari-Erweiterung bei iOS registriert."},{label:"In den Safari-Einstellungen aktivieren",why:"Einstellungen \u2192 Apps \u2192 Safari \u2192 Erweiterungen \u2192 beacio \u2192 \u201EErweiterung erlauben\u201C aktivieren."},{label:"Website-Zugriff erlauben",why:"Tippe auf der Seite auf die Schaltfl\xE4che \u201EaA\u201C in der Adressleiste \u2192 Erweiterungen verwalten \u2192 beacio \u2192 \u201EAuf allen Websites erlauben\u201C."},{label:"Bluetooth beim ersten Scan erlauben",why:"Beim ersten Verbinden fragt Safari, ob diese Seite zugreifen darf \u2014 tippe auf \u201EErlauben\u201C."},{label:"Zur\xFCckkehren und neu laden",why:"Komm zu dieser Seite zur\xFCck, lade sie neu und tippe auf \u201EVerbinden\u201C."}],returnCta:"Zur\xFCck zu {operator}",clipboardHint:"Link wurde au\xDFerdem kopiert \u2014 f\xFCge ihn in Safari ein, falls diese Schaltfl\xE4che {operator} nicht erneut \xF6ffnet.",reload:"Seite neu laden und erneut pr\xFCfen",howSummary:"Wie funktioniert die Einrichtung?",howBody:"beacio nutzt eine einmalige iPhone-App, um die Safari-Erweiterung zu aktivieren. Sobald sie aktiviert und der Zugriff auf dieser Seite erlaubt ist (Schaltfl\xE4che \u201EaA\u201C \u2192 Erweiterungen verwalten \u2192 \u201EAuf allen Websites erlauben\u201C), funktioniert Bluetooth in Safari.",howLink:"Zur vollst\xE4ndigen Einrichtungsanleitung",privacySummary:"Datenschutz: Keine Datenerfassung",privacyBody:"beacio verarbeitet alle Bluetooth-Daten lokal auf deinem Ger\xE4t. Es werden niemals Browserdaten, Ger\xE4tedaten oder pers\xF6nliche Informationen erfasst oder \xFCbertragen.",stillStuck:"Kommst du nicht weiter? Einrichtungsanleitung \xF6ffnen",barTitle:"Bluetooth aktivieren",barText:"Installiere beacio, \xF6ffne die App, aktiviere die Safari-Erweiterung und kehre dann hierher zur\xFCck.",readyToast:"beacio ist bereit \u2014 tippe auf \u201EVerbinden\u201C, um dein Ger\xE4t mit {operator} zu koppeln.",error:{dismiss:"Schlie\xDFen",retry:"Erneut versuchen",titles:{INVALID_PARAMETER:"Etwas ist schiefgelaufen",BLUETOOTH_UNAVAILABLE:"Bluetooth ist nicht verf\xFCgbar",EXTENSION_NOT_INSTALLED:"Bluetooth-Einrichtung abschlie\xDFen",PERMISSION_DENIED:"Bluetooth erlauben, um fortzufahren",DEVICE_NOT_FOUND:"Kein Ger\xE4t gefunden",DEVICE_DISCONNECTED:"Ger\xE4t getrennt",CONNECTION_TIMEOUT:"Zeit\xFCberschreitung der Verbindung",SERVICE_NOT_FOUND:"Ger\xE4tefunktion nicht verf\xFCgbar",CHARACTERISTIC_NOT_FOUND:"Ger\xE4tefunktion nicht verf\xFCgbar",CHARACTERISTIC_NOT_READABLE:"Lesen vom Ger\xE4t nicht m\xF6glich",CHARACTERISTIC_NOT_WRITABLE:"Senden an das Ger\xE4t nicht m\xF6glich",CHARACTERISTIC_NOT_NOTIFIABLE:"Live-Aktualisierungen nicht verf\xFCgbar",GATT_OPERATION_FAILED:"Verbindung unterbrochen",SCAN_ALREADY_IN_PROGRESS:"Suche l\xE4uft bereits",CONNECTION_LIMIT_REACHED:"Zu viele Ger\xE4te verbunden",USER_CANCELLED:"Verbindung abgebrochen",TIMEOUT:"Zeit\xFCberschreitung des Vorgangs",WRITE_INCOMPLETE:"Senden unvollst\xE4ndig"},messages:{INVALID_PARAMETER:"Die Anfrage konnte nicht abgeschlossen werden. Lade die Seite neu und versuche es erneut.",BLUETOOTH_UNAVAILABLE:"Schalte Bluetooth ein und versuche es erneut.",EXTENSION_NOT_INSTALLED:"Bluetooth ist f\xFCr diese Seite noch nicht aktiviert. Schlie\xDFe die Einrichtung ab und versuche dann erneut, dich zu verbinden.",PERMISSION_DENIED:"Der Bluetooth-Zugriff wurde nicht gew\xE4hrt. Tippe selbst auf \u201EVerbinden\u201C (Bluetooth erfordert eine Ber\xFChrung) und erlaube den Zugriff, wenn du gefragt wirst.",DEVICE_NOT_FOUND:"Es wurde kein passendes Ger\xE4t gefunden. Schalte dein Ger\xE4t ein, halte es in der N\xE4he und versuche es erneut.",DEVICE_DISCONNECTED:"Die Verbindung zu deinem Ger\xE4t wurde unterbrochen. Verbinde dich erneut, um fortzufahren.",CONNECTION_TIMEOUT:"Dein Ger\xE4t hat nicht rechtzeitig geantwortet. Halte es in der N\xE4he und eingeschaltet und versuche es erneut.",SERVICE_NOT_FOUND:"Eine erforderliche Funktion wurde auf diesem Ger\xE4t nicht gefunden. Pr\xFCfe, ob es das richtige Ger\xE4t ist, und versuche es erneut.",CHARACTERISTIC_NOT_FOUND:"Eine erforderliche Funktion wurde auf diesem Ger\xE4t nicht gefunden. Pr\xFCfe, ob es das richtige Ger\xE4t ist, und versuche es erneut.",CHARACTERISTIC_NOT_READABLE:"Dieser Wert kann nicht von deinem Ger\xE4t gelesen werden. F\xFCr dieses Element ist keine Aktion erforderlich.",CHARACTERISTIC_NOT_WRITABLE:"Dieser Wert kann nicht an dein Ger\xE4t gesendet werden. F\xFCr dieses Element ist keine Aktion erforderlich.",CHARACTERISTIC_NOT_NOTIFIABLE:"Dieser Wert unterst\xFCtzt auf deinem Ger\xE4t keine Live-Aktualisierungen.",GATT_OPERATION_FAILED:"Etwas hat die Verbindung unterbrochen. Schalte dein Ger\xE4t aus und wieder ein und versuche es erneut.",SCAN_ALREADY_IN_PROGRESS:"Es l\xE4uft bereits eine Ger\xE4tesuche. Warte einen Moment und versuche es erneut.",CONNECTION_LIMIT_REACHED:"Trenne ein anderes Ger\xE4t, bevor du ein neues verbindest.",USER_CANCELLED:"Es wurde kein Ger\xE4t ausgew\xE4hlt. Tippe auf \u201EVerbinden\u201C, um es erneut zu versuchen, wann immer du bereit bist.",TIMEOUT:"Das hat zu lange gedauert. Pr\xFCfe, ob dein Ger\xE4t in der N\xE4he und eingeschaltet ist, und versuche es erneut.",WRITE_INCOMPLETE:"Nur ein Teil der Daten hat dein Ger\xE4t erreicht. Versuche es erneut, um sie noch einmal zu senden."},generic:{title:"Etwas ist schiefgelaufen",body:"Etwas hat die Verbindung unterbrochen. Bitte versuche es erneut."}}},F={en:T,de:Q};function G(e){return !e||typeof e!="string"?"":e.split("-",1)[0].trim().toLowerCase()}function ee(){if(!(typeof navigator>"u"))return navigator.language}function $(e,t){if(t==null)return e;if(Array.isArray(e)||typeof e!="object"||e===null)return t;let r={...e};for(let i of Object.keys(t)){let d=t[i];d!==void 0&&(r[i]=$(e[i],d));}return r}function y(e={}){let t=G(e.lang),r=t&&F[t]||F[G(ee())]||T;return e.strings?$(r,e.strings):r}var Ee=T.steps,te={"installed-inactive":[2,4,5],denied:[3,4,5],"private-browsing":[]},ne=a$2,j="beacio_ready_shown",W=a.READY,K=a.EXTENSION_READY,ie=5,re=300;function q(e){return e.startOnboardingUrl??e.appStoreUrl??ne}function oe(e,t={}){let r=typeof window<"u"?window.location.href:void 0,i=new URL(e,r);if(i.hostname==="apps.apple.com"){let c=i.pathname.match(/id\d+/)?.[0];return i.pathname=c?`/app/${c}`:new URL(a$1).pathname,t.apiKey&&!i.searchParams.has("ct")&&(i.searchParams.set("ct",t.apiKey),i.searchParams.set("mt","8")),i.toString()}return t.operatorName&&!i.searchParams.has("operatorName")&&i.searchParams.set("operatorName",t.operatorName),t.returnUrl&&!i.searchParams.has("return")&&i.searchParams.set("return",t.returnUrl),i.toString()}function Y(e,t,r){k();let i=l().url;window.location.href=oe(e,{apiKey:t,operatorName:r,returnUrl:i});}function n(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function S(e,t,r=""){return e.replace(/\{operator\}/g,t).replace(/\{device\}/g,r)}function Z(e){if(!e||typeof e!="string")return null;let t=typeof window<"u"?window.location.href:"https://beacio.com";try{let r=new URL(e,t);return r.protocol==="http:"||r.protocol==="https:"?r.href:null}catch{return null}}function ae(e){let{operatorName:t=document.title||window.location.hostname,apiKey:r,dismissDays:i$1=14,state:d="not-installed"}=e,c$1=q(e),o=y({lang:e.lang,strings:e.strings}),v=o.buttonText,u=d==="active"?"not-installed":d,{title:h$1}=o.states[u],f=e.body??o.states[u].body,s=e.setupUrl??c$1,w=l(),A=e.accentColor??"#007aff",g=Z(e.brandLogoUrl),N=e.deviceName??"",X=!!(e.accentColor||g||e.deviceName),O=e.privacyBody??o.privacyBody,J=(u==="not-installed"?o.steps:te[u].map(l=>o.steps[l])).map(l=>`<li class="bc-step"><span class="bc-step-l">${n(l.label)}</span><span class="bc-step-w">${n(l.why)}</span></li>`).join(""),a=document.createElement("div");a.id="beacio-banner",a.dataset.beacioState=u,a.innerHTML=`
<style>
/* SB-SDK-11: the partner accent is exposed as a single CSS custom property on the
sheet root; every accent rule below reads var(--bc-accent). When unthemed the
value defaults to the beacio Apple-blue, so the rendered sheet is unchanged. */
#bc-s{--bc-accent:${n(A)}}
#beacio-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-end;
justify-content:center;background:rgba(0,0,0,.4);font-family:-apple-system,BlinkMacSystemFont,
'SF Pro Text',system-ui,sans-serif;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
animation:bc-fi .25s ease-out}
@keyframes bc-fi{from{opacity:0}to{opacity:1}}
@keyframes bc-su{from{transform:translateY(100%)}to{transform:translateY(0)}}
#bc-s{background:#fff;border-radius:16px 16px 0 0;padding:12px 20px 28px;max-width:420px;
width:100%;animation:bc-su .3s ease-out;max-height:90vh;overflow-y:auto;
-webkit-overflow-scrolling:touch}
#bc-s *{box-sizing:border-box;margin:0;padding:0}
.bc-h{width:36px;height:5px;border-radius:3px;background:#d1d1d6;margin:0 auto 10px}
.bc-hdr{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.bc-ic{width:36px;height:36px;border-radius:9px;background:var(--bc-accent);display:flex;
align-items:center;justify-content:center;flex-shrink:0;overflow:hidden}
.bc-ic svg{width:20px;height:20px;fill:#fff}
.bc-ic img{width:100%;height:100%;object-fit:contain}
.bc-tt{font-size:16px;font-weight:600;color:#000}
.bc-bd{font-size:13px;line-height:1.35;color:#8e8e93;margin-bottom:12px}
.bc-steps{list-style:none;margin:0 0 14px;padding:0;counter-reset:bc-step}
.bc-step{position:relative;padding:0 0 8px 28px;font-size:13px;line-height:1.35}
.bc-step::before{counter-increment:bc-step;content:counter(bc-step);position:absolute;left:0;top:0;
width:18px;height:18px;border-radius:50%;background:var(--bc-accent);color:#fff;font-size:11px;
font-weight:600;display:flex;align-items:center;justify-content:center}
.bc-step-l{display:block;font-weight:600;color:#1c1c1e}
.bc-step-w{display:block;color:#8e8e93;margin-top:1px;font-size:12px}
.bc-btn{display:block;width:100%;padding:12px;background:var(--bc-accent);color:#fff;border:none;
border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;text-align:center;
text-decoration:none;-webkit-tap-highlight-color:transparent}
.bc-btn:active{opacity:.85}
.bc-ret{display:block;width:100%;padding:12px;margin-top:8px;background:#34c759;color:#fff;border:none;
border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;text-align:center;
text-decoration:none;-webkit-tap-highlight-color:transparent}
.bc-ret:active{opacity:.85}
.bc-cb{font-size:11px;color:#8e8e93;text-align:center;margin-top:6px}
/* SB-SDK-11 AC3: VISIBLE trust surfaces (not the collapsed <details>) \u2014 the
medical-market "No data collected" reassurance + the no-affiliation microcopy. */
.bc-privacy{font-size:12px;color:#8e8e93;line-height:1.4;margin-top:12px}
.bc-noaff{font-size:11px;color:#8e8e93;line-height:1.3;margin-top:6px;text-align:center}
.bc-det{margin-top:10px}
.bc-det summary{font-size:13px;color:var(--bc-accent);cursor:pointer;list-style:none;padding:2px 0}
.bc-det summary::before{content:'\\25B8 '}
.bc-det[open] summary::before{content:'\\25BE '}
.bc-det p{font-size:12px;color:#8e8e93;line-height:1.4;padding:6px 0 2px}
.bc-det a{color:var(--bc-accent)}
.bc-stuck{display:block;font-size:12px;color:var(--bc-accent);text-align:center;margin-top:10px;
text-decoration:none}
.bc-reload{display:block;width:100%;padding:11px;margin-top:8px;background:none;
border:1px solid var(--bc-accent);border-radius:12px;font-size:15px;font-weight:600;color:var(--bc-accent);
cursor:pointer;text-align:center;-webkit-tap-highlight-color:transparent}
.bc-reload:active{opacity:.7}
.bc-dis{display:block;width:100%;padding:8px;background:none;border:none;font-size:14px;
color:#8e8e93;cursor:pointer;text-align:center;margin-top:4px;
-webkit-tap-highlight-color:transparent}
/* SB-PRD-08: the explicit long opt-out (#bc-dont-show) is visually quieter than the
soft dismiss (#bc-dismiss) above it \u2014 smaller, less padding \u2014 so the soft dismiss
stays the default gesture and the long opt-out is a deliberate secondary choice.
NB keep this comment free of literal UI copy: the <style> block is part of the
banner innerHTML, so any English token here would leak into the localized DOM
(i18n.test.ts no-English-leak guard). */
.bc-dont{font-size:12px;padding:4px 12px;margin-top:0}
@media(prefers-color-scheme:dark){
#bc-s{background:#1c1c1e}
.bc-tt,.bc-step-l{color:#fff}
.bc-bd,.bc-step-w,.bc-cb,.bc-det p,.bc-privacy,.bc-noaff{color:#98989f}
.bc-dis{color:#98989f}
.bc-reload{color:#0a84ff;border-color:#0a84ff}
.bc-h{background:#48484a}
}
</style>
<div id="beacio-overlay">
<div id="bc-s" role="dialog" aria-label="${n(h$1)}">
<div class="bc-h"></div>
<div class="bc-hdr">
<div class="bc-ic">${g?`<img src="${n(g)}" alt="" aria-hidden="true">`:'<svg viewBox="0 0 24 24"><path d="M12 2L7 7l5 5-5 5 5 5V2zm0 6.83L10.83 7 12 5.83v2.34zm0 8.34L10.83 17 12 15.83v1.34zM17 7l-5 5 5 5-2.12 2.12L12 17l-2.88 2.12L7 17l5-5-5-5 2.12-2.12L12 7l2.88-2.12L17 7z"/></svg>'}</div>
<div class="bc-tt">${n(h$1)}</div>
</div>
<div class="bc-bd">${n(S(f,t,N))}</div>
<ol class="bc-steps">${J}</ol>
${u==="not-installed"?`<button class="bc-btn" id="bc-install">${n(v)}</button>`:""}
<a class="bc-ret" id="bc-return" href="${n(w.returnLink)}">${n(S(o.returnCta,t))}</a>
<p class="bc-cb">${n(S(o.clipboardHint,t))}</p>
<button class="bc-reload" id="bc-reload">${n(o.reload)}</button>
<details class="bc-det"><summary>${n(o.howSummary)}</summary><p>${n(o.howBody)} <a href="${n(s)}" target="_blank" rel="noopener">${n(o.howLink)}</a>.</p></details>
<details class="bc-det"><summary>${n(o.privacySummary)}</summary><p>${n(O)}</p></details>
${X?`<p class="bc-privacy" id="bc-privacy">${n(o.privacySummary)} \u2014 ${n(O)}</p><p class="bc-noaff" id="bc-noaff">beacio is an independent Safari extension and is not affiliated with the device maker.</p>`:""}
<a class="bc-stuck" id="bc-stuck" href="${n(s)}" target="_blank" rel="noopener">${n(o.stillStuck)}</a>
<button class="bc-dis" id="bc-dismiss">${n(o.dismiss)}</button>
<button class="bc-dis bc-dont" id="bc-dont-show">${n(o.dontShowAgain)}</button>
</div>
</div>`,k();let R=e.forceShow===true,p=null,x=false;function E(){x=true,p!==null&&(clearTimeout(p),p=null),window.removeEventListener(W,k$1),window.removeEventListener(K,L),document.removeEventListener("visibilitychange",B);}function b(){return c()?(E(),a.remove(),true):false}function k$1(){b();}function L(){b()||D();}function D(){if(x||p!==null)return;let l=0,U=()=>{p=null,!x&&(b()||(l+=1,!(l>=ie)&&(p=setTimeout(U,re))));};U();}function B(){document.visibilityState==="visible"&&(b()||D());}return R||(window.addEventListener(W,k$1),window.addEventListener(K,L),document.addEventListener("visibilitychange",B)),requestAnimationFrame(()=>{a.querySelector("#bc-install")?.addEventListener("click",()=>{Y(c$1,r,t);}),a.querySelector("#bc-reload")?.addEventListener("click",()=>{b()||window.location.reload();}),a.querySelector("#bc-dismiss")?.addEventListener("click",()=>{E(),a.remove(),i();}),a.querySelector("#bc-dont-show")?.addEventListener("click",()=>{E(),a.remove(),h(i$1);}),a.querySelector("#beacio-overlay")?.addEventListener("click",l=>{l.target.id==="beacio-overlay"&&(E(),a.remove(),i());});}),document.body.appendChild(a),R||b(),a}function se(e){let{position:t="bottom",style:r={},apiKey:i$1,operatorName:d}=e,c=y({lang:e.lang,strings:e.strings}),o=c.barText,v=c.buttonText,u=q(e),h=e.accentColor??"#007AFF",f=Z(e.brandLogoUrl),s=document.createElement("div");s.id="beacio-banner";let w=t==="top"?"top:0;border-bottom:1px solid #e5e7eb;":"bottom:0;border-top:1px solid #e5e7eb;",A=Object.entries(r).map(([g,N])=>`${g}:${N}`).join(";");return s.innerHTML=`
<div style="position:fixed;${w}left:0;right:0;z-index:2147483646;
background:#fff;padding:16px;
display:flex;align-items:center;gap:12px;font-family:system-ui,-apple-system,sans-serif;
box-shadow:0 ${t==="top"?"2px":"-2px"} 10px rgba(0,0,0,0.1);${A}">
${f?`<img src="${n(f)}" alt="" aria-hidden="true" width="24" height="24" style="object-fit:contain;flex-shrink:0">`:`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="${n(h)}"/>
<path d="M12 7a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V8a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" fill="white"/>
</svg>`}
<div style="flex:1">
<div style="font-size:14px;font-weight:600;color:#1f2937">${n(c.barTitle)}</div>
<div style="font-size:12px;color:#6b7280;margin-top:2px">${n(o)}</div>
</div>
<button id="beacio-banner-install"
style="background:${n(h)};color:white;padding:8px 16px;border-radius:8px;
border:none;font-size:14px;font-weight:500;white-space:nowrap;cursor:pointer">
${n(v)}</button>
<button id="beacio-banner-close"
style="background:none;border:none;color:#9ca3af;font-size:20px;
cursor:pointer;padding:4px;line-height:1"
aria-label="Close">&times;</button>
</div>`,s.querySelector("#beacio-banner-install")?.addEventListener("click",()=>{Y(u,i$1,d);}),s.querySelector("#beacio-banner-close")?.addEventListener("click",()=>{s.remove(),i();}),document.body.appendChild(s),s}function ce(){try{return localStorage.getItem(j)==="1"}catch{return false}}function le(){try{localStorage.setItem(j,"1");}catch{}}function de(e){if(ce())return null;le();let t=e.operatorName||document.title||window.location.hostname,r=y({lang:e.lang,strings:e.strings}),i=document.createElement("div");return i.id="beacio-banner",i.dataset.beacioState="active",i.innerHTML=`
<style>
#bc-toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483647;
max-width:420px;width:calc(100% - 32px);background:#34c759;color:#fff;border-radius:14px;
padding:14px 16px;display:flex;align-items:center;gap:12px;
font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;
box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bc-tu .3s ease-out}
@keyframes bc-tu{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
#bc-toast svg{width:22px;height:22px;flex-shrink:0;fill:#fff}
.bc-toast-tx{flex:1;font-size:15px;font-weight:600;line-height:1.3}
#bc-toast-x{background:none;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0 4px;
line-height:1;-webkit-tap-highlight-color:transparent}
</style>
<div id="bc-toast" role="status">
<svg viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
<span class="bc-toast-tx">${n(S(r.readyToast,t))}</span>
<button id="bc-toast-x" aria-label="Dismiss">&times;</button>
</div>`,requestAnimationFrame(()=>{i.querySelector("#bc-toast-x")?.addEventListener("click",()=>i.remove());}),document.body.appendChild(i),i}function me(e={}){return e.state==="active"?de(e):!e.forceShow&&g()?null:e.mode==="banner"?se(e):ae(e)}function Te(){let e=document.getElementById("beacio-banner");e&&e.remove();}export{T as a,Q as b,y as c,Ee as d,oe as e,me as f,Te as g};//# sourceMappingURL=chunk-5NAVIZD7.mjs.map
//# sourceMappingURL=chunk-5NAVIZD7.mjs.map

Sorry, the diff of this file is too big to display

var u=class{constructor(e,t){this._connected=false;this._services=new Map;this._device=e;for(let i of t)this._services.set(i.uuid,new l(e,i));}get connected(){return this._connected}async connect(){if(this._device.shouldFailConnect())throw new DOMException("Simulated transient connection failure","NetworkError");return this._connected=true,this.asBluetoothRemoteGATTServer()}disconnect(){this._connected=false;for(let e of this._services.values())e.stopAllNotifications();}async getPrimaryService(e){this._assertConnected();let t=this._services.get(e);if(!t)throw new DOMException(`No Services matching UUID ${e} found`,"NotFoundError");return t.asBluetoothRemoteGATTService()}async getPrimaryServices(e){return this._assertConnected(),(e?[this._services.get(e)].filter(Boolean):Array.from(this._services.values())).map(i=>i.asBluetoothRemoteGATTService())}getService(e){return this._services.get(e)}asBluetoothRemoteGATTServer(e){let t=this;return {get connected(){return t._connected},get device(){return e},connect:()=>t.connect(),disconnect:()=>t.disconnect(),getPrimaryService:r=>t.getPrimaryService(r),getPrimaryServices:r=>t.getPrimaryServices(r)}}_assertConnected(){if(!this._connected)throw new DOMException("GATT Server is disconnected. Cannot perform GATT operations.","NetworkError")}},l=class{constructor(e,t){this._characteristics=new Map;this.uuid=t.uuid,this.isPrimary=t.isPrimary??true;for(let i of t.characteristics??[])this._characteristics.set(i.uuid,new v(i));}async getCharacteristic(e){let t=this._characteristics.get(e);if(!t)throw new DOMException(`No Characteristics matching UUID ${e} found`,"NotFoundError");return t.asBluetoothRemoteGATTCharacteristic(this.asBluetoothRemoteGATTService())}async getCharacteristics(e){let t=e?[this._characteristics.get(e)].filter(Boolean):Array.from(this._characteristics.values()),i=this.asBluetoothRemoteGATTService();return t.map(r=>r.asBluetoothRemoteGATTCharacteristic(i))}getChar(e){return this._characteristics.get(e)}stopAllNotifications(){for(let e of this._characteristics.values())e.stopNotifications();}asBluetoothRemoteGATTService(e){let t=this;return {uuid:this.uuid,isPrimary:this.isPrimary,get device(){return e},getCharacteristic:i=>t.getCharacteristic(i),getCharacteristics:i=>t.getCharacteristics(i),getIncludedService:async()=>{throw new DOMException("Not implemented","NotSupportedError")},getIncludedServices:async()=>[],addEventListener:()=>{},removeEventListener:()=>{},dispatchEvent:()=>true,oncharacteristicvaluechanged:null,onserviceadded:null,onservicechanged:null,onserviceremoved:null}}},v=class{constructor(e){this._notifying=false;this._listeners=new Map;this._descriptors=new Map;if(this.uuid=e.uuid,this._properties={broadcast:e.properties?.broadcast??false,read:e.properties?.read??true,write:e.properties?.write??false,writeWithoutResponse:e.properties?.writeWithoutResponse??false,notify:e.properties?.notify??false,indicate:e.properties?.indicate??false,authenticatedSignedWrites:e.properties?.authenticatedSignedWrites??false,reliableWrite:e.properties?.reliableWrite??false,writableAuxiliaries:e.properties?.writableAuxiliaries??false},e.value){let t=e.value instanceof Uint8Array?e.value.buffer.slice(e.value.byteOffset,e.value.byteOffset+e.value.byteLength):e.value;this._value=new DataView(t);}else this._value=new DataView(new ArrayBuffer(0));for(let t of e.descriptors??[])this._descriptors.set(t.uuid,new h(t));}setValue(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);}emitNotification(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);let i=new Event("characteristicvaluechanged");Object.defineProperty(i,"target",{value:{value:this._value},writable:false});let r=this._listeners.get("characteristicvaluechanged");if(r)for(let c of r)c(i);}stopNotifications(){this._notifying=false;}get isNotifying(){return this._notifying}getDesc(e){return this._descriptors.get(e)}asBluetoothRemoteGATTCharacteristic(e){let t=this;return {uuid:this.uuid,service:e,properties:{broadcast:this._properties.broadcast,read:this._properties.read,writeWithoutResponse:this._properties.writeWithoutResponse,write:this._properties.write,notify:this._properties.notify,indicate:this._properties.indicate,authenticatedSignedWrites:this._properties.authenticatedSignedWrites,reliableWrite:this._properties.reliableWrite,writableAuxiliaries:this._properties.writableAuxiliaries},get value(){return t._value},readValue:async()=>{if(!t._properties.read)throw new DOMException("Characteristic does not support read","NotSupportedError");return t._value},writeValue:async i=>{if(!t._properties.write)throw new DOMException("Characteristic does not support write","NotSupportedError");t._writeValue(i);},writeValueWithResponse:async i=>{if(!t._properties.write)throw new DOMException("Characteristic does not support write","NotSupportedError");t._writeValue(i);},writeValueWithoutResponse:async i=>{if(!t._properties.writeWithoutResponse)throw new DOMException("Characteristic does not support write without response","NotSupportedError");t._writeValue(i);},startNotifications:async function(){if(!t._properties.notify&&!t._properties.indicate)throw new DOMException("Characteristic does not support notifications","NotSupportedError");return t._notifying=true,this},stopNotifications:async function(){return t._notifying=false,this},addEventListener:(i,r)=>{t._listeners.has(i)||t._listeners.set(i,new Set),t._listeners.get(i).add(r);},removeEventListener:(i,r)=>{t._listeners.get(i)?.delete(r);},dispatchEvent:()=>true,getDescriptor:async i=>{let r=t._descriptors.get(i);if(!r)throw new DOMException(`No Descriptors matching UUID ${i} found`,"NotFoundError");return r.asBluetoothRemoteGATTDescriptor(t.asBluetoothRemoteGATTCharacteristic(e))},getDescriptors:async i=>{let r=i?[t._descriptors.get(i)].filter(Boolean):Array.from(t._descriptors.values()),c=t.asBluetoothRemoteGATTCharacteristic(e);return r.map(m=>m.asBluetoothRemoteGATTDescriptor(c))},oncharacteristicvaluechanged:null}}_writeValue(e){let t=e instanceof ArrayBuffer?e:e.buffer??e.buffer;this._value=new DataView(t);}},h=class{constructor(e){if(this.uuid=e.uuid,e.value){let t=e.value instanceof Uint8Array?e.value.buffer.slice(e.value.byteOffset,e.value.byteOffset+e.value.byteLength):e.value;this._value=new DataView(t);}else this._value=new DataView(new ArrayBuffer(0));}setValue(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);}get value(){return this._value}asBluetoothRemoteGATTDescriptor(e){let t=this;return {uuid:this.uuid,characteristic:e,get value(){return t._value},readValue:async()=>t._value,writeValue:async i=>{let r=i instanceof ArrayBuffer?i:i.buffer??i.buffer;t._value=new DataView(r);}}}};var g=0,a=class{constructor(e={}){this._listeners=new Map;this._watchingAdvertisements=false;this.id=e.id??`mock-device-${++g}`,this.name=e.name,this._serviceUUIDs=e.serviceUUIDs??[],this._gatt=new u(this,e.services??[]),this._rssi=e.rssi??-60,this._remainingConnectFailures=e.failConnectAttempts??0,this._writeLimits={withResponse:e.writeLimits?.withResponse??null,withoutResponse:e.writeLimits?.withoutResponse??null,mtu:e.writeLimits?.mtu??null};}matchesFilter(e){return !(e.services&&!e.services.some(i=>this._serviceUUIDs.includes(String(i)))||e.name&&e.name!==this.name||e.namePrefix&&!this.name?.startsWith(e.namePrefix))}asBluetoothDevice(){let e=this,t={id:this.id,name:this.name??null,gatt:null,watchAdvertisements:async i=>{if(e._watchingAdvertisements=true,i?.signal){if(i.signal.aborted){e._watchingAdvertisements=false;return}i.signal.addEventListener("abort",()=>{e._watchingAdvertisements=false;},{once:true});}},addEventListener:(i,r)=>{e._addListener(i,r);},removeEventListener:(i,r)=>{e._removeListener(i,r);},dispatchEvent:i=>true,get watchingAdvertisements(){return e._watchingAdvertisements},unwatchAdvertisements:async()=>{e._watchingAdvertisements=false;},forget:async()=>{},onadvertisementreceived:null,ongattserverdisconnected:null,oncharacteristicvaluechanged:null,onserviceadded:null,onservicechanged:null,onserviceremoved:null};return t.gatt=this._gatt.asBluetoothRemoteGATTServer(t),t.gatt.getMtu=async()=>this._writeLimits.mtu,t.gatt.getWriteLimits=async()=>({...this._writeLimits}),t}shouldFailConnect(){return this._remainingConnectFailures<=0?false:(this._remainingConnectFailures-=1,true)}simulateDisconnect(){this._gatt.disconnect(),this._emit("gattserverdisconnected",new Event("gattserverdisconnected"));}get gatt(){return this._gatt}get serviceUUIDs(){return this._serviceUUIDs}get rssi(){return this._rssi}emitAdvertisement(e={}){if(this._advertisementSink){this._advertisementSink(this,e);return}this.dispatchAdvertisementEvent(e);}setRSSI(e){this._rssi=e;}setAdvertisementSink(e){this._advertisementSink=e;}dispatchAdvertisementEvent(e={}){this._watchingAdvertisements&&this._emit("advertisementreceived",this.createAdvertisementEvent(this.asBluetoothDevice(),e));}createAdvertisementEvent(e,t={}){let i=new Event("advertisementreceived");return Object.defineProperties(i,{device:{value:e,writable:false},name:{value:this.name,writable:false},uuids:{value:[...t.uuids??this._serviceUUIDs],writable:false},rssi:{value:t.rssi??this._rssi,writable:false},txPower:{value:t.txPower,writable:false},manufacturerData:{value:t.manufacturerData??new Map,writable:false},serviceData:{value:t.serviceData??new Map,writable:false}}),i}_addListener(e,t){this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add(t);}_removeListener(e,t){this._listeners.get(e)?.delete(t);}_emit(e,t){let i=this._listeners.get(e);if(i)for(let r of i)r(t);}};var o=()=>Promise.reject(new DOMException("Beacio extension API not implemented in MockBluetooth","NotSupportedError")),d=()=>{},f=class{constructor(e={}){this._devices=new Map;this._listeners=new Map;this._scanActive=false;this.backgroundSync={requestPermission:o,requestBackgroundConnection:o,registerCharacteristicNotifications:o,registerBeaconScanning:o,getRegistrations:o,unregister:o,update:o,connect:o,subscribe:o,scan:o,list:o,destroy:d};this.peripheral={advertising:false,advertise:o,stopAdvertising:o,send:o,destroy:d,addEventListener:d,removeEventListener:d,onwriterequest:null,onsubscriptionchange:null,onconnectionstatechange:null,onadvertisingstatechange:null};this._handleAdvertisement=(e,t)=>{if(e.dispatchAdvertisementEvent(t),!this._scanActive||!this._matchesScan(e))return;let i=e.createAdvertisementEvent(e.asBluetoothDevice(),t),r=this._listeners.get("advertisementreceived");if(r)for(let c of r)c(i);};if(this._available=e.available??true,e.devices)for(let t of e.devices){let i=new a(t);i.setAdvertisementSink(this._handleAdvertisement),this._devices.set(i.id,i);}}async getAvailability(){return this._available}async requestDevice(e){if(!this._available)throw new DOMException("Bluetooth adapter not available","NotFoundError");let t=this._findMatchingDevices(e);if(t.length===0)throw new DOMException("No devices found matching the filter criteria","NotFoundError");return t[0].asBluetoothDevice()}async getDevices(){return Array.from(this._devices.values()).map(e=>e.asBluetoothDevice())}async requestLEScan(e){if(this._scanActive)throw new DOMException("Scan already in progress","InvalidStateError");this._scanActive=true,this._lastScanOptions=e;let t={active:true,keepRepeatedDevices:e?.keepRepeatedDevices??false,acceptAllAdvertisements:e?.acceptAllAdvertisements??false,stop:()=>{this._scanActive=false,this._lastScanOptions=void 0,t.active=false;}};return t}addEventListener(e,t){this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add(t);}removeEventListener(e,t){this._listeners.get(e)?.delete(t);}addDevice(e){let t=new a(e);return t.setAdvertisementSink(this._handleAdvertisement),this._devices.set(t.id,t),t}removeDevice(e){let t=this._devices.get(e);t&&(t.setAdvertisementSink(void 0),t.simulateDisconnect(),this._devices.delete(e));}getDevice(e){return this._devices.get(e)}setAvailable(e){this._available=e;}install(){return typeof globalThis.navigator>"u"?this:(this._installedNavigatorBluetooth=globalThis.navigator.bluetooth,Object.defineProperty(globalThis.navigator,"bluetooth",{value:this,writable:true,configurable:true}),this)}uninstall(){typeof globalThis.navigator>"u"||(Object.defineProperty(globalThis.navigator,"bluetooth",{value:this._installedNavigatorBluetooth,writable:true,configurable:true}),this._installedNavigatorBluetooth=void 0);}emitAdvertisement(e,t={}){let i=this._devices.get(e);if(!i)throw new Error(`Unknown mock device: ${e}`);this._handleAdvertisement(i,t);}reset(){for(let e of this._devices.values())e.setAdvertisementSink(void 0),e.simulateDisconnect();this._devices.clear(),this._listeners.clear(),this._scanActive=false,this._lastScanOptions=void 0,this._available=true;}_findMatchingDevices(e){if(!e||e.acceptAllDevices)return Array.from(this._devices.values());let t=e.filters??[];return Array.from(this._devices.values()).filter(i=>t.some(r=>i.matchesFilter(r)))}_matchesScan(e){let t=this._lastScanOptions;if(!t||t.acceptAllAdvertisements)return true;let i=t.filters??[];return i.length===0?true:i.some(r=>e.matchesFilter(r))}};function p(n){return new f(n)}function _(n){return p(n).install()}var s={services:{HEART_RATE:"0000180d-0000-1000-8000-00805f9b34fb",BATTERY:"0000180f-0000-1000-8000-00805f9b34fb",DEVICE_INFO:"0000180a-0000-1000-8000-00805f9b34fb",ENVIRONMENTAL_SENSING:"0000181a-0000-1000-8000-00805f9b34fb"},characteristics:{HEART_RATE_MEASUREMENT:"00002a37-0000-1000-8000-00805f9b34fb",BODY_SENSOR_LOCATION:"00002a38-0000-1000-8000-00805f9b34fb",BATTERY_LEVEL:"00002a19-0000-1000-8000-00805f9b34fb",MANUFACTURER_NAME:"00002a29-0000-1000-8000-00805f9b34fb",MODEL_NUMBER:"00002a24-0000-1000-8000-00805f9b34fb",TEMPERATURE:"00002a6e-0000-1000-8000-00805f9b34fb"},descriptors:{CCCD:"00002902-0000-1000-8000-00805f9b34fb",USER_DESCRIPTION:"00002901-0000-1000-8000-00805f9b34fb",PRESENTATION_FORMAT:"00002904-0000-1000-8000-00805f9b34fb"}},D={heartRate(n="Mock HR Sensor"){return {name:n,serviceUUIDs:[s.services.HEART_RATE],services:[{uuid:s.services.HEART_RATE,characteristics:[{uuid:s.characteristics.HEART_RATE_MEASUREMENT,properties:{read:true,notify:true},value:new Uint8Array([0,72])},{uuid:s.characteristics.BODY_SENSOR_LOCATION,properties:{read:true},value:new Uint8Array([1])}]}]}},battery(n="Mock Battery Device"){return {name:n,serviceUUIDs:[s.services.BATTERY],services:[{uuid:s.services.BATTERY,characteristics:[{uuid:s.characteristics.BATTERY_LEVEL,properties:{read:true,notify:true},value:new Uint8Array([85])}]}]}},full(n="Mock Full Device"){return {name:n,serviceUUIDs:[s.services.HEART_RATE,s.services.BATTERY,s.services.DEVICE_INFO],services:[{uuid:s.services.HEART_RATE,characteristics:[{uuid:s.characteristics.HEART_RATE_MEASUREMENT,properties:{read:true,notify:true},value:new Uint8Array([0,72])}]},{uuid:s.services.BATTERY,characteristics:[{uuid:s.characteristics.BATTERY_LEVEL,properties:{read:true,notify:true},value:new Uint8Array([100])}]},{uuid:s.services.DEVICE_INFO,characteristics:[{uuid:s.characteristics.MANUFACTURER_NAME,properties:{read:true},value:Uint8Array.from(Array.from("Beacio Test Corp").map(e=>e.charCodeAt(0)))},{uuid:s.characteristics.MODEL_NUMBER,properties:{read:true},value:Uint8Array.from(Array.from("WBT-001").map(e=>e.charCodeAt(0)))}]}]}}};export{u as a,l as b,v as c,h as d,a as e,f,p as g,_ as h,s as i,D as j};//# sourceMappingURL=chunk-67S2RHE2.mjs.map
//# sourceMappingURL=chunk-67S2RHE2.mjs.map
{"version":3,"sources":["../src/testing/mocks/characteristics.ts","../src/testing/mocks/device.ts","../src/testing/mocks/bluetooth.ts","../src/testing/index.ts"],"names":["MockGATTServer","device","configs","config","MockService","service","uuid","s","deviceProxy","self","_device","charConfig","MockCharacteristic","char","chars","c","buffer","descConfig","MockDescriptor","data","event","listeners","listener","value","type","desc","descriptors","charProxy","d","characteristic","deviceIdCounter","MockBleDevice","options","filter","proxy","_event","rssi","sink","unsupportedExtensionApi","noop","MockBluetooth","opts","matching","scan","id","available","deviceId","filters","createMockBluetooth","installMockBluetooth","BLE_UUIDS","devices","name"],"mappings":"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","file":"chunk-67S2RHE2.mjs","sourcesContent":["/**\n * Mock GATT Server, Services, and Characteristics\n *\n * Stateful mocks that simulate real BLE behavior:\n * - Characteristic reads return configured values\n * - Writes store values\n * - Notifications can be pumped programmatically\n */\n\nimport type { MockBleDevice } from './device';\n\nexport interface MockCharacteristicConfig {\n /** Characteristic UUID */\n uuid: string;\n /** Characteristic properties (all default to false except read) */\n properties?: {\n broadcast?: boolean;\n read?: boolean;\n write?: boolean;\n writeWithoutResponse?: boolean;\n notify?: boolean;\n indicate?: boolean;\n authenticatedSignedWrites?: boolean;\n reliableWrite?: boolean;\n writableAuxiliaries?: boolean;\n };\n /** Initial value (DataView or Uint8Array) */\n value?: ArrayBuffer | Uint8Array;\n /** Descriptors for this characteristic */\n descriptors?: MockDescriptorConfig[];\n}\n\nexport interface MockServiceConfig {\n /** Service UUID */\n uuid: string;\n /** Whether this is a primary service (default: true) */\n isPrimary?: boolean;\n /** Characteristics in this service */\n characteristics?: MockCharacteristicConfig[];\n}\n\nexport interface MockDescriptorConfig {\n /** Descriptor UUID */\n uuid: string;\n /** Initial value */\n value?: ArrayBuffer | Uint8Array;\n}\n\n// --- Mock GATT Server ---\n\nexport class MockGATTServer {\n private _connected = false;\n private _device: MockBleDevice;\n private _services: Map<string, MockService> = new Map();\n\n constructor(device: MockBleDevice, configs: MockServiceConfig[]) {\n this._device = device;\n for (const config of configs) {\n this._services.set(\n config.uuid,\n new MockService(device, config)\n );\n }\n }\n\n get connected(): boolean {\n return this._connected;\n }\n\n async connect(): Promise<BluetoothRemoteGATTServer> {\n if (this._device.shouldFailConnect()) {\n throw new DOMException('Simulated transient connection failure', 'NetworkError');\n }\n this._connected = true;\n return this.asBluetoothRemoteGATTServer();\n }\n\n disconnect(): void {\n this._connected = false;\n // Stop all notifications\n for (const service of this._services.values()) {\n service.stopAllNotifications();\n }\n }\n\n async getPrimaryService(uuid: string): Promise<BluetoothRemoteGATTService> {\n this._assertConnected();\n const service = this._services.get(uuid);\n if (!service) {\n throw new DOMException(\n `No Services matching UUID ${uuid} found`,\n 'NotFoundError'\n );\n }\n return service.asBluetoothRemoteGATTService();\n }\n\n async getPrimaryServices(\n uuid?: string\n ): Promise<BluetoothRemoteGATTService[]> {\n this._assertConnected();\n const services = uuid\n ? [this._services.get(uuid)].filter(Boolean)\n : Array.from(this._services.values());\n return (services as MockService[]).map((s) =>\n s.asBluetoothRemoteGATTService()\n );\n }\n\n /** Get a mock service for test control */\n getService(uuid: string): MockService | undefined {\n return this._services.get(uuid);\n }\n\n asBluetoothRemoteGATTServer(deviceProxy?: BluetoothDevice): BluetoothRemoteGATTServer {\n const self = this;\n const server = {\n get connected() {\n return self._connected;\n },\n get device() {\n return deviceProxy!;\n },\n connect: () => self.connect(),\n disconnect: () => self.disconnect(),\n getPrimaryService: (uuid: string) =>\n self.getPrimaryService(uuid),\n getPrimaryServices: (uuid?: string) =>\n self.getPrimaryServices(uuid),\n } as unknown as BluetoothRemoteGATTServer;\n return server;\n }\n\n private _assertConnected(): void {\n if (!this._connected) {\n throw new DOMException(\n 'GATT Server is disconnected. Cannot perform GATT operations.',\n 'NetworkError'\n );\n }\n }\n}\n\n// --- Mock Service ---\n\nexport class MockService {\n readonly uuid: string;\n readonly isPrimary: boolean;\n private _characteristics: Map<string, MockCharacteristic> = new Map();\n\n constructor(_device: MockBleDevice, config: MockServiceConfig) {\n this.uuid = config.uuid;\n this.isPrimary = config.isPrimary ?? true;\n for (const charConfig of config.characteristics ?? []) {\n this._characteristics.set(\n charConfig.uuid,\n new MockCharacteristic(charConfig)\n );\n }\n }\n\n async getCharacteristic(\n uuid: string\n ): Promise<BluetoothRemoteGATTCharacteristic> {\n const char = this._characteristics.get(uuid);\n if (!char) {\n throw new DOMException(\n `No Characteristics matching UUID ${uuid} found`,\n 'NotFoundError'\n );\n }\n return char.asBluetoothRemoteGATTCharacteristic(\n this.asBluetoothRemoteGATTService()\n );\n }\n\n async getCharacteristics(\n uuid?: string\n ): Promise<BluetoothRemoteGATTCharacteristic[]> {\n const chars = uuid\n ? [this._characteristics.get(uuid)].filter(Boolean)\n : Array.from(this._characteristics.values());\n const service = this.asBluetoothRemoteGATTService();\n return (chars as MockCharacteristic[]).map((c) =>\n c.asBluetoothRemoteGATTCharacteristic(service)\n );\n }\n\n /** Get a mock characteristic for test control */\n getChar(uuid: string): MockCharacteristic | undefined {\n return this._characteristics.get(uuid);\n }\n\n stopAllNotifications(): void {\n for (const char of this._characteristics.values()) {\n char.stopNotifications();\n }\n }\n\n asBluetoothRemoteGATTService(deviceProxy?: BluetoothDevice): BluetoothRemoteGATTService {\n const self = this;\n return {\n uuid: this.uuid,\n isPrimary: this.isPrimary,\n get device() {\n return deviceProxy!;\n },\n getCharacteristic: (uuid: string) => self.getCharacteristic(uuid),\n getCharacteristics: (uuid?: string) =>\n self.getCharacteristics(uuid),\n getIncludedService: async () => {\n throw new DOMException('Not implemented', 'NotSupportedError');\n },\n getIncludedServices: async () => [],\n addEventListener: () => {},\n removeEventListener: () => {},\n dispatchEvent: () => true,\n oncharacteristicvaluechanged: null,\n onserviceadded: null,\n onservicechanged: null,\n onserviceremoved: null,\n } as unknown as BluetoothRemoteGATTService;\n }\n}\n\n// --- Mock Characteristic ---\n\nexport class MockCharacteristic {\n readonly uuid: string;\n private _properties: {\n broadcast: boolean;\n read: boolean;\n write: boolean;\n writeWithoutResponse: boolean;\n notify: boolean;\n indicate: boolean;\n authenticatedSignedWrites: boolean;\n reliableWrite: boolean;\n writableAuxiliaries: boolean;\n };\n private _value: DataView;\n private _notifying = false;\n private _listeners: Map<string, Set<EventListener>> = new Map();\n private _descriptors: Map<string, MockDescriptor> = new Map();\n\n constructor(config: MockCharacteristicConfig) {\n this.uuid = config.uuid;\n this._properties = {\n broadcast: config.properties?.broadcast ?? false,\n read: config.properties?.read ?? true,\n write: config.properties?.write ?? false,\n writeWithoutResponse: config.properties?.writeWithoutResponse ?? false,\n notify: config.properties?.notify ?? false,\n indicate: config.properties?.indicate ?? false,\n authenticatedSignedWrites: config.properties?.authenticatedSignedWrites ?? false,\n reliableWrite: config.properties?.reliableWrite ?? false,\n writableAuxiliaries: config.properties?.writableAuxiliaries ?? false,\n };\n\n if (config.value) {\n const buffer =\n config.value instanceof Uint8Array\n ? config.value.buffer.slice(\n config.value.byteOffset,\n config.value.byteOffset + config.value.byteLength\n )\n : config.value;\n this._value = new DataView(buffer);\n } else {\n this._value = new DataView(new ArrayBuffer(0));\n }\n\n for (const descConfig of config.descriptors ?? []) {\n this._descriptors.set(descConfig.uuid, new MockDescriptor(descConfig));\n }\n }\n\n /** Set the characteristic value (for test setup) */\n setValue(data: ArrayBuffer | Uint8Array): void {\n const buffer =\n data instanceof Uint8Array\n ? data.buffer.slice(\n data.byteOffset,\n data.byteOffset + data.byteLength\n )\n : data;\n this._value = new DataView(buffer);\n }\n\n /** Pump a notification to all listeners */\n emitNotification(data: ArrayBuffer | Uint8Array): void {\n const buffer =\n data instanceof Uint8Array\n ? data.buffer.slice(\n data.byteOffset,\n data.byteOffset + data.byteLength\n )\n : data;\n this._value = new DataView(buffer);\n\n const event = new Event('characteristicvaluechanged');\n Object.defineProperty(event, 'target', {\n value: { value: this._value },\n writable: false,\n });\n\n const listeners = this._listeners.get('characteristicvaluechanged');\n if (listeners) {\n for (const listener of listeners) {\n listener(event);\n }\n }\n }\n\n stopNotifications(): void {\n this._notifying = false;\n }\n\n get isNotifying(): boolean {\n return this._notifying;\n }\n\n /** Get a mock descriptor for test control */\n getDesc(uuid: string): MockDescriptor | undefined {\n return this._descriptors.get(uuid);\n }\n\n asBluetoothRemoteGATTCharacteristic(\n service: BluetoothRemoteGATTService\n ): BluetoothRemoteGATTCharacteristic {\n const self = this;\n return {\n uuid: this.uuid,\n service,\n properties: {\n broadcast: this._properties.broadcast,\n read: this._properties.read,\n writeWithoutResponse: this._properties.writeWithoutResponse,\n write: this._properties.write,\n notify: this._properties.notify,\n indicate: this._properties.indicate,\n authenticatedSignedWrites: this._properties.authenticatedSignedWrites,\n reliableWrite: this._properties.reliableWrite,\n writableAuxiliaries: this._properties.writableAuxiliaries,\n },\n get value() {\n return self._value;\n },\n readValue: async () => {\n if (!self._properties.read) {\n throw new DOMException(\n 'Characteristic does not support read',\n 'NotSupportedError'\n );\n }\n return self._value;\n },\n writeValue: async (value: BufferSource) => {\n if (!self._properties.write) {\n throw new DOMException(\n 'Characteristic does not support write',\n 'NotSupportedError'\n );\n }\n self._writeValue(value);\n },\n writeValueWithResponse: async (value: BufferSource) => {\n if (!self._properties.write) {\n throw new DOMException(\n 'Characteristic does not support write',\n 'NotSupportedError'\n );\n }\n self._writeValue(value);\n },\n writeValueWithoutResponse: async (value: BufferSource) => {\n if (!self._properties.writeWithoutResponse) {\n throw new DOMException(\n 'Characteristic does not support write without response',\n 'NotSupportedError'\n );\n }\n self._writeValue(value);\n },\n startNotifications: async function () {\n if (!self._properties.notify && !self._properties.indicate) {\n throw new DOMException(\n 'Characteristic does not support notifications',\n 'NotSupportedError'\n );\n }\n self._notifying = true;\n return this;\n },\n stopNotifications: async function () {\n self._notifying = false;\n return this;\n },\n addEventListener: (type: string, listener: EventListener) => {\n if (!self._listeners.has(type)) {\n self._listeners.set(type, new Set());\n }\n self._listeners.get(type)!.add(listener);\n },\n removeEventListener: (type: string, listener: EventListener) => {\n self._listeners.get(type)?.delete(listener);\n },\n dispatchEvent: () => true,\n getDescriptor: async (uuid: string) => {\n const desc = self._descriptors.get(uuid);\n if (!desc) {\n throw new DOMException(\n `No Descriptors matching UUID ${uuid} found`,\n 'NotFoundError'\n );\n }\n return desc.asBluetoothRemoteGATTDescriptor(\n self.asBluetoothRemoteGATTCharacteristic(service)\n );\n },\n getDescriptors: async (uuid?: string) => {\n const descriptors = uuid\n ? [self._descriptors.get(uuid)].filter(Boolean)\n : Array.from(self._descriptors.values());\n const charProxy = self.asBluetoothRemoteGATTCharacteristic(service);\n return (descriptors as MockDescriptor[]).map((d) =>\n d.asBluetoothRemoteGATTDescriptor(charProxy)\n );\n },\n oncharacteristicvaluechanged: null,\n } as unknown as BluetoothRemoteGATTCharacteristic;\n }\n\n private _writeValue(value: BufferSource): void {\n const buffer =\n value instanceof ArrayBuffer\n ? value\n : (value as DataView).buffer ?? (value as Uint8Array).buffer;\n this._value = new DataView(buffer);\n }\n}\n\n// --- Mock Descriptor ---\n\nexport class MockDescriptor {\n readonly uuid: string;\n private _value: DataView;\n\n constructor(config: MockDescriptorConfig) {\n this.uuid = config.uuid;\n if (config.value) {\n const buffer =\n config.value instanceof Uint8Array\n ? config.value.buffer.slice(\n config.value.byteOffset,\n config.value.byteOffset + config.value.byteLength\n )\n : config.value;\n this._value = new DataView(buffer);\n } else {\n this._value = new DataView(new ArrayBuffer(0));\n }\n }\n\n /** Set the descriptor value (for test setup) */\n setValue(data: ArrayBuffer | Uint8Array): void {\n const buffer =\n data instanceof Uint8Array\n ? data.buffer.slice(\n data.byteOffset,\n data.byteOffset + data.byteLength\n )\n : data;\n this._value = new DataView(buffer);\n }\n\n /** Get the current value */\n get value(): DataView {\n return this._value;\n }\n\n asBluetoothRemoteGATTDescriptor(\n characteristic: BluetoothRemoteGATTCharacteristic\n ): BluetoothRemoteGATTDescriptor {\n const self = this;\n return {\n uuid: this.uuid,\n characteristic,\n get value() {\n return self._value;\n },\n readValue: async () => {\n return self._value;\n },\n writeValue: async (value: BufferSource) => {\n const buffer =\n value instanceof ArrayBuffer\n ? value\n : (value as DataView).buffer ?? (value as Uint8Array).buffer;\n self._value = new DataView(buffer);\n },\n } as unknown as BluetoothRemoteGATTDescriptor;\n }\n}\n","/**\n * Mock BLE Device — stateful device with GATT server, services, characteristics\n */\n\nimport {\n MockGATTServer,\n type MockServiceConfig,\n} from './characteristics';\n\nlet deviceIdCounter = 0;\n\nexport interface MockDeviceOptions {\n /** Device ID (auto-generated if not provided) */\n id?: string;\n /** Device name */\n name?: string;\n /** Advertised service UUIDs */\n serviceUUIDs?: string[];\n /** GATT service configurations */\n services?: MockServiceConfig[];\n /** Initial RSSI value */\n rssi?: number;\n /** Fail the first N connect() attempts with a NetworkError. */\n failConnectAttempts?: number;\n /** Optional platform-reported write limits for MTU-aware write tests. */\n writeLimits?: {\n withResponse?: number | null;\n withoutResponse?: number | null;\n mtu?: number | null;\n };\n}\n\nexport interface MockAdvertisementOptions {\n /** Override RSSI for this advertisement */\n rssi?: number;\n /** Optional TX power value */\n txPower?: number;\n /** Override advertised UUIDs for this advertisement */\n uuids?: string[];\n /** Optional manufacturer data payloads */\n manufacturerData?: Map<number, DataView>;\n /** Optional service data payloads */\n serviceData?: Map<string, DataView>;\n}\n\ninterface ExtendedGatt extends BluetoothRemoteGATTServer {\n getMtu?: () => Promise<number | null>;\n getWriteLimits?: () => Promise<Record<string, number | null>>;\n}\n\nexport class MockBleDevice {\n readonly id: string;\n readonly name: string | undefined;\n private _serviceUUIDs: string[];\n private _gatt: MockGATTServer;\n private _listeners: Map<string, Set<EventListener>> = new Map();\n private _rssi: number;\n private _watchingAdvertisements = false;\n private _advertisementSink?: (\n device: MockBleDevice,\n options: MockAdvertisementOptions\n ) => void;\n private _remainingConnectFailures: number;\n private _writeLimits: {\n withResponse: number | null;\n withoutResponse: number | null;\n mtu: number | null;\n };\n\n constructor(options: MockDeviceOptions = {}) {\n this.id = options.id ?? `mock-device-${++deviceIdCounter}`;\n this.name = options.name;\n this._serviceUUIDs = options.serviceUUIDs ?? [];\n this._gatt = new MockGATTServer(this, options.services ?? []);\n this._rssi = options.rssi ?? -60;\n this._remainingConnectFailures = options.failConnectAttempts ?? 0;\n this._writeLimits = {\n withResponse: options.writeLimits?.withResponse ?? null,\n withoutResponse: options.writeLimits?.withoutResponse ?? null,\n mtu: options.writeLimits?.mtu ?? null,\n };\n }\n\n /** Check if this device matches a scan filter */\n matchesFilter(filter: BluetoothLEScanFilter): boolean {\n if (filter.services) {\n const hasService = filter.services.some((uuid) =>\n this._serviceUUIDs.includes(String(uuid))\n );\n if (!hasService) return false;\n }\n if (filter.name && filter.name !== this.name) return false;\n if (filter.namePrefix && !this.name?.startsWith(filter.namePrefix))\n return false;\n return true;\n }\n\n /** Return a Web Bluetooth-compatible BluetoothDevice object */\n asBluetoothDevice(): BluetoothDevice {\n const self = this;\n // Build the device proxy first, then wire up gatt to avoid circular calls\n const proxy = {\n id: this.id,\n name: this.name ?? null,\n gatt: null as unknown as BluetoothRemoteGATTServer,\n watchAdvertisements: async (options?: { signal?: AbortSignal }) => {\n self._watchingAdvertisements = true;\n if (options?.signal) {\n if (options.signal.aborted) {\n self._watchingAdvertisements = false;\n return;\n }\n\n options.signal.addEventListener(\n 'abort',\n () => {\n self._watchingAdvertisements = false;\n },\n { once: true }\n );\n }\n },\n addEventListener: (type: string, listener: EventListener) => {\n self._addListener(type, listener);\n },\n removeEventListener: (type: string, listener: EventListener) => {\n self._removeListener(type, listener);\n },\n dispatchEvent: (_event: Event) => true,\n get watchingAdvertisements() {\n return self._watchingAdvertisements;\n },\n unwatchAdvertisements: async () => {\n self._watchingAdvertisements = false;\n },\n forget: async () => {},\n onadvertisementreceived: null,\n ongattserverdisconnected: null,\n oncharacteristicvaluechanged: null,\n onserviceadded: null,\n onservicechanged: null,\n onserviceremoved: null,\n } as object as BluetoothDevice;\n // Wire gatt with a back-reference to the proxy (no recursion)\n (proxy as { gatt: ExtendedGatt }).gatt = this._gatt.asBluetoothRemoteGATTServer(proxy) as ExtendedGatt;\n (proxy as { gatt: ExtendedGatt }).gatt.getMtu = async () => this._writeLimits.mtu;\n (proxy as { gatt: ExtendedGatt }).gatt.getWriteLimits = async () => ({ ...this._writeLimits });\n\n return proxy;\n }\n\n shouldFailConnect(): boolean {\n if (this._remainingConnectFailures <= 0) {\n return false;\n }\n this._remainingConnectFailures -= 1;\n return true;\n }\n\n /** Simulate a disconnect event */\n simulateDisconnect(): void {\n this._gatt.disconnect();\n this._emit('gattserverdisconnected', new Event('gattserverdisconnected'));\n }\n\n /** Get the mock GATT server for direct test control */\n get gatt(): MockGATTServer {\n return this._gatt;\n }\n\n get serviceUUIDs(): readonly string[] {\n return this._serviceUUIDs;\n }\n\n get rssi(): number {\n return this._rssi;\n }\n\n /** Emit an advertisement for requestLEScan()/watchAdvertisements() tests */\n emitAdvertisement(options: MockAdvertisementOptions = {}): void {\n if (this._advertisementSink) {\n this._advertisementSink(this, options);\n return;\n }\n\n this.dispatchAdvertisementEvent(options);\n }\n\n /** Update RSSI between advertisements */\n setRSSI(rssi: number): void {\n this._rssi = rssi;\n }\n\n /** Internal hook used by MockBluetooth to receive advertisement pumps */\n setAdvertisementSink(\n sink: ((device: MockBleDevice, options: MockAdvertisementOptions) => void) | undefined\n ): void {\n this._advertisementSink = sink;\n }\n\n /** Internal bridge for watchAdvertisements() listeners */\n dispatchAdvertisementEvent(options: MockAdvertisementOptions = {}): void {\n if (!this._watchingAdvertisements) {\n return;\n }\n\n this._emit(\n 'advertisementreceived',\n this.createAdvertisementEvent(this.asBluetoothDevice(), options)\n );\n }\n\n /** Build a Web Bluetooth-style advertisementreceived event */\n createAdvertisementEvent(\n deviceProxy: BluetoothDevice,\n options: MockAdvertisementOptions = {}\n ): Event {\n const event = new Event('advertisementreceived') as Event & {\n device?: BluetoothDevice;\n name?: string;\n uuids?: string[];\n rssi?: number;\n txPower?: number;\n manufacturerData?: Map<number, DataView>;\n serviceData?: Map<string, DataView>;\n };\n\n Object.defineProperties(event, {\n device: { value: deviceProxy, writable: false },\n name: { value: this.name, writable: false },\n uuids: {\n value: [...(options.uuids ?? this._serviceUUIDs)],\n writable: false,\n },\n rssi: { value: options.rssi ?? this._rssi, writable: false },\n txPower: { value: options.txPower, writable: false },\n manufacturerData: {\n value: options.manufacturerData ?? new Map<number, DataView>(),\n writable: false,\n },\n serviceData: {\n value: options.serviceData ?? new Map<string, DataView>(),\n writable: false,\n },\n });\n\n return event;\n }\n\n // --- Internal ---\n\n private _addListener(type: string, listener: EventListener): void {\n if (!this._listeners.has(type)) {\n this._listeners.set(type, new Set());\n }\n this._listeners.get(type)!.add(listener);\n }\n\n private _removeListener(type: string, listener: EventListener): void {\n this._listeners.get(type)?.delete(listener);\n }\n\n private _emit(type: string, event: Event): void {\n const listeners = this._listeners.get(type);\n if (listeners) {\n for (const listener of listeners) {\n listener(event);\n }\n }\n }\n}\n","/**\n * Mock Bluetooth API — drop-in replacement for navigator.bluetooth\n *\n * Provides a stateful mock that tracks devices, manages connections,\n * and can be configured for various test scenarios.\n */\n\nimport {\n MockBleDevice,\n type MockAdvertisementOptions,\n type MockDeviceOptions,\n} from './device';\nimport type {\n MockCharacteristicConfig,\n MockServiceConfig,\n} from './characteristics';\n\nexport interface MockBluetoothOptions {\n /** Whether Bluetooth is available (default: true) */\n available?: boolean;\n /** Pre-registered devices that will appear in scans */\n devices?: MockDeviceOptions[];\n}\n\nconst unsupportedExtensionApi = (): Promise<never> =>\n Promise.reject(new DOMException('Beacio extension API not implemented in MockBluetooth', 'NotSupportedError'));\n\nconst noop = (): void => {};\n\nexport class MockBluetooth {\n private _available: boolean;\n private _devices: Map<string, MockBleDevice> = new Map();\n private _listeners: Map<string, Set<EventListener>> = new Map();\n private _scanActive = false;\n private _installedNavigatorBluetooth?: unknown;\n private _lastScanOptions?: BluetoothLEScanOptions;\n\n readonly backgroundSync = {\n requestPermission: unsupportedExtensionApi,\n requestBackgroundConnection: unsupportedExtensionApi,\n registerCharacteristicNotifications: unsupportedExtensionApi,\n registerBeaconScanning: unsupportedExtensionApi,\n getRegistrations: unsupportedExtensionApi,\n unregister: unsupportedExtensionApi,\n update: unsupportedExtensionApi,\n connect: unsupportedExtensionApi,\n subscribe: unsupportedExtensionApi,\n scan: unsupportedExtensionApi,\n list: unsupportedExtensionApi,\n destroy: noop,\n };\n\n readonly peripheral = {\n advertising: false,\n advertise: unsupportedExtensionApi,\n stopAdvertising: unsupportedExtensionApi,\n send: unsupportedExtensionApi,\n destroy: noop,\n addEventListener: noop,\n removeEventListener: noop,\n onwriterequest: null,\n onsubscriptionchange: null,\n onconnectionstatechange: null,\n onadvertisingstatechange: null,\n };\n\n constructor(options: MockBluetoothOptions = {}) {\n this._available = options.available ?? true;\n if (options.devices) {\n for (const opts of options.devices) {\n const device = new MockBleDevice(opts);\n device.setAdvertisementSink(this._handleAdvertisement);\n this._devices.set(device.id, device);\n }\n }\n }\n\n // --- Public API (matches navigator.bluetooth) ---\n\n async getAvailability(): Promise<boolean> {\n return this._available;\n }\n\n async requestDevice(\n options?: RequestDeviceOptions\n ): Promise<BluetoothDevice> {\n if (!this._available) {\n throw new DOMException(\n 'Bluetooth adapter not available',\n 'NotFoundError'\n );\n }\n\n const matching = this._findMatchingDevices(options as Record<string, unknown>);\n if (matching.length === 0) {\n throw new DOMException(\n 'No devices found matching the filter criteria',\n 'NotFoundError'\n );\n }\n\n // Return the first matching device (simulates user picking)\n return matching[0].asBluetoothDevice();\n }\n\n async getDevices(): Promise<BluetoothDevice[]> {\n return Array.from(this._devices.values()).map((d) =>\n d.asBluetoothDevice()\n );\n }\n\n async requestLEScan(\n options?: BluetoothLEScanOptions\n ): Promise<BluetoothLEScan> {\n if (this._scanActive) {\n throw new DOMException('Scan already in progress', 'InvalidStateError');\n }\n this._scanActive = true;\n this._lastScanOptions = options;\n const scan = {\n active: true,\n keepRepeatedDevices: options?.keepRepeatedDevices ?? false,\n acceptAllAdvertisements: options?.acceptAllAdvertisements ?? false,\n stop: () => {\n this._scanActive = false;\n this._lastScanOptions = undefined;\n scan.active = false;\n },\n } as BluetoothLEScan & { active: boolean };\n return scan as BluetoothLEScan;\n }\n\n addEventListener(type: string, listener: EventListener): void {\n if (!this._listeners.has(type)) {\n this._listeners.set(type, new Set());\n }\n this._listeners.get(type)!.add(listener);\n }\n\n removeEventListener(type: string, listener: EventListener): void {\n this._listeners.get(type)?.delete(listener);\n }\n\n // --- Test helpers ---\n\n /** Add a device to the mock registry */\n addDevice(options: MockDeviceOptions): MockBleDevice {\n const device = new MockBleDevice(options);\n device.setAdvertisementSink(this._handleAdvertisement);\n this._devices.set(device.id, device);\n return device;\n }\n\n /** Remove a device from the registry */\n removeDevice(id: string): void {\n const device = this._devices.get(id);\n if (device) {\n device.setAdvertisementSink(undefined);\n device.simulateDisconnect();\n this._devices.delete(id);\n }\n }\n\n /** Get a mock device by ID for test assertions */\n getDevice(id: string): MockBleDevice | undefined {\n return this._devices.get(id);\n }\n\n /** Set Bluetooth availability */\n setAvailable(available: boolean): void {\n this._available = available;\n }\n\n /** Install this mock instance onto navigator.bluetooth */\n install(): this {\n if (typeof globalThis.navigator === 'undefined') {\n return this;\n }\n\n this._installedNavigatorBluetooth = (globalThis.navigator as Navigator & {\n bluetooth?: unknown;\n }).bluetooth;\n\n Object.defineProperty(globalThis.navigator, 'bluetooth', {\n value: this,\n writable: true,\n configurable: true,\n });\n return this;\n }\n\n /** Restore the previous navigator.bluetooth value */\n uninstall(): void {\n if (typeof globalThis.navigator === 'undefined') {\n return;\n }\n\n Object.defineProperty(globalThis.navigator, 'bluetooth', {\n value: this._installedNavigatorBluetooth,\n writable: true,\n configurable: true,\n });\n this._installedNavigatorBluetooth = undefined;\n }\n\n /** Emit a Bluetooth-level advertisementreceived event */\n emitAdvertisement(\n deviceId: string,\n options: MockAdvertisementOptions = {}\n ): void {\n const device = this._devices.get(deviceId);\n if (!device) {\n throw new Error(`Unknown mock device: ${deviceId}`);\n }\n\n this._handleAdvertisement(device, options);\n }\n\n /** Reset all state */\n reset(): void {\n for (const device of this._devices.values()) {\n device.setAdvertisementSink(undefined);\n device.simulateDisconnect();\n }\n this._devices.clear();\n this._listeners.clear();\n this._scanActive = false;\n this._lastScanOptions = undefined;\n this._available = true;\n }\n\n // --- Internal ---\n\n private _findMatchingDevices(\n options?: Record<string, unknown>\n ): MockBleDevice[] {\n if (!options || (options as { acceptAllDevices?: boolean }).acceptAllDevices) {\n return Array.from(this._devices.values());\n }\n\n const filters = ((options as { filters?: BluetoothLEScanFilter[] }).filters) ?? [];\n return Array.from(this._devices.values()).filter((device) =>\n filters.some((filter: BluetoothLEScanFilter) => device.matchesFilter(filter))\n );\n }\n\n private readonly _handleAdvertisement = (\n device: MockBleDevice,\n options: MockAdvertisementOptions\n ): void => {\n device.dispatchAdvertisementEvent(options);\n\n if (!this._scanActive) {\n return;\n }\n\n if (!this._matchesScan(device)) {\n return;\n }\n\n const event = device.createAdvertisementEvent(device.asBluetoothDevice(), options);\n const listeners = this._listeners.get('advertisementreceived');\n if (!listeners) {\n return;\n }\n\n for (const listener of listeners) {\n listener(event);\n }\n };\n\n private _matchesScan(device: MockBleDevice): boolean {\n const options = this._lastScanOptions;\n if (!options) {\n return true;\n }\n\n if (options.acceptAllAdvertisements) {\n return true;\n }\n\n const filters = options.filters ?? [];\n if (filters.length === 0) {\n return true;\n }\n\n return filters.some((filter) => device.matchesFilter(filter));\n }\n}\n\n/**\n * Install mock Bluetooth API on the global navigator object.\n * Returns a MockBluetooth instance for test control.\n */\nexport function createMockBluetooth(\n options?: MockBluetoothOptions\n): MockBluetooth {\n return new MockBluetooth(options);\n}\n\n/**\n * Install mock Bluetooth on navigator.bluetooth.\n * Returns the mock instance for control.\n */\nexport function installMockBluetooth(\n options?: MockBluetoothOptions\n): MockBluetooth {\n const mock = createMockBluetooth(options);\n return mock.install();\n}\n\nexport type {\n MockServiceConfig,\n MockCharacteristicConfig,\n MockAdvertisementOptions,\n};\n","/**\n * @beacio/core/testing — Mock Bluetooth API for testing BLE web apps\n *\n * Folded in from the former @beacio/testing package (B10-t): the hardware-free\n * mock/virtual Web Bluetooth surface that powers the \"playground\" first-run\n * (a dev sees `requestDevice()` succeed in Chrome before ever touching iOS).\n * Reached via the `@beacio/core/testing` subpath export.\n *\n * Provides stateful mocks for the Web Bluetooth API:\n * - MockBluetooth: drop-in replacement for navigator.bluetooth\n * - MockBleDevice: stateful device with GATT services/characteristics\n * - MockCharacteristic: value simulation and notification pump\n * - Advertisement simulation for requestLEScan/watchAdvertisements tests\n *\n * Usage:\n * import { createMockBluetooth, installMockBluetooth } from '@beacio/core/testing'\n *\n * // Option A: Create and install on navigator.bluetooth\n * const mock = installMockBluetooth({ available: true })\n *\n * // Option B: Create without installing (for custom setups)\n * const mock = createMockBluetooth()\n *\n * // Add test devices\n * const device = mock.addDevice({\n * name: 'HR Sensor',\n * failConnectAttempts: 1,\n * writeLimits: { withResponse: 20, mtu: 23 },\n * serviceUUIDs: ['0000180d-0000-1000-8000-00805f9b34fb'],\n * services: [{\n * uuid: '0000180d-0000-1000-8000-00805f9b34fb',\n * characteristics: [{\n * uuid: '00002a37-0000-1000-8000-00805f9b34fb',\n * properties: { notify: true, read: true },\n * value: new Uint8Array([0x00, 72]),\n * }],\n * }],\n * })\n *\n * // Pump notifications in tests\n * const char = device.gatt.getService('0000180d-...')?.getChar('00002a37-...')\n * char?.emitNotification(new Uint8Array([0x00, 80]))\n *\n * // Emit advertisements for scan/beacon-style tests\n * device.emitAdvertisement({ rssi: -42 })\n *\n * // Reset between tests\n * mock.reset()\n */\n\nexport {\n MockBluetooth,\n createMockBluetooth,\n installMockBluetooth,\n type MockBluetoothOptions,\n type MockAdvertisementOptions,\n} from './mocks/bluetooth';\n\nexport {\n MockBleDevice,\n type MockDeviceOptions,\n} from './mocks/device';\n\nexport {\n MockGATTServer,\n MockService,\n MockCharacteristic,\n MockDescriptor,\n type MockServiceConfig,\n type MockCharacteristicConfig,\n type MockDescriptorConfig,\n} from './mocks/characteristics';\n\n/** Common Bluetooth SIG UUIDs for test convenience */\nexport const BLE_UUIDS = {\n services: {\n HEART_RATE: '0000180d-0000-1000-8000-00805f9b34fb',\n BATTERY: '0000180f-0000-1000-8000-00805f9b34fb',\n DEVICE_INFO: '0000180a-0000-1000-8000-00805f9b34fb',\n ENVIRONMENTAL_SENSING: '0000181a-0000-1000-8000-00805f9b34fb',\n },\n characteristics: {\n HEART_RATE_MEASUREMENT: '00002a37-0000-1000-8000-00805f9b34fb',\n BODY_SENSOR_LOCATION: '00002a38-0000-1000-8000-00805f9b34fb',\n BATTERY_LEVEL: '00002a19-0000-1000-8000-00805f9b34fb',\n MANUFACTURER_NAME: '00002a29-0000-1000-8000-00805f9b34fb',\n MODEL_NUMBER: '00002a24-0000-1000-8000-00805f9b34fb',\n TEMPERATURE: '00002a6e-0000-1000-8000-00805f9b34fb',\n },\n descriptors: {\n /** Client Characteristic Configuration Descriptor */\n CCCD: '00002902-0000-1000-8000-00805f9b34fb',\n /** Characteristic User Description */\n USER_DESCRIPTION: '00002901-0000-1000-8000-00805f9b34fb',\n /** Characteristic Presentation Format */\n PRESENTATION_FORMAT: '00002904-0000-1000-8000-00805f9b34fb',\n },\n} as const;\n\n/** Pre-configured device factories for common test scenarios */\nexport const devices = {\n /** Heart rate sensor with notification support */\n heartRate(name = 'Mock HR Sensor'): import('./mocks/device').MockDeviceOptions {\n return {\n name,\n serviceUUIDs: [BLE_UUIDS.services.HEART_RATE],\n services: [\n {\n uuid: BLE_UUIDS.services.HEART_RATE,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.HEART_RATE_MEASUREMENT,\n properties: { read: true, notify: true },\n value: new Uint8Array([0x00, 72]), // 72 bpm\n },\n {\n uuid: BLE_UUIDS.characteristics.BODY_SENSOR_LOCATION,\n properties: { read: true },\n value: new Uint8Array([1]), // Chest\n },\n ],\n },\n ],\n };\n },\n\n /** Battery service device */\n battery(name = 'Mock Battery Device'): import('./mocks/device').MockDeviceOptions {\n return {\n name,\n serviceUUIDs: [BLE_UUIDS.services.BATTERY],\n services: [\n {\n uuid: BLE_UUIDS.services.BATTERY,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.BATTERY_LEVEL,\n properties: { read: true, notify: true },\n value: new Uint8Array([85]), // 85%\n },\n ],\n },\n ],\n };\n },\n\n /** Device with multiple services */\n full(name = 'Mock Full Device'): import('./mocks/device').MockDeviceOptions {\n return {\n name,\n serviceUUIDs: [\n BLE_UUIDS.services.HEART_RATE,\n BLE_UUIDS.services.BATTERY,\n BLE_UUIDS.services.DEVICE_INFO,\n ],\n services: [\n {\n uuid: BLE_UUIDS.services.HEART_RATE,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.HEART_RATE_MEASUREMENT,\n properties: { read: true, notify: true },\n value: new Uint8Array([0x00, 72]),\n },\n ],\n },\n {\n uuid: BLE_UUIDS.services.BATTERY,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.BATTERY_LEVEL,\n properties: { read: true, notify: true },\n value: new Uint8Array([100]),\n },\n ],\n },\n {\n uuid: BLE_UUIDS.services.DEVICE_INFO,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.MANUFACTURER_NAME,\n properties: { read: true },\n value: Uint8Array.from(Array.from('Beacio Test Corp').map(c => c.charCodeAt(0))),\n },\n {\n uuid: BLE_UUIDS.characteristics.MODEL_NUMBER,\n properties: { read: true },\n value: Uint8Array.from(Array.from('WBT-001').map(c => c.charCodeAt(0))),\n },\n ],\n },\n ],\n };\n },\n};\n"]}
import {k,l}from'./chunk-GAX5WAKV.mjs';import {b as b$2}from'./chunk-FANWIUKA.mjs';import {b as b$1}from'./chunk-33IHM3NV.mjs';var m=[b$1("heart_rate")],s=class extends b$2{constructor(){super(...arguments);this.service="heart_rate";}onHeartRate(e){return this.subscribe("heart_rate_measurement",n=>{e(b(n));})}async readSensorLocation(){let e=await this.read("body_sensor_location");return k(e)}async resetEnergyExpended(){await this.write("heart_rate_control_point",new Uint8Array([1]));}};s.services=m;function b(t){let r=k(t,0),e=1,n=(r&1)!==0,p=n?l(t,e):k(t,e);e+=n?2:1;let d=(r&4)!==0?(r&2)!==0:null,i=null;r&8&&(i=l(t,e),e+=2);let c=[];if(r&16)for(;e+2<=t.byteLength;)c.push(l(t,e)/1024),e+=2;return {bpm:p,contact:d,energyExpended:i,rrIntervals:c}}export{m as a,s as b,b as c};//# sourceMappingURL=chunk-6OEIU4UO.mjs.map
//# sourceMappingURL=chunk-6OEIU4UO.mjs.map
{"version":3,"sources":["../src/profiles/heart-rate.ts"],"names":["HEART_RATE_SERVICES","resolveUUID","HeartRateProfile","BaseProfile","callback","dv","parseHeartRate","readUint8","flags","offset","is16bit","bpm","readUint16LE","contact","energyExpended","rrIntervals"],"mappings":"+HAUO,IAAMA,CAAAA,CAAyC,CAACC,GAAAA,CAAY,YAAY,CAAC,CAAA,CAyDnEC,CAAAA,CAAN,cAA+BC,GAAY,CAA3C,WAAA,EAAA,CAAA,KAAA,CAAA,GAAA,SAAA,CAAA,CAIL,IAAA,CAAmB,OAAA,CAAU,cAG7B,WAAA,CAAYC,CAAAA,CAAqD,CAC/D,OAAO,IAAA,CAAK,SAAA,CAAU,wBAAA,CAA2BC,CAAAA,EAAO,CACtDD,CAAAA,CAASE,CAAAA,CAAeD,CAAE,CAAC,EAC7B,CAAC,CACH,CAGA,MAAM,oBAAsC,CAC1C,IAAMA,CAAAA,CAAK,MAAM,IAAA,CAAK,IAAA,CAAK,sBAAsB,CAAA,CACjD,OAAOE,CAAAA,CAAUF,CAAE,CACrB,CAGA,MAAM,mBAAA,EAAqC,CACzC,MAAM,IAAA,CAAK,MAAM,0BAAA,CAA4B,IAAI,UAAA,CAAW,CAAC,CAAC,CAAC,CAAC,EAClE,CACF,EAvBaH,CAAAA,CAEK,QAAA,CAAWF,CAAAA,CA2CtB,SAASM,CAAAA,CAAeD,CAAAA,CAA6B,CAC1D,IAAMG,EAAQD,CAAAA,CAAUF,CAAAA,CAAI,CAAC,CAAA,CACzBI,CAAAA,CAAS,CAAA,CAGPC,CAAAA,CAAAA,CAAWF,CAAAA,CAAQ,KAAU,CAAA,CAC7BG,CAAAA,CAAMD,CAAAA,CAAUE,CAAAA,CAAaP,EAAII,CAAM,CAAA,CAAIF,CAAAA,CAAUF,CAAAA,CAAII,CAAM,CAAA,CACrEA,CAAAA,EAAUC,CAAAA,CAAU,CAAA,CAAI,CAAA,CAIxB,IAAMG,CAAAA,CAAAA,CADoBL,CAAAA,CAAQ,KAAU,CAAA,CAAA,CACRA,CAAAA,CAAQ,CAAA,IAAU,CAAA,CAAI,KAGtDM,CAAAA,CAAgC,IAAA,CAChCN,CAAAA,CAAQ,CAAA,GACVM,EAAiBF,CAAAA,CAAaP,CAAAA,CAAII,CAAM,CAAA,CACxCA,CAAAA,EAAU,CAAA,CAAA,CAIZ,IAAMM,CAAAA,CAAwB,EAAC,CAC/B,GAAIP,CAAAA,CAAQ,EAAA,CAGV,KAAOC,CAAAA,CAAS,CAAA,EAAKJ,CAAAA,CAAG,UAAA,EAEtBU,EAAY,IAAA,CAAKH,CAAAA,CAAaP,CAAAA,CAAII,CAAM,CAAA,CAAI,IAAI,CAAA,CAChDA,CAAAA,EAAU,EAId,OAAO,CAAE,GAAA,CAAAE,CAAAA,CAAK,QAAAE,CAAAA,CAAS,cAAA,CAAAC,CAAAA,CAAgB,WAAA,CAAAC,CAAY,CACrD","file":"chunk-6OEIU4UO.mjs","sourcesContent":["import { readUint8, readUint16LE, resolveUUID } from '../index';\nimport { BaseProfile } from './base';\n\n/**\n * Service UUIDs a Heart Rate device may reach after connection (the SIG Heart\n * Rate Service, 0x180D). Canonical 128-bit form (resolved from the SIG alias via\n * the core registry — single source of truth). Use with `optionalServices` /\n * `Beacio.registerServices`, or via {@link deriveOptionalServices} given\n * {@link HeartRateProfile}.\n */\nexport const HEART_RATE_SERVICES: readonly string[] = [resolveUUID('heart_rate')];\n\n/**\n * Parsed heart rate measurement data from the Heart Rate Measurement\n * characteristic (UUID 0x2A37).\n *\n * Fields are populated based on the flags byte in the BLE payload.\n * Optional fields are `null` when the corresponding flag bit is unset.\n */\nexport interface HeartRateData {\n /** Heart rate value in beats per minute (BPM). May be 8-bit or 16-bit depending on the flags byte. */\n bpm: number;\n /** Whether the sensor has skin contact. `null` if the sensor does not support contact detection. */\n contact: boolean | null;\n /** Cumulative energy expended in kilojoules since the last reset. `null` if not present in this measurement. */\n energyExpended: number | null;\n /** RR-interval values in seconds (1/1024 s resolution). Empty array if not present in this measurement. */\n rrIntervals: number[];\n}\n\n/**\n * BLE Heart Rate Service profile (UUID 0x180D).\n *\n * Provides access to heart rate measurements, body sensor location,\n * and the energy-expended reset control point as defined by the\n * Bluetooth SIG Heart Rate Service specification.\n *\n * The measurement characteristic (0x2A37) uses a flags byte:\n * bit 0 = HR format (0 = UINT8, 1 = UINT16), bits 1-2 = sensor contact,\n * bit 3 = energy expended present, bit 4 = RR-interval present.\n *\n * @example\n * ```ts\n * import { HeartRateProfile } from '@beacio/core/profiles';\n *\n * const hr = new HeartRateProfile(device);\n * await hr.connect();\n *\n * // Subscribe to real-time heart rate data\n * const unsubscribe = hr.onHeartRate((data) => {\n * console.log(`BPM: ${data.bpm}`);\n * if (data.contact === false) {\n * console.warn('No skin contact detected');\n * }\n * if (data.rrIntervals.length > 0) {\n * console.log('RR intervals (s):', data.rrIntervals);\n * }\n * });\n *\n * // Read sensor location (e.g. 1 = Chest, 2 = Wrist)\n * const location = await hr.readSensorLocation();\n *\n * // Clean up\n * unsubscribe();\n * hr.stop();\n * ```\n */\nexport class HeartRateProfile extends BaseProfile {\n /** Services this profile's device may reach after connection (Heart Rate, 0x180D). Read by {@link deriveOptionalServices}. */\n static readonly services = HEART_RATE_SERVICES;\n\n protected readonly service = 'heart_rate';\n\n /** Subscribe to heart rate measurements. Returns unsubscribe function. */\n onHeartRate(callback: (data: HeartRateData) => void): () => void {\n return this.subscribe('heart_rate_measurement', (dv) => {\n callback(parseHeartRate(dv));\n });\n }\n\n /** Read body sensor location (0=Other, 1=Chest, 2=Wrist, ...) */\n async readSensorLocation(): Promise<number> {\n const dv = await this.read('body_sensor_location');\n return readUint8(dv);\n }\n\n /** Reset energy expended counter */\n async resetEnergyExpended(): Promise<void> {\n await this.write('heart_rate_control_point', new Uint8Array([1]));\n }\n}\n\n/**\n * Parse a raw Heart Rate Measurement characteristic value (UUID 0x2A37)\n * into a structured {@link HeartRateData} object.\n *\n * The first byte is a flags field that determines the format and which\n * optional fields are present. This function handles all flag combinations\n * defined by the Bluetooth SIG specification.\n *\n * @param dv - Raw characteristic value as a {@link DataView}.\n * @returns Parsed heart rate data with BPM, contact status, energy, and RR intervals.\n *\n * @example\n * ```ts\n * import { parseHeartRate } from '@beacio/core/profiles';\n *\n * // Manually parse a DataView from a notification\n * const data = parseHeartRate(characteristicValue);\n * console.log(`Heart rate: ${data.bpm} BPM`);\n * ```\n */\nexport function parseHeartRate(dv: DataView): HeartRateData {\n const flags = readUint8(dv, 0);\n let offset = 1;\n\n // Bit 0: Heart Rate Format — 0=UINT8, 1=UINT16\n const is16bit = (flags & 0x01) !== 0;\n const bpm = is16bit ? readUint16LE(dv, offset) : readUint8(dv, offset);\n offset += is16bit ? 2 : 1;\n\n // Bits 1-2: Sensor Contact\n const contactSupported = (flags & 0x04) !== 0;\n const contact = contactSupported ? (flags & 0x02) !== 0 : null;\n\n // Bit 3: Energy Expended present\n let energyExpended: number | null = null;\n if (flags & 0x08) {\n energyExpended = readUint16LE(dv, offset);\n offset += 2;\n }\n\n // Bit 4: RR-Interval present\n const rrIntervals: number[] = [];\n if (flags & 0x10) {\n // Consume only complete 2-byte RR pairs; a stray trailing byte (malformed\n // frame) is dropped rather than triggering an out-of-bounds read.\n while (offset + 2 <= dv.byteLength) {\n // RR intervals are in 1/1024 seconds units, convert to seconds\n rrIntervals.push(readUint16LE(dv, offset) / 1024);\n offset += 2;\n }\n }\n\n return { bpm, contact, energyExpended, rrIntervals };\n}\n"]}
var o="__beacioCDNStub";function e(){if(typeof navigator>"u")return "unsupported";let t=navigator;return t.beacio?.__beacio===true?"safari-extension":t.bluetooth&&!t.bluetooth[o]?"native":"unsupported"}function a(){if(typeof navigator>"u")return null;let t=navigator;return t.beacio?.__beacio===true?t.beacio:t.bluetooth&&!t.bluetooth[o]?t.bluetooth:null}export{o as a,e as b,a as c};//# sourceMappingURL=chunk-BSOWECSQ.mjs.map
//# sourceMappingURL=chunk-BSOWECSQ.mjs.map
{"version":3,"sources":["../src/platform.ts"],"names":["CDN_STUB_MARKER","detectPlatform","nav","getBluetoothAPI"],"mappings":"AAYO,IAAMA,CAAAA,CAAkB,kBAmBxB,SAASC,CAAAA,EAA2B,CACzC,GAAI,OAAO,SAAA,CAAc,GAAA,CAAa,OAAO,aAAA,CAG7C,IAAMC,CAAAA,CAAM,SAAA,CACZ,OAAIA,CAAAA,CAAI,MAAA,EAAQ,QAAA,GAAa,IAAA,CAAa,kBAAA,CAGtCA,CAAAA,CAAI,SAAA,EAAa,CAAEA,CAAAA,CAAI,SAAA,CAA4CF,CAAe,CAAA,CAAU,QAAA,CAEzF,aACT,CAaO,SAASG,CAAAA,EAAoC,CAClD,GAAI,OAAO,SAAA,CAAc,GAAA,CAAa,OAAO,IAAA,CAE7C,IAAMD,CAAAA,CAAM,SAAA,CAGZ,OAAIA,CAAAA,CAAI,MAAA,EAAQ,QAAA,GAAa,IAAA,CAAaA,CAAAA,CAAI,MAAA,CAG1CA,CAAAA,CAAI,SAAA,EAAa,CAAEA,CAAAA,CAAI,SAAA,CAA4CF,CAAe,CAAA,CAAUE,CAAAA,CAAI,SAAA,CAE7F,IACT","file":"chunk-BSOWECSQ.mjs","sourcesContent":["import type { Platform } from './types';\n\n/**\n * The own-property key our \"unsupported\" stub stamps on its faux\n * `navigator.bluetooth` (see auto.ts `createUnsupportedBluetoothStub`) so the\n * detectors below never mistake that stub for a real native implementation.\n *\n * Single-sourced here and consumed by every reader ({@link detectPlatform},\n * {@link getBluetoothAPI}, and browser-auto's banner gate) and by the writer in\n * auto.ts, so the native-vs-stub discriminator cannot silently drift apart. Pinned\n * by `tests/cdn-stub-marker.test.ts` (SB-TST-35).\n */\nexport const CDN_STUB_MARKER = '__beacioCDNStub';\n\n/**\n * Detect the current Web Bluetooth platform by probing `navigator`.\n *\n * **Detection order:**\n * 1. Safari extension -- `navigator.beacio?.__beacio === true`\n * 2. Native Web Bluetooth -- `navigator.bluetooth` exists (excluding CDN stubs)\n * 3. Unsupported -- No Web Bluetooth capability\n *\n * @returns The detected {@link Platform} value.\n *\n * @see {@link getBluetoothAPI} for getting the actual API object\n */\ninterface NavigatorWithBeacio {\n beacio?: { __beacio?: boolean } & Bluetooth;\n bluetooth?: Bluetooth;\n}\n\nexport function detectPlatform(): Platform {\n if (typeof navigator === 'undefined') return 'unsupported';\n\n // Safari extension: navigator.beacio with sentinel\n const nav = navigator as NavigatorWithBeacio;\n if (nav.beacio?.__beacio === true) return 'safari-extension';\n\n // Native Web Bluetooth (Chrome, Edge, etc.) — exclude CDN stubs\n if (nav.bluetooth && !(nav.bluetooth as { __beacioCDNStub?: boolean })[CDN_STUB_MARKER]) return 'native';\n\n return 'unsupported';\n}\n\n/**\n * Get the `Bluetooth` API object for the current platform.\n *\n * Returns `navigator.beacio` for the Safari extension, `navigator.bluetooth` for\n * native Web Bluetooth, or `null` if unsupported. CDN stubs (from `@beacio/detect`)\n * are excluded.\n *\n * @returns The platform's `Bluetooth` API object, or `null` if unavailable.\n *\n * @see {@link detectPlatform} for identifying the platform without getting the API\n */\nexport function getBluetoothAPI(): Bluetooth | null {\n if (typeof navigator === 'undefined') return null;\n\n const nav = navigator as NavigatorWithBeacio;\n\n // Safari extension provides full API on navigator.beacio\n if (nav.beacio?.__beacio === true) return nav.beacio as Bluetooth;\n\n // Native Web Bluetooth\n if (nav.bluetooth && !(nav.bluetooth as { __beacioCDNStub?: boolean })[CDN_STUB_MARKER]) return nav.bluetooth;\n\n return null;\n}\n"]}
import {b as b$1}from'./chunk-33IHM3NV.mjs';function b(t){return t instanceof DataView?new DataView(t.buffer,t.byteOffset,t.byteLength):t instanceof ArrayBuffer?new DataView(t):new DataView(t.buffer,t.byteOffset,t.byteLength)}function d(t,e){return t.includes(e)}var p=class{constructor(e){this.cleanups=[];this.device=e;}async connect(){await this.device.connect();}stop(){for(let e of this.cleanups.splice(0))e();}dispose(){this.stop();}async read(e){return this.device.read(this.service,e)}async write(e,i){return this.device.write(this.service,e,i)}async writeWithoutResponse(e,i){return this.device.writeWithoutResponse(this.service,e,i)}async sendChunked(e,i,r={}){return this.device.writeFragmented(this.service,e,i,{mode:"without-response",...r})}async writeValue(e,i,r){return r?.mode==="without-response"?this.device.writeWithoutResponse(this.service,e,i,r):this.device.write(this.service,e,i,r)}async getWriteLimits(){return this.device.getWriteLimits()}async getMtu(){return this.device.getMtu()}subscribe(e,i){let r=this.device.subscribe(this.service,e,i);return this.cleanups.push(r),()=>{r(),this.cleanups=this.cleanups.filter(s=>s!==r);}}onOverflow(e,i){let r=this.device.onCharacteristicOverflow(this.service,e,s=>{i(f(s));});return this.cleanups.push(r),()=>{r(),this.cleanups=this.cleanups.filter(s=>s!==r);}}};function f(t){let e=t.detail,i=e&&typeof e=="object"?e:{};return {evictedCount:typeof i.evictedCount=="number"?i.evictedCount:void 0,queueCapacity:typeof i.queueCapacity=="number"?i.queueCapacity:void 0,seq:typeof i.seq=="number"?i.seq:void 0,timestamp:typeof i.timestamp=="number"?i.timestamp:void 0}}function v(t){let e=b$1(t.service),i=Object.fromEntries(Object.entries(t.characteristics).map(([s,u])=>{let a={...u,uuid:b$1(u.uuid)};if(d(a.capabilities,"read")&&typeof a.parse!="function")throw new Error(`Characteristic ${s} declares read capability but is missing parse()`);if((d(a.capabilities,"write")||d(a.capabilities,"writeWithoutResponse"))&&typeof a.serialize!="function")throw new Error(`Characteristic ${s} declares write capability but is missing serialize()`);return [s,a]}));class r extends p{constructor(){super(...arguments);this.service=e;}getCharacteristicCapabilities(a){return i[a].capabilities}getCharacteristicUUID(a){return i[a].uuid}getServiceUUID(){return e}async readChar(a){let o=i[a],n=await this.read(o.uuid);return o.parse(n)}subscribeChar(a,o){let n=i[a];return this.subscribe(n.uuid,c=>{o(n.parse(c));})}async writeChar(a,o,n){let c=i[a],y=c.serialize(o),C=n?.mode??(d(c.capabilities,"write")?"with-response":"without-response");await this.writeValue(c.uuid,y,{...n,mode:C});}async getWriteLimits(){return super.getWriteLimits()}async getMtu(){return super.getMtu()}}return r.profileName=t.name,r.serviceUUID=e,r.characteristics=i,r}export{b as a,p as b,v as c};//# sourceMappingURL=chunk-FANWIUKA.mjs.map
//# sourceMappingURL=chunk-FANWIUKA.mjs.map
{"version":3,"sources":["../src/profiles/base.ts"],"names":["parseRawBytes","value","hasCapability","capabilities","capability","BaseProfile","device","cleanup","characteristic","options","callback","unsubscribe","candidate","event","decodeNativeOverflow","detail","meta","defineProfile","config","serviceUUID","resolveUUID","characteristics","name","definition","canonical","GeneratedProfile","raw","cb","serialized","mode"],"mappings":"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","file":"chunk-FANWIUKA.mjs","sourcesContent":["import type {\n NotificationCallback,\n NativeOverflowEvent,\n BeacioDevice,\n WriteFragmentedOptions,\n WriteFragmentedResult,\n WriteLimits,\n WriteOptions,\n} from '../index';\nimport { resolveUUID } from '../uuid';\n\n// Sound top type for \"any characteristic definition\": TRead is covariant\n// (parse return → unknown), TWrite is contravariant (serialize param → never),\n// so every CharacteristicDefinition<A, B> is assignable here without `any`.\ntype AnyCharacteristicDefinition = CharacteristicDefinition<unknown, never>;\n\nexport function parseRawBytes(value: BufferSource): DataView {\n if (value instanceof DataView) {\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n }\n\n if (value instanceof ArrayBuffer) {\n return new DataView(value);\n }\n\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n}\n\ntype UUIDLike = string;\ntype Capability = 'read' | 'write' | 'writeWithoutResponse' | 'notify';\ntype CapabilitySet = readonly Capability[];\n\ntype CharacteristicReadConfig<T> = {\n capabilities: readonly ['read'] | readonly ['read', ...Capability[]];\n parse: (dv: DataView) => T;\n};\n\ntype CharacteristicWriteConfig<W> = {\n capabilities: readonly ['write'] | readonly ['writeWithoutResponse'] | readonly ['write', ...Capability[]] | readonly ['writeWithoutResponse', ...Capability[]];\n serialize: (value: W) => BufferSource;\n};\n\ntype CharacteristicReadWriteConfig<T, W> = {\n capabilities:\n | readonly ['read', 'write']\n | readonly ['read', 'writeWithoutResponse']\n | readonly ['write', 'read']\n | readonly ['writeWithoutResponse', 'read']\n | readonly ['read', 'write', ...Capability[]]\n | readonly ['read', 'writeWithoutResponse', ...Capability[]]\n | readonly ['write', 'read', ...Capability[]]\n | readonly ['writeWithoutResponse', 'read', ...Capability[]];\n parse: (dv: DataView) => T;\n serialize: (value: W) => BufferSource;\n};\n\nexport type CharacteristicDefinition<TRead = never, TWrite = never> = {\n uuid: UUIDLike;\n} & (\n | CharacteristicReadConfig<TRead>\n | CharacteristicWriteConfig<TWrite>\n | CharacteristicReadWriteConfig<TRead, TWrite>\n);\n\nexport interface ProfileConfig<C extends Record<string, AnyCharacteristicDefinition>> {\n name: string;\n service: UUIDLike;\n characteristics: C;\n}\n\ntype CapabilityOf<T extends AnyCharacteristicDefinition> = T['capabilities'][number];\ntype ReadableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'read' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\ntype WritableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'write' extends CapabilityOf<C[K]>\n ? K\n : 'writeWithoutResponse' extends CapabilityOf<C[K]>\n ? K\n : never;\n}[keyof C] & string;\ntype NotifiableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'notify' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\n\ntype ReadValue<T> = T extends { parse: (dv: DataView) => infer TResult } ? TResult : never;\ntype WriteValue<T> = T extends { serialize: (value: infer TValue) => BufferSource } ? TValue : never;\ntype CanonicalCharacteristic<C extends AnyCharacteristicDefinition> = Omit<C, 'uuid'> & { uuid: string };\ntype ReadParser<T> = { parse: (dv: DataView) => T };\ntype WriteSerializer<T> = { serialize: (value: T) => BufferSource };\n\nfunction hasCapability(capabilities: CapabilitySet, capability: Capability): boolean {\n return capabilities.includes(capability);\n}\n\nexport abstract class BaseProfile {\n protected device: BeacioDevice;\n protected abstract readonly service: string;\n private cleanups: (() => void)[] = [];\n\n constructor(device: BeacioDevice) {\n this.device = device;\n }\n\n async connect(): Promise<void> {\n await this.device.connect();\n }\n\n stop(): void {\n for (const cleanup of this.cleanups.splice(0)) {\n cleanup();\n }\n }\n\n dispose(): void {\n this.stop();\n }\n\n protected async read(characteristic: string): Promise<DataView> {\n return this.device.read(this.service, characteristic);\n }\n\n protected async write(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.write(this.service, characteristic, value);\n }\n\n protected async writeWithoutResponse(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.writeWithoutResponse(this.service, characteristic, value);\n }\n\n /**\n * Send a payload of any size to `characteristic`, fragmenting it into\n * MTU-sized chunks. This is a thin passthrough to {@link BeacioDevice.writeFragmented},\n * which owns the (already-clamped) chunk-size derivation via the branded\n * `ChunkSize` smart-constructors in the core write-chunker — so the stride is\n * guaranteed `>= 1` and a zero-stride infinite loop is unrepresentable.\n *\n * Profiles MUST use this instead of hand-rolling a `for (offset += step)` /\n * `subarray()` / `writeWithoutResponse()` chunk loop (enforced by the\n * `no-restricted-syntax` guard scoped to `packages/profiles/src`). Defaults to\n * `mode: 'without-response'` — the serial-pipe convention (Nordic UART, HM-10).\n *\n * @param characteristic - Target characteristic UUID or alias on this profile's service.\n * @param value - Bytes to send. Accepts any {@link BufferSource}.\n * @param options - Fragmentation/retry overrides; `mode` defaults to `'without-response'`.\n * @returns The {@link WriteFragmentedResult} (bytes written, chunk size/count, retries).\n */\n protected async sendChunked(\n characteristic: string,\n value: BufferSource,\n options: WriteFragmentedOptions = {},\n ): Promise<WriteFragmentedResult> {\n return this.device.writeFragmented(this.service, characteristic, value, {\n mode: 'without-response',\n ...options,\n });\n }\n\n protected async writeValue(characteristic: string, value: BufferSource, options?: WriteOptions): Promise<void> {\n if (options?.mode === 'without-response') {\n return this.device.writeWithoutResponse(this.service, characteristic, value, options);\n }\n return this.device.write(this.service, characteristic, value, options);\n }\n\n protected async getWriteLimits(): Promise<WriteLimits> {\n return this.device.getWriteLimits();\n }\n\n protected async getMtu(): Promise<number | null> {\n return this.device.getMtu();\n }\n\n protected subscribe(characteristic: string, callback: NotificationCallback): () => void {\n const unsubscribe = this.device.subscribe(this.service, characteristic, callback);\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n\n /**\n * Observe NATIVE notification-queue overflows for `characteristic` on this\n * profile's service. The bounded Swift `EventQueue` evicts notifications under\n * sustained high-frequency load and the polyfill surfaces each eviction as a\n * `beacio:overflow` `CustomEvent` on the characteristic; this decodes that\n * event's `detail` into a typed {@link NativeOverflowEvent} and forwards it to\n * `callback`.\n *\n * Lifecycle parity with {@link subscribe}: the returned unsubscribe is also\n * registered into the profile's cleanup set, so {@link stop}/{@link dispose}\n * detach the listener too. A staleness `callback` should typically re-read the\n * affected characteristic to resynchronise any UI tracking the last notified\n * value rather than trusting that (now-stale) value.\n *\n * @param characteristic - Characteristic UUID or alias on this profile's service.\n * @param callback - Called with the decoded eviction metadata on each overflow.\n * @returns Unsubscribe function.\n */\n protected onOverflow(characteristic: string, callback: (event: NativeOverflowEvent) => void): () => void {\n const unsubscribe = this.device.onCharacteristicOverflow(this.service, characteristic, (event) => {\n callback(decodeNativeOverflow(event));\n });\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n}\n\n/**\n * Decode a `beacio:overflow` {@link Event} (a `CustomEvent` whose `detail` carries\n * the native bounded-queue eviction metadata) into a typed\n * {@link NativeOverflowEvent}. Each field is `undefined` when the native bridge\n * omitted it (forward-compat guard); a conforming bridge supplies all four. Total\n * and side-effect-free — never throws on a malformed or detail-less event.\n */\nfunction decodeNativeOverflow(event: Event): NativeOverflowEvent {\n const detail = (event as CustomEvent).detail as unknown;\n const meta = (detail && typeof detail === 'object') ? (detail as Record<string, unknown>) : {};\n return {\n evictedCount: typeof meta.evictedCount === 'number' ? meta.evictedCount : undefined,\n queueCapacity: typeof meta.queueCapacity === 'number' ? meta.queueCapacity : undefined,\n seq: typeof meta.seq === 'number' ? meta.seq : undefined,\n timestamp: typeof meta.timestamp === 'number' ? meta.timestamp : undefined,\n };\n}\n\ntype DefinedProfileInstance<C extends Record<string, AnyCharacteristicDefinition>> = BaseProfile & {\n readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>>;\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void;\n writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void>;\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability>;\n getCharacteristicUUID<K extends keyof C & string>(name: K): string;\n getServiceUUID(): string;\n getWriteLimits(): Promise<WriteLimits>;\n getMtu(): Promise<number | null>;\n};\n\nexport interface DefinedProfile<C extends Record<string, AnyCharacteristicDefinition>> {\n new (device: BeacioDevice): DefinedProfileInstance<C>;\n readonly profileName: string;\n readonly serviceUUID: string;\n readonly characteristics: {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n}\n\nexport function defineProfile<C extends Record<string, AnyCharacteristicDefinition>>(\n config: ProfileConfig<C>,\n): DefinedProfile<C> {\n const serviceUUID = resolveUUID(config.service);\n const characteristics = Object.fromEntries(\n Object.entries(config.characteristics).map(([name, definition]) => {\n const canonical = {\n ...definition,\n uuid: resolveUUID(definition.uuid),\n };\n\n if (hasCapability(canonical.capabilities, 'read') && typeof (canonical as { parse?: unknown }).parse !== 'function') {\n throw new Error(`Characteristic ${name} declares read capability but is missing parse()`);\n }\n\n if (\n (hasCapability(canonical.capabilities, 'write') || hasCapability(canonical.capabilities, 'writeWithoutResponse'))\n && typeof (canonical as { serialize?: unknown }).serialize !== 'function'\n ) {\n throw new Error(`Characteristic ${name} declares write capability but is missing serialize()`);\n }\n\n return [name, canonical];\n }),\n ) as unknown as {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n\n class GeneratedProfile extends BaseProfile {\n static readonly profileName = config.name;\n static readonly serviceUUID = serviceUUID;\n static readonly characteristics = characteristics;\n\n protected readonly service = serviceUUID;\n\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability> {\n return characteristics[name].capabilities;\n }\n\n getCharacteristicUUID<K extends keyof C & string>(name: K): string {\n return characteristics[name].uuid;\n }\n\n getServiceUUID(): string {\n return serviceUUID;\n }\n\n async readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n const raw = await this.read(characteristic.uuid);\n return characteristic.parse(raw);\n }\n\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n return this.subscribe(characteristic.uuid, (value) => {\n cb(characteristic.parse(value));\n });\n }\n\n async writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & WriteSerializer<WriteValue<C[K]>>;\n const serialized = characteristic.serialize(value);\n const mode = options?.mode ?? (hasCapability(characteristic.capabilities, 'write') ? 'with-response' : 'without-response');\n await this.writeValue(characteristic.uuid, serialized, { ...options, mode });\n }\n\n async getWriteLimits(): Promise<WriteLimits> {\n return super.getWriteLimits();\n }\n\n async getMtu(): Promise<number | null> {\n return super.getMtu();\n }\n}\n\n return GeneratedProfile as unknown as DefinedProfile<C>;\n}\n"]}
import {b as b$1,c}from'./chunk-BSOWECSQ.mjs';import {b}from'./chunk-33IHM3NV.mjs';var T={maxAttempts:0,delayMs:-1,backoffMultiplier:0},U=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),k={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the Beacio iOS app and enable the Safari extension. Use @beacio/core/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this Beacio instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},_=/\b(bluefy|web ble browser|webble browser)\b/gi;function M(o){let e=o.split(`
`,1)[0]??"";return e=e.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),e=e.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),e=e.replace(_,""),e=e.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),e=e.replace(/[\s.,;:]+$/g,"").trim(),e}var a=class o extends Error{constructor(e,t,r){let i=k[e];super(t??i),this.name="BeacioError",this.code=e,this.suggestion=k[e],this.isRetriable=U.has(e),this.retryAfterMs=r?.retryAfterMs;}static from(e,t="GATT_OPERATION_FAILED"){if(e instanceof o)return e;let r=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,i=e instanceof Error?e.message:String(e),n=M(i)||void 0,s=i.toLowerCase();switch(r){case "TypeError":return new o("INVALID_PARAMETER",n);case "NotFoundError":return new o("DEVICE_NOT_FOUND",n);case "NotAllowedError":case "SecurityError":return new o("PERMISSION_DENIED",n);case "NetworkError":return new o("DEVICE_DISCONNECTED",n,{retryAfterMs:1e3});case "TimeoutError":return new o("TIMEOUT",n,{retryAfterMs:1e3});case "InvalidStateError":if(s.includes("disconnect"))return new o("DEVICE_DISCONNECTED",n,{retryAfterMs:1e3});break;}return i.includes("User cancelled")||i.includes("User canceled")?new o("USER_CANCELLED"):s.includes("no devices found")||i.includes("No Devices")?new o("DEVICE_NOT_FOUND"):i.includes("No Services matching")||s.includes("service not found")?new o("SERVICE_NOT_FOUND",n):i.includes("No Characteristics matching")||s.includes("characteristic not found")?new o("CHARACTERISTIC_NOT_FOUND",n):i.includes("GATT Server is disconnected")||s.includes("disconnected")?new o("DEVICE_DISCONNECTED",n,{retryAfterMs:1e3}):s.includes("not supported")&&s.includes("read")?new o("CHARACTERISTIC_NOT_READABLE",n):s.includes("not supported")&&s.includes("write")?new o("CHARACTERISTIC_NOT_WRITABLE",n):s.includes("not supported")&&s.includes("notif")?new o("CHARACTERISTIC_NOT_NOTIFIABLE",n):s.includes("permission")?new o("PERMISSION_DENIED",n):new o(t,n)}};async function I(o,e=T){let t=e.maxAttempts>0?e.maxAttempts:3,r=e.delayMs>=0?e.delayMs:250,i=e.backoffMultiplier>=1?e.backoffMultiplier:1.5;if(!Number.isInteger(t)||t<=0)throw new a("INVALID_PARAMETER",`Invalid maxAttempts: ${t}. Must be a positive integer.`);if(!Number.isFinite(r)||r<0)throw new a("INVALID_PARAMETER",`Invalid delayMs: ${r}. Must be a non-negative number.`);if(!Number.isFinite(i)||i<1)throw new a("INVALID_PARAMETER",`Invalid backoffMultiplier: ${i}. Must be a number >= 1.`);for(let n=1;n<=t;n+=1)try{return await o(n)}catch(s){let u=a.from(s);if(n>=t||!u.isRetriable)throw u;let c=u.retryAfterMs??r*Math.pow(i,n-1);c>0&&await new Promise(l=>{setTimeout(l,c);});}throw new a("GATT_OPERATION_FAILED","Retry loop exited unexpectedly.")}var x=20;function E(o){if(!Number.isInteger(o)||o<=0)throw new a("INVALID_PARAMETER",`Invalid chunkSize: ${o}. Must be a positive integer.`);return o}function W(o,e=x){return Number.isInteger(o)&&o>0?o:E(e)}var S=class{constructor(e){this.deps=e;this.inFlightWrites=new Map;}async write(e,t,r,i){let n=this.deps.d(i?.timeoutMs),s=await this.deps.getCharacteristic(e,t),u=Symbol(`write:${e}:${t}`);this.inFlightWrites.set(u,{service:e,characteristic:t,aborted:false});try{if(i?.mode==="without-response"){await this.deps.withOptionalTimeout(s.writeValueWithoutResponse(r),n,"Write without response timed out");return}await this.deps.withOptionalTimeout(s.writeValueWithResponse(r),n,"Write with response timed out");}catch(c){throw this.inFlightWrites.get(u)?.aborted?new a("WRITE_INCOMPLETE",`Write incomplete for ${e}/${t}: disconnected before completion`,{retryAfterMs:1e3}):a.from(c)}finally{this.inFlightWrites.delete(u);}}async writeFragmented(e,t,r,i){let n=this.toUint8Array(r),s=n.byteLength;if(s===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0};let u=i?.chunkSize!==void 0?E(i.chunkSize):this.k(i?.mtu)??await this.v(void 0,i?.mode),c=i?.maxRetries??0,l=i?.retryDelayMs??0,d=0,m=0,g=0;for(let b=0;b<s;b+=u){let h=Math.min(b+u,s),f=new Uint8Array(n.subarray(b,h)),v=0;for(;;)try{await this.write(e,t,f,i),d+=f.byteLength,m+=1;break}catch(y){if(v>=c)throw d>0&&d<s?new a("WRITE_INCOMPLETE",`Write fragmented incomplete (${d}/${s} bytes written): ${this.errorMessage(y instanceof Error?y:String(y))}`,{retryAfterMs:1e3}):a.from(y);v+=1,g+=1,l>0&&await this.delay(l);}}return {bytesWritten:d,totalBytes:s,chunkSize:u,chunkCount:m,retryCount:g}}async writeLarge(e,t,r,i){let n=this.toUint8Array(r),s=n.byteLength;if(s===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0};let u=await this.v(i?.chunkSize,i?.mode),c=0,l=0;for(let d=0;d<s;d+=u){let m=Math.min(d+u,s),g=n.subarray(d,m),b=new Uint8Array(g);try{await this.write(e,t,b,i),c+=g.byteLength,l+=1;}catch(h){throw c>0&&c<s?new a("WRITE_INCOMPLETE",`Write incomplete (${c}/${s} bytes written): ${this.errorMessage(h instanceof Error?h:String(h))}`):a.from(h)}}if(c!==s)throw new a("WRITE_INCOMPLETE",`Write incomplete (${c}/${s} bytes written)`);return {bytesWritten:c,totalBytes:s,chunkSize:u,chunkCount:l}}async writeWithoutResponse(e,t,r,i){return this.write(e,t,r,{...i,mode:"without-response"})}async getWriteLimits(){if(!this.deps.isConnected())throw new a("DEVICE_DISCONNECTED");let e=this.deps.getTransport(),t=await e?.getWriteLimits?.(),r=t?.mtu??await e?.getMtu?.()??null;return {withResponse:t?.withResponse??null,withoutResponse:t?.withoutResponse??null,mtu:r}}async writeAuto(e,t,r,i){let n=this.toUint8Array(r),s=n.byteLength,u=new Uint8Array(n);if(s===0)return await this.write(e,t,u,i),{bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0,fragmented:false};let c=await this.v(i?.chunkSize,i?.mode);return s<=c?(await this.write(e,t,u,i),{bytesWritten:s,totalBytes:s,chunkSize:s,chunkCount:1,retryCount:0,fragmented:false}):{...await this.writeFragmented(e,t,u,i),fragmented:true}}R(){for(let e of this.inFlightWrites.values())e.aborted=true;}async v(e,t){if(e!==void 0)return E(e);let r=await this.getWriteLimits().catch(()=>({withResponse:null,withoutResponse:null,mtu:null})),i=t==="without-response"?r.withoutResponse:r.withResponse;return typeof i=="number"&&i>0?E(i):typeof r.mtu=="number"&&r.mtu>3?E(r.mtu-3):E(x)}k(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=3)throw new a("INVALID_PARAMETER",`Invalid mtu: ${e}. Must be an integer greater than 3.`);return E(e-3)}delay(e){return new Promise(t=>{setTimeout(t,e);})}toUint8Array(e){return e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}errorMessage(e){return e instanceof Error?e.message:String(e)}};var O=class O{constructor(e){this.deps=e;this.t=new Map;this.I=e=>{let t=e.target,r=t.value;if(r){for(let[i,n]of this.t)if((n.characteristic??this.deps.a.get(i))===t){let[u,c]=i.split(":");for(let l of n.callbacks)try{l(r);}catch(d){this.deps.e(a.from(d),{operation:"device.notification-callback",service:u,characteristic:c});}break}}};}getNotificationStates(){return this.t}subscribe(e,t,r,i){let{unsubscribe:n,ready:s}=this.p(e,t,r);s.catch(l=>{let d=a.from(l);try{i?.onError?.(d);}catch(m){this.deps.e(a.from(m),{operation:"device.subscribe.onError",service:e,characteristic:t});}});let u=i?.autoRecover??true;u&&this.g(this.charKey(e,t),e,t,r);let c=n;return ()=>{c(),u&&this.c(this.charKey(e,t),r);}}async subscribeAsync(e,t,r,i){let{unsubscribe:n,release:s,ready:u}=this.p(e,t,r),c=i?.autoRecover??true;c&&this.g(this.charKey(e,t),e,t,r);try{await u;}catch(l){let d=a.from(l);throw await s(),c&&this.c(this.charKey(e,t),r),d}return ()=>{n(),c&&this.c(this.charKey(e,t),r);}}async*notifications(e,t,r={maxQueueSize:O.DEFAULT_NOTIFICATION_QUEUE_SIZE}){let i=this.P(r.maxQueueSize??O.DEFAULT_NOTIFICATION_QUEUE_SIZE),n=r?.overflowStrategy??"error",s=[],u=0,c={resolve:null,reject:null,done:false,failure:null},l=h=>{if(!c.failure)if(c.resolve){let f=c.resolve;c.resolve=null,c.reject=null,f({value:h,done:false});}else {if(s.length>=i){u+=1;let f={service:e,characteristic:t,strategy:n,queueSize:i,droppedCount:u};this.deps.b(f);try{r?.onOverflow?.(f);}catch(v){this.deps.e(a.from(v),{operation:"device.notifications.onOverflow",service:e,characteristic:t});}if(n==="error"){let v=new a("GATT_OPERATION_FAILED",`Notification queue overflowed (maxQueueSize=${i}). Increase queue size or consume faster.`);c.failure=v;let y=c.reject;c.resolve=null,c.reject=null,y?.(v);return}if(n==="drop-oldest"&&s.shift(),n==="drop-newest")return}s.push(h);}},d=this.charKey(e,t);this.g(d,e,t,l);let{unsubscribe:m,release:g,ready:b}=this.p(e,t,l);try{await b;}catch(h){throw await g(),this.c(d,l),h}try{for(;!c.done;){if(c.failure)throw c.failure;if(s.length>0)yield s.shift();else {let h=await new Promise((f,v)=>{c.resolve=f,c.reject=v;});if(h.done){let f=this.deps.U();if(f&&!this.deps.isIntentionalDisconnect()){if(await f.promise,this.deps.isIntentionalDisconnect())return;continue}return}yield h.value;}}}finally{let h=c.resolve;c.resolve=null,c.reject=null,c.done=true,await g(),this.c(d,l),h&&h({value:void 0,done:true});}}D(e){for(let t of this.t.values())this.y(t),t.nativeActive&&this.E(t.characteristic,{operation:e});this.t.clear();}cleanupSubscriptions(){this.D("notification.cleanup");}suspendSubscriptions(){this.D("notification.suspend");}async recoverSubscriptions(){let e=[...this.deps.r.entries()];for(let[t,r]of e)try{for(let i of r.callbacks){let{ready:n}=this.p(r.service,r.characteristic,i);await n;}}catch(i){this.deps.r.delete(t);let n=a.from(i);this.deps._({service:r.service,characteristic:r.characteristic,error:n}),this.deps.e(n,{operation:"notification.recover",service:r.service,characteristic:r.characteristic});}}p(e,t,r){let i=this.charKey(e,t),n=this.t.get(i);n||(n={callbacks:new Set,characteristic:null,listenerAttached:false,nativeActive:false,reconcilePromise:null},this.t.set(i,n)),n.callbacks.add(r);let s=()=>{let u=this.t.get(i);return u?.callbacks.has(r)?(u.callbacks.delete(r),this.O(i,e,t)):Promise.resolve()};return {unsubscribe:()=>{s();},release:s,ready:this.O(i,e,t)}}O(e,t,r){let i=this.t.get(e);if(!i)return Promise.resolve();let u=(i.reconcilePromise??Promise.resolve()).catch(c=>{this.deps.e(a.from(c),{operation:"notification.reconcile",service:t,characteristic:r});}).then(async()=>{for(;;){if(this.t.get(e)!==i){await this.B(i);return}if(i.callbacks.size===0){if(this.y(i),i.nativeActive){i.nativeActive=false,await this.E(i.characteristic,{operation:"notification.stop",service:t,characteristic:r});continue}this.N(e,i);return}let l=i.characteristic??await this.deps.getCharacteristic(t,r);if(i.characteristic=l,i.listenerAttached||(l.addEventListener("characteristicvaluechanged",this.I),i.listenerAttached=true),!i.nativeActive){if(await l.startNotifications(),i.nativeActive=true,this.t.get(e)!==i){await this.B(i);return}if(i.callbacks.size===0)continue}if(i.callbacks.size!==0)return}}).finally(()=>{i.reconcilePromise===u&&(i.reconcilePromise=null,this.t.get(e)===i&&this.N(e,i));});return i.reconcilePromise=u,u}async B(e){this.y(e),e.nativeActive&&(e.nativeActive=false,await this.E(e.characteristic,{operation:"notification.deactivate"}));}y(e){!e.listenerAttached||!e.characteristic||(e.characteristic.removeEventListener("characteristicvaluechanged",this.I),e.listenerAttached=false);}N(e,t){this.t.get(e)===t&&(t.callbacks.size>0||t.nativeActive||t.reconcilePromise||this.t.delete(e));}P(e){if(!Number.isInteger(e)||e<=0)throw new a("INVALID_PARAMETER",`Invalid maxQueueSize: ${e}. Must be a positive integer.`);return e}async E(e,t){if(e)try{await e.stopNotifications();}catch(r){this.deps.e(a.from(r),t);}}charKey(e,t){return `${b(e)}:${b(t)}`}g(e,t,r,i){let n=this.deps.r.get(e);n||(n={service:t,characteristic:r,callbacks:new Set},this.deps.r.set(e,n)),n.callbacks.add(i);}c(e,t){let r=this.deps.r.get(e);r&&(r.callbacks.delete(t),r.callbacks.size===0&&this.deps.r.delete(e));}};O.DEFAULT_NOTIFICATION_QUEUE_SIZE=256;var A=O;function F(o){if((typeof o=="object"&&o!==null&&"name"in o&&typeof o.name=="string"?o.name:"")==="SecurityError")return true;let t=(o instanceof Error?o.message:String(o)).toLowerCase();return (t.includes("not allowed to access")||t.includes("blocklist")||t.includes("blocked"))&&t.includes("service")}function V(o){return new DOMException(`This site is not allowed to access the Bluetooth service ${o}. Add "${o}" to the optionalServices array in your requestDevice() options, then reconnect.`,"SecurityError")}var C=class{constructor(e,t={}){this.server=null;this.s=null;this.u=new Map;this.a=new Map;this.r=new Map;this.w=new Set;this.A=new Set;this.S=new Set;this.T=new Set;this.x=new Set;this.n=null;this.intentionalDisconnect=false;this.f=null;this.h=null;this.m=null;this.id=e.id,this.name=e.name??null,this.raw=e,this.hooks=t,this.i=new S({getCharacteristic:(r,i)=>this.getCharacteristic(r,i),e:(r,i)=>this.e(r,i),d:r=>this.d(r),withOptionalTimeout:(r,i,n)=>this.withOptionalTimeout(r,i,n),isConnected:()=>this.connected,getTransport:()=>this.raw.gatt}),this.o=new A({getCharacteristic:(r,i)=>this.getCharacteristic(r,i),e:(r,i)=>this.e(r,i),_:r=>this._(r),b:r=>this.b(r),r:this.r,a:this.a,U:()=>this.n,isIntentionalDisconnect:()=>this.intentionalDisconnect}),typeof e.addEventListener=="function"&&e.addEventListener("gattserverdisconnected",()=>{this.M();});}get connected(){return this.server?.connected??false}async connect(e){if(this.connected)return;e?.autoReconnect&&(this.h=e.autoReconnect===true?{}:e.autoReconnect),this.hooks.beforeConnect?.(this);let t=this.raw.gatt;if(!t)throw new a("GATT_OPERATION_FAILED","Device has no GATT server");let r=this.n;try{this.server=await t.connect(),this.f=null,await this.o.recoverSubscriptions();for(let i of this.A)try{i();}catch(n){this.e(a.from(n),{operation:"device.reconnected-listener"});}this.intentionalDisconnect=!1,this.hooks.onConnectionChange?.(this);}catch(i){throw this.server?.connected||(this.server=null,this.hooks.onConnectionChange?.(this)),a.from(i)}finally{this.n===r&&(this.n=null),r?.resolve();}}disconnect(){this.intentionalDisconnect=true,this.f="intentional",this.h=null,this.m?.abort(),this.m=null,this.i.R(),this.o.cleanupSubscriptions(),this.r.clear(),this.n&&(this.n.resolve(),this.n=null),this.server?.disconnect(),this.server=null,this.s=null,this.hooks.onConnectionChange?.(this);}async connectWithRetry(e=T){await I(async()=>{await this.connect();},e);}async read(e,t,r,i){let n=typeof r=="function"?r:void 0,s=typeof r=="function"?i:r,u=this.d(s?.timeoutMs),c=await this.getCharacteristic(e,t);try{let l=await this.withOptionalTimeout(c.readValue(),u,"Read timed out");return n?await n(l):l}catch(l){throw a.from(l)}}async write(e,t,r,i){return this.i.write(e,t,r,i)}async writeFragmented(e,t,r,i){return this.i.writeFragmented(e,t,r,i)}async writeLarge(e,t,r,i){return this.i.writeLarge(e,t,r,i)}async writeWithoutResponse(e,t,r,i){return this.i.writeWithoutResponse(e,t,r,i)}async getWriteLimits(){return this.i.getWriteLimits()}async getMtu(){return (await this.getWriteLimits()).mtu}async writeAuto(e,t,r,i){return this.i.writeAuto(e,t,r,i)}subscribe(e,t,r,i){return this.o.subscribe(e,t,r,i)}async subscribeAsync(e,t,r,i){return this.o.subscribeAsync(e,t,r,i)}onCharacteristicOverflow(e,t,r){let i=null,n=false;return this.getCharacteristic(e,t).then(s=>{n||(s.addEventListener("beacio:overflow",r),i=()=>s.removeEventListener("beacio:overflow",r));}).catch(s=>{this.e(a.from(s),{operation:"device.onCharacteristicOverflow",service:e,characteristic:t});}),()=>{n=true,i?.(),i=null;}}notifications(e,t,r){return this.o.notifications(e,t,r)}async watchAdvertisements(){if(typeof this.raw.watchAdvertisements!="function")throw new a("GATT_OPERATION_FAILED","watchAdvertisements is not supported on this device");await this.raw.watchAdvertisements();}async unwatchAdvertisements(){let e=this.raw;if(typeof e.unwatchAdvertisements!="function")throw new a("GATT_OPERATION_FAILED","unwatchAdvertisements is not supported on this device");await e.unwatchAdvertisements();}async forget(){if(typeof this.raw.forget!="function")throw new a("GATT_OPERATION_FAILED","forget is not supported on this device");await this.raw.forget();}async getPrimaryServices(){if(!this.connected)throw new a("DEVICE_DISCONNECTED");if(this.s)return this.s;try{let t=(await this.server.getPrimaryServices()).map(r=>{let i=this.u.get(r.uuid)??r;return this.u.set(r.uuid,i),i});return this.s=t,t}catch(e){throw a.from(e)}}async getEffectiveMtu(){let e=await this.getWriteLimits();return typeof e.mtu=="number"&&e.mtu>0?e.mtu:typeof e.withResponse=="number"&&e.withResponse>0?e.withResponse+3:typeof e.withoutResponse=="number"&&e.withoutResponse>0?e.withoutResponse+3:23}z(){return this.f}getActiveSubscriptions(){let e=this.o.getNotificationStates();return [...new Set([...e.keys(),...this.r.keys()])].map(r=>{let i=e.get(r),n=this.r.get(r),[s,u]=r.split(":");return {service:s,characteristic:u,callbackCount:i?.callbacks.size??n?.callbacks.size??0,autoRecovering:n!==void 0,nativeActive:i?.nativeActive??false}})}on(e,t){return e==="disconnected"&&this.w.add(t),e==="reconnected"&&this.A.add(t),e==="queue-overflow"&&this.S.add(t),e==="subscription-lost"&&this.T.add(t),()=>{this.off(e,t);}}off(e,t){e==="disconnected"&&this.w.delete(t),e==="reconnected"&&this.A.delete(t),e==="queue-overflow"&&this.S.delete(t),e==="subscription-lost"&&this.T.delete(t);}addErrorListener(e){return this.x.add(e),()=>{this.removeErrorListener(e);}}removeErrorListener(e){this.x.delete(e);}M(){let e=this.intentionalDisconnect?"intentional":"unexpected";if(this.f=e,this.i.R(),this.o.suspendSubscriptions(),this.u.clear(),this.s=null,this.a.clear(),this.server=null,this.hooks.onConnectionChange?.(this),this.r.size>0){let t,r=new Promise(i=>{t=i;});this.n={promise:r,resolve:t};}for(let t of this.w)try{t(e);}catch(r){this.e(a.from(r),{operation:"device.disconnected-listener"});}e==="unexpected"&&this.h&&this.startAutoReconnect(this.h);}startAutoReconnect(e){this.m?.abort();let t=new AbortController;this.m=t;let r=e.maxAttempts??1/0,i=e.initialDelayMs??1e3,n=e.maxDelayMs??3e4,s=e.backoffMultiplier??2;(async()=>{let c=i;for(let l=1;l<=r;l++){if(t.signal.aborted||(await new Promise(d=>{let m=setTimeout(d,c);t.signal.addEventListener("abort",()=>{clearTimeout(m),d();},{once:true});}),t.signal.aborted))return;try{await this.connect();return}catch{c=Math.min(c*s,n);}}this.e(new a("CONNECTION_TIMEOUT",`Auto-reconnect failed after ${r} attempts`),{operation:"device.auto-reconnect"});})();}async withOptionalTimeout(e,t,r){if(t===void 0)return e;let i=null,n=new Promise((s,u)=>{i=setTimeout(()=>{u(new a("TIMEOUT",r));},t);});try{return await Promise.race([e,n])}finally{i!==null&&clearTimeout(i);}}d(e){if(e!==void 0){if(!Number.isFinite(e)||e<=0)throw new a("INVALID_PARAMETER",`Invalid timeoutMs: ${e}. Must be a positive number.`);return e}}fanout(e,t,r){for(let i of e)try{i(t);}catch(n){this.e(a.from(n),{operation:r});}}b(e){this.fanout(this.S,e,"device.queue-overflow-listener");}_(e){this.fanout(this.T,e,"device.subscription-lost-listener");}e(e,t){for(let r of this.x)try{r(e,t);}catch{}}async getCharacteristic(e,t){if(!this.connected)throw new a("DEVICE_DISCONNECTED");let r=this.charKey(e,t),i=this.a.get(r);if(i)return i;let n=await this.getService(e),s=b(t);try{let u=await n.getCharacteristic(s);return this.a.set(r,u),u}catch(u){throw a.from(u)}}async getService(e){let t=b(e);if(this.s){let i=this.s.find(n=>n.uuid===t);if(i)return i}let r=this.u.get(t);if(r)return r;try{let i=await this.server.getPrimaryService(t);return this.u.set(t,i),i}catch(i){throw F(i)?V(t):a.from(i)}}charKey(e,t){return `${b(e)}:${b(t)}`}};var D={platform:"auto",maxConnections:0,defaultOptionalServices:[]};var P=class{constructor(e){this.l=e;}unsupported(){throw this.l()}requestPermission(){this.unsupported();}requestBackgroundConnection(e){this.unsupported();}registerCharacteristicNotifications(e){this.unsupported();}registerBeaconScanning(e){this.unsupported();}getRegistrations(){this.unsupported();}unregister(e){this.unsupported();}update(e,t){this.unsupported();}connect(e){return this.requestBackgroundConnection(e)}subscribe(e){return this.registerCharacteristicNotifications(e)}scan(e){return this.registerBeaconScanning(e)}list(){return this.getRegistrations()}destroy(){}},N=class extends EventTarget{constructor(t){super();this.onwriterequest=null;this.onsubscriptionchange=null;this.onconnectionstatechange=null;this.onadvertisingstatechange=null;this.onnotificationready=null;this.l=t;}get advertising(){return false}unsupported(){throw this.l()}advertise(t){this.unsupported();}addService(t){this.unsupported();}stopAdvertising(){this.unsupported();}send(t){this.unsupported();}destroy(){}},L=class{constructor(e=D){this.devices=new Map;this.registeredOptionalServices=new Set;this.platform=e.platform==="auto"?b$1():e.platform,this.maxConnections=this.W(e.maxConnections),e.defaultOptionalServices.length>0&&this.registerServices(e.defaultOptionalServices),this.bluetooth=this.platform!=="unsupported"?c():null,this.runtimeBluetooth=this.bluetooth,this.isSupported=this.bluetooth!==null,this.C=()=>this.platform==="unsupported"?new a("BLUETOOTH_UNAVAILABLE"):new a("GATT_OPERATION_FAILED","This Beacio feature requires the iOS Safari Beacio extension runtime."),this.unsupportedBackgroundSync=new P(this.C),this.unsupportedPeripheral=new N(this.C);}get backgroundSync(){return this.runtimeBluetooth?.backgroundSync??this.unsupportedBackgroundSync}get peripheral(){return this.runtimeBluetooth?.peripheral??this.unsupportedPeripheral}async requestDevice(e){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");try{let t=await this.bluetooth.requestDevice(this.V(e)??{acceptAllDevices:!0});return this.L(t)}catch(t){throw a.from(t,"DEVICE_NOT_FOUND")}}registerServices(e){for(let t of e)this.registeredOptionalServices.add(b(t));}async getDevices(){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");let e=this.bluetooth;if(typeof e.getDevices!="function")return [];try{return (await e.getDevices()).map(r=>this.L(r))}catch(t){throw a.from(t)}}async getAvailability(){if(!this.bluetooth)return false;try{return await this.bluetooth.getAvailability()}catch{return false}}async requestLEScan(e={acceptAllAdvertisements:true}){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");let t=this.bluetooth;if(typeof t.requestLEScan!="function")return null;try{return await t.requestLEScan(e)}catch(r){throw a.from(r)}}V(e){let t=this.F(e?.optionalServices);if(!e)return t?{optionalServices:t}:void 0;let r=n=>{if(n)return n.map(s=>b(s))},i={};return e.acceptAllDevices!==void 0&&(i.acceptAllDevices=e.acceptAllDevices),e.optionalManufacturerData!==void 0&&(i.optionalManufacturerData=e.optionalManufacturerData),e.filters&&(i.filters=e.filters.map(n=>({...n,services:r(n.services)}))),e.exclusionFilters&&(i.exclusionFilters=e.exclusionFilters.map(n=>({...n,services:r(n.services)}))),t&&(i.optionalServices=t),i}F(e){if(!e&&this.registeredOptionalServices.size===0)return;let t=new Set;for(let r of e??[])t.add(b(r));for(let r of this.registeredOptionalServices)t.add(r);return [...t]}W(e){if(e===0)return null;if(!Number.isInteger(e)||e<0)throw new a("INVALID_PARAMETER",`Invalid maxConnections: ${e}. Must be 0 (unlimited) or a positive integer.`);return e}L(e){let t=this.devices.get(e.id);if(t)return t;let r=new C(e,{beforeConnect:i=>{this.G(i);},onConnectionChange:i=>{this.devices.set(i.id,i);}});return this.devices.set(e.id,r),r}G(e){if(this.maxConnections===null||(this.devices.set(e.id,e),e.connected))return;let t=[...this.devices.values()].filter(r=>r.connected).length;if(t>=this.maxConnections)throw new a("CONNECTION_LIMIT_REACHED",`Connection limit reached (${t}/${this.maxConnections}). Disconnect another device or increase maxConnections before connecting ${e.name??e.id}.`,{retryAfterMs:1e3})}};function q(o){if(!Number.isInteger(o)||o<0||o>100)throw new a("INVALID_PARAMETER",`Invalid percent: ${o}. Must be an integer in 0..100.`);return o}function z(o){return Number.isFinite(o)?Math.min(100,Math.max(0,Math.trunc(o))):0}function w(o,e,t,r){if(!Number.isInteger(t)||t<0||t+r>e.byteLength)throw new a("INVALID_PARAMETER",`${o}: cannot read ${r} byte${r===1?"":"s"} at offset ${t} of a ${e.byteLength}-byte DataView (value too short).`)}function G(o,e=0){return w("readUint8",o,e,1),o.getUint8(e)}function $(o,e=0){return w("readUint16LE",o,e,2),o.getUint16(e,true)}function H(o,e=0){return w("readUint16BE",o,e,2),o.getUint16(e,false)}function Q(o,e=0){return w("readInt16LE",o,e,2),o.getInt16(e,true)}function j(o,e=0){return w("readUint32LE",o,e,4),o.getUint32(e,true)}function K(o,e=0){return w("readFloat32LE",o,e,4),o.getFloat32(e,true)}function Y(o){return new TextDecoder().decode(o.buffer.slice(o.byteOffset,o.byteOffset+o.byteLength))}function Z(o){return new Uint8Array(o.buffer.slice(o.byteOffset,o.byteOffset+o.byteLength))}export{T as a,a as b,I as c,E as d,W as e,C as f,D as g,L as h,q as i,z as j,G as k,$ as l,H as m,Q as n,j as o,K as p,Y as q,Z as r};//# sourceMappingURL=chunk-GAX5WAKV.mjs.map
//# sourceMappingURL=chunk-GAX5WAKV.mjs.map

Sorry, the diff of this file is too big to display

import {b}from'./chunk-FANWIUKA.mjs';var t=new TextDecoder,s=class extends b{constructor(){super(...arguments);this.service="device_information";}async readModelNumber(){return this.readString("model_number_string")}async readSerialNumber(){return this.readString("serial_number_string")}async readFirmwareRevision(){return this.readString("firmware_revision_string")}async readHardwareRevision(){return this.readString("hardware_revision_string")}async readSoftwareRevision(){return this.readString("software_revision_string")}async readManufacturerName(){return this.readString("manufacturer_name_string")}async readSystemId(){return this.read("system_id")}async readAll(){let e={},r=async(a,n)=>{try{e[n]=await a();}catch{}};return await Promise.all([r(()=>this.readModelNumber(),"modelNumber"),r(()=>this.readSerialNumber(),"serialNumber"),r(()=>this.readFirmwareRevision(),"firmwareRevision"),r(()=>this.readHardwareRevision(),"hardwareRevision"),r(()=>this.readSoftwareRevision(),"softwareRevision"),r(()=>this.readManufacturerName(),"manufacturerName"),r(()=>this.readSystemId(),"systemId")]),e}async readString(e){let r=await this.read(e);return t.decode(r.buffer)}};export{s as a};//# sourceMappingURL=chunk-HBDTBK5K.mjs.map
//# sourceMappingURL=chunk-HBDTBK5K.mjs.map
{"version":3,"sources":["../src/profiles/device-info.ts"],"names":["decoder","DeviceInfoProfile","BaseProfile","info","tryRead","fn","key","characteristic","dv"],"mappings":"qCAEA,IAAMA,EAAU,IAAI,WAAA,CAqDPC,EAAN,cAAgCC,CAAY,CAA5C,WAAA,EAAA,CAAA,KAAA,CAAA,GAAA,SAAA,CAAA,CACL,IAAA,CAAmB,OAAA,CAAU,qBAAA,CAE7B,MAAM,eAAA,EAAmC,CACvC,OAAO,IAAA,CAAK,UAAA,CAAW,qBAAqB,CAC9C,CAEA,MAAM,gBAAA,EAAoC,CACxC,OAAO,IAAA,CAAK,WAAW,sBAAsB,CAC/C,CAEA,MAAM,oBAAA,EAAwC,CAC5C,OAAO,KAAK,UAAA,CAAW,0BAA0B,CACnD,CAEA,MAAM,sBAAwC,CAC5C,OAAO,IAAA,CAAK,UAAA,CAAW,0BAA0B,CACnD,CAEA,MAAM,oBAAA,EAAwC,CAC5C,OAAO,IAAA,CAAK,UAAA,CAAW,0BAA0B,CACnD,CAEA,MAAM,oBAAA,EAAwC,CAC5C,OAAO,IAAA,CAAK,WAAW,0BAA0B,CACnD,CAEA,MAAM,cAAkC,CACtC,OAAO,KAAK,IAAA,CAAK,WAAW,CAC9B,CAGA,MAAM,OAAA,EAA+B,CACnC,IAAMC,CAAAA,CAAmB,GACnBC,CAAAA,CAAU,MAAOC,EAA4BC,CAAAA,GAA0B,CAC3E,GAAI,CAAGH,EAAiCG,CAAG,CAAA,CAAI,MAAMD,CAAAA,GAAM,MAAQ,CAA0C,CAC/G,CAAA,CACA,OAAA,MAAM,QAAQ,GAAA,CAAI,CAChBD,EAAQ,IAAM,IAAA,CAAK,iBAAgB,CAAG,aAAa,CAAA,CACnDA,CAAAA,CAAQ,IAAM,IAAA,CAAK,gBAAA,GAAoB,cAAc,CAAA,CACrDA,EAAQ,IAAM,IAAA,CAAK,oBAAA,EAAqB,CAAG,kBAAkB,CAAA,CAC7DA,CAAAA,CAAQ,IAAM,IAAA,CAAK,oBAAA,GAAwB,kBAAkB,CAAA,CAC7DA,CAAAA,CAAQ,IAAM,KAAK,oBAAA,EAAqB,CAAG,kBAAkB,CAAA,CAC7DA,CAAAA,CAAQ,IAAM,IAAA,CAAK,oBAAA,EAAqB,CAAG,kBAAkB,EAC7DA,CAAAA,CAAQ,IAAM,KAAK,YAAA,EAAa,CAAG,UAAU,CAC/C,CAAC,CAAA,CACMD,CACT,CAEA,MAAc,UAAA,CAAWI,EAAyC,CAChE,IAAMC,EAAK,MAAM,IAAA,CAAK,IAAA,CAAKD,CAAc,EACzC,OAAOP,CAAAA,CAAQ,OAAOQ,CAAAA,CAAG,MAAM,CACjC,CACF","file":"chunk-HBDTBK5K.mjs","sourcesContent":["import { BaseProfile } from './base';\n\nconst decoder = new TextDecoder();\n\n/**\n * Aggregated device information read from the Device Information Service.\n *\n * All fields are optional because a peripheral may not expose every\n * characteristic. Use {@link DeviceInfoProfile.readAll} to populate as\n * many fields as the device supports in a single call.\n */\nexport interface DeviceInfo {\n /** Model number string (characteristic 0x2A24). */\n modelNumber?: string;\n /** Serial number string (characteristic 0x2A25). */\n serialNumber?: string;\n /** Firmware revision string (characteristic 0x2A26). */\n firmwareRevision?: string;\n /** Hardware revision string (characteristic 0x2A27). */\n hardwareRevision?: string;\n /** Software revision string (characteristic 0x2A28). */\n softwareRevision?: string;\n /** Manufacturer name string (characteristic 0x2A29). */\n manufacturerName?: string;\n /** Raw System ID value (characteristic 0x2A23) as a {@link DataView}. */\n systemId?: DataView;\n}\n\n/**\n * BLE Device Information Service profile (UUID 0x180A).\n *\n * Reads standard device metadata characteristics such as model number,\n * manufacturer name, firmware revision, and more. String values are\n * decoded from raw bytes with {@link TextDecoder}.\n *\n * @example\n * ```ts\n * import { DeviceInfoProfile } from '@beacio/core/profiles';\n *\n * const info = new DeviceInfoProfile(device);\n * await info.connect();\n *\n * // Read individual fields\n * const manufacturer = await info.readManufacturerName();\n * const model = await info.readModelNumber();\n * console.log(`${manufacturer} ${model}`);\n *\n * // Or read all available fields at once\n * const all = await info.readAll();\n * console.log(all);\n * // { modelNumber: 'Sensor-v2', manufacturerName: 'Acme', ... }\n *\n * info.stop();\n * ```\n */\nexport class DeviceInfoProfile extends BaseProfile {\n protected readonly service = 'device_information';\n\n async readModelNumber(): Promise<string> {\n return this.readString('model_number_string');\n }\n\n async readSerialNumber(): Promise<string> {\n return this.readString('serial_number_string');\n }\n\n async readFirmwareRevision(): Promise<string> {\n return this.readString('firmware_revision_string');\n }\n\n async readHardwareRevision(): Promise<string> {\n return this.readString('hardware_revision_string');\n }\n\n async readSoftwareRevision(): Promise<string> {\n return this.readString('software_revision_string');\n }\n\n async readManufacturerName(): Promise<string> {\n return this.readString('manufacturer_name_string');\n }\n\n async readSystemId(): Promise<DataView> {\n return this.read('system_id');\n }\n\n /** Read all available device info fields. Missing fields return undefined. */\n async readAll(): Promise<DeviceInfo> {\n const info: DeviceInfo = {};\n const tryRead = async (fn: () => Promise<unknown>, key: keyof DeviceInfo) => {\n try { (info as Record<string, unknown>)[key] = await fn(); } catch { /* optional—field may be unsupported */ }\n };\n await Promise.all([\n tryRead(() => this.readModelNumber(), 'modelNumber'),\n tryRead(() => this.readSerialNumber(), 'serialNumber'),\n tryRead(() => this.readFirmwareRevision(), 'firmwareRevision'),\n tryRead(() => this.readHardwareRevision(), 'hardwareRevision'),\n tryRead(() => this.readSoftwareRevision(), 'softwareRevision'),\n tryRead(() => this.readManufacturerName(), 'manufacturerName'),\n tryRead(() => this.readSystemId(), 'systemId'),\n ]);\n return info;\n }\n\n private async readString(characteristic: string): Promise<string> {\n const dv = await this.read(characteristic);\n return decoder.decode(dv.buffer);\n }\n}\n"]}
var t="https://beacio.com/setup";
export{t as a};//# sourceMappingURL=chunk-L7SIDO2A.mjs.map
//# sourceMappingURL=chunk-L7SIDO2A.mjs.map
{"version":3,"sources":["../src/urls.ts"],"names":["SETUP_URL"],"mappings":"AAeO,IAAMA,CAAAA,CAAY","file":"chunk-L7SIDO2A.mjs","sourcesContent":["/**\n * Canonical first-party URLs for the beacio platform.\n *\n * Single source of truth so independent packages (detect's install banner, the\n * react-sdk InstallationWizard) cannot re-diverge onto stale hosts/paths. Lives\n * in @beacio/core because both @beacio/detect and @beacio/react depend on core\n * (core depends on nothing) — importing from here introduces no dependency cycle.\n */\n\n/**\n * The guided zero-config onboarding page: install → enable the Safari extension\n * → return. The default destination when no operator-supplied onboarding/App\n * Store URL override is provided. Authoritative host + path per\n * outreach/campaign/11-rebrand-manifest.md.\n */\nexport const SETUP_URL = 'https://beacio.com/setup';\n"]}
import {b}from'./chunk-TZAX4UTD.mjs';function l(){if(typeof navigator>"u")return false;let t=navigator.userAgent,n=/iPad|iPhone|iPod/.test(t)||navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1,e=/^((?!chrome|android|crios|fxios).)*safari/i.test(t);return n&&e}async function s(){try{let{detectPlatform:t}=await import('./platform-GVNO2UVN.mjs');if(t()==="safari-extension")return "active"}catch{}return new Promise(t=>{let n=b();if(n!=="not-installed"){t(n);return}let e=0,i=setInterval(()=>{e++;let o=b();o!=="not-installed"&&(clearInterval(i),t(o)),e>20&&(clearInterval(i),t("not-installed"));},100);})}async function c(){return await s()!=="not-installed"}export{l as a,s as b,c};//# sourceMappingURL=chunk-QLQHTSFL.mjs.map
//# sourceMappingURL=chunk-QLQHTSFL.mjs.map
{"version":3,"sources":["../src/detect/detect.ts"],"names":["isIOSSafari","ua","isIOS","isSafari","getExtensionInstallState","detectPlatform","resolve","immediateState","getInstallState","checks","interval","state","isExtensionInstalled"],"mappings":"qCAWO,SAASA,CAAAA,EAAuB,CACrC,GAAI,OAAO,SAAA,CAAc,IAAa,OAAO,MAAA,CAE7C,IAAMC,CAAAA,CAAK,SAAA,CAAU,SAAA,CACfC,EACJ,kBAAA,CAAmB,IAAA,CAAKD,CAAE,CAAA,EACzB,SAAA,CAAU,QAAA,GAAa,UAAA,EAAc,SAAA,CAAU,cAAA,CAAiB,CAAA,CAC7DE,CAAAA,CAAW,4CAAA,CAA6C,IAAA,CAAKF,CAAE,EAErE,OAAOC,CAAAA,EAASC,CAClB,CAEA,eAAsBC,CAAAA,EAA2D,CAG/E,GAAI,CACF,GAAM,CAAE,cAAA,CAAAC,CAAe,CAAA,CAAI,MAAM,OAAO,yBAAa,CAAA,CACrD,GAAIA,CAAAA,EAAe,GAAM,kBAAA,CAAoB,OAAO,QACtD,CAAA,KAAQ,CAAqE,CAE7E,OAAO,IAAI,QAASC,CAAAA,EAAY,CAE9B,IAAMC,CAAAA,CAAiBC,CAAAA,EAAgB,CACvC,GAAID,CAAAA,GAAmB,eAAA,CAAiB,CACtCD,CAAAA,CAAQC,CAAc,CAAA,CACtB,MACF,CAIA,IAAIE,CAAAA,CAAS,CAAA,CACPC,CAAAA,CAAW,WAAA,CAAY,IAAM,CACjCD,CAAAA,EAAAA,CACA,IAAME,CAAAA,CAAQH,CAAAA,EAAgB,CAC1BG,CAAAA,GAAU,kBACZ,aAAA,CAAcD,CAAQ,CAAA,CACtBJ,CAAAA,CAAQK,CAAK,CAAA,CAAA,CAEXF,CAAAA,CAAS,EAAA,GAEX,aAAA,CAAcC,CAAQ,CAAA,CACtBJ,CAAAA,CAAQ,eAAe,CAAA,EAE3B,EAAG,GAAG,EACR,CAAC,CACH,CAEA,eAAsBM,CAAAA,EAAyC,CAC7D,OAAQ,MAAMR,CAAAA,EAAyB,GAAO,eAChD","file":"chunk-QLQHTSFL.mjs","sourcesContent":["/**\n * Platform detection utilities for Beacio\n */\n\n// SB-SDK-12: the install-state marker derivation now lives in the single shared\n// install-state module (consumed here, by the react-sdk ExtensionDetector, and by\n// the headless API), so the detection logic is shared rather than duplicated.\nimport { getInstallState, type ExtensionInstallState } from './install-state';\n\nexport type { ExtensionInstallState } from './install-state';\n\nexport function isIOSSafari(): boolean {\n if (typeof navigator === 'undefined') return false;\n\n const ua = navigator.userAgent;\n const isIOS =\n /iPad|iPhone|iPod/.test(ua) ||\n (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);\n const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(ua);\n\n return isIOS && isSafari;\n}\n\nexport async function getExtensionInstallState(): Promise<ExtensionInstallState> {\n // Fast-path: use core's platform detection (detect now lives INSIDE @beacio/core,\n // so this is an intra-package import — no optional-peer boundary any more).\n try {\n const { detectPlatform } = await import('../platform');\n if (detectPlatform() === 'safari-extension') return 'active';\n } catch { /* defensive — platform probe must never throw the install flow */ }\n\n return new Promise((resolve) => {\n // Method 1: Check for the global marker set by injected-full.ts\n const immediateState = getInstallState();\n if (immediateState !== 'not-installed') {\n resolve(immediateState);\n return;\n }\n\n // Method 3: Wait briefly for injection to complete\n // The content script runs at document_start, so injection should be fast\n let checks = 0;\n const interval = setInterval(() => {\n checks++;\n const state = getInstallState();\n if (state !== 'not-installed') {\n clearInterval(interval);\n resolve(state);\n }\n if (checks > 20) {\n // 2 seconds max wait\n clearInterval(interval);\n resolve('not-installed');\n }\n }, 100);\n });\n}\n\nexport async function isExtensionInstalled(): Promise<boolean> {\n return (await getExtensionInstallState()) !== 'not-installed';\n}\n"]}
import {b}from'./chunk-33IHM3NV.mjs';function c(e){return !Array.isArray(e)&&Array.isArray(e.services)}function v(...e){let i=new Set;for(let r of e){let o=c(r)?r.services:r;for(let t of o)i.add(b(t));}return [...i]}export{v as a};//# sourceMappingURL=chunk-SOZ26EXK.mjs.map
//# sourceMappingURL=chunk-SOZ26EXK.mjs.map
{"version":3,"sources":["../src/profiles/services.ts"],"names":["isProfileWithServices","source","deriveOptionalServices","sources","merged","services","service","resolveUUID"],"mappings":"qCAmBA,SAASA,CAAAA,CAAsBC,EAA+D,CAC5F,OAAO,CAAC,KAAA,CAAM,OAAA,CAAQA,CAAM,CAAA,EAAK,KAAA,CAAM,OAAA,CAASA,EAA+B,QAAQ,CACzF,CA8BO,SAASC,CAAAA,CAAAA,GAA0BC,EAA6C,CACrF,IAAMC,CAAAA,CAAS,IAAI,GAAA,CACnB,IAAA,IAAWH,KAAUE,CAAAA,CAAS,CAC5B,IAAME,CAAAA,CAAWL,CAAAA,CAAsBC,CAAM,CAAA,CAAIA,CAAAA,CAAO,QAAA,CAAWA,CAAAA,CACnE,IAAA,IAAWK,CAAAA,IAAWD,EACpBD,CAAAA,CAAO,GAAA,CAAIG,CAAAA,CAAYD,CAAO,CAAC,EAEnC,CACA,OAAO,CAAC,GAAGF,CAAM,CACnB","file":"chunk-SOZ26EXK.mjs","sourcesContent":["import { resolveUUID } from '../index';\n\n/**\n * A profile class that declares the GATT services it (and its device family) may\n * reach after connection, as a static `services` array. {@link deriveOptionalServices}\n * reads this so a caller can pass the profile itself instead of hand-copying its\n * service UUIDs into `optionalServices`.\n */\nexport interface ProfileWithServices {\n readonly services: readonly string[];\n}\n\n/**\n * A source of service UUIDs accepted by {@link deriveOptionalServices}: either a\n * profile class carrying a static `services` array, or a raw list of service\n * names / 4-8-hex / full 128-bit UUID strings.\n */\nexport type OptionalServicesSource = ProfileWithServices | readonly string[];\n\nfunction isProfileWithServices(source: OptionalServicesSource): source is ProfileWithServices {\n return !Array.isArray(source) && Array.isArray((source as ProfileWithServices).services);\n}\n\n/**\n * Flatten one or more profiles / service-UUID arrays into a single canonical,\n * de-duped, lowercase 128-bit `string[]` suitable for `optionalServices` (or\n * {@link Beacio.registerServices}). Every entry is resolved via the core\n * {@link resolveUUID} (names like `'battery_service'`, 4/8-hex, and full UUIDs\n * are all accepted) and de-duped while preserving first-seen order.\n *\n * This retires the hand-maintained parallel `optionalServices` lists a multi-\n * device integration would otherwise keep in sync: declare the profiles (or a\n * vendor bundle such as `StorzBickel.allServices()`) once and derive the list.\n *\n * Pure and idempotent: `deriveOptionalServices(deriveOptionalServices(x))` equals\n * `deriveOptionalServices(x)`, because the output is already canonical UUIDs that\n * {@link resolveUUID} passes through unchanged.\n *\n * @param sources - Profile classes (with a static `services` array) and/or raw\n * service-UUID arrays (names, 4/8-hex, or full 128-bit UUID strings).\n * @returns De-duped canonical lowercase 128-bit service UUIDs, first-seen order.\n * @throws {TypeError} If any value is not a resolvable UUID or known SIG name.\n *\n * @example\n * ```ts\n * import { deriveOptionalServices, NordicUARTProfile, HeartRateProfile } from '@beacio/core/profiles';\n *\n * const optionalServices = deriveOptionalServices(NordicUARTProfile, HeartRateProfile);\n * const device = await ble.requestDevice({ acceptAllDevices: true, optionalServices });\n * ```\n */\nexport function deriveOptionalServices(...sources: OptionalServicesSource[]): string[] {\n const merged = new Set<string>();\n for (const source of sources) {\n const services = isProfileWithServices(source) ? source.services : source;\n for (const service of services) {\n merged.add(resolveUUID(service));\n }\n }\n return [...merged];\n}\n"]}
import {k}from'./chunk-GAX5WAKV.mjs';import {b}from'./chunk-FANWIUKA.mjs';var s=class extends b{constructor(){super(...arguments);this.service="battery_service";}async readLevel(){let e=await this.read("battery_level");return k(e)}onLevelChange(e){return this.subscribe("battery_level",a=>{e(k(a));})}};export{s as a};//# sourceMappingURL=chunk-TPMOXHNG.mjs.map
//# sourceMappingURL=chunk-TPMOXHNG.mjs.map
{"version":3,"sources":["../src/profiles/battery.ts"],"names":["BatteryProfile","BaseProfile","dv","readUint8","callback"],"mappings":"0EA8BO,IAAMA,EAAN,cAA6BC,CAAY,CAAzC,WAAA,EAAA,CAAA,KAAA,CAAA,GAAA,SAAA,CAAA,CACL,KAAmB,OAAA,CAAU,kBAAA,CAG7B,MAAM,SAAA,EAA6B,CACjC,IAAMC,CAAAA,CAAK,MAAM,IAAA,CAAK,IAAA,CAAK,eAAe,CAAA,CAC1C,OAAOC,CAAAA,CAAUD,CAAE,CACrB,CAGA,aAAA,CAAcE,EAA+C,CAC3D,OAAO,KAAK,SAAA,CAAU,eAAA,CAAkBF,CAAAA,EAAO,CAC7CE,EAASD,CAAAA,CAAUD,CAAE,CAAC,EACxB,CAAC,CACH,CACF","file":"chunk-TPMOXHNG.mjs","sourcesContent":["import { readUint8 } from '../index';\nimport { BaseProfile } from './base';\n\n/**\n * BLE Battery Service profile (UUID 0x180F).\n *\n * Reads and subscribes to the Battery Level characteristic (0x2A19),\n * which reports the current charge level as a percentage (0--100).\n *\n * @example\n * ```ts\n * import { BatteryProfile } from '@beacio/core/profiles';\n *\n * const battery = new BatteryProfile(device);\n * await battery.connect();\n *\n * // One-shot read\n * const level = await battery.readLevel();\n * console.log(`Battery: ${level}%`);\n *\n * // Subscribe to level changes\n * const unsubscribe = battery.onLevelChange((level) => {\n * console.log(`Battery changed: ${level}%`);\n * });\n *\n * // Clean up\n * unsubscribe();\n * battery.stop();\n * ```\n */\nexport class BatteryProfile extends BaseProfile {\n protected readonly service = 'battery_service';\n\n /** Read current battery level (0-100). */\n async readLevel(): Promise<number> {\n const dv = await this.read('battery_level');\n return readUint8(dv);\n }\n\n /** Subscribe to battery level changes. Returns unsubscribe function. */\n onLevelChange(callback: (level: number) => void): () => void {\n return this.subscribe('battery_level', (dv) => {\n callback(readUint8(dv));\n });\n }\n}\n"]}
import {a}from'./chunk-3BDZNBBD.mjs';var c=a.EXTENSION_READY,u="beacio_dismiss_until",l="beacio_return",p="link.beacio.com",I="https://apps.apple.com/app/id6761301368";function w(){return typeof window<"u"&&window.__beacio?.status==="installed"}function S(){if(typeof navigator>"u")return false;let t=navigator;return !!(t.beacio&&t.beacio.__beacio)}function m(){return typeof document<"u"&&document.documentElement.dataset.beacioInstalled==="true"}function g(){return typeof document<"u"&&document.documentElement.dataset.beacioExtension==="true"}function r(){return S()||g()?"active":w()||m()?"installed-inactive":"not-installed"}function h(){return r()==="active"}function L(t=3e3){let e=r();return e==="active"||typeof window>"u"?Promise.resolve(e):new Promise(n=>{let o=false,i=f=>{o||(o=true,window.removeEventListener(c,a),clearTimeout(d),n(f));},a=()=>i("active");window.addEventListener(c,a);let d=setTimeout(()=>i(r()),t);})}var E=14,v=1;function R(){try{let t=localStorage.getItem(u);return t?Date.now()<parseInt(t,10):!1}catch{return false}}function _(t=E){try{localStorage.setItem(u,String(Date.now()+t*864e5));}catch{}}function y(){_(v);}function b(){let t=typeof window<"u"?window.location.href:"https://beacio.com",e=new URL(t),n=new URL(`https://${p}/return`);return n.searchParams.set("url",e.toString()),n.toString()}function k(){if(typeof window>"u")return;let t=new URL(window.location.href),e=b();try{localStorage.setItem(l,JSON.stringify({url:t.toString(),returnLink:e,timestamp:Date.now()})),navigator.storage?.persist?.();}catch{}try{navigator.clipboard?.writeText(e);}catch{}}function T(){let t=typeof window<"u"?window.location.href:"";try{let e=localStorage.getItem(l);if(e){let n=JSON.parse(e),o=n.url||t;return {url:o,returnLink:n.returnLink||o}}}catch{}return {url:t,returnLink:t}}
export{I as a,r as b,h as c,L as d,E as e,v as f,R as g,_ as h,y as i,b as j,k,T as l};//# sourceMappingURL=chunk-TZAX4UTD.mjs.map
//# sourceMappingURL=chunk-TZAX4UTD.mjs.map
{"version":3,"sources":["../src/detect/install-state.ts"],"names":["EXTENSION_READY_EVENT","BEACIO_EVENTS","DISMISS_KEY","RETURN_KEY","RETURN_LINK_HOST","APP_STORE_URL","hasWindowMarker","hasNavigatorMarker","nav","hasInstallMarker","hasActiveMarker","getInstallState","isExtensionActive","observeInstallState","timeoutMs","current","resolve","settled","finish","state","onReady","timer","DEFAULT_DISMISS_DAYS","SHORT_DISMISS_DAYS","isDismissed","until","dismiss","days","dismissShort","buildReturnLink","here","returnPageURL","returnLink","saveReturnContext","getReturnContext","raw","parsed","url"],"mappings":"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","file":"chunk-TZAX4UTD.mjs","sourcesContent":["/**\n * SB-SDK-12: the framework-agnostic, ZERO-DOM headless onboarding primitive for\n * vanilla-JS partners (Storz & Bickel's app is vanilla JS + jQuery and cannot use\n * the React wizard; the only vanilla option before this — showInstallBanner —\n * injects beacio chrome).\n *\n * This is the SINGLE shared derivation of install state + the return-link and\n * dismissal bookkeeping. Before this module the same marker logic was duplicated\n * THREE ways (detect.ts resolveInstallState, react-sdk ExtensionDetector\n * readInstallState, banner.ts isExtensionActiveNow) and the return-link/dismissal\n * helpers were module-private in banner.ts, exported nowhere. Now:\n * - detect.ts and the react-sdk ExtensionDetector consume getInstallState()\n * (AC1 \"the detection logic is SHARED, not duplicated\").\n * - banner.ts consumes saveReturnContext / getReturnContext / isDismissed /\n * dismiss from here (behavior + private call sites unchanged).\n * - index.ts re-exports the headless surface so a classic <script> partner can\n * draw its OWN \"Enable Bluetooth in Safari\" card with no beacio pixels.\n *\n * NONE of these helpers inject DOM.\n *\n * SB-SDK-02 (Part B) constraint: this module is reachable from the package-root\n * barrel, and `@beacio/core` is an OPTIONAL peer (it may be absent on a standalone\n * `npm i @beacio/detect`). So it must NOT hard-import core at module top level —\n * that throws at load (guarded by optional-core.test.ts + no-toplevel-core-import\n * .test.ts). The one core value it needs — the EXTENSION_READY event name — is\n * inlined here and pinned to core's BeacioEventName union via a fully-erased\n * `import type` + `satisfies`, exactly like index.ts's BEACIO_EVENTS map: a name\n * that diverges from core's source of truth becomes a COMPILE error while no\n * runtime core load is required (events.test.ts is the seam-crossing control).\n */\n// detect now lives INSIDE @beacio/core: the EXTENSION_READY event name is an\n// intra-package import from core's canonical map (single source of truth).\nimport { BEACIO_EVENTS } from '../events';\n\n/**\n * Where the user is in the irreducibly-manual iOS-26 setup funnel, derived purely\n * from the in-page markers the content script / injected polyfill set:\n * - 'not-installed' → no markers; the app is not installed\n * - 'installed-inactive' → installed, but the Safari extension toggle is off\n * - 'active' → the polyfill is live on this page\n *\n * The shared shape consumed by detect.ts, the react-sdk ExtensionDetector, and\n * the headless API. (The per-site 'denied' refinement is NOT a marker state — it\n * is derived separately in initBeacio from navigator.bluetooth.getAvailability().)\n */\nexport type ExtensionInstallState = 'not-installed' | 'installed-inactive' | 'active';\n\n/**\n * The in-page extension handshake event the injected polyfill dispatches when it\n * goes live. Sourced directly from core's canonical {@link BEACIO_EVENTS} map so\n * it cannot drift ('beacio:extension:ready').\n */\nconst EXTENSION_READY_EVENT = BEACIO_EVENTS.EXTENSION_READY;\n\n/** localStorage keys — the SAME keys banner.ts has always used (back-compat). */\nconst DISMISS_KEY = 'beacio_dismiss_until';\nconst RETURN_KEY = 'beacio_return';\nconst RETURN_LINK_HOST = 'link.beacio.com';\n\n/**\n * SB-SDK-12 (AC4): the canonical id-form App Store URL for the public beacio app.\n * The id form survives the public App Store rename — pinning the banner CTA here\n * means no banner code path can hardcode a NAME slug (`/app/<slug>/id…`) that\n * would 404 or mislead if Apple's slug differs from \"beacio\". The slug-form URL is\n * a SEPARATE concern owned by the CDN/website surfaces; the SDK side uses the id\n * form only.\n */\nexport const APP_STORE_URL = 'https://apps.apple.com/app/id6761301368';\n\n// ─── Install-state markers (the single shared derivation) ────────────────────\n\ninterface BeacioWindow extends Window {\n __beacio?: { status?: string };\n}\n\nfunction hasWindowMarker(): boolean {\n return typeof window !== 'undefined' && (window as BeacioWindow).__beacio?.status === 'installed';\n}\n\nfunction hasNavigatorMarker(): boolean {\n if (typeof navigator === 'undefined') {\n return false;\n }\n const nav = navigator as { beacio?: { __beacio?: boolean } };\n return Boolean(nav.beacio && nav.beacio.__beacio);\n}\n\nfunction hasInstallMarker(): boolean {\n return typeof document !== 'undefined' && document.documentElement.dataset.beacioInstalled === 'true';\n}\n\nfunction hasActiveMarker(): boolean {\n return typeof document !== 'undefined' && document.documentElement.dataset.beacioExtension === 'true';\n}\n\n/**\n * Synchronously read the current install state from the in-page markers. Pure,\n * zero-DOM, side-effect-free — the one accessor detect.ts, the react-sdk\n * ExtensionDetector, and the banner all share. A vanilla-JS partner calls this\n * to decide whether to render its own \"Enable Bluetooth in Safari\" card.\n */\nexport function getInstallState(): ExtensionInstallState {\n if (hasNavigatorMarker() || hasActiveMarker()) {\n return 'active';\n }\n if (hasWindowMarker() || hasInstallMarker()) {\n return 'installed-inactive';\n }\n return 'not-installed';\n}\n\n/** True once the content script has flagged the extension active on this page. */\nexport function isExtensionActive(): boolean {\n return getInstallState() === 'active';\n}\n\n/**\n * SB-SDK-12 (AC3): the headless detector. Resolves the CURRENT install state\n * immediately when it is already 'active' (markers set); otherwise it waits for\n * the in-page extension to announce itself via the canonical EXTENSION_READY\n * handshake ('beacio:extension:ready') — the seam the react-sdk ExtensionDetector\n * and the in-page polyfill already speak — and resolves 'active' when it fires.\n * Falls back to a final marker read after `timeoutMs`. Injects no DOM.\n *\n * This lets a vanilla-JS partner await activation without polling and without any\n * beacio chrome: `const state = await observeInstallState();`.\n */\nexport function observeInstallState(timeoutMs = 3000): Promise<ExtensionInstallState> {\n const current = getInstallState();\n if (current === 'active' || typeof window === 'undefined') {\n return Promise.resolve(current);\n }\n\n return new Promise((resolve) => {\n let settled = false;\n const finish = (state: ExtensionInstallState): void => {\n if (settled) return;\n settled = true;\n window.removeEventListener(EXTENSION_READY_EVENT, onReady);\n clearTimeout(timer);\n resolve(state);\n };\n const onReady = (): void => finish('active');\n\n window.addEventListener(EXTENSION_READY_EVENT, onReady);\n const timer = setTimeout(() => finish(getInstallState()), timeoutMs);\n });\n}\n\n// ─── Dismissal frequency-capping (zero-DOM bookkeeping) ──────────────────────\n\n/**\n * SB-PRD-08 (AC5): the two suppression windows, written to the SAME DISMISS_KEY.\n *\n * - LONG (`DEFAULT_DISMISS_DAYS`) is the EXPLICIT \"Don't show again\" — the user\n * deliberately opted out, so honour it for a fortnight.\n * - SHORT (`SHORT_DISMISS_DAYS`) is a soft \"Not now\" / backdrop tap: the user is\n * interested-but-not-ready, not opted out.\n *\n * Why the long default is 14 and NOT longer in a hardware-companion context: a\n * Storz & Bickel device is a considered EUR300-700 purchase whose owner returns\n * over days/weeks while they actually receive and set up the hardware. The old\n * behaviour applied this 14-day silence to EVERY dismiss gesture, so one reflexive\n * \"Not now\" churned a warm lead. We keep 14 ONLY for the explicit opt-out and make\n * the incidental dismiss a single day, so the passive on-load banner re-appears on\n * the next session while a force-show recovery path (banner.ts) always lets a\n * dismissed user re-open setup immediately. 14 stays the LONG default (configurable\n * via dismissDays) because an explicit opt-out should not nag the next day either.\n */\nexport const DEFAULT_DISMISS_DAYS = 14;\nexport const SHORT_DISMISS_DAYS = 1;\n\n/** True while a prior dismissal is still inside its suppression window. */\nexport function isDismissed(): boolean {\n try {\n const until = localStorage.getItem(DISMISS_KEY);\n if (!until) return false;\n return Date.now() < parseInt(until, 10);\n } catch {\n return false;\n }\n}\n\n/**\n * Suppress the prompt for `days` (default {@link DEFAULT_DISMISS_DAYS} = 14) — the\n * LONG, explicit \"Don't show again\" window. Named `dismiss` on the headless\n * surface; banner.ts calls it directly.\n */\nexport function dismiss(days = DEFAULT_DISMISS_DAYS): void {\n try {\n localStorage.setItem(DISMISS_KEY, String(Date.now() + days * 86400000));\n } catch {\n /* noop */\n }\n}\n\n/**\n * SB-PRD-08 (AC1): the SHORT, soft-dismissal primitive — a \"Not now\" / backdrop\n * tap suppresses the passive banner for {@link SHORT_DISMISS_DAYS} (1 day) only,\n * not the full fortnight, so an interested-but-not-ready user is not silenced for\n * two weeks. Writes the SAME DISMISS_KEY window as {@link dismiss}, just shorter.\n */\nexport function dismissShort(): void {\n dismiss(SHORT_DISMISS_DAYS);\n}\n\n// ─── Return-to-web-app context (zero-DOM; clipboard + localStorage) ──────────\n\n/**\n * SBOPT-P2.4: the PURE, side-effect-free return-link builder — the\n * `https://link.beacio.com/return?url=<encoded current href>` form, with NO\n * localStorage write and NO clipboard copy. {@link saveReturnContext} persists and\n * copies this exact string; the tier-3 headless resolveOnboardingState (index.ts)\n * embeds it in the funnel state so a partner can render its OWN return affordance\n * without tripping those side effects. Falls back to the beacio.com origin when\n * window is absent (SSR).\n */\nexport function buildReturnLink(): string {\n const here = typeof window !== 'undefined' ? window.location.href : 'https://beacio.com';\n const returnPageURL = new URL(here);\n const returnLink = new URL(`https://${RETURN_LINK_HOST}/return`);\n returnLink.searchParams.set('url', returnPageURL.toString());\n return returnLink.toString();\n}\n\n/**\n * Persist (and best-effort copy) the originating page so the return link survives\n * the round trip into Settings and back. The return link is the\n * `https://link.beacio.com/return?url=<encoded current href>` form built by\n * {@link buildReturnLink}. Injects no DOM — a partner surfaces the link in its OWN\n * card.\n */\nexport function saveReturnContext(): void {\n if (typeof window === 'undefined') return;\n const returnPageURL = new URL(window.location.href);\n const returnLink = buildReturnLink();\n\n try {\n localStorage.setItem(\n RETURN_KEY,\n JSON.stringify({ url: returnPageURL.toString(), returnLink, timestamp: Date.now() })\n );\n navigator.storage?.persist?.();\n } catch {\n /* noop */\n }\n try {\n navigator.clipboard?.writeText(returnLink);\n } catch {\n /* noop */\n }\n}\n\n/**\n * The originating page saved by {@link saveReturnContext}, as a VISIBLE, tappable\n * affordance — never relying on a silent clipboard write. Returns the current\n * href as a sensible fallback when nothing was saved.\n */\nexport function getReturnContext(): { url: string; returnLink: string } {\n const here = typeof window !== 'undefined' ? window.location.href : '';\n try {\n const raw = localStorage.getItem(RETURN_KEY);\n if (raw) {\n const parsed = JSON.parse(raw) as { url?: string; returnLink?: string };\n const url = parsed.url || here;\n return { url, returnLink: parsed.returnLink || url };\n }\n } catch {\n /* noop */\n }\n return { url: here, returnLink: here };\n}\n"]}
import {b}from'./chunk-FANWIUKA.mjs';var c="6e400001-b5a3-f393-e0a9-e50e24dcca9e",t="6e400002-b5a3-f393-e0a9-e50e24dcca9e",n="6e400003-b5a3-f393-e0a9-e50e24dcca9e",o=[c],s=class extends b{constructor(){super(...arguments);this.service=c;}onReceive(e){return this.subscribe(n,e)}async send(e){await this.sendChunked(t,e);}};s.services=o;export{o as a,s as b};//# sourceMappingURL=chunk-WKLBTKL5.mjs.map
//# sourceMappingURL=chunk-WKLBTKL5.mjs.map
{"version":3,"sources":["../src/profiles/nordic-uart.ts"],"names":["NUS_SERVICE","NUS_RX","NUS_TX","NUS_SERVICES","NordicUARTProfile","BaseProfile","callback","data"],"mappings":"qCAGA,IAAMA,EAAc,sCAAA,CAEdC,CAAAA,CAAS,uCAETC,CAAAA,CAAS,sCAAA,CAOFC,EAAkC,CAACH,CAAW,CAAA,CAwC9CI,CAAAA,CAAN,cAAgCC,CAAY,CAA5C,kCAIL,IAAA,CAAmB,OAAA,CAAUL,GAS7B,SAAA,CAAUM,CAAAA,CAAiD,CACzD,OAAO,KAAK,SAAA,CAAUJ,CAAAA,CAAQI,CAAQ,CACxC,CASA,MAAM,IAAA,CAAKC,CAAAA,CAAmC,CAI5C,MAAM,IAAA,CAAK,YAAYN,CAAAA,CAAQM,CAAI,EACrC,CACF,EA9BaH,EAEK,QAAA,CAAWD,CAAAA","file":"chunk-WKLBTKL5.mjs","sourcesContent":["import { BaseProfile } from './base';\n\n/** Nordic UART Service (NUS) UUID. */\nconst NUS_SERVICE = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';\n/** RX characteristic: host -> device. Write (without response). */\nconst NUS_RX = '6e400002-b5a3-f393-e0a9-e50e24dcca9e';\n/** TX characteristic: device -> host. Notify. */\nconst NUS_TX = '6e400003-b5a3-f393-e0a9-e50e24dcca9e';\n\n/**\n * Service UUIDs a Nordic UART device may reach after connection (the single NUS\n * service). Use with `optionalServices` / `Beacio.registerServices`, or via\n * {@link deriveOptionalServices} given {@link NordicUARTProfile}.\n */\nexport const NUS_SERVICES: readonly string[] = [NUS_SERVICE];\n\n/**\n * Nordic UART Service (NUS) profile — a bidirectional serial-over-BLE pipe.\n *\n * The de-facto standard \"UART service\" exposed by Espruino devices\n * (Bangle.js, Puck.js, Pixl.js, MDBT42Q), the BBC micro:bit, and Adafruit\n * Bluefruit modules. Data flows over two characteristics on the NUS service\n * `6e400001-b5a3-f393-e0a9-e50e24dcca9e`:\n *\n * - **TX** `6e400003-…` — device -> host, delivered via notifications.\n * Enabled through {@link BaseProfile.subscribe} (the native layer owns the\n * CCCD descriptor; `startNotifications()` covers notify *and* indicate).\n * - **RX** `6e400002-…` — host -> device, sent with write-without-response and\n * chunked to the negotiated MTU.\n *\n * Strictly W3C `navigator.bluetooth` GATT: this profile never reads or writes\n * a CCCD/SCCD descriptor itself.\n *\n * @example\n * ```ts\n * import { NordicUARTProfile, deriveOptionalServices } from '@beacio/core/profiles';\n *\n * // Declare the service from the profile — no hand-copied UUID:\n * // requestDevice({ filters: [{ namePrefix: 'Puck.js' }],\n * // optionalServices: deriveOptionalServices(NordicUARTProfile) })\n * const uart = new NordicUARTProfile(device);\n * await uart.connect();\n *\n * const decoder = new TextDecoder();\n * const unsubscribe = uart.onReceive((chunk) => {\n * process.stdout.write(decoder.decode(chunk));\n * });\n *\n * await uart.send(new TextEncoder().encode('LED1.set()\\n'));\n *\n * unsubscribe();\n * uart.stop();\n * ```\n */\nexport class NordicUARTProfile extends BaseProfile {\n /** Services this profile's device may reach after connection (the NUS service). Read by {@link deriveOptionalServices}. */\n static readonly services = NUS_SERVICES;\n\n protected readonly service = NUS_SERVICE;\n\n /**\n * Subscribe to inbound data from the device (TX characteristic, notify).\n * Each notification is delivered as a raw {@link DataView} chunk.\n *\n * @param callback - Invoked with every inbound chunk.\n * @returns Unsubscribe function. Also cleaned up by {@link BaseProfile.stop}.\n */\n onReceive(callback: (chunk: DataView) => void): () => void {\n return this.subscribe(NUS_TX, callback);\n }\n\n /**\n * Send data to the device (RX characteristic, write-without-response).\n * Payloads larger than the negotiated write-without-response limit are\n * split into MTU-sized chunks and written sequentially.\n *\n * @param data - Bytes to send. Accepts any {@link BufferSource}.\n */\n async send(data: BufferSource): Promise<void> {\n // Delegate fragmentation to the core write-chunker (via BaseProfile.sendChunked),\n // which derives a branded, always-positive ChunkSize from the negotiated\n // write-without-response limit / MTU. No hand-rolled offset loop here.\n await this.sendChunked(NUS_RX, data);\n }\n}\n"]}
export{b as getExtensionInstallState,c as isExtensionInstalled,a as isIOSSafari}from'./chunk-QLQHTSFL.mjs';import'./chunk-TZAX4UTD.mjs';import'./chunk-3BDZNBBD.mjs';//# sourceMappingURL=detect-Q3O2ZCCH.mjs.map
//# sourceMappingURL=detect-Q3O2ZCCH.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"detect-Q3O2ZCCH.mjs"}
/**
* Where the user is in the irreducibly-manual iOS-26 setup funnel, derived purely
* from the in-page markers the content script / injected polyfill set:
* - 'not-installed' → no markers; the app is not installed
* - 'installed-inactive' → installed, but the Safari extension toggle is off
* - 'active' → the polyfill is live on this page
*
* The shared shape consumed by detect.ts, the react-sdk ExtensionDetector, and
* the headless API. (The per-site 'denied' refinement is NOT a marker state — it
* is derived separately in initBeacio from navigator.bluetooth.getAvailability().)
*/
type ExtensionInstallState = 'not-installed' | 'installed-inactive' | 'active';
/**
* SB-SDK-12 (AC4): the canonical id-form App Store URL for the public beacio app.
* The id form survives the public App Store rename — pinning the banner CTA here
* means no banner code path can hardcode a NAME slug (`/app/<slug>/id…`) that
* would 404 or mislead if Apple's slug differs from "beacio". The slug-form URL is
* a SEPARATE concern owned by the CDN/website surfaces; the SDK side uses the id
* form only.
*/
declare const APP_STORE_URL = "https://apps.apple.com/app/id6761301368";
/**
* Synchronously read the current install state from the in-page markers. Pure,
* zero-DOM, side-effect-free — the one accessor detect.ts, the react-sdk
* ExtensionDetector, and the banner all share. A vanilla-JS partner calls this
* to decide whether to render its own "Enable Bluetooth in Safari" card.
*/
declare function getInstallState(): ExtensionInstallState;
/** True once the content script has flagged the extension active on this page. */
declare function isExtensionActive(): boolean;
/**
* SB-SDK-12 (AC3): the headless detector. Resolves the CURRENT install state
* immediately when it is already 'active' (markers set); otherwise it waits for
* the in-page extension to announce itself via the canonical EXTENSION_READY
* handshake ('beacio:extension:ready') — the seam the react-sdk ExtensionDetector
* and the in-page polyfill already speak — and resolves 'active' when it fires.
* Falls back to a final marker read after `timeoutMs`. Injects no DOM.
*
* This lets a vanilla-JS partner await activation without polling and without any
* beacio chrome: `const state = await observeInstallState();`.
*/
declare function observeInstallState(timeoutMs?: number): Promise<ExtensionInstallState>;
/**
* SB-PRD-08 (AC5): the two suppression windows, written to the SAME DISMISS_KEY.
*
* - LONG (`DEFAULT_DISMISS_DAYS`) is the EXPLICIT "Don't show again" — the user
* deliberately opted out, so honour it for a fortnight.
* - SHORT (`SHORT_DISMISS_DAYS`) is a soft "Not now" / backdrop tap: the user is
* interested-but-not-ready, not opted out.
*
* Why the long default is 14 and NOT longer in a hardware-companion context: a
* Storz & Bickel device is a considered EUR300-700 purchase whose owner returns
* over days/weeks while they actually receive and set up the hardware. The old
* behaviour applied this 14-day silence to EVERY dismiss gesture, so one reflexive
* "Not now" churned a warm lead. We keep 14 ONLY for the explicit opt-out and make
* the incidental dismiss a single day, so the passive on-load banner re-appears on
* the next session while a force-show recovery path (banner.ts) always lets a
* dismissed user re-open setup immediately. 14 stays the LONG default (configurable
* via dismissDays) because an explicit opt-out should not nag the next day either.
*/
declare const DEFAULT_DISMISS_DAYS = 14;
declare const SHORT_DISMISS_DAYS = 1;
/** True while a prior dismissal is still inside its suppression window. */
declare function isDismissed(): boolean;
/**
* Suppress the prompt for `days` (default {@link DEFAULT_DISMISS_DAYS} = 14) — the
* LONG, explicit "Don't show again" window. Named `dismiss` on the headless
* surface; banner.ts calls it directly.
*/
declare function dismiss(days?: number): void;
/**
* SB-PRD-08 (AC1): the SHORT, soft-dismissal primitive — a "Not now" / backdrop
* tap suppresses the passive banner for {@link SHORT_DISMISS_DAYS} (1 day) only,
* not the full fortnight, so an interested-but-not-ready user is not silenced for
* two weeks. Writes the SAME DISMISS_KEY window as {@link dismiss}, just shorter.
*/
declare function dismissShort(): void;
/**
* Persist (and best-effort copy) the originating page so the return link survives
* the round trip into Settings and back. The return link is the
* `https://link.beacio.com/return?url=<encoded current href>` form built by
* {@link buildReturnLink}. Injects no DOM — a partner surfaces the link in its OWN
* card.
*/
declare function saveReturnContext(): void;
/**
* The originating page saved by {@link saveReturnContext}, as a VISIBLE, tappable
* affordance — never relying on a silent clipboard write. Returns the current
* href as a sensible fallback when nothing was saved.
*/
declare function getReturnContext(): {
url: string;
returnLink: string;
};
/**
* Platform detection utilities for Beacio
*/
declare function isIOSSafari(): boolean;
declare function getExtensionInstallState(): Promise<ExtensionInstallState>;
declare function isExtensionInstalled(): Promise<boolean>;
/**
* @beacio/detect#i18n — SB-SDK-07
*
* The shared localized-string seam for the two user-facing surfaces in
* @beacio/detect: the install banner (banner.ts) and the branded error
* presenter (error-presenter.ts). The install banner is the SINGLE end-user
* onboarding screen that replaces S&B's Bluefy alert, and it was hardcoded
* English — only `text`/`buttonText` were overridable. S&B is HQ'd in Bayreuth
* and its German users would see English at the make-or-break moment. This
* module centralises every visible token into a typed string pack, ships a
* built-in German (`de`) pack alongside the English (`en`) default, and exposes
* one PURE selector with a documented policy.
*
* Selection policy (resolveStrings):
* 1. an explicit BCP-47 `lang` ALWAYS wins (prefix-matched: 'de', 'de-DE',
* 'de-AT' all select the German pack);
* 2. else the runtime's `navigator.language` is prefix-matched the same way
* (so a German-locale iPhone gets German with zero config);
* 3. else English.
* A caller-supplied partial `strings` object then deep-merges OVER the selected
* pack, so an operator can override one field (e.g. just `buttonText`) without
* restating the whole pack, in any language.
*
* Design constraints (mirroring banner.ts / error-presenter.ts):
* - @beacio/core is an OPTIONAL peer, so this module imports NOTHING from core.
* The BeacioErrorCode union is re-declared LOCALLY (kept in lock-step with
* core's source by error-presenter-core-parity.test.ts, which pins the SAME
* local table this pack's `error.messages` must cover).
* - All copy uses neutral install-path framing only — no "App Store approved /
* cleared / reviewed" language (feedback_no_app_store_status_claims).
* - `{operator}` is the ONLY interpolation token; banner.ts substitutes the
* resolved operator name into it (see fill()).
*
* SDK has zero external consumers, so adding the `lang`/`strings` seam is a free,
* non-breaking change (project_sdk_no_consumers).
*/
/**
* The stable BeacioErrorCode contract (core/src/errors.ts). Re-declared locally
* — not imported — so detect has no runtime @beacio/core dependency. Kept in
* lock-step with core by error-presenter-core-parity.test.ts (the COMPILE-TIME
* EVERY_CODE Record) and by this module's own pack-parity guard in i18n.test.ts.
*/
type BeacioErrorCode$1 = 'INVALID_PARAMETER' | 'BLUETOOTH_UNAVAILABLE' | 'EXTENSION_NOT_INSTALLED' | 'PERMISSION_DENIED' | 'DEVICE_NOT_FOUND' | 'DEVICE_DISCONNECTED' | 'CONNECTION_TIMEOUT' | 'SERVICE_NOT_FOUND' | 'CHARACTERISTIC_NOT_FOUND' | 'CHARACTERISTIC_NOT_READABLE' | 'CHARACTERISTIC_NOT_WRITABLE' | 'CHARACTERISTIC_NOT_NOTIFIABLE' | 'GATT_OPERATION_FAILED' | 'SCAN_ALREADY_IN_PROGRESS' | 'CONNECTION_LIMIT_REACHED' | 'USER_CANCELLED' | 'TIMEOUT' | 'WRITE_INCOMPLETE';
/** Funnel-state lead copy: a title + body shown at the top of the bottom sheet. */
interface StateCopy {
title: string;
body: string;
}
/** A single setup step: the imperative label the user taps + its one-line "why". */
interface SetupStepCopy {
label: string;
why: string;
}
/** A branded error card's headline + body. */
interface ErrorCopy {
title: string;
body: string;
}
/** The error-presenter half of the pack — shared by presentError. */
interface ErrorStrings {
/** Dismiss button label on the error card. */
dismiss: string;
/** Retry affordance label (retriable errors only). */
retry: string;
/** Per-code branded headline. EVERY BeacioErrorCode is present (parity-guarded). */
titles: Record<BeacioErrorCode$1, string>;
/** Per-code branded body. EVERY BeacioErrorCode is present (parity-guarded). */
messages: Record<BeacioErrorCode$1, string>;
/** Fallback copy for an unrecognised error (bare string / unknown DOMException). */
generic: ErrorCopy;
}
/**
* The complete visible-string surface of @beacio/detect. The built-in `en`/`de`
* packs both define EXACTLY these keys (i18n.test.ts pins the parity), so a new
* English-only string cannot silently bypass localization.
*/
interface LocaleStrings {
/** Primary CTA label on the not-installed sheet + the lightweight bar. */
buttonText: string;
/** Sheet soft-dismiss ("Not now") label — short suppression (SB-PRD-08). */
dismiss: string;
/**
* SB-PRD-08: the EXPLICIT "Don't show again" opt-out label — the long-suppression
* control, distinct from the soft "Not now" {@link dismiss} above.
*/
dontShowAgain: string;
/** Per-funnel-state lead copy (the 'active' state renders the toast instead). */
states: {
'not-installed': StateCopy;
'installed-inactive': StateCopy;
denied: StateCopy;
/**
* SB-SDK-17: Private Browsing dead end. iOS Safari disables web extensions in
* Private Browsing (no per-extension opt-in), so beacio is inert and the app
* may already be installed — the recovery is to reopen the page in a normal
* tab, NOT to install anything. A distinct hint (no install CTA, no steps).
*/
'private-browsing': StateCopy;
};
/** The ordered first-run step list (install → … → return). */
steps: SetupStepCopy[];
/** Visible "Return to {operator}" CTA label. */
returnCta: string;
/** Sub-line explaining the link was also copied to the clipboard. */
clipboardHint: string;
/** "Reload page to re-check" control label. */
reload: string;
/** "How does setup work?" <details> summary. */
howSummary: string;
/** "How does setup work?" <details> body (ends with the linked guide phrase). */
howBody: string;
/** Linked phrase inside howBody that points at the setup guide. */
howLink: string;
/** "Privacy: No data collected" <details> summary. */
privacySummary: string;
/** "Privacy: No data collected" <details> body. */
privacyBody: string;
/** "Still stuck? Open the setup guide" affordance. */
stillStuck: string;
/** Lightweight bar banner heading ("Enable Bluetooth"). */
barTitle: string;
/** Lightweight bar banner body text. */
barText: string;
/** Once-only success toast text ("beacio is ready — tap Connect …"). */
readyToast: string;
/** Error-presenter strings (shared with presentError). */
error: ErrorStrings;
}
/**
* English (default) pack — the byte-identical source of today's rendered copy.
* `{operator}` is substituted by banner.ts with the resolved operator name.
*/
declare const EN_STRINGS: LocaleStrings;
/**
* German (`de`) pack. Mirrors EN_STRINGS key-for-key (i18n.test.ts pins the
* parity, so this pack can never fall behind a new English string). Native,
* neutral install-path German; iOS-26 Settings paths use the localized Settings
* labels (Apps → Safari → Erweiterungen) a German iPhone actually shows. The
* stylized brand word "beacio" stays lowercase mid-sentence, matching the
* English copy and the app's lowercase display name.
*/
declare const DE_STRINGS: LocaleStrings;
/** A recursively-optional view of a type, for partial `strings` overrides. */
type DeepPartial<T> = T extends (infer U)[] ? U[] : T extends object ? {
[K in keyof T]?: DeepPartial<T[K]>;
} : T;
/** Options shared by the banner + error presenter for selecting localized copy. */
interface ResolveStringsOptions {
/** Explicit BCP-47 language tag. Always wins when its primary subtag is known. */
lang?: string;
/** Partial overrides deep-merged over the selected pack (any field, any depth). */
strings?: DeepPartial<LocaleStrings>;
}
/**
* PURE locale selector implementing the SB-SDK-07 policy:
* explicit `lang` (prefix-matched) > navigator.language (prefix-matched) > English,
* then a partial `strings` override deep-merged over the selected pack.
*
* Pure + side-effect-free: it reads navigator.language only when no explicit
* `lang` is given, and never mutates the built-in packs. An unknown subtag falls
* through to English (never throws). Returns a fresh object when an override is
* supplied, else the shared pack reference (so identity checks against EN/DE_STRINGS
* hold for the no-override path the tests assert).
*/
declare function resolveStrings(options?: ResolveStringsOptions): LocaleStrings;
/**
* Install prompt UI for Beacio
*
* Two modes:
* 1. Bottom sheet (default) — iOS-native feel, shown on requestDevice() trigger
* 2. Banner — lightweight top/bottom bar for passive prompting
*
* Features:
* - Clipboard context saving for return-to-web-app flow
* - 14-day dismissal frequency capping
* - Configurable install/onboarding redirect
* - Dark mode support via prefers-color-scheme
*/
/**
* Where the user is in the irreducibly-manual iOS-26 setup funnel, so the sheet
* can render the SPECIFIC remaining step instead of restarting the whole flow:
* - 'not-installed' → app not installed; full install→enable→grant walkthrough
* - 'installed-inactive' → installed but the Safari extension toggle is off
* - 'denied' → enabled but per-origin access not granted on THIS site
* - 'private-browsing' → Private Browsing disables extensions; reopen in a normal tab
* - 'active' → ready; render the once-only success toast
* Mirrors ExtensionInstallState ('active' | 'installed-inactive' | 'not-installed')
* plus the in-page refinements only the page flow can distinguish: the per-site
* 'denied' grant and the SB-SDK-17 'private-browsing' dead end.
*/
type BannerState = 'not-installed' | 'installed-inactive' | 'denied' | 'private-browsing' | 'active';
interface BannerOptions {
/** 'sheet' (default) for iOS bottom sheet, 'banner' for lightweight bar */
mode?: 'sheet' | 'banner';
position?: 'top' | 'bottom';
style?: Record<string, string>;
/** Preferred install or onboarding URL to open when the user taps the CTA */
startOnboardingUrl?: string;
/** Legacy install destination option; still supported for compatibility */
appStoreUrl?: string;
/** Operator/app name shown in the prompt (e.g. "FitTracker") */
operatorName?: string;
/** API key for campaign tracking */
apiKey?: string;
/**
* Days to suppress the PASSIVE on-load banner after the EXPLICIT "Don't show
* again" opt-out (default: 14). SB-PRD-08: the soft "Not now"/backdrop tap uses
* a separate, short (1-day) window and is NOT governed by this option, so one
* reflexive dismiss no longer silences guidance for a fortnight.
*/
dismissDays?: number;
/**
* SB-PRD-08 (AC3): ignore the active dismissal cooldown and render anyway. The
* passive on-load banner leaves this false so a dismissed user is not nagged;
* a USER-INITIATED recovery gesture (e.g. tapping Connect, or a "Set up
* Bluetooth"/"Can't connect?" affordance) passes `forceShow: true` to re-open
* the activation flow without the integrator having to clear localStorage.
*/
forceShow?: boolean;
/**
* Funnel position. Lets initBeacio render state-specific guidance (and, on
* 'active', the once-only "ready" toast) without restarting setup. Defaults to
* 'not-installed' for the legacy "show the full walkthrough" call site.
*/
state?: BannerState;
/**
* Setup destination shown behind the "still stuck?" affordance and the
* "How does setup work?" disclosure. Defaults to the canonical /setup page;
* an operator (e.g. Storz & Bickel) can point it at their own branded help.
*/
setupUrl?: string;
/**
* SB-SDK-07: BCP-47 UI language (e.g. 'de'). When set, its primary subtag
* selects the built-in pack; when omitted, the language is derived from
* navigator.language, else English. Always wins over navigator.language.
*/
lang?: string;
/**
* SB-SDK-07: partial copy overrides deep-merged over the selected language
* pack — override one field (e.g. `buttonText`) without restating the rest.
*/
strings?: DeepPartial<LocaleStrings>;
/**
* SB-SDK-11 (tier-2 co-brand): partner accent colour applied to the sheet/bar
* chrome (icon tile, step bullets, primary CTA, disclosure links). Routed
* through a `--bc-accent` CSS variable so every accent rule switches to
* var(--bc-accent); when omitted the variable defaults to the beacio Apple-blue
* (#007aff) and the prompt renders exactly as before. Any CSS colour token.
*/
accentColor?: string;
/**
* SB-SDK-11: partner logo, restricted to a URL (no raw SVG markup) so it can
* never inject script. Validated with `new URL()` against the page origin and
* accepted ONLY when the resolved protocol is http(s); a `javascript:`/`data:`/
* `ftp:` value is dropped and the default beacio chrome icon is kept. Rendered
* as an <img> in place of the inline beacio <svg>.
*/
brandLogoUrl?: string;
/**
* SB-SDK-11: the specific device being connected (e.g. "VOLCANO HYBRID"). When
* set it is interpolated into the `{device}` token of any copy that carries it,
* so a co-brand sheet can read "Connect your VOLCANO HYBRID in Safari".
*/
deviceName?: string;
/**
* SB-SDK-11: a one-shot override for the sheet's lead body copy. Wins over the
* resolved language pack's state body (HTML-escaped via esc(), like all copy).
* For finer-grained per-field overrides use the SB-SDK-07 `strings` seam.
*/
body?: string;
/**
* SB-SDK-11: override for the privacy reassurance body (the medical-market
* trust line). HTML-escaped. Defaults to the resolved pack's privacyBody.
*/
privacyBody?: string;
}
interface SetupStep {
/** Imperative step label the user taps. */
label: string;
/** One-line "why this is required", shown under the label. */
why: string;
}
/**
* The real sequence a first-run owner actually taps on a physical iPhone, each
* grant with its own "why" so no system prompt is a surprise. Ordering and count
* are the contract: install → open app → enable extension → allow website access
* (the aA gesture) → allow Bluetooth on first scan → return.
*
* SB-SDK-07: this is the ENGLISH step list, now sourced from EN_STRINGS.steps so
* the exported constant (mirrored by the react-sdk InstallationWizard) and the
* localized pack never drift. Localized rendering reads the resolved pack's
* steps; the per-state filtering below is by INDEX into this canonical order, so
* it is language-independent (German labels do not match the old English regex).
*/
declare const SETUP_STEPS: readonly SetupStep[];
declare function showInstallBanner(options?: BannerOptions): HTMLElement | null;
declare function removeInstallBanner(): void;
/**
* @beacio/detect#presentError — SB-SDK-05
*
* A drop-in, framework-free branded ERROR presenter. The polished branded surface
* already exists for the INSTALL prompt (banner.ts); this is its sibling for the
* FAILURE path. S&B (and any vanilla-JS site) uses raw `navigator.bluetooth`
* across hundreds of call sites and will not rewrite them — so the worst surface,
* a blocking, stack-leaking `window.alert()`, is converted into a non-blocking,
* dismissible, recovery-oriented card with a ~1-line edit:
*
* catch (error) { beacioDetect.presentError(error); }
*
* Design constraints (mirroring banner.ts):
* - @beacio/core is an OPTIONAL peer (a standalone `npm i @beacio/detect` has no
* core), so this file MUST NOT import @beacio/core — not even the BeacioError
* class. Errors are consumed STRUCTURALLY: anything carrying a `.code` /
* `.message` / `.suggestion` / `.isRetriable` is understood, and the
* BeacioErrorCode → copy map + retriable set are kept LOCAL (pinned to core's
* public contract by the unit test, not by a runtime import).
* - The card NEVER leaks a stack trace, internal codes, WebKit jargon, or a
* competitor name. The friendly body comes from the per-code copy table, NOT
* the raw error string.
* - Identical errors fired in a short window are coalesced to ONE card (defends
* against the backgrounded alert-storm).
* - All user-visible strings are overridable via a copy/locale object
* (PresentErrorOptions.strings) — the i18n seam SB-SDK-07 converges on; the
* `lang` field selects a built-in pack (German shipped), and `strings`
* deep-merges over it. English defaults apply when neither is supplied (no
* regression). The per-code copy + dismiss/retry come from the SAME shared
* i18n module the install banner uses (./i18n), so a localized card and a
* localized banner never drift.
*/
/**
* The stable BeacioErrorCode contract (core/src/errors.ts). Kept local — not
* imported — so detect has no runtime @beacio/core dependency. The presenter unit
* test is the seam-crossing control that this list still matches core's source.
*/
type BeacioErrorCode = 'INVALID_PARAMETER' | 'BLUETOOTH_UNAVAILABLE' | 'EXTENSION_NOT_INSTALLED' | 'PERMISSION_DENIED' | 'DEVICE_NOT_FOUND' | 'DEVICE_DISCONNECTED' | 'CONNECTION_TIMEOUT' | 'SERVICE_NOT_FOUND' | 'CHARACTERISTIC_NOT_FOUND' | 'CHARACTERISTIC_NOT_READABLE' | 'CHARACTERISTIC_NOT_WRITABLE' | 'CHARACTERISTIC_NOT_NOTIFIABLE' | 'GATT_OPERATION_FAILED' | 'SCAN_ALREADY_IN_PROGRESS' | 'CONNECTION_LIMIT_REACHED' | 'USER_CANCELLED' | 'TIMEOUT' | 'WRITE_INCOMPLETE';
/**
* Caller-supplied copy/locale overrides — the i18n seam (SB-SDK-07). Every field
* is optional; an omitted field falls back to the English default, so an existing
* caller that passes nothing is byte-identical to today.
*/
interface PresentErrorStrings {
/** Dismiss button label (English default: "Dismiss"). */
dismiss?: string;
/** Retry affordance label for retriable errors (English default: "Try again"). */
retry?: string;
/** Per-code body override. A code present here replaces the English body. */
messages?: Partial<Record<BeacioErrorCode, string>>;
}
/**
* Options for {@link presentError}. Parity with BannerOptions where it overlaps
* (operatorName, style), plus the retry affordance + the copy/locale seam.
*/
interface PresentErrorOptions {
/** Operator/app name shown in the card (e.g. "STORZ & BICKEL"). */
operatorName?: string;
/**
* SB-SDK-07: BCP-47 UI language (e.g. 'de'). Selects the built-in pack for the
* per-code title/body + dismiss/retry labels; omitted ⇒ derived from
* navigator.language, else English. A per-call `strings` (and the explicit
* dismissText/retryText) still override the selected pack. Always wins over
* navigator.language.
*/
lang?: string;
/** Retry button label override (takes precedence over strings.retry). */
retryText?: string;
/** Dismiss button label override (takes precedence over strings.dismiss). */
dismissText?: string;
/**
* Invoked when the user taps the retry affordance (retriable errors only), so a
* caller can re-run its connect()/operation. The card is dismissed first.
*/
onRetry?: () => void;
/** Extra inline styles merged onto the card container. */
style?: Record<string, string>;
/** Copy/locale overrides for every user-visible string (SB-SDK-07 seam). */
strings?: PresentErrorStrings;
}
/**
* Present a branded, non-blocking, dismissible error card. Replaces a blocking
* `window.alert(error.toString() + error.stack)` with a recovery-oriented surface.
*
* @param errorOrMessage A BeacioError, a raw DOMException/Error, or a string.
* @param options Operator name, copy/locale overrides, and an onRetry handler.
* @returns The card element, or null when the error is coalesced (a card for an
* identical error is already on screen) so callers can no-op safely.
*/
declare function presentError(errorOrMessage: unknown, options?: PresentErrorOptions): HTMLElement | null;
/**
* Analytics event reporter and API key validator.
* Fire-and-forget — analytics must never throw or block.
*/
declare function reportEvent(apiKey: string, event: string, _data?: {
[key: string]: string | number | boolean | null;
}): void;
declare function validateApiKey(apiKey: string): Promise<{
operatorId: string;
appName: string | null;
plan: string;
} | null>;
/**
* @beacio/detect
*
* Detects iOS Safari, checks if the Beacio extension is installed,
* and shows an install banner if not. No-op on all other platforms.
*
* Your existing Web Bluetooth code works unchanged — this package only
* handles the "extension not installed" case on iOS Safari.
*/
interface BeacioOptions {
/** Optional API key for campaign tracking */
key?: string;
/** Operator/app name shown in the prompt (e.g. "FitTracker") */
operatorName?: string;
/**
* SB-SDK-07: BCP-47 UI language (e.g. 'de') for the install banner. Threaded
* to showInstallBanner so the zero-config initBeacio path is localizable;
* omitted ⇒ the banner derives the language from navigator.language, else
* English. A `banner.lang` (below) overrides this for the banner specifically.
*/
lang?: string;
/** Install banner configuration, or false to disable */
banner?: {
/** 'sheet' (default) for iOS bottom sheet, 'banner' for lightweight bar */
mode?: 'sheet' | 'banner';
position?: 'top' | 'bottom';
text?: string;
buttonText?: string;
style?: Record<string, string>;
startOnboardingUrl?: string;
appStoreUrl?: string;
/** Days to suppress after the explicit "Don't show again" opt-out (default: 14) */
dismissDays?: number;
/**
* SB-PRD-08 (AC3): ignore the dismissal cooldown and show anyway. Set this
* on a user-initiated recovery call (e.g. re-invoking initBeacio from a
* Connect / "Can't connect?" gesture) so a previously-dismissed user can
* re-open setup without clearing localStorage.
*/
forceShow?: boolean;
/** SB-SDK-07: BCP-47 language override for the banner (wins over the top-level `lang`). */
lang?: string;
/** SB-SDK-11 (tier-2 co-brand): partner accent colour for the prompt chrome. */
accentColor?: string;
/** SB-SDK-11: partner logo URL (http(s) only; validated). Replaces the beacio glyph. */
brandLogoUrl?: string;
/** SB-SDK-11: the connected device's display name (e.g. "VOLCANO HYBRID"). */
deviceName?: string;
/** SB-SDK-11: one-shot lead body copy override (HTML-escaped). */
body?: string;
/** SB-SDK-11: privacy reassurance body override (HTML-escaped). */
privacyBody?: string;
} | false;
/** Called when the extension is detected and ready */
onReady?: () => void;
/** Called when the extension is installed but Safari still needs activation/allow access */
onInstalledInactive?: () => void;
/** Called when the extension is NOT installed */
onNotInstalled?: () => void;
}
/**
* Initialize Beacio detection.
*
* On iOS Safari: checks if the extension is installed, dispatches events,
* and optionally shows an install banner.
*
* On all other platforms: no-op (returns immediately).
*/
declare function initBeacio(options: BeacioOptions): Promise<void>;
/**
* Where a first-run owner is in the irreducibly-manual iOS-26 setup funnel, as
* DATA a partner renders itself. The union is CLOSED to exactly initBeacio's six
* routing outcomes (a discriminated union + exhaustive switch, not scattered
* undefined checks):
* - 'unsupported' → not iOS Safari; Web Bluetooth via beacio is unavailable.
* - 'not-installed' → app not installed; `installUrl` is the id-form App Store link.
* - 'installed-inactive' → installed but the Safari extension toggle is off; `setupUrl` guides.
* - 'denied' → enabled, but per-origin access not granted on THIS site; `setupUrl` guides.
* - 'private-browsing' → Private Browsing disables extensions; the fix is a normal tab.
* - 'ready' → the polyfill is live and this origin is granted; nothing to prompt.
*
* Required fields, sentinels over optionals (owner's API rule): each variant
* carries only the render-ready URLs its OWN prompt needs, all required — no `?`.
* `returnLink` is the tappable "return to your page" affordance
* (`https://link.beacio.com/return?url=…`) computed purely, with no side effect.
*/
type OnboardingState = {
kind: 'unsupported';
} | {
kind: 'not-installed';
installUrl: string;
returnLink: string;
} | {
kind: 'installed-inactive';
setupUrl: string;
returnLink: string;
} | {
kind: 'denied';
setupUrl: string;
returnLink: string;
} | {
kind: 'private-browsing';
returnLink: string;
} | {
kind: 'ready';
};
/**
* The REQUIRED config for {@link resolveOnboardingState} — no optional args.
* `apiKey` threads the App Store campaign token (ct/mt) onto the install deep link
* exactly as the banner's install button does; `operatorName` threads the operator
* identity onto the guided /setup deep link so it can render "Return to <operator>".
* Pass empty-string sentinels when a field is not in play.
*/
interface OnboardingConfig {
operatorName: string;
apiKey: string;
}
/**
* Resolve the current tier-3 onboarding funnel position WITHOUT rendering any
* beacio chrome. This is the headless projection of initBeacio's routing: the same
* isIOSSafari early-return, the same active → (denied?) → ready split, and the same
* "Private Browsing wins over a marker-suppressed denied" precedence for the
* non-active states — but it returns the position as data for a partner to render,
* dispatching NO events and injecting NO DOM.
*/
declare function resolveOnboardingState(config: OnboardingConfig): Promise<OnboardingState>;
export { APP_STORE_URL, type BannerOptions, type BannerState, type BeacioErrorCode, type BeacioOptions, DEFAULT_DISMISS_DAYS, DE_STRINGS, type DeepPartial, EN_STRINGS, type ErrorCopy, type ErrorStrings, type ExtensionInstallState, type LocaleStrings, type OnboardingConfig, type OnboardingState, type PresentErrorOptions, type PresentErrorStrings, type ResolveStringsOptions, SETUP_STEPS, SHORT_DISMISS_DAYS, type SetupStep, type SetupStepCopy, type StateCopy, dismiss, dismissShort, getExtensionInstallState, getInstallState, getReturnContext, initBeacio, isDismissed, isExtensionActive, isExtensionInstalled, isIOSSafari, observeInstallState, presentError, removeInstallBanner, reportEvent, resolveOnboardingState, resolveStrings, saveReturnContext, showInstallBanner, validateApiKey };
/**
* Where the user is in the irreducibly-manual iOS-26 setup funnel, derived purely
* from the in-page markers the content script / injected polyfill set:
* - 'not-installed' → no markers; the app is not installed
* - 'installed-inactive' → installed, but the Safari extension toggle is off
* - 'active' → the polyfill is live on this page
*
* The shared shape consumed by detect.ts, the react-sdk ExtensionDetector, and
* the headless API. (The per-site 'denied' refinement is NOT a marker state — it
* is derived separately in initBeacio from navigator.bluetooth.getAvailability().)
*/
type ExtensionInstallState = 'not-installed' | 'installed-inactive' | 'active';
/**
* SB-SDK-12 (AC4): the canonical id-form App Store URL for the public beacio app.
* The id form survives the public App Store rename — pinning the banner CTA here
* means no banner code path can hardcode a NAME slug (`/app/<slug>/id…`) that
* would 404 or mislead if Apple's slug differs from "beacio". The slug-form URL is
* a SEPARATE concern owned by the CDN/website surfaces; the SDK side uses the id
* form only.
*/
declare const APP_STORE_URL = "https://apps.apple.com/app/id6761301368";
/**
* Synchronously read the current install state from the in-page markers. Pure,
* zero-DOM, side-effect-free — the one accessor detect.ts, the react-sdk
* ExtensionDetector, and the banner all share. A vanilla-JS partner calls this
* to decide whether to render its own "Enable Bluetooth in Safari" card.
*/
declare function getInstallState(): ExtensionInstallState;
/** True once the content script has flagged the extension active on this page. */
declare function isExtensionActive(): boolean;
/**
* SB-SDK-12 (AC3): the headless detector. Resolves the CURRENT install state
* immediately when it is already 'active' (markers set); otherwise it waits for
* the in-page extension to announce itself via the canonical EXTENSION_READY
* handshake ('beacio:extension:ready') — the seam the react-sdk ExtensionDetector
* and the in-page polyfill already speak — and resolves 'active' when it fires.
* Falls back to a final marker read after `timeoutMs`. Injects no DOM.
*
* This lets a vanilla-JS partner await activation without polling and without any
* beacio chrome: `const state = await observeInstallState();`.
*/
declare function observeInstallState(timeoutMs?: number): Promise<ExtensionInstallState>;
/**
* SB-PRD-08 (AC5): the two suppression windows, written to the SAME DISMISS_KEY.
*
* - LONG (`DEFAULT_DISMISS_DAYS`) is the EXPLICIT "Don't show again" — the user
* deliberately opted out, so honour it for a fortnight.
* - SHORT (`SHORT_DISMISS_DAYS`) is a soft "Not now" / backdrop tap: the user is
* interested-but-not-ready, not opted out.
*
* Why the long default is 14 and NOT longer in a hardware-companion context: a
* Storz & Bickel device is a considered EUR300-700 purchase whose owner returns
* over days/weeks while they actually receive and set up the hardware. The old
* behaviour applied this 14-day silence to EVERY dismiss gesture, so one reflexive
* "Not now" churned a warm lead. We keep 14 ONLY for the explicit opt-out and make
* the incidental dismiss a single day, so the passive on-load banner re-appears on
* the next session while a force-show recovery path (banner.ts) always lets a
* dismissed user re-open setup immediately. 14 stays the LONG default (configurable
* via dismissDays) because an explicit opt-out should not nag the next day either.
*/
declare const DEFAULT_DISMISS_DAYS = 14;
declare const SHORT_DISMISS_DAYS = 1;
/** True while a prior dismissal is still inside its suppression window. */
declare function isDismissed(): boolean;
/**
* Suppress the prompt for `days` (default {@link DEFAULT_DISMISS_DAYS} = 14) — the
* LONG, explicit "Don't show again" window. Named `dismiss` on the headless
* surface; banner.ts calls it directly.
*/
declare function dismiss(days?: number): void;
/**
* SB-PRD-08 (AC1): the SHORT, soft-dismissal primitive — a "Not now" / backdrop
* tap suppresses the passive banner for {@link SHORT_DISMISS_DAYS} (1 day) only,
* not the full fortnight, so an interested-but-not-ready user is not silenced for
* two weeks. Writes the SAME DISMISS_KEY window as {@link dismiss}, just shorter.
*/
declare function dismissShort(): void;
/**
* Persist (and best-effort copy) the originating page so the return link survives
* the round trip into Settings and back. The return link is the
* `https://link.beacio.com/return?url=<encoded current href>` form built by
* {@link buildReturnLink}. Injects no DOM — a partner surfaces the link in its OWN
* card.
*/
declare function saveReturnContext(): void;
/**
* The originating page saved by {@link saveReturnContext}, as a VISIBLE, tappable
* affordance — never relying on a silent clipboard write. Returns the current
* href as a sensible fallback when nothing was saved.
*/
declare function getReturnContext(): {
url: string;
returnLink: string;
};
/**
* Platform detection utilities for Beacio
*/
declare function isIOSSafari(): boolean;
declare function getExtensionInstallState(): Promise<ExtensionInstallState>;
declare function isExtensionInstalled(): Promise<boolean>;
/**
* @beacio/detect#i18n — SB-SDK-07
*
* The shared localized-string seam for the two user-facing surfaces in
* @beacio/detect: the install banner (banner.ts) and the branded error
* presenter (error-presenter.ts). The install banner is the SINGLE end-user
* onboarding screen that replaces S&B's Bluefy alert, and it was hardcoded
* English — only `text`/`buttonText` were overridable. S&B is HQ'd in Bayreuth
* and its German users would see English at the make-or-break moment. This
* module centralises every visible token into a typed string pack, ships a
* built-in German (`de`) pack alongside the English (`en`) default, and exposes
* one PURE selector with a documented policy.
*
* Selection policy (resolveStrings):
* 1. an explicit BCP-47 `lang` ALWAYS wins (prefix-matched: 'de', 'de-DE',
* 'de-AT' all select the German pack);
* 2. else the runtime's `navigator.language` is prefix-matched the same way
* (so a German-locale iPhone gets German with zero config);
* 3. else English.
* A caller-supplied partial `strings` object then deep-merges OVER the selected
* pack, so an operator can override one field (e.g. just `buttonText`) without
* restating the whole pack, in any language.
*
* Design constraints (mirroring banner.ts / error-presenter.ts):
* - @beacio/core is an OPTIONAL peer, so this module imports NOTHING from core.
* The BeacioErrorCode union is re-declared LOCALLY (kept in lock-step with
* core's source by error-presenter-core-parity.test.ts, which pins the SAME
* local table this pack's `error.messages` must cover).
* - All copy uses neutral install-path framing only — no "App Store approved /
* cleared / reviewed" language (feedback_no_app_store_status_claims).
* - `{operator}` is the ONLY interpolation token; banner.ts substitutes the
* resolved operator name into it (see fill()).
*
* SDK has zero external consumers, so adding the `lang`/`strings` seam is a free,
* non-breaking change (project_sdk_no_consumers).
*/
/**
* The stable BeacioErrorCode contract (core/src/errors.ts). Re-declared locally
* — not imported — so detect has no runtime @beacio/core dependency. Kept in
* lock-step with core by error-presenter-core-parity.test.ts (the COMPILE-TIME
* EVERY_CODE Record) and by this module's own pack-parity guard in i18n.test.ts.
*/
type BeacioErrorCode$1 = 'INVALID_PARAMETER' | 'BLUETOOTH_UNAVAILABLE' | 'EXTENSION_NOT_INSTALLED' | 'PERMISSION_DENIED' | 'DEVICE_NOT_FOUND' | 'DEVICE_DISCONNECTED' | 'CONNECTION_TIMEOUT' | 'SERVICE_NOT_FOUND' | 'CHARACTERISTIC_NOT_FOUND' | 'CHARACTERISTIC_NOT_READABLE' | 'CHARACTERISTIC_NOT_WRITABLE' | 'CHARACTERISTIC_NOT_NOTIFIABLE' | 'GATT_OPERATION_FAILED' | 'SCAN_ALREADY_IN_PROGRESS' | 'CONNECTION_LIMIT_REACHED' | 'USER_CANCELLED' | 'TIMEOUT' | 'WRITE_INCOMPLETE';
/** Funnel-state lead copy: a title + body shown at the top of the bottom sheet. */
interface StateCopy {
title: string;
body: string;
}
/** A single setup step: the imperative label the user taps + its one-line "why". */
interface SetupStepCopy {
label: string;
why: string;
}
/** A branded error card's headline + body. */
interface ErrorCopy {
title: string;
body: string;
}
/** The error-presenter half of the pack — shared by presentError. */
interface ErrorStrings {
/** Dismiss button label on the error card. */
dismiss: string;
/** Retry affordance label (retriable errors only). */
retry: string;
/** Per-code branded headline. EVERY BeacioErrorCode is present (parity-guarded). */
titles: Record<BeacioErrorCode$1, string>;
/** Per-code branded body. EVERY BeacioErrorCode is present (parity-guarded). */
messages: Record<BeacioErrorCode$1, string>;
/** Fallback copy for an unrecognised error (bare string / unknown DOMException). */
generic: ErrorCopy;
}
/**
* The complete visible-string surface of @beacio/detect. The built-in `en`/`de`
* packs both define EXACTLY these keys (i18n.test.ts pins the parity), so a new
* English-only string cannot silently bypass localization.
*/
interface LocaleStrings {
/** Primary CTA label on the not-installed sheet + the lightweight bar. */
buttonText: string;
/** Sheet soft-dismiss ("Not now") label — short suppression (SB-PRD-08). */
dismiss: string;
/**
* SB-PRD-08: the EXPLICIT "Don't show again" opt-out label — the long-suppression
* control, distinct from the soft "Not now" {@link dismiss} above.
*/
dontShowAgain: string;
/** Per-funnel-state lead copy (the 'active' state renders the toast instead). */
states: {
'not-installed': StateCopy;
'installed-inactive': StateCopy;
denied: StateCopy;
/**
* SB-SDK-17: Private Browsing dead end. iOS Safari disables web extensions in
* Private Browsing (no per-extension opt-in), so beacio is inert and the app
* may already be installed — the recovery is to reopen the page in a normal
* tab, NOT to install anything. A distinct hint (no install CTA, no steps).
*/
'private-browsing': StateCopy;
};
/** The ordered first-run step list (install → … → return). */
steps: SetupStepCopy[];
/** Visible "Return to {operator}" CTA label. */
returnCta: string;
/** Sub-line explaining the link was also copied to the clipboard. */
clipboardHint: string;
/** "Reload page to re-check" control label. */
reload: string;
/** "How does setup work?" <details> summary. */
howSummary: string;
/** "How does setup work?" <details> body (ends with the linked guide phrase). */
howBody: string;
/** Linked phrase inside howBody that points at the setup guide. */
howLink: string;
/** "Privacy: No data collected" <details> summary. */
privacySummary: string;
/** "Privacy: No data collected" <details> body. */
privacyBody: string;
/** "Still stuck? Open the setup guide" affordance. */
stillStuck: string;
/** Lightweight bar banner heading ("Enable Bluetooth"). */
barTitle: string;
/** Lightweight bar banner body text. */
barText: string;
/** Once-only success toast text ("beacio is ready — tap Connect …"). */
readyToast: string;
/** Error-presenter strings (shared with presentError). */
error: ErrorStrings;
}
/**
* English (default) pack — the byte-identical source of today's rendered copy.
* `{operator}` is substituted by banner.ts with the resolved operator name.
*/
declare const EN_STRINGS: LocaleStrings;
/**
* German (`de`) pack. Mirrors EN_STRINGS key-for-key (i18n.test.ts pins the
* parity, so this pack can never fall behind a new English string). Native,
* neutral install-path German; iOS-26 Settings paths use the localized Settings
* labels (Apps → Safari → Erweiterungen) a German iPhone actually shows. The
* stylized brand word "beacio" stays lowercase mid-sentence, matching the
* English copy and the app's lowercase display name.
*/
declare const DE_STRINGS: LocaleStrings;
/** A recursively-optional view of a type, for partial `strings` overrides. */
type DeepPartial<T> = T extends (infer U)[] ? U[] : T extends object ? {
[K in keyof T]?: DeepPartial<T[K]>;
} : T;
/** Options shared by the banner + error presenter for selecting localized copy. */
interface ResolveStringsOptions {
/** Explicit BCP-47 language tag. Always wins when its primary subtag is known. */
lang?: string;
/** Partial overrides deep-merged over the selected pack (any field, any depth). */
strings?: DeepPartial<LocaleStrings>;
}
/**
* PURE locale selector implementing the SB-SDK-07 policy:
* explicit `lang` (prefix-matched) > navigator.language (prefix-matched) > English,
* then a partial `strings` override deep-merged over the selected pack.
*
* Pure + side-effect-free: it reads navigator.language only when no explicit
* `lang` is given, and never mutates the built-in packs. An unknown subtag falls
* through to English (never throws). Returns a fresh object when an override is
* supplied, else the shared pack reference (so identity checks against EN/DE_STRINGS
* hold for the no-override path the tests assert).
*/
declare function resolveStrings(options?: ResolveStringsOptions): LocaleStrings;
/**
* Install prompt UI for Beacio
*
* Two modes:
* 1. Bottom sheet (default) — iOS-native feel, shown on requestDevice() trigger
* 2. Banner — lightweight top/bottom bar for passive prompting
*
* Features:
* - Clipboard context saving for return-to-web-app flow
* - 14-day dismissal frequency capping
* - Configurable install/onboarding redirect
* - Dark mode support via prefers-color-scheme
*/
/**
* Where the user is in the irreducibly-manual iOS-26 setup funnel, so the sheet
* can render the SPECIFIC remaining step instead of restarting the whole flow:
* - 'not-installed' → app not installed; full install→enable→grant walkthrough
* - 'installed-inactive' → installed but the Safari extension toggle is off
* - 'denied' → enabled but per-origin access not granted on THIS site
* - 'private-browsing' → Private Browsing disables extensions; reopen in a normal tab
* - 'active' → ready; render the once-only success toast
* Mirrors ExtensionInstallState ('active' | 'installed-inactive' | 'not-installed')
* plus the in-page refinements only the page flow can distinguish: the per-site
* 'denied' grant and the SB-SDK-17 'private-browsing' dead end.
*/
type BannerState = 'not-installed' | 'installed-inactive' | 'denied' | 'private-browsing' | 'active';
interface BannerOptions {
/** 'sheet' (default) for iOS bottom sheet, 'banner' for lightweight bar */
mode?: 'sheet' | 'banner';
position?: 'top' | 'bottom';
style?: Record<string, string>;
/** Preferred install or onboarding URL to open when the user taps the CTA */
startOnboardingUrl?: string;
/** Legacy install destination option; still supported for compatibility */
appStoreUrl?: string;
/** Operator/app name shown in the prompt (e.g. "FitTracker") */
operatorName?: string;
/** API key for campaign tracking */
apiKey?: string;
/**
* Days to suppress the PASSIVE on-load banner after the EXPLICIT "Don't show
* again" opt-out (default: 14). SB-PRD-08: the soft "Not now"/backdrop tap uses
* a separate, short (1-day) window and is NOT governed by this option, so one
* reflexive dismiss no longer silences guidance for a fortnight.
*/
dismissDays?: number;
/**
* SB-PRD-08 (AC3): ignore the active dismissal cooldown and render anyway. The
* passive on-load banner leaves this false so a dismissed user is not nagged;
* a USER-INITIATED recovery gesture (e.g. tapping Connect, or a "Set up
* Bluetooth"/"Can't connect?" affordance) passes `forceShow: true` to re-open
* the activation flow without the integrator having to clear localStorage.
*/
forceShow?: boolean;
/**
* Funnel position. Lets initBeacio render state-specific guidance (and, on
* 'active', the once-only "ready" toast) without restarting setup. Defaults to
* 'not-installed' for the legacy "show the full walkthrough" call site.
*/
state?: BannerState;
/**
* Setup destination shown behind the "still stuck?" affordance and the
* "How does setup work?" disclosure. Defaults to the canonical /setup page;
* an operator (e.g. Storz & Bickel) can point it at their own branded help.
*/
setupUrl?: string;
/**
* SB-SDK-07: BCP-47 UI language (e.g. 'de'). When set, its primary subtag
* selects the built-in pack; when omitted, the language is derived from
* navigator.language, else English. Always wins over navigator.language.
*/
lang?: string;
/**
* SB-SDK-07: partial copy overrides deep-merged over the selected language
* pack — override one field (e.g. `buttonText`) without restating the rest.
*/
strings?: DeepPartial<LocaleStrings>;
/**
* SB-SDK-11 (tier-2 co-brand): partner accent colour applied to the sheet/bar
* chrome (icon tile, step bullets, primary CTA, disclosure links). Routed
* through a `--bc-accent` CSS variable so every accent rule switches to
* var(--bc-accent); when omitted the variable defaults to the beacio Apple-blue
* (#007aff) and the prompt renders exactly as before. Any CSS colour token.
*/
accentColor?: string;
/**
* SB-SDK-11: partner logo, restricted to a URL (no raw SVG markup) so it can
* never inject script. Validated with `new URL()` against the page origin and
* accepted ONLY when the resolved protocol is http(s); a `javascript:`/`data:`/
* `ftp:` value is dropped and the default beacio chrome icon is kept. Rendered
* as an <img> in place of the inline beacio <svg>.
*/
brandLogoUrl?: string;
/**
* SB-SDK-11: the specific device being connected (e.g. "VOLCANO HYBRID"). When
* set it is interpolated into the `{device}` token of any copy that carries it,
* so a co-brand sheet can read "Connect your VOLCANO HYBRID in Safari".
*/
deviceName?: string;
/**
* SB-SDK-11: a one-shot override for the sheet's lead body copy. Wins over the
* resolved language pack's state body (HTML-escaped via esc(), like all copy).
* For finer-grained per-field overrides use the SB-SDK-07 `strings` seam.
*/
body?: string;
/**
* SB-SDK-11: override for the privacy reassurance body (the medical-market
* trust line). HTML-escaped. Defaults to the resolved pack's privacyBody.
*/
privacyBody?: string;
}
interface SetupStep {
/** Imperative step label the user taps. */
label: string;
/** One-line "why this is required", shown under the label. */
why: string;
}
/**
* The real sequence a first-run owner actually taps on a physical iPhone, each
* grant with its own "why" so no system prompt is a surprise. Ordering and count
* are the contract: install → open app → enable extension → allow website access
* (the aA gesture) → allow Bluetooth on first scan → return.
*
* SB-SDK-07: this is the ENGLISH step list, now sourced from EN_STRINGS.steps so
* the exported constant (mirrored by the react-sdk InstallationWizard) and the
* localized pack never drift. Localized rendering reads the resolved pack's
* steps; the per-state filtering below is by INDEX into this canonical order, so
* it is language-independent (German labels do not match the old English regex).
*/
declare const SETUP_STEPS: readonly SetupStep[];
declare function showInstallBanner(options?: BannerOptions): HTMLElement | null;
declare function removeInstallBanner(): void;
/**
* @beacio/detect#presentError — SB-SDK-05
*
* A drop-in, framework-free branded ERROR presenter. The polished branded surface
* already exists for the INSTALL prompt (banner.ts); this is its sibling for the
* FAILURE path. S&B (and any vanilla-JS site) uses raw `navigator.bluetooth`
* across hundreds of call sites and will not rewrite them — so the worst surface,
* a blocking, stack-leaking `window.alert()`, is converted into a non-blocking,
* dismissible, recovery-oriented card with a ~1-line edit:
*
* catch (error) { beacioDetect.presentError(error); }
*
* Design constraints (mirroring banner.ts):
* - @beacio/core is an OPTIONAL peer (a standalone `npm i @beacio/detect` has no
* core), so this file MUST NOT import @beacio/core — not even the BeacioError
* class. Errors are consumed STRUCTURALLY: anything carrying a `.code` /
* `.message` / `.suggestion` / `.isRetriable` is understood, and the
* BeacioErrorCode → copy map + retriable set are kept LOCAL (pinned to core's
* public contract by the unit test, not by a runtime import).
* - The card NEVER leaks a stack trace, internal codes, WebKit jargon, or a
* competitor name. The friendly body comes from the per-code copy table, NOT
* the raw error string.
* - Identical errors fired in a short window are coalesced to ONE card (defends
* against the backgrounded alert-storm).
* - All user-visible strings are overridable via a copy/locale object
* (PresentErrorOptions.strings) — the i18n seam SB-SDK-07 converges on; the
* `lang` field selects a built-in pack (German shipped), and `strings`
* deep-merges over it. English defaults apply when neither is supplied (no
* regression). The per-code copy + dismiss/retry come from the SAME shared
* i18n module the install banner uses (./i18n), so a localized card and a
* localized banner never drift.
*/
/**
* The stable BeacioErrorCode contract (core/src/errors.ts). Kept local — not
* imported — so detect has no runtime @beacio/core dependency. The presenter unit
* test is the seam-crossing control that this list still matches core's source.
*/
type BeacioErrorCode = 'INVALID_PARAMETER' | 'BLUETOOTH_UNAVAILABLE' | 'EXTENSION_NOT_INSTALLED' | 'PERMISSION_DENIED' | 'DEVICE_NOT_FOUND' | 'DEVICE_DISCONNECTED' | 'CONNECTION_TIMEOUT' | 'SERVICE_NOT_FOUND' | 'CHARACTERISTIC_NOT_FOUND' | 'CHARACTERISTIC_NOT_READABLE' | 'CHARACTERISTIC_NOT_WRITABLE' | 'CHARACTERISTIC_NOT_NOTIFIABLE' | 'GATT_OPERATION_FAILED' | 'SCAN_ALREADY_IN_PROGRESS' | 'CONNECTION_LIMIT_REACHED' | 'USER_CANCELLED' | 'TIMEOUT' | 'WRITE_INCOMPLETE';
/**
* Caller-supplied copy/locale overrides — the i18n seam (SB-SDK-07). Every field
* is optional; an omitted field falls back to the English default, so an existing
* caller that passes nothing is byte-identical to today.
*/
interface PresentErrorStrings {
/** Dismiss button label (English default: "Dismiss"). */
dismiss?: string;
/** Retry affordance label for retriable errors (English default: "Try again"). */
retry?: string;
/** Per-code body override. A code present here replaces the English body. */
messages?: Partial<Record<BeacioErrorCode, string>>;
}
/**
* Options for {@link presentError}. Parity with BannerOptions where it overlaps
* (operatorName, style), plus the retry affordance + the copy/locale seam.
*/
interface PresentErrorOptions {
/** Operator/app name shown in the card (e.g. "STORZ & BICKEL"). */
operatorName?: string;
/**
* SB-SDK-07: BCP-47 UI language (e.g. 'de'). Selects the built-in pack for the
* per-code title/body + dismiss/retry labels; omitted ⇒ derived from
* navigator.language, else English. A per-call `strings` (and the explicit
* dismissText/retryText) still override the selected pack. Always wins over
* navigator.language.
*/
lang?: string;
/** Retry button label override (takes precedence over strings.retry). */
retryText?: string;
/** Dismiss button label override (takes precedence over strings.dismiss). */
dismissText?: string;
/**
* Invoked when the user taps the retry affordance (retriable errors only), so a
* caller can re-run its connect()/operation. The card is dismissed first.
*/
onRetry?: () => void;
/** Extra inline styles merged onto the card container. */
style?: Record<string, string>;
/** Copy/locale overrides for every user-visible string (SB-SDK-07 seam). */
strings?: PresentErrorStrings;
}
/**
* Present a branded, non-blocking, dismissible error card. Replaces a blocking
* `window.alert(error.toString() + error.stack)` with a recovery-oriented surface.
*
* @param errorOrMessage A BeacioError, a raw DOMException/Error, or a string.
* @param options Operator name, copy/locale overrides, and an onRetry handler.
* @returns The card element, or null when the error is coalesced (a card for an
* identical error is already on screen) so callers can no-op safely.
*/
declare function presentError(errorOrMessage: unknown, options?: PresentErrorOptions): HTMLElement | null;
/**
* Analytics event reporter and API key validator.
* Fire-and-forget — analytics must never throw or block.
*/
declare function reportEvent(apiKey: string, event: string, _data?: {
[key: string]: string | number | boolean | null;
}): void;
declare function validateApiKey(apiKey: string): Promise<{
operatorId: string;
appName: string | null;
plan: string;
} | null>;
/**
* @beacio/detect
*
* Detects iOS Safari, checks if the Beacio extension is installed,
* and shows an install banner if not. No-op on all other platforms.
*
* Your existing Web Bluetooth code works unchanged — this package only
* handles the "extension not installed" case on iOS Safari.
*/
interface BeacioOptions {
/** Optional API key for campaign tracking */
key?: string;
/** Operator/app name shown in the prompt (e.g. "FitTracker") */
operatorName?: string;
/**
* SB-SDK-07: BCP-47 UI language (e.g. 'de') for the install banner. Threaded
* to showInstallBanner so the zero-config initBeacio path is localizable;
* omitted ⇒ the banner derives the language from navigator.language, else
* English. A `banner.lang` (below) overrides this for the banner specifically.
*/
lang?: string;
/** Install banner configuration, or false to disable */
banner?: {
/** 'sheet' (default) for iOS bottom sheet, 'banner' for lightweight bar */
mode?: 'sheet' | 'banner';
position?: 'top' | 'bottom';
text?: string;
buttonText?: string;
style?: Record<string, string>;
startOnboardingUrl?: string;
appStoreUrl?: string;
/** Days to suppress after the explicit "Don't show again" opt-out (default: 14) */
dismissDays?: number;
/**
* SB-PRD-08 (AC3): ignore the dismissal cooldown and show anyway. Set this
* on a user-initiated recovery call (e.g. re-invoking initBeacio from a
* Connect / "Can't connect?" gesture) so a previously-dismissed user can
* re-open setup without clearing localStorage.
*/
forceShow?: boolean;
/** SB-SDK-07: BCP-47 language override for the banner (wins over the top-level `lang`). */
lang?: string;
/** SB-SDK-11 (tier-2 co-brand): partner accent colour for the prompt chrome. */
accentColor?: string;
/** SB-SDK-11: partner logo URL (http(s) only; validated). Replaces the beacio glyph. */
brandLogoUrl?: string;
/** SB-SDK-11: the connected device's display name (e.g. "VOLCANO HYBRID"). */
deviceName?: string;
/** SB-SDK-11: one-shot lead body copy override (HTML-escaped). */
body?: string;
/** SB-SDK-11: privacy reassurance body override (HTML-escaped). */
privacyBody?: string;
} | false;
/** Called when the extension is detected and ready */
onReady?: () => void;
/** Called when the extension is installed but Safari still needs activation/allow access */
onInstalledInactive?: () => void;
/** Called when the extension is NOT installed */
onNotInstalled?: () => void;
}
/**
* Initialize Beacio detection.
*
* On iOS Safari: checks if the extension is installed, dispatches events,
* and optionally shows an install banner.
*
* On all other platforms: no-op (returns immediately).
*/
declare function initBeacio(options: BeacioOptions): Promise<void>;
/**
* Where a first-run owner is in the irreducibly-manual iOS-26 setup funnel, as
* DATA a partner renders itself. The union is CLOSED to exactly initBeacio's six
* routing outcomes (a discriminated union + exhaustive switch, not scattered
* undefined checks):
* - 'unsupported' → not iOS Safari; Web Bluetooth via beacio is unavailable.
* - 'not-installed' → app not installed; `installUrl` is the id-form App Store link.
* - 'installed-inactive' → installed but the Safari extension toggle is off; `setupUrl` guides.
* - 'denied' → enabled, but per-origin access not granted on THIS site; `setupUrl` guides.
* - 'private-browsing' → Private Browsing disables extensions; the fix is a normal tab.
* - 'ready' → the polyfill is live and this origin is granted; nothing to prompt.
*
* Required fields, sentinels over optionals (owner's API rule): each variant
* carries only the render-ready URLs its OWN prompt needs, all required — no `?`.
* `returnLink` is the tappable "return to your page" affordance
* (`https://link.beacio.com/return?url=…`) computed purely, with no side effect.
*/
type OnboardingState = {
kind: 'unsupported';
} | {
kind: 'not-installed';
installUrl: string;
returnLink: string;
} | {
kind: 'installed-inactive';
setupUrl: string;
returnLink: string;
} | {
kind: 'denied';
setupUrl: string;
returnLink: string;
} | {
kind: 'private-browsing';
returnLink: string;
} | {
kind: 'ready';
};
/**
* The REQUIRED config for {@link resolveOnboardingState} — no optional args.
* `apiKey` threads the App Store campaign token (ct/mt) onto the install deep link
* exactly as the banner's install button does; `operatorName` threads the operator
* identity onto the guided /setup deep link so it can render "Return to <operator>".
* Pass empty-string sentinels when a field is not in play.
*/
interface OnboardingConfig {
operatorName: string;
apiKey: string;
}
/**
* Resolve the current tier-3 onboarding funnel position WITHOUT rendering any
* beacio chrome. This is the headless projection of initBeacio's routing: the same
* isIOSSafari early-return, the same active → (denied?) → ready split, and the same
* "Private Browsing wins over a marker-suppressed denied" precedence for the
* non-active states — but it returns the position as data for a partner to render,
* dispatching NO events and injecting NO DOM.
*/
declare function resolveOnboardingState(config: OnboardingConfig): Promise<OnboardingState>;
export { APP_STORE_URL, type BannerOptions, type BannerState, type BeacioErrorCode, type BeacioOptions, DEFAULT_DISMISS_DAYS, DE_STRINGS, type DeepPartial, EN_STRINGS, type ErrorCopy, type ErrorStrings, type ExtensionInstallState, type LocaleStrings, type OnboardingConfig, type OnboardingState, type PresentErrorOptions, type PresentErrorStrings, type ResolveStringsOptions, SETUP_STEPS, SHORT_DISMISS_DAYS, type SetupStep, type SetupStepCopy, type StateCopy, dismiss, dismissShort, getExtensionInstallState, getInstallState, getReturnContext, initBeacio, isDismissed, isExtensionActive, isExtensionInstalled, isIOSSafari, observeInstallState, presentError, removeInstallBanner, reportEvent, resolveOnboardingState, resolveStrings, saveReturnContext, showInstallBanner, validateApiKey };
'use strict';var Ue=Object.defineProperty;var y=(e,t)=>()=>(e&&(t=e(e=0)),t);var K=(e,t)=>{for(var r in t)Ue(e,r,{get:t[r],enumerable:true});};var p,k=y(()=>{p={STATE_CHANGE:"beacio:statechange",READY:"beacio:ready",INSTALLED_INACTIVE:"beacio:installedinactive",NOT_INSTALLED:"beacio:notinstalled",EXTENSION_READY:"beacio:extension:ready",EXTENSION_PING:"beacio:extension:ping",EXTENSION_PONG:"beacio:extension:pong",EXTENSION_ACTIVATE_REQUEST:"beacio:extension:activate-request",EXTENSION_ACTIVATE_RESULT:"beacio:extension:activate-result",EXTENSION_INSTALLED:"beacio:extension:installed",EXTENSION_STATUS_CHANGE:"beacio:extension:statuschange"};});function ze(){return typeof window<"u"&&window.__beacio?.status==="installed"}function Ve(){if(typeof navigator>"u")return false;let e=navigator;return !!(e.beacio&&e.beacio.__beacio)}function $e(){return typeof document<"u"&&document.documentElement.dataset.beacioInstalled==="true"}function He(){return typeof document<"u"&&document.documentElement.dataset.beacioExtension==="true"}function T(){return Ve()||He()?"active":ze()||$e()?"installed-inactive":"not-installed"}function j(){return T()==="active"}function Fe(e=3e3){let t=T();return t==="active"||typeof window>"u"?Promise.resolve(t):new Promise(r=>{let n=false,o=c=>{n||(n=true,window.removeEventListener(le,i),clearTimeout(a),r(c));},i=()=>o("active");window.addEventListener(le,i);let a=setTimeout(()=>o(T()),e);})}function Y(){try{let e=localStorage.getItem(de);return e?Date.now()<parseInt(e,10):!1}catch{return false}}function B(e=exports.DEFAULT_DISMISS_DAYS){try{localStorage.setItem(de,String(Date.now()+e*864e5));}catch{}}function C(){B(exports.SHORT_DISMISS_DAYS);}function X(){let e=typeof window<"u"?window.location.href:"https://beacio.com",t=new URL(e),r=new URL(`https://${Me}/return`);return r.searchParams.set("url",t.toString()),r.toString()}function P(){if(typeof window>"u")return;let e=new URL(window.location.href),t=X();try{localStorage.setItem(ue,JSON.stringify({url:e.toString(),returnLink:t,timestamp:Date.now()})),navigator.storage?.persist?.();}catch{}try{navigator.clipboard?.writeText(t);}catch{}}function U(){let e=typeof window<"u"?window.location.href:"";try{let t=localStorage.getItem(ue);if(t){let r=JSON.parse(t),n=r.url||e;return {url:n,returnLink:r.returnLink||n}}}catch{}return {url:e,returnLink:e}}var le,de,ue,Me;exports.APP_STORE_URL=void 0;exports.DEFAULT_DISMISS_DAYS=void 0;exports.SHORT_DISMISS_DAYS=void 0;var O=y(()=>{k();le=p.EXTENSION_READY,de="beacio_dismiss_until",ue="beacio_return",Me="link.beacio.com",exports.APP_STORE_URL="https://apps.apple.com/app/id6761301368";exports.DEFAULT_DISMISS_DAYS=14,exports.SHORT_DISMISS_DAYS=1;});var fe={};K(fe,{CDN_STUB_MARKER:()=>q,detectPlatform:()=>Ge,getBluetoothAPI:()=>We});function Ge(){if(typeof navigator>"u")return "unsupported";let e=navigator;return e.beacio?.__beacio===true?"safari-extension":e.bluetooth&&!e.bluetooth[q]?"native":"unsupported"}function We(){if(typeof navigator>"u")return null;let e=navigator;return e.beacio?.__beacio===true?e.beacio:e.bluetooth&&!e.bluetooth[q]?e.bluetooth:null}var q,ge=y(()=>{q="__beacioCDNStub";});var J={};K(J,{getExtensionInstallState:()=>Z,isExtensionInstalled:()=>he,isIOSSafari:()=>Ee});function Ee(){if(typeof navigator>"u")return false;let e=navigator.userAgent,t=/iPad|iPhone|iPod/.test(e)||navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1,r=/^((?!chrome|android|crios|fxios).)*safari/i.test(e);return t&&r}async function Z(){try{let{detectPlatform:e}=await Promise.resolve().then(()=>(ge(),fe));if(e()==="safari-extension")return "active"}catch{}return new Promise(e=>{let t=T();if(t!=="not-installed"){e(t);return}let r=0,n=setInterval(()=>{r++;let o=T();o!=="not-installed"&&(clearInterval(n),e(o)),r>20&&(clearInterval(n),e("not-installed"));},100);})}async function he(){return await Z()!=="not-installed"}var M=y(()=>{O();});var z,Q=y(()=>{z="https://beacio.com/setup";});function Se(e){return !e||typeof e!="string"?"":e.split("-",1)[0].trim().toLowerCase()}function Ke(){if(!(typeof navigator>"u"))return navigator.language}function Te(e,t){if(t==null)return e;if(Array.isArray(e)||typeof e!="object"||e===null)return t;let r={...e};for(let n of Object.keys(t)){let o=t[n];o!==void 0&&(r[n]=Te(e[n],o));}return r}function v(e={}){let t=Se(e.lang),r=t&&me[t]||me[Se(Ke())]||exports.EN_STRINGS;return e.strings?Te(r,e.strings):r}exports.EN_STRINGS=void 0;exports.DE_STRINGS=void 0;var me,V=y(()=>{exports.EN_STRINGS={buttonText:"Start Setup",dismiss:"Not now",dontShowAgain:"Don't show again",states:{"not-installed":{title:"Set Up Bluetooth in Safari",body:"Follow the steps below to enable Bluetooth and return to {operator}."},"installed-inactive":{title:"Enable beacio in Safari",body:"beacio is installed but the Safari extension is off. Open Settings \u2192 Apps \u2192 Safari \u2192 Extensions \u2192 beacio and turn on Allow Extension, then return here."},denied:{title:"Allow beacio on this site",body:"beacio is enabled but not yet allowed here. Tap the aA button in the address bar \u2192 Manage Extensions \u2192 beacio \u2192 Allow Every Website, then reload this page."},"private-browsing":{title:"Private Browsing blocks extensions",body:"Private Browsing disables Safari extensions, so beacio cannot run here \u2014 even if it is installed. Open this page in a normal tab to connect your device."}},steps:[{label:"Install beacio",why:"A free one-time companion app from the App Store."},{label:"Open the app once",why:"This registers the Safari extension with iOS."},{label:"Enable in Safari Settings",why:"Settings \u2192 Apps \u2192 Safari \u2192 Extensions \u2192 beacio \u2192 turn on Allow Extension."},{label:"Allow website access",why:"On the site, tap the aA button in the address bar \u2192 Manage Extensions \u2192 beacio \u2192 Allow Every Website."},{label:"Allow Bluetooth on first scan",why:"The first time you connect, Safari will ask to allow this site \u2014 tap Allow."},{label:"Return and reload",why:"Come back to this page, reload, and tap Connect."}],returnCta:"Return to {operator}",clipboardHint:"Link also copied \u2014 paste it into Safari if this button does not reopen {operator}.",reload:"Reload page to re-check",howSummary:"How does setup work?",howBody:"beacio uses a one-time iPhone app to enable the Safari extension. After enabling it and allowing access on this site (aA button \u2192 Manage Extensions \u2192 Allow Every Website), Bluetooth works in Safari.",howLink:"See the full setup guide",privacySummary:"Privacy: No data collected",privacyBody:"beacio processes all Bluetooth data locally on your device. No browsing data, device data, or personal information is ever collected or transmitted.",stillStuck:"Still stuck? Open the setup guide",barTitle:"Enable Bluetooth",barText:"Install Beacio, open the app, enable the Safari extension, then return here.",readyToast:"beacio is ready \u2014 tap Connect to pair your device with {operator}.",error:{dismiss:"Dismiss",retry:"Try again",titles:{INVALID_PARAMETER:"Something went wrong",BLUETOOTH_UNAVAILABLE:"Bluetooth is unavailable",EXTENSION_NOT_INSTALLED:"Finish Bluetooth setup",PERMISSION_DENIED:"Allow Bluetooth to continue",DEVICE_NOT_FOUND:"No device found",DEVICE_DISCONNECTED:"Device disconnected",CONNECTION_TIMEOUT:"Connection timed out",SERVICE_NOT_FOUND:"Device feature unavailable",CHARACTERISTIC_NOT_FOUND:"Device feature unavailable",CHARACTERISTIC_NOT_READABLE:"Cannot read from device",CHARACTERISTIC_NOT_WRITABLE:"Cannot send to device",CHARACTERISTIC_NOT_NOTIFIABLE:"Live updates unavailable",GATT_OPERATION_FAILED:"Connection interrupted",SCAN_ALREADY_IN_PROGRESS:"Already searching",CONNECTION_LIMIT_REACHED:"Too many devices connected",USER_CANCELLED:"Connection cancelled",TIMEOUT:"Operation timed out",WRITE_INCOMPLETE:"Send incomplete"},messages:{INVALID_PARAMETER:"The request could not be completed. Please reload the page and try again.",BLUETOOTH_UNAVAILABLE:"Turn Bluetooth on, then try again.",EXTENSION_NOT_INSTALLED:"Bluetooth is not enabled for this site yet. Finish setup, then try connecting again.",PERMISSION_DENIED:"Bluetooth access was not granted. Tap Connect yourself (Bluetooth needs a tap), then allow access when asked.",DEVICE_NOT_FOUND:"No matching device was found. Switch your device on, keep it close, then try again.",DEVICE_DISCONNECTED:"The connection to your device was lost. Reconnect to continue.",CONNECTION_TIMEOUT:"Your device did not respond in time. Keep it close and powered on, then try again.",SERVICE_NOT_FOUND:"A required feature was not found on this device. Check that it is the right device and try again.",CHARACTERISTIC_NOT_FOUND:"A required feature was not found on this device. Check that it is the right device and try again.",CHARACTERISTIC_NOT_READABLE:"This value cannot be read from your device. No action is needed for this control.",CHARACTERISTIC_NOT_WRITABLE:"This value cannot be sent to your device. No action is needed for this control.",CHARACTERISTIC_NOT_NOTIFIABLE:"This value does not support live updates on your device.",GATT_OPERATION_FAILED:"Something interrupted the connection. Switch your device off and on, then try again.",SCAN_ALREADY_IN_PROGRESS:"A device search is already running. Wait a moment, then try again.",CONNECTION_LIMIT_REACHED:"Disconnect another device before connecting a new one.",USER_CANCELLED:"No device was selected. Tap Connect to try again whenever you are ready.",TIMEOUT:"That took too long. Check your device is close and powered on, then try again.",WRITE_INCOMPLETE:"Only part of the data reached your device. Try again to resend it."},generic:{title:"Something went wrong",body:"Something interrupted the connection. Please try again."}}},exports.DE_STRINGS={buttonText:"Einrichtung starten",dismiss:"Jetzt nicht",dontShowAgain:"Nicht mehr anzeigen",states:{"not-installed":{title:"Bluetooth in Safari einrichten",body:"Folge den Schritten unten, um Bluetooth zu aktivieren und zu {operator} zur\xFCckzukehren."},"installed-inactive":{title:"beacio in Safari aktivieren",body:"beacio ist installiert, aber die Safari-Erweiterung ist deaktiviert. \xD6ffne Einstellungen \u2192 Apps \u2192 Safari \u2192 Erweiterungen \u2192 beacio und aktiviere \u201EErweiterung erlauben\u201C, kehre dann hierher zur\xFCck."},denied:{title:"beacio f\xFCr diese Seite erlauben",body:"beacio ist aktiviert, aber f\xFCr diese Seite noch nicht erlaubt. Tippe auf die Schaltfl\xE4che \u201EaA\u201C in der Adressleiste \u2192 Erweiterungen verwalten \u2192 beacio \u2192 \u201EAuf allen Websites erlauben\u201C und lade diese Seite dann neu."},"private-browsing":{title:"Privates Surfen blockiert Erweiterungen",body:"Im privaten Surfmodus sind Safari-Erweiterungen deaktiviert, daher kann beacio hier nicht laufen \u2014 auch wenn es installiert ist. \xD6ffne diese Seite in einem normalen Tab, um dein Ger\xE4t zu verbinden."}},steps:[{label:"beacio installieren",why:"Eine kostenlose, einmalige Begleit-App aus dem App Store."},{label:"App einmal \xF6ffnen",why:"Damit wird die Safari-Erweiterung bei iOS registriert."},{label:"In den Safari-Einstellungen aktivieren",why:"Einstellungen \u2192 Apps \u2192 Safari \u2192 Erweiterungen \u2192 beacio \u2192 \u201EErweiterung erlauben\u201C aktivieren."},{label:"Website-Zugriff erlauben",why:"Tippe auf der Seite auf die Schaltfl\xE4che \u201EaA\u201C in der Adressleiste \u2192 Erweiterungen verwalten \u2192 beacio \u2192 \u201EAuf allen Websites erlauben\u201C."},{label:"Bluetooth beim ersten Scan erlauben",why:"Beim ersten Verbinden fragt Safari, ob diese Seite zugreifen darf \u2014 tippe auf \u201EErlauben\u201C."},{label:"Zur\xFCckkehren und neu laden",why:"Komm zu dieser Seite zur\xFCck, lade sie neu und tippe auf \u201EVerbinden\u201C."}],returnCta:"Zur\xFCck zu {operator}",clipboardHint:"Link wurde au\xDFerdem kopiert \u2014 f\xFCge ihn in Safari ein, falls diese Schaltfl\xE4che {operator} nicht erneut \xF6ffnet.",reload:"Seite neu laden und erneut pr\xFCfen",howSummary:"Wie funktioniert die Einrichtung?",howBody:"beacio nutzt eine einmalige iPhone-App, um die Safari-Erweiterung zu aktivieren. Sobald sie aktiviert und der Zugriff auf dieser Seite erlaubt ist (Schaltfl\xE4che \u201EaA\u201C \u2192 Erweiterungen verwalten \u2192 \u201EAuf allen Websites erlauben\u201C), funktioniert Bluetooth in Safari.",howLink:"Zur vollst\xE4ndigen Einrichtungsanleitung",privacySummary:"Datenschutz: Keine Datenerfassung",privacyBody:"beacio verarbeitet alle Bluetooth-Daten lokal auf deinem Ger\xE4t. Es werden niemals Browserdaten, Ger\xE4tedaten oder pers\xF6nliche Informationen erfasst oder \xFCbertragen.",stillStuck:"Kommst du nicht weiter? Einrichtungsanleitung \xF6ffnen",barTitle:"Bluetooth aktivieren",barText:"Installiere beacio, \xF6ffne die App, aktiviere die Safari-Erweiterung und kehre dann hierher zur\xFCck.",readyToast:"beacio ist bereit \u2014 tippe auf \u201EVerbinden\u201C, um dein Ger\xE4t mit {operator} zu koppeln.",error:{dismiss:"Schlie\xDFen",retry:"Erneut versuchen",titles:{INVALID_PARAMETER:"Etwas ist schiefgelaufen",BLUETOOTH_UNAVAILABLE:"Bluetooth ist nicht verf\xFCgbar",EXTENSION_NOT_INSTALLED:"Bluetooth-Einrichtung abschlie\xDFen",PERMISSION_DENIED:"Bluetooth erlauben, um fortzufahren",DEVICE_NOT_FOUND:"Kein Ger\xE4t gefunden",DEVICE_DISCONNECTED:"Ger\xE4t getrennt",CONNECTION_TIMEOUT:"Zeit\xFCberschreitung der Verbindung",SERVICE_NOT_FOUND:"Ger\xE4tefunktion nicht verf\xFCgbar",CHARACTERISTIC_NOT_FOUND:"Ger\xE4tefunktion nicht verf\xFCgbar",CHARACTERISTIC_NOT_READABLE:"Lesen vom Ger\xE4t nicht m\xF6glich",CHARACTERISTIC_NOT_WRITABLE:"Senden an das Ger\xE4t nicht m\xF6glich",CHARACTERISTIC_NOT_NOTIFIABLE:"Live-Aktualisierungen nicht verf\xFCgbar",GATT_OPERATION_FAILED:"Verbindung unterbrochen",SCAN_ALREADY_IN_PROGRESS:"Suche l\xE4uft bereits",CONNECTION_LIMIT_REACHED:"Zu viele Ger\xE4te verbunden",USER_CANCELLED:"Verbindung abgebrochen",TIMEOUT:"Zeit\xFCberschreitung des Vorgangs",WRITE_INCOMPLETE:"Senden unvollst\xE4ndig"},messages:{INVALID_PARAMETER:"Die Anfrage konnte nicht abgeschlossen werden. Lade die Seite neu und versuche es erneut.",BLUETOOTH_UNAVAILABLE:"Schalte Bluetooth ein und versuche es erneut.",EXTENSION_NOT_INSTALLED:"Bluetooth ist f\xFCr diese Seite noch nicht aktiviert. Schlie\xDFe die Einrichtung ab und versuche dann erneut, dich zu verbinden.",PERMISSION_DENIED:"Der Bluetooth-Zugriff wurde nicht gew\xE4hrt. Tippe selbst auf \u201EVerbinden\u201C (Bluetooth erfordert eine Ber\xFChrung) und erlaube den Zugriff, wenn du gefragt wirst.",DEVICE_NOT_FOUND:"Es wurde kein passendes Ger\xE4t gefunden. Schalte dein Ger\xE4t ein, halte es in der N\xE4he und versuche es erneut.",DEVICE_DISCONNECTED:"Die Verbindung zu deinem Ger\xE4t wurde unterbrochen. Verbinde dich erneut, um fortzufahren.",CONNECTION_TIMEOUT:"Dein Ger\xE4t hat nicht rechtzeitig geantwortet. Halte es in der N\xE4he und eingeschaltet und versuche es erneut.",SERVICE_NOT_FOUND:"Eine erforderliche Funktion wurde auf diesem Ger\xE4t nicht gefunden. Pr\xFCfe, ob es das richtige Ger\xE4t ist, und versuche es erneut.",CHARACTERISTIC_NOT_FOUND:"Eine erforderliche Funktion wurde auf diesem Ger\xE4t nicht gefunden. Pr\xFCfe, ob es das richtige Ger\xE4t ist, und versuche es erneut.",CHARACTERISTIC_NOT_READABLE:"Dieser Wert kann nicht von deinem Ger\xE4t gelesen werden. F\xFCr dieses Element ist keine Aktion erforderlich.",CHARACTERISTIC_NOT_WRITABLE:"Dieser Wert kann nicht an dein Ger\xE4t gesendet werden. F\xFCr dieses Element ist keine Aktion erforderlich.",CHARACTERISTIC_NOT_NOTIFIABLE:"Dieser Wert unterst\xFCtzt auf deinem Ger\xE4t keine Live-Aktualisierungen.",GATT_OPERATION_FAILED:"Etwas hat die Verbindung unterbrochen. Schalte dein Ger\xE4t aus und wieder ein und versuche es erneut.",SCAN_ALREADY_IN_PROGRESS:"Es l\xE4uft bereits eine Ger\xE4tesuche. Warte einen Moment und versuche es erneut.",CONNECTION_LIMIT_REACHED:"Trenne ein anderes Ger\xE4t, bevor du ein neues verbindest.",USER_CANCELLED:"Es wurde kein Ger\xE4t ausgew\xE4hlt. Tippe auf \u201EVerbinden\u201C, um es erneut zu versuchen, wann immer du bereit bist.",TIMEOUT:"Das hat zu lange gedauert. Pr\xFCfe, ob dein Ger\xE4t in der N\xE4he und eingeschaltet ist, und versuche es erneut.",WRITE_INCOMPLETE:"Nur ein Teil der Daten hat dein Ger\xE4t erreicht. Versuche es erneut, um sie noch einmal zu senden."},generic:{title:"Etwas ist schiefgelaufen",body:"Etwas hat die Verbindung unterbrochen. Bitte versuche es erneut."}}},me={en:exports.EN_STRINGS,de:exports.DE_STRINGS};});var ee={};K(ee,{SETUP_STEPS:()=>exports.SETUP_STEPS,buildOnboardingUrl:()=>_e,removeInstallBanner:()=>Re,showInstallBanner:()=>Oe});function Ne(e){return e.startOnboardingUrl??e.appStoreUrl??Ye}function _e(e,t={}){let r=typeof window<"u"?window.location.href:void 0,n=new URL(e,r);if(n.hostname==="apps.apple.com"){let i=n.pathname.match(/id\d+/)?.[0];return n.pathname=i?`/app/${i}`:new URL(exports.APP_STORE_URL).pathname,t.apiKey&&!n.searchParams.has("ct")&&(n.searchParams.set("ct",t.apiKey),n.searchParams.set("mt","8")),n.toString()}return t.operatorName&&!n.searchParams.has("operatorName")&&n.searchParams.set("operatorName",t.operatorName),t.returnUrl&&!n.searchParams.has("return")&&n.searchParams.set("return",t.returnUrl),n.toString()}function Ae(e,t,r){P();let n=U().url;window.location.href=_e(e,{apiKey:t,operatorName:r,returnUrl:n});}function s(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function $(e,t,r=""){return e.replace(/\{operator\}/g,t).replace(/\{device\}/g,r)}function Ce(e){if(!e||typeof e!="string")return null;let t=typeof window<"u"?window.location.href:"https://beacio.com";try{let r=new URL(e,t);return r.protocol==="http:"||r.protocol==="https:"?r.href:null}catch{return null}}function Ze(e){let{operatorName:t=document.title||window.location.hostname,apiKey:r,dismissDays:n=14,state:o="not-installed"}=e,i=Ne(e),a=v({lang:e.lang,strings:e.strings}),c=a.buttonText,b=o==="active"?"not-installed":o,{title:m}=a.states[b],S=e.body??a.states[b].body,d=e.setupUrl??i,u=U(),N=e.accentColor??"#007aff",h=Ce(e.brandLogoUrl),_=e.deviceName??"",G=!!(e.accentColor||h||e.deviceName),ne=e.privacyBody??a.privacyBody,Pe=(b==="not-installed"?a.steps:je[b].map(f=>a.steps[f])).map(f=>`<li class="bc-step"><span class="bc-step-l">${s(f.label)}</span><span class="bc-step-w">${s(f.why)}</span></li>`).join(""),l=document.createElement("div");l.id="beacio-banner",l.dataset.beacioState=b,l.innerHTML=`
<style>
/* SB-SDK-11: the partner accent is exposed as a single CSS custom property on the
sheet root; every accent rule below reads var(--bc-accent). When unthemed the
value defaults to the beacio Apple-blue, so the rendered sheet is unchanged. */
#bc-s{--bc-accent:${s(N)}}
#beacio-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-end;
justify-content:center;background:rgba(0,0,0,.4);font-family:-apple-system,BlinkMacSystemFont,
'SF Pro Text',system-ui,sans-serif;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
animation:bc-fi .25s ease-out}
@keyframes bc-fi{from{opacity:0}to{opacity:1}}
@keyframes bc-su{from{transform:translateY(100%)}to{transform:translateY(0)}}
#bc-s{background:#fff;border-radius:16px 16px 0 0;padding:12px 20px 28px;max-width:420px;
width:100%;animation:bc-su .3s ease-out;max-height:90vh;overflow-y:auto;
-webkit-overflow-scrolling:touch}
#bc-s *{box-sizing:border-box;margin:0;padding:0}
.bc-h{width:36px;height:5px;border-radius:3px;background:#d1d1d6;margin:0 auto 10px}
.bc-hdr{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.bc-ic{width:36px;height:36px;border-radius:9px;background:var(--bc-accent);display:flex;
align-items:center;justify-content:center;flex-shrink:0;overflow:hidden}
.bc-ic svg{width:20px;height:20px;fill:#fff}
.bc-ic img{width:100%;height:100%;object-fit:contain}
.bc-tt{font-size:16px;font-weight:600;color:#000}
.bc-bd{font-size:13px;line-height:1.35;color:#8e8e93;margin-bottom:12px}
.bc-steps{list-style:none;margin:0 0 14px;padding:0;counter-reset:bc-step}
.bc-step{position:relative;padding:0 0 8px 28px;font-size:13px;line-height:1.35}
.bc-step::before{counter-increment:bc-step;content:counter(bc-step);position:absolute;left:0;top:0;
width:18px;height:18px;border-radius:50%;background:var(--bc-accent);color:#fff;font-size:11px;
font-weight:600;display:flex;align-items:center;justify-content:center}
.bc-step-l{display:block;font-weight:600;color:#1c1c1e}
.bc-step-w{display:block;color:#8e8e93;margin-top:1px;font-size:12px}
.bc-btn{display:block;width:100%;padding:12px;background:var(--bc-accent);color:#fff;border:none;
border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;text-align:center;
text-decoration:none;-webkit-tap-highlight-color:transparent}
.bc-btn:active{opacity:.85}
.bc-ret{display:block;width:100%;padding:12px;margin-top:8px;background:#34c759;color:#fff;border:none;
border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;text-align:center;
text-decoration:none;-webkit-tap-highlight-color:transparent}
.bc-ret:active{opacity:.85}
.bc-cb{font-size:11px;color:#8e8e93;text-align:center;margin-top:6px}
/* SB-SDK-11 AC3: VISIBLE trust surfaces (not the collapsed <details>) \u2014 the
medical-market "No data collected" reassurance + the no-affiliation microcopy. */
.bc-privacy{font-size:12px;color:#8e8e93;line-height:1.4;margin-top:12px}
.bc-noaff{font-size:11px;color:#8e8e93;line-height:1.3;margin-top:6px;text-align:center}
.bc-det{margin-top:10px}
.bc-det summary{font-size:13px;color:var(--bc-accent);cursor:pointer;list-style:none;padding:2px 0}
.bc-det summary::before{content:'\\25B8 '}
.bc-det[open] summary::before{content:'\\25BE '}
.bc-det p{font-size:12px;color:#8e8e93;line-height:1.4;padding:6px 0 2px}
.bc-det a{color:var(--bc-accent)}
.bc-stuck{display:block;font-size:12px;color:var(--bc-accent);text-align:center;margin-top:10px;
text-decoration:none}
.bc-reload{display:block;width:100%;padding:11px;margin-top:8px;background:none;
border:1px solid var(--bc-accent);border-radius:12px;font-size:15px;font-weight:600;color:var(--bc-accent);
cursor:pointer;text-align:center;-webkit-tap-highlight-color:transparent}
.bc-reload:active{opacity:.7}
.bc-dis{display:block;width:100%;padding:8px;background:none;border:none;font-size:14px;
color:#8e8e93;cursor:pointer;text-align:center;margin-top:4px;
-webkit-tap-highlight-color:transparent}
/* SB-PRD-08: the explicit long opt-out (#bc-dont-show) is visually quieter than the
soft dismiss (#bc-dismiss) above it \u2014 smaller, less padding \u2014 so the soft dismiss
stays the default gesture and the long opt-out is a deliberate secondary choice.
NB keep this comment free of literal UI copy: the <style> block is part of the
banner innerHTML, so any English token here would leak into the localized DOM
(i18n.test.ts no-English-leak guard). */
.bc-dont{font-size:12px;padding:4px 12px;margin-top:0}
@media(prefers-color-scheme:dark){
#bc-s{background:#1c1c1e}
.bc-tt,.bc-step-l{color:#fff}
.bc-bd,.bc-step-w,.bc-cb,.bc-det p,.bc-privacy,.bc-noaff{color:#98989f}
.bc-dis{color:#98989f}
.bc-reload{color:#0a84ff;border-color:#0a84ff}
.bc-h{background:#48484a}
}
</style>
<div id="beacio-overlay">
<div id="bc-s" role="dialog" aria-label="${s(m)}">
<div class="bc-h"></div>
<div class="bc-hdr">
<div class="bc-ic">${h?`<img src="${s(h)}" alt="" aria-hidden="true">`:'<svg viewBox="0 0 24 24"><path d="M12 2L7 7l5 5-5 5 5 5V2zm0 6.83L10.83 7 12 5.83v2.34zm0 8.34L10.83 17 12 15.83v1.34zM17 7l-5 5 5 5-2.12 2.12L12 17l-2.88 2.12L7 17l5-5-5-5 2.12-2.12L12 7l2.88-2.12L17 7z"/></svg>'}</div>
<div class="bc-tt">${s(m)}</div>
</div>
<div class="bc-bd">${s($(S,t,_))}</div>
<ol class="bc-steps">${Pe}</ol>
${b==="not-installed"?`<button class="bc-btn" id="bc-install">${s(c)}</button>`:""}
<a class="bc-ret" id="bc-return" href="${s(u.returnLink)}">${s($(a.returnCta,t))}</a>
<p class="bc-cb">${s($(a.clipboardHint,t))}</p>
<button class="bc-reload" id="bc-reload">${s(a.reload)}</button>
<details class="bc-det"><summary>${s(a.howSummary)}</summary><p>${s(a.howBody)} <a href="${s(d)}" target="_blank" rel="noopener">${s(a.howLink)}</a>.</p></details>
<details class="bc-det"><summary>${s(a.privacySummary)}</summary><p>${s(ne)}</p></details>
${G?`<p class="bc-privacy" id="bc-privacy">${s(a.privacySummary)} \u2014 ${s(ne)}</p><p class="bc-noaff" id="bc-noaff">beacio is an independent Safari extension and is not affiliated with the device maker.</p>`:""}
<a class="bc-stuck" id="bc-stuck" href="${s(d)}" target="_blank" rel="noopener">${s(a.stillStuck)}</a>
<button class="bc-dis" id="bc-dismiss">${s(a.dismiss)}</button>
<button class="bc-dis bc-dont" id="bc-dont-show">${s(a.dontShowAgain)}</button>
</div>
</div>`,P();let re=e.forceShow===true,I=null,W=false;function D(){W=true,I!==null&&(clearTimeout(I),I=null),window.removeEventListener(ve,ie),window.removeEventListener(Ie,oe),document.removeEventListener("visibilitychange",se);}function w(){return j()?(D(),l.remove(),true):false}function ie(){w();}function oe(){w()||ae();}function ae(){if(W||I!==null)return;let f=0,ce=()=>{I=null,!W&&(w()||(f+=1,!(f>=Xe)&&(I=setTimeout(ce,qe))));};ce();}function se(){document.visibilityState==="visible"&&(w()||ae());}return re||(window.addEventListener(ve,ie),window.addEventListener(Ie,oe),document.addEventListener("visibilitychange",se)),requestAnimationFrame(()=>{l.querySelector("#bc-install")?.addEventListener("click",()=>{Ae(i,r,t);}),l.querySelector("#bc-reload")?.addEventListener("click",()=>{w()||window.location.reload();}),l.querySelector("#bc-dismiss")?.addEventListener("click",()=>{D(),l.remove(),C();}),l.querySelector("#bc-dont-show")?.addEventListener("click",()=>{D(),l.remove(),B(n);}),l.querySelector("#beacio-overlay")?.addEventListener("click",f=>{f.target.id==="beacio-overlay"&&(D(),l.remove(),C());});}),document.body.appendChild(l),re||w(),l}function Je(e){let{position:t="bottom",style:r={},apiKey:n,operatorName:o}=e,i=v({lang:e.lang,strings:e.strings}),a=i.barText,c=i.buttonText,b=Ne(e),m=e.accentColor??"#007AFF",S=Ce(e.brandLogoUrl),d=document.createElement("div");d.id="beacio-banner";let u=t==="top"?"top:0;border-bottom:1px solid #e5e7eb;":"bottom:0;border-top:1px solid #e5e7eb;",N=Object.entries(r).map(([h,_])=>`${h}:${_}`).join(";");return d.innerHTML=`
<div style="position:fixed;${u}left:0;right:0;z-index:2147483646;
background:#fff;padding:16px;
display:flex;align-items:center;gap:12px;font-family:system-ui,-apple-system,sans-serif;
box-shadow:0 ${t==="top"?"2px":"-2px"} 10px rgba(0,0,0,0.1);${N}">
${S?`<img src="${s(S)}" alt="" aria-hidden="true" width="24" height="24" style="object-fit:contain;flex-shrink:0">`:`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="${s(m)}"/>
<path d="M12 7a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V8a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" fill="white"/>
</svg>`}
<div style="flex:1">
<div style="font-size:14px;font-weight:600;color:#1f2937">${s(i.barTitle)}</div>
<div style="font-size:12px;color:#6b7280;margin-top:2px">${s(a)}</div>
</div>
<button id="beacio-banner-install"
style="background:${s(m)};color:white;padding:8px 16px;border-radius:8px;
border:none;font-size:14px;font-weight:500;white-space:nowrap;cursor:pointer">
${s(c)}</button>
<button id="beacio-banner-close"
style="background:none;border:none;color:#9ca3af;font-size:20px;
cursor:pointer;padding:4px;line-height:1"
aria-label="Close">&times;</button>
</div>`,d.querySelector("#beacio-banner-install")?.addEventListener("click",()=>{Ae(b,n,o);}),d.querySelector("#beacio-banner-close")?.addEventListener("click",()=>{d.remove(),C();}),document.body.appendChild(d),d}function Qe(){try{return localStorage.getItem(xe)==="1"}catch{return false}}function et(){try{localStorage.setItem(xe,"1");}catch{}}function tt(e){if(Qe())return null;et();let t=e.operatorName||document.title||window.location.hostname,r=v({lang:e.lang,strings:e.strings}),n=document.createElement("div");return n.id="beacio-banner",n.dataset.beacioState="active",n.innerHTML=`
<style>
#bc-toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483647;
max-width:420px;width:calc(100% - 32px);background:#34c759;color:#fff;border-radius:14px;
padding:14px 16px;display:flex;align-items:center;gap:12px;
font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;
box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bc-tu .3s ease-out}
@keyframes bc-tu{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
#bc-toast svg{width:22px;height:22px;flex-shrink:0;fill:#fff}
.bc-toast-tx{flex:1;font-size:15px;font-weight:600;line-height:1.3}
#bc-toast-x{background:none;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0 4px;
line-height:1;-webkit-tap-highlight-color:transparent}
</style>
<div id="bc-toast" role="status">
<svg viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
<span class="bc-toast-tx">${s($(r.readyToast,t))}</span>
<button id="bc-toast-x" aria-label="Dismiss">&times;</button>
</div>`,requestAnimationFrame(()=>{n.querySelector("#bc-toast-x")?.addEventListener("click",()=>n.remove());}),document.body.appendChild(n),n}function Oe(e={}){return e.state==="active"?tt(e):!e.forceShow&&Y()?null:e.mode==="banner"?Je(e):Ze(e)}function Re(){let e=document.getElementById("beacio-banner");e&&e.remove();}exports.SETUP_STEPS=void 0;var je,Ye,xe,ve,Ie,Xe,qe,H=y(()=>{Q();k();V();O();exports.SETUP_STEPS=exports.EN_STRINGS.steps,je={"installed-inactive":[2,4,5],denied:[3,4,5],"private-browsing":[]},Ye=z,xe="beacio_ready_shown",ve=p.READY,Ie=p.EXTENSION_READY,Xe=5,qe=300;});M();H();O();V();var Le=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),nt=Object.fromEntries(Object.keys(exports.EN_STRINGS.error.titles).map(e=>[e,{title:exports.EN_STRINGS.error.titles[e],body:exports.EN_STRINGS.error.messages[e]}])),rt=/\b(bluefy|web ble browser|webble browser)\b/gi;function it(e){let t=e.split(`
`,1)[0]??"";return t=t.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),t=t.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),t=t.replace(rt,""),t=t.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),t=t.replace(/[\s.,;:]+$/g,"").trim(),t}var R="beacio-error",ot=1500,De=null,te=0;function L(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function at(e){return typeof e=="object"&&e!==null&&"code"in e&&typeof e.code=="string"&&e.code in nt}function st(e,t){let r=t.toLowerCase();switch(e){case "NotFoundError":return "DEVICE_NOT_FOUND";case "NotAllowedError":case "SecurityError":return "PERMISSION_DENIED";case "NetworkError":return "DEVICE_DISCONNECTED";case "TimeoutError":return "TIMEOUT";case "InvalidStateError":return r.includes("disconnect")?"DEVICE_DISCONNECTED":"GATT_OPERATION_FAILED";}return r.includes("user cancelled")||r.includes("user canceled")?"USER_CANCELLED":r.includes("disconnect")?"DEVICE_DISCONNECTED":r.includes("timeout")?"TIMEOUT":"GATT_OPERATION_FAILED"}function ct(e,t,r){let n=i=>t.titles[i],o=i=>r?.messages?.[i]??t.messages[i];if(typeof e=="string"){let a=it(e)||t.generic.body;return {code:null,title:t.generic.title,body:a,isRetriable:false,signature:`str:${a}`}}if(at(e)){let i=e.code;return {code:i,title:n(i),body:o(i),isRetriable:typeof e.isRetriable=="boolean"?e.isRetriable:Le.has(i),signature:`code:${i}`}}if(typeof e=="object"&&e!==null){let i="name"in e&&typeof e.name=="string"?e.name:"",a=e instanceof Error?e.message:String(e.message??""),c=st(i,a);return {code:c,title:n(c),body:o(c),isRetriable:Le.has(c),signature:`dom:${c}`}}return {code:null,title:t.generic.title,body:t.generic.body,isRetriable:false,signature:"generic"}}function lt(e,t={}){if(typeof document>"u")return null;let{strings:r}=t,n=v({lang:t.lang}).error,o=ct(e,n,r),i=Date.now(),a=document.getElementById(R);if(a&&De===o.signature&&i-te<ot)return null;a&&a.remove(),De=o.signature,te=i;let c=t.operatorName,b=t.dismissText??r?.dismiss??n.dismiss,m=t.retryText??r?.retry??n.retry,S=o.isRetriable,d=Object.entries(t.style??{}).map(([_,G])=>`${_}:${G}`).join(";"),u=document.createElement("div");u.id=R,u.dataset.beacioErrorCode=o.code??"unknown",d&&(u.style.cssText=d);let N=c?`${c} \u2014 ${o.title}`:o.title;u.innerHTML=`
<style>
#${R}{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483646;
max-width:420px;width:calc(100% - 32px);background:#fff;color:#1c1c1e;border-radius:14px;
padding:16px 18px;display:flex;flex-direction:column;gap:10px;
font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;
box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bce-u .3s ease-out}
@keyframes bce-u{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
#${R} *{box-sizing:border-box;margin:0;padding:0}
.bce-row{display:flex;align-items:flex-start;gap:12px}
.bce-ic{width:28px;height:28px;border-radius:8px;background:#ff3b30;flex-shrink:0;display:flex;
align-items:center;justify-content:center}
.bce-ic svg{width:18px;height:18px;fill:#fff}
.bce-tx{flex:1;min-width:0}
.bce-tt{font-size:15px;font-weight:600;line-height:1.3}
.bce-bd{font-size:14px;line-height:1.4;color:#3a3a3c;margin-top:3px}
.bce-x{background:none;border:none;color:#8e8e93;font-size:20px;cursor:pointer;line-height:1;
padding:0 2px;align-self:flex-start}
/* SB-SDK-07: visually-hidden text label on the icon-only dismiss control. The
glyph stays the only visible mark; the label surfaces in the accessibility
tree + DOM text so the LOCALIZED dismiss copy is present (German when lang
selects it), not just an aria-label attribute. */
.bce-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
clip:rect(0,0,0,0);white-space:nowrap;border:0}
.bce-act{display:flex;gap:8px;justify-content:flex-end}
.bce-retry{padding:9px 16px;background:#007aff;color:#fff;border:none;border-radius:10px;
font-size:15px;font-weight:600;cursor:pointer}
.bce-retry:active{opacity:.85}
@media(prefers-color-scheme:dark){
#${R}{background:#1c1c1e;color:#fff}
.bce-bd{color:#aeaeb2}
}
</style>
<div class="bce-row">
<div class="bce-ic"><svg viewBox="0 0 24 24"><path d="M12 2 1 21h22L12 2zm0 5 7.5 13h-15L12 7zm-1 4v4h2v-4h-2zm0 6v2h2v-2h-2z"/></svg></div>
<div class="bce-tx">
<p class="bce-tt">${L(N)}</p>
<p class="bce-bd">${L(o.body)}</p>
</div>
<button class="bce-x" aria-label="${L(b)}">&times;<span class="bce-sr">${L(b)}</span></button>
</div>
${S?`<div class="bce-act"><button class="bce-retry" type="button">${L(m)}</button></div>`:""}`;function h(){u.remove(),te=0;}return u.querySelector(".bce-x")?.addEventListener("click",h),S&&u.querySelector(".bce-retry")?.addEventListener("click",()=>{h(),t.onRetry?.();}),document.body.appendChild(u),u}V();var ke="https://api.beacio.com";function dt(){return {origin:location.hostname,ua:navigator.userAgent}}function E(e,t,r){if(e)try{fetch(`${ke}/v1/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({events:[{event:t,data:dt(),timestamp:Date.now()}]}),keepalive:!0}).catch(()=>{});}catch{}}async function ut(e){try{let t=await fetch(`${ke}/v1/config`,{headers:{Authorization:`Bearer ${e}`}});return t.ok?await t.json():null}catch{return null}}k();O();Q();function pt(e){typeof window>"u"||window.dispatchEvent(new CustomEvent(p.STATE_CHANGE,{detail:{state:e}}));}async function x(e,t,r){if(e.banner===false)return;let{showInstallBanner:n}=await Promise.resolve().then(()=>(H(),ee)),o=typeof e.banner=="object"?e.banner:{},i={...o,apiKey:e.key??"",operatorName:e.operatorName,lang:o.lang??e.lang,state:r??t};n(i);}async function F(){if(typeof navigator>"u")return false;let e=navigator.bluetooth;if(!e||typeof e.getAvailability!="function")return false;try{return await e.getAvailability()===!1}catch{return false}}function Be(){if(typeof window>"u")return false;try{let e=window.localStorage;if(!e)return !1;let t="__beacio_pb_probe__";return e.setItem(t,"1"),e.removeItem(t),!1}catch{return true}}async function Dt(e){let{getExtensionInstallState:t,isIOSSafari:r}=await Promise.resolve().then(()=>(M(),J));if(!r())return;let n=await t();if(pt(n),n==="active"){if(await F()){E(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(p.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await x(e,n,"denied");return}E(e.key??"","extension_active"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(p.READY)),e.onReady?.(),await x(e,n);return}if(Be()){E(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(p.NOT_INSTALLED)),e.onNotInstalled?.(),await x(e,n,"private-browsing");return}if(await F()){E(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(p.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await x(e,n,"denied");return}if(n==="installed-inactive"){E(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(p.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await x(e,n);return}E(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(p.NOT_INSTALLED)),e.onNotInstalled?.(),await x(e,n),e.banner!==false&&E(e.key??"","install_prompted");}function bt(e){let t=typeof window<"u"?window.location.href:"";return `${z}?operatorName=${encodeURIComponent(e.operatorName)}&return=${encodeURIComponent(t)}`}async function kt(e){let{getExtensionInstallState:t,isIOSSafari:r}=await Promise.resolve().then(()=>(M(),J));if(!r())return {kind:"unsupported"};let n=X(),o=bt(e),i=await t();if(i==="active")return await F()?{kind:"denied",setupUrl:o,returnLink:n}:{kind:"ready"};if(Be())return {kind:"private-browsing",returnLink:n};if(await F())return {kind:"denied",setupUrl:o,returnLink:n};if(i==="installed-inactive")return {kind:"installed-inactive",setupUrl:o,returnLink:n};let{buildOnboardingUrl:a}=await Promise.resolve().then(()=>(H(),ee));return {kind:"not-installed",installUrl:a(exports.APP_STORE_URL,{apiKey:e.apiKey,operatorName:e.operatorName}),returnLink:n}}
exports.dismiss=B;exports.dismissShort=C;exports.getExtensionInstallState=Z;exports.getInstallState=T;exports.getReturnContext=U;exports.initBeacio=Dt;exports.isDismissed=Y;exports.isExtensionActive=j;exports.isExtensionInstalled=he;exports.isIOSSafari=Ee;exports.observeInstallState=Fe;exports.presentError=lt;exports.removeInstallBanner=Re;exports.reportEvent=E;exports.resolveOnboardingState=kt;exports.resolveStrings=v;exports.saveReturnContext=P;exports.showInstallBanner=Oe;exports.validateApiKey=ut;//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

Sorry, the diff of this file is too big to display

export{b as getExtensionInstallState,c as isExtensionInstalled,a as isIOSSafari}from'../chunk-QLQHTSFL.mjs';import {a,c}from'../chunk-5NAVIZD7.mjs';export{b as DE_STRINGS,a as EN_STRINGS,d as SETUP_STEPS,g as removeInstallBanner,c as resolveStrings,f as showInstallBanner}from'../chunk-5NAVIZD7.mjs';import {j,a as a$2}from'../chunk-TZAX4UTD.mjs';export{a as APP_STORE_URL,e as DEFAULT_DISMISS_DAYS,f as SHORT_DISMISS_DAYS,h as dismiss,i as dismissShort,b as getInstallState,l as getReturnContext,g as isDismissed,c as isExtensionActive,d as observeInstallState,k as saveReturnContext}from'../chunk-TZAX4UTD.mjs';import {a as a$3}from'../chunk-L7SIDO2A.mjs';import {a as a$1}from'../chunk-3BDZNBBD.mjs';var N=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),X=Object.fromEntries(Object.keys(a.error.titles).map(e=>[e,{title:a.error.titles[e],body:a.error.messages[e]}])),J=/\b(bluefy|web ble browser|webble browser)\b/gi;function Z(e){let t=e.split(`
`,1)[0]??"";return t=t.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),t=t.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),t=t.replace(J,""),t=t.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),t=t.replace(/[\s.,;:]+$/g,"").trim(),t}var E="beacio-error",Q=1500,C=null,y=0;function u(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function ee(e){return typeof e=="object"&&e!==null&&"code"in e&&typeof e.code=="string"&&e.code in X}function te(e,t){let i=t.toLowerCase();switch(e){case "NotFoundError":return "DEVICE_NOT_FOUND";case "NotAllowedError":case "SecurityError":return "PERMISSION_DENIED";case "NetworkError":return "DEVICE_DISCONNECTED";case "TimeoutError":return "TIMEOUT";case "InvalidStateError":return i.includes("disconnect")?"DEVICE_DISCONNECTED":"GATT_OPERATION_FAILED";}return i.includes("user cancelled")||i.includes("user canceled")?"USER_CANCELLED":i.includes("disconnect")?"DEVICE_DISCONNECTED":i.includes("timeout")?"TIMEOUT":"GATT_OPERATION_FAILED"}function ne(e,t,i){let n=r=>t.titles[r],o=r=>i?.messages?.[r]??t.messages[r];if(typeof e=="string"){let s=Z(e)||t.generic.body;return {code:null,title:t.generic.title,body:s,isRetriable:false,signature:`str:${s}`}}if(ee(e)){let r=e.code;return {code:r,title:n(r),body:o(r),isRetriable:typeof e.isRetriable=="boolean"?e.isRetriable:N.has(r),signature:`code:${r}`}}if(typeof e=="object"&&e!==null){let r="name"in e&&typeof e.name=="string"?e.name:"",s=e instanceof Error?e.message:String(e.message??""),c=te(r,s);return {code:c,title:n(c),body:o(c),isRetriable:N.has(c),signature:`dom:${c}`}}return {code:null,title:t.generic.title,body:t.generic.body,isRetriable:false,signature:"generic"}}function re(e,t={}){if(typeof document>"u")return null;let{strings:i}=t,n=c({lang:t.lang}).error,o=ne(e,n,i),r=Date.now(),s=document.getElementById(E);if(s&&C===o.signature&&r-y<Q)return null;s&&s.remove(),C=o.signature,y=r;let c$1=t.operatorName,I=t.dismissText??i?.dismiss??n.dismiss,h=t.retryText??i?.retry??n.retry,S=o.isRetriable,w=Object.entries(t.style??{}).map(([R,D])=>`${R}:${D}`).join(";"),a=document.createElement("div");a.id=E,a.dataset.beacioErrorCode=o.code??"unknown",w&&(a.style.cssText=w);let A=c$1?`${c$1} \u2014 ${o.title}`:o.title;a.innerHTML=`
<style>
#${E}{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483646;
max-width:420px;width:calc(100% - 32px);background:#fff;color:#1c1c1e;border-radius:14px;
padding:16px 18px;display:flex;flex-direction:column;gap:10px;
font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;
box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bce-u .3s ease-out}
@keyframes bce-u{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
#${E} *{box-sizing:border-box;margin:0;padding:0}
.bce-row{display:flex;align-items:flex-start;gap:12px}
.bce-ic{width:28px;height:28px;border-radius:8px;background:#ff3b30;flex-shrink:0;display:flex;
align-items:center;justify-content:center}
.bce-ic svg{width:18px;height:18px;fill:#fff}
.bce-tx{flex:1;min-width:0}
.bce-tt{font-size:15px;font-weight:600;line-height:1.3}
.bce-bd{font-size:14px;line-height:1.4;color:#3a3a3c;margin-top:3px}
.bce-x{background:none;border:none;color:#8e8e93;font-size:20px;cursor:pointer;line-height:1;
padding:0 2px;align-self:flex-start}
/* SB-SDK-07: visually-hidden text label on the icon-only dismiss control. The
glyph stays the only visible mark; the label surfaces in the accessibility
tree + DOM text so the LOCALIZED dismiss copy is present (German when lang
selects it), not just an aria-label attribute. */
.bce-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
clip:rect(0,0,0,0);white-space:nowrap;border:0}
.bce-act{display:flex;gap:8px;justify-content:flex-end}
.bce-retry{padding:9px 16px;background:#007aff;color:#fff;border:none;border-radius:10px;
font-size:15px;font-weight:600;cursor:pointer}
.bce-retry:active{opacity:.85}
@media(prefers-color-scheme:dark){
#${E}{background:#1c1c1e;color:#fff}
.bce-bd{color:#aeaeb2}
}
</style>
<div class="bce-row">
<div class="bce-ic"><svg viewBox="0 0 24 24"><path d="M12 2 1 21h22L12 2zm0 5 7.5 13h-15L12 7zm-1 4v4h2v-4h-2zm0 6v2h2v-2h-2z"/></svg></div>
<div class="bce-tx">
<p class="bce-tt">${u(A)}</p>
<p class="bce-bd">${u(o.body)}</p>
</div>
<button class="bce-x" aria-label="${u(I)}">&times;<span class="bce-sr">${u(I)}</span></button>
</div>
${S?`<div class="bce-act"><button class="bce-retry" type="button">${u(h)}</button></div>`:""}`;function T(){a.remove(),y=0;}return a.querySelector(".bce-x")?.addEventListener("click",T),S&&a.querySelector(".bce-retry")?.addEventListener("click",()=>{T(),t.onRetry?.();}),document.body.appendChild(a),a}var O="https://api.beacio.com";function ie(){return {origin:location.hostname,ua:navigator.userAgent}}function l(e,t,i){if(e)try{fetch(`${O}/v1/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({events:[{event:t,data:ie(),timestamp:Date.now()}]}),keepalive:!0}).catch(()=>{});}catch{}}async function oe(e){try{let t=await fetch(`${O}/v1/config`,{headers:{Authorization:`Bearer ${e}`}});return t.ok?await t.json():null}catch{return null}}function se(e){typeof window>"u"||window.dispatchEvent(new CustomEvent(a$1.STATE_CHANGE,{detail:{state:e}}));}async function g(e,t,i){if(e.banner===false)return;let{showInstallBanner:n}=await import('../banner-DUJ4XQID.mjs'),o=typeof e.banner=="object"?e.banner:{},r={...o,apiKey:e.key??"",operatorName:e.operatorName,lang:o.lang??e.lang,state:i??t};n(r);}async function p(){if(typeof navigator>"u")return false;let e=navigator.bluetooth;if(!e||typeof e.getAvailability!="function")return false;try{return await e.getAvailability()===!1}catch{return false}}function _(){if(typeof window>"u")return false;try{let e=window.localStorage;if(!e)return !1;let t="__beacio_pb_probe__";return e.setItem(t,"1"),e.removeItem(t),!1}catch{return true}}async function pe(e){let{getExtensionInstallState:t,isIOSSafari:i}=await import('../detect-Q3O2ZCCH.mjs');if(!i())return;let n=await t();if(se(n),n==="active"){if(await p()){l(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(a$1.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await g(e,n,"denied");return}l(e.key??"","extension_active"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(a$1.READY)),e.onReady?.(),await g(e,n);return}if(_()){l(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(a$1.NOT_INSTALLED)),e.onNotInstalled?.(),await g(e,n,"private-browsing");return}if(await p()){l(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(a$1.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await g(e,n,"denied");return}if(n==="installed-inactive"){l(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(a$1.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await g(e,n);return}l(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(a$1.NOT_INSTALLED)),e.onNotInstalled?.(),await g(e,n),e.banner!==false&&l(e.key??"","install_prompted");}function ae(e){let t=typeof window<"u"?window.location.href:"";return `${a$3}?operatorName=${encodeURIComponent(e.operatorName)}&return=${encodeURIComponent(t)}`}async function be(e){let{getExtensionInstallState:t,isIOSSafari:i}=await import('../detect-Q3O2ZCCH.mjs');if(!i())return {kind:"unsupported"};let n=j(),o=ae(e),r=await t();if(r==="active")return await p()?{kind:"denied",setupUrl:o,returnLink:n}:{kind:"ready"};if(_())return {kind:"private-browsing",returnLink:n};if(await p())return {kind:"denied",setupUrl:o,returnLink:n};if(r==="installed-inactive")return {kind:"installed-inactive",setupUrl:o,returnLink:n};let{buildOnboardingUrl:s}=await import('../banner-DUJ4XQID.mjs');return {kind:"not-installed",installUrl:s(a$2,{apiKey:e.apiKey,operatorName:e.operatorName}),returnLink:n}}
export{pe as initBeacio,re as presentError,l as reportEvent,be as resolveOnboardingState,oe as validateApiKey};//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map
{"version":3,"sources":["../../src/detect/error-presenter.ts","../../src/detect/api.ts","../../src/detect/index.ts"],"names":["RETRIABLE_CODES","COPY","EN_STRINGS","code","COMPETITOR_TOKENS","sanitizeMessage","raw","line","CARD_ID","DEDUPE_WINDOW_MS","lastSignature","lastShownAt","esc","s","d","isCodedError","error","codeFromDomName","name","message","lower","resolve","input","pack","strings","titleFor","bodyFor","body","presentError","errorOrMessage","options","resolveStrings","resolved","now","existing","operatorName","dismissLabel","retryLabel","showRetry","customStyle","k","v","card","heading","dismiss","API_BASE","buildEventData","reportEvent","apiKey","event","_data","validateApiKey","res","dispatchInstallState","state","BEACIO_EVENTS","maybeShowBanner","bannerStateOverride","showInstallBanner","bannerConfig","bannerOpts","isOriginDenied","bt","isPrivateBrowsingBestEffort","storage","probeKey","initBeacio","getExtensionInstallState","isIOSSafari","installState","buildSetupDeepLink","config","returnUrl","SETUP_URL","resolveOnboardingState","returnLink","buildReturnLink","setupUrl","buildOnboardingUrl","APP_STORE_URL"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @beacio/detect#presentError — SB-SDK-05\n *\n * A drop-in, framework-free branded ERROR presenter. The polished branded surface\n * already exists for the INSTALL prompt (banner.ts); this is its sibling for the\n * FAILURE path. S&B (and any vanilla-JS site) uses raw `navigator.bluetooth`\n * across hundreds of call sites and will not rewrite them — so the worst surface,\n * a blocking, stack-leaking `window.alert()`, is converted into a non-blocking,\n * dismissible, recovery-oriented card with a ~1-line edit:\n *\n * catch (error) { beacioDetect.presentError(error); }\n *\n * Design constraints (mirroring banner.ts):\n * - @beacio/core is an OPTIONAL peer (a standalone `npm i @beacio/detect` has no\n * core), so this file MUST NOT import @beacio/core — not even the BeacioError\n * class. Errors are consumed STRUCTURALLY: anything carrying a `.code` /\n * `.message` / `.suggestion` / `.isRetriable` is understood, and the\n * BeacioErrorCode → copy map + retriable set are kept LOCAL (pinned to core's\n * public contract by the unit test, not by a runtime import).\n * - The card NEVER leaks a stack trace, internal codes, WebKit jargon, or a\n * competitor name. The friendly body comes from the per-code copy table, NOT\n * the raw error string.\n * - Identical errors fired in a short window are coalesced to ONE card (defends\n * against the backgrounded alert-storm).\n * - All user-visible strings are overridable via a copy/locale object\n * (PresentErrorOptions.strings) — the i18n seam SB-SDK-07 converges on; the\n * `lang` field selects a built-in pack (German shipped), and `strings`\n * deep-merges over it. English defaults apply when neither is supplied (no\n * regression). The per-code copy + dismiss/retry come from the SAME shared\n * i18n module the install banner uses (./i18n), so a localized card and a\n * localized banner never drift.\n */\n\n// SB-SDK-07: the shared localized-string seam (same module the install banner\n// consumes). i18n.ts imports NOTHING from @beacio/core — it re-declares the\n// BeacioErrorCode union locally — so this stays within the optional-peer rule\n// (no-toplevel-core-import.test.ts) just like this file's own local tables.\nimport { EN_STRINGS, type LocaleStrings, resolveStrings } from './i18n';\n\n/**\n * The stable BeacioErrorCode contract (core/src/errors.ts). Kept local — not\n * imported — so detect has no runtime @beacio/core dependency. The presenter unit\n * test is the seam-crossing control that this list still matches core's source.\n */\nexport type BeacioErrorCode =\n | 'INVALID_PARAMETER'\n | 'BLUETOOTH_UNAVAILABLE'\n | 'EXTENSION_NOT_INSTALLED'\n | 'PERMISSION_DENIED'\n | 'DEVICE_NOT_FOUND'\n | 'DEVICE_DISCONNECTED'\n | 'CONNECTION_TIMEOUT'\n | 'SERVICE_NOT_FOUND'\n | 'CHARACTERISTIC_NOT_FOUND'\n | 'CHARACTERISTIC_NOT_READABLE'\n | 'CHARACTERISTIC_NOT_WRITABLE'\n | 'CHARACTERISTIC_NOT_NOTIFIABLE'\n | 'GATT_OPERATION_FAILED'\n | 'SCAN_ALREADY_IN_PROGRESS'\n | 'CONNECTION_LIMIT_REACHED'\n | 'USER_CANCELLED'\n | 'TIMEOUT'\n | 'WRITE_INCOMPLETE';\n\n/**\n * Codes that are safe to retry — mirrors RETRIABLE_CODES in core/src/errors.ts.\n * A retriable card shows a retry affordance; a non-retriable one does not.\n */\nconst RETRIABLE_CODES: ReadonlySet<BeacioErrorCode> = new Set<BeacioErrorCode>([\n 'DEVICE_DISCONNECTED',\n 'CONNECTION_TIMEOUT',\n 'GATT_OPERATION_FAILED',\n 'TIMEOUT',\n 'SCAN_ALREADY_IN_PROGRESS',\n 'WRITE_INCOMPLETE',\n]);\n\n/**\n * Per-code friendly headline + body. Plain-English, recovery-oriented, no internal\n * codes, no jargon, no competitor names. This is the body shown to the user — the\n * raw error string (which may carry a stack or a competitor name) is NEVER shown.\n *\n * SB-SDK-07: this is now a VIEW over the English pack (EN_STRINGS.error) so the\n * presenter's English source-of-truth and the shared i18n pack are a SINGLE\n * table — they cannot drift. Localized rendering reads the resolved pack (which\n * may be German); COPY is retained as the membership anchor isCodedError() uses\n * (`code in COPY`) and the English-completeness table error-presenter-core-parity\n * pins to core's BeacioErrorCode set.\n */\nconst COPY: Record<BeacioErrorCode, { title: string; body: string }> = Object.fromEntries(\n (Object.keys(EN_STRINGS.error.titles) as BeacioErrorCode[]).map((code) => [\n code,\n { title: EN_STRINGS.error.titles[code], body: EN_STRINGS.error.messages[code] },\n ])\n) as Record<BeacioErrorCode, { title: string; body: string }>;\n\n/** Competitor/product names that must never surface (mirrors errors.ts COMPETITOR_TOKENS). */\nconst COMPETITOR_TOKENS = /\\b(bluefy|web ble browser|webble browser)\\b/gi;\n\n/**\n * AC1/AC6: the bare-string path (the S&B generateErrorMsg(errMsg) chokepoint) is\n * the ONE input whose body comes from the caller rather than the branded copy\n * table — and S&B builds those strings from the native error (appending\n * `error.stack`, referencing the competitor). Reduce such a string to a single,\n * complete-sentence line that is safe to render: drop everything from the first\n * newline (where stack frames begin), strip native/internal URLs and any residual\n * \"at file:line:col\" fragment, and redact competitor names. Mirrors core's\n * sanitizeNativeMessage (kept local — detect must not import @beacio/core). Returns\n * '' when nothing meaningful remains so the caller falls back to branded copy.\n */\nfunction sanitizeMessage(raw: string): string {\n let line = raw.split('\\n', 1)[0] ?? '';\n line = line.replace(/\\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\\/\\/\\S+/gi, '');\n line = line.replace(/\\bat\\s+\\S+:\\d+:\\d+\\)?/gi, '');\n line = line.replace(COMPETITOR_TOKENS, '');\n line = line.replace(/\\s{2,}/g, ' ').replace(/\\s+([.,;:])/g, '$1').trim();\n line = line.replace(/[\\s.,;:]+$/g, '').trim();\n return line;\n}\n\n/**\n * Caller-supplied copy/locale overrides — the i18n seam (SB-SDK-07). Every field\n * is optional; an omitted field falls back to the English default, so an existing\n * caller that passes nothing is byte-identical to today.\n */\nexport interface PresentErrorStrings {\n /** Dismiss button label (English default: \"Dismiss\"). */\n dismiss?: string;\n /** Retry affordance label for retriable errors (English default: \"Try again\"). */\n retry?: string;\n /** Per-code body override. A code present here replaces the English body. */\n messages?: Partial<Record<BeacioErrorCode, string>>;\n}\n\n/**\n * Options for {@link presentError}. Parity with BannerOptions where it overlaps\n * (operatorName, style), plus the retry affordance + the copy/locale seam.\n */\nexport interface PresentErrorOptions {\n /** Operator/app name shown in the card (e.g. \"STORZ & BICKEL\"). */\n operatorName?: string;\n /**\n * SB-SDK-07: BCP-47 UI language (e.g. 'de'). Selects the built-in pack for the\n * per-code title/body + dismiss/retry labels; omitted ⇒ derived from\n * navigator.language, else English. A per-call `strings` (and the explicit\n * dismissText/retryText) still override the selected pack. Always wins over\n * navigator.language.\n */\n lang?: string;\n /** Retry button label override (takes precedence over strings.retry). */\n retryText?: string;\n /** Dismiss button label override (takes precedence over strings.dismiss). */\n dismissText?: string;\n /**\n * Invoked when the user taps the retry affordance (retriable errors only), so a\n * caller can re-run its connect()/operation. The card is dismissed first.\n */\n onRetry?: () => void;\n /** Extra inline styles merged onto the card container. */\n style?: Record<string, string>;\n /** Copy/locale overrides for every user-visible string (SB-SDK-07 seam). */\n strings?: PresentErrorStrings;\n}\n\nconst CARD_ID = 'beacio-error';\n/** Coalesce window for identical errors (ms) — defends the alert-storm. */\nconst DEDUPE_WINDOW_MS = 1500;\n\n/** Last rendered signature + timestamp, for identical-error debounce. */\nlet lastSignature: string | null = null;\nlet lastShownAt = 0;\n\n/** HTML-escape (same idiom as banner.ts esc()). */\nfunction esc(s: string): string {\n const d = document.createElement('div');\n d.textContent = s;\n return d.innerHTML;\n}\n\n/** Does an unknown value look like a BeacioError (structural, no class import)? */\nfunction isCodedError<T>(error: T): error is T & { code: BeacioErrorCode; message?: string; suggestion?: string; isRetriable?: boolean } {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof (error as { code: string }).code === 'string' &&\n (error as { code: string }).code in COPY\n );\n}\n\n/** Map a raw DOMException name (no BeacioError) to a BeacioErrorCode. */\nfunction codeFromDomName(name: string, message: string): BeacioErrorCode {\n const lower = message.toLowerCase();\n switch (name) {\n case 'NotFoundError':\n return 'DEVICE_NOT_FOUND';\n case 'NotAllowedError':\n case 'SecurityError':\n return 'PERMISSION_DENIED';\n case 'NetworkError':\n return 'DEVICE_DISCONNECTED';\n case 'TimeoutError':\n return 'TIMEOUT';\n case 'InvalidStateError':\n return lower.includes('disconnect') ? 'DEVICE_DISCONNECTED' : 'GATT_OPERATION_FAILED';\n default:\n break;\n }\n if (lower.includes('user cancelled') || lower.includes('user canceled')) return 'USER_CANCELLED';\n if (lower.includes('disconnect')) return 'DEVICE_DISCONNECTED';\n if (lower.includes('timeout')) return 'TIMEOUT';\n return 'GATT_OPERATION_FAILED';\n}\n\ninterface Resolved {\n /** Stable code when known (drives retriable + dedupe signature), else null. */\n code: BeacioErrorCode | null;\n title: string;\n body: string;\n isRetriable: boolean;\n /** Dedupe signature: identical inputs coalesce to one card. */\n signature: string;\n}\n\n/**\n * Normalise any input — a BeacioError-shaped object, a raw DOMException/Error, or\n * a bare string — into branded, stack-free, competitor-free card content. The raw\n * error string is shown ONLY for a bare-string input (the S&B generateErrorMsg\n * path, where the caller passes its own already-friendly message); structured\n * errors always use the per-code copy table so no native jargon/stack leaks.\n */\nfunction resolve(\n input: unknown,\n pack: LocaleStrings['error'],\n strings: PresentErrorStrings | undefined\n): Resolved {\n // SB-SDK-07: per-code title/body come from the RESOLVED language pack; a\n // per-call `strings.messages[code]` override still wins (byte-identical\n // back-compat for callers that pass their own copy). The local COPY table\n // stays as the English source (it IS EN_STRINGS.error), pinned to core's code\n // set by error-presenter-core-parity.test.ts.\n const titleFor = (code: BeacioErrorCode): string => pack.titles[code];\n const bodyFor = (code: BeacioErrorCode): string => strings?.messages?.[code] ?? pack.messages[code];\n\n // Bare string: the caller's own message IS the body (generateErrorMsg path) —\n // but sanitise it first (AC1/AC6) so a string carrying a stack, a native URL, or\n // a competitor name never renders verbatim. When nothing meaningful survives,\n // fall back to the branded generic body so the card is never blank.\n if (typeof input === 'string') {\n const clean = sanitizeMessage(input);\n const body = clean || pack.generic.body;\n return { code: null, title: pack.generic.title, body, isRetriable: false, signature: `str:${body}` };\n }\n\n // BeacioError-shaped (structural): trust .code for copy + retriable.\n if (isCodedError(input)) {\n const code = input.code;\n return {\n code,\n title: titleFor(code),\n body: bodyFor(code),\n isRetriable: typeof input.isRetriable === 'boolean' ? input.isRetriable : RETRIABLE_CODES.has(code),\n signature: `code:${code}`,\n };\n }\n\n // Raw DOMException / Error: classify by name+message, then use branded copy —\n // NEVER the raw .message (it may carry a stack or a competitor name).\n if (typeof input === 'object' && input !== null) {\n const name =\n 'name' in input && typeof (input as { name: string }).name === 'string' ? (input as { name: string }).name : '';\n const message = input instanceof Error ? input.message : String((input as { message?: string }).message ?? '');\n const code = codeFromDomName(name, message);\n return { code, title: titleFor(code), body: bodyFor(code), isRetriable: RETRIABLE_CODES.has(code), signature: `dom:${code}` };\n }\n\n return { code: null, title: pack.generic.title, body: pack.generic.body, isRetriable: false, signature: 'generic' };\n}\n\n/**\n * Present a branded, non-blocking, dismissible error card. Replaces a blocking\n * `window.alert(error.toString() + error.stack)` with a recovery-oriented surface.\n *\n * @param errorOrMessage A BeacioError, a raw DOMException/Error, or a string.\n * @param options Operator name, copy/locale overrides, and an onRetry handler.\n * @returns The card element, or null when the error is coalesced (a card for an\n * identical error is already on screen) so callers can no-op safely.\n */\nexport function presentError(errorOrMessage: unknown, options: PresentErrorOptions = {}): HTMLElement | null {\n // SSR / non-DOM guard (mirrors banner.ts dispatch guards).\n if (typeof document === 'undefined') return null;\n\n const { strings } = options;\n // SB-SDK-07: resolve the localized pack ONCE (explicit lang > navigator.language\n // > English); per-code copy + dismiss/retry derive from it, with `strings` and\n // the explicit dismissText/retryText overriding on top.\n const pack = resolveStrings({ lang: options.lang }).error;\n const resolved = resolve(errorOrMessage, pack, strings);\n\n // AC2: coalesce identical errors fired within a short window into ONE card —\n // suppress the duplicate (and never fall back to alert). An identical card still\n // on screen also suppresses, so a burst never stacks.\n const now = Date.now();\n const existing = document.getElementById(CARD_ID);\n if (existing && lastSignature === resolved.signature && now - lastShownAt < DEDUPE_WINDOW_MS) {\n return null;\n }\n // A new error replaces any prior card (single card surface at a time).\n if (existing) existing.remove();\n lastSignature = resolved.signature;\n lastShownAt = now;\n\n const operatorName = options.operatorName;\n // Precedence: explicit dismissText/retryText > per-call `strings` > the\n // resolved language pack (English when no lang/navigator.language match).\n const dismissLabel = options.dismissText ?? strings?.dismiss ?? pack.dismiss;\n const retryLabel = options.retryText ?? strings?.retry ?? pack.retry;\n const showRetry = resolved.isRetriable;\n\n const customStyle = Object.entries(options.style ?? {})\n .map(([k, v]) => `${k}:${v}`)\n .join(';');\n\n const card = document.createElement('div');\n card.id = CARD_ID;\n card.dataset.beacioErrorCode = resolved.code ?? 'unknown';\n // AC1: merge caller-supplied style overrides onto the card container (parity with\n // BannerOptions, whose bar banner applies options.style). Inline style wins over\n // the stylesheet default so an operator can theme the card without forking.\n if (customStyle) card.style.cssText = customStyle;\n\n // Title carries the operator name when supplied (parity with the banner), so the\n // card is branded to the host app rather than anonymous.\n const heading = operatorName ? `${operatorName} — ${resolved.title}` : resolved.title;\n\n card.innerHTML = `\n<style>\n#${CARD_ID}{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483646;\n max-width:420px;width:calc(100% - 32px);background:#fff;color:#1c1c1e;border-radius:14px;\n padding:16px 18px;display:flex;flex-direction:column;gap:10px;\n font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;\n box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bce-u .3s ease-out}\n@keyframes bce-u{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}\n#${CARD_ID} *{box-sizing:border-box;margin:0;padding:0}\n.bce-row{display:flex;align-items:flex-start;gap:12px}\n.bce-ic{width:28px;height:28px;border-radius:8px;background:#ff3b30;flex-shrink:0;display:flex;\n align-items:center;justify-content:center}\n.bce-ic svg{width:18px;height:18px;fill:#fff}\n.bce-tx{flex:1;min-width:0}\n.bce-tt{font-size:15px;font-weight:600;line-height:1.3}\n.bce-bd{font-size:14px;line-height:1.4;color:#3a3a3c;margin-top:3px}\n.bce-x{background:none;border:none;color:#8e8e93;font-size:20px;cursor:pointer;line-height:1;\n padding:0 2px;align-self:flex-start}\n/* SB-SDK-07: visually-hidden text label on the icon-only dismiss control. The\n glyph stays the only visible mark; the label surfaces in the accessibility\n tree + DOM text so the LOCALIZED dismiss copy is present (German when lang\n selects it), not just an aria-label attribute. */\n.bce-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;\n clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.bce-act{display:flex;gap:8px;justify-content:flex-end}\n.bce-retry{padding:9px 16px;background:#007aff;color:#fff;border:none;border-radius:10px;\n font-size:15px;font-weight:600;cursor:pointer}\n.bce-retry:active{opacity:.85}\n@media(prefers-color-scheme:dark){\n #${CARD_ID}{background:#1c1c1e;color:#fff}\n .bce-bd{color:#aeaeb2}\n}\n</style>\n<div class=\"bce-row\">\n <div class=\"bce-ic\"><svg viewBox=\"0 0 24 24\"><path d=\"M12 2 1 21h22L12 2zm0 5 7.5 13h-15L12 7zm-1 4v4h2v-4h-2zm0 6v2h2v-2h-2z\"/></svg></div>\n <div class=\"bce-tx\">\n <p class=\"bce-tt\">${esc(heading)}</p>\n <p class=\"bce-bd\">${esc(resolved.body)}</p>\n </div>\n <button class=\"bce-x\" aria-label=\"${esc(dismissLabel)}\">&times;<span class=\"bce-sr\">${esc(dismissLabel)}</span></button>\n</div>\n${showRetry ? `<div class=\"bce-act\"><button class=\"bce-retry\" type=\"button\">${esc(retryLabel)}</button></div>` : ''}`;\n\n function dismiss(): void {\n card.remove();\n // Allow an immediate, DIFFERENT error to show; only identical ones within the\n // window are coalesced, and dismissing clears the on-screen-card suppression.\n lastShownAt = 0;\n }\n\n // Wire listeners synchronously (jsdom click in the test fires in the same tick).\n card.querySelector<HTMLElement>('.bce-x')?.addEventListener('click', dismiss);\n // The dismiss aria-label control doubles as the required dismissible button.\n if (showRetry) {\n card.querySelector<HTMLElement>('.bce-retry')?.addEventListener('click', () => {\n dismiss();\n options.onRetry?.();\n });\n }\n\n document.body.appendChild(card);\n return card;\n}\n","/**\n * Analytics event reporter and API key validator.\n * Fire-and-forget — analytics must never throw or block.\n */\n\n// SB-SDK-16: api.beacio.com is the DEPLOYED ingest Worker — the api Worker at\n// cloudflare/workers/api (the `ioswebble-api` Worker) serves it via `custom_domain` and owns the keyed\n// /v1/events + /v1/config handlers. It is NOT a phantom host, and it is NOT\n// beacon.beacio.com (a separate Worker with no such routes — the WF2 triage\n// evidence got this wrong; repointing here would silently 404/401). The host↔\n// Worker coupling is locked by tests/api-host-reconciliation.test.ts so it can't\n// drift. No behaviour rides on this while unkeyed: reportEvent early-returns when\n// !apiKey (below), so this only matters on the keyed-tenant launch path.\nconst API_BASE = 'https://api.beacio.com';\n\n/**\n * SB-SDK-15: the EXHAUSTIVE allow-list of fields that may ever leave the browser.\n * The S&B drop-in pitch (\"closes the GDPR sub-processor gap\", §6.3) depends on the\n * embedded polyfill staying network-silent AND, when an operator opts in with a\n * key, emitting only this minimal non-device set. We build the egress object from\n * these fixed keys instead of spreading caller-supplied `data`, so a future caller\n * passing reportEvent(key, evt, { deviceName, serialNumber, gattValue }) CANNOT\n * smuggle GATT values, device names, or serial numbers onto the wire — the guard\n * is structural, not by-convention (tests/telemetry-privacy.test.ts is the gate).\n */\nfunction buildEventData(): Record<string, string> {\n return { origin: location.hostname, ua: navigator.userAgent };\n}\n\nexport function reportEvent(apiKey: string, event: string, _data?: { [key: string]: string | number | boolean | null }): void {\n if (!apiKey) return;\n try {\n // SB-SDK-16: the ingesting Worker (cloudflare/workers/api) authenticates the keyed\n // /v1/events path ONLY via `Authorization: Bearer` (extractBearerKey); a\n // `?key=` query param is read solely by the /v1/detect pixel and would 401\n // here, silently dropping every event. Send the key in the header.\n fetch(`${API_BASE}/v1/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n events: [{\n event,\n data: buildEventData(),\n timestamp: Date.now(),\n }],\n }),\n keepalive: true,\n }).catch(() => {});\n } catch { /* analytics must never throw */ }\n}\n\nexport async function validateApiKey(\n apiKey: string,\n): Promise<{ operatorId: string; appName: string | null; plan: string } | null> {\n try {\n // SB-SDK-16: /v1/config is Bearer-auth-only on the Worker; a `?key=` param\n // is ignored there and the handler returns 401 → null. Send the key in the\n // Authorization header so a real key can resolve operator config.\n const res = await fetch(`${API_BASE}/v1/config`, {\n headers: { 'Authorization': `Bearer ${apiKey}` },\n });\n if (!res.ok) return null;\n return await res.json();\n } catch {\n return null;\n }\n}\n","/**\n * @beacio/detect\n *\n * Detects iOS Safari, checks if the Beacio extension is installed,\n * and shows an install banner if not. No-op on all other platforms.\n *\n * Your existing Web Bluetooth code works unchanged — this package only\n * handles the \"extension not installed\" case on iOS Safari.\n */\n\nexport { getExtensionInstallState, isExtensionInstalled, isIOSSafari } from './detect';\nexport type { ExtensionInstallState } from './detect';\nexport { showInstallBanner, removeInstallBanner, SETUP_STEPS } from './banner';\nexport type { BannerOptions, BannerState, SetupStep } from './banner';\n// SB-SDK-12: the framework-agnostic, ZERO-DOM headless onboarding API for\n// vanilla-JS partners (Storz & Bickel's app is vanilla JS + jQuery and cannot use\n// the React wizard; showInstallBanner injects beacio chrome). A partner draws its\n// OWN \"Enable Bluetooth in Safari\" card and drives it with these primitives —\n// install-state detection (SHARED with detect.ts + the react-sdk\n// ExtensionDetector), the EXTENSION_READY-aware observer, the return-link/clipboard\n// context helper, the dismissal frequency-cap, and the id-form App Store URL. None\n// of these inject DOM.\n// SB-PRD-08: the soft/hard dismissal split is part of the headless API too. A\n// vanilla-JS partner drawing its OWN onboarding card needs the soft \"Not now\"\n// primitive (dismissShort, a short 1-day suppression) — not just the long\n// `dismiss` — plus the documented window lengths, so it can mirror the banner's\n// soft/hard behaviour without hand-writing the internal localStorage key.\nexport {\n APP_STORE_URL,\n DEFAULT_DISMISS_DAYS,\n dismiss,\n dismissShort,\n getInstallState,\n getReturnContext,\n isDismissed,\n isExtensionActive,\n observeInstallState,\n saveReturnContext,\n SHORT_DISMISS_DAYS,\n} from './install-state';\n// SB-SDK-05: the branded, framework-free error presenter — the FAILURE-path\n// sibling of showInstallBanner. Re-exported from the package root (and from\n// core/browser-auto) so a classic <script> site can call\n// beacioDetect.presentError(error) with no module setup.\nexport { presentError } from './error-presenter';\nexport type { PresentErrorOptions, PresentErrorStrings, BeacioErrorCode } from './error-presenter';\n// SB-SDK-07: the shared localized-string seam (built-in en/de packs + the pure\n// resolver) that both showInstallBanner and presentError consume. Exported so a\n// consumer can inspect/extend the packs or pre-resolve copy. The BeacioErrorCode\n// type is already exported above (the identical local union), so it is NOT\n// re-exported here to avoid an ambiguous re-export.\nexport { DE_STRINGS, EN_STRINGS, resolveStrings } from './i18n';\nexport type {\n DeepPartial,\n ErrorCopy,\n ErrorStrings,\n LocaleStrings,\n ResolveStringsOptions,\n SetupStepCopy,\n StateCopy,\n} from './i18n';\nexport { reportEvent, validateApiKey } from './api';\nimport { reportEvent } from './api';\nimport type { ExtensionInstallState } from './detect';\n// detect now lives INSIDE @beacio/core, so the canonical event-name map is an\n// intra-package import from core's events module — the single source of truth\n// (events.test.ts remains the seam-crossing control that the wire literals match).\nimport { BEACIO_EVENTS } from '../events';\n// SBOPT-P2.4 (tier-3 headless): the id-form App Store URL + the pure return-link\n// builder come from the shared zero-DOM install-state module (both are also part\n// of the SB-SDK-12 headless surface); SETUP_URL is core's canonical /setup page.\n// buildOnboardingUrl (banner.ts) is imported LAZILY inside resolveOnboardingState —\n// like initBeacio's own `await import('./banner')` — so a pure-headless consumer\n// that only imports resolveOnboardingState never pulls the banner DOM code.\nimport { APP_STORE_URL, buildReturnLink } from './install-state';\nimport { SETUP_URL } from '../urls';\nexport interface BeacioOptions {\n /** Optional API key for campaign tracking */\n key?: string;\n /** Operator/app name shown in the prompt (e.g. \"FitTracker\") */\n operatorName?: string;\n /**\n * SB-SDK-07: BCP-47 UI language (e.g. 'de') for the install banner. Threaded\n * to showInstallBanner so the zero-config initBeacio path is localizable;\n * omitted ⇒ the banner derives the language from navigator.language, else\n * English. A `banner.lang` (below) overrides this for the banner specifically.\n */\n lang?: string;\n /** Install banner configuration, or false to disable */\n banner?:\n | {\n /** 'sheet' (default) for iOS bottom sheet, 'banner' for lightweight bar */\n mode?: 'sheet' | 'banner';\n position?: 'top' | 'bottom';\n text?: string;\n buttonText?: string;\n style?: Record<string, string>;\n startOnboardingUrl?: string;\n appStoreUrl?: string;\n /** Days to suppress after the explicit \"Don't show again\" opt-out (default: 14) */\n dismissDays?: number;\n /**\n * SB-PRD-08 (AC3): ignore the dismissal cooldown and show anyway. Set this\n * on a user-initiated recovery call (e.g. re-invoking initBeacio from a\n * Connect / \"Can't connect?\" gesture) so a previously-dismissed user can\n * re-open setup without clearing localStorage.\n */\n forceShow?: boolean;\n /** SB-SDK-07: BCP-47 language override for the banner (wins over the top-level `lang`). */\n lang?: string;\n /** SB-SDK-11 (tier-2 co-brand): partner accent colour for the prompt chrome. */\n accentColor?: string;\n /** SB-SDK-11: partner logo URL (http(s) only; validated). Replaces the beacio glyph. */\n brandLogoUrl?: string;\n /** SB-SDK-11: the connected device's display name (e.g. \"VOLCANO HYBRID\"). */\n deviceName?: string;\n /** SB-SDK-11: one-shot lead body copy override (HTML-escaped). */\n body?: string;\n /** SB-SDK-11: privacy reassurance body override (HTML-escaped). */\n privacyBody?: string;\n }\n | false;\n /** Called when the extension is detected and ready */\n onReady?: () => void;\n /** Called when the extension is installed but Safari still needs activation/allow access */\n onInstalledInactive?: () => void;\n /** Called when the extension is NOT installed */\n onNotInstalled?: () => void;\n}\n\nfunction dispatchInstallState(state: ExtensionInstallState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n window.dispatchEvent(new CustomEvent(BEACIO_EVENTS.STATE_CHANGE, {\n detail: { state }\n }));\n}\n\n/**\n * Show the install banner unless explicitly disabled.\n * No-op when `options.banner === false`.\n */\nasync function maybeShowBanner(\n options: BeacioOptions,\n state: ExtensionInstallState,\n // SB-SDK-03 AC2: lets the caller pass the per-site 'denied' refinement, which the\n // ExtensionInstallState markers cannot express (it has no 'denied' member).\n bannerStateOverride?: import('./banner').BannerState\n): Promise<void> {\n if (options.banner === false) return;\n const { showInstallBanner } = await import('./banner');\n const bannerConfig = typeof options.banner === 'object' ? options.banner : {};\n const bannerOpts: import('./banner').BannerOptions = {\n // SB-SDK-11: the co-brand theme (accentColor / brandLogoUrl / deviceName /\n // body / privacyBody) rides through on this spread of bannerConfig into\n // BannerOptions, so the zero-config initBeacio path is fully themeable.\n ...bannerConfig,\n apiKey: options.key ?? '',\n operatorName: options.operatorName,\n // SB-SDK-07: thread the UI language so the zero-config initBeacio path is\n // localizable; banner.lang wins over the top-level lang, and an omitted lang\n // lets the banner derive from navigator.language (else English).\n lang: bannerConfig.lang ?? options.lang,\n // SB-PRD-03: feed the funnel position so the sheet shows the SPECIFIC remaining\n // step (or, on 'active', the once-only success toast) instead of restarting setup.\n state: bannerStateOverride ?? state,\n };\n showInstallBanner(bannerOpts);\n}\n\n/**\n * SB-SDK-03 AC2: the extension is installed + enabled, but is THIS origin granted\n * access? Safari's per-origin \"Allow Every Website\" is irreducibly manual\n * (project_ios26_safari_extension_settings_readonly). The W3C-conformant probe is\n * `navigator.bluetooth.getAvailability()`: a defined `navigator.bluetooth` that\n * reports unavailable means the polyfill is present but blocked HERE — the\n * per-site 'denied' state. Any throw / undefined surface is treated as NOT denied\n * so a genuinely-active origin never gets downgraded to the guidance sheet.\n */\nasync function isOriginDenied(): Promise<boolean> {\n if (typeof navigator === 'undefined') return false;\n const bt = (navigator as Navigator & { bluetooth?: { getAvailability?: () => Promise<boolean> } })\n .bluetooth;\n if (!bt || typeof bt.getAvailability !== 'function') return false;\n try {\n return (await bt.getAvailability()) === false;\n } catch {\n return false;\n }\n}\n\n/**\n * SB-SDK-17: BEST-EFFORT Private Browsing detection. iOS Safari disables web\n * extensions in Private Browsing (no per-extension opt-in), so beacio is inert and\n * the content script sets no markers — getExtensionInstallState() resolves\n * 'not-installed' and the user wrongly gets the \"install the app\" sheet even though\n * the app may already be installed. The recovery is to reopen the page in a normal\n * tab, so this routes to the dedicated 'private-browsing' hint instead.\n *\n * iOS exposes NO reliable Private-Browsing API, so this is a HEURISTIC, not a\n * guarantee: historically iOS Safari Private mode gives localStorage a zero quota,\n * so a setItem write-probe throws (QuotaExceededError). We write-and-immediately-\n * remove a throwaway key; a throw is read as \"extensions are likely unavailable\".\n * It is deliberately conservative — ANY success (or an unexpected error shape) is\n * treated as NOT private browsing, so a normal tab is never downgraded to the hint.\n * Callers MUST gate this behind isIOSSafari() (the probe is meaningless elsewhere\n * and the false-positive risk is higher on other engines).\n */\nfunction isPrivateBrowsingBestEffort(): boolean {\n if (typeof window === 'undefined') return false;\n try {\n const storage = window.localStorage;\n if (!storage) return false;\n const probeKey = '__beacio_pb_probe__';\n storage.setItem(probeKey, '1');\n storage.removeItem(probeKey);\n return false;\n } catch {\n // A write-probe throw (e.g. QuotaExceededError) is the classic iOS Private\n // Browsing signature. Best-effort only — see the JSDoc above.\n return true;\n }\n}\n\n/**\n * Initialize Beacio detection.\n *\n * On iOS Safari: checks if the extension is installed, dispatches events,\n * and optionally shows an install banner.\n *\n * On all other platforms: no-op (returns immediately).\n */\nexport async function initBeacio(options: BeacioOptions): Promise<void> {\n const { getExtensionInstallState, isIOSSafari } = await import('./detect');\n\n if (!isIOSSafari()) return;\n\n const installState = await getExtensionInstallState();\n dispatchInstallState(installState);\n\n if (installState === 'active') {\n // SB-SDK-03 AC2: the extension is enabled, but this ORIGIN may still be\n // blocked (\"Allow Every Website\" not granted here) — the one funnel position\n // the install-state markers cannot distinguish. The W3C signal is\n // navigator.bluetooth being DEFINED while getAvailability() resolves false\n // (project_ios26_safari_extension_settings_readonly: per-origin grant is\n // irreducibly manual). Derive the 'denied' refinement so the banner shows the\n // aA → Manage Extensions → Allow Every Website guidance, not the success path.\n if (await isOriginDenied()) {\n reportEvent(options.key ?? '', 'extension_installed_inactive');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(BEACIO_EVENTS.INSTALLED_INACTIVE));\n }\n options.onInstalledInactive?.();\n await maybeShowBanner(options, installState, 'denied');\n return;\n }\n\n reportEvent(options.key ?? '', 'extension_active');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(BEACIO_EVENTS.READY));\n }\n options.onReady?.();\n // SB-PRD-03 AC5: on the active transition, surface the once-only \"beacio is\n // ready — tap Connect\" toast (showReadyToast self-suppresses for returning\n // users), replacing the old silent empty state.\n await maybeShowBanner(options, installState);\n return;\n }\n\n // SB-SDK-17: two iOS-Safari dead ends both look like a marker-less\n // 'not-installed'/'installed-inactive' to getExtensionInstallState(), so without\n // this block initBeacio would fall through to the misleading \"install the app\"\n // sheet even though the app may already be installed. We reach here only for the\n // non-active states and only on iOS Safari (the isIOSSafari() guard above already\n // returned for every other platform — AC3: neither heuristic runs off-iOS, and the\n // Private-Browsing write-probe is never even invoked there). Both heuristics are\n // BEST-EFFORT (see isPrivateBrowsingBestEffort / isOriginDenied JSDoc); when\n // neither fires we fall through to today's generic guidance unchanged.\n\n // 1) Private Browsing wins: extensions are globally inert (markers suppressed),\n // so per-origin signals are unreliable and the real recovery is a normal tab.\n if (isPrivateBrowsingBestEffort()) {\n reportEvent(options.key ?? '', 'detect');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(BEACIO_EVENTS.NOT_INSTALLED));\n }\n options.onNotInstalled?.();\n await maybeShowBanner(options, installState, 'private-browsing');\n return;\n }\n\n // 2) Marker-suppressed per-origin \"Deny\": no markers, yet navigator.bluetooth is\n // defined and reports unavailable HERE → a prior \"Deny\" left the extension\n // inert on this origin. Surface the SB-SDK-03 'denied' guidance (aA → Manage\n // Extensions → Allow Every Website), the same copy block the active branch uses.\n if (await isOriginDenied()) {\n reportEvent(options.key ?? '', 'extension_installed_inactive');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(BEACIO_EVENTS.INSTALLED_INACTIVE));\n }\n options.onInstalledInactive?.();\n await maybeShowBanner(options, installState, 'denied');\n return;\n }\n\n if (installState === 'installed-inactive') {\n reportEvent(options.key ?? '', 'extension_installed_inactive');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(BEACIO_EVENTS.INSTALLED_INACTIVE));\n }\n options.onInstalledInactive?.();\n\n await maybeShowBanner(options, installState);\n return;\n }\n\n // Extension NOT installed\n reportEvent(options.key ?? '', 'detect');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(BEACIO_EVENTS.NOT_INSTALLED));\n }\n options.onNotInstalled?.();\n\n // Show install banner unless explicitly disabled\n await maybeShowBanner(options, installState);\n if (options.banner !== false) {\n reportEvent(options.key ?? '', 'install_prompted');\n }\n}\n\n// ─── SBOPT-P2.4: the tier-3 HEADLESS onboarding funnel state ─────────────────\n//\n// initBeacio (above) welds the funnel classification to a banner render: every\n// routing branch reports an event, dispatches a DOM CustomEvent, and calls\n// maybeShowBanner. A tier-3 partner (Storz & Bickel) draws its OWN install prompt\n// and needs that classification with NONE of those side effects. resolveOnboarding-\n// State is the pure, zero-DOM projection of initBeacio's routing OUTCOMES — one\n// tagged-union variant per `return` — so the partner renders its own card from the\n// state instead of beacio chrome.\n\n/**\n * Where a first-run owner is in the irreducibly-manual iOS-26 setup funnel, as\n * DATA a partner renders itself. The union is CLOSED to exactly initBeacio's six\n * routing outcomes (a discriminated union + exhaustive switch, not scattered\n * undefined checks):\n * - 'unsupported' → not iOS Safari; Web Bluetooth via beacio is unavailable.\n * - 'not-installed' → app not installed; `installUrl` is the id-form App Store link.\n * - 'installed-inactive' → installed but the Safari extension toggle is off; `setupUrl` guides.\n * - 'denied' → enabled, but per-origin access not granted on THIS site; `setupUrl` guides.\n * - 'private-browsing' → Private Browsing disables extensions; the fix is a normal tab.\n * - 'ready' → the polyfill is live and this origin is granted; nothing to prompt.\n *\n * Required fields, sentinels over optionals (owner's API rule): each variant\n * carries only the render-ready URLs its OWN prompt needs, all required — no `?`.\n * `returnLink` is the tappable \"return to your page\" affordance\n * (`https://link.beacio.com/return?url=…`) computed purely, with no side effect.\n */\nexport type OnboardingState =\n | { kind: 'unsupported' }\n | { kind: 'not-installed'; installUrl: string; returnLink: string }\n | { kind: 'installed-inactive'; setupUrl: string; returnLink: string }\n | { kind: 'denied'; setupUrl: string; returnLink: string }\n | { kind: 'private-browsing'; returnLink: string }\n | { kind: 'ready' };\n\n/**\n * The REQUIRED config for {@link resolveOnboardingState} — no optional args.\n * `apiKey` threads the App Store campaign token (ct/mt) onto the install deep link\n * exactly as the banner's install button does; `operatorName` threads the operator\n * identity onto the guided /setup deep link so it can render \"Return to <operator>\".\n * Pass empty-string sentinels when a field is not in play.\n */\nexport interface OnboardingConfig {\n operatorName: string;\n apiKey: string;\n}\n\n/**\n * The guided /setup deep link carrying the operator identity + return origin, so\n * /setup renders the branded return CTA instead of generic copy. Built with\n * encodeURIComponent (not URLSearchParams) so the operator name round-trips in the\n * `%20` percent form the /setup page and the partner's rendered link both expect.\n */\nfunction buildSetupDeepLink(config: OnboardingConfig): string {\n const returnUrl = typeof window !== 'undefined' ? window.location.href : '';\n return `${SETUP_URL}?operatorName=${encodeURIComponent(config.operatorName)}&return=${encodeURIComponent(returnUrl)}`;\n}\n\n/**\n * Resolve the current tier-3 onboarding funnel position WITHOUT rendering any\n * beacio chrome. This is the headless projection of initBeacio's routing: the same\n * isIOSSafari early-return, the same active → (denied?) → ready split, and the same\n * \"Private Browsing wins over a marker-suppressed denied\" precedence for the\n * non-active states — but it returns the position as data for a partner to render,\n * dispatching NO events and injecting NO DOM.\n */\nexport async function resolveOnboardingState(config: OnboardingConfig): Promise<OnboardingState> {\n const { getExtensionInstallState, isIOSSafari } = await import('./detect');\n\n if (!isIOSSafari()) return { kind: 'unsupported' };\n\n const returnLink = buildReturnLink();\n const setupUrl = buildSetupDeepLink(config);\n const installState = await getExtensionInstallState();\n\n if (installState === 'active') {\n // Enabled globally, but is THIS origin granted? getAvailability()===false is\n // the per-origin block — initBeacio's active-branch 'denied' refinement.\n if (await isOriginDenied()) return { kind: 'denied', setupUrl, returnLink };\n return { kind: 'ready' };\n }\n\n // Non-active on iOS Safari — mirror initBeacio's precedence exactly:\n // 1) Private Browsing wins (extensions are globally inert; per-origin signals unreliable).\n if (isPrivateBrowsingBestEffort()) return { kind: 'private-browsing', returnLink };\n // 2) Marker-suppressed per-origin Deny (no markers, yet getAvailability()===false here).\n if (await isOriginDenied()) return { kind: 'denied', setupUrl, returnLink };\n // 3) Installed but the Safari extension toggle is off.\n if (installState === 'installed-inactive') return { kind: 'installed-inactive', setupUrl, returnLink };\n\n // 4) App not installed — the id-form App Store deep link (banner.ts's own builder,\n // imported lazily so a pure-headless consumer never pulls the banner DOM code).\n const { buildOnboardingUrl } = await import('./banner');\n return {\n kind: 'not-installed',\n installUrl: buildOnboardingUrl(APP_STORE_URL, { apiKey: config.apiKey, operatorName: config.operatorName }),\n returnLink,\n };\n}\n"]}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

import { N as NativeOverflowEvent } from '../../device-B5NsJWvh.mjs';
import { P as Percentage } from '../../units-C2kcsu3V.mjs';
import { B as BaseProfile } from '../../base-YuFhZsjv.mjs';
/**
* Storz & Bickel Crafty / Crafty+ / Mighty / Mighty+ vaporizer profile.
*
* @experimental UUIDs from PUBLIC reverse-engineering — standard-validated,
* on-device deferred to operator. Storz & Bickel publishes no official GATT
* specification; every UUID and decode below is derived from the vendor's own
* (minified) Web Bluetooth bundle plus independent community re-implementations,
* then cross-checked. The accessors exposed here cover only the HIGH-confidence
* characteristics (corroborated by the official bundle AND >= 1 independent
* source).
*
* Standard-grounded validation (the present trust basis): conformance to the
* Web Bluetooth Living Standard (https://webbluetoothcg.github.io/web-bluetooth/)
* is the authority for the runtime GATT contracts each accessor exercises —
* - §4 Device Discovery — `requestDevice({ filters, optionalServices })`
* (GAP-1 fix: optionalServices must be declared so iOS discover resolves),
* - §6 GATT Interaction — `getPrimaryService(uuid)` / `getCharacteristic(uuid)`
* (BluetoothRemoteGATTService uuid per §6.3),
* - §6.4 — `characteristic.readValue()` returns `Promise<DataView>`; the
* profile's `decodeTemperatureDeciCelsius` consumes `DataView.getInt16`
* little-endian and `decodeBatteryPercent` consumes `DataView.getUint8` —
* spec-conformant reads,
* - §6.4 — `characteristic.writeValue(value: BufferSource)` /
* `writeValueWithResponse` / `writeValueWithoutResponse` (BufferSource input;
* the profile's `encodeTemperatureDeciCelsius` produces a `DataView` — a
* valid BufferSource),
* - §6.4 — `characteristic.startNotifications()` => `characteristicvaluechanged`
* event with `event.target.value: DataView`; the profile subscribes
* exclusively via `BaseProfile.subscribe` (per the "Notifications" note above)
* and never writes a CCCD/SCCD descriptor itself — strictly W3C GATT,
* - §7.1 Standardized UUIDs — `BluetoothUUID.canonicalUUID` resolves to the
* lowercase 128-bit form (e.g. `00001818-0000-1000-8000-00805f9b34fb`);
* this profile stores UUIDs uppercase internally (beacio convention — see
* `NormalizedUUID`) and emits them lowercase to web-facing payloads (matches
* the spec's external canonical form).
* The S&B-derived UUID/opcode VALUES themselves are interface-only
* interoperability facts (see the 5+ source corroboration block immediately
* below) and have NOT been exercised on physical hardware through this library.
* On-device confirmation against a real Volcano/Crafty/Venty is an
* operator-supplied gate, tracked separately at
* `outreach/storz-bickel/onboarding/reviews/PR178-fixes/IP-02.md`. Treat
* reads/writes as standard-conformant in SHAPE but provisional in VALUE until
* device-confirmed.
*
* Device family & encoding
* -------------------------
* The Crafty/Mighty line shares a single GATT tree. The 96-bit vendor base is
* ASCII `STORZ&BICKEL` written **byte-reversed** (`…-4c45-4b43-4942-265a524f5453`,
* which decodes to `LEKCIB&ZROTS`). The sibling Volcano Hybrid line uses the
* *big-endian* form of the same base (`…-5354-4f52-5a26-4249434b454c`) and a
* different characteristic map; it is intentionally out of scope for this
* profile. Mighty/Mighty+ reuse the identical Crafty tree and are
* disambiguated at runtime via the model characteristic (`0x22`).
*
* Temperatures are little-endian uint16 in tenths of a degree Celsius
* (deciCelsius): raw `1822` == `182.2 °C`. Battery level is a little-endian
* uint16 percentage (0–100; only the low byte is used).
*
* Notifications (current temperature, battery) are enabled exclusively through
* {@link BaseProfile.subscribe} (`startNotifications()`); this profile never
* reads or writes a CCCD/SCCD descriptor itself — strictly W3C
* `navigator.bluetooth` GATT.
*
* Sources (verified 2026-06-14):
* - Official S&B Web Bluetooth app bundle (app.storz-bickel.com, js/main.js +
* crafty.js): `serviceUuidCrafty1`, `charactersiticCurrTemperatureChanged`,
* `characteristicWriteTemp`, `characteristicWriteBoostTemp`,
* `characteristicPowerChanged`.
* - J-Cat/crafty-control craftyUuids.ts — https://github.com/J-Cat/crafty-control
* (ServiceUuid, TemperatureUuid 0x11, SetPointUuid 0x21, BoostUuid 0x31,
* BatteryUuid 0x41; verified from source).
* - ligi/VaporizerControl CRAFTY_UUIDS.java — https://github.com/ligi/VaporizerControl
* (DATA_SERVICE craft(1), TEMPERATURE craft(0x11), SETPOINT craft(0x21),
* BOOST craft(0x31), BATTERY craft(0x41); verified from source).
* - gsasouza/sb-crafty-watch-os — https://github.com/gsasouza/sb-crafty-watch-os
* (battery + current-temperature handling; verified from source).
* - firsttris/reactive-volcano-app — https://github.com/firsttris/reactive-volcano-app
* (currTemperatureChanged, writeTemp, writeBoostTemp).
* - 0022111/sbtracker — https://github.com/0022111/sbtracker
* (BleConstants.kt: "// Crafty/Mighty+ (older or traditional protocol)").
*
* @example
* ```ts
* import { StorzBickelProfile } from '@beacio/core/experimental/profiles/storz-bickel';
*
* // requestDevice({ filters: [{ namePrefix: 'S&B' }], optionalServices: [
* // '00000001-4c45-4b43-4942-265a524f5453',
* // ] })
* const vape = new StorzBickelProfile(device);
* await vape.connect();
*
* await vape.setTargetTemperature(182.2);
*
* const off = vape.onCurrentTemperature((c) => console.log(`now ${c} °C`));
* console.log('battery', await vape.batteryLevel(), '%');
*
* off();
* vape.stop();
* ```
*/
/** Crafty/Mighty PRIMARY DATA SERVICE (live temp, setpoint, boost, battery). HIGH confidence. */
declare const STORZ_BICKEL_SERVICE = "00000001-4c45-4b43-4942-265a524f5453";
/**
* HIGH-confidence Crafty/Mighty characteristic UUIDs surfaced by this profile.
*
* Only characteristics rated HIGH in the consolidated reverse-engineering data
* (official bundle + >= 1 independent corroborator) are included. Lower-
* confidence and diagnostic characteristics are intentionally omitted until
* device-confirmed.
*/
declare const STORZ_BICKEL_CHARACTERISTICS: {
/** Current/live temperature. read/notify; deciCelsius LE. HIGH. */
readonly currentTemperature: "00000011-4c45-4b43-4942-265a524f5453";
/** Target/setpoint temperature. read/write; deciCelsius LE (e.g. 1822 = 182.2 °C). HIGH. */
readonly targetTemperature: "00000021-4c45-4b43-4942-265a524f5453";
/** Boost temperature offset. read/write; deciCelsius LE. (NOT heater on/off.) HIGH. */
readonly boostTemperature: "00000031-4c45-4b43-4942-265a524f5453";
/** Battery level percent. read/notify; uint16 LE (low byte used). HIGH. */
readonly batteryLevel: "00000041-4c45-4b43-4942-265a524f5453";
};
/**
* Crafty/Mighty SECONDARY service (device-info: serial number, model/firmware).
* The official bundle opens this via `getPrimaryService(serviceUuidCrafty2)`
* (`main.js:119`, `crafty.js`). Read-only metadata — not surfaced by the
* temperature/battery accessors. MEDIUM confidence (official bundle only).
*/
declare const STORZ_BICKEL_SERVICE_2 = "00000002-4c45-4b43-4942-265a524f5453";
/**
* Crafty/Mighty TERTIARY service (project/status registers, model, hour-meter).
* Opened via `getPrimaryService(serviceUuidCrafty3)` (`main.js:120`, `crafty.js`).
* MEDIUM confidence (official bundle only).
*/
declare const STORZ_BICKEL_SERVICE_3 = "00000003-4c45-4b43-4942-265a524f5453";
/**
* Read-only metadata characteristics on {@link STORZ_BICKEL_SERVICE_2}, pinned
* with provenance (`crafty.js` `primaryServiceCraftyUuid2.getCharacteristic`).
* Surfaced for callers that want device-info; not used by this profile's
* temperature/battery accessors. MEDIUM confidence.
*/
declare const STORZ_BICKEL_SERVICE_2_CHARACTERISTICS: {
/** Serial number. read; UTF-8 (first 8 chars). crafty.js:224. */
readonly serialNumber: "00000052-4c45-4b43-4942-265a524f5453";
/** Model identifier. read. crafty.js:261. */
readonly model: "00000032-4c45-4b43-4942-265a524f5453";
};
/**
* Read-only metadata characteristics on {@link STORZ_BICKEL_SERVICE_3}, pinned
* with provenance (`crafty.js` `primaryServiceCraftyUuid3.getCharacteristic`).
* MEDIUM confidence.
*/
declare const STORZ_BICKEL_SERVICE_3_CHARACTERISTICS: {
/** Firmware/BLE version string. read. crafty.js:323. */
readonly firmwareVersion: "000001c3-4c45-4b43-4942-265a524f5453";
/** Project-status register (model/state flags). read/notify. crafty.js:338. */
readonly projectStatus: "00000023-4c45-4b43-4942-265a524f5453";
};
/**
* The S&B Crafty/Mighty GATT is NOT auth-gated: every documented characteristic
* is reachable after a plain `getPrimaryService` + `getCharacteristic` with no
* pairing/bonding or write-to-unlock handshake (confirmed across the official
* bundle and the independent community re-implementations). This sentinel
* records that fact for integrators / @beacio/detect rather than leaving the
* absence of an auth gate implicit — there is no characteristic to write first.
*/
declare const STORZ_BICKEL_AUTH_GATE: null;
/**
* Crafty/Mighty (Family A) data services, in `getPrimaryService` order: the
* primary data service ({@link STORZ_BICKEL_SERVICE}) plus the device-info and
* project-register services. Canonical lowercase. This is the per-profile
* `services` array read by {@link deriveOptionalServices} (and exposed as the
* static `StorzBickelProfile.services`).
*
* Provenance: `captured/beautified/main.js:118-120` (serviceUuidCrafty1/2/3).
*/
declare const STORZ_BICKEL_SERVICES: readonly ["00000001-4c45-4b43-4942-265a524f5453", "00000002-4c45-4b43-4942-265a524f5453", "00000003-4c45-4b43-4942-265a524f5453"];
/**
* Volcano HYBRID (Family B) services actually opened by the vendor bundle, in
* `getPrimaryService` order (`volcano.js:550/554/558/562`). This family is out
* of scope for {@link StorzBickelProfile}'s accessors but is included in the
* connect-time `optionalServices` bundle so the picker can reach a Volcano.
*
* NOTE — these four UUIDs do NOT share a single base; the bundle mixes two:
* - volcano1/volcano2 use a generic-vendor base `…-1989-0108-1234-123456789abc`
* (NOT an S&B base at all).
* - volcano3/volcano4 use the *big-endian* S&B base `…-5354-4f52-5a26-4249434b454c`
* (ASCII `STORZ&BICKEL`), the same form used by the Veazy/Venty (QVAP) family,
* and the byte-reverse of Crafty's `…-4c45-4b43-4942-265a524f5453` base.
* Each line is individually source-cited to the vendor bundle; the values are
* pinned by the SB-SDK-02 regression below. PENDING on-device confirmation.
*
* `serviceUuidVolcano5` (`10130000-…`, `main.js:125`) is DELIBERATELY excluded:
* it is declared in the bundle but never `getPrimaryService`'d.
*/
declare const STORZ_BICKEL_VOLCANO_SERVICES: readonly ["00000001-1989-0108-1234-123456789abc", "01000002-1989-0108-1234-123456789abc", "10100000-5354-4f52-5a26-4249434b454c", "10110000-5354-4f52-5a26-4249434b454c"];
/**
* Veazy / Venty (Family C, the "QVAP" bundle) services opened by the vendor
* bundle (`qvap.js:556/582`): the vendor data service plus SIG `generic_access`.
* Out of scope for {@link StorzBickelProfile}'s accessors; included in the
* connect-time `optionalServices` bundle.
*/
declare const STORZ_BICKEL_VEAZY_VENTY_SERVICES: readonly ["00000000-5354-4f52-5a26-4249434b454c", "00001800-0000-1000-8000-00805f9b34fb"];
/**
* The three S&B device families' service arrays, keyed by family. Consumed by
* {@link StorzBickel.allServices} to build the de-duped multi-family
* `optionalServices` bundle for a picker that should reach ANY S&B device.
*/
declare const STORZ_BICKEL_FAMILY_SERVICES: {
readonly crafty: readonly ["00000001-4c45-4b43-4942-265a524f5453", "00000002-4c45-4b43-4942-265a524f5453", "00000003-4c45-4b43-4942-265a524f5453"];
readonly volcano: readonly ["00000001-1989-0108-1234-123456789abc", "01000002-1989-0108-1234-123456789abc", "10100000-5354-4f52-5a26-4249434b454c", "10110000-5354-4f52-5a26-4249434b454c"];
readonly veazyVenty: readonly ["00000000-5354-4f52-5a26-4249434b454c", "00001800-0000-1000-8000-00805f9b34fb"];
};
/**
* Decode a Storz & Bickel temperature characteristic value.
*
* Wire format: little-endian uint16 in tenths of a degree Celsius
* (deciCelsius). Raw `1822` decodes to `182.2`.
*
* @param dv - Raw characteristic value (current, target, or boost temperature).
* @returns Temperature in degrees Celsius.
* @throws {BeacioError} INVALID_PARAMETER if the value is shorter than 2 bytes.
*/
declare function decodeTemperatureDeciCelsius(dv: DataView): number;
/**
* Encode a degrees-Celsius temperature into the Storz & Bickel wire format:
* little-endian uint16 deciCelsius, rounded to the nearest 0.1 °C.
*
* @param celsius - Temperature in degrees Celsius (e.g. `182.2`).
* @returns A 2-byte little-endian payload (raw deciCelsius).
*/
declare function encodeTemperatureDeciCelsius(celsius: number): Uint8Array<ArrayBuffer>;
/**
* Decode the Storz & Bickel battery-level characteristic value.
*
* Wire format: little-endian uint16 percentage (0–100); only the low byte is
* populated in practice. The official bundle labels this `power`; community
* sources label it `battery` — both are the same battery-level read path.
*
* @param dv - Raw battery characteristic value.
* @returns {Percentage} Battery level as an integer percentage (0–100).
* @throws {BeacioError} INVALID_PARAMETER if the value is shorter than 1 byte.
*/
declare function decodeBatteryPercent(dv: DataView): Percentage;
declare class StorzBickelProfile extends BaseProfile {
/**
* Crafty/Mighty (Family A) services this profile's device may reach after
* connection. Read by {@link deriveOptionalServices} so a caller can pass the
* profile class itself instead of hand-copying {@link STORZ_BICKEL_SERVICES}.
*/
static readonly services: readonly ["00000001-4c45-4b43-4942-265a524f5453", "00000002-4c45-4b43-4942-265a524f5453", "00000003-4c45-4b43-4942-265a524f5453"];
protected readonly service = "00000001-4c45-4b43-4942-265a524f5453";
/**
* Read the current/live heater temperature (°C).
* Characteristic `00000011-…` (read/notify), deciCelsius LE.
*/
currentTemperature(): Promise<number>;
/**
* Subscribe to live temperature updates (°C). Returns an unsubscribe
* function. Notifications are enabled via {@link BaseProfile.subscribe}
* only — no CCCD write.
*/
onCurrentTemperature(callback: (celsius: number) => void): () => void;
/**
* Observe NATIVE notification-queue overflows on the live-temperature stream.
* Returns an unsubscribe function (also cleaned up by {@link BaseProfile.stop}).
*
* Under sustained high-frequency notifications Safari's bounded Swift
* `EventQueue` evicts samples rather than dropping them silently, and the
* polyfill surfaces each eviction as `beacio:overflow`. When this fires, the
* temperature shown from the last notified value is potentially stale: the
* recommended response is to issue a fresh {@link currentTemperature} read and
* repaint the gauge from that value rather than trusting the last
* `onCurrentTemperature` sample.
*
* Thin-JS: this only surfaces the existing native signal; it changes no flow
* control. The `event` carries the eviction metadata
* ({@link NativeOverflowEvent}: `evictedCount`, `queueCapacity`, `seq`,
* `timestamp`).
*
* @example
* ```ts
* vape.onCurrentTemperatureStale(async () => {
* // notifications were evicted — resync the gauge from a fresh read
* updateGauge(await vape.currentTemperature());
* });
* ```
*/
onCurrentTemperatureStale(callback: (event: NativeOverflowEvent) => void): () => void;
/**
* Read the target/setpoint temperature (°C).
* Characteristic `00000021-…` (read/write), deciCelsius LE.
*/
targetTemperature(): Promise<number>;
/**
* Write the target/setpoint temperature (°C) using write-with-response.
* Characteristic `00000021-…`, deciCelsius LE.
*
* @param celsius - Desired setpoint in degrees Celsius (e.g. `182.2`).
*/
setTargetTemperature(celsius: number): Promise<void>;
/**
* Read the boost temperature **offset** (°C). This is added on top of the
* setpoint while boost is engaged — it is NOT a heater on/off control.
* Characteristic `00000031-…` (read/write), deciCelsius LE.
*/
boost(): Promise<number>;
/**
* Write the boost temperature **offset** (°C) using write-with-response.
* Characteristic `00000031-…`, deciCelsius LE.
*
* @param celsius - Boost offset in degrees Celsius (e.g. `15`).
*/
setBoost(celsius: number): Promise<void>;
/**
* Read the battery level (0–100 %).
* Characteristic `00000041-…` (read/notify), uint16 LE.
*/
batteryLevel(): Promise<Percentage>;
/**
* Subscribe to battery-level updates (0–100 %). Returns an unsubscribe
* function. Notifications are enabled via {@link BaseProfile.subscribe}
* only — no CCCD write.
*/
onBatteryLevel(callback: (percent: Percentage) => void): () => void;
}
/**
* Storz & Bickel vendor-level helpers spanning ALL device families (Crafty/
* Mighty, Volcano HYBRID, Veazy/Venty), as distinct from the single-family
* {@link StorzBickelProfile}.
*/
declare const StorzBickel: {
/**
* The de-duped, canonical-lowercase union of every `getPrimaryService`-opened
* service across all three S&B families ({@link STORZ_BICKEL_FAMILY_SERVICES}).
* Pass this as `optionalServices` to a single `requestDevice` so the picker can
* reach ANY Storz & Bickel device regardless of family.
*
* @returns De-duped canonical service UUIDs (first-seen order).
*
* @example
* ```ts
* const device = await ble.requestDevice({
* filters: [{ namePrefix: 'S&B' }, { namePrefix: 'STORZ' }],
* optionalServices: StorzBickel.allServices(),
* });
* ```
*/
readonly allServices: () => string[];
};
export { STORZ_BICKEL_AUTH_GATE, STORZ_BICKEL_CHARACTERISTICS, STORZ_BICKEL_FAMILY_SERVICES, STORZ_BICKEL_SERVICE, STORZ_BICKEL_SERVICES, STORZ_BICKEL_SERVICE_2, STORZ_BICKEL_SERVICE_2_CHARACTERISTICS, STORZ_BICKEL_SERVICE_3, STORZ_BICKEL_SERVICE_3_CHARACTERISTICS, STORZ_BICKEL_VEAZY_VENTY_SERVICES, STORZ_BICKEL_VOLCANO_SERVICES, StorzBickel, StorzBickelProfile, decodeBatteryPercent, decodeTemperatureDeciCelsius, encodeTemperatureDeciCelsius };
import { N as NativeOverflowEvent } from '../../device-B5NsJWvh.js';
import { P as Percentage } from '../../units-C2kcsu3V.js';
import { B as BaseProfile } from '../../base-BnHcG-k7.js';
/**
* Storz & Bickel Crafty / Crafty+ / Mighty / Mighty+ vaporizer profile.
*
* @experimental UUIDs from PUBLIC reverse-engineering — standard-validated,
* on-device deferred to operator. Storz & Bickel publishes no official GATT
* specification; every UUID and decode below is derived from the vendor's own
* (minified) Web Bluetooth bundle plus independent community re-implementations,
* then cross-checked. The accessors exposed here cover only the HIGH-confidence
* characteristics (corroborated by the official bundle AND >= 1 independent
* source).
*
* Standard-grounded validation (the present trust basis): conformance to the
* Web Bluetooth Living Standard (https://webbluetoothcg.github.io/web-bluetooth/)
* is the authority for the runtime GATT contracts each accessor exercises —
* - §4 Device Discovery — `requestDevice({ filters, optionalServices })`
* (GAP-1 fix: optionalServices must be declared so iOS discover resolves),
* - §6 GATT Interaction — `getPrimaryService(uuid)` / `getCharacteristic(uuid)`
* (BluetoothRemoteGATTService uuid per §6.3),
* - §6.4 — `characteristic.readValue()` returns `Promise<DataView>`; the
* profile's `decodeTemperatureDeciCelsius` consumes `DataView.getInt16`
* little-endian and `decodeBatteryPercent` consumes `DataView.getUint8` —
* spec-conformant reads,
* - §6.4 — `characteristic.writeValue(value: BufferSource)` /
* `writeValueWithResponse` / `writeValueWithoutResponse` (BufferSource input;
* the profile's `encodeTemperatureDeciCelsius` produces a `DataView` — a
* valid BufferSource),
* - §6.4 — `characteristic.startNotifications()` => `characteristicvaluechanged`
* event with `event.target.value: DataView`; the profile subscribes
* exclusively via `BaseProfile.subscribe` (per the "Notifications" note above)
* and never writes a CCCD/SCCD descriptor itself — strictly W3C GATT,
* - §7.1 Standardized UUIDs — `BluetoothUUID.canonicalUUID` resolves to the
* lowercase 128-bit form (e.g. `00001818-0000-1000-8000-00805f9b34fb`);
* this profile stores UUIDs uppercase internally (beacio convention — see
* `NormalizedUUID`) and emits them lowercase to web-facing payloads (matches
* the spec's external canonical form).
* The S&B-derived UUID/opcode VALUES themselves are interface-only
* interoperability facts (see the 5+ source corroboration block immediately
* below) and have NOT been exercised on physical hardware through this library.
* On-device confirmation against a real Volcano/Crafty/Venty is an
* operator-supplied gate, tracked separately at
* `outreach/storz-bickel/onboarding/reviews/PR178-fixes/IP-02.md`. Treat
* reads/writes as standard-conformant in SHAPE but provisional in VALUE until
* device-confirmed.
*
* Device family & encoding
* -------------------------
* The Crafty/Mighty line shares a single GATT tree. The 96-bit vendor base is
* ASCII `STORZ&BICKEL` written **byte-reversed** (`…-4c45-4b43-4942-265a524f5453`,
* which decodes to `LEKCIB&ZROTS`). The sibling Volcano Hybrid line uses the
* *big-endian* form of the same base (`…-5354-4f52-5a26-4249434b454c`) and a
* different characteristic map; it is intentionally out of scope for this
* profile. Mighty/Mighty+ reuse the identical Crafty tree and are
* disambiguated at runtime via the model characteristic (`0x22`).
*
* Temperatures are little-endian uint16 in tenths of a degree Celsius
* (deciCelsius): raw `1822` == `182.2 °C`. Battery level is a little-endian
* uint16 percentage (0–100; only the low byte is used).
*
* Notifications (current temperature, battery) are enabled exclusively through
* {@link BaseProfile.subscribe} (`startNotifications()`); this profile never
* reads or writes a CCCD/SCCD descriptor itself — strictly W3C
* `navigator.bluetooth` GATT.
*
* Sources (verified 2026-06-14):
* - Official S&B Web Bluetooth app bundle (app.storz-bickel.com, js/main.js +
* crafty.js): `serviceUuidCrafty1`, `charactersiticCurrTemperatureChanged`,
* `characteristicWriteTemp`, `characteristicWriteBoostTemp`,
* `characteristicPowerChanged`.
* - J-Cat/crafty-control craftyUuids.ts — https://github.com/J-Cat/crafty-control
* (ServiceUuid, TemperatureUuid 0x11, SetPointUuid 0x21, BoostUuid 0x31,
* BatteryUuid 0x41; verified from source).
* - ligi/VaporizerControl CRAFTY_UUIDS.java — https://github.com/ligi/VaporizerControl
* (DATA_SERVICE craft(1), TEMPERATURE craft(0x11), SETPOINT craft(0x21),
* BOOST craft(0x31), BATTERY craft(0x41); verified from source).
* - gsasouza/sb-crafty-watch-os — https://github.com/gsasouza/sb-crafty-watch-os
* (battery + current-temperature handling; verified from source).
* - firsttris/reactive-volcano-app — https://github.com/firsttris/reactive-volcano-app
* (currTemperatureChanged, writeTemp, writeBoostTemp).
* - 0022111/sbtracker — https://github.com/0022111/sbtracker
* (BleConstants.kt: "// Crafty/Mighty+ (older or traditional protocol)").
*
* @example
* ```ts
* import { StorzBickelProfile } from '@beacio/core/experimental/profiles/storz-bickel';
*
* // requestDevice({ filters: [{ namePrefix: 'S&B' }], optionalServices: [
* // '00000001-4c45-4b43-4942-265a524f5453',
* // ] })
* const vape = new StorzBickelProfile(device);
* await vape.connect();
*
* await vape.setTargetTemperature(182.2);
*
* const off = vape.onCurrentTemperature((c) => console.log(`now ${c} °C`));
* console.log('battery', await vape.batteryLevel(), '%');
*
* off();
* vape.stop();
* ```
*/
/** Crafty/Mighty PRIMARY DATA SERVICE (live temp, setpoint, boost, battery). HIGH confidence. */
declare const STORZ_BICKEL_SERVICE = "00000001-4c45-4b43-4942-265a524f5453";
/**
* HIGH-confidence Crafty/Mighty characteristic UUIDs surfaced by this profile.
*
* Only characteristics rated HIGH in the consolidated reverse-engineering data
* (official bundle + >= 1 independent corroborator) are included. Lower-
* confidence and diagnostic characteristics are intentionally omitted until
* device-confirmed.
*/
declare const STORZ_BICKEL_CHARACTERISTICS: {
/** Current/live temperature. read/notify; deciCelsius LE. HIGH. */
readonly currentTemperature: "00000011-4c45-4b43-4942-265a524f5453";
/** Target/setpoint temperature. read/write; deciCelsius LE (e.g. 1822 = 182.2 °C). HIGH. */
readonly targetTemperature: "00000021-4c45-4b43-4942-265a524f5453";
/** Boost temperature offset. read/write; deciCelsius LE. (NOT heater on/off.) HIGH. */
readonly boostTemperature: "00000031-4c45-4b43-4942-265a524f5453";
/** Battery level percent. read/notify; uint16 LE (low byte used). HIGH. */
readonly batteryLevel: "00000041-4c45-4b43-4942-265a524f5453";
};
/**
* Crafty/Mighty SECONDARY service (device-info: serial number, model/firmware).
* The official bundle opens this via `getPrimaryService(serviceUuidCrafty2)`
* (`main.js:119`, `crafty.js`). Read-only metadata — not surfaced by the
* temperature/battery accessors. MEDIUM confidence (official bundle only).
*/
declare const STORZ_BICKEL_SERVICE_2 = "00000002-4c45-4b43-4942-265a524f5453";
/**
* Crafty/Mighty TERTIARY service (project/status registers, model, hour-meter).
* Opened via `getPrimaryService(serviceUuidCrafty3)` (`main.js:120`, `crafty.js`).
* MEDIUM confidence (official bundle only).
*/
declare const STORZ_BICKEL_SERVICE_3 = "00000003-4c45-4b43-4942-265a524f5453";
/**
* Read-only metadata characteristics on {@link STORZ_BICKEL_SERVICE_2}, pinned
* with provenance (`crafty.js` `primaryServiceCraftyUuid2.getCharacteristic`).
* Surfaced for callers that want device-info; not used by this profile's
* temperature/battery accessors. MEDIUM confidence.
*/
declare const STORZ_BICKEL_SERVICE_2_CHARACTERISTICS: {
/** Serial number. read; UTF-8 (first 8 chars). crafty.js:224. */
readonly serialNumber: "00000052-4c45-4b43-4942-265a524f5453";
/** Model identifier. read. crafty.js:261. */
readonly model: "00000032-4c45-4b43-4942-265a524f5453";
};
/**
* Read-only metadata characteristics on {@link STORZ_BICKEL_SERVICE_3}, pinned
* with provenance (`crafty.js` `primaryServiceCraftyUuid3.getCharacteristic`).
* MEDIUM confidence.
*/
declare const STORZ_BICKEL_SERVICE_3_CHARACTERISTICS: {
/** Firmware/BLE version string. read. crafty.js:323. */
readonly firmwareVersion: "000001c3-4c45-4b43-4942-265a524f5453";
/** Project-status register (model/state flags). read/notify. crafty.js:338. */
readonly projectStatus: "00000023-4c45-4b43-4942-265a524f5453";
};
/**
* The S&B Crafty/Mighty GATT is NOT auth-gated: every documented characteristic
* is reachable after a plain `getPrimaryService` + `getCharacteristic` with no
* pairing/bonding or write-to-unlock handshake (confirmed across the official
* bundle and the independent community re-implementations). This sentinel
* records that fact for integrators / @beacio/detect rather than leaving the
* absence of an auth gate implicit — there is no characteristic to write first.
*/
declare const STORZ_BICKEL_AUTH_GATE: null;
/**
* Crafty/Mighty (Family A) data services, in `getPrimaryService` order: the
* primary data service ({@link STORZ_BICKEL_SERVICE}) plus the device-info and
* project-register services. Canonical lowercase. This is the per-profile
* `services` array read by {@link deriveOptionalServices} (and exposed as the
* static `StorzBickelProfile.services`).
*
* Provenance: `captured/beautified/main.js:118-120` (serviceUuidCrafty1/2/3).
*/
declare const STORZ_BICKEL_SERVICES: readonly ["00000001-4c45-4b43-4942-265a524f5453", "00000002-4c45-4b43-4942-265a524f5453", "00000003-4c45-4b43-4942-265a524f5453"];
/**
* Volcano HYBRID (Family B) services actually opened by the vendor bundle, in
* `getPrimaryService` order (`volcano.js:550/554/558/562`). This family is out
* of scope for {@link StorzBickelProfile}'s accessors but is included in the
* connect-time `optionalServices` bundle so the picker can reach a Volcano.
*
* NOTE — these four UUIDs do NOT share a single base; the bundle mixes two:
* - volcano1/volcano2 use a generic-vendor base `…-1989-0108-1234-123456789abc`
* (NOT an S&B base at all).
* - volcano3/volcano4 use the *big-endian* S&B base `…-5354-4f52-5a26-4249434b454c`
* (ASCII `STORZ&BICKEL`), the same form used by the Veazy/Venty (QVAP) family,
* and the byte-reverse of Crafty's `…-4c45-4b43-4942-265a524f5453` base.
* Each line is individually source-cited to the vendor bundle; the values are
* pinned by the SB-SDK-02 regression below. PENDING on-device confirmation.
*
* `serviceUuidVolcano5` (`10130000-…`, `main.js:125`) is DELIBERATELY excluded:
* it is declared in the bundle but never `getPrimaryService`'d.
*/
declare const STORZ_BICKEL_VOLCANO_SERVICES: readonly ["00000001-1989-0108-1234-123456789abc", "01000002-1989-0108-1234-123456789abc", "10100000-5354-4f52-5a26-4249434b454c", "10110000-5354-4f52-5a26-4249434b454c"];
/**
* Veazy / Venty (Family C, the "QVAP" bundle) services opened by the vendor
* bundle (`qvap.js:556/582`): the vendor data service plus SIG `generic_access`.
* Out of scope for {@link StorzBickelProfile}'s accessors; included in the
* connect-time `optionalServices` bundle.
*/
declare const STORZ_BICKEL_VEAZY_VENTY_SERVICES: readonly ["00000000-5354-4f52-5a26-4249434b454c", "00001800-0000-1000-8000-00805f9b34fb"];
/**
* The three S&B device families' service arrays, keyed by family. Consumed by
* {@link StorzBickel.allServices} to build the de-duped multi-family
* `optionalServices` bundle for a picker that should reach ANY S&B device.
*/
declare const STORZ_BICKEL_FAMILY_SERVICES: {
readonly crafty: readonly ["00000001-4c45-4b43-4942-265a524f5453", "00000002-4c45-4b43-4942-265a524f5453", "00000003-4c45-4b43-4942-265a524f5453"];
readonly volcano: readonly ["00000001-1989-0108-1234-123456789abc", "01000002-1989-0108-1234-123456789abc", "10100000-5354-4f52-5a26-4249434b454c", "10110000-5354-4f52-5a26-4249434b454c"];
readonly veazyVenty: readonly ["00000000-5354-4f52-5a26-4249434b454c", "00001800-0000-1000-8000-00805f9b34fb"];
};
/**
* Decode a Storz & Bickel temperature characteristic value.
*
* Wire format: little-endian uint16 in tenths of a degree Celsius
* (deciCelsius). Raw `1822` decodes to `182.2`.
*
* @param dv - Raw characteristic value (current, target, or boost temperature).
* @returns Temperature in degrees Celsius.
* @throws {BeacioError} INVALID_PARAMETER if the value is shorter than 2 bytes.
*/
declare function decodeTemperatureDeciCelsius(dv: DataView): number;
/**
* Encode a degrees-Celsius temperature into the Storz & Bickel wire format:
* little-endian uint16 deciCelsius, rounded to the nearest 0.1 °C.
*
* @param celsius - Temperature in degrees Celsius (e.g. `182.2`).
* @returns A 2-byte little-endian payload (raw deciCelsius).
*/
declare function encodeTemperatureDeciCelsius(celsius: number): Uint8Array<ArrayBuffer>;
/**
* Decode the Storz & Bickel battery-level characteristic value.
*
* Wire format: little-endian uint16 percentage (0–100); only the low byte is
* populated in practice. The official bundle labels this `power`; community
* sources label it `battery` — both are the same battery-level read path.
*
* @param dv - Raw battery characteristic value.
* @returns {Percentage} Battery level as an integer percentage (0–100).
* @throws {BeacioError} INVALID_PARAMETER if the value is shorter than 1 byte.
*/
declare function decodeBatteryPercent(dv: DataView): Percentage;
declare class StorzBickelProfile extends BaseProfile {
/**
* Crafty/Mighty (Family A) services this profile's device may reach after
* connection. Read by {@link deriveOptionalServices} so a caller can pass the
* profile class itself instead of hand-copying {@link STORZ_BICKEL_SERVICES}.
*/
static readonly services: readonly ["00000001-4c45-4b43-4942-265a524f5453", "00000002-4c45-4b43-4942-265a524f5453", "00000003-4c45-4b43-4942-265a524f5453"];
protected readonly service = "00000001-4c45-4b43-4942-265a524f5453";
/**
* Read the current/live heater temperature (°C).
* Characteristic `00000011-…` (read/notify), deciCelsius LE.
*/
currentTemperature(): Promise<number>;
/**
* Subscribe to live temperature updates (°C). Returns an unsubscribe
* function. Notifications are enabled via {@link BaseProfile.subscribe}
* only — no CCCD write.
*/
onCurrentTemperature(callback: (celsius: number) => void): () => void;
/**
* Observe NATIVE notification-queue overflows on the live-temperature stream.
* Returns an unsubscribe function (also cleaned up by {@link BaseProfile.stop}).
*
* Under sustained high-frequency notifications Safari's bounded Swift
* `EventQueue` evicts samples rather than dropping them silently, and the
* polyfill surfaces each eviction as `beacio:overflow`. When this fires, the
* temperature shown from the last notified value is potentially stale: the
* recommended response is to issue a fresh {@link currentTemperature} read and
* repaint the gauge from that value rather than trusting the last
* `onCurrentTemperature` sample.
*
* Thin-JS: this only surfaces the existing native signal; it changes no flow
* control. The `event` carries the eviction metadata
* ({@link NativeOverflowEvent}: `evictedCount`, `queueCapacity`, `seq`,
* `timestamp`).
*
* @example
* ```ts
* vape.onCurrentTemperatureStale(async () => {
* // notifications were evicted — resync the gauge from a fresh read
* updateGauge(await vape.currentTemperature());
* });
* ```
*/
onCurrentTemperatureStale(callback: (event: NativeOverflowEvent) => void): () => void;
/**
* Read the target/setpoint temperature (°C).
* Characteristic `00000021-…` (read/write), deciCelsius LE.
*/
targetTemperature(): Promise<number>;
/**
* Write the target/setpoint temperature (°C) using write-with-response.
* Characteristic `00000021-…`, deciCelsius LE.
*
* @param celsius - Desired setpoint in degrees Celsius (e.g. `182.2`).
*/
setTargetTemperature(celsius: number): Promise<void>;
/**
* Read the boost temperature **offset** (°C). This is added on top of the
* setpoint while boost is engaged — it is NOT a heater on/off control.
* Characteristic `00000031-…` (read/write), deciCelsius LE.
*/
boost(): Promise<number>;
/**
* Write the boost temperature **offset** (°C) using write-with-response.
* Characteristic `00000031-…`, deciCelsius LE.
*
* @param celsius - Boost offset in degrees Celsius (e.g. `15`).
*/
setBoost(celsius: number): Promise<void>;
/**
* Read the battery level (0–100 %).
* Characteristic `00000041-…` (read/notify), uint16 LE.
*/
batteryLevel(): Promise<Percentage>;
/**
* Subscribe to battery-level updates (0–100 %). Returns an unsubscribe
* function. Notifications are enabled via {@link BaseProfile.subscribe}
* only — no CCCD write.
*/
onBatteryLevel(callback: (percent: Percentage) => void): () => void;
}
/**
* Storz & Bickel vendor-level helpers spanning ALL device families (Crafty/
* Mighty, Volcano HYBRID, Veazy/Venty), as distinct from the single-family
* {@link StorzBickelProfile}.
*/
declare const StorzBickel: {
/**
* The de-duped, canonical-lowercase union of every `getPrimaryService`-opened
* service across all three S&B families ({@link STORZ_BICKEL_FAMILY_SERVICES}).
* Pass this as `optionalServices` to a single `requestDevice` so the picker can
* reach ANY Storz & Bickel device regardless of family.
*
* @returns De-duped canonical service UUIDs (first-seen order).
*
* @example
* ```ts
* const device = await ble.requestDevice({
* filters: [{ namePrefix: 'S&B' }, { namePrefix: 'STORZ' }],
* optionalServices: StorzBickel.allServices(),
* });
* ```
*/
readonly allServices: () => string[];
};
export { STORZ_BICKEL_AUTH_GATE, STORZ_BICKEL_CHARACTERISTICS, STORZ_BICKEL_FAMILY_SERVICES, STORZ_BICKEL_SERVICE, STORZ_BICKEL_SERVICES, STORZ_BICKEL_SERVICE_2, STORZ_BICKEL_SERVICE_2_CHARACTERISTICS, STORZ_BICKEL_SERVICE_3, STORZ_BICKEL_SERVICE_3_CHARACTERISTICS, STORZ_BICKEL_VEAZY_VENTY_SERVICES, STORZ_BICKEL_VOLCANO_SERVICES, StorzBickel, StorzBickelProfile, decodeBatteryPercent, decodeTemperatureDeciCelsius, encodeTemperatureDeciCelsius };
'use strict';var P=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),I={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the Beacio iOS app and enable the Safari extension. Use @beacio/core/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this Beacio instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},M=/\b(bluefy|web ble browser|webble browser)\b/gi;function k(t){let e=t.split(`
`,1)[0]??"";return e=e.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),e=e.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),e=e.replace(M,""),e=e.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),e=e.replace(/[\s.,;:]+$/g,"").trim(),e}var _=class t extends Error{constructor(e,r,i){let n=I[e];super(r??n),this.name="BeacioError",this.code=e,this.suggestion=I[e],this.isRetriable=P.has(e),this.retryAfterMs=i?.retryAfterMs;}static from(e,r="GATT_OPERATION_FAILED"){if(e instanceof t)return e;let i=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,n=e instanceof Error?e.message:String(e),a=k(n)||void 0,o=n.toLowerCase();switch(i){case "TypeError":return new t("INVALID_PARAMETER",a);case "NotFoundError":return new t("DEVICE_NOT_FOUND",a);case "NotAllowedError":case "SecurityError":return new t("PERMISSION_DENIED",a);case "NetworkError":return new t("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3});case "TimeoutError":return new t("TIMEOUT",a,{retryAfterMs:1e3});case "InvalidStateError":if(o.includes("disconnect"))return new t("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3});break;}return n.includes("User cancelled")||n.includes("User canceled")?new t("USER_CANCELLED"):o.includes("no devices found")||n.includes("No Devices")?new t("DEVICE_NOT_FOUND"):n.includes("No Services matching")||o.includes("service not found")?new t("SERVICE_NOT_FOUND",a):n.includes("No Characteristics matching")||o.includes("characteristic not found")?new t("CHARACTERISTIC_NOT_FOUND",a):n.includes("GATT Server is disconnected")||o.includes("disconnected")?new t("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3}):o.includes("not supported")&&o.includes("read")?new t("CHARACTERISTIC_NOT_READABLE",a):o.includes("not supported")&&o.includes("write")?new t("CHARACTERISTIC_NOT_WRITABLE",a):o.includes("not supported")&&o.includes("notif")?new t("CHARACTERISTIC_NOT_NOTIFIABLE",a):o.includes("permission")?new t("PERMISSION_DENIED",a):new t(r,a)}};var p="-0000-1000-8000-00805f9b34fb",w=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,l={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},f={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,local_east_coordinate:10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989};function A(t){return t.toString(16).padStart(8,"0")+p}var F=/^[0-9a-f]{4}$/,W=/^[0-9a-f]{8}$/;function K(t,e){let r=t.length,i=e.length,n=Array.from({length:i+1},(a,o)=>o);for(let a=1;a<=r;a++){let o=a-1;n[0]=a;for(let s=1;s<=i;s++){let u=n[s];n[s]=t[a-1]===e[s-1]?o:1+Math.min(o,n[s],n[s-1]),o=u;}}return n[i]}function z(t){return t.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function D(t,e){let r=e[t];if(r!==void 0)return r;let i=t.replace(/[._-]/g,"");if(i){for(let[n,a]of Object.entries(e))if(n.replace(/[._-]/g,"")===i)return a}}function g(t){if(typeof t=="number"){if(!Number.isInteger(t)||t<0||t>4294967295)throw new TypeError(`Invalid UUID integer: ${t}. Must be a 16-bit or 32-bit unsigned integer.`);return A(t)}let e=t.trim(),r=e.toLowerCase();if(w.test(r))return r;if(F.test(r))return "0000"+r+p;if(W.test(r))return r+p;let i=l[r]??f[r];if(i!==void 0)return A(i);let n=z(e),a=D(n,l);if(a!==void 0)return A(a);let o=D(n,f);if(o!==void 0)return A(o);let s=Object.keys(l).concat(Object.keys(f)),u,T=4;for(let d of s){let v=K(n,d);v<T&&(T=v,u=d);}!u&&n.length>=4&&(u=s.find(d=>d.startsWith(n)));let L=u?` Did you mean "${u}"?`:"";throw new TypeError(`Invalid UUID: "${t}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${L}`)}function C(t){return Number.isFinite(t)?Math.min(100,Math.max(0,Math.trunc(t))):0}function R(t,e,r,i){if(!Number.isInteger(r)||r<0||r+i>e.byteLength)throw new _("INVALID_PARAMETER",`${t}: cannot read ${i} byte${i===1?"":"s"} at offset ${r} of a ${e.byteLength}-byte DataView (value too short).`)}function h(t,e=0){return R("readUint8",t,e,1),t.getUint8(e)}function E(t,e=0){return R("readUint16LE",t,e,2),t.getUint16(e,true)}var x=class{constructor(e){this.cleanups=[];this.device=e;}async connect(){await this.device.connect();}stop(){for(let e of this.cleanups.splice(0))e();}dispose(){this.stop();}async read(e){return this.device.read(this.service,e)}async write(e,r){return this.device.write(this.service,e,r)}async writeWithoutResponse(e,r){return this.device.writeWithoutResponse(this.service,e,r)}async sendChunked(e,r,i={}){return this.device.writeFragmented(this.service,e,r,{mode:"without-response",...i})}async writeValue(e,r,i){return i?.mode==="without-response"?this.device.writeWithoutResponse(this.service,e,r,i):this.device.write(this.service,e,r,i)}async getWriteLimits(){return this.device.getWriteLimits()}async getMtu(){return this.device.getMtu()}subscribe(e,r){let i=this.device.subscribe(this.service,e,r);return this.cleanups.push(i),()=>{i(),this.cleanups=this.cleanups.filter(n=>n!==i);}}onOverflow(e,r){let i=this.device.onCharacteristicOverflow(this.service,e,n=>{r(H(n));});return this.cleanups.push(i),()=>{i(),this.cleanups=this.cleanups.filter(n=>n!==i);}}};function H(t){let e=t.detail,r=e&&typeof e=="object"?e:{};return {evictedCount:typeof r.evictedCount=="number"?r.evictedCount:void 0,queueCapacity:typeof r.queueCapacity=="number"?r.queueCapacity:void 0,seq:typeof r.seq=="number"?r.seq:void 0,timestamp:typeof r.timestamp=="number"?r.timestamp:void 0}}function $(t){return !Array.isArray(t)&&Array.isArray(t.services)}function S(...t){let e=new Set;for(let r of t){let i=$(r)?r.services:r;for(let n of i)e.add(g(n));}return [...e]}var B="00000001-4c45-4b43-4942-265a524f5453",c={currentTemperature:"00000011-4c45-4b43-4942-265a524f5453",targetTemperature:"00000021-4c45-4b43-4942-265a524f5453",boostTemperature:"00000031-4c45-4b43-4942-265a524f5453",batteryLevel:"00000041-4c45-4b43-4942-265a524f5453"},G="00000002-4c45-4b43-4942-265a524f5453",j="00000003-4c45-4b43-4942-265a524f5453",we={serialNumber:"00000052-4c45-4b43-4942-265a524f5453",model:"00000032-4c45-4b43-4942-265a524f5453"},De={firmwareVersion:"000001c3-4c45-4b43-4942-265a524f5453",projectStatus:"00000023-4c45-4b43-4942-265a524f5453"},Re=null,U=[B,G,j],q=["00000001-1989-0108-1234-123456789abc","01000002-1989-0108-1234-123456789abc","10100000-5354-4f52-5a26-4249434b454c","10110000-5354-4f52-5a26-4249434b454c"],Z=["00000000-5354-4f52-5a26-4249434b454c","00001800-0000-1000-8000-00805f9b34fb"],y={crafty:U,volcano:q,veazyVenty:Z};function m(t){return E(t)/10}function O(t){let e=Math.round(t*10),r=new ArrayBuffer(2);return new DataView(r).setUint16(0,e,true),new Uint8Array(r)}function N(t){return C(h(t))}var b=class extends x{constructor(){super(...arguments);this.service=B;}async currentTemperature(){return m(await this.read(c.currentTemperature))}onCurrentTemperature(r){return this.subscribe(c.currentTemperature,i=>{r(m(i));})}onCurrentTemperatureStale(r){return this.onOverflow(c.currentTemperature,r)}async targetTemperature(){return m(await this.read(c.targetTemperature))}async setTargetTemperature(r){await this.write(c.targetTemperature,O(r));}async boost(){return m(await this.read(c.boostTemperature))}async setBoost(r){await this.write(c.boostTemperature,O(r));}async batteryLevel(){return N(await this.read(c.batteryLevel))}onBatteryLevel(r){return this.subscribe(c.batteryLevel,i=>{r(N(i));})}};b.services=U;var Se={allServices(){return S(y.crafty,y.volcano,y.veazyVenty)}};exports.STORZ_BICKEL_AUTH_GATE=Re;exports.STORZ_BICKEL_CHARACTERISTICS=c;exports.STORZ_BICKEL_FAMILY_SERVICES=y;exports.STORZ_BICKEL_SERVICE=B;exports.STORZ_BICKEL_SERVICES=U;exports.STORZ_BICKEL_SERVICE_2=G;exports.STORZ_BICKEL_SERVICE_2_CHARACTERISTICS=we;exports.STORZ_BICKEL_SERVICE_3=j;exports.STORZ_BICKEL_SERVICE_3_CHARACTERISTICS=De;exports.STORZ_BICKEL_VEAZY_VENTY_SERVICES=Z;exports.STORZ_BICKEL_VOLCANO_SERVICES=q;exports.StorzBickel=Se;exports.StorzBickelProfile=b;exports.decodeBatteryPercent=N;exports.decodeTemperatureDeciCelsius=m;exports.encodeTemperatureDeciCelsius=O;//# sourceMappingURL=storz-bickel.js.map
//# sourceMappingURL=storz-bickel.js.map

Sorry, the diff of this file is too big to display

import {a as a$1}from'../../chunk-SOZ26EXK.mjs';import {l as l$1,j,k}from'../../chunk-GAX5WAKV.mjs';import'../../chunk-67S2RHE2.mjs';import'../../chunk-BSOWECSQ.mjs';import'../../chunk-L7SIDO2A.mjs';import'../../chunk-3BDZNBBD.mjs';import {b}from'../../chunk-FANWIUKA.mjs';import'../../chunk-33IHM3NV.mjs';var C="00000001-4c45-4b43-4942-265a524f5453",r={currentTemperature:"00000011-4c45-4b43-4942-265a524f5453",targetTemperature:"00000021-4c45-4b43-4942-265a524f5453",boostTemperature:"00000031-4c45-4b43-4942-265a524f5453",batteryLevel:"00000041-4c45-4b43-4942-265a524f5453"},S="00000002-4c45-4b43-4942-265a524f5453",_="00000003-4c45-4b43-4942-265a524f5453",l={serialNumber:"00000052-4c45-4b43-4942-265a524f5453",model:"00000032-4c45-4b43-4942-265a524f5453"},w={firmwareVersion:"000001c3-4c45-4b43-4942-265a524f5453",projectStatus:"00000023-4c45-4b43-4942-265a524f5453"},L=null,f=[C,S,_],v=["00000001-1989-0108-1234-123456789abc","01000002-1989-0108-1234-123456789abc","10100000-5354-4f52-5a26-4249434b454c","10110000-5354-4f52-5a26-4249434b454c"],I=["00000000-5354-4f52-5a26-4249434b454c","00001800-0000-1000-8000-00805f9b34fb"],o={crafty:f,volcano:v,veazyVenty:I};function a(t){return l$1(t)/10}function E(t){let c=Math.round(t*10),e=new ArrayBuffer(2);return new DataView(e).setUint16(0,c,true),new Uint8Array(e)}function T(t){return j(k(t))}var s=class extends b{constructor(){super(...arguments);this.service=C;}async currentTemperature(){return a(await this.read(r.currentTemperature))}onCurrentTemperature(e){return this.subscribe(r.currentTemperature,n=>{e(a(n));})}onCurrentTemperatureStale(e){return this.onOverflow(r.currentTemperature,e)}async targetTemperature(){return a(await this.read(r.targetTemperature))}async setTargetTemperature(e){await this.write(r.targetTemperature,E(e));}async boost(){return a(await this.read(r.boostTemperature))}async setBoost(e){await this.write(r.boostTemperature,E(e));}async batteryLevel(){return T(await this.read(r.batteryLevel))}onBatteryLevel(e){return this.subscribe(r.batteryLevel,n=>{e(T(n));})}};s.services=f;var V={allServices(){return a$1(o.crafty,o.volcano,o.veazyVenty)}};export{L as STORZ_BICKEL_AUTH_GATE,r as STORZ_BICKEL_CHARACTERISTICS,o as STORZ_BICKEL_FAMILY_SERVICES,C as STORZ_BICKEL_SERVICE,f as STORZ_BICKEL_SERVICES,S as STORZ_BICKEL_SERVICE_2,l as STORZ_BICKEL_SERVICE_2_CHARACTERISTICS,_ as STORZ_BICKEL_SERVICE_3,w as STORZ_BICKEL_SERVICE_3_CHARACTERISTICS,I as STORZ_BICKEL_VEAZY_VENTY_SERVICES,v as STORZ_BICKEL_VOLCANO_SERVICES,V as StorzBickel,s as StorzBickelProfile,T as decodeBatteryPercent,a as decodeTemperatureDeciCelsius,E as encodeTemperatureDeciCelsius};//# sourceMappingURL=storz-bickel.mjs.map
//# sourceMappingURL=storz-bickel.mjs.map
{"version":3,"sources":["../../../src/experimental/profiles/storz-bickel.ts"],"names":["STORZ_BICKEL_SERVICE","STORZ_BICKEL_CHARACTERISTICS","STORZ_BICKEL_SERVICE_2","STORZ_BICKEL_SERVICE_3","STORZ_BICKEL_SERVICE_2_CHARACTERISTICS","STORZ_BICKEL_SERVICE_3_CHARACTERISTICS","STORZ_BICKEL_AUTH_GATE","STORZ_BICKEL_SERVICES","STORZ_BICKEL_VOLCANO_SERVICES","STORZ_BICKEL_VEAZY_VENTY_SERVICES","STORZ_BICKEL_FAMILY_SERVICES","decodeTemperatureDeciCelsius","dv","readUint16LE","encodeTemperatureDeciCelsius","celsius","raw","buffer","decodeBatteryPercent","clampPercent","readUint8","StorzBickelProfile","BaseProfile","callback","StorzBickel","deriveOptionalServices"],"mappings":"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","file":"storz-bickel.mjs","sourcesContent":["import { readUint8, readUint16LE, clampPercent, type Percentage, type NativeOverflowEvent } from '../../index';\nimport { BaseProfile } from '../../profiles/base';\nimport { deriveOptionalServices } from '../../profiles/services';\n\n/**\n * Storz & Bickel Crafty / Crafty+ / Mighty / Mighty+ vaporizer profile.\n *\n * @experimental UUIDs from PUBLIC reverse-engineering — standard-validated,\n * on-device deferred to operator. Storz & Bickel publishes no official GATT\n * specification; every UUID and decode below is derived from the vendor's own\n * (minified) Web Bluetooth bundle plus independent community re-implementations,\n * then cross-checked. The accessors exposed here cover only the HIGH-confidence\n * characteristics (corroborated by the official bundle AND >= 1 independent\n * source).\n *\n * Standard-grounded validation (the present trust basis): conformance to the\n * Web Bluetooth Living Standard (https://webbluetoothcg.github.io/web-bluetooth/)\n * is the authority for the runtime GATT contracts each accessor exercises —\n * - §4 Device Discovery — `requestDevice({ filters, optionalServices })`\n * (GAP-1 fix: optionalServices must be declared so iOS discover resolves),\n * - §6 GATT Interaction — `getPrimaryService(uuid)` / `getCharacteristic(uuid)`\n * (BluetoothRemoteGATTService uuid per §6.3),\n * - §6.4 — `characteristic.readValue()` returns `Promise<DataView>`; the\n * profile's `decodeTemperatureDeciCelsius` consumes `DataView.getInt16`\n * little-endian and `decodeBatteryPercent` consumes `DataView.getUint8` —\n * spec-conformant reads,\n * - §6.4 — `characteristic.writeValue(value: BufferSource)` /\n * `writeValueWithResponse` / `writeValueWithoutResponse` (BufferSource input;\n * the profile's `encodeTemperatureDeciCelsius` produces a `DataView` — a\n * valid BufferSource),\n * - §6.4 — `characteristic.startNotifications()` => `characteristicvaluechanged`\n * event with `event.target.value: DataView`; the profile subscribes\n * exclusively via `BaseProfile.subscribe` (per the \"Notifications\" note above)\n * and never writes a CCCD/SCCD descriptor itself — strictly W3C GATT,\n * - §7.1 Standardized UUIDs — `BluetoothUUID.canonicalUUID` resolves to the\n * lowercase 128-bit form (e.g. `00001818-0000-1000-8000-00805f9b34fb`);\n * this profile stores UUIDs uppercase internally (beacio convention — see\n * `NormalizedUUID`) and emits them lowercase to web-facing payloads (matches\n * the spec's external canonical form).\n * The S&B-derived UUID/opcode VALUES themselves are interface-only\n * interoperability facts (see the 5+ source corroboration block immediately\n * below) and have NOT been exercised on physical hardware through this library.\n * On-device confirmation against a real Volcano/Crafty/Venty is an\n * operator-supplied gate, tracked separately at\n * `outreach/storz-bickel/onboarding/reviews/PR178-fixes/IP-02.md`. Treat\n * reads/writes as standard-conformant in SHAPE but provisional in VALUE until\n * device-confirmed.\n *\n * Device family & encoding\n * -------------------------\n * The Crafty/Mighty line shares a single GATT tree. The 96-bit vendor base is\n * ASCII `STORZ&BICKEL` written **byte-reversed** (`…-4c45-4b43-4942-265a524f5453`,\n * which decodes to `LEKCIB&ZROTS`). The sibling Volcano Hybrid line uses the\n * *big-endian* form of the same base (`…-5354-4f52-5a26-4249434b454c`) and a\n * different characteristic map; it is intentionally out of scope for this\n * profile. Mighty/Mighty+ reuse the identical Crafty tree and are\n * disambiguated at runtime via the model characteristic (`0x22`).\n *\n * Temperatures are little-endian uint16 in tenths of a degree Celsius\n * (deciCelsius): raw `1822` == `182.2 °C`. Battery level is a little-endian\n * uint16 percentage (0–100; only the low byte is used).\n *\n * Notifications (current temperature, battery) are enabled exclusively through\n * {@link BaseProfile.subscribe} (`startNotifications()`); this profile never\n * reads or writes a CCCD/SCCD descriptor itself — strictly W3C\n * `navigator.bluetooth` GATT.\n *\n * Sources (verified 2026-06-14):\n * - Official S&B Web Bluetooth app bundle (app.storz-bickel.com, js/main.js +\n * crafty.js): `serviceUuidCrafty1`, `charactersiticCurrTemperatureChanged`,\n * `characteristicWriteTemp`, `characteristicWriteBoostTemp`,\n * `characteristicPowerChanged`.\n * - J-Cat/crafty-control craftyUuids.ts — https://github.com/J-Cat/crafty-control\n * (ServiceUuid, TemperatureUuid 0x11, SetPointUuid 0x21, BoostUuid 0x31,\n * BatteryUuid 0x41; verified from source).\n * - ligi/VaporizerControl CRAFTY_UUIDS.java — https://github.com/ligi/VaporizerControl\n * (DATA_SERVICE craft(1), TEMPERATURE craft(0x11), SETPOINT craft(0x21),\n * BOOST craft(0x31), BATTERY craft(0x41); verified from source).\n * - gsasouza/sb-crafty-watch-os — https://github.com/gsasouza/sb-crafty-watch-os\n * (battery + current-temperature handling; verified from source).\n * - firsttris/reactive-volcano-app — https://github.com/firsttris/reactive-volcano-app\n * (currTemperatureChanged, writeTemp, writeBoostTemp).\n * - 0022111/sbtracker — https://github.com/0022111/sbtracker\n * (BleConstants.kt: \"// Crafty/Mighty+ (older or traditional protocol)\").\n *\n * @example\n * ```ts\n * import { StorzBickelProfile } from '@beacio/core/experimental/profiles/storz-bickel';\n *\n * // requestDevice({ filters: [{ namePrefix: 'S&B' }], optionalServices: [\n * // '00000001-4c45-4b43-4942-265a524f5453',\n * // ] })\n * const vape = new StorzBickelProfile(device);\n * await vape.connect();\n *\n * await vape.setTargetTemperature(182.2);\n *\n * const off = vape.onCurrentTemperature((c) => console.log(`now ${c} °C`));\n * console.log('battery', await vape.batteryLevel(), '%');\n *\n * off();\n * vape.stop();\n * ```\n */\n\n/** Crafty/Mighty PRIMARY DATA SERVICE (live temp, setpoint, boost, battery). HIGH confidence. */\nexport const STORZ_BICKEL_SERVICE = '00000001-4c45-4b43-4942-265a524f5453';\n\n/**\n * HIGH-confidence Crafty/Mighty characteristic UUIDs surfaced by this profile.\n *\n * Only characteristics rated HIGH in the consolidated reverse-engineering data\n * (official bundle + >= 1 independent corroborator) are included. Lower-\n * confidence and diagnostic characteristics are intentionally omitted until\n * device-confirmed.\n */\nexport const STORZ_BICKEL_CHARACTERISTICS = {\n /** Current/live temperature. read/notify; deciCelsius LE. HIGH. */\n currentTemperature: '00000011-4c45-4b43-4942-265a524f5453',\n /** Target/setpoint temperature. read/write; deciCelsius LE (e.g. 1822 = 182.2 °C). HIGH. */\n targetTemperature: '00000021-4c45-4b43-4942-265a524f5453',\n /** Boost temperature offset. read/write; deciCelsius LE. (NOT heater on/off.) HIGH. */\n boostTemperature: '00000031-4c45-4b43-4942-265a524f5453',\n /** Battery level percent. read/notify; uint16 LE (low byte used). HIGH. */\n batteryLevel: '00000041-4c45-4b43-4942-265a524f5453',\n} as const;\n\n/**\n * Crafty/Mighty SECONDARY service (device-info: serial number, model/firmware).\n * The official bundle opens this via `getPrimaryService(serviceUuidCrafty2)`\n * (`main.js:119`, `crafty.js`). Read-only metadata — not surfaced by the\n * temperature/battery accessors. MEDIUM confidence (official bundle only).\n */\nexport const STORZ_BICKEL_SERVICE_2 = '00000002-4c45-4b43-4942-265a524f5453';\n\n/**\n * Crafty/Mighty TERTIARY service (project/status registers, model, hour-meter).\n * Opened via `getPrimaryService(serviceUuidCrafty3)` (`main.js:120`, `crafty.js`).\n * MEDIUM confidence (official bundle only).\n */\nexport const STORZ_BICKEL_SERVICE_3 = '00000003-4c45-4b43-4942-265a524f5453';\n\n/**\n * Read-only metadata characteristics on {@link STORZ_BICKEL_SERVICE_2}, pinned\n * with provenance (`crafty.js` `primaryServiceCraftyUuid2.getCharacteristic`).\n * Surfaced for callers that want device-info; not used by this profile's\n * temperature/battery accessors. MEDIUM confidence.\n */\nexport const STORZ_BICKEL_SERVICE_2_CHARACTERISTICS = {\n /** Serial number. read; UTF-8 (first 8 chars). crafty.js:224. */\n serialNumber: '00000052-4c45-4b43-4942-265a524f5453',\n /** Model identifier. read. crafty.js:261. */\n model: '00000032-4c45-4b43-4942-265a524f5453',\n} as const;\n\n/**\n * Read-only metadata characteristics on {@link STORZ_BICKEL_SERVICE_3}, pinned\n * with provenance (`crafty.js` `primaryServiceCraftyUuid3.getCharacteristic`).\n * MEDIUM confidence.\n */\nexport const STORZ_BICKEL_SERVICE_3_CHARACTERISTICS = {\n /** Firmware/BLE version string. read. crafty.js:323. */\n firmwareVersion: '000001c3-4c45-4b43-4942-265a524f5453',\n /** Project-status register (model/state flags). read/notify. crafty.js:338. */\n projectStatus: '00000023-4c45-4b43-4942-265a524f5453',\n} as const;\n\n/**\n * The S&B Crafty/Mighty GATT is NOT auth-gated: every documented characteristic\n * is reachable after a plain `getPrimaryService` + `getCharacteristic` with no\n * pairing/bonding or write-to-unlock handshake (confirmed across the official\n * bundle and the independent community re-implementations). This sentinel\n * records that fact for integrators / @beacio/detect rather than leaving the\n * absence of an auth gate implicit — there is no characteristic to write first.\n */\nexport const STORZ_BICKEL_AUTH_GATE = null;\n\n/**\n * Crafty/Mighty (Family A) data services, in `getPrimaryService` order: the\n * primary data service ({@link STORZ_BICKEL_SERVICE}) plus the device-info and\n * project-register services. Canonical lowercase. This is the per-profile\n * `services` array read by {@link deriveOptionalServices} (and exposed as the\n * static `StorzBickelProfile.services`).\n *\n * Provenance: `captured/beautified/main.js:118-120` (serviceUuidCrafty1/2/3).\n */\nexport const STORZ_BICKEL_SERVICES = [\n STORZ_BICKEL_SERVICE,\n STORZ_BICKEL_SERVICE_2,\n STORZ_BICKEL_SERVICE_3,\n] as const;\n\n/**\n * Volcano HYBRID (Family B) services actually opened by the vendor bundle, in\n * `getPrimaryService` order (`volcano.js:550/554/558/562`). This family is out\n * of scope for {@link StorzBickelProfile}'s accessors but is included in the\n * connect-time `optionalServices` bundle so the picker can reach a Volcano.\n *\n * NOTE — these four UUIDs do NOT share a single base; the bundle mixes two:\n * - volcano1/volcano2 use a generic-vendor base `…-1989-0108-1234-123456789abc`\n * (NOT an S&B base at all).\n * - volcano3/volcano4 use the *big-endian* S&B base `…-5354-4f52-5a26-4249434b454c`\n * (ASCII `STORZ&BICKEL`), the same form used by the Veazy/Venty (QVAP) family,\n * and the byte-reverse of Crafty's `…-4c45-4b43-4942-265a524f5453` base.\n * Each line is individually source-cited to the vendor bundle; the values are\n * pinned by the SB-SDK-02 regression below. PENDING on-device confirmation.\n *\n * `serviceUuidVolcano5` (`10130000-…`, `main.js:125`) is DELIBERATELY excluded:\n * it is declared in the bundle but never `getPrimaryService`'d.\n */\nexport const STORZ_BICKEL_VOLCANO_SERVICES = [\n '00000001-1989-0108-1234-123456789abc', // volcano.js:550 serviceUuidVolcano1 — generic-vendor base (1989-0108)\n '01000002-1989-0108-1234-123456789abc', // volcano.js:554 serviceUuidVolcano2 — generic-vendor base (1989-0108)\n '10100000-5354-4f52-5a26-4249434b454c', // volcano.js:558 serviceUuidVolcano3 — big-endian S&B base\n '10110000-5354-4f52-5a26-4249434b454c', // volcano.js:562 serviceUuidVolcano4 — big-endian S&B base\n] as const;\n\n/**\n * Veazy / Venty (Family C, the \"QVAP\" bundle) services opened by the vendor\n * bundle (`qvap.js:556/582`): the vendor data service plus SIG `generic_access`.\n * Out of scope for {@link StorzBickelProfile}'s accessors; included in the\n * connect-time `optionalServices` bundle.\n */\nexport const STORZ_BICKEL_VEAZY_VENTY_SERVICES = [\n '00000000-5354-4f52-5a26-4249434b454c', // qvap.js:556 serviceUuidQvap\n '00001800-0000-1000-8000-00805f9b34fb', // qvap.js:582 serviceUuidQvap1 (generic_access)\n] as const;\n\n/**\n * The three S&B device families' service arrays, keyed by family. Consumed by\n * {@link StorzBickel.allServices} to build the de-duped multi-family\n * `optionalServices` bundle for a picker that should reach ANY S&B device.\n */\nexport const STORZ_BICKEL_FAMILY_SERVICES = {\n crafty: STORZ_BICKEL_SERVICES,\n volcano: STORZ_BICKEL_VOLCANO_SERVICES,\n veazyVenty: STORZ_BICKEL_VEAZY_VENTY_SERVICES,\n} as const;\n\n/**\n * Decode a Storz & Bickel temperature characteristic value.\n *\n * Wire format: little-endian uint16 in tenths of a degree Celsius\n * (deciCelsius). Raw `1822` decodes to `182.2`.\n *\n * @param dv - Raw characteristic value (current, target, or boost temperature).\n * @returns Temperature in degrees Celsius.\n * @throws {BeacioError} INVALID_PARAMETER if the value is shorter than 2 bytes.\n */\nexport function decodeTemperatureDeciCelsius(dv: DataView): number {\n return readUint16LE(dv) / 10;\n}\n\n/**\n * Encode a degrees-Celsius temperature into the Storz & Bickel wire format:\n * little-endian uint16 deciCelsius, rounded to the nearest 0.1 °C.\n *\n * @param celsius - Temperature in degrees Celsius (e.g. `182.2`).\n * @returns A 2-byte little-endian payload (raw deciCelsius).\n */\nexport function encodeTemperatureDeciCelsius(celsius: number): Uint8Array<ArrayBuffer> {\n const raw = Math.round(celsius * 10);\n const buffer = new ArrayBuffer(2);\n new DataView(buffer).setUint16(0, raw, true);\n return new Uint8Array(buffer);\n}\n\n/**\n * Decode the Storz & Bickel battery-level characteristic value.\n *\n * Wire format: little-endian uint16 percentage (0–100); only the low byte is\n * populated in practice. The official bundle labels this `power`; community\n * sources label it `battery` — both are the same battery-level read path.\n *\n * @param dv - Raw battery characteristic value.\n * @returns {Percentage} Battery level as an integer percentage (0–100).\n * @throws {BeacioError} INVALID_PARAMETER if the value is shorter than 1 byte.\n */\nexport function decodeBatteryPercent(dv: DataView): Percentage {\n // Wire format documents a uint16 LE where ONLY the low byte carries 0..100;\n // the high byte is unused/garbage. For a little-endian uint16 the byte at\n // offset 0 IS the low byte, so readUint8(dv) returns exactly the documented\n // payload and structurally discards the high byte (strictly better than\n // `readUint16LE(dv) & 0xff`). clampPercent saturates any malformed frame into\n // 0..100 so a notification decode is total and never throws on bad data.\n return clampPercent(readUint8(dv));\n}\n\nexport class StorzBickelProfile extends BaseProfile {\n /**\n * Crafty/Mighty (Family A) services this profile's device may reach after\n * connection. Read by {@link deriveOptionalServices} so a caller can pass the\n * profile class itself instead of hand-copying {@link STORZ_BICKEL_SERVICES}.\n */\n static readonly services = STORZ_BICKEL_SERVICES;\n\n protected readonly service = STORZ_BICKEL_SERVICE;\n\n /**\n * Read the current/live heater temperature (°C).\n * Characteristic `00000011-…` (read/notify), deciCelsius LE.\n */\n async currentTemperature(): Promise<number> {\n return decodeTemperatureDeciCelsius(await this.read(STORZ_BICKEL_CHARACTERISTICS.currentTemperature));\n }\n\n /**\n * Subscribe to live temperature updates (°C). Returns an unsubscribe\n * function. Notifications are enabled via {@link BaseProfile.subscribe}\n * only — no CCCD write.\n */\n onCurrentTemperature(callback: (celsius: number) => void): () => void {\n return this.subscribe(STORZ_BICKEL_CHARACTERISTICS.currentTemperature, (dv) => {\n callback(decodeTemperatureDeciCelsius(dv));\n });\n }\n\n /**\n * Observe NATIVE notification-queue overflows on the live-temperature stream.\n * Returns an unsubscribe function (also cleaned up by {@link BaseProfile.stop}).\n *\n * Under sustained high-frequency notifications Safari's bounded Swift\n * `EventQueue` evicts samples rather than dropping them silently, and the\n * polyfill surfaces each eviction as `beacio:overflow`. When this fires, the\n * temperature shown from the last notified value is potentially stale: the\n * recommended response is to issue a fresh {@link currentTemperature} read and\n * repaint the gauge from that value rather than trusting the last\n * `onCurrentTemperature` sample.\n *\n * Thin-JS: this only surfaces the existing native signal; it changes no flow\n * control. The `event` carries the eviction metadata\n * ({@link NativeOverflowEvent}: `evictedCount`, `queueCapacity`, `seq`,\n * `timestamp`).\n *\n * @example\n * ```ts\n * vape.onCurrentTemperatureStale(async () => {\n * // notifications were evicted — resync the gauge from a fresh read\n * updateGauge(await vape.currentTemperature());\n * });\n * ```\n */\n onCurrentTemperatureStale(callback: (event: NativeOverflowEvent) => void): () => void {\n return this.onOverflow(STORZ_BICKEL_CHARACTERISTICS.currentTemperature, callback);\n }\n\n /**\n * Read the target/setpoint temperature (°C).\n * Characteristic `00000021-…` (read/write), deciCelsius LE.\n */\n async targetTemperature(): Promise<number> {\n return decodeTemperatureDeciCelsius(await this.read(STORZ_BICKEL_CHARACTERISTICS.targetTemperature));\n }\n\n /**\n * Write the target/setpoint temperature (°C) using write-with-response.\n * Characteristic `00000021-…`, deciCelsius LE.\n *\n * @param celsius - Desired setpoint in degrees Celsius (e.g. `182.2`).\n */\n async setTargetTemperature(celsius: number): Promise<void> {\n await this.write(STORZ_BICKEL_CHARACTERISTICS.targetTemperature, encodeTemperatureDeciCelsius(celsius));\n }\n\n /**\n * Read the boost temperature **offset** (°C). This is added on top of the\n * setpoint while boost is engaged — it is NOT a heater on/off control.\n * Characteristic `00000031-…` (read/write), deciCelsius LE.\n */\n async boost(): Promise<number> {\n return decodeTemperatureDeciCelsius(await this.read(STORZ_BICKEL_CHARACTERISTICS.boostTemperature));\n }\n\n /**\n * Write the boost temperature **offset** (°C) using write-with-response.\n * Characteristic `00000031-…`, deciCelsius LE.\n *\n * @param celsius - Boost offset in degrees Celsius (e.g. `15`).\n */\n async setBoost(celsius: number): Promise<void> {\n await this.write(STORZ_BICKEL_CHARACTERISTICS.boostTemperature, encodeTemperatureDeciCelsius(celsius));\n }\n\n /**\n * Read the battery level (0–100 %).\n * Characteristic `00000041-…` (read/notify), uint16 LE.\n */\n async batteryLevel(): Promise<Percentage> {\n return decodeBatteryPercent(await this.read(STORZ_BICKEL_CHARACTERISTICS.batteryLevel));\n }\n\n /**\n * Subscribe to battery-level updates (0–100 %). Returns an unsubscribe\n * function. Notifications are enabled via {@link BaseProfile.subscribe}\n * only — no CCCD write.\n */\n onBatteryLevel(callback: (percent: Percentage) => void): () => void {\n return this.subscribe(STORZ_BICKEL_CHARACTERISTICS.batteryLevel, (dv) => {\n callback(decodeBatteryPercent(dv));\n });\n }\n}\n\n/**\n * Storz & Bickel vendor-level helpers spanning ALL device families (Crafty/\n * Mighty, Volcano HYBRID, Veazy/Venty), as distinct from the single-family\n * {@link StorzBickelProfile}.\n */\nexport const StorzBickel = {\n /**\n * The de-duped, canonical-lowercase union of every `getPrimaryService`-opened\n * service across all three S&B families ({@link STORZ_BICKEL_FAMILY_SERVICES}).\n * Pass this as `optionalServices` to a single `requestDevice` so the picker can\n * reach ANY Storz & Bickel device regardless of family.\n *\n * @returns De-duped canonical service UUIDs (first-seen order).\n *\n * @example\n * ```ts\n * const device = await ble.requestDevice({\n * filters: [{ namePrefix: 'S&B' }, { namePrefix: 'STORZ' }],\n * optionalServices: StorzBickel.allServices(),\n * });\n * ```\n */\n allServices(): string[] {\n return deriveOptionalServices(\n STORZ_BICKEL_FAMILY_SERVICES.crafty,\n STORZ_BICKEL_FAMILY_SERVICES.volcano,\n STORZ_BICKEL_FAMILY_SERVICES.veazyVenty,\n );\n },\n} as const;\n"]}
{
"version": "1.2.0",
"generatedAt": "2026-07-28T16:12:45.259Z",
"entries": {
"auto.mjs": {
"sha384": "aYL/UPDpKVEWxjTCu8WBs0SUHLu2nopC0ba1P3c8KsnYQSZgqpTUHbOTcWqITIbY",
"size": 6492
},
"browser-auto.global.js": {
"sha384": "I3KOFx/h+X1L+S6nylKMt+s3jcn/9nSlLnYENbavFPYZYqhd3L/E75pZC9WO7zPp",
"size": 53486
}
}
}
export{a as CDN_STUB_MARKER,b as detectPlatform,c as getBluetoothAPI}from'./chunk-BSOWECSQ.mjs';//# sourceMappingURL=platform-GVNO2UVN.mjs.map
//# sourceMappingURL=platform-GVNO2UVN.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"platform-GVNO2UVN.mjs"}
import { B as BaseProfile } from '../base-YuFhZsjv.mjs';
import '../device-B5NsJWvh.mjs';
/**
* BLE Battery Service profile (UUID 0x180F).
*
* Reads and subscribes to the Battery Level characteristic (0x2A19),
* which reports the current charge level as a percentage (0--100).
*
* @example
* ```ts
* import { BatteryProfile } from '@beacio/core/profiles';
*
* const battery = new BatteryProfile(device);
* await battery.connect();
*
* // One-shot read
* const level = await battery.readLevel();
* console.log(`Battery: ${level}%`);
*
* // Subscribe to level changes
* const unsubscribe = battery.onLevelChange((level) => {
* console.log(`Battery changed: ${level}%`);
* });
*
* // Clean up
* unsubscribe();
* battery.stop();
* ```
*/
declare class BatteryProfile extends BaseProfile {
protected readonly service = "battery_service";
/** Read current battery level (0-100). */
readLevel(): Promise<number>;
/** Subscribe to battery level changes. Returns unsubscribe function. */
onLevelChange(callback: (level: number) => void): () => void;
}
export { BatteryProfile };
import { B as BaseProfile } from '../base-BnHcG-k7.js';
import '../device-B5NsJWvh.js';
/**
* BLE Battery Service profile (UUID 0x180F).
*
* Reads and subscribes to the Battery Level characteristic (0x2A19),
* which reports the current charge level as a percentage (0--100).
*
* @example
* ```ts
* import { BatteryProfile } from '@beacio/core/profiles';
*
* const battery = new BatteryProfile(device);
* await battery.connect();
*
* // One-shot read
* const level = await battery.readLevel();
* console.log(`Battery: ${level}%`);
*
* // Subscribe to level changes
* const unsubscribe = battery.onLevelChange((level) => {
* console.log(`Battery changed: ${level}%`);
* });
*
* // Clean up
* unsubscribe();
* battery.stop();
* ```
*/
declare class BatteryProfile extends BaseProfile {
protected readonly service = "battery_service";
/** Read current battery level (0-100). */
readLevel(): Promise<number>;
/** Subscribe to battery level changes. Returns unsubscribe function. */
onLevelChange(callback: (level: number) => void): () => void;
}
export { BatteryProfile };
'use strict';var p=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),u={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the Beacio iOS app and enable the Safari extension. Use @beacio/core/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this Beacio instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},C=/\b(bluefy|web ble browser|webble browser)\b/gi;function f(i){let e=i.split(`
`,1)[0]??"";return e=e.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),e=e.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),e=e.replace(C,""),e=e.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),e=e.replace(/[\s.,;:]+$/g,"").trim(),e}var o=class i extends Error{constructor(e,t,r){let n=u[e];super(t??n),this.name="BeacioError",this.code=e,this.suggestion=u[e],this.isRetriable=p.has(e),this.retryAfterMs=r?.retryAfterMs;}static from(e,t="GATT_OPERATION_FAILED"){if(e instanceof i)return e;let r=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,n=e instanceof Error?e.message:String(e),a=f(n)||void 0,s=n.toLowerCase();switch(r){case "TypeError":return new i("INVALID_PARAMETER",a);case "NotFoundError":return new i("DEVICE_NOT_FOUND",a);case "NotAllowedError":case "SecurityError":return new i("PERMISSION_DENIED",a);case "NetworkError":return new i("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3});case "TimeoutError":return new i("TIMEOUT",a,{retryAfterMs:1e3});case "InvalidStateError":if(s.includes("disconnect"))return new i("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3});break;}return n.includes("User cancelled")||n.includes("User canceled")?new i("USER_CANCELLED"):s.includes("no devices found")||n.includes("No Devices")?new i("DEVICE_NOT_FOUND"):n.includes("No Services matching")||s.includes("service not found")?new i("SERVICE_NOT_FOUND",a):n.includes("No Characteristics matching")||s.includes("characteristic not found")?new i("CHARACTERISTIC_NOT_FOUND",a):n.includes("GATT Server is disconnected")||s.includes("disconnected")?new i("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3}):s.includes("not supported")&&s.includes("read")?new i("CHARACTERISTIC_NOT_READABLE",a):s.includes("not supported")&&s.includes("write")?new i("CHARACTERISTIC_NOT_WRITABLE",a):s.includes("not supported")&&s.includes("notif")?new i("CHARACTERISTIC_NOT_NOTIFIABLE",a):s.includes("permission")?new i("PERMISSION_DENIED",a):new i(t,a)}};function E(i,e,t,r){if(!Number.isInteger(t)||t<0||t+r>e.byteLength)throw new o("INVALID_PARAMETER",`${i}: cannot read ${r} byte${""} at offset ${t} of a ${e.byteLength}-byte DataView (value too short).`)}function c(i,e=0){return E("readUint8",i,e,1),i.getUint8(e)}var d=class{constructor(e){this.cleanups=[];this.device=e;}async connect(){await this.device.connect();}stop(){for(let e of this.cleanups.splice(0))e();}dispose(){this.stop();}async read(e){return this.device.read(this.service,e)}async write(e,t){return this.device.write(this.service,e,t)}async writeWithoutResponse(e,t){return this.device.writeWithoutResponse(this.service,e,t)}async sendChunked(e,t,r={}){return this.device.writeFragmented(this.service,e,t,{mode:"without-response",...r})}async writeValue(e,t,r){return r?.mode==="without-response"?this.device.writeWithoutResponse(this.service,e,t,r):this.device.write(this.service,e,t,r)}async getWriteLimits(){return this.device.getWriteLimits()}async getMtu(){return this.device.getMtu()}subscribe(e,t){let r=this.device.subscribe(this.service,e,t);return this.cleanups.push(r),()=>{r(),this.cleanups=this.cleanups.filter(n=>n!==r);}}onOverflow(e,t){let r=this.device.onCharacteristicOverflow(this.service,e,n=>{t(h(n));});return this.cleanups.push(r),()=>{r(),this.cleanups=this.cleanups.filter(n=>n!==r);}}};function h(i){let e=i.detail,t=e&&typeof e=="object"?e:{};return {evictedCount:typeof t.evictedCount=="number"?t.evictedCount:void 0,queueCapacity:typeof t.queueCapacity=="number"?t.queueCapacity:void 0,seq:typeof t.seq=="number"?t.seq:void 0,timestamp:typeof t.timestamp=="number"?t.timestamp:void 0}}var l=class extends d{constructor(){super(...arguments);this.service="battery_service";}async readLevel(){let t=await this.read("battery_level");return c(t)}onLevelChange(t){return this.subscribe("battery_level",r=>{t(c(r));})}};exports.BatteryProfile=l;//# sourceMappingURL=battery.js.map
//# sourceMappingURL=battery.js.map
{"version":3,"sources":["../../src/errors.ts","../../src/dataview-helpers.ts","../../src/profiles/base.ts","../../src/profiles/battery.ts"],"names":["RETRIABLE_CODES","SUGGESTIONS","COMPETITOR_TOKENS","sanitizeNativeMessage","raw","line","BeacioError","_BeacioError","code","message","options","defaultMessage","error","domName","rawMsg","msg","lowerMsg","assertReadable","reader","dv","offset","size","readUint8","BaseProfile","device","cleanup","characteristic","value","callback","unsubscribe","candidate","event","decodeNativeOverflow","detail","meta","BatteryProfile"],"mappings":"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","file":"battery.js","sourcesContent":["/**\n * Machine-readable error codes for all Beacio operations.\n * Use in catch blocks to handle specific failure modes.\n *\n * @example\n * ```typescript\n * try {\n * await device.read('heart_rate', 'heart_rate_measurement')\n * } catch (e) {\n * if (e instanceof BeacioError) {\n * switch (e.code) {\n * case 'DEVICE_DISCONNECTED': await device.connect(); break;\n * case 'CHARACTERISTIC_NOT_READABLE': device.subscribe(...); break;\n * default: console.error(e.suggestion);\n * }\n * }\n * }\n * ```\n */\nexport type BeacioErrorCode =\n /** Invalid argument passed to an SDK method (e.g. negative timeout, malformed UUID). Not retriable. */\n | 'INVALID_PARAMETER'\n /** Browser or platform does not support Web Bluetooth at all. Not retriable. */\n | 'BLUETOOTH_UNAVAILABLE'\n /** The Beacio Safari extension is not installed. Show an install banner via `@beacio/core/detect`. Not retriable. */\n | 'EXTENSION_NOT_INSTALLED'\n /** User denied Bluetooth permission, or the call was not triggered by a user gesture. Not retriable. */\n | 'PERMISSION_DENIED'\n /** No BLE device matched the given scan filters, or the device picker returned empty. Not retriable. */\n | 'DEVICE_NOT_FOUND'\n /** GATT operation attempted on a disconnected device. Retriable -- call `connect()` first. */\n | 'DEVICE_DISCONNECTED'\n /** Device did not respond within the connection timeout window. Retriable -- check range and advertising state. */\n | 'CONNECTION_TIMEOUT'\n /** The requested GATT service UUID was not found on the connected device. Not retriable. */\n | 'SERVICE_NOT_FOUND'\n /** The requested characteristic UUID was not found in the specified service. Not retriable. */\n | 'CHARACTERISTIC_NOT_FOUND'\n /** The characteristic does not support the read property. Use `subscribe()` for notify-only characteristics. Not retriable. */\n | 'CHARACTERISTIC_NOT_READABLE'\n /** The characteristic does not support write or writeWithoutResponse. Not retriable. */\n | 'CHARACTERISTIC_NOT_WRITABLE'\n /** The characteristic does not support notify or indicate. Use `read()` for polling. Not retriable. */\n | 'CHARACTERISTIC_NOT_NOTIFIABLE'\n /** Generic GATT failure (device busy, stack error, disconnected mid-operation). Retriable. */\n | 'GATT_OPERATION_FAILED'\n /** A BLE scan is already running. Stop the current scan before starting a new one. Retriable. */\n | 'SCAN_ALREADY_IN_PROGRESS'\n /** `Beacio.maxConnections` limit reached. Disconnect another device before connecting. Not retriable. */\n | 'CONNECTION_LIMIT_REACHED'\n /** User dismissed the device picker without selecting a device. Not retriable. */\n | 'USER_CANCELLED'\n /** A read/write/connect operation did not complete within the specified timeout. Retriable. */\n | 'TIMEOUT'\n /** A chunked write was only partially completed. Retry with smaller chunks or reconnect. Retriable. */\n | 'WRITE_INCOMPLETE';\n\n/**\n * Configuration for {@link withRetry}.\n *\n * **Backoff formula:** `delay = delayMs * backoffMultiplier^(attempt - 1)`\n *\n * All fields are required with documented sentinel defaults (no optional arguments).\n * Pass {@link DEFAULT_RETRY_OPTIONS} (optionally spread with overrides) rather than a\n * partial object. A sentinel in any field resolves to that field's documented default.\n *\n * @see {@link withRetry}\n * @see {@link DEFAULT_RETRY_OPTIONS}\n */\nexport interface RetryOptions {\n /** Total attempts including the first call. Sentinel: `0` (use default 3). Otherwise must be a positive integer. */\n maxAttempts: number;\n /** Base delay between retries in milliseconds. Sentinel: any negative value (use default 250). Otherwise must be non-negative. */\n delayMs: number;\n /** Multiplier applied after each failed attempt. Sentinel: any value `< 1` (use default 1.5). Otherwise must be >= 1. */\n backoffMultiplier: number;\n}\n\n/**\n * Canonical sentinel {@link RetryOptions} bag. Pass this (optionally spread with\n * overrides) to {@link withRetry} / `device.connectWithRetry` instead of building a\n * partial object: `withRetry(fn, { ...DEFAULT_RETRY_OPTIONS, maxAttempts: 5 })`. Each\n * field carries its documented default (3 attempts, 250 ms base delay, 1.5x backoff).\n */\nexport const DEFAULT_RETRY_OPTIONS: RetryOptions = {\n maxAttempts: 0,\n delayMs: -1,\n backoffMultiplier: 0,\n};\n\nconst RETRIABLE_CODES: Set<BeacioErrorCode> = new Set([\n 'DEVICE_DISCONNECTED',\n 'CONNECTION_TIMEOUT',\n 'GATT_OPERATION_FAILED',\n 'TIMEOUT',\n 'SCAN_ALREADY_IN_PROGRESS',\n 'WRITE_INCOMPLETE',\n]);\n\nconst SUGGESTIONS: Record<BeacioErrorCode, string> = {\n INVALID_PARAMETER: 'One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.',\n BLUETOOTH_UNAVAILABLE: 'Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.',\n EXTENSION_NOT_INSTALLED: 'Install the Beacio iOS app and enable the Safari extension. Use @beacio/core/detect to show an install banner.',\n PERMISSION_DENIED: 'The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.',\n DEVICE_NOT_FOUND: 'No matching device found. Check your scan filters or ensure the device is advertising.',\n DEVICE_DISCONNECTED: 'Call device.connect() before performing GATT operations.',\n CONNECTION_TIMEOUT: 'The device did not respond in time. Ensure it is in range and advertising.',\n SERVICE_NOT_FOUND: 'The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.',\n CHARACTERISTIC_NOT_FOUND: 'The requested characteristic was not found in this service. Check the characteristic UUID.',\n CHARACTERISTIC_NOT_READABLE: 'This characteristic does not support read. Use device.subscribe() instead if it supports notify.',\n CHARACTERISTIC_NOT_WRITABLE: 'This characteristic does not support write. Check the characteristic properties.',\n CHARACTERISTIC_NOT_NOTIFIABLE: 'This characteristic does not support notifications. Use device.read() for polling instead.',\n GATT_OPERATION_FAILED: 'The GATT operation failed. The device may have disconnected or the characteristic may be busy.',\n SCAN_ALREADY_IN_PROGRESS: 'Stop the current scan before starting a new one.',\n CONNECTION_LIMIT_REACHED: 'Disconnect another device or raise maxConnections for this Beacio instance before connecting more devices.',\n USER_CANCELLED: 'The user cancelled the device picker. No action needed.',\n TIMEOUT: 'The operation timed out. Retry or check device connectivity.',\n WRITE_INCOMPLETE: 'Only part of the payload was written. Retry with smaller chunks or reconnect the device.',\n};\n\n/** Known competitor/product names that must never surface to a Beacio user. */\nconst COMPETITOR_TOKENS = /\\b(bluefy|web ble browser|webble browser)\\b/gi;\n\n/**\n * SB-SDK-05 AC6: reduce a raw native error message to a single, complete-sentence\n * line that is safe to show via a bare `alert(error.toString())` — no stack frames,\n * no native `webkit://`/`http(s)://` URLs, and no competitor names. A clean,\n * single-line native message (e.g. \"Invalid UUID: 'bogus'\") is preserved verbatim\n * so the message-passthrough contracts in errors.test.ts do not regress; only the\n * unsafe trailing content is stripped. Returns '' when nothing meaningful remains,\n * so the caller can fall back to the per-code SUGGESTION default.\n */\nfunction sanitizeNativeMessage(raw: string): string {\n // Keep only the first line — everything from the first newline (where V8/WebKit\n // append \" at …\" stack frames) onward is dropped.\n let line = raw.split('\\n', 1)[0] ?? '';\n // Strip native/internal URLs (webkit://…, http(s)://…) wherever they appear.\n line = line.replace(/\\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\\/\\/\\S+/gi, '');\n // Strip any residual single-line \"at file.js:line:col\" stack fragment.\n line = line.replace(/\\bat\\s+\\S+:\\d+:\\d+\\)?/gi, '');\n // Redact competitor names rather than leak them.\n line = line.replace(COMPETITOR_TOKENS, '');\n // Collapse whitespace left by the redactions and tidy dangling punctuation.\n line = line.replace(/\\s{2,}/g, ' ').replace(/\\s+([.,;:])/g, '$1').trim();\n line = line.replace(/[\\s.,;:]+$/g, '').trim();\n return line;\n}\n\n/**\n * Error class for all Beacio operations. Contains a machine-readable `code`\n * and a human/agent-readable `suggestion` for how to fix the issue.\n */\nexport class BeacioError extends Error {\n /** Machine-readable error code for programmatic handling. */\n readonly code: BeacioErrorCode;\n /** Actionable fix instruction — useful for agents and error UIs. */\n readonly suggestion: string;\n /** Whether the operation is safe to retry automatically. */\n readonly isRetriable: boolean;\n /** Suggested backoff before retrying, when known. */\n readonly retryAfterMs?: number;\n\n constructor(code: BeacioErrorCode, message?: string, options?: { retryAfterMs?: number }) {\n const defaultMessage = SUGGESTIONS[code];\n super(message ?? defaultMessage);\n this.name = 'BeacioError';\n this.code = code;\n this.suggestion = SUGGESTIONS[code];\n this.isRetriable = RETRIABLE_CODES.has(code);\n this.retryAfterMs = options?.retryAfterMs;\n }\n\n /** Convert a native error to a BeacioError with automatic code detection. */\n static from<T>(error: T, code: BeacioErrorCode = 'GATT_OPERATION_FAILED'): BeacioError {\n if (error instanceof BeacioError) return error;\n const domName =\n typeof error === 'object' && error !== null && 'name' in error && typeof (error as { name: string }).name === 'string'\n ? (error as { name: string }).name\n : undefined;\n // SB-SDK-05 AC6: a native DOMException/Error message can carry a multi-line\n // stack, a native URL, and engine/competitor jargon. The CLASSIFICATION below\n // still inspects the full raw text (so e.g. \"GATT Server is disconnected\" is\n // detected even when followed by a stack), but the message that survives onto\n // the BeacioError — and thus into error.toString() / a raw alert() — is the\n // sanitised, single-line form so no stack frame or competitor name ever leaks.\n const rawMsg = error instanceof Error ? error.message : String(error);\n // Empty sanitised result → undefined, so the BeacioError constructor falls back\n // to the per-code SUGGESTION default instead of carrying a blank message.\n const msg = sanitizeNativeMessage(rawMsg) || undefined;\n const lowerMsg = rawMsg.toLowerCase();\n\n switch (domName) {\n // Convention 5: the polyfill rehydrates native validation failures as\n // REAL TypeErrors (invalid UUIDs, malformed filters — Web Bluetooth §7).\n case 'TypeError':\n return new BeacioError('INVALID_PARAMETER', msg);\n case 'NotFoundError':\n return new BeacioError('DEVICE_NOT_FOUND', msg);\n case 'NotAllowedError':\n case 'SecurityError':\n return new BeacioError('PERMISSION_DENIED', msg);\n case 'NetworkError':\n return new BeacioError('DEVICE_DISCONNECTED', msg, { retryAfterMs: 1000 });\n case 'TimeoutError':\n return new BeacioError('TIMEOUT', msg, { retryAfterMs: 1000 });\n case 'InvalidStateError':\n if (lowerMsg.includes('disconnect')) {\n return new BeacioError('DEVICE_DISCONNECTED', msg, { retryAfterMs: 1000 });\n }\n break;\n default:\n break;\n }\n\n // Classification inspects the RAW text (rawMsg) so a stack-suffixed native\n // message still matches; the message carried onto the BeacioError stays the\n // sanitised `msg` (AC6).\n if (rawMsg.includes('User cancelled') || rawMsg.includes('User canceled')) {\n return new BeacioError('USER_CANCELLED');\n }\n if (lowerMsg.includes('no devices found') || rawMsg.includes('No Devices')) {\n return new BeacioError('DEVICE_NOT_FOUND');\n }\n if (rawMsg.includes('No Services matching') || lowerMsg.includes('service not found')) {\n return new BeacioError('SERVICE_NOT_FOUND', msg);\n }\n if (rawMsg.includes('No Characteristics matching') || lowerMsg.includes('characteristic not found')) {\n return new BeacioError('CHARACTERISTIC_NOT_FOUND', msg);\n }\n if (rawMsg.includes('GATT Server is disconnected') || lowerMsg.includes('disconnected')) {\n return new BeacioError('DEVICE_DISCONNECTED', msg, { retryAfterMs: 1000 });\n }\n if (lowerMsg.includes('not supported') && lowerMsg.includes('read')) {\n return new BeacioError('CHARACTERISTIC_NOT_READABLE', msg);\n }\n if (lowerMsg.includes('not supported') && lowerMsg.includes('write')) {\n return new BeacioError('CHARACTERISTIC_NOT_WRITABLE', msg);\n }\n if (lowerMsg.includes('not supported') && lowerMsg.includes('notif')) {\n return new BeacioError('CHARACTERISTIC_NOT_NOTIFIABLE', msg);\n }\n if (lowerMsg.includes('permission')) {\n return new BeacioError('PERMISSION_DENIED', msg);\n }\n\n return new BeacioError(code, msg);\n }\n}\n\n/**\n * Retry an async operation with exponential backoff. Only retries errors\n * whose `isRetriable` flag is `true` (see {@link BeacioError}).\n *\n * **Retriable error codes:** `DEVICE_DISCONNECTED`, `CONNECTION_TIMEOUT`,\n * `GATT_OPERATION_FAILED`, `TIMEOUT`, `SCAN_ALREADY_IN_PROGRESS`, `WRITE_INCOMPLETE`.\n *\n * **Backoff formula:** `delay = delayMs * backoffMultiplier^(attempt - 1)`.\n * If the error includes `retryAfterMs`, that value overrides the calculated delay.\n *\n * @param fn - Async function to retry. Receives the current attempt number (1-based).\n * @param options - Retry configuration (defaults: 3 attempts, 250ms delay, 1.5x backoff).\n * @returns The result of the first successful call.\n *\n * @throws {BeacioError} The last error if all attempts fail or the error is not retriable.\n * @throws {BeacioError} `INVALID_PARAMETER` if options contain invalid values.\n *\n * @example\n * ```typescript\n * import { withRetry } from '@beacio/core'\n *\n * const value = await withRetry(async (attempt) => {\n * console.log(`Attempt ${attempt}`)\n * return await device.read('battery_service', 'battery_level')\n * }, { maxAttempts: 5, delayMs: 500, backoffMultiplier: 2 })\n * ```\n *\n * @see {@link RetryOptions}\n * @see {@link BeacioError.isRetriable}\n */\nexport async function withRetry<T>(fn: (attempt: number) => Promise<T>, options: RetryOptions = DEFAULT_RETRY_OPTIONS): Promise<T> {\n // Sentinel resolution (see RetryOptions): a sentinel in any field falls back to\n // that field's documented default, preserving the historical `?? default` behavior\n // for callers who now pass DEFAULT_RETRY_OPTIONS instead of {}/undefined.\n const maxAttempts = options.maxAttempts > 0 ? options.maxAttempts : 3;\n const delayMs = options.delayMs >= 0 ? options.delayMs : 250;\n const backoffMultiplier = options.backoffMultiplier >= 1 ? options.backoffMultiplier : 1.5;\n\n if (!Number.isInteger(maxAttempts) || maxAttempts <= 0) {\n throw new BeacioError('INVALID_PARAMETER', `Invalid maxAttempts: ${maxAttempts}. Must be a positive integer.`);\n }\n if (!Number.isFinite(delayMs) || delayMs < 0) {\n throw new BeacioError('INVALID_PARAMETER', `Invalid delayMs: ${delayMs}. Must be a non-negative number.`);\n }\n if (!Number.isFinite(backoffMultiplier) || backoffMultiplier < 1) {\n throw new BeacioError('INVALID_PARAMETER', `Invalid backoffMultiplier: ${backoffMultiplier}. Must be a number >= 1.`);\n }\n\n for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n try {\n return await fn(attempt);\n } catch (error) {\n const normalizedError = BeacioError.from(error);\n if (attempt >= maxAttempts || !normalizedError.isRetriable) {\n throw normalizedError;\n }\n\n const nextDelay = normalizedError.retryAfterMs\n ?? delayMs * Math.pow(backoffMultiplier, attempt - 1);\n if (nextDelay > 0) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, nextDelay);\n });\n }\n }\n }\n\n throw new BeacioError('GATT_OPERATION_FAILED', 'Retry loop exited unexpectedly.');\n}\n","/**\n * Ergonomic helpers for reading typed values from `DataView` objects returned\n * by `device.read()` and notification callbacks.\n *\n * BLE characteristics return raw bytes as `DataView`. These helpers eliminate\n * boilerplate for common numeric and string decodings. All functions default\n * to offset 0 for the common case of reading the first value.\n *\n * @example\n * ```typescript\n * import { readUint8, readUint16LE, readUtf8 } from '@beacio/core'\n *\n * const battery = await device.read('battery_service', 'battery_level')\n * const level = readUint8(battery) // 0-100\n *\n * const name = await device.read('generic_access', 'gap.device_name')\n * console.log(readUtf8(name)) // \"My Device\"\n * ```\n *\n * @see {@link BeacioDevice.read} for reading characteristic values\n */\nimport { BeacioError } from './errors';\n\n/**\n * Validate that `size` bytes can be read from `dv` starting at `offset`.\n *\n * BLE peripherals (or a torn notification frame) can deliver a payload that is\n * shorter than the width a decoder expects. A bare `DataView.getX()` would throw\n * a raw `RangeError` (\"Offset is outside the bounds of the DataView\") in that\n * case, which callers cannot distinguish from a programming bug. This converts\n * that into a typed {@link BeacioError} (`INVALID_PARAMETER`) so it can be\n * caught and handled programmatically.\n *\n * @param reader - Name of the calling reader, for a descriptive message.\n * @param dv - Source DataView.\n * @param offset - Requested byte offset.\n * @param size - Number of bytes the reader will consume.\n * @throws {BeacioError} INVALID_PARAMETER if the offset is invalid or the read\n * would run past the end of the DataView.\n */\nfunction assertReadable(reader: string, dv: DataView, offset: number, size: number): void {\n if (!Number.isInteger(offset) || offset < 0 || offset + size > dv.byteLength) {\n throw new BeacioError(\n 'INVALID_PARAMETER',\n `${reader}: cannot read ${size} byte${size === 1 ? '' : 's'} at offset ${offset} of a ${dv.byteLength}-byte DataView (value too short).`,\n );\n }\n}\n\n/**\n * Read an unsigned 8-bit integer from the DataView.\n *\n * @param dv - Source DataView from a characteristic read or notification.\n * @param offset - Byte offset to read from. Defaults to 0.\n * @returns Unsigned integer in range [0, 255].\n * @throws {BeacioError} INVALID_PARAMETER if the DataView is too short for the read.\n */\nexport function readUint8(dv: DataView, offset = 0): number {\n assertReadable('readUint8', dv, offset, 1);\n return dv.getUint8(offset);\n}\n\n/**\n * Read an unsigned 16-bit little-endian integer from the DataView.\n * Little-endian is the standard byte order for most BLE characteristics.\n *\n * @param dv - Source DataView.\n * @param offset - Byte offset to read from. Defaults to 0.\n * @returns Unsigned integer in range [0, 65535].\n * @throws {BeacioError} INVALID_PARAMETER if the DataView is too short for the read.\n */\nexport function readUint16LE(dv: DataView, offset = 0): number {\n assertReadable('readUint16LE', dv, offset, 2);\n return dv.getUint16(offset, true);\n}\n\n/**\n * Read an unsigned 16-bit big-endian integer from the DataView.\n *\n * @param dv - Source DataView.\n * @param offset - Byte offset to read from. Defaults to 0.\n * @returns Unsigned integer in range [0, 65535].\n * @throws {BeacioError} INVALID_PARAMETER if the DataView is too short for the read.\n */\nexport function readUint16BE(dv: DataView, offset = 0): number {\n assertReadable('readUint16BE', dv, offset, 2);\n return dv.getUint16(offset, false);\n}\n\n/**\n * Read a signed 16-bit little-endian integer from the DataView.\n * Common for temperature and other signed sensor values in BLE.\n *\n * @param dv - Source DataView.\n * @param offset - Byte offset to read from. Defaults to 0.\n * @returns Signed integer in range [-32768, 32767].\n * @throws {BeacioError} INVALID_PARAMETER if the DataView is too short for the read.\n */\nexport function readInt16LE(dv: DataView, offset = 0): number {\n assertReadable('readInt16LE', dv, offset, 2);\n return dv.getInt16(offset, true);\n}\n\n/**\n * Read an unsigned 32-bit little-endian integer from the DataView.\n *\n * @param dv - Source DataView.\n * @param offset - Byte offset to read from. Defaults to 0.\n * @returns Unsigned integer in range [0, 4294967295].\n * @throws {BeacioError} INVALID_PARAMETER if the DataView is too short for the read.\n */\nexport function readUint32LE(dv: DataView, offset = 0): number {\n assertReadable('readUint32LE', dv, offset, 4);\n return dv.getUint32(offset, true);\n}\n\n/**\n * Read a 32-bit little-endian IEEE 754 float from the DataView.\n *\n * @param dv - Source DataView.\n * @param offset - Byte offset to read from. Defaults to 0.\n * @returns 32-bit floating point number.\n * @throws {BeacioError} INVALID_PARAMETER if the DataView is too short for the read.\n */\nexport function readFloat32LE(dv: DataView, offset = 0): number {\n assertReadable('readFloat32LE', dv, offset, 4);\n return dv.getFloat32(offset, true);\n}\n\n/**\n * Decode the entire DataView contents as a UTF-8 string.\n * Useful for device name, serial number, and other string characteristics.\n *\n * @param dv - Source DataView.\n * @returns Decoded UTF-8 string.\n *\n * @example\n * ```typescript\n * const name = await device.read('generic_access', 'gap.device_name')\n * console.log(readUtf8(name)) // \"Polar H10\"\n * ```\n */\nexport function readUtf8(dv: DataView): string {\n return new TextDecoder().decode(dv.buffer.slice(dv.byteOffset, dv.byteOffset + dv.byteLength));\n}\n\n/**\n * Copy the DataView contents into a new `Uint8Array`.\n * Useful when you need to store, compare, or forward raw bytes.\n *\n * @param dv - Source DataView.\n * @returns New Uint8Array containing a copy of the DataView bytes.\n */\nexport function readBytes(dv: DataView): Uint8Array {\n return new Uint8Array(dv.buffer.slice(dv.byteOffset, dv.byteOffset + dv.byteLength));\n}\n","import type {\n NotificationCallback,\n NativeOverflowEvent,\n BeacioDevice,\n WriteFragmentedOptions,\n WriteFragmentedResult,\n WriteLimits,\n WriteOptions,\n} from '../index';\nimport { resolveUUID } from '../uuid';\n\n// Sound top type for \"any characteristic definition\": TRead is covariant\n// (parse return → unknown), TWrite is contravariant (serialize param → never),\n// so every CharacteristicDefinition<A, B> is assignable here without `any`.\ntype AnyCharacteristicDefinition = CharacteristicDefinition<unknown, never>;\n\nexport function parseRawBytes(value: BufferSource): DataView {\n if (value instanceof DataView) {\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n }\n\n if (value instanceof ArrayBuffer) {\n return new DataView(value);\n }\n\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n}\n\ntype UUIDLike = string;\ntype Capability = 'read' | 'write' | 'writeWithoutResponse' | 'notify';\ntype CapabilitySet = readonly Capability[];\n\ntype CharacteristicReadConfig<T> = {\n capabilities: readonly ['read'] | readonly ['read', ...Capability[]];\n parse: (dv: DataView) => T;\n};\n\ntype CharacteristicWriteConfig<W> = {\n capabilities: readonly ['write'] | readonly ['writeWithoutResponse'] | readonly ['write', ...Capability[]] | readonly ['writeWithoutResponse', ...Capability[]];\n serialize: (value: W) => BufferSource;\n};\n\ntype CharacteristicReadWriteConfig<T, W> = {\n capabilities:\n | readonly ['read', 'write']\n | readonly ['read', 'writeWithoutResponse']\n | readonly ['write', 'read']\n | readonly ['writeWithoutResponse', 'read']\n | readonly ['read', 'write', ...Capability[]]\n | readonly ['read', 'writeWithoutResponse', ...Capability[]]\n | readonly ['write', 'read', ...Capability[]]\n | readonly ['writeWithoutResponse', 'read', ...Capability[]];\n parse: (dv: DataView) => T;\n serialize: (value: W) => BufferSource;\n};\n\nexport type CharacteristicDefinition<TRead = never, TWrite = never> = {\n uuid: UUIDLike;\n} & (\n | CharacteristicReadConfig<TRead>\n | CharacteristicWriteConfig<TWrite>\n | CharacteristicReadWriteConfig<TRead, TWrite>\n);\n\nexport interface ProfileConfig<C extends Record<string, AnyCharacteristicDefinition>> {\n name: string;\n service: UUIDLike;\n characteristics: C;\n}\n\ntype CapabilityOf<T extends AnyCharacteristicDefinition> = T['capabilities'][number];\ntype ReadableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'read' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\ntype WritableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'write' extends CapabilityOf<C[K]>\n ? K\n : 'writeWithoutResponse' extends CapabilityOf<C[K]>\n ? K\n : never;\n}[keyof C] & string;\ntype NotifiableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'notify' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\n\ntype ReadValue<T> = T extends { parse: (dv: DataView) => infer TResult } ? TResult : never;\ntype WriteValue<T> = T extends { serialize: (value: infer TValue) => BufferSource } ? TValue : never;\ntype CanonicalCharacteristic<C extends AnyCharacteristicDefinition> = Omit<C, 'uuid'> & { uuid: string };\ntype ReadParser<T> = { parse: (dv: DataView) => T };\ntype WriteSerializer<T> = { serialize: (value: T) => BufferSource };\n\nfunction hasCapability(capabilities: CapabilitySet, capability: Capability): boolean {\n return capabilities.includes(capability);\n}\n\nexport abstract class BaseProfile {\n protected device: BeacioDevice;\n protected abstract readonly service: string;\n private cleanups: (() => void)[] = [];\n\n constructor(device: BeacioDevice) {\n this.device = device;\n }\n\n async connect(): Promise<void> {\n await this.device.connect();\n }\n\n stop(): void {\n for (const cleanup of this.cleanups.splice(0)) {\n cleanup();\n }\n }\n\n dispose(): void {\n this.stop();\n }\n\n protected async read(characteristic: string): Promise<DataView> {\n return this.device.read(this.service, characteristic);\n }\n\n protected async write(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.write(this.service, characteristic, value);\n }\n\n protected async writeWithoutResponse(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.writeWithoutResponse(this.service, characteristic, value);\n }\n\n /**\n * Send a payload of any size to `characteristic`, fragmenting it into\n * MTU-sized chunks. This is a thin passthrough to {@link BeacioDevice.writeFragmented},\n * which owns the (already-clamped) chunk-size derivation via the branded\n * `ChunkSize` smart-constructors in the core write-chunker — so the stride is\n * guaranteed `>= 1` and a zero-stride infinite loop is unrepresentable.\n *\n * Profiles MUST use this instead of hand-rolling a `for (offset += step)` /\n * `subarray()` / `writeWithoutResponse()` chunk loop (enforced by the\n * `no-restricted-syntax` guard scoped to `packages/profiles/src`). Defaults to\n * `mode: 'without-response'` — the serial-pipe convention (Nordic UART, HM-10).\n *\n * @param characteristic - Target characteristic UUID or alias on this profile's service.\n * @param value - Bytes to send. Accepts any {@link BufferSource}.\n * @param options - Fragmentation/retry overrides; `mode` defaults to `'without-response'`.\n * @returns The {@link WriteFragmentedResult} (bytes written, chunk size/count, retries).\n */\n protected async sendChunked(\n characteristic: string,\n value: BufferSource,\n options: WriteFragmentedOptions = {},\n ): Promise<WriteFragmentedResult> {\n return this.device.writeFragmented(this.service, characteristic, value, {\n mode: 'without-response',\n ...options,\n });\n }\n\n protected async writeValue(characteristic: string, value: BufferSource, options?: WriteOptions): Promise<void> {\n if (options?.mode === 'without-response') {\n return this.device.writeWithoutResponse(this.service, characteristic, value, options);\n }\n return this.device.write(this.service, characteristic, value, options);\n }\n\n protected async getWriteLimits(): Promise<WriteLimits> {\n return this.device.getWriteLimits();\n }\n\n protected async getMtu(): Promise<number | null> {\n return this.device.getMtu();\n }\n\n protected subscribe(characteristic: string, callback: NotificationCallback): () => void {\n const unsubscribe = this.device.subscribe(this.service, characteristic, callback);\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n\n /**\n * Observe NATIVE notification-queue overflows for `characteristic` on this\n * profile's service. The bounded Swift `EventQueue` evicts notifications under\n * sustained high-frequency load and the polyfill surfaces each eviction as a\n * `beacio:overflow` `CustomEvent` on the characteristic; this decodes that\n * event's `detail` into a typed {@link NativeOverflowEvent} and forwards it to\n * `callback`.\n *\n * Lifecycle parity with {@link subscribe}: the returned unsubscribe is also\n * registered into the profile's cleanup set, so {@link stop}/{@link dispose}\n * detach the listener too. A staleness `callback` should typically re-read the\n * affected characteristic to resynchronise any UI tracking the last notified\n * value rather than trusting that (now-stale) value.\n *\n * @param characteristic - Characteristic UUID or alias on this profile's service.\n * @param callback - Called with the decoded eviction metadata on each overflow.\n * @returns Unsubscribe function.\n */\n protected onOverflow(characteristic: string, callback: (event: NativeOverflowEvent) => void): () => void {\n const unsubscribe = this.device.onCharacteristicOverflow(this.service, characteristic, (event) => {\n callback(decodeNativeOverflow(event));\n });\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n}\n\n/**\n * Decode a `beacio:overflow` {@link Event} (a `CustomEvent` whose `detail` carries\n * the native bounded-queue eviction metadata) into a typed\n * {@link NativeOverflowEvent}. Each field is `undefined` when the native bridge\n * omitted it (forward-compat guard); a conforming bridge supplies all four. Total\n * and side-effect-free — never throws on a malformed or detail-less event.\n */\nfunction decodeNativeOverflow(event: Event): NativeOverflowEvent {\n const detail = (event as CustomEvent).detail as unknown;\n const meta = (detail && typeof detail === 'object') ? (detail as Record<string, unknown>) : {};\n return {\n evictedCount: typeof meta.evictedCount === 'number' ? meta.evictedCount : undefined,\n queueCapacity: typeof meta.queueCapacity === 'number' ? meta.queueCapacity : undefined,\n seq: typeof meta.seq === 'number' ? meta.seq : undefined,\n timestamp: typeof meta.timestamp === 'number' ? meta.timestamp : undefined,\n };\n}\n\ntype DefinedProfileInstance<C extends Record<string, AnyCharacteristicDefinition>> = BaseProfile & {\n readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>>;\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void;\n writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void>;\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability>;\n getCharacteristicUUID<K extends keyof C & string>(name: K): string;\n getServiceUUID(): string;\n getWriteLimits(): Promise<WriteLimits>;\n getMtu(): Promise<number | null>;\n};\n\nexport interface DefinedProfile<C extends Record<string, AnyCharacteristicDefinition>> {\n new (device: BeacioDevice): DefinedProfileInstance<C>;\n readonly profileName: string;\n readonly serviceUUID: string;\n readonly characteristics: {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n}\n\nexport function defineProfile<C extends Record<string, AnyCharacteristicDefinition>>(\n config: ProfileConfig<C>,\n): DefinedProfile<C> {\n const serviceUUID = resolveUUID(config.service);\n const characteristics = Object.fromEntries(\n Object.entries(config.characteristics).map(([name, definition]) => {\n const canonical = {\n ...definition,\n uuid: resolveUUID(definition.uuid),\n };\n\n if (hasCapability(canonical.capabilities, 'read') && typeof (canonical as { parse?: unknown }).parse !== 'function') {\n throw new Error(`Characteristic ${name} declares read capability but is missing parse()`);\n }\n\n if (\n (hasCapability(canonical.capabilities, 'write') || hasCapability(canonical.capabilities, 'writeWithoutResponse'))\n && typeof (canonical as { serialize?: unknown }).serialize !== 'function'\n ) {\n throw new Error(`Characteristic ${name} declares write capability but is missing serialize()`);\n }\n\n return [name, canonical];\n }),\n ) as unknown as {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n\n class GeneratedProfile extends BaseProfile {\n static readonly profileName = config.name;\n static readonly serviceUUID = serviceUUID;\n static readonly characteristics = characteristics;\n\n protected readonly service = serviceUUID;\n\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability> {\n return characteristics[name].capabilities;\n }\n\n getCharacteristicUUID<K extends keyof C & string>(name: K): string {\n return characteristics[name].uuid;\n }\n\n getServiceUUID(): string {\n return serviceUUID;\n }\n\n async readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n const raw = await this.read(characteristic.uuid);\n return characteristic.parse(raw);\n }\n\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n return this.subscribe(characteristic.uuid, (value) => {\n cb(characteristic.parse(value));\n });\n }\n\n async writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & WriteSerializer<WriteValue<C[K]>>;\n const serialized = characteristic.serialize(value);\n const mode = options?.mode ?? (hasCapability(characteristic.capabilities, 'write') ? 'with-response' : 'without-response');\n await this.writeValue(characteristic.uuid, serialized, { ...options, mode });\n }\n\n async getWriteLimits(): Promise<WriteLimits> {\n return super.getWriteLimits();\n }\n\n async getMtu(): Promise<number | null> {\n return super.getMtu();\n }\n}\n\n return GeneratedProfile as unknown as DefinedProfile<C>;\n}\n","import { readUint8 } from '../index';\nimport { BaseProfile } from './base';\n\n/**\n * BLE Battery Service profile (UUID 0x180F).\n *\n * Reads and subscribes to the Battery Level characteristic (0x2A19),\n * which reports the current charge level as a percentage (0--100).\n *\n * @example\n * ```ts\n * import { BatteryProfile } from '@beacio/core/profiles';\n *\n * const battery = new BatteryProfile(device);\n * await battery.connect();\n *\n * // One-shot read\n * const level = await battery.readLevel();\n * console.log(`Battery: ${level}%`);\n *\n * // Subscribe to level changes\n * const unsubscribe = battery.onLevelChange((level) => {\n * console.log(`Battery changed: ${level}%`);\n * });\n *\n * // Clean up\n * unsubscribe();\n * battery.stop();\n * ```\n */\nexport class BatteryProfile extends BaseProfile {\n protected readonly service = 'battery_service';\n\n /** Read current battery level (0-100). */\n async readLevel(): Promise<number> {\n const dv = await this.read('battery_level');\n return readUint8(dv);\n }\n\n /** Subscribe to battery level changes. Returns unsubscribe function. */\n onLevelChange(callback: (level: number) => void): () => void {\n return this.subscribe('battery_level', (dv) => {\n callback(readUint8(dv));\n });\n }\n}\n"]}
export{a as BatteryProfile}from'../chunk-TPMOXHNG.mjs';import'../chunk-GAX5WAKV.mjs';import'../chunk-67S2RHE2.mjs';import'../chunk-BSOWECSQ.mjs';import'../chunk-L7SIDO2A.mjs';import'../chunk-3BDZNBBD.mjs';import'../chunk-FANWIUKA.mjs';import'../chunk-33IHM3NV.mjs';//# sourceMappingURL=battery.mjs.map
//# sourceMappingURL=battery.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"battery.mjs"}
import { B as BaseProfile } from '../base-YuFhZsjv.mjs';
import '../device-B5NsJWvh.mjs';
/**
* Aggregated device information read from the Device Information Service.
*
* All fields are optional because a peripheral may not expose every
* characteristic. Use {@link DeviceInfoProfile.readAll} to populate as
* many fields as the device supports in a single call.
*/
interface DeviceInfo {
/** Model number string (characteristic 0x2A24). */
modelNumber?: string;
/** Serial number string (characteristic 0x2A25). */
serialNumber?: string;
/** Firmware revision string (characteristic 0x2A26). */
firmwareRevision?: string;
/** Hardware revision string (characteristic 0x2A27). */
hardwareRevision?: string;
/** Software revision string (characteristic 0x2A28). */
softwareRevision?: string;
/** Manufacturer name string (characteristic 0x2A29). */
manufacturerName?: string;
/** Raw System ID value (characteristic 0x2A23) as a {@link DataView}. */
systemId?: DataView;
}
/**
* BLE Device Information Service profile (UUID 0x180A).
*
* Reads standard device metadata characteristics such as model number,
* manufacturer name, firmware revision, and more. String values are
* decoded from raw bytes with {@link TextDecoder}.
*
* @example
* ```ts
* import { DeviceInfoProfile } from '@beacio/core/profiles';
*
* const info = new DeviceInfoProfile(device);
* await info.connect();
*
* // Read individual fields
* const manufacturer = await info.readManufacturerName();
* const model = await info.readModelNumber();
* console.log(`${manufacturer} ${model}`);
*
* // Or read all available fields at once
* const all = await info.readAll();
* console.log(all);
* // { modelNumber: 'Sensor-v2', manufacturerName: 'Acme', ... }
*
* info.stop();
* ```
*/
declare class DeviceInfoProfile extends BaseProfile {
protected readonly service = "device_information";
readModelNumber(): Promise<string>;
readSerialNumber(): Promise<string>;
readFirmwareRevision(): Promise<string>;
readHardwareRevision(): Promise<string>;
readSoftwareRevision(): Promise<string>;
readManufacturerName(): Promise<string>;
readSystemId(): Promise<DataView>;
/** Read all available device info fields. Missing fields return undefined. */
readAll(): Promise<DeviceInfo>;
private readString;
}
export { type DeviceInfo, DeviceInfoProfile };
import { B as BaseProfile } from '../base-BnHcG-k7.js';
import '../device-B5NsJWvh.js';
/**
* Aggregated device information read from the Device Information Service.
*
* All fields are optional because a peripheral may not expose every
* characteristic. Use {@link DeviceInfoProfile.readAll} to populate as
* many fields as the device supports in a single call.
*/
interface DeviceInfo {
/** Model number string (characteristic 0x2A24). */
modelNumber?: string;
/** Serial number string (characteristic 0x2A25). */
serialNumber?: string;
/** Firmware revision string (characteristic 0x2A26). */
firmwareRevision?: string;
/** Hardware revision string (characteristic 0x2A27). */
hardwareRevision?: string;
/** Software revision string (characteristic 0x2A28). */
softwareRevision?: string;
/** Manufacturer name string (characteristic 0x2A29). */
manufacturerName?: string;
/** Raw System ID value (characteristic 0x2A23) as a {@link DataView}. */
systemId?: DataView;
}
/**
* BLE Device Information Service profile (UUID 0x180A).
*
* Reads standard device metadata characteristics such as model number,
* manufacturer name, firmware revision, and more. String values are
* decoded from raw bytes with {@link TextDecoder}.
*
* @example
* ```ts
* import { DeviceInfoProfile } from '@beacio/core/profiles';
*
* const info = new DeviceInfoProfile(device);
* await info.connect();
*
* // Read individual fields
* const manufacturer = await info.readManufacturerName();
* const model = await info.readModelNumber();
* console.log(`${manufacturer} ${model}`);
*
* // Or read all available fields at once
* const all = await info.readAll();
* console.log(all);
* // { modelNumber: 'Sensor-v2', manufacturerName: 'Acme', ... }
*
* info.stop();
* ```
*/
declare class DeviceInfoProfile extends BaseProfile {
protected readonly service = "device_information";
readModelNumber(): Promise<string>;
readSerialNumber(): Promise<string>;
readFirmwareRevision(): Promise<string>;
readHardwareRevision(): Promise<string>;
readSoftwareRevision(): Promise<string>;
readManufacturerName(): Promise<string>;
readSystemId(): Promise<DataView>;
/** Read all available device info fields. Missing fields return undefined. */
readAll(): Promise<DeviceInfo>;
private readString;
}
export { type DeviceInfo, DeviceInfoProfile };
'use strict';var a=class{constructor(i){this.cleanups=[];this.device=i;}async connect(){await this.device.connect();}stop(){for(let i of this.cleanups.splice(0))i();}dispose(){this.stop();}async read(i){return this.device.read(this.service,i)}async write(i,e){return this.device.write(this.service,i,e)}async writeWithoutResponse(i,e){return this.device.writeWithoutResponse(this.service,i,e)}async sendChunked(i,e,t={}){return this.device.writeFragmented(this.service,i,e,{mode:"without-response",...t})}async writeValue(i,e,t){return t?.mode==="without-response"?this.device.writeWithoutResponse(this.service,i,e,t):this.device.write(this.service,i,e,t)}async getWriteLimits(){return this.device.getWriteLimits()}async getMtu(){return this.device.getMtu()}subscribe(i,e){let t=this.device.subscribe(this.service,i,e);return this.cleanups.push(t),()=>{t(),this.cleanups=this.cleanups.filter(r=>r!==t);}}onOverflow(i,e){let t=this.device.onCharacteristicOverflow(this.service,i,r=>{e(c(r));});return this.cleanups.push(t),()=>{t(),this.cleanups=this.cleanups.filter(r=>r!==t);}}};function c(s){let i=s.detail,e=i&&typeof i=="object"?i:{};return {evictedCount:typeof e.evictedCount=="number"?e.evictedCount:void 0,queueCapacity:typeof e.queueCapacity=="number"?e.queueCapacity:void 0,seq:typeof e.seq=="number"?e.seq:void 0,timestamp:typeof e.timestamp=="number"?e.timestamp:void 0}}var d=new TextDecoder,n=class extends a{constructor(){super(...arguments);this.service="device_information";}async readModelNumber(){return this.readString("model_number_string")}async readSerialNumber(){return this.readString("serial_number_string")}async readFirmwareRevision(){return this.readString("firmware_revision_string")}async readHardwareRevision(){return this.readString("hardware_revision_string")}async readSoftwareRevision(){return this.readString("software_revision_string")}async readManufacturerName(){return this.readString("manufacturer_name_string")}async readSystemId(){return this.read("system_id")}async readAll(){let e={},t=async(r,o)=>{try{e[o]=await r();}catch{}};return await Promise.all([t(()=>this.readModelNumber(),"modelNumber"),t(()=>this.readSerialNumber(),"serialNumber"),t(()=>this.readFirmwareRevision(),"firmwareRevision"),t(()=>this.readHardwareRevision(),"hardwareRevision"),t(()=>this.readSoftwareRevision(),"softwareRevision"),t(()=>this.readManufacturerName(),"manufacturerName"),t(()=>this.readSystemId(),"systemId")]),e}async readString(e){let t=await this.read(e);return d.decode(t.buffer)}};exports.DeviceInfoProfile=n;//# sourceMappingURL=device-info.js.map
//# sourceMappingURL=device-info.js.map
{"version":3,"sources":["../../src/profiles/base.ts","../../src/profiles/device-info.ts"],"names":["BaseProfile","device","cleanup","characteristic","value","options","callback","unsubscribe","candidate","event","decodeNativeOverflow","detail","meta","decoder","DeviceInfoProfile","info","tryRead","fn","key","dv"],"mappings":"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","file":"device-info.js","sourcesContent":["import type {\n NotificationCallback,\n NativeOverflowEvent,\n BeacioDevice,\n WriteFragmentedOptions,\n WriteFragmentedResult,\n WriteLimits,\n WriteOptions,\n} from '../index';\nimport { resolveUUID } from '../uuid';\n\n// Sound top type for \"any characteristic definition\": TRead is covariant\n// (parse return → unknown), TWrite is contravariant (serialize param → never),\n// so every CharacteristicDefinition<A, B> is assignable here without `any`.\ntype AnyCharacteristicDefinition = CharacteristicDefinition<unknown, never>;\n\nexport function parseRawBytes(value: BufferSource): DataView {\n if (value instanceof DataView) {\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n }\n\n if (value instanceof ArrayBuffer) {\n return new DataView(value);\n }\n\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n}\n\ntype UUIDLike = string;\ntype Capability = 'read' | 'write' | 'writeWithoutResponse' | 'notify';\ntype CapabilitySet = readonly Capability[];\n\ntype CharacteristicReadConfig<T> = {\n capabilities: readonly ['read'] | readonly ['read', ...Capability[]];\n parse: (dv: DataView) => T;\n};\n\ntype CharacteristicWriteConfig<W> = {\n capabilities: readonly ['write'] | readonly ['writeWithoutResponse'] | readonly ['write', ...Capability[]] | readonly ['writeWithoutResponse', ...Capability[]];\n serialize: (value: W) => BufferSource;\n};\n\ntype CharacteristicReadWriteConfig<T, W> = {\n capabilities:\n | readonly ['read', 'write']\n | readonly ['read', 'writeWithoutResponse']\n | readonly ['write', 'read']\n | readonly ['writeWithoutResponse', 'read']\n | readonly ['read', 'write', ...Capability[]]\n | readonly ['read', 'writeWithoutResponse', ...Capability[]]\n | readonly ['write', 'read', ...Capability[]]\n | readonly ['writeWithoutResponse', 'read', ...Capability[]];\n parse: (dv: DataView) => T;\n serialize: (value: W) => BufferSource;\n};\n\nexport type CharacteristicDefinition<TRead = never, TWrite = never> = {\n uuid: UUIDLike;\n} & (\n | CharacteristicReadConfig<TRead>\n | CharacteristicWriteConfig<TWrite>\n | CharacteristicReadWriteConfig<TRead, TWrite>\n);\n\nexport interface ProfileConfig<C extends Record<string, AnyCharacteristicDefinition>> {\n name: string;\n service: UUIDLike;\n characteristics: C;\n}\n\ntype CapabilityOf<T extends AnyCharacteristicDefinition> = T['capabilities'][number];\ntype ReadableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'read' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\ntype WritableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'write' extends CapabilityOf<C[K]>\n ? K\n : 'writeWithoutResponse' extends CapabilityOf<C[K]>\n ? K\n : never;\n}[keyof C] & string;\ntype NotifiableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'notify' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\n\ntype ReadValue<T> = T extends { parse: (dv: DataView) => infer TResult } ? TResult : never;\ntype WriteValue<T> = T extends { serialize: (value: infer TValue) => BufferSource } ? TValue : never;\ntype CanonicalCharacteristic<C extends AnyCharacteristicDefinition> = Omit<C, 'uuid'> & { uuid: string };\ntype ReadParser<T> = { parse: (dv: DataView) => T };\ntype WriteSerializer<T> = { serialize: (value: T) => BufferSource };\n\nfunction hasCapability(capabilities: CapabilitySet, capability: Capability): boolean {\n return capabilities.includes(capability);\n}\n\nexport abstract class BaseProfile {\n protected device: BeacioDevice;\n protected abstract readonly service: string;\n private cleanups: (() => void)[] = [];\n\n constructor(device: BeacioDevice) {\n this.device = device;\n }\n\n async connect(): Promise<void> {\n await this.device.connect();\n }\n\n stop(): void {\n for (const cleanup of this.cleanups.splice(0)) {\n cleanup();\n }\n }\n\n dispose(): void {\n this.stop();\n }\n\n protected async read(characteristic: string): Promise<DataView> {\n return this.device.read(this.service, characteristic);\n }\n\n protected async write(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.write(this.service, characteristic, value);\n }\n\n protected async writeWithoutResponse(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.writeWithoutResponse(this.service, characteristic, value);\n }\n\n /**\n * Send a payload of any size to `characteristic`, fragmenting it into\n * MTU-sized chunks. This is a thin passthrough to {@link BeacioDevice.writeFragmented},\n * which owns the (already-clamped) chunk-size derivation via the branded\n * `ChunkSize` smart-constructors in the core write-chunker — so the stride is\n * guaranteed `>= 1` and a zero-stride infinite loop is unrepresentable.\n *\n * Profiles MUST use this instead of hand-rolling a `for (offset += step)` /\n * `subarray()` / `writeWithoutResponse()` chunk loop (enforced by the\n * `no-restricted-syntax` guard scoped to `packages/profiles/src`). Defaults to\n * `mode: 'without-response'` — the serial-pipe convention (Nordic UART, HM-10).\n *\n * @param characteristic - Target characteristic UUID or alias on this profile's service.\n * @param value - Bytes to send. Accepts any {@link BufferSource}.\n * @param options - Fragmentation/retry overrides; `mode` defaults to `'without-response'`.\n * @returns The {@link WriteFragmentedResult} (bytes written, chunk size/count, retries).\n */\n protected async sendChunked(\n characteristic: string,\n value: BufferSource,\n options: WriteFragmentedOptions = {},\n ): Promise<WriteFragmentedResult> {\n return this.device.writeFragmented(this.service, characteristic, value, {\n mode: 'without-response',\n ...options,\n });\n }\n\n protected async writeValue(characteristic: string, value: BufferSource, options?: WriteOptions): Promise<void> {\n if (options?.mode === 'without-response') {\n return this.device.writeWithoutResponse(this.service, characteristic, value, options);\n }\n return this.device.write(this.service, characteristic, value, options);\n }\n\n protected async getWriteLimits(): Promise<WriteLimits> {\n return this.device.getWriteLimits();\n }\n\n protected async getMtu(): Promise<number | null> {\n return this.device.getMtu();\n }\n\n protected subscribe(characteristic: string, callback: NotificationCallback): () => void {\n const unsubscribe = this.device.subscribe(this.service, characteristic, callback);\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n\n /**\n * Observe NATIVE notification-queue overflows for `characteristic` on this\n * profile's service. The bounded Swift `EventQueue` evicts notifications under\n * sustained high-frequency load and the polyfill surfaces each eviction as a\n * `beacio:overflow` `CustomEvent` on the characteristic; this decodes that\n * event's `detail` into a typed {@link NativeOverflowEvent} and forwards it to\n * `callback`.\n *\n * Lifecycle parity with {@link subscribe}: the returned unsubscribe is also\n * registered into the profile's cleanup set, so {@link stop}/{@link dispose}\n * detach the listener too. A staleness `callback` should typically re-read the\n * affected characteristic to resynchronise any UI tracking the last notified\n * value rather than trusting that (now-stale) value.\n *\n * @param characteristic - Characteristic UUID or alias on this profile's service.\n * @param callback - Called with the decoded eviction metadata on each overflow.\n * @returns Unsubscribe function.\n */\n protected onOverflow(characteristic: string, callback: (event: NativeOverflowEvent) => void): () => void {\n const unsubscribe = this.device.onCharacteristicOverflow(this.service, characteristic, (event) => {\n callback(decodeNativeOverflow(event));\n });\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n}\n\n/**\n * Decode a `beacio:overflow` {@link Event} (a `CustomEvent` whose `detail` carries\n * the native bounded-queue eviction metadata) into a typed\n * {@link NativeOverflowEvent}. Each field is `undefined` when the native bridge\n * omitted it (forward-compat guard); a conforming bridge supplies all four. Total\n * and side-effect-free — never throws on a malformed or detail-less event.\n */\nfunction decodeNativeOverflow(event: Event): NativeOverflowEvent {\n const detail = (event as CustomEvent).detail as unknown;\n const meta = (detail && typeof detail === 'object') ? (detail as Record<string, unknown>) : {};\n return {\n evictedCount: typeof meta.evictedCount === 'number' ? meta.evictedCount : undefined,\n queueCapacity: typeof meta.queueCapacity === 'number' ? meta.queueCapacity : undefined,\n seq: typeof meta.seq === 'number' ? meta.seq : undefined,\n timestamp: typeof meta.timestamp === 'number' ? meta.timestamp : undefined,\n };\n}\n\ntype DefinedProfileInstance<C extends Record<string, AnyCharacteristicDefinition>> = BaseProfile & {\n readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>>;\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void;\n writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void>;\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability>;\n getCharacteristicUUID<K extends keyof C & string>(name: K): string;\n getServiceUUID(): string;\n getWriteLimits(): Promise<WriteLimits>;\n getMtu(): Promise<number | null>;\n};\n\nexport interface DefinedProfile<C extends Record<string, AnyCharacteristicDefinition>> {\n new (device: BeacioDevice): DefinedProfileInstance<C>;\n readonly profileName: string;\n readonly serviceUUID: string;\n readonly characteristics: {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n}\n\nexport function defineProfile<C extends Record<string, AnyCharacteristicDefinition>>(\n config: ProfileConfig<C>,\n): DefinedProfile<C> {\n const serviceUUID = resolveUUID(config.service);\n const characteristics = Object.fromEntries(\n Object.entries(config.characteristics).map(([name, definition]) => {\n const canonical = {\n ...definition,\n uuid: resolveUUID(definition.uuid),\n };\n\n if (hasCapability(canonical.capabilities, 'read') && typeof (canonical as { parse?: unknown }).parse !== 'function') {\n throw new Error(`Characteristic ${name} declares read capability but is missing parse()`);\n }\n\n if (\n (hasCapability(canonical.capabilities, 'write') || hasCapability(canonical.capabilities, 'writeWithoutResponse'))\n && typeof (canonical as { serialize?: unknown }).serialize !== 'function'\n ) {\n throw new Error(`Characteristic ${name} declares write capability but is missing serialize()`);\n }\n\n return [name, canonical];\n }),\n ) as unknown as {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n\n class GeneratedProfile extends BaseProfile {\n static readonly profileName = config.name;\n static readonly serviceUUID = serviceUUID;\n static readonly characteristics = characteristics;\n\n protected readonly service = serviceUUID;\n\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability> {\n return characteristics[name].capabilities;\n }\n\n getCharacteristicUUID<K extends keyof C & string>(name: K): string {\n return characteristics[name].uuid;\n }\n\n getServiceUUID(): string {\n return serviceUUID;\n }\n\n async readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n const raw = await this.read(characteristic.uuid);\n return characteristic.parse(raw);\n }\n\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n return this.subscribe(characteristic.uuid, (value) => {\n cb(characteristic.parse(value));\n });\n }\n\n async writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & WriteSerializer<WriteValue<C[K]>>;\n const serialized = characteristic.serialize(value);\n const mode = options?.mode ?? (hasCapability(characteristic.capabilities, 'write') ? 'with-response' : 'without-response');\n await this.writeValue(characteristic.uuid, serialized, { ...options, mode });\n }\n\n async getWriteLimits(): Promise<WriteLimits> {\n return super.getWriteLimits();\n }\n\n async getMtu(): Promise<number | null> {\n return super.getMtu();\n }\n}\n\n return GeneratedProfile as unknown as DefinedProfile<C>;\n}\n","import { BaseProfile } from './base';\n\nconst decoder = new TextDecoder();\n\n/**\n * Aggregated device information read from the Device Information Service.\n *\n * All fields are optional because a peripheral may not expose every\n * characteristic. Use {@link DeviceInfoProfile.readAll} to populate as\n * many fields as the device supports in a single call.\n */\nexport interface DeviceInfo {\n /** Model number string (characteristic 0x2A24). */\n modelNumber?: string;\n /** Serial number string (characteristic 0x2A25). */\n serialNumber?: string;\n /** Firmware revision string (characteristic 0x2A26). */\n firmwareRevision?: string;\n /** Hardware revision string (characteristic 0x2A27). */\n hardwareRevision?: string;\n /** Software revision string (characteristic 0x2A28). */\n softwareRevision?: string;\n /** Manufacturer name string (characteristic 0x2A29). */\n manufacturerName?: string;\n /** Raw System ID value (characteristic 0x2A23) as a {@link DataView}. */\n systemId?: DataView;\n}\n\n/**\n * BLE Device Information Service profile (UUID 0x180A).\n *\n * Reads standard device metadata characteristics such as model number,\n * manufacturer name, firmware revision, and more. String values are\n * decoded from raw bytes with {@link TextDecoder}.\n *\n * @example\n * ```ts\n * import { DeviceInfoProfile } from '@beacio/core/profiles';\n *\n * const info = new DeviceInfoProfile(device);\n * await info.connect();\n *\n * // Read individual fields\n * const manufacturer = await info.readManufacturerName();\n * const model = await info.readModelNumber();\n * console.log(`${manufacturer} ${model}`);\n *\n * // Or read all available fields at once\n * const all = await info.readAll();\n * console.log(all);\n * // { modelNumber: 'Sensor-v2', manufacturerName: 'Acme', ... }\n *\n * info.stop();\n * ```\n */\nexport class DeviceInfoProfile extends BaseProfile {\n protected readonly service = 'device_information';\n\n async readModelNumber(): Promise<string> {\n return this.readString('model_number_string');\n }\n\n async readSerialNumber(): Promise<string> {\n return this.readString('serial_number_string');\n }\n\n async readFirmwareRevision(): Promise<string> {\n return this.readString('firmware_revision_string');\n }\n\n async readHardwareRevision(): Promise<string> {\n return this.readString('hardware_revision_string');\n }\n\n async readSoftwareRevision(): Promise<string> {\n return this.readString('software_revision_string');\n }\n\n async readManufacturerName(): Promise<string> {\n return this.readString('manufacturer_name_string');\n }\n\n async readSystemId(): Promise<DataView> {\n return this.read('system_id');\n }\n\n /** Read all available device info fields. Missing fields return undefined. */\n async readAll(): Promise<DeviceInfo> {\n const info: DeviceInfo = {};\n const tryRead = async (fn: () => Promise<unknown>, key: keyof DeviceInfo) => {\n try { (info as Record<string, unknown>)[key] = await fn(); } catch { /* optional—field may be unsupported */ }\n };\n await Promise.all([\n tryRead(() => this.readModelNumber(), 'modelNumber'),\n tryRead(() => this.readSerialNumber(), 'serialNumber'),\n tryRead(() => this.readFirmwareRevision(), 'firmwareRevision'),\n tryRead(() => this.readHardwareRevision(), 'hardwareRevision'),\n tryRead(() => this.readSoftwareRevision(), 'softwareRevision'),\n tryRead(() => this.readManufacturerName(), 'manufacturerName'),\n tryRead(() => this.readSystemId(), 'systemId'),\n ]);\n return info;\n }\n\n private async readString(characteristic: string): Promise<string> {\n const dv = await this.read(characteristic);\n return decoder.decode(dv.buffer);\n }\n}\n"]}
export{a as DeviceInfoProfile}from'../chunk-HBDTBK5K.mjs';import'../chunk-FANWIUKA.mjs';import'../chunk-33IHM3NV.mjs';//# sourceMappingURL=device-info.mjs.map
//# sourceMappingURL=device-info.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"device-info.mjs"}
import { B as BaseProfile } from '../base-YuFhZsjv.mjs';
import '../device-B5NsJWvh.mjs';
/**
* Service UUIDs a Heart Rate device may reach after connection (the SIG Heart
* Rate Service, 0x180D). Canonical 128-bit form (resolved from the SIG alias via
* the core registry — single source of truth). Use with `optionalServices` /
* `Beacio.registerServices`, or via {@link deriveOptionalServices} given
* {@link HeartRateProfile}.
*/
declare const HEART_RATE_SERVICES: readonly string[];
/**
* Parsed heart rate measurement data from the Heart Rate Measurement
* characteristic (UUID 0x2A37).
*
* Fields are populated based on the flags byte in the BLE payload.
* Optional fields are `null` when the corresponding flag bit is unset.
*/
interface HeartRateData {
/** Heart rate value in beats per minute (BPM). May be 8-bit or 16-bit depending on the flags byte. */
bpm: number;
/** Whether the sensor has skin contact. `null` if the sensor does not support contact detection. */
contact: boolean | null;
/** Cumulative energy expended in kilojoules since the last reset. `null` if not present in this measurement. */
energyExpended: number | null;
/** RR-interval values in seconds (1/1024 s resolution). Empty array if not present in this measurement. */
rrIntervals: number[];
}
/**
* BLE Heart Rate Service profile (UUID 0x180D).
*
* Provides access to heart rate measurements, body sensor location,
* and the energy-expended reset control point as defined by the
* Bluetooth SIG Heart Rate Service specification.
*
* The measurement characteristic (0x2A37) uses a flags byte:
* bit 0 = HR format (0 = UINT8, 1 = UINT16), bits 1-2 = sensor contact,
* bit 3 = energy expended present, bit 4 = RR-interval present.
*
* @example
* ```ts
* import { HeartRateProfile } from '@beacio/core/profiles';
*
* const hr = new HeartRateProfile(device);
* await hr.connect();
*
* // Subscribe to real-time heart rate data
* const unsubscribe = hr.onHeartRate((data) => {
* console.log(`BPM: ${data.bpm}`);
* if (data.contact === false) {
* console.warn('No skin contact detected');
* }
* if (data.rrIntervals.length > 0) {
* console.log('RR intervals (s):', data.rrIntervals);
* }
* });
*
* // Read sensor location (e.g. 1 = Chest, 2 = Wrist)
* const location = await hr.readSensorLocation();
*
* // Clean up
* unsubscribe();
* hr.stop();
* ```
*/
declare class HeartRateProfile extends BaseProfile {
/** Services this profile's device may reach after connection (Heart Rate, 0x180D). Read by {@link deriveOptionalServices}. */
static readonly services: readonly string[];
protected readonly service = "heart_rate";
/** Subscribe to heart rate measurements. Returns unsubscribe function. */
onHeartRate(callback: (data: HeartRateData) => void): () => void;
/** Read body sensor location (0=Other, 1=Chest, 2=Wrist, ...) */
readSensorLocation(): Promise<number>;
/** Reset energy expended counter */
resetEnergyExpended(): Promise<void>;
}
/**
* Parse a raw Heart Rate Measurement characteristic value (UUID 0x2A37)
* into a structured {@link HeartRateData} object.
*
* The first byte is a flags field that determines the format and which
* optional fields are present. This function handles all flag combinations
* defined by the Bluetooth SIG specification.
*
* @param dv - Raw characteristic value as a {@link DataView}.
* @returns Parsed heart rate data with BPM, contact status, energy, and RR intervals.
*
* @example
* ```ts
* import { parseHeartRate } from '@beacio/core/profiles';
*
* // Manually parse a DataView from a notification
* const data = parseHeartRate(characteristicValue);
* console.log(`Heart rate: ${data.bpm} BPM`);
* ```
*/
declare function parseHeartRate(dv: DataView): HeartRateData;
export { HEART_RATE_SERVICES, type HeartRateData, HeartRateProfile, parseHeartRate };
import { B as BaseProfile } from '../base-BnHcG-k7.js';
import '../device-B5NsJWvh.js';
/**
* Service UUIDs a Heart Rate device may reach after connection (the SIG Heart
* Rate Service, 0x180D). Canonical 128-bit form (resolved from the SIG alias via
* the core registry — single source of truth). Use with `optionalServices` /
* `Beacio.registerServices`, or via {@link deriveOptionalServices} given
* {@link HeartRateProfile}.
*/
declare const HEART_RATE_SERVICES: readonly string[];
/**
* Parsed heart rate measurement data from the Heart Rate Measurement
* characteristic (UUID 0x2A37).
*
* Fields are populated based on the flags byte in the BLE payload.
* Optional fields are `null` when the corresponding flag bit is unset.
*/
interface HeartRateData {
/** Heart rate value in beats per minute (BPM). May be 8-bit or 16-bit depending on the flags byte. */
bpm: number;
/** Whether the sensor has skin contact. `null` if the sensor does not support contact detection. */
contact: boolean | null;
/** Cumulative energy expended in kilojoules since the last reset. `null` if not present in this measurement. */
energyExpended: number | null;
/** RR-interval values in seconds (1/1024 s resolution). Empty array if not present in this measurement. */
rrIntervals: number[];
}
/**
* BLE Heart Rate Service profile (UUID 0x180D).
*
* Provides access to heart rate measurements, body sensor location,
* and the energy-expended reset control point as defined by the
* Bluetooth SIG Heart Rate Service specification.
*
* The measurement characteristic (0x2A37) uses a flags byte:
* bit 0 = HR format (0 = UINT8, 1 = UINT16), bits 1-2 = sensor contact,
* bit 3 = energy expended present, bit 4 = RR-interval present.
*
* @example
* ```ts
* import { HeartRateProfile } from '@beacio/core/profiles';
*
* const hr = new HeartRateProfile(device);
* await hr.connect();
*
* // Subscribe to real-time heart rate data
* const unsubscribe = hr.onHeartRate((data) => {
* console.log(`BPM: ${data.bpm}`);
* if (data.contact === false) {
* console.warn('No skin contact detected');
* }
* if (data.rrIntervals.length > 0) {
* console.log('RR intervals (s):', data.rrIntervals);
* }
* });
*
* // Read sensor location (e.g. 1 = Chest, 2 = Wrist)
* const location = await hr.readSensorLocation();
*
* // Clean up
* unsubscribe();
* hr.stop();
* ```
*/
declare class HeartRateProfile extends BaseProfile {
/** Services this profile's device may reach after connection (Heart Rate, 0x180D). Read by {@link deriveOptionalServices}. */
static readonly services: readonly string[];
protected readonly service = "heart_rate";
/** Subscribe to heart rate measurements. Returns unsubscribe function. */
onHeartRate(callback: (data: HeartRateData) => void): () => void;
/** Read body sensor location (0=Other, 1=Chest, 2=Wrist, ...) */
readSensorLocation(): Promise<number>;
/** Reset energy expended counter */
resetEnergyExpended(): Promise<void>;
}
/**
* Parse a raw Heart Rate Measurement characteristic value (UUID 0x2A37)
* into a structured {@link HeartRateData} object.
*
* The first byte is a flags field that determines the format and which
* optional fields are present. This function handles all flag combinations
* defined by the Bluetooth SIG specification.
*
* @param dv - Raw characteristic value as a {@link DataView}.
* @returns Parsed heart rate data with BPM, contact status, energy, and RR intervals.
*
* @example
* ```ts
* import { parseHeartRate } from '@beacio/core/profiles';
*
* // Manually parse a DataView from a notification
* const data = parseHeartRate(characteristicValue);
* console.log(`Heart rate: ${data.bpm} BPM`);
* ```
*/
declare function parseHeartRate(dv: DataView): HeartRateData;
export { HEART_RATE_SERVICES, type HeartRateData, HeartRateProfile, parseHeartRate };
'use strict';var I=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),y={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the Beacio iOS app and enable the Safari extension. Use @beacio/core/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this Beacio instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},v=/\b(bluefy|web ble browser|webble browser)\b/gi;function R(t){let e=t.split(`
`,1)[0]??"";return e=e.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),e=e.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),e=e.replace(v,""),e=e.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),e=e.replace(/[\s.,;:]+$/g,"").trim(),e}var l=class t extends Error{constructor(e,r,i){let n=y[e];super(r??n),this.name="BeacioError",this.code=e,this.suggestion=y[e],this.isRetriable=I.has(e),this.retryAfterMs=i?.retryAfterMs;}static from(e,r="GATT_OPERATION_FAILED"){if(e instanceof t)return e;let i=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,n=e instanceof Error?e.message:String(e),a=R(n)||void 0,o=n.toLowerCase();switch(i){case "TypeError":return new t("INVALID_PARAMETER",a);case "NotFoundError":return new t("DEVICE_NOT_FOUND",a);case "NotAllowedError":case "SecurityError":return new t("PERMISSION_DENIED",a);case "NetworkError":return new t("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3});case "TimeoutError":return new t("TIMEOUT",a,{retryAfterMs:1e3});case "InvalidStateError":if(o.includes("disconnect"))return new t("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3});break;}return n.includes("User cancelled")||n.includes("User canceled")?new t("USER_CANCELLED"):o.includes("no devices found")||n.includes("No Devices")?new t("DEVICE_NOT_FOUND"):n.includes("No Services matching")||o.includes("service not found")?new t("SERVICE_NOT_FOUND",a):n.includes("No Characteristics matching")||o.includes("characteristic not found")?new t("CHARACTERISTIC_NOT_FOUND",a):n.includes("GATT Server is disconnected")||o.includes("disconnected")?new t("DEVICE_DISCONNECTED",a,{retryAfterMs:1e3}):o.includes("not supported")&&o.includes("read")?new t("CHARACTERISTIC_NOT_READABLE",a):o.includes("not supported")&&o.includes("write")?new t("CHARACTERISTIC_NOT_WRITABLE",a):o.includes("not supported")&&o.includes("notif")?new t("CHARACTERISTIC_NOT_NOTIFIABLE",a):o.includes("permission")?new t("PERMISSION_DENIED",a):new t(r,a)}};var p="-0000-1000-8000-00805f9b34fb",b=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,A={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},x={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,local_east_coordinate:10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989};function f(t){return t.toString(16).padStart(8,"0")+p}var O=/^[0-9a-f]{4}$/,S=/^[0-9a-f]{8}$/;function U(t,e){let r=t.length,i=e.length,n=Array.from({length:i+1},(a,o)=>o);for(let a=1;a<=r;a++){let o=a-1;n[0]=a;for(let s=1;s<=i;s++){let c=n[s];n[s]=t[a-1]===e[s-1]?o:1+Math.min(o,n[s],n[s-1]),o=c;}}return n[i]}function B(t){return t.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function T(t,e){let r=e[t];if(r!==void 0)return r;let i=t.replace(/[._-]/g,"");if(i){for(let[n,a]of Object.entries(e))if(n.replace(/[._-]/g,"")===i)return a}}function g(t){let e=t.trim(),r=e.toLowerCase();if(b.test(r))return r;if(O.test(r))return "0000"+r+p;if(S.test(r))return r+p;let i=A[r]??x[r];if(i!==void 0)return f(i);let n=B(e),a=T(n,A);if(a!==void 0)return f(a);let o=T(n,x);if(o!==void 0)return f(o);let s=Object.keys(A).concat(Object.keys(x)),c,C=4;for(let _ of s){let E=U(n,_);E<C&&(C=E,c=_);}!c&&n.length>=4&&(c=s.find(_=>_.startsWith(n)));let D=c?` Did you mean "${c}"?`:"";throw new TypeError(`Invalid UUID: "${t}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${D}`)}function w(t,e,r,i){if(!Number.isInteger(r)||r<0||r+i>e.byteLength)throw new l("INVALID_PARAMETER",`${t}: cannot read ${i} byte${i===1?"":"s"} at offset ${r} of a ${e.byteLength}-byte DataView (value too short).`)}function u(t,e=0){return w("readUint8",t,e,1),t.getUint8(e)}function d(t,e=0){return w("readUint16LE",t,e,2),t.getUint16(e,true)}var m=class{constructor(e){this.cleanups=[];this.device=e;}async connect(){await this.device.connect();}stop(){for(let e of this.cleanups.splice(0))e();}dispose(){this.stop();}async read(e){return this.device.read(this.service,e)}async write(e,r){return this.device.write(this.service,e,r)}async writeWithoutResponse(e,r){return this.device.writeWithoutResponse(this.service,e,r)}async sendChunked(e,r,i={}){return this.device.writeFragmented(this.service,e,r,{mode:"without-response",...i})}async writeValue(e,r,i){return i?.mode==="without-response"?this.device.writeWithoutResponse(this.service,e,r,i):this.device.write(this.service,e,r,i)}async getWriteLimits(){return this.device.getWriteLimits()}async getMtu(){return this.device.getMtu()}subscribe(e,r){let i=this.device.subscribe(this.service,e,r);return this.cleanups.push(i),()=>{i(),this.cleanups=this.cleanups.filter(n=>n!==i);}}onOverflow(e,r){let i=this.device.onCharacteristicOverflow(this.service,e,n=>{r(L(n));});return this.cleanups.push(i),()=>{i(),this.cleanups=this.cleanups.filter(n=>n!==i);}}};function L(t){let e=t.detail,r=e&&typeof e=="object"?e:{};return {evictedCount:typeof r.evictedCount=="number"?r.evictedCount:void 0,queueCapacity:typeof r.queueCapacity=="number"?r.queueCapacity:void 0,seq:typeof r.seq=="number"?r.seq:void 0,timestamp:typeof r.timestamp=="number"?r.timestamp:void 0}}var M=[g("heart_rate")],h=class extends m{constructor(){super(...arguments);this.service="heart_rate";}onHeartRate(r){return this.subscribe("heart_rate_measurement",i=>{r(k(i));})}async readSensorLocation(){let r=await this.read("body_sensor_location");return u(r)}async resetEnergyExpended(){await this.write("heart_rate_control_point",new Uint8Array([1]));}};h.services=M;function k(t){let e=u(t,0),r=1,i=(e&1)!==0,n=i?d(t,r):u(t,r);r+=i?2:1;let o=(e&4)!==0?(e&2)!==0:null,s=null;e&8&&(s=d(t,r),r+=2);let c=[];if(e&16)for(;r+2<=t.byteLength;)c.push(d(t,r)/1024),r+=2;return {bpm:n,contact:o,energyExpended:s,rrIntervals:c}}exports.HEART_RATE_SERVICES=M;exports.HeartRateProfile=h;exports.parseHeartRate=k;//# sourceMappingURL=heart-rate.js.map
//# sourceMappingURL=heart-rate.js.map

Sorry, the diff of this file is too big to display

export{a as HEART_RATE_SERVICES,b as HeartRateProfile,c as parseHeartRate}from'../chunk-6OEIU4UO.mjs';import'../chunk-GAX5WAKV.mjs';import'../chunk-67S2RHE2.mjs';import'../chunk-BSOWECSQ.mjs';import'../chunk-L7SIDO2A.mjs';import'../chunk-3BDZNBBD.mjs';import'../chunk-FANWIUKA.mjs';import'../chunk-33IHM3NV.mjs';//# sourceMappingURL=heart-rate.mjs.map
//# sourceMappingURL=heart-rate.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"heart-rate.mjs"}
export { B as BaseProfile, d as defineProfile, p as parseRawBytes } from '../base-YuFhZsjv.mjs';
export { HEART_RATE_SERVICES, HeartRateData, HeartRateProfile, parseHeartRate } from './heart-rate.mjs';
export { BatteryProfile } from './battery.mjs';
export { DeviceInfo, DeviceInfoProfile } from './device-info.mjs';
export { NUS_SERVICES, NordicUARTProfile } from './nordic-uart.mjs';
export { HM10SerialProfile } from './serial-ffe0.mjs';
import '../device-B5NsJWvh.mjs';
/**
* A profile class that declares the GATT services it (and its device family) may
* reach after connection, as a static `services` array. {@link deriveOptionalServices}
* reads this so a caller can pass the profile itself instead of hand-copying its
* service UUIDs into `optionalServices`.
*/
interface ProfileWithServices {
readonly services: readonly string[];
}
/**
* A source of service UUIDs accepted by {@link deriveOptionalServices}: either a
* profile class carrying a static `services` array, or a raw list of service
* names / 4-8-hex / full 128-bit UUID strings.
*/
type OptionalServicesSource = ProfileWithServices | readonly string[];
/**
* Flatten one or more profiles / service-UUID arrays into a single canonical,
* de-duped, lowercase 128-bit `string[]` suitable for `optionalServices` (or
* {@link Beacio.registerServices}). Every entry is resolved via the core
* {@link resolveUUID} (names like `'battery_service'`, 4/8-hex, and full UUIDs
* are all accepted) and de-duped while preserving first-seen order.
*
* This retires the hand-maintained parallel `optionalServices` lists a multi-
* device integration would otherwise keep in sync: declare the profiles (or a
* vendor bundle such as `StorzBickel.allServices()`) once and derive the list.
*
* Pure and idempotent: `deriveOptionalServices(deriveOptionalServices(x))` equals
* `deriveOptionalServices(x)`, because the output is already canonical UUIDs that
* {@link resolveUUID} passes through unchanged.
*
* @param sources - Profile classes (with a static `services` array) and/or raw
* service-UUID arrays (names, 4/8-hex, or full 128-bit UUID strings).
* @returns De-duped canonical lowercase 128-bit service UUIDs, first-seen order.
* @throws {TypeError} If any value is not a resolvable UUID or known SIG name.
*
* @example
* ```ts
* import { deriveOptionalServices, NordicUARTProfile, HeartRateProfile } from '@beacio/core/profiles';
*
* const optionalServices = deriveOptionalServices(NordicUARTProfile, HeartRateProfile);
* const device = await ble.requestDevice({ acceptAllDevices: true, optionalServices });
* ```
*/
declare function deriveOptionalServices(...sources: OptionalServicesSource[]): string[];
export { type OptionalServicesSource, type ProfileWithServices, deriveOptionalServices };
export { B as BaseProfile, d as defineProfile, p as parseRawBytes } from '../base-BnHcG-k7.js';
export { HEART_RATE_SERVICES, HeartRateData, HeartRateProfile, parseHeartRate } from './heart-rate.js';
export { BatteryProfile } from './battery.js';
export { DeviceInfo, DeviceInfoProfile } from './device-info.js';
export { NUS_SERVICES, NordicUARTProfile } from './nordic-uart.js';
export { HM10SerialProfile } from './serial-ffe0.js';
import '../device-B5NsJWvh.js';
/**
* A profile class that declares the GATT services it (and its device family) may
* reach after connection, as a static `services` array. {@link deriveOptionalServices}
* reads this so a caller can pass the profile itself instead of hand-copying its
* service UUIDs into `optionalServices`.
*/
interface ProfileWithServices {
readonly services: readonly string[];
}
/**
* A source of service UUIDs accepted by {@link deriveOptionalServices}: either a
* profile class carrying a static `services` array, or a raw list of service
* names / 4-8-hex / full 128-bit UUID strings.
*/
type OptionalServicesSource = ProfileWithServices | readonly string[];
/**
* Flatten one or more profiles / service-UUID arrays into a single canonical,
* de-duped, lowercase 128-bit `string[]` suitable for `optionalServices` (or
* {@link Beacio.registerServices}). Every entry is resolved via the core
* {@link resolveUUID} (names like `'battery_service'`, 4/8-hex, and full UUIDs
* are all accepted) and de-duped while preserving first-seen order.
*
* This retires the hand-maintained parallel `optionalServices` lists a multi-
* device integration would otherwise keep in sync: declare the profiles (or a
* vendor bundle such as `StorzBickel.allServices()`) once and derive the list.
*
* Pure and idempotent: `deriveOptionalServices(deriveOptionalServices(x))` equals
* `deriveOptionalServices(x)`, because the output is already canonical UUIDs that
* {@link resolveUUID} passes through unchanged.
*
* @param sources - Profile classes (with a static `services` array) and/or raw
* service-UUID arrays (names, 4/8-hex, or full 128-bit UUID strings).
* @returns De-duped canonical lowercase 128-bit service UUIDs, first-seen order.
* @throws {TypeError} If any value is not a resolvable UUID or known SIG name.
*
* @example
* ```ts
* import { deriveOptionalServices, NordicUARTProfile, HeartRateProfile } from '@beacio/core/profiles';
*
* const optionalServices = deriveOptionalServices(NordicUARTProfile, HeartRateProfile);
* const device = await ble.requestDevice({ acceptAllDevices: true, optionalServices });
* ```
*/
declare function deriveOptionalServices(...sources: OptionalServicesSource[]): string[];
export { type OptionalServicesSource, type ProfileWithServices, deriveOptionalServices };
'use strict';var m="-0000-1000-8000-00805f9b34fb",I=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,x={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},A={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,local_east_coordinate:10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989};function g(r){return r.toString(16).padStart(8,"0")+m}var k=/^[0-9a-f]{4}$/,F=/^[0-9a-f]{8}$/;function V(r,e){let t=r.length,i=e.length,n=Array.from({length:i+1},(o,a)=>a);for(let o=1;o<=t;o++){let a=o-1;n[0]=o;for(let s=1;s<=i;s++){let c=n[s];n[s]=r[o-1]===e[s-1]?a:1+Math.min(a,n[s],n[s-1]),a=c;}}return n[i]}function W(r){return r.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function R(r,e){let t=e[r];if(t!==void 0)return t;let i=r.replace(/[._-]/g,"");if(i){for(let[n,o]of Object.entries(e))if(n.replace(/[._-]/g,"")===i)return o}}function _(r){if(typeof r=="number"){if(!Number.isInteger(r)||r<0||r>4294967295)throw new TypeError(`Invalid UUID integer: ${r}. Must be a 16-bit or 32-bit unsigned integer.`);return g(r)}let e=r.trim(),t=e.toLowerCase();if(I.test(t))return t;if(k.test(t))return "0000"+t+m;if(F.test(t))return t+m;let i=x[t]??A[t];if(i!==void 0)return g(i);let n=W(e),o=R(n,x);if(o!==void 0)return g(o);let a=R(n,A);if(a!==void 0)return g(a);let s=Object.keys(x).concat(Object.keys(A)),c,u=4;for(let p of s){let T=V(n,p);T<u&&(u=T,c=p);}!c&&n.length>=4&&(c=s.find(p=>p.startsWith(n)));let E=c?` Did you mean "${c}"?`:"";throw new TypeError(`Invalid UUID: "${r}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${E}`)}function K(r){return r instanceof DataView?new DataView(r.buffer,r.byteOffset,r.byteLength):r instanceof ArrayBuffer?new DataView(r):new DataView(r.buffer,r.byteOffset,r.byteLength)}function h(r,e){return r.includes(e)}var d=class{constructor(e){this.cleanups=[];this.device=e;}async connect(){await this.device.connect();}stop(){for(let e of this.cleanups.splice(0))e();}dispose(){this.stop();}async read(e){return this.device.read(this.service,e)}async write(e,t){return this.device.write(this.service,e,t)}async writeWithoutResponse(e,t){return this.device.writeWithoutResponse(this.service,e,t)}async sendChunked(e,t,i={}){return this.device.writeFragmented(this.service,e,t,{mode:"without-response",...i})}async writeValue(e,t,i){return i?.mode==="without-response"?this.device.writeWithoutResponse(this.service,e,t,i):this.device.write(this.service,e,t,i)}async getWriteLimits(){return this.device.getWriteLimits()}async getMtu(){return this.device.getMtu()}subscribe(e,t){let i=this.device.subscribe(this.service,e,t);return this.cleanups.push(i),()=>{i(),this.cleanups=this.cleanups.filter(n=>n!==i);}}onOverflow(e,t){let i=this.device.onCharacteristicOverflow(this.service,e,n=>{t(H(n));});return this.cleanups.push(i),()=>{i(),this.cleanups=this.cleanups.filter(n=>n!==i);}}};function H(r){let e=r.detail,t=e&&typeof e=="object"?e:{};return {evictedCount:typeof t.evictedCount=="number"?t.evictedCount:void 0,queueCapacity:typeof t.queueCapacity=="number"?t.queueCapacity:void 0,seq:typeof t.seq=="number"?t.seq:void 0,timestamp:typeof t.timestamp=="number"?t.timestamp:void 0}}function z(r){let e=_(r.service),t=Object.fromEntries(Object.entries(r.characteristics).map(([n,o])=>{let a={...o,uuid:_(o.uuid)};if(h(a.capabilities,"read")&&typeof a.parse!="function")throw new Error(`Characteristic ${n} declares read capability but is missing parse()`);if((h(a.capabilities,"write")||h(a.capabilities,"writeWithoutResponse"))&&typeof a.serialize!="function")throw new Error(`Characteristic ${n} declares write capability but is missing serialize()`);return [n,a]}));class i extends d{constructor(){super(...arguments);this.service=e;}getCharacteristicCapabilities(a){return t[a].capabilities}getCharacteristicUUID(a){return t[a].uuid}getServiceUUID(){return e}async readChar(a){let s=t[a],c=await this.read(s.uuid);return s.parse(c)}subscribeChar(a,s){let c=t[a];return this.subscribe(c.uuid,u=>{s(c.parse(u));})}async writeChar(a,s,c){let u=t[a],E=u.serialize(s),p=c?.mode??(h(u.capabilities,"write")?"with-response":"without-response");await this.writeValue(u.uuid,E,{...c,mode:p});}async getWriteLimits(){return super.getWriteLimits()}async getMtu(){return super.getMtu()}}return i.profileName=r.name,i.serviceUUID=e,i.characteristics=t,i}var $=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),S={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the Beacio iOS app and enable the Safari extension. Use @beacio/core/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this Beacio instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},G=/\b(bluefy|web ble browser|webble browser)\b/gi;function j(r){let e=r.split(`
`,1)[0]??"";return e=e.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),e=e.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),e=e.replace(G,""),e=e.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),e=e.replace(/[\s.,;:]+$/g,"").trim(),e}var C=class r extends Error{constructor(e,t,i){let n=S[e];super(t??n),this.name="BeacioError",this.code=e,this.suggestion=S[e],this.isRetriable=$.has(e),this.retryAfterMs=i?.retryAfterMs;}static from(e,t="GATT_OPERATION_FAILED"){if(e instanceof r)return e;let i=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,n=e instanceof Error?e.message:String(e),o=j(n)||void 0,a=n.toLowerCase();switch(i){case "TypeError":return new r("INVALID_PARAMETER",o);case "NotFoundError":return new r("DEVICE_NOT_FOUND",o);case "NotAllowedError":case "SecurityError":return new r("PERMISSION_DENIED",o);case "NetworkError":return new r("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3});case "TimeoutError":return new r("TIMEOUT",o,{retryAfterMs:1e3});case "InvalidStateError":if(a.includes("disconnect"))return new r("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3});break;}return n.includes("User cancelled")||n.includes("User canceled")?new r("USER_CANCELLED"):a.includes("no devices found")||n.includes("No Devices")?new r("DEVICE_NOT_FOUND"):n.includes("No Services matching")||a.includes("service not found")?new r("SERVICE_NOT_FOUND",o):n.includes("No Characteristics matching")||a.includes("characteristic not found")?new r("CHARACTERISTIC_NOT_FOUND",o):n.includes("GATT Server is disconnected")||a.includes("disconnected")?new r("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3}):a.includes("not supported")&&a.includes("read")?new r("CHARACTERISTIC_NOT_READABLE",o):a.includes("not supported")&&a.includes("write")?new r("CHARACTERISTIC_NOT_WRITABLE",o):a.includes("not supported")&&a.includes("notif")?new r("CHARACTERISTIC_NOT_NOTIFIABLE",o):a.includes("permission")?new r("PERMISSION_DENIED",o):new r(t,o)}};function N(r,e,t,i){if(!Number.isInteger(t)||t<0||t+i>e.byteLength)throw new C("INVALID_PARAMETER",`${r}: cannot read ${i} byte${i===1?"":"s"} at offset ${t} of a ${e.byteLength}-byte DataView (value too short).`)}function l(r,e=0){return N("readUint8",r,e,1),r.getUint8(e)}function f(r,e=0){return N("readUint16LE",r,e,2),r.getUint16(e,true)}function q(r){return !Array.isArray(r)&&Array.isArray(r.services)}function X(...r){let e=new Set;for(let t of r){let i=q(t)?t.services:t;for(let n of i)e.add(_(n));}return [...e]}var O=[_("heart_rate")],y=class extends d{constructor(){super(...arguments);this.service="heart_rate";}onHeartRate(t){return this.subscribe("heart_rate_measurement",i=>{t(U(i));})}async readSensorLocation(){let t=await this.read("body_sensor_location");return l(t)}async resetEnergyExpended(){await this.write("heart_rate_control_point",new Uint8Array([1]));}};y.services=O;function U(r){let e=l(r,0),t=1,i=(e&1)!==0,n=i?f(r,t):l(r,t);t+=i?2:1;let a=(e&4)!==0?(e&2)!==0:null,s=null;e&8&&(s=f(r,t),t+=2);let c=[];if(e&16)for(;t+2<=r.byteLength;)c.push(f(r,t)/1024),t+=2;return {bpm:n,contact:a,energyExpended:s,rrIntervals:c}}var v=class extends d{constructor(){super(...arguments);this.service="battery_service";}async readLevel(){let t=await this.read("battery_level");return l(t)}onLevelChange(t){return this.subscribe("battery_level",i=>{t(l(i));})}};var Y=new TextDecoder,w=class extends d{constructor(){super(...arguments);this.service="device_information";}async readModelNumber(){return this.readString("model_number_string")}async readSerialNumber(){return this.readString("serial_number_string")}async readFirmwareRevision(){return this.readString("firmware_revision_string")}async readHardwareRevision(){return this.readString("hardware_revision_string")}async readSoftwareRevision(){return this.readString("software_revision_string")}async readManufacturerName(){return this.readString("manufacturer_name_string")}async readSystemId(){return this.read("system_id")}async readAll(){let t={},i=async(n,o)=>{try{t[o]=await n();}catch{}};return await Promise.all([i(()=>this.readModelNumber(),"modelNumber"),i(()=>this.readSerialNumber(),"serialNumber"),i(()=>this.readFirmwareRevision(),"firmwareRevision"),i(()=>this.readHardwareRevision(),"hardwareRevision"),i(()=>this.readSoftwareRevision(),"softwareRevision"),i(()=>this.readManufacturerName(),"manufacturerName"),i(()=>this.readSystemId(),"systemId")]),t}async readString(t){let i=await this.read(t);return Y.decode(i.buffer)}};var B="6e400001-b5a3-f393-e0a9-e50e24dcca9e",Z="6e400002-b5a3-f393-e0a9-e50e24dcca9e",Q="6e400003-b5a3-f393-e0a9-e50e24dcca9e",P=[B],b=class extends d{constructor(){super(...arguments);this.service=B;}onReceive(t){return this.subscribe(Q,t)}async send(t){await this.sendChunked(Z,t);}};b.services=P;var J="0000ffe0-0000-1000-8000-00805f9b34fb",L="0000ffe1-0000-1000-8000-00805f9b34fb",D=class extends d{constructor(){super(...arguments);this.service=J;}onReceive(t){return this.subscribe(L,t)}async send(t){await this.sendChunked(L,t);}};exports.BaseProfile=d;exports.BatteryProfile=v;exports.DeviceInfoProfile=w;exports.HEART_RATE_SERVICES=O;exports.HM10SerialProfile=D;exports.HeartRateProfile=y;exports.NUS_SERVICES=P;exports.NordicUARTProfile=b;exports.defineProfile=z;exports.deriveOptionalServices=X;exports.parseHeartRate=U;exports.parseRawBytes=K;//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

Sorry, the diff of this file is too big to display

export{a as NUS_SERVICES,b as NordicUARTProfile}from'../chunk-WKLBTKL5.mjs';export{a as HM10SerialProfile}from'../chunk-2PX7ZYHS.mjs';export{a as deriveOptionalServices}from'../chunk-SOZ26EXK.mjs';export{a as HEART_RATE_SERVICES,b as HeartRateProfile,c as parseHeartRate}from'../chunk-6OEIU4UO.mjs';export{a as BatteryProfile}from'../chunk-TPMOXHNG.mjs';import'../chunk-GAX5WAKV.mjs';import'../chunk-67S2RHE2.mjs';import'../chunk-BSOWECSQ.mjs';import'../chunk-L7SIDO2A.mjs';import'../chunk-3BDZNBBD.mjs';export{a as DeviceInfoProfile}from'../chunk-HBDTBK5K.mjs';export{b as BaseProfile,c as defineProfile,a as parseRawBytes}from'../chunk-FANWIUKA.mjs';import'../chunk-33IHM3NV.mjs';//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
import { B as BaseProfile } from '../base-YuFhZsjv.mjs';
import '../device-B5NsJWvh.mjs';
/**
* Service UUIDs a Nordic UART device may reach after connection (the single NUS
* service). Use with `optionalServices` / `Beacio.registerServices`, or via
* {@link deriveOptionalServices} given {@link NordicUARTProfile}.
*/
declare const NUS_SERVICES: readonly string[];
/**
* Nordic UART Service (NUS) profile — a bidirectional serial-over-BLE pipe.
*
* The de-facto standard "UART service" exposed by Espruino devices
* (Bangle.js, Puck.js, Pixl.js, MDBT42Q), the BBC micro:bit, and Adafruit
* Bluefruit modules. Data flows over two characteristics on the NUS service
* `6e400001-b5a3-f393-e0a9-e50e24dcca9e`:
*
* - **TX** `6e400003-…` — device -> host, delivered via notifications.
* Enabled through {@link BaseProfile.subscribe} (the native layer owns the
* CCCD descriptor; `startNotifications()` covers notify *and* indicate).
* - **RX** `6e400002-…` — host -> device, sent with write-without-response and
* chunked to the negotiated MTU.
*
* Strictly W3C `navigator.bluetooth` GATT: this profile never reads or writes
* a CCCD/SCCD descriptor itself.
*
* @example
* ```ts
* import { NordicUARTProfile, deriveOptionalServices } from '@beacio/core/profiles';
*
* // Declare the service from the profile — no hand-copied UUID:
* // requestDevice({ filters: [{ namePrefix: 'Puck.js' }],
* // optionalServices: deriveOptionalServices(NordicUARTProfile) })
* const uart = new NordicUARTProfile(device);
* await uart.connect();
*
* const decoder = new TextDecoder();
* const unsubscribe = uart.onReceive((chunk) => {
* process.stdout.write(decoder.decode(chunk));
* });
*
* await uart.send(new TextEncoder().encode('LED1.set()\n'));
*
* unsubscribe();
* uart.stop();
* ```
*/
declare class NordicUARTProfile extends BaseProfile {
/** Services this profile's device may reach after connection (the NUS service). Read by {@link deriveOptionalServices}. */
static readonly services: readonly string[];
protected readonly service = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
/**
* Subscribe to inbound data from the device (TX characteristic, notify).
* Each notification is delivered as a raw {@link DataView} chunk.
*
* @param callback - Invoked with every inbound chunk.
* @returns Unsubscribe function. Also cleaned up by {@link BaseProfile.stop}.
*/
onReceive(callback: (chunk: DataView) => void): () => void;
/**
* Send data to the device (RX characteristic, write-without-response).
* Payloads larger than the negotiated write-without-response limit are
* split into MTU-sized chunks and written sequentially.
*
* @param data - Bytes to send. Accepts any {@link BufferSource}.
*/
send(data: BufferSource): Promise<void>;
}
export { NUS_SERVICES, NordicUARTProfile };
import { B as BaseProfile } from '../base-BnHcG-k7.js';
import '../device-B5NsJWvh.js';
/**
* Service UUIDs a Nordic UART device may reach after connection (the single NUS
* service). Use with `optionalServices` / `Beacio.registerServices`, or via
* {@link deriveOptionalServices} given {@link NordicUARTProfile}.
*/
declare const NUS_SERVICES: readonly string[];
/**
* Nordic UART Service (NUS) profile — a bidirectional serial-over-BLE pipe.
*
* The de-facto standard "UART service" exposed by Espruino devices
* (Bangle.js, Puck.js, Pixl.js, MDBT42Q), the BBC micro:bit, and Adafruit
* Bluefruit modules. Data flows over two characteristics on the NUS service
* `6e400001-b5a3-f393-e0a9-e50e24dcca9e`:
*
* - **TX** `6e400003-…` — device -> host, delivered via notifications.
* Enabled through {@link BaseProfile.subscribe} (the native layer owns the
* CCCD descriptor; `startNotifications()` covers notify *and* indicate).
* - **RX** `6e400002-…` — host -> device, sent with write-without-response and
* chunked to the negotiated MTU.
*
* Strictly W3C `navigator.bluetooth` GATT: this profile never reads or writes
* a CCCD/SCCD descriptor itself.
*
* @example
* ```ts
* import { NordicUARTProfile, deriveOptionalServices } from '@beacio/core/profiles';
*
* // Declare the service from the profile — no hand-copied UUID:
* // requestDevice({ filters: [{ namePrefix: 'Puck.js' }],
* // optionalServices: deriveOptionalServices(NordicUARTProfile) })
* const uart = new NordicUARTProfile(device);
* await uart.connect();
*
* const decoder = new TextDecoder();
* const unsubscribe = uart.onReceive((chunk) => {
* process.stdout.write(decoder.decode(chunk));
* });
*
* await uart.send(new TextEncoder().encode('LED1.set()\n'));
*
* unsubscribe();
* uart.stop();
* ```
*/
declare class NordicUARTProfile extends BaseProfile {
/** Services this profile's device may reach after connection (the NUS service). Read by {@link deriveOptionalServices}. */
static readonly services: readonly string[];
protected readonly service = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
/**
* Subscribe to inbound data from the device (TX characteristic, notify).
* Each notification is delivered as a raw {@link DataView} chunk.
*
* @param callback - Invoked with every inbound chunk.
* @returns Unsubscribe function. Also cleaned up by {@link BaseProfile.stop}.
*/
onReceive(callback: (chunk: DataView) => void): () => void;
/**
* Send data to the device (RX characteristic, write-without-response).
* Payloads larger than the negotiated write-without-response limit are
* split into MTU-sized chunks and written sequentially.
*
* @param data - Bytes to send. Accepts any {@link BufferSource}.
*/
send(data: BufferSource): Promise<void>;
}
export { NUS_SERVICES, NordicUARTProfile };
'use strict';var a=class{constructor(i){this.cleanups=[];this.device=i;}async connect(){await this.device.connect();}stop(){for(let i of this.cleanups.splice(0))i();}dispose(){this.stop();}async read(i){return this.device.read(this.service,i)}async write(i,e){return this.device.write(this.service,i,e)}async writeWithoutResponse(i,e){return this.device.writeWithoutResponse(this.service,i,e)}async sendChunked(i,e,t={}){return this.device.writeFragmented(this.service,i,e,{mode:"without-response",...t})}async writeValue(i,e,t){return t?.mode==="without-response"?this.device.writeWithoutResponse(this.service,i,e,t):this.device.write(this.service,i,e,t)}async getWriteLimits(){return this.device.getWriteLimits()}async getMtu(){return this.device.getMtu()}subscribe(i,e){let t=this.device.subscribe(this.service,i,e);return this.cleanups.push(t),()=>{t(),this.cleanups=this.cleanups.filter(r=>r!==t);}}onOverflow(i,e){let t=this.device.onCharacteristicOverflow(this.service,i,r=>{e(c(r));});return this.cleanups.push(t),()=>{t(),this.cleanups=this.cleanups.filter(r=>r!==t);}}};function c(s){let i=s.detail,e=i&&typeof i=="object"?i:{};return {evictedCount:typeof e.evictedCount=="number"?e.evictedCount:void 0,queueCapacity:typeof e.queueCapacity=="number"?e.queueCapacity:void 0,seq:typeof e.seq=="number"?e.seq:void 0,timestamp:typeof e.timestamp=="number"?e.timestamp:void 0}}var o="6e400001-b5a3-f393-e0a9-e50e24dcca9e",d="6e400002-b5a3-f393-e0a9-e50e24dcca9e",u="6e400003-b5a3-f393-e0a9-e50e24dcca9e",l=[o],n=class extends a{constructor(){super(...arguments);this.service=o;}onReceive(e){return this.subscribe(u,e)}async send(e){await this.sendChunked(d,e);}};n.services=l;exports.NUS_SERVICES=l;exports.NordicUARTProfile=n;//# sourceMappingURL=nordic-uart.js.map
//# sourceMappingURL=nordic-uart.js.map
{"version":3,"sources":["../../src/profiles/base.ts","../../src/profiles/nordic-uart.ts"],"names":["BaseProfile","device","cleanup","characteristic","value","options","callback","unsubscribe","candidate","event","decodeNativeOverflow","detail","meta","NUS_SERVICE","NUS_RX","NUS_TX","NUS_SERVICES","NordicUARTProfile","data"],"mappings":"aA+FO,IAAeA,EAAf,KAA2B,CAKhC,WAAA,CAAYC,CAAAA,CAAsB,CAFlC,IAAA,CAAQ,QAAA,CAA2B,EAAC,CAGlC,KAAK,MAAA,CAASA,EAChB,CAEA,MAAM,SAAyB,CAC7B,MAAM,IAAA,CAAK,MAAA,CAAO,UACpB,CAEA,IAAA,EAAa,CACX,QAAWC,CAAAA,IAAW,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,CAC1CA,CAAAA,GAEJ,CAEA,OAAA,EAAgB,CACd,IAAA,CAAK,IAAA,GACP,CAEA,MAAgB,IAAA,CAAKC,CAAAA,CAA2C,CAC9D,OAAO,KAAK,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,OAAA,CAASA,CAAc,CACtD,CAEA,MAAgB,KAAA,CAAMA,EAAwBC,CAAAA,CAAoC,CAChF,OAAO,IAAA,CAAK,OAAO,KAAA,CAAM,IAAA,CAAK,OAAA,CAASD,CAAAA,CAAgBC,CAAK,CAC9D,CAEA,MAAgB,oBAAA,CAAqBD,EAAwBC,CAAAA,CAAoC,CAC/F,OAAO,IAAA,CAAK,MAAA,CAAO,qBAAqB,IAAA,CAAK,OAAA,CAASD,CAAAA,CAAgBC,CAAK,CAC7E,CAmBA,MAAgB,WAAA,CACdD,CAAAA,CACAC,EACAC,CAAAA,CAAkC,EAAC,CACH,CAChC,OAAO,IAAA,CAAK,MAAA,CAAO,eAAA,CAAgB,IAAA,CAAK,QAASF,CAAAA,CAAgBC,CAAAA,CAAO,CACtE,IAAA,CAAM,mBACN,GAAGC,CACL,CAAC,CACH,CAEA,MAAgB,UAAA,CAAWF,CAAAA,CAAwBC,CAAAA,CAAqBC,EAAuC,CAC7G,OAAIA,GAAS,IAAA,GAAS,kBAAA,CACb,KAAK,MAAA,CAAO,oBAAA,CAAqB,IAAA,CAAK,OAAA,CAASF,EAAgBC,CAAAA,CAAOC,CAAO,CAAA,CAE/E,IAAA,CAAK,OAAO,KAAA,CAAM,IAAA,CAAK,OAAA,CAASF,CAAAA,CAAgBC,EAAOC,CAAO,CACvE,CAEA,MAAgB,gBAAuC,CACrD,OAAO,IAAA,CAAK,MAAA,CAAO,gBACrB,CAEA,MAAgB,MAAA,EAAiC,CAC/C,OAAO,IAAA,CAAK,MAAA,CAAO,MAAA,EACrB,CAEU,SAAA,CAAUF,EAAwBG,CAAAA,CAA4C,CACtF,IAAMC,CAAAA,CAAc,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,KAAK,OAAA,CAASJ,CAAAA,CAAgBG,CAAQ,CAAA,CAChF,YAAK,QAAA,CAAS,IAAA,CAAKC,CAAW,CAAA,CACvB,IAAM,CACXA,CAAAA,EAAY,CACZ,IAAA,CAAK,SAAW,IAAA,CAAK,QAAA,CAAS,MAAA,CAAQC,CAAAA,EAAcA,IAAcD,CAAW,EAC/E,CACF,CAoBU,WAAWJ,CAAAA,CAAwBG,CAAAA,CAA4D,CACvG,IAAMC,EAAc,IAAA,CAAK,MAAA,CAAO,yBAAyB,IAAA,CAAK,OAAA,CAASJ,EAAiBM,CAAAA,EAAU,CAChGH,CAAAA,CAASI,CAAAA,CAAqBD,CAAK,CAAC,EACtC,CAAC,CAAA,CACD,YAAK,QAAA,CAAS,IAAA,CAAKF,CAAW,CAAA,CACvB,IAAM,CACXA,CAAAA,EAAY,CACZ,IAAA,CAAK,SAAW,IAAA,CAAK,QAAA,CAAS,MAAA,CAAQC,CAAAA,EAAcA,IAAcD,CAAW,EAC/E,CACF,CACF,EASA,SAASG,CAAAA,CAAqBD,CAAAA,CAAmC,CAC/D,IAAME,CAAAA,CAAUF,CAAAA,CAAsB,OAChCG,CAAAA,CAAQD,CAAAA,EAAU,OAAOA,CAAAA,EAAW,QAAA,CAAaA,CAAAA,CAAqC,GAC5F,OAAO,CACL,YAAA,CAAc,OAAOC,EAAK,YAAA,EAAiB,QAAA,CAAWA,CAAAA,CAAK,YAAA,CAAe,OAC1E,aAAA,CAAe,OAAOA,CAAAA,CAAK,aAAA,EAAkB,SAAWA,CAAAA,CAAK,aAAA,CAAgB,MAAA,CAC7E,GAAA,CAAK,OAAOA,CAAAA,CAAK,GAAA,EAAQ,QAAA,CAAWA,CAAAA,CAAK,IAAM,MAAA,CAC/C,SAAA,CAAW,OAAOA,CAAAA,CAAK,WAAc,QAAA,CAAWA,CAAAA,CAAK,UAAY,MACnE,CACF,CCjOA,IAAMC,CAAAA,CAAc,sCAAA,CAEdC,CAAAA,CAAS,uCAETC,CAAAA,CAAS,sCAAA,CAOFC,CAAAA,CAAkC,CAACH,CAAW,CAAA,CAwC9CI,CAAAA,CAAN,cAAgCjB,CAAY,CAA5C,WAAA,EAAA,CAAA,KAAA,CAAA,GAAA,SAAA,CAAA,CAIL,IAAA,CAAmB,QAAUa,EAAAA,CAS7B,SAAA,CAAUP,EAAiD,CACzD,OAAO,IAAA,CAAK,SAAA,CAAUS,EAAQT,CAAQ,CACxC,CASA,MAAM,KAAKY,CAAAA,CAAmC,CAI5C,MAAM,IAAA,CAAK,YAAYJ,CAAAA,CAAQI,CAAI,EACrC,CACF,EA9BaD,EAEK,QAAA,CAAWD,CAAAA","file":"nordic-uart.js","sourcesContent":["import type {\n NotificationCallback,\n NativeOverflowEvent,\n BeacioDevice,\n WriteFragmentedOptions,\n WriteFragmentedResult,\n WriteLimits,\n WriteOptions,\n} from '../index';\nimport { resolveUUID } from '../uuid';\n\n// Sound top type for \"any characteristic definition\": TRead is covariant\n// (parse return → unknown), TWrite is contravariant (serialize param → never),\n// so every CharacteristicDefinition<A, B> is assignable here without `any`.\ntype AnyCharacteristicDefinition = CharacteristicDefinition<unknown, never>;\n\nexport function parseRawBytes(value: BufferSource): DataView {\n if (value instanceof DataView) {\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n }\n\n if (value instanceof ArrayBuffer) {\n return new DataView(value);\n }\n\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n}\n\ntype UUIDLike = string;\ntype Capability = 'read' | 'write' | 'writeWithoutResponse' | 'notify';\ntype CapabilitySet = readonly Capability[];\n\ntype CharacteristicReadConfig<T> = {\n capabilities: readonly ['read'] | readonly ['read', ...Capability[]];\n parse: (dv: DataView) => T;\n};\n\ntype CharacteristicWriteConfig<W> = {\n capabilities: readonly ['write'] | readonly ['writeWithoutResponse'] | readonly ['write', ...Capability[]] | readonly ['writeWithoutResponse', ...Capability[]];\n serialize: (value: W) => BufferSource;\n};\n\ntype CharacteristicReadWriteConfig<T, W> = {\n capabilities:\n | readonly ['read', 'write']\n | readonly ['read', 'writeWithoutResponse']\n | readonly ['write', 'read']\n | readonly ['writeWithoutResponse', 'read']\n | readonly ['read', 'write', ...Capability[]]\n | readonly ['read', 'writeWithoutResponse', ...Capability[]]\n | readonly ['write', 'read', ...Capability[]]\n | readonly ['writeWithoutResponse', 'read', ...Capability[]];\n parse: (dv: DataView) => T;\n serialize: (value: W) => BufferSource;\n};\n\nexport type CharacteristicDefinition<TRead = never, TWrite = never> = {\n uuid: UUIDLike;\n} & (\n | CharacteristicReadConfig<TRead>\n | CharacteristicWriteConfig<TWrite>\n | CharacteristicReadWriteConfig<TRead, TWrite>\n);\n\nexport interface ProfileConfig<C extends Record<string, AnyCharacteristicDefinition>> {\n name: string;\n service: UUIDLike;\n characteristics: C;\n}\n\ntype CapabilityOf<T extends AnyCharacteristicDefinition> = T['capabilities'][number];\ntype ReadableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'read' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\ntype WritableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'write' extends CapabilityOf<C[K]>\n ? K\n : 'writeWithoutResponse' extends CapabilityOf<C[K]>\n ? K\n : never;\n}[keyof C] & string;\ntype NotifiableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'notify' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\n\ntype ReadValue<T> = T extends { parse: (dv: DataView) => infer TResult } ? TResult : never;\ntype WriteValue<T> = T extends { serialize: (value: infer TValue) => BufferSource } ? TValue : never;\ntype CanonicalCharacteristic<C extends AnyCharacteristicDefinition> = Omit<C, 'uuid'> & { uuid: string };\ntype ReadParser<T> = { parse: (dv: DataView) => T };\ntype WriteSerializer<T> = { serialize: (value: T) => BufferSource };\n\nfunction hasCapability(capabilities: CapabilitySet, capability: Capability): boolean {\n return capabilities.includes(capability);\n}\n\nexport abstract class BaseProfile {\n protected device: BeacioDevice;\n protected abstract readonly service: string;\n private cleanups: (() => void)[] = [];\n\n constructor(device: BeacioDevice) {\n this.device = device;\n }\n\n async connect(): Promise<void> {\n await this.device.connect();\n }\n\n stop(): void {\n for (const cleanup of this.cleanups.splice(0)) {\n cleanup();\n }\n }\n\n dispose(): void {\n this.stop();\n }\n\n protected async read(characteristic: string): Promise<DataView> {\n return this.device.read(this.service, characteristic);\n }\n\n protected async write(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.write(this.service, characteristic, value);\n }\n\n protected async writeWithoutResponse(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.writeWithoutResponse(this.service, characteristic, value);\n }\n\n /**\n * Send a payload of any size to `characteristic`, fragmenting it into\n * MTU-sized chunks. This is a thin passthrough to {@link BeacioDevice.writeFragmented},\n * which owns the (already-clamped) chunk-size derivation via the branded\n * `ChunkSize` smart-constructors in the core write-chunker — so the stride is\n * guaranteed `>= 1` and a zero-stride infinite loop is unrepresentable.\n *\n * Profiles MUST use this instead of hand-rolling a `for (offset += step)` /\n * `subarray()` / `writeWithoutResponse()` chunk loop (enforced by the\n * `no-restricted-syntax` guard scoped to `packages/profiles/src`). Defaults to\n * `mode: 'without-response'` — the serial-pipe convention (Nordic UART, HM-10).\n *\n * @param characteristic - Target characteristic UUID or alias on this profile's service.\n * @param value - Bytes to send. Accepts any {@link BufferSource}.\n * @param options - Fragmentation/retry overrides; `mode` defaults to `'without-response'`.\n * @returns The {@link WriteFragmentedResult} (bytes written, chunk size/count, retries).\n */\n protected async sendChunked(\n characteristic: string,\n value: BufferSource,\n options: WriteFragmentedOptions = {},\n ): Promise<WriteFragmentedResult> {\n return this.device.writeFragmented(this.service, characteristic, value, {\n mode: 'without-response',\n ...options,\n });\n }\n\n protected async writeValue(characteristic: string, value: BufferSource, options?: WriteOptions): Promise<void> {\n if (options?.mode === 'without-response') {\n return this.device.writeWithoutResponse(this.service, characteristic, value, options);\n }\n return this.device.write(this.service, characteristic, value, options);\n }\n\n protected async getWriteLimits(): Promise<WriteLimits> {\n return this.device.getWriteLimits();\n }\n\n protected async getMtu(): Promise<number | null> {\n return this.device.getMtu();\n }\n\n protected subscribe(characteristic: string, callback: NotificationCallback): () => void {\n const unsubscribe = this.device.subscribe(this.service, characteristic, callback);\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n\n /**\n * Observe NATIVE notification-queue overflows for `characteristic` on this\n * profile's service. The bounded Swift `EventQueue` evicts notifications under\n * sustained high-frequency load and the polyfill surfaces each eviction as a\n * `beacio:overflow` `CustomEvent` on the characteristic; this decodes that\n * event's `detail` into a typed {@link NativeOverflowEvent} and forwards it to\n * `callback`.\n *\n * Lifecycle parity with {@link subscribe}: the returned unsubscribe is also\n * registered into the profile's cleanup set, so {@link stop}/{@link dispose}\n * detach the listener too. A staleness `callback` should typically re-read the\n * affected characteristic to resynchronise any UI tracking the last notified\n * value rather than trusting that (now-stale) value.\n *\n * @param characteristic - Characteristic UUID or alias on this profile's service.\n * @param callback - Called with the decoded eviction metadata on each overflow.\n * @returns Unsubscribe function.\n */\n protected onOverflow(characteristic: string, callback: (event: NativeOverflowEvent) => void): () => void {\n const unsubscribe = this.device.onCharacteristicOverflow(this.service, characteristic, (event) => {\n callback(decodeNativeOverflow(event));\n });\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n}\n\n/**\n * Decode a `beacio:overflow` {@link Event} (a `CustomEvent` whose `detail` carries\n * the native bounded-queue eviction metadata) into a typed\n * {@link NativeOverflowEvent}. Each field is `undefined` when the native bridge\n * omitted it (forward-compat guard); a conforming bridge supplies all four. Total\n * and side-effect-free — never throws on a malformed or detail-less event.\n */\nfunction decodeNativeOverflow(event: Event): NativeOverflowEvent {\n const detail = (event as CustomEvent).detail as unknown;\n const meta = (detail && typeof detail === 'object') ? (detail as Record<string, unknown>) : {};\n return {\n evictedCount: typeof meta.evictedCount === 'number' ? meta.evictedCount : undefined,\n queueCapacity: typeof meta.queueCapacity === 'number' ? meta.queueCapacity : undefined,\n seq: typeof meta.seq === 'number' ? meta.seq : undefined,\n timestamp: typeof meta.timestamp === 'number' ? meta.timestamp : undefined,\n };\n}\n\ntype DefinedProfileInstance<C extends Record<string, AnyCharacteristicDefinition>> = BaseProfile & {\n readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>>;\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void;\n writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void>;\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability>;\n getCharacteristicUUID<K extends keyof C & string>(name: K): string;\n getServiceUUID(): string;\n getWriteLimits(): Promise<WriteLimits>;\n getMtu(): Promise<number | null>;\n};\n\nexport interface DefinedProfile<C extends Record<string, AnyCharacteristicDefinition>> {\n new (device: BeacioDevice): DefinedProfileInstance<C>;\n readonly profileName: string;\n readonly serviceUUID: string;\n readonly characteristics: {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n}\n\nexport function defineProfile<C extends Record<string, AnyCharacteristicDefinition>>(\n config: ProfileConfig<C>,\n): DefinedProfile<C> {\n const serviceUUID = resolveUUID(config.service);\n const characteristics = Object.fromEntries(\n Object.entries(config.characteristics).map(([name, definition]) => {\n const canonical = {\n ...definition,\n uuid: resolveUUID(definition.uuid),\n };\n\n if (hasCapability(canonical.capabilities, 'read') && typeof (canonical as { parse?: unknown }).parse !== 'function') {\n throw new Error(`Characteristic ${name} declares read capability but is missing parse()`);\n }\n\n if (\n (hasCapability(canonical.capabilities, 'write') || hasCapability(canonical.capabilities, 'writeWithoutResponse'))\n && typeof (canonical as { serialize?: unknown }).serialize !== 'function'\n ) {\n throw new Error(`Characteristic ${name} declares write capability but is missing serialize()`);\n }\n\n return [name, canonical];\n }),\n ) as unknown as {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n\n class GeneratedProfile extends BaseProfile {\n static readonly profileName = config.name;\n static readonly serviceUUID = serviceUUID;\n static readonly characteristics = characteristics;\n\n protected readonly service = serviceUUID;\n\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability> {\n return characteristics[name].capabilities;\n }\n\n getCharacteristicUUID<K extends keyof C & string>(name: K): string {\n return characteristics[name].uuid;\n }\n\n getServiceUUID(): string {\n return serviceUUID;\n }\n\n async readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n const raw = await this.read(characteristic.uuid);\n return characteristic.parse(raw);\n }\n\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n return this.subscribe(characteristic.uuid, (value) => {\n cb(characteristic.parse(value));\n });\n }\n\n async writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & WriteSerializer<WriteValue<C[K]>>;\n const serialized = characteristic.serialize(value);\n const mode = options?.mode ?? (hasCapability(characteristic.capabilities, 'write') ? 'with-response' : 'without-response');\n await this.writeValue(characteristic.uuid, serialized, { ...options, mode });\n }\n\n async getWriteLimits(): Promise<WriteLimits> {\n return super.getWriteLimits();\n }\n\n async getMtu(): Promise<number | null> {\n return super.getMtu();\n }\n}\n\n return GeneratedProfile as unknown as DefinedProfile<C>;\n}\n","import { BaseProfile } from './base';\n\n/** Nordic UART Service (NUS) UUID. */\nconst NUS_SERVICE = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';\n/** RX characteristic: host -> device. Write (without response). */\nconst NUS_RX = '6e400002-b5a3-f393-e0a9-e50e24dcca9e';\n/** TX characteristic: device -> host. Notify. */\nconst NUS_TX = '6e400003-b5a3-f393-e0a9-e50e24dcca9e';\n\n/**\n * Service UUIDs a Nordic UART device may reach after connection (the single NUS\n * service). Use with `optionalServices` / `Beacio.registerServices`, or via\n * {@link deriveOptionalServices} given {@link NordicUARTProfile}.\n */\nexport const NUS_SERVICES: readonly string[] = [NUS_SERVICE];\n\n/**\n * Nordic UART Service (NUS) profile — a bidirectional serial-over-BLE pipe.\n *\n * The de-facto standard \"UART service\" exposed by Espruino devices\n * (Bangle.js, Puck.js, Pixl.js, MDBT42Q), the BBC micro:bit, and Adafruit\n * Bluefruit modules. Data flows over two characteristics on the NUS service\n * `6e400001-b5a3-f393-e0a9-e50e24dcca9e`:\n *\n * - **TX** `6e400003-…` — device -> host, delivered via notifications.\n * Enabled through {@link BaseProfile.subscribe} (the native layer owns the\n * CCCD descriptor; `startNotifications()` covers notify *and* indicate).\n * - **RX** `6e400002-…` — host -> device, sent with write-without-response and\n * chunked to the negotiated MTU.\n *\n * Strictly W3C `navigator.bluetooth` GATT: this profile never reads or writes\n * a CCCD/SCCD descriptor itself.\n *\n * @example\n * ```ts\n * import { NordicUARTProfile, deriveOptionalServices } from '@beacio/core/profiles';\n *\n * // Declare the service from the profile — no hand-copied UUID:\n * // requestDevice({ filters: [{ namePrefix: 'Puck.js' }],\n * // optionalServices: deriveOptionalServices(NordicUARTProfile) })\n * const uart = new NordicUARTProfile(device);\n * await uart.connect();\n *\n * const decoder = new TextDecoder();\n * const unsubscribe = uart.onReceive((chunk) => {\n * process.stdout.write(decoder.decode(chunk));\n * });\n *\n * await uart.send(new TextEncoder().encode('LED1.set()\\n'));\n *\n * unsubscribe();\n * uart.stop();\n * ```\n */\nexport class NordicUARTProfile extends BaseProfile {\n /** Services this profile's device may reach after connection (the NUS service). Read by {@link deriveOptionalServices}. */\n static readonly services = NUS_SERVICES;\n\n protected readonly service = NUS_SERVICE;\n\n /**\n * Subscribe to inbound data from the device (TX characteristic, notify).\n * Each notification is delivered as a raw {@link DataView} chunk.\n *\n * @param callback - Invoked with every inbound chunk.\n * @returns Unsubscribe function. Also cleaned up by {@link BaseProfile.stop}.\n */\n onReceive(callback: (chunk: DataView) => void): () => void {\n return this.subscribe(NUS_TX, callback);\n }\n\n /**\n * Send data to the device (RX characteristic, write-without-response).\n * Payloads larger than the negotiated write-without-response limit are\n * split into MTU-sized chunks and written sequentially.\n *\n * @param data - Bytes to send. Accepts any {@link BufferSource}.\n */\n async send(data: BufferSource): Promise<void> {\n // Delegate fragmentation to the core write-chunker (via BaseProfile.sendChunked),\n // which derives a branded, always-positive ChunkSize from the negotiated\n // write-without-response limit / MTU. No hand-rolled offset loop here.\n await this.sendChunked(NUS_RX, data);\n }\n}\n"]}
export{a as NUS_SERVICES,b as NordicUARTProfile}from'../chunk-WKLBTKL5.mjs';import'../chunk-FANWIUKA.mjs';import'../chunk-33IHM3NV.mjs';//# sourceMappingURL=nordic-uart.mjs.map
//# sourceMappingURL=nordic-uart.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"nordic-uart.mjs"}
import { B as BaseProfile } from '../base-YuFhZsjv.mjs';
import '../device-B5NsJWvh.mjs';
/**
* HM-10 (and compatible CC2540/CC2541 modules: HM-11, AT-09, JDY-08, …)
* transparent-serial profile.
*
* Unlike Nordic UART's two-characteristic design, the HM-10 multiplexes both
* directions onto a *single* characteristic `0000ffe1-…` on service
* `0000ffe0-…`: the host writes to it (write-without-response) and the device
* pushes inbound bytes back via notifications on the very same handle.
*
* Strictly W3C `navigator.bluetooth` GATT: notifications are enabled through
* {@link BaseProfile.subscribe} (`startNotifications()`); this profile never
* reads or writes a CCCD/SCCD descriptor itself.
*
* @example
* ```ts
* import { HM10SerialProfile } from '@beacio/core/profiles';
*
* // requestDevice({ filters: [{ services: ['0000ffe0-0000-1000-8000-00805f9b34fb'] }] })
* const serial = new HM10SerialProfile(device);
* await serial.connect();
*
* const decoder = new TextDecoder();
* const unsubscribe = serial.onReceive((chunk) => {
* console.log(decoder.decode(chunk));
* });
*
* await serial.send(new TextEncoder().encode('AT+NAME?\r\n'));
*
* unsubscribe();
* serial.stop();
* ```
*/
declare class HM10SerialProfile extends BaseProfile {
protected readonly service = "0000ffe0-0000-1000-8000-00805f9b34fb";
/**
* Subscribe to inbound data from the module (FFE1 notify).
* Each notification is delivered as a raw {@link DataView} chunk.
*
* @param callback - Invoked with every inbound chunk.
* @returns Unsubscribe function. Also cleaned up by {@link BaseProfile.stop}.
*/
onReceive(callback: (chunk: DataView) => void): () => void;
/**
* Send data to the module (FFE1 write-without-response — the same handle
* used for inbound notifications). Payloads larger than the negotiated
* write-without-response limit are split into MTU-sized chunks and written
* sequentially.
*
* @param data - Bytes to send. Accepts any {@link BufferSource}.
*/
send(data: BufferSource): Promise<void>;
}
export { HM10SerialProfile };
import { B as BaseProfile } from '../base-BnHcG-k7.js';
import '../device-B5NsJWvh.js';
/**
* HM-10 (and compatible CC2540/CC2541 modules: HM-11, AT-09, JDY-08, …)
* transparent-serial profile.
*
* Unlike Nordic UART's two-characteristic design, the HM-10 multiplexes both
* directions onto a *single* characteristic `0000ffe1-…` on service
* `0000ffe0-…`: the host writes to it (write-without-response) and the device
* pushes inbound bytes back via notifications on the very same handle.
*
* Strictly W3C `navigator.bluetooth` GATT: notifications are enabled through
* {@link BaseProfile.subscribe} (`startNotifications()`); this profile never
* reads or writes a CCCD/SCCD descriptor itself.
*
* @example
* ```ts
* import { HM10SerialProfile } from '@beacio/core/profiles';
*
* // requestDevice({ filters: [{ services: ['0000ffe0-0000-1000-8000-00805f9b34fb'] }] })
* const serial = new HM10SerialProfile(device);
* await serial.connect();
*
* const decoder = new TextDecoder();
* const unsubscribe = serial.onReceive((chunk) => {
* console.log(decoder.decode(chunk));
* });
*
* await serial.send(new TextEncoder().encode('AT+NAME?\r\n'));
*
* unsubscribe();
* serial.stop();
* ```
*/
declare class HM10SerialProfile extends BaseProfile {
protected readonly service = "0000ffe0-0000-1000-8000-00805f9b34fb";
/**
* Subscribe to inbound data from the module (FFE1 notify).
* Each notification is delivered as a raw {@link DataView} chunk.
*
* @param callback - Invoked with every inbound chunk.
* @returns Unsubscribe function. Also cleaned up by {@link BaseProfile.stop}.
*/
onReceive(callback: (chunk: DataView) => void): () => void;
/**
* Send data to the module (FFE1 write-without-response — the same handle
* used for inbound notifications). Payloads larger than the negotiated
* write-without-response limit are split into MTU-sized chunks and written
* sequentially.
*
* @param data - Bytes to send. Accepts any {@link BufferSource}.
*/
send(data: BufferSource): Promise<void>;
}
export { HM10SerialProfile };
'use strict';var a=class{constructor(i){this.cleanups=[];this.device=i;}async connect(){await this.device.connect();}stop(){for(let i of this.cleanups.splice(0))i();}dispose(){this.stop();}async read(i){return this.device.read(this.service,i)}async write(i,e){return this.device.write(this.service,i,e)}async writeWithoutResponse(i,e){return this.device.writeWithoutResponse(this.service,i,e)}async sendChunked(i,e,t={}){return this.device.writeFragmented(this.service,i,e,{mode:"without-response",...t})}async writeValue(i,e,t){return t?.mode==="without-response"?this.device.writeWithoutResponse(this.service,i,e,t):this.device.write(this.service,i,e,t)}async getWriteLimits(){return this.device.getWriteLimits()}async getMtu(){return this.device.getMtu()}subscribe(i,e){let t=this.device.subscribe(this.service,i,e);return this.cleanups.push(t),()=>{t(),this.cleanups=this.cleanups.filter(r=>r!==t);}}onOverflow(i,e){let t=this.device.onCharacteristicOverflow(this.service,i,r=>{e(c(r));});return this.cleanups.push(t),()=>{t(),this.cleanups=this.cleanups.filter(r=>r!==t);}}};function c(s){let i=s.detail,e=i&&typeof i=="object"?i:{};return {evictedCount:typeof e.evictedCount=="number"?e.evictedCount:void 0,queueCapacity:typeof e.queueCapacity=="number"?e.queueCapacity:void 0,seq:typeof e.seq=="number"?e.seq:void 0,timestamp:typeof e.timestamp=="number"?e.timestamp:void 0}}var d="0000ffe0-0000-1000-8000-00805f9b34fb",n="0000ffe1-0000-1000-8000-00805f9b34fb",o=class extends a{constructor(){super(...arguments);this.service=d;}onReceive(e){return this.subscribe(n,e)}async send(e){await this.sendChunked(n,e);}};exports.HM10SerialProfile=o;//# sourceMappingURL=serial-ffe0.js.map
//# sourceMappingURL=serial-ffe0.js.map
{"version":3,"sources":["../../src/profiles/base.ts","../../src/profiles/serial-ffe0.ts"],"names":["BaseProfile","device","cleanup","characteristic","value","options","callback","unsubscribe","candidate","event","decodeNativeOverflow","detail","meta","FFE0_SERVICE","FFE1_CHAR","HM10SerialProfile","data"],"mappings":"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","file":"serial-ffe0.js","sourcesContent":["import type {\n NotificationCallback,\n NativeOverflowEvent,\n BeacioDevice,\n WriteFragmentedOptions,\n WriteFragmentedResult,\n WriteLimits,\n WriteOptions,\n} from '../index';\nimport { resolveUUID } from '../uuid';\n\n// Sound top type for \"any characteristic definition\": TRead is covariant\n// (parse return → unknown), TWrite is contravariant (serialize param → never),\n// so every CharacteristicDefinition<A, B> is assignable here without `any`.\ntype AnyCharacteristicDefinition = CharacteristicDefinition<unknown, never>;\n\nexport function parseRawBytes(value: BufferSource): DataView {\n if (value instanceof DataView) {\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n }\n\n if (value instanceof ArrayBuffer) {\n return new DataView(value);\n }\n\n return new DataView(value.buffer, value.byteOffset, value.byteLength);\n}\n\ntype UUIDLike = string;\ntype Capability = 'read' | 'write' | 'writeWithoutResponse' | 'notify';\ntype CapabilitySet = readonly Capability[];\n\ntype CharacteristicReadConfig<T> = {\n capabilities: readonly ['read'] | readonly ['read', ...Capability[]];\n parse: (dv: DataView) => T;\n};\n\ntype CharacteristicWriteConfig<W> = {\n capabilities: readonly ['write'] | readonly ['writeWithoutResponse'] | readonly ['write', ...Capability[]] | readonly ['writeWithoutResponse', ...Capability[]];\n serialize: (value: W) => BufferSource;\n};\n\ntype CharacteristicReadWriteConfig<T, W> = {\n capabilities:\n | readonly ['read', 'write']\n | readonly ['read', 'writeWithoutResponse']\n | readonly ['write', 'read']\n | readonly ['writeWithoutResponse', 'read']\n | readonly ['read', 'write', ...Capability[]]\n | readonly ['read', 'writeWithoutResponse', ...Capability[]]\n | readonly ['write', 'read', ...Capability[]]\n | readonly ['writeWithoutResponse', 'read', ...Capability[]];\n parse: (dv: DataView) => T;\n serialize: (value: W) => BufferSource;\n};\n\nexport type CharacteristicDefinition<TRead = never, TWrite = never> = {\n uuid: UUIDLike;\n} & (\n | CharacteristicReadConfig<TRead>\n | CharacteristicWriteConfig<TWrite>\n | CharacteristicReadWriteConfig<TRead, TWrite>\n);\n\nexport interface ProfileConfig<C extends Record<string, AnyCharacteristicDefinition>> {\n name: string;\n service: UUIDLike;\n characteristics: C;\n}\n\ntype CapabilityOf<T extends AnyCharacteristicDefinition> = T['capabilities'][number];\ntype ReadableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'read' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\ntype WritableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'write' extends CapabilityOf<C[K]>\n ? K\n : 'writeWithoutResponse' extends CapabilityOf<C[K]>\n ? K\n : never;\n}[keyof C] & string;\ntype NotifiableKeys<C extends Record<string, AnyCharacteristicDefinition>> = {\n [K in keyof C]: 'notify' extends CapabilityOf<C[K]> ? K : never;\n}[keyof C] & string;\n\ntype ReadValue<T> = T extends { parse: (dv: DataView) => infer TResult } ? TResult : never;\ntype WriteValue<T> = T extends { serialize: (value: infer TValue) => BufferSource } ? TValue : never;\ntype CanonicalCharacteristic<C extends AnyCharacteristicDefinition> = Omit<C, 'uuid'> & { uuid: string };\ntype ReadParser<T> = { parse: (dv: DataView) => T };\ntype WriteSerializer<T> = { serialize: (value: T) => BufferSource };\n\nfunction hasCapability(capabilities: CapabilitySet, capability: Capability): boolean {\n return capabilities.includes(capability);\n}\n\nexport abstract class BaseProfile {\n protected device: BeacioDevice;\n protected abstract readonly service: string;\n private cleanups: (() => void)[] = [];\n\n constructor(device: BeacioDevice) {\n this.device = device;\n }\n\n async connect(): Promise<void> {\n await this.device.connect();\n }\n\n stop(): void {\n for (const cleanup of this.cleanups.splice(0)) {\n cleanup();\n }\n }\n\n dispose(): void {\n this.stop();\n }\n\n protected async read(characteristic: string): Promise<DataView> {\n return this.device.read(this.service, characteristic);\n }\n\n protected async write(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.write(this.service, characteristic, value);\n }\n\n protected async writeWithoutResponse(characteristic: string, value: BufferSource): Promise<void> {\n return this.device.writeWithoutResponse(this.service, characteristic, value);\n }\n\n /**\n * Send a payload of any size to `characteristic`, fragmenting it into\n * MTU-sized chunks. This is a thin passthrough to {@link BeacioDevice.writeFragmented},\n * which owns the (already-clamped) chunk-size derivation via the branded\n * `ChunkSize` smart-constructors in the core write-chunker — so the stride is\n * guaranteed `>= 1` and a zero-stride infinite loop is unrepresentable.\n *\n * Profiles MUST use this instead of hand-rolling a `for (offset += step)` /\n * `subarray()` / `writeWithoutResponse()` chunk loop (enforced by the\n * `no-restricted-syntax` guard scoped to `packages/profiles/src`). Defaults to\n * `mode: 'without-response'` — the serial-pipe convention (Nordic UART, HM-10).\n *\n * @param characteristic - Target characteristic UUID or alias on this profile's service.\n * @param value - Bytes to send. Accepts any {@link BufferSource}.\n * @param options - Fragmentation/retry overrides; `mode` defaults to `'without-response'`.\n * @returns The {@link WriteFragmentedResult} (bytes written, chunk size/count, retries).\n */\n protected async sendChunked(\n characteristic: string,\n value: BufferSource,\n options: WriteFragmentedOptions = {},\n ): Promise<WriteFragmentedResult> {\n return this.device.writeFragmented(this.service, characteristic, value, {\n mode: 'without-response',\n ...options,\n });\n }\n\n protected async writeValue(characteristic: string, value: BufferSource, options?: WriteOptions): Promise<void> {\n if (options?.mode === 'without-response') {\n return this.device.writeWithoutResponse(this.service, characteristic, value, options);\n }\n return this.device.write(this.service, characteristic, value, options);\n }\n\n protected async getWriteLimits(): Promise<WriteLimits> {\n return this.device.getWriteLimits();\n }\n\n protected async getMtu(): Promise<number | null> {\n return this.device.getMtu();\n }\n\n protected subscribe(characteristic: string, callback: NotificationCallback): () => void {\n const unsubscribe = this.device.subscribe(this.service, characteristic, callback);\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n\n /**\n * Observe NATIVE notification-queue overflows for `characteristic` on this\n * profile's service. The bounded Swift `EventQueue` evicts notifications under\n * sustained high-frequency load and the polyfill surfaces each eviction as a\n * `beacio:overflow` `CustomEvent` on the characteristic; this decodes that\n * event's `detail` into a typed {@link NativeOverflowEvent} and forwards it to\n * `callback`.\n *\n * Lifecycle parity with {@link subscribe}: the returned unsubscribe is also\n * registered into the profile's cleanup set, so {@link stop}/{@link dispose}\n * detach the listener too. A staleness `callback` should typically re-read the\n * affected characteristic to resynchronise any UI tracking the last notified\n * value rather than trusting that (now-stale) value.\n *\n * @param characteristic - Characteristic UUID or alias on this profile's service.\n * @param callback - Called with the decoded eviction metadata on each overflow.\n * @returns Unsubscribe function.\n */\n protected onOverflow(characteristic: string, callback: (event: NativeOverflowEvent) => void): () => void {\n const unsubscribe = this.device.onCharacteristicOverflow(this.service, characteristic, (event) => {\n callback(decodeNativeOverflow(event));\n });\n this.cleanups.push(unsubscribe);\n return () => {\n unsubscribe();\n this.cleanups = this.cleanups.filter((candidate) => candidate !== unsubscribe);\n };\n }\n}\n\n/**\n * Decode a `beacio:overflow` {@link Event} (a `CustomEvent` whose `detail` carries\n * the native bounded-queue eviction metadata) into a typed\n * {@link NativeOverflowEvent}. Each field is `undefined` when the native bridge\n * omitted it (forward-compat guard); a conforming bridge supplies all four. Total\n * and side-effect-free — never throws on a malformed or detail-less event.\n */\nfunction decodeNativeOverflow(event: Event): NativeOverflowEvent {\n const detail = (event as CustomEvent).detail as unknown;\n const meta = (detail && typeof detail === 'object') ? (detail as Record<string, unknown>) : {};\n return {\n evictedCount: typeof meta.evictedCount === 'number' ? meta.evictedCount : undefined,\n queueCapacity: typeof meta.queueCapacity === 'number' ? meta.queueCapacity : undefined,\n seq: typeof meta.seq === 'number' ? meta.seq : undefined,\n timestamp: typeof meta.timestamp === 'number' ? meta.timestamp : undefined,\n };\n}\n\ntype DefinedProfileInstance<C extends Record<string, AnyCharacteristicDefinition>> = BaseProfile & {\n readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>>;\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void;\n writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void>;\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability>;\n getCharacteristicUUID<K extends keyof C & string>(name: K): string;\n getServiceUUID(): string;\n getWriteLimits(): Promise<WriteLimits>;\n getMtu(): Promise<number | null>;\n};\n\nexport interface DefinedProfile<C extends Record<string, AnyCharacteristicDefinition>> {\n new (device: BeacioDevice): DefinedProfileInstance<C>;\n readonly profileName: string;\n readonly serviceUUID: string;\n readonly characteristics: {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n}\n\nexport function defineProfile<C extends Record<string, AnyCharacteristicDefinition>>(\n config: ProfileConfig<C>,\n): DefinedProfile<C> {\n const serviceUUID = resolveUUID(config.service);\n const characteristics = Object.fromEntries(\n Object.entries(config.characteristics).map(([name, definition]) => {\n const canonical = {\n ...definition,\n uuid: resolveUUID(definition.uuid),\n };\n\n if (hasCapability(canonical.capabilities, 'read') && typeof (canonical as { parse?: unknown }).parse !== 'function') {\n throw new Error(`Characteristic ${name} declares read capability but is missing parse()`);\n }\n\n if (\n (hasCapability(canonical.capabilities, 'write') || hasCapability(canonical.capabilities, 'writeWithoutResponse'))\n && typeof (canonical as { serialize?: unknown }).serialize !== 'function'\n ) {\n throw new Error(`Characteristic ${name} declares write capability but is missing serialize()`);\n }\n\n return [name, canonical];\n }),\n ) as unknown as {\n [K in keyof C]: Omit<C[K], 'uuid'> & { uuid: string };\n };\n\n class GeneratedProfile extends BaseProfile {\n static readonly profileName = config.name;\n static readonly serviceUUID = serviceUUID;\n static readonly characteristics = characteristics;\n\n protected readonly service = serviceUUID;\n\n getCharacteristicCapabilities<K extends keyof C & string>(name: K): ReadonlyArray<Capability> {\n return characteristics[name].capabilities;\n }\n\n getCharacteristicUUID<K extends keyof C & string>(name: K): string {\n return characteristics[name].uuid;\n }\n\n getServiceUUID(): string {\n return serviceUUID;\n }\n\n async readChar<K extends ReadableKeys<C>>(name: K): Promise<ReadValue<C[K]>> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n const raw = await this.read(characteristic.uuid);\n return characteristic.parse(raw);\n }\n\n subscribeChar<K extends Extract<ReadableKeys<C>, NotifiableKeys<C>>>(name: K, cb: (value: ReadValue<C[K]>) => void): () => void {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & ReadParser<ReadValue<C[K]>>;\n return this.subscribe(characteristic.uuid, (value) => {\n cb(characteristic.parse(value));\n });\n }\n\n async writeChar<K extends WritableKeys<C>>(name: K, value: WriteValue<C[K]>, options?: WriteOptions): Promise<void> {\n const characteristic = characteristics[name] as unknown as CanonicalCharacteristic<C[K]> & WriteSerializer<WriteValue<C[K]>>;\n const serialized = characteristic.serialize(value);\n const mode = options?.mode ?? (hasCapability(characteristic.capabilities, 'write') ? 'with-response' : 'without-response');\n await this.writeValue(characteristic.uuid, serialized, { ...options, mode });\n }\n\n async getWriteLimits(): Promise<WriteLimits> {\n return super.getWriteLimits();\n }\n\n async getMtu(): Promise<number | null> {\n return super.getMtu();\n }\n}\n\n return GeneratedProfile as unknown as DefinedProfile<C>;\n}\n","import { BaseProfile } from './base';\n\n/** HM-10 / CC2541 \"transparent serial\" service UUID. */\nconst FFE0_SERVICE = '0000ffe0-0000-1000-8000-00805f9b34fb';\n/** Single bidirectional characteristic: write AND notify share this handle. */\nconst FFE1_CHAR = '0000ffe1-0000-1000-8000-00805f9b34fb';\n\n/**\n * HM-10 (and compatible CC2540/CC2541 modules: HM-11, AT-09, JDY-08, …)\n * transparent-serial profile.\n *\n * Unlike Nordic UART's two-characteristic design, the HM-10 multiplexes both\n * directions onto a *single* characteristic `0000ffe1-…` on service\n * `0000ffe0-…`: the host writes to it (write-without-response) and the device\n * pushes inbound bytes back via notifications on the very same handle.\n *\n * Strictly W3C `navigator.bluetooth` GATT: notifications are enabled through\n * {@link BaseProfile.subscribe} (`startNotifications()`); this profile never\n * reads or writes a CCCD/SCCD descriptor itself.\n *\n * @example\n * ```ts\n * import { HM10SerialProfile } from '@beacio/core/profiles';\n *\n * // requestDevice({ filters: [{ services: ['0000ffe0-0000-1000-8000-00805f9b34fb'] }] })\n * const serial = new HM10SerialProfile(device);\n * await serial.connect();\n *\n * const decoder = new TextDecoder();\n * const unsubscribe = serial.onReceive((chunk) => {\n * console.log(decoder.decode(chunk));\n * });\n *\n * await serial.send(new TextEncoder().encode('AT+NAME?\\r\\n'));\n *\n * unsubscribe();\n * serial.stop();\n * ```\n */\nexport class HM10SerialProfile extends BaseProfile {\n protected readonly service = FFE0_SERVICE;\n\n /**\n * Subscribe to inbound data from the module (FFE1 notify).\n * Each notification is delivered as a raw {@link DataView} chunk.\n *\n * @param callback - Invoked with every inbound chunk.\n * @returns Unsubscribe function. Also cleaned up by {@link BaseProfile.stop}.\n */\n onReceive(callback: (chunk: DataView) => void): () => void {\n return this.subscribe(FFE1_CHAR, callback);\n }\n\n /**\n * Send data to the module (FFE1 write-without-response — the same handle\n * used for inbound notifications). Payloads larger than the negotiated\n * write-without-response limit are split into MTU-sized chunks and written\n * sequentially.\n *\n * @param data - Bytes to send. Accepts any {@link BufferSource}.\n */\n async send(data: BufferSource): Promise<void> {\n // Delegate fragmentation to the core write-chunker (via BaseProfile.sendChunked),\n // which derives a branded, always-positive ChunkSize from the negotiated\n // write-without-response limit / MTU. No hand-rolled offset loop here.\n await this.sendChunked(FFE1_CHAR, data);\n }\n}\n"]}
export{a as HM10SerialProfile}from'../chunk-2PX7ZYHS.mjs';import'../chunk-FANWIUKA.mjs';import'../chunk-33IHM3NV.mjs';//# sourceMappingURL=serial-ffe0.mjs.map
//# sourceMappingURL=serial-ffe0.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"serial-ffe0.mjs"}
/**
* Mock GATT Server, Services, and Characteristics
*
* Stateful mocks that simulate real BLE behavior:
* - Characteristic reads return configured values
* - Writes store values
* - Notifications can be pumped programmatically
*/
interface MockCharacteristicConfig {
/** Characteristic UUID */
uuid: string;
/** Characteristic properties (all default to false except read) */
properties?: {
broadcast?: boolean;
read?: boolean;
write?: boolean;
writeWithoutResponse?: boolean;
notify?: boolean;
indicate?: boolean;
authenticatedSignedWrites?: boolean;
reliableWrite?: boolean;
writableAuxiliaries?: boolean;
};
/** Initial value (DataView or Uint8Array) */
value?: ArrayBuffer | Uint8Array;
/** Descriptors for this characteristic */
descriptors?: MockDescriptorConfig[];
}
interface MockServiceConfig {
/** Service UUID */
uuid: string;
/** Whether this is a primary service (default: true) */
isPrimary?: boolean;
/** Characteristics in this service */
characteristics?: MockCharacteristicConfig[];
}
interface MockDescriptorConfig {
/** Descriptor UUID */
uuid: string;
/** Initial value */
value?: ArrayBuffer | Uint8Array;
}
declare class MockGATTServer {
private _connected;
private _device;
private _services;
constructor(device: MockBleDevice, configs: MockServiceConfig[]);
get connected(): boolean;
connect(): Promise<BluetoothRemoteGATTServer>;
disconnect(): void;
getPrimaryService(uuid: string): Promise<BluetoothRemoteGATTService>;
getPrimaryServices(uuid?: string): Promise<BluetoothRemoteGATTService[]>;
/** Get a mock service for test control */
getService(uuid: string): MockService | undefined;
asBluetoothRemoteGATTServer(deviceProxy?: BluetoothDevice): BluetoothRemoteGATTServer;
private _assertConnected;
}
declare class MockService {
readonly uuid: string;
readonly isPrimary: boolean;
private _characteristics;
constructor(_device: MockBleDevice, config: MockServiceConfig);
getCharacteristic(uuid: string): Promise<BluetoothRemoteGATTCharacteristic>;
getCharacteristics(uuid?: string): Promise<BluetoothRemoteGATTCharacteristic[]>;
/** Get a mock characteristic for test control */
getChar(uuid: string): MockCharacteristic | undefined;
stopAllNotifications(): void;
asBluetoothRemoteGATTService(deviceProxy?: BluetoothDevice): BluetoothRemoteGATTService;
}
declare class MockCharacteristic {
readonly uuid: string;
private _properties;
private _value;
private _notifying;
private _listeners;
private _descriptors;
constructor(config: MockCharacteristicConfig);
/** Set the characteristic value (for test setup) */
setValue(data: ArrayBuffer | Uint8Array): void;
/** Pump a notification to all listeners */
emitNotification(data: ArrayBuffer | Uint8Array): void;
stopNotifications(): void;
get isNotifying(): boolean;
/** Get a mock descriptor for test control */
getDesc(uuid: string): MockDescriptor | undefined;
asBluetoothRemoteGATTCharacteristic(service: BluetoothRemoteGATTService): BluetoothRemoteGATTCharacteristic;
private _writeValue;
}
declare class MockDescriptor {
readonly uuid: string;
private _value;
constructor(config: MockDescriptorConfig);
/** Set the descriptor value (for test setup) */
setValue(data: ArrayBuffer | Uint8Array): void;
/** Get the current value */
get value(): DataView;
asBluetoothRemoteGATTDescriptor(characteristic: BluetoothRemoteGATTCharacteristic): BluetoothRemoteGATTDescriptor;
}
/**
* Mock BLE Device — stateful device with GATT server, services, characteristics
*/
interface MockDeviceOptions {
/** Device ID (auto-generated if not provided) */
id?: string;
/** Device name */
name?: string;
/** Advertised service UUIDs */
serviceUUIDs?: string[];
/** GATT service configurations */
services?: MockServiceConfig[];
/** Initial RSSI value */
rssi?: number;
/** Fail the first N connect() attempts with a NetworkError. */
failConnectAttempts?: number;
/** Optional platform-reported write limits for MTU-aware write tests. */
writeLimits?: {
withResponse?: number | null;
withoutResponse?: number | null;
mtu?: number | null;
};
}
interface MockAdvertisementOptions {
/** Override RSSI for this advertisement */
rssi?: number;
/** Optional TX power value */
txPower?: number;
/** Override advertised UUIDs for this advertisement */
uuids?: string[];
/** Optional manufacturer data payloads */
manufacturerData?: Map<number, DataView>;
/** Optional service data payloads */
serviceData?: Map<string, DataView>;
}
declare class MockBleDevice {
readonly id: string;
readonly name: string | undefined;
private _serviceUUIDs;
private _gatt;
private _listeners;
private _rssi;
private _watchingAdvertisements;
private _advertisementSink?;
private _remainingConnectFailures;
private _writeLimits;
constructor(options?: MockDeviceOptions);
/** Check if this device matches a scan filter */
matchesFilter(filter: BluetoothLEScanFilter): boolean;
/** Return a Web Bluetooth-compatible BluetoothDevice object */
asBluetoothDevice(): BluetoothDevice;
shouldFailConnect(): boolean;
/** Simulate a disconnect event */
simulateDisconnect(): void;
/** Get the mock GATT server for direct test control */
get gatt(): MockGATTServer;
get serviceUUIDs(): readonly string[];
get rssi(): number;
/** Emit an advertisement for requestLEScan()/watchAdvertisements() tests */
emitAdvertisement(options?: MockAdvertisementOptions): void;
/** Update RSSI between advertisements */
setRSSI(rssi: number): void;
/** Internal hook used by MockBluetooth to receive advertisement pumps */
setAdvertisementSink(sink: ((device: MockBleDevice, options: MockAdvertisementOptions) => void) | undefined): void;
/** Internal bridge for watchAdvertisements() listeners */
dispatchAdvertisementEvent(options?: MockAdvertisementOptions): void;
/** Build a Web Bluetooth-style advertisementreceived event */
createAdvertisementEvent(deviceProxy: BluetoothDevice, options?: MockAdvertisementOptions): Event;
private _addListener;
private _removeListener;
private _emit;
}
/**
* Mock Bluetooth API — drop-in replacement for navigator.bluetooth
*
* Provides a stateful mock that tracks devices, manages connections,
* and can be configured for various test scenarios.
*/
interface MockBluetoothOptions {
/** Whether Bluetooth is available (default: true) */
available?: boolean;
/** Pre-registered devices that will appear in scans */
devices?: MockDeviceOptions[];
}
declare class MockBluetooth {
private _available;
private _devices;
private _listeners;
private _scanActive;
private _installedNavigatorBluetooth?;
private _lastScanOptions?;
readonly backgroundSync: {
requestPermission: () => Promise<never>;
requestBackgroundConnection: () => Promise<never>;
registerCharacteristicNotifications: () => Promise<never>;
registerBeaconScanning: () => Promise<never>;
getRegistrations: () => Promise<never>;
unregister: () => Promise<never>;
update: () => Promise<never>;
connect: () => Promise<never>;
subscribe: () => Promise<never>;
scan: () => Promise<never>;
list: () => Promise<never>;
destroy: () => void;
};
readonly peripheral: {
advertising: boolean;
advertise: () => Promise<never>;
stopAdvertising: () => Promise<never>;
send: () => Promise<never>;
destroy: () => void;
addEventListener: () => void;
removeEventListener: () => void;
onwriterequest: null;
onsubscriptionchange: null;
onconnectionstatechange: null;
onadvertisingstatechange: null;
};
constructor(options?: MockBluetoothOptions);
getAvailability(): Promise<boolean>;
requestDevice(options?: RequestDeviceOptions): Promise<BluetoothDevice>;
getDevices(): Promise<BluetoothDevice[]>;
requestLEScan(options?: BluetoothLEScanOptions): Promise<BluetoothLEScan>;
addEventListener(type: string, listener: EventListener): void;
removeEventListener(type: string, listener: EventListener): void;
/** Add a device to the mock registry */
addDevice(options: MockDeviceOptions): MockBleDevice;
/** Remove a device from the registry */
removeDevice(id: string): void;
/** Get a mock device by ID for test assertions */
getDevice(id: string): MockBleDevice | undefined;
/** Set Bluetooth availability */
setAvailable(available: boolean): void;
/** Install this mock instance onto navigator.bluetooth */
install(): this;
/** Restore the previous navigator.bluetooth value */
uninstall(): void;
/** Emit a Bluetooth-level advertisementreceived event */
emitAdvertisement(deviceId: string, options?: MockAdvertisementOptions): void;
/** Reset all state */
reset(): void;
private _findMatchingDevices;
private readonly _handleAdvertisement;
private _matchesScan;
}
/**
* Install mock Bluetooth API on the global navigator object.
* Returns a MockBluetooth instance for test control.
*/
declare function createMockBluetooth(options?: MockBluetoothOptions): MockBluetooth;
/**
* Install mock Bluetooth on navigator.bluetooth.
* Returns the mock instance for control.
*/
declare function installMockBluetooth(options?: MockBluetoothOptions): MockBluetooth;
/** Common Bluetooth SIG UUIDs for test convenience */
declare const BLE_UUIDS: {
readonly services: {
readonly HEART_RATE: "0000180d-0000-1000-8000-00805f9b34fb";
readonly BATTERY: "0000180f-0000-1000-8000-00805f9b34fb";
readonly DEVICE_INFO: "0000180a-0000-1000-8000-00805f9b34fb";
readonly ENVIRONMENTAL_SENSING: "0000181a-0000-1000-8000-00805f9b34fb";
};
readonly characteristics: {
readonly HEART_RATE_MEASUREMENT: "00002a37-0000-1000-8000-00805f9b34fb";
readonly BODY_SENSOR_LOCATION: "00002a38-0000-1000-8000-00805f9b34fb";
readonly BATTERY_LEVEL: "00002a19-0000-1000-8000-00805f9b34fb";
readonly MANUFACTURER_NAME: "00002a29-0000-1000-8000-00805f9b34fb";
readonly MODEL_NUMBER: "00002a24-0000-1000-8000-00805f9b34fb";
readonly TEMPERATURE: "00002a6e-0000-1000-8000-00805f9b34fb";
};
readonly descriptors: {
/** Client Characteristic Configuration Descriptor */
readonly CCCD: "00002902-0000-1000-8000-00805f9b34fb";
/** Characteristic User Description */
readonly USER_DESCRIPTION: "00002901-0000-1000-8000-00805f9b34fb";
/** Characteristic Presentation Format */
readonly PRESENTATION_FORMAT: "00002904-0000-1000-8000-00805f9b34fb";
};
};
/** Pre-configured device factories for common test scenarios */
declare const devices: {
/** Heart rate sensor with notification support */
heartRate(name?: string): MockDeviceOptions;
/** Battery service device */
battery(name?: string): MockDeviceOptions;
/** Device with multiple services */
full(name?: string): MockDeviceOptions;
};
export { BLE_UUIDS, type MockAdvertisementOptions, MockBleDevice, MockBluetooth, type MockBluetoothOptions, MockCharacteristic, type MockCharacteristicConfig, MockDescriptor, type MockDescriptorConfig, type MockDeviceOptions, MockGATTServer, MockService, type MockServiceConfig, createMockBluetooth, devices, installMockBluetooth };
/**
* Mock GATT Server, Services, and Characteristics
*
* Stateful mocks that simulate real BLE behavior:
* - Characteristic reads return configured values
* - Writes store values
* - Notifications can be pumped programmatically
*/
interface MockCharacteristicConfig {
/** Characteristic UUID */
uuid: string;
/** Characteristic properties (all default to false except read) */
properties?: {
broadcast?: boolean;
read?: boolean;
write?: boolean;
writeWithoutResponse?: boolean;
notify?: boolean;
indicate?: boolean;
authenticatedSignedWrites?: boolean;
reliableWrite?: boolean;
writableAuxiliaries?: boolean;
};
/** Initial value (DataView or Uint8Array) */
value?: ArrayBuffer | Uint8Array;
/** Descriptors for this characteristic */
descriptors?: MockDescriptorConfig[];
}
interface MockServiceConfig {
/** Service UUID */
uuid: string;
/** Whether this is a primary service (default: true) */
isPrimary?: boolean;
/** Characteristics in this service */
characteristics?: MockCharacteristicConfig[];
}
interface MockDescriptorConfig {
/** Descriptor UUID */
uuid: string;
/** Initial value */
value?: ArrayBuffer | Uint8Array;
}
declare class MockGATTServer {
private _connected;
private _device;
private _services;
constructor(device: MockBleDevice, configs: MockServiceConfig[]);
get connected(): boolean;
connect(): Promise<BluetoothRemoteGATTServer>;
disconnect(): void;
getPrimaryService(uuid: string): Promise<BluetoothRemoteGATTService>;
getPrimaryServices(uuid?: string): Promise<BluetoothRemoteGATTService[]>;
/** Get a mock service for test control */
getService(uuid: string): MockService | undefined;
asBluetoothRemoteGATTServer(deviceProxy?: BluetoothDevice): BluetoothRemoteGATTServer;
private _assertConnected;
}
declare class MockService {
readonly uuid: string;
readonly isPrimary: boolean;
private _characteristics;
constructor(_device: MockBleDevice, config: MockServiceConfig);
getCharacteristic(uuid: string): Promise<BluetoothRemoteGATTCharacteristic>;
getCharacteristics(uuid?: string): Promise<BluetoothRemoteGATTCharacteristic[]>;
/** Get a mock characteristic for test control */
getChar(uuid: string): MockCharacteristic | undefined;
stopAllNotifications(): void;
asBluetoothRemoteGATTService(deviceProxy?: BluetoothDevice): BluetoothRemoteGATTService;
}
declare class MockCharacteristic {
readonly uuid: string;
private _properties;
private _value;
private _notifying;
private _listeners;
private _descriptors;
constructor(config: MockCharacteristicConfig);
/** Set the characteristic value (for test setup) */
setValue(data: ArrayBuffer | Uint8Array): void;
/** Pump a notification to all listeners */
emitNotification(data: ArrayBuffer | Uint8Array): void;
stopNotifications(): void;
get isNotifying(): boolean;
/** Get a mock descriptor for test control */
getDesc(uuid: string): MockDescriptor | undefined;
asBluetoothRemoteGATTCharacteristic(service: BluetoothRemoteGATTService): BluetoothRemoteGATTCharacteristic;
private _writeValue;
}
declare class MockDescriptor {
readonly uuid: string;
private _value;
constructor(config: MockDescriptorConfig);
/** Set the descriptor value (for test setup) */
setValue(data: ArrayBuffer | Uint8Array): void;
/** Get the current value */
get value(): DataView;
asBluetoothRemoteGATTDescriptor(characteristic: BluetoothRemoteGATTCharacteristic): BluetoothRemoteGATTDescriptor;
}
/**
* Mock BLE Device — stateful device with GATT server, services, characteristics
*/
interface MockDeviceOptions {
/** Device ID (auto-generated if not provided) */
id?: string;
/** Device name */
name?: string;
/** Advertised service UUIDs */
serviceUUIDs?: string[];
/** GATT service configurations */
services?: MockServiceConfig[];
/** Initial RSSI value */
rssi?: number;
/** Fail the first N connect() attempts with a NetworkError. */
failConnectAttempts?: number;
/** Optional platform-reported write limits for MTU-aware write tests. */
writeLimits?: {
withResponse?: number | null;
withoutResponse?: number | null;
mtu?: number | null;
};
}
interface MockAdvertisementOptions {
/** Override RSSI for this advertisement */
rssi?: number;
/** Optional TX power value */
txPower?: number;
/** Override advertised UUIDs for this advertisement */
uuids?: string[];
/** Optional manufacturer data payloads */
manufacturerData?: Map<number, DataView>;
/** Optional service data payloads */
serviceData?: Map<string, DataView>;
}
declare class MockBleDevice {
readonly id: string;
readonly name: string | undefined;
private _serviceUUIDs;
private _gatt;
private _listeners;
private _rssi;
private _watchingAdvertisements;
private _advertisementSink?;
private _remainingConnectFailures;
private _writeLimits;
constructor(options?: MockDeviceOptions);
/** Check if this device matches a scan filter */
matchesFilter(filter: BluetoothLEScanFilter): boolean;
/** Return a Web Bluetooth-compatible BluetoothDevice object */
asBluetoothDevice(): BluetoothDevice;
shouldFailConnect(): boolean;
/** Simulate a disconnect event */
simulateDisconnect(): void;
/** Get the mock GATT server for direct test control */
get gatt(): MockGATTServer;
get serviceUUIDs(): readonly string[];
get rssi(): number;
/** Emit an advertisement for requestLEScan()/watchAdvertisements() tests */
emitAdvertisement(options?: MockAdvertisementOptions): void;
/** Update RSSI between advertisements */
setRSSI(rssi: number): void;
/** Internal hook used by MockBluetooth to receive advertisement pumps */
setAdvertisementSink(sink: ((device: MockBleDevice, options: MockAdvertisementOptions) => void) | undefined): void;
/** Internal bridge for watchAdvertisements() listeners */
dispatchAdvertisementEvent(options?: MockAdvertisementOptions): void;
/** Build a Web Bluetooth-style advertisementreceived event */
createAdvertisementEvent(deviceProxy: BluetoothDevice, options?: MockAdvertisementOptions): Event;
private _addListener;
private _removeListener;
private _emit;
}
/**
* Mock Bluetooth API — drop-in replacement for navigator.bluetooth
*
* Provides a stateful mock that tracks devices, manages connections,
* and can be configured for various test scenarios.
*/
interface MockBluetoothOptions {
/** Whether Bluetooth is available (default: true) */
available?: boolean;
/** Pre-registered devices that will appear in scans */
devices?: MockDeviceOptions[];
}
declare class MockBluetooth {
private _available;
private _devices;
private _listeners;
private _scanActive;
private _installedNavigatorBluetooth?;
private _lastScanOptions?;
readonly backgroundSync: {
requestPermission: () => Promise<never>;
requestBackgroundConnection: () => Promise<never>;
registerCharacteristicNotifications: () => Promise<never>;
registerBeaconScanning: () => Promise<never>;
getRegistrations: () => Promise<never>;
unregister: () => Promise<never>;
update: () => Promise<never>;
connect: () => Promise<never>;
subscribe: () => Promise<never>;
scan: () => Promise<never>;
list: () => Promise<never>;
destroy: () => void;
};
readonly peripheral: {
advertising: boolean;
advertise: () => Promise<never>;
stopAdvertising: () => Promise<never>;
send: () => Promise<never>;
destroy: () => void;
addEventListener: () => void;
removeEventListener: () => void;
onwriterequest: null;
onsubscriptionchange: null;
onconnectionstatechange: null;
onadvertisingstatechange: null;
};
constructor(options?: MockBluetoothOptions);
getAvailability(): Promise<boolean>;
requestDevice(options?: RequestDeviceOptions): Promise<BluetoothDevice>;
getDevices(): Promise<BluetoothDevice[]>;
requestLEScan(options?: BluetoothLEScanOptions): Promise<BluetoothLEScan>;
addEventListener(type: string, listener: EventListener): void;
removeEventListener(type: string, listener: EventListener): void;
/** Add a device to the mock registry */
addDevice(options: MockDeviceOptions): MockBleDevice;
/** Remove a device from the registry */
removeDevice(id: string): void;
/** Get a mock device by ID for test assertions */
getDevice(id: string): MockBleDevice | undefined;
/** Set Bluetooth availability */
setAvailable(available: boolean): void;
/** Install this mock instance onto navigator.bluetooth */
install(): this;
/** Restore the previous navigator.bluetooth value */
uninstall(): void;
/** Emit a Bluetooth-level advertisementreceived event */
emitAdvertisement(deviceId: string, options?: MockAdvertisementOptions): void;
/** Reset all state */
reset(): void;
private _findMatchingDevices;
private readonly _handleAdvertisement;
private _matchesScan;
}
/**
* Install mock Bluetooth API on the global navigator object.
* Returns a MockBluetooth instance for test control.
*/
declare function createMockBluetooth(options?: MockBluetoothOptions): MockBluetooth;
/**
* Install mock Bluetooth on navigator.bluetooth.
* Returns the mock instance for control.
*/
declare function installMockBluetooth(options?: MockBluetoothOptions): MockBluetooth;
/** Common Bluetooth SIG UUIDs for test convenience */
declare const BLE_UUIDS: {
readonly services: {
readonly HEART_RATE: "0000180d-0000-1000-8000-00805f9b34fb";
readonly BATTERY: "0000180f-0000-1000-8000-00805f9b34fb";
readonly DEVICE_INFO: "0000180a-0000-1000-8000-00805f9b34fb";
readonly ENVIRONMENTAL_SENSING: "0000181a-0000-1000-8000-00805f9b34fb";
};
readonly characteristics: {
readonly HEART_RATE_MEASUREMENT: "00002a37-0000-1000-8000-00805f9b34fb";
readonly BODY_SENSOR_LOCATION: "00002a38-0000-1000-8000-00805f9b34fb";
readonly BATTERY_LEVEL: "00002a19-0000-1000-8000-00805f9b34fb";
readonly MANUFACTURER_NAME: "00002a29-0000-1000-8000-00805f9b34fb";
readonly MODEL_NUMBER: "00002a24-0000-1000-8000-00805f9b34fb";
readonly TEMPERATURE: "00002a6e-0000-1000-8000-00805f9b34fb";
};
readonly descriptors: {
/** Client Characteristic Configuration Descriptor */
readonly CCCD: "00002902-0000-1000-8000-00805f9b34fb";
/** Characteristic User Description */
readonly USER_DESCRIPTION: "00002901-0000-1000-8000-00805f9b34fb";
/** Characteristic Presentation Format */
readonly PRESENTATION_FORMAT: "00002904-0000-1000-8000-00805f9b34fb";
};
};
/** Pre-configured device factories for common test scenarios */
declare const devices: {
/** Heart rate sensor with notification support */
heartRate(name?: string): MockDeviceOptions;
/** Battery service device */
battery(name?: string): MockDeviceOptions;
/** Device with multiple services */
full(name?: string): MockDeviceOptions;
};
export { BLE_UUIDS, type MockAdvertisementOptions, MockBleDevice, MockBluetooth, type MockBluetoothOptions, MockCharacteristic, type MockCharacteristicConfig, MockDescriptor, type MockDescriptorConfig, type MockDeviceOptions, MockGATTServer, MockService, type MockServiceConfig, createMockBluetooth, devices, installMockBluetooth };
'use strict';var u=class{constructor(e,t){this._connected=false;this._services=new Map;this._device=e;for(let i of t)this._services.set(i.uuid,new l(e,i));}get connected(){return this._connected}async connect(){if(this._device.shouldFailConnect())throw new DOMException("Simulated transient connection failure","NetworkError");return this._connected=true,this.asBluetoothRemoteGATTServer()}disconnect(){this._connected=false;for(let e of this._services.values())e.stopAllNotifications();}async getPrimaryService(e){this._assertConnected();let t=this._services.get(e);if(!t)throw new DOMException(`No Services matching UUID ${e} found`,"NotFoundError");return t.asBluetoothRemoteGATTService()}async getPrimaryServices(e){return this._assertConnected(),(e?[this._services.get(e)].filter(Boolean):Array.from(this._services.values())).map(i=>i.asBluetoothRemoteGATTService())}getService(e){return this._services.get(e)}asBluetoothRemoteGATTServer(e){let t=this;return {get connected(){return t._connected},get device(){return e},connect:()=>t.connect(),disconnect:()=>t.disconnect(),getPrimaryService:r=>t.getPrimaryService(r),getPrimaryServices:r=>t.getPrimaryServices(r)}}_assertConnected(){if(!this._connected)throw new DOMException("GATT Server is disconnected. Cannot perform GATT operations.","NetworkError")}},l=class{constructor(e,t){this._characteristics=new Map;this.uuid=t.uuid,this.isPrimary=t.isPrimary??true;for(let i of t.characteristics??[])this._characteristics.set(i.uuid,new v(i));}async getCharacteristic(e){let t=this._characteristics.get(e);if(!t)throw new DOMException(`No Characteristics matching UUID ${e} found`,"NotFoundError");return t.asBluetoothRemoteGATTCharacteristic(this.asBluetoothRemoteGATTService())}async getCharacteristics(e){let t=e?[this._characteristics.get(e)].filter(Boolean):Array.from(this._characteristics.values()),i=this.asBluetoothRemoteGATTService();return t.map(r=>r.asBluetoothRemoteGATTCharacteristic(i))}getChar(e){return this._characteristics.get(e)}stopAllNotifications(){for(let e of this._characteristics.values())e.stopNotifications();}asBluetoothRemoteGATTService(e){let t=this;return {uuid:this.uuid,isPrimary:this.isPrimary,get device(){return e},getCharacteristic:i=>t.getCharacteristic(i),getCharacteristics:i=>t.getCharacteristics(i),getIncludedService:async()=>{throw new DOMException("Not implemented","NotSupportedError")},getIncludedServices:async()=>[],addEventListener:()=>{},removeEventListener:()=>{},dispatchEvent:()=>true,oncharacteristicvaluechanged:null,onserviceadded:null,onservicechanged:null,onserviceremoved:null}}},v=class{constructor(e){this._notifying=false;this._listeners=new Map;this._descriptors=new Map;if(this.uuid=e.uuid,this._properties={broadcast:e.properties?.broadcast??false,read:e.properties?.read??true,write:e.properties?.write??false,writeWithoutResponse:e.properties?.writeWithoutResponse??false,notify:e.properties?.notify??false,indicate:e.properties?.indicate??false,authenticatedSignedWrites:e.properties?.authenticatedSignedWrites??false,reliableWrite:e.properties?.reliableWrite??false,writableAuxiliaries:e.properties?.writableAuxiliaries??false},e.value){let t=e.value instanceof Uint8Array?e.value.buffer.slice(e.value.byteOffset,e.value.byteOffset+e.value.byteLength):e.value;this._value=new DataView(t);}else this._value=new DataView(new ArrayBuffer(0));for(let t of e.descriptors??[])this._descriptors.set(t.uuid,new h(t));}setValue(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);}emitNotification(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);let i=new Event("characteristicvaluechanged");Object.defineProperty(i,"target",{value:{value:this._value},writable:false});let r=this._listeners.get("characteristicvaluechanged");if(r)for(let c of r)c(i);}stopNotifications(){this._notifying=false;}get isNotifying(){return this._notifying}getDesc(e){return this._descriptors.get(e)}asBluetoothRemoteGATTCharacteristic(e){let t=this;return {uuid:this.uuid,service:e,properties:{broadcast:this._properties.broadcast,read:this._properties.read,writeWithoutResponse:this._properties.writeWithoutResponse,write:this._properties.write,notify:this._properties.notify,indicate:this._properties.indicate,authenticatedSignedWrites:this._properties.authenticatedSignedWrites,reliableWrite:this._properties.reliableWrite,writableAuxiliaries:this._properties.writableAuxiliaries},get value(){return t._value},readValue:async()=>{if(!t._properties.read)throw new DOMException("Characteristic does not support read","NotSupportedError");return t._value},writeValue:async i=>{if(!t._properties.write)throw new DOMException("Characteristic does not support write","NotSupportedError");t._writeValue(i);},writeValueWithResponse:async i=>{if(!t._properties.write)throw new DOMException("Characteristic does not support write","NotSupportedError");t._writeValue(i);},writeValueWithoutResponse:async i=>{if(!t._properties.writeWithoutResponse)throw new DOMException("Characteristic does not support write without response","NotSupportedError");t._writeValue(i);},startNotifications:async function(){if(!t._properties.notify&&!t._properties.indicate)throw new DOMException("Characteristic does not support notifications","NotSupportedError");return t._notifying=true,this},stopNotifications:async function(){return t._notifying=false,this},addEventListener:(i,r)=>{t._listeners.has(i)||t._listeners.set(i,new Set),t._listeners.get(i).add(r);},removeEventListener:(i,r)=>{t._listeners.get(i)?.delete(r);},dispatchEvent:()=>true,getDescriptor:async i=>{let r=t._descriptors.get(i);if(!r)throw new DOMException(`No Descriptors matching UUID ${i} found`,"NotFoundError");return r.asBluetoothRemoteGATTDescriptor(t.asBluetoothRemoteGATTCharacteristic(e))},getDescriptors:async i=>{let r=i?[t._descriptors.get(i)].filter(Boolean):Array.from(t._descriptors.values()),c=t.asBluetoothRemoteGATTCharacteristic(e);return r.map(m=>m.asBluetoothRemoteGATTDescriptor(c))},oncharacteristicvaluechanged:null}}_writeValue(e){let t=e instanceof ArrayBuffer?e:e.buffer??e.buffer;this._value=new DataView(t);}},h=class{constructor(e){if(this.uuid=e.uuid,e.value){let t=e.value instanceof Uint8Array?e.value.buffer.slice(e.value.byteOffset,e.value.byteOffset+e.value.byteLength):e.value;this._value=new DataView(t);}else this._value=new DataView(new ArrayBuffer(0));}setValue(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);}get value(){return this._value}asBluetoothRemoteGATTDescriptor(e){let t=this;return {uuid:this.uuid,characteristic:e,get value(){return t._value},readValue:async()=>t._value,writeValue:async i=>{let r=i instanceof ArrayBuffer?i:i.buffer??i.buffer;t._value=new DataView(r);}}}};var g=0,a=class{constructor(e={}){this._listeners=new Map;this._watchingAdvertisements=false;this.id=e.id??`mock-device-${++g}`,this.name=e.name,this._serviceUUIDs=e.serviceUUIDs??[],this._gatt=new u(this,e.services??[]),this._rssi=e.rssi??-60,this._remainingConnectFailures=e.failConnectAttempts??0,this._writeLimits={withResponse:e.writeLimits?.withResponse??null,withoutResponse:e.writeLimits?.withoutResponse??null,mtu:e.writeLimits?.mtu??null};}matchesFilter(e){return !(e.services&&!e.services.some(i=>this._serviceUUIDs.includes(String(i)))||e.name&&e.name!==this.name||e.namePrefix&&!this.name?.startsWith(e.namePrefix))}asBluetoothDevice(){let e=this,t={id:this.id,name:this.name??null,gatt:null,watchAdvertisements:async i=>{if(e._watchingAdvertisements=true,i?.signal){if(i.signal.aborted){e._watchingAdvertisements=false;return}i.signal.addEventListener("abort",()=>{e._watchingAdvertisements=false;},{once:true});}},addEventListener:(i,r)=>{e._addListener(i,r);},removeEventListener:(i,r)=>{e._removeListener(i,r);},dispatchEvent:i=>true,get watchingAdvertisements(){return e._watchingAdvertisements},unwatchAdvertisements:async()=>{e._watchingAdvertisements=false;},forget:async()=>{},onadvertisementreceived:null,ongattserverdisconnected:null,oncharacteristicvaluechanged:null,onserviceadded:null,onservicechanged:null,onserviceremoved:null};return t.gatt=this._gatt.asBluetoothRemoteGATTServer(t),t.gatt.getMtu=async()=>this._writeLimits.mtu,t.gatt.getWriteLimits=async()=>({...this._writeLimits}),t}shouldFailConnect(){return this._remainingConnectFailures<=0?false:(this._remainingConnectFailures-=1,true)}simulateDisconnect(){this._gatt.disconnect(),this._emit("gattserverdisconnected",new Event("gattserverdisconnected"));}get gatt(){return this._gatt}get serviceUUIDs(){return this._serviceUUIDs}get rssi(){return this._rssi}emitAdvertisement(e={}){if(this._advertisementSink){this._advertisementSink(this,e);return}this.dispatchAdvertisementEvent(e);}setRSSI(e){this._rssi=e;}setAdvertisementSink(e){this._advertisementSink=e;}dispatchAdvertisementEvent(e={}){this._watchingAdvertisements&&this._emit("advertisementreceived",this.createAdvertisementEvent(this.asBluetoothDevice(),e));}createAdvertisementEvent(e,t={}){let i=new Event("advertisementreceived");return Object.defineProperties(i,{device:{value:e,writable:false},name:{value:this.name,writable:false},uuids:{value:[...t.uuids??this._serviceUUIDs],writable:false},rssi:{value:t.rssi??this._rssi,writable:false},txPower:{value:t.txPower,writable:false},manufacturerData:{value:t.manufacturerData??new Map,writable:false},serviceData:{value:t.serviceData??new Map,writable:false}}),i}_addListener(e,t){this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add(t);}_removeListener(e,t){this._listeners.get(e)?.delete(t);}_emit(e,t){let i=this._listeners.get(e);if(i)for(let r of i)r(t);}};var o=()=>Promise.reject(new DOMException("Beacio extension API not implemented in MockBluetooth","NotSupportedError")),d=()=>{},f=class{constructor(e={}){this._devices=new Map;this._listeners=new Map;this._scanActive=false;this.backgroundSync={requestPermission:o,requestBackgroundConnection:o,registerCharacteristicNotifications:o,registerBeaconScanning:o,getRegistrations:o,unregister:o,update:o,connect:o,subscribe:o,scan:o,list:o,destroy:d};this.peripheral={advertising:false,advertise:o,stopAdvertising:o,send:o,destroy:d,addEventListener:d,removeEventListener:d,onwriterequest:null,onsubscriptionchange:null,onconnectionstatechange:null,onadvertisingstatechange:null};this._handleAdvertisement=(e,t)=>{if(e.dispatchAdvertisementEvent(t),!this._scanActive||!this._matchesScan(e))return;let i=e.createAdvertisementEvent(e.asBluetoothDevice(),t),r=this._listeners.get("advertisementreceived");if(r)for(let c of r)c(i);};if(this._available=e.available??true,e.devices)for(let t of e.devices){let i=new a(t);i.setAdvertisementSink(this._handleAdvertisement),this._devices.set(i.id,i);}}async getAvailability(){return this._available}async requestDevice(e){if(!this._available)throw new DOMException("Bluetooth adapter not available","NotFoundError");let t=this._findMatchingDevices(e);if(t.length===0)throw new DOMException("No devices found matching the filter criteria","NotFoundError");return t[0].asBluetoothDevice()}async getDevices(){return Array.from(this._devices.values()).map(e=>e.asBluetoothDevice())}async requestLEScan(e){if(this._scanActive)throw new DOMException("Scan already in progress","InvalidStateError");this._scanActive=true,this._lastScanOptions=e;let t={active:true,keepRepeatedDevices:e?.keepRepeatedDevices??false,acceptAllAdvertisements:e?.acceptAllAdvertisements??false,stop:()=>{this._scanActive=false,this._lastScanOptions=void 0,t.active=false;}};return t}addEventListener(e,t){this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add(t);}removeEventListener(e,t){this._listeners.get(e)?.delete(t);}addDevice(e){let t=new a(e);return t.setAdvertisementSink(this._handleAdvertisement),this._devices.set(t.id,t),t}removeDevice(e){let t=this._devices.get(e);t&&(t.setAdvertisementSink(void 0),t.simulateDisconnect(),this._devices.delete(e));}getDevice(e){return this._devices.get(e)}setAvailable(e){this._available=e;}install(){return typeof globalThis.navigator>"u"?this:(this._installedNavigatorBluetooth=globalThis.navigator.bluetooth,Object.defineProperty(globalThis.navigator,"bluetooth",{value:this,writable:true,configurable:true}),this)}uninstall(){typeof globalThis.navigator>"u"||(Object.defineProperty(globalThis.navigator,"bluetooth",{value:this._installedNavigatorBluetooth,writable:true,configurable:true}),this._installedNavigatorBluetooth=void 0);}emitAdvertisement(e,t={}){let i=this._devices.get(e);if(!i)throw new Error(`Unknown mock device: ${e}`);this._handleAdvertisement(i,t);}reset(){for(let e of this._devices.values())e.setAdvertisementSink(void 0),e.simulateDisconnect();this._devices.clear(),this._listeners.clear(),this._scanActive=false,this._lastScanOptions=void 0,this._available=true;}_findMatchingDevices(e){if(!e||e.acceptAllDevices)return Array.from(this._devices.values());let t=e.filters??[];return Array.from(this._devices.values()).filter(i=>t.some(r=>i.matchesFilter(r)))}_matchesScan(e){let t=this._lastScanOptions;if(!t||t.acceptAllAdvertisements)return true;let i=t.filters??[];return i.length===0?true:i.some(r=>e.matchesFilter(r))}};function p(n){return new f(n)}function _(n){return p(n).install()}var s={services:{HEART_RATE:"0000180d-0000-1000-8000-00805f9b34fb",BATTERY:"0000180f-0000-1000-8000-00805f9b34fb",DEVICE_INFO:"0000180a-0000-1000-8000-00805f9b34fb",ENVIRONMENTAL_SENSING:"0000181a-0000-1000-8000-00805f9b34fb"},characteristics:{HEART_RATE_MEASUREMENT:"00002a37-0000-1000-8000-00805f9b34fb",BODY_SENSOR_LOCATION:"00002a38-0000-1000-8000-00805f9b34fb",BATTERY_LEVEL:"00002a19-0000-1000-8000-00805f9b34fb",MANUFACTURER_NAME:"00002a29-0000-1000-8000-00805f9b34fb",MODEL_NUMBER:"00002a24-0000-1000-8000-00805f9b34fb",TEMPERATURE:"00002a6e-0000-1000-8000-00805f9b34fb"},descriptors:{CCCD:"00002902-0000-1000-8000-00805f9b34fb",USER_DESCRIPTION:"00002901-0000-1000-8000-00805f9b34fb",PRESENTATION_FORMAT:"00002904-0000-1000-8000-00805f9b34fb"}},D={heartRate(n="Mock HR Sensor"){return {name:n,serviceUUIDs:[s.services.HEART_RATE],services:[{uuid:s.services.HEART_RATE,characteristics:[{uuid:s.characteristics.HEART_RATE_MEASUREMENT,properties:{read:true,notify:true},value:new Uint8Array([0,72])},{uuid:s.characteristics.BODY_SENSOR_LOCATION,properties:{read:true},value:new Uint8Array([1])}]}]}},battery(n="Mock Battery Device"){return {name:n,serviceUUIDs:[s.services.BATTERY],services:[{uuid:s.services.BATTERY,characteristics:[{uuid:s.characteristics.BATTERY_LEVEL,properties:{read:true,notify:true},value:new Uint8Array([85])}]}]}},full(n="Mock Full Device"){return {name:n,serviceUUIDs:[s.services.HEART_RATE,s.services.BATTERY,s.services.DEVICE_INFO],services:[{uuid:s.services.HEART_RATE,characteristics:[{uuid:s.characteristics.HEART_RATE_MEASUREMENT,properties:{read:true,notify:true},value:new Uint8Array([0,72])}]},{uuid:s.services.BATTERY,characteristics:[{uuid:s.characteristics.BATTERY_LEVEL,properties:{read:true,notify:true},value:new Uint8Array([100])}]},{uuid:s.services.DEVICE_INFO,characteristics:[{uuid:s.characteristics.MANUFACTURER_NAME,properties:{read:true},value:Uint8Array.from(Array.from("Beacio Test Corp").map(e=>e.charCodeAt(0)))},{uuid:s.characteristics.MODEL_NUMBER,properties:{read:true},value:Uint8Array.from(Array.from("WBT-001").map(e=>e.charCodeAt(0)))}]}]}}};exports.BLE_UUIDS=s;exports.MockBleDevice=a;exports.MockBluetooth=f;exports.MockCharacteristic=v;exports.MockDescriptor=h;exports.MockGATTServer=u;exports.MockService=l;exports.createMockBluetooth=p;exports.devices=D;exports.installMockBluetooth=_;//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map
{"version":3,"sources":["../../src/testing/mocks/characteristics.ts","../../src/testing/mocks/device.ts","../../src/testing/mocks/bluetooth.ts","../../src/testing/index.ts"],"names":["MockGATTServer","device","configs","config","MockService","service","uuid","s","deviceProxy","self","_device","charConfig","MockCharacteristic","char","chars","c","buffer","descConfig","MockDescriptor","data","event","listeners","listener","value","type","desc","descriptors","charProxy","d","characteristic","deviceIdCounter","MockBleDevice","options","filter","proxy","_event","rssi","sink","unsupportedExtensionApi","noop","MockBluetooth","opts","matching","scan","id","available","deviceId","filters","createMockBluetooth","installMockBluetooth","BLE_UUIDS","devices","name"],"mappings":"aAkDO,IAAMA,CAAAA,CAAN,KAAqB,CAK1B,WAAA,CAAYC,EAAuBC,CAAAA,CAA8B,CAJjE,IAAA,CAAQ,UAAA,CAAa,MAErB,IAAA,CAAQ,SAAA,CAAsC,IAAI,GAAA,CAGhD,KAAK,OAAA,CAAUD,CAAAA,CACf,IAAA,IAAWE,CAAAA,IAAUD,CAAAA,CACnB,IAAA,CAAK,SAAA,CAAU,GAAA,CACbC,EAAO,IAAA,CACP,IAAIC,CAAAA,CAAYH,CAAAA,CAAQE,CAAM,CAChC,EAEJ,CAEA,IAAI,WAAqB,CACvB,OAAO,IAAA,CAAK,UACd,CAEA,MAAM,OAAA,EAA8C,CAClD,GAAI,IAAA,CAAK,OAAA,CAAQ,iBAAA,EAAkB,CACjC,MAAM,IAAI,YAAA,CAAa,wCAAA,CAA0C,cAAc,EAEjF,OAAA,IAAA,CAAK,UAAA,CAAa,IAAA,CACX,IAAA,CAAK,6BACd,CAEA,UAAA,EAAmB,CACjB,KAAK,UAAA,CAAa,KAAA,CAElB,IAAA,IAAWE,CAAAA,IAAW,KAAK,SAAA,CAAU,MAAA,EAAO,CAC1CA,CAAAA,CAAQ,uBAEZ,CAEA,MAAM,iBAAA,CAAkBC,CAAAA,CAAmD,CACzE,IAAA,CAAK,gBAAA,GACL,IAAMD,CAAAA,CAAU,IAAA,CAAK,SAAA,CAAU,IAAIC,CAAI,CAAA,CACvC,GAAI,CAACD,EACH,MAAM,IAAI,YAAA,CACR,CAAA,0BAAA,EAA6BC,CAAI,CAAA,MAAA,CAAA,CACjC,eACF,CAAA,CAEF,OAAOD,CAAAA,CAAQ,4BAAA,EACjB,CAEA,MAAM,kBAAA,CACJC,CAAAA,CACuC,CACvC,OAAA,IAAA,CAAK,kBAAiB,CAAA,CACLA,CAAAA,CACb,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAIA,CAAI,CAAC,EAAE,MAAA,CAAO,OAAO,CAAA,CACzC,KAAA,CAAM,KAAK,IAAA,CAAK,SAAA,CAAU,MAAA,EAAQ,GACH,GAAA,CAAKC,CAAAA,EACtCA,CAAAA,CAAE,4BAAA,EACJ,CACF,CAGA,UAAA,CAAWD,CAAAA,CAAuC,CAChD,OAAO,IAAA,CAAK,SAAA,CAAU,GAAA,CAAIA,CAAI,CAChC,CAEA,2BAAA,CAA4BE,CAAAA,CAA0D,CACpF,IAAMC,CAAAA,CAAO,IAAA,CAeb,OAde,CACb,IAAI,SAAA,EAAY,CACd,OAAOA,CAAAA,CAAK,UACd,CAAA,CACA,IAAI,QAAS,CACX,OAAOD,CACT,CAAA,CACA,QAAS,IAAMC,CAAAA,CAAK,OAAA,EAAQ,CAC5B,UAAA,CAAY,IAAMA,CAAAA,CAAK,UAAA,GACvB,iBAAA,CAAoBH,CAAAA,EAClBG,CAAAA,CAAK,iBAAA,CAAkBH,CAAI,CAAA,CAC7B,kBAAA,CAAqBA,CAAAA,EACnBG,CAAAA,CAAK,mBAAmBH,CAAI,CAChC,CAEF,CAEQ,gBAAA,EAAyB,CAC/B,GAAI,CAAC,KAAK,UAAA,CACR,MAAM,IAAI,YAAA,CACR,+DACA,cACF,CAEJ,CACF,CAAA,CAIaF,EAAN,KAAkB,CAKvB,WAAA,CAAYM,CAAAA,CAAwBP,EAA2B,CAF/D,IAAA,CAAQ,gBAAA,CAAoD,IAAI,IAG9D,IAAA,CAAK,IAAA,CAAOA,CAAAA,CAAO,IAAA,CACnB,KAAK,SAAA,CAAYA,CAAAA,CAAO,SAAA,EAAa,IAAA,CACrC,QAAWQ,CAAAA,IAAcR,CAAAA,CAAO,eAAA,EAAmB,EAAC,CAClD,IAAA,CAAK,gBAAA,CAAiB,GAAA,CACpBQ,EAAW,IAAA,CACX,IAAIC,CAAAA,CAAmBD,CAAU,CACnC,EAEJ,CAEA,MAAM,iBAAA,CACJL,EAC4C,CAC5C,IAAMO,CAAAA,CAAO,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAIP,CAAI,CAAA,CAC3C,GAAI,CAACO,CAAAA,CACH,MAAM,IAAI,aACR,CAAA,iCAAA,EAAoCP,CAAI,CAAA,MAAA,CAAA,CACxC,eACF,EAEF,OAAOO,CAAAA,CAAK,mCAAA,CACV,IAAA,CAAK,4BAAA,EACP,CACF,CAEA,MAAM,kBAAA,CACJP,CAAAA,CAC8C,CAC9C,IAAMQ,EAAQR,CAAAA,CACV,CAAC,IAAA,CAAK,gBAAA,CAAiB,IAAIA,CAAI,CAAC,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAChD,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,iBAAiB,MAAA,EAAQ,CAAA,CACvCD,CAAAA,CAAU,KAAK,4BAAA,EAA6B,CAClD,OAAQS,CAAAA,CAA+B,IAAKC,CAAAA,EAC1CA,CAAAA,CAAE,mCAAA,CAAoCV,CAAO,CAC/C,CACF,CAGA,OAAA,CAAQC,EAA8C,CACpD,OAAO,IAAA,CAAK,gBAAA,CAAiB,IAAIA,CAAI,CACvC,CAEA,oBAAA,EAA6B,CAC3B,IAAA,IAAWO,CAAAA,IAAQ,IAAA,CAAK,gBAAA,CAAiB,MAAA,EAAO,CAC9CA,CAAAA,CAAK,iBAAA,GAET,CAEA,4BAAA,CAA6BL,CAAAA,CAA2D,CACtF,IAAMC,CAAAA,CAAO,IAAA,CACb,OAAO,CACL,KAAM,IAAA,CAAK,IAAA,CACX,SAAA,CAAW,IAAA,CAAK,SAAA,CAChB,IAAI,MAAA,EAAS,CACX,OAAOD,CACT,CAAA,CACA,iBAAA,CAAoBF,CAAAA,EAAiBG,EAAK,iBAAA,CAAkBH,CAAI,CAAA,CAChE,kBAAA,CAAqBA,GACnBG,CAAAA,CAAK,kBAAA,CAAmBH,CAAI,CAAA,CAC9B,mBAAoB,SAAY,CAC9B,MAAM,IAAI,aAAa,iBAAA,CAAmB,mBAAmB,CAC/D,CAAA,CACA,oBAAqB,SAAY,EAAC,CAClC,gBAAA,CAAkB,IAAM,CAAC,CAAA,CACzB,mBAAA,CAAqB,IAAM,CAAC,CAAA,CAC5B,aAAA,CAAe,IAAM,KACrB,4BAAA,CAA8B,IAAA,CAC9B,cAAA,CAAgB,IAAA,CAChB,iBAAkB,IAAA,CAClB,gBAAA,CAAkB,IACpB,CACF,CACF,CAAA,CAIaM,CAAAA,CAAN,KAAyB,CAkB9B,WAAA,CAAYT,CAAAA,CAAkC,CAJ9C,IAAA,CAAQ,WAAa,KAAA,CACrB,IAAA,CAAQ,UAAA,CAA8C,IAAI,IAC1D,IAAA,CAAQ,YAAA,CAA4C,IAAI,GAAA,CAgBtD,GAbA,IAAA,CAAK,IAAA,CAAOA,CAAAA,CAAO,IAAA,CACnB,IAAA,CAAK,WAAA,CAAc,CACjB,SAAA,CAAWA,EAAO,UAAA,EAAY,SAAA,EAAa,KAAA,CAC3C,IAAA,CAAMA,EAAO,UAAA,EAAY,IAAA,EAAQ,IAAA,CACjC,KAAA,CAAOA,EAAO,UAAA,EAAY,KAAA,EAAS,KAAA,CACnC,oBAAA,CAAsBA,EAAO,UAAA,EAAY,oBAAA,EAAwB,KAAA,CACjE,MAAA,CAAQA,EAAO,UAAA,EAAY,MAAA,EAAU,KAAA,CACrC,QAAA,CAAUA,EAAO,UAAA,EAAY,QAAA,EAAY,KAAA,CACzC,yBAAA,CAA2BA,EAAO,UAAA,EAAY,yBAAA,EAA6B,KAAA,CAC3E,aAAA,CAAeA,CAAAA,CAAO,UAAA,EAAY,aAAA,EAAiB,KAAA,CACnD,oBAAqBA,CAAAA,CAAO,UAAA,EAAY,mBAAA,EAAuB,KACjE,EAEIA,CAAAA,CAAO,KAAA,CAAO,CAChB,IAAMa,EACJb,CAAAA,CAAO,KAAA,YAAiB,UAAA,CACpBA,CAAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA,CAClBA,CAAAA,CAAO,MAAM,UAAA,CACbA,CAAAA,CAAO,KAAA,CAAM,UAAA,CAAaA,EAAO,KAAA,CAAM,UACzC,CAAA,CACAA,CAAAA,CAAO,MACb,IAAA,CAAK,MAAA,CAAS,IAAI,QAAA,CAASa,CAAM,EACnC,CAAA,KACE,IAAA,CAAK,OAAS,IAAI,QAAA,CAAS,IAAI,WAAA,CAAY,CAAC,CAAC,CAAA,CAG/C,IAAA,IAAWC,CAAAA,IAAcd,EAAO,WAAA,EAAe,EAAC,CAC9C,IAAA,CAAK,aAAa,GAAA,CAAIc,CAAAA,CAAW,IAAA,CAAM,IAAIC,EAAeD,CAAU,CAAC,EAEzE,CAGA,SAASE,CAAAA,CAAsC,CAC7C,IAAMH,CAAAA,CACJG,aAAgB,UAAA,CACZA,CAAAA,CAAK,MAAA,CAAO,KAAA,CACVA,CAAAA,CAAK,UAAA,CACLA,CAAAA,CAAK,UAAA,CAAaA,EAAK,UACzB,CAAA,CACAA,CAAAA,CACN,IAAA,CAAK,OAAS,IAAI,QAAA,CAASH,CAAM,EACnC,CAGA,gBAAA,CAAiBG,CAAAA,CAAsC,CACrD,IAAMH,EACJG,CAAAA,YAAgB,UAAA,CACZA,CAAAA,CAAK,MAAA,CAAO,MACVA,CAAAA,CAAK,UAAA,CACLA,CAAAA,CAAK,UAAA,CAAaA,EAAK,UACzB,CAAA,CACAA,CAAAA,CACN,IAAA,CAAK,OAAS,IAAI,QAAA,CAASH,CAAM,CAAA,CAEjC,IAAMI,CAAAA,CAAQ,IAAI,KAAA,CAAM,4BAA4B,CAAA,CACpD,MAAA,CAAO,cAAA,CAAeA,CAAAA,CAAO,SAAU,CACrC,KAAA,CAAO,CAAE,KAAA,CAAO,KAAK,MAAO,CAAA,CAC5B,QAAA,CAAU,KACZ,CAAC,CAAA,CAED,IAAMC,CAAAA,CAAY,IAAA,CAAK,WAAW,GAAA,CAAI,4BAA4B,CAAA,CAClE,GAAIA,EACF,IAAA,IAAWC,CAAAA,IAAYD,CAAAA,CACrBC,CAAAA,CAASF,CAAK,EAGpB,CAEA,iBAAA,EAA0B,CACxB,IAAA,CAAK,UAAA,CAAa,MACpB,CAEA,IAAI,WAAA,EAAuB,CACzB,OAAO,IAAA,CAAK,UACd,CAGA,OAAA,CAAQd,CAAAA,CAA0C,CAChD,OAAO,IAAA,CAAK,YAAA,CAAa,GAAA,CAAIA,CAAI,CACnC,CAEA,mCAAA,CACED,CAAAA,CACmC,CACnC,IAAMI,CAAAA,CAAO,IAAA,CACb,OAAO,CACL,IAAA,CAAM,IAAA,CAAK,IAAA,CACX,OAAA,CAAAJ,EACA,UAAA,CAAY,CACV,SAAA,CAAW,IAAA,CAAK,WAAA,CAAY,SAAA,CAC5B,IAAA,CAAM,IAAA,CAAK,YAAY,IAAA,CACvB,oBAAA,CAAsB,IAAA,CAAK,WAAA,CAAY,qBACvC,KAAA,CAAO,IAAA,CAAK,WAAA,CAAY,KAAA,CACxB,OAAQ,IAAA,CAAK,WAAA,CAAY,MAAA,CACzB,QAAA,CAAU,KAAK,WAAA,CAAY,QAAA,CAC3B,yBAAA,CAA2B,IAAA,CAAK,YAAY,yBAAA,CAC5C,aAAA,CAAe,IAAA,CAAK,WAAA,CAAY,cAChC,mBAAA,CAAqB,IAAA,CAAK,WAAA,CAAY,mBACxC,EACA,IAAI,KAAA,EAAQ,CACV,OAAOI,CAAAA,CAAK,MACd,CAAA,CACA,SAAA,CAAW,SAAY,CACrB,GAAI,CAACA,CAAAA,CAAK,YAAY,IAAA,CACpB,MAAM,IAAI,YAAA,CACR,uCACA,mBACF,CAAA,CAEF,OAAOA,CAAAA,CAAK,MACd,CAAA,CACA,UAAA,CAAY,MAAOc,GAAwB,CACzC,GAAI,CAACd,CAAAA,CAAK,YAAY,KAAA,CACpB,MAAM,IAAI,YAAA,CACR,wCACA,mBACF,CAAA,CAEFA,CAAAA,CAAK,WAAA,CAAYc,CAAK,EACxB,CAAA,CACA,sBAAA,CAAwB,MAAOA,CAAAA,EAAwB,CACrD,GAAI,CAACd,EAAK,WAAA,CAAY,KAAA,CACpB,MAAM,IAAI,aACR,uCAAA,CACA,mBACF,CAAA,CAEFA,CAAAA,CAAK,YAAYc,CAAK,EACxB,CAAA,CACA,yBAAA,CAA2B,MAAOA,CAAAA,EAAwB,CACxD,GAAI,CAACd,EAAK,WAAA,CAAY,oBAAA,CACpB,MAAM,IAAI,aACR,wDAAA,CACA,mBACF,CAAA,CAEFA,CAAAA,CAAK,WAAA,CAAYc,CAAK,EACxB,CAAA,CACA,mBAAoB,gBAAkB,CACpC,GAAI,CAACd,EAAK,WAAA,CAAY,MAAA,EAAU,CAACA,CAAAA,CAAK,YAAY,QAAA,CAChD,MAAM,IAAI,YAAA,CACR,+CAAA,CACA,mBACF,CAAA,CAEF,OAAAA,EAAK,UAAA,CAAa,IAAA,CACX,IACT,CAAA,CACA,kBAAmB,gBAAkB,CACnC,OAAAA,CAAAA,CAAK,WAAa,KAAA,CACX,IACT,CAAA,CACA,gBAAA,CAAkB,CAACe,CAAAA,CAAcF,CAAAA,GAA4B,CACtDb,EAAK,UAAA,CAAW,GAAA,CAAIe,CAAI,CAAA,EAC3Bf,EAAK,UAAA,CAAW,GAAA,CAAIe,CAAAA,CAAM,IAAI,GAAK,CAAA,CAErCf,CAAAA,CAAK,UAAA,CAAW,GAAA,CAAIe,CAAI,CAAA,CAAG,GAAA,CAAIF,CAAQ,EACzC,EACA,mBAAA,CAAqB,CAACE,CAAAA,CAAcF,CAAAA,GAA4B,CAC9Db,CAAAA,CAAK,UAAA,CAAW,GAAA,CAAIe,CAAI,GAAG,MAAA,CAAOF,CAAQ,EAC5C,CAAA,CACA,aAAA,CAAe,IAAM,IAAA,CACrB,aAAA,CAAe,MAAOhB,CAAAA,EAAiB,CACrC,IAAMmB,CAAAA,CAAOhB,EAAK,YAAA,CAAa,GAAA,CAAIH,CAAI,CAAA,CACvC,GAAI,CAACmB,CAAAA,CACH,MAAM,IAAI,YAAA,CACR,CAAA,6BAAA,EAAgCnB,CAAI,CAAA,MAAA,CAAA,CACpC,eACF,CAAA,CAEF,OAAOmB,CAAAA,CAAK,+BAAA,CACVhB,EAAK,mCAAA,CAAoCJ,CAAO,CAClD,CACF,EACA,cAAA,CAAgB,MAAOC,CAAAA,EAAkB,CACvC,IAAMoB,CAAAA,CAAcpB,CAAAA,CAChB,CAACG,EAAK,YAAA,CAAa,GAAA,CAAIH,CAAI,CAAC,EAAE,MAAA,CAAO,OAAO,CAAA,CAC5C,KAAA,CAAM,KAAKG,CAAAA,CAAK,YAAA,CAAa,MAAA,EAAQ,EACnCkB,CAAAA,CAAYlB,CAAAA,CAAK,mCAAA,CAAoCJ,CAAO,EAClE,OAAQqB,CAAAA,CAAiC,GAAA,CAAKE,CAAAA,EAC5CA,EAAE,+BAAA,CAAgCD,CAAS,CAC7C,CACF,EACA,4BAAA,CAA8B,IAChC,CACF,CAEQ,WAAA,CAAYJ,CAAAA,CAA2B,CAC7C,IAAMP,EACJO,CAAAA,YAAiB,WAAA,CACbA,CAAAA,CACCA,CAAAA,CAAmB,QAAWA,CAAAA,CAAqB,MAAA,CAC1D,IAAA,CAAK,MAAA,CAAS,IAAI,QAAA,CAASP,CAAM,EACnC,CACF,CAAA,CAIaE,CAAAA,CAAN,KAAqB,CAI1B,YAAYf,CAAAA,CAA8B,CAExC,GADA,IAAA,CAAK,KAAOA,CAAAA,CAAO,IAAA,CACfA,CAAAA,CAAO,KAAA,CAAO,CAChB,IAAMa,CAAAA,CACJb,CAAAA,CAAO,KAAA,YAAiB,UAAA,CACpBA,CAAAA,CAAO,KAAA,CAAM,MAAA,CAAO,MAClBA,CAAAA,CAAO,KAAA,CAAM,UAAA,CACbA,CAAAA,CAAO,MAAM,UAAA,CAAaA,CAAAA,CAAO,KAAA,CAAM,UACzC,EACAA,CAAAA,CAAO,KAAA,CACb,IAAA,CAAK,MAAA,CAAS,IAAI,QAAA,CAASa,CAAM,EACnC,CAAA,KACE,KAAK,MAAA,CAAS,IAAI,QAAA,CAAS,IAAI,YAAY,CAAC,CAAC,EAEjD,CAGA,SAASG,CAAAA,CAAsC,CAC7C,IAAMH,CAAAA,CACJG,CAAAA,YAAgB,UAAA,CACZA,CAAAA,CAAK,MAAA,CAAO,MACVA,CAAAA,CAAK,UAAA,CACLA,CAAAA,CAAK,UAAA,CAAaA,EAAK,UACzB,CAAA,CACAA,CAAAA,CACN,IAAA,CAAK,OAAS,IAAI,QAAA,CAASH,CAAM,EACnC,CAGA,IAAI,KAAA,EAAkB,CACpB,OAAO,IAAA,CAAK,MACd,CAEA,+BAAA,CACEa,EAC+B,CAC/B,IAAMpB,CAAAA,CAAO,IAAA,CACb,OAAO,CACL,IAAA,CAAM,IAAA,CAAK,IAAA,CACX,cAAA,CAAAoB,CAAAA,CACA,IAAI,KAAA,EAAQ,CACV,OAAOpB,CAAAA,CAAK,MACd,CAAA,CACA,UAAW,SACFA,CAAAA,CAAK,MAAA,CAEd,UAAA,CAAY,MAAOc,CAAAA,EAAwB,CACzC,IAAMP,CAAAA,CACJO,aAAiB,WAAA,CACbA,CAAAA,CACCA,CAAAA,CAAmB,MAAA,EAAWA,EAAqB,MAAA,CAC1Dd,CAAAA,CAAK,MAAA,CAAS,IAAI,SAASO,CAAM,EACnC,CACF,CACF,CACF,EC9eA,IAAIc,CAAAA,CAAkB,CAAA,CAyCTC,CAAAA,CAAN,KAAoB,CAmBzB,WAAA,CAAYC,EAA6B,EAAC,CAAG,CAd7C,IAAA,CAAQ,WAA8C,IAAI,GAAA,CAE1D,IAAA,CAAQ,uBAAA,CAA0B,MAahC,IAAA,CAAK,EAAA,CAAKA,CAAAA,CAAQ,EAAA,EAAM,CAAA,YAAA,EAAe,EAAEF,CAAe,CAAA,CAAA,CACxD,KAAK,IAAA,CAAOE,CAAAA,CAAQ,IAAA,CACpB,IAAA,CAAK,cAAgBA,CAAAA,CAAQ,YAAA,EAAgB,EAAC,CAC9C,KAAK,KAAA,CAAQ,IAAIhC,CAAAA,CAAe,IAAA,CAAMgC,CAAAA,CAAQ,QAAA,EAAY,EAAE,EAC5D,IAAA,CAAK,KAAA,CAAQA,CAAAA,CAAQ,IAAA,EAAQ,IAC7B,IAAA,CAAK,yBAAA,CAA4BA,CAAAA,CAAQ,mBAAA,EAAuB,EAChE,IAAA,CAAK,YAAA,CAAe,CAClB,YAAA,CAAcA,EAAQ,WAAA,EAAa,YAAA,EAAgB,IAAA,CACnD,eAAA,CAAiBA,EAAQ,WAAA,EAAa,eAAA,EAAmB,IAAA,CACzD,GAAA,CAAKA,EAAQ,WAAA,EAAa,GAAA,EAAO,IACnC,EACF,CAGA,aAAA,CAAcC,CAAAA,CAAwC,CAQpD,OAPI,EAAAA,CAAAA,CAAO,QAAA,EAIL,CAHeA,EAAO,QAAA,CAAS,IAAA,CAAM3B,CAAAA,EACvC,IAAA,CAAK,cAAc,QAAA,CAAS,MAAA,CAAOA,CAAI,CAAC,CAC1C,CAAA,EAGE2B,CAAAA,CAAO,IAAA,EAAQA,CAAAA,CAAO,OAAS,IAAA,CAAK,IAAA,EACpCA,CAAAA,CAAO,UAAA,EAAc,CAAC,IAAA,CAAK,IAAA,EAAM,UAAA,CAAWA,CAAAA,CAAO,UAAU,CAAA,CAGnE,CAGA,iBAAA,EAAqC,CACnC,IAAMxB,CAAAA,CAAO,IAAA,CAEPyB,CAAAA,CAAQ,CACZ,EAAA,CAAI,IAAA,CAAK,EAAA,CACT,IAAA,CAAM,KAAK,IAAA,EAAQ,IAAA,CACnB,IAAA,CAAM,IAAA,CACN,oBAAqB,MAAOF,CAAAA,EAAuC,CAEjE,GADAvB,EAAK,uBAAA,CAA0B,IAAA,CAC3BuB,CAAAA,EAAS,MAAA,CAAQ,CACnB,GAAIA,CAAAA,CAAQ,MAAA,CAAO,OAAA,CAAS,CAC1BvB,CAAAA,CAAK,uBAAA,CAA0B,KAAA,CAC/B,MACF,CAEAuB,CAAAA,CAAQ,MAAA,CAAO,gBAAA,CACb,OAAA,CACA,IAAM,CACJvB,CAAAA,CAAK,uBAAA,CAA0B,MACjC,CAAA,CACA,CAAE,IAAA,CAAM,IAAK,CACf,EACF,CACF,CAAA,CACA,gBAAA,CAAkB,CAACe,CAAAA,CAAcF,CAAAA,GAA4B,CAC3Db,CAAAA,CAAK,aAAae,CAAAA,CAAMF,CAAQ,EAClC,CAAA,CACA,mBAAA,CAAqB,CAACE,CAAAA,CAAcF,CAAAA,GAA4B,CAC9Db,CAAAA,CAAK,eAAA,CAAgBe,CAAAA,CAAMF,CAAQ,EACrC,CAAA,CACA,aAAA,CAAgBa,CAAAA,EAAkB,IAAA,CAClC,IAAI,sBAAA,EAAyB,CAC3B,OAAO1B,CAAAA,CAAK,uBACd,CAAA,CACA,qBAAA,CAAuB,SAAY,CACjCA,CAAAA,CAAK,uBAAA,CAA0B,MACjC,CAAA,CACA,OAAQ,SAAY,CAAC,CAAA,CACrB,uBAAA,CAAyB,KACzB,wBAAA,CAA0B,IAAA,CAC1B,4BAAA,CAA8B,IAAA,CAC9B,eAAgB,IAAA,CAChB,gBAAA,CAAkB,IAAA,CAClB,gBAAA,CAAkB,IACpB,CAAA,CAEA,OAACyB,CAAAA,CAAiC,IAAA,CAAO,KAAK,KAAA,CAAM,2BAAA,CAA4BA,CAAK,CAAA,CACpFA,EAAiC,IAAA,CAAK,MAAA,CAAS,SAAY,IAAA,CAAK,YAAA,CAAa,GAAA,CAC7EA,CAAAA,CAAiC,IAAA,CAAK,eAAiB,UAAa,CAAE,GAAG,IAAA,CAAK,YAAa,CAAA,CAAA,CAErFA,CACT,CAEA,iBAAA,EAA6B,CAC3B,OAAI,IAAA,CAAK,yBAAA,EAA6B,CAAA,CAC7B,KAAA,EAET,IAAA,CAAK,yBAAA,EAA6B,CAAA,CAC3B,KACT,CAGA,kBAAA,EAA2B,CACzB,IAAA,CAAK,MAAM,UAAA,EAAW,CACtB,IAAA,CAAK,KAAA,CAAM,yBAA0B,IAAI,KAAA,CAAM,wBAAwB,CAAC,EAC1E,CAGA,IAAI,IAAA,EAAuB,CACzB,OAAO,IAAA,CAAK,KACd,CAEA,IAAI,YAAA,EAAkC,CACpC,OAAO,IAAA,CAAK,aACd,CAEA,IAAI,IAAA,EAAe,CACjB,OAAO,IAAA,CAAK,KACd,CAGA,iBAAA,CAAkBF,EAAoC,EAAC,CAAS,CAC9D,GAAI,KAAK,kBAAA,CAAoB,CAC3B,IAAA,CAAK,kBAAA,CAAmB,KAAMA,CAAO,CAAA,CACrC,MACF,CAEA,IAAA,CAAK,0BAAA,CAA2BA,CAAO,EACzC,CAGA,OAAA,CAAQI,CAAAA,CAAoB,CAC1B,IAAA,CAAK,MAAQA,EACf,CAGA,oBAAA,CACEC,CAAAA,CACM,CACN,IAAA,CAAK,kBAAA,CAAqBA,EAC5B,CAGA,0BAAA,CAA2BL,CAAAA,CAAoC,EAAC,CAAS,CAClE,IAAA,CAAK,uBAAA,EAIV,IAAA,CAAK,KAAA,CACH,wBACA,IAAA,CAAK,wBAAA,CAAyB,IAAA,CAAK,iBAAA,GAAqBA,CAAO,CACjE,EACF,CAGA,wBAAA,CACExB,CAAAA,CACAwB,CAAAA,CAAoC,GAC7B,CACP,IAAMZ,CAAAA,CAAQ,IAAI,MAAM,uBAAuB,CAAA,CAU/C,OAAA,MAAA,CAAO,gBAAA,CAAiBA,EAAO,CAC7B,MAAA,CAAQ,CAAE,KAAA,CAAOZ,EAAa,QAAA,CAAU,KAAM,CAAA,CAC9C,IAAA,CAAM,CAAE,KAAA,CAAO,IAAA,CAAK,IAAA,CAAM,QAAA,CAAU,KAAM,CAAA,CAC1C,KAAA,CAAO,CACL,KAAA,CAAO,CAAC,GAAIwB,CAAAA,CAAQ,KAAA,EAAS,IAAA,CAAK,aAAc,CAAA,CAChD,QAAA,CAAU,KACZ,EACA,IAAA,CAAM,CAAE,KAAA,CAAOA,CAAAA,CAAQ,MAAQ,IAAA,CAAK,KAAA,CAAO,QAAA,CAAU,KAAM,EAC3D,OAAA,CAAS,CAAE,KAAA,CAAOA,CAAAA,CAAQ,OAAA,CAAS,QAAA,CAAU,KAAM,CAAA,CACnD,iBAAkB,CAChB,KAAA,CAAOA,CAAAA,CAAQ,gBAAA,EAAoB,IAAI,GAAA,CACvC,QAAA,CAAU,KACZ,CAAA,CACA,YAAa,CACX,KAAA,CAAOA,CAAAA,CAAQ,WAAA,EAAe,IAAI,GAAA,CAClC,QAAA,CAAU,KACZ,CACF,CAAC,CAAA,CAEMZ,CACT,CAIQ,aAAaI,CAAAA,CAAcF,CAAAA,CAA+B,CAC3D,IAAA,CAAK,WAAW,GAAA,CAAIE,CAAI,CAAA,EAC3B,IAAA,CAAK,WAAW,GAAA,CAAIA,CAAAA,CAAM,IAAI,GAAK,EAErC,IAAA,CAAK,UAAA,CAAW,GAAA,CAAIA,CAAI,EAAG,GAAA,CAAIF,CAAQ,EACzC,CAEQ,gBAAgBE,CAAAA,CAAcF,CAAAA,CAA+B,CACnE,IAAA,CAAK,UAAA,CAAW,GAAA,CAAIE,CAAI,CAAA,EAAG,OAAOF,CAAQ,EAC5C,CAEQ,KAAA,CAAME,EAAcJ,CAAAA,CAAoB,CAC9C,IAAMC,CAAAA,CAAY,KAAK,UAAA,CAAW,GAAA,CAAIG,CAAI,CAAA,CAC1C,GAAIH,CAAAA,CACF,IAAA,IAAWC,CAAAA,IAAYD,EACrBC,CAAAA,CAASF,CAAK,EAGpB,CACF,ECtPA,IAAMkB,CAAAA,CAA0B,IAC9B,OAAA,CAAQ,OAAO,IAAI,YAAA,CAAa,uDAAA,CAAyD,mBAAmB,CAAC,CAAA,CAEzGC,CAAAA,CAAO,IAAY,CAAC,CAAA,CAEbC,CAAAA,CAAN,KAAoB,CAqCzB,YAAYR,CAAAA,CAAgC,EAAC,CAAG,CAnChD,KAAQ,QAAA,CAAuC,IAAI,GAAA,CACnD,IAAA,CAAQ,WAA8C,IAAI,GAAA,CAC1D,IAAA,CAAQ,WAAA,CAAc,MAItB,IAAA,CAAS,cAAA,CAAiB,CACxB,iBAAA,CAAmBM,EACnB,2BAAA,CAA6BA,CAAAA,CAC7B,mCAAA,CAAqCA,CAAAA,CACrC,uBAAwBA,CAAAA,CACxB,gBAAA,CAAkBA,CAAAA,CAClB,UAAA,CAAYA,CAAAA,CACZ,MAAA,CAAQA,CAAAA,CACR,OAAA,CAASA,EACT,SAAA,CAAWA,CAAAA,CACX,IAAA,CAAMA,CAAAA,CACN,KAAMA,CAAAA,CACN,OAAA,CAASC,CACX,CAAA,CAEA,KAAS,UAAA,CAAa,CACpB,WAAA,CAAa,KAAA,CACb,SAAA,CAAWD,CAAAA,CACX,eAAA,CAAiBA,CAAAA,CACjB,KAAMA,CAAAA,CACN,OAAA,CAASC,CAAAA,CACT,gBAAA,CAAkBA,EAClB,mBAAA,CAAqBA,CAAAA,CACrB,cAAA,CAAgB,IAAA,CAChB,qBAAsB,IAAA,CACtB,uBAAA,CAAyB,IAAA,CACzB,wBAAA,CAA0B,IAC5B,CAAA,CAsLA,IAAA,CAAiB,oBAAA,CAAuB,CACtCtC,CAAAA,CACA+B,CAAAA,GACS,CAOT,GANA/B,EAAO,0BAAA,CAA2B+B,CAAO,CAAA,CAErC,CAAC,KAAK,WAAA,EAIN,CAAC,IAAA,CAAK,YAAA,CAAa/B,CAAM,CAAA,CAC3B,OAGF,IAAMmB,CAAAA,CAAQnB,EAAO,wBAAA,CAAyBA,CAAAA,CAAO,iBAAA,EAAkB,CAAG+B,CAAO,CAAA,CAC3EX,CAAAA,CAAY,IAAA,CAAK,UAAA,CAAW,IAAI,uBAAuB,CAAA,CAC7D,GAAKA,CAAAA,CAIL,IAAA,IAAWC,CAAAA,IAAYD,CAAAA,CACrBC,CAAAA,CAASF,CAAK,EAElB,CAAA,CAzME,GADA,IAAA,CAAK,WAAaY,CAAAA,CAAQ,SAAA,EAAa,IAAA,CACnCA,CAAAA,CAAQ,QACV,IAAA,IAAWS,CAAAA,IAAQT,CAAAA,CAAQ,OAAA,CAAS,CAClC,IAAM/B,CAAAA,CAAS,IAAI8B,EAAcU,CAAI,CAAA,CACrCxC,CAAAA,CAAO,oBAAA,CAAqB,KAAK,oBAAoB,CAAA,CACrD,IAAA,CAAK,QAAA,CAAS,IAAIA,CAAAA,CAAO,EAAA,CAAIA,CAAM,EACrC,CAEJ,CAIA,MAAM,eAAA,EAAoC,CACxC,OAAO,IAAA,CAAK,UACd,CAEA,MAAM,aAAA,CACJ+B,CAAAA,CAC0B,CAC1B,GAAI,CAAC,IAAA,CAAK,UAAA,CACR,MAAM,IAAI,aACR,iCAAA,CACA,eACF,CAAA,CAGF,IAAMU,EAAW,IAAA,CAAK,oBAAA,CAAqBV,CAAkC,CAAA,CAC7E,GAAIU,CAAAA,CAAS,MAAA,GAAW,CAAA,CACtB,MAAM,IAAI,YAAA,CACR,+CAAA,CACA,eACF,CAAA,CAIF,OAAOA,CAAAA,CAAS,CAAC,CAAA,CAAE,mBACrB,CAEA,MAAM,UAAA,EAAyC,CAC7C,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,SAAS,MAAA,EAAQ,CAAA,CAAE,GAAA,CAAKd,GAC7CA,CAAAA,CAAE,iBAAA,EACJ,CACF,CAEA,MAAM,aAAA,CACJI,CAAAA,CAC0B,CAC1B,GAAI,IAAA,CAAK,WAAA,CACP,MAAM,IAAI,aAAa,0BAAA,CAA4B,mBAAmB,CAAA,CAExE,IAAA,CAAK,WAAA,CAAc,IAAA,CACnB,IAAA,CAAK,gBAAA,CAAmBA,EACxB,IAAMW,CAAAA,CAAO,CACX,MAAA,CAAQ,KACR,mBAAA,CAAqBX,CAAAA,EAAS,mBAAA,EAAuB,KAAA,CACrD,wBAAyBA,CAAAA,EAAS,uBAAA,EAA2B,KAAA,CAC7D,IAAA,CAAM,IAAM,CACV,IAAA,CAAK,WAAA,CAAc,KAAA,CACnB,KAAK,gBAAA,CAAmB,MAAA,CACxBW,CAAAA,CAAK,MAAA,CAAS,MAChB,CACF,CAAA,CACA,OAAOA,CACT,CAEA,gBAAA,CAAiBnB,CAAAA,CAAcF,CAAAA,CAA+B,CACvD,IAAA,CAAK,UAAA,CAAW,GAAA,CAAIE,CAAI,GAC3B,IAAA,CAAK,UAAA,CAAW,GAAA,CAAIA,CAAAA,CAAM,IAAI,GAAK,CAAA,CAErC,IAAA,CAAK,UAAA,CAAW,IAAIA,CAAI,CAAA,CAAG,GAAA,CAAIF,CAAQ,EACzC,CAEA,mBAAA,CAAoBE,CAAAA,CAAcF,EAA+B,CAC/D,IAAA,CAAK,UAAA,CAAW,GAAA,CAAIE,CAAI,CAAA,EAAG,MAAA,CAAOF,CAAQ,EAC5C,CAKA,SAAA,CAAUU,CAAAA,CAA2C,CACnD,IAAM/B,CAAAA,CAAS,IAAI8B,CAAAA,CAAcC,CAAO,EACxC,OAAA/B,CAAAA,CAAO,oBAAA,CAAqB,IAAA,CAAK,oBAAoB,CAAA,CACrD,IAAA,CAAK,QAAA,CAAS,GAAA,CAAIA,EAAO,EAAA,CAAIA,CAAM,CAAA,CAC5BA,CACT,CAGA,YAAA,CAAa2C,CAAAA,CAAkB,CAC7B,IAAM3C,EAAS,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI2C,CAAE,EAC/B3C,CAAAA,GACFA,CAAAA,CAAO,oBAAA,CAAqB,MAAS,EACrCA,CAAAA,CAAO,kBAAA,EAAmB,CAC1B,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO2C,CAAE,CAAA,EAE3B,CAGA,SAAA,CAAUA,CAAAA,CAAuC,CAC/C,OAAO,KAAK,QAAA,CAAS,GAAA,CAAIA,CAAE,CAC7B,CAGA,YAAA,CAAaC,CAAAA,CAA0B,CACrC,IAAA,CAAK,UAAA,CAAaA,EACpB,CAGA,OAAA,EAAgB,CACd,OAAI,OAAO,UAAA,CAAW,SAAA,CAAc,IAC3B,IAAA,EAGT,IAAA,CAAK,4BAAA,CAAgC,UAAA,CAAW,UAE7C,SAAA,CAEH,MAAA,CAAO,cAAA,CAAe,UAAA,CAAW,SAAA,CAAW,WAAA,CAAa,CACvD,KAAA,CAAO,KACP,QAAA,CAAU,IAAA,CACV,YAAA,CAAc,IAChB,CAAC,CAAA,CACM,IAAA,CACT,CAGA,SAAA,EAAkB,CACZ,OAAO,UAAA,CAAW,SAAA,CAAc,GAAA,GAIpC,OAAO,cAAA,CAAe,UAAA,CAAW,SAAA,CAAW,WAAA,CAAa,CACvD,KAAA,CAAO,IAAA,CAAK,4BAAA,CACZ,QAAA,CAAU,KACV,YAAA,CAAc,IAChB,CAAC,CAAA,CACD,KAAK,4BAAA,CAA+B,MAAA,EACtC,CAGA,iBAAA,CACEC,CAAAA,CACAd,CAAAA,CAAoC,EAAC,CAC/B,CACN,IAAM/B,CAAAA,CAAS,IAAA,CAAK,QAAA,CAAS,IAAI6C,CAAQ,CAAA,CACzC,GAAI,CAAC7C,EACH,MAAM,IAAI,KAAA,CAAM,CAAA,qBAAA,EAAwB6C,CAAQ,CAAA,CAAE,CAAA,CAGpD,IAAA,CAAK,qBAAqB7C,CAAAA,CAAQ+B,CAAO,EAC3C,CAGA,OAAc,CACZ,IAAA,IAAW/B,CAAAA,IAAU,IAAA,CAAK,SAAS,MAAA,EAAO,CACxCA,CAAAA,CAAO,oBAAA,CAAqB,MAAS,CAAA,CACrCA,CAAAA,CAAO,kBAAA,GAET,IAAA,CAAK,QAAA,CAAS,KAAA,EAAM,CACpB,KAAK,UAAA,CAAW,KAAA,EAAM,CACtB,IAAA,CAAK,YAAc,KAAA,CACnB,IAAA,CAAK,gBAAA,CAAmB,MAAA,CACxB,KAAK,UAAA,CAAa,KACpB,CAIQ,oBAAA,CACN+B,EACiB,CACjB,GAAI,CAACA,CAAAA,EAAYA,EAA2C,gBAAA,CAC1D,OAAO,KAAA,CAAM,IAAA,CAAK,KAAK,QAAA,CAAS,MAAA,EAAQ,CAAA,CAG1C,IAAMe,CAAAA,CAAYf,CAAAA,CAAkD,OAAA,EAAY,EAAC,CACjF,OAAO,KAAA,CAAM,IAAA,CAAK,KAAK,QAAA,CAAS,MAAA,EAAQ,CAAA,CAAE,OAAQ/B,CAAAA,EAChD8C,CAAAA,CAAQ,IAAA,CAAMd,CAAAA,EAAkChC,CAAAA,CAAO,aAAA,CAAcgC,CAAM,CAAC,CAC9E,CACF,CA2BQ,YAAA,CAAahC,CAAAA,CAAgC,CACnD,IAAM+B,CAAAA,CAAU,IAAA,CAAK,gBAAA,CAKrB,GAJI,CAACA,CAAAA,EAIDA,CAAAA,CAAQ,uBAAA,CACV,OAAO,KAAA,CAGT,IAAMe,CAAAA,CAAUf,EAAQ,OAAA,EAAW,EAAC,CACpC,OAAIe,EAAQ,MAAA,GAAW,CAAA,CACd,IAAA,CAGFA,CAAAA,CAAQ,KAAMd,CAAAA,EAAWhC,CAAAA,CAAO,aAAA,CAAcgC,CAAM,CAAC,CAC9D,CACF,EAMO,SAASe,EACdhB,CAAAA,CACe,CACf,OAAO,IAAIQ,EAAcR,CAAO,CAClC,CAMO,SAASiB,EACdjB,CAAAA,CACe,CAEf,OADagB,CAAAA,CAAoBhB,CAAO,CAAA,CAC5B,OAAA,EACd,CC3OO,IAAMkB,CAAAA,CAAY,CACvB,QAAA,CAAU,CACR,UAAA,CAAY,sCAAA,CACZ,OAAA,CAAS,sCAAA,CACT,YAAa,sCAAA,CACb,qBAAA,CAAuB,sCACzB,CAAA,CACA,eAAA,CAAiB,CACf,sBAAA,CAAwB,sCAAA,CACxB,qBAAsB,sCAAA,CACtB,aAAA,CAAe,sCAAA,CACf,iBAAA,CAAmB,uCACnB,YAAA,CAAc,sCAAA,CACd,WAAA,CAAa,sCACf,EACA,WAAA,CAAa,CAEX,IAAA,CAAM,sCAAA,CAEN,gBAAA,CAAkB,sCAAA,CAElB,mBAAA,CAAqB,sCACvB,CACF,CAAA,CAGaC,CAAAA,CAAU,CAErB,SAAA,CAAUC,EAAO,gBAAA,CAA8D,CAC7E,OAAO,CACL,KAAAA,CAAAA,CACA,YAAA,CAAc,CAACF,CAAAA,CAAU,SAAS,UAAU,CAAA,CAC5C,QAAA,CAAU,CACR,CACE,IAAA,CAAMA,CAAAA,CAAU,QAAA,CAAS,UAAA,CACzB,gBAAiB,CACf,CACE,IAAA,CAAMA,CAAAA,CAAU,gBAAgB,sBAAA,CAChC,UAAA,CAAY,CAAE,IAAA,CAAM,IAAA,CAAM,MAAA,CAAQ,IAAK,CAAA,CACvC,MAAO,IAAI,UAAA,CAAW,CAAC,CAAA,CAAM,EAAE,CAAC,CAClC,CAAA,CACA,CACE,KAAMA,CAAAA,CAAU,eAAA,CAAgB,oBAAA,CAChC,UAAA,CAAY,CAAE,IAAA,CAAM,IAAK,CAAA,CACzB,MAAO,IAAI,UAAA,CAAW,CAAC,CAAC,CAAC,CAC3B,CACF,CACF,CACF,CACF,CACF,CAAA,CAGA,OAAA,CAAQE,CAAAA,CAAO,qBAAA,CAAmE,CAChF,OAAO,CACL,KAAAA,CAAAA,CACA,YAAA,CAAc,CAACF,CAAAA,CAAU,SAAS,OAAO,CAAA,CACzC,QAAA,CAAU,CACR,CACE,IAAA,CAAMA,CAAAA,CAAU,QAAA,CAAS,OAAA,CACzB,gBAAiB,CACf,CACE,IAAA,CAAMA,CAAAA,CAAU,gBAAgB,aAAA,CAChC,UAAA,CAAY,CAAE,IAAA,CAAM,KAAM,MAAA,CAAQ,IAAK,CAAA,CACvC,KAAA,CAAO,IAAI,UAAA,CAAW,CAAC,EAAE,CAAC,CAC5B,CACF,CACF,CACF,CACF,CACF,CAAA,CAGA,IAAA,CAAKE,CAAAA,CAAO,mBAAgE,CAC1E,OAAO,CACL,IAAA,CAAAA,EACA,YAAA,CAAc,CACZF,CAAAA,CAAU,QAAA,CAAS,UAAA,CACnBA,CAAAA,CAAU,QAAA,CAAS,OAAA,CACnBA,EAAU,QAAA,CAAS,WACrB,CAAA,CACA,QAAA,CAAU,CACR,CACE,IAAA,CAAMA,CAAAA,CAAU,QAAA,CAAS,WACzB,eAAA,CAAiB,CACf,CACE,IAAA,CAAMA,CAAAA,CAAU,eAAA,CAAgB,sBAAA,CAChC,UAAA,CAAY,CAAE,IAAA,CAAM,IAAA,CAAM,MAAA,CAAQ,IAAK,EACvC,KAAA,CAAO,IAAI,UAAA,CAAW,CAAC,EAAM,EAAE,CAAC,CAClC,CACF,CACF,CAAA,CACA,CACE,IAAA,CAAMA,CAAAA,CAAU,SAAS,OAAA,CACzB,eAAA,CAAiB,CACf,CACE,KAAMA,CAAAA,CAAU,eAAA,CAAgB,aAAA,CAChC,UAAA,CAAY,CAAE,IAAA,CAAM,IAAA,CAAM,MAAA,CAAQ,IAAK,CAAA,CACvC,KAAA,CAAO,IAAI,UAAA,CAAW,CAAC,GAAG,CAAC,CAC7B,CACF,CACF,CAAA,CACA,CACE,IAAA,CAAMA,CAAAA,CAAU,SAAS,WAAA,CACzB,eAAA,CAAiB,CACf,CACE,KAAMA,CAAAA,CAAU,eAAA,CAAgB,iBAAA,CAChC,UAAA,CAAY,CAAE,IAAA,CAAM,IAAK,CAAA,CACzB,KAAA,CAAO,WAAW,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,kBAAkB,EAAE,GAAA,CAAInC,CAAAA,EAAKA,CAAAA,CAAE,UAAA,CAAW,CAAC,CAAC,CAAC,CACjF,EACA,CACE,IAAA,CAAMmC,CAAAA,CAAU,eAAA,CAAgB,aAChC,UAAA,CAAY,CAAE,IAAA,CAAM,IAAK,EACzB,KAAA,CAAO,UAAA,CAAW,IAAA,CAAK,KAAA,CAAM,KAAK,SAAS,CAAA,CAAE,GAAA,CAAInC,CAAAA,EAAKA,EAAE,UAAA,CAAW,CAAC,CAAC,CAAC,CACxE,CACF,CACF,CACF,CACF,CACF,CACF","file":"index.js","sourcesContent":["/**\n * Mock GATT Server, Services, and Characteristics\n *\n * Stateful mocks that simulate real BLE behavior:\n * - Characteristic reads return configured values\n * - Writes store values\n * - Notifications can be pumped programmatically\n */\n\nimport type { MockBleDevice } from './device';\n\nexport interface MockCharacteristicConfig {\n /** Characteristic UUID */\n uuid: string;\n /** Characteristic properties (all default to false except read) */\n properties?: {\n broadcast?: boolean;\n read?: boolean;\n write?: boolean;\n writeWithoutResponse?: boolean;\n notify?: boolean;\n indicate?: boolean;\n authenticatedSignedWrites?: boolean;\n reliableWrite?: boolean;\n writableAuxiliaries?: boolean;\n };\n /** Initial value (DataView or Uint8Array) */\n value?: ArrayBuffer | Uint8Array;\n /** Descriptors for this characteristic */\n descriptors?: MockDescriptorConfig[];\n}\n\nexport interface MockServiceConfig {\n /** Service UUID */\n uuid: string;\n /** Whether this is a primary service (default: true) */\n isPrimary?: boolean;\n /** Characteristics in this service */\n characteristics?: MockCharacteristicConfig[];\n}\n\nexport interface MockDescriptorConfig {\n /** Descriptor UUID */\n uuid: string;\n /** Initial value */\n value?: ArrayBuffer | Uint8Array;\n}\n\n// --- Mock GATT Server ---\n\nexport class MockGATTServer {\n private _connected = false;\n private _device: MockBleDevice;\n private _services: Map<string, MockService> = new Map();\n\n constructor(device: MockBleDevice, configs: MockServiceConfig[]) {\n this._device = device;\n for (const config of configs) {\n this._services.set(\n config.uuid,\n new MockService(device, config)\n );\n }\n }\n\n get connected(): boolean {\n return this._connected;\n }\n\n async connect(): Promise<BluetoothRemoteGATTServer> {\n if (this._device.shouldFailConnect()) {\n throw new DOMException('Simulated transient connection failure', 'NetworkError');\n }\n this._connected = true;\n return this.asBluetoothRemoteGATTServer();\n }\n\n disconnect(): void {\n this._connected = false;\n // Stop all notifications\n for (const service of this._services.values()) {\n service.stopAllNotifications();\n }\n }\n\n async getPrimaryService(uuid: string): Promise<BluetoothRemoteGATTService> {\n this._assertConnected();\n const service = this._services.get(uuid);\n if (!service) {\n throw new DOMException(\n `No Services matching UUID ${uuid} found`,\n 'NotFoundError'\n );\n }\n return service.asBluetoothRemoteGATTService();\n }\n\n async getPrimaryServices(\n uuid?: string\n ): Promise<BluetoothRemoteGATTService[]> {\n this._assertConnected();\n const services = uuid\n ? [this._services.get(uuid)].filter(Boolean)\n : Array.from(this._services.values());\n return (services as MockService[]).map((s) =>\n s.asBluetoothRemoteGATTService()\n );\n }\n\n /** Get a mock service for test control */\n getService(uuid: string): MockService | undefined {\n return this._services.get(uuid);\n }\n\n asBluetoothRemoteGATTServer(deviceProxy?: BluetoothDevice): BluetoothRemoteGATTServer {\n const self = this;\n const server = {\n get connected() {\n return self._connected;\n },\n get device() {\n return deviceProxy!;\n },\n connect: () => self.connect(),\n disconnect: () => self.disconnect(),\n getPrimaryService: (uuid: string) =>\n self.getPrimaryService(uuid),\n getPrimaryServices: (uuid?: string) =>\n self.getPrimaryServices(uuid),\n } as unknown as BluetoothRemoteGATTServer;\n return server;\n }\n\n private _assertConnected(): void {\n if (!this._connected) {\n throw new DOMException(\n 'GATT Server is disconnected. Cannot perform GATT operations.',\n 'NetworkError'\n );\n }\n }\n}\n\n// --- Mock Service ---\n\nexport class MockService {\n readonly uuid: string;\n readonly isPrimary: boolean;\n private _characteristics: Map<string, MockCharacteristic> = new Map();\n\n constructor(_device: MockBleDevice, config: MockServiceConfig) {\n this.uuid = config.uuid;\n this.isPrimary = config.isPrimary ?? true;\n for (const charConfig of config.characteristics ?? []) {\n this._characteristics.set(\n charConfig.uuid,\n new MockCharacteristic(charConfig)\n );\n }\n }\n\n async getCharacteristic(\n uuid: string\n ): Promise<BluetoothRemoteGATTCharacteristic> {\n const char = this._characteristics.get(uuid);\n if (!char) {\n throw new DOMException(\n `No Characteristics matching UUID ${uuid} found`,\n 'NotFoundError'\n );\n }\n return char.asBluetoothRemoteGATTCharacteristic(\n this.asBluetoothRemoteGATTService()\n );\n }\n\n async getCharacteristics(\n uuid?: string\n ): Promise<BluetoothRemoteGATTCharacteristic[]> {\n const chars = uuid\n ? [this._characteristics.get(uuid)].filter(Boolean)\n : Array.from(this._characteristics.values());\n const service = this.asBluetoothRemoteGATTService();\n return (chars as MockCharacteristic[]).map((c) =>\n c.asBluetoothRemoteGATTCharacteristic(service)\n );\n }\n\n /** Get a mock characteristic for test control */\n getChar(uuid: string): MockCharacteristic | undefined {\n return this._characteristics.get(uuid);\n }\n\n stopAllNotifications(): void {\n for (const char of this._characteristics.values()) {\n char.stopNotifications();\n }\n }\n\n asBluetoothRemoteGATTService(deviceProxy?: BluetoothDevice): BluetoothRemoteGATTService {\n const self = this;\n return {\n uuid: this.uuid,\n isPrimary: this.isPrimary,\n get device() {\n return deviceProxy!;\n },\n getCharacteristic: (uuid: string) => self.getCharacteristic(uuid),\n getCharacteristics: (uuid?: string) =>\n self.getCharacteristics(uuid),\n getIncludedService: async () => {\n throw new DOMException('Not implemented', 'NotSupportedError');\n },\n getIncludedServices: async () => [],\n addEventListener: () => {},\n removeEventListener: () => {},\n dispatchEvent: () => true,\n oncharacteristicvaluechanged: null,\n onserviceadded: null,\n onservicechanged: null,\n onserviceremoved: null,\n } as unknown as BluetoothRemoteGATTService;\n }\n}\n\n// --- Mock Characteristic ---\n\nexport class MockCharacteristic {\n readonly uuid: string;\n private _properties: {\n broadcast: boolean;\n read: boolean;\n write: boolean;\n writeWithoutResponse: boolean;\n notify: boolean;\n indicate: boolean;\n authenticatedSignedWrites: boolean;\n reliableWrite: boolean;\n writableAuxiliaries: boolean;\n };\n private _value: DataView;\n private _notifying = false;\n private _listeners: Map<string, Set<EventListener>> = new Map();\n private _descriptors: Map<string, MockDescriptor> = new Map();\n\n constructor(config: MockCharacteristicConfig) {\n this.uuid = config.uuid;\n this._properties = {\n broadcast: config.properties?.broadcast ?? false,\n read: config.properties?.read ?? true,\n write: config.properties?.write ?? false,\n writeWithoutResponse: config.properties?.writeWithoutResponse ?? false,\n notify: config.properties?.notify ?? false,\n indicate: config.properties?.indicate ?? false,\n authenticatedSignedWrites: config.properties?.authenticatedSignedWrites ?? false,\n reliableWrite: config.properties?.reliableWrite ?? false,\n writableAuxiliaries: config.properties?.writableAuxiliaries ?? false,\n };\n\n if (config.value) {\n const buffer =\n config.value instanceof Uint8Array\n ? config.value.buffer.slice(\n config.value.byteOffset,\n config.value.byteOffset + config.value.byteLength\n )\n : config.value;\n this._value = new DataView(buffer);\n } else {\n this._value = new DataView(new ArrayBuffer(0));\n }\n\n for (const descConfig of config.descriptors ?? []) {\n this._descriptors.set(descConfig.uuid, new MockDescriptor(descConfig));\n }\n }\n\n /** Set the characteristic value (for test setup) */\n setValue(data: ArrayBuffer | Uint8Array): void {\n const buffer =\n data instanceof Uint8Array\n ? data.buffer.slice(\n data.byteOffset,\n data.byteOffset + data.byteLength\n )\n : data;\n this._value = new DataView(buffer);\n }\n\n /** Pump a notification to all listeners */\n emitNotification(data: ArrayBuffer | Uint8Array): void {\n const buffer =\n data instanceof Uint8Array\n ? data.buffer.slice(\n data.byteOffset,\n data.byteOffset + data.byteLength\n )\n : data;\n this._value = new DataView(buffer);\n\n const event = new Event('characteristicvaluechanged');\n Object.defineProperty(event, 'target', {\n value: { value: this._value },\n writable: false,\n });\n\n const listeners = this._listeners.get('characteristicvaluechanged');\n if (listeners) {\n for (const listener of listeners) {\n listener(event);\n }\n }\n }\n\n stopNotifications(): void {\n this._notifying = false;\n }\n\n get isNotifying(): boolean {\n return this._notifying;\n }\n\n /** Get a mock descriptor for test control */\n getDesc(uuid: string): MockDescriptor | undefined {\n return this._descriptors.get(uuid);\n }\n\n asBluetoothRemoteGATTCharacteristic(\n service: BluetoothRemoteGATTService\n ): BluetoothRemoteGATTCharacteristic {\n const self = this;\n return {\n uuid: this.uuid,\n service,\n properties: {\n broadcast: this._properties.broadcast,\n read: this._properties.read,\n writeWithoutResponse: this._properties.writeWithoutResponse,\n write: this._properties.write,\n notify: this._properties.notify,\n indicate: this._properties.indicate,\n authenticatedSignedWrites: this._properties.authenticatedSignedWrites,\n reliableWrite: this._properties.reliableWrite,\n writableAuxiliaries: this._properties.writableAuxiliaries,\n },\n get value() {\n return self._value;\n },\n readValue: async () => {\n if (!self._properties.read) {\n throw new DOMException(\n 'Characteristic does not support read',\n 'NotSupportedError'\n );\n }\n return self._value;\n },\n writeValue: async (value: BufferSource) => {\n if (!self._properties.write) {\n throw new DOMException(\n 'Characteristic does not support write',\n 'NotSupportedError'\n );\n }\n self._writeValue(value);\n },\n writeValueWithResponse: async (value: BufferSource) => {\n if (!self._properties.write) {\n throw new DOMException(\n 'Characteristic does not support write',\n 'NotSupportedError'\n );\n }\n self._writeValue(value);\n },\n writeValueWithoutResponse: async (value: BufferSource) => {\n if (!self._properties.writeWithoutResponse) {\n throw new DOMException(\n 'Characteristic does not support write without response',\n 'NotSupportedError'\n );\n }\n self._writeValue(value);\n },\n startNotifications: async function () {\n if (!self._properties.notify && !self._properties.indicate) {\n throw new DOMException(\n 'Characteristic does not support notifications',\n 'NotSupportedError'\n );\n }\n self._notifying = true;\n return this;\n },\n stopNotifications: async function () {\n self._notifying = false;\n return this;\n },\n addEventListener: (type: string, listener: EventListener) => {\n if (!self._listeners.has(type)) {\n self._listeners.set(type, new Set());\n }\n self._listeners.get(type)!.add(listener);\n },\n removeEventListener: (type: string, listener: EventListener) => {\n self._listeners.get(type)?.delete(listener);\n },\n dispatchEvent: () => true,\n getDescriptor: async (uuid: string) => {\n const desc = self._descriptors.get(uuid);\n if (!desc) {\n throw new DOMException(\n `No Descriptors matching UUID ${uuid} found`,\n 'NotFoundError'\n );\n }\n return desc.asBluetoothRemoteGATTDescriptor(\n self.asBluetoothRemoteGATTCharacteristic(service)\n );\n },\n getDescriptors: async (uuid?: string) => {\n const descriptors = uuid\n ? [self._descriptors.get(uuid)].filter(Boolean)\n : Array.from(self._descriptors.values());\n const charProxy = self.asBluetoothRemoteGATTCharacteristic(service);\n return (descriptors as MockDescriptor[]).map((d) =>\n d.asBluetoothRemoteGATTDescriptor(charProxy)\n );\n },\n oncharacteristicvaluechanged: null,\n } as unknown as BluetoothRemoteGATTCharacteristic;\n }\n\n private _writeValue(value: BufferSource): void {\n const buffer =\n value instanceof ArrayBuffer\n ? value\n : (value as DataView).buffer ?? (value as Uint8Array).buffer;\n this._value = new DataView(buffer);\n }\n}\n\n// --- Mock Descriptor ---\n\nexport class MockDescriptor {\n readonly uuid: string;\n private _value: DataView;\n\n constructor(config: MockDescriptorConfig) {\n this.uuid = config.uuid;\n if (config.value) {\n const buffer =\n config.value instanceof Uint8Array\n ? config.value.buffer.slice(\n config.value.byteOffset,\n config.value.byteOffset + config.value.byteLength\n )\n : config.value;\n this._value = new DataView(buffer);\n } else {\n this._value = new DataView(new ArrayBuffer(0));\n }\n }\n\n /** Set the descriptor value (for test setup) */\n setValue(data: ArrayBuffer | Uint8Array): void {\n const buffer =\n data instanceof Uint8Array\n ? data.buffer.slice(\n data.byteOffset,\n data.byteOffset + data.byteLength\n )\n : data;\n this._value = new DataView(buffer);\n }\n\n /** Get the current value */\n get value(): DataView {\n return this._value;\n }\n\n asBluetoothRemoteGATTDescriptor(\n characteristic: BluetoothRemoteGATTCharacteristic\n ): BluetoothRemoteGATTDescriptor {\n const self = this;\n return {\n uuid: this.uuid,\n characteristic,\n get value() {\n return self._value;\n },\n readValue: async () => {\n return self._value;\n },\n writeValue: async (value: BufferSource) => {\n const buffer =\n value instanceof ArrayBuffer\n ? value\n : (value as DataView).buffer ?? (value as Uint8Array).buffer;\n self._value = new DataView(buffer);\n },\n } as unknown as BluetoothRemoteGATTDescriptor;\n }\n}\n","/**\n * Mock BLE Device — stateful device with GATT server, services, characteristics\n */\n\nimport {\n MockGATTServer,\n type MockServiceConfig,\n} from './characteristics';\n\nlet deviceIdCounter = 0;\n\nexport interface MockDeviceOptions {\n /** Device ID (auto-generated if not provided) */\n id?: string;\n /** Device name */\n name?: string;\n /** Advertised service UUIDs */\n serviceUUIDs?: string[];\n /** GATT service configurations */\n services?: MockServiceConfig[];\n /** Initial RSSI value */\n rssi?: number;\n /** Fail the first N connect() attempts with a NetworkError. */\n failConnectAttempts?: number;\n /** Optional platform-reported write limits for MTU-aware write tests. */\n writeLimits?: {\n withResponse?: number | null;\n withoutResponse?: number | null;\n mtu?: number | null;\n };\n}\n\nexport interface MockAdvertisementOptions {\n /** Override RSSI for this advertisement */\n rssi?: number;\n /** Optional TX power value */\n txPower?: number;\n /** Override advertised UUIDs for this advertisement */\n uuids?: string[];\n /** Optional manufacturer data payloads */\n manufacturerData?: Map<number, DataView>;\n /** Optional service data payloads */\n serviceData?: Map<string, DataView>;\n}\n\ninterface ExtendedGatt extends BluetoothRemoteGATTServer {\n getMtu?: () => Promise<number | null>;\n getWriteLimits?: () => Promise<Record<string, number | null>>;\n}\n\nexport class MockBleDevice {\n readonly id: string;\n readonly name: string | undefined;\n private _serviceUUIDs: string[];\n private _gatt: MockGATTServer;\n private _listeners: Map<string, Set<EventListener>> = new Map();\n private _rssi: number;\n private _watchingAdvertisements = false;\n private _advertisementSink?: (\n device: MockBleDevice,\n options: MockAdvertisementOptions\n ) => void;\n private _remainingConnectFailures: number;\n private _writeLimits: {\n withResponse: number | null;\n withoutResponse: number | null;\n mtu: number | null;\n };\n\n constructor(options: MockDeviceOptions = {}) {\n this.id = options.id ?? `mock-device-${++deviceIdCounter}`;\n this.name = options.name;\n this._serviceUUIDs = options.serviceUUIDs ?? [];\n this._gatt = new MockGATTServer(this, options.services ?? []);\n this._rssi = options.rssi ?? -60;\n this._remainingConnectFailures = options.failConnectAttempts ?? 0;\n this._writeLimits = {\n withResponse: options.writeLimits?.withResponse ?? null,\n withoutResponse: options.writeLimits?.withoutResponse ?? null,\n mtu: options.writeLimits?.mtu ?? null,\n };\n }\n\n /** Check if this device matches a scan filter */\n matchesFilter(filter: BluetoothLEScanFilter): boolean {\n if (filter.services) {\n const hasService = filter.services.some((uuid) =>\n this._serviceUUIDs.includes(String(uuid))\n );\n if (!hasService) return false;\n }\n if (filter.name && filter.name !== this.name) return false;\n if (filter.namePrefix && !this.name?.startsWith(filter.namePrefix))\n return false;\n return true;\n }\n\n /** Return a Web Bluetooth-compatible BluetoothDevice object */\n asBluetoothDevice(): BluetoothDevice {\n const self = this;\n // Build the device proxy first, then wire up gatt to avoid circular calls\n const proxy = {\n id: this.id,\n name: this.name ?? null,\n gatt: null as unknown as BluetoothRemoteGATTServer,\n watchAdvertisements: async (options?: { signal?: AbortSignal }) => {\n self._watchingAdvertisements = true;\n if (options?.signal) {\n if (options.signal.aborted) {\n self._watchingAdvertisements = false;\n return;\n }\n\n options.signal.addEventListener(\n 'abort',\n () => {\n self._watchingAdvertisements = false;\n },\n { once: true }\n );\n }\n },\n addEventListener: (type: string, listener: EventListener) => {\n self._addListener(type, listener);\n },\n removeEventListener: (type: string, listener: EventListener) => {\n self._removeListener(type, listener);\n },\n dispatchEvent: (_event: Event) => true,\n get watchingAdvertisements() {\n return self._watchingAdvertisements;\n },\n unwatchAdvertisements: async () => {\n self._watchingAdvertisements = false;\n },\n forget: async () => {},\n onadvertisementreceived: null,\n ongattserverdisconnected: null,\n oncharacteristicvaluechanged: null,\n onserviceadded: null,\n onservicechanged: null,\n onserviceremoved: null,\n } as object as BluetoothDevice;\n // Wire gatt with a back-reference to the proxy (no recursion)\n (proxy as { gatt: ExtendedGatt }).gatt = this._gatt.asBluetoothRemoteGATTServer(proxy) as ExtendedGatt;\n (proxy as { gatt: ExtendedGatt }).gatt.getMtu = async () => this._writeLimits.mtu;\n (proxy as { gatt: ExtendedGatt }).gatt.getWriteLimits = async () => ({ ...this._writeLimits });\n\n return proxy;\n }\n\n shouldFailConnect(): boolean {\n if (this._remainingConnectFailures <= 0) {\n return false;\n }\n this._remainingConnectFailures -= 1;\n return true;\n }\n\n /** Simulate a disconnect event */\n simulateDisconnect(): void {\n this._gatt.disconnect();\n this._emit('gattserverdisconnected', new Event('gattserverdisconnected'));\n }\n\n /** Get the mock GATT server for direct test control */\n get gatt(): MockGATTServer {\n return this._gatt;\n }\n\n get serviceUUIDs(): readonly string[] {\n return this._serviceUUIDs;\n }\n\n get rssi(): number {\n return this._rssi;\n }\n\n /** Emit an advertisement for requestLEScan()/watchAdvertisements() tests */\n emitAdvertisement(options: MockAdvertisementOptions = {}): void {\n if (this._advertisementSink) {\n this._advertisementSink(this, options);\n return;\n }\n\n this.dispatchAdvertisementEvent(options);\n }\n\n /** Update RSSI between advertisements */\n setRSSI(rssi: number): void {\n this._rssi = rssi;\n }\n\n /** Internal hook used by MockBluetooth to receive advertisement pumps */\n setAdvertisementSink(\n sink: ((device: MockBleDevice, options: MockAdvertisementOptions) => void) | undefined\n ): void {\n this._advertisementSink = sink;\n }\n\n /** Internal bridge for watchAdvertisements() listeners */\n dispatchAdvertisementEvent(options: MockAdvertisementOptions = {}): void {\n if (!this._watchingAdvertisements) {\n return;\n }\n\n this._emit(\n 'advertisementreceived',\n this.createAdvertisementEvent(this.asBluetoothDevice(), options)\n );\n }\n\n /** Build a Web Bluetooth-style advertisementreceived event */\n createAdvertisementEvent(\n deviceProxy: BluetoothDevice,\n options: MockAdvertisementOptions = {}\n ): Event {\n const event = new Event('advertisementreceived') as Event & {\n device?: BluetoothDevice;\n name?: string;\n uuids?: string[];\n rssi?: number;\n txPower?: number;\n manufacturerData?: Map<number, DataView>;\n serviceData?: Map<string, DataView>;\n };\n\n Object.defineProperties(event, {\n device: { value: deviceProxy, writable: false },\n name: { value: this.name, writable: false },\n uuids: {\n value: [...(options.uuids ?? this._serviceUUIDs)],\n writable: false,\n },\n rssi: { value: options.rssi ?? this._rssi, writable: false },\n txPower: { value: options.txPower, writable: false },\n manufacturerData: {\n value: options.manufacturerData ?? new Map<number, DataView>(),\n writable: false,\n },\n serviceData: {\n value: options.serviceData ?? new Map<string, DataView>(),\n writable: false,\n },\n });\n\n return event;\n }\n\n // --- Internal ---\n\n private _addListener(type: string, listener: EventListener): void {\n if (!this._listeners.has(type)) {\n this._listeners.set(type, new Set());\n }\n this._listeners.get(type)!.add(listener);\n }\n\n private _removeListener(type: string, listener: EventListener): void {\n this._listeners.get(type)?.delete(listener);\n }\n\n private _emit(type: string, event: Event): void {\n const listeners = this._listeners.get(type);\n if (listeners) {\n for (const listener of listeners) {\n listener(event);\n }\n }\n }\n}\n","/**\n * Mock Bluetooth API — drop-in replacement for navigator.bluetooth\n *\n * Provides a stateful mock that tracks devices, manages connections,\n * and can be configured for various test scenarios.\n */\n\nimport {\n MockBleDevice,\n type MockAdvertisementOptions,\n type MockDeviceOptions,\n} from './device';\nimport type {\n MockCharacteristicConfig,\n MockServiceConfig,\n} from './characteristics';\n\nexport interface MockBluetoothOptions {\n /** Whether Bluetooth is available (default: true) */\n available?: boolean;\n /** Pre-registered devices that will appear in scans */\n devices?: MockDeviceOptions[];\n}\n\nconst unsupportedExtensionApi = (): Promise<never> =>\n Promise.reject(new DOMException('Beacio extension API not implemented in MockBluetooth', 'NotSupportedError'));\n\nconst noop = (): void => {};\n\nexport class MockBluetooth {\n private _available: boolean;\n private _devices: Map<string, MockBleDevice> = new Map();\n private _listeners: Map<string, Set<EventListener>> = new Map();\n private _scanActive = false;\n private _installedNavigatorBluetooth?: unknown;\n private _lastScanOptions?: BluetoothLEScanOptions;\n\n readonly backgroundSync = {\n requestPermission: unsupportedExtensionApi,\n requestBackgroundConnection: unsupportedExtensionApi,\n registerCharacteristicNotifications: unsupportedExtensionApi,\n registerBeaconScanning: unsupportedExtensionApi,\n getRegistrations: unsupportedExtensionApi,\n unregister: unsupportedExtensionApi,\n update: unsupportedExtensionApi,\n connect: unsupportedExtensionApi,\n subscribe: unsupportedExtensionApi,\n scan: unsupportedExtensionApi,\n list: unsupportedExtensionApi,\n destroy: noop,\n };\n\n readonly peripheral = {\n advertising: false,\n advertise: unsupportedExtensionApi,\n stopAdvertising: unsupportedExtensionApi,\n send: unsupportedExtensionApi,\n destroy: noop,\n addEventListener: noop,\n removeEventListener: noop,\n onwriterequest: null,\n onsubscriptionchange: null,\n onconnectionstatechange: null,\n onadvertisingstatechange: null,\n };\n\n constructor(options: MockBluetoothOptions = {}) {\n this._available = options.available ?? true;\n if (options.devices) {\n for (const opts of options.devices) {\n const device = new MockBleDevice(opts);\n device.setAdvertisementSink(this._handleAdvertisement);\n this._devices.set(device.id, device);\n }\n }\n }\n\n // --- Public API (matches navigator.bluetooth) ---\n\n async getAvailability(): Promise<boolean> {\n return this._available;\n }\n\n async requestDevice(\n options?: RequestDeviceOptions\n ): Promise<BluetoothDevice> {\n if (!this._available) {\n throw new DOMException(\n 'Bluetooth adapter not available',\n 'NotFoundError'\n );\n }\n\n const matching = this._findMatchingDevices(options as Record<string, unknown>);\n if (matching.length === 0) {\n throw new DOMException(\n 'No devices found matching the filter criteria',\n 'NotFoundError'\n );\n }\n\n // Return the first matching device (simulates user picking)\n return matching[0].asBluetoothDevice();\n }\n\n async getDevices(): Promise<BluetoothDevice[]> {\n return Array.from(this._devices.values()).map((d) =>\n d.asBluetoothDevice()\n );\n }\n\n async requestLEScan(\n options?: BluetoothLEScanOptions\n ): Promise<BluetoothLEScan> {\n if (this._scanActive) {\n throw new DOMException('Scan already in progress', 'InvalidStateError');\n }\n this._scanActive = true;\n this._lastScanOptions = options;\n const scan = {\n active: true,\n keepRepeatedDevices: options?.keepRepeatedDevices ?? false,\n acceptAllAdvertisements: options?.acceptAllAdvertisements ?? false,\n stop: () => {\n this._scanActive = false;\n this._lastScanOptions = undefined;\n scan.active = false;\n },\n } as BluetoothLEScan & { active: boolean };\n return scan as BluetoothLEScan;\n }\n\n addEventListener(type: string, listener: EventListener): void {\n if (!this._listeners.has(type)) {\n this._listeners.set(type, new Set());\n }\n this._listeners.get(type)!.add(listener);\n }\n\n removeEventListener(type: string, listener: EventListener): void {\n this._listeners.get(type)?.delete(listener);\n }\n\n // --- Test helpers ---\n\n /** Add a device to the mock registry */\n addDevice(options: MockDeviceOptions): MockBleDevice {\n const device = new MockBleDevice(options);\n device.setAdvertisementSink(this._handleAdvertisement);\n this._devices.set(device.id, device);\n return device;\n }\n\n /** Remove a device from the registry */\n removeDevice(id: string): void {\n const device = this._devices.get(id);\n if (device) {\n device.setAdvertisementSink(undefined);\n device.simulateDisconnect();\n this._devices.delete(id);\n }\n }\n\n /** Get a mock device by ID for test assertions */\n getDevice(id: string): MockBleDevice | undefined {\n return this._devices.get(id);\n }\n\n /** Set Bluetooth availability */\n setAvailable(available: boolean): void {\n this._available = available;\n }\n\n /** Install this mock instance onto navigator.bluetooth */\n install(): this {\n if (typeof globalThis.navigator === 'undefined') {\n return this;\n }\n\n this._installedNavigatorBluetooth = (globalThis.navigator as Navigator & {\n bluetooth?: unknown;\n }).bluetooth;\n\n Object.defineProperty(globalThis.navigator, 'bluetooth', {\n value: this,\n writable: true,\n configurable: true,\n });\n return this;\n }\n\n /** Restore the previous navigator.bluetooth value */\n uninstall(): void {\n if (typeof globalThis.navigator === 'undefined') {\n return;\n }\n\n Object.defineProperty(globalThis.navigator, 'bluetooth', {\n value: this._installedNavigatorBluetooth,\n writable: true,\n configurable: true,\n });\n this._installedNavigatorBluetooth = undefined;\n }\n\n /** Emit a Bluetooth-level advertisementreceived event */\n emitAdvertisement(\n deviceId: string,\n options: MockAdvertisementOptions = {}\n ): void {\n const device = this._devices.get(deviceId);\n if (!device) {\n throw new Error(`Unknown mock device: ${deviceId}`);\n }\n\n this._handleAdvertisement(device, options);\n }\n\n /** Reset all state */\n reset(): void {\n for (const device of this._devices.values()) {\n device.setAdvertisementSink(undefined);\n device.simulateDisconnect();\n }\n this._devices.clear();\n this._listeners.clear();\n this._scanActive = false;\n this._lastScanOptions = undefined;\n this._available = true;\n }\n\n // --- Internal ---\n\n private _findMatchingDevices(\n options?: Record<string, unknown>\n ): MockBleDevice[] {\n if (!options || (options as { acceptAllDevices?: boolean }).acceptAllDevices) {\n return Array.from(this._devices.values());\n }\n\n const filters = ((options as { filters?: BluetoothLEScanFilter[] }).filters) ?? [];\n return Array.from(this._devices.values()).filter((device) =>\n filters.some((filter: BluetoothLEScanFilter) => device.matchesFilter(filter))\n );\n }\n\n private readonly _handleAdvertisement = (\n device: MockBleDevice,\n options: MockAdvertisementOptions\n ): void => {\n device.dispatchAdvertisementEvent(options);\n\n if (!this._scanActive) {\n return;\n }\n\n if (!this._matchesScan(device)) {\n return;\n }\n\n const event = device.createAdvertisementEvent(device.asBluetoothDevice(), options);\n const listeners = this._listeners.get('advertisementreceived');\n if (!listeners) {\n return;\n }\n\n for (const listener of listeners) {\n listener(event);\n }\n };\n\n private _matchesScan(device: MockBleDevice): boolean {\n const options = this._lastScanOptions;\n if (!options) {\n return true;\n }\n\n if (options.acceptAllAdvertisements) {\n return true;\n }\n\n const filters = options.filters ?? [];\n if (filters.length === 0) {\n return true;\n }\n\n return filters.some((filter) => device.matchesFilter(filter));\n }\n}\n\n/**\n * Install mock Bluetooth API on the global navigator object.\n * Returns a MockBluetooth instance for test control.\n */\nexport function createMockBluetooth(\n options?: MockBluetoothOptions\n): MockBluetooth {\n return new MockBluetooth(options);\n}\n\n/**\n * Install mock Bluetooth on navigator.bluetooth.\n * Returns the mock instance for control.\n */\nexport function installMockBluetooth(\n options?: MockBluetoothOptions\n): MockBluetooth {\n const mock = createMockBluetooth(options);\n return mock.install();\n}\n\nexport type {\n MockServiceConfig,\n MockCharacteristicConfig,\n MockAdvertisementOptions,\n};\n","/**\n * @beacio/core/testing — Mock Bluetooth API for testing BLE web apps\n *\n * Folded in from the former @beacio/testing package (B10-t): the hardware-free\n * mock/virtual Web Bluetooth surface that powers the \"playground\" first-run\n * (a dev sees `requestDevice()` succeed in Chrome before ever touching iOS).\n * Reached via the `@beacio/core/testing` subpath export.\n *\n * Provides stateful mocks for the Web Bluetooth API:\n * - MockBluetooth: drop-in replacement for navigator.bluetooth\n * - MockBleDevice: stateful device with GATT services/characteristics\n * - MockCharacteristic: value simulation and notification pump\n * - Advertisement simulation for requestLEScan/watchAdvertisements tests\n *\n * Usage:\n * import { createMockBluetooth, installMockBluetooth } from '@beacio/core/testing'\n *\n * // Option A: Create and install on navigator.bluetooth\n * const mock = installMockBluetooth({ available: true })\n *\n * // Option B: Create without installing (for custom setups)\n * const mock = createMockBluetooth()\n *\n * // Add test devices\n * const device = mock.addDevice({\n * name: 'HR Sensor',\n * failConnectAttempts: 1,\n * writeLimits: { withResponse: 20, mtu: 23 },\n * serviceUUIDs: ['0000180d-0000-1000-8000-00805f9b34fb'],\n * services: [{\n * uuid: '0000180d-0000-1000-8000-00805f9b34fb',\n * characteristics: [{\n * uuid: '00002a37-0000-1000-8000-00805f9b34fb',\n * properties: { notify: true, read: true },\n * value: new Uint8Array([0x00, 72]),\n * }],\n * }],\n * })\n *\n * // Pump notifications in tests\n * const char = device.gatt.getService('0000180d-...')?.getChar('00002a37-...')\n * char?.emitNotification(new Uint8Array([0x00, 80]))\n *\n * // Emit advertisements for scan/beacon-style tests\n * device.emitAdvertisement({ rssi: -42 })\n *\n * // Reset between tests\n * mock.reset()\n */\n\nexport {\n MockBluetooth,\n createMockBluetooth,\n installMockBluetooth,\n type MockBluetoothOptions,\n type MockAdvertisementOptions,\n} from './mocks/bluetooth';\n\nexport {\n MockBleDevice,\n type MockDeviceOptions,\n} from './mocks/device';\n\nexport {\n MockGATTServer,\n MockService,\n MockCharacteristic,\n MockDescriptor,\n type MockServiceConfig,\n type MockCharacteristicConfig,\n type MockDescriptorConfig,\n} from './mocks/characteristics';\n\n/** Common Bluetooth SIG UUIDs for test convenience */\nexport const BLE_UUIDS = {\n services: {\n HEART_RATE: '0000180d-0000-1000-8000-00805f9b34fb',\n BATTERY: '0000180f-0000-1000-8000-00805f9b34fb',\n DEVICE_INFO: '0000180a-0000-1000-8000-00805f9b34fb',\n ENVIRONMENTAL_SENSING: '0000181a-0000-1000-8000-00805f9b34fb',\n },\n characteristics: {\n HEART_RATE_MEASUREMENT: '00002a37-0000-1000-8000-00805f9b34fb',\n BODY_SENSOR_LOCATION: '00002a38-0000-1000-8000-00805f9b34fb',\n BATTERY_LEVEL: '00002a19-0000-1000-8000-00805f9b34fb',\n MANUFACTURER_NAME: '00002a29-0000-1000-8000-00805f9b34fb',\n MODEL_NUMBER: '00002a24-0000-1000-8000-00805f9b34fb',\n TEMPERATURE: '00002a6e-0000-1000-8000-00805f9b34fb',\n },\n descriptors: {\n /** Client Characteristic Configuration Descriptor */\n CCCD: '00002902-0000-1000-8000-00805f9b34fb',\n /** Characteristic User Description */\n USER_DESCRIPTION: '00002901-0000-1000-8000-00805f9b34fb',\n /** Characteristic Presentation Format */\n PRESENTATION_FORMAT: '00002904-0000-1000-8000-00805f9b34fb',\n },\n} as const;\n\n/** Pre-configured device factories for common test scenarios */\nexport const devices = {\n /** Heart rate sensor with notification support */\n heartRate(name = 'Mock HR Sensor'): import('./mocks/device').MockDeviceOptions {\n return {\n name,\n serviceUUIDs: [BLE_UUIDS.services.HEART_RATE],\n services: [\n {\n uuid: BLE_UUIDS.services.HEART_RATE,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.HEART_RATE_MEASUREMENT,\n properties: { read: true, notify: true },\n value: new Uint8Array([0x00, 72]), // 72 bpm\n },\n {\n uuid: BLE_UUIDS.characteristics.BODY_SENSOR_LOCATION,\n properties: { read: true },\n value: new Uint8Array([1]), // Chest\n },\n ],\n },\n ],\n };\n },\n\n /** Battery service device */\n battery(name = 'Mock Battery Device'): import('./mocks/device').MockDeviceOptions {\n return {\n name,\n serviceUUIDs: [BLE_UUIDS.services.BATTERY],\n services: [\n {\n uuid: BLE_UUIDS.services.BATTERY,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.BATTERY_LEVEL,\n properties: { read: true, notify: true },\n value: new Uint8Array([85]), // 85%\n },\n ],\n },\n ],\n };\n },\n\n /** Device with multiple services */\n full(name = 'Mock Full Device'): import('./mocks/device').MockDeviceOptions {\n return {\n name,\n serviceUUIDs: [\n BLE_UUIDS.services.HEART_RATE,\n BLE_UUIDS.services.BATTERY,\n BLE_UUIDS.services.DEVICE_INFO,\n ],\n services: [\n {\n uuid: BLE_UUIDS.services.HEART_RATE,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.HEART_RATE_MEASUREMENT,\n properties: { read: true, notify: true },\n value: new Uint8Array([0x00, 72]),\n },\n ],\n },\n {\n uuid: BLE_UUIDS.services.BATTERY,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.BATTERY_LEVEL,\n properties: { read: true, notify: true },\n value: new Uint8Array([100]),\n },\n ],\n },\n {\n uuid: BLE_UUIDS.services.DEVICE_INFO,\n characteristics: [\n {\n uuid: BLE_UUIDS.characteristics.MANUFACTURER_NAME,\n properties: { read: true },\n value: Uint8Array.from(Array.from('Beacio Test Corp').map(c => c.charCodeAt(0))),\n },\n {\n uuid: BLE_UUIDS.characteristics.MODEL_NUMBER,\n properties: { read: true },\n value: Uint8Array.from(Array.from('WBT-001').map(c => c.charCodeAt(0))),\n },\n ],\n },\n ],\n };\n },\n};\n"]}
export{i as BLE_UUIDS,e as MockBleDevice,f as MockBluetooth,c as MockCharacteristic,d as MockDescriptor,a as MockGATTServer,b as MockService,g as createMockBluetooth,j as devices,h as installMockBluetooth}from'../chunk-67S2RHE2.mjs';//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
declare const percentageBrand: unique symbol;
/**
* A validated integer percentage in [0, 100].
*
* Nominal/branded: a raw `number` can never be passed where a percentage is
* required — the brand is attachable only by {@link percent} (strict) or
* {@link clampPercent} (lenient), both of which guarantee `0 <= value <= 100`
* and that the value is an integer. Typing a decoded battery level as
* `Percentage` makes an out-of-range value (e.g. a stray 356% from an
* unmasked uint16) unrepresentable at the type level — not just by a runtime
* check at the call site.
*
* `units.ts` is the home for unit-of-measure invariants (percentage today;
* future Celsius/decibel brands can co-locate here), keeping them distinct
* from write/chunk concerns in {@link ./write-chunker}.
*/
type Percentage = number & {
readonly [percentageBrand]: true;
};
/**
* Strict smart-constructor for {@link Percentage}. Throws `INVALID_PARAMETER`
* unless `n` is an integer in `0..100`. Use when the caller supplied an
* explicit value that must be rejected (not silently corrected) if invalid.
*/
declare function percent(n: number): Percentage;
/**
* Lenient smart-constructor for {@link Percentage}. Saturates any
* number/`NaN`/non-integer input into `0..100` (`NaN` -> `0`). Never throws.
*
* Takes a plain `number` (not `number | null | undefined` like
* {@link clampChunkSize}) because its inputs are concrete decoded bytes, never
* a nullable platform-reported limit — honoring the repo's "required fields,
* no optional arguments" convention. `Math.trunc` (not a saturating-only
* clamp) guards against a fractional sneaking through from any non-byte caller;
* for an integer byte it is a no-op.
*/
declare function clampPercent(n: number): Percentage;
export { type Percentage as P, clampPercent as c, percent as p };
declare const percentageBrand: unique symbol;
/**
* A validated integer percentage in [0, 100].
*
* Nominal/branded: a raw `number` can never be passed where a percentage is
* required — the brand is attachable only by {@link percent} (strict) or
* {@link clampPercent} (lenient), both of which guarantee `0 <= value <= 100`
* and that the value is an integer. Typing a decoded battery level as
* `Percentage` makes an out-of-range value (e.g. a stray 356% from an
* unmasked uint16) unrepresentable at the type level — not just by a runtime
* check at the call site.
*
* `units.ts` is the home for unit-of-measure invariants (percentage today;
* future Celsius/decibel brands can co-locate here), keeping them distinct
* from write/chunk concerns in {@link ./write-chunker}.
*/
type Percentage = number & {
readonly [percentageBrand]: true;
};
/**
* Strict smart-constructor for {@link Percentage}. Throws `INVALID_PARAMETER`
* unless `n` is an integer in `0..100`. Use when the caller supplied an
* explicit value that must be rejected (not silently corrected) if invalid.
*/
declare function percent(n: number): Percentage;
/**
* Lenient smart-constructor for {@link Percentage}. Saturates any
* number/`NaN`/non-integer input into `0..100` (`NaN` -> `0`). Never throws.
*
* Takes a plain `number` (not `number | null | undefined` like
* {@link clampChunkSize}) because its inputs are concrete decoded bytes, never
* a nullable platform-reported limit — honoring the repo's "required fields,
* no optional arguments" convention. `Math.trunc` (not a saturating-only
* clamp) guards against a fractional sneaking through from any non-byte caller;
* for an integer byte it is a no-op.
*/
declare function clampPercent(n: number): Percentage;
export { type Percentage as P, clampPercent as c, percent as p };
+16
-16
# @beacio/core — Agent Instructions
## What this package does
Platform-agnostic Web Bluetooth SDK. Provides `WebBLE` (entry point),
`WebBLEDevice` (connected device wrapper), and `WebBLEError` (typed errors).
Platform-agnostic Web Bluetooth SDK. Provides `beacio` (entry point),
`BeacioDevice` (connected device wrapper), and `BeacioError` (typed errors).
Works on any browser with Web Bluetooth support.

@@ -16,5 +16,5 @@

```typescript
import { WebBLE } from '@beacio/core';
import { beacio } from '@beacio/core';
const ble = new WebBLE();
const ble = new Beacio();
const device = await ble.requestDevice({

@@ -28,4 +28,4 @@ filters: [{ services: ['heart_rate'] }]

## Key API surface
- `new WebBLE(options?)` — creates SDK instance, detects platform
- `ble.requestDevice(options?)` — opens device picker, returns `WebBLEDevice`
- `new Beacio(options?)` — creates SDK instance, detects platform
- `ble.requestDevice(options?)` — opens device picker, returns `BeacioDevice`
- `ble.getAvailability()` — checks if Bluetooth is available

@@ -40,3 +40,3 @@ - `device.connect()` / `device.disconnect()` — GATT connection lifecycle

- `resolveUUID(name)` — converts human-readable names to full UUIDs
- `WebBLEError` — typed error with `.code` (`UNSUPPORTED`, `NOT_CONNECTED`, `DEVICE_NOT_FOUND`, `USER_CANCELLED`, `GATT_ERROR`, `TIMEOUT`)
- `BeacioError` — typed error with `.code` (`BLUETOOTH_UNAVAILABLE`, `DEVICE_DISCONNECTED`, `DEVICE_NOT_FOUND`, `USER_CANCELLED`, `GATT_OPERATION_FAILED`, `TIMEOUT`) and a human/agent-readable `.suggestion`

@@ -46,10 +46,10 @@ ## DO

- Call `device.connect()` before any read/write/subscribe
- Check `WebBLEError.code` for programmatic error handling
- Check `BeacioError.code` for programmatic error handling
- Store the unsubscribe function returned by `device.subscribe()` and call it on cleanup
- Use `@beacio/profiles` when a built-in profile exists for your device type
- Use `@beacio/core/profiles` when a built-in profile exists for your device type
## DO NOT
- Do not write raw GATT parsing code when a profile exists in `@beacio/profiles`
- Do not catch errors silently — surface `WebBLEError.code` and `.hint` to the user
- Do not call `device.read()` / `device.write()` before `device.connect()` — throws `NOT_CONNECTED`
- Do not write raw GATT parsing code when a profile exists in `@beacio/core/profiles`
- Do not catch errors silently — surface `BeacioError.code` and `.suggestion` to the user
- Do not call `device.read()` / `device.write()` before `device.connect()` — throws `DEVICE_DISCONNECTED`
- Do not access `device.raw` unless you need the underlying `BluetoothDevice` for an unsupported operation

@@ -105,3 +105,3 @@

} catch (e) {
if (e instanceof WebBLEError) {
if (e instanceof BeacioError) {
console.error(e.code, e.suggestion) // machine-readable + actionable

@@ -149,6 +149,6 @@ }

} catch (e) {
if (e instanceof WebBLEError) {
if (e instanceof BeacioError) {
switch (e.code) {
case 'NOT_CONNECTED': /* reconnect */ break;
case 'GATT_ERROR': /* retry or surface */ break;
case 'DEVICE_DISCONNECTED': /* reconnect */ break;
case 'GATT_OPERATION_FAILED': /* retry or surface */ break;
}

@@ -155,0 +155,0 @@ }

@@ -5,2 +5,19 @@ # Changelog

## 1.2.0 — 2026-07-27
- Version aligned with App Store / Safari extension **1.2.0**.
- Stability freeze surfaces (internal slots, error matrix, batch GATT wire protocol, experimental Storz path).
- CDN / install pins move to `@beacio/core@1.2.0` (see `onboarding-manifest.json`). Fielded `@beacio/core@1.0.0` CDN pins remain supported via the extension skew cell (U-SKEW-01).
## 1.0.0 — 2026-06-21
- First externally-consumed stable release (Storz & Bickel is the first real consumer). The
"breaking changes are free" zero-consumers assumption is **retired** for the consumed
`navigator.bluetooth` polyfill behaviour and `optionalServices` surfaces: from 1.0.0 these
follow semver — additive-only across minor/patch, with a behavioural breaking change only in a
major release. See the backward-compatibility + pinned-version contract in
[`outreach/storz-bickel/07-support-scope.md`](../../outreach/storz-bickel/07-support-scope.md)
(§7), which also defines the pre-publish change-notification path. Consumers pin the exact
immutable `@beacio/core@1.2.0/dist/auto.mjs` rather than a floating tag.
## 2.0.0-beta.2 — 2026-06-03

@@ -7,0 +24,0 @@

@@ -0,2 +1,23 @@

/**
* @beacio/core/auto — Transparent Web Bluetooth polyfill.
*
* Usage: import '@beacio/core/auto';
*
* - Chrome/Edge (native bluetooth): no-op
* - Safari iOS (with extension): ensures navigator.bluetooth maps to extension API
* - Safari iOS (without extension): lazy-loads install prompt on first requestDevice()
* - Unsupported platforms: no-op (graceful degradation)
*/
/**
* Install the transparent W3C `navigator.bluetooth` polyfill for the current
* platform (no-op on native/unsupported per the branches below). Runs once at
* module load via the bottom-of-file call for `import '@beacio/core/auto'`
* consumers; also EXPORTED so the consolidated `browser-auto` entry can invoke
* it explicitly (a bare side-effect import is tree-shakeable under the package's
* `sideEffects` allowlist). Idempotent: the module-level guard makes a second
* call a no-op so the two entry points never double-register the permissions
* shim or the extension-ready listener.
*/
declare function applyPolyfill(): void;
export { }
export { applyPolyfill };

@@ -0,2 +1,23 @@

/**
* @beacio/core/auto — Transparent Web Bluetooth polyfill.
*
* Usage: import '@beacio/core/auto';
*
* - Chrome/Edge (native bluetooth): no-op
* - Safari iOS (with extension): ensures navigator.bluetooth maps to extension API
* - Safari iOS (without extension): lazy-loads install prompt on first requestDevice()
* - Unsupported platforms: no-op (graceful degradation)
*/
/**
* Install the transparent W3C `navigator.bluetooth` polyfill for the current
* platform (no-op on native/unsupported per the branches below). Runs once at
* module load via the bottom-of-file call for `import '@beacio/core/auto'`
* consumers; also EXPORTED so the consolidated `browser-auto` entry can invoke
* it explicitly (a bare side-effect import is tree-shakeable under the package's
* `sideEffects` allowlist). Idempotent: the module-level guard makes a second
* call a no-op so the two entry points never double-register the permissions
* shim or the extension-ready listener.
*/
declare function applyPolyfill(): void;
export { }
export { applyPolyfill };

@@ -1,78 +0,178 @@

'use strict';var Z=Object.defineProperty;var c=(e,n)=>()=>(e&&(n=e(e=0)),n);var g=(e,n)=>{for(var t in n)Z(e,t,{get:n[t],enumerable:true});};function ae(){try{let e=localStorage.getItem(k);return e?Date.now()<parseInt(e,10):!1}catch{return false}}function h(e){try{localStorage.setItem(k,String(Date.now()+e*864e5));}catch{}}function se(){let e=new URL(window.location.href),n=new URL(`https://${oe}/return`);n.searchParams.set("url",e.toString());try{localStorage.setItem(ie,JSON.stringify({url:e.toString(),returnLink:n.toString(),timestamp:Date.now()})),navigator.storage?.persist?.();}catch{}try{navigator.clipboard?.writeText(n.toString());}catch{}}function D(e){return e.startOnboardingUrl??e.appStoreUrl??re}function C(e,n){se();let t=new URL(e,window.location.href),r=t.hostname==="apps.apple.com";n&&r&&!t.searchParams.has("ct")&&(t.searchParams.set("ct",n),t.searchParams.set("mt","8")),window.location.href=t.toString();}function _(e){let n=document.createElement("div");return n.textContent=e,n.innerHTML}function ce(e){let{operatorName:n=document.title||window.location.hostname,buttonText:t="Start Setup",apiKey:r,dismissDays:i=14}=e,a=D(e),o=document.createElement("div");return o.id="ioswebble-banner",o.innerHTML=`
'use strict';var dt=Object.defineProperty;var g=(e,t)=>()=>(e&&(t=e(e=0)),t);var j=(e,t)=>{for(var r in t)dt(e,r,{get:t[r],enumerable:true});};var Se={};j(Se,{CDN_STUB_MARKER:()=>O,detectPlatform:()=>ee,getBluetoothAPI:()=>z});function ee(){if(typeof navigator>"u")return "unsupported";let e=navigator;return e.beacio?.__beacio===true?"safari-extension":e.bluetooth&&!e.bluetooth[O]?"native":"unsupported"}function z(){if(typeof navigator>"u")return null;let e=navigator;return e.beacio?.__beacio===true?e.beacio:e.bluetooth&&!e.bluetooth[O]?e.bluetooth:null}var O,te=g(()=>{O="__beacioCDNStub";});var l,R=g(()=>{l={STATE_CHANGE:"beacio:statechange",READY:"beacio:ready",INSTALLED_INACTIVE:"beacio:installedinactive",NOT_INSTALLED:"beacio:notinstalled",EXTENSION_READY:"beacio:extension:ready",EXTENSION_PING:"beacio:extension:ping",EXTENSION_PONG:"beacio:extension:pong",EXTENSION_ACTIVATE_REQUEST:"beacio:extension:activate-request",EXTENSION_ACTIVATE_RESULT:"beacio:extension:activate-result",EXTENSION_INSTALLED:"beacio:extension:installed",EXTENSION_STATUS_CHANGE:"beacio:extension:statuschange"};});function ft(){return typeof window<"u"&&window.__beacio?.status==="installed"}function gt(){if(typeof navigator>"u")return false;let e=navigator;return !!(e.beacio&&e.beacio.__beacio)}function bt(){return typeof document<"u"&&document.documentElement.dataset.beacioInstalled==="true"}function mt(){return typeof document<"u"&&document.documentElement.dataset.beacioExtension==="true"}function E(){return gt()||mt()?"active":ft()||bt()?"installed-inactive":"not-installed"}function $(){return E()==="active"}function ke(e=3e3){let t=E();return t==="active"||typeof window>"u"?Promise.resolve(t):new Promise(r=>{let n=false,i=c=>{n||(n=true,window.removeEventListener(Re,o),clearTimeout(a),r(c));},o=()=>i("active");window.addEventListener(Re,o);let a=setTimeout(()=>i(E()),e);})}function H(){try{let e=localStorage.getItem(Be);return e?Date.now()<parseInt(e,10):!1}catch{return false}}function B(e=re){try{localStorage.setItem(Be,String(Date.now()+e*864e5));}catch{}}function w(){B(ie);}function oe(){let e=typeof window<"u"?window.location.href:"https://beacio.com",t=new URL(e),r=new URL(`https://${pt}/return`);return r.searchParams.set("url",t.toString()),r.toString()}function L(){if(typeof window>"u")return;let e=new URL(window.location.href),t=oe();try{localStorage.setItem(Le,JSON.stringify({url:e.toString(),returnLink:t,timestamp:Date.now()})),navigator.storage?.persist?.();}catch{}try{navigator.clipboard?.writeText(t);}catch{}}function k(){let e=typeof window<"u"?window.location.href:"";try{let t=localStorage.getItem(Le);if(t){let r=JSON.parse(t),n=r.url||e;return {url:n,returnLink:r.returnLink||n}}}catch{}return {url:e,returnLink:e}}var Re,Be,Le,pt,T,re,ie,U=g(()=>{R();Re=l.EXTENSION_READY,Be="beacio_dismiss_until",Le="beacio_return",pt="link.beacio.com",T="https://apps.apple.com/app/id6761301368";re=14,ie=1;});var ce={};j(ce,{getExtensionInstallState:()=>V,isExtensionInstalled:()=>se,isIOSSafari:()=>ae});function ae(){if(typeof navigator>"u")return false;let e=navigator.userAgent,t=/iPad|iPhone|iPod/.test(e)||navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1,r=/^((?!chrome|android|crios|fxios).)*safari/i.test(e);return t&&r}async function V(){try{let{detectPlatform:e}=await Promise.resolve().then(()=>(te(),Se));if(e()==="safari-extension")return "active"}catch{}return new Promise(e=>{let t=E();if(t!=="not-installed"){e(t);return}let r=0,n=setInterval(()=>{r++;let i=E();i!=="not-installed"&&(clearInterval(n),e(i)),r>20&&(clearInterval(n),e("not-installed"));},100);})}async function se(){return await V()!=="not-installed"}var W=g(()=>{U();});var K,le=g(()=>{K="https://beacio.com/setup";});function Pe(e){return !e||typeof e!="string"?"":e.split("-",1)[0].trim().toLowerCase()}function _t(){if(!(typeof navigator>"u"))return navigator.language}function Me(e,t){if(t==null)return e;if(Array.isArray(e)||typeof e!="object"||e===null)return t;let r={...e};for(let n of Object.keys(t)){let i=t[n];i!==void 0&&(r[n]=Me(e[n],i));}return r}function A(e={}){let t=Pe(e.lang),r=t&&Ue[t]||Ue[Pe(_t())]||b;return e.strings?Me(r,e.strings):r}var b,de,Ue,q=g(()=>{b={buttonText:"Start Setup",dismiss:"Not now",dontShowAgain:"Don't show again",states:{"not-installed":{title:"Set Up Bluetooth in Safari",body:"Follow the steps below to enable Bluetooth and return to {operator}."},"installed-inactive":{title:"Enable beacio in Safari",body:"beacio is installed but the Safari extension is off. Open Settings \u2192 Apps \u2192 Safari \u2192 Extensions \u2192 beacio and turn on Allow Extension, then return here."},denied:{title:"Allow beacio on this site",body:"beacio is enabled but not yet allowed here. Tap the aA button in the address bar \u2192 Manage Extensions \u2192 beacio \u2192 Allow Every Website, then reload this page."},"private-browsing":{title:"Private Browsing blocks extensions",body:"Private Browsing disables Safari extensions, so beacio cannot run here \u2014 even if it is installed. Open this page in a normal tab to connect your device."}},steps:[{label:"Install beacio",why:"A free one-time companion app from the App Store."},{label:"Open the app once",why:"This registers the Safari extension with iOS."},{label:"Enable in Safari Settings",why:"Settings \u2192 Apps \u2192 Safari \u2192 Extensions \u2192 beacio \u2192 turn on Allow Extension."},{label:"Allow website access",why:"On the site, tap the aA button in the address bar \u2192 Manage Extensions \u2192 beacio \u2192 Allow Every Website."},{label:"Allow Bluetooth on first scan",why:"The first time you connect, Safari will ask to allow this site \u2014 tap Allow."},{label:"Return and reload",why:"Come back to this page, reload, and tap Connect."}],returnCta:"Return to {operator}",clipboardHint:"Link also copied \u2014 paste it into Safari if this button does not reopen {operator}.",reload:"Reload page to re-check",howSummary:"How does setup work?",howBody:"beacio uses a one-time iPhone app to enable the Safari extension. After enabling it and allowing access on this site (aA button \u2192 Manage Extensions \u2192 Allow Every Website), Bluetooth works in Safari.",howLink:"See the full setup guide",privacySummary:"Privacy: No data collected",privacyBody:"beacio processes all Bluetooth data locally on your device. No browsing data, device data, or personal information is ever collected or transmitted.",stillStuck:"Still stuck? Open the setup guide",barTitle:"Enable Bluetooth",barText:"Install Beacio, open the app, enable the Safari extension, then return here.",readyToast:"beacio is ready \u2014 tap Connect to pair your device with {operator}.",error:{dismiss:"Dismiss",retry:"Try again",titles:{INVALID_PARAMETER:"Something went wrong",BLUETOOTH_UNAVAILABLE:"Bluetooth is unavailable",EXTENSION_NOT_INSTALLED:"Finish Bluetooth setup",PERMISSION_DENIED:"Allow Bluetooth to continue",DEVICE_NOT_FOUND:"No device found",DEVICE_DISCONNECTED:"Device disconnected",CONNECTION_TIMEOUT:"Connection timed out",SERVICE_NOT_FOUND:"Device feature unavailable",CHARACTERISTIC_NOT_FOUND:"Device feature unavailable",CHARACTERISTIC_NOT_READABLE:"Cannot read from device",CHARACTERISTIC_NOT_WRITABLE:"Cannot send to device",CHARACTERISTIC_NOT_NOTIFIABLE:"Live updates unavailable",GATT_OPERATION_FAILED:"Connection interrupted",SCAN_ALREADY_IN_PROGRESS:"Already searching",CONNECTION_LIMIT_REACHED:"Too many devices connected",USER_CANCELLED:"Connection cancelled",TIMEOUT:"Operation timed out",WRITE_INCOMPLETE:"Send incomplete"},messages:{INVALID_PARAMETER:"The request could not be completed. Please reload the page and try again.",BLUETOOTH_UNAVAILABLE:"Turn Bluetooth on, then try again.",EXTENSION_NOT_INSTALLED:"Bluetooth is not enabled for this site yet. Finish setup, then try connecting again.",PERMISSION_DENIED:"Bluetooth access was not granted. Tap Connect yourself (Bluetooth needs a tap), then allow access when asked.",DEVICE_NOT_FOUND:"No matching device was found. Switch your device on, keep it close, then try again.",DEVICE_DISCONNECTED:"The connection to your device was lost. Reconnect to continue.",CONNECTION_TIMEOUT:"Your device did not respond in time. Keep it close and powered on, then try again.",SERVICE_NOT_FOUND:"A required feature was not found on this device. Check that it is the right device and try again.",CHARACTERISTIC_NOT_FOUND:"A required feature was not found on this device. Check that it is the right device and try again.",CHARACTERISTIC_NOT_READABLE:"This value cannot be read from your device. No action is needed for this control.",CHARACTERISTIC_NOT_WRITABLE:"This value cannot be sent to your device. No action is needed for this control.",CHARACTERISTIC_NOT_NOTIFIABLE:"This value does not support live updates on your device.",GATT_OPERATION_FAILED:"Something interrupted the connection. Switch your device off and on, then try again.",SCAN_ALREADY_IN_PROGRESS:"A device search is already running. Wait a moment, then try again.",CONNECTION_LIMIT_REACHED:"Disconnect another device before connecting a new one.",USER_CANCELLED:"No device was selected. Tap Connect to try again whenever you are ready.",TIMEOUT:"That took too long. Check your device is close and powered on, then try again.",WRITE_INCOMPLETE:"Only part of the data reached your device. Try again to resend it."},generic:{title:"Something went wrong",body:"Something interrupted the connection. Please try again."}}},de={buttonText:"Einrichtung starten",dismiss:"Jetzt nicht",dontShowAgain:"Nicht mehr anzeigen",states:{"not-installed":{title:"Bluetooth in Safari einrichten",body:"Folge den Schritten unten, um Bluetooth zu aktivieren und zu {operator} zur\xFCckzukehren."},"installed-inactive":{title:"beacio in Safari aktivieren",body:"beacio ist installiert, aber die Safari-Erweiterung ist deaktiviert. \xD6ffne Einstellungen \u2192 Apps \u2192 Safari \u2192 Erweiterungen \u2192 beacio und aktiviere \u201EErweiterung erlauben\u201C, kehre dann hierher zur\xFCck."},denied:{title:"beacio f\xFCr diese Seite erlauben",body:"beacio ist aktiviert, aber f\xFCr diese Seite noch nicht erlaubt. Tippe auf die Schaltfl\xE4che \u201EaA\u201C in der Adressleiste \u2192 Erweiterungen verwalten \u2192 beacio \u2192 \u201EAuf allen Websites erlauben\u201C und lade diese Seite dann neu."},"private-browsing":{title:"Privates Surfen blockiert Erweiterungen",body:"Im privaten Surfmodus sind Safari-Erweiterungen deaktiviert, daher kann beacio hier nicht laufen \u2014 auch wenn es installiert ist. \xD6ffne diese Seite in einem normalen Tab, um dein Ger\xE4t zu verbinden."}},steps:[{label:"beacio installieren",why:"Eine kostenlose, einmalige Begleit-App aus dem App Store."},{label:"App einmal \xF6ffnen",why:"Damit wird die Safari-Erweiterung bei iOS registriert."},{label:"In den Safari-Einstellungen aktivieren",why:"Einstellungen \u2192 Apps \u2192 Safari \u2192 Erweiterungen \u2192 beacio \u2192 \u201EErweiterung erlauben\u201C aktivieren."},{label:"Website-Zugriff erlauben",why:"Tippe auf der Seite auf die Schaltfl\xE4che \u201EaA\u201C in der Adressleiste \u2192 Erweiterungen verwalten \u2192 beacio \u2192 \u201EAuf allen Websites erlauben\u201C."},{label:"Bluetooth beim ersten Scan erlauben",why:"Beim ersten Verbinden fragt Safari, ob diese Seite zugreifen darf \u2014 tippe auf \u201EErlauben\u201C."},{label:"Zur\xFCckkehren und neu laden",why:"Komm zu dieser Seite zur\xFCck, lade sie neu und tippe auf \u201EVerbinden\u201C."}],returnCta:"Zur\xFCck zu {operator}",clipboardHint:"Link wurde au\xDFerdem kopiert \u2014 f\xFCge ihn in Safari ein, falls diese Schaltfl\xE4che {operator} nicht erneut \xF6ffnet.",reload:"Seite neu laden und erneut pr\xFCfen",howSummary:"Wie funktioniert die Einrichtung?",howBody:"beacio nutzt eine einmalige iPhone-App, um die Safari-Erweiterung zu aktivieren. Sobald sie aktiviert und der Zugriff auf dieser Seite erlaubt ist (Schaltfl\xE4che \u201EaA\u201C \u2192 Erweiterungen verwalten \u2192 \u201EAuf allen Websites erlauben\u201C), funktioniert Bluetooth in Safari.",howLink:"Zur vollst\xE4ndigen Einrichtungsanleitung",privacySummary:"Datenschutz: Keine Datenerfassung",privacyBody:"beacio verarbeitet alle Bluetooth-Daten lokal auf deinem Ger\xE4t. Es werden niemals Browserdaten, Ger\xE4tedaten oder pers\xF6nliche Informationen erfasst oder \xFCbertragen.",stillStuck:"Kommst du nicht weiter? Einrichtungsanleitung \xF6ffnen",barTitle:"Bluetooth aktivieren",barText:"Installiere beacio, \xF6ffne die App, aktiviere die Safari-Erweiterung und kehre dann hierher zur\xFCck.",readyToast:"beacio ist bereit \u2014 tippe auf \u201EVerbinden\u201C, um dein Ger\xE4t mit {operator} zu koppeln.",error:{dismiss:"Schlie\xDFen",retry:"Erneut versuchen",titles:{INVALID_PARAMETER:"Etwas ist schiefgelaufen",BLUETOOTH_UNAVAILABLE:"Bluetooth ist nicht verf\xFCgbar",EXTENSION_NOT_INSTALLED:"Bluetooth-Einrichtung abschlie\xDFen",PERMISSION_DENIED:"Bluetooth erlauben, um fortzufahren",DEVICE_NOT_FOUND:"Kein Ger\xE4t gefunden",DEVICE_DISCONNECTED:"Ger\xE4t getrennt",CONNECTION_TIMEOUT:"Zeit\xFCberschreitung der Verbindung",SERVICE_NOT_FOUND:"Ger\xE4tefunktion nicht verf\xFCgbar",CHARACTERISTIC_NOT_FOUND:"Ger\xE4tefunktion nicht verf\xFCgbar",CHARACTERISTIC_NOT_READABLE:"Lesen vom Ger\xE4t nicht m\xF6glich",CHARACTERISTIC_NOT_WRITABLE:"Senden an das Ger\xE4t nicht m\xF6glich",CHARACTERISTIC_NOT_NOTIFIABLE:"Live-Aktualisierungen nicht verf\xFCgbar",GATT_OPERATION_FAILED:"Verbindung unterbrochen",SCAN_ALREADY_IN_PROGRESS:"Suche l\xE4uft bereits",CONNECTION_LIMIT_REACHED:"Zu viele Ger\xE4te verbunden",USER_CANCELLED:"Verbindung abgebrochen",TIMEOUT:"Zeit\xFCberschreitung des Vorgangs",WRITE_INCOMPLETE:"Senden unvollst\xE4ndig"},messages:{INVALID_PARAMETER:"Die Anfrage konnte nicht abgeschlossen werden. Lade die Seite neu und versuche es erneut.",BLUETOOTH_UNAVAILABLE:"Schalte Bluetooth ein und versuche es erneut.",EXTENSION_NOT_INSTALLED:"Bluetooth ist f\xFCr diese Seite noch nicht aktiviert. Schlie\xDFe die Einrichtung ab und versuche dann erneut, dich zu verbinden.",PERMISSION_DENIED:"Der Bluetooth-Zugriff wurde nicht gew\xE4hrt. Tippe selbst auf \u201EVerbinden\u201C (Bluetooth erfordert eine Ber\xFChrung) und erlaube den Zugriff, wenn du gefragt wirst.",DEVICE_NOT_FOUND:"Es wurde kein passendes Ger\xE4t gefunden. Schalte dein Ger\xE4t ein, halte es in der N\xE4he und versuche es erneut.",DEVICE_DISCONNECTED:"Die Verbindung zu deinem Ger\xE4t wurde unterbrochen. Verbinde dich erneut, um fortzufahren.",CONNECTION_TIMEOUT:"Dein Ger\xE4t hat nicht rechtzeitig geantwortet. Halte es in der N\xE4he und eingeschaltet und versuche es erneut.",SERVICE_NOT_FOUND:"Eine erforderliche Funktion wurde auf diesem Ger\xE4t nicht gefunden. Pr\xFCfe, ob es das richtige Ger\xE4t ist, und versuche es erneut.",CHARACTERISTIC_NOT_FOUND:"Eine erforderliche Funktion wurde auf diesem Ger\xE4t nicht gefunden. Pr\xFCfe, ob es das richtige Ger\xE4t ist, und versuche es erneut.",CHARACTERISTIC_NOT_READABLE:"Dieser Wert kann nicht von deinem Ger\xE4t gelesen werden. F\xFCr dieses Element ist keine Aktion erforderlich.",CHARACTERISTIC_NOT_WRITABLE:"Dieser Wert kann nicht an dein Ger\xE4t gesendet werden. F\xFCr dieses Element ist keine Aktion erforderlich.",CHARACTERISTIC_NOT_NOTIFIABLE:"Dieser Wert unterst\xFCtzt auf deinem Ger\xE4t keine Live-Aktualisierungen.",GATT_OPERATION_FAILED:"Etwas hat die Verbindung unterbrochen. Schalte dein Ger\xE4t aus und wieder ein und versuche es erneut.",SCAN_ALREADY_IN_PROGRESS:"Es l\xE4uft bereits eine Ger\xE4tesuche. Warte einen Moment und versuche es erneut.",CONNECTION_LIMIT_REACHED:"Trenne ein anderes Ger\xE4t, bevor du ein neues verbindest.",USER_CANCELLED:"Es wurde kein Ger\xE4t ausgew\xE4hlt. Tippe auf \u201EVerbinden\u201C, um es erneut zu versuchen, wann immer du bereit bist.",TIMEOUT:"Das hat zu lange gedauert. Pr\xFCfe, ob dein Ger\xE4t in der N\xE4he und eingeschaltet ist, und versuche es erneut.",WRITE_INCOMPLETE:"Nur ein Teil der Daten hat dein Ger\xE4t erreicht. Versuche es erneut, um sie noch einmal zu senden."},generic:{title:"Etwas ist schiefgelaufen",body:"Etwas hat die Verbindung unterbrochen. Bitte versuche es erneut."}}},Ue={en:b,de};});var ge={};j(ge,{SETUP_STEPS:()=>ue,buildOnboardingUrl:()=>$e,removeInstallBanner:()=>fe,showInstallBanner:()=>pe});function Ge(e){return e.startOnboardingUrl??e.appStoreUrl??xt}function $e(e,t={}){let r=typeof window<"u"?window.location.href:void 0,n=new URL(e,r);if(n.hostname==="apps.apple.com"){let o=n.pathname.match(/id\d+/)?.[0];return n.pathname=o?`/app/${o}`:new URL(T).pathname,t.apiKey&&!n.searchParams.has("ct")&&(n.searchParams.set("ct",t.apiKey),n.searchParams.set("mt","8")),n.toString()}return t.operatorName&&!n.searchParams.has("operatorName")&&n.searchParams.set("operatorName",t.operatorName),t.returnUrl&&!n.searchParams.has("return")&&n.searchParams.set("return",t.returnUrl),n.toString()}function He(e,t,r){L();let n=k().url;window.location.href=$e(e,{apiKey:t,operatorName:r,returnUrl:n});}function s(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function Y(e,t,r=""){return e.replace(/\{operator\}/g,t).replace(/\{device\}/g,r)}function Ve(e){if(!e||typeof e!="string")return null;let t=typeof window<"u"?window.location.href:"https://beacio.com";try{let r=new URL(e,t);return r.protocol==="http:"||r.protocol==="https:"?r.href:null}catch{return null}}function vt(e){let{operatorName:t=document.title||window.location.hostname,apiKey:r,dismissDays:n=14,state:i="not-installed"}=e,o=Ge(e),a=A({lang:e.lang,strings:e.strings}),c=a.buttonText,d=i==="active"?"not-installed":i,{title:_}=a.states[d],v=e.body??a.states[d].body,p=e.setupUrl??o,f=k(),C=e.accentColor??"#007aff",x=Ve(e.brandLogoUrl),D=e.deviceName??"",J=!!(e.accentColor||x||e.deviceName),_e=e.privacyBody??a.privacyBody,lt=(d==="not-installed"?a.steps:ht[d].map(h=>a.steps[h])).map(h=>`<li class="bc-step"><span class="bc-step-l">${s(h.label)}</span><span class="bc-step-w">${s(h.why)}</span></li>`).join(""),u=document.createElement("div");u.id="beacio-banner",u.dataset.beacioState=d,u.innerHTML=`
<style>
#ioswebble-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-end;
/* SB-SDK-11: the partner accent is exposed as a single CSS custom property on the
sheet root; every accent rule below reads var(--bc-accent). When unthemed the
value defaults to the beacio Apple-blue, so the rendered sheet is unchanged. */
#bc-s{--bc-accent:${s(C)}}
#beacio-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-end;
justify-content:center;background:rgba(0,0,0,.4);font-family:-apple-system,BlinkMacSystemFont,
'SF Pro Text',system-ui,sans-serif;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
animation:iwb-fi .25s ease-out}
@keyframes iwb-fi{from{opacity:0}to{opacity:1}}
@keyframes iwb-su{from{transform:translateY(100%)}to{transform:translateY(0)}}
#iwb-s{background:#fff;border-radius:16px 16px 0 0;padding:12px 24px 34px;max-width:420px;
width:100%;animation:iwb-su .3s ease-out}
#iwb-s *{box-sizing:border-box;margin:0;padding:0}
.iwb-h{width:36px;height:5px;border-radius:3px;background:#d1d1d6;margin:0 auto 16px}
.iwb-hdr{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.iwb-ic{width:40px;height:40px;border-radius:10px;background:#007aff;display:flex;
align-items:center;justify-content:center;flex-shrink:0}
.iwb-ic svg{width:22px;height:22px;fill:#fff}
.iwb-tt{font-size:17px;font-weight:600;color:#000}
.iwb-bd{font-size:15px;line-height:1.4;color:#8e8e93;margin-bottom:16px}
.iwb-mt{font-size:13px;color:#8e8e93;margin-bottom:20px;display:flex;align-items:center;gap:8px}
.iwb-st{color:#ff9500;letter-spacing:1px}
.iwb-btn{display:block;width:100%;padding:14px;background:#007aff;color:#fff;border:none;
border-radius:12px;font-size:17px;font-weight:600;cursor:pointer;text-align:center;
animation:bc-fi .25s ease-out}
@keyframes bc-fi{from{opacity:0}to{opacity:1}}
@keyframes bc-su{from{transform:translateY(100%)}to{transform:translateY(0)}}
#bc-s{background:#fff;border-radius:16px 16px 0 0;padding:12px 20px 28px;max-width:420px;
width:100%;animation:bc-su .3s ease-out;max-height:90vh;overflow-y:auto;
-webkit-overflow-scrolling:touch}
#bc-s *{box-sizing:border-box;margin:0;padding:0}
.bc-h{width:36px;height:5px;border-radius:3px;background:#d1d1d6;margin:0 auto 10px}
.bc-hdr{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.bc-ic{width:36px;height:36px;border-radius:9px;background:var(--bc-accent);display:flex;
align-items:center;justify-content:center;flex-shrink:0;overflow:hidden}
.bc-ic svg{width:20px;height:20px;fill:#fff}
.bc-ic img{width:100%;height:100%;object-fit:contain}
.bc-tt{font-size:16px;font-weight:600;color:#000}
.bc-bd{font-size:13px;line-height:1.35;color:#8e8e93;margin-bottom:12px}
.bc-steps{list-style:none;margin:0 0 14px;padding:0;counter-reset:bc-step}
.bc-step{position:relative;padding:0 0 8px 28px;font-size:13px;line-height:1.35}
.bc-step::before{counter-increment:bc-step;content:counter(bc-step);position:absolute;left:0;top:0;
width:18px;height:18px;border-radius:50%;background:var(--bc-accent);color:#fff;font-size:11px;
font-weight:600;display:flex;align-items:center;justify-content:center}
.bc-step-l{display:block;font-weight:600;color:#1c1c1e}
.bc-step-w{display:block;color:#8e8e93;margin-top:1px;font-size:12px}
.bc-btn{display:block;width:100%;padding:12px;background:var(--bc-accent);color:#fff;border:none;
border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;text-align:center;
text-decoration:none;-webkit-tap-highlight-color:transparent}
.bc-btn:active{opacity:.85}
.bc-ret{display:block;width:100%;padding:12px;margin-top:8px;background:#34c759;color:#fff;border:none;
border-radius:12px;font-size:16px;font-weight:600;cursor:pointer;text-align:center;
text-decoration:none;-webkit-tap-highlight-color:transparent}
.bc-ret:active{opacity:.85}
.bc-cb{font-size:11px;color:#8e8e93;text-align:center;margin-top:6px}
/* SB-SDK-11 AC3: VISIBLE trust surfaces (not the collapsed <details>) \u2014 the
medical-market "No data collected" reassurance + the no-affiliation microcopy. */
.bc-privacy{font-size:12px;color:#8e8e93;line-height:1.4;margin-top:12px}
.bc-noaff{font-size:11px;color:#8e8e93;line-height:1.3;margin-top:6px;text-align:center}
.bc-det{margin-top:10px}
.bc-det summary{font-size:13px;color:var(--bc-accent);cursor:pointer;list-style:none;padding:2px 0}
.bc-det summary::before{content:'\\25B8 '}
.bc-det[open] summary::before{content:'\\25BE '}
.bc-det p{font-size:12px;color:#8e8e93;line-height:1.4;padding:6px 0 2px}
.bc-det a{color:var(--bc-accent)}
.bc-stuck{display:block;font-size:12px;color:var(--bc-accent);text-align:center;margin-top:10px;
text-decoration:none}
.bc-reload{display:block;width:100%;padding:11px;margin-top:8px;background:none;
border:1px solid var(--bc-accent);border-radius:12px;font-size:15px;font-weight:600;color:var(--bc-accent);
cursor:pointer;text-align:center;-webkit-tap-highlight-color:transparent}
.bc-reload:active{opacity:.7}
.bc-dis{display:block;width:100%;padding:8px;background:none;border:none;font-size:14px;
color:#8e8e93;cursor:pointer;text-align:center;margin-top:4px;
-webkit-tap-highlight-color:transparent}
.iwb-btn:active{opacity:.85}
.iwb-det{margin-top:16px}
.iwb-det summary{font-size:15px;color:#007aff;cursor:pointer;list-style:none;padding:4px 0}
.iwb-det summary::before{content:'\\25B8 '}
.iwb-det[open] summary::before{content:'\\25BE '}
.iwb-det p{font-size:13px;color:#8e8e93;line-height:1.5;padding:8px 0 4px}
.iwb-dis{display:block;width:100%;padding:12px;background:none;border:none;font-size:15px;
color:#8e8e93;cursor:pointer;text-align:center;margin-top:8px;
-webkit-tap-highlight-color:transparent}
/* SB-PRD-08: the explicit long opt-out (#bc-dont-show) is visually quieter than the
soft dismiss (#bc-dismiss) above it \u2014 smaller, less padding \u2014 so the soft dismiss
stays the default gesture and the long opt-out is a deliberate secondary choice.
NB keep this comment free of literal UI copy: the <style> block is part of the
banner innerHTML, so any English token here would leak into the localized DOM
(i18n.test.ts no-English-leak guard). */
.bc-dont{font-size:12px;padding:4px 12px;margin-top:0}
@media(prefers-color-scheme:dark){
#iwb-s{background:#1c1c1e}
.iwb-tt{color:#fff}
.iwb-bd,.iwb-mt,.iwb-det p{color:#98989f}
.iwb-dis{color:#98989f}
.iwb-h{background:#48484a}
#bc-s{background:#1c1c1e}
.bc-tt,.bc-step-l{color:#fff}
.bc-bd,.bc-step-w,.bc-cb,.bc-det p,.bc-privacy,.bc-noaff{color:#98989f}
.bc-dis{color:#98989f}
.bc-reload{color:#0a84ff;border-color:#0a84ff}
.bc-h{background:#48484a}
}
</style>
<div id="ioswebble-overlay">
<div id="iwb-s">
<div class="iwb-h"></div>
<div class="iwb-hdr">
<div class="iwb-ic"><svg viewBox="0 0 24 24"><path d="M12 2L7 7l5 5-5 5 5 5V2zm0 6.83L10.83 7 12 5.83v2.34zm0 8.34L10.83 17 12 15.83v1.34zM17 7l-5 5 5 5-2.12 2.12L12 17l-2.88 2.12L7 17l5-5-5-5 2.12-2.12L12 7l2.88-2.12L17 7z"/></svg></div>
<div class="iwb-tt">Set Up Bluetooth in Safari</div>
<div id="beacio-overlay">
<div id="bc-s" role="dialog" aria-label="${s(_)}">
<div class="bc-h"></div>
<div class="bc-hdr">
<div class="bc-ic">${x?`<img src="${s(x)}" alt="" aria-hidden="true">`:'<svg viewBox="0 0 24 24"><path d="M12 2L7 7l5 5-5 5 5 5V2zm0 6.83L10.83 7 12 5.83v2.34zm0 8.34L10.83 17 12 15.83v1.34zM17 7l-5 5 5 5-2.12 2.12L12 17l-2.88 2.12L7 17l5-5-5-5 2.12-2.12L12 7l2.88-2.12L17 7z"/></svg>'}</div>
<div class="bc-tt">${s(_)}</div>
</div>
<div class="iwb-bd">To connect to your device, install WebBLE, open the app once, enable the Safari extension, then return to ${_(n)}.</div>
<div class="iwb-mt"><span>Install</span><span>\u2192</span><span>Open app</span><span>\u2192</span><span>Enable in Safari</span><span>\u2192</span><span>Return here</span></div>
<button class="iwb-btn" id="iwb-install">${_(t)}</button>
<details class="iwb-det"><summary>How does setup work?</summary><p>WebBLE uses an iPhone app to guide the one-time Safari extension setup. After install, open the app, enable the extension in Safari, then come back to this page and try again.</p></details>
<details class="iwb-det"><summary>Privacy: No data collected</summary><p>WebBLE processes all Bluetooth data locally on your device. No browsing data, device data, or personal information is ever collected or transmitted.</p></details>
<button class="iwb-dis" id="iwb-dismiss">Not now</button>
<div class="bc-bd">${s(Y(v,t,D))}</div>
<ol class="bc-steps">${lt}</ol>
${d==="not-installed"?`<button class="bc-btn" id="bc-install">${s(c)}</button>`:""}
<a class="bc-ret" id="bc-return" href="${s(f.returnLink)}">${s(Y(a.returnCta,t))}</a>
<p class="bc-cb">${s(Y(a.clipboardHint,t))}</p>
<button class="bc-reload" id="bc-reload">${s(a.reload)}</button>
<details class="bc-det"><summary>${s(a.howSummary)}</summary><p>${s(a.howBody)} <a href="${s(p)}" target="_blank" rel="noopener">${s(a.howLink)}</a>.</p></details>
<details class="bc-det"><summary>${s(a.privacySummary)}</summary><p>${s(_e)}</p></details>
${J?`<p class="bc-privacy" id="bc-privacy">${s(a.privacySummary)} \u2014 ${s(_e)}</p><p class="bc-noaff" id="bc-noaff">beacio is an independent Safari extension and is not affiliated with the device maker.</p>`:""}
<a class="bc-stuck" id="bc-stuck" href="${s(p)}" target="_blank" rel="noopener">${s(a.stillStuck)}</a>
<button class="bc-dis" id="bc-dismiss">${s(a.dismiss)}</button>
<button class="bc-dis bc-dont" id="bc-dont-show">${s(a.dontShowAgain)}</button>
</div>
</div>`,requestAnimationFrame(()=>{o.querySelector("#iwb-install")?.addEventListener("click",()=>{C(a,r);}),o.querySelector("#iwb-dismiss")?.addEventListener("click",()=>{o.remove(),h(i);}),o.querySelector("#ioswebble-overlay")?.addEventListener("click",d=>{d.target.id==="ioswebble-overlay"&&(o.remove(),h(i));});}),document.body.appendChild(o),o}function le(e){let{position:n="bottom",text:t="Install WebBLE, open the app, enable the Safari extension, then return here.",buttonText:r="Start Setup",style:i={},apiKey:a,dismissDays:o=14}=e,d=D(e),s=document.createElement("div");s.id="ioswebble-banner";let m=n==="top"?"top:0;border-bottom:1px solid #e5e7eb;":"bottom:0;border-top:1px solid #e5e7eb;",V=Object.entries(i).map(([Y,X])=>`${Y}:${X}`).join(";");return s.innerHTML=`
<div style="position:fixed;${m}left:0;right:0;z-index:2147483646;
</div>`,L();let he=e.forceShow===true,y=null,Q=false;function F(){Q=true,y!==null&&(clearTimeout(y),y=null),window.removeEventListener(Fe,xe),window.removeEventListener(je,Ee),document.removeEventListener("visibilitychange",ve);}function S(){return $()?(F(),u.remove(),true):false}function xe(){S();}function Ee(){S()||Ae();}function Ae(){if(Q||y!==null)return;let h=0,ye=()=>{y=null,!Q&&(S()||(h+=1,!(h>=Et)&&(y=setTimeout(ye,At))));};ye();}function ve(){document.visibilityState==="visible"&&(S()||Ae());}return he||(window.addEventListener(Fe,xe),window.addEventListener(je,Ee),document.addEventListener("visibilitychange",ve)),requestAnimationFrame(()=>{u.querySelector("#bc-install")?.addEventListener("click",()=>{He(o,r,t);}),u.querySelector("#bc-reload")?.addEventListener("click",()=>{S()||window.location.reload();}),u.querySelector("#bc-dismiss")?.addEventListener("click",()=>{F(),u.remove(),w();}),u.querySelector("#bc-dont-show")?.addEventListener("click",()=>{F(),u.remove(),B(n);}),u.querySelector("#beacio-overlay")?.addEventListener("click",h=>{h.target.id==="beacio-overlay"&&(F(),u.remove(),w());});}),document.body.appendChild(u),he||S(),u}function yt(e){let{position:t="bottom",style:r={},apiKey:n,operatorName:i}=e,o=A({lang:e.lang,strings:e.strings}),a=o.barText,c=o.buttonText,d=Ge(e),_=e.accentColor??"#007AFF",v=Ve(e.brandLogoUrl),p=document.createElement("div");p.id="beacio-banner";let f=t==="top"?"top:0;border-bottom:1px solid #e5e7eb;":"bottom:0;border-top:1px solid #e5e7eb;",C=Object.entries(r).map(([x,D])=>`${x}:${D}`).join(";");return p.innerHTML=`
<div style="position:fixed;${f}left:0;right:0;z-index:2147483646;
background:#fff;padding:16px;
display:flex;align-items:center;gap:12px;font-family:system-ui,-apple-system,sans-serif;
box-shadow:0 ${n==="top"?"2px":"-2px"} 10px rgba(0,0,0,0.1);${V}">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#007AFF"/>
box-shadow:0 ${t==="top"?"2px":"-2px"} 10px rgba(0,0,0,0.1);${C}">
${v?`<img src="${s(v)}" alt="" aria-hidden="true" width="24" height="24" style="object-fit:contain;flex-shrink:0">`:`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="${s(_)}"/>
<path d="M12 7a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V8a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" fill="white"/>
</svg>
</svg>`}
<div style="flex:1">
<div style="font-size:14px;font-weight:600;color:#1f2937">Enable Bluetooth</div>
<div style="font-size:12px;color:#6b7280;margin-top:2px">${_(t)}</div>
<div style="font-size:14px;font-weight:600;color:#1f2937">${s(o.barTitle)}</div>
<div style="font-size:12px;color:#6b7280;margin-top:2px">${s(a)}</div>
</div>
<button id="ioswebble-banner-install"
style="background:#007AFF;color:white;padding:8px 16px;border-radius:8px;
<button id="beacio-banner-install"
style="background:${s(_)};color:white;padding:8px 16px;border-radius:8px;
border:none;font-size:14px;font-weight:500;white-space:nowrap;cursor:pointer">
${_(r)}</button>
<button id="ioswebble-banner-close"
${s(c)}</button>
<button id="beacio-banner-close"
style="background:none;border:none;color:#9ca3af;font-size:20px;
cursor:pointer;padding:4px;line-height:1"
aria-label="Close">&times;</button>
</div>`,s.querySelector("#ioswebble-banner-install")?.addEventListener("click",()=>{C(d,a);}),s.querySelector("#ioswebble-banner-close")?.addEventListener("click",()=>{s.remove(),h(o);}),document.body.appendChild(s),s}function p(e={}){return ae()?null:e.mode==="banner"?le(e):ce(e)}function f(){let e=document.getElementById("ioswebble-banner");e&&e.remove();}var re,k,ie,oe,v=c(()=>{re="https://ioswebble.com/setup",k="ioswebble_dismiss_until",ie="ioswebble_return",oe="link.ioswebble.com";});var F={};g(F,{removeInstallBanner:()=>f,showInstallBanner:()=>p});var P=c(()=>{v();});function x(){if(typeof navigator>"u")return false;let e=navigator.userAgent,n=/iPad|iPhone|iPod/.test(e)||navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1,t=/^((?!chrome|android|crios|fxios).)*safari/i.test(e);return n&&t}function ue(){return typeof window<"u"&&window.__webble?.status==="installed"}function de(){return typeof navigator>"u"?false:!!(navigator.webble&&navigator.webble.__webble)}function _e(){return typeof document<"u"&&document.documentElement.dataset.webbleInstalled==="true"}function pe(){return typeof document<"u"&&document.documentElement.dataset.webbleExtension==="true"}function j(){return de()||pe()?"active":ue()||_e()?"installed-inactive":"not-installed"}async function u(){try{let{detectPlatform:e}=await import('@beacio/core');if(e()==="safari-extension")return "active"}catch{}return new Promise(e=>{let n=j();if(n!=="not-installed"){e(n);return}let t=0,r=setInterval(()=>{t++;let i=j();i!=="not-installed"&&(clearInterval(r),e(i)),t>20&&(clearInterval(r),e("not-installed"));},100);})}async function b(){return await u()!=="not-installed"}var y=c(()=>{});var U={};g(U,{getExtensionInstallState:()=>u,isExtensionInstalled:()=>b,isIOSSafari:()=>x});var L=c(()=>{y();});function l(e,n,t){if(e)try{fetch(`${N}/v1/events?key=${encodeURIComponent(e)}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:[{event:n,data:{origin:location.hostname,ua:navigator.userAgent,...t},timestamp:Date.now()}]}),keepalive:!0}).catch(()=>{});}catch{}}async function z(e){try{let n=await fetch(`${N}/v1/config?key=${encodeURIComponent(e)}`);return n.ok?await n.json():null}catch{return null}}function fe(e){typeof window>"u"||window.dispatchEvent(new CustomEvent("ioswebble:statechange",{detail:{state:e}}));}async function O(e){if(e.banner===false)return;let{showInstallBanner:n}=await Promise.resolve().then(()=>(P(),F)),r={...typeof e.banner=="object"?e.banner:{},apiKey:e.key??"",operatorName:e.operatorName};n(r);}async function M(e){let{getExtensionInstallState:n,isIOSSafari:t}=await Promise.resolve().then(()=>(L(),U));if(!t())return;let r=await n();if(fe(r),r==="active"){l(e.key??"","extension_active"),typeof window<"u"&&window.dispatchEvent(new CustomEvent("ioswebble:ready")),e.onReady?.();return}if(r==="installed-inactive"){l(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent("ioswebble:installedinactive")),e.onInstalledInactive?.(),await O(e);return}l(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent("ioswebble:notinstalled")),e.onNotInstalled?.(),await O(e),e.banner!==false&&l(e.key??"","install_prompted");}var N,R=c(()=>{N="https://api.ioswebble.com";});var T={};g(T,{getExtensionInstallState:()=>u,initBeacio:()=>M,isExtensionInstalled:()=>b,isIOSSafari:()=>x,removeInstallBanner:()=>f,reportEvent:()=>l,showInstallBanner:()=>p,validateApiKey:()=>z});var $=c(()=>{R();y();v();});function S(){if(typeof navigator>"u")return "unsupported";let e=navigator;return e.webble?.__webble===true?"safari-extension":e.bluetooth&&!e.bluetooth.__webbleCDNStub?"native":"unsupported"}function w(){if(typeof navigator>"u")return null;let e=navigator;return e.webble?.__webble===true?e.webble:e.bluetooth&&!e.bluetooth.__webbleCDNStub?e.bluetooth:null}var G="-0000-1000-8000-00805f9b34fb",J={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},Q={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,"local_east_coordinate.xml":10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989};function E(e){return e.toString(16).padStart(8,"0")+G}var ee=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;var te={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function B(e){let n=Number(e);if(!Number.isFinite(n))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);let t=Math.trunc(n);if(t<0||t>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);return E(t+0)}function A(e,n,t){if(typeof e=="number")return B(e);if(ee.test(e))return e;let r=n[e.toLowerCase()];if(r!==void 0)return E(r);throw new TypeError(`Failed to execute '${t}' on 'BluetoothUUID': Invalid UUID or registry name: "${e}"`)}function ne(e){return A(e,te,"getDescriptor")}var I={canonicalUUID:B,getService:e=>A(e,J,"getService"),getCharacteristic:e=>A(e,Q,"getCharacteristic"),getDescriptor:ne};function W(e){if(typeof navigator>"u"||!navigator.permissions)return;let n=navigator.permissions.query.bind(navigator.permissions);navigator.permissions.query=async function(t){if(t.name!=="bluetooth")return n(t);let r=t.deviceId,i=[];if(typeof e.getDevices=="function")try{let s=await e.getDevices();i=r===void 0?[...s]:s.filter(m=>m.id===r);}catch{i=[];}let a=Object.freeze(i),o=new EventTarget;return Object.create(o,{state:{get:()=>"prompt",enumerable:true},name:{get:()=>"bluetooth",enumerable:true},onchange:{value:null,writable:true,enumerable:true},devices:{get:()=>a,enumerable:true}})};}var H=new Set(["requestDevice","getAvailability","getDevices","referringDevice","onavailabilitychanged","onadvertisementreceived","ongattserverdisconnected","oncharacteristicvaluechanged","onserviceadded","onservicechanged","onserviceremoved","addEventListener","removeEventListener","dispatchEvent"]);function K(e){return e.startsWith("on")}function q(e){class n extends EventTarget{}let t=new n,r=e;for(let i of H){if(K(i)){Object.defineProperty(t,i,{get:()=>r[i]??null,set:o=>{r[i]=o;},enumerable:true,configurable:true});continue}if(i==="referringDevice"){Object.defineProperty(t,i,{get:()=>r[i]??null,enumerable:true,configurable:true});continue}let a=r[i];typeof a=="function"&&Object.defineProperty(t,i,{value:a.bind(e),writable:true,enumerable:true,configurable:true});}return t}function xe(){class e extends EventTarget{}let n=new e;Object.defineProperty(n,"requestDevice",{value:async(...t)=>{try{let r=await Promise.resolve().then(()=>($(),T));typeof r.showInstallBanner=="function"&&r.showInstallBanner();}catch{}throw new DOMException("Web Bluetooth is not supported on this platform. On iOS Safari, install the WebBLE extension. See: https://ioswebble.com","NotFoundError")},writable:true,enumerable:true,configurable:true}),Object.defineProperty(n,"getAvailability",{value:async()=>false,writable:true,enumerable:true,configurable:true}),Object.defineProperty(n,"getDevices",{value:async()=>[],writable:true,enumerable:true,configurable:true}),Object.defineProperty(n,"referringDevice",{get:()=>null,enumerable:true,configurable:true});for(let t of H){if(!K(t))continue;let r=t.slice(2),i=null;Object.defineProperty(n,t,{get:()=>i,set:a=>{i!==null&&n.removeEventListener(r,i),i=typeof a=="function"?a:null,i!==null&&n.addEventListener(r,i);},enumerable:true,configurable:true});}return Object.defineProperty(n,"__webbleCDNStub",{value:true,writable:false,enumerable:false,configurable:true}),n}function be(){if(typeof navigator>"u")return;let e=navigator;if(typeof window<"u"&&!window.BluetoothUUID&&(window.BluetoothUUID=I),typeof window<"u"&&window.isSecureContext===false)return;let n=S();if(n!=="native"){if(n==="safari-extension"){let t=w();if(t&&!e.bluetooth){let r=q(t);Object.defineProperty(navigator,"bluetooth",{get:()=>r,configurable:true});}if(typeof window<"u"&&!window.webbleIOS){let r=t?.peripheral||t?.backgroundSync?{peripheral:t.peripheral,backgroundSync:t.backgroundSync,getCapabilities:()=>t.getCapabilities?.()}:void 0;r&&Object.defineProperty(window,"webbleIOS",{value:Object.freeze(r),writable:false,enumerable:true,configurable:false});}t&&W(t);return}if(!e.bluetooth){let t=xe();Object.defineProperty(navigator,"bluetooth",{get:()=>t,configurable:true}),typeof window<"u"&&window.addEventListener("webble:extension:ready",()=>{let r=w();if(!r||r===t)return;let i=navigator.bluetooth;if(i!==void 0&&i!==t)return;let a=q(r);Object.defineProperty(navigator,"bluetooth",{get:()=>a,configurable:true}),W(r);},{once:true});}}}be();
//# sourceMappingURL=auto.js.map
</div>`,p.querySelector("#beacio-banner-install")?.addEventListener("click",()=>{He(d,n,i);}),p.querySelector("#beacio-banner-close")?.addEventListener("click",()=>{p.remove(),w();}),document.body.appendChild(p),p}function St(){try{return localStorage.getItem(ze)==="1"}catch{return false}}function Tt(){try{localStorage.setItem(ze,"1");}catch{}}function wt(e){if(St())return null;Tt();let t=e.operatorName||document.title||window.location.hostname,r=A({lang:e.lang,strings:e.strings}),n=document.createElement("div");return n.id="beacio-banner",n.dataset.beacioState="active",n.innerHTML=`
<style>
#bc-toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483647;
max-width:420px;width:calc(100% - 32px);background:#34c759;color:#fff;border-radius:14px;
padding:14px 16px;display:flex;align-items:center;gap:12px;
font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;
box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bc-tu .3s ease-out}
@keyframes bc-tu{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
#bc-toast svg{width:22px;height:22px;flex-shrink:0;fill:#fff}
.bc-toast-tx{flex:1;font-size:15px;font-weight:600;line-height:1.3}
#bc-toast-x{background:none;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0 4px;
line-height:1;-webkit-tap-highlight-color:transparent}
</style>
<div id="bc-toast" role="status">
<svg viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
<span class="bc-toast-tx">${s(Y(r.readyToast,t))}</span>
<button id="bc-toast-x" aria-label="Dismiss">&times;</button>
</div>`,requestAnimationFrame(()=>{n.querySelector("#bc-toast-x")?.addEventListener("click",()=>n.remove());}),document.body.appendChild(n),n}function pe(e={}){return e.state==="active"?wt(e):!e.forceShow&&H()?null:e.mode==="banner"?yt(e):vt(e)}function fe(){let e=document.getElementById("beacio-banner");e&&e.remove();}var ue,ht,xt,ze,Fe,je,Et,At,X=g(()=>{le();R();q();U();ue=b.steps,ht={"installed-inactive":[2,4,5],denied:[3,4,5],"private-browsing":[]},xt=K,ze="beacio_ready_shown",Fe=l.READY,je=l.EXTENSION_READY,Et=5,At=300;});function Ct(e){let t=e.split(`
`,1)[0]??"";return t=t.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),t=t.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),t=t.replace(Nt,""),t=t.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),t=t.replace(/[\s.,;:]+$/g,"").trim(),t}function M(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function Ot(e){return typeof e=="object"&&e!==null&&"code"in e&&typeof e.code=="string"&&e.code in It}function Rt(e,t){let r=t.toLowerCase();switch(e){case "NotFoundError":return "DEVICE_NOT_FOUND";case "NotAllowedError":case "SecurityError":return "PERMISSION_DENIED";case "NetworkError":return "DEVICE_DISCONNECTED";case "TimeoutError":return "TIMEOUT";case "InvalidStateError":return r.includes("disconnect")?"DEVICE_DISCONNECTED":"GATT_OPERATION_FAILED";}return r.includes("user cancelled")||r.includes("user canceled")?"USER_CANCELLED":r.includes("disconnect")?"DEVICE_DISCONNECTED":r.includes("timeout")?"TIMEOUT":"GATT_OPERATION_FAILED"}function Bt(e,t,r){let n=o=>t.titles[o],i=o=>r?.messages?.[o]??t.messages[o];if(typeof e=="string"){let a=Ct(e)||t.generic.body;return {code:null,title:t.generic.title,body:a,isRetriable:false,signature:`str:${a}`}}if(Ot(e)){let o=e.code;return {code:o,title:n(o),body:i(o),isRetriable:typeof e.isRetriable=="boolean"?e.isRetriable:We.has(o),signature:`code:${o}`}}if(typeof e=="object"&&e!==null){let o="name"in e&&typeof e.name=="string"?e.name:"",a=e instanceof Error?e.message:String(e.message??""),c=Rt(o,a);return {code:c,title:n(c),body:i(c),isRetriable:We.has(c),signature:`dom:${c}`}}return {code:null,title:t.generic.title,body:t.generic.body,isRetriable:false,signature:"generic"}}function qe(e,t={}){if(typeof document>"u")return null;let{strings:r}=t,n=A({lang:t.lang}).error,i=Bt(e,n,r),o=Date.now(),a=document.getElementById(P);if(a&&Ke===i.signature&&o-be<Dt)return null;a&&a.remove(),Ke=i.signature,be=o;let c=t.operatorName,d=t.dismissText??r?.dismiss??n.dismiss,_=t.retryText??r?.retry??n.retry,v=i.isRetriable,p=Object.entries(t.style??{}).map(([D,J])=>`${D}:${J}`).join(";"),f=document.createElement("div");f.id=P,f.dataset.beacioErrorCode=i.code??"unknown",p&&(f.style.cssText=p);let C=c?`${c} \u2014 ${i.title}`:i.title;f.innerHTML=`
<style>
#${P}{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483646;
max-width:420px;width:calc(100% - 32px);background:#fff;color:#1c1c1e;border-radius:14px;
padding:16px 18px;display:flex;flex-direction:column;gap:10px;
font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif;
box-shadow:0 6px 20px rgba(0,0,0,.2);animation:bce-u .3s ease-out}
@keyframes bce-u{from{opacity:0;transform:translate(-50%,12px)}to{opacity:1;transform:translate(-50%,0)}}
#${P} *{box-sizing:border-box;margin:0;padding:0}
.bce-row{display:flex;align-items:flex-start;gap:12px}
.bce-ic{width:28px;height:28px;border-radius:8px;background:#ff3b30;flex-shrink:0;display:flex;
align-items:center;justify-content:center}
.bce-ic svg{width:18px;height:18px;fill:#fff}
.bce-tx{flex:1;min-width:0}
.bce-tt{font-size:15px;font-weight:600;line-height:1.3}
.bce-bd{font-size:14px;line-height:1.4;color:#3a3a3c;margin-top:3px}
.bce-x{background:none;border:none;color:#8e8e93;font-size:20px;cursor:pointer;line-height:1;
padding:0 2px;align-self:flex-start}
/* SB-SDK-07: visually-hidden text label on the icon-only dismiss control. The
glyph stays the only visible mark; the label surfaces in the accessibility
tree + DOM text so the LOCALIZED dismiss copy is present (German when lang
selects it), not just an aria-label attribute. */
.bce-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
clip:rect(0,0,0,0);white-space:nowrap;border:0}
.bce-act{display:flex;gap:8px;justify-content:flex-end}
.bce-retry{padding:9px 16px;background:#007aff;color:#fff;border:none;border-radius:10px;
font-size:15px;font-weight:600;cursor:pointer}
.bce-retry:active{opacity:.85}
@media(prefers-color-scheme:dark){
#${P}{background:#1c1c1e;color:#fff}
.bce-bd{color:#aeaeb2}
}
</style>
<div class="bce-row">
<div class="bce-ic"><svg viewBox="0 0 24 24"><path d="M12 2 1 21h22L12 2zm0 5 7.5 13h-15L12 7zm-1 4v4h2v-4h-2zm0 6v2h2v-2h-2z"/></svg></div>
<div class="bce-tx">
<p class="bce-tt">${M(C)}</p>
<p class="bce-bd">${M(i.body)}</p>
</div>
<button class="bce-x" aria-label="${M(d)}">&times;<span class="bce-sr">${M(d)}</span></button>
</div>
${v?`<div class="bce-act"><button class="bce-retry" type="button">${M(_)}</button></div>`:""}`;function x(){f.remove(),be=0;}return f.querySelector(".bce-x")?.addEventListener("click",x),v&&f.querySelector(".bce-retry")?.addEventListener("click",()=>{x(),t.onRetry?.();}),document.body.appendChild(f),f}var We,It,Nt,P,Dt,Ke,be,Ye=g(()=>{q();We=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),It=Object.fromEntries(Object.keys(b.error.titles).map(e=>[e,{title:b.error.titles[e],body:b.error.messages[e]}])),Nt=/\b(bluefy|web ble browser|webble browser)\b/gi;P="beacio-error",Dt=1500,Ke=null,be=0;});function Lt(){return {origin:location.hostname,ua:navigator.userAgent}}function m(e,t,r){if(e)try{fetch(`${Xe}/v1/events`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({events:[{event:t,data:Lt(),timestamp:Date.now()}]}),keepalive:!0}).catch(()=>{});}catch{}}async function Ze(e){try{let t=await fetch(`${Xe}/v1/config`,{headers:{Authorization:`Bearer ${e}`}});return t.ok?await t.json():null}catch{return null}}var Xe,me=g(()=>{Xe="https://api.beacio.com";});var Qe={};j(Qe,{APP_STORE_URL:()=>T,DEFAULT_DISMISS_DAYS:()=>re,DE_STRINGS:()=>de,EN_STRINGS:()=>b,SETUP_STEPS:()=>ue,SHORT_DISMISS_DAYS:()=>ie,dismiss:()=>B,dismissShort:()=>w,getExtensionInstallState:()=>V,getInstallState:()=>E,getReturnContext:()=>k,initBeacio:()=>Ut,isDismissed:()=>H,isExtensionActive:()=>$,isExtensionInstalled:()=>se,isIOSSafari:()=>ae,observeInstallState:()=>ke,presentError:()=>qe,removeInstallBanner:()=>fe,reportEvent:()=>m,resolveOnboardingState:()=>Mt,resolveStrings:()=>A,saveReturnContext:()=>L,showInstallBanner:()=>pe,validateApiKey:()=>Ze});function kt(e){typeof window>"u"||window.dispatchEvent(new CustomEvent(l.STATE_CHANGE,{detail:{state:e}}));}async function I(e,t,r){if(e.banner===false)return;let{showInstallBanner:n}=await Promise.resolve().then(()=>(X(),ge)),i=typeof e.banner=="object"?e.banner:{},o={...i,apiKey:e.key??"",operatorName:e.operatorName,lang:i.lang??e.lang,state:r??t};n(o);}async function Z(){if(typeof navigator>"u")return false;let e=navigator.bluetooth;if(!e||typeof e.getAvailability!="function")return false;try{return await e.getAvailability()===!1}catch{return false}}function Je(){if(typeof window>"u")return false;try{let e=window.localStorage;if(!e)return !1;let t="__beacio_pb_probe__";return e.setItem(t,"1"),e.removeItem(t),!1}catch{return true}}async function Ut(e){let{getExtensionInstallState:t,isIOSSafari:r}=await Promise.resolve().then(()=>(W(),ce));if(!r())return;let n=await t();if(kt(n),n==="active"){if(await Z()){m(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await I(e,n,"denied");return}m(e.key??"","extension_active"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.READY)),e.onReady?.(),await I(e,n);return}if(Je()){m(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.NOT_INSTALLED)),e.onNotInstalled?.(),await I(e,n,"private-browsing");return}if(await Z()){m(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await I(e,n,"denied");return}if(n==="installed-inactive"){m(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.INSTALLED_INACTIVE)),e.onInstalledInactive?.(),await I(e,n);return}m(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent(l.NOT_INSTALLED)),e.onNotInstalled?.(),await I(e,n),e.banner!==false&&m(e.key??"","install_prompted");}function Pt(e){let t=typeof window<"u"?window.location.href:"";return `${K}?operatorName=${encodeURIComponent(e.operatorName)}&return=${encodeURIComponent(t)}`}async function Mt(e){let{getExtensionInstallState:t,isIOSSafari:r}=await Promise.resolve().then(()=>(W(),ce));if(!r())return {kind:"unsupported"};let n=oe(),i=Pt(e),o=await t();if(o==="active")return await Z()?{kind:"denied",setupUrl:i,returnLink:n}:{kind:"ready"};if(Je())return {kind:"private-browsing",returnLink:n};if(await Z())return {kind:"denied",setupUrl:i,returnLink:n};if(o==="installed-inactive")return {kind:"installed-inactive",setupUrl:i,returnLink:n};let{buildOnboardingUrl:a}=await Promise.resolve().then(()=>(X(),ge));return {kind:"not-installed",installUrl:a(T,{apiKey:e.apiKey,operatorName:e.operatorName}),returnLink:n}}var et=g(()=>{W();X();U();Ye();q();me();me();R();U();le();});te();var Te="-0000-1000-8000-00805f9b34fb",we=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,Ie={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},Ne={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,local_east_coordinate:10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989},Ce={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function De(e){return e.toString(16).padStart(8,"0")+Te}function ne(e){let t=Number(e);if(!Number.isFinite(t))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);let r=Math.trunc(t);if(r<0||r>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);return De(r+0)}function G(e,t,r){if(typeof e=="number")return ne(e);let n=String(e);if(we.test(n))return n;let i=t[n.toLowerCase()];if(i!==void 0)return De(i);throw new TypeError(`Failed to execute '${r}' on 'BluetoothUUID': Invalid UUID or registry name: "${n}"`)}function ut(e){return G(e,Ce,"getDescriptor")}var Oe={canonicalUUID:ne,getService:e=>G(e,Ie,"getService"),getCharacteristic:e=>G(e,Ne,"getCharacteristic"),getDescriptor:ut};R();function tt(e){if(typeof navigator>"u"||!navigator.permissions)return;let t=navigator.permissions.query.bind(navigator.permissions);navigator.permissions.query=async function(r){if(r.name!=="bluetooth")return t(r);let n=r.deviceId,i=[];if(typeof e.getDevices=="function")try{let d=await e.getDevices();i=n===void 0?[...d]:d.filter(_=>_.id===n);}catch{i=[];}let o=Object.freeze(i),a=new EventTarget;return Object.create(a,{state:{get:()=>"prompt",enumerable:true},name:{get:()=>"bluetooth",enumerable:true},onchange:{value:null,writable:true,enumerable:true},devices:{get:()=>o,enumerable:true}})};}var st=new Set(["requestDevice","getAvailability","getDevices","referringDevice","onavailabilitychanged","onadvertisementreceived","ongattserverdisconnected","oncharacteristicvaluechanged","onserviceadded","onservicechanged","onserviceremoved","addEventListener","removeEventListener","dispatchEvent"]);function ct(e){return e.startsWith("on")}function nt(e){class t extends EventTarget{}let r=new t,n=e;for(let i of st){if(ct(i)){Object.defineProperty(r,i,{get:()=>n[i]??null,set:a=>{n[i]=a;},enumerable:true,configurable:true});continue}if(i==="referringDevice"){Object.defineProperty(r,i,{get:()=>n[i]??null,enumerable:true,configurable:true});continue}let o=n[i];typeof o=="function"&&Object.defineProperty(r,i,{value:o.bind(e),writable:true,enumerable:true,configurable:true});}return r}function Ft(){let e=(typeof window<"u"?window.beacioAutoReconnect:void 0)??{};return {enabled:e.enabled!==false,backoff:{maxAttempts:e.maxAttempts??1/0,initialDelayMs:e.initialDelayMs??1e3,maxDelayMs:e.maxDelayMs??3e4,backoffMultiplier:e.backoffMultiplier??2}}}function N(e,t){let r=new Map;return new Proxy(e,{get(n,i,o){if(typeof i=="string"&&Object.prototype.hasOwnProperty.call(t,i))return t[i];let a=Reflect.get(n,i,o);if(typeof a!="function"||Object.prototype.hasOwnProperty.call(n,i))return a;let c=r.get(i);return c||(c=a.bind(n),r.set(i,c)),c}})}function rt(e,t,r){let n=`${t}|${e.uuid}`;return N(e,{startNotifications:async()=>{let i=await e.startNotifications();return r.subscriptions.set(n,{service:t,characteristic:e.uuid}),i},stopNotifications:async()=>{let i=await e.stopNotifications();return r.subscriptions.delete(n),i}})}function it(e,t){return N(e,{getCharacteristic:async r=>{let n=await e.getCharacteristic(r);return rt(n,e.uuid,t)},getCharacteristics:async r=>(await e.getCharacteristics(r)).map(i=>rt(i,e.uuid,t))})}function jt(e,t){return t.server=e,N(e,{getPrimaryService:async r=>{let n=await e.getPrimaryService(r);return it(n,t)},getPrimaryServices:async r=>(await e.getPrimaryServices(r)).map(i=>it(i,t))})}function zt(e,t){return N(e,{connect:async()=>{t.intentional=false;let r=await e.connect();return jt(r,t)},disconnect:()=>{t.intentional=true,t.subscriptions.clear(),t.server=null,e.disconnect();}})}async function Gt(e,t){for(let{service:r,characteristic:n}of [...t.subscriptions.values()])try{await(await(await e.getPrimaryService(r)).getCharacteristic(n)).startNotifications();}catch{t.subscriptions.delete(`${r}|${n}`);}}function $t(e,t){if(e.reconnecting)return;let r=e.server;if(!r)return;e.reconnecting=true;let n=[...new Set([...e.subscriptions.values()].map(i=>i.service))];(async()=>{let i=t.initialDelayMs;for(let o=1;o<=t.maxAttempts&&!(e.intentional||(await new Promise(a=>setTimeout(a,i)),e.intentional));o+=1)try{let a=r.connectAndDiscover;typeof a=="function"&&n.length>0?await a.call(r,n):await r.connect(),await Gt(r,e),e.reconnecting=!1;return}catch{i=Math.min(i*t.backoffMultiplier,t.maxDelayMs);}e.reconnecting=false;})();}function Ht(e,t){if(!e||typeof e.addEventListener!="function")return e;let r={server:null,intentional:false,reconnecting:false,subscriptions:new Map};e.addEventListener("gattserverdisconnected",()=>{if(r.intentional){r.intentional=false;return}$t(r,t);});let n;return N(e,{get gatt(){let i=e.gatt;if(i)return n||(n=zt(i,r)),n}})}function ot(e){let t=Ft(),r=e;if(!t.enabled||typeof r.requestDevice!="function")return e;let n=r.requestDevice.bind(e);return N(e,{requestDevice:async(...i)=>{let o=await n(...i);return Ht(o,t.backoff)}})}function Vt(){class e extends EventTarget{}let t=new e;Object.defineProperty(t,"requestDevice",{value:async(...r)=>{try{let n=await Promise.resolve().then(()=>(et(),Qe));typeof n.showInstallBanner=="function"&&n.showInstallBanner();}catch{}throw new DOMException("Web Bluetooth is not supported on this platform. On iOS Safari, install the Beacio extension. See: https://beacio.com","NotFoundError")},writable:true,enumerable:true,configurable:true}),Object.defineProperty(t,"getAvailability",{value:async()=>false,writable:true,enumerable:true,configurable:true}),Object.defineProperty(t,"getDevices",{value:async()=>[],writable:true,enumerable:true,configurable:true}),Object.defineProperty(t,"referringDevice",{get:()=>null,enumerable:true,configurable:true});for(let r of st){if(!ct(r))continue;let n=r.slice(2),i=null;Object.defineProperty(t,r,{get:()=>i,set:o=>{i!==null&&t.removeEventListener(n,i),i=typeof o=="function"?o:null,i!==null&&t.addEventListener(n,i);},enumerable:true,configurable:true});}return Object.defineProperty(t,O,{value:true,writable:false,enumerable:false,configurable:true}),t}var at=false;function Wt(){if(at||typeof navigator>"u")return;at=true;let e=navigator;if(typeof window<"u"&&!window.BluetoothUUID&&(window.BluetoothUUID=Oe),typeof window<"u"&&window.isSecureContext===false)return;let t=ee();if(t!=="native"){if(t==="safari-extension"){let r=z();if(r&&!e.bluetooth){let n=nt(ot(r));Object.defineProperty(navigator,"bluetooth",{get:()=>n,configurable:true});}if(typeof window<"u"&&!window.beacioIOS){let n=r,i=n?.peripheral||n?.backgroundSync?{peripheral:n.peripheral,backgroundSync:n.backgroundSync,getCapabilities:()=>n?.getCapabilities?.()}:void 0;i&&Object.defineProperty(window,"beacioIOS",{value:Object.freeze(i),writable:false,enumerable:true,configurable:false});}r&&tt(r);return}if(!e.bluetooth){let r=Vt();Object.defineProperty(navigator,"bluetooth",{get:()=>r,configurable:true}),typeof window<"u"&&window.addEventListener(l.EXTENSION_READY,()=>{let n=z();if(!n||n===r)return;let i=navigator.bluetooth;if(i!==void 0&&i!==r)return;let o=nt(ot(n));Object.defineProperty(navigator,"bluetooth",{get:()=>o,configurable:true}),tt(n);},{once:true});}}}Wt();
exports.applyPolyfill=Wt;//# sourceMappingURL=auto.js.map
//# sourceMappingURL=auto.js.map

@@ -1,2 +0,3 @@

import {i,a,b as b$1}from'./chunk-FKTUFPPD.mjs';function f(o){if(typeof navigator>"u"||!navigator.permissions)return;let r=navigator.permissions.query.bind(navigator.permissions);navigator.permissions.query=async function(e){if(e.name!=="bluetooth")return r(e);let n=e.deviceId,t=[];if(typeof o.getDevices=="function")try{let s=await o.getDevices();t=n===void 0?[...s]:s.filter(v=>v.id===n);}catch{t=[];}let i=Object.freeze(t),a=new EventTarget;return Object.create(a,{state:{get:()=>"prompt",enumerable:true},name:{get:()=>"bluetooth",enumerable:true},onchange:{value:null,writable:true,enumerable:true},devices:{get:()=>i,enumerable:true}})};}var d=new Set(["requestDevice","getAvailability","getDevices","referringDevice","onavailabilitychanged","onadvertisementreceived","ongattserverdisconnected","oncharacteristicvaluechanged","onserviceadded","onservicechanged","onserviceremoved","addEventListener","removeEventListener","dispatchEvent"]);function g(o){return o.startsWith("on")}function b(o){class r extends EventTarget{}let e=new r,n=o;for(let t of d){if(g(t)){Object.defineProperty(e,t,{get:()=>n[t]??null,set:a=>{n[t]=a;},enumerable:true,configurable:true});continue}if(t==="referringDevice"){Object.defineProperty(e,t,{get:()=>n[t]??null,enumerable:true,configurable:true});continue}let i=n[t];typeof i=="function"&&Object.defineProperty(e,t,{value:i.bind(o),writable:true,enumerable:true,configurable:true});}return e}function p(){class o extends EventTarget{}let r=new o;Object.defineProperty(r,"requestDevice",{value:async(...e)=>{try{let n=await import('./dist-F6NYWN3W.mjs');typeof n.showInstallBanner=="function"&&n.showInstallBanner();}catch{}throw new DOMException("Web Bluetooth is not supported on this platform. On iOS Safari, install the WebBLE extension. See: https://ioswebble.com","NotFoundError")},writable:true,enumerable:true,configurable:true}),Object.defineProperty(r,"getAvailability",{value:async()=>false,writable:true,enumerable:true,configurable:true}),Object.defineProperty(r,"getDevices",{value:async()=>[],writable:true,enumerable:true,configurable:true}),Object.defineProperty(r,"referringDevice",{get:()=>null,enumerable:true,configurable:true});for(let e of d){if(!g(e))continue;let n=e.slice(2),t=null;Object.defineProperty(r,e,{get:()=>t,set:i=>{t!==null&&r.removeEventListener(n,t),t=typeof i=="function"?i:null,t!==null&&r.addEventListener(n,t);},enumerable:true,configurable:true});}return Object.defineProperty(r,"__webbleCDNStub",{value:true,writable:false,enumerable:false,configurable:true}),r}function w(){if(typeof navigator>"u")return;let o=navigator;if(typeof window<"u"&&!window.BluetoothUUID&&(window.BluetoothUUID=i),typeof window<"u"&&window.isSecureContext===false)return;let r=a();if(r!=="native"){if(r==="safari-extension"){let e=b$1();if(e&&!o.bluetooth){let n=b(e);Object.defineProperty(navigator,"bluetooth",{get:()=>n,configurable:true});}if(typeof window<"u"&&!window.webbleIOS){let n=e?.peripheral||e?.backgroundSync?{peripheral:e.peripheral,backgroundSync:e.backgroundSync,getCapabilities:()=>e.getCapabilities?.()}:void 0;n&&Object.defineProperty(window,"webbleIOS",{value:Object.freeze(n),writable:false,enumerable:true,configurable:false});}e&&f(e);return}if(!o.bluetooth){let e=p();Object.defineProperty(navigator,"bluetooth",{get:()=>e,configurable:true}),typeof window<"u"&&window.addEventListener("webble:extension:ready",()=>{let n=b$1();if(!n||n===e)return;let t=navigator.bluetooth;if(t!==void 0&&t!==e)return;let i=b(n);Object.defineProperty(navigator,"bluetooth",{get:()=>i,configurable:true}),f(n);},{once:true});}}}w();//# sourceMappingURL=auto.mjs.map
import {b,c as c$1,a as a$1}from'./chunk-BSOWECSQ.mjs';import {a}from'./chunk-3BDZNBBD.mjs';import {g as g$1}from'./chunk-33IHM3NV.mjs';function g(e){if(typeof navigator>"u"||!navigator.permissions)return;let r=navigator.permissions.query.bind(navigator.permissions);navigator.permissions.query=async function(t){if(t.name!=="bluetooth")return r(t);let o=t.deviceId,n=[];if(typeof e.getDevices=="function")try{let l=await e.getDevices();n=o===void 0?[...l]:l.filter(B=>B.id===o);}catch{n=[];}let i=Object.freeze(n),a=new EventTarget;return Object.create(a,{state:{get:()=>"prompt",enumerable:true},name:{get:()=>"bluetooth",enumerable:true},onchange:{value:null,writable:true,enumerable:true},devices:{get:()=>i,enumerable:true}})};}var S=new Set(["requestDevice","getAvailability","getDevices","referringDevice","onavailabilitychanged","onadvertisementreceived","ongattserverdisconnected","oncharacteristicvaluechanged","onserviceadded","onservicechanged","onserviceremoved","addEventListener","removeEventListener","dispatchEvent"]);function D(e){return e.startsWith("on")}function p(e){class r extends EventTarget{}let t=new r,o=e;for(let n of S){if(D(n)){Object.defineProperty(t,n,{get:()=>o[n]??null,set:a=>{o[n]=a;},enumerable:true,configurable:true});continue}if(n==="referringDevice"){Object.defineProperty(t,n,{get:()=>o[n]??null,enumerable:true,configurable:true});continue}let i=o[n];typeof i=="function"&&Object.defineProperty(t,n,{value:i.bind(e),writable:true,enumerable:true,configurable:true});}return t}function T(){let e=(typeof window<"u"?window.beacioAutoReconnect:void 0)??{};return {enabled:e.enabled!==false,backoff:{maxAttempts:e.maxAttempts??1/0,initialDelayMs:e.initialDelayMs??1e3,maxDelayMs:e.maxDelayMs??3e4,backoffMultiplier:e.backoffMultiplier??2}}}function c(e,r){let t=new Map;return new Proxy(e,{get(o,n,i){if(typeof n=="string"&&Object.prototype.hasOwnProperty.call(r,n))return r[n];let a=Reflect.get(o,n,i);if(typeof a!="function"||Object.prototype.hasOwnProperty.call(o,n))return a;let s=t.get(n);return s||(s=a.bind(o),t.set(n,s)),s}})}function h(e,r,t){let o=`${r}|${e.uuid}`;return c(e,{startNotifications:async()=>{let n=await e.startNotifications();return t.subscriptions.set(o,{service:r,characteristic:e.uuid}),n},stopNotifications:async()=>{let n=await e.stopNotifications();return t.subscriptions.delete(o),n}})}function m(e,r){return c(e,{getCharacteristic:async t=>{let o=await e.getCharacteristic(t);return h(o,e.uuid,r)},getCharacteristics:async t=>(await e.getCharacteristics(t)).map(n=>h(n,e.uuid,r))})}function j(e,r){return r.server=e,c(e,{getPrimaryService:async t=>{let o=await e.getPrimaryService(t);return m(o,r)},getPrimaryServices:async t=>(await e.getPrimaryServices(t)).map(n=>m(n,r))})}function P(e,r){return c(e,{connect:async()=>{r.intentional=false;let t=await e.connect();return j(t,r)},disconnect:()=>{r.intentional=true,r.subscriptions.clear(),r.server=null,e.disconnect();}})}async function R(e,r){for(let{service:t,characteristic:o}of [...r.subscriptions.values()])try{await(await(await e.getPrimaryService(t)).getCharacteristic(o)).startNotifications();}catch{r.subscriptions.delete(`${t}|${o}`);}}function A(e,r){if(e.reconnecting)return;let t=e.server;if(!t)return;e.reconnecting=true;let o=[...new Set([...e.subscriptions.values()].map(n=>n.service))];(async()=>{let n=r.initialDelayMs;for(let i=1;i<=r.maxAttempts&&!(e.intentional||(await new Promise(a=>setTimeout(a,n)),e.intentional));i+=1)try{let a=t.connectAndDiscover;typeof a=="function"&&o.length>0?await a.call(t,o):await t.connect(),await R(t,e),e.reconnecting=!1;return}catch{n=Math.min(n*r.backoffMultiplier,r.maxDelayMs);}e.reconnecting=false;})();}function O(e,r){if(!e||typeof e.addEventListener!="function")return e;let t={server:null,intentional:false,reconnecting:false,subscriptions:new Map};e.addEventListener("gattserverdisconnected",()=>{if(t.intentional){t.intentional=false;return}A(t,r);});let o;return c(e,{get gatt(){let n=e.gatt;if(n)return o||(o=P(n,t)),o}})}function y(e){let r=T(),t=e;if(!r.enabled||typeof t.requestDevice!="function")return e;let o=t.requestDevice.bind(e);return c(e,{requestDevice:async(...n)=>{let i=await o(...n);return O(i,r.backoff)}})}function U(){class e extends EventTarget{}let r=new e;Object.defineProperty(r,"requestDevice",{value:async(...t)=>{try{let o=await import('./detect/index.mjs');typeof o.showInstallBanner=="function"&&o.showInstallBanner();}catch{}throw new DOMException("Web Bluetooth is not supported on this platform. On iOS Safari, install the Beacio extension. See: https://beacio.com","NotFoundError")},writable:true,enumerable:true,configurable:true}),Object.defineProperty(r,"getAvailability",{value:async()=>false,writable:true,enumerable:true,configurable:true}),Object.defineProperty(r,"getDevices",{value:async()=>[],writable:true,enumerable:true,configurable:true}),Object.defineProperty(r,"referringDevice",{get:()=>null,enumerable:true,configurable:true});for(let t of S){if(!D(t))continue;let o=t.slice(2),n=null;Object.defineProperty(r,t,{get:()=>n,set:i=>{n!==null&&r.removeEventListener(o,n),n=typeof i=="function"?i:null,n!==null&&r.addEventListener(o,n);},enumerable:true,configurable:true});}return Object.defineProperty(r,a$1,{value:true,writable:false,enumerable:false,configurable:true}),r}var w=false;function C(){if(w||typeof navigator>"u")return;w=true;let e=navigator;if(typeof window<"u"&&!window.BluetoothUUID&&(window.BluetoothUUID=g$1),typeof window<"u"&&window.isSecureContext===false)return;let r=b();if(r!=="native"){if(r==="safari-extension"){let t=c$1();if(t&&!e.bluetooth){let o=p(y(t));Object.defineProperty(navigator,"bluetooth",{get:()=>o,configurable:true});}if(typeof window<"u"&&!window.beacioIOS){let o=t,n=o?.peripheral||o?.backgroundSync?{peripheral:o.peripheral,backgroundSync:o.backgroundSync,getCapabilities:()=>o?.getCapabilities?.()}:void 0;n&&Object.defineProperty(window,"beacioIOS",{value:Object.freeze(n),writable:false,enumerable:true,configurable:false});}t&&g(t);return}if(!e.bluetooth){let t=U();Object.defineProperty(navigator,"bluetooth",{get:()=>t,configurable:true}),typeof window<"u"&&window.addEventListener(a.EXTENSION_READY,()=>{let o=c$1();if(!o||o===t)return;let n=navigator.bluetooth;if(n!==void 0&&n!==t)return;let i=p(y(o));Object.defineProperty(navigator,"bluetooth",{get:()=>i,configurable:true}),g(o);},{once:true});}}}C();
export{C as applyPolyfill};//# sourceMappingURL=auto.mjs.map
//# sourceMappingURL=auto.mjs.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/auto.ts"],"names":["patchPermissionsAPI","api","originalQuery","descriptor","requestedDeviceId","devices","granted","device","frozenDevices","target","W3C_BLUETOOTH_MEMBERS","isEventHandlerMember","prop","buildW3CFacade","WebBLEW3CBluetooth","facade","source","member","value","createUnsupportedBluetoothStub","WebBLEUnsupportedBluetooth","stub","_args","detect","eventType","current","next","applyPolyfill","bluetoothNavigator","BluetoothUUID","platform","detectPlatform","getBluetoothAPI","ios","upgraded"],"mappings":"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","file":"auto.mjs","sourcesContent":["/**\n * @beacio/core/auto — Transparent Web Bluetooth polyfill.\n *\n * Usage: import '@beacio/core/auto';\n *\n * - Chrome/Edge (native bluetooth): no-op\n * - Safari iOS (with extension): ensures navigator.bluetooth maps to extension API\n * - Safari iOS (without extension): lazy-loads install prompt on first requestDevice()\n * - Unsupported platforms: no-op (graceful degradation)\n */\n\nimport { detectPlatform, getBluetoothAPI } from './platform';\nimport { BluetoothUUID } from './uuid';\n\n/**\n * Patch navigator.permissions.query to support { name: 'bluetooth' } (§4.1\n * Permission API Integration).\n *\n * Honesty rules (permissions-query-bluetooth-unsupported):\n * - Patched ONLY when an extension-backed bluetooth API actually exists —\n * unsupported platforms keep the browser's native behavior (TypeError on\n * the name, matching Chrome).\n * - `state` is always 'prompt': with a chooser-based UA every new-device\n * access can prompt, so 'granted' is never synthesized.\n * - The §4.1 BluetoothPermissionResult.devices array is backed by the native\n * grant query (getDevices()), filtered by descriptor.deviceId when present.\n * descriptor.filters matching needs advertisement data the page does not\n * have, so filters are ignored (best-effort superset, never an error).\n */\nfunction patchPermissionsAPI(api: { getDevices?: () => Promise<unknown[]> }): void {\n if (typeof navigator === 'undefined' || !navigator.permissions) return;\n\n const originalQuery = navigator.permissions.query.bind(navigator.permissions);\n navigator.permissions.query = async function (\n descriptor: PermissionDescriptor\n ): Promise<PermissionStatus> {\n if ((descriptor as any).name !== 'bluetooth') {\n return originalQuery(descriptor);\n }\n\n const requestedDeviceId = (descriptor as { deviceId?: string }).deviceId;\n let devices: unknown[] = [];\n if (typeof api.getDevices === 'function') {\n try {\n const granted = await api.getDevices();\n devices = requestedDeviceId === undefined\n ? [...granted]\n : granted.filter((device) => (device as { id?: string }).id === requestedDeviceId);\n } catch {\n devices = [];\n }\n }\n const frozenDevices = Object.freeze(devices);\n\n const target = new EventTarget();\n const status = Object.create(target, {\n state: { get: () => 'prompt' as PermissionState, enumerable: true },\n name: { get: () => 'bluetooth', enumerable: true },\n onchange: { value: null, writable: true, enumerable: true },\n devices: { get: () => frozenDevices, enumerable: true },\n }) as PermissionStatus;\n return status;\n };\n}\n\n/**\n * Members allowed on the polyfilled `navigator.bluetooth`. Everything else\n * (peripheral, backgroundSync, getCapabilities, debug, __webble, etc.) is\n * filtered out so the polyfill surface matches the W3C Web Bluetooth spec\n * exactly. iOS-specific capabilities are reached via `window.webbleIOS`.\n */\nconst W3C_BLUETOOTH_MEMBERS: ReadonlySet<string> = new Set([\n // Bluetooth interface (spec §4)\n 'requestDevice',\n 'getAvailability',\n 'getDevices',\n // §4 \"[SameObject] readonly attribute BluetoothDevice? referringDevice\" —\n // constant null on iOS (no referring-device navigation mechanism exists),\n // but the attribute must be present and read null, never undefined.\n 'referringDevice',\n // §6.6.6 IDL event handlers — Bluetooth includes\n // BluetoothDeviceEventHandlers, CharacteristicEventHandlers AND\n // ServiceEventHandlers, so all mixin onX attributes are part of the\n // standard surface (bubbled §6.6.1 tree events are handled at the root).\n 'onavailabilitychanged',\n 'onadvertisementreceived',\n 'ongattserverdisconnected',\n 'oncharacteristicvaluechanged',\n 'onserviceadded',\n 'onservicechanged',\n 'onserviceremoved',\n // EventTarget\n 'addEventListener',\n 'removeEventListener',\n 'dispatchEvent',\n]);\n\n/**\n * Event-handler IDL attributes must round-trip with identity (the getter\n * returns the exact function assigned) and their accessors must run against\n * the real Bluetooth instance — never bind them and never route their\n * get/set through the proxy receiver.\n */\nfunction isEventHandlerMember(prop: string): boolean {\n return prop.startsWith('on');\n}\n\n/**\n * W3C facade over the live vendor API.\n *\n * A PLAIN EventTarget-derived object instead of a Proxy\n * (facade-proxy-violates-essential-invariants): the injected vendor object\n * carries NON-CONFIGURABLE own properties (`debug`, `__webble`), and any\n * Proxy whose traps hide non-configurable target properties throws\n * TypeError during ordinary introspection (Object.keys, spread, `in`,\n * JSON.stringify). A plain object satisfies every ES invariant by\n * construction — vendor members are simply absent.\n *\n * - Methods are bound ONCE at build time → stable identities ([SameObject]\n * method-identity half of navigator-bluetooth-getter-new-proxy-per-access).\n * - onX EventHandler attributes forward get/set to the live instance so the\n * accessor runs against its real private storage and assignment reads back\n * (proxy-receiver-breaks-handler-attribute-readback).\n * - referringDevice forwards (defaulting to null per §4) and stays readonly.\n * - Expando writes behave like any plain platform object wrapper and never\n * reach the vendor surface.\n */\nfunction buildW3CFacade(api: object): object {\n class WebBLEW3CBluetooth extends EventTarget {}\n const facade = new WebBLEW3CBluetooth();\n const source = api as Record<string, unknown>;\n\n for (const member of W3C_BLUETOOTH_MEMBERS) {\n if (isEventHandlerMember(member)) {\n Object.defineProperty(facade, member, {\n get: () => (source[member] as unknown) ?? null,\n set: (value) => { source[member] = value; },\n enumerable: true,\n configurable: true,\n });\n continue;\n }\n if (member === 'referringDevice') {\n Object.defineProperty(facade, member, {\n get: () => (source[member] as unknown) ?? null,\n enumerable: true,\n configurable: true,\n });\n continue;\n }\n const value = source[member];\n if (typeof value === 'function') {\n Object.defineProperty(facade, member, {\n value: (value as (...args: unknown[]) => unknown).bind(api),\n writable: true,\n enumerable: true,\n configurable: true,\n });\n }\n // Members the live API does not implement as functions (e.g. EventTarget\n // methods on a partial mock) fall through to the facade's own inherited\n // EventTarget implementation.\n }\n return facade;\n}\n\n/**\n * §4 IDL-shaped fallback for platforms with no Web Bluetooth support\n * (unsupported-platform-stub-shape-nonconformant): a real EventTarget with\n * the full Bluetooth member set. requestDevice rejects with a NotFoundError\n * DOMException (never a plain Error), getAvailability resolves false,\n * getDevices resolves [], referringDevice is null.\n */\nfunction createUnsupportedBluetoothStub(): object {\n class WebBLEUnsupportedBluetooth extends EventTarget {}\n const stub = new WebBLEUnsupportedBluetooth();\n\n Object.defineProperty(stub, 'requestDevice', {\n value: async (..._args: unknown[]) => {\n // Attempt dynamic import of @beacio/detect for install banner\n try {\n const detect = await import('@beacio/detect');\n if (typeof detect.showInstallBanner === 'function') {\n detect.showInstallBanner();\n }\n } catch {\n // @beacio/detect not installed — reject with the guidance below\n }\n // §4 requestDevice: when no device/chooser can ever match, the spec\n // rejection class is NotFoundError — never a plain Error.\n throw new DOMException(\n 'Web Bluetooth is not supported on this platform. ' +\n 'On iOS Safari, install the WebBLE extension. ' +\n 'See: https://ioswebble.com',\n 'NotFoundError'\n );\n },\n writable: true,\n enumerable: true,\n configurable: true,\n });\n Object.defineProperty(stub, 'getAvailability', {\n value: async () => false,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n Object.defineProperty(stub, 'getDevices', {\n value: async () => [],\n writable: true,\n enumerable: true,\n configurable: true,\n });\n Object.defineProperty(stub, 'referringDevice', {\n get: () => null,\n enumerable: true,\n configurable: true,\n });\n\n for (const member of W3C_BLUETOOTH_MEMBERS) {\n if (!isEventHandlerMember(member)) continue;\n const eventType = member.slice(2);\n let current: EventListener | null = null;\n Object.defineProperty(stub, member, {\n get: () => current,\n set: (next: unknown) => {\n if (current !== null) stub.removeEventListener(eventType, current);\n current = typeof next === 'function' ? (next as EventListener) : null;\n if (current !== null) stub.addEventListener(eventType, current);\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n // Marker so detectPlatform()/getBluetoothAPI() never mistake our own stub\n // for a native implementation (same convention as the CDN stubs).\n Object.defineProperty(stub, '__webbleCDNStub', {\n value: true,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n return stub;\n}\n\nfunction applyPolyfill(): void {\n if (typeof navigator === 'undefined') return;\n\n const bluetoothNavigator = navigator as Navigator & {\n bluetooth?: Bluetooth;\n };\n\n // Expose BluetoothUUID global (spec §4) on all platforms\n if (typeof window !== 'undefined' && !(window as any).BluetoothUUID) {\n (window as any).BluetoothUUID = BluetoothUUID;\n }\n\n // §10 [SecureContext] (polyfill-installs-in-insecure-contexts): the spec\n // marks `Navigator.bluetooth` [SecureContext], so plain-http pages must\n // never get the attribute — not even the throwing \"unsupported\" stub — and\n // navigator.permissions must stay unpatched. BluetoothUUID (above) is a\n // plain global and may stay. `=== false` keeps SSR/legacy environments that\n // do not implement isSecureContext on their previous behavior.\n if (typeof window !== 'undefined' && window.isSecureContext === false) {\n return;\n }\n\n const platform = detectPlatform();\n\n if (platform === 'native') {\n // Chrome, Edge, etc. — native Web Bluetooth already works\n return;\n }\n\n if (platform === 'safari-extension') {\n // Extension provides the full vendor surface on navigator.webble. We expose\n // two distinct facades here:\n // 1. navigator.bluetooth — W3C-only proxy (requestDevice, getAvailability,\n // getDevices, onavailabilitychanged, EventTarget). Non-standard iOS\n // members (peripheral, backgroundSync, getCapabilities) are hidden so\n // portable code matches Chrome/Edge exactly.\n // 2. window.webbleIOS — vendor-prefixed iOS capabilities. The extension\n // already mounts this; we only mirror when missing (e.g. if the\n // polyfill loads in a context where it wasn't mounted).\n const api = getBluetoothAPI();\n if (api && !bluetoothNavigator.bluetooth) {\n // [SameObject] (navigator-bluetooth-getter-new-proxy-per-access): build\n // the facade ONCE — every access returns the identical object with\n // stable method identities.\n const facade = buildW3CFacade(api);\n Object.defineProperty(navigator, 'bluetooth', {\n get: () => facade,\n configurable: true,\n });\n }\n if (typeof window !== 'undefined' && !(window as any).webbleIOS) {\n const ios = (api as any)?.peripheral || (api as any)?.backgroundSync\n ? { peripheral: (api as any).peripheral, backgroundSync: (api as any).backgroundSync, getCapabilities: () => (api as any).getCapabilities?.() }\n : undefined;\n if (ios) {\n Object.defineProperty(window, 'webbleIOS', {\n value: Object.freeze(ios), writable: false, enumerable: true, configurable: false,\n });\n }\n }\n // Permissions API (§4.1): extension active — honest shim backed by the\n // native grant query. State is 'prompt' (never synthetic 'granted').\n if (api) {\n patchPermissionsAPI(api as { getDevices?: () => Promise<unknown[]> });\n }\n return;\n }\n\n // Unsupported or Safari without extension — install the §4 IDL-shaped stub\n // (unsupported-platform-stub-shape-nonconformant).\n // navigator.permissions is intentionally NOT patched here: with no working\n // bluetooth API behind it, a synthetic PermissionStatus would be a lie —\n // the browser's native TypeError on the name matches Chrome's behavior.\n if (!bluetoothNavigator.bluetooth) {\n // [SameObject]: one stub for the page's lifetime.\n const stub = createUnsupportedBluetoothStub();\n Object.defineProperty(navigator, 'bluetooth', {\n get: () => stub,\n configurable: true,\n });\n\n // §10 (api-unavailable-at-document-start): this one-shot probe can lose\n // the race against the extension's injected script — the throwing\n // \"unsupported\" stub must not stay installed forever on a page where the\n // extension comes up moments later. Re-bind deterministically on the\n // extension's ready signal.\n if (typeof window !== 'undefined') {\n window.addEventListener('webble:extension:ready', () => {\n const api = getBluetoothAPI();\n if (!api || (api as unknown) === stub) return;\n const current = (navigator as { bluetooth?: unknown }).bluetooth;\n if (current !== undefined && current !== stub) return; // page/native owns it now\n const upgraded = buildW3CFacade(api);\n Object.defineProperty(navigator, 'bluetooth', {\n get: () => upgraded,\n configurable: true,\n });\n // Extension active — the honest §4.1 permissions shim now applies.\n patchPermissionsAPI(api as { getDevices?: () => Promise<unknown[]> });\n }, { once: true });\n }\n }\n}\n\napplyPolyfill();\n"]}
{"version":3,"sources":["../src/auto.ts"],"names":["patchPermissionsAPI","api","originalQuery","descriptor","requestedDeviceId","devices","granted","device","frozenDevices","target","W3C_BLUETOOTH_MEMBERS","isEventHandlerMember","prop","buildW3CFacade","BeacioW3CBluetooth","facade","source","member","value","resolveAutoReconnectConfig","raw","forwardingProxy","overrides","boundCache","obj","receiver","bound","superviseCharacteristic","characteristic","serviceUuid","state","key","result","superviseService","service","uuid","c","superviseServer","server","s","superviseGatt","gatt","recoverSubscriptions","startReconnectLoop","backoff","serviceUUIDs","delay","attempt","resolve","fastPath","superviseDevice","supervisedGatt","withAutoReconnect","config","originalRequestDevice","args","createUnsupportedBluetoothStub","BeacioUnsupportedBluetooth","stub","_args","detect","eventType","current","next","CDN_STUB_MARKER","polyfillApplied","applyPolyfill","bluetoothNavigator","BluetoothUUID","platform","detectPlatform","getBluetoothAPI","apiRec","ios","BEACIO_EVENTS","upgraded"],"mappings":"wIA+BA,SAASA,CAAAA,CAAoBC,CAAAA,CAA8D,CACzF,GAAI,OAAO,SAAA,CAAc,GAAA,EAAe,CAAC,SAAA,CAAU,YAAa,OAEhE,IAAMC,CAAAA,CAAgB,SAAA,CAAU,YAAY,KAAA,CAAM,IAAA,CAAK,SAAA,CAAU,WAAW,EAC5E,SAAA,CAAU,WAAA,CAAY,KAAA,CAAQ,eAC5BC,EAC2B,CAC3B,GAAKA,CAAAA,CAAgC,IAAA,GAAS,YAC5C,OAAOD,CAAAA,CAAcC,CAAU,CAAA,CAGjC,IAAMC,CAAAA,CAAqBD,CAAAA,CAAqC,QAAA,CAC5DE,CAAAA,CAA6B,EAAC,CAClC,GAAI,OAAOJ,EAAI,UAAA,EAAe,UAAA,CAC5B,GAAI,CACF,IAAMK,CAAAA,CAAU,MAAML,CAAAA,CAAI,UAAA,GAC1BI,CAAAA,CAAUD,CAAAA,GAAsB,KAAA,CAAA,CAC5B,CAAC,GAAGE,CAAO,CAAA,CACXA,CAAAA,CAAQ,MAAA,CAAQC,GAAYA,CAAAA,CAA2B,EAAA,GAAOH,CAAiB,EACrF,MAAQ,CACNC,CAAAA,CAAU,GACZ,CAEF,IAAMG,CAAAA,CAAgB,MAAA,CAAO,MAAA,CAAOH,CAAO,CAAA,CAErCI,CAAAA,CAAS,IAAI,YAOnB,OANe,MAAA,CAAO,MAAA,CAAOA,CAAAA,CAAQ,CACnC,KAAA,CAAO,CAAE,GAAA,CAAK,IAAM,SAA6B,UAAA,CAAY,IAAK,CAAA,CAClE,IAAA,CAAM,CAAE,GAAA,CAAK,IAAM,WAAA,CAAa,UAAA,CAAY,IAAK,CAAA,CACjD,QAAA,CAAU,CAAE,KAAA,CAAO,KAAM,QAAA,CAAU,IAAA,CAAM,UAAA,CAAY,IAAK,EAC1D,OAAA,CAAS,CAAE,GAAA,CAAK,IAAMD,EAAe,UAAA,CAAY,IAAK,CACxD,CAAC,CAEH,EACF,CAQA,IAAME,CAAAA,CAA6C,IAAI,GAAA,CAAI,CAEzD,eAAA,CACA,iBAAA,CACA,aAIA,iBAAA,CAKA,uBAAA,CACA,yBAAA,CACA,0BAAA,CACA,+BACA,gBAAA,CACA,kBAAA,CACA,kBAAA,CAEA,kBAAA,CACA,sBACA,eACF,CAAC,CAAA,CAQD,SAASC,EAAqBC,CAAAA,CAAuB,CACnD,OAAOA,CAAAA,CAAK,WAAW,IAAI,CAC7B,CAsBA,SAASC,CAAAA,CAAeZ,CAAAA,CAAqB,CAC3C,MAAMa,UAA2B,WAAY,EAC7C,IAAMC,EAAS,IAAID,CAAAA,CACbE,CAAAA,CAASf,CAAAA,CAEf,QAAWgB,CAAAA,IAAUP,CAAAA,CAAuB,CAC1C,GAAIC,EAAqBM,CAAM,CAAA,CAAG,CAChC,MAAA,CAAO,eAAeF,CAAAA,CAAQE,CAAAA,CAAQ,CACpC,GAAA,CAAK,IAAOD,CAAAA,CAAOC,CAAM,CAAA,EAAuB,IAAA,CAChD,IAAMC,CAAAA,EAAU,CAAEF,CAAAA,CAAOC,CAAM,EAAIC,EAAO,CAAA,CAC1C,UAAA,CAAY,IAAA,CACZ,aAAc,IAChB,CAAC,CAAA,CACD,QACF,CACA,GAAID,CAAAA,GAAW,iBAAA,CAAmB,CAChC,OAAO,cAAA,CAAeF,CAAAA,CAAQE,CAAAA,CAAQ,CACpC,IAAK,IAAOD,CAAAA,CAAOC,CAAM,CAAA,EAAgC,KACzD,UAAA,CAAY,IAAA,CACZ,YAAA,CAAc,IAChB,CAAC,CAAA,CACD,QACF,CACA,IAAMC,EAAQF,CAAAA,CAAOC,CAAM,CAAA,CACvB,OAAOC,CAAAA,EAAU,UAAA,EACnB,MAAA,CAAO,cAAA,CAAeH,EAAQE,CAAAA,CAAQ,CACpC,KAAA,CAAQC,CAAAA,CAAwC,KAAKjB,CAAG,CAAA,CACxD,QAAA,CAAU,IAAA,CACV,WAAY,IAAA,CACZ,YAAA,CAAc,IAChB,CAAC,EAKL,CACA,OAAOc,CACT,CAqCA,SAASI,CAAAA,EAA6E,CACpF,IAAMC,CAAAA,CAAAA,CAAO,OAAO,MAAA,CAAW,GAAA,CAC1B,MAAA,CAA4D,mBAAA,CAC7D,SAAc,EAAC,CACnB,OAAO,CACL,QAASA,CAAAA,CAAI,OAAA,GAAY,KAAA,CACzB,OAAA,CAAS,CACP,WAAA,CAAaA,CAAAA,CAAI,WAAA,EAAe,CAAA,CAAA,CAAA,CAChC,eAAgBA,CAAAA,CAAI,cAAA,EAAkB,GAAA,CACtC,UAAA,CAAYA,EAAI,UAAA,EAAc,GAAA,CAC9B,iBAAA,CAAmBA,CAAAA,CAAI,mBAAqB,CAC9C,CACF,CACF,CA2BA,SAASC,CAAAA,CAAkCZ,CAAAA,CAAWa,CAAAA,CAAyC,CAQ7F,IAAMC,CAAAA,CAAa,IAAI,GAAA,CACvB,OAAO,IAAI,KAAA,CAAMd,CAAAA,CAAQ,CACvB,GAAA,CAAIe,CAAAA,CAAKZ,CAAAA,CAAMa,CAAAA,CAAU,CACvB,GAAI,OAAOb,CAAAA,EAAS,QAAA,EAAY,MAAA,CAAO,UAAU,cAAA,CAAe,IAAA,CAAKU,CAAAA,CAAWV,CAAI,EAClF,OAAOU,CAAAA,CAAUV,CAAI,CAAA,CAEvB,IAAMM,CAAAA,CAAQ,OAAA,CAAQ,GAAA,CAAIM,CAAAA,CAAKZ,EAAMa,CAAQ,CAAA,CAE7C,GADI,OAAOP,GAAU,UAAA,EACjB,MAAA,CAAO,SAAA,CAAU,cAAA,CAAe,KAAKM,CAAAA,CAAKZ,CAAI,CAAA,CAAG,OAAOM,CAAAA,CAC5D,IAAIQ,CAAAA,CAAQH,CAAAA,CAAW,IAAIX,CAAI,CAAA,CAC/B,OAAKc,CAAAA,GACHA,EAAQR,CAAAA,CAAM,IAAA,CAAKM,CAAG,CAAA,CACtBD,EAAW,GAAA,CAAIX,CAAAA,CAAMc,CAAK,CAAA,CAAA,CAErBA,CACT,CACF,CAAC,CACH,CAGA,SAASC,CAAAA,CACPC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACmC,CACnC,IAAMC,CAAAA,CAAM,CAAA,EAAGF,CAAW,IAAID,CAAAA,CAAe,IAAI,CAAA,CAAA,CACjD,OAAOP,CAAAA,CAAgBO,CAAAA,CAAgB,CACrC,kBAAA,CAAoB,SAAY,CAC9B,IAAMI,CAAAA,CAAS,MAAMJ,EAAe,kBAAA,EAAmB,CACvD,OAAAE,CAAAA,CAAM,cAAc,GAAA,CAAIC,CAAAA,CAAK,CAAE,OAAA,CAASF,EAAa,cAAA,CAAgBD,CAAAA,CAAe,IAAK,CAAC,EACnFI,CACT,CAAA,CACA,iBAAA,CAAmB,SAAY,CAC7B,IAAMA,CAAAA,CAAS,MAAMJ,CAAAA,CAAe,mBAAkB,CACtD,OAAAE,CAAAA,CAAM,aAAA,CAAc,OAAOC,CAAG,CAAA,CACvBC,CACT,CACF,CAAC,CACH,CAGA,SAASC,CAAAA,CACPC,EACAJ,CAAAA,CAC4B,CAC5B,OAAOT,CAAAA,CAAgBa,EAAS,CAC9B,iBAAA,CAAmB,MAAOC,CAAAA,EAAsC,CAC9D,IAAMP,CAAAA,CAAiB,MAAMM,CAAAA,CAAQ,kBAAkBC,CAAI,CAAA,CAC3D,OAAOR,CAAAA,CAAwBC,EAAgBM,CAAAA,CAAQ,IAAA,CAAMJ,CAAK,CACpE,EACA,kBAAA,CAAoB,MAAOK,CAAAA,EAAAA,CACD,MAAOD,CAAAA,CAE5B,kBAAA,CAAmBC,CAAI,CAAA,EACH,IAAKC,CAAAA,EAAMT,CAAAA,CAAwBS,CAAAA,CAAGF,CAAAA,CAAQ,KAAMJ,CAAK,CAAC,CAErF,CAAC,CACH,CAGA,SAASO,CAAAA,CACPC,CAAAA,CACAR,EAC2B,CAC3B,OAAAA,CAAAA,CAAM,MAAA,CAASQ,EACRjB,CAAAA,CAAgBiB,CAAAA,CAAQ,CAC7B,iBAAA,CAAmB,MAAOH,CAAAA,EAA+B,CACvD,IAAMD,CAAAA,CAAU,MAAMI,CAAAA,CAAO,iBAAA,CAAkBH,CAAI,CAAA,CACnD,OAAOF,CAAAA,CAAiBC,CAAAA,CAASJ,CAAK,CACxC,EACA,kBAAA,CAAoB,MAAOK,CAAAA,EAAAA,CACR,MAAMG,EAAO,kBAAA,CAAmBH,CAAI,CAAA,EACrC,GAAA,CAAKI,GAAMN,CAAAA,CAAiBM,CAAAA,CAAGT,CAAK,CAAC,CAEzD,CAAC,CACH,CAGA,SAASU,EACPC,CAAAA,CACAX,CAAAA,CAC2B,CAC3B,OAAOT,EAAgBoB,CAAAA,CAAM,CAC3B,OAAA,CAAS,SAAY,CACnBX,CAAAA,CAAM,WAAA,CAAc,KAAA,CACpB,IAAMQ,CAAAA,CAAS,MAAMG,CAAAA,CAAK,OAAA,GAC1B,OAAOJ,CAAAA,CAAgBC,CAAAA,CAAQR,CAAK,CACtC,CAAA,CACA,UAAA,CAAY,IAAM,CAGhBA,EAAM,WAAA,CAAc,IAAA,CACpBA,CAAAA,CAAM,aAAA,CAAc,OAAM,CAC1BA,CAAAA,CAAM,MAAA,CAAS,IAAA,CACfW,EAAK,UAAA,GACP,CACF,CAAC,CACH,CAGA,eAAeC,CAAAA,CACbJ,CAAAA,CACAR,EACe,CACf,IAAA,GAAW,CAAE,OAAA,CAAAI,EAAS,cAAA,CAAAN,CAAe,CAAA,GAAK,CAAC,GAAGE,CAAAA,CAAM,aAAA,CAAc,MAAA,EAAQ,EACxE,GAAI,CAGF,KAAA,CADW,KAAA,CADC,MAAMQ,CAAAA,CAAO,iBAAA,CAAkBJ,CAAO,CAAA,EAC7B,kBAAkBN,CAAc,CAAA,EAC5C,kBAAA,GACX,MAAQ,CAINE,CAAAA,CAAM,aAAA,CAAc,MAAA,CAAO,GAAGI,CAAO,CAAA,CAAA,EAAIN,CAAc,CAAA,CAAE,EAC3D,CAEJ,CAGA,SAASe,CAAAA,CAAmBb,CAAAA,CAAwBc,CAAAA,CAAgC,CAClF,GAAId,EAAM,YAAA,CAAc,OACxB,IAAMQ,CAAAA,CAASR,EAAM,MAAA,CACrB,GAAI,CAACQ,CAAAA,CAAQ,OACbR,CAAAA,CAAM,YAAA,CAAe,IAAA,CAGrB,IAAMe,EAAe,CAAC,GAAG,IAAI,GAAA,CAAI,CAAC,GAAGf,CAAAA,CAAM,aAAA,CAAc,MAAA,EAAQ,CAAA,CAAE,GAAA,CAAKS,CAAAA,EAAMA,CAAAA,CAAE,OAAO,CAAC,CAAC,CAAA,CAAA,CAEnF,SAAY,CAChB,IAAIO,CAAAA,CAAQF,CAAAA,CAAQ,eACpB,IAAA,IAASG,CAAAA,CAAU,CAAA,CAAGA,CAAAA,EAAWH,EAAQ,WAAA,EACnC,EAAAd,CAAAA,CAAM,WAAA,GACV,MAAM,IAAI,OAAA,CAAekB,CAAAA,EAAY,UAAA,CAAWA,EAASF,CAAK,CAAC,CAAA,CAC3DhB,CAAAA,CAAM,cAH0CiB,CAAAA,EAAW,CAAA,CAI/D,GAAI,CACF,IAAME,CAAAA,CAAYX,CAAAA,CAEf,kBAAA,CACC,OAAOW,GAAa,UAAA,EAAcJ,CAAAA,CAAa,MAAA,CAAS,CAAA,CAG1D,MAAMI,CAAAA,CAAS,IAAA,CAAKX,CAAAA,CAAQO,CAAY,CAAA,CAExC,MAAMP,CAAAA,CAAO,OAAA,GAEf,MAAMI,CAAAA,CAAqBJ,CAAAA,CAAQR,CAAK,EACxCA,CAAAA,CAAM,YAAA,CAAe,CAAA,CAAA,CACrB,MACF,MAAQ,CACNgB,CAAAA,CAAQ,IAAA,CAAK,GAAA,CAAIA,EAAQF,CAAAA,CAAQ,iBAAA,CAAmBA,CAAAA,CAAQ,UAAU,EACxE,CAEFd,CAAAA,CAAM,YAAA,CAAe,MACvB,KACF,CAOA,SAASoB,CAAAA,CAAgB3C,EAAyBqC,CAAAA,CAA2C,CAC3F,GAAI,CAACrC,GAAU,OAAOA,CAAAA,CAAO,gBAAA,EAAqB,UAAA,CAAY,OAAOA,CAAAA,CAErE,IAAMuB,CAAAA,CAAyB,CAC7B,OAAQ,IAAA,CACR,WAAA,CAAa,KAAA,CACb,YAAA,CAAc,MACd,aAAA,CAAe,IAAI,GACrB,CAAA,CAIAvB,EAAO,gBAAA,CAAiB,wBAAA,CAA0B,IAAM,CACtD,GAAIuB,CAAAA,CAAM,WAAA,CAAa,CACrBA,CAAAA,CAAM,YAAc,KAAA,CACpB,MACF,CACAa,CAAAA,CAAmBb,CAAAA,CAAOc,CAAO,EACnC,CAAC,EAED,IAAIO,CAAAA,CACJ,OAAO9B,CAAAA,CAAgBd,EAAQ,CAC7B,IAAI,IAAA,EAA8C,CAChD,IAAMa,CAAAA,CAAMb,CAAAA,CAAO,IAAA,CACnB,GAAKa,EAGL,OAAK+B,CAAAA,GAAgBA,CAAAA,CAAiBX,CAAAA,CAAcpB,EAAKU,CAAK,CAAA,CAAA,CACvDqB,CACT,CACF,CAA8B,CAChC,CAOA,SAASC,CAAAA,CAAkBnD,EAAqB,CAC9C,IAAMoD,CAAAA,CAASlC,CAAAA,GACTH,CAAAA,CAASf,CAAAA,CACf,GAAI,CAACoD,EAAO,OAAA,EAAW,OAAOrC,CAAAA,CAAO,aAAA,EAAkB,WAAY,OAAOf,CAAAA,CAE1E,IAAMqD,CAAAA,CAAwBtC,EAAO,aAAA,CAAc,IAAA,CAAKf,CAAG,CAAA,CAC3D,OAAOoB,CAAAA,CAAgBpB,CAAAA,CAAK,CAC1B,aAAA,CAAe,SAAUsD,CAAAA,GAAiC,CACxD,IAAMhD,CAAAA,CAAS,MAAM+C,CAAAA,CAAsB,GAAGC,CAAI,CAAA,CAClD,OAAOL,CAAAA,CAAgB3C,CAAAA,CAAQ8C,CAAAA,CAAO,OAAO,CAC/C,CACF,CAAC,CACH,CASA,SAASG,CAAAA,EAAyC,CAChD,MAAMC,UAAmC,WAAY,EACrD,IAAMC,EAAO,IAAID,CAAAA,CAEjB,MAAA,CAAO,cAAA,CAAeC,EAAM,eAAA,CAAiB,CAC3C,KAAA,CAAO,MAAA,GAAUC,IAAkC,CAWjD,GAAI,CACF,IAAMC,EAAS,MAAM,OAAO,oBAAU,CAAA,CAClC,OAAOA,CAAAA,CAAO,iBAAA,EAAsB,UAAA,EACtCA,CAAAA,CAAO,oBAEX,CAAA,KAAQ,CAER,CAGA,MAAM,IAAI,YAAA,CACR,uHAAA,CAGA,eACF,CACF,CAAA,CACA,QAAA,CAAU,IAAA,CACV,UAAA,CAAY,KACZ,YAAA,CAAc,IAChB,CAAC,CAAA,CACD,OAAO,cAAA,CAAeF,CAAAA,CAAM,iBAAA,CAAmB,CAC7C,MAAO,SAAY,KAAA,CACnB,QAAA,CAAU,IAAA,CACV,WAAY,IAAA,CACZ,YAAA,CAAc,IAChB,CAAC,EACD,MAAA,CAAO,cAAA,CAAeA,CAAAA,CAAM,YAAA,CAAc,CACxC,KAAA,CAAO,SAAY,GACnB,QAAA,CAAU,IAAA,CACV,UAAA,CAAY,IAAA,CACZ,aAAc,IAChB,CAAC,CAAA,CACD,MAAA,CAAO,eAAeA,CAAAA,CAAM,iBAAA,CAAmB,CAC7C,GAAA,CAAK,IAAM,IAAA,CACX,UAAA,CAAY,IAAA,CACZ,YAAA,CAAc,IAChB,CAAC,CAAA,CAED,IAAA,IAAWzC,CAAAA,IAAUP,EAAuB,CAC1C,GAAI,CAACC,CAAAA,CAAqBM,CAAM,CAAA,CAAG,SACnC,IAAM4C,CAAAA,CAAY5C,EAAO,KAAA,CAAM,CAAC,CAAA,CAC5B6C,CAAAA,CAAgC,KACpC,MAAA,CAAO,cAAA,CAAeJ,CAAAA,CAAMzC,CAAAA,CAAQ,CAClC,GAAA,CAAK,IAAM6C,CAAAA,CACX,GAAA,CAAMC,GAA+B,CAC/BD,CAAAA,GAAY,IAAA,EAAMJ,CAAAA,CAAK,oBAAoBG,CAAAA,CAAWC,CAAO,CAAA,CACjEA,CAAAA,CAAU,OAAOC,CAAAA,EAAS,UAAA,CAAcA,CAAAA,CAAyB,IAAA,CAC7DD,IAAY,IAAA,EAAMJ,CAAAA,CAAK,gBAAA,CAAiBG,CAAAA,CAAWC,CAAO,EAChE,CAAA,CACA,UAAA,CAAY,IAAA,CACZ,YAAA,CAAc,IAChB,CAAC,EACH,CAKA,OAAA,MAAA,CAAO,cAAA,CAAeJ,CAAAA,CAAMM,GAAAA,CAAiB,CAC3C,KAAA,CAAO,IAAA,CACP,QAAA,CAAU,KAAA,CACV,WAAY,KAAA,CACZ,YAAA,CAAc,IAChB,CAAC,EACMN,CACT,CAEA,IAAIO,CAAAA,CAAkB,MAYf,SAASC,CAAAA,EAAsB,CAEpC,GADID,GACA,OAAO,SAAA,CAAc,GAAA,CAAa,OACtCA,EAAkB,IAAA,CAElB,IAAME,CAAAA,CAAqB,SAAA,CAe3B,GAVI,OAAO,MAAA,CAAW,GAAA,EAAe,CAAE,OAAsC,aAAA,GAC1E,MAAA,CAAsC,aAAA,CAAgBC,GAAAA,CAAAA,CASrD,OAAO,MAAA,CAAW,GAAA,EAAe,MAAA,CAAO,eAAA,GAAoB,MAC9D,OAGF,IAAMC,CAAAA,CAAWC,CAAAA,GAEjB,GAAID,CAAAA,GAAa,QAAA,CAKjB,CAAA,GAAIA,IAAa,kBAAA,CAAoB,CAUnC,IAAMpE,CAAAA,CAAMsE,KAAgB,CAC5B,GAAItE,CAAAA,EAAO,CAACkE,EAAmB,SAAA,CAAW,CAOxC,IAAMpD,CAAAA,CAASF,CAAAA,CAAeuC,CAAAA,CAAkBnD,CAAG,CAAC,EACpD,MAAA,CAAO,cAAA,CAAe,SAAA,CAAW,WAAA,CAAa,CAC5C,GAAA,CAAK,IAAMc,CAAAA,CACX,YAAA,CAAc,IAChB,CAAC,EACH,CACA,GAAI,OAAO,MAAA,CAAW,GAAA,EAAe,CAAE,MAAA,CAAkC,UAAW,CAClF,IAAMyD,CAAAA,CAASvE,CAAAA,CACTwE,EAAMD,CAAAA,EAAQ,UAAA,EAAcA,CAAAA,EAAQ,cAAA,CACtC,CAAE,UAAA,CAAYA,CAAAA,CAAO,UAAA,CAAY,cAAA,CAAgBA,EAAO,cAAA,CAAgB,eAAA,CAAiB,IAAMA,CAAAA,EAAQ,mBAAoB,CAAA,CAC3H,MAAA,CACAC,CAAAA,EACF,OAAO,cAAA,CAAe,MAAA,CAAQ,WAAA,CAAa,CACzC,MAAO,MAAA,CAAO,MAAA,CAAOA,CAAuF,CAAA,CAAG,SAAU,KAAA,CAAO,UAAA,CAAY,IAAA,CAAM,YAAA,CAAc,KAClK,CAAC,EAEL,CAGIxE,CAAAA,EACFD,EAAoBC,CAAwD,CAAA,CAE9E,MACF,CAOA,GAAI,CAACkE,CAAAA,CAAmB,SAAA,CAAW,CAEjC,IAAMT,CAAAA,CAAOF,CAAAA,EAA+B,CAC5C,OAAO,cAAA,CAAe,SAAA,CAAW,WAAA,CAAa,CAC5C,IAAK,IAAME,CAAAA,CACX,YAAA,CAAc,IAChB,CAAC,CAAA,CAOG,OAAO,MAAA,CAAW,GAAA,EACpB,OAAO,gBAAA,CAAiBgB,CAAAA,CAAc,eAAA,CAAiB,IAAM,CAC3D,IAAMzE,CAAAA,CAAMsE,GAAAA,EAAgB,CAC5B,GAAI,CAACtE,CAAAA,EAAQA,CAAAA,GAAmByD,CAAAA,CAAM,OACtC,IAAMI,CAAAA,CAAW,SAAA,CAAqC,SAAA,CACtD,GAAIA,CAAAA,GAAY,MAAA,EAAaA,CAAAA,GAAYJ,CAAAA,CAAM,OAC/C,IAAMiB,CAAAA,CAAW9D,CAAAA,CAAeuC,CAAAA,CAAkBnD,CAAG,CAAC,CAAA,CACtD,MAAA,CAAO,cAAA,CAAe,UAAW,WAAA,CAAa,CAC5C,GAAA,CAAK,IAAM0E,EACX,YAAA,CAAc,IAChB,CAAC,CAAA,CAED3E,EAAoBC,CAAwD,EAC9E,CAAA,CAAG,CAAE,KAAM,IAAK,CAAC,EAErB,CAAA,CACF,CAEAiE,CAAAA,EAAc","file":"auto.mjs","sourcesContent":["/**\n * @beacio/core/auto — Transparent Web Bluetooth polyfill.\n *\n * Usage: import '@beacio/core/auto';\n *\n * - Chrome/Edge (native bluetooth): no-op\n * - Safari iOS (with extension): ensures navigator.bluetooth maps to extension API\n * - Safari iOS (without extension): lazy-loads install prompt on first requestDevice()\n * - Unsupported platforms: no-op (graceful degradation)\n */\n\nimport { detectPlatform, getBluetoothAPI, CDN_STUB_MARKER } from './platform';\nimport { BluetoothUUID } from './uuid';\nimport { BEACIO_EVENTS } from './events';\nimport type { RawAutoReconnectConfig } from './types';\n\n/**\n * Patch navigator.permissions.query to support { name: 'bluetooth' } (§4.1\n * Permission API Integration).\n *\n * Honesty rules (permissions-query-bluetooth-unsupported):\n * - Patched ONLY when an extension-backed bluetooth API actually exists —\n * unsupported platforms keep the browser's native behavior (TypeError on\n * the name, matching Chrome).\n * - `state` is always 'prompt': with a chooser-based UA every new-device\n * access can prompt, so 'granted' is never synthesized.\n * - The §4.1 BluetoothPermissionResult.devices array is backed by the native\n * grant query (getDevices()), filtered by descriptor.deviceId when present.\n * descriptor.filters matching needs advertisement data the page does not\n * have, so filters are ignored (best-effort superset, never an error).\n */\nfunction patchPermissionsAPI(api: { getDevices?: () => Promise<BluetoothDevice[]> }): void {\n if (typeof navigator === 'undefined' || !navigator.permissions) return;\n\n const originalQuery = navigator.permissions.query.bind(navigator.permissions);\n navigator.permissions.query = async function (\n descriptor: PermissionDescriptor\n ): Promise<PermissionStatus> {\n if ((descriptor as { name: string }).name !== 'bluetooth') {\n return originalQuery(descriptor);\n }\n\n const requestedDeviceId = (descriptor as { deviceId?: string }).deviceId;\n let devices: BluetoothDevice[] = [];\n if (typeof api.getDevices === 'function') {\n try {\n const granted = await api.getDevices();\n devices = requestedDeviceId === undefined\n ? [...granted]\n : granted.filter((device) => (device as { id?: string }).id === requestedDeviceId);\n } catch {\n devices = [];\n }\n }\n const frozenDevices = Object.freeze(devices);\n\n const target = new EventTarget();\n const status = Object.create(target, {\n state: { get: () => 'prompt' as PermissionState, enumerable: true },\n name: { get: () => 'bluetooth', enumerable: true },\n onchange: { value: null, writable: true, enumerable: true },\n devices: { get: () => frozenDevices, enumerable: true },\n }) as PermissionStatus;\n return status;\n };\n}\n\n/**\n * Members allowed on the polyfilled `navigator.bluetooth`. Everything else\n * (peripheral, backgroundSync, getCapabilities, debug, __beacio, etc.) is\n * filtered out so the polyfill surface matches the W3C Web Bluetooth spec\n * exactly. iOS-specific capabilities are reached via `window.beacioIOS`.\n */\nconst W3C_BLUETOOTH_MEMBERS: ReadonlySet<string> = new Set([\n // Bluetooth interface (spec §4)\n 'requestDevice',\n 'getAvailability',\n 'getDevices',\n // §4 \"[SameObject] readonly attribute BluetoothDevice? referringDevice\" —\n // constant null on iOS (no referring-device navigation mechanism exists),\n // but the attribute must be present and read null, never undefined.\n 'referringDevice',\n // §6.6.6 IDL event handlers — Bluetooth includes\n // BluetoothDeviceEventHandlers, CharacteristicEventHandlers AND\n // ServiceEventHandlers, so all mixin onX attributes are part of the\n // standard surface (bubbled §6.6.1 tree events are handled at the root).\n 'onavailabilitychanged',\n 'onadvertisementreceived',\n 'ongattserverdisconnected',\n 'oncharacteristicvaluechanged',\n 'onserviceadded',\n 'onservicechanged',\n 'onserviceremoved',\n // EventTarget\n 'addEventListener',\n 'removeEventListener',\n 'dispatchEvent',\n]);\n\n/**\n * Event-handler IDL attributes must round-trip with identity (the getter\n * returns the exact function assigned) and their accessors must run against\n * the real Bluetooth instance — never bind them and never route their\n * get/set through the proxy receiver.\n */\nfunction isEventHandlerMember(prop: string): boolean {\n return prop.startsWith('on');\n}\n\n/**\n * W3C facade over the live vendor API.\n *\n * A PLAIN EventTarget-derived object instead of a Proxy\n * (facade-proxy-violates-essential-invariants): the injected vendor object\n * carries NON-CONFIGURABLE own properties (`debug`, `__beacio`), and any\n * Proxy whose traps hide non-configurable target properties throws\n * TypeError during ordinary introspection (Object.keys, spread, `in`,\n * JSON.stringify). A plain object satisfies every ES invariant by\n * construction — vendor members are simply absent.\n *\n * - Methods are bound ONCE at build time → stable identities ([SameObject]\n * method-identity half of navigator-bluetooth-getter-new-proxy-per-access).\n * - onX EventHandler attributes forward get/set to the live instance so the\n * accessor runs against its real private storage and assignment reads back\n * (proxy-receiver-breaks-handler-attribute-readback).\n * - referringDevice forwards (defaulting to null per §4) and stays readonly.\n * - Expando writes behave like any plain platform object wrapper and never\n * reach the vendor surface.\n */\nfunction buildW3CFacade(api: object): object {\n class BeacioW3CBluetooth extends EventTarget {}\n const facade = new BeacioW3CBluetooth();\n const source = api as { [key: string]: EventListener | BluetoothServiceUUID | BluetoothCharacteristicUUID | object };\n\n for (const member of W3C_BLUETOOTH_MEMBERS) {\n if (isEventHandlerMember(member)) {\n Object.defineProperty(facade, member, {\n get: () => (source[member] as EventListener) ?? null,\n set: (value) => { source[member] = value; },\n enumerable: true,\n configurable: true,\n });\n continue;\n }\n if (member === 'referringDevice') {\n Object.defineProperty(facade, member, {\n get: () => (source[member] as BluetoothDevice | null) ?? null,\n enumerable: true,\n configurable: true,\n });\n continue;\n }\n const value = source[member];\n if (typeof value === 'function') {\n Object.defineProperty(facade, member, {\n value: (value as (...args: object[]) => object).bind(api),\n writable: true,\n enumerable: true,\n configurable: true,\n });\n }\n // Members the live API does not implement as functions (e.g. EventTarget\n // methods on a partial mock) fall through to the facade's own inherited\n // EventTarget implementation.\n }\n return facade;\n}\n\n// ---------------------------------------------------------------------------\n// SB-SDK-13 — Foreground auto-reconnect + subscription recovery for the RAW\n// polyfilled navigator.bluetooth path.\n//\n// beacio's free foreground reconnect engine lives on the BeacioDevice wrapper\n// (device.ts handleDisconnect/startAutoReconnect + notification-manager\n// recoverSubscriptions). Real drop-in apps — the Storz & Bickel demo — consume\n// the BARE global (navigator.bluetooth.requestDevice / device.gatt /\n// characteristic.startNotifications) and so never reach that engine: a transient\n// drop forces a full page reload.\n//\n// We close that gap WITHOUT touching the W3C facade surface or asking the app to\n// instantiate Beacio: requestDevice's returned device is interposed by a thin,\n// invariant-safe forwarding Proxy (get-trap only — it hides and lies about\n// nothing, only swapping a handful of prototype METHOD identities, so frozen\n// vendor instances and their readonly own props pass through untouched, unlike\n// the facade's rejected hide-non-configurable Proxy). The interposer records the\n// live gatt server, the discovered service UUIDs, and the (service,\n// characteristic) pairs that had startNotifications active, then on an UNEXPECTED\n// gattserverdisconnected runs the documented exponential backoff loop\n// (defaults mirror AutoReconnectOptions: 1s/30s/2x/Infinity), reconnecting via\n// the discovery fast-path (gatt.connectAndDiscover) when present and re-arming\n// every recorded subscription. An intentional gatt.disconnect() flips a flag so\n// the echoed event is ignored (matches device.ts:861 intentional/unexpected\n// classification). Default-on for the beacio runtime; never installed off it.\n// ---------------------------------------------------------------------------\n\n/** Resolved backoff knobs for the supervisor (mirrors device.ts:898-901 defaults). */\ninterface ResolvedBackoff {\n maxAttempts: number;\n initialDelayMs: number;\n maxDelayMs: number;\n backoffMultiplier: number;\n}\n\nfunction resolveAutoReconnectConfig(): { enabled: boolean; backoff: ResolvedBackoff } {\n const raw = (typeof window !== 'undefined'\n ? (window as { beacioAutoReconnect?: RawAutoReconnectConfig }).beacioAutoReconnect\n : undefined) ?? {};\n return {\n enabled: raw.enabled !== false,\n backoff: {\n maxAttempts: raw.maxAttempts ?? Infinity,\n initialDelayMs: raw.initialDelayMs ?? 1000,\n maxDelayMs: raw.maxDelayMs ?? 30000,\n backoffMultiplier: raw.backoffMultiplier ?? 2,\n },\n };\n}\n\n/**\n * Per-device reconnect state shared between the interposing proxies and the\n * disconnect supervisor.\n */\ninterface SupervisorState {\n /** Live gatt server from the most recent connect()/connectAndDiscover(). */\n server: BluetoothRemoteGATTServer | null;\n /** Whether the app called gatt.disconnect() itself (→ ignore the echoed event). */\n intentional: boolean;\n /** Whether a backoff loop is already running (guards against re-entrancy). */\n reconnecting: boolean;\n /** `service|characteristic` → the UUIDs needed to re-walk + re-arm on reconnect. */\n subscriptions: Map<string, { service: string; characteristic: string }>;\n}\n\n/**\n * A transparent get-trap forwarding Proxy. The `overrides` map supplies replacement\n * values (typically wrapped methods) for specific keys; every other access reflects\n * the target faithfully so the proxy preserves all ES essential invariants —\n * including readonly own props on a frozen vendor instance — because it never hides\n * a property nor reports a value different from the target for any OWN property\n * (the overridden keys are prototype methods, which carry no own-descriptor on the\n * instance). This is why a plain forwarding interposer is safe here even though the\n * facade (which had to HIDE non-configurable vendor own props) could not be a Proxy.\n */\nfunction forwardingProxy<T extends object>(target: T, overrides: { [key: string]: object }): T {\n // PROTOTYPE methods (EventTarget's addEventListener, the GATT class methods) are\n // bound to the target so they keep a valid `this` when called as `proxy.method()`\n // — otherwise WebKit throws \"Illegal invocation\" / private-field brand-check\n // errors. OWN function-valued props are returned UNBOUND so app-assigned event\n // handlers (navigator.bluetooth.onX = fn) round-trip with their exact identity\n // and so partial-mock own methods keep their jest-spy reference. Bindings are\n // cached per key for a STABLE identity across reads ([SameObject]).\n const boundCache = new Map<PropertyKey, (...args: object[]) => object>();\n return new Proxy(target, {\n get(obj, prop, receiver) {\n if (typeof prop === 'string' && Object.prototype.hasOwnProperty.call(overrides, prop)) {\n return overrides[prop];\n }\n const value = Reflect.get(obj, prop, receiver);\n if (typeof value !== 'function') return value;\n if (Object.prototype.hasOwnProperty.call(obj, prop)) return value;\n let bound = boundCache.get(prop) as ((...a: object[]) => object) | undefined;\n if (!bound) {\n bound = value.bind(obj) as (...a: object[]) => object;\n boundCache.set(prop, bound);\n }\n return bound;\n },\n });\n}\n\n/** Wrap a characteristic so start/stopNotifications keep the recovery registry in sync. */\nfunction superviseCharacteristic(\n characteristic: BluetoothRemoteGATTCharacteristic,\n serviceUuid: string,\n state: SupervisorState,\n): BluetoothRemoteGATTCharacteristic {\n const key = `${serviceUuid}|${characteristic.uuid}`;\n return forwardingProxy(characteristic, {\n startNotifications: async () => {\n const result = await characteristic.startNotifications();\n state.subscriptions.set(key, { service: serviceUuid, characteristic: characteristic.uuid });\n return result;\n },\n stopNotifications: async () => {\n const result = await characteristic.stopNotifications();\n state.subscriptions.delete(key);\n return result;\n },\n }) as BluetoothRemoteGATTCharacteristic;\n}\n\n/** Wrap a service so characteristics it hands out are supervised. */\nfunction superviseService(\n service: BluetoothRemoteGATTService,\n state: SupervisorState,\n): BluetoothRemoteGATTService {\n return forwardingProxy(service, {\n getCharacteristic: async (uuid: BluetoothCharacteristicUUID) => {\n const characteristic = await service.getCharacteristic(uuid);\n return superviseCharacteristic(characteristic, service.uuid, state);\n },\n getCharacteristics: async (uuid?: BluetoothCharacteristicUUID) => {\n const characteristics = await (service as {\n getCharacteristics: (u?: BluetoothCharacteristicUUID) => Promise<BluetoothRemoteGATTCharacteristic[]>;\n }).getCharacteristics(uuid);\n return characteristics.map((c) => superviseCharacteristic(c, service.uuid, state));\n },\n }) as BluetoothRemoteGATTService;\n}\n\n/** Wrap a gatt server so service lookups are supervised and the live server is recorded. */\nfunction superviseServer(\n server: BluetoothRemoteGATTServer,\n state: SupervisorState,\n): BluetoothRemoteGATTServer {\n state.server = server;\n return forwardingProxy(server, {\n getPrimaryService: async (uuid: BluetoothServiceUUID) => {\n const service = await server.getPrimaryService(uuid);\n return superviseService(service, state);\n },\n getPrimaryServices: async (uuid?: BluetoothServiceUUID) => {\n const services = await server.getPrimaryServices(uuid);\n return services.map((s) => superviseService(s, state));\n },\n }) as BluetoothRemoteGATTServer;\n}\n\n/** Wrap a device's gatt so connect/disconnect feed the supervisor. */\nfunction superviseGatt(\n gatt: BluetoothRemoteGATTServer,\n state: SupervisorState,\n): BluetoothRemoteGATTServer {\n return forwardingProxy(gatt, {\n connect: async () => {\n state.intentional = false;\n const server = await gatt.connect();\n return superviseServer(server, state);\n },\n disconnect: () => {\n // App-initiated teardown — the next gattserverdisconnected is its echo and\n // must NOT trigger auto-reconnect (matches device.ts:861).\n state.intentional = true;\n state.subscriptions.clear();\n state.server = null;\n gatt.disconnect();\n },\n }) as BluetoothRemoteGATTServer;\n}\n\n/** Re-acquire and re-subscribe every recorded characteristic after a reconnect. */\nasync function recoverSubscriptions(\n server: BluetoothRemoteGATTServer,\n state: SupervisorState,\n): Promise<void> {\n for (const { service, characteristic } of [...state.subscriptions.values()]) {\n try {\n const svc = await server.getPrimaryService(service);\n const ch = await svc.getCharacteristic(characteristic);\n await ch.startNotifications();\n } catch {\n // Characteristic may no longer exist after a firmware/service change; drop\n // the stale entry so we stop trying to re-arm it (mirrors\n // notification-manager.recoverSubscriptions deleting stale registry keys).\n state.subscriptions.delete(`${service}|${characteristic}`);\n }\n }\n}\n\n/** Run the documented exponential-backoff reconnect loop after an unexpected drop. */\nfunction startReconnectLoop(state: SupervisorState, backoff: ResolvedBackoff): void {\n if (state.reconnecting) return;\n const server = state.server;\n if (!server) return;\n state.reconnecting = true;\n\n // Snapshot which UUIDs to discover so the fast-path can warm them in one round-trip.\n const serviceUUIDs = [...new Set([...state.subscriptions.values()].map((s) => s.service))];\n\n void (async () => {\n let delay = backoff.initialDelayMs;\n for (let attempt = 1; attempt <= backoff.maxAttempts; attempt += 1) {\n if (state.intentional) break;\n await new Promise<void>((resolve) => setTimeout(resolve, delay));\n if (state.intentional) break;\n try {\n const fastPath = (server as {\n connectAndDiscover?: (uuids: BluetoothServiceUUID[]) => Promise<BluetoothRemoteGATTService[]>;\n }).connectAndDiscover;\n if (typeof fastPath === 'function' && serviceUUIDs.length > 0) {\n // Discovery fast-path (AC#3): one warm-up round-trip for all services\n // instead of a connect() then per-service getPrimaryService chain.\n await fastPath.call(server, serviceUUIDs);\n } else {\n await server.connect();\n }\n await recoverSubscriptions(server, state);\n state.reconnecting = false;\n return;\n } catch {\n delay = Math.min(delay * backoff.backoffMultiplier, backoff.maxDelayMs);\n }\n }\n state.reconnecting = false;\n })();\n}\n\n/**\n * Interpose a foreground auto-reconnect supervisor over a device returned by the\n * polyfilled requestDevice. No-op-safe: a device without addEventListener/gatt\n * (e.g. a partial shape) is returned untouched.\n */\nfunction superviseDevice(device: BluetoothDevice, backoff: ResolvedBackoff): BluetoothDevice {\n if (!device || typeof device.addEventListener !== 'function') return device;\n\n const state: SupervisorState = {\n server: null,\n intentional: false,\n reconnecting: false,\n subscriptions: new Map(),\n };\n\n // Attach to the RAW device so we see the extension's gattserverdisconnected\n // regardless of any listener the app adds through the wrapper.\n device.addEventListener('gattserverdisconnected', () => {\n if (state.intentional) {\n state.intentional = false;\n return;\n }\n startReconnectLoop(state, backoff);\n });\n\n let supervisedGatt: BluetoothRemoteGATTServer | undefined;\n return forwardingProxy(device, {\n get gatt(): BluetoothRemoteGATTServer | undefined {\n const raw = device.gatt;\n if (!raw) return undefined;\n // Cache so the intentional flag set via device.gatt.disconnect() is visible\n // to the same supervised gatt the app keeps using ([SameObject]-style).\n if (!supervisedGatt) supervisedGatt = superviseGatt(raw, state);\n return supervisedGatt;\n },\n } as { [key: string]: object }) as BluetoothDevice;\n}\n\n/**\n * Wrap a vendor API's requestDevice so every returned device is supervised\n * (SB-SDK-13). Returns the SAME api object when auto-reconnect is disabled or the\n * api has no requestDevice, so the W3C facade build is unaffected off the feature.\n */\nfunction withAutoReconnect(api: object): object {\n const config = resolveAutoReconnectConfig();\n const source = api as { requestDevice?: (...args: RequestDeviceOptions[]) => Promise<BluetoothDevice> };\n if (!config.enabled || typeof source.requestDevice !== 'function') return api;\n\n const originalRequestDevice = source.requestDevice.bind(api);\n return forwardingProxy(api, {\n requestDevice: async (...args: RequestDeviceOptions[]) => {\n const device = await originalRequestDevice(...args);\n return superviseDevice(device, config.backoff);\n },\n });\n}\n\n/**\n * §4 IDL-shaped fallback for platforms with no Web Bluetooth support\n * (unsupported-platform-stub-shape-nonconformant): a real EventTarget with\n * the full Bluetooth member set. requestDevice rejects with a NotFoundError\n * DOMException (never a plain Error), getAvailability resolves false,\n * getDevices resolves [], referringDevice is null.\n */\nfunction createUnsupportedBluetoothStub(): object {\n class BeacioUnsupportedBluetooth extends EventTarget {}\n const stub = new BeacioUnsupportedBluetooth();\n\n Object.defineProperty(stub, 'requestDevice', {\n value: async (..._args: RequestDeviceOptions[]) => {\n // Lazy-load the local detect surface for the install banner. detect now\n // lives INSIDE @beacio/core (src/detect/), so this is an intra-package\n // dynamic import — code-split into its own chunk so the eager polyfill\n // graph never carries the banner UI until the unsupported stub is used.\n // SB-SDK-07: this zero-config call passes NO lang, so the banner's i18n\n // seam (src/detect/i18n.ts resolveStrings) derives the language from\n // navigator.language — a German-locale iPhone gets the German banner with\n // no config here. An explicit `lang` is only ever supplied by a caller that\n // wires showInstallBanner/initBeacio directly (e.g. the S&B demo passes\n // lang:'de'); core's stub stays config-free and localizable.\n try {\n const detect = await import('./detect');\n if (typeof detect.showInstallBanner === 'function') {\n detect.showInstallBanner();\n }\n } catch {\n // Defensive — the banner import must never break the rejection path.\n }\n // §4 requestDevice: when no device/chooser can ever match, the spec\n // rejection class is NotFoundError — never a plain Error.\n throw new DOMException(\n 'Web Bluetooth is not supported on this platform. ' +\n 'On iOS Safari, install the Beacio extension. ' +\n 'See: https://beacio.com',\n 'NotFoundError'\n );\n },\n writable: true,\n enumerable: true,\n configurable: true,\n });\n Object.defineProperty(stub, 'getAvailability', {\n value: async () => false,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n Object.defineProperty(stub, 'getDevices', {\n value: async () => [],\n writable: true,\n enumerable: true,\n configurable: true,\n });\n Object.defineProperty(stub, 'referringDevice', {\n get: () => null,\n enumerable: true,\n configurable: true,\n });\n\n for (const member of W3C_BLUETOOTH_MEMBERS) {\n if (!isEventHandlerMember(member)) continue;\n const eventType = member.slice(2);\n let current: EventListener | null = null;\n Object.defineProperty(stub, member, {\n get: () => current,\n set: (next: EventListener | null) => {\n if (current !== null) stub.removeEventListener(eventType, current);\n current = typeof next === 'function' ? (next as EventListener) : null;\n if (current !== null) stub.addEventListener(eventType, current);\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n // Marker so detectPlatform()/getBluetoothAPI() never mistake our own stub\n // for a native implementation (same convention as the CDN stubs). Keyed off the\n // shared CDN_STUB_MARKER so the writer can never drift from the readers.\n Object.defineProperty(stub, CDN_STUB_MARKER, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n return stub;\n}\n\nlet polyfillApplied = false;\n\n/**\n * Install the transparent W3C `navigator.bluetooth` polyfill for the current\n * platform (no-op on native/unsupported per the branches below). Runs once at\n * module load via the bottom-of-file call for `import '@beacio/core/auto'`\n * consumers; also EXPORTED so the consolidated `browser-auto` entry can invoke\n * it explicitly (a bare side-effect import is tree-shakeable under the package's\n * `sideEffects` allowlist). Idempotent: the module-level guard makes a second\n * call a no-op so the two entry points never double-register the permissions\n * shim or the extension-ready listener.\n */\nexport function applyPolyfill(): void {\n if (polyfillApplied) return;\n if (typeof navigator === 'undefined') return;\n polyfillApplied = true;\n\n const bluetoothNavigator = navigator as Navigator & {\n bluetooth?: Bluetooth;\n };\n\n // Expose BluetoothUUID global (spec §4) on all platforms\n if (typeof window !== 'undefined' && !(window as { BluetoothUUID?: object }).BluetoothUUID) {\n (window as { BluetoothUUID?: object }).BluetoothUUID = BluetoothUUID;\n }\n\n // §10 [SecureContext] (polyfill-installs-in-insecure-contexts): the spec\n // marks `Navigator.bluetooth` [SecureContext], so plain-http pages must\n // never get the attribute — not even the throwing \"unsupported\" stub — and\n // navigator.permissions must stay unpatched. BluetoothUUID (above) is a\n // plain global and may stay. `=== false` keeps SSR/legacy environments that\n // do not implement isSecureContext on their previous behavior.\n if (typeof window !== 'undefined' && window.isSecureContext === false) {\n return;\n }\n\n const platform = detectPlatform();\n\n if (platform === 'native') {\n // Chrome, Edge, etc. — native Web Bluetooth already works\n return;\n }\n\n if (platform === 'safari-extension') {\n // Extension provides the full vendor surface on navigator.beacio. We expose\n // two distinct facades here:\n // 1. navigator.bluetooth — W3C-only proxy (requestDevice, getAvailability,\n // getDevices, onavailabilitychanged, EventTarget). Non-standard iOS\n // members (peripheral, backgroundSync, getCapabilities) are hidden so\n // portable code matches Chrome/Edge exactly.\n // 2. window.beacioIOS — vendor-prefixed iOS capabilities. The extension\n // already mounts this; we only mirror when missing (e.g. if the\n // polyfill loads in a context where it wasn't mounted).\n const api = getBluetoothAPI();\n if (api && !bluetoothNavigator.bluetooth) {\n // [SameObject] (navigator-bluetooth-getter-new-proxy-per-access): build\n // the facade ONCE — every access returns the identical object with\n // stable method identities. The facade is built over the auto-reconnect\n // interposer (SB-SDK-13) so requestDevice hands back supervised devices on\n // the beacio runtime; every other W3C member forwards to the vendor api\n // unchanged, and the surface is byte-for-byte identical off the feature.\n const facade = buildW3CFacade(withAutoReconnect(api));\n Object.defineProperty(navigator, 'bluetooth', {\n get: () => facade,\n configurable: true,\n });\n }\n if (typeof window !== 'undefined' && !(window as { beacioIOS?: object }).beacioIOS) {\n const apiRec = api as { peripheral?: object; backgroundSync?: object; getCapabilities?: () => object } | undefined;\n const ios = apiRec?.peripheral || apiRec?.backgroundSync\n ? { peripheral: apiRec.peripheral, backgroundSync: apiRec.backgroundSync, getCapabilities: () => apiRec?.getCapabilities?.() }\n : undefined;\n if (ios) {\n Object.defineProperty(window, 'beacioIOS', {\n value: Object.freeze(ios as { peripheral?: object; backgroundSync?: object; getCapabilities?: () => object }), writable: false, enumerable: true, configurable: false,\n });\n }\n }\n // Permissions API (§4.1): extension active — honest shim backed by the\n // native grant query. State is 'prompt' (never synthetic 'granted').\n if (api) {\n patchPermissionsAPI(api as { getDevices?: () => Promise<BluetoothDevice[]> });\n }\n return;\n }\n\n // Unsupported or Safari without extension — install the §4 IDL-shaped stub\n // (unsupported-platform-stub-shape-nonconformant).\n // navigator.permissions is intentionally NOT patched here: with no working\n // bluetooth API behind it, a synthetic PermissionStatus would be a lie —\n // the browser's native TypeError on the name matches Chrome's behavior.\n if (!bluetoothNavigator.bluetooth) {\n // [SameObject]: one stub for the page's lifetime.\n const stub = createUnsupportedBluetoothStub();\n Object.defineProperty(navigator, 'bluetooth', {\n get: () => stub,\n configurable: true,\n });\n\n // §10 (api-unavailable-at-document-start): this one-shot probe can lose\n // the race against the extension's injected script — the throwing\n // \"unsupported\" stub must not stay installed forever on a page where the\n // extension comes up moments later. Re-bind deterministically on the\n // extension's ready signal.\n if (typeof window !== 'undefined') {\n window.addEventListener(BEACIO_EVENTS.EXTENSION_READY, () => {\n const api = getBluetoothAPI();\n if (!api || (api as object) === stub) return;\n const current = (navigator as { bluetooth?: object }).bluetooth;\n if (current !== undefined && current !== stub) return; // page/native owns it now\n const upgraded = buildW3CFacade(withAutoReconnect(api));\n Object.defineProperty(navigator, 'bluetooth', {\n get: () => upgraded,\n configurable: true,\n });\n // Extension active — the honest §4.1 permissions shim now applies.\n patchPermissionsAPI(api as { getDevices?: () => Promise<BluetoothDevice[]> });\n }, { once: true });\n }\n }\n}\n\napplyPolyfill();\n"]}

@@ -1,2 +0,4 @@

var WebBLECore=(function(exports){'use strict';var G=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),M={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the WebBLE iOS app and enable the Safari extension. Use @beacio/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this WebBLE instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},u=class n extends Error{constructor(e,t,i){let r=M[e];super(t??r),this.name="WebBLEError",this.code=e,this.suggestion=M[e],this.isRetriable=G.has(e),this.retryAfterMs=i?.retryAfterMs;}static from(e,t="GATT_OPERATION_FAILED"){if(e instanceof n)return e;let i=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,r=e instanceof Error?e.message:String(e),o=r.toLowerCase();switch(i){case "TypeError":return new n("INVALID_PARAMETER",r);case "NotFoundError":return new n("DEVICE_NOT_FOUND",r);case "NotAllowedError":case "SecurityError":return new n("PERMISSION_DENIED",r);case "NetworkError":return new n("DEVICE_DISCONNECTED",r,{retryAfterMs:1e3});case "TimeoutError":return new n("TIMEOUT",r,{retryAfterMs:1e3});case "InvalidStateError":if(o.includes("disconnect"))return new n("DEVICE_DISCONNECTED",r,{retryAfterMs:1e3});break;}return r.includes("User cancelled")||r.includes("User canceled")?new n("USER_CANCELLED"):o.includes("no devices found")||r.includes("No Devices")?new n("DEVICE_NOT_FOUND"):r.includes("No Services matching")||o.includes("service not found")?new n("SERVICE_NOT_FOUND",r):r.includes("No Characteristics matching")||o.includes("characteristic not found")?new n("CHARACTERISTIC_NOT_FOUND",r):r.includes("GATT Server is disconnected")||o.includes("disconnected")?new n("DEVICE_DISCONNECTED",r,{retryAfterMs:1e3}):o.includes("not supported")&&o.includes("read")?new n("CHARACTERISTIC_NOT_READABLE",r):o.includes("not supported")&&o.includes("write")?new n("CHARACTERISTIC_NOT_WRITABLE",r):o.includes("not supported")&&o.includes("notif")?new n("CHARACTERISTIC_NOT_NOTIFIABLE",r):o.includes("permission")?new n("PERMISSION_DENIED",r):new n(t,r)}};async function S(n,e={}){let t=e.maxAttempts??3,i=e.delayMs??250,r=e.backoffMultiplier??1.5;if(!Number.isInteger(t)||t<=0)throw new u("INVALID_PARAMETER",`Invalid maxAttempts: ${t}. Must be a positive integer.`);if(!Number.isFinite(i)||i<0)throw new u("INVALID_PARAMETER",`Invalid delayMs: ${i}. Must be a non-negative number.`);if(!Number.isFinite(r)||r<1)throw new u("INVALID_PARAMETER",`Invalid backoffMultiplier: ${r}. Must be a number >= 1.`);for(let o=1;o<=t;o+=1)try{return await n(o)}catch(a){let c=u.from(a);if(o>=t||!c.isRetriable)throw c;let s=c.retryAfterMs??i*Math.pow(r,o-1);s>0&&await new Promise(l=>{setTimeout(l,s);});}throw new u("GATT_OPERATION_FAILED","Retry loop exited unexpectedly.")}var D="-0000-1000-8000-00805f9b34fb",y={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},w={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,"local_east_coordinate.xml":10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989};function A(n){return n.toString(16).padStart(8,"0")+D}var b,E;function z(){if(!b){b=new Map;for(let[n,e]of Object.entries(y)){let t=A(e);b.has(t)||b.set(t,n);}}return b}function $(){if(!E){E=new Map;for(let[n,e]of Object.entries(w)){let t=A(e);E.has(t)||E.set(t,n);}}return E}var U=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,q=/^[0-9a-f]{4}$/,j=/^[0-9a-f]{8}$/;function H(n,e){let t=n.length,i=e.length,r=Array.from({length:i+1},(o,a)=>a);for(let o=1;o<=t;o++){let a=o-1;r[0]=o;for(let c=1;c<=i;c++){let s=r[c];r[c]=n[o-1]===e[c-1]?a:1+Math.min(a,r[c],r[c-1]),a=s;}}return r[i]}function Q(n){return n.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function F(n,e){let t=e[n];if(t!==void 0)return t;let i=n.replace(/[._-]/g,"");if(i){for(let[r,o]of Object.entries(e))if(r.replace(/[._-]/g,"")===i)return o}}function g(n){if(typeof n=="number"){if(!Number.isInteger(n)||n<0||n>4294967295)throw new TypeError(`Invalid UUID integer: ${n}. Must be a 16-bit or 32-bit unsigned integer.`);return A(n)}let e=n.trim(),t=e.toLowerCase();if(U.test(t))return t;if(q.test(t))return "0000"+t+D;if(j.test(t))return t+D;let i=y[t]??w[t];if(i!==void 0)return A(i);let r=Q(e),o=F(r,y);if(o!==void 0)return A(o);let a=F(r,w);if(a!==void 0)return A(a);let c=Object.keys(y).concat(Object.keys(w)),s,l=4;for(let p of c){let m=H(r,p);m<l&&(l=m,s=p);}!s&&r.length>=4&&(s=c.find(p=>p.startsWith(r)));let d=s?` Did you mean "${s}"?`:"";throw new TypeError(`Invalid UUID: "${n}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${d}`)}function Z(n){return z().get(n.toLowerCase())}function K(n){return $().get(n.toLowerCase())}function X(n){let e=n.replace(/^(gap|gatt)\./,"");return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(e)?e.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" "):n}var Y={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function O(n){let e=Number(n);if(!Number.isFinite(e))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${n}`);let t=Math.trunc(e);if(t<0||t>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${n}`);return A(t+0)}function I(n,e,t){if(typeof n=="number")return O(n);if(U.test(n))return n;let i=e[n.toLowerCase()];if(i!==void 0)return A(i);throw new TypeError(`Failed to execute '${t}' on 'BluetoothUUID': Invalid UUID or registry name: "${n}"`)}function V(n){return I(n,Y,"getDescriptor")}var J={canonicalUUID:O,getService:n=>I(n,y,"getService"),getCharacteristic:n=>I(n,w,"getCharacteristic"),getDescriptor:V};var R=class{constructor(e){this.deps=e;this.inFlightWrites=new Map;}async write(e,t,i,r){let o=this.deps.validateTimeoutMs(r?.timeoutMs),a=await this.deps.getCharacteristic(e,t),c=Symbol(`write:${e}:${t}`);this.inFlightWrites.set(c,{service:e,characteristic:t,aborted:false});try{if(r?.mode==="without-response"){await this.deps.withOptionalTimeout(a.writeValueWithoutResponse(i),o,"Write without response timed out");return}await this.deps.withOptionalTimeout(a.writeValueWithResponse(i),o,"Write with response timed out");}catch(s){throw this.inFlightWrites.get(c)?.aborted?new u("WRITE_INCOMPLETE",`Write incomplete for ${e}/${t}: disconnected before completion`,{retryAfterMs:1e3}):u.from(s)}finally{this.inFlightWrites.delete(c);}}async writeFragmented(e,t,i,r){let o=this.toUint8Array(i),a=o.byteLength;if(a===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0};let c=r?.chunkSize??this.deriveChunkSizeFromMtu(r?.mtu)??await this.deriveChunkSize(void 0,r?.mode),s=r?.maxRetries??0,l=r?.retryDelayMs??0,d=0,p=0,m=0;for(let _=0;_<a;_+=c){let h=Math.min(_+c,a),f=new Uint8Array(o.subarray(_,h)),v=0;for(;;)try{await this.write(e,t,f,r),d+=f.byteLength,p+=1;break}catch(L){if(v>=s)throw d>0&&d<a?new u("WRITE_INCOMPLETE",`Write fragmented incomplete (${d}/${a} bytes written): ${this.errorMessage(L)}`,{retryAfterMs:1e3}):u.from(L);v+=1,m+=1,l>0&&await this.delay(l);}}return {bytesWritten:d,totalBytes:a,chunkSize:c,chunkCount:p,retryCount:m}}async writeLarge(e,t,i,r){let o=this.toUint8Array(i),a=o.byteLength;if(a===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0};let c=await this.deriveChunkSize(r?.chunkSize,r?.mode),s=0,l=0;for(let d=0;d<a;d+=c){let p=Math.min(d+c,a),m=o.subarray(d,p),_=new Uint8Array(m);try{await this.write(e,t,_,r),s+=m.byteLength,l+=1;}catch(h){throw s>0&&s<a?new u("WRITE_INCOMPLETE",`Write incomplete (${s}/${a} bytes written): ${this.errorMessage(h)}`):u.from(h)}}if(s!==a)throw new u("WRITE_INCOMPLETE",`Write incomplete (${s}/${a} bytes written)`);return {bytesWritten:s,totalBytes:a,chunkSize:c,chunkCount:l}}async writeWithoutResponse(e,t,i,r){return this.write(e,t,i,{...r,mode:"without-response"})}async getWriteLimits(){if(!this.deps.isConnected())throw new u("DEVICE_DISCONNECTED");let e=this.deps.getTransport(),t=await e?.getWriteLimits?.(),i=t?.mtu??await e?.getMtu?.()??null;return {withResponse:t?.withResponse??null,withoutResponse:t?.withoutResponse??null,mtu:i}}async writeAuto(e,t,i,r){let o=this.toUint8Array(i),a=o.byteLength,c=new Uint8Array(o);if(a===0)return await this.write(e,t,c,r),{bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0,fragmented:false};let s=await this.deriveChunkSize(r?.chunkSize,r?.mode);return a<=s?(await this.write(e,t,c,r),{bytesWritten:a,totalBytes:a,chunkSize:a,chunkCount:1,retryCount:0,fragmented:false}):{...await this.writeFragmented(e,t,c,r),fragmented:true}}abortInFlightWrites(){for(let e of this.inFlightWrites.values())e.aborted=true;}async deriveChunkSize(e,t){if(e!==void 0){if(!Number.isInteger(e)||e<=0)throw new u("INVALID_PARAMETER",`Invalid chunkSize: ${e}. Must be a positive integer.`);return e}let i=await this.getWriteLimits().catch(()=>({withResponse:null,withoutResponse:null,mtu:null})),r=t==="without-response"?i.withoutResponse:i.withResponse;return typeof r=="number"&&r>0?r:typeof i.mtu=="number"&&i.mtu>3?i.mtu-3:20}deriveChunkSizeFromMtu(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=3)throw new u("INVALID_PARAMETER",`Invalid mtu: ${e}. Must be an integer greater than 3.`);return e-3}delay(e){return new Promise(t=>{setTimeout(t,e);})}toUint8Array(e){return e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}errorMessage(e){return e instanceof Error?e.message:String(e)}};var x=class x{constructor(e){this.deps=e;this.notificationStates=new Map;this.handleNotification=e=>{let t=e.target,i=t.value;if(i){for(let[r,o]of this.notificationStates)if((o.characteristic??this.deps.charCache.get(r))===t){let[c,s]=r.split(":");for(let l of o.callbacks)try{l(i);}catch(d){this.deps.emitError(u.from(d),{operation:"device.notification-callback",service:c,characteristic:s});}break}}};}getNotificationStates(){return this.notificationStates}subscribe(e,t,i,r){let{unsubscribe:o,ready:a}=this.registerNotificationConsumer(e,t,i);a.catch(l=>{let d=u.from(l);try{r?.onError?.(d);}catch(p){this.deps.emitError(u.from(p),{operation:"device.subscribe.onError",service:e,characteristic:t});}});let c=r?.autoRecover??true;c&&this.addToRecoveryRegistry(this.charKey(e,t),e,t,i);let s=o;return ()=>{s(),c&&this.removeFromRecoveryRegistry(this.charKey(e,t),i);}}async subscribeAsync(e,t,i,r){let{unsubscribe:o,release:a,ready:c}=this.registerNotificationConsumer(e,t,i),s=r?.autoRecover??true;s&&this.addToRecoveryRegistry(this.charKey(e,t),e,t,i);try{await c;}catch(l){let d=u.from(l);throw await a(),s&&this.removeFromRecoveryRegistry(this.charKey(e,t),i),d}return ()=>{o(),s&&this.removeFromRecoveryRegistry(this.charKey(e,t),i);}}async*notifications(e,t,i={maxQueueSize:x.DEFAULT_NOTIFICATION_QUEUE_SIZE}){let r=this.validateMaxQueueSize(i.maxQueueSize??x.DEFAULT_NOTIFICATION_QUEUE_SIZE),o=i?.overflowStrategy??"error",a=[],c=0,s={resolve:null,reject:null,done:false,failure:null},l=h=>{if(!s.failure)if(s.resolve){let f=s.resolve;s.resolve=null,s.reject=null,f({value:h,done:false});}else {if(a.length>=r){c+=1;let f={service:e,characteristic:t,strategy:o,queueSize:r,droppedCount:c};this.deps.emitQueueOverflow(f);try{i?.onOverflow?.(f);}catch(v){this.deps.emitError(u.from(v),{operation:"device.notifications.onOverflow",service:e,characteristic:t});}if(o==="error"){let v=new u("GATT_OPERATION_FAILED",`Notification queue overflowed (maxQueueSize=${r}). Increase queue size or consume faster.`);s.failure=v;let L=s.reject;s.resolve=null,s.reject=null,L?.(v);return}if(o==="drop-oldest"&&a.shift(),o==="drop-newest")return}a.push(h);}},d=this.charKey(e,t);this.addToRecoveryRegistry(d,e,t,l);let{unsubscribe:p,release:m,ready:_}=this.registerNotificationConsumer(e,t,l);try{await _;}catch(h){throw await m(),this.removeFromRecoveryRegistry(d,l),h}try{for(;!s.done;){if(s.failure)throw s.failure;if(a.length>0)yield a.shift();else {let h=await new Promise((f,v)=>{s.resolve=f,s.reject=v;});if(h.done){let f=this.deps.getReconnectGate();if(f&&!this.deps.isIntentionalDisconnect()){if(await f.promise,this.deps.isIntentionalDisconnect())return;continue}return}yield h.value;}}}finally{let h=s.resolve;s.resolve=null,s.reject=null,s.done=true,await m(),this.removeFromRecoveryRegistry(d,l),h&&h({value:void 0,done:true});}}teardownSubscriptions(e){for(let t of this.notificationStates.values())this.detachNotificationListener(t),t.nativeActive&&this.stopNotificationsSafely(t.characteristic,{operation:e});this.notificationStates.clear();}cleanupSubscriptions(){this.teardownSubscriptions("notification.cleanup");}suspendSubscriptions(){this.teardownSubscriptions("notification.suspend");}async recoverSubscriptions(){let e=[...this.deps.recoveryRegistry.entries()];for(let[t,i]of e)try{for(let r of i.callbacks){let{ready:o}=this.registerNotificationConsumer(i.service,i.characteristic,r);await o;}}catch(r){this.deps.recoveryRegistry.delete(t);let o=u.from(r);this.deps.emitSubscriptionLost({service:i.service,characteristic:i.characteristic,error:o}),this.deps.emitError(o,{operation:"notification.recover",service:i.service,characteristic:i.characteristic});}}registerNotificationConsumer(e,t,i){let r=this.charKey(e,t),o=this.notificationStates.get(r);o||(o={callbacks:new Set,characteristic:null,listenerAttached:false,nativeActive:false,reconcilePromise:null},this.notificationStates.set(r,o)),o.callbacks.add(i);let a=()=>{let c=this.notificationStates.get(r);return c?.callbacks.has(i)?(c.callbacks.delete(i),this.syncNotificationState(r,e,t)):Promise.resolve()};return {unsubscribe:()=>{a();},release:a,ready:this.syncNotificationState(r,e,t)}}syncNotificationState(e,t,i){let r=this.notificationStates.get(e);if(!r)return Promise.resolve();let c=(r.reconcilePromise??Promise.resolve()).catch(s=>{this.deps.emitError(u.from(s),{operation:"notification.reconcile",service:t,characteristic:i});}).then(async()=>{for(;;){if(this.notificationStates.get(e)!==r){await this.deactivateNotificationState(r);return}if(r.callbacks.size===0){if(this.detachNotificationListener(r),r.nativeActive){r.nativeActive=false,await this.stopNotificationsSafely(r.characteristic,{operation:"notification.stop",service:t,characteristic:i});continue}this.deleteNotificationStateIfIdle(e,r);return}let l=r.characteristic??await this.deps.getCharacteristic(t,i);if(r.characteristic=l,r.listenerAttached||(l.addEventListener("characteristicvaluechanged",this.handleNotification),r.listenerAttached=true),!r.nativeActive){if(await l.startNotifications(),r.nativeActive=true,this.notificationStates.get(e)!==r){await this.deactivateNotificationState(r);return}if(r.callbacks.size===0)continue}if(r.callbacks.size!==0)return}}).finally(()=>{r.reconcilePromise===c&&(r.reconcilePromise=null,this.notificationStates.get(e)===r&&this.deleteNotificationStateIfIdle(e,r));});return r.reconcilePromise=c,c}async deactivateNotificationState(e){this.detachNotificationListener(e),e.nativeActive&&(e.nativeActive=false,await this.stopNotificationsSafely(e.characteristic,{operation:"notification.deactivate"}));}detachNotificationListener(e){!e.listenerAttached||!e.characteristic||(e.characteristic.removeEventListener("characteristicvaluechanged",this.handleNotification),e.listenerAttached=false);}deleteNotificationStateIfIdle(e,t){this.notificationStates.get(e)===t&&(t.callbacks.size>0||t.nativeActive||t.reconcilePromise||this.notificationStates.delete(e));}validateMaxQueueSize(e){if(!Number.isInteger(e)||e<=0)throw new u("INVALID_PARAMETER",`Invalid maxQueueSize: ${e}. Must be a positive integer.`);return e}async stopNotificationsSafely(e,t){if(e)try{await e.stopNotifications();}catch(i){this.deps.emitError(u.from(i),t);}}charKey(e,t){return `${g(e)}:${g(t)}`}addToRecoveryRegistry(e,t,i,r){let o=this.deps.recoveryRegistry.get(e);o||(o={service:t,characteristic:i,callbacks:new Set},this.deps.recoveryRegistry.set(e,o)),o.callbacks.add(r);}removeFromRecoveryRegistry(e,t){let i=this.deps.recoveryRegistry.get(e);i&&(i.callbacks.delete(t),i.callbacks.size===0&&this.deps.recoveryRegistry.delete(e));}};x.DEFAULT_NOTIFICATION_QUEUE_SIZE=256;var T=x;var C=class{constructor(e,t={}){this.server=null;this.primaryServicesCache=null;this.serviceCache=new Map;this.charCache=new Map;this.recoveryRegistry=new Map;this.disconnectListeners=new Set;this.reconnectedListeners=new Set;this.queueOverflowListeners=new Set;this.subscriptionLostListeners=new Set;this.errorListeners=new Set;this.reconnectGate=null;this.intentionalDisconnect=false;this.lastDisconnectReason=null;this.autoReconnectConfig=null;this.autoReconnectAbort=null;this.id=e.id,this.name=e.name??null,this.raw=e,this.hooks=t,this.writeChunker=new R({getCharacteristic:(i,r)=>this.getCharacteristic(i,r),emitError:(i,r)=>this.emitError(i,r),validateTimeoutMs:i=>this.validateTimeoutMs(i),withOptionalTimeout:(i,r,o)=>this.withOptionalTimeout(i,r,o),isConnected:()=>this.connected,getTransport:()=>this.raw.gatt}),this.notificationManager=new T({getCharacteristic:(i,r)=>this.getCharacteristic(i,r),emitError:(i,r)=>this.emitError(i,r),emitSubscriptionLost:i=>this.emitSubscriptionLost(i),emitQueueOverflow:i=>this.emitQueueOverflow(i),recoveryRegistry:this.recoveryRegistry,charCache:this.charCache,getReconnectGate:()=>this.reconnectGate,isIntentionalDisconnect:()=>this.intentionalDisconnect}),typeof e.addEventListener=="function"&&e.addEventListener("gattserverdisconnected",()=>{this.handleDisconnect();});}get connected(){return this.server?.connected??false}async connect(e){if(this.connected)return;e?.autoReconnect&&(this.autoReconnectConfig=e.autoReconnect===true?{}:e.autoReconnect),this.hooks.beforeConnect?.(this);let t=this.raw.gatt;if(!t)throw new u("GATT_OPERATION_FAILED","Device has no GATT server");let i=this.reconnectGate;try{this.server=await t.connect(),this.lastDisconnectReason=null,await this.notificationManager.recoverSubscriptions();for(let r of this.reconnectedListeners)try{r();}catch(o){this.emitError(u.from(o),{operation:"device.reconnected-listener"});}this.intentionalDisconnect=!1,this.hooks.onConnectionChange?.(this);}catch(r){throw this.server?.connected||(this.server=null,this.hooks.onConnectionChange?.(this)),u.from(r)}finally{this.reconnectGate===i&&(this.reconnectGate=null),i?.resolve();}}disconnect(){this.intentionalDisconnect=true,this.lastDisconnectReason="intentional",this.autoReconnectConfig=null,this.autoReconnectAbort?.abort(),this.autoReconnectAbort=null,this.writeChunker.abortInFlightWrites(),this.notificationManager.cleanupSubscriptions(),this.recoveryRegistry.clear(),this.reconnectGate&&(this.reconnectGate.resolve(),this.reconnectGate=null),this.server?.disconnect(),this.server=null,this.primaryServicesCache=null,this.hooks.onConnectionChange?.(this);}async connectWithRetry(e={}){await S(async()=>{await this.connect();},e);}async read(e,t,i,r){let o=typeof i=="function"?i:void 0,a=typeof i=="function"?r:i,c=this.validateTimeoutMs(a?.timeoutMs),s=await this.getCharacteristic(e,t);try{let l=await this.withOptionalTimeout(s.readValue(),c,"Read timed out");return o?await o(l):l}catch(l){throw u.from(l)}}async write(e,t,i,r){return this.writeChunker.write(e,t,i,r)}async writeFragmented(e,t,i,r){return this.writeChunker.writeFragmented(e,t,i,r)}async writeLarge(e,t,i,r){return this.writeChunker.writeLarge(e,t,i,r)}async writeWithoutResponse(e,t,i,r){return this.writeChunker.writeWithoutResponse(e,t,i,r)}async getWriteLimits(){return this.writeChunker.getWriteLimits()}async getMtu(){return (await this.getWriteLimits()).mtu}async writeAuto(e,t,i,r){return this.writeChunker.writeAuto(e,t,i,r)}subscribe(e,t,i,r){return this.notificationManager.subscribe(e,t,i,r)}async subscribeAsync(e,t,i,r){return this.notificationManager.subscribeAsync(e,t,i,r)}notifications(e,t,i){return this.notificationManager.notifications(e,t,i)}async watchAdvertisements(){if(typeof this.raw.watchAdvertisements!="function")throw new u("GATT_OPERATION_FAILED","watchAdvertisements is not supported on this device");await this.raw.watchAdvertisements();}async unwatchAdvertisements(){let e=this.raw;if(typeof e.unwatchAdvertisements!="function")throw new u("GATT_OPERATION_FAILED","unwatchAdvertisements is not supported on this device");await e.unwatchAdvertisements();}async forget(){if(typeof this.raw.forget!="function")throw new u("GATT_OPERATION_FAILED","forget is not supported on this device");await this.raw.forget();}async getPrimaryServices(){if(!this.connected)throw new u("DEVICE_DISCONNECTED");if(this.primaryServicesCache)return this.primaryServicesCache;try{let t=(await this.server.getPrimaryServices()).map(i=>{let r=this.serviceCache.get(i.uuid)??i;return this.serviceCache.set(i.uuid,r),r});return this.primaryServicesCache=t,t}catch(e){throw u.from(e)}}async getEffectiveMtu(){let e=await this.getWriteLimits();return typeof e.mtu=="number"&&e.mtu>0?e.mtu:typeof e.withResponse=="number"&&e.withResponse>0?e.withResponse+3:typeof e.withoutResponse=="number"&&e.withoutResponse>0?e.withoutResponse+3:23}getLastDisconnectReason(){return this.lastDisconnectReason}getActiveSubscriptions(){let e=this.notificationManager.getNotificationStates();return [...new Set([...e.keys(),...this.recoveryRegistry.keys()])].map(i=>{let r=e.get(i),o=this.recoveryRegistry.get(i),[a,c]=i.split(":");return {service:a,characteristic:c,callbackCount:r?.callbacks.size??o?.callbacks.size??0,autoRecovering:o!==void 0,nativeActive:r?.nativeActive??false}})}on(e,t){return e==="disconnected"&&this.disconnectListeners.add(t),e==="reconnected"&&this.reconnectedListeners.add(t),e==="queue-overflow"&&this.queueOverflowListeners.add(t),e==="subscription-lost"&&this.subscriptionLostListeners.add(t),()=>{this.off(e,t);}}off(e,t){e==="disconnected"&&this.disconnectListeners.delete(t),e==="reconnected"&&this.reconnectedListeners.delete(t),e==="queue-overflow"&&this.queueOverflowListeners.delete(t),e==="subscription-lost"&&this.subscriptionLostListeners.delete(t);}addErrorListener(e){return this.errorListeners.add(e),()=>{this.removeErrorListener(e);}}removeErrorListener(e){this.errorListeners.delete(e);}handleDisconnect(){let e=this.intentionalDisconnect?"intentional":"unexpected";if(this.lastDisconnectReason=e,this.writeChunker.abortInFlightWrites(),this.notificationManager.suspendSubscriptions(),this.serviceCache.clear(),this.primaryServicesCache=null,this.charCache.clear(),this.server=null,this.hooks.onConnectionChange?.(this),this.recoveryRegistry.size>0){let t,i=new Promise(r=>{t=r;});this.reconnectGate={promise:i,resolve:t};}for(let t of this.disconnectListeners)try{t(e);}catch(i){this.emitError(u.from(i),{operation:"device.disconnected-listener"});}e==="unexpected"&&this.autoReconnectConfig&&this.startAutoReconnect(this.autoReconnectConfig);}startAutoReconnect(e){this.autoReconnectAbort?.abort();let t=new AbortController;this.autoReconnectAbort=t;let i=e.maxAttempts??1/0,r=e.initialDelayMs??1e3,o=e.maxDelayMs??3e4,a=e.backoffMultiplier??2;(async()=>{let s=r;for(let l=1;l<=i;l++){if(t.signal.aborted||(await new Promise(d=>{let p=setTimeout(d,s);t.signal.addEventListener("abort",()=>{clearTimeout(p),d();},{once:true});}),t.signal.aborted))return;try{await this.connect();return}catch{s=Math.min(s*a,o);}}this.emitError(new u("CONNECTION_TIMEOUT",`Auto-reconnect failed after ${i} attempts`),{operation:"device.auto-reconnect"});})();}async withOptionalTimeout(e,t,i){if(t===void 0)return e;let r=null,o=new Promise((a,c)=>{r=setTimeout(()=>{c(new u("TIMEOUT",i));},t);});try{return await Promise.race([e,o])}finally{r!==null&&clearTimeout(r);}}validateTimeoutMs(e){if(e!==void 0){if(!Number.isFinite(e)||e<=0)throw new u("INVALID_PARAMETER",`Invalid timeoutMs: ${e}. Must be a positive number.`);return e}}fanout(e,t,i){for(let r of e)try{r(t);}catch(o){this.emitError(u.from(o),{operation:i});}}emitQueueOverflow(e){this.fanout(this.queueOverflowListeners,e,"device.queue-overflow-listener");}emitSubscriptionLost(e){this.fanout(this.subscriptionLostListeners,e,"device.subscription-lost-listener");}emitError(e,t){for(let i of this.errorListeners)try{i(e,t);}catch{}}async getCharacteristic(e,t){if(!this.connected)throw new u("DEVICE_DISCONNECTED");let i=this.charKey(e,t),r=this.charCache.get(i);if(r)return r;let o=await this.getService(e),a=g(t);try{let c=await o.getCharacteristic(a);return this.charCache.set(i,c),c}catch(c){throw u.from(c)}}async getService(e){let t=g(e);if(this.primaryServicesCache){let r=this.primaryServicesCache.find(o=>o.uuid===t);if(r)return r}let i=this.serviceCache.get(t);if(i)return i;try{let r=await this.server.getPrimaryService(t);return this.serviceCache.set(t,r),r}catch(r){throw u.from(r)}}charKey(e,t){return `${g(e)}:${g(t)}`}};function N(){if(typeof navigator>"u")return "unsupported";let n=navigator;return n.webble?.__webble===true?"safari-extension":n.bluetooth&&!n.bluetooth.__webbleCDNStub?"native":"unsupported"}function B(){if(typeof navigator>"u")return null;let n=navigator;return n.webble?.__webble===true?n.webble:n.bluetooth&&!n.bluetooth.__webbleCDNStub?n.bluetooth:null}var P=class{constructor(e){this.errorFactory=e;}unsupported(){throw this.errorFactory()}requestPermission(){this.unsupported();}requestBackgroundConnection(e){this.unsupported();}registerCharacteristicNotifications(e){this.unsupported();}registerBeaconScanning(e){this.unsupported();}getRegistrations(){this.unsupported();}unregister(e){this.unsupported();}update(e,t){this.unsupported();}connect(e){return this.requestBackgroundConnection(e)}subscribe(e){return this.registerCharacteristicNotifications(e)}scan(e){return this.registerBeaconScanning(e)}list(){return this.getRegistrations()}destroy(){}},W=class extends EventTarget{constructor(t){super();this.onwriterequest=null;this.onsubscriptionchange=null;this.onconnectionstatechange=null;this.onadvertisingstatechange=null;this.onnotificationready=null;this.errorFactory=t;}get advertising(){return false}unsupported(){throw this.errorFactory()}advertise(t){this.unsupported();}addService(t){this.unsupported();}registerService(t){return this.addService(t)}startAdvertising(t){return this.advertise(t)}stopAdvertising(){this.unsupported();}send(t){this.unsupported();}sendNotification(t){return this.send(t)}destroy(){}},k=class{constructor(e){this.devices=new Map;this.platform=e?.platform??N(),this.maxConnections=this.normalizeMaxConnections(e?.maxConnections),this.bluetooth=this.platform!=="unsupported"?B():null,this.runtimeBluetooth=this.bluetooth,this.isSupported=this.bluetooth!==null,this.unsupportedFeatureErrorFactory=()=>this.platform==="unsupported"?new u("BLUETOOTH_UNAVAILABLE"):new u("GATT_OPERATION_FAILED","This WebBLE feature requires the iOS Safari WebBLE extension runtime."),this.unsupportedBackgroundSync=new P(this.unsupportedFeatureErrorFactory),this.unsupportedPeripheral=new W(this.unsupportedFeatureErrorFactory);}get backgroundSync(){return this.runtimeBluetooth?.backgroundSync??this.unsupportedBackgroundSync}get peripheral(){return this.runtimeBluetooth?.peripheral??this.unsupportedPeripheral}async requestDevice(e){if(!this.bluetooth)throw new u("BLUETOOTH_UNAVAILABLE");try{let t=await this.bluetooth.requestDevice(this.normalizeRequestDeviceOptions(e)??{acceptAllDevices:!0});return this.wrapDevice(t)}catch(t){throw u.from(t,"DEVICE_NOT_FOUND")}}async getDevices(){if(!this.bluetooth)throw new u("BLUETOOTH_UNAVAILABLE");let e=this.bluetooth;if(typeof e.getDevices!="function")return [];try{return (await e.getDevices()).map(i=>this.wrapDevice(i))}catch(t){throw u.from(t)}}async getAvailability(){if(!this.bluetooth)return false;try{return await this.bluetooth.getAvailability()}catch{return false}}async requestLEScan(e={acceptAllAdvertisements:true}){if(!this.bluetooth)throw new u("BLUETOOTH_UNAVAILABLE");let t=this.bluetooth;if(typeof t.requestLEScan!="function")return null;try{return await t.requestLEScan(e)}catch(i){throw u.from(i)}}normalizeRequestDeviceOptions(e){if(!e)return;let t=r=>{if(r)return r.map(o=>g(o))},i={};return e.acceptAllDevices!==void 0&&(i.acceptAllDevices=e.acceptAllDevices),e.optionalManufacturerData!==void 0&&(i.optionalManufacturerData=e.optionalManufacturerData),e.filters&&(i.filters=e.filters.map(r=>({...r,services:t(r.services)}))),e.exclusionFilters&&(i.exclusionFilters=e.exclusionFilters.map(r=>({...r,services:t(r.services)}))),e.optionalServices&&(i.optionalServices=t(e.optionalServices)),i}normalizeMaxConnections(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=0)throw new u("INVALID_PARAMETER",`Invalid maxConnections: ${e}. Must be a positive integer.`);return e}wrapDevice(e){let t=this.devices.get(e.id);if(t)return t;let i=new C(e,{beforeConnect:r=>{this.assertConnectionCapacity(r);},onConnectionChange:r=>{this.devices.set(r.id,r);}});return this.devices.set(e.id,i),i}assertConnectionCapacity(e){if(this.maxConnections===null||(this.devices.set(e.id,e),e.connected))return;let t=[...this.devices.values()].filter(i=>i.connected).length;if(t>=this.maxConnections)throw new u("CONNECTION_LIMIT_REACHED",`Connection limit reached (${t}/${this.maxConnections}). Disconnect another device or increase maxConnections before connecting ${e.name??e.id}.`,{retryAfterMs:1e3})}};function ee(n,e=0){return n.getUint8(e)}function te(n,e=0){return n.getUint16(e,true)}function re(n,e=0){return n.getUint16(e,false)}function ie(n,e=0){return n.getInt16(e,true)}function ne(n,e=0){return n.getUint32(e,true)}function oe(n,e=0){return n.getFloat32(e,true)}function se(n){return new TextDecoder().decode(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength))}function ae(n){return new Uint8Array(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength))}exports.BluetoothUUID=J;exports.WebBLE=k;exports.WebBLEDevice=C;exports.WebBLEError=u;exports.canonicalUUID=O;exports.detectPlatform=N;exports.getBluetoothAPI=B;exports.getCharacteristicName=K;exports.getDescriptor=V;exports.getDisplayName=X;exports.getServiceName=Z;exports.readBytes=ae;exports.readFloat32LE=oe;exports.readInt16LE=ie;exports.readUint16BE=re;exports.readUint16LE=te;exports.readUint32LE=ne;exports.readUint8=ee;exports.readUtf8=se;exports.resolveUUID=g;exports.withRetry=S;return exports;})({});//# sourceMappingURL=browser.global.js.map
var BeacioCore=(function(exports){'use strict';var U={maxAttempts:0,delayMs:-1,backoffMultiplier:0},ae=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),J={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the Beacio iOS app and enable the Safari extension. Use @beacio/core/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this Beacio instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},ce=/\b(bluefy|web ble browser|webble browser)\b/gi;function ue(n){let e=n.split(`
`,1)[0]??"";return e=e.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),e=e.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),e=e.replace(ce,""),e=e.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),e=e.replace(/[\s.,;:]+$/g,"").trim(),e}var a=class n extends Error{constructor(e,t,i){let r=J[e];super(t??r),this.name="BeacioError",this.code=e,this.suggestion=J[e],this.isRetriable=ae.has(e),this.retryAfterMs=i?.retryAfterMs;}static from(e,t="GATT_OPERATION_FAILED"){if(e instanceof n)return e;let i=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,r=e instanceof Error?e.message:String(e),o=ue(r)||void 0,s=r.toLowerCase();switch(i){case "TypeError":return new n("INVALID_PARAMETER",o);case "NotFoundError":return new n("DEVICE_NOT_FOUND",o);case "NotAllowedError":case "SecurityError":return new n("PERMISSION_DENIED",o);case "NetworkError":return new n("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3});case "TimeoutError":return new n("TIMEOUT",o,{retryAfterMs:1e3});case "InvalidStateError":if(s.includes("disconnect"))return new n("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3});break;}return r.includes("User cancelled")||r.includes("User canceled")?new n("USER_CANCELLED"):s.includes("no devices found")||r.includes("No Devices")?new n("DEVICE_NOT_FOUND"):r.includes("No Services matching")||s.includes("service not found")?new n("SERVICE_NOT_FOUND",o):r.includes("No Characteristics matching")||s.includes("characteristic not found")?new n("CHARACTERISTIC_NOT_FOUND",o):r.includes("GATT Server is disconnected")||s.includes("disconnected")?new n("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3}):s.includes("not supported")&&s.includes("read")?new n("CHARACTERISTIC_NOT_READABLE",o):s.includes("not supported")&&s.includes("write")?new n("CHARACTERISTIC_NOT_WRITABLE",o):s.includes("not supported")&&s.includes("notif")?new n("CHARACTERISTIC_NOT_NOTIFIABLE",o):s.includes("permission")?new n("PERMISSION_DENIED",o):new n(t,o)}};async function q(n,e=U){let t=e.maxAttempts>0?e.maxAttempts:3,i=e.delayMs>=0?e.delayMs:250,r=e.backoffMultiplier>=1?e.backoffMultiplier:1.5;if(!Number.isInteger(t)||t<=0)throw new a("INVALID_PARAMETER",`Invalid maxAttempts: ${t}. Must be a positive integer.`);if(!Number.isFinite(i)||i<0)throw new a("INVALID_PARAMETER",`Invalid delayMs: ${i}. Must be a non-negative number.`);if(!Number.isFinite(r)||r<1)throw new a("INVALID_PARAMETER",`Invalid backoffMultiplier: ${r}. Must be a number >= 1.`);for(let o=1;o<=t;o+=1)try{return await n(o)}catch(s){let u=a.from(s);if(o>=t||!u.isRetriable)throw u;let c=u.retryAfterMs??i*Math.pow(r,o-1);c>0&&await new Promise(l=>{setTimeout(l,c);});}throw new a("GATT_OPERATION_FAILED","Retry loop exited unexpectedly.")}var R="-0000-1000-8000-00805f9b34fb",$=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,x={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},S={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,local_east_coordinate:10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989},ee={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function te(n){return n.toString(16).padStart(8,"0")+R}function F(n){let e=Number(n);if(!Number.isFinite(e))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${n}`);let t=Math.trunc(e);if(t<0||t>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${n}`);return te(t+0)}function W(n,e,t){if(typeof n=="number")return F(n);let i=String(n);if($.test(i))return i;let r=e[i.toLowerCase()];if(r!==void 0)return te(r);throw new TypeError(`Failed to execute '${t}' on 'BluetoothUUID': Invalid UUID or registry name: "${i}"`)}function C(n){return n.toString(16).padStart(8,"0")+R}var B,O;function le(){if(!B){B=new Map;for(let[n,e]of Object.entries(x)){let t=C(e);B.has(t)||B.set(t,n);}}return B}function de(){if(!O){O=new Map;for(let[n,e]of Object.entries(S)){let t=C(e);O.has(t)||O.set(t,n);}}return O}var he=/^[0-9a-f]{4}$/,pe=/^[0-9a-f]{8}$/;function fe(n,e){let t=n.length,i=e.length,r=Array.from({length:i+1},(o,s)=>s);for(let o=1;o<=t;o++){let s=o-1;r[0]=o;for(let u=1;u<=i;u++){let c=r[u];r[u]=n[o-1]===e[u-1]?s:1+Math.min(s,r[u],r[u-1]),s=c;}}return r[i]}function ve(n){return n.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function ie(n,e){let t=e[n];if(t!==void 0)return t;let i=n.replace(/[._-]/g,"");if(i){for(let[r,o]of Object.entries(e))if(r.replace(/[._-]/g,"")===i)return o}}function m(n){if(typeof n=="number"){if(!Number.isInteger(n)||n<0||n>4294967295)throw new TypeError(`Invalid UUID integer: ${n}. Must be a 16-bit or 32-bit unsigned integer.`);return C(n)}let e=n.trim(),t=e.toLowerCase();if($.test(t))return t;if(he.test(t))return "0000"+t+R;if(pe.test(t))return t+R;let i=x[t]??S[t];if(i!==void 0)return C(i);let r=ve(e),o=ie(r,x);if(o!==void 0)return C(o);let s=ie(r,S);if(s!==void 0)return C(s);let u=Object.keys(x).concat(Object.keys(S)),c,l=4;for(let f of u){let _=fe(r,f);_<l&&(l=_,c=f);}!c&&r.length>=4&&(c=u.find(f=>f.startsWith(r)));let d=c?` Did you mean "${c}"?`:"";throw new TypeError(`Invalid UUID: "${n}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${d}`)}function me(n){return le().get(n.toLowerCase())}function ge(n){return de().get(n.toLowerCase())}function _e(n){let e=n.replace(/^(gap|gatt)\./,"");return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(e)?e.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" "):n}function re(n){return W(n,ee,"getDescriptor")}var Ae={canonicalUUID:F,getService:n=>W(n,x,"getService"),getCharacteristic:n=>W(n,S,"getCharacteristic"),getDescriptor:re};var ne=20;function y(n){if(!Number.isInteger(n)||n<=0)throw new a("INVALID_PARAMETER",`Invalid chunkSize: ${n}. Must be a positive integer.`);return n}function be(n,e=ne){return Number.isInteger(n)&&n>0?n:y(e)}var V=class{constructor(e){this.deps=e;this.inFlightWrites=new Map;}async write(e,t,i,r){let o=this.deps.validateTimeoutMs(r?.timeoutMs),s=await this.deps.getCharacteristic(e,t),u=Symbol(`write:${e}:${t}`);this.inFlightWrites.set(u,{service:e,characteristic:t,aborted:false});try{if(r?.mode==="without-response"){await this.deps.withOptionalTimeout(s.writeValueWithoutResponse(i),o,"Write without response timed out");return}await this.deps.withOptionalTimeout(s.writeValueWithResponse(i),o,"Write with response timed out");}catch(c){throw this.inFlightWrites.get(u)?.aborted?new a("WRITE_INCOMPLETE",`Write incomplete for ${e}/${t}: disconnected before completion`,{retryAfterMs:1e3}):a.from(c)}finally{this.inFlightWrites.delete(u);}}async writeFragmented(e,t,i,r){let o=this.toUint8Array(i),s=o.byteLength;if(s===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0};let u=r?.chunkSize!==void 0?y(r.chunkSize):this.deriveChunkSizeFromMtu(r?.mtu)??await this.deriveChunkSize(void 0,r?.mode),c=r?.maxRetries??0,l=r?.retryDelayMs??0,d=0,f=0,_=0;for(let b=0;b<s;b+=u){let p=Math.min(b+u,s),g=new Uint8Array(o.subarray(b,p)),A=0;for(;;)try{await this.write(e,t,g,r),d+=g.byteLength,f+=1;break}catch(w){if(A>=c)throw d>0&&d<s?new a("WRITE_INCOMPLETE",`Write fragmented incomplete (${d}/${s} bytes written): ${this.errorMessage(w instanceof Error?w:String(w))}`,{retryAfterMs:1e3}):a.from(w);A+=1,_+=1,l>0&&await this.delay(l);}}return {bytesWritten:d,totalBytes:s,chunkSize:u,chunkCount:f,retryCount:_}}async writeLarge(e,t,i,r){let o=this.toUint8Array(i),s=o.byteLength;if(s===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0};let u=await this.deriveChunkSize(r?.chunkSize,r?.mode),c=0,l=0;for(let d=0;d<s;d+=u){let f=Math.min(d+u,s),_=o.subarray(d,f),b=new Uint8Array(_);try{await this.write(e,t,b,r),c+=_.byteLength,l+=1;}catch(p){throw c>0&&c<s?new a("WRITE_INCOMPLETE",`Write incomplete (${c}/${s} bytes written): ${this.errorMessage(p instanceof Error?p:String(p))}`):a.from(p)}}if(c!==s)throw new a("WRITE_INCOMPLETE",`Write incomplete (${c}/${s} bytes written)`);return {bytesWritten:c,totalBytes:s,chunkSize:u,chunkCount:l}}async writeWithoutResponse(e,t,i,r){return this.write(e,t,i,{...r,mode:"without-response"})}async getWriteLimits(){if(!this.deps.isConnected())throw new a("DEVICE_DISCONNECTED");let e=this.deps.getTransport(),t=await e?.getWriteLimits?.(),i=t?.mtu??await e?.getMtu?.()??null;return {withResponse:t?.withResponse??null,withoutResponse:t?.withoutResponse??null,mtu:i}}async writeAuto(e,t,i,r){let o=this.toUint8Array(i),s=o.byteLength,u=new Uint8Array(o);if(s===0)return await this.write(e,t,u,r),{bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0,fragmented:false};let c=await this.deriveChunkSize(r?.chunkSize,r?.mode);return s<=c?(await this.write(e,t,u,r),{bytesWritten:s,totalBytes:s,chunkSize:s,chunkCount:1,retryCount:0,fragmented:false}):{...await this.writeFragmented(e,t,u,r),fragmented:true}}abortInFlightWrites(){for(let e of this.inFlightWrites.values())e.aborted=true;}async deriveChunkSize(e,t){if(e!==void 0)return y(e);let i=await this.getWriteLimits().catch(()=>({withResponse:null,withoutResponse:null,mtu:null})),r=t==="without-response"?i.withoutResponse:i.withResponse;return typeof r=="number"&&r>0?y(r):typeof i.mtu=="number"&&i.mtu>3?y(i.mtu-3):y(ne)}deriveChunkSizeFromMtu(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=3)throw new a("INVALID_PARAMETER",`Invalid mtu: ${e}. Must be an integer greater than 3.`);return y(e-3)}delay(e){return new Promise(t=>{setTimeout(t,e);})}toUint8Array(e){return e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}errorMessage(e){return e instanceof Error?e.message:String(e)}};var N=class N{constructor(e){this.deps=e;this.notificationStates=new Map;this.handleNotification=e=>{let t=e.target,i=t.value;if(i){for(let[r,o]of this.notificationStates)if((o.characteristic??this.deps.charCache.get(r))===t){let[u,c]=r.split(":");for(let l of o.callbacks)try{l(i);}catch(d){this.deps.emitError(a.from(d),{operation:"device.notification-callback",service:u,characteristic:c});}break}}};}getNotificationStates(){return this.notificationStates}subscribe(e,t,i,r){let{unsubscribe:o,ready:s}=this.registerNotificationConsumer(e,t,i);s.catch(l=>{let d=a.from(l);try{r?.onError?.(d);}catch(f){this.deps.emitError(a.from(f),{operation:"device.subscribe.onError",service:e,characteristic:t});}});let u=r?.autoRecover??true;u&&this.addToRecoveryRegistry(this.charKey(e,t),e,t,i);let c=o;return ()=>{c(),u&&this.removeFromRecoveryRegistry(this.charKey(e,t),i);}}async subscribeAsync(e,t,i,r){let{unsubscribe:o,release:s,ready:u}=this.registerNotificationConsumer(e,t,i),c=r?.autoRecover??true;c&&this.addToRecoveryRegistry(this.charKey(e,t),e,t,i);try{await u;}catch(l){let d=a.from(l);throw await s(),c&&this.removeFromRecoveryRegistry(this.charKey(e,t),i),d}return ()=>{o(),c&&this.removeFromRecoveryRegistry(this.charKey(e,t),i);}}async*notifications(e,t,i={maxQueueSize:N.DEFAULT_NOTIFICATION_QUEUE_SIZE}){let r=this.validateMaxQueueSize(i.maxQueueSize??N.DEFAULT_NOTIFICATION_QUEUE_SIZE),o=i?.overflowStrategy??"error",s=[],u=0,c={resolve:null,reject:null,done:false,failure:null},l=p=>{if(!c.failure)if(c.resolve){let g=c.resolve;c.resolve=null,c.reject=null,g({value:p,done:false});}else {if(s.length>=r){u+=1;let g={service:e,characteristic:t,strategy:o,queueSize:r,droppedCount:u};this.deps.emitQueueOverflow(g);try{i?.onOverflow?.(g);}catch(A){this.deps.emitError(a.from(A),{operation:"device.notifications.onOverflow",service:e,characteristic:t});}if(o==="error"){let A=new a("GATT_OPERATION_FAILED",`Notification queue overflowed (maxQueueSize=${r}). Increase queue size or consume faster.`);c.failure=A;let w=c.reject;c.resolve=null,c.reject=null,w?.(A);return}if(o==="drop-oldest"&&s.shift(),o==="drop-newest")return}s.push(p);}},d=this.charKey(e,t);this.addToRecoveryRegistry(d,e,t,l);let{unsubscribe:f,release:_,ready:b}=this.registerNotificationConsumer(e,t,l);try{await b;}catch(p){throw await _(),this.removeFromRecoveryRegistry(d,l),p}try{for(;!c.done;){if(c.failure)throw c.failure;if(s.length>0)yield s.shift();else {let p=await new Promise((g,A)=>{c.resolve=g,c.reject=A;});if(p.done){let g=this.deps.getReconnectGate();if(g&&!this.deps.isIntentionalDisconnect()){if(await g.promise,this.deps.isIntentionalDisconnect())return;continue}return}yield p.value;}}}finally{let p=c.resolve;c.resolve=null,c.reject=null,c.done=true,await _(),this.removeFromRecoveryRegistry(d,l),p&&p({value:void 0,done:true});}}teardownSubscriptions(e){for(let t of this.notificationStates.values())this.detachNotificationListener(t),t.nativeActive&&this.stopNotificationsSafely(t.characteristic,{operation:e});this.notificationStates.clear();}cleanupSubscriptions(){this.teardownSubscriptions("notification.cleanup");}suspendSubscriptions(){this.teardownSubscriptions("notification.suspend");}async recoverSubscriptions(){let e=[...this.deps.recoveryRegistry.entries()];for(let[t,i]of e)try{for(let r of i.callbacks){let{ready:o}=this.registerNotificationConsumer(i.service,i.characteristic,r);await o;}}catch(r){this.deps.recoveryRegistry.delete(t);let o=a.from(r);this.deps.emitSubscriptionLost({service:i.service,characteristic:i.characteristic,error:o}),this.deps.emitError(o,{operation:"notification.recover",service:i.service,characteristic:i.characteristic});}}registerNotificationConsumer(e,t,i){let r=this.charKey(e,t),o=this.notificationStates.get(r);o||(o={callbacks:new Set,characteristic:null,listenerAttached:false,nativeActive:false,reconcilePromise:null},this.notificationStates.set(r,o)),o.callbacks.add(i);let s=()=>{let u=this.notificationStates.get(r);return u?.callbacks.has(i)?(u.callbacks.delete(i),this.syncNotificationState(r,e,t)):Promise.resolve()};return {unsubscribe:()=>{s();},release:s,ready:this.syncNotificationState(r,e,t)}}syncNotificationState(e,t,i){let r=this.notificationStates.get(e);if(!r)return Promise.resolve();let u=(r.reconcilePromise??Promise.resolve()).catch(c=>{this.deps.emitError(a.from(c),{operation:"notification.reconcile",service:t,characteristic:i});}).then(async()=>{for(;;){if(this.notificationStates.get(e)!==r){await this.deactivateNotificationState(r);return}if(r.callbacks.size===0){if(this.detachNotificationListener(r),r.nativeActive){r.nativeActive=false,await this.stopNotificationsSafely(r.characteristic,{operation:"notification.stop",service:t,characteristic:i});continue}this.deleteNotificationStateIfIdle(e,r);return}let l=r.characteristic??await this.deps.getCharacteristic(t,i);if(r.characteristic=l,r.listenerAttached||(l.addEventListener("characteristicvaluechanged",this.handleNotification),r.listenerAttached=true),!r.nativeActive){if(await l.startNotifications(),r.nativeActive=true,this.notificationStates.get(e)!==r){await this.deactivateNotificationState(r);return}if(r.callbacks.size===0)continue}if(r.callbacks.size!==0)return}}).finally(()=>{r.reconcilePromise===u&&(r.reconcilePromise=null,this.notificationStates.get(e)===r&&this.deleteNotificationStateIfIdle(e,r));});return r.reconcilePromise=u,u}async deactivateNotificationState(e){this.detachNotificationListener(e),e.nativeActive&&(e.nativeActive=false,await this.stopNotificationsSafely(e.characteristic,{operation:"notification.deactivate"}));}detachNotificationListener(e){!e.listenerAttached||!e.characteristic||(e.characteristic.removeEventListener("characteristicvaluechanged",this.handleNotification),e.listenerAttached=false);}deleteNotificationStateIfIdle(e,t){this.notificationStates.get(e)===t&&(t.callbacks.size>0||t.nativeActive||t.reconcilePromise||this.notificationStates.delete(e));}validateMaxQueueSize(e){if(!Number.isInteger(e)||e<=0)throw new a("INVALID_PARAMETER",`Invalid maxQueueSize: ${e}. Must be a positive integer.`);return e}async stopNotificationsSafely(e,t){if(e)try{await e.stopNotifications();}catch(i){this.deps.emitError(a.from(i),t);}}charKey(e,t){return `${m(e)}:${m(t)}`}addToRecoveryRegistry(e,t,i,r){let o=this.deps.recoveryRegistry.get(e);o||(o={service:t,characteristic:i,callbacks:new Set},this.deps.recoveryRegistry.set(e,o)),o.callbacks.add(r);}removeFromRecoveryRegistry(e,t){let i=this.deps.recoveryRegistry.get(e);i&&(i.callbacks.delete(t),i.callbacks.size===0&&this.deps.recoveryRegistry.delete(e));}};N.DEFAULT_NOTIFICATION_QUEUE_SIZE=256;var G=N;function ye(n){if((typeof n=="object"&&n!==null&&"name"in n&&typeof n.name=="string"?n.name:"")==="SecurityError")return true;let t=(n instanceof Error?n.message:String(n)).toLowerCase();return (t.includes("not allowed to access")||t.includes("blocklist")||t.includes("blocked"))&&t.includes("service")}function Ee(n){return new DOMException(`This site is not allowed to access the Bluetooth service ${n}. Add "${n}" to the optionalServices array in your requestDevice() options, then reconnect.`,"SecurityError")}var I=class{constructor(e,t={}){this.server=null;this.primaryServicesCache=null;this.serviceCache=new Map;this.charCache=new Map;this.recoveryRegistry=new Map;this.disconnectListeners=new Set;this.reconnectedListeners=new Set;this.queueOverflowListeners=new Set;this.subscriptionLostListeners=new Set;this.errorListeners=new Set;this.reconnectGate=null;this.intentionalDisconnect=false;this.lastDisconnectReason=null;this.autoReconnectConfig=null;this.autoReconnectAbort=null;this.id=e.id,this.name=e.name??null,this.raw=e,this.hooks=t,this.writeChunker=new V({getCharacteristic:(i,r)=>this.getCharacteristic(i,r),emitError:(i,r)=>this.emitError(i,r),validateTimeoutMs:i=>this.validateTimeoutMs(i),withOptionalTimeout:(i,r,o)=>this.withOptionalTimeout(i,r,o),isConnected:()=>this.connected,getTransport:()=>this.raw.gatt}),this.notificationManager=new G({getCharacteristic:(i,r)=>this.getCharacteristic(i,r),emitError:(i,r)=>this.emitError(i,r),emitSubscriptionLost:i=>this.emitSubscriptionLost(i),emitQueueOverflow:i=>this.emitQueueOverflow(i),recoveryRegistry:this.recoveryRegistry,charCache:this.charCache,getReconnectGate:()=>this.reconnectGate,isIntentionalDisconnect:()=>this.intentionalDisconnect}),typeof e.addEventListener=="function"&&e.addEventListener("gattserverdisconnected",()=>{this.handleDisconnect();});}get connected(){return this.server?.connected??false}async connect(e){if(this.connected)return;e?.autoReconnect&&(this.autoReconnectConfig=e.autoReconnect===true?{}:e.autoReconnect),this.hooks.beforeConnect?.(this);let t=this.raw.gatt;if(!t)throw new a("GATT_OPERATION_FAILED","Device has no GATT server");let i=this.reconnectGate;try{this.server=await t.connect(),this.lastDisconnectReason=null,await this.notificationManager.recoverSubscriptions();for(let r of this.reconnectedListeners)try{r();}catch(o){this.emitError(a.from(o),{operation:"device.reconnected-listener"});}this.intentionalDisconnect=!1,this.hooks.onConnectionChange?.(this);}catch(r){throw this.server?.connected||(this.server=null,this.hooks.onConnectionChange?.(this)),a.from(r)}finally{this.reconnectGate===i&&(this.reconnectGate=null),i?.resolve();}}disconnect(){this.intentionalDisconnect=true,this.lastDisconnectReason="intentional",this.autoReconnectConfig=null,this.autoReconnectAbort?.abort(),this.autoReconnectAbort=null,this.writeChunker.abortInFlightWrites(),this.notificationManager.cleanupSubscriptions(),this.recoveryRegistry.clear(),this.reconnectGate&&(this.reconnectGate.resolve(),this.reconnectGate=null),this.server?.disconnect(),this.server=null,this.primaryServicesCache=null,this.hooks.onConnectionChange?.(this);}async connectWithRetry(e=U){await q(async()=>{await this.connect();},e);}async read(e,t,i,r){let o=typeof i=="function"?i:void 0,s=typeof i=="function"?r:i,u=this.validateTimeoutMs(s?.timeoutMs),c=await this.getCharacteristic(e,t);try{let l=await this.withOptionalTimeout(c.readValue(),u,"Read timed out");return o?await o(l):l}catch(l){throw a.from(l)}}async write(e,t,i,r){return this.writeChunker.write(e,t,i,r)}async writeFragmented(e,t,i,r){return this.writeChunker.writeFragmented(e,t,i,r)}async writeLarge(e,t,i,r){return this.writeChunker.writeLarge(e,t,i,r)}async writeWithoutResponse(e,t,i,r){return this.writeChunker.writeWithoutResponse(e,t,i,r)}async getWriteLimits(){return this.writeChunker.getWriteLimits()}async getMtu(){return (await this.getWriteLimits()).mtu}async writeAuto(e,t,i,r){return this.writeChunker.writeAuto(e,t,i,r)}subscribe(e,t,i,r){return this.notificationManager.subscribe(e,t,i,r)}async subscribeAsync(e,t,i,r){return this.notificationManager.subscribeAsync(e,t,i,r)}onCharacteristicOverflow(e,t,i){let r=null,o=false;return this.getCharacteristic(e,t).then(s=>{o||(s.addEventListener("beacio:overflow",i),r=()=>s.removeEventListener("beacio:overflow",i));}).catch(s=>{this.emitError(a.from(s),{operation:"device.onCharacteristicOverflow",service:e,characteristic:t});}),()=>{o=true,r?.(),r=null;}}notifications(e,t,i){return this.notificationManager.notifications(e,t,i)}async watchAdvertisements(){if(typeof this.raw.watchAdvertisements!="function")throw new a("GATT_OPERATION_FAILED","watchAdvertisements is not supported on this device");await this.raw.watchAdvertisements();}async unwatchAdvertisements(){let e=this.raw;if(typeof e.unwatchAdvertisements!="function")throw new a("GATT_OPERATION_FAILED","unwatchAdvertisements is not supported on this device");await e.unwatchAdvertisements();}async forget(){if(typeof this.raw.forget!="function")throw new a("GATT_OPERATION_FAILED","forget is not supported on this device");await this.raw.forget();}async getPrimaryServices(){if(!this.connected)throw new a("DEVICE_DISCONNECTED");if(this.primaryServicesCache)return this.primaryServicesCache;try{let t=(await this.server.getPrimaryServices()).map(i=>{let r=this.serviceCache.get(i.uuid)??i;return this.serviceCache.set(i.uuid,r),r});return this.primaryServicesCache=t,t}catch(e){throw a.from(e)}}async getEffectiveMtu(){let e=await this.getWriteLimits();return typeof e.mtu=="number"&&e.mtu>0?e.mtu:typeof e.withResponse=="number"&&e.withResponse>0?e.withResponse+3:typeof e.withoutResponse=="number"&&e.withoutResponse>0?e.withoutResponse+3:23}getLastDisconnectReason(){return this.lastDisconnectReason}getActiveSubscriptions(){let e=this.notificationManager.getNotificationStates();return [...new Set([...e.keys(),...this.recoveryRegistry.keys()])].map(i=>{let r=e.get(i),o=this.recoveryRegistry.get(i),[s,u]=i.split(":");return {service:s,characteristic:u,callbackCount:r?.callbacks.size??o?.callbacks.size??0,autoRecovering:o!==void 0,nativeActive:r?.nativeActive??false}})}on(e,t){return e==="disconnected"&&this.disconnectListeners.add(t),e==="reconnected"&&this.reconnectedListeners.add(t),e==="queue-overflow"&&this.queueOverflowListeners.add(t),e==="subscription-lost"&&this.subscriptionLostListeners.add(t),()=>{this.off(e,t);}}off(e,t){e==="disconnected"&&this.disconnectListeners.delete(t),e==="reconnected"&&this.reconnectedListeners.delete(t),e==="queue-overflow"&&this.queueOverflowListeners.delete(t),e==="subscription-lost"&&this.subscriptionLostListeners.delete(t);}addErrorListener(e){return this.errorListeners.add(e),()=>{this.removeErrorListener(e);}}removeErrorListener(e){this.errorListeners.delete(e);}handleDisconnect(){let e=this.intentionalDisconnect?"intentional":"unexpected";if(this.lastDisconnectReason=e,this.writeChunker.abortInFlightWrites(),this.notificationManager.suspendSubscriptions(),this.serviceCache.clear(),this.primaryServicesCache=null,this.charCache.clear(),this.server=null,this.hooks.onConnectionChange?.(this),this.recoveryRegistry.size>0){let t,i=new Promise(r=>{t=r;});this.reconnectGate={promise:i,resolve:t};}for(let t of this.disconnectListeners)try{t(e);}catch(i){this.emitError(a.from(i),{operation:"device.disconnected-listener"});}e==="unexpected"&&this.autoReconnectConfig&&this.startAutoReconnect(this.autoReconnectConfig);}startAutoReconnect(e){this.autoReconnectAbort?.abort();let t=new AbortController;this.autoReconnectAbort=t;let i=e.maxAttempts??1/0,r=e.initialDelayMs??1e3,o=e.maxDelayMs??3e4,s=e.backoffMultiplier??2;(async()=>{let c=r;for(let l=1;l<=i;l++){if(t.signal.aborted||(await new Promise(d=>{let f=setTimeout(d,c);t.signal.addEventListener("abort",()=>{clearTimeout(f),d();},{once:true});}),t.signal.aborted))return;try{await this.connect();return}catch{c=Math.min(c*s,o);}}this.emitError(new a("CONNECTION_TIMEOUT",`Auto-reconnect failed after ${i} attempts`),{operation:"device.auto-reconnect"});})();}async withOptionalTimeout(e,t,i){if(t===void 0)return e;let r=null,o=new Promise((s,u)=>{r=setTimeout(()=>{u(new a("TIMEOUT",i));},t);});try{return await Promise.race([e,o])}finally{r!==null&&clearTimeout(r);}}validateTimeoutMs(e){if(e!==void 0){if(!Number.isFinite(e)||e<=0)throw new a("INVALID_PARAMETER",`Invalid timeoutMs: ${e}. Must be a positive number.`);return e}}fanout(e,t,i){for(let r of e)try{r(t);}catch(o){this.emitError(a.from(o),{operation:i});}}emitQueueOverflow(e){this.fanout(this.queueOverflowListeners,e,"device.queue-overflow-listener");}emitSubscriptionLost(e){this.fanout(this.subscriptionLostListeners,e,"device.subscription-lost-listener");}emitError(e,t){for(let i of this.errorListeners)try{i(e,t);}catch{}}async getCharacteristic(e,t){if(!this.connected)throw new a("DEVICE_DISCONNECTED");let i=this.charKey(e,t),r=this.charCache.get(i);if(r)return r;let o=await this.getService(e),s=m(t);try{let u=await o.getCharacteristic(s);return this.charCache.set(i,u),u}catch(u){throw a.from(u)}}async getService(e){let t=m(e);if(this.primaryServicesCache){let r=this.primaryServicesCache.find(o=>o.uuid===t);if(r)return r}let i=this.serviceCache.get(t);if(i)return i;try{let r=await this.server.getPrimaryService(t);return this.serviceCache.set(t,r),r}catch(r){throw ye(r)?Ee(t):a.from(r)}}charKey(e,t){return `${m(e)}:${m(t)}`}};var oe="__beacioCDNStub";function H(){if(typeof navigator>"u")return "unsupported";let n=navigator;return n.beacio?.__beacio===true?"safari-extension":n.bluetooth&&!n.bluetooth[oe]?"native":"unsupported"}function j(){if(typeof navigator>"u")return null;let n=navigator;return n.beacio?.__beacio===true?n.beacio:n.bluetooth&&!n.bluetooth[oe]?n.bluetooth:null}var Q={platform:"auto",maxConnections:0,defaultOptionalServices:[]};var Y=class{constructor(e){this.errorFactory=e;}unsupported(){throw this.errorFactory()}requestPermission(){this.unsupported();}requestBackgroundConnection(e){this.unsupported();}registerCharacteristicNotifications(e){this.unsupported();}registerBeaconScanning(e){this.unsupported();}getRegistrations(){this.unsupported();}unregister(e){this.unsupported();}update(e,t){this.unsupported();}connect(e){return this.requestBackgroundConnection(e)}subscribe(e){return this.registerCharacteristicNotifications(e)}scan(e){return this.registerBeaconScanning(e)}list(){return this.getRegistrations()}destroy(){}},X=class extends EventTarget{constructor(t){super();this.onwriterequest=null;this.onsubscriptionchange=null;this.onconnectionstatechange=null;this.onadvertisingstatechange=null;this.onnotificationready=null;this.errorFactory=t;}get advertising(){return false}unsupported(){throw this.errorFactory()}advertise(t){this.unsupported();}addService(t){this.unsupported();}stopAdvertising(){this.unsupported();}send(t){this.unsupported();}destroy(){}},K=class{constructor(e=Q){this.devices=new Map;this.registeredOptionalServices=new Set;this.platform=e.platform==="auto"?H():e.platform,this.maxConnections=this.normalizeMaxConnections(e.maxConnections),e.defaultOptionalServices.length>0&&this.registerServices(e.defaultOptionalServices),this.bluetooth=this.platform!=="unsupported"?j():null,this.runtimeBluetooth=this.bluetooth,this.isSupported=this.bluetooth!==null,this.unsupportedFeatureErrorFactory=()=>this.platform==="unsupported"?new a("BLUETOOTH_UNAVAILABLE"):new a("GATT_OPERATION_FAILED","This Beacio feature requires the iOS Safari Beacio extension runtime."),this.unsupportedBackgroundSync=new Y(this.unsupportedFeatureErrorFactory),this.unsupportedPeripheral=new X(this.unsupportedFeatureErrorFactory);}get backgroundSync(){return this.runtimeBluetooth?.backgroundSync??this.unsupportedBackgroundSync}get peripheral(){return this.runtimeBluetooth?.peripheral??this.unsupportedPeripheral}async requestDevice(e){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");try{let t=await this.bluetooth.requestDevice(this.normalizeRequestDeviceOptions(e)??{acceptAllDevices:!0});return this.wrapDevice(t)}catch(t){throw a.from(t,"DEVICE_NOT_FOUND")}}registerServices(e){for(let t of e)this.registeredOptionalServices.add(m(t));}async getDevices(){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");let e=this.bluetooth;if(typeof e.getDevices!="function")return [];try{return (await e.getDevices()).map(i=>this.wrapDevice(i))}catch(t){throw a.from(t)}}async getAvailability(){if(!this.bluetooth)return false;try{return await this.bluetooth.getAvailability()}catch{return false}}async requestLEScan(e={acceptAllAdvertisements:true}){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");let t=this.bluetooth;if(typeof t.requestLEScan!="function")return null;try{return await t.requestLEScan(e)}catch(i){throw a.from(i)}}normalizeRequestDeviceOptions(e){let t=this.mergeOptionalServices(e?.optionalServices);if(!e)return t?{optionalServices:t}:void 0;let i=o=>{if(o)return o.map(s=>m(s))},r={};return e.acceptAllDevices!==void 0&&(r.acceptAllDevices=e.acceptAllDevices),e.optionalManufacturerData!==void 0&&(r.optionalManufacturerData=e.optionalManufacturerData),e.filters&&(r.filters=e.filters.map(o=>({...o,services:i(o.services)}))),e.exclusionFilters&&(r.exclusionFilters=e.exclusionFilters.map(o=>({...o,services:i(o.services)}))),t&&(r.optionalServices=t),r}mergeOptionalServices(e){if(!e&&this.registeredOptionalServices.size===0)return;let t=new Set;for(let i of e??[])t.add(m(i));for(let i of this.registeredOptionalServices)t.add(i);return [...t]}normalizeMaxConnections(e){if(e===0)return null;if(!Number.isInteger(e)||e<0)throw new a("INVALID_PARAMETER",`Invalid maxConnections: ${e}. Must be 0 (unlimited) or a positive integer.`);return e}wrapDevice(e){let t=this.devices.get(e.id);if(t)return t;let i=new I(e,{beforeConnect:r=>{this.assertConnectionCapacity(r);},onConnectionChange:r=>{this.devices.set(r.id,r);}});return this.devices.set(e.id,i),i}assertConnectionCapacity(e){if(this.maxConnections===null||(this.devices.set(e.id,e),e.connected))return;let t=[...this.devices.values()].filter(i=>i.connected).length;if(t>=this.maxConnections)throw new a("CONNECTION_LIMIT_REACHED",`Connection limit reached (${t}/${this.maxConnections}). Disconnect another device or increase maxConnections before connecting ${e.name??e.id}.`,{retryAfterMs:1e3})}};function we(n){if(!Number.isInteger(n)||n<0||n>100)throw new a("INVALID_PARAMETER",`Invalid percent: ${n}. Must be an integer in 0..100.`);return n}function xe(n){return Number.isFinite(n)?Math.min(100,Math.max(0,Math.trunc(n))):0}var Se="https://beacio.com/setup";var Ce={STATE_CHANGE:"beacio:statechange",READY:"beacio:ready",INSTALLED_INACTIVE:"beacio:installedinactive",NOT_INSTALLED:"beacio:notinstalled",EXTENSION_READY:"beacio:extension:ready",EXTENSION_PING:"beacio:extension:ping",EXTENSION_PONG:"beacio:extension:pong",EXTENSION_ACTIVATE_REQUEST:"beacio:extension:activate-request",EXTENSION_ACTIVATE_RESULT:"beacio:extension:activate-result",EXTENSION_INSTALLED:"beacio:extension:installed",EXTENSION_STATUS_CHANGE:"beacio:extension:statuschange"};function T(n,e,t,i){if(!Number.isInteger(t)||t<0||t+i>e.byteLength)throw new a("INVALID_PARAMETER",`${n}: cannot read ${i} byte${i===1?"":"s"} at offset ${t} of a ${e.byteLength}-byte DataView (value too short).`)}function Te(n,e=0){return T("readUint8",n,e,1),n.getUint8(e)}function De(n,e=0){return T("readUint16LE",n,e,2),n.getUint16(e,true)}function Re(n,e=0){return T("readUint16BE",n,e,2),n.getUint16(e,false)}function Be(n,e=0){return T("readInt16LE",n,e,2),n.getInt16(e,true)}function Oe(n,e=0){return T("readUint32LE",n,e,4),n.getUint32(e,true)}function Ne(n,e=0){return T("readFloat32LE",n,e,4),n.getFloat32(e,true)}function Ie(n){return new TextDecoder().decode(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength))}function Le(n){return new Uint8Array(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength))}var D=class{constructor(e,t){this._connected=false;this._services=new Map;this._device=e;for(let i of t)this._services.set(i.uuid,new L(e,i));}get connected(){return this._connected}async connect(){if(this._device.shouldFailConnect())throw new DOMException("Simulated transient connection failure","NetworkError");return this._connected=true,this.asBluetoothRemoteGATTServer()}disconnect(){this._connected=false;for(let e of this._services.values())e.stopAllNotifications();}async getPrimaryService(e){this._assertConnected();let t=this._services.get(e);if(!t)throw new DOMException(`No Services matching UUID ${e} found`,"NotFoundError");return t.asBluetoothRemoteGATTService()}async getPrimaryServices(e){return this._assertConnected(),(e?[this._services.get(e)].filter(Boolean):Array.from(this._services.values())).map(i=>i.asBluetoothRemoteGATTService())}getService(e){return this._services.get(e)}asBluetoothRemoteGATTServer(e){let t=this;return {get connected(){return t._connected},get device(){return e},connect:()=>t.connect(),disconnect:()=>t.disconnect(),getPrimaryService:r=>t.getPrimaryService(r),getPrimaryServices:r=>t.getPrimaryServices(r)}}_assertConnected(){if(!this._connected)throw new DOMException("GATT Server is disconnected. Cannot perform GATT operations.","NetworkError")}},L=class{constructor(e,t){this._characteristics=new Map;this.uuid=t.uuid,this.isPrimary=t.isPrimary??true;for(let i of t.characteristics??[])this._characteristics.set(i.uuid,new P(i));}async getCharacteristic(e){let t=this._characteristics.get(e);if(!t)throw new DOMException(`No Characteristics matching UUID ${e} found`,"NotFoundError");return t.asBluetoothRemoteGATTCharacteristic(this.asBluetoothRemoteGATTService())}async getCharacteristics(e){let t=e?[this._characteristics.get(e)].filter(Boolean):Array.from(this._characteristics.values()),i=this.asBluetoothRemoteGATTService();return t.map(r=>r.asBluetoothRemoteGATTCharacteristic(i))}getChar(e){return this._characteristics.get(e)}stopAllNotifications(){for(let e of this._characteristics.values())e.stopNotifications();}asBluetoothRemoteGATTService(e){let t=this;return {uuid:this.uuid,isPrimary:this.isPrimary,get device(){return e},getCharacteristic:i=>t.getCharacteristic(i),getCharacteristics:i=>t.getCharacteristics(i),getIncludedService:async()=>{throw new DOMException("Not implemented","NotSupportedError")},getIncludedServices:async()=>[],addEventListener:()=>{},removeEventListener:()=>{},dispatchEvent:()=>true,oncharacteristicvaluechanged:null,onserviceadded:null,onservicechanged:null,onserviceremoved:null}}},P=class{constructor(e){this._notifying=false;this._listeners=new Map;this._descriptors=new Map;if(this.uuid=e.uuid,this._properties={broadcast:e.properties?.broadcast??false,read:e.properties?.read??true,write:e.properties?.write??false,writeWithoutResponse:e.properties?.writeWithoutResponse??false,notify:e.properties?.notify??false,indicate:e.properties?.indicate??false,authenticatedSignedWrites:e.properties?.authenticatedSignedWrites??false,reliableWrite:e.properties?.reliableWrite??false,writableAuxiliaries:e.properties?.writableAuxiliaries??false},e.value){let t=e.value instanceof Uint8Array?e.value.buffer.slice(e.value.byteOffset,e.value.byteOffset+e.value.byteLength):e.value;this._value=new DataView(t);}else this._value=new DataView(new ArrayBuffer(0));for(let t of e.descriptors??[])this._descriptors.set(t.uuid,new k(t));}setValue(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);}emitNotification(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);let i=new Event("characteristicvaluechanged");Object.defineProperty(i,"target",{value:{value:this._value},writable:false});let r=this._listeners.get("characteristicvaluechanged");if(r)for(let o of r)o(i);}stopNotifications(){this._notifying=false;}get isNotifying(){return this._notifying}getDesc(e){return this._descriptors.get(e)}asBluetoothRemoteGATTCharacteristic(e){let t=this;return {uuid:this.uuid,service:e,properties:{broadcast:this._properties.broadcast,read:this._properties.read,writeWithoutResponse:this._properties.writeWithoutResponse,write:this._properties.write,notify:this._properties.notify,indicate:this._properties.indicate,authenticatedSignedWrites:this._properties.authenticatedSignedWrites,reliableWrite:this._properties.reliableWrite,writableAuxiliaries:this._properties.writableAuxiliaries},get value(){return t._value},readValue:async()=>{if(!t._properties.read)throw new DOMException("Characteristic does not support read","NotSupportedError");return t._value},writeValue:async i=>{if(!t._properties.write)throw new DOMException("Characteristic does not support write","NotSupportedError");t._writeValue(i);},writeValueWithResponse:async i=>{if(!t._properties.write)throw new DOMException("Characteristic does not support write","NotSupportedError");t._writeValue(i);},writeValueWithoutResponse:async i=>{if(!t._properties.writeWithoutResponse)throw new DOMException("Characteristic does not support write without response","NotSupportedError");t._writeValue(i);},startNotifications:async function(){if(!t._properties.notify&&!t._properties.indicate)throw new DOMException("Characteristic does not support notifications","NotSupportedError");return t._notifying=true,this},stopNotifications:async function(){return t._notifying=false,this},addEventListener:(i,r)=>{t._listeners.has(i)||t._listeners.set(i,new Set),t._listeners.get(i).add(r);},removeEventListener:(i,r)=>{t._listeners.get(i)?.delete(r);},dispatchEvent:()=>true,getDescriptor:async i=>{let r=t._descriptors.get(i);if(!r)throw new DOMException(`No Descriptors matching UUID ${i} found`,"NotFoundError");return r.asBluetoothRemoteGATTDescriptor(t.asBluetoothRemoteGATTCharacteristic(e))},getDescriptors:async i=>{let r=i?[t._descriptors.get(i)].filter(Boolean):Array.from(t._descriptors.values()),o=t.asBluetoothRemoteGATTCharacteristic(e);return r.map(s=>s.asBluetoothRemoteGATTDescriptor(o))},oncharacteristicvaluechanged:null}}_writeValue(e){let t=e instanceof ArrayBuffer?e:e.buffer??e.buffer;this._value=new DataView(t);}},k=class{constructor(e){if(this.uuid=e.uuid,e.value){let t=e.value instanceof Uint8Array?e.value.buffer.slice(e.value.byteOffset,e.value.byteOffset+e.value.byteLength):e.value;this._value=new DataView(t);}else this._value=new DataView(new ArrayBuffer(0));}setValue(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);}get value(){return this._value}asBluetoothRemoteGATTDescriptor(e){let t=this;return {uuid:this.uuid,characteristic:e,get value(){return t._value},readValue:async()=>t._value,writeValue:async i=>{let r=i instanceof ArrayBuffer?i:i.buffer??i.buffer;t._value=new DataView(r);}}}};var Pe=0,E=class{constructor(e={}){this._listeners=new Map;this._watchingAdvertisements=false;this.id=e.id??`mock-device-${++Pe}`,this.name=e.name,this._serviceUUIDs=e.serviceUUIDs??[],this._gatt=new D(this,e.services??[]),this._rssi=e.rssi??-60,this._remainingConnectFailures=e.failConnectAttempts??0,this._writeLimits={withResponse:e.writeLimits?.withResponse??null,withoutResponse:e.writeLimits?.withoutResponse??null,mtu:e.writeLimits?.mtu??null};}matchesFilter(e){return !(e.services&&!e.services.some(i=>this._serviceUUIDs.includes(String(i)))||e.name&&e.name!==this.name||e.namePrefix&&!this.name?.startsWith(e.namePrefix))}asBluetoothDevice(){let e=this,t={id:this.id,name:this.name??null,gatt:null,watchAdvertisements:async i=>{if(e._watchingAdvertisements=true,i?.signal){if(i.signal.aborted){e._watchingAdvertisements=false;return}i.signal.addEventListener("abort",()=>{e._watchingAdvertisements=false;},{once:true});}},addEventListener:(i,r)=>{e._addListener(i,r);},removeEventListener:(i,r)=>{e._removeListener(i,r);},dispatchEvent:i=>true,get watchingAdvertisements(){return e._watchingAdvertisements},unwatchAdvertisements:async()=>{e._watchingAdvertisements=false;},forget:async()=>{},onadvertisementreceived:null,ongattserverdisconnected:null,oncharacteristicvaluechanged:null,onserviceadded:null,onservicechanged:null,onserviceremoved:null};return t.gatt=this._gatt.asBluetoothRemoteGATTServer(t),t.gatt.getMtu=async()=>this._writeLimits.mtu,t.gatt.getWriteLimits=async()=>({...this._writeLimits}),t}shouldFailConnect(){return this._remainingConnectFailures<=0?false:(this._remainingConnectFailures-=1,true)}simulateDisconnect(){this._gatt.disconnect(),this._emit("gattserverdisconnected",new Event("gattserverdisconnected"));}get gatt(){return this._gatt}get serviceUUIDs(){return this._serviceUUIDs}get rssi(){return this._rssi}emitAdvertisement(e={}){if(this._advertisementSink){this._advertisementSink(this,e);return}this.dispatchAdvertisementEvent(e);}setRSSI(e){this._rssi=e;}setAdvertisementSink(e){this._advertisementSink=e;}dispatchAdvertisementEvent(e={}){this._watchingAdvertisements&&this._emit("advertisementreceived",this.createAdvertisementEvent(this.asBluetoothDevice(),e));}createAdvertisementEvent(e,t={}){let i=new Event("advertisementreceived");return Object.defineProperties(i,{device:{value:e,writable:false},name:{value:this.name,writable:false},uuids:{value:[...t.uuids??this._serviceUUIDs],writable:false},rssi:{value:t.rssi??this._rssi,writable:false},txPower:{value:t.txPower,writable:false},manufacturerData:{value:t.manufacturerData??new Map,writable:false},serviceData:{value:t.serviceData??new Map,writable:false}}),i}_addListener(e,t){this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add(t);}_removeListener(e,t){this._listeners.get(e)?.delete(t);}_emit(e,t){let i=this._listeners.get(e);if(i)for(let r of i)r(t);}};var v=()=>Promise.reject(new DOMException("Beacio extension API not implemented in MockBluetooth","NotSupportedError")),z=()=>{},M=class{constructor(e={}){this._devices=new Map;this._listeners=new Map;this._scanActive=false;this.backgroundSync={requestPermission:v,requestBackgroundConnection:v,registerCharacteristicNotifications:v,registerBeaconScanning:v,getRegistrations:v,unregister:v,update:v,connect:v,subscribe:v,scan:v,list:v,destroy:z};this.peripheral={advertising:false,advertise:v,stopAdvertising:v,send:v,destroy:z,addEventListener:z,removeEventListener:z,onwriterequest:null,onsubscriptionchange:null,onconnectionstatechange:null,onadvertisingstatechange:null};this._handleAdvertisement=(e,t)=>{if(e.dispatchAdvertisementEvent(t),!this._scanActive||!this._matchesScan(e))return;let i=e.createAdvertisementEvent(e.asBluetoothDevice(),t),r=this._listeners.get("advertisementreceived");if(r)for(let o of r)o(i);};if(this._available=e.available??true,e.devices)for(let t of e.devices){let i=new E(t);i.setAdvertisementSink(this._handleAdvertisement),this._devices.set(i.id,i);}}async getAvailability(){return this._available}async requestDevice(e){if(!this._available)throw new DOMException("Bluetooth adapter not available","NotFoundError");let t=this._findMatchingDevices(e);if(t.length===0)throw new DOMException("No devices found matching the filter criteria","NotFoundError");return t[0].asBluetoothDevice()}async getDevices(){return Array.from(this._devices.values()).map(e=>e.asBluetoothDevice())}async requestLEScan(e){if(this._scanActive)throw new DOMException("Scan already in progress","InvalidStateError");this._scanActive=true,this._lastScanOptions=e;let t={active:true,keepRepeatedDevices:e?.keepRepeatedDevices??false,acceptAllAdvertisements:e?.acceptAllAdvertisements??false,stop:()=>{this._scanActive=false,this._lastScanOptions=void 0,t.active=false;}};return t}addEventListener(e,t){this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add(t);}removeEventListener(e,t){this._listeners.get(e)?.delete(t);}addDevice(e){let t=new E(e);return t.setAdvertisementSink(this._handleAdvertisement),this._devices.set(t.id,t),t}removeDevice(e){let t=this._devices.get(e);t&&(t.setAdvertisementSink(void 0),t.simulateDisconnect(),this._devices.delete(e));}getDevice(e){return this._devices.get(e)}setAvailable(e){this._available=e;}install(){return typeof globalThis.navigator>"u"?this:(this._installedNavigatorBluetooth=globalThis.navigator.bluetooth,Object.defineProperty(globalThis.navigator,"bluetooth",{value:this,writable:true,configurable:true}),this)}uninstall(){typeof globalThis.navigator>"u"||(Object.defineProperty(globalThis.navigator,"bluetooth",{value:this._installedNavigatorBluetooth,writable:true,configurable:true}),this._installedNavigatorBluetooth=void 0);}emitAdvertisement(e,t={}){let i=this._devices.get(e);if(!i)throw new Error(`Unknown mock device: ${e}`);this._handleAdvertisement(i,t);}reset(){for(let e of this._devices.values())e.setAdvertisementSink(void 0),e.simulateDisconnect();this._devices.clear(),this._listeners.clear(),this._scanActive=false,this._lastScanOptions=void 0,this._available=true;}_findMatchingDevices(e){if(!e||e.acceptAllDevices)return Array.from(this._devices.values());let t=e.filters??[];return Array.from(this._devices.values()).filter(i=>t.some(r=>i.matchesFilter(r)))}_matchesScan(e){let t=this._lastScanOptions;if(!t||t.acceptAllAdvertisements)return true;let i=t.filters??[];return i.length===0?true:i.some(r=>e.matchesFilter(r))}};function Z(n){return new M(n)}function se(n){return Z(n).install()}var h={services:{HEART_RATE:"0000180d-0000-1000-8000-00805f9b34fb",BATTERY:"0000180f-0000-1000-8000-00805f9b34fb",DEVICE_INFO:"0000180a-0000-1000-8000-00805f9b34fb",ENVIRONMENTAL_SENSING:"0000181a-0000-1000-8000-00805f9b34fb"},characteristics:{HEART_RATE_MEASUREMENT:"00002a37-0000-1000-8000-00805f9b34fb",BODY_SENSOR_LOCATION:"00002a38-0000-1000-8000-00805f9b34fb",BATTERY_LEVEL:"00002a19-0000-1000-8000-00805f9b34fb",MANUFACTURER_NAME:"00002a29-0000-1000-8000-00805f9b34fb",MODEL_NUMBER:"00002a24-0000-1000-8000-00805f9b34fb",TEMPERATURE:"00002a6e-0000-1000-8000-00805f9b34fb"},descriptors:{CCCD:"00002902-0000-1000-8000-00805f9b34fb",USER_DESCRIPTION:"00002901-0000-1000-8000-00805f9b34fb",PRESENTATION_FORMAT:"00002904-0000-1000-8000-00805f9b34fb"}},ke={heartRate(n="Mock HR Sensor"){return {name:n,serviceUUIDs:[h.services.HEART_RATE],services:[{uuid:h.services.HEART_RATE,characteristics:[{uuid:h.characteristics.HEART_RATE_MEASUREMENT,properties:{read:true,notify:true},value:new Uint8Array([0,72])},{uuid:h.characteristics.BODY_SENSOR_LOCATION,properties:{read:true},value:new Uint8Array([1])}]}]}},battery(n="Mock Battery Device"){return {name:n,serviceUUIDs:[h.services.BATTERY],services:[{uuid:h.services.BATTERY,characteristics:[{uuid:h.characteristics.BATTERY_LEVEL,properties:{read:true,notify:true},value:new Uint8Array([85])}]}]}},full(n="Mock Full Device"){return {name:n,serviceUUIDs:[h.services.HEART_RATE,h.services.BATTERY,h.services.DEVICE_INFO],services:[{uuid:h.services.HEART_RATE,characteristics:[{uuid:h.characteristics.HEART_RATE_MEASUREMENT,properties:{read:true,notify:true},value:new Uint8Array([0,72])}]},{uuid:h.services.BATTERY,characteristics:[{uuid:h.characteristics.BATTERY_LEVEL,properties:{read:true,notify:true},value:new Uint8Array([100])}]},{uuid:h.services.DEVICE_INFO,characteristics:[{uuid:h.characteristics.MANUFACTURER_NAME,properties:{read:true},value:Uint8Array.from(Array.from("Beacio Test Corp").map(e=>e.charCodeAt(0)))},{uuid:h.characteristics.MODEL_NUMBER,properties:{read:true},value:Uint8Array.from(Array.from("WBT-001").map(e=>e.charCodeAt(0)))}]}]}}};
exports.BEACIO_EVENTS=Ce;exports.BLE_UUIDS=h;exports.Beacio=K;exports.BeacioDevice=I;exports.BeacioError=a;exports.BluetoothUUID=Ae;exports.DEFAULT_BEACIO_OPTIONS=Q;exports.DEFAULT_RETRY_OPTIONS=U;exports.MockBleDevice=E;exports.MockBluetooth=M;exports.MockCharacteristic=P;exports.MockDescriptor=k;exports.MockGATTServer=D;exports.MockService=L;exports.SETUP_URL=Se;exports.canonicalUUID=F;exports.chunkSize=y;exports.clampChunkSize=be;exports.clampPercent=xe;exports.createMockBluetooth=Z;exports.detectPlatform=H;exports.getBluetoothAPI=j;exports.getCharacteristicName=ge;exports.getDescriptor=re;exports.getDisplayName=_e;exports.getServiceName=me;exports.installMockBluetooth=se;exports.mockDevices=ke;exports.percent=we;exports.readBytes=Le;exports.readFloat32LE=Ne;exports.readInt16LE=Be;exports.readUint16BE=Re;exports.readUint16LE=De;exports.readUint32LE=Oe;exports.readUint8=Te;exports.readUtf8=Ie;exports.resolveUUID=m;exports.withRetry=q;return exports;})({});//# sourceMappingURL=browser.global.js.map
//# sourceMappingURL=browser.global.js.map
declare global {
interface Navigator {
bluetooth: Bluetooth;
webble?: Bluetooth;
beacio?: Bluetooth;
}
}
declare global {
interface Navigator {
bluetooth: Bluetooth;
webble?: Bluetooth;
beacio?: Bluetooth;
}
}

@@ -1,2 +0,4 @@

'use strict';var G=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),M={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the WebBLE iOS app and enable the Safari extension. Use @beacio/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this WebBLE instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},u=class n extends Error{constructor(e,t,i){let r=M[e];super(t??r),this.name="WebBLEError",this.code=e,this.suggestion=M[e],this.isRetriable=G.has(e),this.retryAfterMs=i?.retryAfterMs;}static from(e,t="GATT_OPERATION_FAILED"){if(e instanceof n)return e;let i=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,r=e instanceof Error?e.message:String(e),o=r.toLowerCase();switch(i){case "TypeError":return new n("INVALID_PARAMETER",r);case "NotFoundError":return new n("DEVICE_NOT_FOUND",r);case "NotAllowedError":case "SecurityError":return new n("PERMISSION_DENIED",r);case "NetworkError":return new n("DEVICE_DISCONNECTED",r,{retryAfterMs:1e3});case "TimeoutError":return new n("TIMEOUT",r,{retryAfterMs:1e3});case "InvalidStateError":if(o.includes("disconnect"))return new n("DEVICE_DISCONNECTED",r,{retryAfterMs:1e3});break;}return r.includes("User cancelled")||r.includes("User canceled")?new n("USER_CANCELLED"):o.includes("no devices found")||r.includes("No Devices")?new n("DEVICE_NOT_FOUND"):r.includes("No Services matching")||o.includes("service not found")?new n("SERVICE_NOT_FOUND",r):r.includes("No Characteristics matching")||o.includes("characteristic not found")?new n("CHARACTERISTIC_NOT_FOUND",r):r.includes("GATT Server is disconnected")||o.includes("disconnected")?new n("DEVICE_DISCONNECTED",r,{retryAfterMs:1e3}):o.includes("not supported")&&o.includes("read")?new n("CHARACTERISTIC_NOT_READABLE",r):o.includes("not supported")&&o.includes("write")?new n("CHARACTERISTIC_NOT_WRITABLE",r):o.includes("not supported")&&o.includes("notif")?new n("CHARACTERISTIC_NOT_NOTIFIABLE",r):o.includes("permission")?new n("PERMISSION_DENIED",r):new n(t,r)}};async function S(n,e={}){let t=e.maxAttempts??3,i=e.delayMs??250,r=e.backoffMultiplier??1.5;if(!Number.isInteger(t)||t<=0)throw new u("INVALID_PARAMETER",`Invalid maxAttempts: ${t}. Must be a positive integer.`);if(!Number.isFinite(i)||i<0)throw new u("INVALID_PARAMETER",`Invalid delayMs: ${i}. Must be a non-negative number.`);if(!Number.isFinite(r)||r<1)throw new u("INVALID_PARAMETER",`Invalid backoffMultiplier: ${r}. Must be a number >= 1.`);for(let o=1;o<=t;o+=1)try{return await n(o)}catch(a){let c=u.from(a);if(o>=t||!c.isRetriable)throw c;let s=c.retryAfterMs??i*Math.pow(r,o-1);s>0&&await new Promise(l=>{setTimeout(l,s);});}throw new u("GATT_OPERATION_FAILED","Retry loop exited unexpectedly.")}var D="-0000-1000-8000-00805f9b34fb",y={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},w={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,"local_east_coordinate.xml":10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989};function A(n){return n.toString(16).padStart(8,"0")+D}var b,E;function z(){if(!b){b=new Map;for(let[n,e]of Object.entries(y)){let t=A(e);b.has(t)||b.set(t,n);}}return b}function $(){if(!E){E=new Map;for(let[n,e]of Object.entries(w)){let t=A(e);E.has(t)||E.set(t,n);}}return E}var U=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,q=/^[0-9a-f]{4}$/,j=/^[0-9a-f]{8}$/;function H(n,e){let t=n.length,i=e.length,r=Array.from({length:i+1},(o,a)=>a);for(let o=1;o<=t;o++){let a=o-1;r[0]=o;for(let c=1;c<=i;c++){let s=r[c];r[c]=n[o-1]===e[c-1]?a:1+Math.min(a,r[c],r[c-1]),a=s;}}return r[i]}function Q(n){return n.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function F(n,e){let t=e[n];if(t!==void 0)return t;let i=n.replace(/[._-]/g,"");if(i){for(let[r,o]of Object.entries(e))if(r.replace(/[._-]/g,"")===i)return o}}function g(n){if(typeof n=="number"){if(!Number.isInteger(n)||n<0||n>4294967295)throw new TypeError(`Invalid UUID integer: ${n}. Must be a 16-bit or 32-bit unsigned integer.`);return A(n)}let e=n.trim(),t=e.toLowerCase();if(U.test(t))return t;if(q.test(t))return "0000"+t+D;if(j.test(t))return t+D;let i=y[t]??w[t];if(i!==void 0)return A(i);let r=Q(e),o=F(r,y);if(o!==void 0)return A(o);let a=F(r,w);if(a!==void 0)return A(a);let c=Object.keys(y).concat(Object.keys(w)),s,l=4;for(let p of c){let m=H(r,p);m<l&&(l=m,s=p);}!s&&r.length>=4&&(s=c.find(p=>p.startsWith(r)));let d=s?` Did you mean "${s}"?`:"";throw new TypeError(`Invalid UUID: "${n}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${d}`)}function Z(n){return z().get(n.toLowerCase())}function K(n){return $().get(n.toLowerCase())}function X(n){let e=n.replace(/^(gap|gatt)\./,"");return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(e)?e.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" "):n}var Y={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function O(n){let e=Number(n);if(!Number.isFinite(e))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${n}`);let t=Math.trunc(e);if(t<0||t>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${n}`);return A(t+0)}function I(n,e,t){if(typeof n=="number")return O(n);if(U.test(n))return n;let i=e[n.toLowerCase()];if(i!==void 0)return A(i);throw new TypeError(`Failed to execute '${t}' on 'BluetoothUUID': Invalid UUID or registry name: "${n}"`)}function V(n){return I(n,Y,"getDescriptor")}var J={canonicalUUID:O,getService:n=>I(n,y,"getService"),getCharacteristic:n=>I(n,w,"getCharacteristic"),getDescriptor:V};var R=class{constructor(e){this.deps=e;this.inFlightWrites=new Map;}async write(e,t,i,r){let o=this.deps.validateTimeoutMs(r?.timeoutMs),a=await this.deps.getCharacteristic(e,t),c=Symbol(`write:${e}:${t}`);this.inFlightWrites.set(c,{service:e,characteristic:t,aborted:false});try{if(r?.mode==="without-response"){await this.deps.withOptionalTimeout(a.writeValueWithoutResponse(i),o,"Write without response timed out");return}await this.deps.withOptionalTimeout(a.writeValueWithResponse(i),o,"Write with response timed out");}catch(s){throw this.inFlightWrites.get(c)?.aborted?new u("WRITE_INCOMPLETE",`Write incomplete for ${e}/${t}: disconnected before completion`,{retryAfterMs:1e3}):u.from(s)}finally{this.inFlightWrites.delete(c);}}async writeFragmented(e,t,i,r){let o=this.toUint8Array(i),a=o.byteLength;if(a===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0};let c=r?.chunkSize??this.deriveChunkSizeFromMtu(r?.mtu)??await this.deriveChunkSize(void 0,r?.mode),s=r?.maxRetries??0,l=r?.retryDelayMs??0,d=0,p=0,m=0;for(let _=0;_<a;_+=c){let h=Math.min(_+c,a),f=new Uint8Array(o.subarray(_,h)),v=0;for(;;)try{await this.write(e,t,f,r),d+=f.byteLength,p+=1;break}catch(L){if(v>=s)throw d>0&&d<a?new u("WRITE_INCOMPLETE",`Write fragmented incomplete (${d}/${a} bytes written): ${this.errorMessage(L)}`,{retryAfterMs:1e3}):u.from(L);v+=1,m+=1,l>0&&await this.delay(l);}}return {bytesWritten:d,totalBytes:a,chunkSize:c,chunkCount:p,retryCount:m}}async writeLarge(e,t,i,r){let o=this.toUint8Array(i),a=o.byteLength;if(a===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0};let c=await this.deriveChunkSize(r?.chunkSize,r?.mode),s=0,l=0;for(let d=0;d<a;d+=c){let p=Math.min(d+c,a),m=o.subarray(d,p),_=new Uint8Array(m);try{await this.write(e,t,_,r),s+=m.byteLength,l+=1;}catch(h){throw s>0&&s<a?new u("WRITE_INCOMPLETE",`Write incomplete (${s}/${a} bytes written): ${this.errorMessage(h)}`):u.from(h)}}if(s!==a)throw new u("WRITE_INCOMPLETE",`Write incomplete (${s}/${a} bytes written)`);return {bytesWritten:s,totalBytes:a,chunkSize:c,chunkCount:l}}async writeWithoutResponse(e,t,i,r){return this.write(e,t,i,{...r,mode:"without-response"})}async getWriteLimits(){if(!this.deps.isConnected())throw new u("DEVICE_DISCONNECTED");let e=this.deps.getTransport(),t=await e?.getWriteLimits?.(),i=t?.mtu??await e?.getMtu?.()??null;return {withResponse:t?.withResponse??null,withoutResponse:t?.withoutResponse??null,mtu:i}}async writeAuto(e,t,i,r){let o=this.toUint8Array(i),a=o.byteLength,c=new Uint8Array(o);if(a===0)return await this.write(e,t,c,r),{bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0,fragmented:false};let s=await this.deriveChunkSize(r?.chunkSize,r?.mode);return a<=s?(await this.write(e,t,c,r),{bytesWritten:a,totalBytes:a,chunkSize:a,chunkCount:1,retryCount:0,fragmented:false}):{...await this.writeFragmented(e,t,c,r),fragmented:true}}abortInFlightWrites(){for(let e of this.inFlightWrites.values())e.aborted=true;}async deriveChunkSize(e,t){if(e!==void 0){if(!Number.isInteger(e)||e<=0)throw new u("INVALID_PARAMETER",`Invalid chunkSize: ${e}. Must be a positive integer.`);return e}let i=await this.getWriteLimits().catch(()=>({withResponse:null,withoutResponse:null,mtu:null})),r=t==="without-response"?i.withoutResponse:i.withResponse;return typeof r=="number"&&r>0?r:typeof i.mtu=="number"&&i.mtu>3?i.mtu-3:20}deriveChunkSizeFromMtu(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=3)throw new u("INVALID_PARAMETER",`Invalid mtu: ${e}. Must be an integer greater than 3.`);return e-3}delay(e){return new Promise(t=>{setTimeout(t,e);})}toUint8Array(e){return e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}errorMessage(e){return e instanceof Error?e.message:String(e)}};var x=class x{constructor(e){this.deps=e;this.notificationStates=new Map;this.handleNotification=e=>{let t=e.target,i=t.value;if(i){for(let[r,o]of this.notificationStates)if((o.characteristic??this.deps.charCache.get(r))===t){let[c,s]=r.split(":");for(let l of o.callbacks)try{l(i);}catch(d){this.deps.emitError(u.from(d),{operation:"device.notification-callback",service:c,characteristic:s});}break}}};}getNotificationStates(){return this.notificationStates}subscribe(e,t,i,r){let{unsubscribe:o,ready:a}=this.registerNotificationConsumer(e,t,i);a.catch(l=>{let d=u.from(l);try{r?.onError?.(d);}catch(p){this.deps.emitError(u.from(p),{operation:"device.subscribe.onError",service:e,characteristic:t});}});let c=r?.autoRecover??true;c&&this.addToRecoveryRegistry(this.charKey(e,t),e,t,i);let s=o;return ()=>{s(),c&&this.removeFromRecoveryRegistry(this.charKey(e,t),i);}}async subscribeAsync(e,t,i,r){let{unsubscribe:o,release:a,ready:c}=this.registerNotificationConsumer(e,t,i),s=r?.autoRecover??true;s&&this.addToRecoveryRegistry(this.charKey(e,t),e,t,i);try{await c;}catch(l){let d=u.from(l);throw await a(),s&&this.removeFromRecoveryRegistry(this.charKey(e,t),i),d}return ()=>{o(),s&&this.removeFromRecoveryRegistry(this.charKey(e,t),i);}}async*notifications(e,t,i={maxQueueSize:x.DEFAULT_NOTIFICATION_QUEUE_SIZE}){let r=this.validateMaxQueueSize(i.maxQueueSize??x.DEFAULT_NOTIFICATION_QUEUE_SIZE),o=i?.overflowStrategy??"error",a=[],c=0,s={resolve:null,reject:null,done:false,failure:null},l=h=>{if(!s.failure)if(s.resolve){let f=s.resolve;s.resolve=null,s.reject=null,f({value:h,done:false});}else {if(a.length>=r){c+=1;let f={service:e,characteristic:t,strategy:o,queueSize:r,droppedCount:c};this.deps.emitQueueOverflow(f);try{i?.onOverflow?.(f);}catch(v){this.deps.emitError(u.from(v),{operation:"device.notifications.onOverflow",service:e,characteristic:t});}if(o==="error"){let v=new u("GATT_OPERATION_FAILED",`Notification queue overflowed (maxQueueSize=${r}). Increase queue size or consume faster.`);s.failure=v;let L=s.reject;s.resolve=null,s.reject=null,L?.(v);return}if(o==="drop-oldest"&&a.shift(),o==="drop-newest")return}a.push(h);}},d=this.charKey(e,t);this.addToRecoveryRegistry(d,e,t,l);let{unsubscribe:p,release:m,ready:_}=this.registerNotificationConsumer(e,t,l);try{await _;}catch(h){throw await m(),this.removeFromRecoveryRegistry(d,l),h}try{for(;!s.done;){if(s.failure)throw s.failure;if(a.length>0)yield a.shift();else {let h=await new Promise((f,v)=>{s.resolve=f,s.reject=v;});if(h.done){let f=this.deps.getReconnectGate();if(f&&!this.deps.isIntentionalDisconnect()){if(await f.promise,this.deps.isIntentionalDisconnect())return;continue}return}yield h.value;}}}finally{let h=s.resolve;s.resolve=null,s.reject=null,s.done=true,await m(),this.removeFromRecoveryRegistry(d,l),h&&h({value:void 0,done:true});}}teardownSubscriptions(e){for(let t of this.notificationStates.values())this.detachNotificationListener(t),t.nativeActive&&this.stopNotificationsSafely(t.characteristic,{operation:e});this.notificationStates.clear();}cleanupSubscriptions(){this.teardownSubscriptions("notification.cleanup");}suspendSubscriptions(){this.teardownSubscriptions("notification.suspend");}async recoverSubscriptions(){let e=[...this.deps.recoveryRegistry.entries()];for(let[t,i]of e)try{for(let r of i.callbacks){let{ready:o}=this.registerNotificationConsumer(i.service,i.characteristic,r);await o;}}catch(r){this.deps.recoveryRegistry.delete(t);let o=u.from(r);this.deps.emitSubscriptionLost({service:i.service,characteristic:i.characteristic,error:o}),this.deps.emitError(o,{operation:"notification.recover",service:i.service,characteristic:i.characteristic});}}registerNotificationConsumer(e,t,i){let r=this.charKey(e,t),o=this.notificationStates.get(r);o||(o={callbacks:new Set,characteristic:null,listenerAttached:false,nativeActive:false,reconcilePromise:null},this.notificationStates.set(r,o)),o.callbacks.add(i);let a=()=>{let c=this.notificationStates.get(r);return c?.callbacks.has(i)?(c.callbacks.delete(i),this.syncNotificationState(r,e,t)):Promise.resolve()};return {unsubscribe:()=>{a();},release:a,ready:this.syncNotificationState(r,e,t)}}syncNotificationState(e,t,i){let r=this.notificationStates.get(e);if(!r)return Promise.resolve();let c=(r.reconcilePromise??Promise.resolve()).catch(s=>{this.deps.emitError(u.from(s),{operation:"notification.reconcile",service:t,characteristic:i});}).then(async()=>{for(;;){if(this.notificationStates.get(e)!==r){await this.deactivateNotificationState(r);return}if(r.callbacks.size===0){if(this.detachNotificationListener(r),r.nativeActive){r.nativeActive=false,await this.stopNotificationsSafely(r.characteristic,{operation:"notification.stop",service:t,characteristic:i});continue}this.deleteNotificationStateIfIdle(e,r);return}let l=r.characteristic??await this.deps.getCharacteristic(t,i);if(r.characteristic=l,r.listenerAttached||(l.addEventListener("characteristicvaluechanged",this.handleNotification),r.listenerAttached=true),!r.nativeActive){if(await l.startNotifications(),r.nativeActive=true,this.notificationStates.get(e)!==r){await this.deactivateNotificationState(r);return}if(r.callbacks.size===0)continue}if(r.callbacks.size!==0)return}}).finally(()=>{r.reconcilePromise===c&&(r.reconcilePromise=null,this.notificationStates.get(e)===r&&this.deleteNotificationStateIfIdle(e,r));});return r.reconcilePromise=c,c}async deactivateNotificationState(e){this.detachNotificationListener(e),e.nativeActive&&(e.nativeActive=false,await this.stopNotificationsSafely(e.characteristic,{operation:"notification.deactivate"}));}detachNotificationListener(e){!e.listenerAttached||!e.characteristic||(e.characteristic.removeEventListener("characteristicvaluechanged",this.handleNotification),e.listenerAttached=false);}deleteNotificationStateIfIdle(e,t){this.notificationStates.get(e)===t&&(t.callbacks.size>0||t.nativeActive||t.reconcilePromise||this.notificationStates.delete(e));}validateMaxQueueSize(e){if(!Number.isInteger(e)||e<=0)throw new u("INVALID_PARAMETER",`Invalid maxQueueSize: ${e}. Must be a positive integer.`);return e}async stopNotificationsSafely(e,t){if(e)try{await e.stopNotifications();}catch(i){this.deps.emitError(u.from(i),t);}}charKey(e,t){return `${g(e)}:${g(t)}`}addToRecoveryRegistry(e,t,i,r){let o=this.deps.recoveryRegistry.get(e);o||(o={service:t,characteristic:i,callbacks:new Set},this.deps.recoveryRegistry.set(e,o)),o.callbacks.add(r);}removeFromRecoveryRegistry(e,t){let i=this.deps.recoveryRegistry.get(e);i&&(i.callbacks.delete(t),i.callbacks.size===0&&this.deps.recoveryRegistry.delete(e));}};x.DEFAULT_NOTIFICATION_QUEUE_SIZE=256;var T=x;var C=class{constructor(e,t={}){this.server=null;this.primaryServicesCache=null;this.serviceCache=new Map;this.charCache=new Map;this.recoveryRegistry=new Map;this.disconnectListeners=new Set;this.reconnectedListeners=new Set;this.queueOverflowListeners=new Set;this.subscriptionLostListeners=new Set;this.errorListeners=new Set;this.reconnectGate=null;this.intentionalDisconnect=false;this.lastDisconnectReason=null;this.autoReconnectConfig=null;this.autoReconnectAbort=null;this.id=e.id,this.name=e.name??null,this.raw=e,this.hooks=t,this.writeChunker=new R({getCharacteristic:(i,r)=>this.getCharacteristic(i,r),emitError:(i,r)=>this.emitError(i,r),validateTimeoutMs:i=>this.validateTimeoutMs(i),withOptionalTimeout:(i,r,o)=>this.withOptionalTimeout(i,r,o),isConnected:()=>this.connected,getTransport:()=>this.raw.gatt}),this.notificationManager=new T({getCharacteristic:(i,r)=>this.getCharacteristic(i,r),emitError:(i,r)=>this.emitError(i,r),emitSubscriptionLost:i=>this.emitSubscriptionLost(i),emitQueueOverflow:i=>this.emitQueueOverflow(i),recoveryRegistry:this.recoveryRegistry,charCache:this.charCache,getReconnectGate:()=>this.reconnectGate,isIntentionalDisconnect:()=>this.intentionalDisconnect}),typeof e.addEventListener=="function"&&e.addEventListener("gattserverdisconnected",()=>{this.handleDisconnect();});}get connected(){return this.server?.connected??false}async connect(e){if(this.connected)return;e?.autoReconnect&&(this.autoReconnectConfig=e.autoReconnect===true?{}:e.autoReconnect),this.hooks.beforeConnect?.(this);let t=this.raw.gatt;if(!t)throw new u("GATT_OPERATION_FAILED","Device has no GATT server");let i=this.reconnectGate;try{this.server=await t.connect(),this.lastDisconnectReason=null,await this.notificationManager.recoverSubscriptions();for(let r of this.reconnectedListeners)try{r();}catch(o){this.emitError(u.from(o),{operation:"device.reconnected-listener"});}this.intentionalDisconnect=!1,this.hooks.onConnectionChange?.(this);}catch(r){throw this.server?.connected||(this.server=null,this.hooks.onConnectionChange?.(this)),u.from(r)}finally{this.reconnectGate===i&&(this.reconnectGate=null),i?.resolve();}}disconnect(){this.intentionalDisconnect=true,this.lastDisconnectReason="intentional",this.autoReconnectConfig=null,this.autoReconnectAbort?.abort(),this.autoReconnectAbort=null,this.writeChunker.abortInFlightWrites(),this.notificationManager.cleanupSubscriptions(),this.recoveryRegistry.clear(),this.reconnectGate&&(this.reconnectGate.resolve(),this.reconnectGate=null),this.server?.disconnect(),this.server=null,this.primaryServicesCache=null,this.hooks.onConnectionChange?.(this);}async connectWithRetry(e={}){await S(async()=>{await this.connect();},e);}async read(e,t,i,r){let o=typeof i=="function"?i:void 0,a=typeof i=="function"?r:i,c=this.validateTimeoutMs(a?.timeoutMs),s=await this.getCharacteristic(e,t);try{let l=await this.withOptionalTimeout(s.readValue(),c,"Read timed out");return o?await o(l):l}catch(l){throw u.from(l)}}async write(e,t,i,r){return this.writeChunker.write(e,t,i,r)}async writeFragmented(e,t,i,r){return this.writeChunker.writeFragmented(e,t,i,r)}async writeLarge(e,t,i,r){return this.writeChunker.writeLarge(e,t,i,r)}async writeWithoutResponse(e,t,i,r){return this.writeChunker.writeWithoutResponse(e,t,i,r)}async getWriteLimits(){return this.writeChunker.getWriteLimits()}async getMtu(){return (await this.getWriteLimits()).mtu}async writeAuto(e,t,i,r){return this.writeChunker.writeAuto(e,t,i,r)}subscribe(e,t,i,r){return this.notificationManager.subscribe(e,t,i,r)}async subscribeAsync(e,t,i,r){return this.notificationManager.subscribeAsync(e,t,i,r)}notifications(e,t,i){return this.notificationManager.notifications(e,t,i)}async watchAdvertisements(){if(typeof this.raw.watchAdvertisements!="function")throw new u("GATT_OPERATION_FAILED","watchAdvertisements is not supported on this device");await this.raw.watchAdvertisements();}async unwatchAdvertisements(){let e=this.raw;if(typeof e.unwatchAdvertisements!="function")throw new u("GATT_OPERATION_FAILED","unwatchAdvertisements is not supported on this device");await e.unwatchAdvertisements();}async forget(){if(typeof this.raw.forget!="function")throw new u("GATT_OPERATION_FAILED","forget is not supported on this device");await this.raw.forget();}async getPrimaryServices(){if(!this.connected)throw new u("DEVICE_DISCONNECTED");if(this.primaryServicesCache)return this.primaryServicesCache;try{let t=(await this.server.getPrimaryServices()).map(i=>{let r=this.serviceCache.get(i.uuid)??i;return this.serviceCache.set(i.uuid,r),r});return this.primaryServicesCache=t,t}catch(e){throw u.from(e)}}async getEffectiveMtu(){let e=await this.getWriteLimits();return typeof e.mtu=="number"&&e.mtu>0?e.mtu:typeof e.withResponse=="number"&&e.withResponse>0?e.withResponse+3:typeof e.withoutResponse=="number"&&e.withoutResponse>0?e.withoutResponse+3:23}getLastDisconnectReason(){return this.lastDisconnectReason}getActiveSubscriptions(){let e=this.notificationManager.getNotificationStates();return [...new Set([...e.keys(),...this.recoveryRegistry.keys()])].map(i=>{let r=e.get(i),o=this.recoveryRegistry.get(i),[a,c]=i.split(":");return {service:a,characteristic:c,callbackCount:r?.callbacks.size??o?.callbacks.size??0,autoRecovering:o!==void 0,nativeActive:r?.nativeActive??false}})}on(e,t){return e==="disconnected"&&this.disconnectListeners.add(t),e==="reconnected"&&this.reconnectedListeners.add(t),e==="queue-overflow"&&this.queueOverflowListeners.add(t),e==="subscription-lost"&&this.subscriptionLostListeners.add(t),()=>{this.off(e,t);}}off(e,t){e==="disconnected"&&this.disconnectListeners.delete(t),e==="reconnected"&&this.reconnectedListeners.delete(t),e==="queue-overflow"&&this.queueOverflowListeners.delete(t),e==="subscription-lost"&&this.subscriptionLostListeners.delete(t);}addErrorListener(e){return this.errorListeners.add(e),()=>{this.removeErrorListener(e);}}removeErrorListener(e){this.errorListeners.delete(e);}handleDisconnect(){let e=this.intentionalDisconnect?"intentional":"unexpected";if(this.lastDisconnectReason=e,this.writeChunker.abortInFlightWrites(),this.notificationManager.suspendSubscriptions(),this.serviceCache.clear(),this.primaryServicesCache=null,this.charCache.clear(),this.server=null,this.hooks.onConnectionChange?.(this),this.recoveryRegistry.size>0){let t,i=new Promise(r=>{t=r;});this.reconnectGate={promise:i,resolve:t};}for(let t of this.disconnectListeners)try{t(e);}catch(i){this.emitError(u.from(i),{operation:"device.disconnected-listener"});}e==="unexpected"&&this.autoReconnectConfig&&this.startAutoReconnect(this.autoReconnectConfig);}startAutoReconnect(e){this.autoReconnectAbort?.abort();let t=new AbortController;this.autoReconnectAbort=t;let i=e.maxAttempts??1/0,r=e.initialDelayMs??1e3,o=e.maxDelayMs??3e4,a=e.backoffMultiplier??2;(async()=>{let s=r;for(let l=1;l<=i;l++){if(t.signal.aborted||(await new Promise(d=>{let p=setTimeout(d,s);t.signal.addEventListener("abort",()=>{clearTimeout(p),d();},{once:true});}),t.signal.aborted))return;try{await this.connect();return}catch{s=Math.min(s*a,o);}}this.emitError(new u("CONNECTION_TIMEOUT",`Auto-reconnect failed after ${i} attempts`),{operation:"device.auto-reconnect"});})();}async withOptionalTimeout(e,t,i){if(t===void 0)return e;let r=null,o=new Promise((a,c)=>{r=setTimeout(()=>{c(new u("TIMEOUT",i));},t);});try{return await Promise.race([e,o])}finally{r!==null&&clearTimeout(r);}}validateTimeoutMs(e){if(e!==void 0){if(!Number.isFinite(e)||e<=0)throw new u("INVALID_PARAMETER",`Invalid timeoutMs: ${e}. Must be a positive number.`);return e}}fanout(e,t,i){for(let r of e)try{r(t);}catch(o){this.emitError(u.from(o),{operation:i});}}emitQueueOverflow(e){this.fanout(this.queueOverflowListeners,e,"device.queue-overflow-listener");}emitSubscriptionLost(e){this.fanout(this.subscriptionLostListeners,e,"device.subscription-lost-listener");}emitError(e,t){for(let i of this.errorListeners)try{i(e,t);}catch{}}async getCharacteristic(e,t){if(!this.connected)throw new u("DEVICE_DISCONNECTED");let i=this.charKey(e,t),r=this.charCache.get(i);if(r)return r;let o=await this.getService(e),a=g(t);try{let c=await o.getCharacteristic(a);return this.charCache.set(i,c),c}catch(c){throw u.from(c)}}async getService(e){let t=g(e);if(this.primaryServicesCache){let r=this.primaryServicesCache.find(o=>o.uuid===t);if(r)return r}let i=this.serviceCache.get(t);if(i)return i;try{let r=await this.server.getPrimaryService(t);return this.serviceCache.set(t,r),r}catch(r){throw u.from(r)}}charKey(e,t){return `${g(e)}:${g(t)}`}};function N(){if(typeof navigator>"u")return "unsupported";let n=navigator;return n.webble?.__webble===true?"safari-extension":n.bluetooth&&!n.bluetooth.__webbleCDNStub?"native":"unsupported"}function B(){if(typeof navigator>"u")return null;let n=navigator;return n.webble?.__webble===true?n.webble:n.bluetooth&&!n.bluetooth.__webbleCDNStub?n.bluetooth:null}var P=class{constructor(e){this.errorFactory=e;}unsupported(){throw this.errorFactory()}requestPermission(){this.unsupported();}requestBackgroundConnection(e){this.unsupported();}registerCharacteristicNotifications(e){this.unsupported();}registerBeaconScanning(e){this.unsupported();}getRegistrations(){this.unsupported();}unregister(e){this.unsupported();}update(e,t){this.unsupported();}connect(e){return this.requestBackgroundConnection(e)}subscribe(e){return this.registerCharacteristicNotifications(e)}scan(e){return this.registerBeaconScanning(e)}list(){return this.getRegistrations()}destroy(){}},W=class extends EventTarget{constructor(t){super();this.onwriterequest=null;this.onsubscriptionchange=null;this.onconnectionstatechange=null;this.onadvertisingstatechange=null;this.onnotificationready=null;this.errorFactory=t;}get advertising(){return false}unsupported(){throw this.errorFactory()}advertise(t){this.unsupported();}addService(t){this.unsupported();}registerService(t){return this.addService(t)}startAdvertising(t){return this.advertise(t)}stopAdvertising(){this.unsupported();}send(t){this.unsupported();}sendNotification(t){return this.send(t)}destroy(){}},k=class{constructor(e){this.devices=new Map;this.platform=e?.platform??N(),this.maxConnections=this.normalizeMaxConnections(e?.maxConnections),this.bluetooth=this.platform!=="unsupported"?B():null,this.runtimeBluetooth=this.bluetooth,this.isSupported=this.bluetooth!==null,this.unsupportedFeatureErrorFactory=()=>this.platform==="unsupported"?new u("BLUETOOTH_UNAVAILABLE"):new u("GATT_OPERATION_FAILED","This WebBLE feature requires the iOS Safari WebBLE extension runtime."),this.unsupportedBackgroundSync=new P(this.unsupportedFeatureErrorFactory),this.unsupportedPeripheral=new W(this.unsupportedFeatureErrorFactory);}get backgroundSync(){return this.runtimeBluetooth?.backgroundSync??this.unsupportedBackgroundSync}get peripheral(){return this.runtimeBluetooth?.peripheral??this.unsupportedPeripheral}async requestDevice(e){if(!this.bluetooth)throw new u("BLUETOOTH_UNAVAILABLE");try{let t=await this.bluetooth.requestDevice(this.normalizeRequestDeviceOptions(e)??{acceptAllDevices:!0});return this.wrapDevice(t)}catch(t){throw u.from(t,"DEVICE_NOT_FOUND")}}async getDevices(){if(!this.bluetooth)throw new u("BLUETOOTH_UNAVAILABLE");let e=this.bluetooth;if(typeof e.getDevices!="function")return [];try{return (await e.getDevices()).map(i=>this.wrapDevice(i))}catch(t){throw u.from(t)}}async getAvailability(){if(!this.bluetooth)return false;try{return await this.bluetooth.getAvailability()}catch{return false}}async requestLEScan(e={acceptAllAdvertisements:true}){if(!this.bluetooth)throw new u("BLUETOOTH_UNAVAILABLE");let t=this.bluetooth;if(typeof t.requestLEScan!="function")return null;try{return await t.requestLEScan(e)}catch(i){throw u.from(i)}}normalizeRequestDeviceOptions(e){if(!e)return;let t=r=>{if(r)return r.map(o=>g(o))},i={};return e.acceptAllDevices!==void 0&&(i.acceptAllDevices=e.acceptAllDevices),e.optionalManufacturerData!==void 0&&(i.optionalManufacturerData=e.optionalManufacturerData),e.filters&&(i.filters=e.filters.map(r=>({...r,services:t(r.services)}))),e.exclusionFilters&&(i.exclusionFilters=e.exclusionFilters.map(r=>({...r,services:t(r.services)}))),e.optionalServices&&(i.optionalServices=t(e.optionalServices)),i}normalizeMaxConnections(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=0)throw new u("INVALID_PARAMETER",`Invalid maxConnections: ${e}. Must be a positive integer.`);return e}wrapDevice(e){let t=this.devices.get(e.id);if(t)return t;let i=new C(e,{beforeConnect:r=>{this.assertConnectionCapacity(r);},onConnectionChange:r=>{this.devices.set(r.id,r);}});return this.devices.set(e.id,i),i}assertConnectionCapacity(e){if(this.maxConnections===null||(this.devices.set(e.id,e),e.connected))return;let t=[...this.devices.values()].filter(i=>i.connected).length;if(t>=this.maxConnections)throw new u("CONNECTION_LIMIT_REACHED",`Connection limit reached (${t}/${this.maxConnections}). Disconnect another device or increase maxConnections before connecting ${e.name??e.id}.`,{retryAfterMs:1e3})}};function ee(n,e=0){return n.getUint8(e)}function te(n,e=0){return n.getUint16(e,true)}function re(n,e=0){return n.getUint16(e,false)}function ie(n,e=0){return n.getInt16(e,true)}function ne(n,e=0){return n.getUint32(e,true)}function oe(n,e=0){return n.getFloat32(e,true)}function se(n){return new TextDecoder().decode(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength))}function ae(n){return new Uint8Array(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength))}exports.BluetoothUUID=J;exports.WebBLE=k;exports.WebBLEDevice=C;exports.WebBLEError=u;exports.canonicalUUID=O;exports.detectPlatform=N;exports.getBluetoothAPI=B;exports.getCharacteristicName=K;exports.getDescriptor=V;exports.getDisplayName=X;exports.getServiceName=Z;exports.readBytes=ae;exports.readFloat32LE=oe;exports.readInt16LE=ie;exports.readUint16BE=re;exports.readUint16LE=te;exports.readUint32LE=ne;exports.readUint8=ee;exports.readUtf8=se;exports.resolveUUID=g;exports.withRetry=S;//# sourceMappingURL=index.js.map
'use strict';var U={maxAttempts:0,delayMs:-1,backoffMultiplier:0},ae=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),J={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the Beacio iOS app and enable the Safari extension. Use @beacio/core/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this Beacio instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},ce=/\b(bluefy|web ble browser|webble browser)\b/gi;function ue(n){let e=n.split(`
`,1)[0]??"";return e=e.replace(/\b(?:webkit|https?|chrome|moz-extension|safari-web-extension):\/\/\S+/gi,""),e=e.replace(/\bat\s+\S+:\d+:\d+\)?/gi,""),e=e.replace(ce,""),e=e.replace(/\s{2,}/g," ").replace(/\s+([.,;:])/g,"$1").trim(),e=e.replace(/[\s.,;:]+$/g,"").trim(),e}var a=class n extends Error{constructor(e,t,i){let r=J[e];super(t??r),this.name="BeacioError",this.code=e,this.suggestion=J[e],this.isRetriable=ae.has(e),this.retryAfterMs=i?.retryAfterMs;}static from(e,t="GATT_OPERATION_FAILED"){if(e instanceof n)return e;let i=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,r=e instanceof Error?e.message:String(e),o=ue(r)||void 0,s=r.toLowerCase();switch(i){case "TypeError":return new n("INVALID_PARAMETER",o);case "NotFoundError":return new n("DEVICE_NOT_FOUND",o);case "NotAllowedError":case "SecurityError":return new n("PERMISSION_DENIED",o);case "NetworkError":return new n("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3});case "TimeoutError":return new n("TIMEOUT",o,{retryAfterMs:1e3});case "InvalidStateError":if(s.includes("disconnect"))return new n("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3});break;}return r.includes("User cancelled")||r.includes("User canceled")?new n("USER_CANCELLED"):s.includes("no devices found")||r.includes("No Devices")?new n("DEVICE_NOT_FOUND"):r.includes("No Services matching")||s.includes("service not found")?new n("SERVICE_NOT_FOUND",o):r.includes("No Characteristics matching")||s.includes("characteristic not found")?new n("CHARACTERISTIC_NOT_FOUND",o):r.includes("GATT Server is disconnected")||s.includes("disconnected")?new n("DEVICE_DISCONNECTED",o,{retryAfterMs:1e3}):s.includes("not supported")&&s.includes("read")?new n("CHARACTERISTIC_NOT_READABLE",o):s.includes("not supported")&&s.includes("write")?new n("CHARACTERISTIC_NOT_WRITABLE",o):s.includes("not supported")&&s.includes("notif")?new n("CHARACTERISTIC_NOT_NOTIFIABLE",o):s.includes("permission")?new n("PERMISSION_DENIED",o):new n(t,o)}};async function $(n,e=U){let t=e.maxAttempts>0?e.maxAttempts:3,i=e.delayMs>=0?e.delayMs:250,r=e.backoffMultiplier>=1?e.backoffMultiplier:1.5;if(!Number.isInteger(t)||t<=0)throw new a("INVALID_PARAMETER",`Invalid maxAttempts: ${t}. Must be a positive integer.`);if(!Number.isFinite(i)||i<0)throw new a("INVALID_PARAMETER",`Invalid delayMs: ${i}. Must be a non-negative number.`);if(!Number.isFinite(r)||r<1)throw new a("INVALID_PARAMETER",`Invalid backoffMultiplier: ${r}. Must be a number >= 1.`);for(let o=1;o<=t;o+=1)try{return await n(o)}catch(s){let u=a.from(s);if(o>=t||!u.isRetriable)throw u;let c=u.retryAfterMs??i*Math.pow(r,o-1);c>0&&await new Promise(l=>{setTimeout(l,c);});}throw new a("GATT_OPERATION_FAILED","Retry loop exited unexpectedly.")}var C="-0000-1000-8000-00805f9b34fb",q=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,x={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},T={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,local_east_coordinate:10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989},ee={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function te(n){return n.toString(16).padStart(8,"0")+C}function W(n){let e=Number(n);if(!Number.isFinite(e))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${n}`);let t=Math.trunc(e);if(t<0||t>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${n}`);return te(t+0)}function F(n,e,t){if(typeof n=="number")return W(n);let i=String(n);if(q.test(i))return i;let r=e[i.toLowerCase()];if(r!==void 0)return te(r);throw new TypeError(`Failed to execute '${t}' on 'BluetoothUUID': Invalid UUID or registry name: "${i}"`)}function S(n){return n.toString(16).padStart(8,"0")+C}var R,O;function le(){if(!R){R=new Map;for(let[n,e]of Object.entries(x)){let t=S(e);R.has(t)||R.set(t,n);}}return R}function de(){if(!O){O=new Map;for(let[n,e]of Object.entries(T)){let t=S(e);O.has(t)||O.set(t,n);}}return O}var he=/^[0-9a-f]{4}$/,pe=/^[0-9a-f]{8}$/;function fe(n,e){let t=n.length,i=e.length,r=Array.from({length:i+1},(o,s)=>s);for(let o=1;o<=t;o++){let s=o-1;r[0]=o;for(let u=1;u<=i;u++){let c=r[u];r[u]=n[o-1]===e[u-1]?s:1+Math.min(s,r[u],r[u-1]),s=c;}}return r[i]}function ve(n){return n.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function ie(n,e){let t=e[n];if(t!==void 0)return t;let i=n.replace(/[._-]/g,"");if(i){for(let[r,o]of Object.entries(e))if(r.replace(/[._-]/g,"")===i)return o}}function m(n){if(typeof n=="number"){if(!Number.isInteger(n)||n<0||n>4294967295)throw new TypeError(`Invalid UUID integer: ${n}. Must be a 16-bit or 32-bit unsigned integer.`);return S(n)}let e=n.trim(),t=e.toLowerCase();if(q.test(t))return t;if(he.test(t))return "0000"+t+C;if(pe.test(t))return t+C;let i=x[t]??T[t];if(i!==void 0)return S(i);let r=ve(e),o=ie(r,x);if(o!==void 0)return S(o);let s=ie(r,T);if(s!==void 0)return S(s);let u=Object.keys(x).concat(Object.keys(T)),c,l=4;for(let f of u){let _=fe(r,f);_<l&&(l=_,c=f);}!c&&r.length>=4&&(c=u.find(f=>f.startsWith(r)));let d=c?` Did you mean "${c}"?`:"";throw new TypeError(`Invalid UUID: "${n}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${d}`)}function me(n){return le().get(n.toLowerCase())}function ge(n){return de().get(n.toLowerCase())}function _e(n){let e=n.replace(/^(gap|gatt)\./,"");return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(e)?e.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" "):n}function re(n){return F(n,ee,"getDescriptor")}var Ae={canonicalUUID:W,getService:n=>F(n,x,"getService"),getCharacteristic:n=>F(n,T,"getCharacteristic"),getDescriptor:re};var ne=20;function E(n){if(!Number.isInteger(n)||n<=0)throw new a("INVALID_PARAMETER",`Invalid chunkSize: ${n}. Must be a positive integer.`);return n}function be(n,e=ne){return Number.isInteger(n)&&n>0?n:E(e)}var V=class{constructor(e){this.deps=e;this.inFlightWrites=new Map;}async write(e,t,i,r){let o=this.deps.d(r?.timeoutMs),s=await this.deps.getCharacteristic(e,t),u=Symbol(`write:${e}:${t}`);this.inFlightWrites.set(u,{service:e,characteristic:t,aborted:false});try{if(r?.mode==="without-response"){await this.deps.withOptionalTimeout(s.writeValueWithoutResponse(i),o,"Write without response timed out");return}await this.deps.withOptionalTimeout(s.writeValueWithResponse(i),o,"Write with response timed out");}catch(c){throw this.inFlightWrites.get(u)?.aborted?new a("WRITE_INCOMPLETE",`Write incomplete for ${e}/${t}: disconnected before completion`,{retryAfterMs:1e3}):a.from(c)}finally{this.inFlightWrites.delete(u);}}async writeFragmented(e,t,i,r){let o=this.toUint8Array(i),s=o.byteLength;if(s===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0};let u=r?.chunkSize!==void 0?E(r.chunkSize):this.L(r?.mtu)??await this.m(void 0,r?.mode),c=r?.maxRetries??0,l=r?.retryDelayMs??0,d=0,f=0,_=0;for(let b=0;b<s;b+=u){let p=Math.min(b+u,s),g=new Uint8Array(o.subarray(b,p)),A=0;for(;;)try{await this.write(e,t,g,r),d+=g.byteLength,f+=1;break}catch(w){if(A>=c)throw d>0&&d<s?new a("WRITE_INCOMPLETE",`Write fragmented incomplete (${d}/${s} bytes written): ${this.errorMessage(w instanceof Error?w:String(w))}`,{retryAfterMs:1e3}):a.from(w);A+=1,_+=1,l>0&&await this.delay(l);}}return {bytesWritten:d,totalBytes:s,chunkSize:u,chunkCount:f,retryCount:_}}async writeLarge(e,t,i,r){let o=this.toUint8Array(i),s=o.byteLength;if(s===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0};let u=await this.m(r?.chunkSize,r?.mode),c=0,l=0;for(let d=0;d<s;d+=u){let f=Math.min(d+u,s),_=o.subarray(d,f),b=new Uint8Array(_);try{await this.write(e,t,b,r),c+=_.byteLength,l+=1;}catch(p){throw c>0&&c<s?new a("WRITE_INCOMPLETE",`Write incomplete (${c}/${s} bytes written): ${this.errorMessage(p instanceof Error?p:String(p))}`):a.from(p)}}if(c!==s)throw new a("WRITE_INCOMPLETE",`Write incomplete (${c}/${s} bytes written)`);return {bytesWritten:c,totalBytes:s,chunkSize:u,chunkCount:l}}async writeWithoutResponse(e,t,i,r){return this.write(e,t,i,{...r,mode:"without-response"})}async getWriteLimits(){if(!this.deps.isConnected())throw new a("DEVICE_DISCONNECTED");let e=this.deps.getTransport(),t=await e?.getWriteLimits?.(),i=t?.mtu??await e?.getMtu?.()??null;return {withResponse:t?.withResponse??null,withoutResponse:t?.withoutResponse??null,mtu:i}}async writeAuto(e,t,i,r){let o=this.toUint8Array(i),s=o.byteLength,u=new Uint8Array(o);if(s===0)return await this.write(e,t,u,r),{bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0,fragmented:false};let c=await this.m(r?.chunkSize,r?.mode);return s<=c?(await this.write(e,t,u,r),{bytesWritten:s,totalBytes:s,chunkSize:s,chunkCount:1,retryCount:0,fragmented:false}):{...await this.writeFragmented(e,t,u,r),fragmented:true}}B(){for(let e of this.inFlightWrites.values())e.aborted=true;}async m(e,t){if(e!==void 0)return E(e);let i=await this.getWriteLimits().catch(()=>({withResponse:null,withoutResponse:null,mtu:null})),r=t==="without-response"?i.withoutResponse:i.withResponse;return typeof r=="number"&&r>0?E(r):typeof i.mtu=="number"&&i.mtu>3?E(i.mtu-3):E(ne)}L(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=3)throw new a("INVALID_PARAMETER",`Invalid mtu: ${e}. Must be an integer greater than 3.`);return E(e-3)}delay(e){return new Promise(t=>{setTimeout(t,e);})}toUint8Array(e){return e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}errorMessage(e){return e instanceof Error?e.message:String(e)}};var I=class I{constructor(e){this.deps=e;this.t=new Map;this.C=e=>{let t=e.target,i=t.value;if(i){for(let[r,o]of this.t)if((o.characteristic??this.deps.a.get(r))===t){let[u,c]=r.split(":");for(let l of o.callbacks)try{l(i);}catch(d){this.deps.e(a.from(d),{operation:"device.notification-callback",service:u,characteristic:c});}break}}};}getNotificationStates(){return this.t}subscribe(e,t,i,r){let{unsubscribe:o,ready:s}=this.h(e,t,i);s.catch(l=>{let d=a.from(l);try{r?.onError?.(d);}catch(f){this.deps.e(a.from(f),{operation:"device.subscribe.onError",service:e,characteristic:t});}});let u=r?.autoRecover??true;u&&this.g(this.charKey(e,t),e,t,i);let c=o;return ()=>{c(),u&&this.c(this.charKey(e,t),i);}}async subscribeAsync(e,t,i,r){let{unsubscribe:o,release:s,ready:u}=this.h(e,t,i),c=r?.autoRecover??true;c&&this.g(this.charKey(e,t),e,t,i);try{await u;}catch(l){let d=a.from(l);throw await s(),c&&this.c(this.charKey(e,t),i),d}return ()=>{o(),c&&this.c(this.charKey(e,t),i);}}async*notifications(e,t,i={maxQueueSize:I.DEFAULT_NOTIFICATION_QUEUE_SIZE}){let r=this.k(i.maxQueueSize??I.DEFAULT_NOTIFICATION_QUEUE_SIZE),o=i?.overflowStrategy??"error",s=[],u=0,c={resolve:null,reject:null,done:false,failure:null},l=p=>{if(!c.failure)if(c.resolve){let g=c.resolve;c.resolve=null,c.reject=null,g({value:p,done:false});}else {if(s.length>=r){u+=1;let g={service:e,characteristic:t,strategy:o,queueSize:r,droppedCount:u};this.deps._(g);try{i?.onOverflow?.(g);}catch(A){this.deps.e(a.from(A),{operation:"device.notifications.onOverflow",service:e,characteristic:t});}if(o==="error"){let A=new a("GATT_OPERATION_FAILED",`Notification queue overflowed (maxQueueSize=${r}). Increase queue size or consume faster.`);c.failure=A;let w=c.reject;c.resolve=null,c.reject=null,w?.(A);return}if(o==="drop-oldest"&&s.shift(),o==="drop-newest")return}s.push(p);}},d=this.charKey(e,t);this.g(d,e,t,l);let{unsubscribe:f,release:_,ready:b}=this.h(e,t,l);try{await b;}catch(p){throw await _(),this.c(d,l),p}try{for(;!c.done;){if(c.failure)throw c.failure;if(s.length>0)yield s.shift();else {let p=await new Promise((g,A)=>{c.resolve=g,c.reject=A;});if(p.done){let g=this.deps.M();if(g&&!this.deps.isIntentionalDisconnect()){if(await g.promise,this.deps.isIntentionalDisconnect())return;continue}return}yield p.value;}}}finally{let p=c.resolve;c.resolve=null,c.reject=null,c.done=true,await _(),this.c(d,l),p&&p({value:void 0,done:true});}}R(e){for(let t of this.t.values())this.A(t),t.nativeActive&&this.b(t.characteristic,{operation:e});this.t.clear();}cleanupSubscriptions(){this.R("notification.cleanup");}suspendSubscriptions(){this.R("notification.suspend");}async recoverSubscriptions(){let e=[...this.deps.i.entries()];for(let[t,i]of e)try{for(let r of i.callbacks){let{ready:o}=this.h(i.service,i.characteristic,r);await o;}}catch(r){this.deps.i.delete(t);let o=a.from(r);this.deps.E({service:i.service,characteristic:i.characteristic,error:o}),this.deps.e(o,{operation:"notification.recover",service:i.service,characteristic:i.characteristic});}}h(e,t,i){let r=this.charKey(e,t),o=this.t.get(r);o||(o={callbacks:new Set,characteristic:null,listenerAttached:false,nativeActive:false,reconcilePromise:null},this.t.set(r,o)),o.callbacks.add(i);let s=()=>{let u=this.t.get(r);return u?.callbacks.has(i)?(u.callbacks.delete(i),this.O(r,e,t)):Promise.resolve()};return {unsubscribe:()=>{s();},release:s,ready:this.O(r,e,t)}}O(e,t,i){let r=this.t.get(e);if(!r)return Promise.resolve();let u=(r.reconcilePromise??Promise.resolve()).catch(c=>{this.deps.e(a.from(c),{operation:"notification.reconcile",service:t,characteristic:i});}).then(async()=>{for(;;){if(this.t.get(e)!==r){await this.I(r);return}if(r.callbacks.size===0){if(this.A(r),r.nativeActive){r.nativeActive=false,await this.b(r.characteristic,{operation:"notification.stop",service:t,characteristic:i});continue}this.N(e,r);return}let l=r.characteristic??await this.deps.getCharacteristic(t,i);if(r.characteristic=l,r.listenerAttached||(l.addEventListener("characteristicvaluechanged",this.C),r.listenerAttached=true),!r.nativeActive){if(await l.startNotifications(),r.nativeActive=true,this.t.get(e)!==r){await this.I(r);return}if(r.callbacks.size===0)continue}if(r.callbacks.size!==0)return}}).finally(()=>{r.reconcilePromise===u&&(r.reconcilePromise=null,this.t.get(e)===r&&this.N(e,r));});return r.reconcilePromise=u,u}async I(e){this.A(e),e.nativeActive&&(e.nativeActive=false,await this.b(e.characteristic,{operation:"notification.deactivate"}));}A(e){!e.listenerAttached||!e.characteristic||(e.characteristic.removeEventListener("characteristicvaluechanged",this.C),e.listenerAttached=false);}N(e,t){this.t.get(e)===t&&(t.callbacks.size>0||t.nativeActive||t.reconcilePromise||this.t.delete(e));}k(e){if(!Number.isInteger(e)||e<=0)throw new a("INVALID_PARAMETER",`Invalid maxQueueSize: ${e}. Must be a positive integer.`);return e}async b(e,t){if(e)try{await e.stopNotifications();}catch(i){this.deps.e(a.from(i),t);}}charKey(e,t){return `${m(e)}:${m(t)}`}g(e,t,i,r){let o=this.deps.i.get(e);o||(o={service:t,characteristic:i,callbacks:new Set},this.deps.i.set(e,o)),o.callbacks.add(r);}c(e,t){let i=this.deps.i.get(e);i&&(i.callbacks.delete(t),i.callbacks.size===0&&this.deps.i.delete(e));}};I.DEFAULT_NOTIFICATION_QUEUE_SIZE=256;var G=I;function Ee(n){if((typeof n=="object"&&n!==null&&"name"in n&&typeof n.name=="string"?n.name:"")==="SecurityError")return true;let t=(n instanceof Error?n.message:String(n)).toLowerCase();return (t.includes("not allowed to access")||t.includes("blocklist")||t.includes("blocked"))&&t.includes("service")}function ye(n){return new DOMException(`This site is not allowed to access the Bluetooth service ${n}. Add "${n}" to the optionalServices array in your requestDevice() options, then reconnect.`,"SecurityError")}var N=class{constructor(e,t={}){this.server=null;this.s=null;this.u=new Map;this.a=new Map;this.i=new Map;this.y=new Set;this.w=new Set;this.x=new Set;this.T=new Set;this.S=new Set;this.n=null;this.intentionalDisconnect=false;this.p=null;this.f=null;this.v=null;this.id=e.id,this.name=e.name??null,this.raw=e,this.hooks=t,this.r=new V({getCharacteristic:(i,r)=>this.getCharacteristic(i,r),e:(i,r)=>this.e(i,r),d:i=>this.d(i),withOptionalTimeout:(i,r,o)=>this.withOptionalTimeout(i,r,o),isConnected:()=>this.connected,getTransport:()=>this.raw.gatt}),this.o=new G({getCharacteristic:(i,r)=>this.getCharacteristic(i,r),e:(i,r)=>this.e(i,r),E:i=>this.E(i),_:i=>this._(i),i:this.i,a:this.a,M:()=>this.n,isIntentionalDisconnect:()=>this.intentionalDisconnect}),typeof e.addEventListener=="function"&&e.addEventListener("gattserverdisconnected",()=>{this.U();});}get connected(){return this.server?.connected??false}async connect(e){if(this.connected)return;e?.autoReconnect&&(this.f=e.autoReconnect===true?{}:e.autoReconnect),this.hooks.beforeConnect?.(this);let t=this.raw.gatt;if(!t)throw new a("GATT_OPERATION_FAILED","Device has no GATT server");let i=this.n;try{this.server=await t.connect(),this.p=null,await this.o.recoverSubscriptions();for(let r of this.w)try{r();}catch(o){this.e(a.from(o),{operation:"device.reconnected-listener"});}this.intentionalDisconnect=!1,this.hooks.onConnectionChange?.(this);}catch(r){throw this.server?.connected||(this.server=null,this.hooks.onConnectionChange?.(this)),a.from(r)}finally{this.n===i&&(this.n=null),i?.resolve();}}disconnect(){this.intentionalDisconnect=true,this.p="intentional",this.f=null,this.v?.abort(),this.v=null,this.r.B(),this.o.cleanupSubscriptions(),this.i.clear(),this.n&&(this.n.resolve(),this.n=null),this.server?.disconnect(),this.server=null,this.s=null,this.hooks.onConnectionChange?.(this);}async connectWithRetry(e=U){await $(async()=>{await this.connect();},e);}async read(e,t,i,r){let o=typeof i=="function"?i:void 0,s=typeof i=="function"?r:i,u=this.d(s?.timeoutMs),c=await this.getCharacteristic(e,t);try{let l=await this.withOptionalTimeout(c.readValue(),u,"Read timed out");return o?await o(l):l}catch(l){throw a.from(l)}}async write(e,t,i,r){return this.r.write(e,t,i,r)}async writeFragmented(e,t,i,r){return this.r.writeFragmented(e,t,i,r)}async writeLarge(e,t,i,r){return this.r.writeLarge(e,t,i,r)}async writeWithoutResponse(e,t,i,r){return this.r.writeWithoutResponse(e,t,i,r)}async getWriteLimits(){return this.r.getWriteLimits()}async getMtu(){return (await this.getWriteLimits()).mtu}async writeAuto(e,t,i,r){return this.r.writeAuto(e,t,i,r)}subscribe(e,t,i,r){return this.o.subscribe(e,t,i,r)}async subscribeAsync(e,t,i,r){return this.o.subscribeAsync(e,t,i,r)}onCharacteristicOverflow(e,t,i){let r=null,o=false;return this.getCharacteristic(e,t).then(s=>{o||(s.addEventListener("beacio:overflow",i),r=()=>s.removeEventListener("beacio:overflow",i));}).catch(s=>{this.e(a.from(s),{operation:"device.onCharacteristicOverflow",service:e,characteristic:t});}),()=>{o=true,r?.(),r=null;}}notifications(e,t,i){return this.o.notifications(e,t,i)}async watchAdvertisements(){if(typeof this.raw.watchAdvertisements!="function")throw new a("GATT_OPERATION_FAILED","watchAdvertisements is not supported on this device");await this.raw.watchAdvertisements();}async unwatchAdvertisements(){let e=this.raw;if(typeof e.unwatchAdvertisements!="function")throw new a("GATT_OPERATION_FAILED","unwatchAdvertisements is not supported on this device");await e.unwatchAdvertisements();}async forget(){if(typeof this.raw.forget!="function")throw new a("GATT_OPERATION_FAILED","forget is not supported on this device");await this.raw.forget();}async getPrimaryServices(){if(!this.connected)throw new a("DEVICE_DISCONNECTED");if(this.s)return this.s;try{let t=(await this.server.getPrimaryServices()).map(i=>{let r=this.u.get(i.uuid)??i;return this.u.set(i.uuid,r),r});return this.s=t,t}catch(e){throw a.from(e)}}async getEffectiveMtu(){let e=await this.getWriteLimits();return typeof e.mtu=="number"&&e.mtu>0?e.mtu:typeof e.withResponse=="number"&&e.withResponse>0?e.withResponse+3:typeof e.withoutResponse=="number"&&e.withoutResponse>0?e.withoutResponse+3:23}z(){return this.p}getActiveSubscriptions(){let e=this.o.getNotificationStates();return [...new Set([...e.keys(),...this.i.keys()])].map(i=>{let r=e.get(i),o=this.i.get(i),[s,u]=i.split(":");return {service:s,characteristic:u,callbackCount:r?.callbacks.size??o?.callbacks.size??0,autoRecovering:o!==void 0,nativeActive:r?.nativeActive??false}})}on(e,t){return e==="disconnected"&&this.y.add(t),e==="reconnected"&&this.w.add(t),e==="queue-overflow"&&this.x.add(t),e==="subscription-lost"&&this.T.add(t),()=>{this.off(e,t);}}off(e,t){e==="disconnected"&&this.y.delete(t),e==="reconnected"&&this.w.delete(t),e==="queue-overflow"&&this.x.delete(t),e==="subscription-lost"&&this.T.delete(t);}addErrorListener(e){return this.S.add(e),()=>{this.removeErrorListener(e);}}removeErrorListener(e){this.S.delete(e);}U(){let e=this.intentionalDisconnect?"intentional":"unexpected";if(this.p=e,this.r.B(),this.o.suspendSubscriptions(),this.u.clear(),this.s=null,this.a.clear(),this.server=null,this.hooks.onConnectionChange?.(this),this.i.size>0){let t,i=new Promise(r=>{t=r;});this.n={promise:i,resolve:t};}for(let t of this.y)try{t(e);}catch(i){this.e(a.from(i),{operation:"device.disconnected-listener"});}e==="unexpected"&&this.f&&this.startAutoReconnect(this.f);}startAutoReconnect(e){this.v?.abort();let t=new AbortController;this.v=t;let i=e.maxAttempts??1/0,r=e.initialDelayMs??1e3,o=e.maxDelayMs??3e4,s=e.backoffMultiplier??2;(async()=>{let c=r;for(let l=1;l<=i;l++){if(t.signal.aborted||(await new Promise(d=>{let f=setTimeout(d,c);t.signal.addEventListener("abort",()=>{clearTimeout(f),d();},{once:true});}),t.signal.aborted))return;try{await this.connect();return}catch{c=Math.min(c*s,o);}}this.e(new a("CONNECTION_TIMEOUT",`Auto-reconnect failed after ${i} attempts`),{operation:"device.auto-reconnect"});})();}async withOptionalTimeout(e,t,i){if(t===void 0)return e;let r=null,o=new Promise((s,u)=>{r=setTimeout(()=>{u(new a("TIMEOUT",i));},t);});try{return await Promise.race([e,o])}finally{r!==null&&clearTimeout(r);}}d(e){if(e!==void 0){if(!Number.isFinite(e)||e<=0)throw new a("INVALID_PARAMETER",`Invalid timeoutMs: ${e}. Must be a positive number.`);return e}}fanout(e,t,i){for(let r of e)try{r(t);}catch(o){this.e(a.from(o),{operation:i});}}_(e){this.fanout(this.x,e,"device.queue-overflow-listener");}E(e){this.fanout(this.T,e,"device.subscription-lost-listener");}e(e,t){for(let i of this.S)try{i(e,t);}catch{}}async getCharacteristic(e,t){if(!this.connected)throw new a("DEVICE_DISCONNECTED");let i=this.charKey(e,t),r=this.a.get(i);if(r)return r;let o=await this.getService(e),s=m(t);try{let u=await o.getCharacteristic(s);return this.a.set(i,u),u}catch(u){throw a.from(u)}}async getService(e){let t=m(e);if(this.s){let r=this.s.find(o=>o.uuid===t);if(r)return r}let i=this.u.get(t);if(i)return i;try{let r=await this.server.getPrimaryService(t);return this.u.set(t,r),r}catch(r){throw Ee(r)?ye(t):a.from(r)}}charKey(e,t){return `${m(e)}:${m(t)}`}};var oe="__beacioCDNStub";function H(){if(typeof navigator>"u")return "unsupported";let n=navigator;return n.beacio?.__beacio===true?"safari-extension":n.bluetooth&&!n.bluetooth[oe]?"native":"unsupported"}function j(){if(typeof navigator>"u")return null;let n=navigator;return n.beacio?.__beacio===true?n.beacio:n.bluetooth&&!n.bluetooth[oe]?n.bluetooth:null}var Q={platform:"auto",maxConnections:0,defaultOptionalServices:[]};var Y=class{constructor(e){this.l=e;}unsupported(){throw this.l()}requestPermission(){this.unsupported();}requestBackgroundConnection(e){this.unsupported();}registerCharacteristicNotifications(e){this.unsupported();}registerBeaconScanning(e){this.unsupported();}getRegistrations(){this.unsupported();}unregister(e){this.unsupported();}update(e,t){this.unsupported();}connect(e){return this.requestBackgroundConnection(e)}subscribe(e){return this.registerCharacteristicNotifications(e)}scan(e){return this.registerBeaconScanning(e)}list(){return this.getRegistrations()}destroy(){}},X=class extends EventTarget{constructor(t){super();this.onwriterequest=null;this.onsubscriptionchange=null;this.onconnectionstatechange=null;this.onadvertisingstatechange=null;this.onnotificationready=null;this.l=t;}get advertising(){return false}unsupported(){throw this.l()}advertise(t){this.unsupported();}addService(t){this.unsupported();}stopAdvertising(){this.unsupported();}send(t){this.unsupported();}destroy(){}},K=class{constructor(e=Q){this.devices=new Map;this.registeredOptionalServices=new Set;this.platform=e.platform==="auto"?H():e.platform,this.maxConnections=this.W(e.maxConnections),e.defaultOptionalServices.length>0&&this.registerServices(e.defaultOptionalServices),this.bluetooth=this.platform!=="unsupported"?j():null,this.runtimeBluetooth=this.bluetooth,this.isSupported=this.bluetooth!==null,this.D=()=>this.platform==="unsupported"?new a("BLUETOOTH_UNAVAILABLE"):new a("GATT_OPERATION_FAILED","This Beacio feature requires the iOS Safari Beacio extension runtime."),this.unsupportedBackgroundSync=new Y(this.D),this.unsupportedPeripheral=new X(this.D);}get backgroundSync(){return this.runtimeBluetooth?.backgroundSync??this.unsupportedBackgroundSync}get peripheral(){return this.runtimeBluetooth?.peripheral??this.unsupportedPeripheral}async requestDevice(e){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");try{let t=await this.bluetooth.requestDevice(this.F(e)??{acceptAllDevices:!0});return this.P(t)}catch(t){throw a.from(t,"DEVICE_NOT_FOUND")}}registerServices(e){for(let t of e)this.registeredOptionalServices.add(m(t));}async getDevices(){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");let e=this.bluetooth;if(typeof e.getDevices!="function")return [];try{return (await e.getDevices()).map(i=>this.P(i))}catch(t){throw a.from(t)}}async getAvailability(){if(!this.bluetooth)return false;try{return await this.bluetooth.getAvailability()}catch{return false}}async requestLEScan(e={acceptAllAdvertisements:true}){if(!this.bluetooth)throw new a("BLUETOOTH_UNAVAILABLE");let t=this.bluetooth;if(typeof t.requestLEScan!="function")return null;try{return await t.requestLEScan(e)}catch(i){throw a.from(i)}}F(e){let t=this.V(e?.optionalServices);if(!e)return t?{optionalServices:t}:void 0;let i=o=>{if(o)return o.map(s=>m(s))},r={};return e.acceptAllDevices!==void 0&&(r.acceptAllDevices=e.acceptAllDevices),e.optionalManufacturerData!==void 0&&(r.optionalManufacturerData=e.optionalManufacturerData),e.filters&&(r.filters=e.filters.map(o=>({...o,services:i(o.services)}))),e.exclusionFilters&&(r.exclusionFilters=e.exclusionFilters.map(o=>({...o,services:i(o.services)}))),t&&(r.optionalServices=t),r}V(e){if(!e&&this.registeredOptionalServices.size===0)return;let t=new Set;for(let i of e??[])t.add(m(i));for(let i of this.registeredOptionalServices)t.add(i);return [...t]}W(e){if(e===0)return null;if(!Number.isInteger(e)||e<0)throw new a("INVALID_PARAMETER",`Invalid maxConnections: ${e}. Must be 0 (unlimited) or a positive integer.`);return e}P(e){let t=this.devices.get(e.id);if(t)return t;let i=new N(e,{beforeConnect:r=>{this.G(r);},onConnectionChange:r=>{this.devices.set(r.id,r);}});return this.devices.set(e.id,i),i}G(e){if(this.maxConnections===null||(this.devices.set(e.id,e),e.connected))return;let t=[...this.devices.values()].filter(i=>i.connected).length;if(t>=this.maxConnections)throw new a("CONNECTION_LIMIT_REACHED",`Connection limit reached (${t}/${this.maxConnections}). Disconnect another device or increase maxConnections before connecting ${e.name??e.id}.`,{retryAfterMs:1e3})}};function we(n){if(!Number.isInteger(n)||n<0||n>100)throw new a("INVALID_PARAMETER",`Invalid percent: ${n}. Must be an integer in 0..100.`);return n}function xe(n){return Number.isFinite(n)?Math.min(100,Math.max(0,Math.trunc(n))):0}var Te="https://beacio.com/setup";var Se={STATE_CHANGE:"beacio:statechange",READY:"beacio:ready",INSTALLED_INACTIVE:"beacio:installedinactive",NOT_INSTALLED:"beacio:notinstalled",EXTENSION_READY:"beacio:extension:ready",EXTENSION_PING:"beacio:extension:ping",EXTENSION_PONG:"beacio:extension:pong",EXTENSION_ACTIVATE_REQUEST:"beacio:extension:activate-request",EXTENSION_ACTIVATE_RESULT:"beacio:extension:activate-result",EXTENSION_INSTALLED:"beacio:extension:installed",EXTENSION_STATUS_CHANGE:"beacio:extension:statuschange"};function D(n,e,t,i){if(!Number.isInteger(t)||t<0||t+i>e.byteLength)throw new a("INVALID_PARAMETER",`${n}: cannot read ${i} byte${i===1?"":"s"} at offset ${t} of a ${e.byteLength}-byte DataView (value too short).`)}function De(n,e=0){return D("readUint8",n,e,1),n.getUint8(e)}function Be(n,e=0){return D("readUint16LE",n,e,2),n.getUint16(e,true)}function Ce(n,e=0){return D("readUint16BE",n,e,2),n.getUint16(e,false)}function Re(n,e=0){return D("readInt16LE",n,e,2),n.getInt16(e,true)}function Oe(n,e=0){return D("readUint32LE",n,e,4),n.getUint32(e,true)}function Ie(n,e=0){return D("readFloat32LE",n,e,4),n.getFloat32(e,true)}function Ne(n){return new TextDecoder().decode(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength))}function Pe(n){return new Uint8Array(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength))}var B=class{constructor(e,t){this._connected=false;this._services=new Map;this._device=e;for(let i of t)this._services.set(i.uuid,new P(e,i));}get connected(){return this._connected}async connect(){if(this._device.shouldFailConnect())throw new DOMException("Simulated transient connection failure","NetworkError");return this._connected=true,this.asBluetoothRemoteGATTServer()}disconnect(){this._connected=false;for(let e of this._services.values())e.stopAllNotifications();}async getPrimaryService(e){this._assertConnected();let t=this._services.get(e);if(!t)throw new DOMException(`No Services matching UUID ${e} found`,"NotFoundError");return t.asBluetoothRemoteGATTService()}async getPrimaryServices(e){return this._assertConnected(),(e?[this._services.get(e)].filter(Boolean):Array.from(this._services.values())).map(i=>i.asBluetoothRemoteGATTService())}getService(e){return this._services.get(e)}asBluetoothRemoteGATTServer(e){let t=this;return {get connected(){return t._connected},get device(){return e},connect:()=>t.connect(),disconnect:()=>t.disconnect(),getPrimaryService:r=>t.getPrimaryService(r),getPrimaryServices:r=>t.getPrimaryServices(r)}}_assertConnected(){if(!this._connected)throw new DOMException("GATT Server is disconnected. Cannot perform GATT operations.","NetworkError")}},P=class{constructor(e,t){this._characteristics=new Map;this.uuid=t.uuid,this.isPrimary=t.isPrimary??true;for(let i of t.characteristics??[])this._characteristics.set(i.uuid,new L(i));}async getCharacteristic(e){let t=this._characteristics.get(e);if(!t)throw new DOMException(`No Characteristics matching UUID ${e} found`,"NotFoundError");return t.asBluetoothRemoteGATTCharacteristic(this.asBluetoothRemoteGATTService())}async getCharacteristics(e){let t=e?[this._characteristics.get(e)].filter(Boolean):Array.from(this._characteristics.values()),i=this.asBluetoothRemoteGATTService();return t.map(r=>r.asBluetoothRemoteGATTCharacteristic(i))}getChar(e){return this._characteristics.get(e)}stopAllNotifications(){for(let e of this._characteristics.values())e.stopNotifications();}asBluetoothRemoteGATTService(e){let t=this;return {uuid:this.uuid,isPrimary:this.isPrimary,get device(){return e},getCharacteristic:i=>t.getCharacteristic(i),getCharacteristics:i=>t.getCharacteristics(i),getIncludedService:async()=>{throw new DOMException("Not implemented","NotSupportedError")},getIncludedServices:async()=>[],addEventListener:()=>{},removeEventListener:()=>{},dispatchEvent:()=>true,oncharacteristicvaluechanged:null,onserviceadded:null,onservicechanged:null,onserviceremoved:null}}},L=class{constructor(e){this._notifying=false;this._listeners=new Map;this._descriptors=new Map;if(this.uuid=e.uuid,this._properties={broadcast:e.properties?.broadcast??false,read:e.properties?.read??true,write:e.properties?.write??false,writeWithoutResponse:e.properties?.writeWithoutResponse??false,notify:e.properties?.notify??false,indicate:e.properties?.indicate??false,authenticatedSignedWrites:e.properties?.authenticatedSignedWrites??false,reliableWrite:e.properties?.reliableWrite??false,writableAuxiliaries:e.properties?.writableAuxiliaries??false},e.value){let t=e.value instanceof Uint8Array?e.value.buffer.slice(e.value.byteOffset,e.value.byteOffset+e.value.byteLength):e.value;this._value=new DataView(t);}else this._value=new DataView(new ArrayBuffer(0));for(let t of e.descriptors??[])this._descriptors.set(t.uuid,new k(t));}setValue(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);}emitNotification(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);let i=new Event("characteristicvaluechanged");Object.defineProperty(i,"target",{value:{value:this._value},writable:false});let r=this._listeners.get("characteristicvaluechanged");if(r)for(let o of r)o(i);}stopNotifications(){this._notifying=false;}get isNotifying(){return this._notifying}getDesc(e){return this._descriptors.get(e)}asBluetoothRemoteGATTCharacteristic(e){let t=this;return {uuid:this.uuid,service:e,properties:{broadcast:this._properties.broadcast,read:this._properties.read,writeWithoutResponse:this._properties.writeWithoutResponse,write:this._properties.write,notify:this._properties.notify,indicate:this._properties.indicate,authenticatedSignedWrites:this._properties.authenticatedSignedWrites,reliableWrite:this._properties.reliableWrite,writableAuxiliaries:this._properties.writableAuxiliaries},get value(){return t._value},readValue:async()=>{if(!t._properties.read)throw new DOMException("Characteristic does not support read","NotSupportedError");return t._value},writeValue:async i=>{if(!t._properties.write)throw new DOMException("Characteristic does not support write","NotSupportedError");t._writeValue(i);},writeValueWithResponse:async i=>{if(!t._properties.write)throw new DOMException("Characteristic does not support write","NotSupportedError");t._writeValue(i);},writeValueWithoutResponse:async i=>{if(!t._properties.writeWithoutResponse)throw new DOMException("Characteristic does not support write without response","NotSupportedError");t._writeValue(i);},startNotifications:async function(){if(!t._properties.notify&&!t._properties.indicate)throw new DOMException("Characteristic does not support notifications","NotSupportedError");return t._notifying=true,this},stopNotifications:async function(){return t._notifying=false,this},addEventListener:(i,r)=>{t._listeners.has(i)||t._listeners.set(i,new Set),t._listeners.get(i).add(r);},removeEventListener:(i,r)=>{t._listeners.get(i)?.delete(r);},dispatchEvent:()=>true,getDescriptor:async i=>{let r=t._descriptors.get(i);if(!r)throw new DOMException(`No Descriptors matching UUID ${i} found`,"NotFoundError");return r.asBluetoothRemoteGATTDescriptor(t.asBluetoothRemoteGATTCharacteristic(e))},getDescriptors:async i=>{let r=i?[t._descriptors.get(i)].filter(Boolean):Array.from(t._descriptors.values()),o=t.asBluetoothRemoteGATTCharacteristic(e);return r.map(s=>s.asBluetoothRemoteGATTDescriptor(o))},oncharacteristicvaluechanged:null}}_writeValue(e){let t=e instanceof ArrayBuffer?e:e.buffer??e.buffer;this._value=new DataView(t);}},k=class{constructor(e){if(this.uuid=e.uuid,e.value){let t=e.value instanceof Uint8Array?e.value.buffer.slice(e.value.byteOffset,e.value.byteOffset+e.value.byteLength):e.value;this._value=new DataView(t);}else this._value=new DataView(new ArrayBuffer(0));}setValue(e){let t=e instanceof Uint8Array?e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength):e;this._value=new DataView(t);}get value(){return this._value}asBluetoothRemoteGATTDescriptor(e){let t=this;return {uuid:this.uuid,characteristic:e,get value(){return t._value},readValue:async()=>t._value,writeValue:async i=>{let r=i instanceof ArrayBuffer?i:i.buffer??i.buffer;t._value=new DataView(r);}}}};var Le=0,y=class{constructor(e={}){this._listeners=new Map;this._watchingAdvertisements=false;this.id=e.id??`mock-device-${++Le}`,this.name=e.name,this._serviceUUIDs=e.serviceUUIDs??[],this._gatt=new B(this,e.services??[]),this._rssi=e.rssi??-60,this._remainingConnectFailures=e.failConnectAttempts??0,this._writeLimits={withResponse:e.writeLimits?.withResponse??null,withoutResponse:e.writeLimits?.withoutResponse??null,mtu:e.writeLimits?.mtu??null};}matchesFilter(e){return !(e.services&&!e.services.some(i=>this._serviceUUIDs.includes(String(i)))||e.name&&e.name!==this.name||e.namePrefix&&!this.name?.startsWith(e.namePrefix))}asBluetoothDevice(){let e=this,t={id:this.id,name:this.name??null,gatt:null,watchAdvertisements:async i=>{if(e._watchingAdvertisements=true,i?.signal){if(i.signal.aborted){e._watchingAdvertisements=false;return}i.signal.addEventListener("abort",()=>{e._watchingAdvertisements=false;},{once:true});}},addEventListener:(i,r)=>{e._addListener(i,r);},removeEventListener:(i,r)=>{e._removeListener(i,r);},dispatchEvent:i=>true,get watchingAdvertisements(){return e._watchingAdvertisements},unwatchAdvertisements:async()=>{e._watchingAdvertisements=false;},forget:async()=>{},onadvertisementreceived:null,ongattserverdisconnected:null,oncharacteristicvaluechanged:null,onserviceadded:null,onservicechanged:null,onserviceremoved:null};return t.gatt=this._gatt.asBluetoothRemoteGATTServer(t),t.gatt.getMtu=async()=>this._writeLimits.mtu,t.gatt.getWriteLimits=async()=>({...this._writeLimits}),t}shouldFailConnect(){return this._remainingConnectFailures<=0?false:(this._remainingConnectFailures-=1,true)}simulateDisconnect(){this._gatt.disconnect(),this._emit("gattserverdisconnected",new Event("gattserverdisconnected"));}get gatt(){return this._gatt}get serviceUUIDs(){return this._serviceUUIDs}get rssi(){return this._rssi}emitAdvertisement(e={}){if(this._advertisementSink){this._advertisementSink(this,e);return}this.dispatchAdvertisementEvent(e);}setRSSI(e){this._rssi=e;}setAdvertisementSink(e){this._advertisementSink=e;}dispatchAdvertisementEvent(e={}){this._watchingAdvertisements&&this._emit("advertisementreceived",this.createAdvertisementEvent(this.asBluetoothDevice(),e));}createAdvertisementEvent(e,t={}){let i=new Event("advertisementreceived");return Object.defineProperties(i,{device:{value:e,writable:false},name:{value:this.name,writable:false},uuids:{value:[...t.uuids??this._serviceUUIDs],writable:false},rssi:{value:t.rssi??this._rssi,writable:false},txPower:{value:t.txPower,writable:false},manufacturerData:{value:t.manufacturerData??new Map,writable:false},serviceData:{value:t.serviceData??new Map,writable:false}}),i}_addListener(e,t){this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add(t);}_removeListener(e,t){this._listeners.get(e)?.delete(t);}_emit(e,t){let i=this._listeners.get(e);if(i)for(let r of i)r(t);}};var v=()=>Promise.reject(new DOMException("Beacio extension API not implemented in MockBluetooth","NotSupportedError")),z=()=>{},M=class{constructor(e={}){this._devices=new Map;this._listeners=new Map;this._scanActive=false;this.backgroundSync={requestPermission:v,requestBackgroundConnection:v,registerCharacteristicNotifications:v,registerBeaconScanning:v,getRegistrations:v,unregister:v,update:v,connect:v,subscribe:v,scan:v,list:v,destroy:z};this.peripheral={advertising:false,advertise:v,stopAdvertising:v,send:v,destroy:z,addEventListener:z,removeEventListener:z,onwriterequest:null,onsubscriptionchange:null,onconnectionstatechange:null,onadvertisingstatechange:null};this._handleAdvertisement=(e,t)=>{if(e.dispatchAdvertisementEvent(t),!this._scanActive||!this._matchesScan(e))return;let i=e.createAdvertisementEvent(e.asBluetoothDevice(),t),r=this._listeners.get("advertisementreceived");if(r)for(let o of r)o(i);};if(this._available=e.available??true,e.devices)for(let t of e.devices){let i=new y(t);i.setAdvertisementSink(this._handleAdvertisement),this._devices.set(i.id,i);}}async getAvailability(){return this._available}async requestDevice(e){if(!this._available)throw new DOMException("Bluetooth adapter not available","NotFoundError");let t=this._findMatchingDevices(e);if(t.length===0)throw new DOMException("No devices found matching the filter criteria","NotFoundError");return t[0].asBluetoothDevice()}async getDevices(){return Array.from(this._devices.values()).map(e=>e.asBluetoothDevice())}async requestLEScan(e){if(this._scanActive)throw new DOMException("Scan already in progress","InvalidStateError");this._scanActive=true,this._lastScanOptions=e;let t={active:true,keepRepeatedDevices:e?.keepRepeatedDevices??false,acceptAllAdvertisements:e?.acceptAllAdvertisements??false,stop:()=>{this._scanActive=false,this._lastScanOptions=void 0,t.active=false;}};return t}addEventListener(e,t){this._listeners.has(e)||this._listeners.set(e,new Set),this._listeners.get(e).add(t);}removeEventListener(e,t){this._listeners.get(e)?.delete(t);}addDevice(e){let t=new y(e);return t.setAdvertisementSink(this._handleAdvertisement),this._devices.set(t.id,t),t}removeDevice(e){let t=this._devices.get(e);t&&(t.setAdvertisementSink(void 0),t.simulateDisconnect(),this._devices.delete(e));}getDevice(e){return this._devices.get(e)}setAvailable(e){this._available=e;}install(){return typeof globalThis.navigator>"u"?this:(this._installedNavigatorBluetooth=globalThis.navigator.bluetooth,Object.defineProperty(globalThis.navigator,"bluetooth",{value:this,writable:true,configurable:true}),this)}uninstall(){typeof globalThis.navigator>"u"||(Object.defineProperty(globalThis.navigator,"bluetooth",{value:this._installedNavigatorBluetooth,writable:true,configurable:true}),this._installedNavigatorBluetooth=void 0);}emitAdvertisement(e,t={}){let i=this._devices.get(e);if(!i)throw new Error(`Unknown mock device: ${e}`);this._handleAdvertisement(i,t);}reset(){for(let e of this._devices.values())e.setAdvertisementSink(void 0),e.simulateDisconnect();this._devices.clear(),this._listeners.clear(),this._scanActive=false,this._lastScanOptions=void 0,this._available=true;}_findMatchingDevices(e){if(!e||e.acceptAllDevices)return Array.from(this._devices.values());let t=e.filters??[];return Array.from(this._devices.values()).filter(i=>t.some(r=>i.matchesFilter(r)))}_matchesScan(e){let t=this._lastScanOptions;if(!t||t.acceptAllAdvertisements)return true;let i=t.filters??[];return i.length===0?true:i.some(r=>e.matchesFilter(r))}};function Z(n){return new M(n)}function se(n){return Z(n).install()}var h={services:{HEART_RATE:"0000180d-0000-1000-8000-00805f9b34fb",BATTERY:"0000180f-0000-1000-8000-00805f9b34fb",DEVICE_INFO:"0000180a-0000-1000-8000-00805f9b34fb",ENVIRONMENTAL_SENSING:"0000181a-0000-1000-8000-00805f9b34fb"},characteristics:{HEART_RATE_MEASUREMENT:"00002a37-0000-1000-8000-00805f9b34fb",BODY_SENSOR_LOCATION:"00002a38-0000-1000-8000-00805f9b34fb",BATTERY_LEVEL:"00002a19-0000-1000-8000-00805f9b34fb",MANUFACTURER_NAME:"00002a29-0000-1000-8000-00805f9b34fb",MODEL_NUMBER:"00002a24-0000-1000-8000-00805f9b34fb",TEMPERATURE:"00002a6e-0000-1000-8000-00805f9b34fb"},descriptors:{CCCD:"00002902-0000-1000-8000-00805f9b34fb",USER_DESCRIPTION:"00002901-0000-1000-8000-00805f9b34fb",PRESENTATION_FORMAT:"00002904-0000-1000-8000-00805f9b34fb"}},ke={heartRate(n="Mock HR Sensor"){return {name:n,serviceUUIDs:[h.services.HEART_RATE],services:[{uuid:h.services.HEART_RATE,characteristics:[{uuid:h.characteristics.HEART_RATE_MEASUREMENT,properties:{read:true,notify:true},value:new Uint8Array([0,72])},{uuid:h.characteristics.BODY_SENSOR_LOCATION,properties:{read:true},value:new Uint8Array([1])}]}]}},battery(n="Mock Battery Device"){return {name:n,serviceUUIDs:[h.services.BATTERY],services:[{uuid:h.services.BATTERY,characteristics:[{uuid:h.characteristics.BATTERY_LEVEL,properties:{read:true,notify:true},value:new Uint8Array([85])}]}]}},full(n="Mock Full Device"){return {name:n,serviceUUIDs:[h.services.HEART_RATE,h.services.BATTERY,h.services.DEVICE_INFO],services:[{uuid:h.services.HEART_RATE,characteristics:[{uuid:h.characteristics.HEART_RATE_MEASUREMENT,properties:{read:true,notify:true},value:new Uint8Array([0,72])}]},{uuid:h.services.BATTERY,characteristics:[{uuid:h.characteristics.BATTERY_LEVEL,properties:{read:true,notify:true},value:new Uint8Array([100])}]},{uuid:h.services.DEVICE_INFO,characteristics:[{uuid:h.characteristics.MANUFACTURER_NAME,properties:{read:true},value:Uint8Array.from(Array.from("Beacio Test Corp").map(e=>e.charCodeAt(0)))},{uuid:h.characteristics.MODEL_NUMBER,properties:{read:true},value:Uint8Array.from(Array.from("WBT-001").map(e=>e.charCodeAt(0)))}]}]}}};
exports.BEACIO_EVENTS=Se;exports.BLE_UUIDS=h;exports.Beacio=K;exports.BeacioDevice=N;exports.BeacioError=a;exports.BluetoothUUID=Ae;exports.DEFAULT_BEACIO_OPTIONS=Q;exports.DEFAULT_RETRY_OPTIONS=U;exports.MockBleDevice=y;exports.MockBluetooth=M;exports.MockCharacteristic=L;exports.MockDescriptor=k;exports.MockGATTServer=B;exports.MockService=P;exports.SETUP_URL=Te;exports.canonicalUUID=W;exports.chunkSize=E;exports.clampChunkSize=be;exports.clampPercent=xe;exports.createMockBluetooth=Z;exports.detectPlatform=H;exports.getBluetoothAPI=j;exports.getCharacteristicName=ge;exports.getDescriptor=re;exports.getDisplayName=_e;exports.getServiceName=me;exports.installMockBluetooth=se;exports.mockDevices=ke;exports.percent=we;exports.readBytes=Pe;exports.readFloat32LE=Ie;exports.readInt16LE=Re;exports.readUint16BE=Ce;exports.readUint16LE=Be;exports.readUint32LE=Oe;exports.readUint8=De;exports.readUtf8=Ne;exports.resolveUUID=m;exports.withRetry=$;//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

@@ -1,2 +0,2 @@

import {c,a,b as b$1}from'./chunk-FKTUFPPD.mjs';export{i as BluetoothUUID,g as canonicalUUID,a as detectPlatform,b as getBluetoothAPI,e as getCharacteristicName,h as getDescriptor,f as getDisplayName,d as getServiceName,c as resolveUUID}from'./chunk-FKTUFPPD.mjs';var M=new Set(["DEVICE_DISCONNECTED","CONNECTION_TIMEOUT","GATT_OPERATION_FAILED","TIMEOUT","SCAN_ALREADY_IN_PROGRESS","WRITE_INCOMPLETE"]),N={INVALID_PARAMETER:"One or more input parameters were invalid. Check UUIDs, payload sizes, and option values.",BLUETOOTH_UNAVAILABLE:"Check that the browser supports Web Bluetooth and the device has Bluetooth enabled.",EXTENSION_NOT_INSTALLED:"Install the WebBLE iOS app and enable the Safari extension. Use @beacio/detect to show an install banner.",PERMISSION_DENIED:"The user denied Bluetooth permission or the request was not triggered by a user gesture. Call requestDevice() from a click/tap handler and try again.",DEVICE_NOT_FOUND:"No matching device found. Check your scan filters or ensure the device is advertising.",DEVICE_DISCONNECTED:"Call device.connect() before performing GATT operations.",CONNECTION_TIMEOUT:"The device did not respond in time. Ensure it is in range and advertising.",SERVICE_NOT_FOUND:"The requested service was not found on this device. Check the service UUID and ensure it is included in requestDevice filters.",CHARACTERISTIC_NOT_FOUND:"The requested characteristic was not found in this service. Check the characteristic UUID.",CHARACTERISTIC_NOT_READABLE:"This characteristic does not support read. Use device.subscribe() instead if it supports notify.",CHARACTERISTIC_NOT_WRITABLE:"This characteristic does not support write. Check the characteristic properties.",CHARACTERISTIC_NOT_NOTIFIABLE:"This characteristic does not support notifications. Use device.read() for polling instead.",GATT_OPERATION_FAILED:"The GATT operation failed. The device may have disconnected or the characteristic may be busy.",SCAN_ALREADY_IN_PROGRESS:"Stop the current scan before starting a new one.",CONNECTION_LIMIT_REACHED:"Disconnect another device or raise maxConnections for this WebBLE instance before connecting more devices.",USER_CANCELLED:"The user cancelled the device picker. No action needed.",TIMEOUT:"The operation timed out. Retry or check device connectivity.",WRITE_INCOMPLETE:"Only part of the payload was written. Retry with smaller chunks or reconnect the device."},o=class u extends Error{constructor(e,t,r){let i=N[e];super(t??i),this.name="WebBLEError",this.code=e,this.suggestion=N[e],this.isRetriable=M.has(e),this.retryAfterMs=r?.retryAfterMs;}static from(e,t="GATT_OPERATION_FAILED"){if(e instanceof u)return e;let r=typeof e=="object"&&e!==null&&"name"in e&&typeof e.name=="string"?e.name:void 0,i=e instanceof Error?e.message:String(e),n=i.toLowerCase();switch(r){case "TypeError":return new u("INVALID_PARAMETER",i);case "NotFoundError":return new u("DEVICE_NOT_FOUND",i);case "NotAllowedError":case "SecurityError":return new u("PERMISSION_DENIED",i);case "NetworkError":return new u("DEVICE_DISCONNECTED",i,{retryAfterMs:1e3});case "TimeoutError":return new u("TIMEOUT",i,{retryAfterMs:1e3});case "InvalidStateError":if(n.includes("disconnect"))return new u("DEVICE_DISCONNECTED",i,{retryAfterMs:1e3});break;}return i.includes("User cancelled")||i.includes("User canceled")?new u("USER_CANCELLED"):n.includes("no devices found")||i.includes("No Devices")?new u("DEVICE_NOT_FOUND"):i.includes("No Services matching")||n.includes("service not found")?new u("SERVICE_NOT_FOUND",i):i.includes("No Characteristics matching")||n.includes("characteristic not found")?new u("CHARACTERISTIC_NOT_FOUND",i):i.includes("GATT Server is disconnected")||n.includes("disconnected")?new u("DEVICE_DISCONNECTED",i,{retryAfterMs:1e3}):n.includes("not supported")&&n.includes("read")?new u("CHARACTERISTIC_NOT_READABLE",i):n.includes("not supported")&&n.includes("write")?new u("CHARACTERISTIC_NOT_WRITABLE",i):n.includes("not supported")&&n.includes("notif")?new u("CHARACTERISTIC_NOT_NOTIFIABLE",i):n.includes("permission")?new u("PERMISSION_DENIED",i):new u(t,i)}};async function A(u,e={}){let t=e.maxAttempts??3,r=e.delayMs??250,i=e.backoffMultiplier??1.5;if(!Number.isInteger(t)||t<=0)throw new o("INVALID_PARAMETER",`Invalid maxAttempts: ${t}. Must be a positive integer.`);if(!Number.isFinite(r)||r<0)throw new o("INVALID_PARAMETER",`Invalid delayMs: ${r}. Must be a non-negative number.`);if(!Number.isFinite(i)||i<1)throw new o("INVALID_PARAMETER",`Invalid backoffMultiplier: ${i}. Must be a number >= 1.`);for(let n=1;n<=t;n+=1)try{return await u(n)}catch(a){let c=o.from(a);if(n>=t||!c.isRetriable)throw c;let s=c.retryAfterMs??r*Math.pow(i,n-1);s>0&&await new Promise(l=>{setTimeout(l,s);});}throw new o("GATT_OPERATION_FAILED","Retry loop exited unexpectedly.")}var L=class{constructor(e){this.deps=e;this.inFlightWrites=new Map;}async write(e,t,r,i){let n=this.deps.validateTimeoutMs(i?.timeoutMs),a=await this.deps.getCharacteristic(e,t),c=Symbol(`write:${e}:${t}`);this.inFlightWrites.set(c,{service:e,characteristic:t,aborted:false});try{if(i?.mode==="without-response"){await this.deps.withOptionalTimeout(a.writeValueWithoutResponse(r),n,"Write without response timed out");return}await this.deps.withOptionalTimeout(a.writeValueWithResponse(r),n,"Write with response timed out");}catch(s){throw this.inFlightWrites.get(c)?.aborted?new o("WRITE_INCOMPLETE",`Write incomplete for ${e}/${t}: disconnected before completion`,{retryAfterMs:1e3}):o.from(s)}finally{this.inFlightWrites.delete(c);}}async writeFragmented(e,t,r,i){let n=this.toUint8Array(r),a=n.byteLength;if(a===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0};let c=i?.chunkSize??this.deriveChunkSizeFromMtu(i?.mtu)??await this.deriveChunkSize(void 0,i?.mode),s=i?.maxRetries??0,l=i?.retryDelayMs??0,h=0,v=0,E=0;for(let g=0;g<a;g+=c){let d=Math.min(g+c,a),f=new Uint8Array(n.subarray(g,d)),m=0;for(;;)try{await this.write(e,t,f,i),h+=f.byteLength,v+=1;break}catch(w){if(m>=s)throw h>0&&h<a?new o("WRITE_INCOMPLETE",`Write fragmented incomplete (${h}/${a} bytes written): ${this.errorMessage(w)}`,{retryAfterMs:1e3}):o.from(w);m+=1,E+=1,l>0&&await this.delay(l);}}return {bytesWritten:h,totalBytes:a,chunkSize:c,chunkCount:v,retryCount:E}}async writeLarge(e,t,r,i){let n=this.toUint8Array(r),a=n.byteLength;if(a===0)return {bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0};let c=await this.deriveChunkSize(i?.chunkSize,i?.mode),s=0,l=0;for(let h=0;h<a;h+=c){let v=Math.min(h+c,a),E=n.subarray(h,v),g=new Uint8Array(E);try{await this.write(e,t,g,i),s+=E.byteLength,l+=1;}catch(d){throw s>0&&s<a?new o("WRITE_INCOMPLETE",`Write incomplete (${s}/${a} bytes written): ${this.errorMessage(d)}`):o.from(d)}}if(s!==a)throw new o("WRITE_INCOMPLETE",`Write incomplete (${s}/${a} bytes written)`);return {bytesWritten:s,totalBytes:a,chunkSize:c,chunkCount:l}}async writeWithoutResponse(e,t,r,i){return this.write(e,t,r,{...i,mode:"without-response"})}async getWriteLimits(){if(!this.deps.isConnected())throw new o("DEVICE_DISCONNECTED");let e=this.deps.getTransport(),t=await e?.getWriteLimits?.(),r=t?.mtu??await e?.getMtu?.()??null;return {withResponse:t?.withResponse??null,withoutResponse:t?.withoutResponse??null,mtu:r}}async writeAuto(e,t,r,i){let n=this.toUint8Array(r),a=n.byteLength,c=new Uint8Array(n);if(a===0)return await this.write(e,t,c,i),{bytesWritten:0,totalBytes:0,chunkSize:0,chunkCount:0,retryCount:0,fragmented:false};let s=await this.deriveChunkSize(i?.chunkSize,i?.mode);return a<=s?(await this.write(e,t,c,i),{bytesWritten:a,totalBytes:a,chunkSize:a,chunkCount:1,retryCount:0,fragmented:false}):{...await this.writeFragmented(e,t,c,i),fragmented:true}}abortInFlightWrites(){for(let e of this.inFlightWrites.values())e.aborted=true;}async deriveChunkSize(e,t){if(e!==void 0){if(!Number.isInteger(e)||e<=0)throw new o("INVALID_PARAMETER",`Invalid chunkSize: ${e}. Must be a positive integer.`);return e}let r=await this.getWriteLimits().catch(()=>({withResponse:null,withoutResponse:null,mtu:null})),i=t==="without-response"?r.withoutResponse:r.withResponse;return typeof i=="number"&&i>0?i:typeof r.mtu=="number"&&r.mtu>3?r.mtu-3:20}deriveChunkSizeFromMtu(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=3)throw new o("INVALID_PARAMETER",`Invalid mtu: ${e}. Must be an integer greater than 3.`);return e-3}delay(e){return new Promise(t=>{setTimeout(t,e);})}toUint8Array(e){return e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}errorMessage(e){return e instanceof Error?e.message:String(e)}};var y=class y{constructor(e){this.deps=e;this.notificationStates=new Map;this.handleNotification=e=>{let t=e.target,r=t.value;if(r){for(let[i,n]of this.notificationStates)if((n.characteristic??this.deps.charCache.get(i))===t){let[c,s]=i.split(":");for(let l of n.callbacks)try{l(r);}catch(h){this.deps.emitError(o.from(h),{operation:"device.notification-callback",service:c,characteristic:s});}break}}};}getNotificationStates(){return this.notificationStates}subscribe(e,t,r,i){let{unsubscribe:n,ready:a}=this.registerNotificationConsumer(e,t,r);a.catch(l=>{let h=o.from(l);try{i?.onError?.(h);}catch(v){this.deps.emitError(o.from(v),{operation:"device.subscribe.onError",service:e,characteristic:t});}});let c=i?.autoRecover??true;c&&this.addToRecoveryRegistry(this.charKey(e,t),e,t,r);let s=n;return ()=>{s(),c&&this.removeFromRecoveryRegistry(this.charKey(e,t),r);}}async subscribeAsync(e,t,r,i){let{unsubscribe:n,release:a,ready:c}=this.registerNotificationConsumer(e,t,r),s=i?.autoRecover??true;s&&this.addToRecoveryRegistry(this.charKey(e,t),e,t,r);try{await c;}catch(l){let h=o.from(l);throw await a(),s&&this.removeFromRecoveryRegistry(this.charKey(e,t),r),h}return ()=>{n(),s&&this.removeFromRecoveryRegistry(this.charKey(e,t),r);}}async*notifications(e,t,r={maxQueueSize:y.DEFAULT_NOTIFICATION_QUEUE_SIZE}){let i=this.validateMaxQueueSize(r.maxQueueSize??y.DEFAULT_NOTIFICATION_QUEUE_SIZE),n=r?.overflowStrategy??"error",a=[],c=0,s={resolve:null,reject:null,done:false,failure:null},l=d=>{if(!s.failure)if(s.resolve){let f=s.resolve;s.resolve=null,s.reject=null,f({value:d,done:false});}else {if(a.length>=i){c+=1;let f={service:e,characteristic:t,strategy:n,queueSize:i,droppedCount:c};this.deps.emitQueueOverflow(f);try{r?.onOverflow?.(f);}catch(m){this.deps.emitError(o.from(m),{operation:"device.notifications.onOverflow",service:e,characteristic:t});}if(n==="error"){let m=new o("GATT_OPERATION_FAILED",`Notification queue overflowed (maxQueueSize=${i}). Increase queue size or consume faster.`);s.failure=m;let w=s.reject;s.resolve=null,s.reject=null,w?.(m);return}if(n==="drop-oldest"&&a.shift(),n==="drop-newest")return}a.push(d);}},h=this.charKey(e,t);this.addToRecoveryRegistry(h,e,t,l);let{unsubscribe:v,release:E,ready:g}=this.registerNotificationConsumer(e,t,l);try{await g;}catch(d){throw await E(),this.removeFromRecoveryRegistry(h,l),d}try{for(;!s.done;){if(s.failure)throw s.failure;if(a.length>0)yield a.shift();else {let d=await new Promise((f,m)=>{s.resolve=f,s.reject=m;});if(d.done){let f=this.deps.getReconnectGate();if(f&&!this.deps.isIntentionalDisconnect()){if(await f.promise,this.deps.isIntentionalDisconnect())return;continue}return}yield d.value;}}}finally{let d=s.resolve;s.resolve=null,s.reject=null,s.done=true,await E(),this.removeFromRecoveryRegistry(h,l),d&&d({value:void 0,done:true});}}teardownSubscriptions(e){for(let t of this.notificationStates.values())this.detachNotificationListener(t),t.nativeActive&&this.stopNotificationsSafely(t.characteristic,{operation:e});this.notificationStates.clear();}cleanupSubscriptions(){this.teardownSubscriptions("notification.cleanup");}suspendSubscriptions(){this.teardownSubscriptions("notification.suspend");}async recoverSubscriptions(){let e=[...this.deps.recoveryRegistry.entries()];for(let[t,r]of e)try{for(let i of r.callbacks){let{ready:n}=this.registerNotificationConsumer(r.service,r.characteristic,i);await n;}}catch(i){this.deps.recoveryRegistry.delete(t);let n=o.from(i);this.deps.emitSubscriptionLost({service:r.service,characteristic:r.characteristic,error:n}),this.deps.emitError(n,{operation:"notification.recover",service:r.service,characteristic:r.characteristic});}}registerNotificationConsumer(e,t,r){let i=this.charKey(e,t),n=this.notificationStates.get(i);n||(n={callbacks:new Set,characteristic:null,listenerAttached:false,nativeActive:false,reconcilePromise:null},this.notificationStates.set(i,n)),n.callbacks.add(r);let a=()=>{let c=this.notificationStates.get(i);return c?.callbacks.has(r)?(c.callbacks.delete(r),this.syncNotificationState(i,e,t)):Promise.resolve()};return {unsubscribe:()=>{a();},release:a,ready:this.syncNotificationState(i,e,t)}}syncNotificationState(e,t,r){let i=this.notificationStates.get(e);if(!i)return Promise.resolve();let c=(i.reconcilePromise??Promise.resolve()).catch(s=>{this.deps.emitError(o.from(s),{operation:"notification.reconcile",service:t,characteristic:r});}).then(async()=>{for(;;){if(this.notificationStates.get(e)!==i){await this.deactivateNotificationState(i);return}if(i.callbacks.size===0){if(this.detachNotificationListener(i),i.nativeActive){i.nativeActive=false,await this.stopNotificationsSafely(i.characteristic,{operation:"notification.stop",service:t,characteristic:r});continue}this.deleteNotificationStateIfIdle(e,i);return}let l=i.characteristic??await this.deps.getCharacteristic(t,r);if(i.characteristic=l,i.listenerAttached||(l.addEventListener("characteristicvaluechanged",this.handleNotification),i.listenerAttached=true),!i.nativeActive){if(await l.startNotifications(),i.nativeActive=true,this.notificationStates.get(e)!==i){await this.deactivateNotificationState(i);return}if(i.callbacks.size===0)continue}if(i.callbacks.size!==0)return}}).finally(()=>{i.reconcilePromise===c&&(i.reconcilePromise=null,this.notificationStates.get(e)===i&&this.deleteNotificationStateIfIdle(e,i));});return i.reconcilePromise=c,c}async deactivateNotificationState(e){this.detachNotificationListener(e),e.nativeActive&&(e.nativeActive=false,await this.stopNotificationsSafely(e.characteristic,{operation:"notification.deactivate"}));}detachNotificationListener(e){!e.listenerAttached||!e.characteristic||(e.characteristic.removeEventListener("characteristicvaluechanged",this.handleNotification),e.listenerAttached=false);}deleteNotificationStateIfIdle(e,t){this.notificationStates.get(e)===t&&(t.callbacks.size>0||t.nativeActive||t.reconcilePromise||this.notificationStates.delete(e));}validateMaxQueueSize(e){if(!Number.isInteger(e)||e<=0)throw new o("INVALID_PARAMETER",`Invalid maxQueueSize: ${e}. Must be a positive integer.`);return e}async stopNotificationsSafely(e,t){if(e)try{await e.stopNotifications();}catch(r){this.deps.emitError(o.from(r),t);}}charKey(e,t){return `${c(e)}:${c(t)}`}addToRecoveryRegistry(e,t,r,i){let n=this.deps.recoveryRegistry.get(e);n||(n={service:t,characteristic:r,callbacks:new Set},this.deps.recoveryRegistry.set(e,n)),n.callbacks.add(i);}removeFromRecoveryRegistry(e,t){let r=this.deps.recoveryRegistry.get(e);r&&(r.callbacks.delete(t),r.callbacks.size===0&&this.deps.recoveryRegistry.delete(e));}};y.DEFAULT_NOTIFICATION_QUEUE_SIZE=256;var R=y;var b=class{constructor(e,t={}){this.server=null;this.primaryServicesCache=null;this.serviceCache=new Map;this.charCache=new Map;this.recoveryRegistry=new Map;this.disconnectListeners=new Set;this.reconnectedListeners=new Set;this.queueOverflowListeners=new Set;this.subscriptionLostListeners=new Set;this.errorListeners=new Set;this.reconnectGate=null;this.intentionalDisconnect=false;this.lastDisconnectReason=null;this.autoReconnectConfig=null;this.autoReconnectAbort=null;this.id=e.id,this.name=e.name??null,this.raw=e,this.hooks=t,this.writeChunker=new L({getCharacteristic:(r,i)=>this.getCharacteristic(r,i),emitError:(r,i)=>this.emitError(r,i),validateTimeoutMs:r=>this.validateTimeoutMs(r),withOptionalTimeout:(r,i,n)=>this.withOptionalTimeout(r,i,n),isConnected:()=>this.connected,getTransport:()=>this.raw.gatt}),this.notificationManager=new R({getCharacteristic:(r,i)=>this.getCharacteristic(r,i),emitError:(r,i)=>this.emitError(r,i),emitSubscriptionLost:r=>this.emitSubscriptionLost(r),emitQueueOverflow:r=>this.emitQueueOverflow(r),recoveryRegistry:this.recoveryRegistry,charCache:this.charCache,getReconnectGate:()=>this.reconnectGate,isIntentionalDisconnect:()=>this.intentionalDisconnect}),typeof e.addEventListener=="function"&&e.addEventListener("gattserverdisconnected",()=>{this.handleDisconnect();});}get connected(){return this.server?.connected??false}async connect(e){if(this.connected)return;e?.autoReconnect&&(this.autoReconnectConfig=e.autoReconnect===true?{}:e.autoReconnect),this.hooks.beforeConnect?.(this);let t=this.raw.gatt;if(!t)throw new o("GATT_OPERATION_FAILED","Device has no GATT server");let r=this.reconnectGate;try{this.server=await t.connect(),this.lastDisconnectReason=null,await this.notificationManager.recoverSubscriptions();for(let i of this.reconnectedListeners)try{i();}catch(n){this.emitError(o.from(n),{operation:"device.reconnected-listener"});}this.intentionalDisconnect=!1,this.hooks.onConnectionChange?.(this);}catch(i){throw this.server?.connected||(this.server=null,this.hooks.onConnectionChange?.(this)),o.from(i)}finally{this.reconnectGate===r&&(this.reconnectGate=null),r?.resolve();}}disconnect(){this.intentionalDisconnect=true,this.lastDisconnectReason="intentional",this.autoReconnectConfig=null,this.autoReconnectAbort?.abort(),this.autoReconnectAbort=null,this.writeChunker.abortInFlightWrites(),this.notificationManager.cleanupSubscriptions(),this.recoveryRegistry.clear(),this.reconnectGate&&(this.reconnectGate.resolve(),this.reconnectGate=null),this.server?.disconnect(),this.server=null,this.primaryServicesCache=null,this.hooks.onConnectionChange?.(this);}async connectWithRetry(e={}){await A(async()=>{await this.connect();},e);}async read(e,t,r,i){let n=typeof r=="function"?r:void 0,a=typeof r=="function"?i:r,c=this.validateTimeoutMs(a?.timeoutMs),s=await this.getCharacteristic(e,t);try{let l=await this.withOptionalTimeout(s.readValue(),c,"Read timed out");return n?await n(l):l}catch(l){throw o.from(l)}}async write(e,t,r,i){return this.writeChunker.write(e,t,r,i)}async writeFragmented(e,t,r,i){return this.writeChunker.writeFragmented(e,t,r,i)}async writeLarge(e,t,r,i){return this.writeChunker.writeLarge(e,t,r,i)}async writeWithoutResponse(e,t,r,i){return this.writeChunker.writeWithoutResponse(e,t,r,i)}async getWriteLimits(){return this.writeChunker.getWriteLimits()}async getMtu(){return (await this.getWriteLimits()).mtu}async writeAuto(e,t,r,i){return this.writeChunker.writeAuto(e,t,r,i)}subscribe(e,t,r,i){return this.notificationManager.subscribe(e,t,r,i)}async subscribeAsync(e,t,r,i){return this.notificationManager.subscribeAsync(e,t,r,i)}notifications(e,t,r){return this.notificationManager.notifications(e,t,r)}async watchAdvertisements(){if(typeof this.raw.watchAdvertisements!="function")throw new o("GATT_OPERATION_FAILED","watchAdvertisements is not supported on this device");await this.raw.watchAdvertisements();}async unwatchAdvertisements(){let e=this.raw;if(typeof e.unwatchAdvertisements!="function")throw new o("GATT_OPERATION_FAILED","unwatchAdvertisements is not supported on this device");await e.unwatchAdvertisements();}async forget(){if(typeof this.raw.forget!="function")throw new o("GATT_OPERATION_FAILED","forget is not supported on this device");await this.raw.forget();}async getPrimaryServices(){if(!this.connected)throw new o("DEVICE_DISCONNECTED");if(this.primaryServicesCache)return this.primaryServicesCache;try{let t=(await this.server.getPrimaryServices()).map(r=>{let i=this.serviceCache.get(r.uuid)??r;return this.serviceCache.set(r.uuid,i),i});return this.primaryServicesCache=t,t}catch(e){throw o.from(e)}}async getEffectiveMtu(){let e=await this.getWriteLimits();return typeof e.mtu=="number"&&e.mtu>0?e.mtu:typeof e.withResponse=="number"&&e.withResponse>0?e.withResponse+3:typeof e.withoutResponse=="number"&&e.withoutResponse>0?e.withoutResponse+3:23}getLastDisconnectReason(){return this.lastDisconnectReason}getActiveSubscriptions(){let e=this.notificationManager.getNotificationStates();return [...new Set([...e.keys(),...this.recoveryRegistry.keys()])].map(r=>{let i=e.get(r),n=this.recoveryRegistry.get(r),[a,c]=r.split(":");return {service:a,characteristic:c,callbackCount:i?.callbacks.size??n?.callbacks.size??0,autoRecovering:n!==void 0,nativeActive:i?.nativeActive??false}})}on(e,t){return e==="disconnected"&&this.disconnectListeners.add(t),e==="reconnected"&&this.reconnectedListeners.add(t),e==="queue-overflow"&&this.queueOverflowListeners.add(t),e==="subscription-lost"&&this.subscriptionLostListeners.add(t),()=>{this.off(e,t);}}off(e,t){e==="disconnected"&&this.disconnectListeners.delete(t),e==="reconnected"&&this.reconnectedListeners.delete(t),e==="queue-overflow"&&this.queueOverflowListeners.delete(t),e==="subscription-lost"&&this.subscriptionLostListeners.delete(t);}addErrorListener(e){return this.errorListeners.add(e),()=>{this.removeErrorListener(e);}}removeErrorListener(e){this.errorListeners.delete(e);}handleDisconnect(){let e=this.intentionalDisconnect?"intentional":"unexpected";if(this.lastDisconnectReason=e,this.writeChunker.abortInFlightWrites(),this.notificationManager.suspendSubscriptions(),this.serviceCache.clear(),this.primaryServicesCache=null,this.charCache.clear(),this.server=null,this.hooks.onConnectionChange?.(this),this.recoveryRegistry.size>0){let t,r=new Promise(i=>{t=i;});this.reconnectGate={promise:r,resolve:t};}for(let t of this.disconnectListeners)try{t(e);}catch(r){this.emitError(o.from(r),{operation:"device.disconnected-listener"});}e==="unexpected"&&this.autoReconnectConfig&&this.startAutoReconnect(this.autoReconnectConfig);}startAutoReconnect(e){this.autoReconnectAbort?.abort();let t=new AbortController;this.autoReconnectAbort=t;let r=e.maxAttempts??1/0,i=e.initialDelayMs??1e3,n=e.maxDelayMs??3e4,a=e.backoffMultiplier??2;(async()=>{let s=i;for(let l=1;l<=r;l++){if(t.signal.aborted||(await new Promise(h=>{let v=setTimeout(h,s);t.signal.addEventListener("abort",()=>{clearTimeout(v),h();},{once:true});}),t.signal.aborted))return;try{await this.connect();return}catch{s=Math.min(s*a,n);}}this.emitError(new o("CONNECTION_TIMEOUT",`Auto-reconnect failed after ${r} attempts`),{operation:"device.auto-reconnect"});})();}async withOptionalTimeout(e,t,r){if(t===void 0)return e;let i=null,n=new Promise((a,c)=>{i=setTimeout(()=>{c(new o("TIMEOUT",r));},t);});try{return await Promise.race([e,n])}finally{i!==null&&clearTimeout(i);}}validateTimeoutMs(e){if(e!==void 0){if(!Number.isFinite(e)||e<=0)throw new o("INVALID_PARAMETER",`Invalid timeoutMs: ${e}. Must be a positive number.`);return e}}fanout(e,t,r){for(let i of e)try{i(t);}catch(n){this.emitError(o.from(n),{operation:r});}}emitQueueOverflow(e){this.fanout(this.queueOverflowListeners,e,"device.queue-overflow-listener");}emitSubscriptionLost(e){this.fanout(this.subscriptionLostListeners,e,"device.subscription-lost-listener");}emitError(e,t){for(let r of this.errorListeners)try{r(e,t);}catch{}}async getCharacteristic(e,t){if(!this.connected)throw new o("DEVICE_DISCONNECTED");let r=this.charKey(e,t),i=this.charCache.get(r);if(i)return i;let n=await this.getService(e),a=c(t);try{let c=await n.getCharacteristic(a);return this.charCache.set(r,c),c}catch(c){throw o.from(c)}}async getService(e){let t=c(e);if(this.primaryServicesCache){let i=this.primaryServicesCache.find(n=>n.uuid===t);if(i)return i}let r=this.serviceCache.get(t);if(r)return r;try{let i=await this.server.getPrimaryService(t);return this.serviceCache.set(t,i),i}catch(i){throw o.from(i)}}charKey(e,t){return `${c(e)}:${c(t)}`}};var S=class{constructor(e){this.errorFactory=e;}unsupported(){throw this.errorFactory()}requestPermission(){this.unsupported();}requestBackgroundConnection(e){this.unsupported();}registerCharacteristicNotifications(e){this.unsupported();}registerBeaconScanning(e){this.unsupported();}getRegistrations(){this.unsupported();}unregister(e){this.unsupported();}update(e,t){this.unsupported();}connect(e){return this.requestBackgroundConnection(e)}subscribe(e){return this.registerCharacteristicNotifications(e)}scan(e){return this.registerBeaconScanning(e)}list(){return this.getRegistrations()}destroy(){}},O=class extends EventTarget{constructor(t){super();this.onwriterequest=null;this.onsubscriptionchange=null;this.onconnectionstatechange=null;this.onadvertisingstatechange=null;this.onnotificationready=null;this.errorFactory=t;}get advertising(){return false}unsupported(){throw this.errorFactory()}advertise(t){this.unsupported();}addService(t){this.unsupported();}registerService(t){return this.addService(t)}startAdvertising(t){return this.advertise(t)}stopAdvertising(){this.unsupported();}send(t){this.unsupported();}sendNotification(t){return this.send(t)}destroy(){}},I=class{constructor(e){this.devices=new Map;this.platform=e?.platform??a(),this.maxConnections=this.normalizeMaxConnections(e?.maxConnections),this.bluetooth=this.platform!=="unsupported"?b$1():null,this.runtimeBluetooth=this.bluetooth,this.isSupported=this.bluetooth!==null,this.unsupportedFeatureErrorFactory=()=>this.platform==="unsupported"?new o("BLUETOOTH_UNAVAILABLE"):new o("GATT_OPERATION_FAILED","This WebBLE feature requires the iOS Safari WebBLE extension runtime."),this.unsupportedBackgroundSync=new S(this.unsupportedFeatureErrorFactory),this.unsupportedPeripheral=new O(this.unsupportedFeatureErrorFactory);}get backgroundSync(){return this.runtimeBluetooth?.backgroundSync??this.unsupportedBackgroundSync}get peripheral(){return this.runtimeBluetooth?.peripheral??this.unsupportedPeripheral}async requestDevice(e){if(!this.bluetooth)throw new o("BLUETOOTH_UNAVAILABLE");try{let t=await this.bluetooth.requestDevice(this.normalizeRequestDeviceOptions(e)??{acceptAllDevices:!0});return this.wrapDevice(t)}catch(t){throw o.from(t,"DEVICE_NOT_FOUND")}}async getDevices(){if(!this.bluetooth)throw new o("BLUETOOTH_UNAVAILABLE");let e=this.bluetooth;if(typeof e.getDevices!="function")return [];try{return (await e.getDevices()).map(r=>this.wrapDevice(r))}catch(t){throw o.from(t)}}async getAvailability(){if(!this.bluetooth)return false;try{return await this.bluetooth.getAvailability()}catch{return false}}async requestLEScan(e={acceptAllAdvertisements:true}){if(!this.bluetooth)throw new o("BLUETOOTH_UNAVAILABLE");let t=this.bluetooth;if(typeof t.requestLEScan!="function")return null;try{return await t.requestLEScan(e)}catch(r){throw o.from(r)}}normalizeRequestDeviceOptions(e){if(!e)return;let t=i=>{if(i)return i.map(n=>c(n))},r={};return e.acceptAllDevices!==void 0&&(r.acceptAllDevices=e.acceptAllDevices),e.optionalManufacturerData!==void 0&&(r.optionalManufacturerData=e.optionalManufacturerData),e.filters&&(r.filters=e.filters.map(i=>({...i,services:t(i.services)}))),e.exclusionFilters&&(r.exclusionFilters=e.exclusionFilters.map(i=>({...i,services:t(i.services)}))),e.optionalServices&&(r.optionalServices=t(e.optionalServices)),r}normalizeMaxConnections(e){if(e===void 0)return null;if(!Number.isInteger(e)||e<=0)throw new o("INVALID_PARAMETER",`Invalid maxConnections: ${e}. Must be a positive integer.`);return e}wrapDevice(e){let t=this.devices.get(e.id);if(t)return t;let r=new b(e,{beforeConnect:i=>{this.assertConnectionCapacity(i);},onConnectionChange:i=>{this.devices.set(i.id,i);}});return this.devices.set(e.id,r),r}assertConnectionCapacity(e){if(this.maxConnections===null||(this.devices.set(e.id,e),e.connected))return;let t=[...this.devices.values()].filter(r=>r.connected).length;if(t>=this.maxConnections)throw new o("CONNECTION_LIMIT_REACHED",`Connection limit reached (${t}/${this.maxConnections}). Disconnect another device or increase maxConnections before connecting ${e.name??e.id}.`,{retryAfterMs:1e3})}};function U(u,e=0){return u.getUint8(e)}function F(u,e=0){return u.getUint16(e,true)}function x(u,e=0){return u.getUint16(e,false)}function V(u,e=0){return u.getInt16(e,true)}function G(u,e=0){return u.getUint32(e,true)}function q(u,e=0){return u.getFloat32(e,true)}function z(u){return new TextDecoder().decode(u.buffer.slice(u.byteOffset,u.byteOffset+u.byteLength))}function $(u){return new Uint8Array(u.buffer.slice(u.byteOffset,u.byteOffset+u.byteLength))}export{I as WebBLE,b as WebBLEDevice,o as WebBLEError,$ as readBytes,q as readFloat32LE,V as readInt16LE,x as readUint16BE,F as readUint16LE,G as readUint32LE,U as readUint8,z as readUtf8,A as withRetry};//# sourceMappingURL=index.mjs.map
export{h as Beacio,f as BeacioDevice,b as BeacioError,g as DEFAULT_BEACIO_OPTIONS,a as DEFAULT_RETRY_OPTIONS,d as chunkSize,e as clampChunkSize,j as clampPercent,i as percent,r as readBytes,p as readFloat32LE,n as readInt16LE,m as readUint16BE,l as readUint16LE,o as readUint32LE,k as readUint8,q as readUtf8,c as withRetry}from'./chunk-GAX5WAKV.mjs';export{i as BLE_UUIDS,e as MockBleDevice,f as MockBluetooth,c as MockCharacteristic,d as MockDescriptor,a as MockGATTServer,b as MockService,g as createMockBluetooth,h as installMockBluetooth,j as mockDevices}from'./chunk-67S2RHE2.mjs';export{b as detectPlatform,c as getBluetoothAPI}from'./chunk-BSOWECSQ.mjs';export{a as SETUP_URL}from'./chunk-L7SIDO2A.mjs';export{a as BEACIO_EVENTS}from'./chunk-3BDZNBBD.mjs';export{g as BluetoothUUID,a as canonicalUUID,d as getCharacteristicName,f as getDescriptor,e as getDisplayName,c as getServiceName,b as resolveUUID}from'./chunk-33IHM3NV.mjs';//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map
{
"name": "@beacio/core",
"version": "1.0.0",
"description": "The only Web Bluetooth polyfill for Safari iOS — make navigator.bluetooth work on iPhone. Scan, connect, read/write BLE devices from any browser",
"version": "1.2.0",
"description": "The only Web Bluetooth polyfill for Safari iOS \u2014 make navigator.bluetooth work on iPhone. Scan, connect, read/write BLE devices from any browser",
"main": "dist/index.js",
"module": "dist/index.mjs",
"unpkg": "dist/browser.global.js",
"jsdelivr": "dist/browser.global.js",
"unpkg": "dist/browser-auto.global.js",
"jsdelivr": "dist/browser-auto.global.js",
"types": "dist/index.d.ts",

@@ -21,2 +21,10 @@ "exports": {

},
"./detect": {
"types": "./dist/detect/index.d.ts",
"import": "./dist/detect/index.mjs",
"require": "./dist/detect/index.js"
},
"./browser-auto": {
"default": "./dist/browser-auto.global.js"
},
"./global": {

@@ -26,2 +34,42 @@ "types": "./dist/global.d.ts",

"require": "./dist/global.js"
},
"./profiles": {
"types": "./dist/profiles/index.d.ts",
"import": "./dist/profiles/index.mjs",
"require": "./dist/profiles/index.js"
},
"./profiles/heart-rate": {
"types": "./dist/profiles/heart-rate.d.ts",
"import": "./dist/profiles/heart-rate.mjs",
"require": "./dist/profiles/heart-rate.js"
},
"./profiles/battery": {
"types": "./dist/profiles/battery.d.ts",
"import": "./dist/profiles/battery.mjs",
"require": "./dist/profiles/battery.js"
},
"./profiles/device-info": {
"types": "./dist/profiles/device-info.d.ts",
"import": "./dist/profiles/device-info.mjs",
"require": "./dist/profiles/device-info.js"
},
"./profiles/nordic-uart": {
"types": "./dist/profiles/nordic-uart.d.ts",
"import": "./dist/profiles/nordic-uart.mjs",
"require": "./dist/profiles/nordic-uart.js"
},
"./profiles/serial-ffe0": {
"types": "./dist/profiles/serial-ffe0.d.ts",
"import": "./dist/profiles/serial-ffe0.mjs",
"require": "./dist/profiles/serial-ffe0.js"
},
"./experimental/profiles/storz-bickel": {
"types": "./dist/experimental/profiles/storz-bickel.d.ts",
"import": "./dist/experimental/profiles/storz-bickel.mjs",
"require": "./dist/experimental/profiles/storz-bickel.js"
},
"./testing": {
"types": "./dist/testing/index.d.ts",
"import": "./dist/testing/index.mjs",
"require": "./dist/testing/index.js"
}

@@ -31,3 +79,4 @@ },

"./dist/auto.mjs",
"./dist/auto.js"
"./dist/auto.js",
"./dist/browser-auto.global.js"
],

@@ -42,6 +91,7 @@ "files": [

"scripts": {
"build": "tsup && tsup --config tsup.browser.config.ts",
"build": "tsup && tsup --config tsup.browser.config.ts && tsup --config tsup.browser-auto.config.ts && node scripts/generate-integrity.mjs",
"dev": "tsup --watch",
"test": "jest",
"typecheck": "tsc --noEmit",
"integrity": "node scripts/generate-integrity.mjs",
"prepublishOnly": "npm run build && npm run typecheck"

@@ -54,3 +104,3 @@ },

"webbluetooth",
"webble",
"beacio",
"safari",

@@ -78,8 +128,8 @@ "sdk",

"type": "git",
"url": "https://github.com/wklm/ioswebble-sdk.git",
"url": "https://github.com/wklm/beacio-sdk.git",
"directory": "packages/core"
},
"homepage": "https://ioswebble.com/docs#core-package",
"homepage": "https://beacio.com/docs#core-package",
"bugs": {
"url": "https://github.com/wklm/ioswebble-sdk/issues"
"url": "https://github.com/wklm/beacio-sdk/issues"
},

@@ -91,2 +141,3 @@ "publishConfig": {

"@types/jest": "^30",
"@types/node": "^26.1.1",
"@types/web-bluetooth": "^0.0.21",

@@ -97,3 +148,4 @@ "jest": "^30",

"tsup": "^8.0.0",
"typescript": "^5.9.3"
"typescript": "npm:@typescript/typescript6@^6.0.2",
"typescript-native": "npm:typescript@^7.0.2"
},

@@ -100,0 +152,0 @@ "engines": {

+34
-34
<p align="center">
<a href="https://ioswebble.com"><img src="https://ioswebble.com/img/logo.png" alt="WebBLE" width="84" height="84"></a>
<a href="https://beacio.com"><img src="https://beacio.com/img/logo.png" alt="beacio" width="84" height="84"></a>
</p>

@@ -29,3 +29,3 @@

2. Add `import '@beacio/core/auto';` to the first browser entry file that runs in your app.
3. Make sure the WebBLE Safari extension is installed and enabled.
3. Make sure the beacio Safari extension is installed and enabled.
4. Call `requestDevice()` only from a direct user gesture such as a button click.

@@ -49,5 +49,5 @@

```typescript
import { WebBLE } from '@beacio/core';
import { beacio } from '@beacio/core';
const ble = new WebBLE();
const ble = new Beacio();
const device = await ble.requestDevice({

@@ -60,3 +60,3 @@ filters: [{ services: ['heart_rate'] }],

For direct browser-script usage, load the browser bundle from a CDN package root or `dist/browser.global.js`. It exposes the full core API as `window.WebBLECore`.
For direct browser-script usage, load the browser bundle from a CDN package root or `dist/browser.global.js`. It exposes the full core API as `window.BeacioCore`.

@@ -69,3 +69,3 @@ ## Selective imports & tree-shaking

// Full SDK (~4KB gzipped)
import { WebBLE, WebBLEDevice, WebBLEError } from '@beacio/core';
import { beacio, BeacioDevice, BeacioError } from '@beacio/core';

@@ -79,3 +79,3 @@ // Just UUID helpers (~1KB gzipped)

You do **not** need `@beacio/profiles` or `@beacio/react-sdk` for basic BLE operations. `@beacio/core` is fully self-contained.
You do **not** need `@beacio/core/profiles` or `@beacio/react-sdk` for basic BLE operations. `@beacio/core` is fully self-contained.

@@ -87,5 +87,5 @@ ## Scanning for devices

```typescript
import { WebBLE } from '@beacio/core';
import { beacio } from '@beacio/core';
const ble = new WebBLE();
const ble = new Beacio();
const device = await ble.requestDevice({

@@ -135,5 +135,5 @@ filters: [{ services: ['heart_rate'] }],

```typescript
import { WebBLE, WebBLEError } from '@beacio/core';
import { beacio, BeacioError } from '@beacio/core';
const ble = new WebBLE();
const ble = new Beacio();
try {

@@ -144,3 +144,3 @@ const device = await ble.requestDevice({

} catch (err) {
if (err instanceof WebBLEError) {
if (err instanceof BeacioError) {
switch (err.code) {

@@ -157,3 +157,3 @@ case 'USER_CANCELLED':

case 'EXTENSION_NOT_INSTALLED':
// iOS Safari: WebBLE extension not active
// iOS Safari: beacio extension not active
break;

@@ -166,3 +166,3 @@ }

> **iOS Safari note:** The WebBLE Safari extension must be installed and enabled under Settings > Apps > Safari > Extensions. Use `@beacio/detect` to auto-prompt users when the extension is missing.
> **iOS Safari note:** The beacio Safari extension must be installed and enabled under Settings > Apps > Safari > Extensions. Use the `@beacio/core/detect` banner to auto-prompt users when the extension is missing.

@@ -254,3 +254,3 @@ ## Connecting & GATT service access

```typescript
const ble = new WebBLE({ maxConnections: 2 });
const ble = new Beacio({ maxConnections: 2 });
```

@@ -327,3 +327,3 @@

Partial transfer failures throw `WebBLEError` with code `WRITE_INCOMPLETE` and retry metadata when available. Use `device.getWriteLimits()`, `device.getMtu()`, or `device.getEffectiveMtu()` when you need to choose chunk sizes explicitly.
Partial transfer failures throw `BeacioError` with code `WRITE_INCOMPLETE` and retry metadata when available. Use `device.getWriteLimits()`, `device.getMtu()`, or `device.getEffectiveMtu()` when you need to choose chunk sizes explicitly.

@@ -345,3 +345,3 @@ ### Retry utility

`withRetry()` automatically stops on non-retriable `WebBLEError`s and prefers `error.retryAfterMs` when the SDK can infer a safer retry delay.
`withRetry()` automatically stops on non-retriable `BeacioError`s and prefers `error.retryAfterMs` when the SDK can infer a safer retry delay.

@@ -351,5 +351,5 @@ ### Full lifecycle example

```typescript
import { WebBLE, WebBLEError } from '@beacio/core';
import { beacio, BeacioError } from '@beacio/core';
const ble = new WebBLE({ maxConnections: 2 });
const ble = new Beacio({ maxConnections: 2 });

@@ -359,3 +359,3 @@ // 1. Check availability

console.log('Web Bluetooth not available');
// On iOS Safari, suggest installing the WebBLE extension
// On iOS Safari, suggest installing the beacio extension
}

@@ -390,6 +390,6 @@

All SDK errors are `WebBLEError` instances with a typed `code` and a human-readable `suggestion`:
All SDK errors are `BeacioError` instances with a typed `code` and a human-readable `suggestion`:
```typescript
import { WebBLEError } from '@beacio/core';
import { BeacioError } from '@beacio/core';

@@ -400,3 +400,3 @@ try {

} catch (err) {
if (err instanceof WebBLEError) {
if (err instanceof BeacioError) {
console.log(err.code); // e.g. 'SERVICE_NOT_FOUND'

@@ -428,3 +428,3 @@ console.log(err.message); // Technical detail

| `SCAN_ALREADY_IN_PROGRESS` | Another scan is already running |
| `CONNECTION_LIMIT_REACHED` | The current `WebBLE` instance has already reached `maxConnections` |
| `CONNECTION_LIMIT_REACHED` | The current `beacio` instance has already reached `maxConnections` |
| `TIMEOUT` | Operation timed out |

@@ -435,9 +435,9 @@ | `WRITE_INCOMPLETE` | A multi-part or interrupted write transferred only part of the payload |

### `WebBLE`
### `beacio`
| Member | Description |
|--------|-------------|
| `new WebBLE(options?)` | Create SDK instance |
| `requestDevice(options?): Promise<WebBLEDevice>` | Scan and select a BLE device |
| `getDevices(): Promise<WebBLEDevice[]>` | Return already-granted devices when supported by the browser |
| `new Beacio(options?)` | Create SDK instance |
| `requestDevice(options?): Promise<BeacioDevice>` | Scan and select a BLE device |
| `getDevices(): Promise<BeacioDevice[]>` | Return already-granted devices when supported by the browser |
| `getAvailability(): Promise<boolean>` | Check if Bluetooth is available |

@@ -448,3 +448,3 @@ | `maxConnections: number \| null` | Optional SDK-managed connection pool limit |

### `WebBLEDevice`
### `BeacioDevice`

@@ -474,7 +474,7 @@ | Member | Description |

### `WebBLEError`
### `BeacioError`
| Member | Description |
|--------|-------------|
| `code: WebBLEErrorCode` | Typed error code (see table above) |
| `code: BeacioErrorCode` | Typed error code (see table above) |
| `message: string` | Error detail |

@@ -484,3 +484,3 @@ | `suggestion: string` | Human-readable recovery hint |

| `retryAfterMs?: number` | Suggested delay before retrying when known |
| `WebBLEError.from(error, fallbackCode)` | Wrap unknown errors |
| `BeacioError.from(error, fallbackCode)` | Wrap unknown errors |

@@ -495,3 +495,3 @@ ### Utility functions

| `detectPlatform(): Platform` | Returns `'ios-safari'`, `'chrome'`, or `'unsupported'` |
| `withRetry(fn, options): Promise<T>` | Retry a BLE operation using `WebBLEError` retry metadata |
| `withRetry(fn, options): Promise<T>` | Retry a BLE operation using `BeacioError` retry metadata |

@@ -506,3 +506,3 @@ ## AI agent integration

Full SDK reference for LLM context: <https://ioswebble.com/llms-full.txt>
Full SDK reference for LLM context: <https://beacio.com/llms-full.txt>

@@ -509,0 +509,0 @@ ## Two scopes

export{b as removeInstallBanner,a as showInstallBanner}from'./chunk-VJVS2CEP.mjs';//# sourceMappingURL=banner-DT5I7URC-ZD7P2QSM.mjs.map
//# sourceMappingURL=banner-DT5I7URC-ZD7P2QSM.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"banner-DT5I7URC-ZD7P2QSM.mjs"}
function j(){if(typeof navigator>"u")return "unsupported";let e=navigator;return e.webble?.__webble===true?"safari-extension":e.bluetooth&&!e.bluetooth.__webbleCDNStub?"native":"unsupported"}function N(){if(typeof navigator>"u")return null;let e=navigator;return e.webble?.__webble===true?e.webble:e.bluetooth&&!e.bluetooth.__webbleCDNStub?e.bluetooth:null}var d="-0000-1000-8000-00805f9b34fb",A={generic_access:6144,generic_attribute:6145,immediate_alert:6146,link_loss:6147,tx_power:6148,current_time:6149,reference_time_update:6150,next_dst_change:6151,glucose:6152,health_thermometer:6153,device_information:6154,heart_rate:6157,phone_alert_status:6158,battery_service:6159,blood_pressure:6160,alert_notification:6161,human_interface_device:6162,scan_parameters:6163,running_speed_and_cadence:6164,automation_io:6165,cycling_speed_and_cadence:6166,cycling_power:6168,location_and_navigation:6169,environmental_sensing:6170,body_composition:6171,user_data:6172,weight_scale:6173,bond_management:6174,continuous_glucose_monitoring:6175,internet_protocol_support:6176,indoor_positioning:6177,pulse_oximeter:6178,http_proxy:6179,transport_discovery:6180,object_transfer:6181,fitness_machine:6182,mesh_provisioning:6183,mesh_proxy:6184,reconnection_configuration:6185},l={"gap.device_name":10752,"gap.appearance":10753,"gap.peripheral_privacy_flag":10754,"gap.reconnection_address":10755,"gap.peripheral_preferred_connection_parameters":10756,"gatt.service_changed":10757,alert_level:10758,tx_power_level:10759,date_time:10760,day_of_week:10761,day_date_time:10762,exact_time_100:10763,exact_time_256:10764,dst_offset:10765,time_zone:10766,local_time_information:10767,secondary_time_zone:10768,time_with_dst:10769,time_accuracy:10770,time_source:10771,reference_time_information:10772,time_broadcast:10773,time_update_control_point:10774,time_update_state:10775,glucose_measurement:10776,battery_level:10777,battery_power_state:10778,battery_level_state:10779,temperature_measurement:10780,temperature_type:10781,intermediate_temperature:10782,temperature_celsius:10783,temperature_fahrenheit:10784,measurement_interval:10785,boot_keyboard_input_report:10786,system_id:10787,model_number_string:10788,serial_number_string:10789,firmware_revision_string:10790,hardware_revision_string:10791,software_revision_string:10792,manufacturer_name_string:10793,"ieee_11073-20601_regulatory_certification_data_list":10794,current_time:10795,magnetic_declination:10796,position_2d:10799,position_3d:10800,scan_refresh:10801,boot_keyboard_output_report:10802,boot_mouse_input_report:10803,glucose_measurement_context:10804,blood_pressure_measurement:10805,intermediate_cuff_pressure:10806,heart_rate_measurement:10807,body_sensor_location:10808,heart_rate_control_point:10809,removable:10810,service_required:10811,scientific_temperature_celsius:10812,string:10813,network_availability:10814,alert_status:10815,ringer_control_point:10816,ringer_setting:10817,alert_category_id_bit_mask:10818,alert_category_id:10819,alert_notification_control_point:10820,unread_alert_status:10821,new_alert:10822,supported_new_alert_category:10823,supported_unread_alert_category:10824,blood_pressure_feature:10825,hid_information:10826,report_map:10827,hid_control_point:10828,report:10829,protocol_mode:10830,scan_interval_window:10831,pnp_id:10832,glucose_feature:10833,record_access_control_point:10834,rsc_measurement:10835,rsc_feature:10836,sc_control_point:10837,digital:10838,digital_output:10839,analog:10840,analog_output:10841,aggregate:10842,csc_measurement:10843,csc_feature:10844,sensor_location:10845,plx_spot_check_measurement:10846,plx_continuous_measurement:10847,plx_features:10848,pulse_oximetry_control_point:10850,cycling_power_measurement:10851,cycling_power_vector:10852,cycling_power_feature:10853,cycling_power_control_point:10854,location_and_speed:10855,navigation:10856,position_quality:10857,ln_feature:10858,ln_control_point:10859,elevation:10860,pressure:10861,temperature:10862,humidity:10863,true_wind_speed:10864,true_wind_direction:10865,apparent_wind_speed:10866,apparent_wind_direction:10867,gust_factor:10868,pollen_concentration:10869,uv_index:10870,irradiance:10871,rainfall:10872,wind_chill:10873,heat_index:10874,dew_point:10875,descriptor_value_changed:10877,aerobic_heart_rate_lower_limit:10878,aerobic_threshold:10879,age:10880,anaerobic_heart_rate_lower_limit:10881,anaerobic_heart_rate_upper_limit:10882,anaerobic_threshold:10883,aerobic_heart_rate_upper_limit:10884,date_of_birth:10885,date_of_threshold_assessment:10886,email_address:10887,fat_burn_heart_rate_lower_limit:10888,fat_burn_heart_rate_upper_limit:10889,first_name:10890,five_zone_heart_rate_limits:10891,gender:10892,heart_rate_max:10893,height:10894,hip_circumference:10895,last_name:10896,maximum_recommended_heart_rate:10897,resting_heart_rate:10898,sport_type_for_aerobic_and_anaerobic_thresholds:10899,three_zone_heart_rate_limits:10900,two_zone_heart_rate_limit:10901,vo2_max:10902,waist_circumference:10903,weight:10904,database_change_increment:10905,user_index:10906,body_composition_feature:10907,body_composition_measurement:10908,weight_measurement:10909,weight_scale_feature:10910,user_control_point:10911,magnetic_flux_density_2d:10912,magnetic_flux_density_3d:10913,language:10914,barometric_pressure_trend:10915,bond_management_control_point:10916,bond_management_feature:10917,"gap.central_address_resolution_support":10918,cgm_measurement:10919,cgm_feature:10920,cgm_status:10921,cgm_session_start_time:10922,cgm_session_run_time:10923,cgm_specific_ops_control_point:10924,indoor_positioning_configuration:10925,latitude:10926,longitude:10927,local_north_coordinate:10928,"local_east_coordinate.xml":10929,floor_number:10930,altitude:10931,uncertainty:10932,location_name:10933,uri:10934,http_headers:10935,http_status_code:10936,http_entity_body:10937,http_control_point:10938,https_security:10939,tds_control_point:10940,ots_feature:10941,object_name:10942,object_type:10943,object_size:10944,object_first_created:10945,object_last_modified:10946,object_id:10947,object_properties:10948,object_action_control_point:10949,object_list_control_point:10950,object_list_filter:10951,object_changed:10952,resolvable_private_address_only:10953,fitness_machine_feature:10956,treadmill_data:10957,cross_trainer_data:10958,step_climber_data:10959,stair_climber_data:10960,rower_data:10961,indoor_bike_data:10962,training_status:10963,supported_speed_range:10964,supported_inclination_range:10965,supported_resistance_level_range:10966,supported_heart_rate_range:10967,supported_power_range:10968,fitness_machine_control_point:10969,fitness_machine_status:10970,date_utc:10989};function s(e){return e.toString(16).padStart(8,"0")+d}var x,u;function v(){if(!x){x=new Map;for(let[e,r]of Object.entries(A)){let t=s(r);x.has(t)||x.set(t,e);}}return x}function C(){if(!u){u=new Map;for(let[e,r]of Object.entries(l)){let t=s(r);u.has(t)||u.set(t,e);}}return u}var b=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,D=/^[0-9a-f]{4}$/,B=/^[0-9a-f]{8}$/;function F(e,r){let t=e.length,i=r.length,n=Array.from({length:i+1},(o,_)=>_);for(let o=1;o<=t;o++){let _=o-1;n[0]=o;for(let a=1;a<=i;a++){let c=n[a];n[a]=e[o-1]===r[a-1]?_:1+Math.min(_,n[a],n[a-1]),_=c;}}return n[i]}function E(e){return e.trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/[-.\s]+/g,"_").replace(/_+/g,"_").replace(/^_+|_+$/g,"").toLowerCase()}function h(e,r){let t=r[e];if(t!==void 0)return t;let i=e.replace(/[._-]/g,"");if(i){for(let[n,o]of Object.entries(r))if(n.replace(/[._-]/g,"")===i)return o}}function $(e){if(typeof e=="number"){if(!Number.isInteger(e)||e<0||e>4294967295)throw new TypeError(`Invalid UUID integer: ${e}. Must be a 16-bit or 32-bit unsigned integer.`);return s(e)}let r=e.trim(),t=r.toLowerCase();if(b.test(t))return t;if(D.test(t))return "0000"+t+d;if(B.test(t))return t+d;let i=A[t]??l[t];if(i!==void 0)return s(i);let n=E(r),o=h(n,A);if(o!==void 0)return s(o);let _=h(n,l);if(_!==void 0)return s(_);let a=Object.keys(A).concat(Object.keys(l)),c,m=4;for(let p of a){let f=F(n,p);f<m&&(m=f,c=p);}!c&&n.length>=4&&(c=a.find(p=>p.startsWith(n)));let w=c?` Did you mean "${c}"?`:"";throw new TypeError(`Invalid UUID: "${e}". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${w}`)}function M(e){return v().get(e.toLowerCase())}function z(e){return C().get(e.toLowerCase())}function R(e){let r=e.replace(/^(gap|gatt)\./,"");return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(r)?r.split("_").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" "):e}var I={"gatt.characteristic_extended_properties":10496,"gatt.characteristic_user_description":10497,"gatt.client_characteristic_configuration":10498,"gatt.server_characteristic_configuration":10499,"gatt.characteristic_presentation_format":10500,"gatt.characteristic_aggregate_format":10501,valid_range:10502,external_report_reference:10503,report_reference:10504,number_of_digitals:10505,value_trigger_setting:10506,es_configuration:10507,es_measurement:10508,es_trigger_setting:10509,time_trigger_setting:10510};function y(e){let r=Number(e);if(!Number.isFinite(r))throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);let t=Math.trunc(r);if(t<0||t>4294967295)throw new TypeError(`Failed to execute 'canonicalUUID' on 'BluetoothUUID': Value is not a valid unsigned long: ${e}`);return s(t+0)}function g(e,r,t){if(typeof e=="number")return y(e);if(b.test(e))return e;let i=r[e.toLowerCase()];if(i!==void 0)return s(i);throw new TypeError(`Failed to execute '${t}' on 'BluetoothUUID': Invalid UUID or registry name: "${e}"`)}function U(e){return g(e,I,"getDescriptor")}var k={canonicalUUID:y,getService:e=>g(e,A,"getService"),getCharacteristic:e=>g(e,l,"getCharacteristic"),getDescriptor:U};export{j as a,N as b,$ as c,M as d,z as e,R as f,y as g,U as h,k as i};//# sourceMappingURL=chunk-FKTUFPPD.mjs.map
//# sourceMappingURL=chunk-FKTUFPPD.mjs.map
{"version":3,"sources":["../src/platform.ts","../src/uuid.ts"],"names":["detectPlatform","nav","getBluetoothAPI","BASE_SUFFIX","SERVICES","CHARACTERISTICS","hexToUUID","hex","serviceNameMap","charNameMap","getServiceNameMap","name","uuid","getCharNameMap","UUID_RE","HEX4_RE","HEX8_RE","levenshtein","a","b","m","n","row","_","i","prev","j","tmp","normalizeBluetoothName","input","lookupNamedUUID","table","directMatch","compactName","candidateName","candidateHex","resolveUUID","nameOrUUID","raw","lower","exactAlias","normalizedName","serviceHex","charHex","allNames","closest","bestDist","d","hint","getServiceName","getCharacteristicName","getDisplayName","bare","word","DESCRIPTORS","canonicalUUID","alias","converted","truncated","resolveUUIDName","getter","getDescriptor","BluetoothUUID"],"mappings":"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","file":"chunk-FKTUFPPD.mjs","sourcesContent":["import type { Platform } from './types';\n\n/**\n * Detect the current Web Bluetooth platform by probing `navigator`.\n *\n * **Detection order:**\n * 1. Safari extension -- `navigator.webble?.__webble === true`\n * 2. Native Web Bluetooth -- `navigator.bluetooth` exists (excluding CDN stubs)\n * 3. Unsupported -- No Web Bluetooth capability\n *\n * @returns The detected {@link Platform} value.\n *\n * @see {@link getBluetoothAPI} for getting the actual API object\n */\nexport function detectPlatform(): Platform {\n if (typeof navigator === 'undefined') return 'unsupported';\n\n // Safari extension: navigator.webble with sentinel\n const nav = navigator as any;\n if (nav.webble?.__webble === true) return 'safari-extension';\n\n // Native Web Bluetooth (Chrome, Edge, etc.) — exclude CDN stubs\n if (nav.bluetooth && !nav.bluetooth.__webbleCDNStub) return 'native';\n\n return 'unsupported';\n}\n\n/**\n * Get the `Bluetooth` API object for the current platform.\n *\n * Returns `navigator.webble` for the Safari extension, `navigator.bluetooth` for\n * native Web Bluetooth, or `null` if unsupported. CDN stubs (from `@beacio/detect`)\n * are excluded.\n *\n * @returns The platform's `Bluetooth` API object, or `null` if unavailable.\n *\n * @see {@link detectPlatform} for identifying the platform without getting the API\n */\nexport function getBluetoothAPI(): Bluetooth | null {\n if (typeof navigator === 'undefined') return null;\n\n const nav = navigator as any;\n\n // Safari extension provides full API on navigator.webble\n if (nav.webble?.__webble === true) return nav.webble as Bluetooth;\n\n // Native Web Bluetooth\n if (nav.bluetooth && !nav.bluetooth.__webbleCDNStub) return nav.bluetooth;\n\n return null;\n}\n","const BASE_SUFFIX = '-0000-1000-8000-00805f9b34fb';\n\n// GATT assigned numbers (Web Bluetooth §7.2), generated from the vendored\n// WebBluetoothCG registries — the single source of truth shared with\n// `Shared (Extension)/UUIDResolver.swift` and `src/cdn/webble.ts`.\n// Regenerate with: node scripts/registries/generate.mjs\n\n// registries:begin core-services\n// GENERATED from registries/gatt_assigned_services.txt by scripts/registries/generate.mjs — do not edit by hand.\nconst SERVICES: Record<string, number> = {\n 'generic_access': 0x1800,\n 'generic_attribute': 0x1801,\n 'immediate_alert': 0x1802,\n 'link_loss': 0x1803,\n 'tx_power': 0x1804,\n 'current_time': 0x1805,\n 'reference_time_update': 0x1806,\n 'next_dst_change': 0x1807,\n 'glucose': 0x1808,\n 'health_thermometer': 0x1809,\n 'device_information': 0x180A,\n 'heart_rate': 0x180D,\n 'phone_alert_status': 0x180E,\n 'battery_service': 0x180F,\n 'blood_pressure': 0x1810,\n 'alert_notification': 0x1811,\n 'human_interface_device': 0x1812,\n 'scan_parameters': 0x1813,\n 'running_speed_and_cadence': 0x1814,\n 'automation_io': 0x1815,\n 'cycling_speed_and_cadence': 0x1816,\n 'cycling_power': 0x1818,\n 'location_and_navigation': 0x1819,\n 'environmental_sensing': 0x181A,\n 'body_composition': 0x181B,\n 'user_data': 0x181C,\n 'weight_scale': 0x181D,\n 'bond_management': 0x181E,\n 'continuous_glucose_monitoring': 0x181F,\n 'internet_protocol_support': 0x1820,\n 'indoor_positioning': 0x1821,\n 'pulse_oximeter': 0x1822,\n 'http_proxy': 0x1823,\n 'transport_discovery': 0x1824,\n 'object_transfer': 0x1825,\n 'fitness_machine': 0x1826,\n 'mesh_provisioning': 0x1827,\n 'mesh_proxy': 0x1828,\n 'reconnection_configuration': 0x1829,\n};\n// registries:end core-services\n\n// registries:begin core-characteristics\n// GENERATED from registries/gatt_assigned_characteristics.txt by scripts/registries/generate.mjs — do not edit by hand.\nconst CHARACTERISTICS: Record<string, number> = {\n 'gap.device_name': 0x2A00,\n 'gap.appearance': 0x2A01,\n 'gap.peripheral_privacy_flag': 0x2A02,\n 'gap.reconnection_address': 0x2A03,\n 'gap.peripheral_preferred_connection_parameters': 0x2A04,\n 'gatt.service_changed': 0x2A05,\n 'alert_level': 0x2A06,\n 'tx_power_level': 0x2A07,\n 'date_time': 0x2A08,\n 'day_of_week': 0x2A09,\n 'day_date_time': 0x2A0A,\n 'exact_time_100': 0x2A0B,\n 'exact_time_256': 0x2A0C,\n 'dst_offset': 0x2A0D,\n 'time_zone': 0x2A0E,\n 'local_time_information': 0x2A0F,\n 'secondary_time_zone': 0x2A10,\n 'time_with_dst': 0x2A11,\n 'time_accuracy': 0x2A12,\n 'time_source': 0x2A13,\n 'reference_time_information': 0x2A14,\n 'time_broadcast': 0x2A15,\n 'time_update_control_point': 0x2A16,\n 'time_update_state': 0x2A17,\n 'glucose_measurement': 0x2A18,\n 'battery_level': 0x2A19,\n 'battery_power_state': 0x2A1A,\n 'battery_level_state': 0x2A1B,\n 'temperature_measurement': 0x2A1C,\n 'temperature_type': 0x2A1D,\n 'intermediate_temperature': 0x2A1E,\n 'temperature_celsius': 0x2A1F,\n 'temperature_fahrenheit': 0x2A20,\n 'measurement_interval': 0x2A21,\n 'boot_keyboard_input_report': 0x2A22,\n 'system_id': 0x2A23,\n 'model_number_string': 0x2A24,\n 'serial_number_string': 0x2A25,\n 'firmware_revision_string': 0x2A26,\n 'hardware_revision_string': 0x2A27,\n 'software_revision_string': 0x2A28,\n 'manufacturer_name_string': 0x2A29,\n 'ieee_11073-20601_regulatory_certification_data_list': 0x2A2A,\n 'current_time': 0x2A2B,\n 'magnetic_declination': 0x2A2C,\n 'position_2d': 0x2A2F,\n 'position_3d': 0x2A30,\n 'scan_refresh': 0x2A31,\n 'boot_keyboard_output_report': 0x2A32,\n 'boot_mouse_input_report': 0x2A33,\n 'glucose_measurement_context': 0x2A34,\n 'blood_pressure_measurement': 0x2A35,\n 'intermediate_cuff_pressure': 0x2A36,\n 'heart_rate_measurement': 0x2A37,\n 'body_sensor_location': 0x2A38,\n 'heart_rate_control_point': 0x2A39,\n 'removable': 0x2A3A,\n 'service_required': 0x2A3B,\n 'scientific_temperature_celsius': 0x2A3C,\n 'string': 0x2A3D,\n 'network_availability': 0x2A3E,\n 'alert_status': 0x2A3F,\n 'ringer_control_point': 0x2A40,\n 'ringer_setting': 0x2A41,\n 'alert_category_id_bit_mask': 0x2A42,\n 'alert_category_id': 0x2A43,\n 'alert_notification_control_point': 0x2A44,\n 'unread_alert_status': 0x2A45,\n 'new_alert': 0x2A46,\n 'supported_new_alert_category': 0x2A47,\n 'supported_unread_alert_category': 0x2A48,\n 'blood_pressure_feature': 0x2A49,\n 'hid_information': 0x2A4A,\n 'report_map': 0x2A4B,\n 'hid_control_point': 0x2A4C,\n 'report': 0x2A4D,\n 'protocol_mode': 0x2A4E,\n 'scan_interval_window': 0x2A4F,\n 'pnp_id': 0x2A50,\n 'glucose_feature': 0x2A51,\n 'record_access_control_point': 0x2A52,\n 'rsc_measurement': 0x2A53,\n 'rsc_feature': 0x2A54,\n 'sc_control_point': 0x2A55,\n 'digital': 0x2A56,\n 'digital_output': 0x2A57,\n 'analog': 0x2A58,\n 'analog_output': 0x2A59,\n 'aggregate': 0x2A5A,\n 'csc_measurement': 0x2A5B,\n 'csc_feature': 0x2A5C,\n 'sensor_location': 0x2A5D,\n 'plx_spot_check_measurement': 0x2A5E,\n 'plx_continuous_measurement': 0x2A5F,\n 'plx_features': 0x2A60,\n 'pulse_oximetry_control_point': 0x2A62,\n 'cycling_power_measurement': 0x2A63,\n 'cycling_power_vector': 0x2A64,\n 'cycling_power_feature': 0x2A65,\n 'cycling_power_control_point': 0x2A66,\n 'location_and_speed': 0x2A67,\n 'navigation': 0x2A68,\n 'position_quality': 0x2A69,\n 'ln_feature': 0x2A6A,\n 'ln_control_point': 0x2A6B,\n 'elevation': 0x2A6C,\n 'pressure': 0x2A6D,\n 'temperature': 0x2A6E,\n 'humidity': 0x2A6F,\n 'true_wind_speed': 0x2A70,\n 'true_wind_direction': 0x2A71,\n 'apparent_wind_speed': 0x2A72,\n 'apparent_wind_direction': 0x2A73,\n 'gust_factor': 0x2A74,\n 'pollen_concentration': 0x2A75,\n 'uv_index': 0x2A76,\n 'irradiance': 0x2A77,\n 'rainfall': 0x2A78,\n 'wind_chill': 0x2A79,\n 'heat_index': 0x2A7A,\n 'dew_point': 0x2A7B,\n 'descriptor_value_changed': 0x2A7D,\n 'aerobic_heart_rate_lower_limit': 0x2A7E,\n 'aerobic_threshold': 0x2A7F,\n 'age': 0x2A80,\n 'anaerobic_heart_rate_lower_limit': 0x2A81,\n 'anaerobic_heart_rate_upper_limit': 0x2A82,\n 'anaerobic_threshold': 0x2A83,\n 'aerobic_heart_rate_upper_limit': 0x2A84,\n 'date_of_birth': 0x2A85,\n 'date_of_threshold_assessment': 0x2A86,\n 'email_address': 0x2A87,\n 'fat_burn_heart_rate_lower_limit': 0x2A88,\n 'fat_burn_heart_rate_upper_limit': 0x2A89,\n 'first_name': 0x2A8A,\n 'five_zone_heart_rate_limits': 0x2A8B,\n 'gender': 0x2A8C,\n 'heart_rate_max': 0x2A8D,\n 'height': 0x2A8E,\n 'hip_circumference': 0x2A8F,\n 'last_name': 0x2A90,\n 'maximum_recommended_heart_rate': 0x2A91,\n 'resting_heart_rate': 0x2A92,\n 'sport_type_for_aerobic_and_anaerobic_thresholds': 0x2A93,\n 'three_zone_heart_rate_limits': 0x2A94,\n 'two_zone_heart_rate_limit': 0x2A95,\n 'vo2_max': 0x2A96,\n 'waist_circumference': 0x2A97,\n 'weight': 0x2A98,\n 'database_change_increment': 0x2A99,\n 'user_index': 0x2A9A,\n 'body_composition_feature': 0x2A9B,\n 'body_composition_measurement': 0x2A9C,\n 'weight_measurement': 0x2A9D,\n 'weight_scale_feature': 0x2A9E,\n 'user_control_point': 0x2A9F,\n 'magnetic_flux_density_2d': 0x2AA0,\n 'magnetic_flux_density_3d': 0x2AA1,\n 'language': 0x2AA2,\n 'barometric_pressure_trend': 0x2AA3,\n 'bond_management_control_point': 0x2AA4,\n 'bond_management_feature': 0x2AA5,\n 'gap.central_address_resolution_support': 0x2AA6,\n 'cgm_measurement': 0x2AA7,\n 'cgm_feature': 0x2AA8,\n 'cgm_status': 0x2AA9,\n 'cgm_session_start_time': 0x2AAA,\n 'cgm_session_run_time': 0x2AAB,\n 'cgm_specific_ops_control_point': 0x2AAC,\n 'indoor_positioning_configuration': 0x2AAD,\n 'latitude': 0x2AAE,\n 'longitude': 0x2AAF,\n 'local_north_coordinate': 0x2AB0,\n 'local_east_coordinate.xml': 0x2AB1,\n 'floor_number': 0x2AB2,\n 'altitude': 0x2AB3,\n 'uncertainty': 0x2AB4,\n 'location_name': 0x2AB5,\n 'uri': 0x2AB6,\n 'http_headers': 0x2AB7,\n 'http_status_code': 0x2AB8,\n 'http_entity_body': 0x2AB9,\n 'http_control_point': 0x2ABA,\n 'https_security': 0x2ABB,\n 'tds_control_point': 0x2ABC,\n 'ots_feature': 0x2ABD,\n 'object_name': 0x2ABE,\n 'object_type': 0x2ABF,\n 'object_size': 0x2AC0,\n 'object_first_created': 0x2AC1,\n 'object_last_modified': 0x2AC2,\n 'object_id': 0x2AC3,\n 'object_properties': 0x2AC4,\n 'object_action_control_point': 0x2AC5,\n 'object_list_control_point': 0x2AC6,\n 'object_list_filter': 0x2AC7,\n 'object_changed': 0x2AC8,\n 'resolvable_private_address_only': 0x2AC9,\n 'fitness_machine_feature': 0x2ACC,\n 'treadmill_data': 0x2ACD,\n 'cross_trainer_data': 0x2ACE,\n 'step_climber_data': 0x2ACF,\n 'stair_climber_data': 0x2AD0,\n 'rower_data': 0x2AD1,\n 'indoor_bike_data': 0x2AD2,\n 'training_status': 0x2AD3,\n 'supported_speed_range': 0x2AD4,\n 'supported_inclination_range': 0x2AD5,\n 'supported_resistance_level_range': 0x2AD6,\n 'supported_heart_rate_range': 0x2AD7,\n 'supported_power_range': 0x2AD8,\n 'fitness_machine_control_point': 0x2AD9,\n 'fitness_machine_status': 0x2ADA,\n 'date_utc': 0x2AED,\n};\n// registries:end core-characteristics\n\nfunction hexToUUID(hex: number): string {\n return hex.toString(16).padStart(8, '0') + BASE_SUFFIX;\n}\n\n// Reverse maps for name lookups (built lazily)\nlet serviceNameMap: Map<string, string> | undefined;\nlet charNameMap: Map<string, string> | undefined;\n\nfunction getServiceNameMap(): Map<string, string> {\n if (!serviceNameMap) {\n serviceNameMap = new Map();\n // First definition wins, so a canonical name (e.g. generic_access) beats its\n // SIG abbreviation (gap) when multiple names map to the same UUID.\n for (const [name, hex] of Object.entries(SERVICES)) {\n const uuid = hexToUUID(hex);\n if (!serviceNameMap.has(uuid)) serviceNameMap.set(uuid, name);\n }\n }\n return serviceNameMap;\n}\n\nfunction getCharNameMap(): Map<string, string> {\n if (!charNameMap) {\n charNameMap = new Map();\n // First definition wins (canonical name beats any SIG abbreviation alias).\n for (const [name, hex] of Object.entries(CHARACTERISTICS)) {\n const uuid = hexToUUID(hex);\n if (!charNameMap.has(uuid)) charNameMap.set(uuid, name);\n }\n }\n return charNameMap;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\nconst HEX4_RE = /^[0-9a-f]{4}$/;\nconst HEX8_RE = /^[0-9a-f]{8}$/;\n\n/** Single-row Levenshtein distance — O(m·n) time, O(n) space. */\nfunction levenshtein(a: string, b: string): number {\n const m = a.length, n = b.length;\n const row = Array.from({ length: n + 1 }, (_, i) => i);\n for (let i = 1; i <= m; i++) {\n let prev = i - 1;\n row[0] = i;\n for (let j = 1; j <= n; j++) {\n const tmp = row[j];\n row[j] = a[i - 1] === b[j - 1]\n ? prev\n : 1 + Math.min(prev, row[j], row[j - 1]);\n prev = tmp;\n }\n }\n return row[n];\n}\n\nfunction normalizeBluetoothName(input: string): string {\n return input\n .trim()\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')\n .replace(/[-.\\s]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toLowerCase();\n}\n\nfunction lookupNamedUUID(name: string, table: Record<string, number>): number | undefined {\n const directMatch = table[name];\n if (directMatch !== undefined) return directMatch;\n\n // Registry names may contain dots and hyphens ('gap.device_name',\n // 'ieee_11073-20601_…'); compare with all separators stripped so normalized\n // camelCase/kebab-case inputs still match.\n const compactName = name.replace(/[._-]/g, '');\n if (!compactName) return undefined;\n\n for (const [candidateName, candidateHex] of Object.entries(table)) {\n if (candidateName.replace(/[._-]/g, '') === compactName) {\n return candidateHex;\n }\n }\n\n return undefined;\n}\n\n/**\n * Resolve a service/characteristic name, number, or short UUID to a full 128-bit UUID string.\n *\n * **Supported input formats:**\n * 1. **Named alias** -- Bluetooth SIG service or characteristic name (e.g. `'heart_rate'`, `'battery_level'`)\n * 2. **16-bit integer** -- Numeric service/characteristic ID (e.g. `0x180D`)\n * 3. **4-hex string** -- Short 16-bit hex (e.g. `'180d'`)\n * 4. **8-hex string** -- 32-bit hex (e.g. `'0000180d'`)\n * 5. **Full 128-bit UUID** -- Passed through unchanged (e.g. `'0000180d-0000-1000-8000-00805f9b34fb'`)\n *\n * **Fuzzy matching:** If the input looks like a name but does not match any known alias,\n * Levenshtein edit distance (threshold <= 3) is used to suggest corrections. Name\n * normalization converts camelCase/PascalCase to snake_case and replaces hyphens/dots/spaces\n * with underscores before matching.\n *\n * @param nameOrUUID - Service/characteristic name, hex string, numeric ID, or full UUID.\n * @returns Canonical lowercase 128-bit UUID string.\n *\n * @throws {TypeError} If a numeric input is out of the 32-bit unsigned range.\n * @throws {TypeError} If a string input is not a valid UUID format or known name (includes \"Did you mean?\" hint).\n *\n * @example\n * ```typescript\n * resolveUUID('heart_rate') // '0000180d-0000-1000-8000-00805f9b34fb'\n * resolveUUID('180d') // '0000180d-0000-1000-8000-00805f9b34fb'\n * resolveUUID(0x180D) // '0000180d-0000-1000-8000-00805f9b34fb'\n * resolveUUID('battery_level') // '00002a19-0000-1000-8000-00805f9b34fb'\n * resolveUUID('HeartRate') // '0000180d-...' (camelCase normalized)\n * resolveUUID('heart_rat') // throws Error: Did you mean \"heart_rate\"?\n * ```\n *\n * @see {@link getServiceName} for reverse lookup (UUID to name)\n * @see {@link getCharacteristicName} for reverse lookup (UUID to name)\n */\nexport function resolveUUID(nameOrUUID: string | number): string {\n // Numeric input: 16-bit or 32-bit Bluetooth UUID integer\n if (typeof nameOrUUID === 'number') {\n if (!Number.isInteger(nameOrUUID) || nameOrUUID < 0 || nameOrUUID > 0xFFFFFFFF) {\n throw new TypeError(`Invalid UUID integer: ${nameOrUUID}. Must be a 16-bit or 32-bit unsigned integer.`);\n }\n return hexToUUID(nameOrUUID);\n }\n\n const raw = nameOrUUID.trim();\n const lower = raw.toLowerCase();\n\n // Full 128-bit UUID\n if (UUID_RE.test(lower)) return lower;\n\n // 4-digit hex shorthand\n if (HEX4_RE.test(lower)) return '0000' + lower + BASE_SUFFIX;\n\n // 8-digit hex shorthand\n if (HEX8_RE.test(lower)) return lower + BASE_SUFFIX;\n\n // Exact registry-name match first — registry names may contain dots\n // ('gap.device_name') that name normalization would otherwise destroy.\n const exactAlias = SERVICES[lower] ?? CHARACTERISTICS[lower];\n if (exactAlias !== undefined) return hexToUUID(exactAlias);\n\n const normalizedName = normalizeBluetoothName(raw);\n\n // Named service\n const serviceHex = lookupNamedUUID(normalizedName, SERVICES);\n if (serviceHex !== undefined) return hexToUUID(serviceHex);\n\n // Named characteristic\n const charHex = lookupNamedUUID(normalizedName, CHARACTERISTICS);\n if (charHex !== undefined) return hexToUUID(charHex);\n\n // Reject strings that don't look like valid UUIDs or hex shorthand.\n // Likely a typo of a Bluetooth SIG name (e.g. \"heart_rat\" instead of \"heart_rate\").\n // AIDEV-NOTE: Uses Levenshtein distance (≤3) with prefix fallback to catch typos\n // beyond simple character-position mismatches (e.g. \"heartrate\" → \"heart_rate\").\n const allNames = Object.keys(SERVICES).concat(Object.keys(CHARACTERISTICS));\n\n // Levenshtein match — find the closest name within edit distance 3\n let closest: string | undefined;\n let bestDist = 4; // threshold + 1\n for (const name of allNames) {\n const d = levenshtein(normalizedName, name);\n if (d < bestDist) {\n bestDist = d;\n closest = name;\n }\n }\n\n // Prefix fallback — if no close Levenshtein match, check if input is a prefix\n // of a known name (minimum 4 chars to avoid overly broad matches).\n if (!closest && normalizedName.length >= 4) {\n closest = allNames.find((name) => name.startsWith(normalizedName));\n }\n\n const hint = closest ? ` Did you mean \"${closest}\"?` : '';\n // §7.1 ResolveUUIDName: \"Otherwise, throw a TypeError.\" (a real TypeError,\n // so `err instanceof TypeError` holds for spec-conformant callers).\n throw new TypeError(`Invalid UUID: \"${nameOrUUID}\". Expected a 128-bit UUID, 4/8-digit hex, or a known Bluetooth SIG name.${hint}`);\n}\n\n/**\n * Get the human-readable Bluetooth SIG service name for a UUID, if known.\n *\n * @param uuid - Full 128-bit UUID string (case-insensitive).\n * @returns Service name (e.g. `'heart_rate'`), or `undefined` if not a known SIG service.\n *\n * @see {@link resolveUUID} for the reverse operation (name to UUID)\n */\nexport function getServiceName(uuid: string): string | undefined {\n return getServiceNameMap().get(uuid.toLowerCase());\n}\n\n/**\n * Get the human-readable Bluetooth SIG characteristic name for a UUID, if known.\n *\n * @param uuid - Full 128-bit UUID string (case-insensitive).\n * @returns Characteristic name (e.g. `'heart_rate_measurement'`), or `undefined` if not a known SIG characteristic.\n *\n * @see {@link resolveUUID} for the reverse operation (name to UUID)\n */\nexport function getCharacteristicName(uuid: string): string | undefined {\n return getCharNameMap().get(uuid.toLowerCase());\n}\n\n/**\n * Format a Bluetooth SIG snake_case name (e.g. `'heart_rate'`) as Title Case\n * (e.g. `'Heart Rate'`) for display in a UI.\n *\n * Unknown inputs — anything that does not look like a snake_case SIG name, such\n * as a raw UUID string or hex shorthand — are returned unchanged so callers can\n * use the raw value as a fallback label.\n *\n * @param name - A snake_case SIG name, or a raw UUID/hex string.\n * @returns Title-cased name, or the input unchanged when it is not a SIG name.\n *\n * @example\n * ```typescript\n * getDisplayName('heart_rate') // 'Heart Rate'\n * getDisplayName('heart_rate_measurement') // 'Heart Rate Measurement'\n * getDisplayName('gap.device_name') // 'Device Name'\n * getDisplayName('0000180d-0000-1000-8000-00805f9b34fb') // (unchanged)\n * ```\n */\nexport function getDisplayName(name: string): string {\n // Registry names in the GAP/GATT namespaces are dot-prefixed\n // ('gap.device_name', 'gatt.client_characteristic_configuration'). The\n // prefix is registry plumbing, not part of the SIG human-readable name —\n // strip it before formatting so 0x2A00 displays as 'Device Name'.\n const bare = name.replace(/^(gap|gatt)\\./, '');\n // Raw UUIDs / hex shorthand are not SIG names — return them unchanged so the\n // caller can show the raw identifier as a fallback label.\n if (!/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(bare)) return name;\n return bare\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n// ---------------------------------------------------------------------------\n// BluetoothUUID — Web Bluetooth spec §4\n// https://webbluetoothcg.github.io/web-bluetooth/#bluetoothuuid\n//\n// Static methods to resolve service, characteristic, and descriptor\n// names/aliases to canonical 128-bit UUID strings.\n// ---------------------------------------------------------------------------\n\n// Descriptor name → 16-bit alias map (descriptors are not part of\n// SERVICES/CHARACTERISTICS). Keys use the registry dot form, e.g.\n// 'gatt.client_characteristic_configuration'.\n\n// registries:begin core-descriptors\n// GENERATED from registries/gatt_assigned_descriptors.txt by scripts/registries/generate.mjs — do not edit by hand.\nconst DESCRIPTORS: Record<string, number> = {\n 'gatt.characteristic_extended_properties': 0x2900,\n 'gatt.characteristic_user_description': 0x2901,\n 'gatt.client_characteristic_configuration': 0x2902,\n 'gatt.server_characteristic_configuration': 0x2903,\n 'gatt.characteristic_presentation_format': 0x2904,\n 'gatt.characteristic_aggregate_format': 0x2905,\n 'valid_range': 0x2906,\n 'external_report_reference': 0x2907,\n 'report_reference': 0x2908,\n 'number_of_digitals': 0x2909,\n 'value_trigger_setting': 0x290A,\n 'es_configuration': 0x290B,\n 'es_measurement': 0x290C,\n 'es_trigger_setting': 0x290D,\n 'time_trigger_setting': 0x290E,\n};\n// registries:end core-descriptors\n\n/**\n * Convert a 16-bit or 32-bit integer alias to a canonical 128-bit UUID string.\n * Implements `BluetoothUUID.canonicalUUID()` from the Web Bluetooth spec.\n *\n * Spec IDL: `static UUID canonicalUUID([EnforceRange] unsigned long alias)` —\n * the WebIDL `[EnforceRange]` conversion is ToNumber, then truncation, then a\n * range check. Fractional inputs like `2.5` therefore CONVERT (to `2`); only\n * NaN, ±Infinity, and values outside [0, 2^32 − 1] after truncation throw.\n *\n * @param alias - 16-bit or 32-bit unsigned integer (0 to 0xFFFFFFFF).\n * @returns Canonical lowercase 128-bit UUID string.\n * @throws {TypeError} If the alias fails [EnforceRange] unsigned long conversion.\n *\n * @example\n * ```typescript\n * canonicalUUID(0x180D) // '0000180d-0000-1000-8000-00805f9b34fb'\n * canonicalUUID(0x2A37) // '00002a37-0000-1000-8000-00805f9b34fb'\n * canonicalUUID(2.5) // '00000002-0000-1000-8000-00805f9b34fb'\n * ```\n *\n * @see {@link resolveUUID} for resolving names and hex strings\n */\nexport function canonicalUUID(alias: number): string {\n // [EnforceRange] unsigned long (WebIDL §3.2.4.9): ToNumber → reject\n // non-finite → truncate toward zero → reject out of [0, 2^32 − 1].\n const converted = Number(alias);\n if (!Number.isFinite(converted)) {\n throw new TypeError(\n `Failed to execute 'canonicalUUID' on 'BluetoothUUID': ` +\n `Value is not a valid unsigned long: ${alias}`\n );\n }\n const truncated = Math.trunc(converted);\n if (truncated < 0 || truncated > 0xFFFFFFFF) {\n throw new TypeError(\n `Failed to execute 'canonicalUUID' on 'BluetoothUUID': ` +\n `Value is not a valid unsigned long: ${alias}`\n );\n }\n // `+ 0` normalizes -0 (from truncating e.g. -0.5) to 0.\n return hexToUUID(truncated + 0);\n}\n\n/**\n * §7.1 ResolveUUIDName, scoped to a single GATT assigned-numbers table.\n * Names are table-scoped because the registries reuse names across categories\n * (`current_time` is service 0x1805 AND characteristic 0x2A2B), so\n * `BluetoothUUID.getService()` / `getCharacteristic()` / `getDescriptor()`\n * must each consult only their own table. Unknown names throw a TypeError\n * (spec: \"Otherwise, throw a TypeError\").\n *\n * Spec-exact strictness: a §7 valid UUID is LOWERCASE 128-bit only, so\n * uppercase UUID strings and bare 4/8-hex abbreviations are rejected here\n * (the lenient forms remain available on the SDK-level {@link resolveUUID}).\n * Name lookup is case-folded because the generated tables key the registry's\n * mixed-case spellings (e.g. `magnetic_flux_density_2D`) in lowercase.\n */\nfunction resolveUUIDName(\n name: string | number,\n table: Record<string, number>,\n getter: string,\n): string {\n if (typeof name === 'number') return canonicalUUID(name);\n if (UUID_RE.test(name)) return name;\n const alias = table[name.toLowerCase()];\n if (alias !== undefined) return hexToUUID(alias);\n throw new TypeError(\n `Failed to execute '${getter}' on 'BluetoothUUID': Invalid UUID or registry name: \"${name}\"`\n );\n}\n\n/**\n * Resolve a descriptor name or UUID alias to a canonical 128-bit UUID.\n * Implements `BluetoothUUID.getDescriptor()` from the Web Bluetooth spec\n * (§7.1 ResolveUUIDName against GATT assigned descriptors only): accepts a\n * registry descriptor name in its registry dot form\n * (e.g. `'gatt.client_characteristic_configuration'`), an integer alias, or\n * a valid lowercase 128-bit UUID. Anything else throws a TypeError.\n *\n * @param name - Descriptor name, 16/32-bit integer alias, or full UUID string.\n * @returns Canonical 128-bit UUID string.\n * @throws {TypeError} For unknown names, bare hex shorthand, or uppercase UUIDs.\n *\n * @example\n * ```typescript\n * getDescriptor('gatt.client_characteristic_configuration') // '00002902-...'\n * getDescriptor(0x2902) // '00002902-...'\n * ```\n *\n * @see {@link resolveUUID} for the lenient SDK-level resolver\n */\nexport function getDescriptor(name: string | number): string {\n return resolveUUIDName(name, DESCRIPTORS, 'getDescriptor');\n}\n\n/**\n * BluetoothUUID namespace object conforming to the Web Bluetooth spec.\n * Can be assigned to `window.BluetoothUUID` for spec compliance.\n * Each getter is scoped to its own GATT assigned-numbers table (§7.1).\n */\nexport const BluetoothUUID = {\n canonicalUUID,\n getService: (name: string | number) => resolveUUIDName(name, SERVICES, 'getService'),\n getCharacteristic: (name: string | number) => resolveUUIDName(name, CHARACTERISTICS, 'getCharacteristic'),\n getDescriptor,\n} as const;\n"]}
var x="https://ioswebble.com/setup",p="ioswebble_dismiss_until",h="ioswebble_return",v="link.ioswebble.com";function y(){try{let e=localStorage.getItem(p);return e?Date.now()<parseInt(e,10):!1}catch{return false}}function c(e){try{localStorage.setItem(p,String(Date.now()+e*864e5));}catch{}}function S(){let e=new URL(window.location.href),t=new URL(`https://${v}/return`);t.searchParams.set("url",e.toString());try{localStorage.setItem(h,JSON.stringify({url:e.toString(),returnLink:t.toString(),timestamp:Date.now()})),navigator.storage?.persist?.();}catch{}try{navigator.clipboard?.writeText(t.toString());}catch{}}function b(e){return e.startOnboardingUrl??e.appStoreUrl??x}function u(e,t){S();let n=new URL(e,window.location.href),r=n.hostname==="apps.apple.com";t&&r&&!n.searchParams.has("ct")&&(n.searchParams.set("ct",t),n.searchParams.set("mt","8")),window.location.href=n.toString();}function s(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function k(e){let{operatorName:t=document.title||window.location.hostname,buttonText:n="Start Setup",apiKey:r,dismissDays:a=14}=e,l=b(e),i=document.createElement("div");return i.id="ioswebble-banner",i.innerHTML=`
<style>
#ioswebble-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-end;
justify-content:center;background:rgba(0,0,0,.4);font-family:-apple-system,BlinkMacSystemFont,
'SF Pro Text',system-ui,sans-serif;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
animation:iwb-fi .25s ease-out}
@keyframes iwb-fi{from{opacity:0}to{opacity:1}}
@keyframes iwb-su{from{transform:translateY(100%)}to{transform:translateY(0)}}
#iwb-s{background:#fff;border-radius:16px 16px 0 0;padding:12px 24px 34px;max-width:420px;
width:100%;animation:iwb-su .3s ease-out}
#iwb-s *{box-sizing:border-box;margin:0;padding:0}
.iwb-h{width:36px;height:5px;border-radius:3px;background:#d1d1d6;margin:0 auto 16px}
.iwb-hdr{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.iwb-ic{width:40px;height:40px;border-radius:10px;background:#007aff;display:flex;
align-items:center;justify-content:center;flex-shrink:0}
.iwb-ic svg{width:22px;height:22px;fill:#fff}
.iwb-tt{font-size:17px;font-weight:600;color:#000}
.iwb-bd{font-size:15px;line-height:1.4;color:#8e8e93;margin-bottom:16px}
.iwb-mt{font-size:13px;color:#8e8e93;margin-bottom:20px;display:flex;align-items:center;gap:8px}
.iwb-st{color:#ff9500;letter-spacing:1px}
.iwb-btn{display:block;width:100%;padding:14px;background:#007aff;color:#fff;border:none;
border-radius:12px;font-size:17px;font-weight:600;cursor:pointer;text-align:center;
-webkit-tap-highlight-color:transparent}
.iwb-btn:active{opacity:.85}
.iwb-det{margin-top:16px}
.iwb-det summary{font-size:15px;color:#007aff;cursor:pointer;list-style:none;padding:4px 0}
.iwb-det summary::before{content:'\\25B8 '}
.iwb-det[open] summary::before{content:'\\25BE '}
.iwb-det p{font-size:13px;color:#8e8e93;line-height:1.5;padding:8px 0 4px}
.iwb-dis{display:block;width:100%;padding:12px;background:none;border:none;font-size:15px;
color:#8e8e93;cursor:pointer;text-align:center;margin-top:8px;
-webkit-tap-highlight-color:transparent}
@media(prefers-color-scheme:dark){
#iwb-s{background:#1c1c1e}
.iwb-tt{color:#fff}
.iwb-bd,.iwb-mt,.iwb-det p{color:#98989f}
.iwb-dis{color:#98989f}
.iwb-h{background:#48484a}
}
</style>
<div id="ioswebble-overlay">
<div id="iwb-s">
<div class="iwb-h"></div>
<div class="iwb-hdr">
<div class="iwb-ic"><svg viewBox="0 0 24 24"><path d="M12 2L7 7l5 5-5 5 5 5V2zm0 6.83L10.83 7 12 5.83v2.34zm0 8.34L10.83 17 12 15.83v1.34zM17 7l-5 5 5 5-2.12 2.12L12 17l-2.88 2.12L7 17l5-5-5-5 2.12-2.12L12 7l2.88-2.12L17 7z"/></svg></div>
<div class="iwb-tt">Set Up Bluetooth in Safari</div>
</div>
<div class="iwb-bd">To connect to your device, install WebBLE, open the app once, enable the Safari extension, then return to ${s(t)}.</div>
<div class="iwb-mt"><span>Install</span><span>\u2192</span><span>Open app</span><span>\u2192</span><span>Enable in Safari</span><span>\u2192</span><span>Return here</span></div>
<button class="iwb-btn" id="iwb-install">${s(n)}</button>
<details class="iwb-det"><summary>How does setup work?</summary><p>WebBLE uses an iPhone app to guide the one-time Safari extension setup. After install, open the app, enable the extension in Safari, then come back to this page and try again.</p></details>
<details class="iwb-det"><summary>Privacy: No data collected</summary><p>WebBLE processes all Bluetooth data locally on your device. No browsing data, device data, or personal information is ever collected or transmitted.</p></details>
<button class="iwb-dis" id="iwb-dismiss">Not now</button>
</div>
</div>`,requestAnimationFrame(()=>{i.querySelector("#iwb-install")?.addEventListener("click",()=>{u(l,r);}),i.querySelector("#iwb-dismiss")?.addEventListener("click",()=>{i.remove(),c(a);}),i.querySelector("#ioswebble-overlay")?.addEventListener("click",d=>{d.target.id==="ioswebble-overlay"&&(i.remove(),c(a));});}),document.body.appendChild(i),i}function L(e){let{position:t="bottom",text:n="Install WebBLE, open the app, enable the Safari extension, then return here.",buttonText:r="Start Setup",style:a={},apiKey:l,dismissDays:i=14}=e,d=b(e),o=document.createElement("div");o.id="ioswebble-banner";let m=t==="top"?"top:0;border-bottom:1px solid #e5e7eb;":"bottom:0;border-top:1px solid #e5e7eb;",f=Object.entries(a).map(([w,g])=>`${w}:${g}`).join(";");return o.innerHTML=`
<div style="position:fixed;${m}left:0;right:0;z-index:2147483646;
background:#fff;padding:16px;
display:flex;align-items:center;gap:12px;font-family:system-ui,-apple-system,sans-serif;
box-shadow:0 ${t==="top"?"2px":"-2px"} 10px rgba(0,0,0,0.1);${f}">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#007AFF"/>
<path d="M12 7a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V8a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" fill="white"/>
</svg>
<div style="flex:1">
<div style="font-size:14px;font-weight:600;color:#1f2937">Enable Bluetooth</div>
<div style="font-size:12px;color:#6b7280;margin-top:2px">${s(n)}</div>
</div>
<button id="ioswebble-banner-install"
style="background:#007AFF;color:white;padding:8px 16px;border-radius:8px;
border:none;font-size:14px;font-weight:500;white-space:nowrap;cursor:pointer">
${s(r)}</button>
<button id="ioswebble-banner-close"
style="background:none;border:none;color:#9ca3af;font-size:20px;
cursor:pointer;padding:4px;line-height:1"
aria-label="Close">&times;</button>
</div>`,o.querySelector("#ioswebble-banner-install")?.addEventListener("click",()=>{u(d,l);}),o.querySelector("#ioswebble-banner-close")?.addEventListener("click",()=>{o.remove(),c(i);}),document.body.appendChild(o),o}function E(e={}){return y()?null:e.mode==="banner"?L(e):k(e)}function z(){let e=document.getElementById("ioswebble-banner");e&&e.remove();}export{E as a,z as b};//# sourceMappingURL=chunk-VJVS2CEP.mjs.map
//# sourceMappingURL=chunk-VJVS2CEP.mjs.map
{"version":3,"sources":["../../detect/src/banner.ts"],"names":["DEFAULT_ONBOARDING_URL","DISMISS_KEY","RETURN_KEY","RETURN_LINK_HOST","isDismissed","until","setDismissed","days","saveReturnContext","returnPageURL","returnLink","resolveOnboardingUrl","options","redirectToOnboarding","url","apiKey","parsed","isAppStore","esc","s","d","showBottomSheet","operatorName","buttonText","dismissDays","onboardingUrl","overlay","e","showBarBanner","position","text","style","el","posStyle","customStyle","k","v","showInstallBanner","removeInstallBanner"],"mappings":"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","file":"chunk-VJVS2CEP.mjs","sourcesContent":["/**\n * Install prompt UI for WebBLE\n *\n * Two modes:\n * 1. Bottom sheet (default) — iOS-native feel, shown on requestDevice() trigger\n * 2. Banner — lightweight top/bottom bar for passive prompting\n *\n * Features:\n * - Clipboard context saving for return-to-web-app flow\n * - 14-day dismissal frequency capping\n * - Configurable install/onboarding redirect\n * - Dark mode support via prefers-color-scheme\n */\n\nexport interface BannerOptions {\n /** 'sheet' (default) for iOS bottom sheet, 'banner' for lightweight bar */\n mode?: 'sheet' | 'banner';\n position?: 'top' | 'bottom';\n text?: string;\n buttonText?: string;\n style?: Record<string, string>;\n /** Preferred install or onboarding URL to open when the user taps the CTA */\n startOnboardingUrl?: string;\n /** Legacy install destination option; still supported for compatibility */\n appStoreUrl?: string;\n /** Operator/app name shown in the prompt (e.g. \"FitTracker\") */\n operatorName?: string;\n /** API key for campaign tracking */\n apiKey?: string;\n /** Days to suppress after dismiss (default: 14) */\n dismissDays?: number;\n}\n\n// AIDEV-NOTE: Canonical zero-config onboarding default — the guided /setup page (install →\n// enable Safari extension → return), not a bare App Store search. Lets an agent wire the banner\n// with no URL config and still send users to a flow that actually completes setup.\nconst DEFAULT_ONBOARDING_URL = 'https://ioswebble.com/setup';\nconst DISMISS_KEY = 'ioswebble_dismiss_until';\nconst RETURN_KEY = 'ioswebble_return';\nconst RETURN_LINK_HOST = 'link.ioswebble.com';\n\nfunction isDismissed(): boolean {\n try {\n const until = localStorage.getItem(DISMISS_KEY);\n if (!until) return false;\n return Date.now() < parseInt(until, 10);\n } catch {\n return false;\n }\n}\n\nfunction setDismissed(days: number): void {\n try {\n localStorage.setItem(DISMISS_KEY, String(Date.now() + days * 86400000));\n } catch {\n /* noop */\n }\n}\n\nfunction saveReturnContext(): void {\n const returnPageURL = new URL(window.location.href);\n const returnLink = new URL(`https://${RETURN_LINK_HOST}/return`);\n returnLink.searchParams.set('url', returnPageURL.toString());\n\n try {\n localStorage.setItem(\n RETURN_KEY,\n JSON.stringify({ url: returnPageURL.toString(), returnLink: returnLink.toString(), timestamp: Date.now() })\n );\n navigator.storage?.persist?.();\n } catch {\n /* noop */\n }\n try {\n navigator.clipboard?.writeText(returnLink.toString());\n } catch {\n /* noop */\n }\n}\n\nfunction resolveOnboardingUrl(options: Pick<BannerOptions, 'startOnboardingUrl' | 'appStoreUrl'>): string {\n return options.startOnboardingUrl ?? options.appStoreUrl ?? DEFAULT_ONBOARDING_URL;\n}\n\nfunction redirectToOnboarding(url: string, apiKey?: string): void {\n saveReturnContext();\n\n const parsed = new URL(url, window.location.href);\n const isAppStore = parsed.hostname === 'apps.apple.com';\n\n if (apiKey && isAppStore && !parsed.searchParams.has('ct')) {\n parsed.searchParams.set('ct', apiKey);\n parsed.searchParams.set('mt', '8');\n }\n\n window.location.href = parsed.toString();\n}\n\nfunction esc(s: string): string {\n const d = document.createElement('div');\n d.textContent = s;\n return d.innerHTML;\n}\n\n// ─── Bottom Sheet ──────────────────────────────────────────────────────────\n\nfunction showBottomSheet(options: BannerOptions): HTMLElement {\n const {\n operatorName = document.title || window.location.hostname,\n buttonText = 'Start Setup',\n apiKey,\n dismissDays = 14,\n } = options;\n const onboardingUrl = resolveOnboardingUrl(options);\n\n const overlay = document.createElement('div');\n overlay.id = 'ioswebble-banner';\n overlay.innerHTML = `\n<style>\n#ioswebble-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:flex-end;\n justify-content:center;background:rgba(0,0,0,.4);font-family:-apple-system,BlinkMacSystemFont,\n 'SF Pro Text',system-ui,sans-serif;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);\n animation:iwb-fi .25s ease-out}\n@keyframes iwb-fi{from{opacity:0}to{opacity:1}}\n@keyframes iwb-su{from{transform:translateY(100%)}to{transform:translateY(0)}}\n#iwb-s{background:#fff;border-radius:16px 16px 0 0;padding:12px 24px 34px;max-width:420px;\n width:100%;animation:iwb-su .3s ease-out}\n#iwb-s *{box-sizing:border-box;margin:0;padding:0}\n.iwb-h{width:36px;height:5px;border-radius:3px;background:#d1d1d6;margin:0 auto 16px}\n.iwb-hdr{display:flex;align-items:center;gap:12px;margin-bottom:12px}\n.iwb-ic{width:40px;height:40px;border-radius:10px;background:#007aff;display:flex;\n align-items:center;justify-content:center;flex-shrink:0}\n.iwb-ic svg{width:22px;height:22px;fill:#fff}\n.iwb-tt{font-size:17px;font-weight:600;color:#000}\n.iwb-bd{font-size:15px;line-height:1.4;color:#8e8e93;margin-bottom:16px}\n.iwb-mt{font-size:13px;color:#8e8e93;margin-bottom:20px;display:flex;align-items:center;gap:8px}\n.iwb-st{color:#ff9500;letter-spacing:1px}\n.iwb-btn{display:block;width:100%;padding:14px;background:#007aff;color:#fff;border:none;\n border-radius:12px;font-size:17px;font-weight:600;cursor:pointer;text-align:center;\n -webkit-tap-highlight-color:transparent}\n.iwb-btn:active{opacity:.85}\n.iwb-det{margin-top:16px}\n.iwb-det summary{font-size:15px;color:#007aff;cursor:pointer;list-style:none;padding:4px 0}\n.iwb-det summary::before{content:'\\\\25B8 '}\n.iwb-det[open] summary::before{content:'\\\\25BE '}\n.iwb-det p{font-size:13px;color:#8e8e93;line-height:1.5;padding:8px 0 4px}\n.iwb-dis{display:block;width:100%;padding:12px;background:none;border:none;font-size:15px;\n color:#8e8e93;cursor:pointer;text-align:center;margin-top:8px;\n -webkit-tap-highlight-color:transparent}\n@media(prefers-color-scheme:dark){\n #iwb-s{background:#1c1c1e}\n .iwb-tt{color:#fff}\n .iwb-bd,.iwb-mt,.iwb-det p{color:#98989f}\n .iwb-dis{color:#98989f}\n .iwb-h{background:#48484a}\n}\n</style>\n<div id=\"ioswebble-overlay\">\n<div id=\"iwb-s\">\n <div class=\"iwb-h\"></div>\n <div class=\"iwb-hdr\">\n <div class=\"iwb-ic\"><svg viewBox=\"0 0 24 24\"><path d=\"M12 2L7 7l5 5-5 5 5 5V2zm0 6.83L10.83 7 12 5.83v2.34zm0 8.34L10.83 17 12 15.83v1.34zM17 7l-5 5 5 5-2.12 2.12L12 17l-2.88 2.12L7 17l5-5-5-5 2.12-2.12L12 7l2.88-2.12L17 7z\"/></svg></div>\n <div class=\"iwb-tt\">Set Up Bluetooth in Safari</div>\n </div>\n <div class=\"iwb-bd\">To connect to your device, install WebBLE, open the app once, enable the Safari extension, then return to ${esc(operatorName)}.</div>\n <div class=\"iwb-mt\"><span>Install</span><span>→</span><span>Open app</span><span>→</span><span>Enable in Safari</span><span>→</span><span>Return here</span></div>\n <button class=\"iwb-btn\" id=\"iwb-install\">${esc(buttonText)}</button>\n <details class=\"iwb-det\"><summary>How does setup work?</summary><p>WebBLE uses an iPhone app to guide the one-time Safari extension setup. After install, open the app, enable the extension in Safari, then come back to this page and try again.</p></details>\n <details class=\"iwb-det\"><summary>Privacy: No data collected</summary><p>WebBLE processes all Bluetooth data locally on your device. No browsing data, device data, or personal information is ever collected or transmitted.</p></details>\n <button class=\"iwb-dis\" id=\"iwb-dismiss\">Not now</button>\n</div>\n</div>`;\n\n requestAnimationFrame(() => {\n overlay.querySelector('#iwb-install')?.addEventListener('click', () => {\n redirectToOnboarding(onboardingUrl, apiKey);\n });\n overlay.querySelector('#iwb-dismiss')?.addEventListener('click', () => {\n overlay.remove();\n setDismissed(dismissDays);\n });\n overlay.querySelector('#ioswebble-overlay')?.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).id === 'ioswebble-overlay') {\n overlay.remove();\n setDismissed(dismissDays);\n }\n });\n });\n\n document.body.appendChild(overlay);\n return overlay;\n}\n\n// ─── Lightweight Banner ────────────────────────────────────────────────────\n\nfunction showBarBanner(options: BannerOptions): HTMLElement {\n const {\n position = 'bottom',\n text = 'Install WebBLE, open the app, enable the Safari extension, then return here.',\n buttonText = 'Start Setup',\n style = {},\n apiKey,\n dismissDays = 14,\n } = options;\n const onboardingUrl = resolveOnboardingUrl(options);\n\n const el = document.createElement('div');\n el.id = 'ioswebble-banner';\n\n const posStyle =\n position === 'top'\n ? 'top:0;border-bottom:1px solid #e5e7eb;'\n : 'bottom:0;border-top:1px solid #e5e7eb;';\n\n const customStyle = Object.entries(style)\n .map(([k, v]) => `${k}:${v}`)\n .join(';');\n\n el.innerHTML = `\n <div style=\"position:fixed;${posStyle}left:0;right:0;z-index:2147483646;\n background:#fff;padding:16px;\n display:flex;align-items:center;gap:12px;font-family:system-ui,-apple-system,sans-serif;\n box-shadow:0 ${position === 'top' ? '2px' : '-2px'} 10px rgba(0,0,0,0.1);${customStyle}\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"#007AFF\"/>\n <path d=\"M12 7a1 1 0 0 1 1 1v4a1 1 0 0 1-2 0V8a1 1 0 0 1 1-1zm0 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\" fill=\"white\"/>\n </svg>\n <div style=\"flex:1\">\n <div style=\"font-size:14px;font-weight:600;color:#1f2937\">Enable Bluetooth</div>\n <div style=\"font-size:12px;color:#6b7280;margin-top:2px\">${esc(text)}</div>\n </div>\n <button id=\"ioswebble-banner-install\"\n style=\"background:#007AFF;color:white;padding:8px 16px;border-radius:8px;\n border:none;font-size:14px;font-weight:500;white-space:nowrap;cursor:pointer\">\n ${esc(buttonText)}</button>\n <button id=\"ioswebble-banner-close\"\n style=\"background:none;border:none;color:#9ca3af;font-size:20px;\n cursor:pointer;padding:4px;line-height:1\"\n aria-label=\"Close\">&times;</button>\n </div>`;\n\n el.querySelector('#ioswebble-banner-install')?.addEventListener('click', () => {\n redirectToOnboarding(onboardingUrl, apiKey);\n });\n el.querySelector('#ioswebble-banner-close')?.addEventListener('click', () => {\n el.remove();\n setDismissed(dismissDays);\n });\n\n document.body.appendChild(el);\n return el;\n}\n\n// ─── Public API ────────────────────────────────────────────────────────────\n\nexport function showInstallBanner(options: BannerOptions = {}): HTMLElement | null {\n if (isDismissed()) return null;\n return options.mode === 'banner' ? showBarBanner(options) : showBottomSheet(options);\n}\n\nexport function removeInstallBanner(): void {\n const el = document.getElementById('ioswebble-banner');\n if (el) el.remove();\n}\n"]}
function d(){if(typeof navigator>"u")return false;let t=navigator.userAgent,e=/iPad|iPhone|iPod/.test(t)||navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1,n=/^((?!chrome|android|crios|fxios).)*safari/i.test(t);return e&&n}function o(){return typeof window<"u"&&window.__webble?.status==="installed"}function s(){return typeof navigator>"u"?false:!!(navigator.webble&&navigator.webble.__webble)}function l(){return typeof document<"u"&&document.documentElement.dataset.webbleInstalled==="true"}function u(){return typeof document<"u"&&document.documentElement.dataset.webbleExtension==="true"}function r(){return s()||u()?"active":o()||l()?"installed-inactive":"not-installed"}async function c(){try{let{detectPlatform:t}=await import('@beacio/core');if(t()==="safari-extension")return "active"}catch{}return new Promise(t=>{let e=r();if(e!=="not-installed"){t(e);return}let n=0,a=setInterval(()=>{n++;let i=r();i!=="not-installed"&&(clearInterval(a),t(i)),n>20&&(clearInterval(a),t("not-installed"));},100);})}async function f(){return await c()!=="not-installed"}export{d as a,c as b,f as c};//# sourceMappingURL=chunk-ZOGE47CD.mjs.map
//# sourceMappingURL=chunk-ZOGE47CD.mjs.map
{"version":3,"sources":["../../detect/src/detect.ts"],"names":["isIOSSafari","ua","isIOS","isSafari","hasWindowMarker","hasNavigatorMarker","hasInstallMarker","hasActiveMarker","resolveInstallState","getExtensionInstallState","detectPlatform","resolve","immediateState","checks","interval","state","isExtensionInstalled"],"mappings":"AAIO,SAASA,GAAuB,CACrC,GAAI,OAAO,SAAA,CAAc,GAAA,CAAa,OAAO,MAAA,CAE7C,IAAMC,EAAK,SAAA,CAAU,SAAA,CACfC,EACJ,kBAAA,CAAmB,IAAA,CAAKD,CAAE,CAAA,EACzB,SAAA,CAAU,WAAa,UAAA,EAAc,SAAA,CAAU,cAAA,CAAiB,CAAA,CAC7DE,EAAW,4CAAA,CAA6C,IAAA,CAAKF,CAAE,CAAA,CAErE,OAAOC,GAASC,CAClB,CAIA,SAASC,CAAAA,EAA2B,CAClC,OAAO,OAAO,MAAA,CAAW,KAAgB,MAAA,CAAe,QAAA,EAAU,SAAW,WAC/E,CAEA,SAASC,CAAAA,EAA8B,CACrC,OAAI,OAAO,UAAc,GAAA,CAChB,KAAA,CAEF,GAAS,SAAA,CAAkB,MAAA,EAAW,UAAkB,MAAA,CAAO,QAAA,CACxE,CAEA,SAASC,CAAAA,EAA4B,CACnC,OAAO,OAAO,SAAa,GAAA,EAAe,QAAA,CAAS,gBAAgB,OAAA,CAAQ,eAAA,GAAoB,MACjG,CAEA,SAASC,GAA2B,CAClC,OAAO,OAAO,QAAA,CAAa,GAAA,EAAe,SAAS,eAAA,CAAgB,OAAA,CAAQ,kBAAoB,MACjG,CAEA,SAASC,CAAAA,EAA6C,CACpD,OAAIH,CAAAA,EAAmB,EAAKE,CAAAA,EAAgB,CACnC,SAELH,CAAAA,EAAgB,EAAKE,GAAiB,CACjC,oBAAA,CAEF,eACT,CAEA,eAAsBG,GAA2D,CAE/E,GAAI,CACF,GAAM,CAAE,eAAAC,CAAe,CAAA,CAAI,MAAM,OAAO,cAAc,EACtD,GAAIA,CAAAA,KAAqB,kBAAA,CAAoB,OAAO,QACtD,CAAA,KAAQ,CAA0C,CAElD,OAAO,IAAI,QAASC,CAAAA,EAAY,CAE9B,IAAMC,CAAAA,CAAiBJ,CAAAA,GACvB,GAAII,CAAAA,GAAmB,gBAAiB,CACtCD,CAAAA,CAAQC,CAAc,CAAA,CACtB,MACF,CAIA,IAAIC,EAAS,CAAA,CACPC,CAAAA,CAAW,YAAY,IAAM,CACjCD,IACA,IAAME,CAAAA,CAAQP,GAAoB,CAC9BO,CAAAA,GAAU,kBACZ,aAAA,CAAcD,CAAQ,EACtBH,CAAAA,CAAQI,CAAK,GAEXF,CAAAA,CAAS,EAAA,GAEX,cAAcC,CAAQ,CAAA,CACtBH,EAAQ,eAAe,CAAA,EAE3B,EAAG,GAAG,EACR,CAAC,CACH,CAEA,eAAsBK,CAAAA,EAAyC,CAC7D,OAAQ,MAAMP,CAAAA,KAAgC,eAChD","file":"chunk-ZOGE47CD.mjs","sourcesContent":["/**\n * Platform detection utilities for WebBLE\n */\n\nexport function isIOSSafari(): boolean {\n if (typeof navigator === 'undefined') return false;\n\n const ua = navigator.userAgent;\n const isIOS =\n /iPad|iPhone|iPod/.test(ua) ||\n (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);\n const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(ua);\n\n return isIOS && isSafari;\n}\n\nexport type ExtensionInstallState = 'not-installed' | 'installed-inactive' | 'active';\n\nfunction hasWindowMarker(): boolean {\n return typeof window !== 'undefined' && (window as any).__webble?.status === 'installed';\n}\n\nfunction hasNavigatorMarker(): boolean {\n if (typeof navigator === 'undefined') {\n return false;\n }\n return Boolean((navigator as any).webble && (navigator as any).webble.__webble);\n}\n\nfunction hasInstallMarker(): boolean {\n return typeof document !== 'undefined' && document.documentElement.dataset.webbleInstalled === 'true';\n}\n\nfunction hasActiveMarker(): boolean {\n return typeof document !== 'undefined' && document.documentElement.dataset.webbleExtension === 'true';\n}\n\nfunction resolveInstallState(): ExtensionInstallState {\n if (hasNavigatorMarker() || hasActiveMarker()) {\n return 'active';\n }\n if (hasWindowMarker() || hasInstallMarker()) {\n return 'installed-inactive';\n }\n return 'not-installed';\n}\n\nexport async function getExtensionInstallState(): Promise<ExtensionInstallState> {\n // Fast-path: if @beacio/core is installed, use its platform detection\n try {\n const { detectPlatform } = await import('@beacio/core');\n if (detectPlatform() === 'safari-extension') return 'active';\n } catch { /* core not installed — fall through */ }\n\n return new Promise((resolve) => {\n // Method 1: Check for the global marker set by injected-full.ts\n const immediateState = resolveInstallState();\n if (immediateState !== 'not-installed') {\n resolve(immediateState);\n return;\n }\n\n // Method 3: Wait briefly for injection to complete\n // The content script runs at document_start, so injection should be fast\n let checks = 0;\n const interval = setInterval(() => {\n checks++;\n const state = resolveInstallState();\n if (state !== 'not-installed') {\n clearInterval(interval);\n resolve(state);\n }\n if (checks > 20) {\n // 2 seconds max wait\n clearInterval(interval);\n resolve('not-installed');\n }\n }, 100);\n });\n}\n\nexport async function isExtensionInstalled(): Promise<boolean> {\n return (await getExtensionInstallState()) !== 'not-installed';\n}\n"]}
export{b as getExtensionInstallState,c as isExtensionInstalled,a as isIOSSafari}from'./chunk-ZOGE47CD.mjs';//# sourceMappingURL=detect-BZJUQOJ3-HIQZ5XOU.mjs.map
//# sourceMappingURL=detect-BZJUQOJ3-HIQZ5XOU.mjs.map
{"version":3,"sources":[],"names":[],"mappings":"","file":"detect-BZJUQOJ3-HIQZ5XOU.mjs"}
export{b as removeInstallBanner,a as showInstallBanner}from'./chunk-VJVS2CEP.mjs';export{b as getExtensionInstallState,c as isExtensionInstalled,a as isIOSSafari}from'./chunk-ZOGE47CD.mjs';var r="https://api.ioswebble.com";function a(e,n,i){if(e)try{fetch(`${r}/v1/events?key=${encodeURIComponent(e)}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:[{event:n,data:{origin:location.hostname,ua:navigator.userAgent,...i},timestamp:Date.now()}]}),keepalive:!0}).catch(()=>{});}catch{}}async function w(e){try{let n=await fetch(`${r}/v1/config?key=${encodeURIComponent(e)}`);return n.ok?await n.json():null}catch{return null}}function u(e){typeof window>"u"||window.dispatchEvent(new CustomEvent("ioswebble:statechange",{detail:{state:e}}));}async function o(e){if(e.banner===false)return;let{showInstallBanner:n}=await import('./banner-DT5I7URC-ZD7P2QSM.mjs'),t={...typeof e.banner=="object"?e.banner:{},apiKey:e.key??"",operatorName:e.operatorName};n(t);}async function v(e){let{getExtensionInstallState:n,isIOSSafari:i}=await import('./detect-BZJUQOJ3-HIQZ5XOU.mjs');if(!i())return;let t=await n();if(u(t),t==="active"){a(e.key??"","extension_active"),typeof window<"u"&&window.dispatchEvent(new CustomEvent("ioswebble:ready")),e.onReady?.();return}if(t==="installed-inactive"){a(e.key??"","extension_installed_inactive"),typeof window<"u"&&window.dispatchEvent(new CustomEvent("ioswebble:installedinactive")),e.onInstalledInactive?.(),await o(e);return}a(e.key??"","detect"),typeof window<"u"&&window.dispatchEvent(new CustomEvent("ioswebble:notinstalled")),e.onNotInstalled?.(),await o(e),e.banner!==false&&a(e.key??"","install_prompted");}
export{v as initBeacio,a as reportEvent,w as validateApiKey};//# sourceMappingURL=dist-F6NYWN3W.mjs.map
//# sourceMappingURL=dist-F6NYWN3W.mjs.map
{"version":3,"sources":["../../detect/src/api.ts","../../detect/src/index.ts"],"names":["API_BASE","reportEvent","apiKey","event","data","validateApiKey","res","dispatchInstallState","state","maybeShowBanner","options","showInstallBanner","bannerOpts","initBeacio","getExtensionInstallState","isIOSSafari","installState"],"mappings":"6LAKA,IAAMA,CAAAA,CAAW,2BAAA,CAEV,SAASC,CAAAA,CAAYC,CAAAA,CAAgBC,CAAAA,CAAeC,CAAAA,CAAsC,CAC/F,GAAKF,CAAAA,CACL,GAAI,CACF,MAAM,CAAA,EAAGF,CAAQ,CAAA,eAAA,EAAkB,kBAAA,CAAmBE,CAAM,CAAC,CAAA,CAAA,CAAI,CAC/D,OAAQ,MAAA,CACR,OAAA,CAAS,CAAE,cAAA,CAAgB,kBAAmB,CAAA,CAC9C,IAAA,CAAM,IAAA,CAAK,UAAU,CACnB,MAAA,CAAQ,CAAC,CACP,KAAA,CAAAC,CAAAA,CACA,IAAA,CAAM,CAAE,MAAA,CAAQ,QAAA,CAAS,QAAA,CAAU,EAAA,CAAI,SAAA,CAAU,SAAA,CAAW,GAAGC,CAAK,EACpE,SAAA,CAAW,IAAA,CAAK,GAAA,EAClB,CAAC,CACH,CAAC,CAAA,CACD,UAAW,CAAA,CACb,CAAC,CAAA,CAAE,KAAA,CAAM,IAAM,CAAC,CAAC,EACnB,MAAQ,CAAmC,CAC7C,CAEA,eAAsBC,CAAAA,CACpBH,CAAAA,CAC8E,CAC9E,GAAI,CACF,IAAMI,CAAAA,CAAM,MAAM,KAAA,CAAM,CAAA,EAAGN,CAAQ,CAAA,eAAA,EAAkB,mBAAmBE,CAAM,CAAC,CAAA,CAAE,CAAA,CACjF,OAAKI,CAAAA,CAAI,EAAA,CACF,MAAMA,EAAI,IAAA,EAAK,CADF,IAEtB,CAAA,KAAQ,CACN,OAAO,IACT,CACF,CCUA,SAASC,CAAAA,CAAqBC,CAAAA,CAAoC,CAC5D,OAAO,MAAA,CAAW,GAAA,EAItB,MAAA,CAAO,aAAA,CAAc,IAAI,WAAA,CAAY,uBAAA,CAAyB,CAC5D,MAAA,CAAQ,CAAE,KAAA,CAAAA,CAAM,CAClB,CAAC,CAAC,EACJ,CAMA,eAAeC,CAAAA,CAAgBC,CAAAA,CAAuC,CACpE,GAAIA,CAAAA,CAAQ,MAAA,GAAW,KAAA,CAAO,OAC9B,GAAM,CAAE,iBAAA,CAAAC,CAAkB,CAAA,CAAI,MAAM,OAAO,gCAAU,CAAA,CAE/CC,CAAAA,CAA+C,CACnD,GAFmB,OAAOF,CAAAA,CAAQ,MAAA,EAAW,QAAA,CAAWA,CAAAA,CAAQ,MAAA,CAAS,EAAC,CAG1E,OAAQA,CAAAA,CAAQ,GAAA,EAAO,EAAA,CACvB,YAAA,CAAcA,CAAAA,CAAQ,YACxB,CAAA,CACAC,CAAAA,CAAkBC,CAAU,EAC9B,CAUA,eAAsBC,CAAAA,CAAWH,CAAAA,CAAuC,CACtE,GAAM,CAAE,yBAAAI,CAAAA,CAA0B,WAAA,CAAAC,CAAY,CAAA,CAAI,MAAM,OAAO,gCAAU,CAAA,CAEzE,GAAI,CAACA,CAAAA,EAAY,CAAG,OAEpB,IAAMC,CAAAA,CAAe,MAAMF,GAAyB,CAGpD,GAFAP,CAAAA,CAAqBS,CAAY,CAAA,CAE7BA,CAAAA,GAAiB,QAAA,CAAU,CAC7Bf,EAAYS,CAAAA,CAAQ,GAAA,EAAO,EAAA,CAAI,kBAAkB,CAAA,CAC7C,OAAO,MAAA,CAAW,GAAA,EACpB,OAAO,aAAA,CAAc,IAAI,WAAA,CAAY,iBAAiB,CAAC,CAAA,CAEzDA,CAAAA,CAAQ,OAAA,IAAU,CAClB,MACF,CAEA,GAAIM,CAAAA,GAAiB,oBAAA,CAAsB,CACzCf,CAAAA,CAAYS,EAAQ,GAAA,EAAO,EAAA,CAAI,8BAA8B,CAAA,CACzD,OAAO,MAAA,CAAW,GAAA,EACpB,MAAA,CAAO,cAAc,IAAI,WAAA,CAAY,6BAA6B,CAAC,CAAA,CAErEA,CAAAA,CAAQ,mBAAA,IAAsB,CAE9B,MAAMD,CAAAA,CAAgBC,CAAO,CAAA,CAC7B,MACF,CAGAT,CAAAA,CAAYS,CAAAA,CAAQ,GAAA,EAAO,GAAI,QAAQ,CAAA,CACnC,OAAO,MAAA,CAAW,GAAA,EACpB,MAAA,CAAO,aAAA,CAAc,IAAI,YAAY,wBAAwB,CAAC,CAAA,CAEhEA,CAAAA,CAAQ,cAAA,IAAiB,CAGzB,MAAMD,CAAAA,CAAgBC,CAAO,CAAA,CACzBA,CAAAA,CAAQ,MAAA,GAAW,KAAA,EACrBT,CAAAA,CAAYS,CAAAA,CAAQ,GAAA,EAAO,EAAA,CAAI,kBAAkB,EAErD","file":"dist-F6NYWN3W.mjs","sourcesContent":["/**\n * Analytics event reporter and API key validator.\n * Fire-and-forget — analytics must never throw or block.\n */\n\nconst API_BASE = 'https://api.ioswebble.com';\n\nexport function reportEvent(apiKey: string, event: string, data?: Record<string, unknown>): void {\n if (!apiKey) return;\n try {\n fetch(`${API_BASE}/v1/events?key=${encodeURIComponent(apiKey)}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n events: [{\n event,\n data: { origin: location.hostname, ua: navigator.userAgent, ...data },\n timestamp: Date.now(),\n }],\n }),\n keepalive: true,\n }).catch(() => {});\n } catch { /* analytics must never throw */ }\n}\n\nexport async function validateApiKey(\n apiKey: string,\n): Promise<{ operatorId: string; appName: string | null; plan: string } | null> {\n try {\n const res = await fetch(`${API_BASE}/v1/config?key=${encodeURIComponent(apiKey)}`);\n if (!res.ok) return null;\n return await res.json();\n } catch {\n return null;\n }\n}\n","/**\n * @beacio/detect\n *\n * Detects iOS Safari, checks if the WebBLE extension is installed,\n * and shows an install banner if not. No-op on all other platforms.\n *\n * Your existing Web Bluetooth code works unchanged — this package only\n * handles the \"extension not installed\" case on iOS Safari.\n */\n\nexport { getExtensionInstallState, isExtensionInstalled, isIOSSafari } from './detect';\nexport type { ExtensionInstallState } from './detect';\nexport { showInstallBanner, removeInstallBanner } from './banner';\nexport type { BannerOptions } from './banner';\nexport { reportEvent, validateApiKey } from './api';\nimport { reportEvent } from './api';\nimport type { ExtensionInstallState } from './detect';\nexport interface BeacioOptions {\n /** Optional API key for campaign tracking */\n key?: string;\n /** Operator/app name shown in the prompt (e.g. \"FitTracker\") */\n operatorName?: string;\n /** Install banner configuration, or false to disable */\n banner?:\n | {\n /** 'sheet' (default) for iOS bottom sheet, 'banner' for lightweight bar */\n mode?: 'sheet' | 'banner';\n position?: 'top' | 'bottom';\n text?: string;\n buttonText?: string;\n style?: Record<string, string>;\n startOnboardingUrl?: string;\n appStoreUrl?: string;\n /** Days to suppress after dismiss (default: 14) */\n dismissDays?: number;\n }\n | false;\n /** Called when the extension is detected and ready */\n onReady?: () => void;\n /** Called when the extension is installed but Safari still needs activation/allow access */\n onInstalledInactive?: () => void;\n /** Called when the extension is NOT installed */\n onNotInstalled?: () => void;\n}\n\nfunction dispatchInstallState(state: ExtensionInstallState): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n window.dispatchEvent(new CustomEvent('ioswebble:statechange', {\n detail: { state }\n }));\n}\n\n/**\n * Show the install banner unless explicitly disabled.\n * No-op when `options.banner === false`.\n */\nasync function maybeShowBanner(options: BeacioOptions): Promise<void> {\n if (options.banner === false) return;\n const { showInstallBanner } = await import('./banner');\n const bannerConfig = typeof options.banner === 'object' ? options.banner : {};\n const bannerOpts: import('./banner').BannerOptions = {\n ...bannerConfig,\n apiKey: options.key ?? '',\n operatorName: options.operatorName,\n };\n showInstallBanner(bannerOpts);\n}\n\n/**\n * Initialize WebBLE detection.\n *\n * On iOS Safari: checks if the extension is installed, dispatches events,\n * and optionally shows an install banner.\n *\n * On all other platforms: no-op (returns immediately).\n */\nexport async function initBeacio(options: BeacioOptions): Promise<void> {\n const { getExtensionInstallState, isIOSSafari } = await import('./detect');\n\n if (!isIOSSafari()) return;\n\n const installState = await getExtensionInstallState();\n dispatchInstallState(installState);\n\n if (installState === 'active') {\n reportEvent(options.key ?? '', 'extension_active');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('ioswebble:ready'));\n }\n options.onReady?.();\n return;\n }\n\n if (installState === 'installed-inactive') {\n reportEvent(options.key ?? '', 'extension_installed_inactive');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('ioswebble:installedinactive'));\n }\n options.onInstalledInactive?.();\n\n await maybeShowBanner(options);\n return;\n }\n\n // Extension NOT installed\n reportEvent(options.key ?? '', 'detect');\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('ioswebble:notinstalled'));\n }\n options.onNotInstalled?.();\n\n // Show install banner unless explicitly disabled\n await maybeShowBanner(options);\n if (options.banner !== false) {\n reportEvent(options.key ?? '', 'install_prompted');\n }\n}\n"]}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display