@ledgerhq/device-mockserver-react 0.0.0-develop-20260930001543

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 (133) hide show
  1. package/LICENSE.MD +202 -0
  2. package/README.md +45 -0
  3. package/lib/cjs/index.js +2 -0
  4. package/lib/cjs/index.js.map +7 -0
  5. package/lib/cjs/package.json +67 -0
  6. package/lib/cjs/src/DeviceOsInfo.js +2 -0
  7. package/lib/cjs/src/DeviceOsInfo.js.map +7 -0
  8. package/lib/cjs/src/DeviceOsInfo.test.js +2 -0
  9. package/lib/cjs/src/DeviceOsInfo.test.js.map +7 -0
  10. package/lib/cjs/src/DeviceScreen.js +2 -0
  11. package/lib/cjs/src/DeviceScreen.js.map +7 -0
  12. package/lib/cjs/src/DeviceScreen.test.js +2 -0
  13. package/lib/cjs/src/DeviceScreen.test.js.map +7 -0
  14. package/lib/cjs/src/DeviceScreenButtons.js +2 -0
  15. package/lib/cjs/src/DeviceScreenButtons.js.map +7 -0
  16. package/lib/cjs/src/DeviceScreenButtons.test.js +2 -0
  17. package/lib/cjs/src/DeviceScreenButtons.test.js.map +7 -0
  18. package/lib/cjs/src/DeviceScreenImage.js +2 -0
  19. package/lib/cjs/src/DeviceScreenImage.js.map +7 -0
  20. package/lib/cjs/src/DeviceScreenImage.test.js +2 -0
  21. package/lib/cjs/src/DeviceScreenImage.test.js.map +7 -0
  22. package/lib/cjs/src/MockServerDevice.js +2 -0
  23. package/lib/cjs/src/MockServerDevice.js.map +7 -0
  24. package/lib/cjs/src/MockServerDevice.test.js +2 -0
  25. package/lib/cjs/src/MockServerDevice.test.js.map +7 -0
  26. package/lib/cjs/src/ScreenFrame.js +2 -0
  27. package/lib/cjs/src/ScreenFrame.js.map +7 -0
  28. package/lib/cjs/src/deviceModels.js +2 -0
  29. package/lib/cjs/src/deviceModels.js.map +7 -0
  30. package/lib/cjs/src/deviceModels.test.js +2 -0
  31. package/lib/cjs/src/deviceModels.test.js.map +7 -0
  32. package/lib/cjs/src/index.js +2 -0
  33. package/lib/cjs/src/index.js.map +7 -0
  34. package/lib/cjs/src/mockServerScreenApi.js +2 -0
  35. package/lib/cjs/src/mockServerScreenApi.js.map +7 -0
  36. package/lib/cjs/src/mockServerScreenApi.test.js +2 -0
  37. package/lib/cjs/src/mockServerScreenApi.test.js.map +7 -0
  38. package/lib/cjs/src/types.js +2 -0
  39. package/lib/cjs/src/types.js.map +7 -0
  40. package/lib/cjs/src/useMockServerDevice.js +2 -0
  41. package/lib/cjs/src/useMockServerDevice.js.map +7 -0
  42. package/lib/cjs/src/useMockServerDevice.test.js +2 -0
  43. package/lib/cjs/src/useMockServerDevice.test.js.map +7 -0
  44. package/lib/cjs/src/useScreenPolling.js +2 -0
  45. package/lib/cjs/src/useScreenPolling.js.map +7 -0
  46. package/lib/esm/index.js +2 -0
  47. package/lib/esm/index.js.map +7 -0
  48. package/lib/esm/package.json +67 -0
  49. package/lib/esm/src/DeviceOsInfo.js +2 -0
  50. package/lib/esm/src/DeviceOsInfo.js.map +7 -0
  51. package/lib/esm/src/DeviceOsInfo.test.js +2 -0
  52. package/lib/esm/src/DeviceOsInfo.test.js.map +7 -0
  53. package/lib/esm/src/DeviceScreen.js +2 -0
  54. package/lib/esm/src/DeviceScreen.js.map +7 -0
  55. package/lib/esm/src/DeviceScreen.test.js +2 -0
  56. package/lib/esm/src/DeviceScreen.test.js.map +7 -0
  57. package/lib/esm/src/DeviceScreenButtons.js +2 -0
  58. package/lib/esm/src/DeviceScreenButtons.js.map +7 -0
  59. package/lib/esm/src/DeviceScreenButtons.test.js +2 -0
  60. package/lib/esm/src/DeviceScreenButtons.test.js.map +7 -0
  61. package/lib/esm/src/DeviceScreenImage.js +2 -0
  62. package/lib/esm/src/DeviceScreenImage.js.map +7 -0
  63. package/lib/esm/src/DeviceScreenImage.test.js +2 -0
  64. package/lib/esm/src/DeviceScreenImage.test.js.map +7 -0
  65. package/lib/esm/src/MockServerDevice.js +2 -0
  66. package/lib/esm/src/MockServerDevice.js.map +7 -0
  67. package/lib/esm/src/MockServerDevice.test.js +2 -0
  68. package/lib/esm/src/MockServerDevice.test.js.map +7 -0
  69. package/lib/esm/src/ScreenFrame.js +2 -0
  70. package/lib/esm/src/ScreenFrame.js.map +7 -0
  71. package/lib/esm/src/deviceModels.js +2 -0
  72. package/lib/esm/src/deviceModels.js.map +7 -0
  73. package/lib/esm/src/deviceModels.test.js +2 -0
  74. package/lib/esm/src/deviceModels.test.js.map +7 -0
  75. package/lib/esm/src/index.js +2 -0
  76. package/lib/esm/src/index.js.map +7 -0
  77. package/lib/esm/src/mockServerScreenApi.js +2 -0
  78. package/lib/esm/src/mockServerScreenApi.js.map +7 -0
  79. package/lib/esm/src/mockServerScreenApi.test.js +2 -0
  80. package/lib/esm/src/mockServerScreenApi.test.js.map +7 -0
  81. package/lib/esm/src/types.js +1 -0
  82. package/lib/esm/src/types.js.map +7 -0
  83. package/lib/esm/src/useMockServerDevice.js +2 -0
  84. package/lib/esm/src/useMockServerDevice.js.map +7 -0
  85. package/lib/esm/src/useMockServerDevice.test.js +2 -0
  86. package/lib/esm/src/useMockServerDevice.test.js.map +7 -0
  87. package/lib/esm/src/useScreenPolling.js +2 -0
  88. package/lib/esm/src/useScreenPolling.js.map +7 -0
  89. package/lib/types/index.d.ts +2 -0
  90. package/lib/types/index.d.ts.map +1 -0
  91. package/lib/types/src/DeviceOsInfo.d.ts +12 -0
  92. package/lib/types/src/DeviceOsInfo.d.ts.map +1 -0
  93. package/lib/types/src/DeviceOsInfo.test.d.ts +2 -0
  94. package/lib/types/src/DeviceOsInfo.test.d.ts.map +1 -0
  95. package/lib/types/src/DeviceScreen.d.ts +17 -0
  96. package/lib/types/src/DeviceScreen.d.ts.map +1 -0
  97. package/lib/types/src/DeviceScreen.test.d.ts +2 -0
  98. package/lib/types/src/DeviceScreen.test.d.ts.map +1 -0
  99. package/lib/types/src/DeviceScreenButtons.d.ts +8 -0
  100. package/lib/types/src/DeviceScreenButtons.d.ts.map +1 -0
  101. package/lib/types/src/DeviceScreenButtons.test.d.ts +2 -0
  102. package/lib/types/src/DeviceScreenButtons.test.d.ts.map +1 -0
  103. package/lib/types/src/DeviceScreenImage.d.ts +10 -0
  104. package/lib/types/src/DeviceScreenImage.d.ts.map +1 -0
  105. package/lib/types/src/DeviceScreenImage.test.d.ts +2 -0
  106. package/lib/types/src/DeviceScreenImage.test.d.ts.map +1 -0
  107. package/lib/types/src/MockServerDevice.d.ts +10 -0
  108. package/lib/types/src/MockServerDevice.d.ts.map +1 -0
  109. package/lib/types/src/MockServerDevice.test.d.ts +2 -0
  110. package/lib/types/src/MockServerDevice.test.d.ts.map +1 -0
  111. package/lib/types/src/ScreenFrame.d.ts +12 -0
  112. package/lib/types/src/ScreenFrame.d.ts.map +1 -0
  113. package/lib/types/src/deviceModels.d.ts +14 -0
  114. package/lib/types/src/deviceModels.d.ts.map +1 -0
  115. package/lib/types/src/deviceModels.test.d.ts +2 -0
  116. package/lib/types/src/deviceModels.test.d.ts.map +1 -0
  117. package/lib/types/src/index.d.ts +7 -0
  118. package/lib/types/src/index.d.ts.map +1 -0
  119. package/lib/types/src/mockServerScreenApi.d.ts +4 -0
  120. package/lib/types/src/mockServerScreenApi.d.ts.map +1 -0
  121. package/lib/types/src/mockServerScreenApi.test.d.ts +2 -0
  122. package/lib/types/src/mockServerScreenApi.test.d.ts.map +1 -0
  123. package/lib/types/src/types.d.ts +41 -0
  124. package/lib/types/src/types.d.ts.map +1 -0
  125. package/lib/types/src/useMockServerDevice.d.ts +8 -0
  126. package/lib/types/src/useMockServerDevice.d.ts.map +1 -0
  127. package/lib/types/src/useMockServerDevice.test.d.ts +2 -0
  128. package/lib/types/src/useMockServerDevice.test.d.ts.map +1 -0
  129. package/lib/types/src/useScreenPolling.d.ts +3 -0
  130. package/lib/types/src/useScreenPolling.d.ts.map +1 -0
  131. package/lib/types/tsconfig.prod.tsbuildinfo +1 -0
  132. package/package.json +67 -0
  133. package/tailwind.css +2 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceScreen.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nimport { type ScreenApi } from \"./types\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst DEVICE = {\n id: \"device-1\",\n device_type: \"stax\",\n apps: [{ name: \"Bitcoin\", version: \"2.5.0\" }],\n} as unknown as Device;\n\nconst anApi = (overrides: Partial<ScreenApi> = {}): ScreenApi => ({\n screenshot: vi.fn(() => Promise.resolve(null)),\n idle: vi.fn(() =>\n Promise.resolve({ kind: \"os-info\" as const, device: DEVICE }),\n ),\n pressButton: vi.fn(() => Promise.resolve()),\n touch: vi.fn(() => Promise.resolve()),\n openApp: vi.fn(() => Promise.resolve()),\n ...overrides,\n});\n\ndescribe(\"DeviceScreen\", () => {\n let container: HTMLDivElement;\n let root: Root;\n\n const query = (testId: string) =>\n container.querySelector<HTMLElement>(`[data-testid=\"${testId}\"]`);\n const render = (props: Partial<DeviceScreenProps> & { api: ScreenApi }) =>\n act(() => root.render(<DeviceScreen deviceType=\"stax\" {...props} />));\n const wait = (ms = 0) => act(() => vi.advanceTimersByTimeAsync(ms));\n const pointer = (target: Element, type: string, init: MouseEventInit = {}) =>\n act(() => {\n target.dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n URL.createObjectURL = vi.fn(() => \"blob:frame\");\n URL.revokeObjectURL = vi.fn();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n HTMLElement.prototype.releasePointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n describe(\"inline\", () => {\n it(\"shows a placeholder until the screen answers\", () => {\n render({ api: anApi({ screenshot: () => new Promise(() => {}) }) });\n\n expect(query(\"placeholder_device-screen-loading\")).not.toBeNull();\n });\n\n it(\"shows the live screen of a running app\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.resolve(new Blob([\"png\"])) }),\n });\n await wait();\n\n expect(query(\"image_device-screen\")?.getAttribute(\"src\")).toBe(\n \"blob:frame\",\n );\n });\n\n it(\"shows the installed apps while no app runs\", async () => {\n render({ api: anApi() });\n await wait();\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n });\n\n it(\"says why a device without an emulator has no screen\", async () => {\n render({ api: anApi(), deviceType: \"nanoS\" });\n await wait();\n\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n expect(container.textContent).toContain(\n \"Speculos has no emulator for the Nano S\",\n );\n });\n\n it(\"says nothing about emulators for a device that has one\", async () => {\n render({ api: anApi() });\n await wait();\n\n expect(container.textContent).not.toContain(\"has no emulator\");\n });\n\n it(\"says there is no screen when the api has nothing to show instead\", async () => {\n render({ api: anApi({ idle: undefined }) });\n await wait();\n\n expect(container.textContent).toContain(\"No screen to capture\");\n });\n\n it(\"shows why the screen is unavailable\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.reject(new Error(\"offline\")) }),\n });\n await wait();\n\n expect(container.textContent).toContain(\"Screen unavailable\");\n expect(container.textContent).toContain(\"offline\");\n });\n\n it(\"keeps the last frame through a few failures while live\", async () => {\n const screenshot = vi\n .fn<() => Promise<Blob | null>>()\n .mockResolvedValueOnce(new Blob([\"png\"]))\n .mockRejectedValue(new Error(\"app quitting\"));\n const failures = () => screenshot.mock.calls.length - 1;\n render({ api: anApi({ screenshot }) });\n await wait();\n\n while (failures() < 3) await wait(500);\n expect(query(\"image_device-screen\")).not.toBeNull();\n\n while (failures() < 4) await wait(500);\n expect(query(\"image_device-screen\")).toBeNull();\n expect(container.textContent).toContain(\"app quitting\");\n });\n\n it(\"drives a Nano with its buttons and refreshes after\", async () => {\n const api = anApi({\n screenshot: vi.fn(() => Promise.resolve(new Blob([\"png\"]))),\n });\n render({ api, deviceType: \"nanoX\" });\n await wait();\n\n pointer(query(\"button_device-screen-left\")!, \"pointerdown\");\n pointer(query(\"button_device-screen-left\")!, \"pointerup\");\n await wait();\n\n expect(api.pressButton).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n expect(api.screenshot).toHaveBeenCalledTimes(2);\n });\n\n it(\"drives a touch device by its screen, without buttons\", async () => {\n render({\n api: anApi({ screenshot: () => Promise.resolve(new Blob([\"png\"])) }),\n });\n await wait();\n\n expect(query(\"image_device-screen\")).not.toBeNull();\n expect(query(\"button_device-screen-left\")).toBeNull();\n });\n });\n\n describe(\"floating\", () => {\n const windowElement = () => query(\"container_device-screen\")!;\n const header = () => windowElement().firstElementChild!;\n\n it(\"titles the window with the model and firmware\", async () => {\n render({ api: anApi(), floating: true, firmwareVersion: \"1.10.1\" });\n await wait();\n\n expect(windowElement().textContent).toContain(\"Stax - 1.10.1\");\n expect(windowElement().style.width).toBe(\"224px\");\n });\n\n it(\"stops polling while collapsed\", async () => {\n const api = anApi();\n render({ api, floating: true, defaultCollapsed: true });\n await wait(5000);\n\n expect(api.screenshot).not.toHaveBeenCalled();\n expect(query(\"button_open-app-Bitcoin\")).toBeNull();\n\n act(() => query(\"button_toggle-device-screen\")!.click());\n await wait();\n\n expect(api.screenshot).toHaveBeenCalled();\n expect(query(\"button_open-app-Bitcoin\")).not.toBeNull();\n });\n\n it(\"is dragged by its header\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n pointer(header(), \"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(header(), \"pointermove\", { clientX: 110, clientY: 60 });\n pointer(header(), \"pointerup\");\n pointer(header(), \"pointermove\", { clientX: 500, clientY: 500 });\n\n expect(windowElement().style.left).toBe(\"100px\");\n expect(windowElement().style.top).toBe(\"50px\");\n });\n\n describe(\"when the viewport shrinks\", () => {\n const viewport = { width: window.innerWidth, height: window.innerHeight };\n const resizeViewport = (width: number, height: number) =>\n act(() => {\n Object.assign(window, { innerWidth: width, innerHeight: height });\n window.dispatchEvent(new Event(\"resize\"));\n });\n\n afterEach(() => {\n Object.assign(window, {\n innerWidth: viewport.width,\n innerHeight: viewport.height,\n });\n });\n\n it(\"pulls a dragged window back on screen\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n windowElement().getBoundingClientRect = () =>\n ({ left: 0, top: 0, width: 224, height: 300 }) as DOMRect;\n pointer(header(), \"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(header(), \"pointermove\", { clientX: 610, clientY: 410 });\n pointer(header(), \"pointerup\");\n expect(windowElement().style.left).toBe(\"600px\");\n\n resizeViewport(500, 500);\n\n expect(windowElement().style.left).toBe(\"276px\");\n expect(windowElement().style.top).toBe(\"200px\");\n });\n\n it(\"leaves a window that was never moved in its corner\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n resizeViewport(500, 500);\n\n expect(windowElement().style.left).toBe(\"\");\n expect(windowElement().style.right).toBe(\"16px\");\n });\n });\n\n it(\"is not dragged from its button or with another mouse button\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n\n pointer(query(\"button_toggle-device-screen\")!, \"pointerdown\");\n pointer(header(), \"pointerdown\", { button: 2 });\n pointer(header(), \"pointermove\", { clientX: 110, clientY: 60 });\n\n expect(windowElement().style.left).toBe(\"\");\n });\n\n it(\"is resized from its edge, down to a minimum width\", async () => {\n render({ api: anApi(), floating: true });\n await wait();\n windowElement().getBoundingClientRect = () =>\n ({ left: 100, top: 80, width: 224, height: 300 }) as DOMRect;\n const handle = query(\"handle_resize-device-screen\")!;\n\n pointer(handle, \"pointerdown\", { clientX: 324 });\n pointer(handle, \"pointermove\", { clientX: 424 });\n expect(windowElement().style.width).toBe(\"324px\");\n expect(windowElement().style.left).toBe(\"100px\");\n\n pointer(handle, \"pointermove\", { clientX: 0 });\n pointer(handle, \"pointerup\");\n pointer(handle, \"pointermove\", { clientX: 600 });\n expect(windowElement().style.width).toBe(\"200px\");\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["React", "act", "createRoot", "DeviceScreen", "DEVICE", "anApi", "overrides", "container", "root", "query", "testId", "render", "props", "wait", "ms", "pointer", "target", "type", "init", "screenshot", "failures", "api", "windowElement", "header", "viewport", "resizeViewport", "width", "height", "handle"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import d,{useCallback as i,useEffect as f,useRef as a}from"react";const y=[{button:"left",label:"Left"},{button:"both",label:"Both"},{button:"right",label:"Right"}],b=r=>r===" "||r==="Enter",m=150,v=({onPress:r})=>{const n=a(null),s=a(null),u=a(r);u.current=r;const c=i(()=>{s.current&&(clearTimeout(s.current),s.current=null)},[]),p=i(e=>{if(n.current)return;const o={button:e,pressed:!1};n.current=o,s.current=setTimeout(()=>{o.pressed=!0,u.current(e,"press")},m)},[]),l=i(()=>{const e=n.current;e&&(n.current=null,c(),u.current(e.button,e.pressed?"release":"press-and-release"))},[c]);return f(()=>()=>{const e=n.current;n.current=null,c(),e?.pressed&&u.current(e.button,"release")},[c]),d.createElement("div",{className:"flex gap-8"},y.map(({button:e,label:o})=>d.createElement("button",{key:e,type:"button","aria-label":`${o} button`,onPointerDown:t=>{t.button===0&&(t.currentTarget.setPointerCapture(t.pointerId),p(e))},onPointerUp:l,onPointerCancel:l,onKeyDown:t=>{b(t.key)&&(t.preventDefault(),p(e))},onKeyUp:t=>b(t.key)&&l(),className:"border-muted body-3 text-base bg-muted hover:bg-muted-pressed active:bg-active flex-1 rounded-md border py-8 select-none",style:{touchAction:"none"},"data-testid":`button_device-screen-${e}`},o)))};export{v as DeviceScreenButtons};
