@pollen-robotics/reachy-mini-sdk 1.7.3-main.b44388c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/LICENSE +201 -0
  3. package/README.md +129 -0
  4. package/host/APP_AUTHOR_GUIDE.md +646 -0
  5. package/host/README.md +155 -0
  6. package/host/SPEC.md +618 -0
  7. package/host/dist/ReachyHost.d.ts +39 -0
  8. package/host/dist/ReachyHost.d.ts.map +1 -0
  9. package/host/dist/assets/index.d.ts +16 -0
  10. package/host/dist/assets/index.d.ts.map +1 -0
  11. package/host/dist/chunks/index-C3B3FuBC.js +402 -0
  12. package/host/dist/chunks/index-C3B3FuBC.js.map +1 -0
  13. package/host/dist/chunks/mountHost-8f-laxwI.js +48280 -0
  14. package/host/dist/chunks/mountHost-8f-laxwI.js.map +1 -0
  15. package/host/dist/chunks/reachy-mini-sdk-CM-WMoGJ.js +1425 -0
  16. package/host/dist/chunks/reachy-mini-sdk-CM-WMoGJ.js.map +1 -0
  17. package/host/dist/components/ConnectingView.d.ts +13 -0
  18. package/host/dist/components/ConnectingView.d.ts.map +1 -0
  19. package/host/dist/components/EmbedFrame.d.ts +16 -0
  20. package/host/dist/components/EmbedFrame.d.ts.map +1 -0
  21. package/host/dist/components/ErrorView.d.ts +21 -0
  22. package/host/dist/components/ErrorView.d.ts.map +1 -0
  23. package/host/dist/components/LeavingView.d.ts +33 -0
  24. package/host/dist/components/LeavingView.d.ts.map +1 -0
  25. package/host/dist/components/PickerView.d.ts +51 -0
  26. package/host/dist/components/PickerView.d.ts.map +1 -0
  27. package/host/dist/components/ReachyHostShell.d.ts +21 -0
  28. package/host/dist/components/ReachyHostShell.d.ts.map +1 -0
  29. package/host/dist/components/SignInView.d.ts +32 -0
  30. package/host/dist/components/SignInView.d.ts.map +1 -0
  31. package/host/dist/components/StepsProgressIndicator.d.ts +41 -0
  32. package/host/dist/components/StepsProgressIndicator.d.ts.map +1 -0
  33. package/host/dist/components/TopBar.d.ts +52 -0
  34. package/host/dist/components/TopBar.d.ts.map +1 -0
  35. package/host/dist/components/WelcomeBackOverlay.d.ts +28 -0
  36. package/host/dist/components/WelcomeBackOverlay.d.ts.map +1 -0
  37. package/host/dist/embed/index.d.ts +150 -0
  38. package/host/dist/embed/index.d.ts.map +1 -0
  39. package/host/dist/entry/auto.d.ts +12 -0
  40. package/host/dist/entry/auto.d.ts.map +1 -0
  41. package/host/dist/entry/auto.js +7 -0
  42. package/host/dist/entry/auto.js.map +1 -0
  43. package/host/dist/entry/embed.d.ts +12 -0
  44. package/host/dist/entry/embed.d.ts.map +1 -0
  45. package/host/dist/entry/embed.js +7 -0
  46. package/host/dist/entry/embed.js.map +1 -0
  47. package/host/dist/hooks/useHfProfile.d.ts +7 -0
  48. package/host/dist/hooks/useHfProfile.d.ts.map +1 -0
  49. package/host/dist/hooks/useHostBridge.d.ts +37 -0
  50. package/host/dist/hooks/useHostBridge.d.ts.map +1 -0
  51. package/host/dist/hooks/useOAuth.d.ts +16 -0
  52. package/host/dist/hooks/useOAuth.d.ts.map +1 -0
  53. package/host/dist/hooks/useRobots.d.ts +17 -0
  54. package/host/dist/hooks/useRobots.d.ts.map +1 -0
  55. package/host/dist/hooks/useSdk.d.ts +16 -0
  56. package/host/dist/hooks/useSdk.d.ts.map +1 -0
  57. package/host/dist/index.d.ts +25 -0
  58. package/host/dist/index.d.ts.map +1 -0
  59. package/host/dist/index.js +15 -0
  60. package/host/dist/index.js.map +1 -0
  61. package/host/dist/lib/centralListener.d.ts +73 -0
  62. package/host/dist/lib/centralListener.d.ts.map +1 -0
  63. package/host/dist/lib/centralRest.d.ts +35 -0
  64. package/host/dist/lib/centralRest.d.ts.map +1 -0
  65. package/host/dist/lib/protocol.d.ts +230 -0
  66. package/host/dist/lib/protocol.d.ts.map +1 -0
  67. package/host/dist/lib/protocol.js +48 -0
  68. package/host/dist/lib/protocol.js.map +1 -0
  69. package/host/dist/lib/sdk-types.d.ts +46 -0
  70. package/host/dist/lib/sdk-types.d.ts.map +1 -0
  71. package/host/dist/lib/settings.d.ts +69 -0
  72. package/host/dist/lib/settings.d.ts.map +1 -0
  73. package/host/dist/lib/signalingUrl.d.ts +28 -0
  74. package/host/dist/lib/signalingUrl.d.ts.map +1 -0
  75. package/host/dist/lib/theme.d.ts +4 -0
  76. package/host/dist/lib/theme.d.ts.map +1 -0
  77. package/host/dist/lib/themeMode.d.ts +9 -0
  78. package/host/dist/lib/themeMode.d.ts.map +1 -0
  79. package/host/dist/lib/tokens.d.ts +64 -0
  80. package/host/dist/lib/tokens.d.ts.map +1 -0
  81. package/host/dist/mountHost.d.ts +36 -0
  82. package/host/dist/mountHost.d.ts.map +1 -0
  83. package/package.json +109 -0
  84. package/reachy-mini-sdk.d.ts +285 -0
  85. package/reachy-mini-sdk.js +2164 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reachy-mini-sdk-CM-WMoGJ.js","sources":["../../../node_modules/@huggingface/hub/dist/browser/chunk-NHABU752.mjs","../../../node_modules/@huggingface/hub/dist/browser/index.mjs","../../../reachy-mini-sdk.js"],"sourcesContent":["var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => {\n __defNormalProp(obj, typeof key !== \"symbol\" ? key + \"\" : key, value);\n return value;\n};\n\nexport {\n __publicField\n};\n","import {\n __publicField\n} from \"./chunk-NHABU752.mjs\";\n\n// src/consts.ts\nvar HUB_URL = \"https://huggingface.co\";\n\n// src/error.ts\nasync function createApiError(response, opts) {\n var _a, _b;\n const error = new HubApiError(response.url, response.status, (_a = response.headers.get(\"X-Request-Id\")) != null ? _a : opts == null ? void 0 : opts.requestId);\n error.message = `Api error with status ${error.statusCode}${(opts == null ? void 0 : opts.message) ? `. ${opts.message}` : \"\"}`;\n const trailer = [`URL: ${error.url}`, error.requestId ? `Request ID: ${error.requestId}` : void 0].filter(Boolean).join(\". \");\n if ((_b = response.headers.get(\"Content-Type\")) == null ? void 0 : _b.startsWith(\"application/json\")) {\n const json = await response.json();\n error.message = json.error || json.message || error.message;\n error.data = json;\n } else {\n error.data = { message: await response.text() };\n }\n error.message += `. ${trailer}`;\n throw error;\n}\nvar HubApiError = class extends Error {\n constructor(url, statusCode, requestId, message) {\n super(message);\n __publicField(this, \"statusCode\");\n __publicField(this, \"url\");\n __publicField(this, \"requestId\");\n __publicField(this, \"data\");\n this.statusCode = statusCode;\n this.requestId = requestId;\n this.url = url;\n }\n};\nvar InvalidApiResponseFormatError = class extends Error {\n};\n\n// src/utils/checkCredentials.ts\nfunction checkCredentials(credentials) {\n if (!credentials || credentials.accessToken === void 0 || credentials.accessToken === null) {\n return;\n }\n if (!credentials.accessToken.startsWith(\"hf_\")) {\n throw new TypeError(\"Your access token must start with 'hf_'\");\n }\n}\n\n// src/utils/range.ts\nfunction range(n, b) {\n return b ? Array(b - n).fill(0).map((_, i) => n + i) : Array(n).fill(0).map((_, i) => i);\n}\n\n// src/utils/chunk.ts\nfunction chunk(arr, chunkSize) {\n if (isNaN(chunkSize) || chunkSize < 1) {\n throw new RangeError(\"Invalid chunk size: \" + chunkSize);\n }\n if (!arr.length) {\n return [];\n }\n if (arr.length <= chunkSize) {\n return [arr];\n }\n return range(Math.ceil(arr.length / chunkSize)).map((i) => {\n return arr.slice(i * chunkSize, (i + 1) * chunkSize);\n });\n}\n\n// src/utils/promisesQueue.ts\nasync function promisesQueue(factories, concurrency) {\n const results = [];\n const executing = /* @__PURE__ */ new Set();\n let index = 0;\n for (const factory of factories) {\n const closureIndex = index++;\n const e = factory().then((r) => {\n results[closureIndex] = r;\n executing.delete(e);\n });\n executing.add(e);\n if (executing.size >= concurrency) {\n await Promise.race(executing);\n }\n }\n await Promise.all(executing);\n return results;\n}\n\n// src/utils/promisesQueueStreaming.ts\nasync function promisesQueueStreaming(factories, concurrency) {\n const executing = [];\n for await (const factory of factories) {\n const e = factory().then(() => {\n executing.splice(executing.indexOf(e), 1);\n });\n executing.push(e);\n if (executing.length >= concurrency) {\n await Promise.race(executing);\n }\n }\n await Promise.all(executing);\n}\n\n// src/utils/eventToGenerator.ts\nasync function* eventToGenerator(cb) {\n const promises = [];\n function addPromise() {\n let resolve2;\n let reject;\n const p = new Promise((res, rej) => {\n resolve2 = res;\n reject = rej;\n });\n promises.push({ p, resolve: resolve2, reject });\n }\n addPromise();\n const callbackRes = Promise.resolve().then(\n () => cb(\n (y) => {\n var _a;\n addPromise();\n (_a = promises.at(-2)) == null ? void 0 : _a.resolve({ done: false, value: y });\n },\n (r) => {\n var _a;\n addPromise();\n (_a = promises.at(-2)) == null ? void 0 : _a.resolve({ done: true, value: r });\n },\n (err) => {\n var _a;\n return (_a = promises.shift()) == null ? void 0 : _a.reject(err);\n }\n )\n ).catch((err) => {\n var _a;\n return (_a = promises.shift()) == null ? void 0 : _a.reject(err);\n });\n while (1) {\n const p = promises[0];\n if (!p) {\n throw new Error(\"Logic error in eventGenerator, promises should never be empty\");\n }\n const result = await p.p;\n promises.shift();\n if (result.done) {\n await callbackRes;\n return result.value;\n }\n yield result.value;\n }\n throw new Error(\"Unreachable\");\n}\n\n// src/utils/hexFromBytes.ts\nfunction hexFromBytes(arr) {\n if (globalThis.Buffer) {\n return globalThis.Buffer.from(arr).toString(\"hex\");\n } else {\n const bin = [];\n arr.forEach((byte) => {\n bin.push(byte.toString(16).padStart(2, \"0\"));\n });\n return bin.join(\"\");\n }\n}\n\n// src/utils/isBackend.ts\nvar isBrowser = typeof window !== \"undefined\" && typeof window.document !== \"undefined\";\nvar isWebWorker = typeof self === \"object\" && self.constructor && self.constructor.name === \"DedicatedWorkerGlobalScope\";\nvar isBackend = !isBrowser && !isWebWorker;\n\n// src/utils/isFrontend.ts\nvar isFrontend = !isBackend;\n\n// src/utils/sha256.ts\nasync function getWebWorkerCode() {\n const sha256Module = await import(\"./sha256-wrapper-2GO3XT7J.mjs\");\n return URL.createObjectURL(new Blob([sha256Module.createSHA256WorkerCode()]));\n}\nvar pendingWorkers = [];\nvar runningWorkers = /* @__PURE__ */ new Set();\nvar resolve;\nvar waitPromise = new Promise((r) => {\n resolve = r;\n});\nasync function getWorker(poolSize) {\n {\n const worker2 = pendingWorkers.pop();\n if (worker2) {\n runningWorkers.add(worker2);\n return worker2;\n }\n }\n if (!poolSize) {\n const worker2 = new Worker(await getWebWorkerCode());\n runningWorkers.add(worker2);\n return worker2;\n }\n if (poolSize <= 0) {\n throw new TypeError(\"Invalid webworker pool size: \" + poolSize);\n }\n while (runningWorkers.size >= poolSize) {\n await waitPromise;\n }\n const worker = new Worker(await getWebWorkerCode());\n runningWorkers.add(worker);\n return worker;\n}\nasync function freeWorker(worker, poolSize) {\n if (!poolSize) {\n return destroyWorker(worker);\n }\n runningWorkers.delete(worker);\n pendingWorkers.push(worker);\n const r = resolve;\n waitPromise = new Promise((r2) => {\n resolve = r2;\n });\n r();\n}\nfunction destroyWorker(worker) {\n runningWorkers.delete(worker);\n worker.terminate();\n const r = resolve;\n waitPromise = new Promise((r2) => {\n resolve = r2;\n });\n r();\n}\nasync function* sha256(buffer, opts) {\n var _a, _b;\n yield 0;\n const maxCryptoSize = typeof (opts == null ? void 0 : opts.useWebWorker) === \"object\" && (opts == null ? void 0 : opts.useWebWorker.minSize) !== void 0 ? opts.useWebWorker.minSize : 1e7;\n if (buffer.size < maxCryptoSize && ((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) {\n const res = hexFromBytes(\n new Uint8Array(\n await globalThis.crypto.subtle.digest(\"SHA-256\", buffer instanceof Blob ? await buffer.arrayBuffer() : buffer)\n )\n );\n yield 1;\n return res;\n }\n if (isFrontend) {\n if (opts == null ? void 0 : opts.useWebWorker) {\n try {\n const poolSize = typeof (opts == null ? void 0 : opts.useWebWorker) === \"object\" ? opts.useWebWorker.poolSize : void 0;\n const worker = await getWorker(poolSize);\n return yield* eventToGenerator((yieldCallback, returnCallback, rejectCallack) => {\n worker.addEventListener(\"message\", (event) => {\n var _a2;\n if (event.data.sha256) {\n freeWorker(worker, poolSize);\n returnCallback(event.data.sha256);\n } else if (event.data.progress) {\n yieldCallback(event.data.progress);\n try {\n (_a2 = opts.abortSignal) == null ? void 0 : _a2.throwIfAborted();\n } catch (err) {\n destroyWorker(worker);\n rejectCallack(err);\n }\n } else {\n destroyWorker(worker);\n rejectCallack(event);\n }\n });\n worker.addEventListener(\"error\", (event) => {\n destroyWorker(worker);\n rejectCallack(event.error);\n });\n worker.postMessage({ file: buffer });\n });\n } catch (err) {\n console.warn(\"Failed to use web worker for sha256\", err);\n }\n }\n if (!wasmModule) {\n wasmModule = await import(\"./sha256-wrapper-2GO3XT7J.mjs\");\n }\n const sha2562 = await wasmModule.createSHA256();\n sha2562.init();\n const reader = buffer.stream().getReader();\n const total = buffer.size;\n let bytesDone = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n sha2562.update(value);\n bytesDone += value.length;\n yield bytesDone / total;\n (_b = opts == null ? void 0 : opts.abortSignal) == null ? void 0 : _b.throwIfAborted();\n }\n return sha2562.digest(\"hex\");\n }\n if (!cryptoModule) {\n cryptoModule = await import(\"./sha256-node-TNZ2WHTI.mjs\");\n }\n return yield* cryptoModule.sha256Node(buffer, { abortSignal: opts == null ? void 0 : opts.abortSignal });\n}\nvar cryptoModule;\nvar wasmModule;\n\n// src/utils/toRepoId.ts\nfunction toRepoId(repo) {\n if (typeof repo !== \"string\") {\n return repo;\n }\n if (repo.startsWith(\"model/\") || repo.startsWith(\"models/\")) {\n throw new TypeError(\n \"A repo designation for a model should not start with 'models/', directly specify the model namespace / name\"\n );\n }\n if (repo.startsWith(\"space/\")) {\n throw new TypeError(\"Spaces should start with 'spaces/', plural, not 'space/'\");\n }\n if (repo.startsWith(\"dataset/\")) {\n throw new TypeError(\"Datasets should start with 'dataset/', plural, not 'dataset/'\");\n }\n const slashes = repo.split(\"/\").length - 1;\n if (repo.startsWith(\"spaces/\")) {\n if (slashes !== 2) {\n throw new TypeError(\"Space Id must include namespace and name of the space\");\n }\n return {\n type: \"space\",\n name: repo.slice(\"spaces/\".length)\n };\n }\n if (repo.startsWith(\"datasets/\")) {\n if (slashes > 2) {\n throw new TypeError(\"Too many slashes in repo designation: \" + repo);\n }\n return {\n type: \"dataset\",\n name: repo.slice(\"datasets/\".length)\n };\n }\n if (slashes > 1) {\n throw new TypeError(\"Too many slashes in repo designation: \" + repo);\n }\n return {\n type: \"model\",\n name: repo\n };\n}\n\n// src/utils/WebBlob.ts\nvar WebBlob = class extends Blob {\n constructor(url, start, end, contentType, full, customFetch) {\n super([]);\n __publicField(this, \"url\");\n __publicField(this, \"start\");\n __publicField(this, \"end\");\n __publicField(this, \"contentType\");\n __publicField(this, \"full\");\n __publicField(this, \"fetch\");\n this.url = url;\n this.start = start;\n this.end = end;\n this.contentType = contentType;\n this.full = full;\n this.fetch = customFetch;\n }\n static async create(url, opts) {\n var _a, _b;\n const customFetch = (_a = opts == null ? void 0 : opts.fetch) != null ? _a : fetch;\n const response = await customFetch(url, { method: \"HEAD\" });\n const size = Number(response.headers.get(\"content-length\"));\n const contentType = response.headers.get(\"content-type\") || \"\";\n const supportRange = response.headers.get(\"accept-ranges\") === \"bytes\";\n if (!supportRange || size < ((_b = opts == null ? void 0 : opts.cacheBelow) != null ? _b : 1e6)) {\n return await (await customFetch(url)).blob();\n }\n return new WebBlob(url, 0, size, contentType, true, customFetch);\n }\n get size() {\n return this.end - this.start;\n }\n get type() {\n return this.contentType;\n }\n slice(start = 0, end = this.size) {\n if (start < 0 || end < 0) {\n new TypeError(\"Unsupported negative start/end on FileBlob.slice\");\n }\n const slice = new WebBlob(\n this.url,\n this.start + start,\n Math.min(this.start + end, this.end),\n this.contentType,\n start === 0 && end === this.size ? this.full : false,\n this.fetch\n );\n return slice;\n }\n async arrayBuffer() {\n const result = await this.fetchRange();\n return result.arrayBuffer();\n }\n async text() {\n const result = await this.fetchRange();\n return result.text();\n }\n stream() {\n const stream = new TransformStream();\n this.fetchRange().then((response) => {\n var _a;\n return (_a = response.body) == null ? void 0 : _a.pipeThrough(stream);\n }).catch((error) => stream.writable.abort(error.message));\n return stream.readable;\n }\n fetchRange() {\n const fetch2 = this.fetch;\n if (this.full) {\n return fetch2(this.url);\n }\n return fetch2(this.url, {\n headers: {\n Range: `bytes=${this.start}-${this.end - 1}`\n }\n });\n }\n};\n\n// src/utils/createBlob.ts\nasync function createBlob(url, opts) {\n if (url.protocol === \"http:\" || url.protocol === \"https:\") {\n return WebBlob.create(url, { fetch: opts == null ? void 0 : opts.fetch });\n }\n if (isFrontend) {\n throw new TypeError(`Unsupported URL protocol \"${url.protocol}\"`);\n }\n if (url.protocol === \"file:\") {\n const { FileBlob } = await import(\"./FileBlob-7MRLQ6TG.mjs\");\n return FileBlob.create(url);\n }\n throw new TypeError(`Unsupported URL protocol \"${url.protocol}\"`);\n}\n\n// src/utils/base64FromBytes.ts\nfunction base64FromBytes(arr) {\n if (globalThis.Buffer) {\n return globalThis.Buffer.from(arr).toString(\"base64\");\n } else {\n const bin = [];\n arr.forEach((byte) => {\n bin.push(String.fromCharCode(byte));\n });\n return globalThis.btoa(bin.join(\"\"));\n }\n}\n\n// src/lib/commit.ts\nvar CONCURRENT_SHAS = 5;\nvar CONCURRENT_LFS_UPLOADS = 5;\nvar MULTIPART_PARALLEL_UPLOAD = 5;\nfunction isFileOperation(op) {\n const ret = op.operation === \"addOrUpdate\";\n if (ret && !(op.content instanceof Blob)) {\n throw new TypeError(\"Precondition failed: op.content should be a Blob\");\n }\n return ret;\n}\nasync function* commitIter(params) {\n var _a, _b, _c, _d, _e, _f, _g;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n yield { event: \"phase\", phase: \"preuploading\" };\n const lfsShas = /* @__PURE__ */ new Map();\n const abortController = new AbortController();\n const abortSignal = abortController.signal;\n if (!abortSignal.throwIfAborted) {\n abortSignal.throwIfAborted = () => {\n if (abortSignal.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n };\n }\n if (params.abortSignal) {\n params.abortSignal.addEventListener(\"abort\", () => abortController.abort());\n }\n try {\n const allOperations = await Promise.all(\n params.operations.map(async (operation) => {\n if (operation.operation !== \"addOrUpdate\") {\n return operation;\n }\n if (!(operation.content instanceof URL)) {\n return { ...operation, content: operation.content };\n }\n const lazyBlob = await createBlob(operation.content, { fetch: params.fetch });\n abortSignal == null ? void 0 : abortSignal.throwIfAborted();\n return {\n ...operation,\n content: lazyBlob\n };\n })\n );\n const gitAttributes = (_a = allOperations.filter(isFileOperation).find((op) => op.path === \".gitattributes\")) == null ? void 0 : _a.content;\n for (const operations of chunk(allOperations.filter(isFileOperation), 100)) {\n const payload = {\n gitAttributes: gitAttributes && await gitAttributes.text(),\n files: await Promise.all(\n operations.map(async (operation) => ({\n path: operation.path,\n size: operation.content.size,\n sample: base64FromBytes(new Uint8Array(await operation.content.slice(0, 512).arrayBuffer()))\n }))\n )\n };\n abortSignal == null ? void 0 : abortSignal.throwIfAborted();\n const res = await ((_b = params.fetch) != null ? _b : fetch)(\n `${(_c = params.hubUrl) != null ? _c : HUB_URL}/api/${repoId.type}s/${repoId.name}/preupload/${encodeURIComponent(\n (_d = params.branch) != null ? _d : \"main\"\n )}` + (params.isPullRequest ? \"?create_pr=1\" : \"\"),\n {\n method: \"POST\",\n headers: {\n ...params.credentials && { Authorization: `Bearer ${params.credentials.accessToken}` },\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(payload),\n signal: abortSignal\n }\n );\n if (!res.ok) {\n throw await createApiError(res);\n }\n const json = await res.json();\n for (const file of json.files) {\n if (file.uploadMode === \"lfs\") {\n lfsShas.set(file.path, null);\n }\n }\n }\n yield { event: \"phase\", phase: \"uploadingLargeFiles\" };\n for (const operations of chunk(\n allOperations.filter(isFileOperation).filter((op) => lfsShas.has(op.path)),\n 100\n )) {\n const shas = yield* eventToGenerator((yieldCallback, returnCallback, rejectCallack) => {\n return promisesQueue(\n operations.map((op) => async () => {\n const iterator = sha256(op.content, { useWebWorker: params.useWebWorkers, abortSignal });\n let res2;\n do {\n res2 = await iterator.next();\n if (!res2.done) {\n yieldCallback({ event: \"fileProgress\", path: op.path, progress: res2.value, state: \"hashing\" });\n }\n } while (!res2.done);\n const sha = res2.value;\n lfsShas.set(op.path, res2.value);\n return sha;\n }),\n CONCURRENT_SHAS\n ).then(returnCallback, rejectCallack);\n });\n abortSignal == null ? void 0 : abortSignal.throwIfAborted();\n const payload = {\n operation: \"upload\",\n // multipart is a custom protocol for HF\n transfers: [\"basic\", \"multipart\"],\n hash_algo: \"sha_256\",\n ...!params.isPullRequest && {\n ref: {\n name: (_e = params.branch) != null ? _e : \"main\"\n }\n },\n objects: operations.map((op, i) => ({\n oid: shas[i],\n size: op.content.size\n }))\n };\n const res = await ((_f = params.fetch) != null ? _f : fetch)(\n `${(_g = params.hubUrl) != null ? _g : HUB_URL}/${repoId.type === \"model\" ? \"\" : repoId.type + \"s/\"}${repoId.name}.git/info/lfs/objects/batch`,\n {\n method: \"POST\",\n headers: {\n ...params.credentials && { Authorization: `Bearer ${params.credentials.accessToken}` },\n Accept: \"application/vnd.git-lfs+json\",\n \"Content-Type\": \"application/vnd.git-lfs+json\"\n },\n body: JSON.stringify(payload),\n signal: abortSignal\n }\n );\n if (!res.ok) {\n throw await createApiError(res);\n }\n const json = await res.json();\n const batchRequestId = res.headers.get(\"X-Request-Id\") || void 0;\n const shaToOperation = new Map(operations.map((op, i) => [shas[i], op]));\n yield* eventToGenerator((yieldCallback, returnCallback, rejectCallback) => {\n return promisesQueueStreaming(\n json.objects.map((obj) => async () => {\n var _a2, _b2, _c2;\n const op = shaToOperation.get(obj.oid);\n if (!op) {\n throw new InvalidApiResponseFormatError(\"Unrequested object ID in response\");\n }\n abortSignal == null ? void 0 : abortSignal.throwIfAborted();\n if (obj.error) {\n const errorMessage = `Error while doing LFS batch call for ${operations[shas.indexOf(obj.oid)].path}: ${obj.error.message}${batchRequestId ? ` - Request ID: ${batchRequestId}` : \"\"}`;\n throw new HubApiError(res.url, obj.error.code, batchRequestId, errorMessage);\n }\n if (!((_a2 = obj.actions) == null ? void 0 : _a2.upload)) {\n yieldCallback({\n event: \"fileProgress\",\n path: op.path,\n progress: 1,\n state: \"uploading\"\n });\n return;\n }\n yieldCallback({\n event: \"fileProgress\",\n path: op.path,\n progress: 0,\n state: \"uploading\"\n });\n const content = op.content;\n const header = obj.actions.upload.header;\n if (header == null ? void 0 : header.chunk_size) {\n const chunkSize = parseInt(header.chunk_size);\n const completionUrl = obj.actions.upload.href;\n const parts = Object.keys(header).filter((key) => /^[0-9]+$/.test(key));\n if (parts.length !== Math.ceil(content.size / chunkSize)) {\n throw new Error(\"Invalid server response to upload large LFS file, wrong number of parts\");\n }\n const completeReq = {\n oid: obj.oid,\n parts: parts.map((part) => ({\n partNumber: +part,\n etag: \"\"\n }))\n };\n const progressCallback = (progress) => yieldCallback({ event: \"fileProgress\", path: op.path, progress, state: \"uploading\" });\n await promisesQueueStreaming(\n parts.map((part) => async () => {\n var _a3;\n abortSignal == null ? void 0 : abortSignal.throwIfAborted();\n const index = parseInt(part) - 1;\n const slice = content.slice(index * chunkSize, (index + 1) * chunkSize);\n const res3 = await ((_a3 = params.fetch) != null ? _a3 : fetch)(header[part], {\n method: \"PUT\",\n /** Unfortunately, browsers don't support our inherited version of Blob in fetch calls */\n body: slice instanceof WebBlob && isFrontend ? await slice.arrayBuffer() : slice,\n signal: abortSignal,\n ...{\n progressHint: {\n path: op.path,\n part: index,\n numParts: parts.length,\n progressCallback\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n }\n });\n if (!res3.ok) {\n throw await createApiError(res3, {\n requestId: batchRequestId,\n message: `Error while uploading part ${part} of ${operations[shas.indexOf(obj.oid)].path} to LFS storage`\n });\n }\n const eTag = res3.headers.get(\"ETag\");\n if (!eTag) {\n throw new Error(\"Cannot get ETag of part during multipart upload\");\n }\n completeReq.parts[Number(part) - 1].etag = eTag;\n }),\n MULTIPART_PARALLEL_UPLOAD\n );\n abortSignal == null ? void 0 : abortSignal.throwIfAborted();\n const res2 = await ((_b2 = params.fetch) != null ? _b2 : fetch)(completionUrl, {\n method: \"POST\",\n body: JSON.stringify(completeReq),\n headers: {\n Accept: \"application/vnd.git-lfs+json\",\n \"Content-Type\": \"application/vnd.git-lfs+json\"\n },\n signal: abortSignal\n });\n if (!res2.ok) {\n throw await createApiError(res2, {\n requestId: batchRequestId,\n message: `Error completing multipart upload of ${operations[shas.indexOf(obj.oid)].path} to LFS storage`\n });\n }\n yieldCallback({\n event: \"fileProgress\",\n path: op.path,\n progress: 1,\n state: \"uploading\"\n });\n } else {\n const res2 = await ((_c2 = params.fetch) != null ? _c2 : fetch)(obj.actions.upload.href, {\n method: \"PUT\",\n headers: {\n ...batchRequestId ? { \"X-Request-Id\": batchRequestId } : void 0\n },\n /** Unfortunately, browsers don't support our inherited version of Blob in fetch calls */\n body: content instanceof WebBlob && isFrontend ? await content.arrayBuffer() : content,\n signal: abortSignal,\n ...{\n progressHint: {\n path: op.path,\n progressCallback: (progress) => yieldCallback({\n event: \"fileProgress\",\n path: op.path,\n progress,\n state: \"uploading\"\n })\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n }\n });\n if (!res2.ok) {\n throw await createApiError(res2, {\n requestId: batchRequestId,\n message: `Error while uploading ${operations[shas.indexOf(obj.oid)].path} to LFS storage`\n });\n }\n yieldCallback({\n event: \"fileProgress\",\n path: op.path,\n progress: 1,\n state: \"uploading\"\n });\n }\n }),\n CONCURRENT_LFS_UPLOADS\n ).then(returnCallback, rejectCallback);\n });\n }\n abortSignal == null ? void 0 : abortSignal.throwIfAborted();\n yield { event: \"phase\", phase: \"committing\" };\n return yield* eventToGenerator(\n async (yieldCallback, returnCallback, rejectCallback) => {\n var _a2, _b2, _c2;\n return ((_a2 = params.fetch) != null ? _a2 : fetch)(\n `${(_b2 = params.hubUrl) != null ? _b2 : HUB_URL}/api/${repoId.type}s/${repoId.name}/commit/${encodeURIComponent(\n (_c2 = params.branch) != null ? _c2 : \"main\"\n )}` + (params.isPullRequest ? \"?create_pr=1\" : \"\"),\n {\n method: \"POST\",\n headers: {\n ...params.credentials && { Authorization: `Bearer ${params.credentials.accessToken}` },\n \"Content-Type\": \"application/x-ndjson\"\n },\n body: [\n {\n key: \"header\",\n value: {\n summary: params.title,\n description: params.description,\n parentCommit: params.parentCommit\n }\n },\n ...await Promise.all(\n allOperations.map((operation) => {\n if (isFileOperation(operation)) {\n const sha = lfsShas.get(operation.path);\n if (sha) {\n return {\n key: \"lfsFile\",\n value: {\n path: operation.path,\n algo: \"sha256\",\n size: operation.content.size,\n oid: sha\n }\n };\n }\n }\n return convertOperationToNdJson(operation);\n })\n )\n ].map((x) => JSON.stringify(x)).join(\"\\n\"),\n signal: abortSignal,\n ...{\n progressHint: {\n progressCallback: (progress) => {\n for (const op of allOperations) {\n if (isFileOperation(op) && !lfsShas.has(op.path)) {\n yieldCallback({\n event: \"fileProgress\",\n path: op.path,\n progress,\n state: \"uploading\"\n });\n }\n }\n }\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n }\n }\n ).then(async (res) => {\n if (!res.ok) {\n throw await createApiError(res);\n }\n const json = await res.json();\n returnCallback({\n pullRequestUrl: json.pullRequestUrl,\n commit: {\n oid: json.commitOid,\n url: json.commitUrl\n },\n hookOutput: json.hookOutput\n });\n }).catch(rejectCallback);\n }\n );\n } catch (err) {\n abortController.abort();\n throw err;\n }\n}\nasync function commit(params) {\n const iterator = commitIter(params);\n let res = await iterator.next();\n while (!res.done) {\n res = await iterator.next();\n }\n return res.value;\n}\nasync function convertOperationToNdJson(operation) {\n switch (operation.operation) {\n case \"addOrUpdate\": {\n return {\n key: \"file\",\n value: {\n content: base64FromBytes(new Uint8Array(await operation.content.arrayBuffer())),\n path: operation.path,\n encoding: \"base64\"\n }\n };\n }\n case \"delete\": {\n return {\n key: \"deletedFile\",\n value: {\n path: operation.path\n }\n };\n }\n default:\n throw new TypeError(\"Unknown operation: \" + operation.operation);\n }\n}\n\n// src/lib/count-commits.ts\nasync function countCommits(params) {\n var _a, _b, _c, _d;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n const url = `${(_a = params.hubUrl) != null ? _a : HUB_URL}/api/${repoId.type}s/${repoId.name}/commits/${(_b = params.revision) != null ? _b : \"main\"}?limit=1`;\n const res = await ((_c = params.fetch) != null ? _c : fetch)(url, {\n headers: params.credentials ? { Authorization: `Bearer ${params.credentials.accessToken}` } : {}\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n return parseInt((_d = res.headers.get(\"x-total-count\")) != null ? _d : \"0\", 10);\n}\n\n// src/lib/create-repo.ts\nasync function createRepo(params) {\n var _a, _b;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n const [namespace, repoName] = repoId.name.split(\"/\");\n if (!namespace || !repoName) {\n throw new TypeError(\n `\"${repoId.name}\" is not a fully qualified repo name. It should be of the form \"{namespace}/{repoName}\".`\n );\n }\n const res = await ((_a = params.fetch) != null ? _a : fetch)(`${(_b = params.hubUrl) != null ? _b : HUB_URL}/api/repos/create`, {\n method: \"POST\",\n body: JSON.stringify({\n name: repoName,\n private: params.private,\n organization: namespace,\n license: params.license,\n ...repoId.type === \"space\" ? {\n type: \"space\",\n sdk: \"static\"\n } : {\n type: repoId.type\n },\n files: params.files ? await Promise.all(\n params.files.map(async (file) => ({\n encoding: \"base64\",\n path: file.path,\n content: base64FromBytes(\n new Uint8Array(file.content instanceof Blob ? await file.content.arrayBuffer() : file.content)\n )\n }))\n ) : void 0\n }),\n headers: {\n Authorization: `Bearer ${params.credentials.accessToken}`,\n \"Content-Type\": \"application/json\"\n }\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n const output = await res.json();\n return { repoUrl: output.url };\n}\n\n// src/lib/delete-file.ts\nfunction deleteFile(params) {\n var _a;\n return commit({\n credentials: params.credentials,\n repo: params.repo,\n operations: [\n {\n operation: \"delete\",\n path: params.path\n }\n ],\n title: (_a = params.commitTitle) != null ? _a : `Delete ${params.path}`,\n description: params.commitDescription,\n hubUrl: params.hubUrl,\n branch: params.branch,\n isPullRequest: params.isPullRequest,\n parentCommit: params.parentCommit,\n fetch: params.fetch\n });\n}\n\n// src/lib/delete-files.ts\nfunction deleteFiles(params) {\n var _a;\n return commit({\n credentials: params.credentials,\n repo: params.repo,\n operations: params.paths.map((path) => ({\n operation: \"delete\",\n path\n })),\n title: (_a = params.commitTitle) != null ? _a : `Deletes ${params.paths.length} files`,\n description: params.commitDescription,\n hubUrl: params.hubUrl,\n branch: params.branch,\n isPullRequest: params.isPullRequest,\n parentCommit: params.parentCommit,\n fetch: params.fetch\n });\n}\n\n// src/lib/delete-repo.ts\nasync function deleteRepo(params) {\n var _a, _b;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n const [namespace, repoName] = repoId.name.split(\"/\");\n const res = await ((_a = params.fetch) != null ? _a : fetch)(`${(_b = params.hubUrl) != null ? _b : HUB_URL}/api/repos/delete`, {\n method: \"DELETE\",\n body: JSON.stringify({\n name: repoName,\n organization: namespace,\n type: repoId.type\n }),\n headers: {\n Authorization: `Bearer ${params.credentials.accessToken}`,\n \"Content-Type\": \"application/json\"\n }\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n}\n\n// src/lib/download-file.ts\nasync function downloadFile(params) {\n var _a, _b, _c;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n const url = `${(_a = params.hubUrl) != null ? _a : HUB_URL}/${repoId.type === \"model\" ? \"\" : `${repoId.type}s/`}${repoId.name}/${params.raw ? \"raw\" : \"resolve\"}/${encodeURIComponent((_b = params.revision) != null ? _b : \"main\")}/${params.path}`;\n const resp = await ((_c = params.fetch) != null ? _c : fetch)(url, {\n headers: {\n ...params.credentials ? {\n Authorization: `Bearer ${params.credentials.accessToken}`\n } : {},\n ...params.range ? {\n Range: `bytes=${params.range[0]}-${params.range[1]}`\n } : {}\n }\n });\n if (resp.status === 404 && resp.headers.get(\"X-Error-Code\") === \"EntryNotFound\") {\n return null;\n } else if (!resp.ok) {\n throw await createApiError(resp);\n }\n return resp;\n}\n\n// src/lib/file-download-info.ts\nasync function fileDownloadInfo(params) {\n var _a, _b, _c;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n const hubUrl = (_a = params.hubUrl) != null ? _a : HUB_URL;\n const url = `${hubUrl}/${repoId.type === \"model\" ? \"\" : `${repoId.type}s/`}${repoId.name}/${params.raw ? \"raw\" : \"resolve\"}/${encodeURIComponent((_b = params.revision) != null ? _b : \"main\")}/${params.path}` + (params.noContentDisposition ? \"?noContentDisposition=1\" : \"\");\n const resp = await ((_c = params.fetch) != null ? _c : fetch)(url, {\n method: \"GET\",\n headers: {\n ...params.credentials && {\n Authorization: `Bearer ${params.credentials.accessToken}`\n },\n Range: \"bytes=0-0\"\n }\n });\n if (resp.status === 404 && resp.headers.get(\"X-Error-Code\") === \"EntryNotFound\") {\n return null;\n }\n if (!resp.ok) {\n throw await createApiError(resp);\n }\n const etag = resp.headers.get(\"ETag\");\n if (!etag) {\n throw new InvalidApiResponseFormatError(\"Expected ETag\");\n }\n const contentRangeHeader = resp.headers.get(\"content-range\");\n if (!contentRangeHeader) {\n throw new InvalidApiResponseFormatError(\"Expected size information\");\n }\n const [, parsedSize] = contentRangeHeader.split(\"/\");\n const size = parseInt(parsedSize);\n if (isNaN(size)) {\n throw new InvalidApiResponseFormatError(\"Invalid file size received\");\n }\n return {\n etag,\n size,\n downloadLink: new URL(resp.url).hostname !== new URL(hubUrl).hostname ? resp.url : null\n };\n}\n\n// src/lib/file-exists.ts\nasync function fileExists(params) {\n var _a, _b, _c;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n const hubUrl = (_a = params.hubUrl) != null ? _a : HUB_URL;\n const url = `${hubUrl}/${repoId.type === \"model\" ? \"\" : `${repoId.type}s/`}${repoId.name}/raw/${encodeURIComponent(\n (_b = params.revision) != null ? _b : \"main\"\n )}/${params.path}`;\n const resp = await ((_c = params.fetch) != null ? _c : fetch)(url, {\n method: \"HEAD\",\n headers: params.credentials ? { Authorization: `Bearer ${params.credentials.accessToken}` } : {}\n });\n if (resp.status === 404) {\n return false;\n }\n if (!resp.ok) {\n throw await createApiError(resp);\n }\n return true;\n}\n\n// src/utils/parseLinkHeader.ts\nfunction parseLinkHeader(header) {\n const regex = /<(https?:[/][/][^>]+)>;\\s+rel=\"([^\"]+)\"/g;\n return Object.fromEntries([...header.matchAll(regex)].map(([, url, rel]) => [rel, url]));\n}\n\n// src/lib/list-commits.ts\nasync function* listCommits(params) {\n var _a, _b, _c, _d;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n let url = `${(_a = params.hubUrl) != null ? _a : HUB_URL}/api/${repoId.type}s/${repoId.name}/commits/${(_b = params.revision) != null ? _b : \"main\"}?limit=${(_c = params.batchSize) != null ? _c : 100}`;\n while (url) {\n const res = await ((_d = params.fetch) != null ? _d : fetch)(url, {\n headers: params.credentials ? { Authorization: `Bearer ${params.credentials.accessToken}` } : {}\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n const resJson = await res.json();\n for (const commit2 of resJson) {\n yield {\n oid: commit2.id,\n title: commit2.title,\n message: commit2.message,\n authors: commit2.authors.map((author) => ({\n username: author.user,\n avatarUrl: author.avatar\n })),\n date: new Date(commit2.date)\n };\n }\n const linkHeader = res.headers.get(\"Link\");\n url = linkHeader ? parseLinkHeader(linkHeader).next : void 0;\n }\n}\n\n// src/utils/pick.ts\nfunction pick(o, props) {\n return Object.assign(\n {},\n ...props.map((prop) => {\n if (o[prop] !== void 0) {\n return { [prop]: o[prop] };\n }\n })\n );\n}\n\n// src/lib/list-datasets.ts\nvar EXPAND_KEYS = [\n \"private\",\n \"downloads\",\n \"gated\",\n \"likes\",\n \"lastModified\"\n];\nasync function* listDatasets(params) {\n var _a, _b, _c, _d, _e, _f, _g, _h, _i;\n checkCredentials(params == null ? void 0 : params.credentials);\n let totalToFetch = (_a = params == null ? void 0 : params.limit) != null ? _a : Infinity;\n const search = new URLSearchParams([\n ...Object.entries({\n limit: String(Math.min(totalToFetch, 500)),\n ...((_b = params == null ? void 0 : params.search) == null ? void 0 : _b.owner) ? { author: params.search.owner } : void 0,\n ...((_c = params == null ? void 0 : params.search) == null ? void 0 : _c.query) ? { search: params.search.query } : void 0\n }),\n ...(_f = (_e = (_d = params == null ? void 0 : params.search) == null ? void 0 : _d.tags) == null ? void 0 : _e.map((tag) => [\"filter\", tag])) != null ? _f : [],\n ...EXPAND_KEYS.map((val) => [\"expand\", val]),\n ...(_h = (_g = params == null ? void 0 : params.additionalFields) == null ? void 0 : _g.map((val) => [\"expand\", val])) != null ? _h : []\n ]).toString();\n let url = `${(params == null ? void 0 : params.hubUrl) || HUB_URL}/api/datasets` + (search ? \"?\" + search : \"\");\n while (url) {\n const res = await ((_i = params == null ? void 0 : params.fetch) != null ? _i : fetch)(url, {\n headers: {\n accept: \"application/json\",\n ...(params == null ? void 0 : params.credentials) ? { Authorization: `Bearer ${params.credentials.accessToken}` } : void 0\n }\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n const items = await res.json();\n for (const item of items) {\n yield {\n ...(params == null ? void 0 : params.additionalFields) && pick(item, params.additionalFields),\n id: item._id,\n name: item.id,\n private: item.private,\n downloads: item.downloads,\n likes: item.likes,\n gated: item.gated,\n updatedAt: new Date(item.lastModified)\n };\n totalToFetch--;\n if (totalToFetch <= 0) {\n return;\n }\n }\n const linkHeader = res.headers.get(\"Link\");\n url = linkHeader ? parseLinkHeader(linkHeader).next : void 0;\n }\n}\n\n// src/lib/list-files.ts\nasync function* listFiles(params) {\n var _a;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n let url = `${params.hubUrl || HUB_URL}/api/${repoId.type}s/${repoId.name}/tree/${params.revision || \"main\"}${params.path ? \"/\" + params.path : \"\"}?recursive=${!!params.recursive}&expand=${!!params.expand}`;\n while (url) {\n const res = await ((_a = params.fetch) != null ? _a : fetch)(url, {\n headers: {\n accept: \"application/json\",\n ...params.credentials ? { Authorization: `Bearer ${params.credentials.accessToken}` } : void 0\n }\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n const items = await res.json();\n for (const item of items) {\n yield item;\n }\n const linkHeader = res.headers.get(\"Link\");\n url = linkHeader ? parseLinkHeader(linkHeader).next : void 0;\n }\n}\n\n// src/lib/list-models.ts\nvar EXPAND_KEYS2 = [\n \"pipeline_tag\",\n \"private\",\n \"gated\",\n \"downloads\",\n \"likes\",\n \"lastModified\"\n];\nasync function* listModels(params) {\n var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;\n checkCredentials(params == null ? void 0 : params.credentials);\n let totalToFetch = (_a = params == null ? void 0 : params.limit) != null ? _a : Infinity;\n const search = new URLSearchParams([\n ...Object.entries({\n limit: String(Math.min(totalToFetch, 500)),\n ...((_b = params == null ? void 0 : params.search) == null ? void 0 : _b.owner) ? { author: params.search.owner } : void 0,\n ...((_c = params == null ? void 0 : params.search) == null ? void 0 : _c.task) ? { pipeline_tag: params.search.task } : void 0,\n ...((_d = params == null ? void 0 : params.search) == null ? void 0 : _d.query) ? { search: params.search.query } : void 0\n }),\n ...(_g = (_f = (_e = params == null ? void 0 : params.search) == null ? void 0 : _e.tags) == null ? void 0 : _f.map((tag) => [\"filter\", tag])) != null ? _g : [],\n ...EXPAND_KEYS2.map((val) => [\"expand\", val]),\n ...(_i = (_h = params == null ? void 0 : params.additionalFields) == null ? void 0 : _h.map((val) => [\"expand\", val])) != null ? _i : []\n ]).toString();\n let url = `${(params == null ? void 0 : params.hubUrl) || HUB_URL}/api/models?${search}`;\n while (url) {\n const res = await ((_j = params == null ? void 0 : params.fetch) != null ? _j : fetch)(url, {\n headers: {\n accept: \"application/json\",\n ...(params == null ? void 0 : params.credentials) ? { Authorization: `Bearer ${params.credentials.accessToken}` } : void 0\n }\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n const items = await res.json();\n for (const item of items) {\n yield {\n ...(params == null ? void 0 : params.additionalFields) && pick(item, params.additionalFields),\n id: item._id,\n name: item.id,\n private: item.private,\n task: item.pipeline_tag,\n downloads: item.downloads,\n gated: item.gated,\n likes: item.likes,\n updatedAt: new Date(item.lastModified)\n };\n totalToFetch--;\n if (totalToFetch <= 0) {\n return;\n }\n }\n const linkHeader = res.headers.get(\"Link\");\n url = linkHeader ? parseLinkHeader(linkHeader).next : void 0;\n }\n}\n\n// src/lib/list-spaces.ts\nvar EXPAND_KEYS3 = [\"sdk\", \"likes\", \"private\", \"lastModified\"];\nasync function* listSpaces(params) {\n var _a, _b, _c, _d, _e, _f, _g;\n checkCredentials(params == null ? void 0 : params.credentials);\n const search = new URLSearchParams([\n ...Object.entries({\n limit: \"500\",\n ...((_a = params == null ? void 0 : params.search) == null ? void 0 : _a.owner) ? { author: params.search.owner } : void 0,\n ...((_b = params == null ? void 0 : params.search) == null ? void 0 : _b.query) ? { search: params.search.query } : void 0\n }),\n ...(_e = (_d = (_c = params == null ? void 0 : params.search) == null ? void 0 : _c.tags) == null ? void 0 : _d.map((tag) => [\"filter\", tag])) != null ? _e : [],\n ...[...EXPAND_KEYS3, ...(_f = params == null ? void 0 : params.additionalFields) != null ? _f : []].map((val) => [\"expand\", val])\n ]).toString();\n let url = `${(params == null ? void 0 : params.hubUrl) || HUB_URL}/api/spaces?${search}`;\n while (url) {\n const res = await ((_g = params == null ? void 0 : params.fetch) != null ? _g : fetch)(url, {\n headers: {\n accept: \"application/json\",\n ...(params == null ? void 0 : params.credentials) ? { Authorization: `Bearer ${params.credentials.accessToken}` } : void 0\n }\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n const items = await res.json();\n for (const item of items) {\n yield {\n ...(params == null ? void 0 : params.additionalFields) && pick(item, params.additionalFields),\n id: item._id,\n name: item.id,\n sdk: item.sdk,\n likes: item.likes,\n private: item.private,\n updatedAt: new Date(item.lastModified)\n };\n }\n const linkHeader = res.headers.get(\"Link\");\n url = linkHeader ? parseLinkHeader(linkHeader).next : void 0;\n }\n}\n\n// src/lib/oauth-handle-redirect.ts\nasync function oauthHandleRedirect(opts) {\n var _a, _b;\n if (typeof window === \"undefined\") {\n throw new Error(\"oauthHandleRedirect is only available in the browser\");\n }\n const searchParams = new URLSearchParams(window.location.search);\n const [error, errorDescription] = [searchParams.get(\"error\"), searchParams.get(\"error_description\")];\n if (error) {\n throw new Error(`${error}: ${errorDescription}`);\n }\n const code = searchParams.get(\"code\");\n const nonce = localStorage.getItem(\"huggingface.co:oauth:nonce\");\n if (!code) {\n throw new Error(\"Missing oauth code from query parameters in redirected URL\");\n }\n if (!nonce) {\n throw new Error(\"Missing oauth nonce from localStorage\");\n }\n const codeVerifier = localStorage.getItem(\"huggingface.co:oauth:code_verifier\");\n if (!codeVerifier) {\n throw new Error(\"Missing oauth code_verifier from localStorage\");\n }\n const state = searchParams.get(\"state\");\n if (!state) {\n throw new Error(\"Missing oauth state from query parameters in redirected URL\");\n }\n let parsedState;\n try {\n parsedState = JSON.parse(state);\n } catch (e) {\n throw new Error(\"Invalid oauth state in redirected URL, unable to parse JSON: \" + state);\n }\n if (parsedState.nonce !== nonce) {\n throw new Error(\"Invalid oauth state in redirected URL\");\n }\n const hubUrl = (opts == null ? void 0 : opts.hubUrl) || HUB_URL;\n const openidConfigUrl = `${new URL(hubUrl).origin}/.well-known/openid-configuration`;\n const openidConfigRes = await fetch(openidConfigUrl, {\n headers: {\n Accept: \"application/json\"\n }\n });\n if (!openidConfigRes.ok) {\n throw await createApiError(openidConfigRes);\n }\n const opendidConfig = await openidConfigRes.json();\n const tokenRes = await fetch(opendidConfig.token_endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\"\n },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: parsedState.redirectUri,\n code_verifier: codeVerifier\n }).toString()\n });\n localStorage.removeItem(\"huggingface.co:oauth:code_verifier\");\n localStorage.removeItem(\"huggingface.co:oauth:nonce\");\n if (!tokenRes.ok) {\n throw await createApiError(tokenRes);\n }\n const token = await tokenRes.json();\n const accessTokenExpiresAt = new Date(Date.now() + token.expires_in * 1e3);\n const userInfoRes = await fetch(opendidConfig.userinfo_endpoint, {\n headers: {\n Authorization: `Bearer ${token.access_token}`\n }\n });\n if (!userInfoRes.ok) {\n throw await createApiError(userInfoRes);\n }\n const userInfo = await userInfoRes.json();\n return {\n accessToken: token.access_token,\n accessTokenExpiresAt,\n userInfo: {\n id: userInfo.sub,\n name: userInfo.name,\n fullname: userInfo.preferred_username,\n email: userInfo.email,\n emailVerified: userInfo.email_verified,\n avatarUrl: userInfo.picture,\n websiteUrl: userInfo.website,\n isPro: userInfo.isPro,\n orgs: (_b = (_a = userInfo.orgs) == null ? void 0 : _a.map((org) => ({\n id: org.sub,\n name: org.name,\n fullname: org.name,\n isEnterprise: org.isEnterprise,\n canPay: org.canPay,\n avatarUrl: org.picture,\n roleInOrg: org.roleInOrg\n }))) != null ? _b : []\n },\n state: parsedState.state,\n scope: token.scope\n };\n}\nasync function oauthHandleRedirectIfPresent(opts) {\n if (typeof window === \"undefined\") {\n throw new Error(\"oauthHandleRedirect is only available in the browser\");\n }\n const searchParams = new URLSearchParams(window.location.search);\n if (searchParams.has(\"error\")) {\n return oauthHandleRedirect(opts);\n }\n if (searchParams.has(\"code\")) {\n if (!localStorage.getItem(\"huggingface.co:oauth:nonce\")) {\n console.warn(\n \"Missing oauth nonce from localStorage. This can happen when the user refreshes the page after logging in, without changing the URL.\"\n );\n return false;\n }\n return oauthHandleRedirect(opts);\n }\n return false;\n}\n\n// src/lib/oauth-login-url.ts\nasync function oauthLoginUrl(opts) {\n var _a, _b;\n if (typeof window === \"undefined\") {\n throw new Error(\"oauthLogin is only available in the browser\");\n }\n const hubUrl = (opts == null ? void 0 : opts.hubUrl) || HUB_URL;\n const openidConfigUrl = `${new URL(hubUrl).origin}/.well-known/openid-configuration`;\n const openidConfigRes = await fetch(openidConfigUrl, {\n headers: {\n Accept: \"application/json\"\n }\n });\n if (!openidConfigRes.ok) {\n throw await createApiError(openidConfigRes);\n }\n const opendidConfig = await openidConfigRes.json();\n const newNonce = globalThis.crypto.randomUUID();\n const newCodeVerifier = globalThis.crypto.randomUUID() + globalThis.crypto.randomUUID();\n localStorage.setItem(\"huggingface.co:oauth:nonce\", newNonce);\n localStorage.setItem(\"huggingface.co:oauth:code_verifier\", newCodeVerifier);\n const redirectUri = (opts == null ? void 0 : opts.redirectUrl) || window.location.href;\n const state = JSON.stringify({\n nonce: newNonce,\n redirectUri,\n state: opts == null ? void 0 : opts.state\n });\n const variables = (_b = (_a = window == null ? void 0 : window.huggingface) == null ? void 0 : _a.variables) != null ? _b : null;\n const clientId = (opts == null ? void 0 : opts.clientId) || (variables == null ? void 0 : variables.OAUTH_CLIENT_ID);\n if (!clientId) {\n if (variables) {\n throw new Error(\"Missing clientId, please add hf_oauth: true to the README.md's metadata in your static Space\");\n }\n throw new Error(\"Missing clientId\");\n }\n const challenge = base64FromBytes(\n new Uint8Array(await globalThis.crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(newCodeVerifier)))\n ).replace(/[+]/g, \"-\").replace(/[/]/g, \"_\").replace(/=/g, \"\");\n return `${opendidConfig.authorization_endpoint}?${new URLSearchParams({\n client_id: clientId,\n scope: (opts == null ? void 0 : opts.scopes) || (variables == null ? void 0 : variables.OAUTH_SCOPES) || \"openid profile\",\n response_type: \"code\",\n redirect_uri: redirectUri,\n state,\n code_challenge: challenge,\n code_challenge_method: \"S256\"\n }).toString()}`;\n}\n\n// src/utils/typedInclude.ts\nfunction typedInclude(arr, v) {\n return arr.includes(v);\n}\n\n// src/utils/omit.ts\nfunction omit(o, props) {\n const propsArr = Array.isArray(props) ? props : [props];\n const letsKeep = Object.keys(o).filter((prop) => !typedInclude(propsArr, prop));\n return pick(o, letsKeep);\n}\n\n// src/utils/typedEntries.ts\nfunction typedEntries(obj) {\n return Object.entries(obj);\n}\n\n// src/lib/parse-safetensors-metadata.ts\nvar SAFETENSORS_FILE = \"model.safetensors\";\nvar SAFETENSORS_INDEX_FILE = \"model.safetensors.index.json\";\nvar RE_SAFETENSORS_FILE = /\\.safetensors$/;\nvar RE_SAFETENSORS_INDEX_FILE = /\\.safetensors\\.index\\.json$/;\nvar RE_SAFETENSORS_SHARD_FILE = /^(?<prefix>(?<basePrefix>.*?)[_-])(?<shard>\\d{5})-of-(?<total>\\d{5})\\.safetensors$/;\nfunction parseSafetensorsShardFilename(filename) {\n const match = RE_SAFETENSORS_SHARD_FILE.exec(filename);\n if (match && match.groups) {\n return {\n prefix: match.groups[\"prefix\"],\n basePrefix: match.groups[\"basePrefix\"],\n shard: match.groups[\"shard\"],\n total: match.groups[\"total\"]\n };\n }\n return null;\n}\nvar PARALLEL_DOWNLOADS = 20;\nvar MAX_HEADER_LENGTH = 25e6;\nvar SafetensorParseError = class extends Error {\n};\nasync function parseSingleFile(path, params) {\n const firstResp = await downloadFile({\n ...params,\n path,\n range: [0, 7]\n });\n if (!firstResp) {\n throw new SafetensorParseError(`Failed to parse file ${path}: failed to fetch safetensors header length.`);\n }\n const bufLengthOfHeaderLE = await firstResp.arrayBuffer();\n const lengthOfHeader = new DataView(bufLengthOfHeaderLE).getBigUint64(0, true);\n if (lengthOfHeader <= 0) {\n throw new SafetensorParseError(`Failed to parse file ${path}: safetensors header is malformed.`);\n }\n if (lengthOfHeader > MAX_HEADER_LENGTH) {\n throw new SafetensorParseError(\n `Failed to parse file ${path}: safetensor header is too big. Maximum supported size is ${MAX_HEADER_LENGTH} bytes.`\n );\n }\n const secondResp = await downloadFile({ ...params, path, range: [8, 7 + Number(lengthOfHeader)] });\n if (!secondResp) {\n throw new SafetensorParseError(`Failed to parse file ${path}: failed to fetch safetensors header.`);\n }\n try {\n const header = await secondResp.json();\n return header;\n } catch (err) {\n throw new SafetensorParseError(`Failed to parse file ${path}: safetensors header is not valid JSON.`);\n }\n}\nasync function parseShardedIndex(path, params) {\n const indexResp = await downloadFile({\n ...params,\n path,\n range: [0, 1e7]\n });\n if (!indexResp) {\n throw new SafetensorParseError(`Failed to parse file ${path}: failed to fetch safetensors index.`);\n }\n let index;\n try {\n index = await indexResp.json();\n } catch (error) {\n throw new SafetensorParseError(`Failed to parse file ${path}: not a valid JSON.`);\n }\n const pathPrefix = path.slice(0, path.lastIndexOf(\"/\") + 1);\n const filenames = [...new Set(Object.values(index.weight_map))];\n const shardedMap = Object.fromEntries(\n await promisesQueue(\n filenames.map(\n (filename) => async () => [filename, await parseSingleFile(pathPrefix + filename, params)]\n ),\n PARALLEL_DOWNLOADS\n )\n );\n return { index, headers: shardedMap };\n}\nasync function parseSafetensorsMetadata(params) {\n var _a, _b, _c, _d;\n checkCredentials(params.credentials);\n const repoId = toRepoId(params.repo);\n if (repoId.type !== \"model\") {\n throw new TypeError(\"Only model repos should contain safetensors files.\");\n }\n if (RE_SAFETENSORS_FILE.test((_a = params.path) != null ? _a : \"\") || await fileExists({ ...params, path: SAFETENSORS_FILE })) {\n const header = await parseSingleFile((_b = params.path) != null ? _b : SAFETENSORS_FILE, params);\n return {\n sharded: false,\n header,\n ...params.computeParametersCount && {\n parameterCount: computeNumOfParamsByDtypeSingleFile(header)\n }\n };\n } else if (RE_SAFETENSORS_INDEX_FILE.test((_c = params.path) != null ? _c : \"\") || await fileExists({ ...params, path: SAFETENSORS_INDEX_FILE })) {\n const { index, headers } = await parseShardedIndex((_d = params.path) != null ? _d : SAFETENSORS_INDEX_FILE, params);\n return {\n sharded: true,\n index,\n headers,\n ...params.computeParametersCount && {\n parameterCount: computeNumOfParamsByDtypeSharded(headers)\n }\n };\n } else {\n throw new Error(\"model id does not seem to contain safetensors weights\");\n }\n}\nfunction computeNumOfParamsByDtypeSingleFile(header) {\n var _a;\n const counter = {};\n const tensors = omit(header, \"__metadata__\");\n for (const [, v] of typedEntries(tensors)) {\n if (v.shape.length === 0) {\n continue;\n }\n counter[v.dtype] = ((_a = counter[v.dtype]) != null ? _a : 0) + v.shape.reduce((a, b) => a * b);\n }\n return counter;\n}\nfunction computeNumOfParamsByDtypeSharded(shardedMap) {\n var _a;\n const counter = {};\n for (const header of Object.values(shardedMap)) {\n for (const [k, v] of typedEntries(computeNumOfParamsByDtypeSingleFile(header))) {\n counter[k] = ((_a = counter[k]) != null ? _a : 0) + (v != null ? v : 0);\n }\n }\n return counter;\n}\n\n// src/lib/upload-file.ts\nfunction uploadFile(params) {\n var _a, _b;\n const path = params.file instanceof URL ? (_a = params.file.pathname.split(\"/\").at(-1)) != null ? _a : \"file\" : \"path\" in params.file ? params.file.path : params.file.name;\n return commit({\n credentials: params.credentials,\n repo: params.repo,\n operations: [\n {\n operation: \"addOrUpdate\",\n path,\n content: \"content\" in params.file ? params.file.content : params.file\n }\n ],\n title: (_b = params.commitTitle) != null ? _b : `Add ${path}`,\n description: params.commitDescription,\n hubUrl: params.hubUrl,\n branch: params.branch,\n isPullRequest: params.isPullRequest,\n parentCommit: params.parentCommit,\n fetch: params.fetch,\n useWebWorkers: params.useWebWorkers,\n abortSignal: params.abortSignal\n });\n}\n\n// src/lib/upload-files.ts\nfunction uploadFiles(params) {\n var _a;\n return commit({\n credentials: params.credentials,\n repo: params.repo,\n operations: params.files.map((file) => {\n var _a2;\n return {\n operation: \"addOrUpdate\",\n path: file instanceof URL ? (_a2 = file.pathname.split(\"/\").at(-1)) != null ? _a2 : \"file\" : \"path\" in file ? file.path : file.name,\n content: \"content\" in file ? file.content : file\n };\n }),\n title: (_a = params.commitTitle) != null ? _a : `Add ${params.files.length} files`,\n description: params.commitDescription,\n hubUrl: params.hubUrl,\n branch: params.branch,\n isPullRequest: params.isPullRequest,\n parentCommit: params.parentCommit,\n fetch: params.fetch,\n useWebWorkers: params.useWebWorkers,\n abortSignal: params.abortSignal\n });\n}\n\n// src/lib/upload-files-with-progress.ts\nvar multipartUploadTracking = /* @__PURE__ */ new WeakMap();\nasync function* uploadFilesWithProgress(params) {\n var _a;\n return yield* commitIter({\n credentials: params.credentials,\n repo: params.repo,\n operations: params.files.map((file) => {\n var _a2;\n return {\n operation: \"addOrUpdate\",\n path: file instanceof URL ? (_a2 = file.pathname.split(\"/\").at(-1)) != null ? _a2 : \"file\" : \"path\" in file ? file.path : file.name,\n content: \"content\" in file ? file.content : file\n };\n }),\n title: (_a = params.commitTitle) != null ? _a : `Add ${params.files.length} files`,\n description: params.commitDescription,\n hubUrl: params.hubUrl,\n branch: params.branch,\n isPullRequest: params.isPullRequest,\n parentCommit: params.parentCommit,\n useWebWorkers: params.useWebWorkers,\n abortSignal: params.abortSignal,\n fetch: async (input, init) => {\n var _a2;\n if (!init) {\n return fetch(input);\n }\n if (!typedInclude([\"PUT\", \"POST\"], init.method) || !(\"progressHint\" in init) || !init.progressHint || typeof XMLHttpRequest === \"undefined\" || typeof input !== \"string\" || !(init.body instanceof ArrayBuffer) && !(init.body instanceof Blob) && !(init.body instanceof File) && typeof init.body !== \"string\") {\n return fetch(input, init);\n }\n const progressHint = init.progressHint;\n const progressCallback = progressHint.progressCallback;\n const xhr = new XMLHttpRequest();\n xhr.upload.addEventListener(\"progress\", (event) => {\n if (event.lengthComputable) {\n if (progressHint.part !== void 0) {\n let tracking = multipartUploadTracking.get(progressCallback);\n if (!tracking) {\n tracking = { numParts: progressHint.numParts, partsProgress: {} };\n multipartUploadTracking.set(progressCallback, tracking);\n }\n tracking.partsProgress[progressHint.part] = event.loaded / event.total;\n let totalProgress = 0;\n for (const partProgress of Object.values(tracking.partsProgress)) {\n totalProgress += partProgress;\n }\n if (totalProgress === tracking.numParts) {\n progressCallback(0.9999999999);\n } else {\n progressCallback(totalProgress / tracking.numParts);\n }\n } else {\n if (event.loaded === event.total) {\n progressCallback(0.9999999999);\n } else {\n progressCallback(event.loaded / event.total);\n }\n }\n }\n });\n xhr.open(init.method, input, true);\n if (init.headers) {\n const headers = new Headers(init.headers);\n headers.forEach((value, key) => {\n xhr.setRequestHeader(key, value);\n });\n }\n (_a2 = init.signal) == null ? void 0 : _a2.throwIfAborted();\n xhr.send(init.body);\n return new Promise((resolve2, reject) => {\n xhr.addEventListener(\"load\", () => {\n resolve2(\n new Response(xhr.responseText, {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: Object.fromEntries(\n xhr.getAllResponseHeaders().trim().split(\"\\n\").map((header) => [header.slice(0, header.indexOf(\":\")), header.slice(header.indexOf(\":\") + 1).trim()])\n )\n })\n );\n });\n xhr.addEventListener(\"error\", () => {\n reject(new Error(xhr.statusText));\n });\n if (init.signal) {\n init.signal.addEventListener(\"abort\", () => {\n var _a3;\n xhr.abort();\n try {\n (_a3 = init.signal) == null ? void 0 : _a3.throwIfAborted();\n } catch (err) {\n reject(err);\n }\n });\n }\n });\n }\n });\n}\n\n// src/lib/who-am-i.ts\nasync function whoAmI(params) {\n var _a, _b, _c;\n checkCredentials(params.credentials);\n const res = await ((_a = params.fetch) != null ? _a : fetch)(`${(_b = params.hubUrl) != null ? _b : HUB_URL}/api/whoami-v2`, {\n headers: {\n Authorization: `Bearer ${params.credentials.accessToken}`\n }\n });\n if (!res.ok) {\n throw await createApiError(res);\n }\n const response = await res.json();\n if (typeof ((_c = response.auth.accessToken) == null ? void 0 : _c.createdAt) === \"string\") {\n response.auth.accessToken.createdAt = new Date(response.auth.accessToken.createdAt);\n }\n return response;\n}\nexport {\n HubApiError,\n InvalidApiResponseFormatError,\n RE_SAFETENSORS_FILE,\n RE_SAFETENSORS_INDEX_FILE,\n RE_SAFETENSORS_SHARD_FILE,\n SAFETENSORS_FILE,\n SAFETENSORS_INDEX_FILE,\n sha256 as __internal_sha256,\n commit,\n commitIter,\n countCommits,\n createRepo,\n deleteFile,\n deleteFiles,\n deleteRepo,\n downloadFile,\n fileDownloadInfo,\n fileExists,\n listCommits,\n listDatasets,\n listFiles,\n listModels,\n listSpaces,\n oauthHandleRedirect,\n oauthHandleRedirectIfPresent,\n oauthLoginUrl,\n parseSafetensorsMetadata,\n parseSafetensorsShardFilename,\n uploadFile,\n uploadFiles,\n uploadFilesWithProgress,\n whoAmI\n};\n","/**\n * reachy-mini.js — Browser SDK for controlling a Reachy Mini robot over WebRTC.\n * https://github.com/pollen-robotics/reachy-mini\n *\n * QUICK START\n * ───────────\n * import { ReachyMini } from \"./reachy-mini.js\";\n * const robot = new ReachyMini();\n *\n * // 1. Auth (HuggingFace OAuth — required for the signaling server)\n * if (!await robot.authenticate()) { robot.login(); return; }\n *\n * // 2. Connect to signaling server (SSE)\n * await robot.connect();\n *\n * // 3. Pick a robot once the list arrives\n * robot.addEventListener(\"robotsChanged\", (e) => {\n * const robots = e.detail.robots; // [{ id, meta: { name } }, ...]\n * });\n *\n * // 4. Start a WebRTC session (resolves when video + data channel ready)\n * const detach = robot.attachVideo(document.querySelector(\"video\"));\n * await robot.startSession(robotId);\n *\n * // 5. Send commands — degree-friendly helpers, all built on setTarget()\n * robot.setHeadRpyDeg(0, 10, -5); // roll, pitch, yaw in degrees\n * robot.setAntennasDeg(30, -30); // right, left in degrees\n * robot.setBodyYawDeg(15); // body yaw in degrees\n *\n * // …or compose an atomic update in raw wire units (full SE(3); no XYZ loss):\n * robot.setTarget({\n * head: rpyToMatrix(0, 10, -5).flat(), // number[16] flat row-major 4×4\n * antennas: [degToRad(30), degToRad(-30)],\n * body_yaw: degToRad(15),\n * });\n * robot.playSound(\"wake_up.wav\"); // filename on robot\n * const ver = await robot.getVersion(); // e.g. \"1.5.1\"\n *\n * // 6. Receive live state (emitted every ~500 ms while streaming; call\n * // robot.requestState() yourself for higher rates — see its JSDoc).\n * // State payload is the daemon's raw wire shape — use the math\n * // utilities exported from this module for degree conversions.\n * robot.addEventListener(\"state\", (e) => {\n * const { head, antennas, body_yaw, motor_mode, is_move_running } = e.detail;\n * // head: number[16] — flat row-major 4×4 (full SE(3))\n * // antennas: [rightRad, leftRad]\n * // body_yaw: number — radians\n * // motor_mode: \"enabled\" | \"disabled\" | \"gravity_compensation\"\n * // is_move_running: boolean\n * // For human-friendly head RPY:\n * // const rpy = matrixToRpy(head); // { roll, pitch, yaw } in degrees\n * });\n *\n * // 7. Audio controls\n * robot.setAudioMuted(false); // unmute robot speaker (muted by default)\n * robot.setMicMuted(false); // unmute your mic → robot speaker (if supported)\n *\n * // 8. Cleanup\n * detach(); // remove video binding\n * await robot.stopSession(); // back to 'connected'\n * robot.disconnect(); // back to 'disconnected' (keeps auth)\n * robot.logout(); // clear HF credentials too\n *\n *\n * STATE MACHINE\n * ─────────────\n * 'disconnected' ──connect()──▸ 'connected' ──startSession()──▸ 'streaming'\n * ▴ disconnect() ▴ stopSession()\n * └─────────────────────────────┘\n *\n *\n * CONSTRUCTOR OPTIONS\n * ───────────────────\n * new ReachyMini({\n * signalingUrl: string, // default: \"https://pollen-robotics-reachy-mini-central.hf.space\"\n * enableMicrophone: boolean, // default: true — acquire mic for bidirectional audio\n * videoJitterBufferTargetMs: number, // default: 0 — receiver-side jitter buffer hint, ms\n * // 0 = \"render ASAP\" (teleop). Spec range [0, 4000].\n * // Raise (100–400) on flaky links to trade latency for resilience.\n * autoStartFromUrl: boolean, // default: false — when true AND the URL carries a `robot_peer_id` hint,\n * // auto-call `startSession(preselectedRobotId)` after\n * // `connect()` resolves and that robot appears online.\n * // One-shot per page load; suits iframe-embedded apps\n * // that want zero-tap entry from the host shell.\n * })\n *\n *\n * READ-ONLY PROPERTIES\n * ────────────────────\n * .state \"disconnected\" | \"connected\" | \"streaming\"\n * .robots Array<{ id: string, meta: { name: string } }>\n * .robotState Mirror of the latest \"state\" event detail —\n * { head: number[16], antennas: [rightRad, leftRad],\n * body_yaw, motor_mode, is_move_running }\n * (fields only present once the daemon sends them;\n * see EVENTS below)\n * .username string | null — HF username after authenticate()\n * .isAuthenticated boolean — true if a valid HF token is available\n * .micSupported boolean — true if robot offers bidirectional audio\n * .micMuted boolean — your microphone mute state\n * .audioMuted boolean — robot speaker mute state (local)\n * .preselectedRobotId string | null — peer id from `?robot_peer_id=` /\n * `#robot_peer_id=`; null if absent.\n * Use it to skip your robot picker\n * when a host iframe (e.g. the\n * Reachy Mini mobile shell) embeds\n * this app.\n * .isEmbedded boolean — true iff `preselectedRobotId !==\n * null`. Branch your UX on this:\n * when true, hide the robot picker\n * and your sign-in screen (the\n * host has already handled both).\n *\n *\n * EVENTS (EventTarget — use addEventListener)\n * ──────────────────────────────────────────────\n * \"connected\" { peerId: string }\n * \"disconnected\" { reason: string }\n * \"robotsChanged\" { robots: Array<{ id, meta }> }\n * \"streaming\" { sessionId: string, robotId: string }\n * \"sessionStopped\" { reason: string }\n * \"state\" { head: number[16], // flat row-major 4×4, when daemon sends head_pose\n * antennas: [rightRad, leftRad], // when daemon sends antennas\n * body_yaw: number, // radians, when daemon sends body_yaw\n * motor_mode: string, // when daemon sends motor_mode\n * is_move_running: boolean } // when daemon sends is_move_running\n * \"videoTrack\" { track: MediaStreamTrack, stream: MediaStream }\n * \"micSupported\" { supported: boolean }\n * \"error\" { source: \"signaling\"|\"webrtc\"|\"robot\", error: Error|string }\n *\n *\n * EXPORTS\n * ───────\n * export default ReachyMini;\n * export { ReachyMini, rpyToMatrix, matrixToRpy, degToRad, radToDeg };\n */\n\nimport {\n oauthHandleRedirectIfPresent,\n oauthLoginUrl,\n} from \"@huggingface/hub\";\n\n// ─── Math utilities ──────────────────────────────────────────────────────────\n\n/** @param {number} deg @returns {number} */\nexport function degToRad(deg) { return deg * Math.PI / 180; }\n\n/** @param {number} rad @returns {number} */\nexport function radToDeg(rad) { return rad * 180 / Math.PI; }\n\n/**\n * Roll/pitch/yaw (degrees) → 4×4 rotation matrix (ZYX convention).\n * This is the wire format for the robot's `set_target` command.\n * @param {number} rollDeg @param {number} pitchDeg @param {number} yawDeg\n * @returns {number[][]} 4×4 matrix\n */\nexport function rpyToMatrix(rollDeg, pitchDeg, yawDeg) {\n const r = degToRad(rollDeg), p = degToRad(pitchDeg), y = degToRad(yawDeg);\n const cy = Math.cos(y), sy = Math.sin(y);\n const cp = Math.cos(p), sp = Math.sin(p);\n const cr = Math.cos(r), sr = Math.sin(r);\n return [\n [cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr, 0],\n [sy * cp, sy * sp * sr + cy * cr, sy * sp * cr - cy * sr, 0],\n [-sp, cp * sr, cp * cr, 0],\n [0, 0, 0, 1],\n ];\n}\n\n/**\n * Rotation matrix (3×3 or 4×4) → { roll, pitch, yaw } in degrees.\n * @param {number[][]} m @returns {{ roll: number, pitch: number, yaw: number }}\n */\nexport function matrixToRpy(m) {\n return {\n roll: radToDeg(Math.atan2(m[2][1], m[2][2])),\n pitch: radToDeg(Math.asin(-m[2][0])),\n yaw: radToDeg(Math.atan2(m[1][0], m[0][0])),\n };\n}\n\n// ─── Internal helpers ────────────────────────────────────────────────────────\n\n/** Clamp a volume to [0, 100] and round to integer — mirrors the server-side\n * Field(..., ge=0, le=100) validator so calling setVolume(150) doesn't 400. */\nfunction clampVolume(v) {\n const n = Math.round(Number(v) || 0);\n return Math.max(0, Math.min(100, n));\n}\n\n/**\n * Pick up HuggingFace credentials passed via the URL fragment and move them\n * into `sessionStorage`, where `authenticate()` looks them up.\n *\n * This is the bridge that lets a host page (e.g. the Reachy Mini mobile\n * app, or the vibe-coder preview iframe) embed a Space hosting a SDK\n * consumer despite `X-Frame-Options: SAMEORIGIN` on `huggingface.co/login`:\n * the host already holds a valid token (through its own OAuth flow) and\n * appends it to the iframe URL as\n *\n * #hf_token=<jwt>&hf_username=<handle>&hf_token_expires=<iso>\n *\n * Fragments are NOT sent over HTTP, so the credentials never leak to\n * the HF Space backend or to intermediate proxies.\n *\n * Why all three keys: `authenticate()`'s cache check requires the token,\n * the username AND a future expiry to ALL be present in `sessionStorage`,\n * otherwise it returns `false` and the app falls through to a full OAuth\n * round-trip — which can't complete inside an iframe.\n *\n * Called once from the top of `authenticate()` so SDK consumers don't\n * need any boilerplate of their own. We clear the fragment right after\n * reading it so a page reload does not keep the credentials visible in\n * the address bar.\n *\n * No-op when:\n * - there is no `window` (SSR / Worker contexts),\n * - the URL has no fragment,\n * - the fragment carries no `hf_token` (other apps may use the\n * fragment for theme / route / etc.; we leave those alone).\n */\nfunction consumeFragmentCredentials() {\n if (typeof window === 'undefined' || !window.location.hash) return;\n const raw = window.location.hash.startsWith('#')\n ? window.location.hash.slice(1)\n : window.location.hash;\n let params;\n try { params = new URLSearchParams(raw); } catch (_e) { return; }\n const token = params.get('hf_token');\n if (!token) return;\n // `hf_username` is required by the cache check. Hosts that haven't\n // resolved the user's HF handle yet may pass a literal \"user\"\n // placeholder; the SDK only uses the value for display and never\n // round-trips it server-side, so the placeholder is harmless.\n const username = params.get('hf_username') || 'user';\n // `hf_token_expires` is a far-future ISO date for personal access\n // tokens (no real expiry). Hosts typically synthesise ~1 year out;\n // we accept whatever was sent and fall back to \"1 year from now\"\n // if the parameter is missing or unparseable, so a partial fragment\n // still gets the user logged in.\n const expiresParam = params.get('hf_token_expires');\n const expires =\n expiresParam && !Number.isNaN(new Date(expiresParam).getTime())\n ? expiresParam\n : new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString();\n try {\n sessionStorage.setItem('hf_token', token);\n sessionStorage.setItem('hf_username', username);\n sessionStorage.setItem('hf_token_expires', expires);\n } catch (err) {\n console.warn('[reachy-mini] could not persist pre-seeded HF credentials:', err);\n }\n // Strip the auth keys from the address bar but keep any other hash\n // params the app or SDK might care about (theme, embedded, …).\n params.delete('hf_token');\n params.delete('hf_username');\n params.delete('hf_token_expires');\n const remaining = params.toString();\n const cleanUrl =\n window.location.pathname +\n window.location.search +\n (remaining ? '#' + remaining : '');\n try { window.history.replaceState(null, '', cleanUrl); } catch (_e) {}\n}\n\n/**\n * Pick up a preselected robot peer id from the URL.\n *\n * Looked up in this order:\n * 1. URL fragment (`#robot_peer_id=<peerId>`)\n * 2. URL query (`?robot_peer_id=<peerId>`)\n *\n * Both spellings are accepted because:\n * - the Reachy Mini mobile shell sends it in the query today,\n * - the vibe-coder preview / future hosts may prefer the fragment for\n * symmetry with `consumeFragmentCredentials`,\n * - the value is NOT a secret (peer ids are public on the central\n * signaling server's robot listing) so query is fine.\n *\n * Returns `null` when no peer id is found in either location, when there\n * is no `window` (SSR / Worker context), or on parse error. Unlike\n * credentials, we do NOT strip the param from the URL: the value is\n * harmless to keep visible and removing it would break tools that read\n * the URL for context.\n *\n * @returns {string|null}\n */\nfunction readPreselectedRobotIdFromUrl() {\n if (typeof window === 'undefined') return null;\n // 1. Fragment (`#robot_peer_id=…`).\n if (window.location.hash) {\n const raw = window.location.hash.startsWith('#')\n ? window.location.hash.slice(1)\n : window.location.hash;\n try {\n const params = new URLSearchParams(raw);\n const fromHash = params.get('robot_peer_id');\n if (fromHash) return fromHash;\n } catch (_e) { /* malformed fragment — fall through */ }\n }\n // 2. Query (`?robot_peer_id=…`).\n if (window.location.search) {\n try {\n const params = new URLSearchParams(window.location.search);\n const fromQuery = params.get('robot_peer_id');\n if (fromQuery) return fromQuery;\n } catch (_e) { /* malformed query — fall through */ }\n }\n return null;\n}\n\n/** Check if the audio m= section of an SDP has a=sendrecv (bidirectional audio). */\nfunction sdpHasAudioSendRecv(sdp) {\n const lines = sdp.split('\\r\\n');\n let inAudio = false;\n for (const line of lines) {\n if (line.startsWith('m=audio')) inAudio = true;\n else if (line.startsWith('m=')) inAudio = false;\n if (inAudio && line === 'a=sendrecv') return true;\n }\n return false;\n}\n\n// ─── ReachyMini class ────────────────────────────────────────────────────────\n\nexport class ReachyMini extends EventTarget {\n\n /** @param {{ signalingUrl?: string, enableMicrophone?: boolean, clientId?: string, appName?: string, videoJitterBufferTargetMs?: number, autoStartFromUrl?: boolean }} [options] */\n constructor(options = {}) {\n super();\n this._signalingUrl = options.signalingUrl || 'https://pollen-robotics-reachy-mini-central.hf.space';\n this._enableMicrophone = options.enableMicrophone !== false;\n this._clientId = options.clientId || null;\n this._appName = options.appName || 'unknown';\n // Hint to the receiver's WebRTC jitter buffer (ms). 0 = \"render ASAP\",\n // appropriate for teleop. Spec range [0, 4000]. Browsers that don't\n // implement RTCRtpReceiver.jitterBufferTarget fall back to default\n // buffering (~150-200 ms).\n this._videoJitterBufferTargetMs = options.videoJitterBufferTargetMs ?? 0;\n // When true AND the URL carried a `robot_peer_id` hint at\n // construction (so `preselectedRobotId !== null`), the SDK\n // auto-calls `startSession(preselectedRobotId)` as soon as\n // that robot appears in the central's robot list after the\n // app's own `connect()` resolves. Lets host-iframe-embedded\n // consumers (mobile shell, vibe-coder preview) skip their\n // robot picker AND skip the manual `startSession` call —\n // they just `await robot.connect()` and receive a `streaming`\n // event when the SDK has dialed in. One-shot: a manual\n // `stopSession()` followed by another `startSession()` is\n // not auto-replayed. Default `false` keeps the standalone\n // Space behavior unchanged.\n this._autoStartFromUrl = options.autoStartFromUrl === true;\n this._autoStartAttempted = false;\n\n this._state = 'disconnected'; // 'disconnected' | 'connected' | 'streaming'\n this._robots = []; // latest robot list from signaling\n this._robotState = {}; // populated from daemon state events (wire shape)\n\n // Preselected robot peer id read from the URL at construction\n // time. When a host iframe (typically the Reachy Mini mobile\n // shell) embeds an SDK consumer, it appends the peer id of the\n // robot it's already connected to via\n // `?robot_peer_id=…` (or `#robot_peer_id=…`). Apps can read\n // `robot.preselectedRobotId` and call `startSession(id)`\n // directly to skip their robot picker. Captured ONCE at\n // construction so subsequent URL changes (history navigation,\n // hash mutations from `consumeFragmentCredentials`) don't move\n // the target out from under the consumer.\n this._preselectedRobotId = readPreselectedRobotIdFromUrl();\n\n // Auth\n this._token = null;\n this._username = null;\n this._tokenExpires = null;\n\n // Signaling\n this._peerId = null;\n this._sseAbortController = null;\n\n // WebRTC\n this._pc = null; // RTCPeerConnection\n this._dc = null; // RTCDataChannel (robot commands)\n this._sessionId = null;\n this._selectedRobotId = null;\n\n // Audio\n this._micStream = null; // MediaStream from getUserMedia\n this._micMuted = true;\n this._audioMuted = true;\n this._micSupported = false; // set after SDP negotiation\n\n // Timers\n this._latencyMonitorId = null;\n this._stateRefreshInterval = null;\n\n // getVersion() / getHardwareId() promise plumbing\n this._versionResolve = null;\n this._hardwareIdResolve = null;\n\n // Volume getter/setter promise plumbing (get_volume / set_volume).\n // Speaker and microphone are tracked separately so two in-flight\n // requests can't collide on the same slot.\n this._volumeResolve = null;\n this._micVolumeResolve = null;\n\n // subscribeLogs(): a Set of {onLine, onError} subscribers. The\n // first add sends `subscribe_logs`; removing the last sends\n // `unsubscribe_logs`. We keep a single daemon-side stream and\n // fan out to local subscribers in `_handleRobotMessage`.\n this._logSubscribers = new Set();\n\n // startSession() promise plumbing\n this._sessionResolve = null;\n this._sessionReject = null;\n this._iceConnected = false;\n this._dcOpen = false;\n\n // Motion-completion plumbing for wakeUp() / gotoSleep().\n //\n // The daemon's data-channel handler dispatches `wake_up` and\n // `goto_sleep` as async tasks and replies with\n // `{status: \"ok\", command, completed: true}` when the trajectory\n // ACTUALLY finishes (or with `{error, command}` on failure). We\n // surface that as a Promise so callers can `await robot.gotoSleep()`\n // and chain `setMotorMode('disabled')` without racing the\n // trajectory player.\n //\n // Queues, not single slots: the data-channel protocol has no\n // request IDs, but FIFO ordering is guaranteed by the daemon's\n // serialised dispatcher, so the N-th response matches the N-th\n // request. A queue makes back-to-back calls (e.g. teardown firing\n // on top of an in-flight wake_up) safe; a single slot would\n // silently drop the earlier awaiter.\n this._pendingMotionCompletions = {\n wake_up: [],\n goto_sleep: [],\n };\n\n // Set by attachVideo()\n this._videoElement = null;\n }\n\n // ─── Read-only properties ────────────────────────────────────────────\n\n /** @returns {\"disconnected\"|\"connected\"|\"streaming\"} */\n get state() { return this._state; }\n\n /** @returns {Array<{id: string, meta: {name: string}}>} */\n get robots() { return this._robots; }\n\n /**\n * Latest robot state (same shape as the \"state\" event detail).\n * Mirrors the daemon's wire format — fields appear only once the\n * daemon has sent the corresponding source field. Use the exported\n * math utilities (``matrixToRpy``, ``radToDeg``) for human units.\n * @returns {{\n * head?: number[],\n * antennas?: number[],\n * body_yaw?: number,\n * motor_mode?: \"enabled\"|\"disabled\"|\"gravity_compensation\",\n * is_move_running?: boolean,\n * }}\n */\n get robotState() { return this._robotState; }\n\n /** @returns {string|null} HuggingFace username, set after authenticate(). */\n get username() { return this._username; }\n\n /** @returns {boolean} True if a valid HF token is available. */\n get isAuthenticated() { return !!this._token; }\n\n /** @returns {boolean} True if the robot's SDP offered bidirectional audio. */\n get micSupported() { return this._micSupported; }\n\n /** @returns {boolean} */\n get micMuted() { return this._micMuted; }\n\n /** @returns {boolean} */\n get audioMuted() { return this._audioMuted; }\n\n /**\n * Peer id of the robot the embedding host wants this session to\n * target, captured from the URL at construction time. Apps that\n * want to support iframe-embedding without forcing the user to\n * re-pick a robot read this and pass it straight to\n * `startSession()` once `connect()` resolves:\n *\n * await robot.connect();\n * await robot.startSession(robot.preselectedRobotId ?? pickedId);\n *\n * Returns `null` when the URL carries no `robot_peer_id` (typical\n * standalone Space load). The value is also exposed on the\n * \"robotsChanged\" payload via the `meta` sidecar for\n * convenience, but the most direct read is right here.\n *\n * @returns {string|null}\n */\n get preselectedRobotId() { return this._preselectedRobotId; }\n\n /**\n * Convenience flag for apps that want to branch their UX on\n * \"am I embedded in a host shell?\". True iff the URL carried a\n * `robot_peer_id` hint at construction time (which only happens\n * when a host iframe — mobile shell, vibe-coder preview, etc. —\n * is the parent). Apps typically use it to skip their robot\n * picker and their sign-in screen, since both are duplicated\n * work the host has already done.\n *\n * @returns {boolean}\n */\n get isEmbedded() { return this._preselectedRobotId !== null; }\n\n /**\n * Internal: try to honour the `autoStartFromUrl` constructor\n * option. Called from the signaling-message handler after every\n * `robotsChanged` emit, so a robot that comes online after the\n * SDK is already `connected` still triggers the auto-start.\n * No-op unless `autoStartFromUrl` is set, the URL carries a\n * preselect, the SDK is `connected`, the preselected robot is\n * in the latest list, and we haven't already attempted in this\n * page load. Errors are swallowed to a `console.warn` — the\n * normal `startSession` rejection / `sessionRejected` event\n * still fires for app-level handling.\n *\n * Defers the actual `startSession()` call by one macrotask\n * (`setTimeout(..., 0)`) so it runs OUTSIDE the\n * `_handleSignalingMessage` callstack that just processed the\n * `'list'` message. Reproduced on Android WebView: firing\n * `startSession` synchronously inside the SSE handler races the\n * daemon's setup, leading to a connected-but-no-keyframe state\n * where the receiver eternally NACKs and the iframe shows a\n * black <video>. The macrotask-deferral is the minimum nudge\n * that consistently resolves the race in our reproduction; if\n * it ever proves insufficient on slower hardware, bump to\n * a small explicit delay (e.g. 250 ms).\n */\n _maybeAutoStart() {\n if (!this._autoStartFromUrl) return;\n if (this._autoStartAttempted) return;\n if (!this._preselectedRobotId) return;\n if (this._state !== 'connected') return;\n const match = this._robots.find((r) => r.id === this._preselectedRobotId);\n if (!match) return;\n this._autoStartAttempted = true;\n const peerId = this._preselectedRobotId;\n setTimeout(() => {\n // Re-check state in case a manual stopSession / disconnect\n // landed between the schedule and the fire.\n if (this._state !== 'connected') return;\n this.startSession(peerId).catch((err) => {\n console.warn('[reachy-mini] autoStartFromUrl: startSession rejected:', err);\n });\n }, 0);\n }\n\n // ─── Auth ────────────────────────────────────────────────────────────\n\n /**\n * Check for a valid HuggingFace token.\n *\n * Resolution order:\n * 1. URL fragment hand-off (`#hf_token=…&hf_username=…&hf_token_expires=…`).\n * A host iframe — typically the Reachy Mini mobile app or a\n * vibe-coder preview — can pass credentials through the URL\n * fragment to bypass HF's `X-Frame-Options: SAMEORIGIN` block\n * on `huggingface.co/login`. Seeded into `sessionStorage` and\n * then stripped from the address bar so a page reload does not\n * keep the credentials visible.\n * 2. OAuth redirect callback (standalone Space, first sign-in).\n * 3. `sessionStorage` cache (subsequent loads in any context).\n *\n * @returns {Promise<boolean>} true → token ready, false → call login()\n */\n async authenticate() {\n try {\n // 1. Iframe hand-off. No-op when the URL has no fragment or\n // the fragment carries no `hf_token`, so this is free on\n // standalone Space loads.\n consumeFragmentCredentials();\n\n // 2. OAuth redirect callback.\n const result = await oauthHandleRedirectIfPresent();\n if (result) {\n this._username = result.userInfo.preferred_username || result.userInfo.name;\n this._token = result.accessToken;\n this._tokenExpires = result.accessTokenExpiresAt;\n sessionStorage.setItem('hf_token', this._token);\n sessionStorage.setItem('hf_username', this._username);\n sessionStorage.setItem('hf_token_expires', this._tokenExpires);\n return true;\n }\n\n // 3. sessionStorage cache. Both paths above also write here,\n // so this is the canonical lookup for any subsequent call.\n const t = sessionStorage.getItem('hf_token');\n const u = sessionStorage.getItem('hf_username');\n const e = sessionStorage.getItem('hf_token_expires');\n if (t && u && e && new Date(e) > new Date()) {\n this._token = t;\n this._username = u;\n this._tokenExpires = e;\n return true;\n }\n return false;\n } catch (e) {\n console.error('Auth error:', e);\n return false;\n }\n }\n\n /** Redirect the browser to the HuggingFace OAuth login page. */\n async login() {\n const opts = {};\n if (this._clientId) opts.clientId = this._clientId;\n window.location.href = await oauthLoginUrl(opts);\n }\n\n /** Clear stored HF credentials and disconnect everything. */\n logout() {\n sessionStorage.removeItem('hf_token');\n sessionStorage.removeItem('hf_username');\n sessionStorage.removeItem('hf_token_expires');\n this._username = null;\n this._tokenExpires = null;\n this.disconnect();\n }\n\n // ─── Lifecycle ───────────────────────────────────────────────────────\n\n /**\n * Open SSE signaling connection. Resolves once the server sends `welcome`.\n * Emits \"robotsChanged\" as robots come and go.\n * @param {string} [token] — HF access token. Omit to use the one from authenticate().\n * @returns {Promise<void>}\n */\n async connect(token) {\n if (this._state !== 'disconnected') throw new Error('Already connected');\n if (token) this._token = token;\n if (!this._token) throw new Error('No token — call authenticate() first or pass a token');\n this._sseAbortController = new AbortController();\n\n let res;\n try {\n // Token goes in the Authorization header, not the URL —\n // keeps it out of DevTools Network tab, browser history,\n // Referer, and any server/proxy access log. We use fetch\n // + manual stream reader (below) rather than EventSource\n // specifically to allow custom headers.\n res = await fetch(\n `${this._signalingUrl}/events`,\n {\n signal: this._sseAbortController.signal,\n headers: { 'Authorization': `Bearer ${this._token}` },\n },\n );\n } catch (e) {\n this._sseAbortController = null;\n throw e;\n }\n if (!res.ok) {\n this._sseAbortController = null;\n throw new Error(`HTTP ${res.status}`);\n }\n\n return new Promise((resolve, reject) => {\n let welcomed = false;\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n const readLoop = async () => {\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop();\n for (const line of lines) {\n if (!line.startsWith('data:')) continue;\n try {\n const msg = JSON.parse(line.slice(5).trim());\n if (!welcomed && msg.type === 'welcome') {\n welcomed = true;\n this._peerId = msg.peerId;\n this._state = 'connected';\n await this._sendToServer({\n type: 'setPeerStatus',\n roles: ['listener'],\n meta: { name: this._appName },\n });\n this._emit('connected', { peerId: msg.peerId });\n resolve();\n }\n this._handleSignalingMessage(msg);\n } catch (_) { /* malformed JSON — skip */ }\n }\n }\n } catch (e) {\n if (e.name !== 'AbortError') {\n this._emit('error', { source: 'signaling', error: e });\n }\n if (!welcomed) { reject(e); return; }\n }\n // SSE stream ended (server closed or network drop)\n if (this._state !== 'disconnected') {\n this._state = 'disconnected';\n this._emit('disconnected', { reason: 'SSE closed' });\n }\n if (!welcomed) reject(new Error('Connection closed before welcome'));\n };\n\n readLoop();\n });\n }\n\n /**\n * One-shot bring-up: auth → SSE connect → robot selection → session →\n * wake up. The all-in-one entry point that captures the common\n * \"embed *or* standalone, just get me streaming\" flow so each\n * consumer does not have to re-implement it.\n *\n * What it does, in order:\n * 1. **Auth.** If `this._token` is not set, calls `authenticate()`\n * (which honours the iframe URL-fragment hand-off, the OAuth\n * redirect callback, and the `sessionStorage` cache). Throws if\n * none yield a token — the consumer should call `login()` and\n * retry after the redirect. Pass an explicit `token` to skip\n * `authenticate()` entirely.\n * 2. **Connect.** If `state === 'disconnected'`, opens the SSE\n * signaling channel.\n * 3. **Pick a robot.**\n * - **Embed mode** (`this.isEmbedded`): uses\n * `this._preselectedRobotId` from the URL. No picker callback\n * invoked; we briefly wait for that robot to appear in the\n * SSE list, then proceed.\n * - **Standalone**: GETs `/api/robot-status` for the owner's\n * robots with busy state, dedupes by `install_id`, sorts by\n * freshness. If `autoPickIfSingle` and exactly one free, picks\n * it. Else calls the consumer-supplied `pickRobot(robots)`\n * callback. Throws if neither yields an id.\n * 4. **Start session.** Awaits `startSession(robotId)` (ICE + DC).\n * 5. **Wake up.** Awaits `ensureAwake()` so sliders don't silently\n * no-op against a torque-off robot.\n *\n * @param {{\n * token?: string, // skip authenticate(); use this raw HF token\n * pickRobot?: (robots: Array<{\n * id: string,\n * name: string|null,\n * busy: boolean,\n * activeApp: string|null,\n * meta: object,\n * lastSeenAgeSeconds: number|null,\n * }>) => Promise<string|null>, // called only in standalone, multi-robot case\n * autoPickIfSingle?: boolean, // default true — skip the callback when 1 free robot\n * filterBusy?: boolean, // default true — hide busy robots from the picker\n * wakeOnConnect?: boolean, // default true — call ensureAwake() after startSession\n * }} [options]\n * @returns {Promise<{\n * robotId: string,\n * robotName: string|null,\n * isEmbedded: boolean,\n * alreadyStreaming?: boolean,\n * }>}\n */\n async autoConnect(options = {}) {\n const {\n token = null,\n pickRobot = null,\n autoPickIfSingle = true,\n filterBusy = true,\n wakeOnConnect = true,\n } = options;\n\n // Idempotent fast-path: caller invoked autoConnect() on an\n // already-streaming session (e.g. on a route change inside an\n // SPA). Return the current selection rather than tearing down.\n if (this._state === 'streaming') {\n const cur = this._robots?.find((r) => r.id === this._selectedRobotId);\n return {\n robotId: this._selectedRobotId,\n robotName: cur?.meta?.name ?? null,\n isEmbedded: this.isEmbedded,\n alreadyStreaming: true,\n };\n }\n\n // autoConnect takes over the bring-up — disable the SDK's\n // own `autoStartFromUrl` so the two paths don't race and\n // both call `startSession()` against the same preselected\n // robot. The race used to manifest as central rejecting the\n // second attempt with \"Robot is busy: <appName>\" — the\n // appName being our own first attempt. Restored on the way\n // out so a later `stopSession()` followed by a fresh\n // listener attach still benefits from auto-start.\n const _prevAutoStartFromUrl = this._autoStartFromUrl;\n this._autoStartFromUrl = false;\n\n try {\n // 1. Auth.\n if (token) {\n this._token = token;\n } else if (!this._token) {\n const ok = await this.authenticate();\n if (!ok) {\n // login() does a full page redirect; we don't trigger\n // it here so the consumer can decide (a desktop tray\n // wants different recovery than a standalone Space).\n throw new Error('Not authenticated — call login() or pass a token');\n }\n }\n\n // 2. SSE connect.\n if (this._state === 'disconnected') {\n await this.connect();\n }\n\n // 3. Resolve the target robot.\n let robotId;\n let robotName = null;\n if (this.isEmbedded) {\n robotId = this._preselectedRobotId;\n // Wait briefly for the preselected robot to surface in the\n // SSE list. Best-effort: if it never shows we still try\n // startSession() — central may know about a robot the SSE\n // list pushes only a moment later.\n try {\n await this._waitForRobotInList(robotId, 5000);\n } catch (_) { /* fall through */ }\n const found = this._robots?.find((r) => r.id === robotId);\n robotName = found?.meta?.name ?? null;\n } else {\n const robots = await this._fetchOwnedRobots({ filterBusy });\n if (robots.length === 0) {\n throw new Error('No reachable robots');\n }\n if (autoPickIfSingle && robots.length === 1 && !robots[0].busy) {\n robotId = robots[0].id;\n robotName = robots[0].name;\n } else if (pickRobot) {\n const picked = await pickRobot(robots);\n if (!picked) throw new Error('Robot selection cancelled');\n robotId = picked;\n robotName = robots.find((r) => r.id === picked)?.name ?? null;\n } else {\n throw new Error(\n 'Multiple robots available — pass a pickRobot callback to autoConnect()',\n );\n }\n }\n\n // 4. Session.\n await this.startSession(robotId);\n\n // 5. Wake.\n if (wakeOnConnect && typeof this.ensureAwake === 'function') {\n try { await this.ensureAwake(); }\n catch (e) { console.warn('[reachy-mini] autoConnect: ensureAwake failed:', e); }\n }\n\n return { robotId, robotName, isEmbedded: this.isEmbedded };\n } finally {\n this._autoStartFromUrl = _prevAutoStartFromUrl;\n }\n }\n\n /**\n * Fetch the caller's robots with busy state, deduped + sorted.\n * One-shot snapshot — no live subscription. Falls back to the SSE\n * `_robots` cache if `/api/robot-status` is unavailable (older\n * central deployments don't expose it).\n *\n * Dedup: same physical robot can appear twice transiently after a\n * daemon reinstall (new peerId, same install_id). Last-writer-wins\n * on `install_id`, then `hardware_id`, then `peerId` (= no dedup).\n *\n * @returns {Promise<Array<{\n * id: string,\n * name: string|null,\n * busy: boolean,\n * activeApp: string|null,\n * meta: object,\n * lastSeenAgeSeconds: number|null,\n * }>>}\n */\n async _fetchOwnedRobots({ filterBusy = true } = {}) {\n try {\n const res = await fetch(`${this._signalingUrl}/api/robot-status`, {\n headers: { 'Authorization': `Bearer ${this._token}` },\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const json = await res.json();\n const seen = new Map(); // dedup key → projected robot\n for (const r of (json.robots || [])) {\n if (filterBusy && r.busy) continue;\n const key = r.meta?.install_id ?? r.meta?.hardware_id ?? r.peerId;\n seen.set(key, {\n id: r.peerId,\n name: r.robotName ?? r.meta?.name ?? null,\n busy: !!r.busy,\n activeApp: r.activeApp ?? null,\n meta: r.meta ?? {},\n lastSeenAgeSeconds: r.last_seen_age_seconds ?? null,\n });\n }\n return Array.from(seen.values()).sort(\n (a, b) => (a.lastSeenAgeSeconds ?? Infinity) - (b.lastSeenAgeSeconds ?? Infinity),\n );\n } catch (e) {\n console.warn('[reachy-mini] /api/robot-status unavailable, using SSE list:', e);\n return (this._robots || []).map((r) => ({\n id: r.id,\n name: r.meta?.name ?? null,\n busy: false, // unknown — SSE list does not carry busy state\n activeApp: null,\n meta: r.meta ?? {},\n lastSeenAgeSeconds: null,\n }));\n }\n }\n\n /**\n * Resolve once `robotId` appears in `_robots`, or reject after\n * `timeoutMs`. Used by `autoConnect()`'s embed branch so the preselected\n * robot has a chance to surface from the first SSE `list` push before\n * `startSession()` is fired.\n */\n _waitForRobotInList(robotId, timeoutMs) {\n if (this._robots?.find((r) => r.id === robotId)) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const onChange = () => {\n if (this._robots?.find((r) => r.id === robotId)) {\n this.removeEventListener('robotsChanged', onChange);\n clearTimeout(timeoutId);\n resolve();\n }\n };\n const timeoutId = setTimeout(() => {\n this.removeEventListener('robotsChanged', onChange);\n reject(new Error(`Timeout waiting for robot ${robotId} in list`));\n }, timeoutMs);\n this.addEventListener('robotsChanged', onChange);\n });\n }\n\n /**\n * Start a WebRTC session with the given robot.\n * Acquires the microphone (if enabled), negotiates SDP, and waits for\n * both ICE connection and data channel to be ready before resolving.\n * Emits \"videoTrack\" when the robot's camera stream arrives.\n * Emits \"micSupported\" once SDP negotiation reveals whether the robot\n * accepts bidirectional audio.\n * @param {string} robotId — one of the ids from the robots list\n * @returns {Promise<void>}\n */\n async startSession(robotId) {\n if (this._state !== 'connected') throw new Error('Not connected');\n this._selectedRobotId = robotId;\n this._iceConnected = false;\n this._dcOpen = false;\n this._micSupported = false;\n // Buffer for ICE candidates that arrive before the SDP\n // exchange completes (see _handlePeerMessage). Reset on every\n // fresh session so stale candidates from a previous attempt\n // don't get applied to a new RTCPeerConnection.\n this._pendingRemoteIce = [];\n\n // Acquire mic eagerly so the browser permission prompt appears now,\n // but tracks stay disabled (muted) until the user explicitly unmutes.\n if (this._enableMicrophone) {\n try {\n this._micStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n this._micStream.getAudioTracks().forEach(t => { t.enabled = false; });\n this._micMuted = true;\n } catch (e) {\n console.warn('Microphone not available:', e);\n // Fall back to a silent placeholder track. We MUST add an\n // audio track before createAnswer or the answer SDP comes\n // back as recvonly for audio - which negotiates the audio\n // SENDER side off the wire entirely. A host that wants to\n // later inject a different audio source (e.g. a synthesised\n // AI voice via replaceTrack on the sender) needs a live\n // sendrecv slot, even if the initial track is silent.\n try {\n const ctx = new (window.AudioContext || window.webkitAudioContext)();\n const dst = ctx.createMediaStreamDestination();\n // A muted oscillator keeps the track \"alive\" without\n // emitting any audible signal.\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n gain.gain.value = 0;\n osc.connect(gain).connect(dst);\n osc.start();\n this._micStream = dst.stream;\n this._micStream.getAudioTracks().forEach(t => { t.enabled = false; });\n this._micMuted = true;\n this._silentMicFallback = { ctx, osc };\n } catch (fallbackErr) {\n console.warn('Silent mic fallback failed:', fallbackErr);\n this._micStream = null;\n }\n }\n }\n\n this._pc = new RTCPeerConnection({\n iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],\n });\n\n return new Promise((resolve, reject) => {\n this._sessionResolve = resolve;\n this._sessionReject = reject;\n\n this._pc.ontrack = (e) => {\n if (e.track.kind === 'video') {\n // Tell the receiver's jitter buffer to minimise its hold\n // time. Both properties target the same internal buffer;\n // browsers ignore whichever they don't implement.\n const ms = this._videoJitterBufferTargetMs;\n try { e.receiver.jitterBufferTarget = ms; } catch (_) {}\n try { e.receiver.playoutDelayHint = ms / 1000; } catch (_) {}\n this._emit('videoTrack', { track: e.track, stream: e.streams[0] });\n }\n };\n\n this._pc.onicecandidate = async (e) => {\n if (e.candidate && this._sessionId) {\n await this._sendToServer({\n type: 'peer',\n sessionId: this._sessionId,\n ice: {\n candidate: e.candidate.candidate,\n sdpMLineIndex: e.candidate.sdpMLineIndex,\n sdpMid: e.candidate.sdpMid,\n },\n });\n }\n };\n\n this._pc.oniceconnectionstatechange = () => {\n const s = this._pc?.iceConnectionState;\n if (!s) return;\n if (s === 'connected' || s === 'completed') {\n this._iceConnected = true;\n this._checkSessionReady();\n } else if (s === 'failed') {\n const err = new Error('ICE connection failed');\n if (this._sessionReject) {\n this._sessionReject(err);\n this._sessionResolve = null;\n this._sessionReject = null;\n }\n this._emit('error', { source: 'webrtc', error: err });\n } else if (s === 'disconnected') {\n this._emit('error', { source: 'webrtc', error: new Error('ICE disconnected') });\n }\n };\n\n this._pc.ondatachannel = (e) => {\n this._dc = e.channel;\n this._dc.onopen = () => {\n this._dcOpen = true;\n this._checkSessionReady();\n };\n this._dc.onmessage = (ev) => this._handleRobotMessage(JSON.parse(ev.data));\n };\n\n this._sendToServer({ type: 'startSession', peerId: robotId }).then((r) => {\n if (r?.type === 'sessionRejected') {\n this._failSessionRejected(r);\n return;\n }\n if (r?.sessionId) this._sessionId = r.sessionId;\n });\n });\n }\n\n /**\n * Internal: handle a sessionRejected response from central.\n * Releases resources allocated by startSession() (RTCPeerConnection,\n * microphone stream) and rejects the pending startSession() promise\n * with an Error carrying `.reason` and `.activeApp`.\n *\n * Called from both the POST-response path (primary) and the SSE\n * handler (defensive, in case the server changes).\n */\n _failSessionRejected(msg) {\n const err = new Error(\n msg.reason === 'robot_busy'\n ? `Robot is busy: \"${msg.activeApp || 'another app'}\" is already connected`\n : `Session rejected: ${msg.reason || 'unknown reason'}`\n );\n err.reason = msg.reason;\n err.activeApp = msg.activeApp;\n\n // Release resources allocated optimistically in startSession()\n // before we knew the server would refuse.\n if (this._pc) { this._pc.close(); this._pc = null; }\n if (this._micStream) { this._micStream.getTracks().forEach(t => t.stop()); this._micStream = null; }\n this._iceConnected = false;\n this._dcOpen = false;\n this._micMuted = true;\n this._micSupported = false;\n\n this._emit('sessionRejected', { reason: msg.reason, activeApp: msg.activeApp });\n\n if (this._sessionReject) {\n const reject = this._sessionReject;\n this._sessionResolve = null;\n this._sessionReject = null;\n reject(err);\n }\n }\n\n /**\n * End the WebRTC session. Returns to \"connected\" state so you can\n * startSession() again with the same or a different robot.\n * @returns {Promise<void>}\n */\n async stopSession() {\n if (this._versionResolve) { this._versionResolve(null); this._versionResolve = null; }\n if (this._hardwareIdResolve) { this._hardwareIdResolve(null); this._hardwareIdResolve = null; }\n if (this._volumeResolve) { this._volumeResolve(null); this._volumeResolve = null; }\n if (this._micVolumeResolve) { this._micVolumeResolve(null); this._micVolumeResolve = null; }\n // Drop any active log subscribers — the daemon-side subprocess\n // is torn down on peer-disconnect, so resubscribing across a\n // reconnect requires a fresh subscribeLogs() call from the\n // consumer.\n this._logSubscribers.clear();\n // Drain any in-flight wakeUp() / gotoSleep() awaiters before\n // the data channel is killed below, so callers don't sit on a\n // promise that can never resolve.\n this._rejectPendingMotionCompletions(new Error('Session stopped'));\n if (this._sessionReject) {\n this._sessionReject(new Error('Session stopped'));\n this._sessionResolve = null;\n this._sessionReject = null;\n }\n\n if (this._stateRefreshInterval) { clearInterval(this._stateRefreshInterval); this._stateRefreshInterval = null; }\n if (this._latencyMonitorId) { clearInterval(this._latencyMonitorId); this._latencyMonitorId = null; }\n\n if (this._sessionId) {\n await this._sendToServer({ type: 'endSession', sessionId: this._sessionId });\n }\n\n if (this._micStream) { this._micStream.getTracks().forEach(t => t.stop()); this._micStream = null; }\n this._micMuted = true;\n this._micSupported = false;\n\n if (this._pc) { this._pc.close(); this._pc = null; }\n if (this._dc) { this._dc.close(); this._dc = null; }\n\n this._sessionId = null;\n this._iceConnected = false;\n this._dcOpen = false;\n\n const wasStreaming = this._state === 'streaming';\n if (wasStreaming) {\n this._state = 'connected';\n this._emit('sessionStopped', { reason: 'user' });\n }\n }\n\n /**\n * Full teardown — abort SSE, close WebRTC.\n * Auth state is preserved (call logout() to also clear credentials).\n */\n disconnect() {\n if (this._sseAbortController) { this._sseAbortController.abort(); this._sseAbortController = null; }\n\n if (this._versionResolve) { this._versionResolve(null); this._versionResolve = null; }\n if (this._hardwareIdResolve) { this._hardwareIdResolve(null); this._hardwareIdResolve = null; }\n if (this._volumeResolve) { this._volumeResolve(null); this._volumeResolve = null; }\n if (this._micVolumeResolve) { this._micVolumeResolve(null); this._micVolumeResolve = null; }\n this._logSubscribers.clear();\n // Same rationale as in stopSession(): drain pending motion\n // awaiters before tearing down the data channel.\n this._rejectPendingMotionCompletions(new Error('Disconnected'));\n if (this._sessionReject) {\n this._sessionReject(new Error('Disconnected'));\n this._sessionResolve = null;\n this._sessionReject = null;\n }\n\n if (this._stateRefreshInterval) { clearInterval(this._stateRefreshInterval); this._stateRefreshInterval = null; }\n if (this._latencyMonitorId) { clearInterval(this._latencyMonitorId); this._latencyMonitorId = null; }\n\n if (this._sessionId && this._token) {\n this._sendToServer({ type: 'endSession', sessionId: this._sessionId }); // fire-and-forget\n }\n\n if (this._micStream) { this._micStream.getTracks().forEach(t => t.stop()); this._micStream = null; }\n if (this._pc) { this._pc.close(); this._pc = null; }\n if (this._dc) { this._dc.close(); this._dc = null; }\n\n this._sessionId = null;\n this._micMuted = true;\n this._micSupported = false;\n this._iceConnected = false;\n this._dcOpen = false;\n this._peerId = null;\n this._robots = [];\n this._state = 'disconnected';\n this._emit('disconnected', { reason: 'user' });\n }\n\n // ─── Commands ────────────────────────────────────────────────────────\n // All return false if the data channel is not open, true if sent.\n\n /**\n * Send a target pose to the robot. Wire-shape, raw units only —\n * single source of truth for motion commands. Every field is\n * optional; omitted fields leave the daemon's previous target\n * unchanged, so partial updates compose naturally.\n *\n * For human units (degrees), use the ``setHeadRpyDeg`` /\n * ``setAntennasDeg`` / ``setBodyYawDeg`` thin wrappers below.\n *\n * @param {object} [target]\n * @param {number[]} [target.head] 16-element flat row-major 4×4\n * matrix (full SE(3); preserves translation, no XYZ loss).\n * @param {number[]} [target.antennas] ``[rightRad, leftRad]``.\n * @param {number} [target.body_yaw] Body yaw in radians.\n * @returns {boolean} false if the data channel is not open.\n * @throws {TypeError} if any provided field has the wrong shape or\n * contains a non-finite value (NaN, Infinity). Validation runs at\n * the JS boundary so caller mistakes surface with a stack trace\n * pointing to the call site, not as a confusing daemon-side error.\n */\n setTarget({ head, antennas, body_yaw } = {}) {\n const cmd = { type: \"set_full_target\" };\n if (head !== undefined) {\n if (!Array.isArray(head) || head.length !== 16\n || !head.every((n) => Number.isFinite(n))) {\n throw new TypeError(\n 'setTarget: head must be a 16-element flat row-major 4×4 matrix '\n + `of finite numbers; got ${Array.isArray(head) ? `Array(${head.length})` : typeof head}`\n );\n }\n cmd.head = head;\n }\n if (antennas !== undefined) {\n if (!Array.isArray(antennas) || antennas.length !== 2\n || !antennas.every((n) => Number.isFinite(n))) {\n throw new TypeError(\n 'setTarget: antennas must be [rightRad, leftRad] (2 finite numbers); '\n + `got ${Array.isArray(antennas) ? `Array(${antennas.length})` : typeof antennas}`\n );\n }\n cmd.antennas = antennas;\n }\n if (body_yaw !== undefined) {\n if (!Number.isFinite(body_yaw)) {\n throw new TypeError(\n `setTarget: body_yaw must be a finite number (radians); got ${body_yaw}`\n );\n }\n cmd.body_yaw = body_yaw;\n }\n return this._sendCommand(cmd);\n }\n\n /**\n * Smooth daemon-side interpolation to a target pose over\n * ``duration`` seconds. Mirrors ``setTarget``'s wire shape (head\n * is a 16-element flat row-major 4×4, antennas are\n * ``[rightRad, leftRad]``, body_yaw is radians) and adds a\n * required ``duration`` field. The daemon dispatches the command\n * to its lerp planner instead of jumping to the target.\n *\n * Use this for one-shot smooth approaches to an arbitrary pose\n * (e.g. soft-return-to-base after recording, or pre-positioning\n * before a streamed playback). For continuous streamed motion,\n * use ``setTarget`` and lerp client-side.\n *\n * @param {{head?: number[], antennas?: number[], body_yaw?: number, duration: number}} args\n * @returns {boolean} false if the data channel is not open.\n * @throws {TypeError} if any provided field has the wrong shape\n * or contains a non-finite value (NaN, Infinity), or if\n * ``duration`` is missing or non-positive.\n */\n gotoTarget({ head, antennas, body_yaw, duration } = {}) {\n const cmd = { type: \"goto_target\" };\n if (head !== undefined) {\n if (!Array.isArray(head) || head.length !== 16\n || !head.every((n) => Number.isFinite(n))) {\n throw new TypeError(\n 'gotoTarget: head must be a 16-element flat row-major 4×4 matrix '\n + `of finite numbers; got ${Array.isArray(head) ? `Array(${head.length})` : typeof head}`\n );\n }\n cmd.head = head;\n }\n if (antennas !== undefined) {\n if (!Array.isArray(antennas) || antennas.length !== 2\n || !antennas.every((n) => Number.isFinite(n))) {\n throw new TypeError(\n 'gotoTarget: antennas must be [rightRad, leftRad] (2 finite numbers); '\n + `got ${Array.isArray(antennas) ? `Array(${antennas.length})` : typeof antennas}`\n );\n }\n cmd.antennas = antennas;\n }\n if (body_yaw !== undefined) {\n if (!Number.isFinite(body_yaw)) {\n throw new TypeError(\n `gotoTarget: body_yaw must be a finite number (radians); got ${body_yaw}`\n );\n }\n cmd.body_yaw = body_yaw;\n }\n if (!Number.isFinite(duration) || duration <= 0) {\n throw new TypeError(\n `gotoTarget: duration must be a positive finite number (seconds); got ${duration}`\n );\n }\n cmd.duration = duration;\n return this._sendCommand(cmd);\n }\n\n /**\n * Set head orientation from roll/pitch/yaw in degrees.\n * Convenience wrapper over ``setTarget``.\n * @param {number} rollDeg @param {number} pitchDeg @param {number} yawDeg\n * @returns {boolean}\n */\n setHeadRpyDeg(rollDeg, pitchDeg, yawDeg) {\n return this.setTarget({ head: rpyToMatrix(rollDeg, pitchDeg, yawDeg).flat() });\n }\n\n /**\n * Set antenna positions from degrees.\n * Convenience wrapper over ``setTarget``.\n * @param {number} rightDeg @param {number} leftDeg\n * @returns {boolean}\n */\n setAntennasDeg(rightDeg, leftDeg) {\n return this.setTarget({ antennas: [degToRad(rightDeg), degToRad(leftDeg)] });\n }\n\n /**\n * Set body yaw from degrees.\n * Convenience wrapper over ``setTarget``.\n * @param {number} yawDeg\n * @returns {boolean}\n */\n setBodyYawDeg(yawDeg) {\n return this.setTarget({ body_yaw: degToRad(yawDeg) });\n }\n\n /**\n * Play a sound file on the robot.\n * @param {string} file — filename available on the robot (e.g. \"wake_up.wav\")\n * @returns {boolean}\n */\n playSound(file) {\n return this._sendCommand({ type: \"play_sound\", file });\n }\n\n /**\n * Set the motor control mode.\n *\n * @param {\"enabled\"|\"disabled\"|\"gravity_compensation\"} mode\n * - \"enabled\" torque on, position-controlled.\n * - \"disabled\" torque off; the robot is backdrivable\n * and will not hold any pose.\n * - \"gravity_compensation\" torque on in current-control mode;\n * motors actively cancel gravity so the\n * robot is easy to move by hand.\n * @returns {boolean} false if the data channel is not open.\n */\n setMotorMode(mode) {\n return this._sendCommand({ type: \"set_motor_mode\", mode });\n }\n\n /**\n * Toggle torque on/off, optionally per-motor.\n *\n * @param {boolean} on\n * @param {string[]} [ids] motor names (e.g. [\"left_antenna\"]). When\n * omitted, applies globally — equivalent to setMotorMode(\"enabled\"\n * | \"disabled\").\n * @returns {boolean} false if the data channel is not open.\n */\n setMotorTorque(on, ids = null) {\n return this._sendCommand({ type: \"set_torque\", on, ids });\n }\n\n /**\n * Play the wake-up animation (full head/antennas trajectory on the\n * robot, ~1-3 s depending on the starting head pose) and resolve\n * when the daemon reports the trajectory player has actually\n * finished.\n *\n * This helper sends a ``set_motor_mode: \"enabled\"`` command *before*\n * the ``wake_up`` command so the animation actually moves the motors.\n * The robot's ``wake_up`` handler does not touch motor mode itself;\n * if torque is off when the trajectory runs, the commanded positions\n * are silently ignored and the robot stays limp. Both commands\n * travel over the same data channel so ordering at the backend is\n * preserved.\n *\n * The returned promise resolves on the daemon's\n * ``{command: \"wake_up\", completed: true}`` response (sent after\n * the trajectory player is fully done, not just when the command\n * is enqueued). Lets a UI overlay (e.g. the host's \"Wake-up\" step)\n * stay up for exactly the right duration, and lets callers chain\n * setup that depends on the head being in the awake pose without\n * racing the trajectory.\n *\n * Semantics match the REST endpoint ``POST /api/move/play/wake_up``\n * plus the LAN convention of enabling motors before playing motion\n * trajectories.\n *\n * @param {object} [options]\n * @param {number} [options.timeoutMs=8000] hard upper bound; the\n * promise rejects with a TimeoutError-shaped Error if the daemon\n * stops responding (e.g. data channel went down mid-animation\n * without firing close events).\n * @returns {Promise<void>}\n */\n wakeUp({ timeoutMs = 8000 } = {}) {\n this._sendCommand({ type: \"set_motor_mode\", mode: \"enabled\" });\n return this._sendCommandAwaitCompletion(\"wake_up\", timeoutMs);\n }\n\n /**\n * Play the goto-sleep animation and resolve when the daemon reports\n * the trajectory player has finished. See ``wakeUp`` for the\n * completion-signal rationale.\n *\n * Does NOT touch motor mode: the daemon's ``goto_sleep`` handler\n * manages the transition out of torque on its own (motors must stay\n * powered during the trajectory to move into the sleep pose, then\n * are typically disabled by the daemon once the pose is reached).\n *\n * The awaitable form lets callers chain ``setMotorMode('disabled')``\n * AFTER the trajectory lands instead of racing it, which previously\n * caused the head to drop mid-animation when consumers tore down\n * too eagerly.\n *\n * Semantics match ``POST /api/move/play/goto_sleep`` and the\n * ``\"goto_sleep\"`` WebRTC command.\n *\n * @param {object} [options]\n * @param {number} [options.timeoutMs=8000]\n * @returns {Promise<void>}\n */\n gotoSleep({ timeoutMs = 8000 } = {}) {\n return this._sendCommandAwaitCompletion(\"goto_sleep\", timeoutMs);\n }\n\n /**\n * Internal: send a motion command and resolve when the daemon's\n * matching ``{command, completed: true}`` response lands.\n *\n * Pushes one entry onto ``_pendingMotionCompletions[command]``; the\n * data-channel reader (``_handleRobotMessage``) shifts the oldest\n * entry off the queue when a response arrives, which preserves the\n * FIFO matching that the daemon's serialised dispatcher relies on.\n *\n * Rejects immediately if the data channel is not open; the underlying\n * ``_sendCommand`` returns false in that case and we never enqueue an\n * awaiter that the daemon could never reach.\n *\n * @param {\"wake_up\"|\"goto_sleep\"} command\n * @param {number} timeoutMs\n * @returns {Promise<void>}\n */\n _sendCommandAwaitCompletion(command, timeoutMs) {\n if (!this._sendCommand({ type: command })) {\n return Promise.reject(new Error(`${command}: data channel not open`));\n }\n return new Promise((resolve, reject) => {\n const entry = {\n resolve,\n reject,\n timer: setTimeout(() => {\n const queue = this._pendingMotionCompletions[command];\n const idx = queue.indexOf(entry);\n if (idx !== -1) queue.splice(idx, 1);\n reject(new Error(`${command} timed out after ${timeoutMs}ms`));\n }, timeoutMs),\n };\n this._pendingMotionCompletions[command].push(entry);\n });\n }\n\n /**\n * Internal: drain every pending motion-completion resolver with the\n * given error. Called by ``stopSession()`` and ``disconnect()`` so a\n * teardown that interrupts an in-flight ``gotoSleep`` does not leave\n * the caller awaiting forever.\n */\n _rejectPendingMotionCompletions(error) {\n for (const command of Object.keys(this._pendingMotionCompletions)) {\n const queue = this._pendingMotionCompletions[command];\n while (queue.length) {\n const entry = queue.shift();\n clearTimeout(entry.timer);\n entry.reject(error);\n }\n }\n }\n\n /**\n * Whether the robot's motors are currently powered (the \"awake\" state).\n *\n * Reads ``motor_mode`` from the last state event. Both ``\"enabled\"``\n * and ``\"gravity_compensation\"`` count as awake: in gravity-comp the\n * motors are actively holding the arm against gravity, so the robot\n * is *not* limp and playing wake_up on top would fight the user.\n * Only ``\"disabled\"`` (true sleep) is considered not-awake.\n *\n * Returns ``false`` before the first state event arrives (typical\n * right after ``startSession()``). Use ``ensureAwake()`` if you want\n * to wait for the first state before deciding.\n *\n * @returns {boolean}\n */\n isAwake() {\n const mode = this._robotState?.motor_mode;\n return mode === \"enabled\" || mode === \"gravity_compensation\";\n }\n\n /**\n * Wake the robot up if it is currently asleep, otherwise no-op.\n *\n * Intended as the first line of any app after ``startSession()``\n * resolves — robots are often left in the sleep pose (torque off,\n * head resting on the base) and commanded positions are silently\n * ignored in that state.\n *\n * If no state event has arrived yet, waits up to ``timeoutMs`` for\n * one before deciding. If still no state, falls back to sending\n * ``wakeUp()`` (safe: the daemon's wake_up handler is idempotent\n * at the motion level — it moves to the awake pose from wherever\n * the head currently is).\n *\n * @param {number} [timeoutMs=1000] how long to wait for the first\n * state event before falling through to wakeUp().\n * @returns {Promise<boolean>} true if the robot is awake afterwards.\n */\n async ensureAwake(timeoutMs = 1000) {\n if (this._robotState?.motor_mode === undefined) {\n await new Promise((resolve) => {\n const done = () => {\n this.removeEventListener('state', done);\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(done, timeoutMs);\n this.addEventListener('state', done);\n this.requestState();\n });\n }\n if (this.isAwake()) return true;\n // wakeUp() now returns a Promise. Fire-and-forget here — we\n // intentionally do not await the trajectory completion, the\n // caller of ensureAwake() decides whether to block on the\n // animation. Catch the rejection so a teardown that interrupts\n // the wake doesn't surface an unhandledrejection event from\n // this internal helper.\n this.wakeUp().catch(() => { /* swallow: caller may have torn down */ });\n return true;\n }\n\n /**\n * Request the daemon version.\n * Resolves with the version string (or null if unavailable).\n * @returns {Promise<string|null>}\n */\n getVersion() {\n return new Promise((resolve, reject) => {\n if (!this._dc || this._dc.readyState !== 'open') {\n reject(new Error('Data channel not open'));\n return;\n }\n if (this._versionResolve) {\n this._versionResolve(null);\n }\n this._versionResolve = resolve;\n this._sendCommand({ type: \"get_version\" });\n });\n }\n\n /**\n * Request the robot's unique hardware ID — the Pollen audio device's\n * USB serial. Same value across Lite and Wireless variants, stable\n * across reboots and OS reinstalls. Useful for fleet management,\n * per-robot calibration cache keys, or identifying which physical\n * robot a session is bound to.\n * Resolves with the hardware ID string (or null if no robot is\n * attached, e.g. the daemon is running on a developer machine).\n * @returns {Promise<string|null>}\n */\n getHardwareId() {\n return new Promise((resolve, reject) => {\n if (!this._dc || this._dc.readyState !== 'open') {\n reject(new Error('Data channel not open'));\n return;\n }\n if (this._hardwareIdResolve) {\n this._hardwareIdResolve(null);\n }\n this._hardwareIdResolve = resolve;\n this._sendCommand({ type: \"get_hardware_id\" });\n });\n }\n\n /**\n * Query the current speaker volume (0-100).\n * Resolves with null if volume control is unavailable (platform unsupported\n * or audio stack down).\n * @returns {Promise<number|null>}\n */\n getVolume() {\n return this._volumeRoundtrip({ type: \"get_volume\" }, \"_volumeResolve\");\n }\n\n /**\n * Set the speaker volume (0-100). Persists for the next connection\n * (same semantics as the REST /api/volume/set endpoint).\n * Resolves with the applied volume, or null on failure.\n * @param {number} volume 0-100\n * @returns {Promise<number|null>}\n */\n setVolume(volume) {\n return this._volumeRoundtrip(\n { type: \"set_volume\", volume: clampVolume(volume) },\n \"_volumeResolve\",\n );\n }\n\n /**\n * Query the current microphone input volume (0-100).\n * @returns {Promise<number|null>}\n */\n getMicrophoneVolume() {\n return this._volumeRoundtrip(\n { type: \"get_microphone_volume\" },\n \"_micVolumeResolve\",\n );\n }\n\n /**\n * Set the microphone input volume (0-100). Persists across sessions.\n * @param {number} volume 0-100\n * @returns {Promise<number|null>}\n */\n setMicrophoneVolume(volume) {\n return this._volumeRoundtrip(\n { type: \"set_microphone_volume\", volume: clampVolume(volume) },\n \"_micVolumeResolve\",\n );\n }\n\n /**\n * Internal: send a volume command and await the single-slot response.\n * The slot name selects which resolver (speaker vs mic) owns the\n * pending request so the two can be in-flight concurrently without\n * collision.\n */\n _volumeRoundtrip(command, slot) {\n return new Promise((resolve, reject) => {\n if (!this._dc || this._dc.readyState !== 'open') {\n reject(new Error('Data channel not open'));\n return;\n }\n // If a previous request on the same slot is still pending,\n // resolve it to null so its caller doesn't hang forever.\n if (this[slot]) this[slot](null);\n this[slot] = resolve;\n this._sendCommand(command);\n });\n }\n\n /**\n * Send an arbitrary JSON command over the data channel.\n * @param {object} data @returns {boolean}\n */\n sendRaw(data) {\n return this._sendCommand(data);\n }\n\n /**\n * Subscribe to the daemon's `journalctl -u reachy-mini-daemon`\n * stream over the WebRTC data channel.\n *\n * One daemon-side subprocess is shared across all local subscribers:\n * the first call sends `subscribe_logs`, removing the last subscriber\n * sends `unsubscribe_logs`. Calling the returned `unsubscribe()`\n * twice is a no-op.\n *\n * @param {{\n * onLine: (entry: { timestamp: string, line: string }) => void,\n * onError?: (error: string) => void,\n * }} options\n * @returns {() => void} unsubscribe\n */\n subscribeLogs({ onLine, onError } = {}) {\n if (typeof onLine !== 'function') {\n throw new TypeError('subscribeLogs: onLine callback is required');\n }\n const sub = { onLine, onError };\n const wasEmpty = this._logSubscribers.size === 0;\n this._logSubscribers.add(sub);\n if (wasEmpty) this._sendCommand({ type: 'subscribe_logs' });\n\n let detached = false;\n return () => {\n if (detached) return;\n detached = true;\n this._logSubscribers.delete(sub);\n if (this._logSubscribers.size === 0) {\n this._sendCommand({ type: 'unsubscribe_logs' });\n }\n };\n }\n\n /**\n * Request a state snapshot. The response arrives as a \"state\" event.\n * Called automatically every 500 ms while streaming.\n *\n * Safe to call at a higher rate if you need faster telemetry: e.g.\n * ``setInterval(() => robot.requestState(), 20)`` for ~50 Hz, or drive\n * it from a ``requestAnimationFrame`` loop for display-rate updates.\n * On LAN the daemon can sustain ~90-100 Hz round-trips over the\n * datachannel; over the internet expect the WebRTC path's RTT to\n * dominate. The built-in 500 ms poll keeps running in parallel — it\n * is harmless, as state responses are idempotent.\n *\n * @returns {boolean}\n */\n requestState() {\n return this._sendCommand({ type: \"get_state\" });\n }\n\n // ─── Audio ───────────────────────────────────────────────────────────\n\n /**\n * Mute/unmute the robot's audio playback (speaker) locally.\n * Audio is muted by default — browsers require a user gesture to unmute.\n * @param {boolean} muted\n */\n setAudioMuted(muted) {\n this._audioMuted = muted;\n if (this._videoElement) this._videoElement.muted = muted;\n }\n\n /**\n * Mute/unmute your microphone. Only works if micSupported is true.\n * Mic is muted by default even after acquisition.\n * @param {boolean} muted\n */\n setMicMuted(muted) {\n this._micMuted = muted;\n if (this._micStream) {\n this._micStream.getAudioTracks().forEach(t => { t.enabled = !muted; });\n }\n }\n\n // ─── Video helper ────────────────────────────────────────────────────\n\n /**\n * Bind a `<video>` element to this robot's stream.\n * Call before startSession(). Sets srcObject when the video track arrives,\n * applies audio mute state, and runs a latency monitor that snaps to the\n * live edge if the buffer grows > 0.5 s.\n *\n * @param {HTMLVideoElement} videoElement\n * @returns {() => void} cleanup function — call to detach video and stop monitoring\n */\n attachVideo(videoElement) {\n this._videoElement = videoElement;\n videoElement.muted = this._audioMuted;\n\n const onVideoTrack = (e) => {\n videoElement.srcObject = e.detail.stream;\n videoElement.playsInline = true;\n if ('requestVideoFrameCallback' in videoElement) {\n this._startLatencyMonitor(videoElement);\n }\n };\n\n const onSessionStopped = () => { videoElement.srcObject = null; };\n\n this.addEventListener('videoTrack', onVideoTrack);\n this.addEventListener('sessionStopped', onSessionStopped);\n\n return () => {\n this.removeEventListener('videoTrack', onVideoTrack);\n this.removeEventListener('sessionStopped', onSessionStopped);\n if (this._latencyMonitorId) { clearInterval(this._latencyMonitorId); this._latencyMonitorId = null; }\n videoElement.srcObject = null;\n this._videoElement = null;\n };\n }\n\n // ─── Private ─────────────────────────────────────────────────────────\n\n _emit(name, detail) {\n this.dispatchEvent(new CustomEvent(name, { detail }));\n }\n\n async _sendToServer(message) {\n // Mirrors connect()'s guard — a missing token between connect and\n // send (e.g. logout mid-session) would otherwise produce\n // \"Authorization: Bearer undefined\", which central correctly 401s\n // but silently returns null to the caller, hiding the real cause.\n if (!this._token) throw new Error('No token — authenticate() first');\n try {\n // Token in Authorization header, not URL — same reasoning as\n // connect()'s SSE fetch: never put secrets in URLs.\n const res = await fetch(`${this._signalingUrl}/send`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this._token}`,\n },\n body: JSON.stringify(message),\n });\n if (!res.ok) {\n // Surface 4xx/5xx with the rejected message type. The\n // browser already logs \"Failed to load resource: <status>\"\n // but never says which call produced it, which makes\n // tardy `peer`/`endSession`/`setPeerStatus` races\n // (typical after a session has been torn down) hard to\n // diagnose. Returning null preserves the historical\n // contract for callers that only care about the success\n // path.\n let body = '';\n try { body = await res.text(); } catch { /* ignore */ }\n console.warn(\n `[reachy-mini] /send rejected (${res.status}) for type=${message?.type}; body=${body || '<empty>'}`,\n );\n return null;\n }\n return await res.json();\n } catch (e) {\n console.error('Send error:', e);\n return null;\n }\n }\n\n _sendCommand(cmd) {\n if (!this._dc || this._dc.readyState !== 'open') return false;\n this._dc.send(JSON.stringify(cmd));\n return true;\n }\n\n /** Resolves the startSession() promise once both ICE and datachannel are ready. */\n _checkSessionReady() {\n if (this._iceConnected && this._dcOpen && this._sessionResolve) {\n this._state = 'streaming';\n this.requestState();\n this._stateRefreshInterval = setInterval(() => this.requestState(), 500);\n this._emit('streaming', { sessionId: this._sessionId, robotId: this._selectedRobotId });\n this._sessionResolve();\n this._sessionResolve = null;\n this._sessionReject = null;\n }\n }\n\n async _handleSignalingMessage(msg) {\n switch (msg.type) {\n case 'welcome':\n break; // handled in connect()\n case 'list':\n this._robots = msg.producers || [];\n this._emit('robotsChanged', { robots: this._robots });\n this._maybeAutoStart();\n break;\n case 'peerStatusChanged': {\n const list = await this._sendToServer({ type: 'list' });\n if (list?.producers) {\n this._robots = list.producers;\n this._emit('robotsChanged', { robots: this._robots });\n this._maybeAutoStart();\n }\n break;\n }\n case 'sessionStarted':\n this._sessionId = msg.sessionId;\n break;\n case 'sessionRejected':\n // Defensive: the current central server returns sessionRejected\n // as the direct POST response (handled at the startSession() call\n // site). This branch is a safety net in case the server ever\n // pushes the rejection over SSE instead.\n this._failSessionRejected(msg);\n break;\n case 'endSession':\n this._handleEndSession(msg);\n break;\n case 'peer':\n this._handlePeerMessage(msg);\n break;\n }\n }\n\n /**\n * Internal: handle an endSession pushed from central.\n *\n * Two user-visible scenarios converge here:\n *\n * 1. Pending startSession — central accepted our request but the\n * robot-side relay then refused (e.g. a local Python app holds\n * the daemon's robot lock). Without this handler, the client's\n * startSession() promise would hang forever because ICE/datachannel\n * never come up.\n * 2. Streaming was evicted — a local Python app started on the robot\n * while we were connected, so the relay tore down our session.\n *\n * The ``reason`` is forwarded verbatim from the relay through central:\n * - \"robot_busy_local_app\": daemon lock held by a local Python app\n * (refused before any media negotiation).\n * - \"local_app_started\": a local Python app started mid-session and\n * evicted us.\n * - \"robot_busy_local\": relay's safety-net for stale/concurrent\n * sessions (should be rare).\n */\n _handleEndSession(msg) {\n const reason = msg.reason;\n const friendly = reason === 'robot_busy_local_app'\n ? 'Robot is busy: a local Python app is running'\n : reason === 'local_app_started'\n ? 'Disconnected: a local Python app started on the robot'\n : reason === 'robot_busy_local'\n ? 'Robot is busy: another session is already active'\n : null;\n\n // Case 1: a startSession() is still pending — reject its promise\n // with the same error shape as sessionRejected so app code can\n // treat both paths identically.\n if (this._sessionReject) {\n const err = new Error(\n friendly || `Session ended before it could start: ${reason || 'unknown reason'}`\n );\n err.reason = reason;\n this._emit('sessionRejected', { reason, activeApp: null });\n // Release resources allocated optimistically by startSession().\n if (this._pc) { this._pc.close(); this._pc = null; }\n if (this._micStream) { this._micStream.getTracks().forEach(t => t.stop()); this._micStream = null; }\n this._iceConnected = false;\n this._dcOpen = false;\n this._micMuted = true;\n this._micSupported = false;\n const reject = this._sessionReject;\n this._sessionResolve = null;\n this._sessionReject = null;\n reject(err);\n return;\n }\n\n // Case 2: we were streaming and got kicked. Leave cleanup of _pc,\n // _dc, mic and timers to stopSession() so the event listener path\n // matches the user-initiated stop path exactly.\n if (this._state === 'streaming') {\n this._emit('sessionStopped', {\n reason: reason || 'remote_end',\n message: friendly,\n });\n // Fire-and-forget: we just react to what the server already\n // decided. stopSession() sends its own endSession back but\n // central has already dropped the session, so the echo is\n // harmless.\n this.stopSession().catch(() => { });\n }\n }\n\n async _handlePeerMessage(msg) {\n if (!this._pc) return;\n try {\n if (msg.sdp) {\n const sdp = msg.sdp;\n if (sdp.type === 'offer') {\n const supportsMic = sdpHasAudioSendRecv(sdp.sdp);\n this._micSupported = supportsMic;\n this._emit('micSupported', { supported: supportsMic });\n\n // Mic track must be added BEFORE setRemoteDescription so the\n // generated answer naturally includes sendrecv for audio.\n if (supportsMic && this._micStream) {\n for (const track of this._micStream.getAudioTracks()) {\n this._pc.addTrack(track, this._micStream);\n }\n }\n\n await this._pc.setRemoteDescription(new RTCSessionDescription(sdp));\n const answer = await this._pc.createAnswer();\n await this._pc.setLocalDescription(answer);\n await this._sendToServer({\n type: 'peer',\n sessionId: this._sessionId,\n sdp: { type: 'answer', sdp: answer.sdp },\n });\n } else {\n await this._pc.setRemoteDescription(new RTCSessionDescription(sdp));\n }\n // Replay any ICE candidates that arrived before the\n // SDP exchange completed (see the buffering branch\n // below for context).\n const pending = this._pendingRemoteIce;\n if (pending && pending.length) {\n this._pendingRemoteIce = [];\n for (const ice of pending) {\n try {\n await this._pc.addIceCandidate(new RTCIceCandidate(ice));\n } catch (err) {\n console.warn('[reachy-mini] buffered ICE candidate rejected:', err);\n }\n }\n }\n }\n if (msg.ice) {\n // Safari (and the iOS WKWebView Tauri ships on) rejects\n // empty candidate strings with `OperationError: Expect\n // line: candidate:<candidate-str>`. The signaling\n // server uses an empty string as the end-of-candidates\n // marker (legal per the WebRTC spec but optional).\n // Chrome / Firefox swallow it silently; we mirror that\n // here so the iOS WebView stops surfacing the noise as\n // a robot-side WebRTC error event.\n if (!msg.ice.candidate) return;\n if (this._pc.remoteDescription) {\n await this._pc.addIceCandidate(new RTCIceCandidate(msg.ice));\n } else {\n // The signaling transport (SSE through central) is\n // not strictly ordered across the offer / ICE\n // streams when the SDK runs inside a cross-origin\n // iframe or in Safari / iOS WKWebView: the first\n // ICE candidates can land before the offer SDP\n // does. Calling addIceCandidate before\n // setRemoteDescription throws\n // `InvalidStateError: The remote description was\n // null` and the candidate is silently lost,\n // sometimes wedging ICE altogether. Buffer here\n // and replay above as soon as the offer has been\n // applied.\n if (!this._pendingRemoteIce) this._pendingRemoteIce = [];\n this._pendingRemoteIce.push(msg.ice);\n }\n }\n } catch (e) {\n console.error('WebRTC error:', e);\n this._emit('error', { source: 'webrtc', error: e });\n }\n }\n\n /** Parse robot messages and dispatch. */\n _handleRobotMessage(data) {\n if ('version' in data && this._versionResolve) {\n this._versionResolve(data.version);\n this._versionResolve = null;\n return;\n }\n if ('hardware_id' in data && this._hardwareIdResolve) {\n this._hardwareIdResolve(data.hardware_id);\n this._hardwareIdResolve = null;\n return;\n }\n // Volume responses. Backend tags each response with `command` so we\n // know which pending resolver (speaker vs mic) to fulfil. If a\n // response arrives with no matching pending request (e.g. stale\n // after reconnect), just ignore it — the data channel protocol\n // has no multiplexing, so unmatched replies are expected.\n if (data.command === 'get_volume' || data.command === 'set_volume') {\n if (this._volumeResolve) {\n this._volumeResolve(data.status === 'error' ? null : data.volume);\n this._volumeResolve = null;\n }\n return;\n }\n if (data.command === 'get_microphone_volume' || data.command === 'set_microphone_volume') {\n if (this._micVolumeResolve) {\n this._micVolumeResolve(data.status === 'error' ? null : data.volume);\n this._micVolumeResolve = null;\n }\n return;\n }\n // Motion completion responses. The daemon emits\n // `{status: \"ok\", command: \"wake_up\"|\"goto_sleep\", completed: true}`\n // after the trajectory player is fully done, or `{error, command}`\n // on failure. Route them to the FIFO queue of pending awaiters\n // populated by `_sendCommandAwaitCompletion`; the N-th response\n // matches the N-th request thanks to the daemon's serialised\n // dispatcher.\n if (\n (data.command === 'wake_up' || data.command === 'goto_sleep') &&\n this._pendingMotionCompletions &&\n this._pendingMotionCompletions[data.command]\n ) {\n const queue = this._pendingMotionCompletions[data.command];\n if (data.completed === true && queue.length > 0) {\n const entry = queue.shift();\n clearTimeout(entry.timer);\n entry.resolve();\n return;\n }\n if (data.error && queue.length > 0) {\n const entry = queue.shift();\n clearTimeout(entry.timer);\n entry.reject(new Error(`${data.command}: ${data.error}`));\n return;\n }\n }\n if (data.type === 'log_line') {\n for (const sub of this._logSubscribers) {\n try {\n sub.onLine({ timestamp: data.timestamp, line: data.line });\n } catch (e) {\n console.error('subscribeLogs onLine threw:', e);\n }\n }\n return;\n }\n if (data.type === 'log_stream_error') {\n for (const sub of this._logSubscribers) {\n if (typeof sub.onError === 'function') {\n try { sub.onError(data.error); }\n catch (e) { console.error('subscribeLogs onError threw:', e); }\n }\n }\n return;\n }\n if (data.state) {\n const s = data.state;\n // Wire-shape pass-through. The daemon ships the head pose as a\n // nested 4×4 (numpy tolist()); we flatten to 16 numbers so\n // consumers can hand it straight to WebGL / Three.js / trajectory\n // logs. Everything else is forwarded as-is.\n if (s.head_pose) this._robotState.head = s.head_pose.flat();\n if (s.antennas) this._robotState.antennas = [s.antennas[0], s.antennas[1]];\n if (typeof s.body_yaw === 'number') this._robotState.body_yaw = s.body_yaw;\n if (s.motor_mode) this._robotState.motor_mode = s.motor_mode;\n if (typeof s.is_move_running === 'boolean') this._robotState.is_move_running = s.is_move_running;\n this._emit('state', { ...this._robotState });\n }\n if (data.error) {\n this._emit('error', { source: 'robot', error: data.error });\n }\n }\n\n /** Snap video playback to live edge if buffered lag exceeds 0.5 s. */\n _startLatencyMonitor(video) {\n if (this._latencyMonitorId) clearInterval(this._latencyMonitorId);\n this._latencyMonitorId = setInterval(() => {\n if (!video.srcObject || video.paused) return;\n const buf = video.buffered;\n if (buf.length > 0) {\n const end = buf.end(buf.length - 1);\n const lag = end - video.currentTime;\n if (lag > 0.5) {\n console.log(`Latency correction: was ${lag.toFixed(2)}s behind`);\n video.currentTime = end - 0.1;\n }\n }\n }, 2000);\n }\n}\n\nexport default ReachyMini;\n"],"names":["__defProp","__defNormalProp","obj","key","value","__publicField","HUB_URL","createApiError","response","opts","_a","_b","error","HubApiError","trailer","json","url","statusCode","requestId","message","base64FromBytes","arr","bin","byte","oauthHandleRedirect","searchParams","errorDescription","code","nonce","codeVerifier","state","parsedState","hubUrl","openidConfigUrl","openidConfigRes","opendidConfig","tokenRes","token","accessTokenExpiresAt","userInfoRes","userInfo","org","oauthHandleRedirectIfPresent","oauthLoginUrl","newNonce","newCodeVerifier","redirectUri","variables","clientId","challenge","degToRad","deg","rpyToMatrix","rollDeg","pitchDeg","yawDeg","r","p","y","cy","sy","cp","sp","cr","sr","clampVolume","v","n","consumeFragmentCredentials","raw","params","username","expiresParam","expires","err","remaining","cleanUrl","readPreselectedRobotIdFromUrl","fromHash","fromQuery","sdpHasAudioSendRecv","sdp","lines","inAudio","line","ReachyMini","options","peerId","result","u","e","res","resolve","reject","welcomed","reader","decoder","buffer","done","msg","pickRobot","autoPickIfSingle","filterBusy","wakeOnConnect","cur","_prevAutoStartFromUrl","robotId","robotName","robots","picked","seen","a","b","timeoutMs","onChange","timeoutId","ctx","dst","osc","gain","t","fallbackErr","ms","s","ev","head","antennas","body_yaw","cmd","duration","rightDeg","leftDeg","file","mode","on","ids","command","entry","queue","idx","timer","volume","slot","data","onLine","onError","sub","wasEmpty","detached","muted","videoElement","onVideoTrack","onSessionStopped","name","detail","body","list","reason","friendly","supportsMic","track","answer","pending","ice","video","buf","end","lag"],"mappings":"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","x_google_ignoreList":[0,1]}
