@hsuite/native-connect-angular 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/USAGE_EXAMPLES.md +476 -0
- package/assets/wallets/extension.svg +7 -0
- package/assets/wallets/hashpack.svg +6 -0
- package/assets/wallets/hsuite.svg +11 -0
- package/assets/wallets/kabila.svg +11 -0
- package/assets/wallets/walletconnect.svg +13 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/coverage-summary.json +50 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +476 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +476 -0
- package/coverage/lcov-report/lib/components/account-selector/account-actions/account-actions.component.ts.html +868 -0
- package/coverage/lcov-report/lib/components/account-selector/account-actions/index.html +116 -0
- package/coverage/lcov-report/lib/components/account-selector/account-filter/account-filter.component.ts.html +1288 -0
- package/coverage/lcov-report/lib/components/account-selector/account-filter/index.html +116 -0
- package/coverage/lcov-report/lib/components/account-selector/account-formatting.service.ts.html +685 -0
- package/coverage/lcov-report/lib/components/account-selector/account-grouping.service.ts.html +766 -0
- package/coverage/lcov-report/lib/components/account-selector/account-list/account-list.component.ts.html +1495 -0
- package/coverage/lcov-report/lib/components/account-selector/account-list/index.html +116 -0
- package/coverage/lcov-report/lib/components/account-selector/account-selector.component.ts.html +1495 -0
- package/coverage/lcov-report/lib/components/account-selector/account-selector.service.ts.html +1588 -0
- package/coverage/lcov-report/lib/components/account-selector/index.html +161 -0
- package/coverage/lcov-report/lib/components/wallet-account-display/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-account-display/wallet-account-display.component.ts.html +505 -0
- package/coverage/lcov-report/lib/components/wallet-connect-button/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connect-button/wallet-connect-button.component.ts.html +805 -0
- package/coverage/lcov-report/lib/components/wallet-connect-prompt/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts.html +409 -0
- package/coverage/lcov-report/lib/components/wallet-connected-guard/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts.html +304 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts.html +436 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/connection-method-step/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/qr-pairing-step/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts.html +2287 -0
- package/coverage/lcov-report/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts.html +2275 -0
- package/coverage/lcov-report/lib/components/wallet-session-display/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-session-display/wallet-session-display.component.ts.html +676 -0
- package/coverage/lcov-report/lib/components/wallet-transaction-status/index.html +116 -0
- package/coverage/lcov-report/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts.html +703 -0
- package/coverage/lcov-report/lib/directives/index.html +146 -0
- package/coverage/lcov-report/lib/directives/wallet-connected.directive.ts.html +670 -0
- package/coverage/lcov-report/lib/directives/wallet-context.directive.ts.html +547 -0
- package/coverage/lcov-report/lib/directives/wallet-events.directive.ts.html +781 -0
- package/coverage/lcov-report/lib/hsuite-wallet.module.ts.html +715 -0
- package/coverage/lcov-report/lib/index.html +116 -0
- package/coverage/lcov-report/lib/models/connection-config.model.ts.html +280 -0
- package/coverage/lcov-report/lib/models/index.html +131 -0
- package/coverage/lcov-report/lib/models/provider-types.ts.html +577 -0
- package/coverage/lcov-report/lib/providers/base-wallet-provider.ts.html +1138 -0
- package/coverage/lcov-report/lib/providers/hsuite-native/channel-client.service.ts.html +2671 -0
- package/coverage/lcov-report/lib/providers/hsuite-native/index.html +116 -0
- package/coverage/lcov-report/lib/providers/hsuite-native-provider.ts.html +2347 -0
- package/coverage/lcov-report/lib/providers/index.html +146 -0
- package/coverage/lcov-report/lib/providers/p2p-native/index.html +131 -0
- package/coverage/lcov-report/lib/providers/p2p-native/p2p-native.provider.ts.html +2254 -0
- package/coverage/lcov-report/lib/providers/p2p-native/p2p-session-manager.ts.html +2170 -0
- package/coverage/lcov-report/lib/providers/wallet-error-handler.ts.html +1132 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/index.html +176 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/session-health.ts.html +673 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-client-manager.ts.html +1177 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-provider.ts.html +2563 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-session-store.ts.html +904 -0
- package/coverage/lcov-report/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts.html +982 -0
- package/coverage/lcov-report/lib/providers/walletconnect/signers/hedera-signer.ts.html +1915 -0
- package/coverage/lcov-report/lib/providers/walletconnect/signers/index.html +146 -0
- package/coverage/lcov-report/lib/providers/walletconnect/signers/signer-factory.ts.html +445 -0
- package/coverage/lcov-report/lib/providers/walletconnect/signers/xrpl-signer.ts.html +1519 -0
- package/coverage/lcov-report/lib/services/index.html +191 -0
- package/coverage/lcov-report/lib/services/logger.service.ts.html +463 -0
- package/coverage/lcov-report/lib/services/transaction-builders/base-transaction-builder.service.ts.html +1840 -0
- package/coverage/lcov-report/lib/services/transaction-builders/hedera-amount-utils.ts.html +337 -0
- package/coverage/lcov-report/lib/services/transaction-builders/hedera-transaction-builder.service.ts.html +3940 -0
- package/coverage/lcov-report/lib/services/transaction-builders/index.html +161 -0
- package/coverage/lcov-report/lib/services/transaction-builders/xrpl-transaction-builder.service.ts.html +2581 -0
- package/coverage/lcov-report/lib/services/transaction.service.ts.html +1123 -0
- package/coverage/lcov-report/lib/services/unified-wallet.service.ts.html +2641 -0
- package/coverage/lcov-report/lib/services/wallet-context.service.ts.html +637 -0
- package/coverage/lcov-report/lib/services/wallet-event-bus.service.ts.html +643 -0
- package/coverage/lcov-report/lib/services/wallet-providers.service.ts.html +496 -0
- package/coverage/lcov-report/lib/transports/chrome-extension-transport.ts.html +823 -0
- package/coverage/lcov-report/lib/transports/index.html +116 -0
- package/coverage/lcov-report/lib/utils/index.html +116 -0
- package/coverage/lcov-report/lib/utils/ledger-icons.util.ts.html +319 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +19252 -0
- package/coverage/lib/components/account-selector/account-actions/account-actions.component.ts.html +868 -0
- package/coverage/lib/components/account-selector/account-actions/index.html +116 -0
- package/coverage/lib/components/account-selector/account-filter/account-filter.component.ts.html +1288 -0
- package/coverage/lib/components/account-selector/account-filter/index.html +116 -0
- package/coverage/lib/components/account-selector/account-formatting.service.ts.html +685 -0
- package/coverage/lib/components/account-selector/account-grouping.service.ts.html +766 -0
- package/coverage/lib/components/account-selector/account-list/account-list.component.ts.html +1495 -0
- package/coverage/lib/components/account-selector/account-list/index.html +116 -0
- package/coverage/lib/components/account-selector/account-selector.component.ts.html +1495 -0
- package/coverage/lib/components/account-selector/account-selector.service.ts.html +1588 -0
- package/coverage/lib/components/account-selector/index.html +161 -0
- package/coverage/lib/components/wallet-account-display/index.html +116 -0
- package/coverage/lib/components/wallet-account-display/wallet-account-display.component.ts.html +505 -0
- package/coverage/lib/components/wallet-connect-button/index.html +116 -0
- package/coverage/lib/components/wallet-connect-button/wallet-connect-button.component.ts.html +805 -0
- package/coverage/lib/components/wallet-connect-prompt/index.html +116 -0
- package/coverage/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts.html +409 -0
- package/coverage/lib/components/wallet-connected-guard/index.html +116 -0
- package/coverage/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts.html +304 -0
- package/coverage/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts.html +436 -0
- package/coverage/lib/components/wallet-connection-modal/connection-method-step/index.html +116 -0
- package/coverage/lib/components/wallet-connection-modal/index.html +116 -0
- package/coverage/lib/components/wallet-connection-modal/qr-pairing-step/index.html +116 -0
- package/coverage/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts.html +2287 -0
- package/coverage/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts.html +2275 -0
- package/coverage/lib/components/wallet-session-display/index.html +116 -0
- package/coverage/lib/components/wallet-session-display/wallet-session-display.component.ts.html +676 -0
- package/coverage/lib/components/wallet-transaction-status/index.html +116 -0
- package/coverage/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts.html +703 -0
- package/coverage/lib/directives/index.html +146 -0
- package/coverage/lib/directives/wallet-connected.directive.ts.html +670 -0
- package/coverage/lib/directives/wallet-context.directive.ts.html +547 -0
- package/coverage/lib/directives/wallet-events.directive.ts.html +781 -0
- package/coverage/lib/hsuite-wallet.module.ts.html +715 -0
- package/coverage/lib/index.html +116 -0
- package/coverage/lib/models/connection-config.model.ts.html +280 -0
- package/coverage/lib/models/index.html +131 -0
- package/coverage/lib/models/provider-types.ts.html +577 -0
- package/coverage/lib/providers/base-wallet-provider.ts.html +1138 -0
- package/coverage/lib/providers/hsuite-native/channel-client.service.ts.html +2671 -0
- package/coverage/lib/providers/hsuite-native/index.html +116 -0
- package/coverage/lib/providers/hsuite-native-provider.ts.html +2347 -0
- package/coverage/lib/providers/index.html +146 -0
- package/coverage/lib/providers/p2p-native/index.html +131 -0
- package/coverage/lib/providers/p2p-native/p2p-native.provider.ts.html +2254 -0
- package/coverage/lib/providers/p2p-native/p2p-session-manager.ts.html +2170 -0
- package/coverage/lib/providers/wallet-error-handler.ts.html +1132 -0
- package/coverage/lib/providers/walletconnect/core/index.html +176 -0
- package/coverage/lib/providers/walletconnect/core/session-health.ts.html +673 -0
- package/coverage/lib/providers/walletconnect/core/walletconnect-client-manager.ts.html +1177 -0
- package/coverage/lib/providers/walletconnect/core/walletconnect-provider.ts.html +2563 -0
- package/coverage/lib/providers/walletconnect/core/walletconnect-session-store.ts.html +904 -0
- package/coverage/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts.html +982 -0
- package/coverage/lib/providers/walletconnect/signers/hedera-signer.ts.html +1915 -0
- package/coverage/lib/providers/walletconnect/signers/index.html +146 -0
- package/coverage/lib/providers/walletconnect/signers/signer-factory.ts.html +445 -0
- package/coverage/lib/providers/walletconnect/signers/xrpl-signer.ts.html +1519 -0
- package/coverage/lib/services/index.html +191 -0
- package/coverage/lib/services/logger.service.ts.html +463 -0
- package/coverage/lib/services/transaction-builders/base-transaction-builder.service.ts.html +1840 -0
- package/coverage/lib/services/transaction-builders/hedera-amount-utils.ts.html +337 -0
- package/coverage/lib/services/transaction-builders/hedera-transaction-builder.service.ts.html +3940 -0
- package/coverage/lib/services/transaction-builders/index.html +161 -0
- package/coverage/lib/services/transaction-builders/xrpl-transaction-builder.service.ts.html +2581 -0
- package/coverage/lib/services/transaction.service.ts.html +1123 -0
- package/coverage/lib/services/unified-wallet.service.ts.html +2641 -0
- package/coverage/lib/services/wallet-context.service.ts.html +637 -0
- package/coverage/lib/services/wallet-event-bus.service.ts.html +643 -0
- package/coverage/lib/services/wallet-providers.service.ts.html +496 -0
- package/coverage/lib/transports/chrome-extension-transport.ts.html +823 -0
- package/coverage/lib/transports/index.html +116 -0
- package/coverage/lib/utils/index.html +116 -0
- package/coverage/lib/utils/ledger-icons.util.ts.html +319 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/README.md +48 -0
- package/dist/fesm2022/hsuite-native-connect-angular.mjs +14592 -0
- package/dist/fesm2022/hsuite-native-connect-angular.mjs.map +1 -0
- package/dist/index.d.ts +6949 -0
- package/examples/minimal-connect.ts +178 -0
- package/examples/multi-protocol.ts +495 -0
- package/examples/transaction-signing.ts +361 -0
- package/jest.config.json +45 -0
- package/karma.conf.js +42 -0
- package/ng-package.json +20 -0
- package/package.json +60 -0
- package/src/index.ts +203 -0
- package/src/lib/components/account-selector/account-actions/account-actions.component.ts +261 -0
- package/src/lib/components/account-selector/account-filter/account-filter.component.ts +401 -0
- package/src/lib/components/account-selector/account-formatting.service.ts +200 -0
- package/src/lib/components/account-selector/account-grouping.service.ts +227 -0
- package/src/lib/components/account-selector/account-list/account-list.component.ts +470 -0
- package/src/lib/components/account-selector/account-selector.component.html +135 -0
- package/src/lib/components/account-selector/account-selector.component.scss +2039 -0
- package/src/lib/components/account-selector/account-selector.component.ts +470 -0
- package/src/lib/components/account-selector/account-selector.service.ts +501 -0
- package/src/lib/components/wallet-account-display/wallet-account-display.component.html +34 -0
- package/src/lib/components/wallet-account-display/wallet-account-display.component.scss +99 -0
- package/src/lib/components/wallet-account-display/wallet-account-display.component.ts +140 -0
- package/src/lib/components/wallet-connect-button/wallet-connect-button.component.html +14 -0
- package/src/lib/components/wallet-connect-button/wallet-connect-button.component.scss +272 -0
- package/src/lib/components/wallet-connect-button/wallet-connect-button.component.ts +240 -0
- package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.html +24 -0
- package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.scss +50 -0
- package/src/lib/components/wallet-connect-prompt/wallet-connect-prompt.component.ts +108 -0
- package/src/lib/components/wallet-connected-guard/wallet-connected-guard.component.html +24 -0
- package/src/lib/components/wallet-connected-guard/wallet-connected-guard.component.ts +73 -0
- package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.html +56 -0
- package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.scss +218 -0
- package/src/lib/components/wallet-connection-modal/connection-method-step/connection-method-step.component.ts +117 -0
- package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.html +94 -0
- package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.scss +272 -0
- package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts +734 -0
- package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.html +197 -0
- package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.scss +678 -0
- package/src/lib/components/wallet-connection-modal/wallet-connection-modal.component.ts +730 -0
- package/src/lib/components/wallet-session-display/wallet-session-display.component.html +110 -0
- package/src/lib/components/wallet-session-display/wallet-session-display.component.scss +179 -0
- package/src/lib/components/wallet-session-display/wallet-session-display.component.ts +197 -0
- package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.html +65 -0
- package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.scss +254 -0
- package/src/lib/components/wallet-transaction-status/wallet-transaction-status.component.ts +206 -0
- package/src/lib/directives/wallet-connected.directive.ts +195 -0
- package/src/lib/directives/wallet-context.directive.ts +154 -0
- package/src/lib/directives/wallet-events.directive.ts +232 -0
- package/src/lib/hsuite-wallet.module.ts +210 -0
- package/src/lib/models/connection-config.model.ts +65 -0
- package/src/lib/models/provider-types.ts +164 -0
- package/src/lib/models/unified-account.model.ts +76 -0
- package/src/lib/models/wallet-context.model.ts +121 -0
- package/src/lib/models/wallet-events.model.ts +158 -0
- package/src/lib/providers/base-wallet-provider.ts +351 -0
- package/src/lib/providers/hsuite-native/channel-client.service.spec.ts +73 -0
- package/src/lib/providers/hsuite-native/channel-client.service.ts +862 -0
- package/src/lib/providers/hsuite-native/index.ts +8 -0
- package/src/lib/providers/hsuite-native-provider.ts +754 -0
- package/src/lib/providers/mobile-native/mobile-native.provider.spec.ts +19 -0
- package/src/lib/providers/p2p-native/index.ts +30 -0
- package/src/lib/providers/p2p-native/p2p-native.provider.spec.ts +523 -0
- package/src/lib/providers/p2p-native/p2p-native.provider.ts +723 -0
- package/src/lib/providers/p2p-native/p2p-session-manager.ts +695 -0
- package/src/lib/providers/wallet-error-handler.ts +349 -0
- package/src/lib/providers/walletconnect/core/base-signer.interface.ts +122 -0
- package/src/lib/providers/walletconnect/core/session-health.ts +196 -0
- package/src/lib/providers/walletconnect/core/walletconnect-client-manager.ts +364 -0
- package/src/lib/providers/walletconnect/core/walletconnect-provider.integration.spec.ts +348 -0
- package/src/lib/providers/walletconnect/core/walletconnect-provider.ts +826 -0
- package/src/lib/providers/walletconnect/core/walletconnect-session-store.ts +273 -0
- package/src/lib/providers/walletconnect/core/walletconnect-signing-orchestrator.ts +299 -0
- package/src/lib/providers/walletconnect/core/walletconnect-types.ts +48 -0
- package/src/lib/providers/walletconnect/index.ts +33 -0
- package/src/lib/providers/walletconnect/signers/hedera-signer.spec.ts +367 -0
- package/src/lib/providers/walletconnect/signers/hedera-signer.ts +610 -0
- package/src/lib/providers/walletconnect/signers/signer-factory.spec.ts +62 -0
- package/src/lib/providers/walletconnect/signers/signer-factory.ts +120 -0
- package/src/lib/providers/walletconnect/signers/xrpl-signer.spec.ts +296 -0
- package/src/lib/providers/walletconnect/signers/xrpl-signer.ts +478 -0
- package/src/lib/services/logger.service.ts +126 -0
- package/src/lib/services/transaction-builders/base-transaction-builder.service.ts +585 -0
- package/src/lib/services/transaction-builders/hedera-amount-utils.ts +84 -0
- package/src/lib/services/transaction-builders/hedera-transaction-builder.service.spec.ts +741 -0
- package/src/lib/services/transaction-builders/hedera-transaction-builder.service.ts +1285 -0
- package/src/lib/services/transaction-builders/index.ts +54 -0
- package/src/lib/services/transaction-builders/xrpl-transaction-builder.service.spec.ts +937 -0
- package/src/lib/services/transaction-builders/xrpl-transaction-builder.service.ts +832 -0
- package/src/lib/services/transaction.service.ts +346 -0
- package/src/lib/services/unified-wallet.service.spec.ts +1382 -0
- package/src/lib/services/unified-wallet.service.ts +852 -0
- package/src/lib/services/wallet-context.service.ts +184 -0
- package/src/lib/services/wallet-event-bus.service.ts +186 -0
- package/src/lib/services/wallet-providers.service.ts +137 -0
- package/src/lib/transports/chrome-extension-transport.ts +246 -0
- package/src/lib/utils/index.ts +14 -0
- package/src/lib/utils/ledger-icons.util.ts +78 -0
- package/test/test-setup.ts +21 -0
- package/test-setup.ts +63 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +29 -0
- package/tsconfig.spec.json +15 -0
- package/vitest.config.ts +48 -0
package/src/lib/components/wallet-connection-modal/qr-pairing-step/qr-pairing-step.component.ts
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
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 qr-pairing-step.component.ts
|
|
13
|
+
* @description QR code pairing step for mobile wallet connection
|
|
14
|
+
*
|
|
15
|
+
* Implements single-QR Nostr-first pairing flow:
|
|
16
|
+
* 1. Display compact QR code with session invite
|
|
17
|
+
* 2. Wait for wallet to scan and connect via Nostr
|
|
18
|
+
* 3. Wait for user approval in wallet
|
|
19
|
+
* 4. Connection established (P2P upgrade happens automatically)
|
|
20
|
+
*
|
|
21
|
+
* This enables fully decentralized cross-device connections with only
|
|
22
|
+
* a single QR scan - much simpler than the previous 2-QR WebRTC flow.
|
|
23
|
+
*
|
|
24
|
+
* Features:
|
|
25
|
+
* - Single QR code display (no answer scanning needed)
|
|
26
|
+
* - QR code with HSuite logo in center
|
|
27
|
+
* - Countdown timer with expiry handling
|
|
28
|
+
* - Refresh capability for expired offers
|
|
29
|
+
* - Waiting for approval state
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { CommonModule } from '@angular/common';
|
|
33
|
+
import {
|
|
34
|
+
Component,
|
|
35
|
+
Input,
|
|
36
|
+
Output,
|
|
37
|
+
EventEmitter,
|
|
38
|
+
ChangeDetectionStrategy,
|
|
39
|
+
signal,
|
|
40
|
+
OnChanges,
|
|
41
|
+
SimpleChanges,
|
|
42
|
+
OnDestroy,
|
|
43
|
+
AfterViewInit,
|
|
44
|
+
ElementRef,
|
|
45
|
+
ViewChild,
|
|
46
|
+
inject,
|
|
47
|
+
} from '@angular/core';
|
|
48
|
+
import { copyToClipboard } from '@hsuite/native-connect-ui';
|
|
49
|
+
import { IonIcon, IonSpinner, IonButton } from '@ionic/angular/standalone';
|
|
50
|
+
import { addIcons } from 'ionicons';
|
|
51
|
+
import {
|
|
52
|
+
qrCodeOutline,
|
|
53
|
+
refreshOutline,
|
|
54
|
+
checkmarkCircleOutline,
|
|
55
|
+
closeCircleOutline,
|
|
56
|
+
phonePortraitOutline,
|
|
57
|
+
arrowBackOutline,
|
|
58
|
+
timeOutline,
|
|
59
|
+
copyOutline,
|
|
60
|
+
checkmarkOutline,
|
|
61
|
+
} from 'ionicons/icons';
|
|
62
|
+
import * as QRCode from 'qrcode'; // qrcode has no default export; use namespace import for toCanvas
|
|
63
|
+
|
|
64
|
+
import { LoggerService } from '../../../services/logger.service';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Connection state for the QR pairing step.
|
|
68
|
+
*
|
|
69
|
+
* States:
|
|
70
|
+
* - loading: Generating session invite
|
|
71
|
+
* - displaying: Showing QR code for wallet to scan
|
|
72
|
+
* - waiting_wallet: QR scanned, waiting for wallet to connect via Nostr
|
|
73
|
+
* - waiting_approval: Wallet connected, waiting for user to approve
|
|
74
|
+
* - connected: Session approved and connected
|
|
75
|
+
* - expired: QR code expired
|
|
76
|
+
* - error: An error occurred
|
|
77
|
+
*/
|
|
78
|
+
export type QrPairingState =
|
|
79
|
+
| 'loading'
|
|
80
|
+
| 'displaying'
|
|
81
|
+
| 'waiting_wallet'
|
|
82
|
+
| 'waiting_approval'
|
|
83
|
+
| 'connected'
|
|
84
|
+
| 'expired'
|
|
85
|
+
| 'error';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* QR Pairing Step Component
|
|
89
|
+
*
|
|
90
|
+
* Implements single-QR Nostr-first pairing for cross-device P2P connections.
|
|
91
|
+
* Much simpler than the previous 2-QR WebRTC approach.
|
|
92
|
+
*/
|
|
93
|
+
@Component({
|
|
94
|
+
selector: 'hsuite-qr-pairing-step',
|
|
95
|
+
standalone: true,
|
|
96
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
97
|
+
imports: [CommonModule, IonIcon, IonSpinner, IonButton],
|
|
98
|
+
template: `
|
|
99
|
+
<div class="qr-pairing-container">
|
|
100
|
+
<!-- Hidden canvas for QR generation - always rendered -->
|
|
101
|
+
<canvas #qrCanvas width="300" height="300" class="hidden-canvas"></canvas>
|
|
102
|
+
|
|
103
|
+
<!-- Loading State -->
|
|
104
|
+
<div *ngIf="pairingState() === 'loading'" class="state-panel loading">
|
|
105
|
+
<ion-spinner name="crescent"></ion-spinner>
|
|
106
|
+
<p>Generating secure connection...</p>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- QR Display State -->
|
|
110
|
+
<div *ngIf="pairingState() === 'displaying'" class="qr-display">
|
|
111
|
+
<div class="qr-header">
|
|
112
|
+
<h3>Scan with Your Wallet</h3>
|
|
113
|
+
<p class="qr-subtitle">Open HSuite Wallet and scan this QR code</p>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="qr-wrapper">
|
|
117
|
+
<canvas #qrCanvasDisplay width="300" height="300"></canvas>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="qr-meta">
|
|
121
|
+
<div class="countdown" [class.warning]="countdown() < 30">
|
|
122
|
+
<ion-icon name="time-outline"></ion-icon>
|
|
123
|
+
<span>{{ formatCountdown() }}</span>
|
|
124
|
+
</div>
|
|
125
|
+
<ion-button fill="clear" size="small" (click)="copyUri()">
|
|
126
|
+
<ion-icon
|
|
127
|
+
slot="icon-only"
|
|
128
|
+
[name]="copied() ? 'checkmark-outline' : 'copy-outline'"
|
|
129
|
+
></ion-icon>
|
|
130
|
+
</ion-button>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div class="instructions">
|
|
134
|
+
<ol>
|
|
135
|
+
<li>Open HSuite Wallet on your phone</li>
|
|
136
|
+
<li>Go to Sessions tab</li>
|
|
137
|
+
<li>Tap "Scan QR" and scan this code</li>
|
|
138
|
+
</ol>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Waiting for Wallet State -->
|
|
143
|
+
<div *ngIf="pairingState() === 'waiting_wallet'" class="state-panel waiting">
|
|
144
|
+
<div class="waiting-icon-wrapper">
|
|
145
|
+
<ion-spinner name="crescent"></ion-spinner>
|
|
146
|
+
</div>
|
|
147
|
+
<h3>Waiting for Wallet</h3>
|
|
148
|
+
<p class="waiting-description">Scan the QR code with your wallet app to connect.</p>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<!-- Waiting for Approval State -->
|
|
152
|
+
<div *ngIf="pairingState() === 'waiting_approval'" class="state-panel waiting-approval">
|
|
153
|
+
<div class="waiting-icon-wrapper">
|
|
154
|
+
<ion-spinner name="crescent"></ion-spinner>
|
|
155
|
+
</div>
|
|
156
|
+
<h3>Waiting for Wallet Approval</h3>
|
|
157
|
+
<p class="waiting-description">
|
|
158
|
+
The wallet is connected. Please approve the session in your wallet app.
|
|
159
|
+
</p>
|
|
160
|
+
<div class="waiting-steps">
|
|
161
|
+
<div class="waiting-step">
|
|
162
|
+
<span class="step-check">✓</span>
|
|
163
|
+
<span>QR code scanned</span>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="waiting-step">
|
|
166
|
+
<span class="step-check">✓</span>
|
|
167
|
+
<span>Wallet connected</span>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="waiting-step active">
|
|
170
|
+
<ion-spinner name="dots"></ion-spinner>
|
|
171
|
+
<span>Awaiting approval in wallet...</span>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<!-- Connected State -->
|
|
177
|
+
<div *ngIf="pairingState() === 'connected'" class="state-panel connected">
|
|
178
|
+
<ion-icon name="checkmark-circle-outline" color="success"></ion-icon>
|
|
179
|
+
<p>Connected successfully!</p>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<!-- Error State -->
|
|
183
|
+
<div *ngIf="pairingState() === 'error'" class="state-panel error">
|
|
184
|
+
<ion-icon name="close-circle-outline" color="danger"></ion-icon>
|
|
185
|
+
<p>{{ errorMessage || 'Connection failed' }}</p>
|
|
186
|
+
<ion-button fill="outline" (click)="refresh()">
|
|
187
|
+
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
|
188
|
+
Try Again
|
|
189
|
+
</ion-button>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<!-- Expired State -->
|
|
193
|
+
<div *ngIf="pairingState() === 'expired'" class="state-panel expired">
|
|
194
|
+
<ion-icon name="time-outline" color="warning"></ion-icon>
|
|
195
|
+
<p>QR code expired</p>
|
|
196
|
+
<ion-button fill="outline" (click)="refresh()">
|
|
197
|
+
<ion-icon slot="start" name="refresh-outline"></ion-icon>
|
|
198
|
+
Generate New
|
|
199
|
+
</ion-button>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<!-- Footer Actions (cancel) - hidden during waiting/connected -->
|
|
203
|
+
<div
|
|
204
|
+
class="footer-actions"
|
|
205
|
+
*ngIf="!['waiting_wallet', 'waiting_approval', 'connected'].includes(pairingState())"
|
|
206
|
+
>
|
|
207
|
+
<ion-button fill="clear" (click)="cancel()">
|
|
208
|
+
<ion-icon slot="start" name="arrow-back-outline"></ion-icon>
|
|
209
|
+
Cancel
|
|
210
|
+
</ion-button>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
`,
|
|
214
|
+
styles: [
|
|
215
|
+
`
|
|
216
|
+
.qr-pairing-container {
|
|
217
|
+
display: flex;
|
|
218
|
+
flex-direction: column;
|
|
219
|
+
padding: 1rem;
|
|
220
|
+
min-height: 400px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.hidden-canvas {
|
|
224
|
+
position: absolute;
|
|
225
|
+
left: -9999px;
|
|
226
|
+
top: -9999px;
|
|
227
|
+
visibility: hidden;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.qr-display {
|
|
231
|
+
display: flex;
|
|
232
|
+
flex-direction: column;
|
|
233
|
+
align-items: center;
|
|
234
|
+
flex: 1;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.qr-header {
|
|
238
|
+
text-align: center;
|
|
239
|
+
margin-bottom: 1.5rem;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.qr-header h3 {
|
|
243
|
+
margin: 0 0 0.5rem 0;
|
|
244
|
+
font-size: 1.25rem;
|
|
245
|
+
font-weight: 600;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.qr-subtitle {
|
|
249
|
+
margin: 0;
|
|
250
|
+
color: var(--ion-color-medium-shade);
|
|
251
|
+
font-size: 0.875rem;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.qr-wrapper {
|
|
255
|
+
display: flex;
|
|
256
|
+
justify-content: center;
|
|
257
|
+
padding: 1rem;
|
|
258
|
+
background: white;
|
|
259
|
+
border-radius: 12px;
|
|
260
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
261
|
+
margin-bottom: 1rem;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.qr-meta {
|
|
265
|
+
display: flex;
|
|
266
|
+
justify-content: center;
|
|
267
|
+
align-items: center;
|
|
268
|
+
gap: 1rem;
|
|
269
|
+
margin-bottom: 1rem;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.countdown {
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
gap: 0.25rem;
|
|
276
|
+
padding: 0.25rem 0.75rem;
|
|
277
|
+
background: var(--ion-color-light);
|
|
278
|
+
border-radius: 1rem;
|
|
279
|
+
font-size: 0.875rem;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.countdown.warning {
|
|
283
|
+
background: var(--ion-color-warning-tint);
|
|
284
|
+
color: var(--ion-color-warning-shade);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.instructions {
|
|
288
|
+
margin-bottom: 1rem;
|
|
289
|
+
width: 100%;
|
|
290
|
+
max-width: 300px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.instructions ol {
|
|
294
|
+
text-align: left;
|
|
295
|
+
padding-left: 1.25rem;
|
|
296
|
+
margin: 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.instructions li {
|
|
300
|
+
margin-bottom: 0.5rem;
|
|
301
|
+
color: var(--ion-color-medium-shade);
|
|
302
|
+
font-size: 0.875rem;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.state-panel {
|
|
306
|
+
display: flex;
|
|
307
|
+
flex-direction: column;
|
|
308
|
+
align-items: center;
|
|
309
|
+
justify-content: center;
|
|
310
|
+
gap: 1rem;
|
|
311
|
+
flex: 1;
|
|
312
|
+
text-align: center;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.state-panel ion-spinner {
|
|
316
|
+
width: 48px;
|
|
317
|
+
height: 48px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.state-panel ion-icon {
|
|
321
|
+
font-size: 64px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Waiting States */
|
|
325
|
+
.waiting,
|
|
326
|
+
.waiting-approval {
|
|
327
|
+
padding: 1.5rem;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.waiting h3,
|
|
331
|
+
.waiting-approval h3 {
|
|
332
|
+
margin: 0 0 0.5rem 0;
|
|
333
|
+
font-size: 1.25rem;
|
|
334
|
+
font-weight: 600;
|
|
335
|
+
color: var(--ion-color-primary);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.waiting-description {
|
|
339
|
+
color: var(--ion-color-medium-shade);
|
|
340
|
+
font-size: 0.9rem;
|
|
341
|
+
margin-bottom: 1.5rem;
|
|
342
|
+
max-width: 280px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.waiting-icon-wrapper {
|
|
346
|
+
width: 80px;
|
|
347
|
+
height: 80px;
|
|
348
|
+
border-radius: 50%;
|
|
349
|
+
background: var(--ion-color-primary-tint);
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
margin-bottom: 1rem;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.waiting-icon-wrapper ion-spinner {
|
|
357
|
+
width: 40px;
|
|
358
|
+
height: 40px;
|
|
359
|
+
--color: var(--ion-color-primary);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.waiting-steps {
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-direction: column;
|
|
365
|
+
gap: 0.75rem;
|
|
366
|
+
padding: 1rem;
|
|
367
|
+
background: var(--ion-color-light);
|
|
368
|
+
border-radius: 12px;
|
|
369
|
+
width: 100%;
|
|
370
|
+
max-width: 280px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.waiting-step {
|
|
374
|
+
display: flex;
|
|
375
|
+
align-items: center;
|
|
376
|
+
gap: 0.75rem;
|
|
377
|
+
font-size: 0.875rem;
|
|
378
|
+
color: var(--ion-color-medium-shade);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.waiting-step .step-check {
|
|
382
|
+
width: 20px;
|
|
383
|
+
height: 20px;
|
|
384
|
+
border-radius: 50%;
|
|
385
|
+
background: var(--ion-color-success);
|
|
386
|
+
color: white;
|
|
387
|
+
font-size: 0.75rem;
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
justify-content: center;
|
|
391
|
+
flex-shrink: 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.waiting-step.active {
|
|
395
|
+
color: var(--ion-color-primary);
|
|
396
|
+
font-weight: 500;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.waiting-step.active ion-spinner {
|
|
400
|
+
width: 20px;
|
|
401
|
+
height: 20px;
|
|
402
|
+
--color: var(--ion-color-primary);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.footer-actions {
|
|
406
|
+
margin-top: auto;
|
|
407
|
+
padding-top: 1rem;
|
|
408
|
+
border-top: 1px solid var(--ion-color-light);
|
|
409
|
+
display: flex;
|
|
410
|
+
justify-content: center;
|
|
411
|
+
}
|
|
412
|
+
`,
|
|
413
|
+
],
|
|
414
|
+
})
|
|
415
|
+
export class QrPairingStepComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|
416
|
+
private readonly logger = inject(LoggerService).scoped('QrPairingStep');
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The encoded offer data to display as QR code (hsc:connect?i=...)
|
|
420
|
+
*/
|
|
421
|
+
@Input() offerData: string = '';
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* App name to display in instructions
|
|
425
|
+
*/
|
|
426
|
+
@Input() appName: string = 'This app';
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Whether currently loading/generating the offer
|
|
430
|
+
*/
|
|
431
|
+
@Input() isLoading: boolean = false;
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Whether waiting for wallet to connect via Nostr
|
|
435
|
+
*/
|
|
436
|
+
@Input() isWaitingForWallet: boolean = false;
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Whether waiting for wallet user to approve the session
|
|
440
|
+
*/
|
|
441
|
+
@Input() isWaitingForApproval: boolean = false;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Error message if any
|
|
445
|
+
*/
|
|
446
|
+
@Input() errorMessage: string | null = null;
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Expiry time in seconds for the QR code
|
|
450
|
+
*/
|
|
451
|
+
@Input() expirySeconds: number = 120;
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Emitted when QR code expires
|
|
455
|
+
*/
|
|
456
|
+
@Output() expired = new EventEmitter<void>();
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Emitted when connection is established
|
|
460
|
+
*/
|
|
461
|
+
@Output() connected = new EventEmitter<void>();
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Emitted when user cancels/goes back
|
|
465
|
+
*/
|
|
466
|
+
@Output() cancelled = new EventEmitter<void>();
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Emitted when user requests refresh
|
|
470
|
+
*/
|
|
471
|
+
@Output() refreshRequested = new EventEmitter<void>();
|
|
472
|
+
|
|
473
|
+
@ViewChild('qrCanvas') qrCanvas?: ElementRef<HTMLCanvasElement>;
|
|
474
|
+
@ViewChild('qrCanvasDisplay') qrCanvasDisplay?: ElementRef<HTMLCanvasElement>;
|
|
475
|
+
|
|
476
|
+
// Internal state
|
|
477
|
+
readonly pairingState = signal<QrPairingState>('loading');
|
|
478
|
+
readonly countdown = signal<number>(120);
|
|
479
|
+
readonly copied = signal<boolean>(false);
|
|
480
|
+
|
|
481
|
+
private countdownInterval?: ReturnType<typeof setInterval>;
|
|
482
|
+
private viewReady = false;
|
|
483
|
+
private pendingQrGeneration = false;
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
*
|
|
487
|
+
*/
|
|
488
|
+
constructor() {
|
|
489
|
+
addIcons({
|
|
490
|
+
qrCodeOutline,
|
|
491
|
+
refreshOutline,
|
|
492
|
+
checkmarkCircleOutline,
|
|
493
|
+
closeCircleOutline,
|
|
494
|
+
phonePortraitOutline,
|
|
495
|
+
arrowBackOutline,
|
|
496
|
+
timeOutline,
|
|
497
|
+
copyOutline,
|
|
498
|
+
checkmarkOutline,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
*
|
|
504
|
+
*/
|
|
505
|
+
ngAfterViewInit(): void {
|
|
506
|
+
this.viewReady = true;
|
|
507
|
+
// Generate QR if we have pending data
|
|
508
|
+
if (this.pendingQrGeneration && this.offerData) {
|
|
509
|
+
void this.generateQRCode(this.offerData);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
*
|
|
515
|
+
* @param changes
|
|
516
|
+
*/
|
|
517
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
518
|
+
// Update state based on inputs
|
|
519
|
+
if (
|
|
520
|
+
changes['isLoading'] ||
|
|
521
|
+
changes['isWaitingForWallet'] ||
|
|
522
|
+
changes['isWaitingForApproval'] ||
|
|
523
|
+
changes['offerData'] ||
|
|
524
|
+
changes['errorMessage']
|
|
525
|
+
) {
|
|
526
|
+
this.updateState();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (changes['offerData'] && this.offerData) {
|
|
530
|
+
if (this.viewReady) {
|
|
531
|
+
void this.generateQRCode(this.offerData);
|
|
532
|
+
} else {
|
|
533
|
+
this.pendingQrGeneration = true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (changes['expirySeconds']) {
|
|
538
|
+
this.countdown.set(this.expirySeconds);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
*
|
|
544
|
+
*/
|
|
545
|
+
ngOnDestroy(): void {
|
|
546
|
+
this.stopCountdown();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Format countdown for display (MM:SS)
|
|
551
|
+
*/
|
|
552
|
+
formatCountdown(): string {
|
|
553
|
+
const seconds = this.countdown();
|
|
554
|
+
const mins = Math.floor(seconds / 60);
|
|
555
|
+
const secs = seconds % 60;
|
|
556
|
+
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Handle QR code expiry
|
|
561
|
+
*/
|
|
562
|
+
onQrExpired(): void {
|
|
563
|
+
this.pairingState.set('expired');
|
|
564
|
+
this.expired.emit();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Request a new QR code
|
|
569
|
+
*/
|
|
570
|
+
refresh(): void {
|
|
571
|
+
this.pairingState.set('loading');
|
|
572
|
+
this.refreshRequested.emit();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Go back / cancel
|
|
577
|
+
*/
|
|
578
|
+
cancel(): void {
|
|
579
|
+
this.cancelled.emit();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Copy the offer URI to clipboard
|
|
584
|
+
*/
|
|
585
|
+
async copyUri(): Promise<void> {
|
|
586
|
+
if (!this.offerData) return;
|
|
587
|
+
|
|
588
|
+
const success = await copyToClipboard(this.offerData);
|
|
589
|
+
|
|
590
|
+
if (success) {
|
|
591
|
+
this.copied.set(true);
|
|
592
|
+
|
|
593
|
+
// Reset after 2 seconds
|
|
594
|
+
setTimeout(() => {
|
|
595
|
+
this.copied.set(false);
|
|
596
|
+
}, 2000);
|
|
597
|
+
} else {
|
|
598
|
+
this.logger.error('Failed to copy URI');
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// ========== Private Methods ==========
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Generate QR code with HSuite logo overlay
|
|
606
|
+
* @param data
|
|
607
|
+
*/
|
|
608
|
+
private async generateQRCode(data: string): Promise<void> {
|
|
609
|
+
const canvas = this.qrCanvas?.nativeElement;
|
|
610
|
+
if (!canvas) {
|
|
611
|
+
this.logger.warn('Canvas not available for QR generation');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
try {
|
|
616
|
+
// Generate QR code - session invites are small (~300 bytes), so we can use higher error correction
|
|
617
|
+
await QRCode.toCanvas(canvas, data, {
|
|
618
|
+
width: 300,
|
|
619
|
+
margin: 2,
|
|
620
|
+
color: {
|
|
621
|
+
dark: '#000000',
|
|
622
|
+
light: '#ffffff',
|
|
623
|
+
},
|
|
624
|
+
errorCorrectionLevel: 'M', // Medium error correction
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// Add HSuite logo in center
|
|
628
|
+
const ctx = canvas.getContext('2d');
|
|
629
|
+
if (!ctx) return;
|
|
630
|
+
|
|
631
|
+
// Load logo
|
|
632
|
+
const logo = new Image();
|
|
633
|
+
logo.crossOrigin = 'anonymous';
|
|
634
|
+
logo.onload = () => {
|
|
635
|
+
// Logo size is 20% of QR code
|
|
636
|
+
const logoSize = Math.floor(canvas.width * 0.2);
|
|
637
|
+
const logoX = (canvas.width - logoSize) / 2;
|
|
638
|
+
const logoY = (canvas.height - logoSize) / 2;
|
|
639
|
+
|
|
640
|
+
// Draw white background circle for logo
|
|
641
|
+
const circlePadding = 4;
|
|
642
|
+
ctx.fillStyle = '#ffffff';
|
|
643
|
+
ctx.beginPath();
|
|
644
|
+
ctx.arc(canvas.width / 2, canvas.height / 2, logoSize / 2 + circlePadding, 0, 2 * Math.PI);
|
|
645
|
+
ctx.fill();
|
|
646
|
+
|
|
647
|
+
// Draw logo
|
|
648
|
+
ctx.drawImage(logo, logoX, logoY, logoSize, logoSize);
|
|
649
|
+
|
|
650
|
+
// Show the QR display and copy to visible canvas
|
|
651
|
+
this.pairingState.set('displaying');
|
|
652
|
+
this.startCountdown();
|
|
653
|
+
|
|
654
|
+
// Copy to display canvas after a tick
|
|
655
|
+
setTimeout(() => this.copyToDisplayCanvas(), 0);
|
|
656
|
+
};
|
|
657
|
+
logo.onerror = () => {
|
|
658
|
+
// Still show QR even without logo
|
|
659
|
+
this.pairingState.set('displaying');
|
|
660
|
+
this.startCountdown();
|
|
661
|
+
setTimeout(() => this.copyToDisplayCanvas(), 0);
|
|
662
|
+
};
|
|
663
|
+
logo.src = '/assets/hsuite_icon.png';
|
|
664
|
+
} catch (error) {
|
|
665
|
+
this.logger.error('Failed to generate QR code', {
|
|
666
|
+
error: error instanceof Error ? error.message : String(error),
|
|
667
|
+
});
|
|
668
|
+
this.pairingState.set('error');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Copy the generated QR from hidden canvas to display canvas
|
|
674
|
+
*/
|
|
675
|
+
private copyToDisplayCanvas(): void {
|
|
676
|
+
const srcCanvas = this.qrCanvas?.nativeElement;
|
|
677
|
+
const destCanvas = this.qrCanvasDisplay?.nativeElement;
|
|
678
|
+
|
|
679
|
+
if (!srcCanvas || !destCanvas) {
|
|
680
|
+
this.logger.warn('Cannot copy to display canvas - missing canvas elements');
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const destCtx = destCanvas.getContext('2d');
|
|
685
|
+
if (!destCtx) return;
|
|
686
|
+
|
|
687
|
+
destCtx.drawImage(srcCanvas, 0, 0);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Start countdown timer
|
|
692
|
+
*/
|
|
693
|
+
private startCountdown(): void {
|
|
694
|
+
this.stopCountdown();
|
|
695
|
+
this.countdown.set(this.expirySeconds);
|
|
696
|
+
|
|
697
|
+
this.countdownInterval = setInterval(() => {
|
|
698
|
+
const current = this.countdown();
|
|
699
|
+
if (current <= 1) {
|
|
700
|
+
this.stopCountdown();
|
|
701
|
+
this.onQrExpired();
|
|
702
|
+
} else {
|
|
703
|
+
this.countdown.set(current - 1);
|
|
704
|
+
}
|
|
705
|
+
}, 1000);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Stop countdown timer
|
|
710
|
+
*/
|
|
711
|
+
private stopCountdown(): void {
|
|
712
|
+
if (this.countdownInterval) {
|
|
713
|
+
clearInterval(this.countdownInterval);
|
|
714
|
+
this.countdownInterval = undefined;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Update internal state based on inputs.
|
|
720
|
+
* Priority: error > waiting_approval > waiting_wallet > loading
|
|
721
|
+
*/
|
|
722
|
+
private updateState(): void {
|
|
723
|
+
if (this.errorMessage) {
|
|
724
|
+
this.pairingState.set('error');
|
|
725
|
+
} else if (this.isWaitingForApproval) {
|
|
726
|
+
this.pairingState.set('waiting_approval');
|
|
727
|
+
} else if (this.isWaitingForWallet) {
|
|
728
|
+
this.pairingState.set('waiting_wallet');
|
|
729
|
+
} else if (this.isLoading && !this.offerData) {
|
|
730
|
+
this.pairingState.set('loading');
|
|
731
|
+
}
|
|
732
|
+
// Note: 'displaying' is set after QR generation completes
|
|
733
|
+
}
|
|
734
|
+
}
|