@hsuite/native-connect-angular 2.1.0 → 2.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.
Files changed (183) hide show
  1. package/coverage/coverage-summary.json +25 -21
  2. package/coverage/index.html +108 -108
  3. package/coverage/lcov-report/index.html +108 -108
  4. package/coverage/lcov-report/lib/components/account-selector/account-actions/account-actions.component.ts.html +1 -1
  5. package/coverage/lcov-report/lib/components/account-selector/account-actions/index.html +1 -1
  6. package/coverage/lcov-report/lib/components/account-selector/account-filter/account-filter.component.ts.html +1 -1
  7. package/coverage/lcov-report/lib/components/account-selector/account-filter/index.html +1 -1
  8. package/coverage/lcov-report/lib/components/account-selector/account-formatting.service.ts.html +13 -19
  9. package/coverage/lcov-report/lib/components/account-selector/account-grouping.service.ts.html +1 -1
  10. package/coverage/lcov-report/lib/components/account-selector/account-list/account-list.component.ts.html +1 -1
  11. package/coverage/lcov-report/lib/components/account-selector/account-list/index.html +1 -1
  12. package/coverage/lcov-report/lib/components/account-selector/account-selector.component.ts.html +1 -1
  13. package/coverage/lcov-report/lib/components/account-selector/account-selector.service.ts.html +1 -1
  14. package/coverage/lcov-report/lib/components/account-selector/index.html +5 -5
  15. package/coverage/lcov-report/lib/components/wallet-account-display/index.html +1 -1
  16. package/coverage/lcov-report/lib/components/wallet-account-display/wallet-account-display.component.ts.html +10 -10
  17. package/coverage/lcov-report/lib/components/wallet-connect-button/index.html +1 -1
  18. package/coverage/lcov-report/lib/components/wallet-connect-button/wallet-connect-button.component.ts.html +1 -1
  19. package/coverage/lcov-report/lib/components/wallet-connect-prompt/index.html +1 -1
  20. package/coverage/lcov-report/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts.html +1 -1
  21. package/coverage/lcov-report/lib/components/wallet-connected-guard/index.html +1 -1
  22. package/coverage/lcov-report/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts.html +1 -1
  23. package/coverage/lcov-report/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts.html +1 -1
  24. package/coverage/lcov-report/lib/components/wallet-connection-modal/connection-method-step/index.html +1 -1
  25. package/coverage/lcov-report/lib/components/wallet-connection-modal/index.html +15 -15
  26. package/coverage/lcov-report/lib/components/wallet-connection-modal/qr-pairing-step/index.html +1 -1
  27. package/coverage/lcov-report/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts.html +1 -1
  28. package/coverage/lcov-report/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts.html +84 -36
  29. package/coverage/lcov-report/lib/components/wallet-session-display/index.html +1 -1
  30. package/coverage/lcov-report/lib/components/wallet-session-display/wallet-session-display.component.ts.html +1 -1
  31. package/coverage/lcov-report/lib/components/wallet-transaction-status/index.html +1 -1
  32. package/coverage/lcov-report/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts.html +1 -1
  33. package/coverage/lcov-report/lib/directives/index.html +1 -1
  34. package/coverage/lcov-report/lib/directives/wallet-connected.directive.ts.html +1 -1
  35. package/coverage/lcov-report/lib/directives/wallet-context.directive.ts.html +1 -1
  36. package/coverage/lcov-report/lib/directives/wallet-events.directive.ts.html +1 -1
  37. package/coverage/lcov-report/lib/hsuite-wallet.module.ts.html +1 -1
  38. package/coverage/lcov-report/lib/index.html +1 -1
  39. package/coverage/lcov-report/lib/models/connection-config.model.ts.html +1 -1
  40. package/coverage/lcov-report/lib/models/index.html +7 -7
  41. package/coverage/lcov-report/lib/models/provider-types.ts.html +11 -5
  42. package/coverage/lcov-report/lib/providers/base-wallet-provider.ts.html +20 -20
  43. package/coverage/lcov-report/lib/providers/hsuite-native/channel-client.service.ts.html +639 -636
  44. package/coverage/lcov-report/lib/providers/hsuite-native/index.html +19 -19
  45. package/coverage/lcov-report/lib/providers/hsuite-native-provider.ts.html +1 -1
  46. package/coverage/lcov-report/lib/providers/index.html +18 -18
  47. package/coverage/lcov-report/lib/providers/p2p-native/index.html +22 -22
  48. package/coverage/lcov-report/lib/providers/p2p-native/p2p-native.provider.ts.html +993 -993
  49. package/coverage/lcov-report/lib/providers/p2p-native/p2p-session-manager.ts.html +7 -4
  50. package/coverage/lcov-report/lib/providers/wallet-error-handler.ts.html +1 -1
  51. package/coverage/lcov-report/lib/providers/walletconnect/core/index.html +65 -65
  52. package/coverage/lcov-report/lib/providers/walletconnect/core/session-health.ts.html +240 -240
  53. package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-client-manager.ts.html +559 -559
  54. package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-provider.ts.html +1108 -1105
  55. package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-session-store.ts.html +493 -493
  56. package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts.html +366 -366
  57. package/coverage/lcov-report/lib/providers/walletconnect/signers/hedera-signer.ts.html +730 -730
  58. package/coverage/lcov-report/lib/providers/walletconnect/signers/index.html +43 -43
  59. package/coverage/lcov-report/lib/providers/walletconnect/signers/signer-factory.ts.html +234 -234
  60. package/coverage/lcov-report/lib/providers/walletconnect/signers/xrpl-signer.ts.html +650 -650
  61. package/coverage/lcov-report/lib/services/hsuite-auth.interceptor.ts.html +568 -0
  62. package/coverage/lcov-report/lib/services/index.html +50 -20
  63. package/coverage/lcov-report/lib/services/logger.service.ts.html +1 -1
  64. package/coverage/lcov-report/lib/services/smart-session.service.ts.html +1264 -0
  65. package/coverage/lcov-report/lib/services/transaction-builders/active-account-source.ts.html +250 -0
  66. package/coverage/lcov-report/lib/services/transaction-builders/base-transaction-builder.service.ts.html +1 -1
  67. package/coverage/lcov-report/lib/services/transaction-builders/hedera-amount-utils.ts.html +155 -155
  68. package/coverage/lcov-report/lib/services/transaction-builders/hedera-transaction-builder.service.ts.html +2156 -2156
  69. package/coverage/lcov-report/lib/services/transaction-builders/index.html +58 -43
  70. package/coverage/lcov-report/lib/services/transaction-builders/xrpl-transaction-builder.service.ts.html +1674 -1674
  71. package/coverage/lcov-report/lib/services/transaction.service.ts.html +4 -4
  72. package/coverage/lcov-report/lib/services/unified-wallet.service.ts.html +3 -3
  73. package/coverage/lcov-report/lib/services/wallet-context.service.ts.html +1 -1
  74. package/coverage/lcov-report/lib/services/wallet-event-bus.service.ts.html +249 -249
  75. package/coverage/lcov-report/lib/services/wallet-providers.service.ts.html +1 -1
  76. package/coverage/lcov-report/lib/transports/chrome-extension-transport.ts.html +1 -1
  77. package/coverage/lcov-report/lib/transports/index.html +1 -1
  78. package/coverage/lcov-report/lib/utils/index.html +36 -21
  79. package/coverage/lcov-report/lib/utils/ledger-icons.util.ts.html +254 -161
  80. package/coverage/lcov-report/lib/utils/ledger-ui-registry.ts.html +676 -0
  81. package/coverage/lcov.info +8567 -6896
  82. package/coverage/lib/components/account-selector/account-actions/account-actions.component.ts.html +1 -1
  83. package/coverage/lib/components/account-selector/account-actions/index.html +1 -1
  84. package/coverage/lib/components/account-selector/account-filter/account-filter.component.ts.html +1 -1
  85. package/coverage/lib/components/account-selector/account-filter/index.html +1 -1
  86. package/coverage/lib/components/account-selector/account-formatting.service.ts.html +13 -19
  87. package/coverage/lib/components/account-selector/account-grouping.service.ts.html +1 -1
  88. package/coverage/lib/components/account-selector/account-list/account-list.component.ts.html +1 -1
  89. package/coverage/lib/components/account-selector/account-list/index.html +1 -1
  90. package/coverage/lib/components/account-selector/account-selector.component.ts.html +1 -1
  91. package/coverage/lib/components/account-selector/account-selector.service.ts.html +1 -1
  92. package/coverage/lib/components/account-selector/index.html +5 -5
  93. package/coverage/lib/components/wallet-account-display/index.html +1 -1
  94. package/coverage/lib/components/wallet-account-display/wallet-account-display.component.ts.html +10 -10
  95. package/coverage/lib/components/wallet-connect-button/index.html +1 -1
  96. package/coverage/lib/components/wallet-connect-button/wallet-connect-button.component.ts.html +1 -1
  97. package/coverage/lib/components/wallet-connect-prompt/index.html +1 -1
  98. package/coverage/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts.html +1 -1
  99. package/coverage/lib/components/wallet-connected-guard/index.html +1 -1
  100. package/coverage/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts.html +1 -1
  101. package/coverage/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts.html +1 -1
  102. package/coverage/lib/components/wallet-connection-modal/connection-method-step/index.html +1 -1
  103. package/coverage/lib/components/wallet-connection-modal/index.html +15 -15
  104. package/coverage/lib/components/wallet-connection-modal/qr-pairing-step/index.html +1 -1
  105. package/coverage/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts.html +1 -1
  106. package/coverage/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts.html +84 -36
  107. package/coverage/lib/components/wallet-session-display/index.html +1 -1
  108. package/coverage/lib/components/wallet-session-display/wallet-session-display.component.ts.html +1 -1
  109. package/coverage/lib/components/wallet-transaction-status/index.html +1 -1
  110. package/coverage/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts.html +1 -1
  111. package/coverage/lib/directives/index.html +1 -1
  112. package/coverage/lib/directives/wallet-connected.directive.ts.html +1 -1
  113. package/coverage/lib/directives/wallet-context.directive.ts.html +1 -1
  114. package/coverage/lib/directives/wallet-events.directive.ts.html +1 -1
  115. package/coverage/lib/hsuite-wallet.module.ts.html +1 -1
  116. package/coverage/lib/index.html +1 -1
  117. package/coverage/lib/models/connection-config.model.ts.html +1 -1
  118. package/coverage/lib/models/index.html +7 -7
  119. package/coverage/lib/models/provider-types.ts.html +11 -5
  120. package/coverage/lib/providers/base-wallet-provider.ts.html +20 -20
  121. package/coverage/lib/providers/hsuite-native/channel-client.service.ts.html +639 -636
  122. package/coverage/lib/providers/hsuite-native/index.html +19 -19
  123. package/coverage/lib/providers/hsuite-native-provider.ts.html +1 -1
  124. package/coverage/lib/providers/index.html +18 -18
  125. package/coverage/lib/providers/p2p-native/index.html +22 -22
  126. package/coverage/lib/providers/p2p-native/p2p-native.provider.ts.html +993 -993
  127. package/coverage/lib/providers/p2p-native/p2p-session-manager.ts.html +7 -4
  128. package/coverage/lib/providers/wallet-error-handler.ts.html +1 -1
  129. package/coverage/lib/providers/walletconnect/core/index.html +65 -65
  130. package/coverage/lib/providers/walletconnect/core/session-health.ts.html +240 -240
  131. package/coverage/lib/providers/walletconnect/core/walletconnect-client-manager.ts.html +559 -559
  132. package/coverage/lib/providers/walletconnect/core/walletconnect-provider.ts.html +1108 -1105
  133. package/coverage/lib/providers/walletconnect/core/walletconnect-session-store.ts.html +493 -493
  134. package/coverage/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts.html +366 -366
  135. package/coverage/lib/providers/walletconnect/signers/hedera-signer.ts.html +730 -730
  136. package/coverage/lib/providers/walletconnect/signers/index.html +43 -43
  137. package/coverage/lib/providers/walletconnect/signers/signer-factory.ts.html +234 -234
  138. package/coverage/lib/providers/walletconnect/signers/xrpl-signer.ts.html +650 -650
  139. package/coverage/lib/services/hsuite-auth.interceptor.ts.html +568 -0
  140. package/coverage/lib/services/index.html +50 -20
  141. package/coverage/lib/services/logger.service.ts.html +1 -1
  142. package/coverage/lib/services/smart-session.service.ts.html +1264 -0
  143. package/coverage/lib/services/transaction-builders/active-account-source.ts.html +250 -0
  144. package/coverage/lib/services/transaction-builders/base-transaction-builder.service.ts.html +1 -1
  145. package/coverage/lib/services/transaction-builders/hedera-amount-utils.ts.html +155 -155
  146. package/coverage/lib/services/transaction-builders/hedera-transaction-builder.service.ts.html +2156 -2156
  147. package/coverage/lib/services/transaction-builders/index.html +58 -43
  148. package/coverage/lib/services/transaction-builders/xrpl-transaction-builder.service.ts.html +1674 -1674
  149. package/coverage/lib/services/transaction.service.ts.html +4 -4
  150. package/coverage/lib/services/unified-wallet.service.ts.html +3 -3
  151. package/coverage/lib/services/wallet-context.service.ts.html +1 -1
  152. package/coverage/lib/services/wallet-event-bus.service.ts.html +249 -249
  153. package/coverage/lib/services/wallet-providers.service.ts.html +1 -1
  154. package/coverage/lib/transports/chrome-extension-transport.ts.html +1 -1
  155. package/coverage/lib/transports/index.html +1 -1
  156. package/coverage/lib/utils/index.html +36 -21
  157. package/coverage/lib/utils/ledger-icons.util.ts.html +254 -161
  158. package/coverage/lib/utils/ledger-ui-registry.ts.html +676 -0
  159. package/dist/fesm2022/hsuite-native-connect-angular.mjs +854 -308
  160. package/dist/fesm2022/hsuite-native-connect-angular.mjs.map +1 -1
  161. package/dist/index.d.ts +456 -30
  162. package/package.json +4 -4
  163. package/src/index.ts +26 -0
  164. package/src/lib/components/account-selector/account-formatting.service.ts +8 -10
  165. package/src/lib/components/wallet-account-display/wallet-account-display.component.ts +9 -9
  166. package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts +34 -18
  167. package/src/lib/models/provider-types.ts +3 -1
  168. package/src/lib/models/unified-account.model.ts +4 -1
  169. package/src/lib/providers/hsuite-native/channel-client.service.ts +1 -0
  170. package/src/lib/providers/p2p-native/p2p-session-manager.ts +1 -0
  171. package/src/lib/providers/walletconnect/core/walletconnect-provider.ts +2 -1
  172. package/src/lib/services/hsuite-auth.interceptor.ts +159 -0
  173. package/src/lib/services/smart-session.service.ts +378 -0
  174. package/src/lib/services/transaction-builders/active-account-source.spec.ts +75 -0
  175. package/src/lib/services/transaction-builders/active-account-source.ts +55 -0
  176. package/src/lib/services/transaction-builders/hedera-transaction-builder.service.ts +4 -4
  177. package/src/lib/services/transaction-builders/index.ts +1 -0
  178. package/src/lib/services/transaction.service.ts +7 -3
  179. package/src/lib/services/unified-wallet.service.spec.ts +1 -1
  180. package/src/lib/services/unified-wallet.service.ts +2 -2
  181. package/src/lib/utils/index.ts +1 -0
  182. package/src/lib/utils/ledger-icons.util.ts +61 -30
  183. package/src/lib/utils/ledger-ui-registry.ts +197 -0
