{"version":3,"sources":["../src/errors.ts","../src/dataview-helpers.ts"],"names":["CARRY_NATIVE","DEFAULT_RETRY_OPTIONS","SUGGESTIONS","IOS_GRANT_WORDING_CANONICAL","BeacioError","_BeacioError","code","message","options","defaultMessage","RETRIABLE_CODES","error","rawMsg","sanitised","sanitizeNativeMessage","classification","classifyThrown","carried","RETRY_AFTER_MS","withRetry","fn","maxAttempts","delayMs","backoffMultiplier","attempt","normalizedError","nextDelay","resolve","assertReadable","reader","dv","offset","size","readUint8","readUint16LE","readUint16BE","readInt16LE","readUint32LE","readFloat32LE","readUtf8","readBytes"],"mappings":"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","file":"chunk-OFHCNHDO.mjs","sourcesContent":["import {\n  type BeacioErrorCode,\n  classifyThrown,\n  IOS_GRANT_WORDING_CANONICAL,\n  type NativeMessageValue,\n  RETRIABLE_CODES,\n  RETRY_AFTER_MS,\n  sanitizeNativeMessage,\n} from './error-taxonomy';\n\n// The code union, the retriable set, the NotFoundError fragment table and the\n// classifier all live in ./error-taxonomy — the leaf module the branded card\n// (`./detect/error-presenter.ts`) classifies through too, so the SDK and the UI\n// cannot disagree about the same DOMException. Re-exported here because\n// `BeacioErrorCode` is part of this module's published surface (src/index.ts).\nexport type { BeacioErrorCode };\n\n/**\n * Whether a classification's native sentence survives onto `BeacioError.message`.\n * A Record over the union rather than a ternary ON PURPOSE: a third\n * {@link NativeMessageValue} member fails to compile HERE instead of being\n * silently treated as \"drop the native text\", which would make a device's own\n * message vanish from `.message` with no test to catch it.\n */\nconst CARRY_NATIVE: Record<NativeMessageValue, boolean> = {\n  'adds-detail': true,\n  // A dismissed chooser and a bare \"no devices found\" say nothing the per-code\n  // SUGGESTION does not already say — and echoing Chromium's cancellation\n  // wording reads as a failure when none occurred.\n  'restates-the-code': false,\n};\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 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  EXTENSION_NOT_ENABLED: `Beacio is installed but not enabled on this site. In Safari tap aA in the address bar, then Manage Extensions, then beacio, then ${IOS_GRANT_WORDING_CANONICAL}, and reload this page.`,\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/**\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  /**\n   * Convert a native error (DOMException, Error, string) to a BeacioError with\n   * automatic code detection. The DECISION is delegated to the shared\n   * ./error-taxonomy seam (`classifyThrown` takes the thrown value directly —\n   * the same one the branded card classifies through) so this method holds only\n   * what is genuinely SDK-side: which message survives onto `.message`, and the\n   * per-code backoff hint.\n   */\n  static from<T>(error: T, code: BeacioErrorCode = 'GATT_OPERATION_FAILED'): BeacioError {\n    if (error instanceof BeacioError) return error;\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 CLASSIFIER still\n    // inspects the full RAW text (so e.g. \"GATT Server is disconnected\" is detected\n    // even when followed by a stack), but the message that survives onto the\n    // 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 sanitised = sanitizeNativeMessage(rawMsg) || undefined;\n\n    const classification = classifyThrown(error, code);\n    const carried = CARRY_NATIVE[classification.nativeMessage] ? sanitised : undefined;\n    return new BeacioError(classification.code, carried, { retryAfterMs: RETRY_AFTER_MS[classification.code] });\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"]}