@ledgerhq/device-mockserver-client 1.2.0 → 1.3.0

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 (44) hide show
  1. package/lib/cjs/package.json +2 -2
  2. package/lib/cjs/src/DmkNetworkClient.stub.js +1 -1
  3. package/lib/cjs/src/DmkNetworkClient.stub.js.map +3 -3
  4. package/lib/cjs/src/MockClient.js +1 -1
  5. package/lib/cjs/src/MockClient.js.map +3 -3
  6. package/lib/cjs/src/MockClient.test.js +1 -1
  7. package/lib/cjs/src/MockClient.test.js.map +3 -3
  8. package/lib/cjs/src/index.js +1 -1
  9. package/lib/cjs/src/index.js.map +1 -1
  10. package/lib/cjs/src/model/Device.js +1 -1
  11. package/lib/cjs/src/model/Device.js.map +2 -2
  12. package/lib/cjs/src/model/Speculos.js +1 -1
  13. package/lib/cjs/src/model/Speculos.js.map +1 -1
  14. package/lib/esm/package.json +2 -2
  15. package/lib/esm/src/DmkNetworkClient.stub.js +1 -1
  16. package/lib/esm/src/DmkNetworkClient.stub.js.map +3 -3
  17. package/lib/esm/src/MockClient.js +1 -1
  18. package/lib/esm/src/MockClient.js.map +3 -3
  19. package/lib/esm/src/MockClient.test.js +1 -1
  20. package/lib/esm/src/MockClient.test.js.map +3 -3
  21. package/lib/esm/src/index.js +1 -1
  22. package/lib/esm/src/index.js.map +1 -1
  23. package/lib/esm/src/model/Device.js +1 -1
  24. package/lib/esm/src/model/Device.js.map +2 -2
  25. package/lib/esm/src/model/Speculos.js +1 -1
  26. package/lib/esm/src/model/Speculos.js.map +1 -1
  27. package/lib/types/src/DmkNetworkClient.stub.d.ts +7 -1
  28. package/lib/types/src/DmkNetworkClient.stub.d.ts.map +1 -1
  29. package/lib/types/src/MockClient.d.ts +11 -1
  30. package/lib/types/src/MockClient.d.ts.map +1 -1
  31. package/lib/types/src/index.d.ts +1 -1
  32. package/lib/types/src/index.d.ts.map +1 -1
  33. package/lib/types/src/model/Auth.d.ts +2 -0
  34. package/lib/types/src/model/Auth.d.ts.map +1 -1
  35. package/lib/types/src/model/Device.d.ts +24 -0
  36. package/lib/types/src/model/Device.d.ts.map +1 -1
  37. package/lib/types/src/model/Session.d.ts +2 -0
  38. package/lib/types/src/model/Session.d.ts.map +1 -1
  39. package/lib/types/src/model/SessionExport.d.ts +2 -0
  40. package/lib/types/src/model/SessionExport.d.ts.map +1 -1
  41. package/lib/types/src/model/Speculos.d.ts +7 -0
  42. package/lib/types/src/model/Speculos.d.ts.map +1 -1
  43. package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
  44. package/package.json +5 -5
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/MockClient.ts"],
4
- "sourcesContent": ["import {\n bufferToHexaString,\n DmkNetworkClient,\n} from \"@ledgerhq/device-management-kit\";\nimport { array, type Codec } from \"purify-ts\";\n\nimport {\n authResponseCodec,\n type ConnectionState,\n connectionStateCodec,\n} from \"./model/Auth\";\nimport {\n type CommandResponse,\n commandResponseCodec,\n} from \"./model/CommandResponse\";\nimport { type Device, deviceCodec, type DeviceConfig } from \"./model/Device\";\nimport { type Mock, mockCodec, type MockConfig } from \"./model/Mock\";\nimport { type Session, sessionCodec } from \"./model/Session\";\nimport { type SessionExport, sessionExportCodec } from \"./model/SessionExport\";\nimport { type SpeculosInstance, speculosInstanceCodec } from \"./model/Speculos\";\n\nexport interface MockClientOptions {\n /**\n * An existing mock server session token. When provided the client operates\n * within that session; when omitted the client lazily creates its own session\n * through POST /auth.\n */\n readonly token?: string;\n /** Inject a custom network client (mainly for testing). */\n readonly httpClient?: DmkNetworkClient;\n}\n\n/**\n * HTTP client for the device mock server.\n *\n * Implements the bearer-token contract: the session is\n * resolved from an `Authorization: Bearer <token>` header rather than a\n * `session_id` header, and sessions, devices and mocks are exposed as REST\n * resources. The token can be injected or self-provisioned via /auth.\n */\nexport class MockClient {\n private readonly client: DmkNetworkClient;\n private token?: string;\n private authPromise?: Promise<string>;\n\n constructor(baseUrl: string, options: MockClientOptions = {}) {\n this.client =\n options.httpClient ??\n new DmkNetworkClient({ baseUrl: this.normalizeUrl(baseUrl) });\n this.token = options.token;\n }\n\n // --- Authentication -------------------------------------------------------\n\n /** Create a session and store the returned bearer token. */\n async authenticate(): Promise<string> {\n const data = await this.client.post(\"auth\", {});\n const token = this.decode(authResponseCodec, data).token;\n this.token = token;\n return token;\n }\n\n /** The current bearer token, if a session has been established. */\n getToken(): string | undefined {\n return this.token;\n }\n\n // --- Devices --------------------------------------------------------------\n\n async listDevices(): Promise<Device[]> {\n const data = await this.client.get(\"devices\", {\n headers: await this.authHeaders(),\n });\n return this.decode(array(deviceCodec), data);\n }\n\n async addDevice(config: DeviceConfig = {}): Promise<Device> {\n const data = await this.client.post(\"devices\", config, {\n headers: await this.authHeaders(),\n });\n return this.decode(deviceCodec, data);\n }\n\n async getDevice(deviceId: string): Promise<Device> {\n const data = await this.client.get(`devices/${deviceId}`, {\n headers: await this.authHeaders(),\n });\n return this.decode(deviceCodec, data);\n }\n\n async editDevice(deviceId: string, config: DeviceConfig): Promise<Device> {\n const data = await this.client.patch(`devices/${deviceId}`, config, {\n headers: await this.authHeaders(),\n });\n return this.decode(deviceCodec, data);\n }\n\n async deleteDevice(deviceId: string): Promise<boolean> {\n await this.client.delete(`devices/${deviceId}`, {\n headers: await this.authHeaders(),\n });\n return true;\n }\n\n // --- Connection state -----------------------------------------------------\n\n async connect(deviceId: string): Promise<ConnectionState> {\n const data = await this.client.post(\n `devices/${deviceId}/connect`,\n {},\n { headers: await this.authHeaders() },\n );\n return this.decode(connectionStateCodec, data);\n }\n\n async disconnect(deviceId: string): Promise<boolean> {\n await this.client.post(\n `devices/${deviceId}/disconnect`,\n {},\n { headers: await this.authHeaders() },\n );\n return true;\n }\n\n /** Disconnect every device attached to the session. */\n async disconnectAll(): Promise<boolean> {\n const devices = await this.listDevices();\n await Promise.all(\n devices\n .filter((device) => device.connected)\n .map((device) => this.disconnect(device.id)),\n );\n return true;\n }\n\n // --- APDU simulation ------------------------------------------------------\n\n async sendApdu(\n deviceId: string,\n apdu: Uint8Array | string,\n ): Promise<CommandResponse> {\n const hex =\n typeof apdu === \"string\" ? apdu : bufferToHexaString(apdu, false);\n const data = await this.client.post(\n `devices/${deviceId}/apdu`,\n { apdu: hex },\n { headers: await this.authHeaders() },\n );\n return this.decode(commandResponseCodec, data);\n }\n\n // --- Mocks (device-scoped) ------------------------------------------------\n\n async listMocks(deviceId: string): Promise<Mock[]> {\n const data = await this.client.get(`devices/${deviceId}/mocks`, {\n headers: await this.authHeaders(),\n });\n return this.decode(array(mockCodec), data);\n }\n\n async addMock(deviceId: string, config: MockConfig): Promise<Mock> {\n const data = await this.client.post(`devices/${deviceId}/mocks`, config, {\n headers: await this.authHeaders(),\n });\n return this.decode(mockCodec, data);\n }\n\n async editMock(\n deviceId: string,\n mockId: string,\n config: MockConfig,\n ): Promise<Mock> {\n const data = await this.client.patch(\n `devices/${deviceId}/mocks/${mockId}`,\n config,\n { headers: await this.authHeaders() },\n );\n return this.decode(mockCodec, data);\n }\n\n async deleteMock(deviceId: string, mockId: string): Promise<boolean> {\n await this.client.delete(`devices/${deviceId}/mocks/${mockId}`, {\n headers: await this.authHeaders(),\n });\n return true;\n }\n\n async clearMocks(deviceId: string): Promise<boolean> {\n await this.client.delete(`devices/${deviceId}/mocks`, {\n headers: await this.authHeaders(),\n });\n return true;\n }\n\n // --- Speculos -------------------------------------------------------------\n\n /**\n * Resolve the live Speculos instance backing a device (the one currently\n * proxying its APDUs). Throws if the device has no active instance.\n */\n async getSpeculos(deviceId: string): Promise<SpeculosInstance> {\n const data = await this.client.get(`devices/${deviceId}/speculos`, {\n headers: await this.authHeaders(),\n });\n return this.decode(speculosInstanceCodec, data);\n }\n\n // --- Session --------------------------------------------------------------\n\n async getSession(): Promise<Session> {\n const data = await this.client.get(\"sessions/current\", {\n headers: await this.authHeaders(),\n });\n return this.decode(sessionCodec, data);\n }\n\n async disposeSession(): Promise<boolean> {\n await this.client.delete(\"sessions/current\", {\n headers: await this.authHeaders(),\n });\n this.token = undefined;\n this.authPromise = undefined;\n return true;\n }\n\n // --- Import / Export ------------------------------------------------------\n\n /** Export the session's devices and mocks as a portable snapshot. */\n async exportSession(): Promise<SessionExport> {\n const data = await this.client.get(\"export\", {\n headers: await this.authHeaders(),\n });\n return this.decode(sessionExportCodec, data);\n }\n\n /**\n * Replace the session's devices and mocks with a previously exported\n * snapshot, returning the resulting (normalized) state.\n */\n async importSession(snapshot: SessionExport): Promise<SessionExport> {\n const data = await this.client.post(\"import\", snapshot, {\n headers: await this.authHeaders(),\n });\n return this.decode(sessionExportCodec, data);\n }\n\n // --- Helpers --------------------------------------------------------------\n\n private decode<T>(codec: Codec<T>, data: unknown): T {\n return codec.decode(data).caseOf({\n Left: (error) => {\n throw new Error(`MockClient: invalid server response (${error})`);\n },\n Right: (value) => value,\n });\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n const token = await this.ensureToken();\n return { Authorization: `Bearer ${token}` };\n }\n\n private ensureToken(): Promise<string> {\n if (this.token) {\n return Promise.resolve(this.token);\n }\n if (!this.authPromise) {\n this.authPromise = this.authenticate();\n }\n return this.authPromise;\n }\n\n private normalizeUrl(baseUrl: string): string {\n return baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["bufferToHexaString", "DmkNetworkClient", "array", "authResponseCodec", "connectionStateCodec", "commandResponseCodec", "deviceCodec", "mockCodec", "sessionCodec", "sessionExportCodec", "speculosInstanceCodec", "MockClient", "baseUrl", "options", "data", "token", "config", "deviceId", "devices", "device", "apdu", "hex", "mockId", "snapshot", "codec", "error", "value"]
4
+ "sourcesContent": ["import {\n bufferToHexaString,\n DmkNetworkClient,\n} from \"@ledgerhq/device-management-kit\";\nimport { array, type Codec } from \"purify-ts\";\n\nimport {\n authResponseCodec,\n type ConnectionState,\n connectionStateCodec,\n} from \"./model/Auth\";\nimport {\n type CommandResponse,\n commandResponseCodec,\n} from \"./model/CommandResponse\";\nimport { type Device, deviceCodec, type DeviceConfig } from \"./model/Device\";\nimport { type Mock, mockCodec, type MockConfig } from \"./model/Mock\";\nimport { type Session, sessionCodec } from \"./model/Session\";\nimport { type SessionExport, sessionExportCodec } from \"./model/SessionExport\";\nimport {\n type SpeculosAction,\n type SpeculosButton,\n type SpeculosInstance,\n speculosInstanceCodec,\n} from \"./model/Speculos\";\n\nexport interface MockClientOptions {\n /**\n * An existing mock server session token. When provided the client operates\n * within that session; when omitted the client lazily creates its own session\n * through POST /auth.\n */\n readonly token?: string;\n /** Inject a custom network client (mainly for testing). */\n readonly httpClient?: DmkNetworkClient;\n}\n\n/**\n * HTTP client for the device mock server.\n *\n * Implements the bearer-token contract: the session is\n * resolved from an `Authorization: Bearer <token>` header rather than a\n * `session_id` header, and sessions, devices and mocks are exposed as REST\n * resources. The token can be injected or self-provisioned via /auth.\n */\nexport class MockClient {\n private readonly client: DmkNetworkClient;\n private token?: string;\n private authPromise?: Promise<string>;\n\n constructor(baseUrl: string, options: MockClientOptions = {}) {\n this.client =\n options.httpClient ??\n new DmkNetworkClient({ baseUrl: this.normalizeUrl(baseUrl) });\n this.token = options.token;\n }\n\n // --- Authentication -------------------------------------------------------\n\n /** Create a session and store the returned bearer token. */\n async authenticate(): Promise<string> {\n const data = await this.client.post(\"auth\", {});\n const token = this.decode(authResponseCodec, data).token;\n this.token = token;\n return token;\n }\n\n /** The current bearer token, if a session has been established. */\n getToken(): string | undefined {\n return this.token;\n }\n\n // --- Devices --------------------------------------------------------------\n\n async listDevices(): Promise<Device[]> {\n const data = await this.client.get(\"devices\", {\n headers: await this.authHeaders(),\n });\n return this.decode(array(deviceCodec), data);\n }\n\n async addDevice(config: DeviceConfig = {}): Promise<Device> {\n const data = await this.client.post(\"devices\", config, {\n headers: await this.authHeaders(),\n });\n return this.decode(deviceCodec, data);\n }\n\n async getDevice(deviceId: string): Promise<Device> {\n const data = await this.client.get(`devices/${deviceId}`, {\n headers: await this.authHeaders(),\n });\n return this.decode(deviceCodec, data);\n }\n\n async editDevice(deviceId: string, config: DeviceConfig): Promise<Device> {\n const data = await this.client.patch(`devices/${deviceId}`, config, {\n headers: await this.authHeaders(),\n });\n return this.decode(deviceCodec, data);\n }\n\n async deleteDevice(deviceId: string): Promise<boolean> {\n await this.client.delete(`devices/${deviceId}`, {\n headers: await this.authHeaders(),\n });\n return true;\n }\n\n // --- Connection state -----------------------------------------------------\n\n async connect(deviceId: string): Promise<ConnectionState> {\n const data = await this.client.post(\n `devices/${deviceId}/connect`,\n {},\n { headers: await this.authHeaders() },\n );\n return this.decode(connectionStateCodec, data);\n }\n\n async disconnect(deviceId: string): Promise<boolean> {\n await this.client.post(\n `devices/${deviceId}/disconnect`,\n {},\n { headers: await this.authHeaders() },\n );\n return true;\n }\n\n /** Disconnect every device attached to the session. */\n async disconnectAll(): Promise<boolean> {\n const devices = await this.listDevices();\n await Promise.all(\n devices\n .filter((device) => device.connected)\n .map((device) => this.disconnect(device.id)),\n );\n return true;\n }\n\n // --- APDU simulation ------------------------------------------------------\n\n async sendApdu(\n deviceId: string,\n apdu: Uint8Array | string,\n ): Promise<CommandResponse> {\n const hex =\n typeof apdu === \"string\" ? apdu : bufferToHexaString(apdu, false);\n const data = await this.client.post(\n `devices/${deviceId}/apdu`,\n { apdu: hex },\n { headers: await this.authHeaders() },\n );\n return this.decode(commandResponseCodec, data);\n }\n\n // --- Mocks (device-scoped) ------------------------------------------------\n\n async listMocks(deviceId: string): Promise<Mock[]> {\n const data = await this.client.get(`devices/${deviceId}/mocks`, {\n headers: await this.authHeaders(),\n });\n return this.decode(array(mockCodec), data);\n }\n\n async addMock(deviceId: string, config: MockConfig): Promise<Mock> {\n const data = await this.client.post(`devices/${deviceId}/mocks`, config, {\n headers: await this.authHeaders(),\n });\n return this.decode(mockCodec, data);\n }\n\n async editMock(\n deviceId: string,\n mockId: string,\n config: MockConfig,\n ): Promise<Mock> {\n const data = await this.client.patch(\n `devices/${deviceId}/mocks/${mockId}`,\n config,\n { headers: await this.authHeaders() },\n );\n return this.decode(mockCodec, data);\n }\n\n async deleteMock(deviceId: string, mockId: string): Promise<boolean> {\n await this.client.delete(`devices/${deviceId}/mocks/${mockId}`, {\n headers: await this.authHeaders(),\n });\n return true;\n }\n\n async clearMocks(deviceId: string): Promise<boolean> {\n await this.client.delete(`devices/${deviceId}/mocks`, {\n headers: await this.authHeaders(),\n });\n return true;\n }\n\n // --- Speculos -------------------------------------------------------------\n\n /**\n * Resolve the live Speculos instance backing a device (the one currently\n * proxying its APDUs). Throws if the device has no active instance.\n */\n async getSpeculos(deviceId: string): Promise<SpeculosInstance> {\n const data = await this.client.get(`devices/${deviceId}/speculos`, {\n headers: await this.authHeaders(),\n });\n return this.decode(speculosInstanceCodec, data);\n }\n\n /**\n * Capture the device's current screen as a PNG. Throws with status 409 when\n * the device has no active instance, which is the case whenever no app is\n * running.\n */\n async getScreenshot(deviceId: string): Promise<Blob> {\n const data = await this.client.get(\n `devices/${deviceId}/speculos/screenshot`,\n { headers: await this.authHeaders(), responseType: \"blob\" },\n );\n return data as Blob;\n }\n\n /** Actuate a physical button on a button-driven device. */\n async pressButton(\n deviceId: string,\n button: SpeculosButton,\n action: SpeculosAction = \"press-and-release\",\n ): Promise<void> {\n await this.client.post(\n `devices/${deviceId}/speculos/button/${button}`,\n { action },\n { headers: await this.authHeaders() },\n );\n }\n\n /** Tap a touchscreen device, in device screen pixels. */\n async touchScreen(\n deviceId: string,\n x: number,\n y: number,\n action: SpeculosAction = \"press-and-release\",\n ): Promise<void> {\n await this.client.post(\n `devices/${deviceId}/speculos/finger`,\n { action, x, y },\n { headers: await this.authHeaders() },\n );\n }\n\n // --- Session --------------------------------------------------------------\n\n async getSession(): Promise<Session> {\n const data = await this.client.get(\"sessions/current\", {\n headers: await this.authHeaders(),\n });\n return this.decode(sessionCodec, data);\n }\n\n async disposeSession(): Promise<boolean> {\n await this.client.delete(\"sessions/current\", {\n headers: await this.authHeaders(),\n });\n this.token = undefined;\n this.authPromise = undefined;\n return true;\n }\n\n // --- Import / Export ------------------------------------------------------\n\n /** Export the session's devices and mocks as a portable snapshot. */\n async exportSession(): Promise<SessionExport> {\n const data = await this.client.get(\"export\", {\n headers: await this.authHeaders(),\n });\n return this.decode(sessionExportCodec, data);\n }\n\n /**\n * Replace the session's devices and mocks with a previously exported\n * snapshot, returning the resulting (normalized) state.\n */\n async importSession(snapshot: SessionExport): Promise<SessionExport> {\n const data = await this.client.post(\"import\", snapshot, {\n headers: await this.authHeaders(),\n });\n return this.decode(sessionExportCodec, data);\n }\n\n // --- Helpers --------------------------------------------------------------\n\n private decode<T>(codec: Codec<T>, data: unknown): T {\n return codec.decode(data).caseOf({\n Left: (error) => {\n throw new Error(`MockClient: invalid server response (${error})`);\n },\n Right: (value) => value,\n });\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n const token = await this.ensureToken();\n return { Authorization: `Bearer ${token}` };\n }\n\n private ensureToken(): Promise<string> {\n if (this.token) {\n return Promise.resolve(this.token);\n }\n if (!this.authPromise) {\n this.authPromise = this.authenticate();\n }\n return this.authPromise;\n }\n\n private normalizeUrl(baseUrl: string): string {\n return baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["bufferToHexaString", "DmkNetworkClient", "array", "authResponseCodec", "connectionStateCodec", "commandResponseCodec", "deviceCodec", "mockCodec", "sessionCodec", "sessionExportCodec", "speculosInstanceCodec", "MockClient", "baseUrl", "options", "data", "token", "config", "deviceId", "devices", "device", "apdu", "hex", "mockId", "button", "action", "x", "y", "snapshot", "codec", "error", "value"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{httpClientStubBuilder as s}from"./DmkNetworkClient.stub";import{MockClient as n}from"./MockClient";const i=(e={})=>({id:"dev-1",name:"Ledger Nano X",device_type:"nanoX",connectivity_type:"USB",...e});describe("MockClient",()=>{describe("authentication",()=>{it("lazily creates a session via /auth when no token is provided",async()=>{const e=s().mockResponse({method:"post",endpoint:"auth",response:{token:"tok-123",expires_at:42}}).mockResponse({method:"get",endpoint:"devices",response:[]}),t=new n("http://localhost:8080",{httpClient:e}),o=await t.listDevices();expect(t.getToken()).toBe("tok-123"),expect(e.calls).toContainEqual({method:"post",endpoint:"auth",body:{}}),expect(o).toEqual([])}),it("does not call /auth when a token is injected",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices",response:[]}),t=new n("http://localhost:8080",{token:"injected",httpClient:e});await t.listDevices(),expect(t.getToken()).toBe("injected"),expect(e.calls.find(o=>o.endpoint==="auth")).toBeUndefined()}),it("returns the token from an explicit authenticate() call",async()=>{const e=s().mockResponse({method:"post",endpoint:"auth",response:{token:"tok-abc",expires_at:99}}),t=new n("http://localhost:8080",{httpClient:e}),o=await t.authenticate();expect(o).toBe("tok-abc"),expect(t.getToken()).toBe("tok-abc")}),it("reuses a single in-flight /auth call for concurrent requests",async()=>{const e=s().mockResponse({method:"post",endpoint:"auth",response:{token:"tok-shared",expires_at:1}}).mockResponse({method:"get",endpoint:"devices",response:[]}),t=new n("http://localhost:8080",{httpClient:e});await Promise.all([t.listDevices(),t.listDevices()]);const o=e.calls.filter(c=>c.endpoint==="auth");expect(o).toHaveLength(1)}),it("returns undefined token before any session is established",()=>{const e=new n("http://localhost:8080",{httpClient:s()});expect(e.getToken()).toBeUndefined()})}),describe("constructor",()=>{it("normalizes a base url without a trailing slash",()=>{const e=new n("http://localhost:8080/"),t=new n("http://localhost:8080");expect(e.getToken()).toBeUndefined(),expect(t.getToken()).toBeUndefined()})}),describe("devices",()=>{it("lists devices",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices",response:[i()]}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).listDevices();expect(o).toEqual([expect.objectContaining({id:"dev-1"})])}),it("adds a device with a default empty config",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices",response:i()}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).addDevice();expect(o.id).toBe("dev-1"),expect(e.calls).toContainEqual({method:"post",endpoint:"devices",body:{}})}),it("gets a single device",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices/dev-1",response:i()}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).getDevice("dev-1");expect(o.id).toBe("dev-1")}),it("edits a device",async()=>{const e=s().mockResponse({method:"patch",endpoint:"devices/dev-1",response:i({name:"Renamed"})}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).editDevice("dev-1",{name:"Renamed"});expect(o.name).toBe("Renamed"),expect(e.calls).toContainEqual({method:"patch",endpoint:"devices/dev-1",body:{name:"Renamed"}})}),it("deletes a device",async()=>{const e=s().mockResponse({method:"delete",endpoint:"devices/dev-1",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.deleteDevice("dev-1")).resolves.toBe(!0),expect(e.calls).toContainEqual({method:"delete",endpoint:"devices/dev-1"})})}),describe("connection state",()=>{it("connects a device",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices/dev-1/connect",response:{device:i(),connected:!0}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).connect("dev-1");expect(o.connected).toBe(!0),expect(o.device.id).toBe("dev-1")}),it("disconnects a device",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices/dev-1/disconnect",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.disconnect("dev-1")).resolves.toBe(!0)}),it("disconnects every connected device",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices",response:[i({id:"dev-1",connected:!0}),i({id:"dev-2",connected:!1}),i({id:"dev-3",connected:!0})]}).mockResponse({method:"post",endpoint:"devices/dev-1/disconnect",response:{}}).mockResponse({method:"post",endpoint:"devices/dev-3/disconnect",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.disconnectAll()).resolves.toBe(!0);const o=e.calls.filter(c=>c.endpoint.endsWith("/disconnect"));expect(o.map(c=>c.endpoint)).toEqual(["devices/dev-1/disconnect","devices/dev-3/disconnect"])})}),describe("mocks",()=>{it("lists device-scoped mocks",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices/dev-1/mocks",response:[{id:"m1",prefix:"e0010000",responses:["9000"]}]}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).listMocks("dev-1");expect(o).toEqual([expect.objectContaining({id:"m1"})])}),it("creates a device-scoped mock",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices/dev-1/mocks",response:{id:"m1",prefix:"e0010000",responses:["9000"]}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).addMock("dev-1",{prefix:"e0010000",response:"9000"});expect(o.id).toBe("m1"),expect(e.calls).toContainEqual({method:"post",endpoint:"devices/dev-1/mocks",body:{prefix:"e0010000",response:"9000"}})}),it("edits a mock",async()=>{const e=s().mockResponse({method:"patch",endpoint:"devices/dev-1/mocks/m1",response:{id:"m1",prefix:"e0010000",responses:["6985"]}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).editMock("dev-1","m1",{prefix:"e0010000",response:"6985"});expect(o.responses).toEqual(["6985"]),expect(e.calls).toContainEqual({method:"patch",endpoint:"devices/dev-1/mocks/m1",body:{prefix:"e0010000",response:"6985"}})}),it("deletes a single mock",async()=>{const e=s().mockResponse({method:"delete",endpoint:"devices/dev-1/mocks/m1",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.deleteMock("dev-1","m1")).resolves.toBe(!0),expect(e.calls).toContainEqual({method:"delete",endpoint:"devices/dev-1/mocks/m1"})}),it("clears all mocks of a device",async()=>{const e=s().mockResponse({method:"delete",endpoint:"devices/dev-1/mocks",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.clearMocks("dev-1")).resolves.toBe(!0),expect(e.calls).toContainEqual({method:"delete",endpoint:"devices/dev-1/mocks"})})}),describe("speculos",()=>{it("resolves the live speculos instance backing a device",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices/dev-1/speculos",response:{run_id:"run-1",speculos_url:"https://speculos:5000",model:"stax"}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).getSpeculos("dev-1");expect(o).toEqual({run_id:"run-1",speculos_url:"https://speculos:5000",model:"stax"})})}),describe("session",()=>{it("fetches the current session",async()=>{const e=s().mockResponse({method:"get",endpoint:"sessions/current",response:{id:"sess-1",created_at:1,expires_at:2,devices:[i()]}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).getSession();expect(o.id).toBe("sess-1"),expect(o.devices).toHaveLength(1)}),it("disposes the session and clears the stored token",async()=>{const e=s().mockResponse({method:"delete",endpoint:"sessions/current",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.disposeSession()).resolves.toBe(!0),expect(t.getToken()).toBeUndefined()})}),describe("response validation",()=>{it("throws a descriptive error when the server response is malformed",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices/dev-1",response:{id:123}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.getDevice("dev-1")).rejects.toThrow(/MockClient: invalid server response/)})}),describe("import/export",()=>{it("exports the session snapshot",async()=>{const e={devices:[{name:"Ledger Stax",device_type:"stax",mocks:[{prefix:"ff",responses:["9000"]}]}]},t=s().mockResponse({method:"get",endpoint:"export",response:e}),c=await new n("http://localhost:8080",{token:"tok",httpClient:t}).exportSession();expect(c).toEqual(e)}),it("posts a snapshot to the import endpoint",async()=>{const e={devices:[{name:"Ledger Flex",device_type:"flex",mocks:[{prefix:"e0010000",responses:["aa9000","5515"]}]}]},t=s().mockResponse({method:"post",endpoint:"import",response:e}),c=await new n("http://localhost:8080",{token:"tok",httpClient:t}).importSession(e);expect(c).toEqual(e),expect(t.calls).toContainEqual({method:"post",endpoint:"import",body:e})})}),describe("apdu",()=>{it("sends a binary APDU as hex to the device apdu endpoint",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices/dev-1/apdu",response:{response:"9000"}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).sendApdu("dev-1",Uint8Array.from([224,1,0,0]));expect(o.response).toBe("9000"),expect(e.calls).toContainEqual({method:"post",endpoint:"devices/dev-1/apdu",body:{apdu:"e0010000"}})})})});
1
+ import{httpClientStubBuilder as s}from"./DmkNetworkClient.stub";import{MockClient as n}from"./MockClient";const i=(e={})=>({id:"dev-1",name:"Ledger Nano X",device_type:"nanoX",connectivity_type:"USB",...e});describe("MockClient",()=>{describe("authentication",()=>{it("lazily creates a session via /auth when no token is provided",async()=>{const e=s().mockResponse({method:"post",endpoint:"auth",response:{token:"tok-123",expires_at:42}}).mockResponse({method:"get",endpoint:"devices",response:[]}),t=new n("http://localhost:8080",{httpClient:e}),o=await t.listDevices();expect(t.getToken()).toBe("tok-123"),expect(e.calls).toContainEqual({method:"post",endpoint:"auth",body:{}}),expect(o).toEqual([])}),it("does not call /auth when a token is injected",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices",response:[]}),t=new n("http://localhost:8080",{token:"injected",httpClient:e});await t.listDevices(),expect(t.getToken()).toBe("injected"),expect(e.calls.find(o=>o.endpoint==="auth")).toBeUndefined()}),it("returns the token from an explicit authenticate() call",async()=>{const e=s().mockResponse({method:"post",endpoint:"auth",response:{token:"tok-abc",expires_at:99}}),t=new n("http://localhost:8080",{httpClient:e}),o=await t.authenticate();expect(o).toBe("tok-abc"),expect(t.getToken()).toBe("tok-abc")}),it("reuses a single in-flight /auth call for concurrent requests",async()=>{const e=s().mockResponse({method:"post",endpoint:"auth",response:{token:"tok-shared",expires_at:1}}).mockResponse({method:"get",endpoint:"devices",response:[]}),t=new n("http://localhost:8080",{httpClient:e});await Promise.all([t.listDevices(),t.listDevices()]);const o=e.calls.filter(c=>c.endpoint==="auth");expect(o).toHaveLength(1)}),it("returns undefined token before any session is established",()=>{const e=new n("http://localhost:8080",{httpClient:s()});expect(e.getToken()).toBeUndefined()})}),describe("constructor",()=>{it("normalizes a base url without a trailing slash",()=>{const e=new n("http://localhost:8080/"),t=new n("http://localhost:8080");expect(e.getToken()).toBeUndefined(),expect(t.getToken()).toBeUndefined()})}),describe("devices",()=>{it("lists devices",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices",response:[i()]}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).listDevices();expect(o).toEqual([expect.objectContaining({id:"dev-1"})])}),it("adds a device with a default empty config",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices",response:i()}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).addDevice();expect(o.id).toBe("dev-1"),expect(e.calls).toContainEqual({method:"post",endpoint:"devices",body:{}})}),it("gets a single device",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices/dev-1",response:i()}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).getDevice("dev-1");expect(o.id).toBe("dev-1")}),it("edits a device",async()=>{const e=s().mockResponse({method:"patch",endpoint:"devices/dev-1",response:i({name:"Renamed"})}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).editDevice("dev-1",{name:"Renamed"});expect(o.name).toBe("Renamed"),expect(e.calls).toContainEqual({method:"patch",endpoint:"devices/dev-1",body:{name:"Renamed"}})}),it("deletes a device",async()=>{const e=s().mockResponse({method:"delete",endpoint:"devices/dev-1",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.deleteDevice("dev-1")).resolves.toBe(!0),expect(e.calls).toContainEqual({method:"delete",endpoint:"devices/dev-1"})})}),describe("connection state",()=>{it("connects a device",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices/dev-1/connect",response:{device:i(),connected:!0}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).connect("dev-1");expect(o.connected).toBe(!0),expect(o.device.id).toBe("dev-1")}),it("disconnects a device",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices/dev-1/disconnect",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.disconnect("dev-1")).resolves.toBe(!0)}),it("disconnects every connected device",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices",response:[i({id:"dev-1",connected:!0}),i({id:"dev-2",connected:!1}),i({id:"dev-3",connected:!0})]}).mockResponse({method:"post",endpoint:"devices/dev-1/disconnect",response:{}}).mockResponse({method:"post",endpoint:"devices/dev-3/disconnect",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.disconnectAll()).resolves.toBe(!0);const o=e.calls.filter(c=>c.endpoint.endsWith("/disconnect"));expect(o.map(c=>c.endpoint)).toEqual(["devices/dev-1/disconnect","devices/dev-3/disconnect"])})}),describe("mocks",()=>{it("lists device-scoped mocks",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices/dev-1/mocks",response:[{id:"m1",prefix:"e0010000",responses:["9000"]}]}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).listMocks("dev-1");expect(o).toEqual([expect.objectContaining({id:"m1"})])}),it("creates a device-scoped mock",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices/dev-1/mocks",response:{id:"m1",prefix:"e0010000",responses:["9000"]}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).addMock("dev-1",{prefix:"e0010000",response:"9000"});expect(o.id).toBe("m1"),expect(e.calls).toContainEqual({method:"post",endpoint:"devices/dev-1/mocks",body:{prefix:"e0010000",response:"9000"}})}),it("edits a mock",async()=>{const e=s().mockResponse({method:"patch",endpoint:"devices/dev-1/mocks/m1",response:{id:"m1",prefix:"e0010000",responses:["6985"]}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).editMock("dev-1","m1",{prefix:"e0010000",response:"6985"});expect(o.responses).toEqual(["6985"]),expect(e.calls).toContainEqual({method:"patch",endpoint:"devices/dev-1/mocks/m1",body:{prefix:"e0010000",response:"6985"}})}),it("deletes a single mock",async()=>{const e=s().mockResponse({method:"delete",endpoint:"devices/dev-1/mocks/m1",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.deleteMock("dev-1","m1")).resolves.toBe(!0),expect(e.calls).toContainEqual({method:"delete",endpoint:"devices/dev-1/mocks/m1"})}),it("clears all mocks of a device",async()=>{const e=s().mockResponse({method:"delete",endpoint:"devices/dev-1/mocks",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.clearMocks("dev-1")).resolves.toBe(!0),expect(e.calls).toContainEqual({method:"delete",endpoint:"devices/dev-1/mocks"})})}),describe("speculos",()=>{it("resolves the live speculos instance backing a device",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices/dev-1/speculos",response:{run_id:"run-1",speculos_url:"https://speculos:5000",model:"stax"}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).getSpeculos("dev-1");expect(o).toEqual({run_id:"run-1",speculos_url:"https://speculos:5000",model:"stax"})}),it("requests the screenshot as a blob",async()=>{const e=new Blob([new Uint8Array([137,80])],{type:"image/png"}),t=s().mockResponse({method:"get",endpoint:"devices/dev-1/speculos/screenshot",response:e}),c=await new n("http://localhost:8080",{token:"tok",httpClient:t}).getScreenshot("dev-1");expect(c).toBe(e);const p=t.configs.find(({endpoint:d})=>d==="devices/dev-1/speculos/screenshot");expect(p?.config?.responseType).toBe("blob")}),it("presses a button",async()=>{const e=s();await new n("http://localhost:8080",{token:"tok",httpClient:e}).pressButton("dev-1","both"),expect(e.calls).toContainEqual({method:"post",endpoint:"devices/dev-1/speculos/button/both",body:{action:"press-and-release"}})}),it("forwards an explicit button action",async()=>{const e=s();await new n("http://localhost:8080",{token:"tok",httpClient:e}).pressButton("dev-1","left","press"),expect(e.calls).toContainEqual({method:"post",endpoint:"devices/dev-1/speculos/button/left",body:{action:"press"}})}),it("taps the screen at device coordinates",async()=>{const e=s();await new n("http://localhost:8080",{token:"tok",httpClient:e}).touchScreen("dev-1",200,537),expect(e.calls).toContainEqual({method:"post",endpoint:"devices/dev-1/speculos/finger",body:{action:"press-and-release",x:200,y:537}})})}),describe("session",()=>{it("fetches the current session",async()=>{const e=s().mockResponse({method:"get",endpoint:"sessions/current",response:{id:"sess-1",created_at:1,expires_at:2,devices:[i()]}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).getSession();expect(o.id).toBe("sess-1"),expect(o.devices).toHaveLength(1)}),it("disposes the session and clears the stored token",async()=>{const e=s().mockResponse({method:"delete",endpoint:"sessions/current",response:{}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.disposeSession()).resolves.toBe(!0),expect(t.getToken()).toBeUndefined()})}),describe("response validation",()=>{it("throws a descriptive error when the server response is malformed",async()=>{const e=s().mockResponse({method:"get",endpoint:"devices/dev-1",response:{id:123}}),t=new n("http://localhost:8080",{token:"tok",httpClient:e});await expect(t.getDevice("dev-1")).rejects.toThrow(/MockClient: invalid server response/)})}),describe("import/export",()=>{it("exports the session snapshot",async()=>{const e={devices:[{name:"Ledger Stax",device_type:"stax",mocks:[{prefix:"ff",responses:["9000"]}]}]},t=s().mockResponse({method:"get",endpoint:"export",response:e}),c=await new n("http://localhost:8080",{token:"tok",httpClient:t}).exportSession();expect(c).toEqual(e)}),it("posts a snapshot to the import endpoint",async()=>{const e={devices:[{name:"Ledger Flex",device_type:"flex",mocks:[{prefix:"e0010000",responses:["aa9000","5515"]}]}]},t=s().mockResponse({method:"post",endpoint:"import",response:e}),c=await new n("http://localhost:8080",{token:"tok",httpClient:t}).importSession(e);expect(c).toEqual(e),expect(t.calls).toContainEqual({method:"post",endpoint:"import",body:e})})}),describe("apdu",()=>{it("sends a binary APDU as hex to the device apdu endpoint",async()=>{const e=s().mockResponse({method:"post",endpoint:"devices/dev-1/apdu",response:{response:"9000"}}),o=await new n("http://localhost:8080",{token:"tok",httpClient:e}).sendApdu("dev-1",Uint8Array.from([224,1,0,0]));expect(o.response).toBe("9000"),expect(e.calls).toContainEqual({method:"post",endpoint:"devices/dev-1/apdu",body:{apdu:"e0010000"}})})})});
2
2
  //# sourceMappingURL=MockClient.test.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/MockClient.test.ts"],
4
- "sourcesContent": ["import { httpClientStubBuilder } from \"./DmkNetworkClient.stub\";\nimport { MockClient } from \"./MockClient\";\n\nconst aDevice = (overrides: Record<string, unknown> = {}) => ({\n id: \"dev-1\",\n name: \"Ledger Nano X\",\n device_type: \"nanoX\",\n connectivity_type: \"USB\",\n ...overrides,\n});\n\ndescribe(\"MockClient\", () => {\n describe(\"authentication\", () => {\n it(\"lazily creates a session via /auth when no token is provided\", async () => {\n const http = httpClientStubBuilder()\n .mockResponse({\n method: \"post\",\n endpoint: \"auth\",\n response: { token: \"tok-123\", expires_at: 42 },\n })\n .mockResponse({\n method: \"get\",\n endpoint: \"devices\",\n response: [],\n });\n const client = new MockClient(\"http://localhost:8080\", {\n httpClient: http,\n });\n\n const devices = await client.listDevices();\n\n expect(client.getToken()).toBe(\"tok-123\");\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"auth\",\n body: {},\n });\n expect(devices).toEqual([]);\n });\n\n it(\"does not call /auth when a token is injected\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices\",\n response: [],\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"injected\",\n httpClient: http,\n });\n\n await client.listDevices();\n\n expect(client.getToken()).toBe(\"injected\");\n expect(\n http.calls.find((call) => call.endpoint === \"auth\"),\n ).toBeUndefined();\n });\n\n it(\"returns the token from an explicit authenticate() call\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"auth\",\n response: { token: \"tok-abc\", expires_at: 99 },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n httpClient: http,\n });\n\n const token = await client.authenticate();\n\n expect(token).toBe(\"tok-abc\");\n expect(client.getToken()).toBe(\"tok-abc\");\n });\n\n it(\"reuses a single in-flight /auth call for concurrent requests\", async () => {\n const http = httpClientStubBuilder()\n .mockResponse({\n method: \"post\",\n endpoint: \"auth\",\n response: { token: \"tok-shared\", expires_at: 1 },\n })\n .mockResponse({ method: \"get\", endpoint: \"devices\", response: [] });\n const client = new MockClient(\"http://localhost:8080\", {\n httpClient: http,\n });\n\n await Promise.all([client.listDevices(), client.listDevices()]);\n\n const authCalls = http.calls.filter((call) => call.endpoint === \"auth\");\n expect(authCalls).toHaveLength(1);\n });\n\n it(\"returns undefined token before any session is established\", () => {\n const client = new MockClient(\"http://localhost:8080\", {\n httpClient: httpClientStubBuilder(),\n });\n\n expect(client.getToken()).toBeUndefined();\n });\n });\n\n describe(\"constructor\", () => {\n it(\"normalizes a base url without a trailing slash\", () => {\n const withSlash = new MockClient(\"http://localhost:8080/\");\n const withoutSlash = new MockClient(\"http://localhost:8080\");\n\n expect(withSlash.getToken()).toBeUndefined();\n expect(withoutSlash.getToken()).toBeUndefined();\n });\n });\n\n describe(\"devices\", () => {\n it(\"lists devices\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices\",\n response: [aDevice()],\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const devices = await client.listDevices();\n\n expect(devices).toEqual([expect.objectContaining({ id: \"dev-1\" })]);\n });\n\n it(\"adds a device with a default empty config\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices\",\n response: aDevice(),\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const device = await client.addDevice();\n\n expect(device.id).toBe(\"dev-1\");\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices\",\n body: {},\n });\n });\n\n it(\"gets a single device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1\",\n response: aDevice(),\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const device = await client.getDevice(\"dev-1\");\n\n expect(device.id).toBe(\"dev-1\");\n });\n\n it(\"edits a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"patch\",\n endpoint: \"devices/dev-1\",\n response: aDevice({ name: \"Renamed\" }),\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const device = await client.editDevice(\"dev-1\", { name: \"Renamed\" });\n\n expect(device.name).toBe(\"Renamed\");\n expect(http.calls).toContainEqual({\n method: \"patch\",\n endpoint: \"devices/dev-1\",\n body: { name: \"Renamed\" },\n });\n });\n\n it(\"deletes a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"delete\",\n endpoint: \"devices/dev-1\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.deleteDevice(\"dev-1\")).resolves.toBe(true);\n expect(http.calls).toContainEqual({\n method: \"delete\",\n endpoint: \"devices/dev-1\",\n });\n });\n });\n\n describe(\"connection state\", () => {\n it(\"connects a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/connect\",\n response: { device: aDevice(), connected: true },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const state = await client.connect(\"dev-1\");\n\n expect(state.connected).toBe(true);\n expect(state.device.id).toBe(\"dev-1\");\n });\n\n it(\"disconnects a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/disconnect\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.disconnect(\"dev-1\")).resolves.toBe(true);\n });\n\n it(\"disconnects every connected device\", async () => {\n const http = httpClientStubBuilder()\n .mockResponse({\n method: \"get\",\n endpoint: \"devices\",\n response: [\n aDevice({ id: \"dev-1\", connected: true }),\n aDevice({ id: \"dev-2\", connected: false }),\n aDevice({ id: \"dev-3\", connected: true }),\n ],\n })\n .mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/disconnect\",\n response: {},\n })\n .mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-3/disconnect\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.disconnectAll()).resolves.toBe(true);\n\n const disconnectCalls = http.calls.filter((call) =>\n call.endpoint.endsWith(\"/disconnect\"),\n );\n expect(disconnectCalls.map((call) => call.endpoint)).toEqual([\n \"devices/dev-1/disconnect\",\n \"devices/dev-3/disconnect\",\n ]);\n });\n });\n\n describe(\"mocks\", () => {\n it(\"lists device-scoped mocks\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1/mocks\",\n response: [{ id: \"m1\", prefix: \"e0010000\", responses: [\"9000\"] }],\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const mocks = await client.listMocks(\"dev-1\");\n\n expect(mocks).toEqual([expect.objectContaining({ id: \"m1\" })]);\n });\n\n it(\"creates a device-scoped mock\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/mocks\",\n response: { id: \"m1\", prefix: \"e0010000\", responses: [\"9000\"] },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const mock = await client.addMock(\"dev-1\", {\n prefix: \"e0010000\",\n response: \"9000\",\n });\n\n expect(mock.id).toBe(\"m1\");\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices/dev-1/mocks\",\n body: { prefix: \"e0010000\", response: \"9000\" },\n });\n });\n\n it(\"edits a mock\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"patch\",\n endpoint: \"devices/dev-1/mocks/m1\",\n response: { id: \"m1\", prefix: \"e0010000\", responses: [\"6985\"] },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const mock = await client.editMock(\"dev-1\", \"m1\", {\n prefix: \"e0010000\",\n response: \"6985\",\n });\n\n expect(mock.responses).toEqual([\"6985\"]);\n expect(http.calls).toContainEqual({\n method: \"patch\",\n endpoint: \"devices/dev-1/mocks/m1\",\n body: { prefix: \"e0010000\", response: \"6985\" },\n });\n });\n\n it(\"deletes a single mock\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"delete\",\n endpoint: \"devices/dev-1/mocks/m1\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.deleteMock(\"dev-1\", \"m1\")).resolves.toBe(true);\n expect(http.calls).toContainEqual({\n method: \"delete\",\n endpoint: \"devices/dev-1/mocks/m1\",\n });\n });\n\n it(\"clears all mocks of a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"delete\",\n endpoint: \"devices/dev-1/mocks\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.clearMocks(\"dev-1\")).resolves.toBe(true);\n expect(http.calls).toContainEqual({\n method: \"delete\",\n endpoint: \"devices/dev-1/mocks\",\n });\n });\n });\n\n describe(\"speculos\", () => {\n it(\"resolves the live speculos instance backing a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1/speculos\",\n response: {\n run_id: \"run-1\",\n speculos_url: \"https://speculos:5000\",\n model: \"stax\",\n },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const instance = await client.getSpeculos(\"dev-1\");\n\n expect(instance).toEqual({\n run_id: \"run-1\",\n speculos_url: \"https://speculos:5000\",\n model: \"stax\",\n });\n });\n });\n\n describe(\"session\", () => {\n it(\"fetches the current session\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"sessions/current\",\n response: {\n id: \"sess-1\",\n created_at: 1,\n expires_at: 2,\n devices: [aDevice()],\n },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const session = await client.getSession();\n\n expect(session.id).toBe(\"sess-1\");\n expect(session.devices).toHaveLength(1);\n });\n\n it(\"disposes the session and clears the stored token\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"delete\",\n endpoint: \"sessions/current\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.disposeSession()).resolves.toBe(true);\n expect(client.getToken()).toBeUndefined();\n });\n });\n\n describe(\"response validation\", () => {\n it(\"throws a descriptive error when the server response is malformed\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1\",\n response: { id: 123 } as unknown as object,\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.getDevice(\"dev-1\")).rejects.toThrow(\n /MockClient: invalid server response/,\n );\n });\n });\n\n describe(\"import/export\", () => {\n it(\"exports the session snapshot\", async () => {\n const snapshot = {\n devices: [\n {\n name: \"Ledger Stax\",\n device_type: \"stax\",\n mocks: [{ prefix: \"ff\", responses: [\"9000\"] }],\n },\n ],\n };\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"export\",\n response: snapshot,\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const result = await client.exportSession();\n\n expect(result).toEqual(snapshot);\n });\n\n it(\"posts a snapshot to the import endpoint\", async () => {\n const snapshot = {\n devices: [\n {\n name: \"Ledger Flex\",\n device_type: \"flex\",\n mocks: [{ prefix: \"e0010000\", responses: [\"aa9000\", \"5515\"] }],\n },\n ],\n };\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"import\",\n response: snapshot,\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const result = await client.importSession(snapshot);\n\n expect(result).toEqual(snapshot);\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"import\",\n body: snapshot,\n });\n });\n });\n\n describe(\"apdu\", () => {\n it(\"sends a binary APDU as hex to the device apdu endpoint\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/apdu\",\n response: { response: \"9000\" },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const result = await client.sendApdu(\n \"dev-1\",\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.response).toBe(\"9000\");\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices/dev-1/apdu\",\n body: { apdu: \"e0010000\" },\n });\n });\n });\n});\n"],
5
- "mappings": "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",
6
- "names": ["httpClientStubBuilder", "MockClient", "aDevice", "overrides", "http", "client", "devices", "call", "token", "authCalls", "withSlash", "withoutSlash", "device", "state", "disconnectCalls", "mocks", "mock", "instance", "session", "snapshot", "result"]
4
+ "sourcesContent": ["import { httpClientStubBuilder } from \"./DmkNetworkClient.stub\";\nimport { MockClient } from \"./MockClient\";\n\nconst aDevice = (overrides: Record<string, unknown> = {}) => ({\n id: \"dev-1\",\n name: \"Ledger Nano X\",\n device_type: \"nanoX\",\n connectivity_type: \"USB\",\n ...overrides,\n});\n\ndescribe(\"MockClient\", () => {\n describe(\"authentication\", () => {\n it(\"lazily creates a session via /auth when no token is provided\", async () => {\n const http = httpClientStubBuilder()\n .mockResponse({\n method: \"post\",\n endpoint: \"auth\",\n response: { token: \"tok-123\", expires_at: 42 },\n })\n .mockResponse({\n method: \"get\",\n endpoint: \"devices\",\n response: [],\n });\n const client = new MockClient(\"http://localhost:8080\", {\n httpClient: http,\n });\n\n const devices = await client.listDevices();\n\n expect(client.getToken()).toBe(\"tok-123\");\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"auth\",\n body: {},\n });\n expect(devices).toEqual([]);\n });\n\n it(\"does not call /auth when a token is injected\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices\",\n response: [],\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"injected\",\n httpClient: http,\n });\n\n await client.listDevices();\n\n expect(client.getToken()).toBe(\"injected\");\n expect(\n http.calls.find((call) => call.endpoint === \"auth\"),\n ).toBeUndefined();\n });\n\n it(\"returns the token from an explicit authenticate() call\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"auth\",\n response: { token: \"tok-abc\", expires_at: 99 },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n httpClient: http,\n });\n\n const token = await client.authenticate();\n\n expect(token).toBe(\"tok-abc\");\n expect(client.getToken()).toBe(\"tok-abc\");\n });\n\n it(\"reuses a single in-flight /auth call for concurrent requests\", async () => {\n const http = httpClientStubBuilder()\n .mockResponse({\n method: \"post\",\n endpoint: \"auth\",\n response: { token: \"tok-shared\", expires_at: 1 },\n })\n .mockResponse({ method: \"get\", endpoint: \"devices\", response: [] });\n const client = new MockClient(\"http://localhost:8080\", {\n httpClient: http,\n });\n\n await Promise.all([client.listDevices(), client.listDevices()]);\n\n const authCalls = http.calls.filter((call) => call.endpoint === \"auth\");\n expect(authCalls).toHaveLength(1);\n });\n\n it(\"returns undefined token before any session is established\", () => {\n const client = new MockClient(\"http://localhost:8080\", {\n httpClient: httpClientStubBuilder(),\n });\n\n expect(client.getToken()).toBeUndefined();\n });\n });\n\n describe(\"constructor\", () => {\n it(\"normalizes a base url without a trailing slash\", () => {\n const withSlash = new MockClient(\"http://localhost:8080/\");\n const withoutSlash = new MockClient(\"http://localhost:8080\");\n\n expect(withSlash.getToken()).toBeUndefined();\n expect(withoutSlash.getToken()).toBeUndefined();\n });\n });\n\n describe(\"devices\", () => {\n it(\"lists devices\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices\",\n response: [aDevice()],\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const devices = await client.listDevices();\n\n expect(devices).toEqual([expect.objectContaining({ id: \"dev-1\" })]);\n });\n\n it(\"adds a device with a default empty config\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices\",\n response: aDevice(),\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const device = await client.addDevice();\n\n expect(device.id).toBe(\"dev-1\");\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices\",\n body: {},\n });\n });\n\n it(\"gets a single device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1\",\n response: aDevice(),\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const device = await client.getDevice(\"dev-1\");\n\n expect(device.id).toBe(\"dev-1\");\n });\n\n it(\"edits a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"patch\",\n endpoint: \"devices/dev-1\",\n response: aDevice({ name: \"Renamed\" }),\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const device = await client.editDevice(\"dev-1\", { name: \"Renamed\" });\n\n expect(device.name).toBe(\"Renamed\");\n expect(http.calls).toContainEqual({\n method: \"patch\",\n endpoint: \"devices/dev-1\",\n body: { name: \"Renamed\" },\n });\n });\n\n it(\"deletes a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"delete\",\n endpoint: \"devices/dev-1\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.deleteDevice(\"dev-1\")).resolves.toBe(true);\n expect(http.calls).toContainEqual({\n method: \"delete\",\n endpoint: \"devices/dev-1\",\n });\n });\n });\n\n describe(\"connection state\", () => {\n it(\"connects a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/connect\",\n response: { device: aDevice(), connected: true },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const state = await client.connect(\"dev-1\");\n\n expect(state.connected).toBe(true);\n expect(state.device.id).toBe(\"dev-1\");\n });\n\n it(\"disconnects a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/disconnect\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.disconnect(\"dev-1\")).resolves.toBe(true);\n });\n\n it(\"disconnects every connected device\", async () => {\n const http = httpClientStubBuilder()\n .mockResponse({\n method: \"get\",\n endpoint: \"devices\",\n response: [\n aDevice({ id: \"dev-1\", connected: true }),\n aDevice({ id: \"dev-2\", connected: false }),\n aDevice({ id: \"dev-3\", connected: true }),\n ],\n })\n .mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/disconnect\",\n response: {},\n })\n .mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-3/disconnect\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.disconnectAll()).resolves.toBe(true);\n\n const disconnectCalls = http.calls.filter((call) =>\n call.endpoint.endsWith(\"/disconnect\"),\n );\n expect(disconnectCalls.map((call) => call.endpoint)).toEqual([\n \"devices/dev-1/disconnect\",\n \"devices/dev-3/disconnect\",\n ]);\n });\n });\n\n describe(\"mocks\", () => {\n it(\"lists device-scoped mocks\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1/mocks\",\n response: [{ id: \"m1\", prefix: \"e0010000\", responses: [\"9000\"] }],\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const mocks = await client.listMocks(\"dev-1\");\n\n expect(mocks).toEqual([expect.objectContaining({ id: \"m1\" })]);\n });\n\n it(\"creates a device-scoped mock\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/mocks\",\n response: { id: \"m1\", prefix: \"e0010000\", responses: [\"9000\"] },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const mock = await client.addMock(\"dev-1\", {\n prefix: \"e0010000\",\n response: \"9000\",\n });\n\n expect(mock.id).toBe(\"m1\");\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices/dev-1/mocks\",\n body: { prefix: \"e0010000\", response: \"9000\" },\n });\n });\n\n it(\"edits a mock\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"patch\",\n endpoint: \"devices/dev-1/mocks/m1\",\n response: { id: \"m1\", prefix: \"e0010000\", responses: [\"6985\"] },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const mock = await client.editMock(\"dev-1\", \"m1\", {\n prefix: \"e0010000\",\n response: \"6985\",\n });\n\n expect(mock.responses).toEqual([\"6985\"]);\n expect(http.calls).toContainEqual({\n method: \"patch\",\n endpoint: \"devices/dev-1/mocks/m1\",\n body: { prefix: \"e0010000\", response: \"6985\" },\n });\n });\n\n it(\"deletes a single mock\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"delete\",\n endpoint: \"devices/dev-1/mocks/m1\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.deleteMock(\"dev-1\", \"m1\")).resolves.toBe(true);\n expect(http.calls).toContainEqual({\n method: \"delete\",\n endpoint: \"devices/dev-1/mocks/m1\",\n });\n });\n\n it(\"clears all mocks of a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"delete\",\n endpoint: \"devices/dev-1/mocks\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.clearMocks(\"dev-1\")).resolves.toBe(true);\n expect(http.calls).toContainEqual({\n method: \"delete\",\n endpoint: \"devices/dev-1/mocks\",\n });\n });\n });\n\n describe(\"speculos\", () => {\n it(\"resolves the live speculos instance backing a device\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1/speculos\",\n response: {\n run_id: \"run-1\",\n speculos_url: \"https://speculos:5000\",\n model: \"stax\",\n },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const instance = await client.getSpeculos(\"dev-1\");\n\n expect(instance).toEqual({\n run_id: \"run-1\",\n speculos_url: \"https://speculos:5000\",\n model: \"stax\",\n });\n });\n\n it(\"requests the screenshot as a blob\", async () => {\n const png = new Blob([new Uint8Array([0x89, 0x50])], {\n type: \"image/png\",\n });\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1/speculos/screenshot\",\n response: png,\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const screenshot = await client.getScreenshot(\"dev-1\");\n\n expect(screenshot).toBe(png);\n // Anything but \"blob\" would decode the PNG as text and corrupt it.\n const request = http.configs.find(\n ({ endpoint }) => endpoint === \"devices/dev-1/speculos/screenshot\",\n );\n expect(request?.config?.responseType).toBe(\"blob\");\n });\n\n it(\"presses a button\", async () => {\n const http = httpClientStubBuilder();\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await client.pressButton(\"dev-1\", \"both\");\n\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices/dev-1/speculos/button/both\",\n body: { action: \"press-and-release\" },\n });\n });\n\n it(\"forwards an explicit button action\", async () => {\n const http = httpClientStubBuilder();\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await client.pressButton(\"dev-1\", \"left\", \"press\");\n\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices/dev-1/speculos/button/left\",\n body: { action: \"press\" },\n });\n });\n\n it(\"taps the screen at device coordinates\", async () => {\n const http = httpClientStubBuilder();\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await client.touchScreen(\"dev-1\", 200, 537);\n\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices/dev-1/speculos/finger\",\n body: { action: \"press-and-release\", x: 200, y: 537 },\n });\n });\n });\n\n describe(\"session\", () => {\n it(\"fetches the current session\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"sessions/current\",\n response: {\n id: \"sess-1\",\n created_at: 1,\n expires_at: 2,\n devices: [aDevice()],\n },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const session = await client.getSession();\n\n expect(session.id).toBe(\"sess-1\");\n expect(session.devices).toHaveLength(1);\n });\n\n it(\"disposes the session and clears the stored token\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"delete\",\n endpoint: \"sessions/current\",\n response: {},\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.disposeSession()).resolves.toBe(true);\n expect(client.getToken()).toBeUndefined();\n });\n });\n\n describe(\"response validation\", () => {\n it(\"throws a descriptive error when the server response is malformed\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"devices/dev-1\",\n response: { id: 123 } as unknown as object,\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n await expect(client.getDevice(\"dev-1\")).rejects.toThrow(\n /MockClient: invalid server response/,\n );\n });\n });\n\n describe(\"import/export\", () => {\n it(\"exports the session snapshot\", async () => {\n const snapshot = {\n devices: [\n {\n name: \"Ledger Stax\",\n device_type: \"stax\",\n mocks: [{ prefix: \"ff\", responses: [\"9000\"] }],\n },\n ],\n };\n const http = httpClientStubBuilder().mockResponse({\n method: \"get\",\n endpoint: \"export\",\n response: snapshot,\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const result = await client.exportSession();\n\n expect(result).toEqual(snapshot);\n });\n\n it(\"posts a snapshot to the import endpoint\", async () => {\n const snapshot = {\n devices: [\n {\n name: \"Ledger Flex\",\n device_type: \"flex\",\n mocks: [{ prefix: \"e0010000\", responses: [\"aa9000\", \"5515\"] }],\n },\n ],\n };\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"import\",\n response: snapshot,\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const result = await client.importSession(snapshot);\n\n expect(result).toEqual(snapshot);\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"import\",\n body: snapshot,\n });\n });\n });\n\n describe(\"apdu\", () => {\n it(\"sends a binary APDU as hex to the device apdu endpoint\", async () => {\n const http = httpClientStubBuilder().mockResponse({\n method: \"post\",\n endpoint: \"devices/dev-1/apdu\",\n response: { response: \"9000\" },\n });\n const client = new MockClient(\"http://localhost:8080\", {\n token: \"tok\",\n httpClient: http,\n });\n\n const result = await client.sendApdu(\n \"dev-1\",\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.response).toBe(\"9000\");\n expect(http.calls).toContainEqual({\n method: \"post\",\n endpoint: \"devices/dev-1/apdu\",\n body: { apdu: \"e0010000\" },\n });\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["httpClientStubBuilder", "MockClient", "aDevice", "overrides", "http", "client", "devices", "call", "token", "authCalls", "withSlash", "withoutSlash", "device", "state", "disconnectCalls", "mocks", "mock", "instance", "png", "screenshot", "request", "endpoint", "session", "snapshot", "result"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{MockClient as t}from"./MockClient";import{catalogAppCodec as n,deviceConfigCodec as c}from"./model/Device";import{mockConfigCodec as r}from"./model/Mock";import{sessionExportCodec as m}from"./model/SessionExport";export{t as MockClient,n as catalogAppCodec,c as deviceConfigCodec,r as mockConfigCodec,m as sessionExportCodec};
1
+ import{MockClient as t}from"./MockClient";import{catalogAppCodec as n,deviceConfigCodec as c}from"./model/Device";import{mockConfigCodec as s}from"./model/Mock";import{sessionExportCodec as m}from"./model/SessionExport";export{t as MockClient,n as catalogAppCodec,c as deviceConfigCodec,s as mockConfigCodec,m as sessionExportCodec};
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/index.ts"],
4
- "sourcesContent": ["export type { MockClientOptions } from \"./MockClient\";\nexport { MockClient } from \"./MockClient\";\nexport type { AuthResponse, ConnectionState } from \"./model/Auth\";\nexport type { CommandResponse } from \"./model/CommandResponse\";\nexport {\n type CatalogApp,\n catalogAppCodec,\n type Device,\n type DeviceApp,\n type DeviceConfig,\n deviceConfigCodec,\n type DeviceConnectivityType,\n} from \"./model/Device\";\nexport { type Mock, type MockConfig, mockConfigCodec } from \"./model/Mock\";\nexport type { Session } from \"./model/Session\";\nexport { type SessionExport, sessionExportCodec } from \"./model/SessionExport\";\nexport type { SpeculosInstance } from \"./model/Speculos\";\n"],
4
+ "sourcesContent": ["export type { MockClientOptions } from \"./MockClient\";\nexport { MockClient } from \"./MockClient\";\nexport type { AuthResponse, ConnectionState } from \"./model/Auth\";\nexport type { CommandResponse } from \"./model/CommandResponse\";\nexport {\n type CatalogApp,\n catalogAppCodec,\n type Device,\n type DeviceApp,\n type DeviceConfig,\n deviceConfigCodec,\n type DeviceConnectivityType,\n} from \"./model/Device\";\nexport { type Mock, type MockConfig, mockConfigCodec } from \"./model/Mock\";\nexport type { Session } from \"./model/Session\";\nexport { type SessionExport, sessionExportCodec } from \"./model/SessionExport\";\nexport type {\n SpeculosAction,\n SpeculosButton,\n SpeculosInstance,\n} from \"./model/Speculos\";\n"],
5
5
  "mappings": "AACA,OAAS,cAAAA,MAAkB,eAG3B,OAEE,mBAAAC,EAIA,qBAAAC,MAEK,iBACP,OAAqC,mBAAAC,MAAuB,eAE5D,OAA6B,sBAAAC,MAA0B",
6
6
  "names": ["MockClient", "catalogAppCodec", "deviceConfigCodec", "mockConfigCodec", "sessionExportCodec"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{array as o,boolean as t,Codec as r,number as a,optional as n,string as e}from"purify-ts";import{mockConfigCodec as c}from"./Mock";const i=r.interface({name:e,version:e,hash:n(e)}),s=r.interface({hash:e,name:e,version:e}),y=r.interface({id:e,name:e,device_type:e,connectivity_type:e,firmware_version:n(e),apps:n(o(i)),masks:n(o(a)),connected:n(t)}),l=r.interface({name:n(e),device_type:n(e),connectivity_type:n(e),firmware_version:n(e),apps:n(o(i)),masks:n(o(a)),mocks:n(o(c)),catalog:n(o(s))});export{s as catalogAppCodec,i as deviceAppCodec,y as deviceCodec,l as deviceConfigCodec};
1
+ import{array as o,boolean as a,Codec as r,number as i,optional as e,string as n}from"purify-ts";import{mockConfigCodec as c}from"./Mock";const t=r.interface({name:n,version:n,hash:e(n)}),d=r.interface({hash:n,name:n,version:n}),y=r.interface({id:n,name:n,device_type:n,connectivity_type:n,firmware_version:e(n),apps:e(o(t)),masks:e(o(i)),connected:e(a),onboarded:e(a),language:e(n)}),l=r.interface({name:e(n),device_type:e(n),connectivity_type:e(n),firmware_version:e(n),apps:e(o(t)),masks:e(o(i)),mocks:e(o(c)),catalog:e(o(d)),onboarded:e(a),language:e(n)});export{d as catalogAppCodec,t as deviceAppCodec,y as deviceCodec,l as deviceConfigCodec};
2
2
  //# sourceMappingURL=Device.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/model/Device.ts"],
4
- "sourcesContent": ["import { array, boolean, Codec, number, optional, string } from \"purify-ts\";\n\nimport { type MockConfig, mockConfigCodec } from \"./Mock\";\n\nexport type DeviceConnectivityType = \"USB\" | \"BLE\";\n\nexport interface DeviceApp {\n readonly name: string;\n readonly version: string;\n readonly hash?: string;\n}\n\nexport const deviceAppCodec = Codec.interface({\n name: string,\n version: string,\n hash: optional(string),\n});\n\n/**\n * An installable app known to the mock \"app store\", keyed by its install\n * `hash`. The secure-channel install flow resolves the hash sent by DMK to one\n * of these entries to learn which app is being installed.\n */\nexport interface CatalogApp {\n readonly hash: string;\n readonly name: string;\n readonly version: string;\n}\n\nexport const catalogAppCodec = Codec.interface({\n hash: string,\n name: string,\n version: string,\n});\n\n/**\n * A mocked device attached to a session.\n *\n * The device exposes rich metadata (firmware\n * version, installed applications, memory masks) so DMK can build a realistic\n * device session.\n */\nexport interface Device {\n readonly id: string;\n readonly name: string;\n /** DeviceModelId enum value, e.g. \"nanoX\", \"stax\", \"flex\". */\n readonly device_type: string;\n readonly connectivity_type: string;\n readonly firmware_version?: string;\n readonly apps?: DeviceApp[];\n readonly masks?: number[];\n readonly connected?: boolean;\n}\n\nexport const deviceCodec = Codec.interface({\n id: string,\n name: string,\n device_type: string,\n connectivity_type: string,\n firmware_version: optional(string),\n apps: optional(array(deviceAppCodec)),\n masks: optional(array(number)),\n connected: optional(boolean),\n});\n\n/**\n * Payload used to attach (POST /devices) or edit (PATCH /devices/:id) a device.\n */\nexport interface DeviceConfig {\n readonly name?: string;\n readonly device_type?: string;\n readonly connectivity_type?: string;\n readonly firmware_version?: string;\n readonly apps?: DeviceApp[];\n readonly masks?: number[];\n /** Device-scoped APDU mocks, used when attaching (POST) or importing a device. */\n readonly mocks?: MockConfig[];\n /** Installable apps the mock \"app store\" can resolve from an install hash. */\n readonly catalog?: CatalogApp[];\n}\n\nexport const deviceConfigCodec = Codec.interface({\n name: optional(string),\n device_type: optional(string),\n connectivity_type: optional(string),\n firmware_version: optional(string),\n apps: optional(array(deviceAppCodec)),\n masks: optional(array(number)),\n mocks: optional(array(mockConfigCodec)),\n catalog: optional(array(catalogAppCodec)),\n});\n"],
5
- "mappings": "AAAA,OAAS,SAAAA,EAAO,WAAAC,EAAS,SAAAC,EAAO,UAAAC,EAAQ,YAAAC,EAAU,UAAAC,MAAc,YAEhE,OAA0B,mBAAAC,MAAuB,SAU1C,MAAMC,EAAiBL,EAAM,UAAU,CAC5C,KAAMG,EACN,QAASA,EACT,KAAMD,EAASC,CAAM,CACvB,CAAC,EAaYG,EAAkBN,EAAM,UAAU,CAC7C,KAAMG,EACN,KAAMA,EACN,QAASA,CACX,CAAC,EAqBYI,EAAcP,EAAM,UAAU,CACzC,GAAIG,EACJ,KAAMA,EACN,YAAaA,EACb,kBAAmBA,EACnB,iBAAkBD,EAASC,CAAM,EACjC,KAAMD,EAASJ,EAAMO,CAAc,CAAC,EACpC,MAAOH,EAASJ,EAAMG,CAAM,CAAC,EAC7B,UAAWC,EAASH,CAAO,CAC7B,CAAC,EAkBYS,EAAoBR,EAAM,UAAU,CAC/C,KAAME,EAASC,CAAM,EACrB,YAAaD,EAASC,CAAM,EAC5B,kBAAmBD,EAASC,CAAM,EAClC,iBAAkBD,EAASC,CAAM,EACjC,KAAMD,EAASJ,EAAMO,CAAc,CAAC,EACpC,MAAOH,EAASJ,EAAMG,CAAM,CAAC,EAC7B,MAAOC,EAASJ,EAAMM,CAAe,CAAC,EACtC,QAASF,EAASJ,EAAMQ,CAAe,CAAC,CAC1C,CAAC",
4
+ "sourcesContent": ["import { array, boolean, Codec, number, optional, string } from \"purify-ts\";\n\nimport { type MockConfig, mockConfigCodec } from \"./Mock\";\n\nexport type DeviceConnectivityType = \"USB\" | \"BLE\";\n\nexport interface DeviceApp {\n readonly name: string;\n readonly version: string;\n readonly hash?: string;\n}\n\nexport const deviceAppCodec = Codec.interface({\n name: string,\n version: string,\n hash: optional(string),\n});\n\n/**\n * An installable app known to the mock \"app store\", keyed by its install\n * `hash`. The secure-channel install flow resolves the hash sent by DMK to one\n * of these entries to learn which app is being installed.\n */\nexport interface CatalogApp {\n readonly hash: string;\n readonly name: string;\n readonly version: string;\n}\n\nexport const catalogAppCodec = Codec.interface({\n hash: string,\n name: string,\n version: string,\n});\n\n/**\n * A mocked device attached to a session.\n *\n * The device exposes rich metadata (firmware\n * version, installed applications, memory masks) so DMK can build a realistic\n * device session.\n */\nexport interface Device {\n readonly id: string;\n readonly name: string;\n /** DeviceModelId enum value, e.g. \"nanoX\", \"stax\", \"flex\". */\n readonly device_type: string;\n readonly connectivity_type: string;\n readonly firmware_version?: string;\n readonly apps?: DeviceApp[];\n readonly masks?: number[];\n readonly connected?: boolean;\n /**\n * Whether the device is onboarded. Omitted/`true` reports a normal onboarded\n * device; `false` starts the onboarding simulation (the device reports itself\n * as not onboarded and auto-advances through the onboarding steps as it is\n * polled).\n */\n readonly onboarded?: boolean;\n /**\n * The installed language pack, by name (`\"french\"`, `\"german\"`, \u2026). Omitted\n * means the device runs on its built-in English, as one with no pack\n * installed does. Installing a pack over the wire sets it.\n */\n readonly language?: string;\n}\n\nexport const deviceCodec = Codec.interface({\n id: string,\n name: string,\n device_type: string,\n connectivity_type: string,\n firmware_version: optional(string),\n apps: optional(array(deviceAppCodec)),\n masks: optional(array(number)),\n connected: optional(boolean),\n onboarded: optional(boolean),\n language: optional(string),\n});\n\n/**\n * Payload used to attach (POST /devices) or edit (PATCH /devices/:id) a device.\n */\nexport interface DeviceConfig {\n readonly name?: string;\n readonly device_type?: string;\n readonly connectivity_type?: string;\n readonly firmware_version?: string;\n readonly apps?: DeviceApp[];\n readonly masks?: number[];\n /** Device-scoped APDU mocks, used when attaching (POST) or importing a device. */\n readonly mocks?: MockConfig[];\n /** Installable apps the mock \"app store\" can resolve from an install hash. */\n readonly catalog?: CatalogApp[];\n /**\n * Whether the device is onboarded. Omitted/`true` reports a normal onboarded\n * device; `false` starts the onboarding simulation.\n */\n readonly onboarded?: boolean;\n /** The installed language pack, by name (`\"french\"`, `\"german\"`, \u2026). */\n readonly language?: string;\n}\n\nexport const deviceConfigCodec = Codec.interface({\n name: optional(string),\n device_type: optional(string),\n connectivity_type: optional(string),\n firmware_version: optional(string),\n apps: optional(array(deviceAppCodec)),\n masks: optional(array(number)),\n mocks: optional(array(mockConfigCodec)),\n catalog: optional(array(catalogAppCodec)),\n onboarded: optional(boolean),\n language: optional(string),\n});\n"],
5
+ "mappings": "AAAA,OAAS,SAAAA,EAAO,WAAAC,EAAS,SAAAC,EAAO,UAAAC,EAAQ,YAAAC,EAAU,UAAAC,MAAc,YAEhE,OAA0B,mBAAAC,MAAuB,SAU1C,MAAMC,EAAiBL,EAAM,UAAU,CAC5C,KAAMG,EACN,QAASA,EACT,KAAMD,EAASC,CAAM,CACvB,CAAC,EAaYG,EAAkBN,EAAM,UAAU,CAC7C,KAAMG,EACN,KAAMA,EACN,QAASA,CACX,CAAC,EAkCYI,EAAcP,EAAM,UAAU,CACzC,GAAIG,EACJ,KAAMA,EACN,YAAaA,EACb,kBAAmBA,EACnB,iBAAkBD,EAASC,CAAM,EACjC,KAAMD,EAASJ,EAAMO,CAAc,CAAC,EACpC,MAAOH,EAASJ,EAAMG,CAAM,CAAC,EAC7B,UAAWC,EAASH,CAAO,EAC3B,UAAWG,EAASH,CAAO,EAC3B,SAAUG,EAASC,CAAM,CAC3B,CAAC,EAyBYK,EAAoBR,EAAM,UAAU,CAC/C,KAAME,EAASC,CAAM,EACrB,YAAaD,EAASC,CAAM,EAC5B,kBAAmBD,EAASC,CAAM,EAClC,iBAAkBD,EAASC,CAAM,EACjC,KAAMD,EAASJ,EAAMO,CAAc,CAAC,EACpC,MAAOH,EAASJ,EAAMG,CAAM,CAAC,EAC7B,MAAOC,EAASJ,EAAMM,CAAe,CAAC,EACtC,QAASF,EAASJ,EAAMQ,CAAe,CAAC,EACxC,UAAWJ,EAASH,CAAO,EAC3B,SAAUG,EAASC,CAAM,CAC3B,CAAC",
6
6
  "names": ["array", "boolean", "Codec", "number", "optional", "string", "mockConfigCodec", "deviceAppCodec", "catalogAppCodec", "deviceCodec", "deviceConfigCodec"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{Codec as r,string as e}from"purify-ts";const o=r.interface({run_id:e,speculos_url:e,model:e});export{o as speculosInstanceCodec};
1
+ import{Codec as r,string as e}from"purify-ts";const s=r.interface({run_id:e,speculos_url:e,model:e});export{s as speculosInstanceCodec};
2
2
  //# sourceMappingURL=Speculos.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/model/Speculos.ts"],
4
- "sourcesContent": ["import { Codec, string } from \"purify-ts\";\n\n/**\n * The live Speculos emulator instance backing a device, returned by\n * `GET /devices/:id/speculos`. Control calls are proxied through\n * `/devices/:id/speculos/*`.\n */\nexport interface SpeculosInstance {\n /** Speculinho run id owning the emulator pod. */\n readonly run_id: string;\n /** Per-pod emulator URL (reachable from the mock server). */\n readonly speculos_url: string;\n /** Device model (e.g. \"nanoX\", \"stax\", \"flex\"). */\n readonly model: string;\n}\n\nexport const speculosInstanceCodec = Codec.interface({\n run_id: string,\n speculos_url: string,\n model: string,\n});\n"],
4
+ "sourcesContent": ["import { Codec, string } from \"purify-ts\";\n\n/**\n * The live Speculos emulator instance backing a device, returned by\n * `GET /devices/:id/speculos`. Control calls are proxied through\n * `/devices/:id/speculos/*`.\n */\nexport interface SpeculosInstance {\n /** Speculinho run id owning the emulator pod. */\n readonly run_id: string;\n /** Per-pod emulator URL (reachable from the mock server). */\n readonly speculos_url: string;\n /** Device model (e.g. \"nanoX\", \"stax\", \"flex\"). */\n readonly model: string;\n}\n\nexport const speculosInstanceCodec = Codec.interface({\n run_id: string,\n speculos_url: string,\n model: string,\n});\n\n/** A physical button on a button-driven device. */\nexport type SpeculosButton = \"left\" | \"right\" | \"both\";\n\n/**\n * How an input is delivered. `press-and-release` covers a normal click or tap;\n * the split variants exist for flows that require a held input.\n */\nexport type SpeculosAction = \"press\" | \"release\" | \"press-and-release\";\n"],
5
5
  "mappings": "AAAA,OAAS,SAAAA,EAAO,UAAAC,MAAc,YAgBvB,MAAMC,EAAwBF,EAAM,UAAU,CACnD,OAAQC,EACR,aAAcA,EACd,MAAOA,CACT,CAAC",
6
6
  "names": ["Codec", "string", "speculosInstanceCodec"]
7
7
  }
@@ -1,4 +1,4 @@
1
- import { type DmkNetworkClient } from "@ledgerhq/device-management-kit";
1
+ import { type DmkNetworkClient, type DmkRequestConfig } from "@ledgerhq/device-management-kit";
2
2
  type Method = "get" | "post" | "patch" | "delete";
3
3
  export type DmkNetworkClientStub = DmkNetworkClient & {
4
4
  responses: Record<Method, Record<string, unknown>>;
@@ -7,6 +7,12 @@ export type DmkNetworkClientStub = DmkNetworkClient & {
7
7
  endpoint: string;
8
8
  body?: object;
9
9
  }[];
10
+ /** Per-call request config, kept aside so `calls` stays easy to match on. */
11
+ configs: {
12
+ method: Method;
13
+ endpoint: string;
14
+ config?: DmkRequestConfig;
15
+ }[];
10
16
  mockResponse(args: {
11
17
  method: Method;
12
18
  endpoint: string;
@@ -1 +1 @@
1
- {"version":3,"file":"DmkNetworkClient.stub.d.ts","sourceRoot":"","sources":["../../../src/DmkNetworkClient.stub.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,iCAAiC,CAAC;AAEzC,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElD,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7D,YAAY,CAAC,IAAI,EAAE;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,oBAAoB,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAO,oBA2DxC,CAAC"}
1
+ {"version":3,"file":"DmkNetworkClient.stub.d.ts","sourceRoot":"","sources":["../../../src/DmkNetworkClient.stub.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,iCAAiC,CAAC;AAEzC,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElD,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7D,6EAA6E;IAC7E,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,gBAAgB,CAAA;KAAE,EAAE,CAAC;IAC3E,YAAY,CAAC,IAAI,EAAE;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,oBAAoB,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAO,oBAiExC,CAAC"}
@@ -5,7 +5,7 @@ import { type Device, type DeviceConfig } from "./model/Device";
5
5
  import { type Mock, type MockConfig } from "./model/Mock";
6
6
  import { type Session } from "./model/Session";
7
7
  import { type SessionExport } from "./model/SessionExport";
8
- import { type SpeculosInstance } from "./model/Speculos";
8
+ import { type SpeculosAction, type SpeculosButton, type SpeculosInstance } from "./model/Speculos";
9
9
  export interface MockClientOptions {
10
10
  /**
11
11
  * An existing mock server session token. When provided the client operates
@@ -53,6 +53,16 @@ export declare class MockClient {
53
53
  * proxying its APDUs). Throws if the device has no active instance.
54
54
  */
55
55
  getSpeculos(deviceId: string): Promise<SpeculosInstance>;
56
+ /**
57
+ * Capture the device's current screen as a PNG. Throws with status 409 when
58
+ * the device has no active instance, which is the case whenever no app is
59
+ * running.
60
+ */
61
+ getScreenshot(deviceId: string): Promise<Blob>;
62
+ /** Actuate a physical button on a button-driven device. */
63
+ pressButton(deviceId: string, button: SpeculosButton, action?: SpeculosAction): Promise<void>;
64
+ /** Tap a touchscreen device, in device screen pixels. */
65
+ touchScreen(deviceId: string, x: number, y: number, action?: SpeculosAction): Promise<void>;
56
66
  getSession(): Promise<Session>;
57
67
  disposeSession(): Promise<boolean>;
58
68
  /** Export the session's devices and mocks as a portable snapshot. */
@@ -1 +1 @@
1
- {"version":3,"file":"MockClient.d.ts","sourceRoot":"","sources":["../../../src/MockClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MockClient.d.ts","sourceRoot":"","sources":["../../../src/MockClient.ts"],"names":[],"mappings":"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"}
@@ -6,5 +6,5 @@ export { type CatalogApp, catalogAppCodec, type Device, type DeviceApp, type Dev
6
6
  export { type Mock, type MockConfig, mockConfigCodec } from "./model/Mock";
7
7
  export type { Session } from "./model/Session";
8
8
  export { type SessionExport, sessionExportCodec } from "./model/SessionExport";
9
- export type { SpeculosInstance } from "./model/Speculos";
9
+ export type { SpeculosAction, SpeculosButton, SpeculosInstance, } from "./model/Speculos";
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EACL,KAAK,UAAU,EACf,eAAe,EACf,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,iBAAiB,EACjB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3E,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,KAAK,aAAa,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EACL,KAAK,UAAU,EACf,eAAe,EACf,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,iBAAiB,EACjB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3E,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,KAAK,aAAa,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,YAAY,EACV,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
@@ -33,6 +33,8 @@ export declare const connectionStateCodec: Codec<{
33
33
  }[] | undefined;
34
34
  masks: number[] | undefined;
35
35
  connected: boolean | undefined;
36
+ onboarded: boolean | undefined;
37
+ language: string | undefined;
36
38
  };
37
39
  connected: boolean;
38
40
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"Auth.d.ts","sourceRoot":"","sources":["../../../../src/model/Auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,EAAkB,MAAM,WAAW,CAAC;AAE3D,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,UAAU,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,eAAO,MAAM,iBAAiB;;;EAG5B,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;EAG/B,CAAC"}
1
+ {"version":3,"file":"Auth.d.ts","sourceRoot":"","sources":["../../../../src/model/Auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,EAAkB,MAAM,WAAW,CAAC;AAE3D,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,UAAU,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,eAAO,MAAM,iBAAiB;;;EAG5B,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;EAG/B,CAAC"}
@@ -43,6 +43,19 @@ export interface Device {
43
43
  readonly apps?: DeviceApp[];
44
44
  readonly masks?: number[];
45
45
  readonly connected?: boolean;
46
+ /**
47
+ * Whether the device is onboarded. Omitted/`true` reports a normal onboarded
48
+ * device; `false` starts the onboarding simulation (the device reports itself
49
+ * as not onboarded and auto-advances through the onboarding steps as it is
50
+ * polled).
51
+ */
52
+ readonly onboarded?: boolean;
53
+ /**
54
+ * The installed language pack, by name (`"french"`, `"german"`, …). Omitted
55
+ * means the device runs on its built-in English, as one with no pack
56
+ * installed does. Installing a pack over the wire sets it.
57
+ */
58
+ readonly language?: string;
46
59
  }
47
60
  export declare const deviceCodec: Codec<{
48
61
  id: string;
@@ -57,6 +70,8 @@ export declare const deviceCodec: Codec<{
57
70
  }[] | undefined;
58
71
  masks: number[] | undefined;
59
72
  connected: boolean | undefined;
73
+ onboarded: boolean | undefined;
74
+ language: string | undefined;
60
75
  }>;
61
76
  /**
62
77
  * Payload used to attach (POST /devices) or edit (PATCH /devices/:id) a device.
@@ -72,6 +87,13 @@ export interface DeviceConfig {
72
87
  readonly mocks?: MockConfig[];
73
88
  /** Installable apps the mock "app store" can resolve from an install hash. */
74
89
  readonly catalog?: CatalogApp[];
90
+ /**
91
+ * Whether the device is onboarded. Omitted/`true` reports a normal onboarded
92
+ * device; `false` starts the onboarding simulation.
93
+ */
94
+ readonly onboarded?: boolean;
95
+ /** The installed language pack, by name (`"french"`, `"german"`, …). */
96
+ readonly language?: string;
75
97
  }
76
98
  export declare const deviceConfigCodec: Codec<{
77
99
  name: string | undefined;
@@ -94,5 +116,7 @@ export declare const deviceConfigCodec: Codec<{
94
116
  name: string;
95
117
  version: string;
96
118
  }[] | undefined;
119
+ onboarded: boolean | undefined;
120
+ language: string | undefined;
97
121
  }>;
98
122
  //# sourceMappingURL=Device.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Device.d.ts","sourceRoot":"","sources":["../../../../src/model/Device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,EAA4B,MAAM,WAAW,CAAC;AAE5E,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,QAAQ,CAAC;AAE1D,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,KAAK,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,cAAc;;;;EAIzB,CAAC;AAEH;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,eAAe;;;;EAI1B,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;EAStB,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;EAS5B,CAAC"}
1
+ {"version":3,"file":"Device.d.ts","sourceRoot":"","sources":["../../../../src/model/Device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,EAA4B,MAAM,WAAW,CAAC;AAE5E,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,QAAQ,CAAC;AAE1D,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,KAAK,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,cAAc;;;;EAIzB,CAAC;AAEH;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,eAAe;;;;EAI1B,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;EAWtB,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;EAW5B,CAAC"}
@@ -28,6 +28,8 @@ export declare const sessionCodec: Codec<{
28
28
  }[] | undefined;
29
29
  masks: number[] | undefined;
30
30
  connected: boolean | undefined;
31
+ onboarded: boolean | undefined;
32
+ language: string | undefined;
31
33
  }[];
32
34
  }>;
33
35
  //# sourceMappingURL=Session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Session.d.ts","sourceRoot":"","sources":["../../../../src/model/Session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,EAAkB,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,UAAU,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;EAKvB,CAAC"}
1
+ {"version":3,"file":"Session.d.ts","sourceRoot":"","sources":["../../../../src/model/Session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,EAAkB,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,UAAU,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;EAKvB,CAAC"}
@@ -33,6 +33,8 @@ export declare const sessionExportCodec: Codec<{
33
33
  name: string;
34
34
  version: string;
35
35
  }[] | undefined;
36
+ onboarded: boolean | undefined;
37
+ language: string | undefined;
36
38
  }[];
37
39
  }>;
38
40
  //# sourceMappingURL=SessionExport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionExport.d.ts","sourceRoot":"","sources":["../../../../src/model/SessionExport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,UAAU,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;CAClC;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;EAE7B,CAAC"}
1
+ {"version":3,"file":"SessionExport.d.ts","sourceRoot":"","sources":["../../../../src/model/SessionExport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,UAAU,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;CAClC;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;EAE7B,CAAC"}
@@ -17,4 +17,11 @@ export declare const speculosInstanceCodec: Codec<{
17
17
  speculos_url: string;
18
18
  model: string;
19
19
  }>;
20
+ /** A physical button on a button-driven device. */
21
+ export type SpeculosButton = "left" | "right" | "both";
22
+ /**
23
+ * How an input is delivered. `press-and-release` covers a normal click or tap;
24
+ * the split variants exist for flows that require a held input.
25
+ */
26
+ export type SpeculosAction = "press" | "release" | "press-and-release";
20
27
  //# sourceMappingURL=Speculos.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Speculos.d.ts","sourceRoot":"","sources":["../../../../src/model/Speculos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAU,MAAM,WAAW,CAAC;AAE1C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,qBAAqB;;;;EAIhC,CAAC"}
1
+ {"version":3,"file":"Speculos.d.ts","sourceRoot":"","sources":["../../../../src/model/Speculos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAU,MAAM,WAAW,CAAC;AAE1C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,qBAAqB;;;;EAIhC,CAAC;AAEH,mDAAmD;AACnD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEvD;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,mBAAmB,CAAC"}