@ledgerhq/device-transport-kit-mockserver 1.0.2 → 1.1.1

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.
@@ -1,2 +1,2 @@
1
- "use strict";var p=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var D=(o,e)=>{for(var r in e)p(o,r,{get:e[r],enumerable:!0})},h=(o,e,r,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of m(e))!y.call(o,i)&&i!==r&&p(o,i,{get:()=>e[i],enumerable:!(t=v(e,i))||t.enumerable});return o};var b=o=>h(p({},"__esModule",{value:!0}),o);var k={};D(k,{MockTransport:()=>l,mockserverIdentifier:()=>u,mockserverTransportFactory:()=>f});module.exports=b(k);var n=require("@ledgerhq/device-management-kit"),g=require("@ledgerhq/device-mockserver-client"),c=require("purify-ts"),d=require("rxjs");const u="MOCKSERVER";class l{logger;mockClient;identifier=u;constructor(e,r){this.logger=e("MockTransport"),this.mockClient=new g.MockClient(r.mockUrl)}isSupported(){return!0}getIdentifier(){return this.identifier}listenToAvailableDevices(){return(0,d.from)([])}startDiscovering(){return this.logger.debug("startDiscovering"),(0,d.from)(this.mockClient.scan().then(e=>e.map(r=>({id:r.id,deviceModel:{id:r.device_type,productName:r.name,usbProductId:16,bootloaderUsbProductId:1,getBlockSize(){return 32},usbOnly:!0,memorySize:320*1024,masks:[823132160]},transport:this.identifier})))).pipe((0,d.mergeMap)(e=>e))}stopDiscovering(){this.logger.debug("stopDiscovering")}async connect(e){this.logger.debug("connect");const r=e.deviceId;try{const t=await this.mockClient.connect(r),i={sendApdu:s=>this.sendApdu(r,e.deviceId,e.onDisconnect,s),deviceModel:{id:t.device.device_type,productName:t.device.name,usbProductId:16,legacyUsbProductId:1,bootloaderUsbProductId:16,getBlockSize(){return 32},usbOnly:!0,memorySize:320*1024,masks:[823132160]},id:e.deviceId,type:t.device.connectivity_type,transport:this.identifier};return(0,c.Right)(i)}catch(t){return(0,c.Left)(new n.OpeningConnectionError(t))}}async disconnect(e){this.logger.debug("disconnect");const r=e.connectedDevice.id;try{return await this.mockClient.disconnect(r)?(0,c.Right)(void 0):(0,c.Left)(new n.DisconnectError(new Error(`Failed to disconnect ${r}`)))}catch(t){return(0,c.Left)(new n.DisconnectError(t))}}async sendApdu(e,r,t,i){this.logger.debug("send");try{const s=await this.mockClient.send(e,i);this.logger.debug((0,n.formatApduSentLog)(i));const a={statusCode:this.mockClient.fromHexString(s.response.substring(s.response.length-4,s.response.length)),data:this.mockClient.fromHexString(s.response.substring(0,s.response.length-4))};return this.logger.debug((0,n.formatApduReceivedLog)(a)),(0,c.Right)(a)}catch(s){return t(r),(0,c.Left)(new n.NoAccessibleDeviceError(s))}}}const f=({config:o,loggerServiceFactory:e})=>new l(e,o);0&&(module.exports={MockTransport,mockserverIdentifier,mockserverTransportFactory});
1
+ "use strict";var a=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var m=(s,e)=>{for(var t in e)a(s,t,{get:e[t],enumerable:!0})},y=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of u(e))!h.call(s,n)&&n!==t&&a(s,n,{get:()=>e[n],enumerable:!(r=g(e,n))||r.enumerable});return s};var f=s=>y(a({},"__esModule",{value:!0}),s);var I={};m(I,{MockTransport:()=>D,mockserverIdentifier:()=>v,mockserverTransportFactory:()=>E});module.exports=f(I);var i=require("@ledgerhq/device-management-kit"),p=require("@ledgerhq/device-mockserver-client"),c=require("purify-ts"),o=require("rxjs");const v="MOCKSERVER",T=[823132160],b=new i.StaticDeviceModelDataSource,k=1e3,C=1e3;class D{logger;mockClient;identifier=v;disconnectPolls=new Map;constructor(e,t,r){this.logger=e("MockTransport"),this.mockClient=new p.MockClient(t,{token:r})}isSupported(){return!0}getIdentifier(){return this.identifier}listenToAvailableDevices(){return this.logger.debug("listenToAvailableDevices"),(0,o.timer)(0,k).pipe((0,o.switchMap)(()=>(0,o.from)(this.mockClient.listDevices()).pipe((0,o.map)(e=>this.mapToDiscoveredDevices(e)),(0,o.catchError)(e=>(this.logger.error("listenToAvailableDevices failed",{data:{error:e}}),(0,o.of)([]))))))}startDiscovering(){return this.logger.debug("startDiscovering"),(0,o.from)(this.mockClient.listDevices()).pipe((0,o.map)(e=>this.mapToDiscoveredDevices(e)),(0,o.mergeMap)(e=>(0,o.from)(e)))}stopDiscovering(){this.logger.debug("stopDiscovering")}async connect(e){this.logger.debug("connect");const t=e.deviceId;try{const{device:r}=await this.mockClient.connect(t),n={sendApdu:d=>this.sendApdu(t,e.deviceId,e.onDisconnect,d),deviceModel:this.buildDeviceModel(r),id:e.deviceId,type:r.connectivity_type,transport:this.identifier};return this.listenForDisconnect(t,e.onDisconnect),(0,c.Right)(n)}catch(r){return(0,c.Left)(new i.OpeningConnectionError(r))}}async disconnect(e){this.logger.debug("disconnect");const t=e.connectedDevice.id;this.clearDisconnectPoll(t);try{return await this.mockClient.disconnect(t)?(0,c.Right)(void 0):(0,c.Left)(new i.DisconnectError(new Error(`Failed to disconnect ${t}`)))}catch(r){return(0,c.Left)(new i.DisconnectError(r))}}async sendApdu(e,t,r,n){this.logger.debug("send");try{const d=await this.mockClient.sendApdu(e,n);this.logger.debug((0,i.formatApduSentLog)(n));const l={statusCode:(0,i.hexaStringToBuffer)(d.response.slice(-4))??new Uint8Array,data:(0,i.hexaStringToBuffer)(d.response.slice(0,-4))??new Uint8Array};return this.logger.debug((0,i.formatApduReceivedLog)(l)),(0,c.Right)(l)}catch(d){return this.clearDisconnectPoll(t),r(t),(0,c.Left)(new i.NoAccessibleDeviceError(d))}}listenForDisconnect(e,t){this.clearDisconnectPoll(e);const r=setInterval(()=>{this.mockClient.listDevices().then(n=>{this.disconnectPolls.get(e)!==r||n.some(l=>l.id===e&&l.connected!==!1)||(this.logger.info(`Device ${e} is no longer connected, disconnecting`),this.clearDisconnectPoll(e),t(e))}).catch(n=>{this.logger.error("disconnect poll failed",{data:{error:n}})})},C);this.disconnectPolls.set(e,r)}clearDisconnectPoll(e){const t=this.disconnectPolls.get(e);t!==void 0&&(clearInterval(t),this.disconnectPolls.delete(e))}mapToDiscoveredDevices(e){return e.map(t=>({id:t.id,deviceModel:this.buildDeviceModel(t),transport:this.identifier}))}buildDeviceModel(e){const t=e.device_type,r=b.getAllDeviceModels().find(n=>n.id===t);return{id:t,productName:e.name,usbProductId:16,legacyUsbProductId:1,bootloaderUsbProductId:1,getBlockSize:r?r.getBlockSize:()=>32,usbOnly:!0,memorySize:r?.memorySize??320*1024,masks:e.masks??T}}}const E=(s,e)=>({config:t,loggerServiceFactory:r})=>new D(r,s??t.mockUrl,e);0&&(module.exports={MockTransport,mockserverIdentifier,mockserverTransportFactory});
2
2
  //# sourceMappingURL=MockserverTransport.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/MockserverTransport.ts"],
