@ledgerhq/live-common 34.35.0-nightly.3 → 34.35.0-nightly.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/lib/apps/support.d.ts +1 -0
  2. package/lib/apps/support.d.ts.map +1 -1
  3. package/lib/apps/support.js +10 -1
  4. package/lib/apps/support.js.map +1 -1
  5. package/lib/e2e/enum/Account.js +5 -5
  6. package/lib/e2e/enum/Account.js.map +1 -1
  7. package/lib/e2e/index.d.ts +2 -0
  8. package/lib/e2e/index.d.ts.map +1 -1
  9. package/lib/e2e/speculos.d.ts +4 -1
  10. package/lib/e2e/speculos.d.ts.map +1 -1
  11. package/lib/e2e/speculos.js +24 -8
  12. package/lib/e2e/speculos.js.map +1 -1
  13. package/lib/e2e/speculosCI.d.ts +5 -0
  14. package/lib/e2e/speculosCI.d.ts.map +1 -0
  15. package/lib/e2e/speculosCI.js +129 -0
  16. package/lib/e2e/speculosCI.js.map +1 -0
  17. package/lib/featureFlags/defaultFeatures.d.ts.map +1 -1
  18. package/lib/featureFlags/defaultFeatures.js +1 -0
  19. package/lib/featureFlags/defaultFeatures.js.map +1 -1
  20. package/lib/featureFlags/useFeature.d.ts +1 -1
  21. package/lib/featureFlags/useFeature.d.ts.map +1 -1
  22. package/lib/hw/actions/manager.d.ts +1 -1
  23. package/lib/hw/actions/manager.d.ts.map +1 -1
  24. package/lib/hw/connectApp.d.ts.map +1 -1
  25. package/lib/hw/connectApp.js +56 -5
  26. package/lib/hw/connectApp.js.map +1 -1
  27. package/lib/hw/connectAppEventMapper.d.ts +29 -0
  28. package/lib/hw/connectAppEventMapper.d.ts.map +1 -0
  29. package/lib/hw/connectAppEventMapper.js +300 -0
  30. package/lib/hw/connectAppEventMapper.js.map +1 -0
  31. package/lib/hw/connectManager.d.ts +3 -2
  32. package/lib/hw/connectManager.d.ts.map +1 -1
  33. package/lib/hw/connectManager.js +34 -3
  34. package/lib/hw/connectManager.js.map +1 -1
  35. package/lib/hw/connectManagerEventMapper.d.ts +16 -0
  36. package/lib/hw/connectManagerEventMapper.d.ts.map +1 -0
  37. package/lib/hw/connectManagerEventMapper.js +78 -0
  38. package/lib/hw/connectManagerEventMapper.js.map +1 -0
  39. package/lib/notifications/AnnouncementProvider/index.d.ts.map +1 -1
  40. package/lib/notifications/AnnouncementProvider/index.js +12 -10
  41. package/lib/notifications/AnnouncementProvider/index.js.map +1 -1
  42. package/lib/notifications/AnnouncementProvider/machine.d.ts +1 -4
  43. package/lib/notifications/AnnouncementProvider/machine.d.ts.map +1 -1
  44. package/lib/notifications/AnnouncementProvider/machine.js +9 -8
  45. package/lib/notifications/AnnouncementProvider/machine.js.map +1 -1
  46. package/lib/notifications/ServiceStatusProvider/index.d.ts.map +1 -1
  47. package/lib/notifications/ServiceStatusProvider/index.js +5 -4
  48. package/lib/notifications/ServiceStatusProvider/index.js.map +1 -1
  49. package/lib/notifications/ServiceStatusProvider/machine.d.ts +1 -4
  50. package/lib/notifications/ServiceStatusProvider/machine.d.ts.map +1 -1
  51. package/lib/notifications/ServiceStatusProvider/machine.js +4 -4
  52. package/lib/notifications/ServiceStatusProvider/machine.js.map +1 -1
  53. package/lib/wallet-api/ModularDrawer/types.d.ts +63 -0
  54. package/lib/wallet-api/ModularDrawer/types.d.ts.map +1 -0
  55. package/lib/wallet-api/ModularDrawer/types.js +25 -0
  56. package/lib/wallet-api/ModularDrawer/types.js.map +1 -0
  57. package/lib/wallet-api/ModularDrawer/utils.d.ts +5 -0
  58. package/lib/wallet-api/ModularDrawer/utils.d.ts.map +1 -0
  59. package/lib/wallet-api/ModularDrawer/utils.js +30 -0
  60. package/lib/wallet-api/ModularDrawer/utils.js.map +1 -0
  61. package/lib/wallet-api/react.d.ts +2 -0
  62. package/lib/wallet-api/react.d.ts.map +1 -1
  63. package/lib/wallet-api/react.js +2 -1
  64. package/lib/wallet-api/react.js.map +1 -1
  65. package/lib-es/apps/support.d.ts +1 -0
  66. package/lib-es/apps/support.d.ts.map +1 -1
  67. package/lib-es/apps/support.js +8 -0
  68. package/lib-es/apps/support.js.map +1 -1
  69. package/lib-es/e2e/enum/Account.js +5 -5
  70. package/lib-es/e2e/enum/Account.js.map +1 -1
  71. package/lib-es/e2e/index.d.ts +2 -0
  72. package/lib-es/e2e/index.d.ts.map +1 -1
  73. package/lib-es/e2e/speculos.d.ts +4 -1
  74. package/lib-es/e2e/speculos.d.ts.map +1 -1
  75. package/lib-es/e2e/speculos.js +24 -8
  76. package/lib-es/e2e/speculos.js.map +1 -1
  77. package/lib-es/e2e/speculosCI.d.ts +5 -0
  78. package/lib-es/e2e/speculosCI.d.ts.map +1 -0
  79. package/lib-es/e2e/speculosCI.js +121 -0
  80. package/lib-es/e2e/speculosCI.js.map +1 -0
  81. package/lib-es/featureFlags/defaultFeatures.d.ts.map +1 -1
  82. package/lib-es/featureFlags/defaultFeatures.js +1 -0
  83. package/lib-es/featureFlags/defaultFeatures.js.map +1 -1
  84. package/lib-es/featureFlags/useFeature.d.ts +1 -1
  85. package/lib-es/featureFlags/useFeature.d.ts.map +1 -1
  86. package/lib-es/hw/actions/manager.d.ts +1 -1
  87. package/lib-es/hw/actions/manager.d.ts.map +1 -1
  88. package/lib-es/hw/connectApp.d.ts.map +1 -1
  89. package/lib-es/hw/connectApp.js +57 -6
  90. package/lib-es/hw/connectApp.js.map +1 -1
  91. package/lib-es/hw/connectAppEventMapper.d.ts +29 -0
  92. package/lib-es/hw/connectAppEventMapper.d.ts.map +1 -0
  93. package/lib-es/hw/connectAppEventMapper.js +296 -0
  94. package/lib-es/hw/connectAppEventMapper.js.map +1 -0
  95. package/lib-es/hw/connectManager.d.ts +3 -2
  96. package/lib-es/hw/connectManager.d.ts.map +1 -1
  97. package/lib-es/hw/connectManager.js +34 -4
  98. package/lib-es/hw/connectManager.js.map +1 -1
  99. package/lib-es/hw/connectManagerEventMapper.d.ts +16 -0
  100. package/lib-es/hw/connectManagerEventMapper.d.ts.map +1 -0
  101. package/lib-es/hw/connectManagerEventMapper.js +74 -0
  102. package/lib-es/hw/connectManagerEventMapper.js.map +1 -0
  103. package/lib-es/notifications/AnnouncementProvider/index.d.ts.map +1 -1
  104. package/lib-es/notifications/AnnouncementProvider/index.js +12 -10
  105. package/lib-es/notifications/AnnouncementProvider/index.js.map +1 -1
  106. package/lib-es/notifications/AnnouncementProvider/machine.d.ts +1 -4
  107. package/lib-es/notifications/AnnouncementProvider/machine.d.ts.map +1 -1
  108. package/lib-es/notifications/AnnouncementProvider/machine.js +9 -8
  109. package/lib-es/notifications/AnnouncementProvider/machine.js.map +1 -1
  110. package/lib-es/notifications/ServiceStatusProvider/index.d.ts.map +1 -1
  111. package/lib-es/notifications/ServiceStatusProvider/index.js +5 -4
  112. package/lib-es/notifications/ServiceStatusProvider/index.js.map +1 -1
  113. package/lib-es/notifications/ServiceStatusProvider/machine.d.ts +1 -4
  114. package/lib-es/notifications/ServiceStatusProvider/machine.d.ts.map +1 -1
  115. package/lib-es/notifications/ServiceStatusProvider/machine.js +4 -4
  116. package/lib-es/notifications/ServiceStatusProvider/machine.js.map +1 -1
  117. package/lib-es/wallet-api/ModularDrawer/types.d.ts +63 -0
  118. package/lib-es/wallet-api/ModularDrawer/types.d.ts.map +1 -0
  119. package/lib-es/wallet-api/ModularDrawer/types.js +22 -0
  120. package/lib-es/wallet-api/ModularDrawer/types.js.map +1 -0
  121. package/lib-es/wallet-api/ModularDrawer/utils.d.ts +5 -0
  122. package/lib-es/wallet-api/ModularDrawer/utils.d.ts.map +1 -0
  123. package/lib-es/wallet-api/ModularDrawer/utils.js +25 -0
  124. package/lib-es/wallet-api/ModularDrawer/utils.js.map +1 -0
  125. package/lib-es/wallet-api/react.d.ts +2 -0
  126. package/lib-es/wallet-api/react.d.ts.map +1 -1
  127. package/lib-es/wallet-api/react.js +2 -1
  128. package/lib-es/wallet-api/react.js.map +1 -1
  129. package/package.json +50 -49
  130. package/src/apps/support.ts +11 -0
  131. package/src/e2e/enum/Account.ts +5 -5
  132. package/src/e2e/speculos.ts +29 -10
  133. package/src/e2e/speculosCI.ts +161 -0
  134. package/src/featureFlags/defaultFeatures.ts +1 -0
  135. package/src/hw/actions/manager.ts +1 -1
  136. package/src/hw/connectApp.ts +245 -178
  137. package/src/hw/connectAppEventMapper.ts +364 -0
  138. package/src/hw/connectManager.ts +116 -74
  139. package/src/hw/connectManagerEventMapper.ts +109 -0
  140. package/src/notifications/AnnouncementProvider/index.tsx +21 -17
  141. package/src/notifications/AnnouncementProvider/machine.ts +9 -8
  142. package/src/notifications/ServiceStatusProvider/index.tsx +11 -8
  143. package/src/notifications/ServiceStatusProvider/machine.ts +4 -4
  144. package/src/wallet-api/ModularDrawer/types.ts +45 -0
  145. package/src/wallet-api/ModularDrawer/utils.ts +37 -0
  146. package/src/wallet-api/react.ts +37 -31
  147. package/lib/index.d.ts +0 -2
  148. package/lib/index.d.ts.map +0 -1
  149. package/lib/index.js +0 -3
  150. package/lib/index.js.map +0 -1
  151. package/lib-es/index.d.ts +0 -1
  152. package/lib-es/index.d.ts.map +0 -1
  153. package/lib-es/index.js +0 -2
  154. package/lib-es/index.js.map +0 -1
  155. package/src/index.ts +0 -0
