@ledgerhq/device-mockserver-react-core 0.0.0-delayed-signing-20261113142655
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.
- package/LICENSE.MD +202 -0
- package/README.md +29 -0
- package/lib/cjs/index.js +2 -0
- package/lib/cjs/index.js.map +7 -0
- package/lib/cjs/package.json +61 -0
- package/lib/cjs/src/deviceModels.js +2 -0
- package/lib/cjs/src/deviceModels.js.map +7 -0
- package/lib/cjs/src/index.js +2 -0
- package/lib/cjs/src/index.js.map +7 -0
- package/lib/cjs/src/mockServerScreenApi.js +2 -0
- package/lib/cjs/src/mockServerScreenApi.js.map +7 -0
- package/lib/cjs/src/types.js +2 -0
- package/lib/cjs/src/types.js.map +7 -0
- package/lib/cjs/src/useButtonHold.js +2 -0
- package/lib/cjs/src/useButtonHold.js.map +7 -0
- package/lib/cjs/src/useHold.js +2 -0
- package/lib/cjs/src/useHold.js.map +7 -0
- package/lib/cjs/src/useMockServerDevice.js +2 -0
- package/lib/cjs/src/useMockServerDevice.js.map +7 -0
- package/lib/cjs/src/useOpenApp.js +2 -0
- package/lib/cjs/src/useOpenApp.js.map +7 -0
- package/lib/cjs/src/useScreenPolling.js +2 -0
- package/lib/cjs/src/useScreenPolling.js.map +7 -0
- package/lib/cjs/src/useTouchHold.js +2 -0
- package/lib/cjs/src/useTouchHold.js.map +7 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/index.js.map +7 -0
- package/lib/esm/package.json +61 -0
- package/lib/esm/src/deviceModels.js +2 -0
- package/lib/esm/src/deviceModels.js.map +7 -0
- package/lib/esm/src/index.js +2 -0
- package/lib/esm/src/index.js.map +7 -0
- package/lib/esm/src/mockServerScreenApi.js +2 -0
- package/lib/esm/src/mockServerScreenApi.js.map +7 -0
- package/lib/esm/src/types.js +1 -0
- package/lib/esm/src/types.js.map +7 -0
- package/lib/esm/src/useButtonHold.js +2 -0
- package/lib/esm/src/useButtonHold.js.map +7 -0
- package/lib/esm/src/useHold.js +2 -0
- package/lib/esm/src/useHold.js.map +7 -0
- package/lib/esm/src/useMockServerDevice.js +2 -0
- package/lib/esm/src/useMockServerDevice.js.map +7 -0
- package/lib/esm/src/useOpenApp.js +2 -0
- package/lib/esm/src/useOpenApp.js.map +7 -0
- package/lib/esm/src/useScreenPolling.js +2 -0
- package/lib/esm/src/useScreenPolling.js.map +7 -0
- package/lib/esm/src/useTouchHold.js +2 -0
- package/lib/esm/src/useTouchHold.js.map +7 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/src/deviceModels.d.ts +12 -0
- package/lib/types/src/deviceModels.d.ts.map +1 -0
- package/lib/types/src/index.d.ts +10 -0
- package/lib/types/src/index.d.ts.map +1 -0
- package/lib/types/src/mockServerScreenApi.d.ts +3 -0
- package/lib/types/src/mockServerScreenApi.d.ts.map +1 -0
- package/lib/types/src/types.d.ts +60 -0
- package/lib/types/src/types.d.ts.map +1 -0
- package/lib/types/src/useButtonHold.d.ts +6 -0
- package/lib/types/src/useButtonHold.d.ts.map +1 -0
- package/lib/types/src/useHold.d.ts +23 -0
- package/lib/types/src/useHold.d.ts.map +1 -0
- package/lib/types/src/useMockServerDevice.d.ts +9 -0
- package/lib/types/src/useMockServerDevice.d.ts.map +1 -0
- package/lib/types/src/useOpenApp.d.ts +19 -0
- package/lib/types/src/useOpenApp.d.ts.map +1 -0
- package/lib/types/src/useScreenPolling.d.ts +3 -0
- package/lib/types/src/useScreenPolling.d.ts.map +1 -0
- package/lib/types/src/useTouchHold.d.ts +23 -0
- package/lib/types/src/useTouchHold.d.ts.map +1 -0
- package/lib/types/tsconfig.prod.tsbuildinfo +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["export { type DeviceScreenModel, findDeviceScreenModel } from \"./deviceModels\";\nexport { mockServerScreenApi } from \"./mockServerScreenApi\";\nexport type {\n DeviceScreenInput,\n DeviceScreenState,\n MockServerClient,\n ScreenApi,\n ScreenPlatform,\n} from \"./types\";\nexport { type ButtonHold, useButtonHold } from \"./useButtonHold\";\nexport { type Hold, HOLD_THRESHOLD_MS, useHold } from \"./useHold\";\nexport { useMockServerDevice } from \"./useMockServerDevice\";\nexport {\n appIconUrl,\n OPEN_APP_TIMEOUT_MS,\n openableApps,\n type OpenApp,\n useOpenApp,\n} from \"./useOpenApp\";\nexport { useScreenPolling } from \"./useScreenPolling\";\nexport {\n type ScreenPoint,\n type Size,\n toScreenPoint,\n type TouchHold,\n useTouchHold,\n} from \"./useTouchHold\";\n"],
|
|
5
|
+
"mappings": "AAAA,OAAiC,yBAAAA,MAA6B,iBAC9D,OAAS,uBAAAC,MAA2B,wBAQpC,OAA0B,iBAAAC,MAAqB,kBAC/C,OAAoB,qBAAAC,EAAmB,WAAAC,MAAe,YACtD,OAAS,uBAAAC,MAA2B,wBACpC,OACE,cAAAC,EACA,uBAAAC,EACA,gBAAAC,EAEA,cAAAC,MACK,eACP,OAAS,oBAAAC,MAAwB,qBACjC,OAGE,iBAAAC,EAEA,gBAAAC,MACK",
|
|
6
|
+
"names": ["findDeviceScreenModel", "mockServerScreenApi", "useButtonHold", "HOLD_THRESHOLD_MS", "useHold", "useMockServerDevice", "appIconUrl", "OPEN_APP_TIMEOUT_MS", "openableApps", "useOpenApp", "useScreenPolling", "toScreenPoint", "useTouchHold"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const c=o=>o?.status===409,a=(o,t)=>({screenshot:async()=>{try{return await o.getScreenshot(t)}catch(n){if(c(n))return null;throw n}},idle:async()=>({kind:"os-info",device:await o.getDevice(t)}),pressButton:(n,e)=>o.pressButton(t,n,e),touch:(n,e,r)=>o.touchScreen(t,n,e,r),openApp:async n=>{const e=new TextEncoder().encode(n);if(e.length>255)throw new Error(`App name too long to open: ${n}`);const{response:r}=await o.sendApdu(t,Uint8Array.from([224,216,0,0,e.length,...e])),s=r.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 MockServerClient, 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: MockServerClient,\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": "AAGA,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 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/useButtonHold.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n type SpeculosAction,\n type SpeculosButton,\n} from \"@ledgerhq/device-mockserver-client\";\n\nimport { type Hold, useHold } from \"./useHold\";\n\nexport type ButtonHold = Hold<SpeculosButton>;\n\n/** A physical button's down and up, as the actions the device gets. */\nexport const useButtonHold = (\n onPress: (button: SpeculosButton, action: SpeculosAction) => void,\n): ButtonHold => useHold(onPress);\n"],
|
|
5
|
+
"mappings": "AAKA,OAAoB,WAAAA,MAAe,YAK5B,MAAMC,EACXC,GACeF,EAAQE,CAAO",
|
|
6
|
+
"names": ["useHold", "useButtonHold", "onPress"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{useCallback as l,useEffect as i,useMemo as f,useRef as o}from"react";const p=150;function m(s){const r=o(null),n=o(null),t=o(s);t.current=s;const u=l(()=>{n.current&&(clearTimeout(n.current),n.current=null)},[]),c=l(e=>{if(!t.current||r.current)return!1;const d={target:e,pressed:!1};return r.current=d,n.current=setTimeout(()=>{d.pressed=!0,t.current?.(e,"press")},p),!0},[]),a=l(()=>{const e=r.current;return e?(r.current=null,u(),t.current?.(e.target,e.pressed?"release":"press-and-release"),!0):!1},[u]);return i(()=>()=>{const e=r.current;r.current=null,u(),e?.pressed&&t.current?.(e.target,"release")},[u]),f(()=>({hold:c,release:a}),[c,a])}export{p as HOLD_THRESHOLD_MS,m as useHold};
|
|
2
|
+
//# sourceMappingURL=useHold.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/useHold.ts"],
|
|
4
|
+
"sourcesContent": ["import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { type SpeculosAction } from \"@ledgerhq/device-mockserver-client\";\n\n/**\n * How long a button or finger stays down before the interaction counts as a\n * hold. A click sent as a separate press and release is two requests the\n * device can receive in either order, and the emulator only sees what is down\n * when its event loop samples it, so a click that beats the loop is dropped:\n * a quick tap, or a chord of both buttons. One press-and-release leaves the\n * timing to the emulator.\n */\nexport const HOLD_THRESHOLD_MS = 150;\n\ninterface Held<T> {\n target: T;\n pressed: boolean;\n}\n\nexport interface Hold<T> {\n /** Went down. False when ignored: no handler, or something is held. */\n hold(target: T): boolean;\n /** Went up. False when nothing was held. */\n release(): boolean;\n}\n\n/**\n * Turns a button's or a finger's down and up into the actions the device\n * gets: a quick click as one press-and-release, a longer hold as a press then\n * a release. Whatever is still pressed on unmount is released.\n */\nexport function useHold<T>(\n onAction: ((target: T, action: SpeculosAction) => void) | undefined,\n): Hold<T> {\n const held = useRef<Held<T> | null>(null);\n const holdTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const actionRef = useRef(onAction);\n actionRef.current = onAction;\n\n const clearHoldTimer = useCallback(() => {\n if (!holdTimer.current) return;\n clearTimeout(holdTimer.current);\n holdTimer.current = null;\n }, []);\n\n const hold = useCallback((target: T) => {\n if (!actionRef.current || held.current) return false;\n const entry: Held<T> = { target, pressed: false };\n held.current = entry;\n holdTimer.current = setTimeout(() => {\n entry.pressed = true;\n actionRef.current?.(target, \"press\");\n }, HOLD_THRESHOLD_MS);\n return true;\n }, []);\n\n const release = useCallback(() => {\n const entry = held.current;\n if (!entry) return false;\n held.current = null;\n clearHoldTimer();\n actionRef.current?.(\n entry.target,\n entry.pressed ? \"release\" : \"press-and-release\",\n );\n return true;\n }, [clearHoldTimer]);\n\n useEffect(\n () => () => {\n const entry = held.current;\n held.current = null;\n clearHoldTimer();\n if (entry?.pressed) actionRef.current?.(entry.target, \"release\");\n },\n [clearHoldTimer],\n );\n\n return useMemo(() => ({ hold, release }), [hold, release]);\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAS,eAAAA,EAAa,aAAAC,EAAW,WAAAC,EAAS,UAAAC,MAAc,QAWjD,MAAMC,EAAoB,IAmB1B,SAASC,EACdC,EACS,CACT,MAAMC,EAAOJ,EAAuB,IAAI,EAClCK,EAAYL,EAA6C,IAAI,EAC7DM,EAAYN,EAAOG,CAAQ,EACjCG,EAAU,QAAUH,EAEpB,MAAMI,EAAiBV,EAAY,IAAM,CAClCQ,EAAU,UACf,aAAaA,EAAU,OAAO,EAC9BA,EAAU,QAAU,KACtB,EAAG,CAAC,CAAC,EAECG,EAAOX,EAAaY,GAAc,CACtC,GAAI,CAACH,EAAU,SAAWF,EAAK,QAAS,MAAO,GAC/C,MAAMM,EAAiB,CAAE,OAAAD,EAAQ,QAAS,EAAM,EAChD,OAAAL,EAAK,QAAUM,EACfL,EAAU,QAAU,WAAW,IAAM,CACnCK,EAAM,QAAU,GAChBJ,EAAU,UAAUG,EAAQ,OAAO,CACrC,EAAGR,CAAiB,EACb,EACT,EAAG,CAAC,CAAC,EAECU,EAAUd,EAAY,IAAM,CAChC,MAAMa,EAAQN,EAAK,QACnB,OAAKM,GACLN,EAAK,QAAU,KACfG,EAAe,EACfD,EAAU,UACRI,EAAM,OACNA,EAAM,QAAU,UAAY,mBAC9B,EACO,IAPY,EAQrB,EAAG,CAACH,CAAc,CAAC,EAEnB,OAAAT,EACE,IAAM,IAAM,CACV,MAAMY,EAAQN,EAAK,QACnBA,EAAK,QAAU,KACfG,EAAe,EACXG,GAAO,SAASJ,EAAU,UAAUI,EAAM,OAAQ,SAAS,CACjE,EACA,CAACH,CAAc,CACjB,EAEOR,EAAQ,KAAO,CAAE,KAAAS,EAAM,QAAAG,CAAQ,GAAI,CAACH,EAAMG,CAAO,CAAC,CAC3D",
|
|
6
|
+
"names": ["useCallback", "useEffect", "useMemo", "useRef", "HOLD_THRESHOLD_MS", "useHold", "onAction", "held", "holdTimer", "actionRef", "clearHoldTimer", "hold", "target", "entry", "release"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{useEffect as f,useState as l}from"react";const s=2e3;function p(n,e){const[u,t]=l();return f(()=>{let r=!1,i;if(t(void 0),!e)return;const o=()=>{n.getDevice(e).then(c=>{r||t(c)},()=>{r||(t(null),i=setTimeout(o,s))})};return o(),()=>{r=!0,clearTimeout(i)}},[n,e]),u}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 { type Device } from \"@ledgerhq/device-mockserver-client\";\n\nimport { type MockServerClient } from \"./types\";\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: MockServerClient,\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,QAKpC,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{useCallback as d,useEffect as f,useRef as g,useState as p}from"react";const l="BOLOS",a=1e4,v=e=>(e.apps??[]).filter(({name:n})=>n!==l),O=e=>`https://cdn.live.ledger.com/icons/${e.toLowerCase().replaceAll(" ","_")}.png`;function T(e){const[n,t]=p(),[u,r]=p(),s=g(void 0);f(()=>()=>clearTimeout(s.current),[]);const c=d(o=>{!e||n||(t(o),r(void 0),e(o).then(()=>{s.current=setTimeout(()=>{t(void 0),r(`${o} opened but its screen never showed up`)},a)},i=>{t(void 0),r(i instanceof Error?i.message:String(i))}))},[e,n]);return{opening:n,error:u,open:c}}export{a as OPEN_APP_TIMEOUT_MS,O as appIconUrl,v as openableApps,T as useOpenApp};
|
|
2
|
+
//# sourceMappingURL=useOpenApp.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/useOpenApp.ts"],
|
|
4
|
+
"sourcesContent": ["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { type Device } from \"@ledgerhq/device-mockserver-client\";\n\n/** The OS reports itself as an app; it is not one the user can open. */\nconst OS_APP_NAME = \"BOLOS\";\n\nexport const OPEN_APP_TIMEOUT_MS = 10_000;\n\n/** The apps a device lists that the user can open. */\nexport const openableApps = (device: Device): NonNullable<Device[\"apps\"]> =>\n (device.apps ?? []).filter(({ name }) => name !== OS_APP_NAME);\n\n/** Ledger Live's app icons, keyed by the app's Manager API icon name. */\nexport const appIconUrl = (appName: string): string =>\n `https://cdn.live.ledger.com/icons/${appName.toLowerCase().replaceAll(\" \", \"_\")}.png`;\n\nexport interface OpenApp {\n /**\n * The app being opened. Kept on success, until the live screen replaces the\n * app list or the wait times out.\n */\n opening: string | undefined;\n error: string | undefined;\n /** Ignored without a handler, or while another app is opening. */\n open(appName: string): void;\n}\n\n/** Omitted, the apps are listed but cannot be opened. */\nexport function useOpenApp(\n onOpenApp: ((appName: string) => Promise<void>) | undefined,\n): OpenApp {\n const [opening, setOpening] = useState<string>();\n const [error, setError] = useState<string>();\n\n const openTimer = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined,\n );\n useEffect(() => () => clearTimeout(openTimer.current), []);\n\n const open = useCallback(\n (appName: string) => {\n if (!onOpenApp || opening) return;\n setOpening(appName);\n setError(undefined);\n onOpenApp(appName).then(\n () => {\n openTimer.current = setTimeout(() => {\n setOpening(undefined);\n setError(`${appName} opened but its screen never showed up`);\n }, OPEN_APP_TIMEOUT_MS);\n },\n (cause: unknown) => {\n setOpening(undefined);\n setError(cause instanceof Error ? cause.message : String(cause));\n },\n );\n },\n [onOpenApp, opening],\n );\n\n return { opening, error, open };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAS,eAAAA,EAAa,aAAAC,EAAW,UAAAC,EAAQ,YAAAC,MAAgB,QAIzD,MAAMC,EAAc,QAEPC,EAAsB,IAGtBC,EAAgBC,IAC1BA,EAAO,MAAQ,CAAC,GAAG,OAAO,CAAC,CAAE,KAAAC,CAAK,IAAMA,IAASJ,CAAW,EAGlDK,EAAcC,GACzB,qCAAqCA,EAAQ,YAAY,EAAE,WAAW,IAAK,GAAG,CAAC,OAc1E,SAASC,EACdC,EACS,CACT,KAAM,CAACC,EAASC,CAAU,EAAIX,EAAiB,EACzC,CAACY,EAAOC,CAAQ,EAAIb,EAAiB,EAErCc,EAAYf,EAChB,MACF,EACAD,EAAU,IAAM,IAAM,aAAagB,EAAU,OAAO,EAAG,CAAC,CAAC,EAEzD,MAAMC,EAAOlB,EACVU,GAAoB,CACf,CAACE,GAAaC,IAClBC,EAAWJ,CAAO,EAClBM,EAAS,MAAS,EAClBJ,EAAUF,CAAO,EAAE,KACjB,IAAM,CACJO,EAAU,QAAU,WAAW,IAAM,CACnCH,EAAW,MAAS,EACpBE,EAAS,GAAGN,CAAO,wCAAwC,CAC7D,EAAGL,CAAmB,CACxB,EACCc,GAAmB,CAClBL,EAAW,MAAS,EACpBE,EAASG,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CAAC,CACjE,CACF,EACF,EACA,CAACP,EAAWC,CAAO,CACrB,EAEA,MAAO,CAAE,QAAAA,EAAS,MAAAE,EAAO,KAAAG,CAAK,CAChC",
|
|
6
|
+
"names": ["useCallback", "useEffect", "useRef", "useState", "OS_APP_NAME", "OPEN_APP_TIMEOUT_MS", "openableApps", "device", "name", "appIconUrl", "appName", "useOpenApp", "onOpenApp", "opening", "setOpening", "error", "setError", "openTimer", "open", "cause"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{useCallback as k,useEffect as L,useMemo as D,useRef as o,useState as p}from"react";const w=500,P=2e3,R=3,T=n=>n instanceof Error?n.message:String(n);function b(n,y,s){const[h,a]=p({kind:"loading"}),u=o(null),S=o(!1),d=o(!1);d.current=h.kind==="image";const E=o(0),g=o(0),l=k(r=>{d.current&&++E.current<=R||a({kind:"error",message:T(r)})},[]),i=k(()=>{u.current&&(s.releaseImageSource?.(u.current),u.current=null)},[s]),I=o(()=>{}),m=D(()=>{const r=e=>void e.then(()=>I.current()).catch(l);return{pressButton:(e,t)=>r(n.pressButton(e,t)),touch:(e,t,c)=>r(n.touch(e,t,c))}},[n,l]),v=k(async()=>{if(S.current)return;S.current=!0;const r=g.current,e=()=>r!==g.current;try{const t=await n.screenshot();if(e())return;if(E.current=0,t){const c=await s.toImageSource(t);if(e()){s.releaseImageSource?.(c);return}c!==u.current&&(i(),u.current=c),a(f=>f.kind==="image"&&f.src===c&&f.input===m?f:{kind:"image",src:c,input:m})}else{const c=await n.idle?.();if(e())return;i(),a(c??{kind:"error",message:"No screen to capture"})}}catch(t){e()||l(t)}finally{S.current=!1}},[n,s,m,l,i]);return I.current=()=>void v(),L(()=>{if(!y){i(),a({kind:"loading"});return}let r,e=!1;const t=async()=>{s.isHidden()||await v(),!e&&(r=setTimeout(()=>void t(),d.current?w:P))};return t(),()=>{e=!0,clearTimeout(r),g.current++}},[y,s,v,i]),L(()=>i,[i]),h}export{b 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 type ScreenPlatform,\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 platform: ScreenPlatform,\n): DeviceScreenState {\n const [state, setState] = useState<DeviceScreenState>({ kind: \"loading\" });\n\n const imageSource = 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 releaseImageSource = useCallback(() => {\n if (imageSource.current) {\n platform.releaseImageSource?.(imageSource.current);\n imageSource.current = null;\n }\n }, [platform]);\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 const source = await platform.toImageSource(blob);\n if (stale()) {\n platform.releaseImageSource?.(source);\n return;\n }\n if (source !== imageSource.current) {\n releaseImageSource();\n imageSource.current = source;\n }\n // A source made from the bytes, like a data URI, repeats while the\n // screen holds still; keeping the state then skips a re-render.\n setState((current) =>\n current.kind === \"image\" &&\n current.src === source &&\n current.input === input\n ? current\n : { kind: \"image\", src: source, input },\n );\n } else {\n const idle = await api.idle?.();\n if (stale()) return;\n releaseImageSource();\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, platform, input, fail, releaseImageSource]);\n refreshRef.current = () => void refresh();\n\n useEffect(() => {\n if (!polling) {\n // Drop the frame rather than keep a released source around, which\n // would flash a broken image when the panel reopens.\n releaseImageSource();\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 (!platform.isHidden()) 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, platform, refresh, releaseImageSource]);\n\n useEffect(() => releaseImageSource, [releaseImageSource]);\n\n return state;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["useCallback", "useEffect", "useMemo", "useRef", "useState", "SCREEN_POLL_MS", "IDLE_POLL_MS", "LIVE_FAILURES_TOLERATED", "describe", "error", "useScreenPolling", "api", "polling", "platform", "state", "setState", "imageSource", "inFlight", "isLive", "liveFailures", "generation", "fail", "releaseImageSource", "refreshRef", "input", "send", "call", "button", "action", "x", "y", "refresh", "started", "stale", "blob", "source", "current", "idle", "timer", "cancelled", "tick"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{useMemo as h}from"react";import{useHold as m}from"./useHold";const c=(o,n,t,e)=>{if(!t.width||!t.height||!e.width||!e.height)return null;const i=(r,u)=>Math.min(Math.max(Math.round(r),0),u-1);return{x:i(o/t.width*e.width,e.width),y:i(n/t.height*e.height,e.height)}};function p(o){const n=h(()=>o&&((t,e)=>o(t.x,t.y,e)),[o]);return m(n)}export{c as toScreenPoint,p as useTouchHold};
|
|
2
|
+
//# sourceMappingURL=useTouchHold.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/useTouchHold.ts"],
|
|
4
|
+
"sourcesContent": ["import { useMemo } from \"react\";\nimport { type SpeculosAction } from \"@ledgerhq/device-mockserver-client\";\n\nimport { type Hold, useHold } from \"./useHold\";\n\nexport interface Size {\n readonly width: number;\n readonly height: number;\n}\n\nexport interface ScreenPoint {\n readonly x: number;\n readonly y: number;\n}\n\n/**\n * Where an offset into the screen as drawn lands in device pixels, or null\n * while either size is unknown.\n */\nexport const toScreenPoint = (\n offsetX: number,\n offsetY: number,\n drawn: Size,\n screen: Size,\n): ScreenPoint | null => {\n if (!drawn.width || !drawn.height || !screen.width || !screen.height) {\n return null;\n }\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((offsetX / drawn.width) * screen.width, screen.width),\n y: clamp((offsetY / drawn.height) * screen.height, screen.height),\n };\n};\n\nexport type TouchHold = Hold<ScreenPoint>;\n\n/**\n * A finger's down and up on the screen, as the actions the device gets: a\n * quick tap as one press-and-release, a hold as a press then a release on the\n * spot where it went down.\n */\nexport function useTouchHold(\n onTouch: ((x: number, y: number, action: SpeculosAction) => void) | undefined,\n): TouchHold {\n const onAction = useMemo(\n () =>\n onTouch &&\n ((point: ScreenPoint, action: SpeculosAction) =>\n onTouch(point.x, point.y, action)),\n [onTouch],\n );\n return useHold(onAction);\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAS,WAAAA,MAAe,QAGxB,OAAoB,WAAAC,MAAe,YAgB5B,MAAMC,EAAgB,CAC3BC,EACAC,EACAC,EACAC,IACuB,CACvB,GAAI,CAACD,EAAM,OAAS,CAACA,EAAM,QAAU,CAACC,EAAO,OAAS,CAACA,EAAO,OAC5D,OAAO,KAGT,MAAMC,EAAQ,CAACC,EAAeC,IAC5B,KAAK,IAAI,KAAK,IAAI,KAAK,MAAMD,CAAK,EAAG,CAAC,EAAGC,EAAO,CAAC,EACnD,MAAO,CACL,EAAGF,EAAOJ,EAAUE,EAAM,MAASC,EAAO,MAAOA,EAAO,KAAK,EAC7D,EAAGC,EAAOH,EAAUC,EAAM,OAAUC,EAAO,OAAQA,EAAO,MAAM,CAClE,CACF,EASO,SAASI,EACdC,EACW,CACX,MAAMC,EAAWZ,EACf,IACEW,IACC,CAACE,EAAoBC,IACpBH,EAAQE,EAAM,EAAGA,EAAM,EAAGC,CAAM,GACpC,CAACH,CAAO,CACV,EACA,OAAOV,EAAQW,CAAQ,CACzB",
|
|
6
|
+
"names": ["useMemo", "useHold", "toScreenPoint", "offsetX", "offsetY", "drawn", "screen", "clamp", "value", "size", "useTouchHold", "onTouch", "onAction", "point", "action"]
|
|
7
|
+
}
|
|
@@ -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
|
+
export interface DeviceScreenModel {
|
|
2
|
+
readonly label: string;
|
|
3
|
+
readonly touch: boolean;
|
|
4
|
+
/** Whether Speculos has an emulator for it, so it can have a screen. */
|
|
5
|
+
readonly speculos: boolean;
|
|
6
|
+
/** In device pixels. */
|
|
7
|
+
readonly screenWidth: number;
|
|
8
|
+
readonly screenHeight: number;
|
|
9
|
+
readonly lightScreen: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const findDeviceScreenModel: (deviceType: string) => DeviceScreenModel;
|
|
12
|
+
//# sourceMappingURL=deviceModels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deviceModels.d.ts","sourceRoot":"","sources":["../../../src/deviceModels.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,wBAAwB;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AA4CD,eAAO,MAAM,qBAAqB,GAAI,YAAY,MAAM,KAAG,iBAC5B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { type DeviceScreenModel, findDeviceScreenModel } from "./deviceModels";
|
|
2
|
+
export { mockServerScreenApi } from "./mockServerScreenApi";
|
|
3
|
+
export type { DeviceScreenInput, DeviceScreenState, MockServerClient, ScreenApi, ScreenPlatform, } from "./types";
|
|
4
|
+
export { type ButtonHold, useButtonHold } from "./useButtonHold";
|
|
5
|
+
export { type Hold, HOLD_THRESHOLD_MS, useHold } from "./useHold";
|
|
6
|
+
export { useMockServerDevice } from "./useMockServerDevice";
|
|
7
|
+
export { appIconUrl, OPEN_APP_TIMEOUT_MS, openableApps, type OpenApp, useOpenApp, } from "./useOpenApp";
|
|
8
|
+
export { useScreenPolling } from "./useScreenPolling";
|
|
9
|
+
export { type ScreenPoint, type Size, toScreenPoint, type TouchHold, useTouchHold, } from "./useTouchHold";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,KAAK,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,KAAK,OAAO,EACZ,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,IAAI,EACT,aAAa,EACb,KAAK,SAAS,EACd,YAAY,GACb,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mockServerScreenApi.d.ts","sourceRoot":"","sources":["../../../src/mockServerScreenApi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAMhE,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,gBAAgB,EACxB,UAAU,MAAM,KACf,SA8BD,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type Device, type MockClient, type SpeculosAction, type SpeculosButton } from "@ledgerhq/device-mockserver-client";
|
|
2
|
+
/**
|
|
3
|
+
* The MockClient calls this library makes. Picking them drops the class's
|
|
4
|
+
* private fields, so a client from another copy of the package still fits.
|
|
5
|
+
*/
|
|
6
|
+
export type MockServerClient = Pick<MockClient, "getDevice" | "getScreenshot" | "pressButton" | "touchScreen" | "sendApdu">;
|
|
7
|
+
/**
|
|
8
|
+
* How the screen is reached. The mock server proxies to the Speculos instance
|
|
9
|
+
* backing a device and a Speculos transport talks to one directly, but both
|
|
10
|
+
* answer the same calls.
|
|
11
|
+
*/
|
|
12
|
+
export interface ScreenApi {
|
|
13
|
+
/** Resolves null when there is no screen to capture right now. */
|
|
14
|
+
screenshot(): Promise<Blob | null>;
|
|
15
|
+
/** What to show while `screenshot` keeps resolving null. */
|
|
16
|
+
idle?(): Promise<DeviceScreenState>;
|
|
17
|
+
pressButton(button: SpeculosButton, action: SpeculosAction): Promise<void>;
|
|
18
|
+
/** Coordinates in device screen pixels. */
|
|
19
|
+
touch(x: number, y: number, action: SpeculosAction): Promise<void>;
|
|
20
|
+
/** Omitted by apis whose device always runs an app. */
|
|
21
|
+
openApp?(appName: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Press and release are separate so a hold reaches the device as a hold: Stax
|
|
25
|
+
* and Flex gate their confirmations behind one.
|
|
26
|
+
*/
|
|
27
|
+
export interface DeviceScreenInput {
|
|
28
|
+
pressButton(button: SpeculosButton, action: SpeculosAction): void;
|
|
29
|
+
touch(x: number, y: number, action: SpeculosAction): void;
|
|
30
|
+
}
|
|
31
|
+
export type DeviceScreenState = {
|
|
32
|
+
kind: "loading";
|
|
33
|
+
}
|
|
34
|
+
/** No app runs, so no Speculos instance: the device record stands in. */
|
|
35
|
+
| {
|
|
36
|
+
kind: "os-info";
|
|
37
|
+
device: Device;
|
|
38
|
+
}
|
|
39
|
+
/** `src` is whatever the platform's `toImageSource` made of the frame. */
|
|
40
|
+
| {
|
|
41
|
+
kind: "image";
|
|
42
|
+
src: string;
|
|
43
|
+
input: DeviceScreenInput;
|
|
44
|
+
} | {
|
|
45
|
+
kind: "error";
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* What a screen needs from the platform drawing it. Polling restarts whenever
|
|
50
|
+
* this object changes, so hosts pass a module-level constant.
|
|
51
|
+
*/
|
|
52
|
+
export interface ScreenPlatform {
|
|
53
|
+
/** Turns a screenshot into a source the platform's image element shows. */
|
|
54
|
+
toImageSource(screenshot: Blob): Promise<string>;
|
|
55
|
+
/** Frees a source once a newer frame or no frame replaces it. */
|
|
56
|
+
releaseImageSource?(source: string): void;
|
|
57
|
+
/** Polling skips its turn while the screen is out of sight. */
|
|
58
|
+
isHidden(): boolean;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,oCAAoC,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,UAAU,EACV,WAAW,GAAG,eAAe,GAAG,aAAa,GAAG,aAAa,GAAG,UAAU,CAC3E,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,kEAAkE;IAClE,UAAU,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACnC,4DAA4D;IAC5D,IAAI,CAAC,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACpC,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,2CAA2C;IAC3C,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,uDAAuD;IACvD,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAClE,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;CAC3D;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE;AACrB,yEAAyE;GACvE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACrC,0EAA0E;GACxE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,2EAA2E;IAC3E,aAAa,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,iEAAiE;IACjE,kBAAkB,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,+DAA+D;IAC/D,QAAQ,IAAI,OAAO,CAAC;CACrB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type SpeculosAction, type SpeculosButton } from "@ledgerhq/device-mockserver-client";
|
|
2
|
+
import { type Hold } from "./useHold";
|
|
3
|
+
export type ButtonHold = Hold<SpeculosButton>;
|
|
4
|
+
/** A physical button's down and up, as the actions the device gets. */
|
|
5
|
+
export declare const useButtonHold: (onPress: (button: SpeculosButton, action: SpeculosAction) => void) => ButtonHold;
|
|
6
|
+
//# sourceMappingURL=useButtonHold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useButtonHold.d.ts","sourceRoot":"","sources":["../../../src/useButtonHold.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,KAAK,IAAI,EAAW,MAAM,WAAW,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;AAE9C,uEAAuE;AACvE,eAAO,MAAM,aAAa,GACxB,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,KAChE,UAA8B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type SpeculosAction } from "@ledgerhq/device-mockserver-client";
|
|
2
|
+
/**
|
|
3
|
+
* How long a button or finger stays down before the interaction counts as a
|
|
4
|
+
* hold. A click sent as a separate press and release is two requests the
|
|
5
|
+
* device can receive in either order, and the emulator only sees what is down
|
|
6
|
+
* when its event loop samples it, so a click that beats the loop is dropped:
|
|
7
|
+
* a quick tap, or a chord of both buttons. One press-and-release leaves the
|
|
8
|
+
* timing to the emulator.
|
|
9
|
+
*/
|
|
10
|
+
export declare const HOLD_THRESHOLD_MS = 150;
|
|
11
|
+
export interface Hold<T> {
|
|
12
|
+
/** Went down. False when ignored: no handler, or something is held. */
|
|
13
|
+
hold(target: T): boolean;
|
|
14
|
+
/** Went up. False when nothing was held. */
|
|
15
|
+
release(): boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Turns a button's or a finger's down and up into the actions the device
|
|
19
|
+
* gets: a quick click as one press-and-release, a longer hold as a press then
|
|
20
|
+
* a release. Whatever is still pressed on unmount is released.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useHold<T>(onAction: ((target: T, action: SpeculosAction) => void) | undefined): Hold<T>;
|
|
23
|
+
//# sourceMappingURL=useHold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useHold.d.ts","sourceRoot":"","sources":["../../../src/useHold.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAOrC,MAAM,WAAW,IAAI,CAAC,CAAC;IACrB,uEAAuE;IACvE,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC;IACzB,4CAA4C;IAC5C,OAAO,IAAI,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACvB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC,GAAG,SAAS,GAClE,IAAI,CAAC,CAAC,CAAC,CA8CT"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Device } from "@ledgerhq/device-mockserver-client";
|
|
2
|
+
import { type MockServerClient } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* The mock server's record of a device: undefined while it loads, or while no
|
|
5
|
+
* device id is given, and null while it cannot be read. A failed read is
|
|
6
|
+
* retried, so a transient error does not leave the device without its model.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useMockServerDevice(client: MockServerClient, deviceId: string): Device | null | undefined;
|
|
9
|
+
//# sourceMappingURL=useMockServerDevice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMockServerDevice.d.ts","sourceRoot":"","sources":["../../../src/useMockServerDevice.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAEjE,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAIhD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,GAAG,SAAS,CA8B3B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Device } from "@ledgerhq/device-mockserver-client";
|
|
2
|
+
export declare const OPEN_APP_TIMEOUT_MS = 10000;
|
|
3
|
+
/** The apps a device lists that the user can open. */
|
|
4
|
+
export declare const openableApps: (device: Device) => NonNullable<Device["apps"]>;
|
|
5
|
+
/** Ledger Live's app icons, keyed by the app's Manager API icon name. */
|
|
6
|
+
export declare const appIconUrl: (appName: string) => string;
|
|
7
|
+
export interface OpenApp {
|
|
8
|
+
/**
|
|
9
|
+
* The app being opened. Kept on success, until the live screen replaces the
|
|
10
|
+
* app list or the wait times out.
|
|
11
|
+
*/
|
|
12
|
+
opening: string | undefined;
|
|
13
|
+
error: string | undefined;
|
|
14
|
+
/** Ignored without a handler, or while another app is opening. */
|
|
15
|
+
open(appName: string): void;
|
|
16
|
+
}
|
|
17
|
+
/** Omitted, the apps are listed but cannot be opened. */
|
|
18
|
+
export declare function useOpenApp(onOpenApp: ((appName: string) => Promise<void>) | undefined): OpenApp;
|
|
19
|
+
//# sourceMappingURL=useOpenApp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOpenApp.d.ts","sourceRoot":"","sources":["../../../src/useOpenApp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAKjE,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C,sDAAsD;AACtD,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,KAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CACR,CAAC;AAEjE,yEAAyE;AACzE,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,MAC0C,CAAC;AAExF,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,kEAAkE;IAClE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,yDAAyD;AACzD,wBAAgB,UAAU,CACxB,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,GAC1D,OAAO,CA+BT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScreenPolling.d.ts","sourceRoot":"","sources":["../../../src/useScreenPolling.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAajB,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,iBAAiB,CAmHnB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type SpeculosAction } from "@ledgerhq/device-mockserver-client";
|
|
2
|
+
import { type Hold } from "./useHold";
|
|
3
|
+
export interface Size {
|
|
4
|
+
readonly width: number;
|
|
5
|
+
readonly height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ScreenPoint {
|
|
8
|
+
readonly x: number;
|
|
9
|
+
readonly y: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Where an offset into the screen as drawn lands in device pixels, or null
|
|
13
|
+
* while either size is unknown.
|
|
14
|
+
*/
|
|
15
|
+
export declare const toScreenPoint: (offsetX: number, offsetY: number, drawn: Size, screen: Size) => ScreenPoint | null;
|
|
16
|
+
export type TouchHold = Hold<ScreenPoint>;
|
|
17
|
+
/**
|
|
18
|
+
* A finger's down and up on the screen, as the actions the device gets: a
|
|
19
|
+
* quick tap as one press-and-release, a hold as a press then a release on the
|
|
20
|
+
* spot where it went down.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useTouchHold(onTouch: ((x: number, y: number, action: SpeculosAction) => void) | undefined): TouchHold;
|
|
23
|
+
//# sourceMappingURL=useTouchHold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTouchHold.d.ts","sourceRoot":"","sources":["../../../src/useTouchHold.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,OAAO,EAAE,KAAK,IAAI,EAAW,MAAM,WAAW,CAAC;AAE/C,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,SAAS,MAAM,EACf,SAAS,MAAM,EACf,OAAO,IAAI,EACX,QAAQ,IAAI,KACX,WAAW,GAAG,IAWhB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;AAE1C;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC,GAAG,SAAS,GAC5E,SAAS,CASX"}
|