2
+ //# sourceMappingURL=DeviceScreenButtons.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceScreenButtons.tsx"],
4
+ "sourcesContent": ["import React, { useCallback, useEffect, useRef } from \"react\";\nimport {\n type SpeculosAction,\n type SpeculosButton,\n} from \"@ledgerhq/device-mockserver-client\";\n\nconst BUTTONS: { button: SpeculosButton; label: string }[] = [\n { button: \"left\", label: \"Left\" },\n { button: \"both\", label: \"Both\" },\n { button: \"right\", label: \"Right\" },\n];\n\nconst isActivationKey = (key: string) => key === \" \" || key === \"Enter\";\n\n/**\n * How long the pointer stays down before the interaction counts as a hold.\n * A click sent as a separate press and release is two requests the device can\n * receive in either order, and a chord of both buttons is only seen when they\n * are down together as its event loop samples them, so a click that beats the\n * loop is dropped. One press-and-release leaves the timing to the emulator.\n */\nconst HOLD_THRESHOLD_MS = 150;\n\ninterface Held {\n button: SpeculosButton;\n pressed: boolean;\n}\n\ntype DeviceScreenButtonsProps = {\n onPress: (button: SpeculosButton, action: SpeculosAction) => void;\n};\n\nexport const DeviceScreenButtons: React.FC<DeviceScreenButtonsProps> = ({\n onPress,\n}) => {\n const held = useRef<Held | null>(null);\n const holdTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pressRef = useRef(onPress);\n pressRef.current = onPress;\n\n const clearHoldTimer = useCallback(() => {\n if (!holdTimer.current) return;\n clearTimeout(holdTimer.current);\n holdTimer.current = null;\n }, []);\n\n const hold = useCallback((button: SpeculosButton) => {\n if (held.current) return;\n const entry: Held = { button, pressed: false };\n held.current = entry;\n holdTimer.current = setTimeout(() => {\n entry.pressed = true;\n pressRef.current(button, \"press\");\n }, HOLD_THRESHOLD_MS);\n }, []);\n\n const release = useCallback(() => {\n const entry = held.current;\n if (!entry) return;\n held.current = null;\n clearHoldTimer();\n pressRef.current(\n entry.button,\n entry.pressed ? \"release\" : \"press-and-release\",\n );\n }, [clearHoldTimer]);\n\n useEffect(\n () => () => {\n const entry = held.current;\n held.current = null;\n clearHoldTimer();\n if (entry?.pressed) pressRef.current(entry.button, \"release\");\n },\n [clearHoldTimer],\n );\n\n // Plain buttons: Lumen's Button does not report press and release apart,\n // and a hold needs both.\n return (\n <div className=\"flex gap-8\">\n {BUTTONS.map(({ button, label }) => (\n <button\n key={button}\n type=\"button\"\n aria-label={`${label} button`}\n onPointerDown={(event) => {\n if (event.button !== 0) return;\n // Capture so the release still arrives if the pointer wanders off.\n event.currentTarget.setPointerCapture(event.pointerId);\n hold(button);\n }}\n onPointerUp={release}\n onPointerCancel={release}\n // Keyboard activation fires no pointer events.\n onKeyDown={(event) => {\n if (!isActivationKey(event.key)) return;\n event.preventDefault();\n hold(button);\n }}\n onKeyUp={(event) => isActivationKey(event.key) && release()}\n className=\"border-muted body-3 text-base bg-muted hover:bg-muted-pressed active:bg-active flex-1 rounded-md border py-8 select-none\"\n style={{ touchAction: \"none\" }}\n data-testid={`button_device-screen-${button}`}\n >\n {label}\n </button>\n ))}\n </div>\n );\n};\n"],
5
+ "mappings": "AAAA,OAAOA,GAAS,eAAAC,EAAa,aAAAC,EAAW,UAAAC,MAAc,QAMtD,MAAMC,EAAuD,CAC3D,CAAE,OAAQ,OAAQ,MAAO,MAAO,EAChC,CAAE,OAAQ,OAAQ,MAAO,MAAO,EAChC,CAAE,OAAQ,QAAS,MAAO,OAAQ,CACpC,EAEMC,EAAmBC,GAAgBA,IAAQ,KAAOA,IAAQ,QAS1DC,EAAoB,IAWbC,EAA0D,CAAC,CACtE,QAAAC,CACF,IAAM,CACJ,MAAMC,EAAOP,EAAoB,IAAI,EAC/BQ,EAAYR,EAA6C,IAAI,EAC7DS,EAAWT,EAAOM,CAAO,EAC/BG,EAAS,QAAUH,EAEnB,MAAMI,EAAiBZ,EAAY,IAAM,CAClCU,EAAU,UACf,aAAaA,EAAU,OAAO,EAC9BA,EAAU,QAAU,KACtB,EAAG,CAAC,CAAC,EAECG,EAAOb,EAAac,GAA2B,CACnD,GAAIL,EAAK,QAAS,OAClB,MAAMM,EAAc,CAAE,OAAAD,EAAQ,QAAS,EAAM,EAC7CL,EAAK,QAAUM,EACfL,EAAU,QAAU,WAAW,IAAM,CACnCK,EAAM,QAAU,GAChBJ,EAAS,QAAQG,EAAQ,OAAO,CAClC,EAAGR,CAAiB,CACtB,EAAG,CAAC,CAAC,EAECU,EAAUhB,EAAY,IAAM,CAChC,MAAMe,EAAQN,EAAK,QACdM,IACLN,EAAK,QAAU,KACfG,EAAe,EACfD,EAAS,QACPI,EAAM,OACNA,EAAM,QAAU,UAAY,mBAC9B,EACF,EAAG,CAACH,CAAc,CAAC,EAEnB,OAAAX,EACE,IAAM,IAAM,CACV,MAAMc,EAAQN,EAAK,QACnBA,EAAK,QAAU,KACfG,EAAe,EACXG,GAAO,SAASJ,EAAS,QAAQI,EAAM,OAAQ,SAAS,CAC9D,EACA,CAACH,CAAc,CACjB,EAKEb,EAAA,cAAC,OAAI,UAAU,cACZI,EAAQ,IAAI,CAAC,CAAE,OAAAW,EAAQ,MAAAG,CAAM,IAC5BlB,EAAA,cAAC,UACC,IAAKe,EACL,KAAK,SACL,aAAY,GAAGG,CAAK,UACpB,cAAgBC,GAAU,CACpBA,EAAM,SAAW,IAErBA,EAAM,cAAc,kBAAkBA,EAAM,SAAS,EACrDL,EAAKC,CAAM,EACb,EACA,YAAaE,EACb,gBAAiBA,EAEjB,UAAYE,GAAU,CACfd,EAAgBc,EAAM,GAAG,IAC9BA,EAAM,eAAe,EACrBL,EAAKC,CAAM,EACb,EACA,QAAUI,GAAUd,EAAgBc,EAAM,GAAG,GAAKF,EAAQ,EAC1D,UAAU,2HACV,MAAO,CAAE,YAAa,MAAO,EAC7B,cAAa,wBAAwBF,CAAM,IAE1CG,CACH,CACD,CACH,CAEJ",
6
+ "names": ["React", "useCallback", "useEffect", "useRef", "BUTTONS", "isActivationKey", "key", "HOLD_THRESHOLD_MS", "DeviceScreenButtons", "onPress", "held", "holdTimer", "pressRef", "clearHoldTimer", "hold", "button", "entry", "release", "label", "event"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import p,{act as o}from"react";import{createRoot as c}from"react-dom/client";import{DeviceScreenButtons as u}from"./DeviceScreenButtons";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});describe("DeviceScreenButtons",()=>{let r,i;const t=vi.fn(),e=s=>r.querySelector(`[data-testid="button_device-screen-${s}"]`),n=(s,l,d={})=>o(()=>{s.dispatchEvent(new MouseEvent(l,{bubbles:!0,button:0,...d}))}),a=(s,l,d)=>o(()=>{s.dispatchEvent(new KeyboardEvent(l,{bubbles:!0,key:d}))});beforeEach(()=>{vi.useFakeTimers(),t.mockReset(),HTMLElement.prototype.setPointerCapture=vi.fn(),r=document.createElement("div"),document.body.append(r),i=c(r),o(()=>i.render(p.createElement(u,{onPress:t})))}),afterEach(()=>{o(()=>i.unmount()),r.remove(),vi.useRealTimers()}),it("sends a quick click as one press-and-release",()=>{n(e("left"),"pointerdown"),n(e("left"),"pointerup"),expect(t).toHaveBeenCalledOnce(),expect(t).toHaveBeenCalledWith("left","press-and-release")}),it("sends a hold as a press then a release",()=>{n(e("both"),"pointerdown"),o(()=>{vi.advanceTimersByTime(200)}),n(e("both"),"pointerup"),expect(t.mock.calls).toEqual([["both","press"],["both","release"]])}),it("ignores buttons other than the main one",()=>{n(e("right"),"pointerdown",{button:2}),n(e("right"),"pointerup",{button:2}),expect(t).not.toHaveBeenCalled()}),it("handles one button at a time",()=>{n(e("left"),"pointerdown"),n(e("right"),"pointerdown"),n(e("left"),"pointerup"),expect(t).toHaveBeenCalledWith("left","press-and-release"),expect(t).toHaveBeenCalledOnce()}),it("drives the buttons from the keyboard",()=>{a(e("right"),"keydown","Enter"),a(e("right"),"keyup","Enter"),a(e("right"),"keydown","a"),expect(t).toHaveBeenCalledOnce(),expect(t).toHaveBeenCalledWith("right","press-and-release")}),it("releases a held button when unmounted",()=>{n(e("left"),"pointerdown"),o(()=>{vi.advanceTimersByTime(200)}),o(()=>i.render(p.createElement(p.Fragment,null))),expect(t).toHaveBeenLastCalledWith("left","release")})});
2
+ //# sourceMappingURL=DeviceScreenButtons.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceScreenButtons.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\nimport { DeviceScreenButtons } from \"./DeviceScreenButtons\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\ndescribe(\"DeviceScreenButtons\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const onPress = vi.fn();\n\n const button = (name: string) =>\n container.querySelector<HTMLButtonElement>(\n `[data-testid=\"button_device-screen-${name}\"]`,\n )!;\n const pointer = (target: Element, type: string, init: MouseEventInit = {}) =>\n act(() => {\n target.dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n const key = (target: Element, type: string, value: string) =>\n act(() => {\n target.dispatchEvent(\n new KeyboardEvent(type, { bubbles: true, key: value }),\n );\n });\n\n beforeEach(() => {\n vi.useFakeTimers();\n onPress.mockReset();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n act(() => root.render(<DeviceScreenButtons onPress={onPress} />));\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n it(\"sends a quick click as one press-and-release\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n pointer(button(\"left\"), \"pointerup\");\n\n expect(onPress).toHaveBeenCalledOnce();\n expect(onPress).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n });\n\n it(\"sends a hold as a press then a release\", () => {\n pointer(button(\"both\"), \"pointerdown\");\n act(() => {\n vi.advanceTimersByTime(200);\n });\n pointer(button(\"both\"), \"pointerup\");\n\n expect(onPress.mock.calls).toEqual([\n [\"both\", \"press\"],\n [\"both\", \"release\"],\n ]);\n });\n\n it(\"ignores buttons other than the main one\", () => {\n pointer(button(\"right\"), \"pointerdown\", { button: 2 });\n pointer(button(\"right\"), \"pointerup\", { button: 2 });\n\n expect(onPress).not.toHaveBeenCalled();\n });\n\n it(\"handles one button at a time\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n pointer(button(\"right\"), \"pointerdown\");\n pointer(button(\"left\"), \"pointerup\");\n\n expect(onPress).toHaveBeenCalledWith(\"left\", \"press-and-release\");\n expect(onPress).toHaveBeenCalledOnce();\n });\n\n it(\"drives the buttons from the keyboard\", () => {\n key(button(\"right\"), \"keydown\", \"Enter\");\n key(button(\"right\"), \"keyup\", \"Enter\");\n key(button(\"right\"), \"keydown\", \"a\");\n\n expect(onPress).toHaveBeenCalledOnce();\n expect(onPress).toHaveBeenCalledWith(\"right\", \"press-and-release\");\n });\n\n it(\"releases a held button when unmounted\", () => {\n pointer(button(\"left\"), \"pointerdown\");\n act(() => {\n vi.advanceTimersByTime(200);\n });\n act(() => root.render(<></>));\n\n expect(onPress).toHaveBeenLastCalledWith(\"left\", \"release\");\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["React", "act", "createRoot", "DeviceScreenButtons", "container", "root", "onPress", "button", "name", "pointer", "target", "type", "init", "key", "value"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import i,{useCallback as y,useEffect as g,useRef as c,useState as v}from"react";import{ScreenFrame as E}from"./ScreenFrame";const D=({src:l,onTouch:n})=>{const a=c(null),[p,u]=v(!1),r=c(null),s=c(n);s.current=n,g(()=>u(!1),[l]);const f=t=>{const e=a.current;if(!e?.naturalWidth||!e.naturalHeight)return null;const o=e.getBoundingClientRect();if(!o.width||!o.height)return null;const d=(P,x)=>Math.min(Math.max(Math.round(P),0),x-1);return{x:d((t.clientX-o.left)/o.width*e.naturalWidth,e.naturalWidth),y:d((t.clientY-o.top)/o.height*e.naturalHeight,e.naturalHeight)}},h=t=>{if(!n||r.current||t.button!==0)return;const e=f(t);e&&(a.current?.setPointerCapture(t.pointerId),r.current=e,n(e.x,e.y,"press"))},m=t=>{const e=r.current;!n||!e||(r.current=null,a.current?.releasePointerCapture(t.pointerId),n(e.x,e.y,"release"))};g(()=>()=>{const t=r.current;t&&(r.current=null,s.current?.(t.x,t.y,"release"))},[]);const b=y(()=>u(!0),[]);return p?i.createElement("p",{className:"body-4 text-error text-center","data-testid":"text_device-screen-undecodable"},"Screenshot could not be decoded"):i.createElement(E,null,i.createElement("img",{ref:a,src:l,alt:"Device screen",draggable:!1,onError:b,onPointerDown:h,onPointerUp:m,onPointerCancel:m,className:`block w-full bg-black select-none ${n?"cursor-pointer":""}`,style:{imageRendering:"pixelated",touchAction:"none"},"data-testid":"image_device-screen"}))};export{D as DeviceScreenImage};
2
+ //# sourceMappingURL=DeviceScreenImage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceScreenImage.tsx"],
4
+ "sourcesContent": ["import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { type SpeculosAction } from \"@ledgerhq/device-mockserver-client\";\n\nimport { ScreenFrame } from \"./ScreenFrame\";\n\ninterface Point {\n readonly x: number;\n readonly y: number;\n}\n\ntype DeviceScreenImageProps = {\n src: string;\n /** Omitted for a button-driven device. */\n onTouch?: (x: number, y: number, action: SpeculosAction) => void;\n};\n\nexport const DeviceScreenImage: React.FC<DeviceScreenImageProps> = ({\n src,\n onTouch,\n}) => {\n const imageRef = useRef<HTMLImageElement>(null);\n const [undecodable, setUndecodable] = useState(false);\n /** Where the finger went down, so the release lands on the same spot. */\n const held = useRef<Point | null>(null);\n const touchRef = useRef(onTouch);\n touchRef.current = onTouch;\n\n useEffect(() => setUndecodable(false), [src]);\n\n const toDevicePoint = (\n event: React.PointerEvent<HTMLImageElement>,\n ): Point | null => {\n const image = imageRef.current;\n if (!image?.naturalWidth || !image.naturalHeight) return null;\n const rect = image.getBoundingClientRect();\n if (!rect.width || !rect.height) return null;\n // The far edge maps to the pixel count itself, one past the last pixel.\n const clamp = (value: number, size: number) =>\n Math.min(Math.max(Math.round(value), 0), size - 1);\n return {\n x: clamp(\n ((event.clientX - rect.left) / rect.width) * image.naturalWidth,\n image.naturalWidth,\n ),\n y: clamp(\n ((event.clientY - rect.top) / rect.height) * image.naturalHeight,\n image.naturalHeight,\n ),\n };\n };\n\n const handlePointerDown = (event: React.PointerEvent<HTMLImageElement>) => {\n if (!onTouch || held.current || event.button !== 0) return;\n const point = toDevicePoint(event);\n if (!point) return;\n // Capture so the release still arrives if the pointer wanders off the\n // image mid-hold; without it the device would stay pressed forever.\n imageRef.current?.setPointerCapture(event.pointerId);\n held.current = point;\n onTouch(point.x, point.y, \"press\");\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLImageElement>) => {\n const point = held.current;\n if (!onTouch || !point) return;\n held.current = null;\n imageRef.current?.releasePointerCapture(event.pointerId);\n onTouch(point.x, point.y, \"release\");\n };\n\n useEffect(\n () => () => {\n const point = held.current;\n if (!point) return;\n held.current = null;\n touchRef.current?.(point.x, point.y, \"release\");\n },\n [],\n );\n\n const handleError = useCallback(() => setUndecodable(true), []);\n\n if (undecodable) {\n return (\n <p\n className=\"body-4 text-error text-center\"\n data-testid=\"text_device-screen-undecodable\"\n >\n Screenshot could not be decoded\n </p>\n );\n }\n\n return (\n <ScreenFrame>\n <img\n ref={imageRef}\n src={src}\n alt=\"Device screen\"\n draggable={false}\n onError={handleError}\n onPointerDown={handlePointerDown}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n className={`block w-full bg-black select-none ${\n onTouch ? \"cursor-pointer\" : \"\"\n }`}\n // Device screens are tiny; smoothing them turns text to mush, and a\n // hold must not start a native image drag.\n style={{ imageRendering: \"pixelated\", touchAction: \"none\" }}\n data-testid=\"image_device-screen\"\n />\n </ScreenFrame>\n );\n};\n"],
5
+ "mappings": "AAAA,OAAOA,GAAS,eAAAC,EAAa,aAAAC,EAAW,UAAAC,EAAQ,YAAAC,MAAgB,QAGhE,OAAS,eAAAC,MAAmB,gBAarB,MAAMC,EAAsD,CAAC,CAClE,IAAAC,EACA,QAAAC,CACF,IAAM,CACJ,MAAMC,EAAWN,EAAyB,IAAI,EACxC,CAACO,EAAaC,CAAc,EAAIP,EAAS,EAAK,EAE9CQ,EAAOT,EAAqB,IAAI,EAChCU,EAAWV,EAAOK,CAAO,EAC/BK,EAAS,QAAUL,EAEnBN,EAAU,IAAMS,EAAe,EAAK,EAAG,CAACJ,CAAG,CAAC,EAE5C,MAAMO,EACJC,GACiB,CACjB,MAAMC,EAAQP,EAAS,QACvB,GAAI,CAACO,GAAO,cAAgB,CAACA,EAAM,cAAe,OAAO,KACzD,MAAMC,EAAOD,EAAM,sBAAsB,EACzC,GAAI,CAACC,EAAK,OAAS,CAACA,EAAK,OAAQ,OAAO,KAExC,MAAMC,EAAQ,CAACC,EAAeC,IAC5B,KAAK,IAAI,KAAK,IAAI,KAAK,MAAMD,CAAK,EAAG,CAAC,EAAGC,EAAO,CAAC,EACnD,MAAO,CACL,EAAGF,GACCH,EAAM,QAAUE,EAAK,MAAQA,EAAK,MAASD,EAAM,aACnDA,EAAM,YACR,EACA,EAAGE,GACCH,EAAM,QAAUE,EAAK,KAAOA,EAAK,OAAUD,EAAM,cACnDA,EAAM,aACR,CACF,CACF,EAEMK,EAAqBN,GAAgD,CACzE,GAAI,CAACP,GAAWI,EAAK,SAAWG,EAAM,SAAW,EAAG,OACpD,MAAMO,EAAQR,EAAcC,CAAK,EAC5BO,IAGLb,EAAS,SAAS,kBAAkBM,EAAM,SAAS,EACnDH,EAAK,QAAUU,EACfd,EAAQc,EAAM,EAAGA,EAAM,EAAG,OAAO,EACnC,EAEMC,EAAmBR,GAAgD,CACvE,MAAMO,EAAQV,EAAK,QACf,CAACJ,GAAW,CAACc,IACjBV,EAAK,QAAU,KACfH,EAAS,SAAS,sBAAsBM,EAAM,SAAS,EACvDP,EAAQc,EAAM,EAAGA,EAAM,EAAG,SAAS,EACrC,EAEApB,EACE,IAAM,IAAM,CACV,MAAMoB,EAAQV,EAAK,QACdU,IACLV,EAAK,QAAU,KACfC,EAAS,UAAUS,EAAM,EAAGA,EAAM,EAAG,SAAS,EAChD,EACA,CAAC,CACH,EAEA,MAAME,EAAcvB,EAAY,IAAMU,EAAe,EAAI,EAAG,CAAC,CAAC,EAE9D,OAAID,EAEAV,EAAA,cAAC,KACC,UAAU,gCACV,cAAY,kCACb,iCAED,EAKFA,EAAA,cAACK,EAAA,KACCL,EAAA,cAAC,OACC,IAAKS,EACL,IAAKF,EACL,IAAI,gBACJ,UAAW,GACX,QAASiB,EACT,cAAeH,EACf,YAAaE,EACb,gBAAiBA,EACjB,UAAW,qCACTf,EAAU,iBAAmB,EAC/B,GAGA,MAAO,CAAE,eAAgB,YAAa,YAAa,MAAO,EAC1D,cAAY,sBACd,CACF,CAEJ",
6
+ "names": ["React", "useCallback", "useEffect", "useRef", "useState", "ScreenFrame", "DeviceScreenImage", "src", "onTouch", "imageRef", "undecodable", "setUndecodable", "held", "touchRef", "toDevicePoint", "event", "image", "rect", "clamp", "value", "size", "handlePointerDown", "point", "handlePointerUp", "handleError"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import s,{act as r}from"react";import{createRoot as p}from"react-dom/client";import{DeviceScreenImage as u}from"./DeviceScreenImage";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});describe("DeviceScreenImage",()=>{let n,c;const e=vi.fn(),i=()=>n.querySelector('[data-testid="image_device-screen"]'),o=l=>r(()=>c.render(s.createElement(u,{src:"blob:1",onTouch:l}))),t=(l,d={})=>r(()=>{i().dispatchEvent(new MouseEvent(l,{bubbles:!0,button:0,...d}))}),a=()=>{Object.defineProperty(i(),"naturalWidth",{value:400}),Object.defineProperty(i(),"naturalHeight",{value:600}),i().getBoundingClientRect=()=>({left:0,top:0,width:200,height:300})};beforeEach(()=>{e.mockReset(),HTMLElement.prototype.setPointerCapture=vi.fn(),HTMLElement.prototype.releasePointerCapture=vi.fn(),n=document.createElement("div"),document.body.append(n),c=p(n)}),afterEach(()=>{r(()=>c.unmount()),n.remove()}),it("maps a touch to device pixels and releases it where it went down",()=>{o(e),a(),t("pointerdown",{clientX:50,clientY:150}),t("pointerup",{clientX:190,clientY:10}),expect(e.mock.calls).toEqual([[100,300,"press"],[100,300,"release"]])}),it("clamps a touch on the far edge to the last pixel",()=>{o(e),a(),t("pointerdown",{clientX:200,clientY:300}),expect(e).toHaveBeenCalledWith(399,599,"press")}),it("ignores touches before the image has a size",()=>{o(e),t("pointerdown",{clientX:10,clientY:10}),t("pointerup"),expect(e).not.toHaveBeenCalled()}),it("ignores touches on a button-driven device",()=>{o(),a(),t("pointerdown",{clientX:10,clientY:10}),expect(e).not.toHaveBeenCalled()}),it("releases a held touch when unmounted",()=>{o(e),a(),t("pointerdown",{clientX:50,clientY:150}),r(()=>c.render(s.createElement(s.Fragment,null))),expect(e).toHaveBeenLastCalledWith(100,300,"release")}),it("says so when the screenshot cannot be decoded",()=>{o(e),r(()=>{i().dispatchEvent(new Event("error"))}),expect(n.querySelector('[data-testid="text_device-screen-undecodable"]')).not.toBeNull()})});
2
+ //# sourceMappingURL=DeviceScreenImage.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/DeviceScreenImage.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\n\nimport { DeviceScreenImage } from \"./DeviceScreenImage\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\ndescribe(\"DeviceScreenImage\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const onTouch = vi.fn();\n\n const image = () =>\n container.querySelector<HTMLImageElement>(\n '[data-testid=\"image_device-screen\"]',\n )!;\n const render = (touch?: typeof onTouch) =>\n act(() => root.render(<DeviceScreenImage src=\"blob:1\" onTouch={touch} />));\n const pointer = (type: string, init: MouseEventInit = {}) =>\n act(() => {\n image().dispatchEvent(\n new MouseEvent(type, { bubbles: true, button: 0, ...init }),\n );\n });\n /** A 400x600 screen drawn at 200x300 from the page's top left corner. */\n const sizeImage = () => {\n Object.defineProperty(image(), \"naturalWidth\", { value: 400 });\n Object.defineProperty(image(), \"naturalHeight\", { value: 600 });\n image().getBoundingClientRect = () =>\n ({ left: 0, top: 0, width: 200, height: 300 }) as DOMRect;\n };\n\n beforeEach(() => {\n onTouch.mockReset();\n HTMLElement.prototype.setPointerCapture = vi.fn();\n HTMLElement.prototype.releasePointerCapture = vi.fn();\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n });\n\n it(\"maps a touch to device pixels and releases it where it went down\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n pointer(\"pointerup\", { clientX: 190, clientY: 10 });\n\n expect(onTouch.mock.calls).toEqual([\n [100, 300, \"press\"],\n [100, 300, \"release\"],\n ]);\n });\n\n it(\"clamps a touch on the far edge to the last pixel\", () => {\n render(onTouch);\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 200, clientY: 300 });\n\n expect(onTouch).toHaveBeenCalledWith(399, 599, \"press\");\n });\n\n it(\"ignores touches before the image has a size\", () => {\n render(onTouch);\n\n pointer(\"pointerdown\", { clientX: 10, clientY: 10 });\n pointer(\"pointerup\");\n\n expect(onTouch).not.toHaveBeenCalled();\n });\n\n it(\"ignores touches on a button-driven device\", () => {\n render();\n sizeImage();\n\n pointer(\"pointerdown\", { clientX: 10, clientY: 10 });\n\n expect(onTouch).not.toHaveBeenCalled();\n });\n\n it(\"releases a held touch when unmounted\", () => {\n render(onTouch);\n sizeImage();\n pointer(\"pointerdown\", { clientX: 50, clientY: 150 });\n\n act(() => root.render(<></>));\n\n expect(onTouch).toHaveBeenLastCalledWith(100, 300, \"release\");\n });\n\n it(\"says so when the screenshot cannot be decoded\", () => {\n render(onTouch);\n\n act(() => {\n image().dispatchEvent(new Event(\"error\"));\n });\n\n expect(\n container.querySelector('[data-testid=\"text_device-screen-undecodable\"]'),\n ).not.toBeNull();\n });\n});\n"],
5
+ "mappings": "AAAA,OAAOA,GAAS,OAAAC,MAAW,QAC3B,OAAS,cAAAC,MAA6B,mBAEtC,OAAS,qBAAAC,MAAyB,sBAElC,OAAO,OAAO,WAAY,CAAE,yBAA0B,EAAK,CAAC,EAE5D,SAAS,oBAAqB,IAAM,CAClC,IAAIC,EACAC,EACJ,MAAMC,EAAU,GAAG,GAAG,EAEhBC,EAAQ,IACZH,EAAU,cACR,qCACF,EACII,EAAUC,GACdR,EAAI,IAAMI,EAAK,OAAOL,EAAA,cAACG,EAAA,CAAkB,IAAI,SAAS,QAASM,EAAO,CAAE,CAAC,EACrEC,EAAU,CAACC,EAAcC,EAAuB,CAAC,IACrDX,EAAI,IAAM,CACRM,EAAM,EAAE,cACN,IAAI,WAAWI,EAAM,CAAE,QAAS,GAAM,OAAQ,EAAG,GAAGC,CAAK,CAAC,CAC5D,CACF,CAAC,EAEGC,EAAY,IAAM,CACtB,OAAO,eAAeN,EAAM,EAAG,eAAgB,CAAE,MAAO,GAAI,CAAC,EAC7D,OAAO,eAAeA,EAAM,EAAG,gBAAiB,CAAE,MAAO,GAAI,CAAC,EAC9DA,EAAM,EAAE,sBAAwB,KAC7B,CAAE,KAAM,EAAG,IAAK,EAAG,MAAO,IAAK,OAAQ,GAAI,EAChD,EAEA,WAAW,IAAM,CACfD,EAAQ,UAAU,EAClB,YAAY,UAAU,kBAAoB,GAAG,GAAG,EAChD,YAAY,UAAU,sBAAwB,GAAG,GAAG,EACpDF,EAAY,SAAS,cAAc,KAAK,EACxC,SAAS,KAAK,OAAOA,CAAS,EAC9BC,EAAOH,EAAWE,CAAS,CAC7B,CAAC,EAED,UAAU,IAAM,CACdH,EAAI,IAAMI,EAAK,QAAQ,CAAC,EACxBD,EAAU,OAAO,CACnB,CAAC,EAED,GAAG,mEAAoE,IAAM,CAC3EI,EAAOF,CAAO,EACdO,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,GAAI,CAAC,EACpDA,EAAQ,YAAa,CAAE,QAAS,IAAK,QAAS,EAAG,CAAC,EAElD,OAAOJ,EAAQ,KAAK,KAAK,EAAE,QAAQ,CACjC,CAAC,IAAK,IAAK,OAAO,EAClB,CAAC,IAAK,IAAK,SAAS,CACtB,CAAC,CACH,CAAC,EAED,GAAG,mDAAoD,IAAM,CAC3DE,EAAOF,CAAO,EACdO,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,IAAK,QAAS,GAAI,CAAC,EAErD,OAAOJ,CAAO,EAAE,qBAAqB,IAAK,IAAK,OAAO,CACxD,CAAC,EAED,GAAG,8CAA+C,IAAM,CACtDE,EAAOF,CAAO,EAEdI,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,EAAG,CAAC,EACnDA,EAAQ,WAAW,EAEnB,OAAOJ,CAAO,EAAE,IAAI,iBAAiB,CACvC,CAAC,EAED,GAAG,4CAA6C,IAAM,CACpDE,EAAO,EACPK,EAAU,EAEVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,EAAG,CAAC,EAEnD,OAAOJ,CAAO,EAAE,IAAI,iBAAiB,CACvC,CAAC,EAED,GAAG,uCAAwC,IAAM,CAC/CE,EAAOF,CAAO,EACdO,EAAU,EACVH,EAAQ,cAAe,CAAE,QAAS,GAAI,QAAS,GAAI,CAAC,EAEpDT,EAAI,IAAMI,EAAK,OAAOL,EAAA,cAAAA,EAAA,aAAE,CAAG,CAAC,EAE5B,OAAOM,CAAO,EAAE,yBAAyB,IAAK,IAAK,SAAS,CAC9D,CAAC,EAED,GAAG,gDAAiD,IAAM,CACxDE,EAAOF,CAAO,EAEdL,EAAI,IAAM,CACRM,EAAM,EAAE,cAAc,IAAI,MAAM,OAAO,CAAC,CAC1C,CAAC,EAED,OACEH,EAAU,cAAc,gDAAgD,CAC1E,EAAE,IAAI,SAAS,CACjB,CAAC,CACH,CAAC",
6
+ "names": ["React", "act", "createRoot", "DeviceScreenImage", "container", "root", "onTouch", "image", "render", "touch", "pointer", "type", "init", "sizeImage"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import s,{useMemo as t}from"react";import{MockClient as v}from"@ledgerhq/device-mockserver-client";import{DeviceScreen as m}from"./DeviceScreen";import{mockServerScreenApi as f}from"./mockServerScreenApi";import{useMockServerDevice as S}from"./useMockServerDevice";const M=({url:i,token:c,deviceId:e,...n})=>{const r=t(()=>new v(i,{token:c}),[i,c]),p=t(()=>f(r,e),[r,e]),o=S(r,e);return o===void 0?null:s.createElement(m,{api:p,deviceType:o?.device_type??"",firmwareVersion:o?.firmware_version,...n})};export{M as MockServerDevice};
2
+ //# sourceMappingURL=MockServerDevice.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/MockServerDevice.tsx"],
4
+ "sourcesContent": ["import React, { useMemo } from \"react\";\nimport { MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nimport { mockServerScreenApi } from \"./mockServerScreenApi\";\nimport { useMockServerDevice } from \"./useMockServerDevice\";\n\nexport type MockServerDeviceProps = Pick<\n DeviceScreenProps,\n \"floating\" | \"defaultCollapsed\"\n> & {\n url: string;\n /** Of the session owning the device: devices are scoped to their session. */\n token: string;\n deviceId: string;\n};\n\nexport const MockServerDevice: React.FC<MockServerDeviceProps> = ({\n url,\n token,\n deviceId,\n ...props\n}) => {\n const client = useMemo(() => new MockClient(url, { token }), [url, token]);\n const api = useMemo(\n () => mockServerScreenApi(client, deviceId),\n [client, deviceId],\n );\n const device = useMockServerDevice(client, deviceId);\n\n // Decides touch or buttons, so nothing renders until it is known. An\n // unreadable record still shows the screen, and its errors.\n if (device === undefined) return null;\n\n return (\n <DeviceScreen\n api={api}\n deviceType={device?.device_type ?? \"\"}\n firmwareVersion={device?.firmware_version}\n {...props}\n />\n );\n};\n"],
5
+ "mappings": "AAAA,OAAOA,GAAS,WAAAC,MAAe,QAC/B,OAAS,cAAAC,MAAkB,qCAE3B,OAAS,gBAAAC,MAA4C,iBACrD,OAAS,uBAAAC,MAA2B,wBACpC,OAAS,uBAAAC,MAA2B,wBAY7B,MAAMC,EAAoD,CAAC,CAChE,IAAAC,EACA,MAAAC,EACA,SAAAC,EACA,GAAGC,CACL,IAAM,CACJ,MAAMC,EAASV,EAAQ,IAAM,IAAIC,EAAWK,EAAK,CAAE,MAAAC,CAAM,CAAC,EAAG,CAACD,EAAKC,CAAK,CAAC,EACnEI,EAAMX,EACV,IAAMG,EAAoBO,EAAQF,CAAQ,EAC1C,CAACE,EAAQF,CAAQ,CACnB,EACMI,EAASR,EAAoBM,EAAQF,CAAQ,EAInD,OAAII,IAAW,OAAkB,KAG/Bb,EAAA,cAACG,EAAA,CACC,IAAKS,EACL,WAAYC,GAAQ,aAAe,GACnC,gBAAiBA,GAAQ,iBACxB,GAAGH,EACN,CAEJ",
6
+ "names": ["React", "useMemo", "MockClient", "DeviceScreen", "mockServerScreenApi", "useMockServerDevice", "MockServerDevice", "url", "token", "deviceId", "props", "client", "api", "device"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import r,{act as o}from"react";import{createRoot as s}from"react-dom/client";import{MockClient as c}from"@ledgerhq/device-mockserver-client";import{MockServerDevice as a}from"./MockServerDevice";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});vi.mock("@ledgerhq/device-mockserver-client",()=>({MockClient:vi.fn()}));describe("MockServerDevice",()=>{let e,n;const t=vi.fn();beforeEach(()=>{vi.useFakeTimers(),t.mockReset(),vi.mocked(c).mockImplementation(()=>({getDevice:t,getScreenshot:()=>new Promise(()=>{})})),e=document.createElement("div"),document.body.append(e),n=s(e)}),afterEach(()=>{o(()=>n.unmount()),e.remove(),vi.useRealTimers()});const i=()=>o(()=>n.render(r.createElement(a,{url:"http://mock",token:"session-token",deviceId:"device-1",floating:!0})));it("renders nothing until the device's model is known",()=>{t.mockReturnValue(new Promise(()=>{})),i(),expect(e.childElementCount).toBe(0)}),it("shows the device's screen in its session",async()=>{t.mockResolvedValue({id:"device-1",device_type:"flex",firmware_version:"1.6.1"}),i(),await o(()=>vi.advanceTimersByTimeAsync(0)),expect(c).toHaveBeenCalledWith("http://mock",{token:"session-token"}),expect(t).toHaveBeenCalledWith("device-1"),expect(e.textContent).toContain("Flex - 1.6.1")}),it("still shows the screen when the device cannot be read",async()=>{t.mockRejectedValue(new Error("offline")),i(),await o(()=>vi.advanceTimersByTimeAsync(0)),expect(e.querySelector('[data-testid="container_device-screen"]')).not.toBeNull()})});
2
+ //# sourceMappingURL=MockServerDevice.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/MockServerDevice.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { MockServerDevice } from \"./MockServerDevice\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nvi.mock(\"@ledgerhq/device-mockserver-client\", () => ({\n MockClient: vi.fn(),\n}));\n\ndescribe(\"MockServerDevice\", () => {\n let container: HTMLDivElement;\n let root: Root;\n const getDevice = vi.fn();\n\n beforeEach(() => {\n vi.useFakeTimers();\n getDevice.mockReset();\n vi.mocked(MockClient).mockImplementation(\n () =>\n ({\n getDevice,\n getScreenshot: () => new Promise(() => {}),\n }) as unknown as MockClient,\n );\n container = document.createElement(\"div\");\n document.body.append(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n container.remove();\n vi.useRealTimers();\n });\n\n const render = () =>\n act(() =>\n root.render(\n <MockServerDevice\n url=\"http://mock\"\n token=\"session-token\"\n deviceId=\"device-1\"\n floating\n />,\n ),\n );\n\n it(\"renders nothing until the device's model is known\", () => {\n getDevice.mockReturnValue(new Promise(() => {}));\n\n render();\n\n expect(container.childElementCount).toBe(0);\n });\n\n it(\"shows the device's screen in its session\", async () => {\n getDevice.mockResolvedValue({\n id: \"device-1\",\n device_type: \"flex\",\n firmware_version: \"1.6.1\",\n });\n\n render();\n await act(() => vi.advanceTimersByTimeAsync(0));\n\n expect(MockClient).toHaveBeenCalledWith(\"http://mock\", {\n token: \"session-token\",\n });\n expect(getDevice).toHaveBeenCalledWith(\"device-1\");\n expect(container.textContent).toContain(\"Flex - 1.6.1\");\n });\n\n it(\"still shows the screen when the device cannot be read\", async () => {\n getDevice.mockRejectedValue(new Error(\"offline\"));\n\n render();\n await act(() => vi.advanceTimersByTimeAsync(0));\n\n expect(\n container.querySelector('[data-testid=\"container_device-screen\"]'),\n ).not.toBeNull();\n });\n});\n"],
5
+ "mappings": "AAAA,OAAOA,GAAS,OAAAC,MAAW,QAC3B,OAAS,cAAAC,MAA6B,mBACtC,OAAS,cAAAC,MAAkB,qCAE3B,OAAS,oBAAAC,MAAwB,qBAEjC,OAAO,OAAO,WAAY,CAAE,yBAA0B,EAAK,CAAC,EAE5D,GAAG,KAAK,qCAAsC,KAAO,CACnD,WAAY,GAAG,GAAG,CACpB,EAAE,EAEF,SAAS,mBAAoB,IAAM,CACjC,IAAIC,EACAC,EACJ,MAAMC,EAAY,GAAG,GAAG,EAExB,WAAW,IAAM,CACf,GAAG,cAAc,EACjBA,EAAU,UAAU,EACpB,GAAG,OAAOJ,CAAU,EAAE,mBACpB,KACG,CACC,UAAAI,EACA,cAAe,IAAM,IAAI,QAAQ,IAAM,CAAC,CAAC,CAC3C,EACJ,EACAF,EAAY,SAAS,cAAc,KAAK,EACxC,SAAS,KAAK,OAAOA,CAAS,EAC9BC,EAAOJ,EAAWG,CAAS,CAC7B,CAAC,EAED,UAAU,IAAM,CACdJ,EAAI,IAAMK,EAAK,QAAQ,CAAC,EACxBD,EAAU,OAAO,EACjB,GAAG,cAAc,CACnB,CAAC,EAED,MAAMG,EAAS,IACbP,EAAI,IACFK,EAAK,OACHN,EAAA,cAACI,EAAA,CACC,IAAI,cACJ,MAAM,gBACN,SAAS,WACT,SAAQ,GACV,CACF,CACF,EAEF,GAAG,oDAAqD,IAAM,CAC5DG,EAAU,gBAAgB,IAAI,QAAQ,IAAM,CAAC,CAAC,CAAC,EAE/CC,EAAO,EAEP,OAAOH,EAAU,iBAAiB,EAAE,KAAK,CAAC,CAC5C,CAAC,EAED,GAAG,2CAA4C,SAAY,CACzDE,EAAU,kBAAkB,CAC1B,GAAI,WACJ,YAAa,OACb,iBAAkB,OACpB,CAAC,EAEDC,EAAO,EACP,MAAMP,EAAI,IAAM,GAAG,yBAAyB,CAAC,CAAC,EAE9C,OAAOE,CAAU,EAAE,qBAAqB,cAAe,CACrD,MAAO,eACT,CAAC,EACD,OAAOI,CAAS,EAAE,qBAAqB,UAAU,EACjD,OAAOF,EAAU,WAAW,EAAE,UAAU,cAAc,CACxD,CAAC,EAED,GAAG,wDAAyD,SAAY,CACtEE,EAAU,kBAAkB,IAAI,MAAM,SAAS,CAAC,EAEhDC,EAAO,EACP,MAAMP,EAAI,IAAM,GAAG,yBAAyB,CAAC,CAAC,EAE9C,OACEI,EAAU,cAAc,yCAAyC,CACnE,EAAE,IAAI,SAAS,CACjB,CAAC,CACH,CAAC",
6
+ "names": ["React", "act", "createRoot", "MockClient", "MockServerDevice", "container", "root", "getDevice", "render"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import r from"react";const d=({children:e})=>r.createElement("div",{className:"border-muted @container relative overflow-hidden rounded-md border"},e);export{d as ScreenFrame};
2
+ //# sourceMappingURL=ScreenFrame.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ScreenFrame.tsx"],
4
+ "sourcesContent": ["import React from \"react\";\n\ntype ScreenFrameProps = {\n children: React.ReactNode;\n};\n\n/**\n * The bezel around whatever stands in for the screen. The border lives here,\n * outside the content, so the live frame and the app list inside it take\n * exactly the screen's size.\n */\nexport const ScreenFrame: React.FC<ScreenFrameProps> = ({ children }) => (\n <div className=\"border-muted @container relative overflow-hidden rounded-md border\">\n {children}\n </div>\n);\n"],
5
+ "mappings": "AAAA,OAAOA,MAAW,QAWX,MAAMC,EAA0C,CAAC,CAAE,SAAAC,CAAS,IACjEF,EAAA,cAAC,OAAI,UAAU,sEACZE,CACH",
6
+ "names": ["React", "ScreenFrame", "children"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import{Apex as r,Flex as c,Nano as t,Stax as l}from"@ledgerhq/lumen-ui-react/symbols";const e=(n,o)=>({label:n,icon:t,touch:!1,speculos:!0,screenWidth:128,screenHeight:o,lightScreen:!1}),s={nanoS:{...e("Nano S",32),speculos:!1},nanoSP:e("Nano S Plus",64),nanoX:e("Nano X",64),stax:{label:"Stax",icon:l,touch:!0,speculos:!0,screenWidth:400,screenHeight:672,lightScreen:!0},flex:{label:"Flex",icon:c,touch:!0,speculos:!0,screenWidth:480,screenHeight:600,lightScreen:!0},apexp:{label:"Apex",icon:r,touch:!0,speculos:!0,screenWidth:300,screenHeight:400,lightScreen:!0}},i=e("Device",64),u=n=>s[n]??i;export{u as findDeviceScreenModel};
2
+ //# sourceMappingURL=deviceModels.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/deviceModels.ts"],
4
+ "sourcesContent": ["import { Apex, Flex, Nano, Stax } from \"@ledgerhq/lumen-ui-react/symbols\";\n\nexport interface DeviceScreenModel {\n readonly label: string;\n readonly icon: typeof Nano;\n readonly touch: boolean;\n /** Whether Speculos has an emulator for it, so it can have a screen. */\n readonly speculos: boolean;\n /** In device pixels. */\n readonly screenWidth: number;\n readonly screenHeight: number;\n readonly lightScreen: boolean;\n}\n\nconst nano = (label: string, screenHeight: number): DeviceScreenModel => ({\n label,\n icon: Nano,\n touch: false,\n speculos: true,\n screenWidth: 128,\n screenHeight,\n lightScreen: false,\n});\n\n/** Keyed by the mock server's `device_type`, a DMK `DeviceModelId`. */\nconst MODELS: Record<string, DeviceScreenModel> = {\n nanoS: { ...nano(\"Nano S\", 32), speculos: false },\n nanoSP: nano(\"Nano S Plus\", 64),\n nanoX: nano(\"Nano X\", 64),\n stax: {\n label: \"Stax\",\n icon: Stax,\n touch: true,\n speculos: true,\n screenWidth: 400,\n screenHeight: 672,\n lightScreen: true,\n },\n flex: {\n label: \"Flex\",\n icon: Flex,\n touch: true,\n speculos: true,\n screenWidth: 480,\n screenHeight: 600,\n lightScreen: true,\n },\n apexp: {\n label: \"Apex\",\n icon: Apex,\n touch: true,\n speculos: true,\n screenWidth: 300,\n screenHeight: 400,\n lightScreen: true,\n },\n};\n\nconst UNKNOWN: DeviceScreenModel = nano(\"Device\", 64);\n\nexport const findDeviceScreenModel = (deviceType: string): DeviceScreenModel =>\n MODELS[deviceType] ?? UNKNOWN;\n"],
5
+ "mappings": "AAAA,OAAS,QAAAA,EAAM,QAAAC,EAAM,QAAAC,EAAM,QAAAC,MAAY,mCAcvC,MAAMC,EAAO,CAACC,EAAeC,KAA6C,CACxE,MAAAD,EACA,KAAMH,EACN,MAAO,GACP,SAAU,GACV,YAAa,IACb,aAAAI,EACA,YAAa,EACf,GAGMC,EAA4C,CAChD,MAAO,CAAE,GAAGH,EAAK,SAAU,EAAE,EAAG,SAAU,EAAM,EAChD,OAAQA,EAAK,cAAe,EAAE,EAC9B,MAAOA,EAAK,SAAU,EAAE,EACxB,KAAM,CACJ,MAAO,OACP,KAAMD,EACN,MAAO,GACP,SAAU,GACV,YAAa,IACb,aAAc,IACd,YAAa,EACf,EACA,KAAM,CACJ,MAAO,OACP,KAAMF,EACN,MAAO,GACP,SAAU,GACV,YAAa,IACb,aAAc,IACd,YAAa,EACf,EACA,MAAO,CACL,MAAO,OACP,KAAMD,EACN,MAAO,GACP,SAAU,GACV,YAAa,IACb,aAAc,IACd,YAAa,EACf,CACF,EAEMQ,EAA6BJ,EAAK,SAAU,EAAE,EAEvCK,EAAyBC,GACpCH,EAAOG,CAAU,GAAKF",
6
+ "names": ["Apex", "Flex", "Nano", "Stax", "nano", "label", "screenHeight", "MODELS", "UNKNOWN", "findDeviceScreenModel", "deviceType"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import{findDeviceScreenModel as e}from"./deviceModels";describe("findDeviceScreenModel",()=>{it("finds a model by the mock server's device type",()=>{expect(e("stax")).toMatchObject({label:"Stax",touch:!0,speculos:!0})}),it("marks Nano S as having no emulator",()=>{expect(e("nanoS").speculos).toBe(!1)}),it("falls back to a button-driven model for an unknown type",()=>{expect(e("unknown")).toMatchObject({label:"Device",touch:!1})})});
2
+ //# sourceMappingURL=deviceModels.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/deviceModels.test.ts"],
4
+ "sourcesContent": ["import { findDeviceScreenModel } from \"./deviceModels\";\n\ndescribe(\"findDeviceScreenModel\", () => {\n it(\"finds a model by the mock server's device type\", () => {\n expect(findDeviceScreenModel(\"stax\")).toMatchObject({\n label: \"Stax\",\n touch: true,\n speculos: true,\n });\n });\n\n it(\"marks Nano S as having no emulator\", () => {\n expect(findDeviceScreenModel(\"nanoS\").speculos).toBe(false);\n });\n\n it(\"falls back to a button-driven model for an unknown type\", () => {\n expect(findDeviceScreenModel(\"unknown\")).toMatchObject({\n label: \"Device\",\n touch: false,\n });\n });\n});\n"],
5
+ "mappings": "AAAA,OAAS,yBAAAA,MAA6B,iBAEtC,SAAS,wBAAyB,IAAM,CACtC,GAAG,iDAAkD,IAAM,CACzD,OAAOA,EAAsB,MAAM,CAAC,EAAE,cAAc,CAClD,MAAO,OACP,MAAO,GACP,SAAU,EACZ,CAAC,CACH,CAAC,EAED,GAAG,qCAAsC,IAAM,CAC7C,OAAOA,EAAsB,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAK,CAC5D,CAAC,EAED,GAAG,0DAA2D,IAAM,CAClE,OAAOA,EAAsB,SAAS,CAAC,EAAE,cAAc,CACrD,MAAO,SACP,MAAO,EACT,CAAC,CACH,CAAC,CACH,CAAC",
6
+ "names": ["findDeviceScreenModel"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import{findDeviceScreenModel as o}from"./deviceModels";import{DeviceScreen as p}from"./DeviceScreen";import{MockServerDevice as v}from"./MockServerDevice";import{mockServerScreenApi as i}from"./mockServerScreenApi";import{useMockServerDevice as D}from"./useMockServerDevice";export{p as DeviceScreen,v as MockServerDevice,o as findDeviceScreenModel,i as mockServerScreenApi,D as useMockServerDevice};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/index.ts"],
4
+ "sourcesContent": ["export { type DeviceScreenModel, findDeviceScreenModel } from \"./deviceModels\";\nexport { DeviceScreen, type DeviceScreenProps } from \"./DeviceScreen\";\nexport {\n MockServerDevice,\n type MockServerDeviceProps,\n} from \"./MockServerDevice\";\nexport { mockServerScreenApi } from \"./mockServerScreenApi\";\nexport type { DeviceScreenState, ScreenApi } from \"./types\";\nexport { useMockServerDevice } from \"./useMockServerDevice\";\n"],
5
+ "mappings": "AAAA,OAAiC,yBAAAA,MAA6B,iBAC9D,OAAS,gBAAAC,MAA4C,iBACrD,OACE,oBAAAC,MAEK,qBACP,OAAS,uBAAAC,MAA2B,wBAEpC,OAAS,uBAAAC,MAA2B",
6
+ "names": ["findDeviceScreenModel", "DeviceScreen", "MockServerDevice", "mockServerScreenApi", "useMockServerDevice"]
7
+ }
@@ -0,0 +1,2 @@
1
+ const c=o=>o?.status===409,a=(o,r)=>({screenshot:async()=>{try{return await o.getScreenshot(r)}catch(n){if(c(n))return null;throw n}},idle:async()=>({kind:"os-info",device:await o.getDevice(r)}),pressButton:(n,t)=>o.pressButton(r,n,t),touch:(n,t,e)=>o.touchScreen(r,n,t,e),openApp:async n=>{const t=new TextEncoder().encode(n);if(t.length>255)throw new Error(`App name too long to open: ${n}`);const{response:e}=await o.sendApdu(r,Uint8Array.from([224,216,0,0,t.length,...t])),s=e.slice(-4);if(s!=="9000")throw new Error(`Could not open ${n} (${s})`)}});export{a as mockServerScreenApi};
2
+ //# sourceMappingURL=mockServerScreenApi.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/mockServerScreenApi.ts"],
4
+ "sourcesContent": ["import { type MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { type ScreenApi } from \"./types\";\n\n/** The proxy answers 409 when the device has no Speculos instance. */\nconst isNoInstance = (error: unknown): boolean =>\n (error as { status?: unknown } | null)?.status === 409;\n\nexport const mockServerScreenApi = (\n client: MockClient,\n deviceId: string,\n): ScreenApi => ({\n screenshot: async () => {\n try {\n return await client.getScreenshot(deviceId);\n } catch (error) {\n if (isNoInstance(error)) return null;\n throw error;\n }\n },\n idle: async () => ({\n kind: \"os-info\",\n device: await client.getDevice(deviceId),\n }),\n pressButton: (button, action) => client.pressButton(deviceId, button, action),\n touch: (x, y, action) => client.touchScreen(deviceId, x, y, action),\n openApp: async (appName) => {\n const name = new TextEncoder().encode(appName);\n // Lc is a single byte.\n if (name.length > 0xff) {\n throw new Error(`App name too long to open: ${appName}`);\n }\n const { response } = await client.sendApdu(\n deviceId,\n Uint8Array.from([0xe0, 0xd8, 0x00, 0x00, name.length, ...name]),\n );\n const statusWord = response.slice(-4);\n if (statusWord !== \"9000\") {\n throw new Error(`Could not open ${appName} (${statusWord})`);\n }\n },\n});\n"],
5
+ "mappings": "AAKA,MAAMA,EAAgBC,GACnBA,GAAuC,SAAW,IAExCC,EAAsB,CACjCC,EACAC,KACe,CACf,WAAY,SAAY,CACtB,GAAI,CACF,OAAO,MAAMD,EAAO,cAAcC,CAAQ,CAC5C,OAASH,EAAO,CACd,GAAID,EAAaC,CAAK,EAAG,OAAO,KAChC,MAAMA,CACR,CACF,EACA,KAAM,UAAa,CACjB,KAAM,UACN,OAAQ,MAAME,EAAO,UAAUC,CAAQ,CACzC,GACA,YAAa,CAACC,EAAQC,IAAWH,EAAO,YAAYC,EAAUC,EAAQC,CAAM,EAC5E,MAAO,CAACC,EAAGC,EAAGF,IAAWH,EAAO,YAAYC,EAAUG,EAAGC,EAAGF,CAAM,EAClE,QAAS,MAAOG,GAAY,CAC1B,MAAMC,EAAO,IAAI,YAAY,EAAE,OAAOD,CAAO,EAE7C,GAAIC,EAAK,OAAS,IAChB,MAAM,IAAI,MAAM,8BAA8BD,CAAO,EAAE,EAEzD,KAAM,CAAE,SAAAE,CAAS,EAAI,MAAMR,EAAO,SAChCC,EACA,WAAW,KAAK,CAAC,IAAM,IAAM,EAAM,EAAMM,EAAK,OAAQ,GAAGA,CAAI,CAAC,CAChE,EACME,EAAaD,EAAS,MAAM,EAAE,EACpC,GAAIC,IAAe,OACjB,MAAM,IAAI,MAAM,kBAAkBH,CAAO,KAAKG,CAAU,GAAG,CAE/D,CACF",
6
+ "names": ["isNoInstance", "error", "mockServerScreenApi", "client", "deviceId", "button", "action", "x", "y", "appName", "name", "response", "statusWord"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import{mockServerScreenApi as o}from"./mockServerScreenApi";const n=e=>({getScreenshot:vi.fn(),getDevice:vi.fn(),pressButton:vi.fn(()=>Promise.resolve()),touchScreen:vi.fn(()=>Promise.resolve()),...e});describe("mockServerScreenApi",()=>{it("resolves the screenshot of the device",async()=>{const e=new Blob(["png"]),t=n({getScreenshot:vi.fn(()=>Promise.resolve(e))});await expect(o(t,"device-1").screenshot()).resolves.toBe(e),expect(t.getScreenshot).toHaveBeenCalledWith("device-1")}),it("resolves no screenshot when the device has no Speculos instance",async()=>{const e=n({getScreenshot:vi.fn(()=>Promise.reject({status:409}))});await expect(o(e,"device-1").screenshot()).resolves.toBeNull()}),it("rethrows any other screenshot failure",async()=>{const e=Object.assign(new Error("boom"),{status:500}),t=n({getScreenshot:vi.fn(()=>Promise.reject(e))});await expect(o(t,"device-1").screenshot()).rejects.toBe(e)}),it("shows the device record while idle",async()=>{const e={id:"device-1",device_type:"nanoX"},t=n({getDevice:vi.fn(()=>Promise.resolve(e))});await expect(o(t,"device-1").idle?.()).resolves.toEqual({kind:"os-info",device:e})}),it("forwards buttons and touches to the device",async()=>{const e=n({}),t=o(e,"device-1");await t.pressButton("left","press"),await t.touch(10,20,"release"),expect(e.pressButton).toHaveBeenCalledWith("device-1","left","press"),expect(e.touchScreen).toHaveBeenCalledWith("device-1",10,20,"release")}),it("opens an app by name",async()=>{const e=vi.fn(()=>Promise.resolve({response:"9000"}));await o(n({sendApdu:e}),"device-1").openApp?.("Bitcoin"),expect(e).toHaveBeenCalledWith("device-1",Uint8Array.from([224,216,0,0,7,...new TextEncoder().encode("Bitcoin")]))}),it("fails to open an app the device refuses",async()=>{const e=vi.fn(()=>Promise.resolve({response:"6807"})),t=o(n({sendApdu:e}),"device-1");await expect(t.openApp?.("Bitcoin")).rejects.toThrow("6807")}),it("refuses an app name too long for one APDU",async()=>{const e=vi.fn(()=>Promise.resolve({response:"9000"})),t=o(n({sendApdu:e}),"device-1");await expect(t.openApp?.("a".repeat(256))).rejects.toThrow("too long"),expect(e).not.toHaveBeenCalled()})});
2
+ //# sourceMappingURL=mockServerScreenApi.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/mockServerScreenApi.test.ts"],
4
+ "sourcesContent": ["import { type MockClient } from \"@ledgerhq/device-mockserver-client\";\n\nimport { mockServerScreenApi } from \"./mockServerScreenApi\";\n\nconst aClient = (overrides: Partial<Record<keyof MockClient, unknown>>) =>\n ({\n getScreenshot: vi.fn(),\n getDevice: vi.fn(),\n pressButton: vi.fn(() => Promise.resolve()),\n touchScreen: vi.fn(() => Promise.resolve()),\n ...overrides,\n }) as unknown as MockClient;\n\ndescribe(\"mockServerScreenApi\", () => {\n it(\"resolves the screenshot of the device\", async () => {\n const blob = new Blob([\"png\"]);\n const client = aClient({\n getScreenshot: vi.fn(() => Promise.resolve(blob)),\n });\n\n await expect(\n mockServerScreenApi(client, \"device-1\").screenshot(),\n ).resolves.toBe(blob);\n expect(client.getScreenshot).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"resolves no screenshot when the device has no Speculos instance\", async () => {\n const client = aClient({\n getScreenshot: vi.fn(() => Promise.reject({ status: 409 })),\n });\n\n await expect(\n mockServerScreenApi(client, \"device-1\").screenshot(),\n ).resolves.toBeNull();\n });\n\n it(\"rethrows any other screenshot failure\", async () => {\n const error = Object.assign(new Error(\"boom\"), { status: 500 });\n const client = aClient({\n getScreenshot: vi.fn(() => Promise.reject(error)),\n });\n\n await expect(\n mockServerScreenApi(client, \"device-1\").screenshot(),\n ).rejects.toBe(error);\n });\n\n it(\"shows the device record while idle\", async () => {\n const device = { id: \"device-1\", device_type: \"nanoX\" };\n const client = aClient({ getDevice: vi.fn(() => Promise.resolve(device)) });\n\n await expect(\n mockServerScreenApi(client, \"device-1\").idle?.(),\n ).resolves.toEqual({\n kind: \"os-info\",\n device,\n });\n });\n\n it(\"forwards buttons and touches to the device\", async () => {\n const client = aClient({});\n const api = mockServerScreenApi(client, \"device-1\");\n\n await api.pressButton(\"left\", \"press\");\n await api.touch(10, 20, \"release\");\n\n expect(client.pressButton).toHaveBeenCalledWith(\n \"device-1\",\n \"left\",\n \"press\",\n );\n expect(client.touchScreen).toHaveBeenCalledWith(\n \"device-1\",\n 10,\n 20,\n \"release\",\n );\n });\n\n it(\"opens an app by name\", async () => {\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"9000\" }));\n const api = mockServerScreenApi(aClient({ sendApdu }), \"device-1\");\n\n await api.openApp?.(\"Bitcoin\");\n\n expect(sendApdu).toHaveBeenCalledWith(\n \"device-1\",\n Uint8Array.from([\n 0xe0,\n 0xd8,\n 0x00,\n 0x00,\n 0x07,\n ...new TextEncoder().encode(\"Bitcoin\"),\n ]),\n );\n });\n\n it(\"fails to open an app the device refuses\", async () => {\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"6807\" }));\n const api = mockServerScreenApi(aClient({ sendApdu }), \"device-1\");\n\n await expect(api.openApp?.(\"Bitcoin\")).rejects.toThrow(\"6807\");\n });\n\n it(\"refuses an app name too long for one APDU\", async () => {\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"9000\" }));\n const api = mockServerScreenApi(aClient({ sendApdu }), \"device-1\");\n\n await expect(api.openApp?.(\"a\".repeat(256))).rejects.toThrow(\"too long\");\n expect(sendApdu).not.toHaveBeenCalled();\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["mockServerScreenApi", "aClient", "overrides", "blob", "client", "error", "device", "api", "sendApdu"]
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ import{useEffect as l,useState as f}from"react";const s=2e3;function p(r,e){const[o,t]=f();return l(()=>{let n=!1,i;if(t(void 0),!e)return;const u=()=>{r.getDevice(e).then(c=>{n||t(c)},()=>{n||(t(null),i=setTimeout(u,s))})};return u(),()=>{n=!0,clearTimeout(i)}},[r,e]),o}export{p as useMockServerDevice};
2
+ //# sourceMappingURL=useMockServerDevice.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/useMockServerDevice.ts"],
4
+ "sourcesContent": ["import { useEffect, useState } from \"react\";\nimport {\n type Device,\n type MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\n\nconst RETRY_MS = 2000;\n\n/**\n * The mock server's record of a device: undefined while it loads, or while no\n * device id is given, and null while it cannot be read. A failed read is\n * retried, so a transient error does not leave the device without its model.\n */\nexport function useMockServerDevice(\n client: MockClient,\n deviceId: string,\n): Device | null | undefined {\n const [device, setDevice] = useState<Device | null>();\n\n useEffect(() => {\n let cancelled = false;\n let retry: ReturnType<typeof setTimeout> | undefined;\n setDevice(undefined);\n if (!deviceId) return;\n\n const load = () => {\n client.getDevice(deviceId).then(\n (record) => {\n if (!cancelled) setDevice(record);\n },\n () => {\n if (cancelled) return;\n setDevice(null);\n retry = setTimeout(load, RETRY_MS);\n },\n );\n };\n load();\n\n return () => {\n cancelled = true;\n clearTimeout(retry);\n };\n }, [client, deviceId]);\n\n return device;\n}\n"],
5
+ "mappings": "AAAA,OAAS,aAAAA,EAAW,YAAAC,MAAgB,QAMpC,MAAMC,EAAW,IAOV,SAASC,EACdC,EACAC,EAC2B,CAC3B,KAAM,CAACC,EAAQC,CAAS,EAAIN,EAAwB,EAEpD,OAAAD,EAAU,IAAM,CACd,IAAIQ,EAAY,GACZC,EAEJ,GADAF,EAAU,MAAS,EACf,CAACF,EAAU,OAEf,MAAMK,EAAO,IAAM,CACjBN,EAAO,UAAUC,CAAQ,EAAE,KACxBM,GAAW,CACLH,GAAWD,EAAUI,CAAM,CAClC,EACA,IAAM,CACAH,IACJD,EAAU,IAAI,EACdE,EAAQ,WAAWC,EAAMR,CAAQ,EACnC,CACF,CACF,EACA,OAAAQ,EAAK,EAEE,IAAM,CACXF,EAAY,GACZ,aAAaC,CAAK,CACpB,CACF,EAAG,CAACL,EAAQC,CAAQ,CAAC,EAEdC,CACT",
6
+ "names": ["useEffect", "useState", "RETRY_MS", "useMockServerDevice", "client", "deviceId", "device", "setDevice", "cancelled", "retry", "load", "record"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import l,{act as i}from"react";import{createRoot as m}from"react-dom/client";import{useMockServerDevice as u}from"./useMockServerDevice";Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:!0});const n={id:"device-1",device_type:"stax"};describe("useMockServerDevice",()=>{let d,o,t;const v=({client:e,deviceId:c})=>(t=u(e,c),null),r=e=>({getDevice:vi.fn(e)}),a=(e,c="device-1")=>i(()=>o.render(l.createElement(v,{client:e,deviceId:c}))),s=()=>i(()=>vi.advanceTimersByTimeAsync(0));beforeEach(()=>{vi.useFakeTimers(),t=void 0,d=document.createElement("div"),o=m(d)}),afterEach(()=>{i(()=>o.unmount()),vi.useRealTimers()}),it("reads the device record",async()=>{const e=r(()=>Promise.resolve(n));a(e),expect(t).toBeUndefined(),await s(),expect(t).toBe(n),expect(e.getDevice).toHaveBeenCalledWith("device-1")}),it("reads nothing without a device id",async()=>{const e=r(()=>Promise.resolve(n));a(e,""),await s(),expect(t).toBeUndefined(),expect(e.getDevice).not.toHaveBeenCalled()}),it("retries a failed read until it succeeds",async()=>{const e=vi.fn().mockRejectedValueOnce(new Error("offline")).mockResolvedValue(n),c=r(e);a(c),await s(),expect(t).toBeNull(),await i(()=>vi.advanceTimersByTimeAsync(2e3)),expect(t).toBe(n),expect(e).toHaveBeenCalledTimes(2)}),it("stops retrying once unmounted",async()=>{const e=r(()=>Promise.reject(new Error("offline")));a(e),await s(),i(()=>o.render(l.createElement(l.Fragment,null))),await i(()=>vi.advanceTimersByTimeAsync(1e4)),expect(e.getDevice).toHaveBeenCalledOnce()})});
2
+ //# sourceMappingURL=useMockServerDevice.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/useMockServerDevice.test.tsx"],
4
+ "sourcesContent": ["import React, { act } from \"react\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport {\n type Device,\n type MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\n\nimport { useMockServerDevice } from \"./useMockServerDevice\";\n\nObject.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });\n\nconst DEVICE = { id: \"device-1\", device_type: \"stax\" } as Device;\n\ndescribe(\"useMockServerDevice\", () => {\n let container: HTMLDivElement;\n let root: Root;\n let result: Device | null | undefined;\n\n const Probe: React.FC<{ client: MockClient; deviceId: string }> = ({\n client,\n deviceId,\n }) => {\n result = useMockServerDevice(client, deviceId);\n return null;\n };\n const aClient = (getDevice: () => Promise<Device>) =>\n ({ getDevice: vi.fn(getDevice) }) as unknown as MockClient;\n const render = (client: MockClient, deviceId = \"device-1\") =>\n act(() => root.render(<Probe client={client} deviceId={deviceId} />));\n const flush = () => act(() => vi.advanceTimersByTimeAsync(0));\n\n beforeEach(() => {\n vi.useFakeTimers();\n result = undefined;\n container = document.createElement(\"div\");\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n vi.useRealTimers();\n });\n\n it(\"reads the device record\", async () => {\n const client = aClient(() => Promise.resolve(DEVICE));\n\n render(client);\n expect(result).toBeUndefined();\n await flush();\n\n expect(result).toBe(DEVICE);\n expect(client.getDevice).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"reads nothing without a device id\", async () => {\n const client = aClient(() => Promise.resolve(DEVICE));\n\n render(client, \"\");\n await flush();\n\n expect(result).toBeUndefined();\n expect(client.getDevice).not.toHaveBeenCalled();\n });\n\n it(\"retries a failed read until it succeeds\", async () => {\n const getDevice = vi\n .fn<() => Promise<Device>>()\n .mockRejectedValueOnce(new Error(\"offline\"))\n .mockResolvedValue(DEVICE);\n const client = aClient(getDevice);\n\n render(client);\n await flush();\n expect(result).toBeNull();\n\n await act(() => vi.advanceTimersByTimeAsync(2000));\n\n expect(result).toBe(DEVICE);\n expect(getDevice).toHaveBeenCalledTimes(2);\n });\n\n it(\"stops retrying once unmounted\", async () => {\n const client = aClient(() => Promise.reject(new Error(\"offline\")));\n\n render(client);\n await flush();\n act(() => root.render(<></>));\n await act(() => vi.advanceTimersByTimeAsync(10_000));\n\n expect(client.getDevice).toHaveBeenCalledOnce();\n });\n});\n"],
5
+ "mappings": "AAAA,OAAOA,GAAS,OAAAC,MAAW,QAC3B,OAAS,cAAAC,MAA6B,mBAMtC,OAAS,uBAAAC,MAA2B,wBAEpC,OAAO,OAAO,WAAY,CAAE,yBAA0B,EAAK,CAAC,EAE5D,MAAMC,EAAS,CAAE,GAAI,WAAY,YAAa,MAAO,EAErD,SAAS,sBAAuB,IAAM,CACpC,IAAIC,EACAC,EACAC,EAEJ,MAAMC,EAA4D,CAAC,CACjE,OAAAC,EACA,SAAAC,CACF,KACEH,EAASJ,EAAoBM,EAAQC,CAAQ,EACtC,MAEHC,EAAWC,IACd,CAAE,UAAW,GAAG,GAAGA,CAAS,CAAE,GAC3BC,EAAS,CAACJ,EAAoBC,EAAW,aAC7CT,EAAI,IAAMK,EAAK,OAAON,EAAA,cAACQ,EAAA,CAAM,OAAQC,EAAQ,SAAUC,EAAU,CAAE,CAAC,EAChEI,EAAQ,IAAMb,EAAI,IAAM,GAAG,yBAAyB,CAAC,CAAC,EAE5D,WAAW,IAAM,CACf,GAAG,cAAc,EACjBM,EAAS,OACTF,EAAY,SAAS,cAAc,KAAK,EACxCC,EAAOJ,EAAWG,CAAS,CAC7B,CAAC,EAED,UAAU,IAAM,CACdJ,EAAI,IAAMK,EAAK,QAAQ,CAAC,EACxB,GAAG,cAAc,CACnB,CAAC,EAED,GAAG,0BAA2B,SAAY,CACxC,MAAMG,EAASE,EAAQ,IAAM,QAAQ,QAAQP,CAAM,CAAC,EAEpDS,EAAOJ,CAAM,EACb,OAAOF,CAAM,EAAE,cAAc,EAC7B,MAAMO,EAAM,EAEZ,OAAOP,CAAM,EAAE,KAAKH,CAAM,EAC1B,OAAOK,EAAO,SAAS,EAAE,qBAAqB,UAAU,CAC1D,CAAC,EAED,GAAG,oCAAqC,SAAY,CAClD,MAAMA,EAASE,EAAQ,IAAM,QAAQ,QAAQP,CAAM,CAAC,EAEpDS,EAAOJ,EAAQ,EAAE,EACjB,MAAMK,EAAM,EAEZ,OAAOP,CAAM,EAAE,cAAc,EAC7B,OAAOE,EAAO,SAAS,EAAE,IAAI,iBAAiB,CAChD,CAAC,EAED,GAAG,0CAA2C,SAAY,CACxD,MAAMG,EAAY,GACf,GAA0B,EAC1B,sBAAsB,IAAI,MAAM,SAAS,CAAC,EAC1C,kBAAkBR,CAAM,EACrBK,EAASE,EAAQC,CAAS,EAEhCC,EAAOJ,CAAM,EACb,MAAMK,EAAM,EACZ,OAAOP,CAAM,EAAE,SAAS,EAExB,MAAMN,EAAI,IAAM,GAAG,yBAAyB,GAAI,CAAC,EAEjD,OAAOM,CAAM,EAAE,KAAKH,CAAM,EAC1B,OAAOQ,CAAS,EAAE,sBAAsB,CAAC,CAC3C,CAAC,EAED,GAAG,gCAAiC,SAAY,CAC9C,MAAMH,EAASE,EAAQ,IAAM,QAAQ,OAAO,IAAI,MAAM,SAAS,CAAC,CAAC,EAEjEE,EAAOJ,CAAM,EACb,MAAMK,EAAM,EACZb,EAAI,IAAMK,EAAK,OAAON,EAAA,cAAAA,EAAA,aAAE,CAAG,CAAC,EAC5B,MAAMC,EAAI,IAAM,GAAG,yBAAyB,GAAM,CAAC,EAEnD,OAAOQ,EAAO,SAAS,EAAE,qBAAqB,CAChD,CAAC,CACH,CAAC",
6
+ "names": ["React", "act", "createRoot", "useMockServerDevice", "DEVICE", "container", "root", "result", "Probe", "client", "deviceId", "aClient", "getDevice", "render", "flush"]
7
+ }
@@ -0,0 +1,2 @@
1
+ import{useCallback as m,useEffect as p,useMemo as R,useRef as s,useState as b}from"react";const y=500,E=2e3,D=3,U=r=>r instanceof Error?r.message:String(r);function T(r,v){const[L,i]=b({kind:"loading"}),o=s(null),a=s(!1),l=s(!1);l.current=L.kind==="image";const g=s(0),f=s(0),u=m(n=>{l.current&&++g.current<=D||i({kind:"error",message:U(n)})},[]),c=m(()=>{o.current&&(URL.revokeObjectURL(o.current),o.current=null)},[]),k=s(()=>{}),h=R(()=>{const n=e=>void e.then(()=>k.current()).catch(u);return{pressButton:(e,t)=>n(r.pressButton(e,t)),touch:(e,t,S)=>n(r.touch(e,t,S))}},[r,u]),d=m(async()=>{if(a.current)return;a.current=!0;const n=f.current,e=()=>n!==f.current;try{const t=await r.screenshot();if(e())return;if(g.current=0,t)c(),o.current=URL.createObjectURL(t),i({kind:"image",src:o.current,input:h});else{const S=await r.idle?.();if(e())return;c(),i(S??{kind:"error",message:"No screen to capture"})}}catch(t){e()||u(t)}finally{a.current=!1}},[r,h,u,c]);return k.current=()=>void d(),p(()=>{if(!v){c(),i({kind:"loading"});return}let n,e=!1;const t=async()=>{document.hidden||await d(),!e&&(n=setTimeout(()=>void t(),l.current?y:E))};return t(),()=>{e=!0,clearTimeout(n),f.current++}},[v,d,c]),p(()=>c,[c]),L}export{T as useScreenPolling};
2
+ //# sourceMappingURL=useScreenPolling.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/useScreenPolling.ts"],
4
+ "sourcesContent": ["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport {\n type DeviceScreenInput,\n type DeviceScreenState,\n type ScreenApi,\n} from \"./types\";\n\nconst SCREEN_POLL_MS = 500;\nconst IDLE_POLL_MS = 2000;\n/**\n * An app quitting fails a few calls while Speculos shuts down; the last frame\n * stays up through them rather than flashing an error before the dashboard.\n */\nconst LIVE_FAILURES_TOLERATED = 3;\n\nconst describe = (error: unknown): string =>\n error instanceof Error ? error.message : String(error);\n\nexport function useScreenPolling(\n api: ScreenApi,\n polling: boolean,\n): DeviceScreenState {\n const [state, setState] = useState<DeviceScreenState>({ kind: \"loading\" });\n\n const objectUrl = useRef<string | null>(null);\n const inFlight = useRef(false);\n const isLive = useRef(false);\n isLive.current = state.kind === \"image\";\n const liveFailures = useRef(0);\n /** Bumped when polling stops, so a response still in flight is dropped. */\n const generation = useRef(0);\n\n const fail = useCallback((error: unknown) => {\n if (isLive.current && ++liveFailures.current <= LIVE_FAILURES_TOLERATED) {\n return;\n }\n setState({ kind: \"error\", message: describe(error) });\n }, []);\n\n const releaseObjectUrl = useCallback(() => {\n if (objectUrl.current) {\n URL.revokeObjectURL(objectUrl.current);\n objectUrl.current = null;\n }\n }, []);\n\n /**\n * Input handlers refresh straight after acting. Going through a ref keeps\n * them stable, so the <img> is not remounted on every poll.\n */\n const refreshRef = useRef<() => void>(() => {});\n\n const input = useMemo<DeviceScreenInput>(() => {\n const send = (call: Promise<void>) =>\n void call.then(() => refreshRef.current()).catch(fail);\n\n return {\n pressButton: (button, action) => send(api.pressButton(button, action)),\n touch: (x, y, action) => send(api.touch(x, y, action)),\n };\n }, [api, fail]);\n\n const refresh = useCallback(async () => {\n if (inFlight.current) return;\n inFlight.current = true;\n const started = generation.current;\n const stale = () => started !== generation.current;\n try {\n const blob = await api.screenshot();\n if (stale()) return;\n liveFailures.current = 0;\n if (blob) {\n releaseObjectUrl();\n objectUrl.current = URL.createObjectURL(blob);\n setState({ kind: \"image\", src: objectUrl.current, input });\n } else {\n const idle = await api.idle?.();\n if (stale()) return;\n releaseObjectUrl();\n setState(idle ?? { kind: \"error\", message: \"No screen to capture\" });\n }\n } catch (error) {\n if (!stale()) fail(error);\n } finally {\n inFlight.current = false;\n }\n }, [api, input, fail, releaseObjectUrl]);\n refreshRef.current = () => void refresh();\n\n useEffect(() => {\n if (!polling) {\n // Drop the frame rather than keep a revoked object URL around, which\n // would flash a broken image when the panel reopens.\n releaseObjectUrl();\n setState({ kind: \"loading\" });\n return;\n }\n\n let timer: ReturnType<typeof setTimeout>;\n let cancelled = false;\n\n const tick = async () => {\n if (!document.hidden) await refresh();\n if (cancelled) return;\n timer = setTimeout(\n () => void tick(),\n isLive.current ? SCREEN_POLL_MS : IDLE_POLL_MS,\n );\n };\n void tick();\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n generation.current++;\n };\n }, [polling, refresh, releaseObjectUrl]);\n\n useEffect(() => releaseObjectUrl, [releaseObjectUrl]);\n\n return state;\n}\n"],
5
+ "mappings": "AAAA,OAAS,eAAAA,EAAa,aAAAC,EAAW,WAAAC,EAAS,UAAAC,EAAQ,YAAAC,MAAgB,QAQlE,MAAMC,EAAiB,IACjBC,EAAe,IAKfC,EAA0B,EAE1BC,EAAYC,GAChBA,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EAEhD,SAASC,EACdC,EACAC,EACmB,CACnB,KAAM,CAACC,EAAOC,CAAQ,EAAIV,EAA4B,CAAE,KAAM,SAAU,CAAC,EAEnEW,EAAYZ,EAAsB,IAAI,EACtCa,EAAWb,EAAO,EAAK,EACvBc,EAASd,EAAO,EAAK,EAC3Bc,EAAO,QAAUJ,EAAM,OAAS,QAChC,MAAMK,EAAef,EAAO,CAAC,EAEvBgB,EAAahB,EAAO,CAAC,EAErBiB,EAAOpB,EAAaS,GAAmB,CACvCQ,EAAO,SAAW,EAAEC,EAAa,SAAWX,GAGhDO,EAAS,CAAE,KAAM,QAAS,QAASN,EAASC,CAAK,CAAE,CAAC,CACtD,EAAG,CAAC,CAAC,EAECY,EAAmBrB,EAAY,IAAM,CACrCe,EAAU,UACZ,IAAI,gBAAgBA,EAAU,OAAO,EACrCA,EAAU,QAAU,KAExB,EAAG,CAAC,CAAC,EAMCO,EAAanB,EAAmB,IAAM,CAAC,CAAC,EAExCoB,EAAQrB,EAA2B,IAAM,CAC7C,MAAMsB,EAAQC,GACZ,KAAKA,EAAK,KAAK,IAAMH,EAAW,QAAQ,CAAC,EAAE,MAAMF,CAAI,EAEvD,MAAO,CACL,YAAa,CAACM,EAAQC,IAAWH,EAAKb,EAAI,YAAYe,EAAQC,CAAM,CAAC,EACrE,MAAO,CAACC,EAAGC,EAAGF,IAAWH,EAAKb,EAAI,MAAMiB,EAAGC,EAAGF,CAAM,CAAC,CACvD,CACF,EAAG,CAAChB,EAAKS,CAAI,CAAC,EAERU,EAAU9B,EAAY,SAAY,CACtC,GAAIgB,EAAS,QAAS,OACtBA,EAAS,QAAU,GACnB,MAAMe,EAAUZ,EAAW,QACrBa,EAAQ,IAAMD,IAAYZ,EAAW,QAC3C,GAAI,CACF,MAAMc,EAAO,MAAMtB,EAAI,WAAW,EAClC,GAAIqB,EAAM,EAAG,OAEb,GADAd,EAAa,QAAU,EACnBe,EACFZ,EAAiB,EACjBN,EAAU,QAAU,IAAI,gBAAgBkB,CAAI,EAC5CnB,EAAS,CAAE,KAAM,QAAS,IAAKC,EAAU,QAAS,MAAAQ,CAAM,CAAC,MACpD,CACL,MAAMW,EAAO,MAAMvB,EAAI,OAAO,EAC9B,GAAIqB,EAAM,EAAG,OACbX,EAAiB,EACjBP,EAASoB,GAAQ,CAAE,KAAM,QAAS,QAAS,sBAAuB,CAAC,CACrE,CACF,OAASzB,EAAO,CACTuB,EAAM,GAAGZ,EAAKX,CAAK,CAC1B,QAAE,CACAO,EAAS,QAAU,EACrB,CACF,EAAG,CAACL,EAAKY,EAAOH,EAAMC,CAAgB,CAAC,EACvC,OAAAC,EAAW,QAAU,IAAM,KAAKQ,EAAQ,EAExC7B,EAAU,IAAM,CACd,GAAI,CAACW,EAAS,CAGZS,EAAiB,EACjBP,EAAS,CAAE,KAAM,SAAU,CAAC,EAC5B,MACF,CAEA,IAAIqB,EACAC,EAAY,GAEhB,MAAMC,EAAO,SAAY,CAClB,SAAS,QAAQ,MAAMP,EAAQ,EAChC,CAAAM,IACJD,EAAQ,WACN,IAAM,KAAKE,EAAK,EAChBpB,EAAO,QAAUZ,EAAiBC,CACpC,EACF,EACA,OAAK+B,EAAK,EAEH,IAAM,CACXD,EAAY,GACZ,aAAaD,CAAK,EAClBhB,EAAW,SACb,CACF,EAAG,CAACP,EAASkB,EAAST,CAAgB,CAAC,EAEvCpB,EAAU,IAAMoB,EAAkB,CAACA,CAAgB,CAAC,EAE7CR,CACT",
6
+ "names": ["useCallback", "useEffect", "useMemo", "useRef", "useState", "SCREEN_POLL_MS", "IDLE_POLL_MS", "LIVE_FAILURES_TOLERATED", "describe", "error", "useScreenPolling", "api", "polling", "state", "setState", "objectUrl", "inFlight", "isLive", "liveFailures", "generation", "fail", "releaseObjectUrl", "refreshRef", "input", "send", "call", "button", "action", "x", "y", "refresh", "started", "stale", "blob", "idle", "timer", "cancelled", "tick"]
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./src";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC"}
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { type Device } from "@ledgerhq/device-mockserver-client";
3
+ import { type DeviceScreenModel } from "./deviceModels";
4
+ type DeviceOsInfoProps = {
5
+ device: Device;
6
+ model: DeviceScreenModel;
7
+ /** Omitted, the apps are listed but cannot be opened. */
8
+ onOpenApp?: (appName: string) => Promise<void>;
9
+ };
10
+ export declare const DeviceOsInfo: React.FC<DeviceOsInfoProps>;
11
+ export {};
12
+ //# sourceMappingURL=DeviceOsInfo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceOsInfo.d.ts","sourceRoot":"","sources":["../../../src/DeviceOsInfo.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAEjE,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AA0CxD,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,iBAAiB,CAAC;IACzB,yDAAyD;IACzD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAiHpD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DeviceOsInfo.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceOsInfo.test.d.ts","sourceRoot":"","sources":["../../../src/DeviceOsInfo.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { type ScreenApi } from "./types";
3
+ export type DeviceScreenProps = {
4
+ api: ScreenApi;
5
+ /** The mock server's `device_type`, a DMK `DeviceModelId`. */
6
+ deviceType: string;
7
+ /** Shown next to the model name in the floating window's header. */
8
+ firmwareVersion?: string;
9
+ /**
10
+ * Render as a window over the page that can be dragged by its header and
11
+ * collapsed to it, rather than inline where it is placed.
12
+ */
13
+ floating?: boolean;
14
+ defaultCollapsed?: boolean;
15
+ };
16
+ export declare const DeviceScreen: React.FC<DeviceScreenProps>;
17
+ //# sourceMappingURL=DeviceScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceScreen.d.ts","sourceRoot":"","sources":["../../../src/DeviceScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAS3D,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAGjE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,SAAS,CAAC;IACf,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AA4BF,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgCpD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DeviceScreen.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceScreen.test.d.ts","sourceRoot":"","sources":["../../../src/DeviceScreen.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { type SpeculosAction, type SpeculosButton } from "@ledgerhq/device-mockserver-client";
3
+ type DeviceScreenButtonsProps = {
4
+ onPress: (button: SpeculosButton, action: SpeculosAction) => void;
5
+ };
6
+ export declare const DeviceScreenButtons: React.FC<DeviceScreenButtonsProps>;
7
+ export {};
8
+ //# sourceMappingURL=DeviceScreenButtons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceScreenButtons.d.ts","sourceRoot":"","sources":["../../../src/DeviceScreenButtons.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,oCAAoC,CAAC;AAwB5C,KAAK,wBAAwB,GAAG;IAC9B,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACnE,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA8ElE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DeviceScreenButtons.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceScreenButtons.test.d.ts","sourceRoot":"","sources":["../../../src/DeviceScreenButtons.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { type SpeculosAction } from "@ledgerhq/device-mockserver-client";
3
+ type DeviceScreenImageProps = {
4
+ src: string;
5
+ /** Omitted for a button-driven device. */
6
+ onTouch?: (x: number, y: number, action: SpeculosAction) => void;
7
+ };
8
+ export declare const DeviceScreenImage: React.FC<DeviceScreenImageProps>;
9
+ export {};
10
+ //# sourceMappingURL=DeviceScreenImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceScreenImage.d.ts","sourceRoot":"","sources":["../../../src/DeviceScreenImage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC;AASzE,KAAK,sBAAsB,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CAClE,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAkG9D,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DeviceScreenImage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceScreenImage.test.d.ts","sourceRoot":"","sources":["../../../src/DeviceScreenImage.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { type DeviceScreenProps } from "./DeviceScreen";
3
+ export type MockServerDeviceProps = Pick<DeviceScreenProps, "floating" | "defaultCollapsed"> & {
4
+ url: string;
5
+ /** Of the session owning the device: devices are scoped to their session. */
6
+ token: string;
7
+ deviceId: string;
8
+ };
9
+ export declare const MockServerDevice: React.FC<MockServerDeviceProps>;
10
+ //# sourceMappingURL=MockServerDevice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockServerDevice.d.ts","sourceRoot":"","sources":["../../../src/MockServerDevice.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAGvC,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAItE,MAAM,MAAM,qBAAqB,GAAG,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,kBAAkB,CAChC,GAAG;IACF,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAyB5D,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=MockServerDevice.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockServerDevice.test.d.ts","sourceRoot":"","sources":["../../../src/MockServerDevice.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ type ScreenFrameProps = {
3
+ children: React.ReactNode;
4
+ };
5
+ /**
6
+ * The bezel around whatever stands in for the screen. The border lives here,
7
+ * outside the content, so the live frame and the app list inside it take
8
+ * exactly the screen's size.
9
+ */
10
+ export declare const ScreenFrame: React.FC<ScreenFrameProps>;
11
+ export {};
12
+ //# sourceMappingURL=ScreenFrame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScreenFrame.d.ts","sourceRoot":"","sources":["../../../src/ScreenFrame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAIlD,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { Nano } from "@ledgerhq/lumen-ui-react/symbols";
2
+ export interface DeviceScreenModel {
3
+ readonly label: string;
4
+ readonly icon: typeof Nano;
5
+ readonly touch: boolean;
6
+ /** Whether Speculos has an emulator for it, so it can have a screen. */
7
+ readonly speculos: boolean;
8
+ /** In device pixels. */
9
+ readonly screenWidth: number;
10
+ readonly screenHeight: number;
11
+ readonly lightScreen: boolean;
12
+ }
13
+ export declare const findDeviceScreenModel: (deviceType: string) => DeviceScreenModel;
14
+ //# sourceMappingURL=deviceModels.d.ts.map