@@ -0,0 +1,364 @@
1
+ import { Observable, Subject, of, share, takeUntil } from "rxjs";
2
+ import { catchError, tap, withLatestFrom } from "rxjs/operators";
3
+ import type {
4
+ DeviceManagementKit,
5
+ DeviceActionState,
6
+ InstallPlan,
7
+ ExecuteDeviceActionReturnType,
8
+ DeviceSessionState,
9
+ GetOsVersionResponse,
10
+ FirmwareUpdateContext as DmkFirmwareUpdateContext,
11
+ } from "@ledgerhq/device-management-kit";
12
+ import {
13
+ DeviceActionStatus,
14
+ DeviceLockedError,
15
+ DeviceSessionStateType,
16
+ DeviceStatus,
17
+ UserInteractionRequired,
18
+ OutOfMemoryDAError,
19
+ SecureChannelError,
20
+ UnsupportedFirmwareDAError,
21
+ } from "@ledgerhq/device-management-kit";
22
+ import type {
23
+ ConnectAppDAOutput,
24
+ ConnectAppDAError,
25
+ ConnectAppDAIntermediateValue,
26
+ } from "@ledgerhq/live-dmk-shared";
27
+ import { DeviceInfo, FirmwareUpdateContext } from "@ledgerhq/types-live";
28
+ import {
29
+ UserRefusedAllowManager,
30
+ UserRefusedOnDevice,
31
+ LatestFirmwareVersionRequired,
32
+ } from "@ledgerhq/errors";
33
+
34
+ import type { SkippedAppOp } from "../apps/types";
35
+ import { SkipReason } from "../apps/types";
36
+ import { parseDeviceInfo } from "../deviceSDK/tasks/getDeviceInfo";
37
+ import { ConnectAppEvent } from "./connectApp";
38
+
39
+ export class ConnectAppEventMapper {
40
+ private openAppRequested: boolean = false;
41
+ private permissionRequested: boolean = false;
42
+ private lastSeenDeviceSent: boolean = false;
43
+ private installPlan: InstallPlan | null = null;
44
+ private eventSubject = new Subject<ConnectAppEvent>();
45
+
46
+ constructor(
47
+ private dmk: DeviceManagementKit,
48
+ private sessionId: string,
49
+ private appName: string,
50
+ private events: ExecuteDeviceActionReturnType<
51
+ ConnectAppDAOutput,
52
+ ConnectAppDAError,
53
+ ConnectAppDAIntermediateValue
54
+ >,
55
+ ) {}
56
+
57
+ map(): Observable<ConnectAppEvent> {
58
+ const cancelAction = this.events.cancel;
59
+ const unsubscribe = new Subject<void>();
60
+
61
+ // Create a shared observable for device session state
62
+ const deviceSessionState = this.dmk
63
+ .getDeviceSessionState({ sessionId: this.sessionId })
64
+ .pipe(share());
65
+
66
+ // Subscribe to device action events
67
+ this.events.observable
68
+ .pipe(
69
+ withLatestFrom(deviceSessionState),
70
+ tap(([event, deviceState]) => this.handleEvent(event, deviceState)),
71
+ takeUntil(unsubscribe),
72
+ catchError(error => this.handleError(error)),
73
+ )
74
+ .subscribe();
75
+
76
+ // Subscribe to device session state events
77
+ deviceSessionState
78
+ .pipe(
79
+ tap(deviceState => this.handleDeviceState(deviceState)),
80
+ takeUntil(unsubscribe),
81
+ )
82
+ .subscribe();
83
+
84
+ return new Observable<ConnectAppEvent>(observer => {
85
+ const sub = this.eventSubject.subscribe(observer);
86
+ return () => {
87
+ sub.unsubscribe();
88
+ cancelAction();
89
+ unsubscribe.next();
90
+ };
91
+ });
92
+ }
93
+
94
+ private handleDeviceState(deviceState: DeviceSessionState): void {
95
+ if (deviceState.sessionStateType === DeviceSessionStateType.Connected) {
96
+ return;
97
+ }
98
+
99
+ if (deviceState.deviceStatus === DeviceStatus.NOT_CONNECTED) {
100
+ this.eventSubject.next({ type: "disconnected", expected: false });
101
+ } else if (
102
+ deviceState.firmwareVersion?.metadata &&
103
+ deviceState.firmwareUpdateContext &&
104
+ !this.lastSeenDeviceSent
105
+ ) {
106
+ this.lastSeenDeviceSent = true;
107
+ this.eventSubject.next({
108
+ type: "device-update-last-seen",
109
+ deviceInfo: this.mapDeviceInfo(deviceState.firmwareVersion.metadata),
110
+ latestFirmware: this.mapLatestFirmware(deviceState.firmwareUpdateContext),
111
+ });
112
+ }
113
+ }
114
+
115
+ private handleEvent(
116
+ event: DeviceActionState<ConnectAppDAOutput, ConnectAppDAError, ConnectAppDAIntermediateValue>,
117
+ deviceState: DeviceSessionState,
118
+ ): void {
119
+ switch (event.status) {
120
+ case DeviceActionStatus.Pending:
121
+ this.handlePendingEvent(event.intermediateValue);
122
+ break;
123
+ case DeviceActionStatus.Completed:
124
+ this.handleCompletedEvent(event.output, deviceState);
125
+ break;
126
+ case DeviceActionStatus.Error:
127
+ this.handleErrorEvent(event.error, deviceState);
128
+ break;
129
+ case DeviceActionStatus.NotStarted:
130
+ case DeviceActionStatus.Stopped:
131
+ this.eventSubject.error(new Error("Unexpected device action status"));
132
+ break;
133
+ }
134
+ }
135
+
136
+ private handlePendingEvent(intermediateValue: ConnectAppDAIntermediateValue): void {
137
+ switch (intermediateValue.requiredUserInteraction) {
138
+ case UserInteractionRequired.ConfirmOpenApp:
139
+ if (!this.openAppRequested) {
140
+ this.openAppRequested = true;
141
+ this.eventSubject.next({ type: "ask-open-app", appName: this.appName });
142
+ }
143
+ break;
144
+ case UserInteractionRequired.AllowSecureConnection:
145
+ if (!this.permissionRequested) {
146
+ this.permissionRequested = true;
147
+ this.eventSubject.next({ type: "device-permission-requested" });
148
+ }
149
+ break;
150
+ case UserInteractionRequired.UnlockDevice:
151
+ this.eventSubject.next({ type: "lockedDevice" });
152
+ break;
153
+ case UserInteractionRequired.None:
154
+ if (this.openAppRequested) {
155
+ this.openAppRequested = false;
156
+ this.eventSubject.next({ type: "device-permission-granted" });
157
+ }
158
+ if (this.permissionRequested) {
159
+ this.permissionRequested = false;
160
+ this.eventSubject.next({ type: "device-permission-granted" });
161
+
162
+ // Simulate apps listing (not systematic step in LDMK)
163
+ this.eventSubject.next({ type: "listing-apps" });
164
+ }
165
+ if (intermediateValue.installPlan !== null) {
166
+ this.handleInstallPlan(intermediateValue.installPlan);
167
+ }
168
+ break;
169
+ }
170
+ }
171
+
172
+ private handleInstallPlan(installPlan: InstallPlan): void {
173
+ // Handle install plan resolved events
174
+ if (this.installPlan === null) {
175
+ // Skipped applications
176
+ const alreadyInstalled = this.mapSkippedApps(
177
+ installPlan.alreadyInstalled,
178
+ SkipReason.AppAlreadyInstalled,
179
+ );
180
+ const missing = this.mapSkippedApps(
181
+ installPlan.missingApplications,
182
+ SkipReason.NoSuchAppOnProvider,
183
+ );
184
+ const skippedAppOps = [...alreadyInstalled, ...missing];
185
+ if (skippedAppOps.length > 0) {
186
+ this.eventSubject.next({
187
+ type: "some-apps-skipped",
188
+ skippedAppOps,
189
+ });
190
+ }
191
+
192
+ // Install queue content
193
+ this.eventSubject.next({
194
+ type: "listed-apps",
195
+ installQueue: installPlan.installPlan.map(app => app.versionName),
196
+ });
197
+ }
198
+
199
+ // Handle ongoing install events
200
+ this.eventSubject.next({
201
+ type: "inline-install",
202
+ progress: installPlan.currentProgress,
203
+ itemProgress: installPlan.currentIndex,
204
+ currentAppOp: {
205
+ type: "install",
206
+ name: installPlan.installPlan[installPlan.currentIndex]!.versionName,
207
+ },
208
+ installQueue: installPlan.installPlan.map(app => app.versionName),
209
+ });
210
+ this.installPlan = installPlan;
211
+ }
212
+
213
+ private handleCompletedEvent(output: ConnectAppDAOutput, deviceState: DeviceSessionState): void {
214
+ if (deviceState.sessionStateType !== DeviceSessionStateType.Connected) {
215
+ // Handle opened app
216
+ const currentApp = deviceState.currentApp;
217
+ let flags: number | Buffer = 0;
218
+ if (typeof currentApp.flags === "number") {
219
+ flags = currentApp.flags;
220
+ } else if (currentApp.flags !== undefined) {
221
+ flags = Buffer.from(currentApp.flags);
222
+ }
223
+ this.eventSubject.next({
224
+ type: "opened",
225
+ app: {
226
+ name: currentApp.name,
227
+ version: currentApp.version,
228
+ flags,
229
+ },
230
+ derivation: output.derivation ? { address: output.derivation } : undefined,
231
+ });
232
+ }
233
+ this.eventSubject.complete();
234
+ }
235
+
236
+ private handleErrorEvent(error: ConnectAppDAError, deviceState: DeviceSessionState): void {
237
+ if (error instanceof OutOfMemoryDAError && this.installPlan !== null) {
238
+ const appNames = this.installPlan.installPlan.map(app => app.versionName);
239
+ this.eventSubject.next({
240
+ type: "app-not-installed",
241
+ appNames,
242
+ appName: appNames[0]!,
243
+ });
244
+ this.eventSubject.complete();
245
+ } else if (
246
+ error instanceof UnsupportedFirmwareDAError &&
247
+ deviceState.sessionStateType !== DeviceSessionStateType.Connected
248
+ ) {
249
+ this.eventSubject.error(
250
+ new LatestFirmwareVersionRequired("LatestFirmwareVersionRequired", {
251
+ current: deviceState.firmwareUpdateContext!.currentFirmware.version,
252
+ latest:
253
+ deviceState.firmwareUpdateContext?.availableUpdate?.finalFirmware.version ||
254
+ deviceState.firmwareUpdateContext!.currentFirmware.version,
255
+ }),
256
+ );
257
+ } else if (error instanceof DeviceLockedError) {
258
+ this.eventSubject.next({ type: "lockedDevice" });
259
+ this.eventSubject.complete();
260
+ } else if (error instanceof SecureChannelError) {
261
+ this.eventSubject.error(new UserRefusedAllowManager());
262
+ } else if ("_tag" in error && error._tag === "WebHidSendReportError") {
263
+ this.eventSubject.next({ type: "disconnected", expected: false });
264
+ this.eventSubject.complete();
265
+ } else if ("errorCode" in error && typeof error.errorCode === "string" && "_tag" in error) {
266
+ this.eventSubject.error(this.mapDeviceError(error.errorCode, error._tag));
267
+ } else {
268
+ this.eventSubject.error(error);
269
+ }
270
+ }
271
+
272
+ private handleError(error: Error): Observable<never> {
273
+ this.eventSubject.error(error);
274
+ return of();
275
+ }
276
+
277
+ private mapDeviceError(errorCode: string, defaultMessage: string): Error {
278
+ switch (errorCode) {
279
+ case "5501":
280
+ return new UserRefusedOnDevice();
281
+ default:
282
+ return new Error(defaultMessage);
283
+ }
284
+ }
285
+
286
+ private mapSkippedApps(appNames: string[], reason: SkipReason): SkippedAppOp[] {
287
+ return appNames.map(name => ({
288
+ reason,
289
+ appOp: {
290
+ type: "install",
291
+ name,
292
+ },
293
+ }));
294
+ }
295
+
296
+ private mapDeviceInfo(osVersion: GetOsVersionResponse): DeviceInfo {
297
+ return parseDeviceInfo({
298
+ isBootloader: osVersion.isBootloader,
299
+ rawVersion: osVersion.seVersion, // Always the SE version since at this step we cannot be in bootloader mode
300
+ targetId: osVersion.targetId,
301
+ seVersion: osVersion.seVersion,
302
+ seTargetId: osVersion.seTargetId,
303
+ mcuBlVersion: undefined, // We cannot be in bootloader mode at this step
304
+ mcuVersion: osVersion.mcuSephVersion,
305
+ mcuTargetId: osVersion.mcuTargetId,
306
+ flags: Buffer.from(osVersion.seFlags),
307
+ bootloaderVersion: osVersion.mcuBootloaderVersion,
308
+ hardwareVersion: parseInt(osVersion.hwVersion, 16),
309
+ languageId: osVersion.langId,
310
+ });
311
+ }
312
+
313
+ private mapLatestFirmware(updateContext: DmkFirmwareUpdateContext): FirmwareUpdateContext | null {
314
+ if (!updateContext.availableUpdate) {
315
+ return null;
316
+ }
317
+ const availableUpdate = updateContext.availableUpdate;
318
+ const osu = availableUpdate.osuFirmware;
319
+ const final = availableUpdate.finalFirmware;
320
+ return {
321
+ osu: {
322
+ id: osu.id,
323
+ perso: osu.perso,
324
+ firmware: osu.firmware,
325
+ firmware_key: osu.firmwareKey,
326
+ hash: osu.hash || "",
327
+ next_se_firmware_final_version: osu.nextFinalFirmware,
328
+ // Following fields are inherited from dto, but unused
329
+ description: undefined,
330
+ display_name: undefined,
331
+ notes: undefined,
332
+ name: "",
333
+ date_creation: "",
334
+ date_last_modified: "",
335
+ device_versions: [],
336
+ providers: [],
337
+ previous_se_firmware_final_version: [],
338
+ },
339
+ final: {
340
+ id: final.id,
341
+ name: final.version,
342
+ version: final.version,
343
+ perso: final.perso,
344
+ firmware: final.firmware || "",
345
+ firmware_key: final.firmwareKey || "",
346
+ hash: final.hash || "",
347
+ bytes: final.bytes || 0,
348
+ mcu_versions: final.mcuVersions,
349
+ // Following fields are inherited from dto, but unused
350
+ description: undefined,
351
+ display_name: undefined,
352
+ notes: undefined,
353
+ se_firmware: 0,
354
+ date_creation: "",
355
+ date_last_modified: "",
356
+ device_versions: [],
357
+ application_versions: [],
358
+ osu_versions: [],
359
+ providers: [],
360
+ },
361
+ shouldFlashMCU: availableUpdate.mcuUpdateRequired,
362
+ };
363
+ }
364
+ }
@@ -1,4 +1,4 @@
1
- import { Observable, concat, from, of, throwError } from "rxjs";
1
+ import { Observable, concat, concatWith, from, of, throwError } from "rxjs";
2
2
  import { concatMap, catchError, delay } from "rxjs/operators";