@@ -0,0 +1,378 @@
1
+ /**
2
+ * HSuite Native Connect
3
+ * Copyright 2024-2025 HSuite (https://hsuite.finance)
4
+ *
5
+ * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
6
+ *
7
+ * This file is part of HSuite Native Connect. For commercial licensing,
8
+ * visit https://hsuite.finance/licensing
9
+ */
10
+
11
+ /**
12
+ * @file Smart-host customer session helper.
13
+ *
14
+ * @module services/smart-session
15
+ *
16
+ * @description
17
+ * SmartSessionService automates the smart-host authentication handshake for
18
+ * HSuite dApps. It discovers the host's `appId`, requests a challenge, signs it
19
+ * with the active wallet account, and exchanges the signed challenge for a
20
+ * short-lived bearer token.
21
+ *
22
+ * **Typical flow:**
23
+ * ```typescript
24
+ * const session = inject(SmartSessionService);
25
+ * await session.login();
26
+ * const bearer = session.getBearer();
27
+ * ```
28
+ *
29
+ * The service also exposes {@link withSession} for automatic 401 recovery:
30
+ * operations that fail with HTTP 401 are retried once after a fresh login.
31
+ */
32
+
33
+ import { HttpClient } from '@angular/common/http';
34
+ import { Injectable, inject, InjectionToken } from '@angular/core';
35
+ import { getLogger } from '@hsuite/native-connect-sdk';
36
+ import { firstValueFrom } from 'rxjs';
37
+
38
+ import type { SignMessageResult } from '../providers/base-wallet-provider';
39
+
40
+ import { TransactionService } from './transaction.service';
41
+ import { UnifiedWalletService } from './unified-wallet.service';
42
+
43
+ const logger = getLogger().scoped?.('SmartSessionService') ?? getLogger();
44
+
45
+ /**
46
+ * Configuration token for {@link SmartSessionService}.
47
+ */
48
+ export const SMART_SESSION_CONFIG = new InjectionToken<SmartSessionConfig>('SMART_SESSION_CONFIG', {
49
+ factory: () => ({}),
50
+ });
51
+
52
+ /**
53
+ * Configuration for the smart-host session handshake.
54
+ */
55
+ export interface SmartSessionConfig {
56
+ /**
57
+ * Base URL for the smart-host API.
58
+ * @default '' (relative to the current origin)
59
+ */
60
+ baseUrl?: string;
61
+
62
+ /**
63
+ * Health endpoint used to discover the host `appId`.
64
+ * @default '/api/health'
65
+ */
66
+ healthEndpoint?: string;
67
+
68
+ /**
69
+ * Challenge endpoint used to obtain a message to sign.
70
+ * @default '/api/auth/challenge'
71
+ */
72
+ challengeEndpoint?: string;
73
+
74
+ /**
75
+ * Verify endpoint used to exchange the signed challenge for a bearer token.
76
+ * @default '/api/auth/verify'
77
+ */
78
+ verifyEndpoint?: string;
79
+
80
+ /**
81
+ * sessionStorage key used to persist the session token and expiry.
82
+ * @default 'hsuite.smart-session'
83
+ */
84
+ storageKey?: string;
85
+ }
86
+
87
+ /**
88
+ * Health response shape expected from the smart host.
89
+ */
90
+ export interface SmartHostHealth {
91
+ appId: string;
92
+ [key: string]: unknown;
93
+ }
94
+
95
+ /**
96
+ * Challenge response shape expected from the smart host.
97
+ */
98
+ export interface SmartHostChallenge {
99
+ challenge: string;
100
+ [key: string]: unknown;
101
+ }
102
+
103
+ /**
104
+ * Verify request payload sent to the smart host.
105
+ */
106
+ export interface SmartHostVerifyRequest {
107
+ appId: string;
108
+ accountAddress: string;
109
+ publicKey?: string;
110
+ signature: string;
111
+ algorithm?: string;
112
+ message: string;
113
+ caipChainId?: string;
114
+ ledgerId?: string;
115
+ networkId?: string;
116
+ }
117
+
118
+ /**
119
+ * Verify response shape expected from the smart host.
120
+ */
121
+ export interface SmartHostVerifyResponse {
122
+ bearer: string;
123
+ expiresAt?: number;
124
+ expiresIn?: number;
125
+ [key: string]: unknown;
126
+ }
127
+
128
+ /**
129
+ * Persisted smart session.
130
+ */
131
+ interface StoredSession {
132
+ bearer: string;
133
+ expiresAt: number;
134
+ }
135
+
136
+ /**
137
+ * Smart-host customer session helper.
138
+ *
139
+ * @service SmartSessionService
140
+ *
141
+ * @description
142
+ * Encapsulates the full challenge → sign → verify → store JWT flow required by
143
+ * HSuite smart hosts. The service is intentionally thin: it delegates signing to
144
+ * {@link TransactionService} and relies on standard Angular `HttpClient` for the
145
+ * host HTTP calls.
146
+ *
147
+ * @providedIn root
148
+ */
149
+ @Injectable({ providedIn: 'root' })
150
+ export class SmartSessionService {
151
+ private readonly http = inject(HttpClient);
152
+ private readonly walletService = inject(UnifiedWalletService);
153
+ private readonly transactionService = inject(TransactionService);
154
+ private readonly config = inject(SMART_SESSION_CONFIG);
155
+
156
+ private get baseUrl(): string {
157
+ return this.config.baseUrl ?? '';
158
+ }
159
+
160
+ private get healthEndpoint(): string {
161
+ return this.config.healthEndpoint ?? '/api/health';
162
+ }
163
+
164
+ private get challengeEndpoint(): string {
165
+ return this.config.challengeEndpoint ?? '/api/auth/challenge';
166
+ }
167
+
168
+ private get verifyEndpoint(): string {
169
+ return this.config.verifyEndpoint ?? '/api/auth/verify';
170
+ }
171
+
172
+ private get storageKey(): string {
173
+ return this.config.storageKey ?? 'hsuite.smart-session';
174
+ }
175
+
176
+ /**
177
+ * Returns the currently stored bearer token, or `null` if there is no session.
178
+ *
179
+ * @returns Bearer token string or null
180
+ */
181
+ getBearer(): string | null {
182
+ const stored = this.readStoredSession();
183
+ return stored && stored.expiresAt > Date.now() ? stored.bearer : null;
184
+ }
185
+
186
+ /**
187
+ * Checks whether a non-expired session is available.
188
+ *
189
+ * @returns true if logged in and the bearer has not expired
190
+ */
191
+ isLoggedIn(): boolean {
192
+ return this.getBearer() !== null;
193
+ }
194
+
195
+ /**
196
+ * Performs the full smart-host login handshake.
197
+ *
198
+ * @description
199
+ * 1. Discovers `appId` from the health endpoint.
200
+ * 2. Fetches a challenge from the challenge endpoint.
201
+ * 3. Signs the challenge with the active wallet account.
202
+ * 4. Exchanges the signed challenge for a bearer token.
203
+ * 5. Stores the bearer and expiry in `sessionStorage`.
204
+ *
205
+ * @returns The verify response from the host
206
+ *
207
+ * @throws {Error} If no active account is available
208
+ * @throws {Error} If the health endpoint does not return an `appId`
209
+ * @throws {Error} If the challenge endpoint does not return a `challenge`
210
+ * @throws {Error} If the verify call fails
211
+ */
212
+ async login(): Promise<SmartHostVerifyResponse> {
213
+ const account = this.walletService.activeAccount();
214
+ if (!account) {
215
+ throw new Error('No active account');
216
+ }
217
+
218
+ logger.info('Starting smart-host login handshake', {
219
+ accountAddress: account.address,
220
+ });
221
+
222
+ const health = await this.fetchHealth();
223
+ if (!health.appId) {
224
+ throw new Error('Smart host health endpoint did not return appId');
225
+ }
226
+
227
+ const challengeResponse = await this.fetchChallenge(health.appId);
228
+ if (!challengeResponse.challenge) {
229
+ throw new Error('Smart host challenge endpoint did not return challenge');
230
+ }
231
+
232
+ const signResult = await this.transactionService.signMessage(challengeResponse.challenge);
233
+
234
+ const verifyResponse = await this.verifyChallenge(
235
+ health.appId,
236
+ account,
237
+ challengeResponse.challenge,
238
+ signResult,
239
+ );
240
+
241
+ if (!verifyResponse.bearer) {
242
+ throw new Error('Smart host verify endpoint did not return bearer');
243
+ }
244
+
245
+ const expiresAt = this.resolveExpiry(verifyResponse);
246
+ this.storeSession({ bearer: verifyResponse.bearer, expiresAt });
247
+
248
+ logger.info('Smart-host login successful', {
249
+ appId: health.appId,
250
+ expiresAt,
251
+ });
252
+
253
+ return verifyResponse;
254
+ }
255
+
256
+ /**
257
+ * Clears the stored session.
258
+ */
259
+ logout(): void {
260
+ this.clearStoredSession();
261
+ logger.info('Smart-host session cleared');
262
+ }
263
+
264
+ /**
265
+ * Runs an HTTP operation with automatic 401 recovery.
266
+ *
267
+ * @description
268
+ * Executes the provided operation. If it rejects with an HTTP 401 error,
269
+ * the service clears the current bearer, performs a fresh login, and retries
270
+ * the operation once. Any other error is propagated immediately.
271
+ *
272
+ * @param operation - Async operation that needs a valid session
273
+ * @returns The result of the operation
274
+ *
275
+ * @throws {Error} The original error if retry is not possible or fails
276
+ */
277
+ async withSession<T>(operation: () => Promise<T>): Promise<T> {
278
+ try {
279
+ return await operation();
280
+ } catch (error) {
281
+ if (this.isUnauthorizedError(error)) {
282
+ logger.warn('Operation received 401; attempting single session refresh');
283
+ this.logout();
284
+ await this.login();
285
+ return await operation();
286
+ }
287
+ throw error;
288
+ }
289
+ }
290
+
291
+ private async fetchHealth(): Promise<SmartHostHealth> {
292
+ return firstValueFrom(this.http.get<SmartHostHealth>(`${this.baseUrl}${this.healthEndpoint}`));
293
+ }
294
+
295
+ private async fetchChallenge(appId: string): Promise<SmartHostChallenge> {
296
+ return firstValueFrom(
297
+ this.http.post<SmartHostChallenge>(`${this.baseUrl}${this.challengeEndpoint}`, { appId }),
298
+ );
299
+ }
300
+
301
+ private async verifyChallenge(
302
+ appId: string,
303
+ account: import('../models/unified-account.model').UnifiedAccount,
304
+ challenge: string,
305
+ signResult: SignMessageResult,
306
+ ): Promise<SmartHostVerifyResponse> {
307
+ const payload: SmartHostVerifyRequest = {
308
+ appId,
309
+ accountAddress: account.address,
310
+ publicKey: signResult.publicKey ?? account.publicKey,
311
+ signature: signResult.signature,
312
+ algorithm: signResult.algorithm,
313
+ message: challenge,
314
+ caipChainId: signResult.caipChainId,
315
+ ledgerId: account.ledgerId,
316
+ networkId: account.networkId,
317
+ };
318
+
319
+ return firstValueFrom(
320
+ this.http.post<SmartHostVerifyResponse>(`${this.baseUrl}${this.verifyEndpoint}`, payload),
321
+ );
322
+ }
323
+
324
+ private resolveExpiry(response: SmartHostVerifyResponse): number {
325
+ if (response.expiresAt) {
326
+ return response.expiresAt;
327
+ }
328
+ if (response.expiresIn) {
329
+ return Date.now() + response.expiresIn * 1000;
330
+ }
331
+ // Default to 5 minutes if no expiry is provided
332
+ return Date.now() + 5 * 60 * 1000;
333
+ }
334
+
335
+ private readStoredSession(): StoredSession | null {
336
+ try {
337
+ const raw = sessionStorage.getItem(this.storageKey);
338
+ if (!raw) {
339
+ return null;
340
+ }
341
+ const parsed = JSON.parse(raw) as StoredSession;
342
+ if (!parsed.bearer || typeof parsed.expiresAt !== 'number') {
343
+ return null;
344
+ }
345
+ return parsed;
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+
351
+ private storeSession(session: StoredSession): void {
352
+ try {
353
+ sessionStorage.setItem(this.storageKey, JSON.stringify(session));
354
+ } catch (error) {
355
+ logger.warn('Failed to store smart session', { error });
356
+ }
357
+ }
358
+
359
+ private clearStoredSession(): void {
360
+ try {
361
+ sessionStorage.removeItem(this.storageKey);
362
+ } catch (error) {
363
+ logger.warn('Failed to clear smart session', { error });
364
+ }
365
+ }
366
+
367
+ private isUnauthorizedError(error: unknown): boolean {
368
+ if (!error || typeof error !== 'object') {
369
+ return false;
370
+ }
371
+ const status = (error as { status?: number }).status;
372
+ if (typeof status === 'number') {
373
+ return status === 401;
374
+ }
375
+ const message = String((error as { message?: unknown }).message ?? '');
376
+ return /\b401\b|unauthorized/i.test(message);
377
+ }
378
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * HSuite Native Connect
3
+ * Copyright 2024-2025 HSuite (https://hsuite.finance)
4
+ *
5
+ * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
6
+ *
7
+ * This file is part of HSuite Native Connect. For commercial licensing,
8
+ * visit https://hsuite.finance/licensing
9
+ */
10
+
11
+ /**
12
+ * @fileoverview #5 decoupling. The Hedera transaction builder must resolve the
13
+ * active account through the ACTIVE_ACCOUNT_SOURCE token, NOT by injecting
14
+ * UnifiedWalletService directly. A default root provider keeps the token
15
+ * pointing at UnifiedWalletService, so existing SDK consumers are unaffected
16
+ * (the comprehensive hedera-transaction-builder.service.spec, which provides a
17
+ * UnifiedWalletService mock, is the backward-compatibility proof). The embedded
18
+ * shell overrides the token with a one-member account context, which is why the
19
+ * stub-of-UnifiedWalletService and its Proxy guard can be deleted.
20
+ */
21
+
22
+ import { TestBed } from '@angular/core/testing';
23
+ import { describe, it, expect } from 'vitest';
24
+ import { signal } from '@angular/core';
25
+
26
+ import { HederaTransactionBuilderService } from './hedera-transaction-builder.service';
27
+ import { ACTIVE_ACCOUNT_SOURCE, type ActiveAccountSource } from './active-account-source';
28
+ import { LoggerService } from '../logger.service';
29
+ import { UnifiedWalletService } from '../unified-wallet.service';
30
+ import type { UnifiedAccount } from '../../models/unified-account.model';
31
+
32
+ const mockLogger = {
33
+ scoped: () => ({ debug() {}, info() {}, warn() {}, error() {} }),
34
+ };
35
+
36
+ function testnetSource(networkId = 'hedera:testnet'): ActiveAccountSource {
37
+ const account: UnifiedAccount = {
38
+ id: `embedded:hedera:${networkId}:0.0.2193470`,
39
+ address: '0.0.2193470',
40
+ ledgerId: 'hedera',
41
+ networkId,
42
+ providerId: 'test',
43
+ providerType: 'hsuite-native',
44
+ metadata: {},
45
+ };
46
+ return { activeAccount: signal<UnifiedAccount | null>(account) };
47
+ }
48
+
49
+ describe('ACTIVE_ACCOUNT_SOURCE (Hedera builder decoupling, #5)', () => {
50
+ it('builds a Hedera transaction from ACTIVE_ACCOUNT_SOURCE with NO UnifiedWalletService provided', async () => {
51
+ TestBed.configureTestingModule({
52
+ providers: [
53
+ { provide: ACTIVE_ACCOUNT_SOURCE, useValue: testnetSource() },
54
+ { provide: LoggerService, useValue: mockLogger },
55
+ // UnifiedWalletService is deliberately NOT provided: the builder must
56
+ // resolve its network purely from the account source.
57
+ ],
58
+ });
59
+
60
+ const builder = TestBed.inject(HederaTransactionBuilderService);
61
+ const payload = await builder.buildSendHbar('0.0.123', '0.0.456', 1);
62
+
63
+ expect(typeof payload).toBe('string');
64
+ expect(payload.length).toBeGreaterThan(0);
65
+ });
66
+
67
+ it('default provider falls back to UnifiedWalletService (backward compatible)', () => {
68
+ const fakeUnified = { activeAccount: signal<UnifiedAccount | null>(null) };
69
+ TestBed.configureTestingModule({
70
+ providers: [{ provide: UnifiedWalletService, useValue: fakeUnified }],
71
+ });
72
+
73
+ expect(TestBed.inject(ACTIVE_ACCOUNT_SOURCE)).toBe(fakeUnified);
74
+ });
75
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * HSuite Native Connect
3
+ * Copyright 2024-2025 HSuite (https://hsuite.finance)
4
+ *
5
+ * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
6
+ *
7
+ * This file is part of HSuite Native Connect. For commercial licensing,
8
+ * visit https://hsuite.finance/licensing
9
+ */
10
+
11
+ /**
12
+ * @file ACTIVE_ACCOUNT_SOURCE: the minimal active-account read surface the
13
+ * transaction builders need.
14
+ *
15
+ * @description
16
+ * The Hedera transaction builder only reads `activeAccount()` from the wallet
17
+ * (to pick the network and the entity key config). Injecting the whole
18
+ * {@link UnifiedWalletService} couples the builder to a heavy root service that
19
+ * also runs the dApp connect stack on construction. This token narrows that
20
+ * dependency to a single member.
21
+ *
22
+ * The default provider points at {@link UnifiedWalletService}, so existing SDK
23
+ * consumers that already provide it need NO wiring change (backward
24
+ * compatible). The embedded widget shell overrides this token with a one-member
25
+ * account context, which keeps the heavy root service from ever being
26
+ * constructed inside the wallet.
27
+ */
28
+
29
+ import { InjectionToken, inject, type Signal } from '@angular/core';
30
+
31
+ import type { UnifiedAccount } from '../../models/unified-account.model';
32
+ import { UnifiedWalletService } from '../unified-wallet.service';
33
+
34
+ /**
35
+ * The only member the transaction builders read from the active wallet account.
36
+ * Implemented by {@link UnifiedWalletService} (the default) and by the embedded
37
+ * shell's one-member account context.
38
+ */
39
+ export interface ActiveAccountSource {
40
+ readonly activeAccount: Signal<UnifiedAccount | null>;
41
+ }
42
+
43
+ /**
44
+ * Injection token for the active-account read surface. Defaults to
45
+ * {@link UnifiedWalletService} so consumers that already provide it need no
46
+ * extra wiring; override it to supply a narrower source (the embedded shell
47
+ * does this to avoid constructing the root service inside the wallet).
48
+ */
49
+ export const ACTIVE_ACCOUNT_SOURCE = new InjectionToken<ActiveAccountSource>(
50
+ 'ACTIVE_ACCOUNT_SOURCE',
51
+ {
52
+ providedIn: 'root',
53
+ factory: () => inject(UnifiedWalletService),
54
+ },
55
+ );
@@ -69,8 +69,8 @@ import {
69
69
  } from '@hashgraph/sdk';
70
70
 
71
71
  import { LoggerService } from '../logger.service';
72
- import { UnifiedWalletService } from '../unified-wallet.service';
73
72
 
73
+ import { ACTIVE_ACCOUNT_SOURCE } from './active-account-source';
74
74
  import { scaleHederaAmountToBaseUnits, assertSafeInteger } from './hedera-amount-utils';
75
75
 
76
76
  /**
@@ -270,7 +270,7 @@ export interface AccountCreateOptions {
270
270
  providedIn: 'root',
271
271
  })
272
272
  export class HederaTransactionBuilderService {
273
- private readonly wallet = inject(UnifiedWalletService);
273
+ private readonly accountSource = inject(ACTIVE_ACCOUNT_SOURCE);
274
274
  private readonly logger = inject(LoggerService).scoped('HederaTransactionBuilder');
275
275
 
276
276
  /**
@@ -286,7 +286,7 @@ export class HederaTransactionBuilderService {
286
286
  * @throws {Error} When used in methods that require a network, if no account is active
287
287
  */
288
288
  private getNetworkId(): string | undefined {
289
- const activeAccount = this.wallet.activeAccount();
289
+ const activeAccount = this.accountSource.activeAccount();
290
290
  return activeAccount?.networkId;
291
291
  }
292
292
 
@@ -337,7 +337,7 @@ export class HederaTransactionBuilderService {
337
337
  * @returns Key configuration or undefined if no active account
338
338
  */
339
339
  private getActiveAccountKeyConfig(): HederaKeyConfig | undefined {
340
- const activeAccount = this.wallet.activeAccount();
340
+ const activeAccount = this.accountSource.activeAccount();
341
341
  if (!activeAccount) {
342
342
  this.logger.debug('No active account for key config');
343
343
  return undefined;
@@ -52,3 +52,4 @@ export {
52
52
  } from './hedera-transaction-builder.service';
53
53
  export { XrplTransactionBuilderService } from './xrpl-transaction-builder.service';
54
54
  export { scaleHederaAmountToBaseUnits, assertSafeInteger } from './hedera-amount-utils';
55
+ export { ACTIVE_ACCOUNT_SOURCE, type ActiveAccountSource } from './active-account-source';
@@ -52,7 +52,11 @@ import { Injectable, inject, signal, type Signal, type WritableSignal } from '@a
52
52
  import { getLogger } from '@hsuite/native-connect-sdk';
53
53
  import { ToastController } from '@ionic/angular/standalone';
54
54
 
55
- import type { SignResult, SubmitResult } from '../providers/base-wallet-provider';
55
+ import type {
56
+ SignMessageResult,
57
+ SignResult,
58
+ SubmitResult,
59
+ } from '../providers/base-wallet-provider';
56
60
 
57
61
  import { UnifiedWalletService } from './unified-wallet.service';
58
62
  import { WalletEventBus } from './wallet-event-bus.service';
@@ -210,9 +214,9 @@ export class TransactionService {
210
214
  * @param message - Message to sign
211
215
  * @returns Sign result with signature
212
216
  */
213
- async signMessage(message: string): Promise<SignResult> {
217
+ async signMessage(message: string): Promise<SignMessageResult> {
214
218
  return this.executeTransaction(async () => {
215
- const result = (await this.walletService.signMessage(message)) as SignResult;
219
+ const result = await this.walletService.signMessage(message);
216
220
  logger.info('Message signed', { hasSignature: !!result.signature });
217
221
  await this.showSuccessToast('Message Signed', 'Your message has been signed successfully.');
218
222
  return result;
@@ -409,7 +409,7 @@ class TestableUnifiedWalletService {
409
409
  });
410
410
  }
411
411
 
412
- async signMessage(message: string, encoding?: 'utf-8' | 'base64'): Promise<unknown> {
412
+ async signMessage(message: string, encoding?: 'utf-8' | 'base64'): Promise<SignMessageResult> {
413
413
  const account = this._activeAccount();
414
414
  if (!account) {
415
415
  throw new Error('No active account');
@@ -49,7 +49,7 @@ import { getLogger } from '@hsuite/native-connect-sdk';
49
49
  import type { ConnectionConfig } from '../models/connection-config.model';
50
50
  import type { ProviderType, ConnectionStatus } from '../models/provider-types';
51
51
  import type { UnifiedAccount } from '../models/unified-account.model';
52
- import { BaseWalletProvider } from '../providers/base-wallet-provider';
52
+ import { BaseWalletProvider, SignMessageResult } from '../providers/base-wallet-provider';
53
53
  import { HsuiteNativeProvider } from '../providers/hsuite-native-provider';
54
54
  import { P2PNativeProvider } from '../providers/p2p-native/p2p-native.provider';
55
55
  import { WalletConnectV2Provider } from '../providers/walletconnect/core/walletconnect-provider';
@@ -563,7 +563,7 @@ export class UnifiedWalletService {
563
563
  * @throws {Error} If provider is not found
564
564
  * @throws {Error} If user rejects the signing request
565
565
  */
566
- async signMessage(message: string, encoding?: 'utf-8' | 'base64'): Promise<unknown> {
566
+ async signMessage(message: string, encoding?: 'utf-8' | 'base64'): Promise<SignMessageResult> {
567
567
  const account = this._activeAccount();
568
568
  if (!account) {
569
569
  throw new Error('No active account');
@@ -11,4 +11,5 @@
11
11
  /**
12
12
  * Utility exports for @hsuite/native-connect-angular
13
13
  */
14
+ export * from './ledger-ui-registry';
14
15
  export * from './ledger-icons.util';