@ledgerhq/device-management-kit-devtools-ui 0.0.0-develop-20260629003450 → 0.0.0-develop-20260701003433
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/lib/build/src/Dashboard.d.ts.map +1 -1
- package/lib/build/src/Dashboard.js +5 -1
- package/lib/build/src/Dashboard.js.map +1 -1
- package/lib/build/src/components/DashboardNavigationBar.d.ts +1 -0
- package/lib/build/src/components/DashboardNavigationBar.d.ts.map +1 -1
- package/lib/build/src/components/DashboardNavigationBar.js +2 -1
- package/lib/build/src/components/DashboardNavigationBar.js.map +1 -1
- package/lib/build/src/hooks/connectorMessageHandlers.d.ts +2 -2
- package/lib/build/src/hooks/connectorMessageHandlers.d.ts.map +1 -1
- package/lib/build/src/hooks/connectorMessageHandlers.js +30 -2
- package/lib/build/src/hooks/connectorMessageHandlers.js.map +1 -1
- package/lib/build/src/hooks/useConnectorMessages.d.ts +15 -0
- package/lib/build/src/hooks/useConnectorMessages.d.ts.map +1 -1
- package/lib/build/src/hooks/useConnectorMessages.js +23 -5
- package/lib/build/src/hooks/useConnectorMessages.js.map +1 -1
- package/lib/build/src/screens/logger/mapConnectorMessageToLogData.d.ts +1 -4
- package/lib/build/src/screens/logger/mapConnectorMessageToLogData.d.ts.map +1 -1
- package/lib/build/src/screens/logger/mapConnectorMessageToLogData.js +2 -6
- package/lib/build/src/screens/logger/mapConnectorMessageToLogData.js.map +1 -1
- package/lib/build/src/screens/mock-capture/deviceConfigFromSession.d.ts +16 -0
- package/lib/build/src/screens/mock-capture/deviceConfigFromSession.d.ts.map +1 -0
- package/lib/build/src/screens/mock-capture/deviceConfigFromSession.js +52 -0
- package/lib/build/src/screens/mock-capture/deviceConfigFromSession.js.map +1 -0
- package/lib/build/src/screens/mock-capture/generateSessionExport.d.ts +36 -0
- package/lib/build/src/screens/mock-capture/generateSessionExport.d.ts.map +1 -0
- package/lib/build/src/screens/mock-capture/generateSessionExport.js +79 -0
- package/lib/build/src/screens/mock-capture/generateSessionExport.js.map +1 -0
- package/lib/build/src/screens/mock-capture/generateSessionExport.test.d.ts +2 -0
- package/lib/build/src/screens/mock-capture/generateSessionExport.test.d.ts.map +1 -0
- package/lib/build/src/screens/mock-capture/generateSessionExport.test.js +125 -0
- package/lib/build/src/screens/mock-capture/generateSessionExport.test.js.map +1 -0
- package/lib/build/src/screens/mock-capture/index.d.ts +23 -0
- package/lib/build/src/screens/mock-capture/index.d.ts.map +1 -0
- package/lib/build/src/screens/mock-capture/index.js +281 -0
- package/lib/build/src/screens/mock-capture/index.js.map +1 -0
- package/lib/build/tsconfig.build.tsbuildinfo +1 -1
- package/lib/cjs/package.json +2 -1
- package/lib/cjs/src/Dashboard.js +3 -3
- package/lib/cjs/src/Dashboard.js.map +3 -3
- package/lib/cjs/src/components/DashboardNavigationBar.js +8 -8
- package/lib/cjs/src/components/DashboardNavigationBar.js.map +2 -2
- package/lib/cjs/src/hooks/connectorMessageHandlers.js +1 -1
- package/lib/cjs/src/hooks/connectorMessageHandlers.js.map +3 -3
- package/lib/cjs/src/hooks/useConnectorMessages.js +1 -1
- package/lib/cjs/src/hooks/useConnectorMessages.js.map +3 -3
- package/lib/cjs/src/screens/logger/mapConnectorMessageToLogData.js +1 -1
- package/lib/cjs/src/screens/logger/mapConnectorMessageToLogData.js.map +3 -3
- package/lib/cjs/src/screens/mock-capture/deviceConfigFromSession.js +2 -0
- package/lib/cjs/src/screens/mock-capture/deviceConfigFromSession.js.map +7 -0
- package/lib/cjs/src/screens/mock-capture/generateSessionExport.js +2 -0
- package/lib/cjs/src/screens/mock-capture/generateSessionExport.js.map +7 -0
- package/lib/cjs/src/screens/mock-capture/generateSessionExport.test.js +2 -0
- package/lib/cjs/src/screens/mock-capture/generateSessionExport.test.js.map +7 -0
- package/lib/cjs/src/screens/mock-capture/index.js +109 -0
- package/lib/cjs/src/screens/mock-capture/index.js.map +7 -0
- package/lib/esm/package.json +2 -1
- package/lib/esm/src/Dashboard.js +3 -3
- package/lib/esm/src/Dashboard.js.map +3 -3
- package/lib/esm/src/components/DashboardNavigationBar.js +8 -8
- package/lib/esm/src/components/DashboardNavigationBar.js.map +2 -2
- package/lib/esm/src/hooks/connectorMessageHandlers.js +1 -1
- package/lib/esm/src/hooks/connectorMessageHandlers.js.map +3 -3
- package/lib/esm/src/hooks/useConnectorMessages.js +1 -1
- package/lib/esm/src/hooks/useConnectorMessages.js.map +3 -3
- package/lib/esm/src/screens/logger/mapConnectorMessageToLogData.js +1 -1
- package/lib/esm/src/screens/logger/mapConnectorMessageToLogData.js.map +3 -3
- package/lib/esm/src/screens/mock-capture/deviceConfigFromSession.js +2 -0
- package/lib/esm/src/screens/mock-capture/deviceConfigFromSession.js.map +7 -0
- package/lib/esm/src/screens/mock-capture/generateSessionExport.js +2 -0
- package/lib/esm/src/screens/mock-capture/generateSessionExport.js.map +7 -0
- package/lib/esm/src/screens/mock-capture/generateSessionExport.test.js +2 -0
- package/lib/esm/src/screens/mock-capture/generateSessionExport.test.js.map +7 -0
- package/lib/esm/src/screens/mock-capture/index.js +109 -0
- package/lib/esm/src/screens/mock-capture/index.js.map +7 -0
- package/package.json +10 -9
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/screens/mock-capture/generateSessionExport.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n type DeviceConfig,\n type MockConfig,\n type SessionExport,\n} from \"@ledgerhq/device-mockserver-client\";\n\nimport { type ApduExchange } from \"../../hooks/useConnectorMessages\";\n\n/**\n * APDU prefixes the mock server synthesizes from device metadata when no\n * explicit mock matches (GetOsVersion, GetAppAndVersion). They are excluded by\n * default so the generated session relies on the device config for handshakes.\n */\nconst HANDSHAKE_PREFIXES = [\"e0010000\", \"b0010000\"];\n\nexport type GenerateSessionExportOptions = {\n /** Include the handshake APDUs (GetOsVersion / GetAppAndVersion) as mocks. */\n includeHandshake?: boolean;\n};\n\nfunction isHandshake(prefix: string): boolean {\n return HANDSHAKE_PREFIXES.some((handshake) => prefix.startsWith(handshake));\n}\n\n/**\n * Collapses a list of responses to a single entry when they are all identical,\n * keeping the ordered sequence otherwise (the mock server serves responses in\n * order and loops once exhausted).\n */\nfunction dedupeResponses(responses: string[]): string[] {\n const allIdentical = responses.every((r) => r === responses[0]);\n return allIdentical ? responses.slice(0, 1) : responses;\n}\n\nfunction buildMocks(\n exchanges: ApduExchange[],\n includeHandshake: boolean,\n): MockConfig[] {\n const responsesByPrefix = new Map<string, string[]>();\n\n for (const exchange of exchanges) {\n const prefix = exchange.apdu.toLowerCase();\n if (!includeHandshake && isHandshake(prefix)) {\n continue;\n }\n const responses = responsesByPrefix.get(prefix) ?? [];\n responses.push(exchange.response.toLowerCase());\n responsesByPrefix.set(prefix, responses);\n }\n\n return [...responsesByPrefix.entries()].map(([prefix, responses]) => ({\n prefix,\n responses: dedupeResponses(responses),\n }));\n}\n\n/**\n * A device and the APDU exchanges captured from it, used to build one entry of\n * the session export.\n */\nexport type DeviceCaptureGroup = {\n device: Partial<DeviceConfig>;\n exchanges: ApduExchange[];\n};\n\n/**\n * Builds a single device config (metadata + derived mocks).\n */\nfunction buildDeviceConfig(\n device: Partial<DeviceConfig>,\n exchanges: ApduExchange[],\n includeHandshake: boolean,\n): Partial<DeviceConfig> {\n const mocks = buildMocks(exchanges, includeHandshake);\n const { name, ...rest } = device;\n return {\n ...(name !== undefined ? { name } : {}),\n ...rest,\n mocks,\n };\n}\n\n/**\n * Derives a mock-server session export from captured APDU exchanges.\n *\n * Each unique request APDU becomes a mock whose `prefix` is the full request\n * hex (so the server matches it exactly), and whose `responses` replay the\n * captured responses in order.\n *\n * @param exchanges Captured APDU exchanges.\n * @param device Device metadata to attach (name, device_type, firmware...).\n * @param options Generation options.\n */\nexport function generateSessionExport(\n exchanges: ApduExchange[],\n device: Partial<DeviceConfig> = {},\n options: GenerateSessionExportOptions = {},\n): SessionExport {\n return {\n devices: [\n buildDeviceConfig(device, exchanges, options.includeHandshake ?? false),\n ],\n };\n}\n\n/**\n * Derives a mock-server session export from several devices, each with its own\n * captured exchanges. Used when more than one device was active during the\n * recording so each gets a distinct entry instead of being merged together.\n *\n * @param groups One device + its exchanges per connected device.\n * @param options Generation options.\n */\nexport function generateMultiDeviceSessionExport(\n groups: DeviceCaptureGroup[],\n options: GenerateSessionExportOptions = {},\n): SessionExport {\n const includeHandshake = options.includeHandshake ?? false;\n return {\n devices: groups.map(({ device, exchanges }) =>\n buildDeviceConfig(device, exchanges, includeHandshake),\n ),\n };\n}\n"],
|
|
5
|
+
"mappings": "AAaA,MAAMA,EAAqB,CAAC,WAAY,UAAU,EAOlD,SAASC,EAAYC,EAAyB,CAC5C,OAAOF,EAAmB,KAAMG,GAAcD,EAAO,WAAWC,CAAS,CAAC,CAC5E,CAOA,SAASC,EAAgBC,EAA+B,CAEtD,OADqBA,EAAU,MAAOC,GAAMA,IAAMD,EAAU,CAAC,CAAC,EACxCA,EAAU,MAAM,EAAG,CAAC,EAAIA,CAChD,CAEA,SAASE,EACPC,EACAC,EACc,CACd,MAAMC,EAAoB,IAAI,IAE9B,UAAWC,KAAYH,EAAW,CAChC,MAAMN,EAASS,EAAS,KAAK,YAAY,EACzC,GAAI,CAACF,GAAoBR,EAAYC,CAAM,EACzC,SAEF,MAAMG,EAAYK,EAAkB,IAAIR,CAAM,GAAK,CAAC,EACpDG,EAAU,KAAKM,EAAS,SAAS,YAAY,CAAC,EAC9CD,EAAkB,IAAIR,EAAQG,CAAS,CACzC,CAEA,MAAO,CAAC,GAAGK,EAAkB,QAAQ,CAAC,EAAE,IAAI,CAAC,CAACR,EAAQG,CAAS,KAAO,CACpE,OAAAH,EACA,UAAWE,EAAgBC,CAAS,CACtC,EAAE,CACJ,CAcA,SAASO,EACPC,EACAL,EACAC,EACuB,CACvB,MAAMK,EAAQP,EAAWC,EAAWC,CAAgB,EAC9C,CAAE,KAAAM,EAAM,GAAGC,CAAK,EAAIH,EAC1B,MAAO,CACL,GAAIE,IAAS,OAAY,CAAE,KAAAA,CAAK,EAAI,CAAC,EACrC,GAAGC,EACH,MAAAF,CACF,CACF,CAaO,SAASG,EACdT,EACAK,EAAgC,CAAC,EACjCK,EAAwC,CAAC,EAC1B,CACf,MAAO,CACL,QAAS,CACPN,EAAkBC,EAAQL,EAAWU,EAAQ,kBAAoB,EAAK,CACxE,CACF,CACF,CAUO,SAASC,EACdC,EACAF,EAAwC,CAAC,EAC1B,CACf,MAAMT,EAAmBS,EAAQ,kBAAoB,GACrD,MAAO,CACL,QAASE,EAAO,IAAI,CAAC,CAAE,OAAAP,EAAQ,UAAAL,CAAU,IACvCI,EAAkBC,EAAQL,EAAWC,CAAgB,CACvD,CACF,CACF",
|
|
6
|
+
"names": ["HANDSHAKE_PREFIXES", "isHandshake", "prefix", "handshake", "dedupeResponses", "responses", "r", "buildMocks", "exchanges", "includeHandshake", "responsesByPrefix", "exchange", "buildDeviceConfig", "device", "mocks", "name", "rest", "generateSessionExport", "options", "generateMultiDeviceSessionExport", "groups"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{describe as i,expect as a,it as t}from"vitest";import{generateMultiDeviceSessionExport as o,generateSessionExport as c}from"./generateSessionExport";const s=(e,n)=>({apdu:e,response:n,sessionId:"session-1",timestamp:"2026-06-29T00:00:00.000Z"});i("generateSessionExport",()=>{t("creates one mock per unique APDU with the full APDU as prefix",()=>{const e=c([s("e004000005","deadbeef9000"),s("e006000000","9000")]);a(e.devices).toHaveLength(1),a(e.devices[0]?.mocks).toEqual([{prefix:"e004000005",responses:["deadbeef9000"]},{prefix:"e006000000",responses:["9000"]}])}),t("accumulates differing responses for the same APDU in order",()=>{const e=c([s("e004000000","aa9000"),s("e004000000","6985"),s("e004000000","bb9000")]);a(e.devices[0]?.mocks).toEqual([{prefix:"e004000000",responses:["aa9000","6985","bb9000"]}])}),t("collapses identical repeated responses to a single entry",()=>{const e=c([s("e004000000","aa9000"),s("e004000000","aa9000")]);a(e.devices[0]?.mocks).toEqual([{prefix:"e004000000",responses:["aa9000"]}])}),t("lowercases prefixes and responses",()=>{const e=c([s("E004000000","AA9000")]);a(e.devices[0]?.mocks).toEqual([{prefix:"e004000000",responses:["aa9000"]}])}),t("skips handshake APDUs by default",()=>{const e=c([s("e001000000","33000004deadbeef9000"),s("b001000000","0105424f4c4f539000"),s("e004000000","aa9000")]);a(e.devices[0]?.mocks).toEqual([{prefix:"e004000000",responses:["aa9000"]}])}),t("includes handshake APDUs when requested",()=>{const e=c([s("e001000000","33000004deadbeef9000"),s("e004000000","aa9000")],{},{includeHandshake:!0});a(e.devices[0]?.mocks).toHaveLength(2)}),t("attaches the provided device metadata",()=>{const e=c([s("e004000000","aa9000")],{name:"Captured Device",device_type:"nanoX",firmware_version:"2.2.3"});a(e.devices[0]).toMatchObject({name:"Captured Device",device_type:"nanoX",firmware_version:"2.2.3"})}),t("places name as the first key of the device",()=>{const e=c([s("e004000000","aa9000")],{device_type:"nanoX",firmware_version:"2.2.3",name:"Captured Device"});a(Object.keys(e.devices[0])[0]).toBe("name")}),t("produces an empty mocks list when there are no exchanges",()=>{const e=c([]);a(e.devices[0]?.mocks).toEqual([])})});i("generateMultiDeviceSessionExport",()=>{t("keeps one device entry per group with its own mocks",()=>{const e=o([{device:{name:"Device A",device_type:"flex"},exchanges:[s("e004000000","aa9000")]},{device:{name:"Device B",device_type:"stax"},exchanges:[s("e006000000","bb9000")]}]);a(e.devices).toHaveLength(2),a(e.devices[0]).toMatchObject({name:"Device A",device_type:"flex",mocks:[{prefix:"e004000000",responses:["aa9000"]}]}),a(e.devices[1]).toMatchObject({name:"Device B",device_type:"stax",mocks:[{prefix:"e006000000",responses:["bb9000"]}]})}),t("does not merge mocks across devices",()=>{const e=o([{device:{name:"A"},exchanges:[s("e004000000","aa9000")]},{device:{name:"B"},exchanges:[s("e004000000","bb9000")]}]);a(e.devices[0]?.mocks).toEqual([{prefix:"e004000000",responses:["aa9000"]}]),a(e.devices[1]?.mocks).toEqual([{prefix:"e004000000",responses:["bb9000"]}])})});
|
|
2
|
+
//# sourceMappingURL=generateSessionExport.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/screens/mock-capture/generateSessionExport.test.ts"],
|
|
4
|
+
"sourcesContent": ["import { describe, expect, it } from \"vitest\";\n\nimport { type ApduExchange } from \"../../hooks/useConnectorMessages\";\nimport {\n generateMultiDeviceSessionExport,\n generateSessionExport,\n} from \"./generateSessionExport\";\n\nconst exchange = (apdu: string, response: string): ApduExchange => ({\n apdu,\n response,\n sessionId: \"session-1\",\n timestamp: \"2026-06-29T00:00:00.000Z\",\n});\n\ndescribe(\"generateSessionExport\", () => {\n it(\"creates one mock per unique APDU with the full APDU as prefix\", () => {\n const result = generateSessionExport([\n exchange(\"e004000005\", \"deadbeef9000\"),\n exchange(\"e006000000\", \"9000\"),\n ]);\n\n expect(result.devices).toHaveLength(1);\n expect(result.devices[0]?.mocks).toEqual([\n { prefix: \"e004000005\", responses: [\"deadbeef9000\"] },\n { prefix: \"e006000000\", responses: [\"9000\"] },\n ]);\n });\n\n it(\"accumulates differing responses for the same APDU in order\", () => {\n const result = generateSessionExport([\n exchange(\"e004000000\", \"aa9000\"),\n exchange(\"e004000000\", \"6985\"),\n exchange(\"e004000000\", \"bb9000\"),\n ]);\n\n expect(result.devices[0]?.mocks).toEqual([\n { prefix: \"e004000000\", responses: [\"aa9000\", \"6985\", \"bb9000\"] },\n ]);\n });\n\n it(\"collapses identical repeated responses to a single entry\", () => {\n const result = generateSessionExport([\n exchange(\"e004000000\", \"aa9000\"),\n exchange(\"e004000000\", \"aa9000\"),\n ]);\n\n expect(result.devices[0]?.mocks).toEqual([\n { prefix: \"e004000000\", responses: [\"aa9000\"] },\n ]);\n });\n\n it(\"lowercases prefixes and responses\", () => {\n const result = generateSessionExport([exchange(\"E004000000\", \"AA9000\")]);\n\n expect(result.devices[0]?.mocks).toEqual([\n { prefix: \"e004000000\", responses: [\"aa9000\"] },\n ]);\n });\n\n it(\"skips handshake APDUs by default\", () => {\n const result = generateSessionExport([\n exchange(\"e001000000\", \"33000004deadbeef9000\"),\n exchange(\"b001000000\", \"0105424f4c4f539000\"),\n exchange(\"e004000000\", \"aa9000\"),\n ]);\n\n expect(result.devices[0]?.mocks).toEqual([\n { prefix: \"e004000000\", responses: [\"aa9000\"] },\n ]);\n });\n\n it(\"includes handshake APDUs when requested\", () => {\n const result = generateSessionExport(\n [\n exchange(\"e001000000\", \"33000004deadbeef9000\"),\n exchange(\"e004000000\", \"aa9000\"),\n ],\n {},\n { includeHandshake: true },\n );\n\n expect(result.devices[0]?.mocks).toHaveLength(2);\n });\n\n it(\"attaches the provided device metadata\", () => {\n const result = generateSessionExport([exchange(\"e004000000\", \"aa9000\")], {\n name: \"Captured Device\",\n device_type: \"nanoX\",\n firmware_version: \"2.2.3\",\n });\n\n expect(result.devices[0]).toMatchObject({\n name: \"Captured Device\",\n device_type: \"nanoX\",\n firmware_version: \"2.2.3\",\n });\n });\n\n it(\"places name as the first key of the device\", () => {\n const result = generateSessionExport([exchange(\"e004000000\", \"aa9000\")], {\n device_type: \"nanoX\",\n firmware_version: \"2.2.3\",\n name: \"Captured Device\",\n });\n\n expect(Object.keys(result.devices[0]!)[0]).toBe(\"name\");\n });\n\n it(\"produces an empty mocks list when there are no exchanges\", () => {\n const result = generateSessionExport([]);\n\n expect(result.devices[0]?.mocks).toEqual([]);\n });\n});\n\ndescribe(\"generateMultiDeviceSessionExport\", () => {\n it(\"keeps one device entry per group with its own mocks\", () => {\n const result = generateMultiDeviceSessionExport([\n {\n device: { name: \"Device A\", device_type: \"flex\" },\n exchanges: [exchange(\"e004000000\", \"aa9000\")],\n },\n {\n device: { name: \"Device B\", device_type: \"stax\" },\n exchanges: [exchange(\"e006000000\", \"bb9000\")],\n },\n ]);\n\n expect(result.devices).toHaveLength(2);\n expect(result.devices[0]).toMatchObject({\n name: \"Device A\",\n device_type: \"flex\",\n mocks: [{ prefix: \"e004000000\", responses: [\"aa9000\"] }],\n });\n expect(result.devices[1]).toMatchObject({\n name: \"Device B\",\n device_type: \"stax\",\n mocks: [{ prefix: \"e006000000\", responses: [\"bb9000\"] }],\n });\n });\n\n it(\"does not merge mocks across devices\", () => {\n const result = generateMultiDeviceSessionExport([\n { device: { name: \"A\" }, exchanges: [exchange(\"e004000000\", \"aa9000\")] },\n { device: { name: \"B\" }, exchanges: [exchange(\"e004000000\", \"bb9000\")] },\n ]);\n\n expect(result.devices[0]?.mocks).toEqual([\n { prefix: \"e004000000\", responses: [\"aa9000\"] },\n ]);\n expect(result.devices[1]?.mocks).toEqual([\n { prefix: \"e004000000\", responses: [\"bb9000\"] },\n ]);\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["describe", "expect", "it", "generateMultiDeviceSessionExport", "generateSessionExport", "exchange", "apdu", "response", "result"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import{useCallback as E,useEffect as I,useMemo as b,useRef as ie,useState as d}from"react";import{MockClient as ae}from"@ledgerhq/device-mockserver-client";import i from"styled-components";import{NotConnectedMessage as ce}from"../../shared/NotConnectedMessage";import{deviceConfigFromSession as N}from"./deviceConfigFromSession";import{generateMultiDeviceSessionExport as de}from"./generateSessionExport";import{Fragment as he,jsx as n,jsxs as s}from"react/jsx-runtime";const pe="http://127.0.0.1:8080",le=`import { DevToolsLogger } from "@ledgerhq/device-management-kit-devtools-core";
|
|
2
|
+
|
|
3
|
+
const dmk = new DeviceManagementKitBuilder()
|
|
4
|
+
.addLogger(new DevToolsLogger(connector))
|
|
5
|
+
.build();`,ue=i.div`
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: 16px;
|
|
9
|
+
padding: 16px 24px;
|
|
10
|
+
overflow: auto;
|
|
11
|
+
height: 100%;
|
|
12
|
+
`,$=i.div`
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: 8px;
|
|
16
|
+
padding: 12px;
|
|
17
|
+
border: 1px solid #e0e0e0;
|
|
18
|
+
border-radius: 6px;
|
|
19
|
+
background: #fff;
|
|
20
|
+
`,A=i.div`
|
|
21
|
+
font-size: 13px;
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
color: #333;
|
|
24
|
+
`,k=i.div`
|
|
25
|
+
display: flex;
|
|
26
|
+
gap: 8px;
|
|
27
|
+
align-items: center;
|
|
28
|
+
flex-wrap: wrap;
|
|
29
|
+
`,V=i.label`
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
gap: 4px;
|
|
33
|
+
font-size: 12px;
|
|
34
|
+
color: #666;
|
|
35
|
+
flex: 1;
|
|
36
|
+
min-width: 140px;
|
|
37
|
+
`,K=i.input`
|
|
38
|
+
padding: 6px 8px;
|
|
39
|
+
border: 1px solid #ddd;
|
|
40
|
+
border-radius: 4px;
|
|
41
|
+
font-size: 12px;
|
|
42
|
+
|
|
43
|
+
&:focus {
|
|
44
|
+
outline: none;
|
|
45
|
+
border-color: #2196f3;
|
|
46
|
+
}
|
|
47
|
+
`,h=i.button`
|
|
48
|
+
padding: 6px 12px;
|
|
49
|
+
border: none;
|
|
50
|
+
border-radius: 4px;
|
|
51
|
+
color: white;
|
|
52
|
+
font-size: 12px;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
transition: all 0.15s ease;
|
|
55
|
+
background: ${({$variant:r})=>r==="danger"?"#e53935":r==="neutral"?"#757575":"#2196f3"};
|
|
56
|
+
|
|
57
|
+
&:hover:not(:disabled) {
|
|
58
|
+
opacity: 0.9;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&:disabled {
|
|
62
|
+
opacity: 0.5;
|
|
63
|
+
cursor: not-allowed;
|
|
64
|
+
}
|
|
65
|
+
`,j=i.span`
|
|
66
|
+
display: inline-block;
|
|
67
|
+
width: 10px;
|
|
68
|
+
height: 10px;
|
|
69
|
+
border-radius: 50%;
|
|
70
|
+
margin-right: 6px;
|
|
71
|
+
background: ${({$active:r})=>r?"#fff":"#bbb"};
|
|
72
|
+
`,ge=i.span`
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
color: #555;
|
|
75
|
+
`,fe=i.textarea`
|
|
76
|
+
margin: 0;
|
|
77
|
+
padding: 12px;
|
|
78
|
+
background: #1e1e1e;
|
|
79
|
+
color: #d4d4d4;
|
|
80
|
+
border: none;
|
|
81
|
+
border-radius: 4px;
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
font-family: monospace;
|
|
84
|
+
min-height: 240px;
|
|
85
|
+
max-height: 480px;
|
|
86
|
+
overflow: auto;
|
|
87
|
+
white-space: pre;
|
|
88
|
+
resize: vertical;
|
|
89
|
+
|
|
90
|
+
&:focus {
|
|
91
|
+
outline: none;
|
|
92
|
+
box-shadow: 0 0 0 1px #2196f3;
|
|
93
|
+
}
|
|
94
|
+
`,H=i.div`
|
|
95
|
+
font-size: 12px;
|
|
96
|
+
padding: 8px;
|
|
97
|
+
border-radius: 4px;
|
|
98
|
+
background: ${({$error:r})=>r?"#ffebee":"#e8f5e9"};
|
|
99
|
+
border: 1px solid ${({$error:r})=>r?"#ffcdd2":"#c8e6c9"};
|
|
100
|
+
color: ${({$error:r})=>r?"#c62828":"#2e7d32"};
|
|
101
|
+
`,me=i.label`
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
gap: 6px;
|
|
105
|
+
font-size: 12px;
|
|
106
|
+
color: #666;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
`,ye=({apduExchanges:r,isRecording:q,isConnected:X,startRecording:Q,stopRecording:W,clearRecorded:P,sessionStates:x,connectedDevices:L})=>{const[w,Y]=d(!1),[S,Z]=d(pe),[m,ee]=d(""),[O,T]=d(!1),[C,y]=d(null),[U,J]=d(new Map),[z,B]=d(new Map);I(()=>{for(const[e,o]of x.entries()){const t=N(o);if(t.apps&&t.apps.length>0){const c=t.apps;J(a=>{const l=a.get(e)??[],u=[...l];for(const g of c)u.some(R=>R.name===g.name&&R.version===g.version)||u.push(g);return u.length===l.length?a:new Map(a).set(e,u)})}if(t.firmware_version){const c=t.firmware_version;B(a=>a.get(e)?a:new Map(a).set(e,c))}}},[x]);const oe=E(()=>{P(),J(new Map),B(new Map)},[P]),F=b(()=>{const e=new Map;for(const o of r){const t=o.sessionId??"",c=e.get(t)??[];c.push(o),e.set(t,c)}return[...e.entries()].map(([o,t])=>{const c=o?x.get(o):void 0,a=o?L.find(se=>se.sessionId===o)?.type:void 0,l=N(c,a),u=z.get(o),g=U.get(o);return{device:{...l,name:l.name||"Mocked device",...u?{firmware_version:u}:{},...g&&g.length>0?{apps:g}:{}},exchanges:t}})},[r,x,L,U,z]),M=b(()=>de(F,{includeHandshake:w}),[F,w]),te=M.devices.reduce((e,o)=>e+(o.mocks?.length??0),0),f=b(()=>JSON.stringify(M,null,2),[M]),[p,_]=d(f),G=ie(f);I(()=>{f!==G.current&&(G.current=f,_(f))},[f]);const v=b(()=>{try{return JSON.parse(p),null}catch(e){return e instanceof Error?e.message:"Invalid JSON"}},[p]),ne=E(()=>{const e=new Blob([p],{type:"application/json"}),o=URL.createObjectURL(e),t=document.createElement("a");t.href=o,t.download=`mock-session-${new Date().toISOString()}.json`,t.click(),URL.revokeObjectURL(o)},[p]),re=E(async()=>{T(!0),y(null);try{const e=JSON.parse(p),o=new ae(S,m?{token:m}:{});m||await o.authenticate();const t=await o.importSession(e),c=t.devices.reduce((a,l)=>a+(l.mocks?.length??0),0);y({error:!1,message:`Imported ${t.devices.length} device(s) with ${c} mock(s) to the mock server.`})}catch(e){y({error:!0,message:e instanceof Error?e.message:"Failed to push session"})}finally{T(!1)}},[S,m,p]);if(!X)return n(ce,{title:"Logger not connected",description:s(he,{children:["Mock capture reads APDU exchanges from the DMK logs. Add"," ",n("code",{children:"DevToolsLogger"})," to your DMK builder to start capturing:"]}),codeExample:le});const D=r.length>0;return s(ue,{children:[s($,{children:[n(A,{children:"Recording"}),s(k,{children:[q?s(h,{$variant:"danger",onClick:W,children:[n(j,{$active:!0}),"Stop"]}):s(h,{$variant:"primary",onClick:Q,children:[n(j,{$active:!1}),"Record"]}),n(h,{$variant:"neutral",onClick:oe,disabled:!D,children:"Clear"}),s(ge,{children:[r.length," exchange(s) captured, ",te," mock(s) derived"]})]}),s(me,{children:[n("input",{type:"checkbox",checked:w,onChange:e=>Y(e.target.checked)}),"Include handshake APDUs (GetOsVersion / GetAppAndVersion)"]})]}),s($,{children:[n(A,{children:"Generated session export"}),n(k,{children:n(h,{onClick:ne,disabled:!D||!!v,children:"Download JSON"})}),n(fe,{value:p,spellCheck:!1,onChange:e=>_(e.target.value)}),v&&s(H,{$error:!0,children:["Invalid JSON: ",v]})]}),s($,{children:[n(A,{children:"Push to mock server"}),s(k,{children:[s(V,{children:["Server URL",n(K,{value:S,onChange:e=>Z(e.target.value)})]}),s(V,{children:["Session token",n(K,{value:m,placeholder:"auto-provisioned if empty",onChange:e=>ee(e.target.value)})]})]}),n(k,{children:n(h,{onClick:re,disabled:!D||O||!!v,children:O?"Pushing...":"Push to mock server"})}),C&&n(H,{$error:C.error,children:C.message})]})]})};export{ye as MockCapture};
|
|
109
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/screens/mock-capture/index.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * @file Mock Capture screen\n *\n * Records APDU exchanges captured passively from the host app DMK logs,\n * derives mock-server mocks from them, and lets the user download the\n * resulting session export or push it directly to a running mock server.\n */\n\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n type ConnectedDevice,\n type DeviceSessionState,\n} from \"@ledgerhq/device-management-kit\";\nimport {\n type DeviceApp,\n MockClient,\n type SessionExport,\n} from \"@ledgerhq/device-mockserver-client\";\nimport styled from \"styled-components\";\n\nimport { type ApduExchange } from \"../../hooks/useConnectorMessages\";\nimport { NotConnectedMessage } from \"../../shared/NotConnectedMessage\";\nimport { deviceConfigFromSession } from \"./deviceConfigFromSession\";\nimport {\n type DeviceCaptureGroup,\n generateMultiDeviceSessionExport,\n} from \"./generateSessionExport\";\n\nconst DEFAULT_MOCK_SERVER_URL = \"http://127.0.0.1:8080\";\n\nconst MOCK_CAPTURE_CODE_EXAMPLE = `import { DevToolsLogger } from \"@ledgerhq/device-management-kit-devtools-core\";\n\nconst dmk = new DeviceManagementKitBuilder()\n .addLogger(new DevToolsLogger(connector))\n .build();`;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 16px 24px;\n overflow: auto;\n height: 100%;\n`;\n\nconst Section = styled.div`\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n background: #fff;\n`;\n\nconst SectionTitle = styled.div`\n font-size: 13px;\n font-weight: 600;\n color: #333;\n`;\n\nconst Row = styled.div`\n display: flex;\n gap: 8px;\n align-items: center;\n flex-wrap: wrap;\n`;\n\nconst Field = styled.label`\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n color: #666;\n flex: 1;\n min-width: 140px;\n`;\n\nconst Input = styled.input`\n padding: 6px 8px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 12px;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n }\n`;\n\nconst Button = styled.button<{ $variant?: \"primary\" | \"danger\" | \"neutral\" }>`\n padding: 6px 12px;\n border: none;\n border-radius: 4px;\n color: white;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.15s ease;\n background: ${({ $variant }) =>\n $variant === \"danger\"\n ? \"#e53935\"\n : $variant === \"neutral\"\n ? \"#757575\"\n : \"#2196f3\"};\n\n &:hover:not(:disabled) {\n opacity: 0.9;\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n`;\n\nconst RecordingDot = styled.span<{ $active: boolean }>`\n display: inline-block;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n margin-right: 6px;\n background: ${({ $active }) => ($active ? \"#fff\" : \"#bbb\")};\n`;\n\nconst Stat = styled.span`\n font-size: 12px;\n color: #555;\n`;\n\nconst JsonEditor = styled.textarea`\n margin: 0;\n padding: 12px;\n background: #1e1e1e;\n color: #d4d4d4;\n border: none;\n border-radius: 4px;\n font-size: 12px;\n font-family: monospace;\n min-height: 240px;\n max-height: 480px;\n overflow: auto;\n white-space: pre;\n resize: vertical;\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 1px #2196f3;\n }\n`;\n\nconst StatusMessage = styled.div<{ $error: boolean }>`\n font-size: 12px;\n padding: 8px;\n border-radius: 4px;\n background: ${({ $error }) => ($error ? \"#ffebee\" : \"#e8f5e9\")};\n border: 1px solid ${({ $error }) => ($error ? \"#ffcdd2\" : \"#c8e6c9\")};\n color: ${({ $error }) => ($error ? \"#c62828\" : \"#2e7d32\")};\n`;\n\nconst Checkbox = styled.label`\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: #666;\n cursor: pointer;\n`;\n\ntype MockCaptureProps = {\n apduExchanges: ApduExchange[];\n isRecording: boolean;\n isConnected: boolean;\n startRecording: () => void;\n stopRecording: () => void;\n clearRecorded: () => void;\n sessionStates: Map<string, DeviceSessionState>;\n connectedDevices: ConnectedDevice[];\n};\n\ntype PushStatus = { error: boolean; message: string } | null;\n\nexport const MockCapture: React.FC<MockCaptureProps> = ({\n apduExchanges,\n isRecording,\n isConnected,\n startRecording,\n stopRecording,\n clearRecorded,\n sessionStates,\n connectedDevices,\n}) => {\n const [includeHandshake, setIncludeHandshake] = useState(false);\n const [serverUrl, setServerUrl] = useState(DEFAULT_MOCK_SERVER_URL);\n const [sessionToken, setSessionToken] = useState(\"\");\n const [isPushing, setIsPushing] = useState(false);\n const [pushStatus, setPushStatus] = useState<PushStatus>(null);\n const [detectedApps, setDetectedApps] = useState<Map<string, DeviceApp[]>>(\n new Map(),\n );\n const [detectedFirmwareVersions, setDetectedFirmwareVersions] = useState<\n Map<string, string>\n >(new Map());\n\n useEffect(() => {\n for (const [sessionId, state] of sessionStates.entries()) {\n const config = deviceConfigFromSession(state);\n if (config.apps && config.apps.length > 0) {\n const seenApps = config.apps;\n setDetectedApps((prev) => {\n const current = prev.get(sessionId) ?? [];\n const merged = [...current];\n for (const app of seenApps) {\n if (\n !merged.some(\n (a) => a.name === app.name && a.version === app.version,\n )\n ) {\n merged.push(app);\n }\n }\n if (merged.length === current.length) return prev;\n return new Map(prev).set(sessionId, merged);\n });\n }\n if (config.firmware_version) {\n const firmwareVersion = config.firmware_version;\n setDetectedFirmwareVersions((prev) => {\n if (prev.get(sessionId)) return prev;\n return new Map(prev).set(sessionId, firmwareVersion);\n });\n }\n }\n }, [sessionStates]);\n\n const handleClear = useCallback(() => {\n clearRecorded();\n setDetectedApps(new Map());\n setDetectedFirmwareVersions(new Map());\n }, [clearRecorded]);\n\n // Group exchanges by the session (device) that performed them, preserving\n // first-seen order, so each connected device gets its own export entry.\n const captureGroups = useMemo<DeviceCaptureGroup[]>(() => {\n const exchangesBySession = new Map<string, ApduExchange[]>();\n for (const exchange of apduExchanges) {\n const sessionId = exchange.sessionId ?? \"\";\n const list = exchangesBySession.get(sessionId) ?? [];\n list.push(exchange);\n exchangesBySession.set(sessionId, list);\n }\n\n return [...exchangesBySession.entries()].map(([sessionId, exchanges]) => {\n const state = sessionId ? sessionStates.get(sessionId) : undefined;\n const connectivityType = sessionId\n ? connectedDevices.find((device) => device.sessionId === sessionId)\n ?.type\n : undefined;\n const base = deviceConfigFromSession(state, connectivityType);\n const firmwareVersion = detectedFirmwareVersions.get(sessionId);\n const apps = detectedApps.get(sessionId);\n const device = {\n ...base,\n name: base.name || \"Mocked device\",\n ...(firmwareVersion ? { firmware_version: firmwareVersion } : {}),\n ...(apps && apps.length > 0 ? { apps } : {}),\n };\n return { device, exchanges };\n });\n }, [\n apduExchanges,\n sessionStates,\n connectedDevices,\n detectedApps,\n detectedFirmwareVersions,\n ]);\n\n const sessionExport = useMemo(\n () => generateMultiDeviceSessionExport(captureGroups, { includeHandshake }),\n [captureGroups, includeHandshake],\n );\n\n const mockCount = sessionExport.devices.reduce(\n (total, device) => total + (device.mocks?.length ?? 0),\n 0,\n );\n\n const generatedJson = useMemo(\n () => JSON.stringify(sessionExport, null, 2),\n [sessionExport],\n );\n\n // Editable JSON, kept in sync with the generated export until it changes\n // (e.g. new exchanges captured or the handshake toggle flipped), at which\n // point the editor is refreshed with the regenerated export.\n const [editedJson, setEditedJson] = useState(generatedJson);\n const lastGeneratedRef = useRef(generatedJson);\n useEffect(() => {\n if (generatedJson !== lastGeneratedRef.current) {\n lastGeneratedRef.current = generatedJson;\n setEditedJson(generatedJson);\n }\n }, [generatedJson]);\n\n const jsonError = useMemo(() => {\n try {\n JSON.parse(editedJson);\n return null;\n } catch (error) {\n return error instanceof Error ? error.message : \"Invalid JSON\";\n }\n }, [editedJson]);\n\n const downloadJson = useCallback(() => {\n const blob = new Blob([editedJson], {\n type: \"application/json\",\n });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `mock-session-${new Date().toISOString()}.json`;\n a.click();\n URL.revokeObjectURL(url);\n }, [editedJson]);\n\n const pushToServer = useCallback(async () => {\n setIsPushing(true);\n setPushStatus(null);\n try {\n const parsedExport = JSON.parse(editedJson) as SessionExport;\n const client = new MockClient(\n serverUrl,\n sessionToken ? { token: sessionToken } : {},\n );\n if (!sessionToken) {\n await client.authenticate();\n }\n const result = await client.importSession(parsedExport);\n const importedMocks = result.devices.reduce(\n (total, device) => total + (device.mocks?.length ?? 0),\n 0,\n );\n setPushStatus({\n error: false,\n message: `Imported ${result.devices.length} device(s) with ${importedMocks} mock(s) to the mock server.`,\n });\n } catch (error) {\n setPushStatus({\n error: true,\n message:\n error instanceof Error ? error.message : \"Failed to push session\",\n });\n } finally {\n setIsPushing(false);\n }\n }, [serverUrl, sessionToken, editedJson]);\n\n if (!isConnected) {\n return (\n <NotConnectedMessage\n title=\"Logger not connected\"\n description={\n <>\n Mock capture reads APDU exchanges from the DMK logs. Add{\" \"}\n <code>DevToolsLogger</code> to your DMK builder to start capturing:\n </>\n }\n codeExample={MOCK_CAPTURE_CODE_EXAMPLE}\n />\n );\n }\n\n const hasExchanges = apduExchanges.length > 0;\n\n return (\n <Container>\n <Section>\n <SectionTitle>Recording</SectionTitle>\n <Row>\n {isRecording ? (\n <Button $variant=\"danger\" onClick={stopRecording}>\n <RecordingDot $active />\n Stop\n </Button>\n ) : (\n <Button $variant=\"primary\" onClick={startRecording}>\n <RecordingDot $active={false} />\n Record\n </Button>\n )}\n <Button\n $variant=\"neutral\"\n onClick={handleClear}\n disabled={!hasExchanges}\n >\n Clear\n </Button>\n <Stat>\n {apduExchanges.length} exchange(s) captured, {mockCount} mock(s)\n derived\n </Stat>\n </Row>\n <Checkbox>\n <input\n type=\"checkbox\"\n checked={includeHandshake}\n onChange={(e) => setIncludeHandshake(e.target.checked)}\n />\n Include handshake APDUs (GetOsVersion / GetAppAndVersion)\n </Checkbox>\n </Section>\n\n <Section>\n <SectionTitle>Generated session export</SectionTitle>\n <Row>\n <Button\n onClick={downloadJson}\n disabled={!hasExchanges || !!jsonError}\n >\n Download JSON\n </Button>\n </Row>\n <JsonEditor\n value={editedJson}\n spellCheck={false}\n onChange={(e) => setEditedJson(e.target.value)}\n />\n {jsonError && (\n <StatusMessage $error>Invalid JSON: {jsonError}</StatusMessage>\n )}\n </Section>\n\n <Section>\n <SectionTitle>Push to mock server</SectionTitle>\n <Row>\n <Field>\n Server URL\n <Input\n value={serverUrl}\n onChange={(e) => setServerUrl(e.target.value)}\n />\n </Field>\n <Field>\n Session token\n <Input\n value={sessionToken}\n placeholder=\"auto-provisioned if empty\"\n onChange={(e) => setSessionToken(e.target.value)}\n />\n </Field>\n </Row>\n <Row>\n <Button\n onClick={pushToServer}\n disabled={!hasExchanges || isPushing || !!jsonError}\n >\n {isPushing ? \"Pushing...\" : \"Push to mock server\"}\n </Button>\n </Row>\n {pushStatus && (\n <StatusMessage $error={pushStatus.error}>\n {pushStatus.message}\n </StatusMessage>\n )}\n </Section>\n </Container>\n );\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["useCallback", "useEffect", "useMemo", "useRef", "useState", "MockClient", "styled", "NotConnectedMessage", "deviceConfigFromSession", "generateMultiDeviceSessionExport", "Fragment", "jsx", "jsxs", "DEFAULT_MOCK_SERVER_URL", "MOCK_CAPTURE_CODE_EXAMPLE", "Container", "Section", "SectionTitle", "Row", "Field", "Input", "Button", "$variant", "RecordingDot", "$active", "Stat", "JsonEditor", "StatusMessage", "$error", "Checkbox", "MockCapture", "apduExchanges", "isRecording", "isConnected", "startRecording", "stopRecording", "clearRecorded", "sessionStates", "connectedDevices", "includeHandshake", "setIncludeHandshake", "serverUrl", "setServerUrl", "sessionToken", "setSessionToken", "isPushing", "setIsPushing", "pushStatus", "setPushStatus", "detectedApps", "setDetectedApps", "detectedFirmwareVersions", "setDetectedFirmwareVersions", "sessionId", "state", "config", "seenApps", "prev", "current", "merged", "app", "a", "firmwareVersion", "handleClear", "captureGroups", "exchangesBySession", "exchange", "list", "exchanges", "connectivityType", "device", "base", "apps", "sessionExport", "mockCount", "total", "generatedJson", "editedJson", "setEditedJson", "lastGeneratedRef", "jsonError", "error", "downloadJson", "blob", "url", "pushToServer", "parsedExport", "client", "result", "importedMocks", "hasExchanges"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
"react-inspector": "9.0.0",
|
|
8
8
|
"react-resizable-panels": "^4.4.1",
|
|
9
9
|
"rxjs": "7.8.2",
|
|
10
|
-
"styled-components": "^5.3.11"
|
|
10
|
+
"styled-components": "^5.3.11",
|
|
11
|
+
"@ledgerhq/device-mockserver-client": "0.0.0-develop-20260701003433"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
14
|
"@types/react": "18.3.12",
|
|
@@ -16,13 +17,13 @@
|
|
|
16
17
|
"@types/styled-system": "^5.1.15",
|
|
17
18
|
"react": "18.3.1",
|
|
18
19
|
"react-dom": "18.3.1",
|
|
19
|
-
"@ledgerhq/device-management-kit": "0.0.0-develop-20260629003450",
|
|
20
|
-
"@ledgerhq/eslint-config-dsdk": "0.0.2",
|
|
21
20
|
"@ledgerhq/ldmk-tool": "0.0.1",
|
|
21
|
+
"@ledgerhq/device-management-kit": "0.0.0-develop-20260701003433",
|
|
22
22
|
"@ledgerhq/prettier-config-dsdk": "0.0.2",
|
|
23
|
-
"@ledgerhq/
|
|
24
|
-
"@ledgerhq/
|
|
25
|
-
"@ledgerhq/
|
|
23
|
+
"@ledgerhq/eslint-config-dsdk": "0.0.2",
|
|
24
|
+
"@ledgerhq/vitest-config-dmk": "0.0.0",
|
|
25
|
+
"@ledgerhq/device-management-kit-devtools-core": "0.0.0-develop-20260701003433",
|
|
26
|
+
"@ledgerhq/tsconfig-dsdk": "1.0.1"
|
|
26
27
|
},
|
|
27
28
|
"exports": {
|
|
28
29
|
".": {
|
|
@@ -45,8 +46,8 @@
|
|
|
45
46
|
"peerDependencies": {
|
|
46
47
|
"react": "18.3.1",
|
|
47
48
|
"react-dom": "18.3.1",
|
|
48
|
-
"@ledgerhq/device-management-kit": "0.0.0-develop-
|
|
49
|
-
"@ledgerhq/device-management-kit-devtools-core": "0.0.0-develop-
|
|
49
|
+
"@ledgerhq/device-management-kit": "0.0.0-develop-20260701003433",
|
|
50
|
+
"@ledgerhq/device-management-kit-devtools-core": "0.0.0-develop-20260701003433"
|
|
50
51
|
},
|
|
51
52
|
"private": false,
|
|
52
53
|
"repository": {
|
|
@@ -54,7 +55,7 @@
|
|
|
54
55
|
"url": "https://github.com/LedgerHQ/device-sdk-ts.git"
|
|
55
56
|
},
|
|
56
57
|
"types": "lib/index.d.ts",
|
|
57
|
-
"version": "0.0.0-develop-
|
|
58
|
+
"version": "0.0.0-develop-20260701003433",
|
|
58
59
|
"scripts": {
|
|
59
60
|
"build": "pnpm ldmk-tool build --entryPoints index.ts,src/*.tsx,src/*.ts,src/**/*.tsx,src/**/*.ts --tsconfig tsconfig.build.json",
|
|
60
61
|
"dev": "concurrently \"pnpm watch:builds\" \"pnpm watch:types\"",
|