3
3
  import {
4
4
  TransportStatusError,
@@ -7,6 +7,9 @@ import {
7
7
  LockedDeviceError,
8
8
  } from "@ledgerhq/errors";
9
9
  import { DeviceInfo } from "@ledgerhq/types-live";
10
+ import type Transport from "@ledgerhq/hw-transport";
11
+ import type { DeviceManagementKit } from "@ledgerhq/device-management-kit";
12
+ import { PrepareConnectManagerDeviceAction } from "@ledgerhq/live-dmk-shared";
10
13
  import type { ListAppsEvent } from "../apps";
11
14
  import { listAppsUseCase } from "../device/use-cases/listAppsUseCase";
12
15
  import { withDevice } from "./deviceAccess";
@@ -17,6 +20,7 @@ import { DeviceNotOnboarded } from "../errors";
17
20
  import attemptToQuitApp, { AttemptToQuitAppEvent } from "./attemptToQuitApp";
18
21
  import { LockedDeviceEvent } from "./actions/types";
19
22
  import { ManagerRequest } from "./actions/manager";
23
+ import { PrepareConnectManagerEventMapper } from "./connectManagerEventMapper";
20
24
 
21
25
  export type Input = {
22
26
  deviceId: string;
@@ -40,85 +44,123 @@ export type ConnectManagerEvent =
40
44
  | ListAppsEvent
41
45
  | LockedDeviceEvent;
42
46
 
43
- const cmd = ({ deviceId, request }: Input): Observable<ConnectManagerEvent> =>
44
- withDevice(deviceId)(
45
- transport =>
46
- new Observable(o => {
47
- const timeoutSub = of({
48
- type: "unresponsiveDevice",
49
- } as ConnectManagerEvent)
50
- .pipe(delay(1000))
51
- .subscribe(e => o.next(e));
47
+ const cmd = (transport: Transport, { request }: Input): Observable<ConnectManagerEvent> =>
48
+ new Observable(o => {
49
+ const timeoutSub = of({
50
+ type: "unresponsiveDevice",
51
+ } as ConnectManagerEvent)
52
+ .pipe(delay(1000))
53
+ .subscribe(e => o.next(e));
52
54
 
53
- const sub = from(getDeviceInfo(transport))
54
- .pipe(
55
- concatMap(deviceInfo => {
56
- timeoutSub.unsubscribe();
55
+ const sub = from(getDeviceInfo(transport))
56
+ .pipe(
57
+ concatMap(deviceInfo => {
58
+ timeoutSub.unsubscribe();
57
59
 
58
- if (!deviceInfo.onboarded && !deviceInfo.isRecoveryMode) {
59
- throw new DeviceNotOnboarded();
60
- }
60
+ if (!deviceInfo.onboarded && !deviceInfo.isRecoveryMode) {
61
+ throw new DeviceNotOnboarded();
62
+ }
61
63
 
62
- if (deviceInfo.isBootloader) {
63
- return of({
64
- type: "bootloader",
65
- deviceInfo,
66
- } as ConnectManagerEvent);
67
- }
64
+ if (deviceInfo.isBootloader) {
65
+ return of({
66
+ type: "bootloader",
67
+ deviceInfo,
68
+ } as ConnectManagerEvent);
69
+ }
68
70
 
69
- if (deviceInfo.isOSU) {
70
- return of({
71
- type: "osu",
72
- deviceInfo,
73
- } as ConnectManagerEvent);
74
- }
71
+ if (deviceInfo.isOSU) {
72
+ return of({
73
+ type: "osu",
74
+ deviceInfo,
75
+ } as ConnectManagerEvent);
76
+ }
75
77
 
76
- return concat(
77
- of({
78
- type: "listingApps",
79
- deviceInfo,
80
- } as ConnectManagerEvent),
81
- listAppsUseCase(transport, deviceInfo),
82
- );
83
- }),
84
- catchError((e: unknown) => {
85
- if (e instanceof LockedDeviceError) {
86
- return of({
87
- type: "lockedDevice",
88
- } as ConnectManagerEvent);
89
- } else if (
90
- e instanceof DeviceOnDashboardExpected ||
91
- (e &&
92
- e instanceof TransportStatusError &&
93
- [
94
- StatusCodes.CLA_NOT_SUPPORTED,
95
- StatusCodes.INS_NOT_SUPPORTED,
96
- StatusCodes.UNKNOWN_APDU,
97
- 0x6e01, // No StatusCodes definition
98
- 0x6d01, // No StatusCodes definition
99
- ].includes(e.statusCode))
100
- ) {
101
- return from(getAppAndVersion(transport)).pipe(
102
- concatMap(appAndVersion => {
103
- return !request?.autoQuitAppDisabled && !isDashboardName(appAndVersion.name)
104
- ? attemptToQuitApp(transport, appAndVersion)
105
- : of({
106
- type: "appDetected",
107
- } as ConnectManagerEvent);
108
- }),
109
- );
110
- }
78
+ return concat(
79
+ of({
80
+ type: "listingApps",
81
+ deviceInfo,
82
+ } as ConnectManagerEvent),
83
+ listAppsUseCase(transport, deviceInfo),
84
+ );
85
+ }),
86
+ catchError((e: unknown) => {
87
+ if (e instanceof LockedDeviceError) {
88
+ return of({
89
+ type: "lockedDevice",
90
+ } as ConnectManagerEvent);
91
+ } else if (
92
+ e instanceof DeviceOnDashboardExpected ||
93
+ (e &&
94
+ e instanceof TransportStatusError &&
95
+ [
96
+ StatusCodes.CLA_NOT_SUPPORTED,
97
+ StatusCodes.INS_NOT_SUPPORTED,
98
+ StatusCodes.UNKNOWN_APDU,
99
+ 0x6e01, // No StatusCodes definition
100
+ 0x6d01, // No StatusCodes definition
101
+ ].includes(e.statusCode))
102
+ ) {
103
+ return from(getAppAndVersion(transport)).pipe(
104
+ concatMap(appAndVersion => {
105
+ return !request?.autoQuitAppDisabled && !isDashboardName(appAndVersion.name)
106
+ ? attemptToQuitApp(transport, appAndVersion)
107
+ : of({
108
+ type: "appDetected",
109
+ } as ConnectManagerEvent);
110
+ }),
111
+ );
112
+ }
111
113
 
112
- return throwError(() => e);
113
- }),
114
- )
115
- .subscribe(o);
114
+ return throwError(() => e);
115
+ }),
116
+ )
117
+ .subscribe(o);
116
118
 
117
- return () => {
118
- timeoutSub.unsubscribe();
119
- sub.unsubscribe();
120
- };
121
- }),
119
+ return () => {
120
+ timeoutSub.unsubscribe();
121
+ sub.unsubscribe();
122
+ };
123
+ });
124
+
125
+ const isDmkTransport = (
126
+ transport: Transport,
127
+ ): transport is Transport & { dmk: DeviceManagementKit; sessionId: string } => {
128
+ return (
129
+ "dmk" in transport &&
130
+ transport.dmk !== undefined &&
131
+ "sessionId" in transport &&
132
+ transport.sessionId !== undefined
122
133
  );
134
+ };
123
135
 
124
- export default cmd;
136
+ export default function connectManagerFactory(
137
+ {
138
+ isLdmkConnectAppEnabled,
139
+ }: {
140
+ isLdmkConnectAppEnabled: boolean;
141
+ } = { isLdmkConnectAppEnabled: false },
142
+ ) {
143
+ if (!isLdmkConnectAppEnabled) {
144
+ return ({ deviceId, request }: Input): Observable<ConnectManagerEvent> =>
145
+ withDevice(deviceId)(transport => cmd(transport, { deviceId, request }));
146
+ }
147
+ return ({ deviceId, request }: Input): Observable<ConnectManagerEvent> =>
148
+ withDevice(deviceId)(transport => {
149
+ if (!isDmkTransport(transport)) {
150
+ return cmd(transport, { deviceId, request });
151
+ }
152
+ const { dmk, sessionId } = transport;
153
+ const deviceAction = new PrepareConnectManagerDeviceAction({
154
+ input: {
155
+ unlockTimeout: 0, // Expect to fail immediately when device is locked
156
+ },
157
+ });
158
+ const observable = dmk.executeDeviceAction({
159
+ sessionId,
160
+ deviceAction,
161
+ });
162
+ return new PrepareConnectManagerEventMapper(observable)
163
+ .map()
164
+ .pipe(concatWith(cmd(transport, { deviceId, request })));
165
+ });
166
+ }
@@ -0,0 +1,109 @@
1
+ import { Observable, Subject, of, takeUntil } from "rxjs";
2
+ import { catchError, tap } from "rxjs/operators";
3
+ import type {
4
+ DeviceActionState,
5
+ ExecuteDeviceActionReturnType,
6
+ } from "@ledgerhq/device-management-kit";
7
+ import {
8
+ DeviceActionStatus,
9
+ DeviceLockedError,
10
+ UserInteractionRequired,
11
+ } from "@ledgerhq/device-management-kit";
12
+ import type {
13
+ PrepareConnectManagerDAOutput,
14
+ PrepareConnectManagerDAError,
15
+ PrepareConnectManagerDAIntermediateValue,
16
+ } from "@ledgerhq/live-dmk-shared";
17
+ import { DisconnectedDevice } from "@ledgerhq/errors";
18
+
19
+ import { LockedDeviceEvent } from "./actions/types";
20
+
21
+ export class PrepareConnectManagerEventMapper {
22
+ private eventSubject = new Subject<LockedDeviceEvent>();
23
+
24
+ constructor(
25
+ private events: ExecuteDeviceActionReturnType<
26
+ PrepareConnectManagerDAOutput,
27
+ PrepareConnectManagerDAError,
28
+ PrepareConnectManagerDAIntermediateValue
29
+ >,
30
+ ) {}
31
+
32
+ map(): Observable<LockedDeviceEvent> {
33
+ const cancelAction = this.events.cancel;
34
+ const unsubscribe = new Subject<void>();
35
+
36
+ // Subscribe to device action events
37
+ this.events.observable
38
+ .pipe(
39
+ tap(event => this.handleEvent(event)),
40
+ takeUntil(unsubscribe),
41
+ catchError(error => this.handleError(error)),
42
+ )
43
+ .subscribe();
44
+
45
+ return new Observable<LockedDeviceEvent>(observer => {
46
+ const sub = this.eventSubject.subscribe(observer);
47
+ return () => {
48
+ sub.unsubscribe();
49
+ cancelAction();
50
+ unsubscribe.next();
51
+ };
52
+ });
53
+ }
54
+
55
+ private handleEvent(
56
+ event: DeviceActionState<
57
+ PrepareConnectManagerDAOutput,
58
+ PrepareConnectManagerDAError,
59
+ PrepareConnectManagerDAIntermediateValue
60
+ >,
61
+ ): void {
62
+ switch (event.status) {
63
+ case DeviceActionStatus.Pending:
64
+ this.handlePendingEvent(event.intermediateValue);
65
+ break;
66
+ case DeviceActionStatus.Completed:
67
+ this.handleCompletedEvent(event.output);
68
+ break;
69
+ case DeviceActionStatus.Error:
70
+ this.handleErrorEvent(event.error);
71
+ break;
72
+ case DeviceActionStatus.NotStarted:
73
+ case DeviceActionStatus.Stopped:
74
+ this.eventSubject.error(new Error("Unexpected device action status"));
75
+ break;
76
+ }
77
+ }
78
+
79
+ private handlePendingEvent(intermediateValue: PrepareConnectManagerDAIntermediateValue): void {
80
+ switch (intermediateValue.requiredUserInteraction) {
81
+ case UserInteractionRequired.UnlockDevice:
82
+ this.eventSubject.next({ type: "lockedDevice" });
83
+ break;
84
+ case UserInteractionRequired.None:
85
+ // Nothing to do
86
+ break;
87
+ }
88
+ }
89
+
90
+ private handleCompletedEvent(_output: PrepareConnectManagerDAOutput): void {
91
+ this.eventSubject.complete();
92
+ }
93
+
94
+ private handleErrorEvent(error: PrepareConnectManagerDAError): void {
95
+ if (error instanceof DeviceLockedError) {
96
+ this.eventSubject.next({ type: "lockedDevice" });
97
+ this.eventSubject.complete();
98
+ } else if ("_tag" in error && error._tag === "WebHidSendReportError") {
99
+ this.eventSubject.error(new DisconnectedDevice());
100
+ } else {
101
+ this.eventSubject.error(error);
102
+ }
103
+ }
104
+
105
+ private handleError(error: Error): Observable<never> {
106
+ this.eventSubject.error(error);
107
+ return of();
108
+ }
109
+ }