@@ -0,0 +1,13 @@
1
+ import type { JSX } from 'react';
2
+ import type { AppConnectingStep } from '../lib/protocol';
3
+ export interface ConnectingViewProps {
4
+ /** Sub-step inside `connecting`. Maps to the 3-dot stepper. */
5
+ step: AppConnectingStep | null;
6
+ /** Optional caption override (apps can send one via
7
+ * `embed:app-state.message`). When provided it wins over the
8
+ * default per-step caption. */
9
+ message?: string | null;
10
+ }
11
+ export declare function ConnectingView({ step, message, }: ConnectingViewProps): JSX.Element;
12
+ export declare const CONNECTING_STEP_ORDER: AppConnectingStep[];
13
+ //# sourceMappingURL=ConnectingView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConnectingView.d.ts","sourceRoot":"","sources":["../../src/components/ConnectingView.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAIjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIzD,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/B;;oCAEgC;IAChC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAiBD,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,OAAO,GACR,EAAE,mBAAmB,GAAG,GAAG,CAAC,OAAO,CA6FnC;AAkED,eAAO,MAAM,qBAAqB,qBAAa,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface EmbedFrameProps {
2
+ /** Same-origin URL of the embedded app's `index.html`.
3
+ * Defaults to `${origin}/?embedded=1`. */
4
+ src: string;
5
+ /** Allow microphone capture inside the iframe (apps that need
6
+ * WebRTC mic input). Maps to the `allow="microphone; ..."`
7
+ * attribute. */
8
+ enableMicrophone: boolean;
9
+ /** Title attribute for a11y. */
10
+ title: string;
11
+ /** Set `visibility` so the iframe can stay mounted (and keep
12
+ * the SDK socket) while the ConnectingView overlay is on top. */
13
+ visible: boolean;
14
+ }
15
+ export declare const EmbedFrame: import("react").ForwardRefExoticComponent<EmbedFrameProps & import("react").RefAttributes<HTMLIFrameElement>>;
16
+ //# sourceMappingURL=EmbedFrame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmbedFrame.d.ts","sourceRoot":"","sources":["../../src/components/EmbedFrame.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,eAAe;IAC9B;+CAC2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ;;qBAEiB;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd;sEACkE;IAClE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,UAAU,+GAoCtB,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Terminal error screen. Reached when:
3
+ * - The SDK throws on `connect()` / `startSession()`.
4
+ * - The embed reports `embed:error { fatal: true }`.
5
+ * - The host catches an uncaught render error (via the React
6
+ * error boundary in `ReachyHost`).
7
+ *
8
+ * Always offers a "Reload" action (full page reload) and a
9
+ * "Back to picker" action that resets the host phase to picking
10
+ * without losing the OAuth session.
11
+ */
12
+ import type { JSX } from 'react';
13
+ export interface ErrorViewProps {
14
+ title?: string;
15
+ message: string;
16
+ detail?: unknown;
17
+ onReload(): void;
18
+ onBackToPicker(): void;
19
+ }
20
+ export declare function ErrorView({ title, message, detail, onReload, onBackToPicker, }: ErrorViewProps): JSX.Element;
21
+ //# sourceMappingURL=ErrorView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorView.d.ts","sourceRoot":"","sources":["../../src/components/ErrorView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAMjC,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,IAAI,IAAI,CAAC;IACjB,cAAc,IAAI,IAAI,CAAC;CACxB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAA8B,EAC9B,OAAO,EACP,MAAM,EACN,QAAQ,EACR,cAAc,GACf,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CA6D9B"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Transition view rendered while the host is tearing the iframe
3
+ * session down (user clicked "End session" / power-off).
4
+ *
5
+ * 1-to-1 port of
6
+ * `reachy_mini_mobile_app/src/ui/screens/session/LeavingView.tsx`
7
+ * — the desktop host and the mobile shell deliberately share the
8
+ * exact same disconnection visual so a user moving between the
9
+ * two clients sees a consistent "Putting Reachy to sleep…" beat.
10
+ *
11
+ * Visual posture
12
+ * ──────────────
13
+ * Mirrors `<ConnectingView>`'s anatomy (stack centred on the
14
+ * vertical axis, primary visual on top, title below) so the
15
+ * leave-screen reads as the symmetric counterpart of the join-
16
+ * screen. But where Connecting carries a 3-dot stepper + bold
17
+ * headline + a two-line caption to narrate a multi-phase
18
+ * bring-up, Leaving is intentionally one beat lighter:
19
+ *
20
+ * - a small, low-contrast spinner instead of the stepper
21
+ * (the user already decided to leave - we don't need to
22
+ * dramatise the wait),
23
+ * - a single short headline, no caption (the goto-sleep
24
+ * trajectory is short enough that a sub-line of explanation
25
+ * reads as filler by the time it lands).
26
+ *
27
+ * The trailing ellipsis on the headline is enough of a
28
+ * "something's still happening" cue without needing a second line
29
+ * of copy.
30
+ */
31
+ import type { JSX } from 'react';
32
+ export declare function LeavingView(): JSX.Element;
33
+ //# sourceMappingURL=LeavingView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeavingView.d.ts","sourceRoot":"","sources":["../../src/components/LeavingView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAKjC,wBAAgB,WAAW,IAAI,GAAG,CAAC,OAAO,CA2CzC"}