4
- "sourcesContent": ["import {\n type ApduResponse,\n type ConnectError,\n type DeviceId,\n type DeviceModelId,\n DisconnectError,\n type DisconnectHandler,\n type DmkConfig,\n type DmkError,\n formatApduReceivedLog,\n formatApduSentLog,\n type LoggerPublisherService,\n NoAccessibleDeviceError,\n OpeningConnectionError,\n type Transport,\n type TransportConnectedDevice,\n type TransportDiscoveredDevice,\n type TransportFactory,\n type TransportIdentifier,\n} from \"@ledgerhq/device-management-kit\";\nimport {\n type CommandResponse,\n type Device,\n MockClient,\n type Session,\n} from \"@ledgerhq/device-mockserver-client\";\nimport { type Either, Left, Right } from \"purify-ts\";\nimport { from, mergeMap, type Observable } from \"rxjs\";\n\nexport const mockserverIdentifier: TransportIdentifier = \"MOCKSERVER\";\n\nexport class MockTransport implements Transport {\n private logger: LoggerPublisherService;\n private mockClient: MockClient;\n private readonly identifier: TransportIdentifier = mockserverIdentifier;\n\n constructor(\n loggerServiceFactory: (tag: string) => LoggerPublisherService,\n config: DmkConfig,\n ) {\n this.logger = loggerServiceFactory(\"MockTransport\");\n this.mockClient = new MockClient(config.mockUrl);\n }\n\n isSupported(): boolean {\n return true;\n }\n\n getIdentifier(): TransportIdentifier {\n return this.identifier;\n }\n\n listenToAvailableDevices(): Observable<TransportDiscoveredDevice[]> {\n return from([]);\n }\n\n startDiscovering(): Observable<TransportDiscoveredDevice> {\n this.logger.debug(\"startDiscovering\");\n return from(\n this.mockClient.scan().then((devices: Device[]) => {\n return devices.map((device: Device) => ({\n id: device.id,\n deviceModel: {\n id: device.device_type as DeviceModelId,\n productName: device.name,\n usbProductId: 0x10,\n bootloaderUsbProductId: 0x0001,\n getBlockSize() {\n return 32;\n },\n usbOnly: true,\n memorySize: 320 * 1024,\n masks: [0x31100000],\n },\n transport: this.identifier,\n }));\n }),\n ).pipe(mergeMap((device) => device));\n }\n\n stopDiscovering(): void {\n //DO NOTHING HERE\n this.logger.debug(\"stopDiscovering\");\n }\n\n async connect(params: {\n deviceId: DeviceId;\n onDisconnect: DisconnectHandler;\n }): Promise<Either<ConnectError, TransportConnectedDevice>> {\n this.logger.debug(\"connect\");\n const sessionId: string = params.deviceId;\n try {\n const session: Session = await this.mockClient.connect(sessionId);\n const connectedDevice = {\n sendApdu: (apdu) => {\n return this.sendApdu(\n sessionId,\n params.deviceId,\n params.onDisconnect,\n apdu,\n );\n },\n deviceModel: {\n id: session.device.device_type,\n productName: session.device.name,\n usbProductId: 0x10,\n legacyUsbProductId: 0x0001,\n bootloaderUsbProductId: 0x10,\n getBlockSize() {\n return 32;\n },\n usbOnly: true,\n memorySize: 320 * 1024,\n masks: [0x31100000],\n },\n id: params.deviceId,\n type: session.device.connectivity_type,\n transport: this.identifier,\n } as TransportConnectedDevice;\n return Right(connectedDevice);\n } catch (error) {\n return Left(new OpeningConnectionError(error as Error));\n }\n }\n\n async disconnect(params: {\n connectedDevice: TransportConnectedDevice;\n }): Promise<Either<DmkError, void>> {\n this.logger.debug(\"disconnect\");\n const sessionId: string = params.connectedDevice.id;\n try {\n const success: boolean = await this.mockClient.disconnect(sessionId);\n if (!success) {\n return Left(\n new DisconnectError(new Error(`Failed to disconnect ${sessionId}`)),\n );\n }\n return Right(undefined);\n } catch (error) {\n return Left(new DisconnectError(error as Error));\n }\n }\n\n async sendApdu(\n sessionId: string,\n deviceId: DeviceId,\n onDisconnect: DisconnectHandler,\n apdu: Uint8Array,\n ): Promise<Either<DmkError, ApduResponse>> {\n this.logger.debug(\"send\");\n try {\n const response: CommandResponse = await this.mockClient.send(\n sessionId,\n apdu,\n );\n this.logger.debug(formatApduSentLog(apdu));\n const apduResponse = {\n statusCode: this.mockClient.fromHexString(\n response.response.substring(\n response.response.length - 4,\n response.response.length,\n ),\n ),\n data: this.mockClient.fromHexString(\n response.response.substring(0, response.response.length - 4),\n ),\n } as ApduResponse;\n this.logger.debug(formatApduReceivedLog(apduResponse));\n return Right(apduResponse);\n } catch (error) {\n onDisconnect(deviceId);\n return Left(new NoAccessibleDeviceError(error as Error));\n }\n }\n}\n\nexport const mockserverTransportFactory: TransportFactory = ({\n config,\n loggerServiceFactory,\n}) => new MockTransport(loggerServiceFactory, config);\n"],
5
- "mappings": "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",
6
- "names": ["MockserverTransport_exports", "__export", "MockTransport", "mockserverIdentifier", "mockserverTransportFactory", "__toCommonJS", "import_device_management_kit", "import_device_mockserver_client", "import_purify_ts", "import_rxjs", "loggerServiceFactory", "config", "devices", "device", "params", "sessionId", "session", "connectedDevice", "apdu", "error", "deviceId", "onDisconnect", "response", "apduResponse"]
4
+ "sourcesContent": ["import {\n type ApduResponse,\n type ConnectError,\n type DeviceId,\n type DeviceModelId,\n DisconnectError,\n type DisconnectHandler,\n type DmkError,\n formatApduReceivedLog,\n formatApduSentLog,\n hexaStringToBuffer,\n type LoggerPublisherService,\n NoAccessibleDeviceError,\n OpeningConnectionError,\n StaticDeviceModelDataSource,\n type Transport,\n type TransportConnectedDevice,\n type TransportDiscoveredDevice,\n type TransportFactory,\n type TransportIdentifier,\n} from \"@ledgerhq/device-management-kit\";\nimport {\n type CommandResponse,\n type Device,\n MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\nimport { type Either, Left, Right } from \"purify-ts\";\nimport {\n catchError,\n from,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n timer,\n} from \"rxjs\";\n\nexport const mockserverIdentifier: TransportIdentifier = \"MOCKSERVER\";\n\nconst DEFAULT_MASKS = [0x31100000];\n\n/**\n * Real per-model memory constants (memory size, block size, masks) so mock\n * devices report the same values as physical ones. Without this, memory-aware\n * device actions (e.g. install/update apps, which run {@link PredictOutOfMemoryTask})\n * compute against wrong constants and wrongly report out-of-memory.\n */\nconst deviceModelDataSource = new StaticDeviceModelDataSource();\n\n/** Interval (ms) at which the mock server is polled for available devices. */\nconst DISCOVERY_POLL_INTERVAL_MS = 1000;\n\n/** Interval (ms) at which a connected device is polled for its liveness. */\nconst DISCONNECT_POLL_INTERVAL_MS = 1000;\n\nexport class MockTransport implements Transport {\n private logger: LoggerPublisherService;\n private mockClient: MockClient;\n private readonly identifier: TransportIdentifier = mockserverIdentifier;\n private readonly disconnectPolls = new Map<\n DeviceId,\n ReturnType<typeof setInterval>\n >();\n\n constructor(\n loggerServiceFactory: (tag: string) => LoggerPublisherService,\n mockUrl: string,\n sessionToken?: string,\n ) {\n this.logger = loggerServiceFactory(\"MockTransport\");\n this.mockClient = new MockClient(mockUrl, { token: sessionToken });\n }\n\n isSupported(): boolean {\n return true;\n }\n\n getIdentifier(): TransportIdentifier {\n return this.identifier;\n }\n\n listenToAvailableDevices(): Observable<TransportDiscoveredDevice[]> {\n this.logger.debug(\"listenToAvailableDevices\");\n return timer(0, DISCOVERY_POLL_INTERVAL_MS).pipe(\n switchMap(() =>\n // Recovery belongs to the request, not to the pipeline: a catchError\n // on the outer chain replaces the timer along with the failed poll, so\n // one unreachable moment would end discovery for the rest of the\n // subscription and no device added later could ever be found.\n from(this.mockClient.listDevices()).pipe(\n map((devices) => this.mapToDiscoveredDevices(devices)),\n catchError((error) => {\n this.logger.error(\"listenToAvailableDevices failed\", {\n data: { error },\n });\n return of<TransportDiscoveredDevice[]>([]);\n }),\n ),\n ),\n );\n }\n\n startDiscovering(): Observable<TransportDiscoveredDevice> {\n this.logger.debug(\"startDiscovering\");\n return from(this.mockClient.listDevices()).pipe(\n map((devices) => this.mapToDiscoveredDevices(devices)),\n mergeMap((devices) => from(devices)),\n );\n }\n\n stopDiscovering(): void {\n //DO NOTHING HERE\n this.logger.debug(\"stopDiscovering\");\n }\n\n async connect(params: {\n deviceId: DeviceId;\n onDisconnect: DisconnectHandler;\n }): Promise<Either<ConnectError, TransportConnectedDevice>> {\n this.logger.debug(\"connect\");\n const deviceId: string = params.deviceId;\n try {\n const { device } = await this.mockClient.connect(deviceId);\n const connectedDevice = {\n sendApdu: (apdu) =>\n this.sendApdu(deviceId, params.deviceId, params.onDisconnect, apdu),\n deviceModel: this.buildDeviceModel(device),\n id: params.deviceId,\n type: device.connectivity_type,\n transport: this.identifier,\n } as TransportConnectedDevice;\n this.listenForDisconnect(deviceId, params.onDisconnect);\n return Right(connectedDevice);\n } catch (error) {\n return Left(new OpeningConnectionError(error as Error));\n }\n }\n\n async disconnect(params: {\n connectedDevice: TransportConnectedDevice;\n }): Promise<Either<DmkError, void>> {\n this.logger.debug(\"disconnect\");\n const deviceId: string = params.connectedDevice.id;\n this.clearDisconnectPoll(deviceId);\n try {\n const success: boolean = await this.mockClient.disconnect(deviceId);\n if (!success) {\n return Left(\n new DisconnectError(new Error(`Failed to disconnect ${deviceId}`)),\n );\n }\n return Right(undefined);\n } catch (error) {\n return Left(new DisconnectError(error as Error));\n }\n }\n\n async sendApdu(\n deviceId: string,\n onDisconnectDeviceId: DeviceId,\n onDisconnect: DisconnectHandler,\n apdu: Uint8Array,\n ): Promise<Either<DmkError, ApduResponse>> {\n this.logger.debug(\"send\");\n try {\n const response: CommandResponse = await this.mockClient.sendApdu(\n deviceId,\n apdu,\n );\n this.logger.debug(formatApduSentLog(apdu));\n const apduResponse = {\n statusCode:\n hexaStringToBuffer(response.response.slice(-4)) ?? new Uint8Array(),\n data:\n hexaStringToBuffer(response.response.slice(0, -4)) ??\n new Uint8Array(),\n } as ApduResponse;\n this.logger.debug(formatApduReceivedLog(apduResponse));\n return Right(apduResponse);\n } catch (error) {\n this.clearDisconnectPoll(onDisconnectDeviceId);\n onDisconnect(onDisconnectDeviceId);\n return Left(new NoAccessibleDeviceError(error as Error));\n }\n }\n\n /**\n * A device can be deleted or disconnected through the API by any client, and\n * nothing announces it the way a physical unplug does. Without the signal,\n * DMK keeps a session that claims to be connected.\n */\n private listenForDisconnect(\n deviceId: DeviceId,\n onDisconnect: DisconnectHandler,\n ): void {\n this.clearDisconnectPoll(deviceId);\n const poll: ReturnType<typeof setInterval> = setInterval(() => {\n this.mockClient\n .listDevices()\n .then((devices) => {\n // clearInterval leaves a request in flight; only the poll still\n // registered may report.\n if (this.disconnectPolls.get(deviceId) !== poll) {\n return;\n }\n const present = devices.some(\n (device) => device.id === deviceId && device.connected !== false,\n );\n if (present) {\n return;\n }\n this.logger.info(\n `Device ${deviceId} is no longer connected, disconnecting`,\n );\n this.clearDisconnectPoll(deviceId);\n onDisconnect(deviceId);\n })\n .catch((error) => {\n // An unreachable server is not proof the device left; the next tick\n // decides.\n this.logger.error(\"disconnect poll failed\", { data: { error } });\n });\n }, DISCONNECT_POLL_INTERVAL_MS);\n this.disconnectPolls.set(deviceId, poll);\n }\n\n private clearDisconnectPoll(deviceId: DeviceId): void {\n const poll = this.disconnectPolls.get(deviceId);\n if (poll === undefined) {\n return;\n }\n clearInterval(poll);\n this.disconnectPolls.delete(deviceId);\n }\n\n private mapToDiscoveredDevices(\n devices: Device[],\n ): TransportDiscoveredDevice[] {\n return devices.map((device) => ({\n id: device.id,\n deviceModel: this.buildDeviceModel(device),\n transport: this.identifier,\n }));\n }\n\n private buildDeviceModel(device: Device) {\n const id = device.device_type as DeviceModelId;\n // Resolve the real model to mirror a physical device's memory constants;\n // fall back to legacy defaults for an unknown device type.\n const knownModel = deviceModelDataSource\n .getAllDeviceModels()\n .find((model) => model.id === id);\n return {\n id,\n productName: device.name,\n usbProductId: 0x10,\n legacyUsbProductId: 0x0001,\n bootloaderUsbProductId: 0x0001,\n getBlockSize: knownModel ? knownModel.getBlockSize : () => 32,\n usbOnly: true,\n memorySize: knownModel?.memorySize ?? 320 * 1024,\n masks: device.masks ?? DEFAULT_MASKS,\n };\n }\n}\n\n/**\n * Build a mock-server transport factory.\n *\n * @param mockUrl Optional mock server URL. When omitted, `config.mockUrl` from\n * the DMK configuration is used.\n * @param sessionToken Optional bearer token to share an existing session\n * When omitted, the client self-provisions a session via /auth.\n */\nexport const mockserverTransportFactory =\n (mockUrl?: string, sessionToken?: string): TransportFactory =>\n ({ config, loggerServiceFactory }) =>\n new MockTransport(\n loggerServiceFactory,\n mockUrl ?? config.mockUrl,\n sessionToken,\n );\n"],
5
+ "mappings": "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",
6
+ "names": ["MockserverTransport_exports", "__export", "MockTransport", "mockserverIdentifier", "mockserverTransportFactory", "__toCommonJS", "import_device_management_kit", "import_device_mockserver_client", "import_purify_ts", "import_rxjs", "DEFAULT_MASKS", "deviceModelDataSource", "DISCOVERY_POLL_INTERVAL_MS", "DISCONNECT_POLL_INTERVAL_MS", "loggerServiceFactory", "mockUrl", "sessionToken", "devices", "error", "params", "deviceId", "device", "connectedDevice", "apdu", "onDisconnectDeviceId", "onDisconnect", "response", "apduResponse", "poll", "id", "knownModel", "model", "config"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ "use strict";var f=require("@ledgerhq/device-management-kit"),u=require("@ledgerhq/device-mockserver-client"),l=require("rxjs"),c=require("./MockserverTransport");vi.mock("@ledgerhq/device-mockserver-client",()=>({MockClient:vi.fn()}));const h=()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}),i={config:{mockUrl:"http://localhost:8080"},loggerServiceFactory:h},m=1e3,a=(t={})=>({id:"device-1",name:"Ledger Nano X",device_type:"nanoX",connectivity_type:"USB",...t}),p=t=>{const e=vi.fn(t);return vi.mocked(u.MockClient).mockImplementation(()=>({listDevices:e})),e},d=(t={})=>{const e={listDevices:vi.fn(()=>Promise.resolve([])),connect:vi.fn(),disconnect:vi.fn(),sendApdu:vi.fn(),...t};return vi.mocked(u.MockClient).mockImplementation(()=>e),e};describe("mockserverTransportFactory",()=>{beforeEach(()=>{vi.clearAllMocks(),p(()=>Promise.resolve([]))}),afterEach(()=>{vi.useRealTimers()}),it("builds a supported transport with the mockserver identifier",()=>{const t=(0,c.mockserverTransportFactory)("http://localhost:8080")(i);expect(t.getIdentifier()).toBe(c.mockserverIdentifier),expect(t.isSupported()).toBe(!0)}),it("accepts an injected session token without throwing",()=>{expect(()=>(0,c.mockserverTransportFactory)("http://localhost:8080","tok-123")(i)).not.toThrow()}),describe("listenToAvailableDevices",()=>{it("emits the mock server devices mapped as discovered devices",async()=>{p(()=>Promise.resolve([a(),a({id:"device-2",name:"Stax"})]));const t=(0,c.mockserverTransportFactory)("http://localhost:8080")(i),e=await(0,l.firstValueFrom)(t.listenToAvailableDevices());expect(e).toEqual([expect.objectContaining({id:"device-1",transport:c.mockserverIdentifier}),expect.objectContaining({id:"device-2",transport:c.mockserverIdentifier})])}),it("keeps polling so newly added devices appear",async()=>{vi.useFakeTimers(),p(()=>Promise.resolve([])).mockResolvedValueOnce([]).mockResolvedValueOnce([a()]);const e=(0,c.mockserverTransportFactory)("http://localhost:8080")(i),n=(0,l.firstValueFrom)(e.listenToAvailableDevices().pipe((0,l.take)(2),(0,l.toArray)()));await vi.advanceTimersByTimeAsync(m);const o=await n;expect(o[0]).toEqual([]),expect(o[1]).toEqual([expect.objectContaining({id:"device-1"})])}),it("emits an empty list when the mock server is unreachable",async()=>{p(()=>Promise.reject(new Error("offline")));const t=(0,c.mockserverTransportFactory)("http://localhost:8080")(i),e=await(0,l.firstValueFrom)(t.listenToAvailableDevices());expect(e).toEqual([])}),it("keeps polling after a failed request",async()=>{vi.useFakeTimers(),p(()=>Promise.resolve([])).mockRejectedValueOnce(new Error("offline")).mockResolvedValueOnce([a()]);const e=(0,c.mockserverTransportFactory)("http://localhost:8080")(i),n=(0,l.firstValueFrom)(e.listenToAvailableDevices().pipe((0,l.take)(2),(0,l.toArray)()));await vi.advanceTimersByTimeAsync(m);const o=await n;expect(o[0]).toEqual([]),expect(o[1]).toEqual([expect.objectContaining({id:"device-1"})])})}),describe("startDiscovering",()=>{it("emits each device returned by the mock server",async()=>{d({listDevices:vi.fn(()=>Promise.resolve([a(),a({id:"device-2",name:"Stax"})]))});const t=(0,c.mockserverTransportFactory)("http://localhost:8080")(i),e=await(0,l.firstValueFrom)(t.startDiscovering().pipe((0,l.toArray)()));expect(e).toEqual([expect.objectContaining({id:"device-1"}),expect.objectContaining({id:"device-2"})])})}),describe("stopDiscovering",()=>{it("does nothing and does not throw",()=>{d();const t=(0,c.mockserverTransportFactory)("http://localhost:8080")(i);expect(()=>t.stopDiscovering()).not.toThrow()})}),describe("connect",()=>{it("returns a connected device on success",async()=>{d({connect:vi.fn(()=>Promise.resolve({device:a({masks:[305419896]}),connected:!0}))});const t=(0,c.mockserverTransportFactory)("http://localhost:8080")(i),e=vi.fn(),n=await t.connect({deviceId:"device-1",onDisconnect:e});expect(n.isRight()).toBe(!0);const o=n.unsafeCoerce();expect(o.id).toBe("device-1"),expect(o.transport).toBe(c.mockserverIdentifier),expect(o.deviceModel.masks).toEqual([305419896])}),it("falls back to the default masks when the device has none",async()=>{d({connect:vi.fn(()=>Promise.resolve({device:a(),connected:!0}))});const e=await(0,c.mockserverTransportFactory)("http://localhost:8080")(i).connect({deviceId:"device-1",onDisconnect:vi.fn()});expect(e.unsafeCoerce().deviceModel.masks).toEqual([823132160]),expect(e.unsafeCoerce().deviceModel.getBlockSize({firmwareVersion:"1.0.0"})).toBe(4*1024)}),it("returns an OpeningConnectionError when the client throws",async()=>{d({connect:vi.fn(()=>Promise.reject(new Error("nope")))});const e=await(0,c.mockserverTransportFactory)("http://localhost:8080")(i).connect({deviceId:"device-1",onDisconnect:vi.fn()});expect(e.isLeft()).toBe(!0),expect(e.swap().unsafeCoerce()).toBeInstanceOf(f.OpeningConnectionError)})}),describe("disconnect",()=>{const t={id:"device-1"};it("returns Right(undefined) when the client disconnects successfully",async()=>{const e=vi.fn(()=>Promise.resolve(!0));d({disconnect:e});const o=await(0,c.mockserverTransportFactory)("http://localhost:8080")(i).disconnect({connectedDevice:t});expect(e).toHaveBeenCalledWith("device-1"),expect(o.isRight()).toBe(!0)}),it("returns a DisconnectError when the client reports failure",async()=>{d({disconnect:vi.fn(()=>Promise.resolve(!1))});const n=await(0,c.mockserverTransportFactory)("http://localhost:8080")(i).disconnect({connectedDevice:t});expect(n.isLeft()).toBe(!0)}),it("returns a DisconnectError when the client throws",async()=>{d({disconnect:vi.fn(()=>Promise.reject(new Error("boom")))});const n=await(0,c.mockserverTransportFactory)("http://localhost:8080")(i).disconnect({connectedDevice:t});expect(n.isLeft()).toBe(!0)})}),describe("sendApdu",()=>{const t=e=>(d({connect:vi.fn(()=>Promise.resolve({device:a(),connected:!0})),sendApdu:e}),(0,c.mockserverTransportFactory)("http://localhost:8080")(i));it("splits the response into data and status code",async()=>{const e=vi.fn(()=>Promise.resolve({response:"aabb9000"})),n=t(e),o=vi.fn(),r=await(await n.connect({deviceId:"device-1",onDisconnect:o})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(r.isRight()).toBe(!0);const v=r.unsafeCoerce();expect(Array.from(v.data)).toEqual([170,187]),expect(Array.from(v.statusCode)).toEqual([144,0]),expect(o).not.toHaveBeenCalled()}),it("calls onDisconnect and returns a NoAccessibleDeviceError on failure",async()=>{const e=vi.fn(()=>Promise.reject(new Error("device lost"))),n=t(e),o=vi.fn(),r=await(await n.connect({deviceId:"device-1",onDisconnect:o})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(r.isLeft()).toBe(!0),expect(o).toHaveBeenCalledWith("device-1")}),it("returns an empty data buffer when the response is only a status word",async()=>{const e=vi.fn(()=>Promise.resolve({response:"9000"})),s=await(await t(e).connect({deviceId:"device-1",onDisconnect:vi.fn()})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(s.isRight()).toBe(!0);const r=s.unsafeCoerce();expect(Array.from(r.data)).toEqual([]),expect(Array.from(r.statusCode)).toEqual([144,0])}),it("falls back to an empty status code when the status word hex is invalid",async()=>{const e=vi.fn(()=>Promise.resolve({response:"aabbXXYY"})),s=await(await t(e).connect({deviceId:"device-1",onDisconnect:vi.fn()})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(s.isRight()).toBe(!0);const r=s.unsafeCoerce();expect(Array.from(r.statusCode)).toEqual([]),expect(Array.from(r.data)).toEqual([170,187])}),it("falls back to an empty data buffer when the data hex is invalid",async()=>{const e=vi.fn(()=>Promise.resolve({response:"ZZZZ9000"})),s=await(await t(e).connect({deviceId:"device-1",onDisconnect:vi.fn()})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(s.isRight()).toBe(!0);const r=s.unsafeCoerce();expect(Array.from(r.data)).toEqual([]),expect(Array.from(r.statusCode)).toEqual([144,0])})}),describe("disconnect polling",()=>{beforeEach(()=>{vi.useFakeTimers()}),afterEach(()=>{vi.useRealTimers()});const t=async e=>{d({connect:vi.fn(()=>Promise.resolve({device:a(),connected:!0})),disconnect:vi.fn(()=>Promise.resolve(!0)),listDevices:e});const n=(0,c.mockserverTransportFactory)("http://localhost:8080")(i),o=vi.fn(),s=(await n.connect({deviceId:"device-1",onDisconnect:o})).unsafeCoerce();return{transport:n,connected:s,onDisconnect:o}};it("calls onDisconnect once the device is gone from the mock server",async()=>{const e=vi.fn(()=>Promise.resolve([a()])),{onDisconnect:n}=await t(e);await vi.advanceTimersByTimeAsync(1e3),expect(n).not.toHaveBeenCalled(),e.mockResolvedValue([]),await vi.advanceTimersByTimeAsync(1e3),expect(n).toHaveBeenCalledWith("device-1")}),it("calls onDisconnect when the server reports the device as disconnected",async()=>{const e=vi.fn(()=>Promise.resolve([a({connected:!1})])),{onDisconnect:n}=await t(e);await vi.advanceTimersByTimeAsync(1e3),expect(n).toHaveBeenCalledWith("device-1")}),it("keeps the device connected while the mock server is unreachable",async()=>{const e=vi.fn(()=>Promise.reject(new Error("offline"))),{onDisconnect:n}=await t(e);await vi.advanceTimersByTimeAsync(3e3),expect(n).not.toHaveBeenCalled()}),it("reports the disconnect only once",async()=>{const e=vi.fn(()=>Promise.resolve([])),{onDisconnect:n}=await t(e);await vi.advanceTimersByTimeAsync(3e3),expect(n).toHaveBeenCalledTimes(1)}),it("stops polling after an explicit disconnect",async()=>{const e=vi.fn(()=>Promise.resolve([a()])),{transport:n,connected:o,onDisconnect:s}=await t(e);await n.disconnect({connectedDevice:o}),e.mockResolvedValue([]),await vi.advanceTimersByTimeAsync(3e3),expect(s).not.toHaveBeenCalled()}),it("stays silent when a request in flight answers after a disconnect",async()=>{let e=v=>{};const n=vi.fn(()=>new Promise(v=>{e=v})),{transport:o,connected:s,onDisconnect:r}=await t(n);await vi.advanceTimersByTimeAsync(1e3),await o.disconnect({connectedDevice:s}),e([]),await vi.advanceTimersByTimeAsync(0),expect(r).not.toHaveBeenCalled()}),it("reports once when two polls overlap",async()=>{const e=[],n=vi.fn(()=>new Promise(s=>{e.push(s)})),{onDisconnect:o}=await t(n);await vi.advanceTimersByTimeAsync(2e3),expect(e).toHaveLength(2),e.forEach(s=>s([])),await vi.advanceTimersByTimeAsync(0),expect(o).toHaveBeenCalledTimes(1)})}),describe("mockserverTransportFactory",()=>{it("uses the config mock url when no explicit url is provided",()=>{const t=d();expect(t).toBeDefined(),(0,c.mockserverTransportFactory)()(i),expect(u.MockClient).toHaveBeenCalledWith("http://localhost:8080",expect.anything())}),it("prefers an explicit url over the config mock url",()=>{d(),(0,c.mockserverTransportFactory)("https://explicit:9090")(i),expect(u.MockClient).toHaveBeenCalledWith("https://explicit:9090",expect.anything())})})});
2
+ //# sourceMappingURL=MockserverTransport.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/MockserverTransport.test.ts"],
4
+ "sourcesContent": ["import {\n type LoggerPublisherService,\n OpeningConnectionError,\n type TransportConnectedDevice,\n} from \"@ledgerhq/device-management-kit\";\nimport { type Device, MockClient } from \"@ledgerhq/device-mockserver-client\";\nimport { firstValueFrom, take, toArray } from \"rxjs\";\n\nimport {\n mockserverIdentifier,\n mockserverTransportFactory,\n} from \"./MockserverTransport\";\n\nvi.mock(\"@ledgerhq/device-mockserver-client\", () => ({\n MockClient: vi.fn(),\n}));\n\ntype TransportArgs = Parameters<\n ReturnType<typeof mockserverTransportFactory>\n>[0];\n\nconst loggerServiceFactory = (): LoggerPublisherService =>\n ({\n debug: vi.fn(),\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n }) as unknown as LoggerPublisherService;\n\nconst transportArgs = {\n config: { mockUrl: \"http://localhost:8080\" },\n loggerServiceFactory,\n} as unknown as TransportArgs;\n\n/** Mirrors the transport's own discovery interval. */\nconst POLL_INTERVAL_MS = 1000;\n\nconst aDevice = (overrides: Partial<Device> = {}): Device => ({\n id: \"device-1\",\n name: \"Ledger Nano X\",\n device_type: \"nanoX\",\n connectivity_type: \"USB\",\n ...overrides,\n});\n\nconst mockListDevices = (\n impl: () => Promise<Device[]>,\n): ReturnType<typeof vi.fn> => {\n const listDevices = vi.fn(impl);\n vi.mocked(MockClient).mockImplementation(\n () => ({ listDevices }) as unknown as MockClient,\n );\n return listDevices;\n};\n\nconst mockClientImpl = (overrides: Partial<Record<string, unknown>> = {}) => {\n const client = {\n listDevices: vi.fn(() => Promise.resolve([])),\n connect: vi.fn(),\n disconnect: vi.fn(),\n sendApdu: vi.fn(),\n ...overrides,\n };\n vi.mocked(MockClient).mockImplementation(\n () => client as unknown as MockClient,\n );\n return client;\n};\n\ndescribe(\"mockserverTransportFactory\", () => {\n beforeEach(() => {\n vi.clearAllMocks();\n mockListDevices(() => Promise.resolve([]));\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n it(\"builds a supported transport with the mockserver identifier\", () => {\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n expect(transport.getIdentifier()).toBe(mockserverIdentifier);\n expect(transport.isSupported()).toBe(true);\n });\n\n it(\"accepts an injected session token without throwing\", () => {\n expect(() =>\n mockserverTransportFactory(\n \"http://localhost:8080\",\n \"tok-123\",\n )(transportArgs),\n ).not.toThrow();\n });\n\n describe(\"listenToAvailableDevices\", () => {\n it(\"emits the mock server devices mapped as discovered devices\", async () => {\n mockListDevices(() =>\n Promise.resolve([aDevice(), aDevice({ id: \"device-2\", name: \"Stax\" })]),\n );\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const devices = await firstValueFrom(\n transport.listenToAvailableDevices(),\n );\n\n expect(devices).toEqual([\n expect.objectContaining({\n id: \"device-1\",\n transport: mockserverIdentifier,\n }),\n expect.objectContaining({\n id: \"device-2\",\n transport: mockserverIdentifier,\n }),\n ]);\n });\n\n it(\"keeps polling so newly added devices appear\", async () => {\n vi.useFakeTimers();\n const listDevices = mockListDevices(() => Promise.resolve([]));\n listDevices.mockResolvedValueOnce([]).mockResolvedValueOnce([aDevice()]);\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const polled = firstValueFrom(\n transport.listenToAvailableDevices().pipe(take(2), toArray()),\n );\n await vi.advanceTimersByTimeAsync(POLL_INTERVAL_MS);\n const emissions = await polled;\n\n expect(emissions[0]).toEqual([]);\n expect(emissions[1]).toEqual([\n expect.objectContaining({ id: \"device-1\" }),\n ]);\n });\n\n it(\"emits an empty list when the mock server is unreachable\", async () => {\n mockListDevices(() => Promise.reject(new Error(\"offline\")));\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const devices = await firstValueFrom(\n transport.listenToAvailableDevices(),\n );\n\n expect(devices).toEqual([]);\n });\n\n it(\"keeps polling after a failed request\", async () => {\n vi.useFakeTimers();\n const listDevices = mockListDevices(() => Promise.resolve([]));\n listDevices\n .mockRejectedValueOnce(new Error(\"offline\"))\n .mockResolvedValueOnce([aDevice()]);\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const polled = firstValueFrom(\n transport.listenToAvailableDevices().pipe(take(2), toArray()),\n );\n await vi.advanceTimersByTimeAsync(POLL_INTERVAL_MS);\n const emissions = await polled;\n\n expect(emissions[0]).toEqual([]);\n expect(emissions[1]).toEqual([\n expect.objectContaining({ id: \"device-1\" }),\n ]);\n });\n });\n\n describe(\"startDiscovering\", () => {\n it(\"emits each device returned by the mock server\", async () => {\n mockClientImpl({\n listDevices: vi.fn(() =>\n Promise.resolve([\n aDevice(),\n aDevice({ id: \"device-2\", name: \"Stax\" }),\n ]),\n ),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const devices = await firstValueFrom(\n transport.startDiscovering().pipe(toArray()),\n );\n\n expect(devices).toEqual([\n expect.objectContaining({ id: \"device-1\" }),\n expect.objectContaining({ id: \"device-2\" }),\n ]);\n });\n });\n\n describe(\"stopDiscovering\", () => {\n it(\"does nothing and does not throw\", () => {\n mockClientImpl();\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n expect(() => transport.stopDiscovering()).not.toThrow();\n });\n });\n\n describe(\"connect\", () => {\n it(\"returns a connected device on success\", async () => {\n mockClientImpl({\n connect: vi.fn(() =>\n Promise.resolve({\n device: aDevice({ masks: [0x12345678] }),\n connected: true,\n }),\n ),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n const onDisconnect = vi.fn();\n\n const result = await transport.connect({\n deviceId: \"device-1\",\n onDisconnect,\n });\n\n expect(result.isRight()).toBe(true);\n const connected = result.unsafeCoerce();\n expect(connected.id).toBe(\"device-1\");\n expect(connected.transport).toBe(mockserverIdentifier);\n expect(connected.deviceModel.masks).toEqual([0x12345678]);\n });\n\n it(\"falls back to the default masks when the device has none\", async () => {\n mockClientImpl({\n connect: vi.fn(() =>\n Promise.resolve({ device: aDevice(), connected: true }),\n ),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.connect({\n deviceId: \"device-1\",\n onDisconnect: vi.fn(),\n });\n\n expect(result.unsafeCoerce().deviceModel.masks).toEqual([0x31100000]);\n // A known model (nanoX) reports its real block size, not the legacy default.\n expect(\n result.unsafeCoerce().deviceModel.getBlockSize({\n firmwareVersion: \"1.0.0\",\n }),\n ).toBe(4 * 1024);\n });\n\n it(\"returns an OpeningConnectionError when the client throws\", async () => {\n mockClientImpl({\n connect: vi.fn(() => Promise.reject(new Error(\"nope\"))),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.connect({\n deviceId: \"device-1\",\n onDisconnect: vi.fn(),\n });\n\n expect(result.isLeft()).toBe(true);\n expect(result.swap().unsafeCoerce()).toBeInstanceOf(\n OpeningConnectionError,\n );\n });\n });\n\n describe(\"disconnect\", () => {\n const connectedDevice = {\n id: \"device-1\",\n } as TransportConnectedDevice;\n\n it(\"returns Right(undefined) when the client disconnects successfully\", async () => {\n const disconnect = vi.fn(() => Promise.resolve(true));\n mockClientImpl({ disconnect });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.disconnect({ connectedDevice });\n\n expect(disconnect).toHaveBeenCalledWith(\"device-1\");\n expect(result.isRight()).toBe(true);\n });\n\n it(\"returns a DisconnectError when the client reports failure\", async () => {\n mockClientImpl({ disconnect: vi.fn(() => Promise.resolve(false)) });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.disconnect({ connectedDevice });\n\n expect(result.isLeft()).toBe(true);\n });\n\n it(\"returns a DisconnectError when the client throws\", async () => {\n mockClientImpl({\n disconnect: vi.fn(() => Promise.reject(new Error(\"boom\"))),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.disconnect({ connectedDevice });\n\n expect(result.isLeft()).toBe(true);\n });\n });\n\n describe(\"sendApdu\", () => {\n const buildConnectedTransport = (sendApdu: ReturnType<typeof vi.fn>) => {\n mockClientImpl({\n connect: vi.fn(() =>\n Promise.resolve({ device: aDevice(), connected: true }),\n ),\n sendApdu,\n });\n return mockserverTransportFactory(\"http://localhost:8080\")(transportArgs);\n };\n\n it(\"splits the response into data and status code\", async () => {\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"aabb9000\" }));\n const transport = buildConnectedTransport(sendApdu);\n const onDisconnect = vi.fn();\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isRight()).toBe(true);\n const apduResponse = result.unsafeCoerce();\n expect(Array.from(apduResponse.data)).toEqual([0xaa, 0xbb]);\n expect(Array.from(apduResponse.statusCode)).toEqual([0x90, 0x00]);\n expect(onDisconnect).not.toHaveBeenCalled();\n });\n\n it(\"calls onDisconnect and returns a NoAccessibleDeviceError on failure\", async () => {\n const sendApdu = vi.fn(() => Promise.reject(new Error(\"device lost\")));\n const transport = buildConnectedTransport(sendApdu);\n const onDisconnect = vi.fn();\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isLeft()).toBe(true);\n expect(onDisconnect).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"returns an empty data buffer when the response is only a status word\", async () => {\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"9000\" }));\n const transport = buildConnectedTransport(sendApdu);\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect: vi.fn() })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isRight()).toBe(true);\n const apduResponse = result.unsafeCoerce();\n expect(Array.from(apduResponse.data)).toEqual([]);\n expect(Array.from(apduResponse.statusCode)).toEqual([0x90, 0x00]);\n });\n\n it(\"falls back to an empty status code when the status word hex is invalid\", async () => {\n // slice(-4) = \"XXYY\" is not valid hex -> hexaStringToBuffer returns null.\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"aabbXXYY\" }));\n const transport = buildConnectedTransport(sendApdu);\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect: vi.fn() })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isRight()).toBe(true);\n const apduResponse = result.unsafeCoerce();\n expect(Array.from(apduResponse.statusCode)).toEqual([]);\n expect(Array.from(apduResponse.data)).toEqual([0xaa, 0xbb]);\n });\n\n it(\"falls back to an empty data buffer when the data hex is invalid\", async () => {\n // slice(0, -4) = \"ZZZZ\" is not valid hex -> hexaStringToBuffer returns null.\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"ZZZZ9000\" }));\n const transport = buildConnectedTransport(sendApdu);\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect: vi.fn() })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isRight()).toBe(true);\n const apduResponse = result.unsafeCoerce();\n expect(Array.from(apduResponse.data)).toEqual([]);\n expect(Array.from(apduResponse.statusCode)).toEqual([0x90, 0x00]);\n });\n });\n\n describe(\"disconnect polling\", () => {\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n const connectTo = async (listDevices: ReturnType<typeof vi.fn>) => {\n mockClientImpl({\n connect: vi.fn(() =>\n Promise.resolve({ device: aDevice(), connected: true }),\n ),\n disconnect: vi.fn(() => Promise.resolve(true)),\n listDevices,\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n const onDisconnect = vi.fn();\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect })\n ).unsafeCoerce();\n return { transport, connected, onDisconnect };\n };\n\n it(\"calls onDisconnect once the device is gone from the mock server\", async () => {\n const listDevices = vi.fn(() => Promise.resolve([aDevice()]));\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(1000);\n expect(onDisconnect).not.toHaveBeenCalled();\n\n listDevices.mockResolvedValue([]);\n await vi.advanceTimersByTimeAsync(1000);\n\n expect(onDisconnect).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"calls onDisconnect when the server reports the device as disconnected\", async () => {\n const listDevices = vi.fn(() =>\n Promise.resolve([aDevice({ connected: false })]),\n );\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(1000);\n\n expect(onDisconnect).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"keeps the device connected while the mock server is unreachable\", async () => {\n const listDevices = vi.fn(() => Promise.reject(new Error(\"offline\")));\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(3000);\n\n expect(onDisconnect).not.toHaveBeenCalled();\n });\n\n it(\"reports the disconnect only once\", async () => {\n const listDevices = vi.fn(() => Promise.resolve([]));\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(3000);\n\n expect(onDisconnect).toHaveBeenCalledTimes(1);\n });\n\n it(\"stops polling after an explicit disconnect\", async () => {\n const listDevices = vi.fn(() => Promise.resolve([aDevice()]));\n const { transport, connected, onDisconnect } =\n await connectTo(listDevices);\n\n await transport.disconnect({ connectedDevice: connected });\n listDevices.mockResolvedValue([]);\n await vi.advanceTimersByTimeAsync(3000);\n\n expect(onDisconnect).not.toHaveBeenCalled();\n });\n\n it(\"stays silent when a request in flight answers after a disconnect\", async () => {\n // Clearing the interval cannot cancel a request already sent.\n let answer = (_: Device[]) => {};\n const listDevices = vi.fn(\n () =>\n new Promise<Device[]>((resolve) => {\n answer = resolve;\n }),\n );\n const { transport, connected, onDisconnect } =\n await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(1000);\n await transport.disconnect({ connectedDevice: connected });\n answer([]);\n await vi.advanceTimersByTimeAsync(0);\n\n expect(onDisconnect).not.toHaveBeenCalled();\n });\n\n it(\"reports once when two polls overlap\", async () => {\n // A request slower than the interval leaves two in flight at once.\n const answers: ((devices: Device[]) => void)[] = [];\n const listDevices = vi.fn(\n () =>\n new Promise<Device[]>((resolve) => {\n answers.push(resolve);\n }),\n );\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(2000);\n expect(answers).toHaveLength(2);\n answers.forEach((answer) => answer([]));\n await vi.advanceTimersByTimeAsync(0);\n\n expect(onDisconnect).toHaveBeenCalledTimes(1);\n });\n });\n\n describe(\"mockserverTransportFactory\", () => {\n it(\"uses the config mock url when no explicit url is provided\", () => {\n const client = mockClientImpl();\n expect(client).toBeDefined();\n\n mockserverTransportFactory()(transportArgs);\n\n expect(MockClient).toHaveBeenCalledWith(\n \"http://localhost:8080\",\n expect.anything(),\n );\n });\n\n it(\"prefers an explicit url over the config mock url\", () => {\n mockClientImpl();\n\n mockserverTransportFactory(\"https://explicit:9090\")(transportArgs);\n\n expect(MockClient).toHaveBeenCalledWith(\n \"https://explicit:9090\",\n expect.anything(),\n );\n });\n });\n});\n"],
5
+ "mappings": "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",
6
+ "names": ["import_device_management_kit", "import_device_mockserver_client", "import_rxjs", "import_MockserverTransport", "loggerServiceFactory", "transportArgs", "POLL_INTERVAL_MS", "aDevice", "overrides", "mockListDevices", "impl", "listDevices", "mockClientImpl", "client", "transport", "devices", "polled", "emissions", "onDisconnect", "result", "connected", "connectedDevice", "disconnect", "buildConnectedTransport", "sendApdu", "apduResponse", "connectTo", "answer", "_", "resolve", "answers"]
7
+ }
@@ -32,7 +32,7 @@
32
32
  "license": "Apache-2.0",
33
33
  "name": "@ledgerhq/device-transport-kit-mockserver",
34
34
  "peerDependencies": {
35
- "@ledgerhq/device-management-kit": "workspace:^",
35
+ "@ledgerhq/device-management-kit": "^1.9.0",
36
36
  "rxjs": "catalog:"
37
37
  },
38
38
  "private": false,
@@ -56,5 +56,5 @@
56
56
  "watch:builds": "pnpm ldmk-tool watch --entryPoints src/index.ts,src/**/*.ts --tsconfig tsconfig.prod.json",
57
57
  "watch:types": "concurrently \"tsc --watch -p tsconfig.prod.json\" \"tsc-alias --watch -p tsconfig.prod.json\""
58
58
  },
59
- "version": "1.0.2"
59
+ "version": "1.1.1"
60
60
  }
@@ -1,2 +1,2 @@
1
- import{DisconnectError as p,formatApduReceivedLog as g,formatApduSentLog as u,NoAccessibleDeviceError as l,OpeningConnectionError as v}from"@ledgerhq/device-management-kit";import{MockClient as m}from"@ledgerhq/device-mockserver-client";import{Left as i,Right as s}from"purify-ts";import{from as a,mergeMap as y}from"rxjs";const D="MOCKSERVER";class h{logger;mockClient;identifier=D;constructor(e,r){this.logger=e("MockTransport"),this.mockClient=new m(r.mockUrl)}isSupported(){return!0}getIdentifier(){return this.identifier}listenToAvailableDevices(){return a([])}startDiscovering(){return this.logger.debug("startDiscovering"),a(this.mockClient.scan().then(e=>e.map(r=>({id:r.id,deviceModel:{id:r.device_type,productName:r.name,usbProductId:16,bootloaderUsbProductId:1,getBlockSize(){return 32},usbOnly:!0,memorySize:320*1024,masks:[823132160]},transport:this.identifier})))).pipe(y(e=>e))}stopDiscovering(){this.logger.debug("stopDiscovering")}async connect(e){this.logger.debug("connect");const r=e.deviceId;try{const t=await this.mockClient.connect(r),o={sendApdu:n=>this.sendApdu(r,e.deviceId,e.onDisconnect,n),deviceModel:{id:t.device.device_type,productName:t.device.name,usbProductId:16,legacyUsbProductId:1,bootloaderUsbProductId:16,getBlockSize(){return 32},usbOnly:!0,memorySize:320*1024,masks:[823132160]},id:e.deviceId,type:t.device.connectivity_type,transport:this.identifier};return s(o)}catch(t){return i(new v(t))}}async disconnect(e){this.logger.debug("disconnect");const r=e.connectedDevice.id;try{return await this.mockClient.disconnect(r)?s(void 0):i(new p(new Error(`Failed to disconnect ${r}`)))}catch(t){return i(new p(t))}}async sendApdu(e,r,t,o){this.logger.debug("send");try{const n=await this.mockClient.send(e,o);this.logger.debug(u(o));const d={statusCode:this.mockClient.fromHexString(n.response.substring(n.response.length-4,n.response.length)),data:this.mockClient.fromHexString(n.response.substring(0,n.response.length-4))};return this.logger.debug(g(d)),s(d)}catch(n){return t(r),i(new l(n))}}}const I=({config:c,loggerServiceFactory:e})=>new h(e,c);export{h as MockTransport,D as mockserverIdentifier,I as mockserverTransportFactory};
1
+ import{DisconnectError as a,formatApduReceivedLog as D,formatApduSentLog as g,hexaStringToBuffer as p,NoAccessibleDeviceError as u,OpeningConnectionError as h,StaticDeviceModelDataSource as m}from"@ledgerhq/device-management-kit";import{MockClient as y}from"@ledgerhq/device-mockserver-client";import{Left as s,Right as c}from"purify-ts";import{catchError as f,from as d,map as v,mergeMap as T,of as b,switchMap as k,timer as C}from"rxjs";const E="MOCKSERVER",I=[823132160],M=new m,A=1e3,P=1e3;class S{logger;mockClient;identifier=E;disconnectPolls=new Map;constructor(e,t,r){this.logger=e("MockTransport"),this.mockClient=new y(t,{token:r})}isSupported(){return!0}getIdentifier(){return this.identifier}listenToAvailableDevices(){return this.logger.debug("listenToAvailableDevices"),C(0,A).pipe(k(()=>d(this.mockClient.listDevices()).pipe(v(e=>this.mapToDiscoveredDevices(e)),f(e=>(this.logger.error("listenToAvailableDevices failed",{data:{error:e}}),b([]))))))}startDiscovering(){return this.logger.debug("startDiscovering"),d(this.mockClient.listDevices()).pipe(v(e=>this.mapToDiscoveredDevices(e)),T(e=>d(e)))}stopDiscovering(){this.logger.debug("stopDiscovering")}async connect(e){this.logger.debug("connect");const t=e.deviceId;try{const{device:r}=await this.mockClient.connect(t),o={sendApdu:n=>this.sendApdu(t,e.deviceId,e.onDisconnect,n),deviceModel:this.buildDeviceModel(r),id:e.deviceId,type:r.connectivity_type,transport:this.identifier};return this.listenForDisconnect(t,e.onDisconnect),c(o)}catch(r){return s(new h(r))}}async disconnect(e){this.logger.debug("disconnect");const t=e.connectedDevice.id;this.clearDisconnectPoll(t);try{return await this.mockClient.disconnect(t)?c(void 0):s(new a(new Error(`Failed to disconnect ${t}`)))}catch(r){return s(new a(r))}}async sendApdu(e,t,r,o){this.logger.debug("send");try{const n=await this.mockClient.sendApdu(e,o);this.logger.debug(g(o));const i={statusCode:p(n.response.slice(-4))??new Uint8Array,data:p(n.response.slice(0,-4))??new Uint8Array};return this.logger.debug(D(i)),c(i)}catch(n){return this.clearDisconnectPoll(t),r(t),s(new u(n))}}listenForDisconnect(e,t){this.clearDisconnectPoll(e);const r=setInterval(()=>{this.mockClient.listDevices().then(o=>{this.disconnectPolls.get(e)!==r||o.some(i=>i.id===e&&i.connected!==!1)||(this.logger.info(`Device ${e} is no longer connected, disconnecting`),this.clearDisconnectPoll(e),t(e))}).catch(o=>{this.logger.error("disconnect poll failed",{data:{error:o}})})},P);this.disconnectPolls.set(e,r)}clearDisconnectPoll(e){const t=this.disconnectPolls.get(e);t!==void 0&&(clearInterval(t),this.disconnectPolls.delete(e))}mapToDiscoveredDevices(e){return e.map(t=>({id:t.id,deviceModel:this.buildDeviceModel(t),transport:this.identifier}))}buildDeviceModel(e){const t=e.device_type,r=M.getAllDeviceModels().find(o=>o.id===t);return{id:t,productName:e.name,usbProductId:16,legacyUsbProductId:1,bootloaderUsbProductId:1,getBlockSize:r?r.getBlockSize:()=>32,usbOnly:!0,memorySize:r?.memorySize??320*1024,masks:e.masks??I}}}const _=(l,e)=>({config:t,loggerServiceFactory:r})=>new S(r,l??t.mockUrl,e);export{S as MockTransport,E as mockserverIdentifier,_ as mockserverTransportFactory};
2
2
  //# sourceMappingURL=MockserverTransport.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/MockserverTransport.ts"],
4
- "sourcesContent": ["import {\n type ApduResponse,\n type ConnectError,\n type DeviceId,\n type DeviceModelId,\n DisconnectError,\n type DisconnectHandler,\n type DmkConfig,\n type DmkError,\n formatApduReceivedLog,\n formatApduSentLog,\n type LoggerPublisherService,\n NoAccessibleDeviceError,\n OpeningConnectionError,\n type Transport,\n type TransportConnectedDevice,\n type TransportDiscoveredDevice,\n type TransportFactory,\n type TransportIdentifier,\n} from \"@ledgerhq/device-management-kit\";\nimport {\n type CommandResponse,\n type Device,\n MockClient,\n type Session,\n} from \"@ledgerhq/device-mockserver-client\";\nimport { type Either, Left, Right } from \"purify-ts\";\nimport { from, mergeMap, type Observable } from \"rxjs\";\n\nexport const mockserverIdentifier: TransportIdentifier = \"MOCKSERVER\";\n\nexport class MockTransport implements Transport {\n private logger: LoggerPublisherService;\n private mockClient: MockClient;\n private readonly identifier: TransportIdentifier = mockserverIdentifier;\n\n constructor(\n loggerServiceFactory: (tag: string) => LoggerPublisherService,\n config: DmkConfig,\n ) {\n this.logger = loggerServiceFactory(\"MockTransport\");\n this.mockClient = new MockClient(config.mockUrl);\n }\n\n isSupported(): boolean {\n return true;\n }\n\n getIdentifier(): TransportIdentifier {\n return this.identifier;\n }\n\n listenToAvailableDevices(): Observable<TransportDiscoveredDevice[]> {\n return from([]);\n }\n\n startDiscovering(): Observable<TransportDiscoveredDevice> {\n this.logger.debug(\"startDiscovering\");\n return from(\n this.mockClient.scan().then((devices: Device[]) => {\n return devices.map((device: Device) => ({\n id: device.id,\n deviceModel: {\n id: device.device_type as DeviceModelId,\n productName: device.name,\n usbProductId: 0x10,\n bootloaderUsbProductId: 0x0001,\n getBlockSize() {\n return 32;\n },\n usbOnly: true,\n memorySize: 320 * 1024,\n masks: [0x31100000],\n },\n transport: this.identifier,\n }));\n }),\n ).pipe(mergeMap((device) => device));\n }\n\n stopDiscovering(): void {\n //DO NOTHING HERE\n this.logger.debug(\"stopDiscovering\");\n }\n\n async connect(params: {\n deviceId: DeviceId;\n onDisconnect: DisconnectHandler;\n }): Promise<Either<ConnectError, TransportConnectedDevice>> {\n this.logger.debug(\"connect\");\n const sessionId: string = params.deviceId;\n try {\n const session: Session = await this.mockClient.connect(sessionId);\n const connectedDevice = {\n sendApdu: (apdu) => {\n return this.sendApdu(\n sessionId,\n params.deviceId,\n params.onDisconnect,\n apdu,\n );\n },\n deviceModel: {\n id: session.device.device_type,\n productName: session.device.name,\n usbProductId: 0x10,\n legacyUsbProductId: 0x0001,\n bootloaderUsbProductId: 0x10,\n getBlockSize() {\n return 32;\n },\n usbOnly: true,\n memorySize: 320 * 1024,\n masks: [0x31100000],\n },\n id: params.deviceId,\n type: session.device.connectivity_type,\n transport: this.identifier,\n } as TransportConnectedDevice;\n return Right(connectedDevice);\n } catch (error) {\n return Left(new OpeningConnectionError(error as Error));\n }\n }\n\n async disconnect(params: {\n connectedDevice: TransportConnectedDevice;\n }): Promise<Either<DmkError, void>> {\n this.logger.debug(\"disconnect\");\n const sessionId: string = params.connectedDevice.id;\n try {\n const success: boolean = await this.mockClient.disconnect(sessionId);\n if (!success) {\n return Left(\n new DisconnectError(new Error(`Failed to disconnect ${sessionId}`)),\n );\n }\n return Right(undefined);\n } catch (error) {\n return Left(new DisconnectError(error as Error));\n }\n }\n\n async sendApdu(\n sessionId: string,\n deviceId: DeviceId,\n onDisconnect: DisconnectHandler,\n apdu: Uint8Array,\n ): Promise<Either<DmkError, ApduResponse>> {\n this.logger.debug(\"send\");\n try {\n const response: CommandResponse = await this.mockClient.send(\n sessionId,\n apdu,\n );\n this.logger.debug(formatApduSentLog(apdu));\n const apduResponse = {\n statusCode: this.mockClient.fromHexString(\n response.response.substring(\n response.response.length - 4,\n response.response.length,\n ),\n ),\n data: this.mockClient.fromHexString(\n response.response.substring(0, response.response.length - 4),\n ),\n } as ApduResponse;\n this.logger.debug(formatApduReceivedLog(apduResponse));\n return Right(apduResponse);\n } catch (error) {\n onDisconnect(deviceId);\n return Left(new NoAccessibleDeviceError(error as Error));\n }\n }\n}\n\nexport const mockserverTransportFactory: TransportFactory = ({\n config,\n loggerServiceFactory,\n}) => new MockTransport(loggerServiceFactory, config);\n"],
5
- "mappings": "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",
6
- "names": ["DisconnectError", "formatApduReceivedLog", "formatApduSentLog", "NoAccessibleDeviceError", "OpeningConnectionError", "MockClient", "Left", "Right", "from", "mergeMap", "mockserverIdentifier", "MockTransport", "loggerServiceFactory", "config", "devices", "device", "params", "sessionId", "session", "connectedDevice", "apdu", "error", "deviceId", "onDisconnect", "response", "apduResponse", "mockserverTransportFactory"]
4
+ "sourcesContent": ["import {\n type ApduResponse,\n type ConnectError,\n type DeviceId,\n type DeviceModelId,\n DisconnectError,\n type DisconnectHandler,\n type DmkError,\n formatApduReceivedLog,\n formatApduSentLog,\n hexaStringToBuffer,\n type LoggerPublisherService,\n NoAccessibleDeviceError,\n OpeningConnectionError,\n StaticDeviceModelDataSource,\n type Transport,\n type TransportConnectedDevice,\n type TransportDiscoveredDevice,\n type TransportFactory,\n type TransportIdentifier,\n} from \"@ledgerhq/device-management-kit\";\nimport {\n type CommandResponse,\n type Device,\n MockClient,\n} from \"@ledgerhq/device-mockserver-client\";\nimport { type Either, Left, Right } from \"purify-ts\";\nimport {\n catchError,\n from,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n timer,\n} from \"rxjs\";\n\nexport const mockserverIdentifier: TransportIdentifier = \"MOCKSERVER\";\n\nconst DEFAULT_MASKS = [0x31100000];\n\n/**\n * Real per-model memory constants (memory size, block size, masks) so mock\n * devices report the same values as physical ones. Without this, memory-aware\n * device actions (e.g. install/update apps, which run {@link PredictOutOfMemoryTask})\n * compute against wrong constants and wrongly report out-of-memory.\n */\nconst deviceModelDataSource = new StaticDeviceModelDataSource();\n\n/** Interval (ms) at which the mock server is polled for available devices. */\nconst DISCOVERY_POLL_INTERVAL_MS = 1000;\n\n/** Interval (ms) at which a connected device is polled for its liveness. */\nconst DISCONNECT_POLL_INTERVAL_MS = 1000;\n\nexport class MockTransport implements Transport {\n private logger: LoggerPublisherService;\n private mockClient: MockClient;\n private readonly identifier: TransportIdentifier = mockserverIdentifier;\n private readonly disconnectPolls = new Map<\n DeviceId,\n ReturnType<typeof setInterval>\n >();\n\n constructor(\n loggerServiceFactory: (tag: string) => LoggerPublisherService,\n mockUrl: string,\n sessionToken?: string,\n ) {\n this.logger = loggerServiceFactory(\"MockTransport\");\n this.mockClient = new MockClient(mockUrl, { token: sessionToken });\n }\n\n isSupported(): boolean {\n return true;\n }\n\n getIdentifier(): TransportIdentifier {\n return this.identifier;\n }\n\n listenToAvailableDevices(): Observable<TransportDiscoveredDevice[]> {\n this.logger.debug(\"listenToAvailableDevices\");\n return timer(0, DISCOVERY_POLL_INTERVAL_MS).pipe(\n switchMap(() =>\n // Recovery belongs to the request, not to the pipeline: a catchError\n // on the outer chain replaces the timer along with the failed poll, so\n // one unreachable moment would end discovery for the rest of the\n // subscription and no device added later could ever be found.\n from(this.mockClient.listDevices()).pipe(\n map((devices) => this.mapToDiscoveredDevices(devices)),\n catchError((error) => {\n this.logger.error(\"listenToAvailableDevices failed\", {\n data: { error },\n });\n return of<TransportDiscoveredDevice[]>([]);\n }),\n ),\n ),\n );\n }\n\n startDiscovering(): Observable<TransportDiscoveredDevice> {\n this.logger.debug(\"startDiscovering\");\n return from(this.mockClient.listDevices()).pipe(\n map((devices) => this.mapToDiscoveredDevices(devices)),\n mergeMap((devices) => from(devices)),\n );\n }\n\n stopDiscovering(): void {\n //DO NOTHING HERE\n this.logger.debug(\"stopDiscovering\");\n }\n\n async connect(params: {\n deviceId: DeviceId;\n onDisconnect: DisconnectHandler;\n }): Promise<Either<ConnectError, TransportConnectedDevice>> {\n this.logger.debug(\"connect\");\n const deviceId: string = params.deviceId;\n try {\n const { device } = await this.mockClient.connect(deviceId);\n const connectedDevice = {\n sendApdu: (apdu) =>\n this.sendApdu(deviceId, params.deviceId, params.onDisconnect, apdu),\n deviceModel: this.buildDeviceModel(device),\n id: params.deviceId,\n type: device.connectivity_type,\n transport: this.identifier,\n } as TransportConnectedDevice;\n this.listenForDisconnect(deviceId, params.onDisconnect);\n return Right(connectedDevice);\n } catch (error) {\n return Left(new OpeningConnectionError(error as Error));\n }\n }\n\n async disconnect(params: {\n connectedDevice: TransportConnectedDevice;\n }): Promise<Either<DmkError, void>> {\n this.logger.debug(\"disconnect\");\n const deviceId: string = params.connectedDevice.id;\n this.clearDisconnectPoll(deviceId);\n try {\n const success: boolean = await this.mockClient.disconnect(deviceId);\n if (!success) {\n return Left(\n new DisconnectError(new Error(`Failed to disconnect ${deviceId}`)),\n );\n }\n return Right(undefined);\n } catch (error) {\n return Left(new DisconnectError(error as Error));\n }\n }\n\n async sendApdu(\n deviceId: string,\n onDisconnectDeviceId: DeviceId,\n onDisconnect: DisconnectHandler,\n apdu: Uint8Array,\n ): Promise<Either<DmkError, ApduResponse>> {\n this.logger.debug(\"send\");\n try {\n const response: CommandResponse = await this.mockClient.sendApdu(\n deviceId,\n apdu,\n );\n this.logger.debug(formatApduSentLog(apdu));\n const apduResponse = {\n statusCode:\n hexaStringToBuffer(response.response.slice(-4)) ?? new Uint8Array(),\n data:\n hexaStringToBuffer(response.response.slice(0, -4)) ??\n new Uint8Array(),\n } as ApduResponse;\n this.logger.debug(formatApduReceivedLog(apduResponse));\n return Right(apduResponse);\n } catch (error) {\n this.clearDisconnectPoll(onDisconnectDeviceId);\n onDisconnect(onDisconnectDeviceId);\n return Left(new NoAccessibleDeviceError(error as Error));\n }\n }\n\n /**\n * A device can be deleted or disconnected through the API by any client, and\n * nothing announces it the way a physical unplug does. Without the signal,\n * DMK keeps a session that claims to be connected.\n */\n private listenForDisconnect(\n deviceId: DeviceId,\n onDisconnect: DisconnectHandler,\n ): void {\n this.clearDisconnectPoll(deviceId);\n const poll: ReturnType<typeof setInterval> = setInterval(() => {\n this.mockClient\n .listDevices()\n .then((devices) => {\n // clearInterval leaves a request in flight; only the poll still\n // registered may report.\n if (this.disconnectPolls.get(deviceId) !== poll) {\n return;\n }\n const present = devices.some(\n (device) => device.id === deviceId && device.connected !== false,\n );\n if (present) {\n return;\n }\n this.logger.info(\n `Device ${deviceId} is no longer connected, disconnecting`,\n );\n this.clearDisconnectPoll(deviceId);\n onDisconnect(deviceId);\n })\n .catch((error) => {\n // An unreachable server is not proof the device left; the next tick\n // decides.\n this.logger.error(\"disconnect poll failed\", { data: { error } });\n });\n }, DISCONNECT_POLL_INTERVAL_MS);\n this.disconnectPolls.set(deviceId, poll);\n }\n\n private clearDisconnectPoll(deviceId: DeviceId): void {\n const poll = this.disconnectPolls.get(deviceId);\n if (poll === undefined) {\n return;\n }\n clearInterval(poll);\n this.disconnectPolls.delete(deviceId);\n }\n\n private mapToDiscoveredDevices(\n devices: Device[],\n ): TransportDiscoveredDevice[] {\n return devices.map((device) => ({\n id: device.id,\n deviceModel: this.buildDeviceModel(device),\n transport: this.identifier,\n }));\n }\n\n private buildDeviceModel(device: Device) {\n const id = device.device_type as DeviceModelId;\n // Resolve the real model to mirror a physical device's memory constants;\n // fall back to legacy defaults for an unknown device type.\n const knownModel = deviceModelDataSource\n .getAllDeviceModels()\n .find((model) => model.id === id);\n return {\n id,\n productName: device.name,\n usbProductId: 0x10,\n legacyUsbProductId: 0x0001,\n bootloaderUsbProductId: 0x0001,\n getBlockSize: knownModel ? knownModel.getBlockSize : () => 32,\n usbOnly: true,\n memorySize: knownModel?.memorySize ?? 320 * 1024,\n masks: device.masks ?? DEFAULT_MASKS,\n };\n }\n}\n\n/**\n * Build a mock-server transport factory.\n *\n * @param mockUrl Optional mock server URL. When omitted, `config.mockUrl` from\n * the DMK configuration is used.\n * @param sessionToken Optional bearer token to share an existing session\n * When omitted, the client self-provisions a session via /auth.\n */\nexport const mockserverTransportFactory =\n (mockUrl?: string, sessionToken?: string): TransportFactory =>\n ({ config, loggerServiceFactory }) =>\n new MockTransport(\n loggerServiceFactory,\n mockUrl ?? config.mockUrl,\n sessionToken,\n );\n"],
5
+ "mappings": "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",
6
+ "names": ["DisconnectError", "formatApduReceivedLog", "formatApduSentLog", "hexaStringToBuffer", "NoAccessibleDeviceError", "OpeningConnectionError", "StaticDeviceModelDataSource", "MockClient", "Left", "Right", "catchError", "from", "map", "mergeMap", "of", "switchMap", "timer", "mockserverIdentifier", "DEFAULT_MASKS", "deviceModelDataSource", "DISCOVERY_POLL_INTERVAL_MS", "DISCONNECT_POLL_INTERVAL_MS", "MockTransport", "loggerServiceFactory", "mockUrl", "sessionToken", "devices", "error", "params", "deviceId", "device", "connectedDevice", "apdu", "onDisconnectDeviceId", "onDisconnect", "response", "apduResponse", "poll", "id", "knownModel", "model", "mockserverTransportFactory", "config"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ import{OpeningConnectionError as y}from"@ledgerhq/device-management-kit";import{MockClient as m}from"@ledgerhq/device-mockserver-client";import{firstValueFrom as v,take as h,toArray as f}from"rxjs";import{mockserverIdentifier as u,mockserverTransportFactory as s}from"./MockserverTransport";vi.mock("@ledgerhq/device-mockserver-client",()=>({MockClient:vi.fn()}));const w=()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}),i={config:{mockUrl:"http://localhost:8080"},loggerServiceFactory:w},x=1e3,a=(t={})=>({id:"device-1",name:"Ledger Nano X",device_type:"nanoX",connectivity_type:"USB",...t}),p=t=>{const e=vi.fn(t);return vi.mocked(m).mockImplementation(()=>({listDevices:e})),e},l=(t={})=>{const e={listDevices:vi.fn(()=>Promise.resolve([])),connect:vi.fn(),disconnect:vi.fn(),sendApdu:vi.fn(),...t};return vi.mocked(m).mockImplementation(()=>e),e};describe("mockserverTransportFactory",()=>{beforeEach(()=>{vi.clearAllMocks(),p(()=>Promise.resolve([]))}),afterEach(()=>{vi.useRealTimers()}),it("builds a supported transport with the mockserver identifier",()=>{const t=s("http://localhost:8080")(i);expect(t.getIdentifier()).toBe(u),expect(t.isSupported()).toBe(!0)}),it("accepts an injected session token without throwing",()=>{expect(()=>s("http://localhost:8080","tok-123")(i)).not.toThrow()}),describe("listenToAvailableDevices",()=>{it("emits the mock server devices mapped as discovered devices",async()=>{p(()=>Promise.resolve([a(),a({id:"device-2",name:"Stax"})]));const t=s("http://localhost:8080")(i),e=await v(t.listenToAvailableDevices());expect(e).toEqual([expect.objectContaining({id:"device-1",transport:u}),expect.objectContaining({id:"device-2",transport:u})])}),it("keeps polling so newly added devices appear",async()=>{vi.useFakeTimers(),p(()=>Promise.resolve([])).mockResolvedValueOnce([]).mockResolvedValueOnce([a()]);const e=s("http://localhost:8080")(i),n=v(e.listenToAvailableDevices().pipe(h(2),f()));await vi.advanceTimersByTimeAsync(x);const o=await n;expect(o[0]).toEqual([]),expect(o[1]).toEqual([expect.objectContaining({id:"device-1"})])}),it("emits an empty list when the mock server is unreachable",async()=>{p(()=>Promise.reject(new Error("offline")));const t=s("http://localhost:8080")(i),e=await v(t.listenToAvailableDevices());expect(e).toEqual([])}),it("keeps polling after a failed request",async()=>{vi.useFakeTimers(),p(()=>Promise.resolve([])).mockRejectedValueOnce(new Error("offline")).mockResolvedValueOnce([a()]);const e=s("http://localhost:8080")(i),n=v(e.listenToAvailableDevices().pipe(h(2),f()));await vi.advanceTimersByTimeAsync(x);const o=await n;expect(o[0]).toEqual([]),expect(o[1]).toEqual([expect.objectContaining({id:"device-1"})])})}),describe("startDiscovering",()=>{it("emits each device returned by the mock server",async()=>{l({listDevices:vi.fn(()=>Promise.resolve([a(),a({id:"device-2",name:"Stax"})]))});const t=s("http://localhost:8080")(i),e=await v(t.startDiscovering().pipe(f()));expect(e).toEqual([expect.objectContaining({id:"device-1"}),expect.objectContaining({id:"device-2"})])})}),describe("stopDiscovering",()=>{it("does nothing and does not throw",()=>{l();const t=s("http://localhost:8080")(i);expect(()=>t.stopDiscovering()).not.toThrow()})}),describe("connect",()=>{it("returns a connected device on success",async()=>{l({connect:vi.fn(()=>Promise.resolve({device:a({masks:[305419896]}),connected:!0}))});const t=s("http://localhost:8080")(i),e=vi.fn(),n=await t.connect({deviceId:"device-1",onDisconnect:e});expect(n.isRight()).toBe(!0);const o=n.unsafeCoerce();expect(o.id).toBe("device-1"),expect(o.transport).toBe(u),expect(o.deviceModel.masks).toEqual([305419896])}),it("falls back to the default masks when the device has none",async()=>{l({connect:vi.fn(()=>Promise.resolve({device:a(),connected:!0}))});const e=await s("http://localhost:8080")(i).connect({deviceId:"device-1",onDisconnect:vi.fn()});expect(e.unsafeCoerce().deviceModel.masks).toEqual([823132160]),expect(e.unsafeCoerce().deviceModel.getBlockSize({firmwareVersion:"1.0.0"})).toBe(4*1024)}),it("returns an OpeningConnectionError when the client throws",async()=>{l({connect:vi.fn(()=>Promise.reject(new Error("nope")))});const e=await s("http://localhost:8080")(i).connect({deviceId:"device-1",onDisconnect:vi.fn()});expect(e.isLeft()).toBe(!0),expect(e.swap().unsafeCoerce()).toBeInstanceOf(y)})}),describe("disconnect",()=>{const t={id:"device-1"};it("returns Right(undefined) when the client disconnects successfully",async()=>{const e=vi.fn(()=>Promise.resolve(!0));l({disconnect:e});const o=await s("http://localhost:8080")(i).disconnect({connectedDevice:t});expect(e).toHaveBeenCalledWith("device-1"),expect(o.isRight()).toBe(!0)}),it("returns a DisconnectError when the client reports failure",async()=>{l({disconnect:vi.fn(()=>Promise.resolve(!1))});const n=await s("http://localhost:8080")(i).disconnect({connectedDevice:t});expect(n.isLeft()).toBe(!0)}),it("returns a DisconnectError when the client throws",async()=>{l({disconnect:vi.fn(()=>Promise.reject(new Error("boom")))});const n=await s("http://localhost:8080")(i).disconnect({connectedDevice:t});expect(n.isLeft()).toBe(!0)})}),describe("sendApdu",()=>{const t=e=>(l({connect:vi.fn(()=>Promise.resolve({device:a(),connected:!0})),sendApdu:e}),s("http://localhost:8080")(i));it("splits the response into data and status code",async()=>{const e=vi.fn(()=>Promise.resolve({response:"aabb9000"})),n=t(e),o=vi.fn(),r=await(await n.connect({deviceId:"device-1",onDisconnect:o})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(r.isRight()).toBe(!0);const d=r.unsafeCoerce();expect(Array.from(d.data)).toEqual([170,187]),expect(Array.from(d.statusCode)).toEqual([144,0]),expect(o).not.toHaveBeenCalled()}),it("calls onDisconnect and returns a NoAccessibleDeviceError on failure",async()=>{const e=vi.fn(()=>Promise.reject(new Error("device lost"))),n=t(e),o=vi.fn(),r=await(await n.connect({deviceId:"device-1",onDisconnect:o})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(r.isLeft()).toBe(!0),expect(o).toHaveBeenCalledWith("device-1")}),it("returns an empty data buffer when the response is only a status word",async()=>{const e=vi.fn(()=>Promise.resolve({response:"9000"})),c=await(await t(e).connect({deviceId:"device-1",onDisconnect:vi.fn()})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(c.isRight()).toBe(!0);const r=c.unsafeCoerce();expect(Array.from(r.data)).toEqual([]),expect(Array.from(r.statusCode)).toEqual([144,0])}),it("falls back to an empty status code when the status word hex is invalid",async()=>{const e=vi.fn(()=>Promise.resolve({response:"aabbXXYY"})),c=await(await t(e).connect({deviceId:"device-1",onDisconnect:vi.fn()})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(c.isRight()).toBe(!0);const r=c.unsafeCoerce();expect(Array.from(r.statusCode)).toEqual([]),expect(Array.from(r.data)).toEqual([170,187])}),it("falls back to an empty data buffer when the data hex is invalid",async()=>{const e=vi.fn(()=>Promise.resolve({response:"ZZZZ9000"})),c=await(await t(e).connect({deviceId:"device-1",onDisconnect:vi.fn()})).unsafeCoerce().sendApdu(Uint8Array.from([224,1,0,0]));expect(c.isRight()).toBe(!0);const r=c.unsafeCoerce();expect(Array.from(r.data)).toEqual([]),expect(Array.from(r.statusCode)).toEqual([144,0])})}),describe("disconnect polling",()=>{beforeEach(()=>{vi.useFakeTimers()}),afterEach(()=>{vi.useRealTimers()});const t=async e=>{l({connect:vi.fn(()=>Promise.resolve({device:a(),connected:!0})),disconnect:vi.fn(()=>Promise.resolve(!0)),listDevices:e});const n=s("http://localhost:8080")(i),o=vi.fn(),c=(await n.connect({deviceId:"device-1",onDisconnect:o})).unsafeCoerce();return{transport:n,connected:c,onDisconnect:o}};it("calls onDisconnect once the device is gone from the mock server",async()=>{const e=vi.fn(()=>Promise.resolve([a()])),{onDisconnect:n}=await t(e);await vi.advanceTimersByTimeAsync(1e3),expect(n).not.toHaveBeenCalled(),e.mockResolvedValue([]),await vi.advanceTimersByTimeAsync(1e3),expect(n).toHaveBeenCalledWith("device-1")}),it("calls onDisconnect when the server reports the device as disconnected",async()=>{const e=vi.fn(()=>Promise.resolve([a({connected:!1})])),{onDisconnect:n}=await t(e);await vi.advanceTimersByTimeAsync(1e3),expect(n).toHaveBeenCalledWith("device-1")}),it("keeps the device connected while the mock server is unreachable",async()=>{const e=vi.fn(()=>Promise.reject(new Error("offline"))),{onDisconnect:n}=await t(e);await vi.advanceTimersByTimeAsync(3e3),expect(n).not.toHaveBeenCalled()}),it("reports the disconnect only once",async()=>{const e=vi.fn(()=>Promise.resolve([])),{onDisconnect:n}=await t(e);await vi.advanceTimersByTimeAsync(3e3),expect(n).toHaveBeenCalledTimes(1)}),it("stops polling after an explicit disconnect",async()=>{const e=vi.fn(()=>Promise.resolve([a()])),{transport:n,connected:o,onDisconnect:c}=await t(e);await n.disconnect({connectedDevice:o}),e.mockResolvedValue([]),await vi.advanceTimersByTimeAsync(3e3),expect(c).not.toHaveBeenCalled()}),it("stays silent when a request in flight answers after a disconnect",async()=>{let e=d=>{};const n=vi.fn(()=>new Promise(d=>{e=d})),{transport:o,connected:c,onDisconnect:r}=await t(n);await vi.advanceTimersByTimeAsync(1e3),await o.disconnect({connectedDevice:c}),e([]),await vi.advanceTimersByTimeAsync(0),expect(r).not.toHaveBeenCalled()}),it("reports once when two polls overlap",async()=>{const e=[],n=vi.fn(()=>new Promise(c=>{e.push(c)})),{onDisconnect:o}=await t(n);await vi.advanceTimersByTimeAsync(2e3),expect(e).toHaveLength(2),e.forEach(c=>c([])),await vi.advanceTimersByTimeAsync(0),expect(o).toHaveBeenCalledTimes(1)})}),describe("mockserverTransportFactory",()=>{it("uses the config mock url when no explicit url is provided",()=>{const t=l();expect(t).toBeDefined(),s()(i),expect(m).toHaveBeenCalledWith("http://localhost:8080",expect.anything())}),it("prefers an explicit url over the config mock url",()=>{l(),s("https://explicit:9090")(i),expect(m).toHaveBeenCalledWith("https://explicit:9090",expect.anything())})})});
2
+ //# sourceMappingURL=MockserverTransport.test.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/MockserverTransport.test.ts"],
4
+ "sourcesContent": ["import {\n type LoggerPublisherService,\n OpeningConnectionError,\n type TransportConnectedDevice,\n} from \"@ledgerhq/device-management-kit\";\nimport { type Device, MockClient } from \"@ledgerhq/device-mockserver-client\";\nimport { firstValueFrom, take, toArray } from \"rxjs\";\n\nimport {\n mockserverIdentifier,\n mockserverTransportFactory,\n} from \"./MockserverTransport\";\n\nvi.mock(\"@ledgerhq/device-mockserver-client\", () => ({\n MockClient: vi.fn(),\n}));\n\ntype TransportArgs = Parameters<\n ReturnType<typeof mockserverTransportFactory>\n>[0];\n\nconst loggerServiceFactory = (): LoggerPublisherService =>\n ({\n debug: vi.fn(),\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n }) as unknown as LoggerPublisherService;\n\nconst transportArgs = {\n config: { mockUrl: \"http://localhost:8080\" },\n loggerServiceFactory,\n} as unknown as TransportArgs;\n\n/** Mirrors the transport's own discovery interval. */\nconst POLL_INTERVAL_MS = 1000;\n\nconst aDevice = (overrides: Partial<Device> = {}): Device => ({\n id: \"device-1\",\n name: \"Ledger Nano X\",\n device_type: \"nanoX\",\n connectivity_type: \"USB\",\n ...overrides,\n});\n\nconst mockListDevices = (\n impl: () => Promise<Device[]>,\n): ReturnType<typeof vi.fn> => {\n const listDevices = vi.fn(impl);\n vi.mocked(MockClient).mockImplementation(\n () => ({ listDevices }) as unknown as MockClient,\n );\n return listDevices;\n};\n\nconst mockClientImpl = (overrides: Partial<Record<string, unknown>> = {}) => {\n const client = {\n listDevices: vi.fn(() => Promise.resolve([])),\n connect: vi.fn(),\n disconnect: vi.fn(),\n sendApdu: vi.fn(),\n ...overrides,\n };\n vi.mocked(MockClient).mockImplementation(\n () => client as unknown as MockClient,\n );\n return client;\n};\n\ndescribe(\"mockserverTransportFactory\", () => {\n beforeEach(() => {\n vi.clearAllMocks();\n mockListDevices(() => Promise.resolve([]));\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n it(\"builds a supported transport with the mockserver identifier\", () => {\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n expect(transport.getIdentifier()).toBe(mockserverIdentifier);\n expect(transport.isSupported()).toBe(true);\n });\n\n it(\"accepts an injected session token without throwing\", () => {\n expect(() =>\n mockserverTransportFactory(\n \"http://localhost:8080\",\n \"tok-123\",\n )(transportArgs),\n ).not.toThrow();\n });\n\n describe(\"listenToAvailableDevices\", () => {\n it(\"emits the mock server devices mapped as discovered devices\", async () => {\n mockListDevices(() =>\n Promise.resolve([aDevice(), aDevice({ id: \"device-2\", name: \"Stax\" })]),\n );\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const devices = await firstValueFrom(\n transport.listenToAvailableDevices(),\n );\n\n expect(devices).toEqual([\n expect.objectContaining({\n id: \"device-1\",\n transport: mockserverIdentifier,\n }),\n expect.objectContaining({\n id: \"device-2\",\n transport: mockserverIdentifier,\n }),\n ]);\n });\n\n it(\"keeps polling so newly added devices appear\", async () => {\n vi.useFakeTimers();\n const listDevices = mockListDevices(() => Promise.resolve([]));\n listDevices.mockResolvedValueOnce([]).mockResolvedValueOnce([aDevice()]);\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const polled = firstValueFrom(\n transport.listenToAvailableDevices().pipe(take(2), toArray()),\n );\n await vi.advanceTimersByTimeAsync(POLL_INTERVAL_MS);\n const emissions = await polled;\n\n expect(emissions[0]).toEqual([]);\n expect(emissions[1]).toEqual([\n expect.objectContaining({ id: \"device-1\" }),\n ]);\n });\n\n it(\"emits an empty list when the mock server is unreachable\", async () => {\n mockListDevices(() => Promise.reject(new Error(\"offline\")));\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const devices = await firstValueFrom(\n transport.listenToAvailableDevices(),\n );\n\n expect(devices).toEqual([]);\n });\n\n it(\"keeps polling after a failed request\", async () => {\n vi.useFakeTimers();\n const listDevices = mockListDevices(() => Promise.resolve([]));\n listDevices\n .mockRejectedValueOnce(new Error(\"offline\"))\n .mockResolvedValueOnce([aDevice()]);\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const polled = firstValueFrom(\n transport.listenToAvailableDevices().pipe(take(2), toArray()),\n );\n await vi.advanceTimersByTimeAsync(POLL_INTERVAL_MS);\n const emissions = await polled;\n\n expect(emissions[0]).toEqual([]);\n expect(emissions[1]).toEqual([\n expect.objectContaining({ id: \"device-1\" }),\n ]);\n });\n });\n\n describe(\"startDiscovering\", () => {\n it(\"emits each device returned by the mock server\", async () => {\n mockClientImpl({\n listDevices: vi.fn(() =>\n Promise.resolve([\n aDevice(),\n aDevice({ id: \"device-2\", name: \"Stax\" }),\n ]),\n ),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const devices = await firstValueFrom(\n transport.startDiscovering().pipe(toArray()),\n );\n\n expect(devices).toEqual([\n expect.objectContaining({ id: \"device-1\" }),\n expect.objectContaining({ id: \"device-2\" }),\n ]);\n });\n });\n\n describe(\"stopDiscovering\", () => {\n it(\"does nothing and does not throw\", () => {\n mockClientImpl();\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n expect(() => transport.stopDiscovering()).not.toThrow();\n });\n });\n\n describe(\"connect\", () => {\n it(\"returns a connected device on success\", async () => {\n mockClientImpl({\n connect: vi.fn(() =>\n Promise.resolve({\n device: aDevice({ masks: [0x12345678] }),\n connected: true,\n }),\n ),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n const onDisconnect = vi.fn();\n\n const result = await transport.connect({\n deviceId: \"device-1\",\n onDisconnect,\n });\n\n expect(result.isRight()).toBe(true);\n const connected = result.unsafeCoerce();\n expect(connected.id).toBe(\"device-1\");\n expect(connected.transport).toBe(mockserverIdentifier);\n expect(connected.deviceModel.masks).toEqual([0x12345678]);\n });\n\n it(\"falls back to the default masks when the device has none\", async () => {\n mockClientImpl({\n connect: vi.fn(() =>\n Promise.resolve({ device: aDevice(), connected: true }),\n ),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.connect({\n deviceId: \"device-1\",\n onDisconnect: vi.fn(),\n });\n\n expect(result.unsafeCoerce().deviceModel.masks).toEqual([0x31100000]);\n // A known model (nanoX) reports its real block size, not the legacy default.\n expect(\n result.unsafeCoerce().deviceModel.getBlockSize({\n firmwareVersion: \"1.0.0\",\n }),\n ).toBe(4 * 1024);\n });\n\n it(\"returns an OpeningConnectionError when the client throws\", async () => {\n mockClientImpl({\n connect: vi.fn(() => Promise.reject(new Error(\"nope\"))),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.connect({\n deviceId: \"device-1\",\n onDisconnect: vi.fn(),\n });\n\n expect(result.isLeft()).toBe(true);\n expect(result.swap().unsafeCoerce()).toBeInstanceOf(\n OpeningConnectionError,\n );\n });\n });\n\n describe(\"disconnect\", () => {\n const connectedDevice = {\n id: \"device-1\",\n } as TransportConnectedDevice;\n\n it(\"returns Right(undefined) when the client disconnects successfully\", async () => {\n const disconnect = vi.fn(() => Promise.resolve(true));\n mockClientImpl({ disconnect });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.disconnect({ connectedDevice });\n\n expect(disconnect).toHaveBeenCalledWith(\"device-1\");\n expect(result.isRight()).toBe(true);\n });\n\n it(\"returns a DisconnectError when the client reports failure\", async () => {\n mockClientImpl({ disconnect: vi.fn(() => Promise.resolve(false)) });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.disconnect({ connectedDevice });\n\n expect(result.isLeft()).toBe(true);\n });\n\n it(\"returns a DisconnectError when the client throws\", async () => {\n mockClientImpl({\n disconnect: vi.fn(() => Promise.reject(new Error(\"boom\"))),\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n\n const result = await transport.disconnect({ connectedDevice });\n\n expect(result.isLeft()).toBe(true);\n });\n });\n\n describe(\"sendApdu\", () => {\n const buildConnectedTransport = (sendApdu: ReturnType<typeof vi.fn>) => {\n mockClientImpl({\n connect: vi.fn(() =>\n Promise.resolve({ device: aDevice(), connected: true }),\n ),\n sendApdu,\n });\n return mockserverTransportFactory(\"http://localhost:8080\")(transportArgs);\n };\n\n it(\"splits the response into data and status code\", async () => {\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"aabb9000\" }));\n const transport = buildConnectedTransport(sendApdu);\n const onDisconnect = vi.fn();\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isRight()).toBe(true);\n const apduResponse = result.unsafeCoerce();\n expect(Array.from(apduResponse.data)).toEqual([0xaa, 0xbb]);\n expect(Array.from(apduResponse.statusCode)).toEqual([0x90, 0x00]);\n expect(onDisconnect).not.toHaveBeenCalled();\n });\n\n it(\"calls onDisconnect and returns a NoAccessibleDeviceError on failure\", async () => {\n const sendApdu = vi.fn(() => Promise.reject(new Error(\"device lost\")));\n const transport = buildConnectedTransport(sendApdu);\n const onDisconnect = vi.fn();\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isLeft()).toBe(true);\n expect(onDisconnect).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"returns an empty data buffer when the response is only a status word\", async () => {\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"9000\" }));\n const transport = buildConnectedTransport(sendApdu);\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect: vi.fn() })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isRight()).toBe(true);\n const apduResponse = result.unsafeCoerce();\n expect(Array.from(apduResponse.data)).toEqual([]);\n expect(Array.from(apduResponse.statusCode)).toEqual([0x90, 0x00]);\n });\n\n it(\"falls back to an empty status code when the status word hex is invalid\", async () => {\n // slice(-4) = \"XXYY\" is not valid hex -> hexaStringToBuffer returns null.\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"aabbXXYY\" }));\n const transport = buildConnectedTransport(sendApdu);\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect: vi.fn() })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isRight()).toBe(true);\n const apduResponse = result.unsafeCoerce();\n expect(Array.from(apduResponse.statusCode)).toEqual([]);\n expect(Array.from(apduResponse.data)).toEqual([0xaa, 0xbb]);\n });\n\n it(\"falls back to an empty data buffer when the data hex is invalid\", async () => {\n // slice(0, -4) = \"ZZZZ\" is not valid hex -> hexaStringToBuffer returns null.\n const sendApdu = vi.fn(() => Promise.resolve({ response: \"ZZZZ9000\" }));\n const transport = buildConnectedTransport(sendApdu);\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect: vi.fn() })\n ).unsafeCoerce();\n\n const result = await connected.sendApdu(\n Uint8Array.from([0xe0, 0x01, 0x00, 0x00]),\n );\n\n expect(result.isRight()).toBe(true);\n const apduResponse = result.unsafeCoerce();\n expect(Array.from(apduResponse.data)).toEqual([]);\n expect(Array.from(apduResponse.statusCode)).toEqual([0x90, 0x00]);\n });\n });\n\n describe(\"disconnect polling\", () => {\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n const connectTo = async (listDevices: ReturnType<typeof vi.fn>) => {\n mockClientImpl({\n connect: vi.fn(() =>\n Promise.resolve({ device: aDevice(), connected: true }),\n ),\n disconnect: vi.fn(() => Promise.resolve(true)),\n listDevices,\n });\n const transport = mockserverTransportFactory(\"http://localhost:8080\")(\n transportArgs,\n );\n const onDisconnect = vi.fn();\n const connected = (\n await transport.connect({ deviceId: \"device-1\", onDisconnect })\n ).unsafeCoerce();\n return { transport, connected, onDisconnect };\n };\n\n it(\"calls onDisconnect once the device is gone from the mock server\", async () => {\n const listDevices = vi.fn(() => Promise.resolve([aDevice()]));\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(1000);\n expect(onDisconnect).not.toHaveBeenCalled();\n\n listDevices.mockResolvedValue([]);\n await vi.advanceTimersByTimeAsync(1000);\n\n expect(onDisconnect).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"calls onDisconnect when the server reports the device as disconnected\", async () => {\n const listDevices = vi.fn(() =>\n Promise.resolve([aDevice({ connected: false })]),\n );\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(1000);\n\n expect(onDisconnect).toHaveBeenCalledWith(\"device-1\");\n });\n\n it(\"keeps the device connected while the mock server is unreachable\", async () => {\n const listDevices = vi.fn(() => Promise.reject(new Error(\"offline\")));\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(3000);\n\n expect(onDisconnect).not.toHaveBeenCalled();\n });\n\n it(\"reports the disconnect only once\", async () => {\n const listDevices = vi.fn(() => Promise.resolve([]));\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(3000);\n\n expect(onDisconnect).toHaveBeenCalledTimes(1);\n });\n\n it(\"stops polling after an explicit disconnect\", async () => {\n const listDevices = vi.fn(() => Promise.resolve([aDevice()]));\n const { transport, connected, onDisconnect } =\n await connectTo(listDevices);\n\n await transport.disconnect({ connectedDevice: connected });\n listDevices.mockResolvedValue([]);\n await vi.advanceTimersByTimeAsync(3000);\n\n expect(onDisconnect).not.toHaveBeenCalled();\n });\n\n it(\"stays silent when a request in flight answers after a disconnect\", async () => {\n // Clearing the interval cannot cancel a request already sent.\n let answer = (_: Device[]) => {};\n const listDevices = vi.fn(\n () =>\n new Promise<Device[]>((resolve) => {\n answer = resolve;\n }),\n );\n const { transport, connected, onDisconnect } =\n await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(1000);\n await transport.disconnect({ connectedDevice: connected });\n answer([]);\n await vi.advanceTimersByTimeAsync(0);\n\n expect(onDisconnect).not.toHaveBeenCalled();\n });\n\n it(\"reports once when two polls overlap\", async () => {\n // A request slower than the interval leaves two in flight at once.\n const answers: ((devices: Device[]) => void)[] = [];\n const listDevices = vi.fn(\n () =>\n new Promise<Device[]>((resolve) => {\n answers.push(resolve);\n }),\n );\n const { onDisconnect } = await connectTo(listDevices);\n\n await vi.advanceTimersByTimeAsync(2000);\n expect(answers).toHaveLength(2);\n answers.forEach((answer) => answer([]));\n await vi.advanceTimersByTimeAsync(0);\n\n expect(onDisconnect).toHaveBeenCalledTimes(1);\n });\n });\n\n describe(\"mockserverTransportFactory\", () => {\n it(\"uses the config mock url when no explicit url is provided\", () => {\n const client = mockClientImpl();\n expect(client).toBeDefined();\n\n mockserverTransportFactory()(transportArgs);\n\n expect(MockClient).toHaveBeenCalledWith(\n \"http://localhost:8080\",\n expect.anything(),\n );\n });\n\n it(\"prefers an explicit url over the config mock url\", () => {\n mockClientImpl();\n\n mockserverTransportFactory(\"https://explicit:9090\")(transportArgs);\n\n expect(MockClient).toHaveBeenCalledWith(\n \"https://explicit:9090\",\n expect.anything(),\n );\n });\n });\n});\n"],
5
+ "mappings": "AAAA,OAEE,0BAAAA,MAEK,kCACP,OAAsB,cAAAC,MAAkB,qCACxC,OAAS,kBAAAC,EAAgB,QAAAC,EAAM,WAAAC,MAAe,OAE9C,OACE,wBAAAC,EACA,8BAAAC,MACK,wBAEP,GAAG,KAAK,qCAAsC,KAAO,CACnD,WAAY,GAAG,GAAG,CACpB,EAAE,EAMF,MAAMC,EAAuB,KAC1B,CACC,MAAO,GAAG,GAAG,EACb,KAAM,GAAG,GAAG,EACZ,KAAM,GAAG,GAAG,EACZ,MAAO,GAAG,GAAG,CACf,GAEIC,EAAgB,CACpB,OAAQ,CAAE,QAAS,uBAAwB,EAC3C,qBAAAD,CACF,EAGME,EAAmB,IAEnBC,EAAU,CAACC,EAA6B,CAAC,KAAe,CAC5D,GAAI,WACJ,KAAM,gBACN,YAAa,QACb,kBAAmB,MACnB,GAAGA,CACL,GAEMC,EACJC,GAC6B,CAC7B,MAAMC,EAAc,GAAG,GAAGD,CAAI,EAC9B,UAAG,OAAOZ,CAAU,EAAE,mBACpB,KAAO,CAAE,YAAAa,CAAY,EACvB,EACOA,CACT,EAEMC,EAAiB,CAACJ,EAA8C,CAAC,IAAM,CAC3E,MAAMK,EAAS,CACb,YAAa,GAAG,GAAG,IAAM,QAAQ,QAAQ,CAAC,CAAC,CAAC,EAC5C,QAAS,GAAG,GAAG,EACf,WAAY,GAAG,GAAG,EAClB,SAAU,GAAG,GAAG,EAChB,GAAGL,CACL,EACA,UAAG,OAAOV,CAAU,EAAE,mBACpB,IAAMe,CACR,EACOA,CACT,EAEA,SAAS,6BAA8B,IAAM,CAC3C,WAAW,IAAM,CACf,GAAG,cAAc,EACjBJ,EAAgB,IAAM,QAAQ,QAAQ,CAAC,CAAC,CAAC,CAC3C,CAAC,EAED,UAAU,IAAM,CACd,GAAG,cAAc,CACnB,CAAC,EAED,GAAG,8DAA+D,IAAM,CACtE,MAAMK,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EAEA,OAAOS,EAAU,cAAc,CAAC,EAAE,KAAKZ,CAAoB,EAC3D,OAAOY,EAAU,YAAY,CAAC,EAAE,KAAK,EAAI,CAC3C,CAAC,EAED,GAAG,qDAAsD,IAAM,CAC7D,OAAO,IACLX,EACE,wBACA,SACF,EAAEE,CAAa,CACjB,EAAE,IAAI,QAAQ,CAChB,CAAC,EAED,SAAS,2BAA4B,IAAM,CACzC,GAAG,6DAA8D,SAAY,CAC3EI,EAAgB,IACd,QAAQ,QAAQ,CAACF,EAAQ,EAAGA,EAAQ,CAAE,GAAI,WAAY,KAAM,MAAO,CAAC,CAAC,CAAC,CACxE,EACA,MAAMO,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EAEMU,EAAU,MAAMhB,EACpBe,EAAU,yBAAyB,CACrC,EAEA,OAAOC,CAAO,EAAE,QAAQ,CACtB,OAAO,iBAAiB,CACtB,GAAI,WACJ,UAAWb,CACb,CAAC,EACD,OAAO,iBAAiB,CACtB,GAAI,WACJ,UAAWA,CACb,CAAC,CACH,CAAC,CACH,CAAC,EAED,GAAG,8CAA+C,SAAY,CAC5D,GAAG,cAAc,EACGO,EAAgB,IAAM,QAAQ,QAAQ,CAAC,CAAC,CAAC,EACjD,sBAAsB,CAAC,CAAC,EAAE,sBAAsB,CAACF,EAAQ,CAAC,CAAC,EACvE,MAAMO,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EAEMW,EAASjB,EACbe,EAAU,yBAAyB,EAAE,KAAKd,EAAK,CAAC,EAAGC,EAAQ,CAAC,CAC9D,EACA,MAAM,GAAG,yBAAyBK,CAAgB,EAClD,MAAMW,EAAY,MAAMD,EAExB,OAAOC,EAAU,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAC/B,OAAOA,EAAU,CAAC,CAAC,EAAE,QAAQ,CAC3B,OAAO,iBAAiB,CAAE,GAAI,UAAW,CAAC,CAC5C,CAAC,CACH,CAAC,EAED,GAAG,0DAA2D,SAAY,CACxER,EAAgB,IAAM,QAAQ,OAAO,IAAI,MAAM,SAAS,CAAC,CAAC,EAC1D,MAAMK,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EAEMU,EAAU,MAAMhB,EACpBe,EAAU,yBAAyB,CACrC,EAEA,OAAOC,CAAO,EAAE,QAAQ,CAAC,CAAC,CAC5B,CAAC,EAED,GAAG,uCAAwC,SAAY,CACrD,GAAG,cAAc,EACGN,EAAgB,IAAM,QAAQ,QAAQ,CAAC,CAAC,CAAC,EAE1D,sBAAsB,IAAI,MAAM,SAAS,CAAC,EAC1C,sBAAsB,CAACF,EAAQ,CAAC,CAAC,EACpC,MAAMO,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EAEMW,EAASjB,EACbe,EAAU,yBAAyB,EAAE,KAAKd,EAAK,CAAC,EAAGC,EAAQ,CAAC,CAC9D,EACA,MAAM,GAAG,yBAAyBK,CAAgB,EAClD,MAAMW,EAAY,MAAMD,EAExB,OAAOC,EAAU,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAC/B,OAAOA,EAAU,CAAC,CAAC,EAAE,QAAQ,CAC3B,OAAO,iBAAiB,CAAE,GAAI,UAAW,CAAC,CAC5C,CAAC,CACH,CAAC,CACH,CAAC,EAED,SAAS,mBAAoB,IAAM,CACjC,GAAG,gDAAiD,SAAY,CAC9DL,EAAe,CACb,YAAa,GAAG,GAAG,IACjB,QAAQ,QAAQ,CACdL,EAAQ,EACRA,EAAQ,CAAE,GAAI,WAAY,KAAM,MAAO,CAAC,CAC1C,CAAC,CACH,CACF,CAAC,EACD,MAAMO,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EAEMU,EAAU,MAAMhB,EACpBe,EAAU,iBAAiB,EAAE,KAAKb,EAAQ,CAAC,CAC7C,EAEA,OAAOc,CAAO,EAAE,QAAQ,CACtB,OAAO,iBAAiB,CAAE,GAAI,UAAW,CAAC,EAC1C,OAAO,iBAAiB,CAAE,GAAI,UAAW,CAAC,CAC5C,CAAC,CACH,CAAC,CACH,CAAC,EAED,SAAS,kBAAmB,IAAM,CAChC,GAAG,kCAAmC,IAAM,CAC1CH,EAAe,EACf,MAAME,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EAEA,OAAO,IAAMS,EAAU,gBAAgB,CAAC,EAAE,IAAI,QAAQ,CACxD,CAAC,CACH,CAAC,EAED,SAAS,UAAW,IAAM,CACxB,GAAG,wCAAyC,SAAY,CACtDF,EAAe,CACb,QAAS,GAAG,GAAG,IACb,QAAQ,QAAQ,CACd,OAAQL,EAAQ,CAAE,MAAO,CAAC,SAAU,CAAE,CAAC,EACvC,UAAW,EACb,CAAC,CACH,CACF,CAAC,EACD,MAAMO,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EACMa,EAAe,GAAG,GAAG,EAErBC,EAAS,MAAML,EAAU,QAAQ,CACrC,SAAU,WACV,aAAAI,CACF,CAAC,EAED,OAAOC,EAAO,QAAQ,CAAC,EAAE,KAAK,EAAI,EAClC,MAAMC,EAAYD,EAAO,aAAa,EACtC,OAAOC,EAAU,EAAE,EAAE,KAAK,UAAU,EACpC,OAAOA,EAAU,SAAS,EAAE,KAAKlB,CAAoB,EACrD,OAAOkB,EAAU,YAAY,KAAK,EAAE,QAAQ,CAAC,SAAU,CAAC,CAC1D,CAAC,EAED,GAAG,2DAA4D,SAAY,CACzER,EAAe,CACb,QAAS,GAAG,GAAG,IACb,QAAQ,QAAQ,CAAE,OAAQL,EAAQ,EAAG,UAAW,EAAK,CAAC,CACxD,CACF,CAAC,EAKD,MAAMY,EAAS,MAJGhB,EAA2B,uBAAuB,EAClEE,CACF,EAE+B,QAAQ,CACrC,SAAU,WACV,aAAc,GAAG,GAAG,CACtB,CAAC,EAED,OAAOc,EAAO,aAAa,EAAE,YAAY,KAAK,EAAE,QAAQ,CAAC,SAAU,CAAC,EAEpE,OACEA,EAAO,aAAa,EAAE,YAAY,aAAa,CAC7C,gBAAiB,OACnB,CAAC,CACH,EAAE,KAAK,EAAI,IAAI,CACjB,CAAC,EAED,GAAG,2DAA4D,SAAY,CACzEP,EAAe,CACb,QAAS,GAAG,GAAG,IAAM,QAAQ,OAAO,IAAI,MAAM,MAAM,CAAC,CAAC,CACxD,CAAC,EAKD,MAAMO,EAAS,MAJGhB,EAA2B,uBAAuB,EAClEE,CACF,EAE+B,QAAQ,CACrC,SAAU,WACV,aAAc,GAAG,GAAG,CACtB,CAAC,EAED,OAAOc,EAAO,OAAO,CAAC,EAAE,KAAK,EAAI,EACjC,OAAOA,EAAO,KAAK,EAAE,aAAa,CAAC,EAAE,eACnCtB,CACF,CACF,CAAC,CACH,CAAC,EAED,SAAS,aAAc,IAAM,CAC3B,MAAMwB,EAAkB,CACtB,GAAI,UACN,EAEA,GAAG,oEAAqE,SAAY,CAClF,MAAMC,EAAa,GAAG,GAAG,IAAM,QAAQ,QAAQ,EAAI,CAAC,EACpDV,EAAe,CAAE,WAAAU,CAAW,CAAC,EAK7B,MAAMH,EAAS,MAJGhB,EAA2B,uBAAuB,EAClEE,CACF,EAE+B,WAAW,CAAE,gBAAAgB,CAAgB,CAAC,EAE7D,OAAOC,CAAU,EAAE,qBAAqB,UAAU,EAClD,OAAOH,EAAO,QAAQ,CAAC,EAAE,KAAK,EAAI,CACpC,CAAC,EAED,GAAG,4DAA6D,SAAY,CAC1EP,EAAe,CAAE,WAAY,GAAG,GAAG,IAAM,QAAQ,QAAQ,EAAK,CAAC,CAAE,CAAC,EAKlE,MAAMO,EAAS,MAJGhB,EAA2B,uBAAuB,EAClEE,CACF,EAE+B,WAAW,CAAE,gBAAAgB,CAAgB,CAAC,EAE7D,OAAOF,EAAO,OAAO,CAAC,EAAE,KAAK,EAAI,CACnC,CAAC,EAED,GAAG,mDAAoD,SAAY,CACjEP,EAAe,CACb,WAAY,GAAG,GAAG,IAAM,QAAQ,OAAO,IAAI,MAAM,MAAM,CAAC,CAAC,CAC3D,CAAC,EAKD,MAAMO,EAAS,MAJGhB,EAA2B,uBAAuB,EAClEE,CACF,EAE+B,WAAW,CAAE,gBAAAgB,CAAgB,CAAC,EAE7D,OAAOF,EAAO,OAAO,CAAC,EAAE,KAAK,EAAI,CACnC,CAAC,CACH,CAAC,EAED,SAAS,WAAY,IAAM,CACzB,MAAMI,EAA2BC,IAC/BZ,EAAe,CACb,QAAS,GAAG,GAAG,IACb,QAAQ,QAAQ,CAAE,OAAQL,EAAQ,EAAG,UAAW,EAAK,CAAC,CACxD,EACA,SAAAiB,CACF,CAAC,EACMrB,EAA2B,uBAAuB,EAAEE,CAAa,GAG1E,GAAG,gDAAiD,SAAY,CAC9D,MAAMmB,EAAW,GAAG,GAAG,IAAM,QAAQ,QAAQ,CAAE,SAAU,UAAW,CAAC,CAAC,EAChEV,EAAYS,EAAwBC,CAAQ,EAC5CN,EAAe,GAAG,GAAG,EAKrBC,EAAS,MAHb,MAAML,EAAU,QAAQ,CAAE,SAAU,WAAY,aAAAI,CAAa,CAAC,GAC9D,aAAa,EAEgB,SAC7B,WAAW,KAAK,CAAC,IAAM,EAAM,EAAM,CAAI,CAAC,CAC1C,EAEA,OAAOC,EAAO,QAAQ,CAAC,EAAE,KAAK,EAAI,EAClC,MAAMM,EAAeN,EAAO,aAAa,EACzC,OAAO,MAAM,KAAKM,EAAa,IAAI,CAAC,EAAE,QAAQ,CAAC,IAAM,GAAI,CAAC,EAC1D,OAAO,MAAM,KAAKA,EAAa,UAAU,CAAC,EAAE,QAAQ,CAAC,IAAM,CAAI,CAAC,EAChE,OAAOP,CAAY,EAAE,IAAI,iBAAiB,CAC5C,CAAC,EAED,GAAG,sEAAuE,SAAY,CACpF,MAAMM,EAAW,GAAG,GAAG,IAAM,QAAQ,OAAO,IAAI,MAAM,aAAa,CAAC,CAAC,EAC/DV,EAAYS,EAAwBC,CAAQ,EAC5CN,EAAe,GAAG,GAAG,EAKrBC,EAAS,MAHb,MAAML,EAAU,QAAQ,CAAE,SAAU,WAAY,aAAAI,CAAa,CAAC,GAC9D,aAAa,EAEgB,SAC7B,WAAW,KAAK,CAAC,IAAM,EAAM,EAAM,CAAI,CAAC,CAC1C,EAEA,OAAOC,EAAO,OAAO,CAAC,EAAE,KAAK,EAAI,EACjC,OAAOD,CAAY,EAAE,qBAAqB,UAAU,CACtD,CAAC,EAED,GAAG,uEAAwE,SAAY,CACrF,MAAMM,EAAW,GAAG,GAAG,IAAM,QAAQ,QAAQ,CAAE,SAAU,MAAO,CAAC,CAAC,EAM5DL,EAAS,MAHb,MAFgBI,EAAwBC,CAAQ,EAEhC,QAAQ,CAAE,SAAU,WAAY,aAAc,GAAG,GAAG,CAAE,CAAC,GACvE,aAAa,EAEgB,SAC7B,WAAW,KAAK,CAAC,IAAM,EAAM,EAAM,CAAI,CAAC,CAC1C,EAEA,OAAOL,EAAO,QAAQ,CAAC,EAAE,KAAK,EAAI,EAClC,MAAMM,EAAeN,EAAO,aAAa,EACzC,OAAO,MAAM,KAAKM,EAAa,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,EAChD,OAAO,MAAM,KAAKA,EAAa,UAAU,CAAC,EAAE,QAAQ,CAAC,IAAM,CAAI,CAAC,CAClE,CAAC,EAED,GAAG,yEAA0E,SAAY,CAEvF,MAAMD,EAAW,GAAG,GAAG,IAAM,QAAQ,QAAQ,CAAE,SAAU,UAAW,CAAC,CAAC,EAMhEL,EAAS,MAHb,MAFgBI,EAAwBC,CAAQ,EAEhC,QAAQ,CAAE,SAAU,WAAY,aAAc,GAAG,GAAG,CAAE,CAAC,GACvE,aAAa,EAEgB,SAC7B,WAAW,KAAK,CAAC,IAAM,EAAM,EAAM,CAAI,CAAC,CAC1C,EAEA,OAAOL,EAAO,QAAQ,CAAC,EAAE,KAAK,EAAI,EAClC,MAAMM,EAAeN,EAAO,aAAa,EACzC,OAAO,MAAM,KAAKM,EAAa,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC,EACtD,OAAO,MAAM,KAAKA,EAAa,IAAI,CAAC,EAAE,QAAQ,CAAC,IAAM,GAAI,CAAC,CAC5D,CAAC,EAED,GAAG,kEAAmE,SAAY,CAEhF,MAAMD,EAAW,GAAG,GAAG,IAAM,QAAQ,QAAQ,CAAE,SAAU,UAAW,CAAC,CAAC,EAMhEL,EAAS,MAHb,MAFgBI,EAAwBC,CAAQ,EAEhC,QAAQ,CAAE,SAAU,WAAY,aAAc,GAAG,GAAG,CAAE,CAAC,GACvE,aAAa,EAEgB,SAC7B,WAAW,KAAK,CAAC,IAAM,EAAM,EAAM,CAAI,CAAC,CAC1C,EAEA,OAAOL,EAAO,QAAQ,CAAC,EAAE,KAAK,EAAI,EAClC,MAAMM,EAAeN,EAAO,aAAa,EACzC,OAAO,MAAM,KAAKM,EAAa,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,EAChD,OAAO,MAAM,KAAKA,EAAa,UAAU,CAAC,EAAE,QAAQ,CAAC,IAAM,CAAI,CAAC,CAClE,CAAC,CACH,CAAC,EAED,SAAS,qBAAsB,IAAM,CACnC,WAAW,IAAM,CACf,GAAG,cAAc,CACnB,CAAC,EAED,UAAU,IAAM,CACd,GAAG,cAAc,CACnB,CAAC,EAED,MAAMC,EAAY,MAAOf,GAA0C,CACjEC,EAAe,CACb,QAAS,GAAG,GAAG,IACb,QAAQ,QAAQ,CAAE,OAAQL,EAAQ,EAAG,UAAW,EAAK,CAAC,CACxD,EACA,WAAY,GAAG,GAAG,IAAM,QAAQ,QAAQ,EAAI,CAAC,EAC7C,YAAAI,CACF,CAAC,EACD,MAAMG,EAAYX,EAA2B,uBAAuB,EAClEE,CACF,EACMa,EAAe,GAAG,GAAG,EACrBE,GACJ,MAAMN,EAAU,QAAQ,CAAE,SAAU,WAAY,aAAAI,CAAa,CAAC,GAC9D,aAAa,EACf,MAAO,CAAE,UAAAJ,EAAW,UAAAM,EAAW,aAAAF,CAAa,CAC9C,EAEA,GAAG,kEAAmE,SAAY,CAChF,MAAMP,EAAc,GAAG,GAAG,IAAM,QAAQ,QAAQ,CAACJ,EAAQ,CAAC,CAAC,CAAC,EACtD,CAAE,aAAAW,CAAa,EAAI,MAAMQ,EAAUf,CAAW,EAEpD,MAAM,GAAG,yBAAyB,GAAI,EACtC,OAAOO,CAAY,EAAE,IAAI,iBAAiB,EAE1CP,EAAY,kBAAkB,CAAC,CAAC,EAChC,MAAM,GAAG,yBAAyB,GAAI,EAEtC,OAAOO,CAAY,EAAE,qBAAqB,UAAU,CACtD,CAAC,EAED,GAAG,wEAAyE,SAAY,CACtF,MAAMP,EAAc,GAAG,GAAG,IACxB,QAAQ,QAAQ,CAACJ,EAAQ,CAAE,UAAW,EAAM,CAAC,CAAC,CAAC,CACjD,EACM,CAAE,aAAAW,CAAa,EAAI,MAAMQ,EAAUf,CAAW,EAEpD,MAAM,GAAG,yBAAyB,GAAI,EAEtC,OAAOO,CAAY,EAAE,qBAAqB,UAAU,CACtD,CAAC,EAED,GAAG,kEAAmE,SAAY,CAChF,MAAMP,EAAc,GAAG,GAAG,IAAM,QAAQ,OAAO,IAAI,MAAM,SAAS,CAAC,CAAC,EAC9D,CAAE,aAAAO,CAAa,EAAI,MAAMQ,EAAUf,CAAW,EAEpD,MAAM,GAAG,yBAAyB,GAAI,EAEtC,OAAOO,CAAY,EAAE,IAAI,iBAAiB,CAC5C,CAAC,EAED,GAAG,mCAAoC,SAAY,CACjD,MAAMP,EAAc,GAAG,GAAG,IAAM,QAAQ,QAAQ,CAAC,CAAC,CAAC,EAC7C,CAAE,aAAAO,CAAa,EAAI,MAAMQ,EAAUf,CAAW,EAEpD,MAAM,GAAG,yBAAyB,GAAI,EAEtC,OAAOO,CAAY,EAAE,sBAAsB,CAAC,CAC9C,CAAC,EAED,GAAG,6CAA8C,SAAY,CAC3D,MAAMP,EAAc,GAAG,GAAG,IAAM,QAAQ,QAAQ,CAACJ,EAAQ,CAAC,CAAC,CAAC,EACtD,CAAE,UAAAO,EAAW,UAAAM,EAAW,aAAAF,CAAa,EACzC,MAAMQ,EAAUf,CAAW,EAE7B,MAAMG,EAAU,WAAW,CAAE,gBAAiBM,CAAU,CAAC,EACzDT,EAAY,kBAAkB,CAAC,CAAC,EAChC,MAAM,GAAG,yBAAyB,GAAI,EAEtC,OAAOO,CAAY,EAAE,IAAI,iBAAiB,CAC5C,CAAC,EAED,GAAG,mEAAoE,SAAY,CAEjF,IAAIS,EAAUC,GAAgB,CAAC,EAC/B,MAAMjB,EAAc,GAAG,GACrB,IACE,IAAI,QAAmBkB,GAAY,CACjCF,EAASE,CACX,CAAC,CACL,EACM,CAAE,UAAAf,EAAW,UAAAM,EAAW,aAAAF,CAAa,EACzC,MAAMQ,EAAUf,CAAW,EAE7B,MAAM,GAAG,yBAAyB,GAAI,EACtC,MAAMG,EAAU,WAAW,CAAE,gBAAiBM,CAAU,CAAC,EACzDO,EAAO,CAAC,CAAC,EACT,MAAM,GAAG,yBAAyB,CAAC,EAEnC,OAAOT,CAAY,EAAE,IAAI,iBAAiB,CAC5C,CAAC,EAED,GAAG,sCAAuC,SAAY,CAEpD,MAAMY,EAA2C,CAAC,EAC5CnB,EAAc,GAAG,GACrB,IACE,IAAI,QAAmBkB,GAAY,CACjCC,EAAQ,KAAKD,CAAO,CACtB,CAAC,CACL,EACM,CAAE,aAAAX,CAAa,EAAI,MAAMQ,EAAUf,CAAW,EAEpD,MAAM,GAAG,yBAAyB,GAAI,EACtC,OAAOmB,CAAO,EAAE,aAAa,CAAC,EAC9BA,EAAQ,QAASH,GAAWA,EAAO,CAAC,CAAC,CAAC,EACtC,MAAM,GAAG,yBAAyB,CAAC,EAEnC,OAAOT,CAAY,EAAE,sBAAsB,CAAC,CAC9C,CAAC,CACH,CAAC,EAED,SAAS,6BAA8B,IAAM,CAC3C,GAAG,4DAA6D,IAAM,CACpE,MAAML,EAASD,EAAe,EAC9B,OAAOC,CAAM,EAAE,YAAY,EAE3BV,EAA2B,EAAEE,CAAa,EAE1C,OAAOP,CAAU,EAAE,qBACjB,wBACA,OAAO,SAAS,CAClB,CACF,CAAC,EAED,GAAG,mDAAoD,IAAM,CAC3Dc,EAAe,EAEfT,EAA2B,uBAAuB,EAAEE,CAAa,EAEjE,OAAOP,CAAU,EAAE,qBACjB,wBACA,OAAO,SAAS,CAClB,CACF,CAAC,CACH,CAAC,CACH,CAAC",
6
+ "names": ["OpeningConnectionError", "MockClient", "firstValueFrom", "take", "toArray", "mockserverIdentifier", "mockserverTransportFactory", "loggerServiceFactory", "transportArgs", "POLL_INTERVAL_MS", "aDevice", "overrides", "mockListDevices", "impl", "listDevices", "mockClientImpl", "client", "transport", "devices", "polled", "emissions", "onDisconnect", "result", "connected", "connectedDevice", "disconnect", "buildConnectedTransport", "sendApdu", "apduResponse", "connectTo", "answer", "_", "resolve", "answers"]
7
+ }
@@ -32,7 +32,7 @@
32
32
  "license": "Apache-2.0",
33
33
  "name": "@ledgerhq/device-transport-kit-mockserver",
34
34
  "peerDependencies": {
35
- "@ledgerhq/device-management-kit": "workspace:^",
35
+ "@ledgerhq/device-management-kit": "^1.9.0",
36
36
  "rxjs": "catalog:"
37
37
  },
38
38
  "private": false,
@@ -56,5 +56,5 @@
56
56
  "watch:builds": "pnpm ldmk-tool watch --entryPoints src/index.ts,src/**/*.ts --tsconfig tsconfig.prod.json",
57
57
  "watch:types": "concurrently \"tsc --watch -p tsconfig.prod.json\" \"tsc-alias --watch -p tsconfig.prod.json\""
58
58
  },
59
- "version": "1.0.2"
59
+ "version": "1.1.1"
60
60
  }
@@ -1,4 +1,4 @@
1
- import { type ApduResponse, type ConnectError, type DeviceId, type DisconnectHandler, type DmkConfig, type DmkError, type LoggerPublisherService, type Transport, type TransportConnectedDevice, type TransportDiscoveredDevice, type TransportFactory, type TransportIdentifier } from "@ledgerhq/device-management-kit";
1
+ import { type ApduResponse, type ConnectError, type DeviceId, type DisconnectHandler, type DmkError, type LoggerPublisherService, type Transport, type TransportConnectedDevice, type TransportDiscoveredDevice, type TransportFactory, type TransportIdentifier } from "@ledgerhq/device-management-kit";
2
2
  import { type Either } from "purify-ts";
3
3
  import { type Observable } from "rxjs";
4
4
  export declare const mockserverIdentifier: TransportIdentifier;
@@ -6,7 +6,8 @@ export declare class MockTransport implements Transport {
6
6
  private logger;
7
7
  private mockClient;
8
8
  private readonly identifier;
9
- constructor(loggerServiceFactory: (tag: string) => LoggerPublisherService, config: DmkConfig);
9
+ private readonly disconnectPolls;
10
+ constructor(loggerServiceFactory: (tag: string) => LoggerPublisherService, mockUrl: string, sessionToken?: string);
10
11
  isSupported(): boolean;
11
12
  getIdentifier(): TransportIdentifier;
12
13
  listenToAvailableDevices(): Observable<TransportDiscoveredDevice[]>;
@@ -19,7 +20,24 @@ export declare class MockTransport implements Transport {
19
20
  disconnect(params: {
20
21
  connectedDevice: TransportConnectedDevice;
21
22
  }): Promise<Either<DmkError, void>>;
22
- sendApdu(sessionId: string, deviceId: DeviceId, onDisconnect: DisconnectHandler, apdu: Uint8Array): Promise<Either<DmkError, ApduResponse>>;
23
+ sendApdu(deviceId: string, onDisconnectDeviceId: DeviceId, onDisconnect: DisconnectHandler, apdu: Uint8Array): Promise<Either<DmkError, ApduResponse>>;
24
+ /**
25
+ * A device can be deleted or disconnected through the API by any client, and
26
+ * nothing announces it the way a physical unplug does. Without the signal,
27
+ * DMK keeps a session that claims to be connected.
28
+ */
29
+ private listenForDisconnect;
30
+ private clearDisconnectPoll;
31
+ private mapToDiscoveredDevices;
32
+ private buildDeviceModel;
23
33
  }
24
- export declare const mockserverTransportFactory: TransportFactory;
34
+ /**
35
+ * Build a mock-server transport factory.
36
+ *
37
+ * @param mockUrl Optional mock server URL. When omitted, `config.mockUrl` from
38
+ * the DMK configuration is used.
39
+ * @param sessionToken Optional bearer token to share an existing session
40
+ * When omitted, the client self-provisions a session via /auth.
41
+ */
42
+ export declare const mockserverTransportFactory: (mockUrl?: string, sessionToken?: string) => TransportFactory;
25
43
  //# sourceMappingURL=MockserverTransport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MockserverTransport.d.ts","sourceRoot":"","sources":["../../src/MockserverTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,EAGb,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,QAAQ,EAGb,KAAK,sBAAsB,EAG3B,KAAK,SAAS,EACd,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AAOzC,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,WAAW,CAAC;AACrD,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvD,eAAO,MAAM,oBAAoB,EAAE,mBAAkC,CAAC;AAEtE,qBAAa,aAAc,YAAW,SAAS;IAC7C,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6C;gBAGtE,oBAAoB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,sBAAsB,EAC7D,MAAM,EAAE,SAAS;IAMnB,WAAW,IAAI,OAAO;IAItB,aAAa,IAAI,mBAAmB;IAIpC,wBAAwB,IAAI,UAAU,CAAC,yBAAyB,EAAE,CAAC;IAInE,gBAAgB,IAAI,UAAU,CAAC,yBAAyB,CAAC;IAwBzD,eAAe,IAAI,IAAI;IAKjB,OAAO,CAAC,MAAM,EAAE;QACpB,QAAQ,EAAE,QAAQ,CAAC;QACnB,YAAY,EAAE,iBAAiB,CAAC;KACjC,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC;IAqCrD,UAAU,CAAC,MAAM,EAAE;QACvB,eAAe,EAAE,wBAAwB,CAAC;KAC3C,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAgB7B,QAAQ,CACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,iBAAiB,EAC/B,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;CA0B3C;AAED,eAAO,MAAM,0BAA0B,EAAE,gBAGY,CAAC"}
1
+ {"version":3,"file":"MockserverTransport.d.ts","sourceRoot":"","sources":["../../src/MockserverTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,EAGb,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EAIb,KAAK,sBAAsB,EAI3B,KAAK,SAAS,EACd,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,WAAW,CAAC;AACrD,OAAO,EAKL,KAAK,UAAU,EAIhB,MAAM,MAAM,CAAC;AAEd,eAAO,MAAM,oBAAoB,EAAE,mBAAkC,CAAC;AAkBtE,qBAAa,aAAc,YAAW,SAAS;IAC7C,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6C;IACxE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG5B;gBAGF,oBAAoB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,sBAAsB,EAC7D,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,MAAM;IAMvB,WAAW,IAAI,OAAO;IAItB,aAAa,IAAI,mBAAmB;IAIpC,wBAAwB,IAAI,UAAU,CAAC,yBAAyB,EAAE,CAAC;IAqBnE,gBAAgB,IAAI,UAAU,CAAC,yBAAyB,CAAC;IAQzD,eAAe,IAAI,IAAI;IAKjB,OAAO,CAAC,MAAM,EAAE;QACpB,QAAQ,EAAE,QAAQ,CAAC;QACnB,YAAY,EAAE,iBAAiB,CAAC;KACjC,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC;IAoBrD,UAAU,CAAC,MAAM,EAAE;QACvB,eAAe,EAAE,wBAAwB,CAAC;KAC3C,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAiB7B,QAAQ,CACZ,QAAQ,EAAE,MAAM,EAChB,oBAAoB,EAAE,QAAQ,EAC9B,YAAY,EAAE,iBAAiB,EAC/B,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAwB1C;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAmC3B,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,gBAAgB;CAmBzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,GACpC,UAAU,MAAM,EAAE,eAAe,MAAM,KAAG,gBAMxC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=MockserverTransport.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockserverTransport.test.d.ts","sourceRoot":"","sources":["../../src/MockserverTransport.test.ts"],"names":[],"mappings":""}