@multiplatform.one/web3 7.23.1 → 7.24.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 +150 -11
- package/dist/cjs/clients/evm.cjs +299 -11
- package/dist/cjs/clients/evm.native.js +500 -12
- package/dist/cjs/clients/evm.native.js.map +1 -1
- package/dist/cjs/clients/index.cjs +5 -0
- package/dist/cjs/clients/index.native.js +5 -0
- package/dist/cjs/clients/index.native.js.map +1 -1
- package/dist/cjs/clients/solana.cjs +39 -21
- package/dist/cjs/clients/solana.native.js +48 -23
- package/dist/cjs/clients/solana.native.js.map +1 -1
- package/dist/cjs/connectkit/GpgProvider.cjs +44 -0
- package/dist/cjs/connectkit/GpgProvider.native.js +51 -0
- package/dist/cjs/connectkit/GpgProvider.native.js.map +1 -0
- package/dist/cjs/connectkit/adapters/BitcoinAdapter.cjs +5 -14
- package/dist/cjs/connectkit/adapters/BitcoinAdapter.native.js +5 -15
- package/dist/cjs/connectkit/adapters/BitcoinAdapter.native.js.map +1 -1
- package/dist/cjs/connectkit/adapters/EthereumAdapter.cjs +6 -15
- package/dist/cjs/connectkit/adapters/EthereumAdapter.native.js +7 -16
- package/dist/cjs/connectkit/adapters/EthereumAdapter.native.js.map +1 -1
- package/dist/cjs/connectkit/adapters/SolanaAdapter.cjs +5 -14
- package/dist/cjs/connectkit/adapters/SolanaAdapter.native.js +5 -15
- package/dist/cjs/connectkit/adapters/SolanaAdapter.native.js.map +1 -1
- package/dist/cjs/connectkit/adapters/SuiAdapter.cjs +5 -14
- package/dist/cjs/connectkit/adapters/SuiAdapter.native.js +5 -15
- package/dist/cjs/connectkit/adapters/SuiAdapter.native.js.map +1 -1
- package/dist/cjs/connectkit/connectors/createWalletMessage.cjs +2 -2
- package/dist/cjs/connectkit/connectors/createWalletMessage.native.js +2 -2
- package/dist/cjs/connectkit/connectors/createWalletMessage.native.js.map +1 -1
- package/dist/cjs/connectkit/index.cjs +4 -3
- package/dist/cjs/connectkit/index.native.js +4 -3
- package/dist/cjs/connectkit/index.native.js.map +1 -1
- package/dist/cjs/connectkit/siwe/SIWEContext.native.js.map +1 -1
- package/dist/cjs/connectkit/siwe/SIWEProvider.cjs +18 -3
- package/dist/cjs/connectkit/siwe/SIWEProvider.native.js +18 -3
- package/dist/cjs/connectkit/siwe/SIWEProvider.native.js.map +1 -1
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.native.js +1 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/keycloak/KeycloakWalletLogin.cjs +255 -0
- package/dist/cjs/keycloak/KeycloakWalletLogin.native.js +33 -0
- package/dist/cjs/keycloak/KeycloakWalletLogin.native.js.map +1 -0
- package/dist/cjs/keycloak/browserFlow.cjs +83 -0
- package/dist/cjs/keycloak/browserFlow.native.js +90 -0
- package/dist/cjs/keycloak/browserFlow.native.js.map +1 -0
- package/dist/cjs/keycloak/device.cjs +36 -0
- package/dist/cjs/keycloak/device.native.js +45 -0
- package/dist/cjs/keycloak/device.native.js.map +1 -0
- package/dist/cjs/keycloak/directGrant.cjs +189 -0
- package/dist/cjs/keycloak/directGrant.native.js +233 -0
- package/dist/cjs/keycloak/directGrant.native.js.map +1 -0
- package/dist/cjs/keycloak/iife.cjs +4 -0
- package/dist/cjs/keycloak/iife.native.js +7 -0
- package/dist/cjs/keycloak/iife.native.js.map +1 -0
- package/dist/cjs/keycloak/index.cjs +53 -0
- package/dist/cjs/keycloak/index.native.js +56 -0
- package/dist/cjs/keycloak/index.native.js.map +1 -0
- package/dist/cjs/keycloak/keycloakWallet.cjs +214 -0
- package/dist/cjs/keycloak/keycloakWallet.native.js +285 -0
- package/dist/cjs/keycloak/keycloakWallet.native.js.map +1 -0
- package/dist/cjs/keycloak/mount.cjs +53 -0
- package/dist/cjs/keycloak/mount.native.js +33 -0
- package/dist/cjs/keycloak/mount.native.js.map +1 -0
- package/dist/cjs/keycloak/registerWallet.cjs +74 -0
- package/dist/cjs/keycloak/registerWallet.native.js +81 -0
- package/dist/cjs/keycloak/registerWallet.native.js.map +1 -0
- package/dist/cjs/keycloak/types.cjs +46 -0
- package/dist/cjs/keycloak/types.native.js +179 -0
- package/dist/cjs/keycloak/types.native.js.map +1 -0
- package/dist/cjs/keycloak/useDirectLogin.cjs +57 -0
- package/dist/cjs/keycloak/useDirectLogin.native.js +77 -0
- package/dist/cjs/keycloak/useDirectLogin.native.js.map +1 -0
- package/dist/cjs/keycloak/useWalletLogin.cjs +97 -0
- package/dist/cjs/keycloak/useWalletLogin.native.js +33 -0
- package/dist/cjs/keycloak/useWalletLogin.native.js.map +1 -0
- package/dist/cjs/message/buildWalletMessage.cjs +113 -0
- package/dist/cjs/message/buildWalletMessage.native.js +121 -0
- package/dist/cjs/message/buildWalletMessage.native.js.map +1 -0
- package/dist/cjs/message/index.cjs +29 -0
- package/dist/cjs/message/index.native.js +32 -0
- package/dist/cjs/message/index.native.js.map +1 -0
- package/dist/cjs/providers/WalletProvider.cjs +8 -2
- package/dist/cjs/siws/message.cjs +5 -16
- package/dist/cjs/siws/message.native.js +6 -16
- package/dist/cjs/siws/message.native.js.map +1 -1
- package/dist/cjs/solana.native.js.map +1 -1
- package/dist/esm/clients/evm.mjs +295 -12
- package/dist/esm/clients/evm.mjs.map +1 -1
- package/dist/esm/clients/evm.native.js +496 -13
- package/dist/esm/clients/evm.native.js.map +1 -1
- package/dist/esm/clients/index.mjs +2 -2
- package/dist/esm/clients/index.mjs.map +1 -1
- package/dist/esm/clients/index.native.js +2 -2
- package/dist/esm/clients/index.native.js.map +1 -1
- package/dist/esm/clients/solana.mjs +39 -21
- package/dist/esm/clients/solana.mjs.map +1 -1
- package/dist/esm/clients/solana.native.js +48 -23
- package/dist/esm/clients/solana.native.js.map +1 -1
- package/dist/esm/connectkit/GpgProvider.mjs +19 -0
- package/dist/esm/connectkit/GpgProvider.mjs.map +1 -0
- package/dist/esm/connectkit/GpgProvider.native.js +23 -0
- package/dist/esm/connectkit/GpgProvider.native.js.map +1 -0
- package/dist/esm/connectkit/adapters/BitcoinAdapter.mjs +5 -14
- package/dist/esm/connectkit/adapters/BitcoinAdapter.mjs.map +1 -1
- package/dist/esm/connectkit/adapters/BitcoinAdapter.native.js +5 -15
- package/dist/esm/connectkit/adapters/BitcoinAdapter.native.js.map +1 -1
- package/dist/esm/connectkit/adapters/EthereumAdapter.mjs +6 -15
- package/dist/esm/connectkit/adapters/EthereumAdapter.mjs.map +1 -1
- package/dist/esm/connectkit/adapters/EthereumAdapter.native.js +7 -16
- package/dist/esm/connectkit/adapters/EthereumAdapter.native.js.map +1 -1
- package/dist/esm/connectkit/adapters/SolanaAdapter.mjs +5 -14
- package/dist/esm/connectkit/adapters/SolanaAdapter.mjs.map +1 -1
- package/dist/esm/connectkit/adapters/SolanaAdapter.native.js +5 -15
- package/dist/esm/connectkit/adapters/SolanaAdapter.native.js.map +1 -1
- package/dist/esm/connectkit/adapters/SuiAdapter.mjs +5 -14
- package/dist/esm/connectkit/adapters/SuiAdapter.mjs.map +1 -1
- package/dist/esm/connectkit/adapters/SuiAdapter.native.js +5 -15
- package/dist/esm/connectkit/adapters/SuiAdapter.native.js.map +1 -1
- package/dist/esm/connectkit/connectors/createWalletMessage.mjs +1 -1
- package/dist/esm/connectkit/connectors/createWalletMessage.native.js +1 -1
- package/dist/esm/connectkit/index.mjs +3 -2
- package/dist/esm/connectkit/index.mjs.map +1 -1
- package/dist/esm/connectkit/index.native.js +3 -2
- package/dist/esm/connectkit/index.native.js.map +1 -1
- package/dist/esm/connectkit/siwe/SIWEContext.mjs.map +1 -1
- package/dist/esm/connectkit/siwe/SIWEContext.native.js.map +1 -1
- package/dist/esm/connectkit/siwe/SIWEProvider.mjs +19 -4
- package/dist/esm/connectkit/siwe/SIWEProvider.mjs.map +1 -1
- package/dist/esm/connectkit/siwe/SIWEProvider.native.js +19 -4
- package/dist/esm/connectkit/siwe/SIWEProvider.native.js.map +1 -1
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +1 -0
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/keycloak/KeycloakWalletLogin.mjs +218 -0
- package/dist/esm/keycloak/KeycloakWalletLogin.mjs.map +1 -0
- package/dist/esm/keycloak/KeycloakWalletLogin.native.js +5 -0
- package/dist/esm/keycloak/KeycloakWalletLogin.native.js.map +1 -0
- package/dist/esm/keycloak/browserFlow.mjs +58 -0
- package/dist/esm/keycloak/browserFlow.mjs.map +1 -0
- package/dist/esm/keycloak/browserFlow.native.js +62 -0
- package/dist/esm/keycloak/browserFlow.native.js.map +1 -0
- package/dist/esm/keycloak/device.mjs +10 -0
- package/dist/esm/keycloak/device.mjs.map +1 -0
- package/dist/esm/keycloak/device.native.js +16 -0
- package/dist/esm/keycloak/device.native.js.map +1 -0
- package/dist/esm/keycloak/directGrant.mjs +164 -0
- package/dist/esm/keycloak/directGrant.mjs.map +1 -0
- package/dist/esm/keycloak/directGrant.native.js +205 -0
- package/dist/esm/keycloak/directGrant.native.js.map +1 -0
- package/dist/esm/keycloak/iife.mjs +5 -0
- package/dist/esm/keycloak/iife.mjs.map +1 -0
- package/dist/esm/keycloak/iife.native.js +5 -0
- package/dist/esm/keycloak/iife.native.js.map +1 -0
- package/dist/esm/keycloak/index.mjs +13 -0
- package/dist/esm/keycloak/index.mjs.map +1 -0
- package/dist/esm/keycloak/index.native.js +13 -0
- package/dist/esm/keycloak/index.native.js.map +1 -0
- package/dist/esm/keycloak/keycloakWallet.mjs +186 -0
- package/dist/esm/keycloak/keycloakWallet.mjs.map +1 -0
- package/dist/esm/keycloak/keycloakWallet.native.js +254 -0
- package/dist/esm/keycloak/keycloakWallet.native.js.map +1 -0
- package/dist/esm/keycloak/mount.mjs +28 -0
- package/dist/esm/keycloak/mount.mjs.map +1 -0
- package/dist/esm/keycloak/mount.native.js +5 -0
- package/dist/esm/keycloak/mount.native.js.map +1 -0
- package/dist/esm/keycloak/registerWallet.mjs +49 -0
- package/dist/esm/keycloak/registerWallet.mjs.map +1 -0
- package/dist/esm/keycloak/registerWallet.native.js +53 -0
- package/dist/esm/keycloak/registerWallet.native.js.map +1 -0
- package/dist/esm/keycloak/types.mjs +20 -0
- package/dist/esm/keycloak/types.mjs.map +1 -0
- package/dist/esm/keycloak/types.native.js +150 -0
- package/dist/esm/keycloak/types.native.js.map +1 -0
- package/dist/esm/keycloak/useDirectLogin.mjs +32 -0
- package/dist/esm/keycloak/useDirectLogin.mjs.map +1 -0
- package/dist/esm/keycloak/useDirectLogin.native.js +49 -0
- package/dist/esm/keycloak/useDirectLogin.native.js.map +1 -0
- package/dist/esm/keycloak/useWalletLogin.mjs +72 -0
- package/dist/esm/keycloak/useWalletLogin.mjs.map +1 -0
- package/dist/esm/keycloak/useWalletLogin.native.js +5 -0
- package/dist/esm/keycloak/useWalletLogin.native.js.map +1 -0
- package/dist/esm/message/buildWalletMessage.mjs +87 -0
- package/dist/esm/message/buildWalletMessage.mjs.map +1 -0
- package/dist/esm/message/buildWalletMessage.native.js +92 -0
- package/dist/esm/message/buildWalletMessage.native.js.map +1 -0
- package/dist/esm/message/index.mjs +3 -0
- package/dist/esm/message/index.mjs.map +1 -0
- package/dist/esm/message/index.native.js +3 -0
- package/dist/esm/message/index.native.js.map +1 -0
- package/dist/esm/providers/WalletProvider.mjs +9 -3
- package/dist/esm/providers/WalletProvider.mjs.map +1 -1
- package/dist/esm/siws/message.mjs +5 -16
- package/dist/esm/siws/message.mjs.map +1 -1
- package/dist/esm/siws/message.native.js +6 -16
- package/dist/esm/siws/message.native.js.map +1 -1
- package/dist/esm/solana.mjs.map +1 -1
- package/dist/esm/solana.native.js.map +1 -1
- package/dist/jsx/clients/evm.mjs +295 -12
- package/dist/jsx/clients/evm.mjs.map +1 -1
- package/dist/jsx/clients/evm.native.js +500 -12
- package/dist/jsx/clients/evm.native.js.map +1 -1
- package/dist/jsx/clients/index.mjs +2 -2
- package/dist/jsx/clients/index.mjs.map +1 -1
- package/dist/jsx/clients/index.native.js +5 -0
- package/dist/jsx/clients/index.native.js.map +1 -1
- package/dist/jsx/clients/solana.mjs +39 -21
- package/dist/jsx/clients/solana.mjs.map +1 -1
- package/dist/jsx/clients/solana.native.js +48 -23
- package/dist/jsx/clients/solana.native.js.map +1 -1
- package/dist/jsx/connectkit/GpgProvider.mjs +19 -0
- package/dist/jsx/connectkit/GpgProvider.mjs.map +1 -0
- package/dist/jsx/connectkit/GpgProvider.native.js +51 -0
- package/dist/jsx/connectkit/GpgProvider.native.js.map +1 -0
- package/dist/jsx/connectkit/adapters/BitcoinAdapter.mjs +5 -14
- package/dist/jsx/connectkit/adapters/BitcoinAdapter.mjs.map +1 -1
- package/dist/jsx/connectkit/adapters/BitcoinAdapter.native.js +5 -15
- package/dist/jsx/connectkit/adapters/BitcoinAdapter.native.js.map +1 -1
- package/dist/jsx/connectkit/adapters/EthereumAdapter.mjs +6 -15
- package/dist/jsx/connectkit/adapters/EthereumAdapter.mjs.map +1 -1
- package/dist/jsx/connectkit/adapters/EthereumAdapter.native.js +7 -16
- package/dist/jsx/connectkit/adapters/EthereumAdapter.native.js.map +1 -1
- package/dist/jsx/connectkit/adapters/SolanaAdapter.mjs +5 -14
- package/dist/jsx/connectkit/adapters/SolanaAdapter.mjs.map +1 -1
- package/dist/jsx/connectkit/adapters/SolanaAdapter.native.js +5 -15
- package/dist/jsx/connectkit/adapters/SolanaAdapter.native.js.map +1 -1
- package/dist/jsx/connectkit/adapters/SuiAdapter.mjs +5 -14
- package/dist/jsx/connectkit/adapters/SuiAdapter.mjs.map +1 -1
- package/dist/jsx/connectkit/adapters/SuiAdapter.native.js +5 -15
- package/dist/jsx/connectkit/adapters/SuiAdapter.native.js.map +1 -1
- package/dist/jsx/connectkit/connectors/createWalletMessage.mjs +1 -1
- package/dist/jsx/connectkit/connectors/createWalletMessage.native.js +2 -2
- package/dist/jsx/connectkit/index.mjs +3 -2
- package/dist/jsx/connectkit/index.mjs.map +1 -1
- package/dist/jsx/connectkit/index.native.js +4 -3
- package/dist/jsx/connectkit/index.native.js.map +1 -1
- package/dist/jsx/connectkit/siwe/SIWEContext.mjs.map +1 -1
- package/dist/jsx/connectkit/siwe/SIWEContext.native.js.map +1 -1
- package/dist/jsx/connectkit/siwe/SIWEProvider.mjs +19 -4
- package/dist/jsx/connectkit/siwe/SIWEProvider.mjs.map +1 -1
- package/dist/jsx/connectkit/siwe/SIWEProvider.native.js +18 -3
- package/dist/jsx/connectkit/siwe/SIWEProvider.native.js.map +1 -1
- package/dist/jsx/index.js +1 -0
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +1 -0
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +1 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/keycloak/KeycloakWalletLogin.mjs +218 -0
- package/dist/jsx/keycloak/KeycloakWalletLogin.mjs.map +1 -0
- package/dist/jsx/keycloak/KeycloakWalletLogin.native.js +33 -0
- package/dist/jsx/keycloak/KeycloakWalletLogin.native.js.map +1 -0
- package/dist/jsx/keycloak/browserFlow.mjs +58 -0
- package/dist/jsx/keycloak/browserFlow.mjs.map +1 -0
- package/dist/jsx/keycloak/browserFlow.native.js +90 -0
- package/dist/jsx/keycloak/browserFlow.native.js.map +1 -0
- package/dist/jsx/keycloak/device.mjs +10 -0
- package/dist/jsx/keycloak/device.mjs.map +1 -0
- package/dist/jsx/keycloak/device.native.js +45 -0
- package/dist/jsx/keycloak/device.native.js.map +1 -0
- package/dist/jsx/keycloak/directGrant.mjs +164 -0
- package/dist/jsx/keycloak/directGrant.mjs.map +1 -0
- package/dist/jsx/keycloak/directGrant.native.js +233 -0
- package/dist/jsx/keycloak/directGrant.native.js.map +1 -0
- package/dist/jsx/keycloak/iife.mjs +5 -0
- package/dist/jsx/keycloak/iife.mjs.map +1 -0
- package/dist/jsx/keycloak/iife.native.js +7 -0
- package/dist/jsx/keycloak/iife.native.js.map +1 -0
- package/dist/jsx/keycloak/index.mjs +13 -0
- package/dist/jsx/keycloak/index.mjs.map +1 -0
- package/dist/jsx/keycloak/index.native.js +56 -0
- package/dist/jsx/keycloak/index.native.js.map +1 -0
- package/dist/jsx/keycloak/keycloakWallet.mjs +186 -0
- package/dist/jsx/keycloak/keycloakWallet.mjs.map +1 -0
- package/dist/jsx/keycloak/keycloakWallet.native.js +285 -0
- package/dist/jsx/keycloak/keycloakWallet.native.js.map +1 -0
- package/dist/jsx/keycloak/mount.mjs +28 -0
- package/dist/jsx/keycloak/mount.mjs.map +1 -0
- package/dist/jsx/keycloak/mount.native.js +33 -0
- package/dist/jsx/keycloak/mount.native.js.map +1 -0
- package/dist/jsx/keycloak/registerWallet.mjs +49 -0
- package/dist/jsx/keycloak/registerWallet.mjs.map +1 -0
- package/dist/jsx/keycloak/registerWallet.native.js +81 -0
- package/dist/jsx/keycloak/registerWallet.native.js.map +1 -0
- package/dist/jsx/keycloak/types.mjs +20 -0
- package/dist/jsx/keycloak/types.mjs.map +1 -0
- package/dist/jsx/keycloak/types.native.js +179 -0
- package/dist/jsx/keycloak/types.native.js.map +1 -0
- package/dist/jsx/keycloak/useDirectLogin.mjs +32 -0
- package/dist/jsx/keycloak/useDirectLogin.mjs.map +1 -0
- package/dist/jsx/keycloak/useDirectLogin.native.js +77 -0
- package/dist/jsx/keycloak/useDirectLogin.native.js.map +1 -0
- package/dist/jsx/keycloak/useWalletLogin.mjs +72 -0
- package/dist/jsx/keycloak/useWalletLogin.mjs.map +1 -0
- package/dist/jsx/keycloak/useWalletLogin.native.js +33 -0
- package/dist/jsx/keycloak/useWalletLogin.native.js.map +1 -0
- package/dist/jsx/message/buildWalletMessage.mjs +87 -0
- package/dist/jsx/message/buildWalletMessage.mjs.map +1 -0
- package/dist/jsx/message/buildWalletMessage.native.js +121 -0
- package/dist/jsx/message/buildWalletMessage.native.js.map +1 -0
- package/dist/jsx/message/index.mjs +3 -0
- package/dist/jsx/message/index.mjs.map +1 -0
- package/dist/jsx/message/index.native.js +32 -0
- package/dist/jsx/message/index.native.js.map +1 -0
- package/dist/jsx/providers/WalletProvider.mjs +9 -3
- package/dist/jsx/providers/WalletProvider.mjs.map +1 -1
- package/dist/jsx/siws/message.mjs +5 -16
- package/dist/jsx/siws/message.mjs.map +1 -1
- package/dist/jsx/siws/message.native.js +6 -16
- package/dist/jsx/siws/message.native.js.map +1 -1
- package/dist/jsx/solana.mjs.map +1 -1
- package/dist/jsx/solana.native.js.map +1 -1
- package/dist/keycloak/login.iife.js +3604 -0
- package/package.json +27 -8
- package/src/clients/evm.spec.ts +644 -10
- package/src/clients/evm.ts +498 -10
- package/src/clients/index.ts +8 -0
- package/src/clients/solana.spec.ts +116 -0
- package/src/clients/solana.ts +51 -31
- package/src/connectkit/GpgProvider.tsx +16 -0
- package/src/connectkit/adapters/BitcoinAdapter.ts +5 -16
- package/src/connectkit/adapters/EthereumAdapter.ts +6 -17
- package/src/connectkit/adapters/SolanaAdapter.ts +5 -16
- package/src/connectkit/adapters/SuiAdapter.ts +5 -16
- package/src/connectkit/connectors/createWalletMessage.ts +1 -1
- package/src/connectkit/index.ts +2 -6
- package/src/connectkit/openConnector/modal.spec.tsx +1 -1
- package/src/connectkit/siwe/SIWEContext.tsx +6 -2
- package/src/connectkit/siwe/SIWEProvider.tsx +22 -5
- package/src/index.ts +3 -0
- package/src/keycloak/KeycloakWalletLogin.native.tsx +7 -0
- package/src/keycloak/KeycloakWalletLogin.spec.tsx +168 -0
- package/src/keycloak/KeycloakWalletLogin.tsx +251 -0
- package/src/keycloak/browserFlow.spec.ts +108 -0
- package/src/keycloak/browserFlow.ts +84 -0
- package/src/keycloak/device.ts +17 -0
- package/src/keycloak/directGrant.spec.ts +331 -0
- package/src/keycloak/directGrant.ts +214 -0
- package/src/keycloak/iife.ts +3 -0
- package/src/keycloak/index.ts +44 -0
- package/src/keycloak/keycloakWallet.spec.ts +216 -0
- package/src/keycloak/keycloakWallet.ts +275 -0
- package/src/keycloak/mount.native.ts +11 -0
- package/src/keycloak/mount.tsx +45 -0
- package/src/keycloak/nonceReset.spec.tsx +205 -0
- package/src/keycloak/registerWallet.spec.ts +88 -0
- package/src/keycloak/registerWallet.ts +59 -0
- package/src/keycloak/types.ts +66 -0
- package/src/keycloak/useDirectLogin.spec.tsx +112 -0
- package/src/keycloak/useDirectLogin.ts +74 -0
- package/src/keycloak/useWalletLogin.native.ts +7 -0
- package/src/keycloak/useWalletLogin.ts +99 -0
- package/src/message/buildWalletMessage.spec.ts +291 -0
- package/src/message/buildWalletMessage.ts +150 -0
- package/src/message/index.ts +8 -0
- package/src/providers/WalletProvider.tsx +15 -2
- package/src/siws/message.ts +3 -22
- package/src/solana.ts +1 -0
- package/types/clients/evm.d.ts +144 -2
- package/types/clients/evm.d.ts.map +1 -1
- package/types/clients/index.d.ts +1 -1
- package/types/clients/index.d.ts.map +1 -1
- package/types/clients/solana.d.ts +14 -5
- package/types/clients/solana.d.ts.map +1 -1
- package/types/connectkit/GpgProvider.d.ts +10 -0
- package/types/connectkit/GpgProvider.d.ts.map +1 -0
- package/types/connectkit/adapters/BitcoinAdapter.d.ts.map +1 -1
- package/types/connectkit/adapters/EthereumAdapter.d.ts.map +1 -1
- package/types/connectkit/adapters/SolanaAdapter.d.ts.map +1 -1
- package/types/connectkit/adapters/SuiAdapter.d.ts.map +1 -1
- package/types/connectkit/connectors/createWalletMessage.d.ts +1 -1
- package/types/connectkit/connectors/createWalletMessage.d.ts.map +1 -1
- package/types/connectkit/index.d.ts +3 -2
- package/types/connectkit/index.d.ts.map +1 -1
- package/types/connectkit/siwe/SIWEContext.d.ts +2 -1
- package/types/connectkit/siwe/SIWEContext.d.ts.map +1 -1
- package/types/connectkit/siwe/SIWEProvider.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/keycloak/KeycloakWalletLogin.d.ts +45 -0
- package/types/keycloak/KeycloakWalletLogin.d.ts.map +1 -0
- package/types/keycloak/KeycloakWalletLogin.native.d.ts +4 -0
- package/types/keycloak/KeycloakWalletLogin.native.d.ts.map +1 -0
- package/types/keycloak/browserFlow.d.ts +27 -0
- package/types/keycloak/browserFlow.d.ts.map +1 -0
- package/types/keycloak/device.d.ts +14 -0
- package/types/keycloak/device.d.ts.map +1 -0
- package/types/keycloak/directGrant.d.ts +43 -0
- package/types/keycloak/directGrant.d.ts.map +1 -0
- package/types/keycloak/iife.d.ts +2 -0
- package/types/keycloak/iife.d.ts.map +1 -0
- package/types/keycloak/index.d.ts +12 -0
- package/types/keycloak/index.d.ts.map +1 -0
- package/types/keycloak/keycloakWallet.d.ts +120 -0
- package/types/keycloak/keycloakWallet.d.ts.map +1 -0
- package/types/keycloak/mount.d.ts +16 -0
- package/types/keycloak/mount.d.ts.map +1 -0
- package/types/keycloak/mount.native.d.ts +5 -0
- package/types/keycloak/mount.native.d.ts.map +1 -0
- package/types/keycloak/registerWallet.d.ts +22 -0
- package/types/keycloak/registerWallet.d.ts.map +1 -0
- package/types/keycloak/types.d.ts +50 -0
- package/types/keycloak/types.d.ts.map +1 -0
- package/types/keycloak/useDirectLogin.d.ts +29 -0
- package/types/keycloak/useDirectLogin.d.ts.map +1 -0
- package/types/keycloak/useWalletLogin.d.ts +26 -0
- package/types/keycloak/useWalletLogin.d.ts.map +1 -0
- package/types/keycloak/useWalletLogin.native.d.ts +4 -0
- package/types/keycloak/useWalletLogin.native.d.ts.map +1 -0
- package/types/message/buildWalletMessage.d.ts +46 -0
- package/types/message/buildWalletMessage.d.ts.map +1 -0
- package/types/message/index.d.ts +2 -0
- package/types/message/index.d.ts.map +1 -0
- package/types/providers/WalletProvider.d.ts +8 -1
- package/types/providers/WalletProvider.d.ts.map +1 -1
- package/types/siws/message.d.ts.map +1 -1
- package/types/solana.d.ts +1 -1
- package/types/solana.d.ts.map +1 -1
- package/dist/cjs/connectkit/adapters/messages.cjs +0 -73
- package/dist/cjs/connectkit/adapters/messages.native.js +0 -79
- package/dist/cjs/connectkit/adapters/messages.native.js.map +0 -1
- package/dist/esm/connectkit/adapters/messages.mjs +0 -48
- package/dist/esm/connectkit/adapters/messages.mjs.map +0 -1
- package/dist/esm/connectkit/adapters/messages.native.js +0 -51
- package/dist/esm/connectkit/adapters/messages.native.js.map +0 -1
- package/dist/jsx/connectkit/adapters/messages.mjs +0 -48
- package/dist/jsx/connectkit/adapters/messages.mjs.map +0 -1
- package/dist/jsx/connectkit/adapters/messages.native.js +0 -79
- package/dist/jsx/connectkit/adapters/messages.native.js.map +0 -1
- package/src/connectkit/adapters/messages.ts +0 -84
- package/types/connectkit/adapters/messages.d.ts +0 -16
- package/types/connectkit/adapters/messages.d.ts.map +0 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { GpgContext, GpgProvider, type GpgContextValue } from "@multiplatform.one/gpg";
|
|
3
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { act, render, renderHook, waitFor } from "@testing-library/react";
|
|
5
|
+
import { connect } from "@wagmi/core";
|
|
6
|
+
import { type ReactNode, useContext } from "react";
|
|
7
|
+
import { describe, expect, it } from "vitest";
|
|
8
|
+
import { WagmiProvider, createConfig, http } from "wagmi";
|
|
9
|
+
import { mainnet } from "wagmi/chains";
|
|
10
|
+
import { fakeKeycloak, json } from "../../tests/fakeKeycloak";
|
|
11
|
+
import { SIWEProvider } from "../connectkit/siwe";
|
|
12
|
+
import { SIWEContext, type SIWEContextValue } from "../connectkit/siwe/SIWEContext";
|
|
13
|
+
import { createMockConnector } from "../providers/mockConnector";
|
|
14
|
+
import { keycloakWallet } from "./keycloakWallet";
|
|
15
|
+
import type { OnSign } from "./types";
|
|
16
|
+
import { useDirectLogin } from "./useDirectLogin";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A kc nonce is shared by SIWEProvider's and GpgProvider's cached copies. Every
|
|
20
|
+
* reset (a direct grant or registration that consumed it) must refetch both,
|
|
21
|
+
* or the next sign-in on either signs a nonce the server already dropped.
|
|
22
|
+
*/
|
|
23
|
+
function setup(
|
|
24
|
+
nonce: (n: number) => Response | Promise<Response> = (n) => new Response(`nonce-${n}`),
|
|
25
|
+
) {
|
|
26
|
+
let issued = 0;
|
|
27
|
+
const server = fakeKeycloak({
|
|
28
|
+
"/nonce": () => {
|
|
29
|
+
issued += 1;
|
|
30
|
+
return nonce(issued);
|
|
31
|
+
},
|
|
32
|
+
"/register": () => json({ registered: true }),
|
|
33
|
+
"/protocol/openid-connect/token": () =>
|
|
34
|
+
json({ access_token: "at", refresh_token: "rt", expires_in: 300 }),
|
|
35
|
+
});
|
|
36
|
+
const kc = keycloakWallet({
|
|
37
|
+
baseUrl: "https://kc.example.com",
|
|
38
|
+
realm: "main",
|
|
39
|
+
domain: "app.example.com",
|
|
40
|
+
uri: "https://app.example.com",
|
|
41
|
+
fetch: server.fetch,
|
|
42
|
+
});
|
|
43
|
+
const config = createConfig({
|
|
44
|
+
chains: [mainnet],
|
|
45
|
+
connectors: [createMockConnector()],
|
|
46
|
+
transports: { [mainnet.id]: http() },
|
|
47
|
+
});
|
|
48
|
+
const nonceRequests = () => server.requests.filter((r) => r.url.endsWith("/nonce"));
|
|
49
|
+
return { server, kc, config, nonceRequests };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface Seen {
|
|
53
|
+
siwe?: SIWEContextValue | null;
|
|
54
|
+
gpg?: GpgContextValue | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Probe({ seen }: { seen: Seen }) {
|
|
58
|
+
seen.siwe = useContext(SIWEContext);
|
|
59
|
+
seen.gpg = useContext(GpgContext);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function providers(
|
|
64
|
+
kc: ReturnType<typeof setup>["kc"],
|
|
65
|
+
config: ReturnType<typeof setup>["config"],
|
|
66
|
+
onSign: OnSign,
|
|
67
|
+
) {
|
|
68
|
+
const queryClient = new QueryClient({
|
|
69
|
+
defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
|
|
70
|
+
});
|
|
71
|
+
return ({ children }: { children: ReactNode }) => (
|
|
72
|
+
<WagmiProvider config={config} reconnectOnMount={false}>
|
|
73
|
+
<QueryClientProvider client={queryClient}>
|
|
74
|
+
<GpgProvider {...kc.gpgConfig(onSign)}>
|
|
75
|
+
<SIWEProvider {...kc.siweConfig(onSign)}>{children}</SIWEProvider>
|
|
76
|
+
</GpgProvider>
|
|
77
|
+
</QueryClientProvider>
|
|
78
|
+
</WagmiProvider>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function nonces(seen: Seen, expected: string) {
|
|
83
|
+
await waitFor(() => {
|
|
84
|
+
expect(seen.siwe?.nonce.data).toBe(expected);
|
|
85
|
+
expect(seen.gpg?.nonce.data).toBe(expected);
|
|
86
|
+
});
|
|
87
|
+
expect(seen.gpg?.message).toContain(`\nNonce: ${expected}\n`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe("nonce reset", () => {
|
|
91
|
+
it("a registration refetches both providers' nonce, so a second register signs a fresh one", async () => {
|
|
92
|
+
const { server, kc, config, nonceRequests } = setup();
|
|
93
|
+
const registration = kc.registerWallet({ accessToken: "at" });
|
|
94
|
+
const seen: Seen = {};
|
|
95
|
+
render(<Probe seen={seen} />, { wrapper: providers(kc, config, registration.onSign) });
|
|
96
|
+
await nonces(seen, "nonce-1");
|
|
97
|
+
expect(nonceRequests()).toHaveLength(1);
|
|
98
|
+
|
|
99
|
+
await act(async () => {
|
|
100
|
+
await connect(config, { connector: config.connectors[0] });
|
|
101
|
+
});
|
|
102
|
+
await waitFor(() => expect(seen.siwe?.nonce.data).toBe("nonce-1"));
|
|
103
|
+
|
|
104
|
+
const registered = () =>
|
|
105
|
+
server.requests
|
|
106
|
+
.filter((r) => r.url.endsWith("/register"))
|
|
107
|
+
.map((r) => (JSON.parse(r.body) as { message: string }).message);
|
|
108
|
+
|
|
109
|
+
await act(async () => {
|
|
110
|
+
expect(await seen.siwe!.signIn()).not.toBe(false);
|
|
111
|
+
});
|
|
112
|
+
await nonces(seen, "nonce-2");
|
|
113
|
+
await act(async () => {
|
|
114
|
+
expect(await seen.siwe!.signIn()).not.toBe(false);
|
|
115
|
+
});
|
|
116
|
+
await nonces(seen, "nonce-3");
|
|
117
|
+
|
|
118
|
+
expect(registered().map((message) => message.match(/\nNonce: (\S+)\n/)?.[1])).toEqual([
|
|
119
|
+
"nonce-1",
|
|
120
|
+
"nonce-2",
|
|
121
|
+
]);
|
|
122
|
+
expect(nonceRequests()).toHaveLength(3);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("a sign-in while the reset's refetch is in flight waits for the fresh nonce", async () => {
|
|
126
|
+
let release!: () => void;
|
|
127
|
+
const slow = new Promise<void>((resolve) => {
|
|
128
|
+
release = resolve;
|
|
129
|
+
});
|
|
130
|
+
const { server, kc, config } = setup(async (n) => {
|
|
131
|
+
if (n === 2) await slow;
|
|
132
|
+
return new Response(`nonce-${n}`);
|
|
133
|
+
});
|
|
134
|
+
const registration = kc.registerWallet({ accessToken: "at" });
|
|
135
|
+
const seen: Seen = {};
|
|
136
|
+
render(<Probe seen={seen} />, { wrapper: providers(kc, config, registration.onSign) });
|
|
137
|
+
await nonces(seen, "nonce-1");
|
|
138
|
+
await act(async () => {
|
|
139
|
+
await connect(config, { connector: config.connectors[0] });
|
|
140
|
+
});
|
|
141
|
+
const registered = () =>
|
|
142
|
+
server.requests
|
|
143
|
+
.filter((r) => r.url.endsWith("/register"))
|
|
144
|
+
.map(
|
|
145
|
+
(r) => (JSON.parse(r.body) as { message: string }).message.match(/\nNonce: (\S+)\n/)?.[1],
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
await act(async () => {
|
|
149
|
+
expect(await seen.siwe!.signIn()).not.toBe(false);
|
|
150
|
+
});
|
|
151
|
+
await waitFor(() => expect(seen.siwe?.nonce.isFetching).toBe(true));
|
|
152
|
+
expect(seen.siwe?.nonce.data).toBe("nonce-1");
|
|
153
|
+
|
|
154
|
+
let second!: Promise<unknown>;
|
|
155
|
+
await act(async () => {
|
|
156
|
+
second = seen.siwe!.signIn();
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
158
|
+
});
|
|
159
|
+
expect(registered()).toEqual(["nonce-1"]);
|
|
160
|
+
|
|
161
|
+
release();
|
|
162
|
+
await act(async () => {
|
|
163
|
+
expect(await second).not.toBe(false);
|
|
164
|
+
});
|
|
165
|
+
expect(registered()).toEqual(["nonce-1", "nonce-2"]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("a direct login refetches GPG's nonce too, with one /nonce per attempt", async () => {
|
|
169
|
+
const { kc, config, nonceRequests } = setup();
|
|
170
|
+
const grant = kc.directGrant({ clientId: "app" });
|
|
171
|
+
const seen: Seen = {};
|
|
172
|
+
const wrapper = providers(kc, config, grant.onSign);
|
|
173
|
+
const { result } = renderHook(
|
|
174
|
+
() => {
|
|
175
|
+
seen.siwe = useContext(SIWEContext);
|
|
176
|
+
seen.gpg = useContext(GpgContext);
|
|
177
|
+
return useDirectLogin(grant);
|
|
178
|
+
},
|
|
179
|
+
{ wrapper },
|
|
180
|
+
);
|
|
181
|
+
await nonces(seen, "nonce-1");
|
|
182
|
+
await act(async () => {
|
|
183
|
+
await connect(config, { connector: config.connectors[0] });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await act(async () => {
|
|
187
|
+
expect(await result.current.signIn()).toMatchObject({ accessToken: "at" });
|
|
188
|
+
});
|
|
189
|
+
await nonces(seen, "nonce-2");
|
|
190
|
+
expect(nonceRequests()).toHaveLength(2);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("unsubscribes when the providers unmount", async () => {
|
|
194
|
+
const { kc, config, nonceRequests } = setup();
|
|
195
|
+
const seen: Seen = {};
|
|
196
|
+
const { unmount } = render(<Probe seen={seen} />, {
|
|
197
|
+
wrapper: providers(kc, config, async () => true),
|
|
198
|
+
});
|
|
199
|
+
await nonces(seen, "nonce-1");
|
|
200
|
+
unmount();
|
|
201
|
+
kc.resetNonce();
|
|
202
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
203
|
+
expect(nonceRequests()).toHaveLength(1);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { fakeKeycloak, json } from "../../tests/fakeKeycloak";
|
|
3
|
+
import { keycloakWallet } from "./keycloakWallet";
|
|
4
|
+
import { KeycloakWalletError } from "./types";
|
|
5
|
+
|
|
6
|
+
function setup(status = 200, body: unknown = { registered: true }) {
|
|
7
|
+
const server = fakeKeycloak({
|
|
8
|
+
"/nonce": () =>
|
|
9
|
+
new Response("nonce", { headers: { "set-cookie": "KEYCLOAK_SESSION_SIWW=k1; Path=/" } }),
|
|
10
|
+
"/register": () => json(body, status),
|
|
11
|
+
});
|
|
12
|
+
const kc = keycloakWallet({ baseUrl: "https://kc", realm: "main", fetch: server.fetch });
|
|
13
|
+
return { server, kc };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("registerWallet", () => {
|
|
17
|
+
it("POSTs message + signature with the user's bearer token and the nonce cookie", async () => {
|
|
18
|
+
const { server, kc } = setup();
|
|
19
|
+
await kc.getNonce();
|
|
20
|
+
const registration = kc.registerWallet({ accessToken: "at-1" });
|
|
21
|
+
await expect(
|
|
22
|
+
registration.register({ message: "the message", signature: "0xsig" }),
|
|
23
|
+
).resolves.toEqual({ registered: true });
|
|
24
|
+
const req = server.requests.at(-1)!;
|
|
25
|
+
expect(req.url).toBe("https://kc/realms/main/siww/register");
|
|
26
|
+
expect(req.method).toBe("POST");
|
|
27
|
+
expect(req.headers.get("authorization")).toBe("Bearer at-1");
|
|
28
|
+
expect(req.headers.get("content-type")).toBe("application/json");
|
|
29
|
+
expect(req.headers.get("cookie")).toBe("KEYCLOAK_SESSION_SIWW=k1");
|
|
30
|
+
expect(JSON.parse(req.body)).toEqual({ message: "the message", signature: "0xsig" });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("onSign takes the SIWEProvider credential and reads the token from a getter", async () => {
|
|
34
|
+
const { server, kc } = setup(200, { registered: false });
|
|
35
|
+
const registration = kc.registerWallet({ accessToken: async () => "at-2" });
|
|
36
|
+
await expect(
|
|
37
|
+
registration.onSign({ username: "0xsig", password: "ignored", message: "m" }),
|
|
38
|
+
).resolves.toBe(true);
|
|
39
|
+
const req = server.requests.at(-1)!;
|
|
40
|
+
expect(req.headers.get("authorization")).toBe("Bearer at-2");
|
|
41
|
+
expect(JSON.parse(req.body)).toEqual({ message: "m", signature: "0xsig" });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("a wallet another user owns is a 409 KeycloakWalletError", async () => {
|
|
45
|
+
const { kc } = setup(409, { message: "wallet already registered" });
|
|
46
|
+
const error = await kc
|
|
47
|
+
.registerWallet({ accessToken: "at" })
|
|
48
|
+
.register({ message: "m", signature: "s" })
|
|
49
|
+
.catch((e) => e);
|
|
50
|
+
expect(error).toBeInstanceOf(KeycloakWalletError);
|
|
51
|
+
expect(error).toMatchObject({ status: 409, message: "wallet already registered" });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("refuses without a signed-in user", async () => {
|
|
55
|
+
const { server, kc } = setup();
|
|
56
|
+
await expect(
|
|
57
|
+
kc.registerWallet({ accessToken: () => null }).register({ message: "m", signature: "s" }),
|
|
58
|
+
).rejects.toThrow(/access token/);
|
|
59
|
+
expect(server.requests.filter((r) => r.url.endsWith("/register"))).toHaveLength(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("drops the memoized nonce after the attempt; the server consumed it", async () => {
|
|
63
|
+
const { server, kc } = setup();
|
|
64
|
+
await kc.getNonce();
|
|
65
|
+
await kc.registerWallet({ accessToken: "at" }).register({ message: "m", signature: "s" });
|
|
66
|
+
await kc.getNonce();
|
|
67
|
+
expect(server.requests.filter((r) => r.url.endsWith("/nonce"))).toHaveLength(2);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it.each([
|
|
71
|
+
[200, { registered: true }],
|
|
72
|
+
[409, { message: "wallet already registered" }],
|
|
73
|
+
])(
|
|
74
|
+
"tells the providers to refetch after a %i, so a second register is not stale",
|
|
75
|
+
async (status, body) => {
|
|
76
|
+
const { kc } = setup(status, body);
|
|
77
|
+
let resets = 0;
|
|
78
|
+
kc.subscribeNonceReset(() => {
|
|
79
|
+
resets += 1;
|
|
80
|
+
});
|
|
81
|
+
await kc
|
|
82
|
+
.registerWallet({ accessToken: "at" })
|
|
83
|
+
.register({ message: "m", signature: "s" })
|
|
84
|
+
.catch(() => undefined);
|
|
85
|
+
expect(resets).toBe(1);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { KeycloakWallet } from "./keycloakWallet";
|
|
2
|
+
import { KeycloakWalletError, type OnSign } from "./types";
|
|
3
|
+
|
|
4
|
+
export interface RegisterWalletOptions {
|
|
5
|
+
/** The signed-in user's access token, or a getter such as `directGrant(...).getAccessToken`. */
|
|
6
|
+
accessToken: string | (() => string | null | Promise<string | null>);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface WalletRegistration {
|
|
10
|
+
/** `onSign` for `<SIWEProvider>`: proves the connected wallet and binds it to the signed-in user. */
|
|
11
|
+
onSign: OnSign;
|
|
12
|
+
/**
|
|
13
|
+
* POST `<resource>/register`. `registered` is false when the wallet was
|
|
14
|
+
* already this user's; another user's wallet is a 409.
|
|
15
|
+
*/
|
|
16
|
+
register(args: { message: string; signature: string }): Promise<{ registered: boolean }>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createWalletRegistration(
|
|
20
|
+
kc: KeycloakWallet,
|
|
21
|
+
opts: RegisterWalletOptions,
|
|
22
|
+
): WalletRegistration {
|
|
23
|
+
const registration: WalletRegistration = {
|
|
24
|
+
async register({ message, signature }) {
|
|
25
|
+
const accessToken =
|
|
26
|
+
typeof opts.accessToken === "function" ? await opts.accessToken() : opts.accessToken;
|
|
27
|
+
if (!accessToken) throw new Error("registerWallet needs a signed-in user's access token");
|
|
28
|
+
try {
|
|
29
|
+
const res = await kc.request(`${kc.resourceUrl}/register`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
authorization: `Bearer ${accessToken}`,
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({ message, signature }),
|
|
36
|
+
});
|
|
37
|
+
const data = (await res.json().catch(() => ({}))) as {
|
|
38
|
+
registered?: boolean;
|
|
39
|
+
message?: string;
|
|
40
|
+
};
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new KeycloakWalletError(
|
|
43
|
+
data.message || `Keycloak wallet register failed (${res.status})`,
|
|
44
|
+
res.status,
|
|
45
|
+
{ error: data.message },
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return { registered: Boolean(data.registered) };
|
|
49
|
+
} finally {
|
|
50
|
+
kc.resetNonce();
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
onSign: async ({ username, message }) => {
|
|
54
|
+
await registration.register({ message, signature: username });
|
|
55
|
+
return true;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
return registration;
|
|
59
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The REST resource the Keycloak wallet SPI mounts under `/realms/<realm>/`:
|
|
3
|
+
* `siww` on the SHC Keycloak, `wallet` on mpo's own SPI.
|
|
4
|
+
*/
|
|
5
|
+
export type KeycloakWalletResource = "siww" | "wallet";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What SIWEProvider and GpgProvider hand their `onSign`: `username` is the raw
|
|
9
|
+
* signature (or the GPG blob), `password` is base64(message).
|
|
10
|
+
*/
|
|
11
|
+
export interface WalletSignIn {
|
|
12
|
+
username: string;
|
|
13
|
+
password: string;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type OnSign = (credentials: WalletSignIn) => Promise<boolean>;
|
|
18
|
+
|
|
19
|
+
export interface KeycloakTokens {
|
|
20
|
+
accessToken: string;
|
|
21
|
+
refreshToken?: string;
|
|
22
|
+
idToken?: string;
|
|
23
|
+
tokenType: string;
|
|
24
|
+
scope?: string;
|
|
25
|
+
/** Epoch ms after which the access token is expired. */
|
|
26
|
+
expiresAt: number;
|
|
27
|
+
/** Epoch ms after which the refresh token is expired; absent for offline or non-expiring tokens. */
|
|
28
|
+
refreshExpiresAt?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class KeycloakWalletError extends Error {
|
|
32
|
+
readonly status: number;
|
|
33
|
+
readonly error?: string;
|
|
34
|
+
readonly errorDescription?: string;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
message: string,
|
|
38
|
+
status: number,
|
|
39
|
+
details: { error?: string; errorDescription?: string } = {},
|
|
40
|
+
) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "KeycloakWalletError";
|
|
43
|
+
this.status = status;
|
|
44
|
+
this.error = details.error;
|
|
45
|
+
this.errorDescription = details.errorDescription;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Keycloak accepted the wallet but the account has required actions left (a
|
|
51
|
+
* new user's profile form, terms, a verified email), which a direct grant
|
|
52
|
+
* cannot run: Keycloak answers 400 invalid_grant "Account is not fully set
|
|
53
|
+
* up". Send the user through the browser login once
|
|
54
|
+
* (`kc.browserLoginUrl(...)` in the system browser or a WebView) so Keycloak
|
|
55
|
+
* shows its own forms; the direct grant succeeds after that.
|
|
56
|
+
*/
|
|
57
|
+
export class ProfileIncompleteError extends KeycloakWalletError {
|
|
58
|
+
constructor(
|
|
59
|
+
message: string,
|
|
60
|
+
status: number,
|
|
61
|
+
details: { error?: string; errorDescription?: string } = {},
|
|
62
|
+
) {
|
|
63
|
+
super(message, status, details);
|
|
64
|
+
this.name = "ProfileIncompleteError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, renderHook } from "@testing-library/react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { fakeKeycloak, json } from "../../tests/fakeKeycloak";
|
|
6
|
+
import { SIWEContext, StatusState, type SIWEContextValue } from "../connectkit/siwe/SIWEContext";
|
|
7
|
+
import { keycloakWallet } from "./keycloakWallet";
|
|
8
|
+
import { ProfileIncompleteError, type OnSign } from "./types";
|
|
9
|
+
import { useDirectLogin } from "./useDirectLogin";
|
|
10
|
+
|
|
11
|
+
function setup(tokenStatus = 200) {
|
|
12
|
+
const server = fakeKeycloak({
|
|
13
|
+
"/nonce": () => new Response("nonce"),
|
|
14
|
+
"/token": () =>
|
|
15
|
+
tokenStatus === 200
|
|
16
|
+
? json({ access_token: "at", refresh_token: "rt", expires_in: 60 })
|
|
17
|
+
: tokenStatus === 400
|
|
18
|
+
? json({ error: "invalid_grant", error_description: "Account is not fully set up" }, 400)
|
|
19
|
+
: json({ error: "invalid_grant", error_description: "Invalid user credentials" }, 401),
|
|
20
|
+
});
|
|
21
|
+
const kc = keycloakWallet({ baseUrl: "https://kc", realm: "main", fetch: server.fetch });
|
|
22
|
+
const grant = kc.directGrant({ clientId: "app" });
|
|
23
|
+
const siwe = (onSign: OnSign, signIn?: () => Promise<unknown>) =>
|
|
24
|
+
({
|
|
25
|
+
onSign,
|
|
26
|
+
signIn:
|
|
27
|
+
signIn ??
|
|
28
|
+
(async () => {
|
|
29
|
+
try {
|
|
30
|
+
await onSign({ username: "0xsig", password: "bQ==", message: "m" });
|
|
31
|
+
return undefined;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
status: StatusState.READY,
|
|
37
|
+
}) as unknown as SIWEContextValue;
|
|
38
|
+
const wrapper =
|
|
39
|
+
(value: SIWEContextValue) =>
|
|
40
|
+
({ children }: { children: ReactNode }) => (
|
|
41
|
+
<SIWEContext.Provider value={value}>{children}</SIWEContext.Provider>
|
|
42
|
+
);
|
|
43
|
+
return { kc, grant, siwe, wrapper };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("useDirectLogin", () => {
|
|
47
|
+
it("signs through SIWEProvider and exposes the grant's tokens", async () => {
|
|
48
|
+
const { kc, grant, siwe, wrapper } = setup();
|
|
49
|
+
let resets = 0;
|
|
50
|
+
kc.subscribeNonceReset(() => {
|
|
51
|
+
resets += 1;
|
|
52
|
+
});
|
|
53
|
+
const { result } = renderHook(() => useDirectLogin(grant), {
|
|
54
|
+
wrapper: wrapper(siwe(grant.onSign)),
|
|
55
|
+
});
|
|
56
|
+
expect(result.current.status).toBe("idle");
|
|
57
|
+
let tokens: unknown;
|
|
58
|
+
await act(async () => {
|
|
59
|
+
tokens = await result.current.signIn();
|
|
60
|
+
});
|
|
61
|
+
expect(tokens).toMatchObject({ accessToken: "at", refreshToken: "rt" });
|
|
62
|
+
expect(result.current.status).toBe("signed-in");
|
|
63
|
+
expect(result.current.tokens?.accessToken).toBe("at");
|
|
64
|
+
expect(resets).toBe(1);
|
|
65
|
+
await act(async () => {
|
|
66
|
+
await result.current.signOut();
|
|
67
|
+
});
|
|
68
|
+
expect(result.current.status).toBe("idle");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("a refused login resolves null and surfaces Keycloak's error", async () => {
|
|
72
|
+
const { grant, siwe, wrapper } = setup(401);
|
|
73
|
+
const { result } = renderHook(() => useDirectLogin(grant), {
|
|
74
|
+
wrapper: wrapper(siwe(grant.onSign)),
|
|
75
|
+
});
|
|
76
|
+
let tokens: unknown = "unset";
|
|
77
|
+
await act(async () => {
|
|
78
|
+
tokens = await result.current.signIn();
|
|
79
|
+
});
|
|
80
|
+
expect(tokens).toBeNull();
|
|
81
|
+
expect(result.current.status).toBe("error");
|
|
82
|
+
expect(result.current.error?.message).toBe("Invalid user credentials");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("an account with required actions left reports profile-incomplete with the typed error", async () => {
|
|
86
|
+
const { grant, siwe, wrapper } = setup(400);
|
|
87
|
+
const { result } = renderHook(() => useDirectLogin(grant), {
|
|
88
|
+
wrapper: wrapper(siwe(grant.onSign)),
|
|
89
|
+
});
|
|
90
|
+
let tokens: unknown = "unset";
|
|
91
|
+
await act(async () => {
|
|
92
|
+
tokens = await result.current.signIn();
|
|
93
|
+
});
|
|
94
|
+
expect(tokens).toBeNull();
|
|
95
|
+
expect(result.current.status).toBe("profile-incomplete");
|
|
96
|
+
expect(result.current.error).toBeInstanceOf(ProfileIncompleteError);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("refuses a SIWEProvider wired to some other onSign", async () => {
|
|
100
|
+
const { grant, siwe, wrapper } = setup();
|
|
101
|
+
const { result } = renderHook(() => useDirectLogin(grant), {
|
|
102
|
+
wrapper: wrapper(siwe(async () => true)),
|
|
103
|
+
});
|
|
104
|
+
await expect(result.current.signIn()).rejects.toThrow(/not this grant's/);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("needs a SIWEProvider", async () => {
|
|
108
|
+
const { grant } = setup();
|
|
109
|
+
const { result } = renderHook(() => useDirectLogin(grant));
|
|
110
|
+
await expect(result.current.signIn()).rejects.toThrow(/inside a SIWEProvider/);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useCallback, useContext, useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { SIWEContext } from "../connectkit/siwe/SIWEContext";
|
|
3
|
+
import type { DirectGrant } from "./directGrant";
|
|
4
|
+
import { ProfileIncompleteError, type KeycloakTokens } from "./types";
|
|
5
|
+
|
|
6
|
+
export type DirectLoginStatus = "idle" | "signing" | "signed-in" | "profile-incomplete" | "error";
|
|
7
|
+
|
|
8
|
+
export interface UseDirectLoginResult {
|
|
9
|
+
/**
|
|
10
|
+
* `profile-incomplete`: Keycloak took the wallet but the account has
|
|
11
|
+
* required actions left, and `error` is the `ProfileIncompleteError`. Open
|
|
12
|
+
* `kc.browserLoginUrl(...)` (system browser or WebView) so Keycloak's own
|
|
13
|
+
* forms run, then `signIn()` again.
|
|
14
|
+
*/
|
|
15
|
+
status: DirectLoginStatus;
|
|
16
|
+
tokens: KeycloakTokens | null;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
/**
|
|
19
|
+
* Sign in with the connected wallet through SIWEProvider and exchange the
|
|
20
|
+
* signature for tokens. Resolves null when the wallet or Keycloak refused.
|
|
21
|
+
*/
|
|
22
|
+
signIn(): Promise<KeycloakTokens | null>;
|
|
23
|
+
refresh(): Promise<KeycloakTokens>;
|
|
24
|
+
signOut(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Direct (browserless) wallet login: SIWEProvider signs, the grant trades the
|
|
29
|
+
* signature for Keycloak tokens. Mount it under
|
|
30
|
+
* `<SIWEProvider {...kc.siweConfig(grant.onSign)}>`; every login attempt resets
|
|
31
|
+
* the kc nonce, which refetches it in SIWEProvider and GpgProvider alike.
|
|
32
|
+
*/
|
|
33
|
+
export function useDirectLogin(grant: DirectGrant): UseDirectLoginResult {
|
|
34
|
+
const siwe = useContext(SIWEContext);
|
|
35
|
+
const state = useSyncExternalStore(
|
|
36
|
+
grant.subscribe,
|
|
37
|
+
() => grant.state,
|
|
38
|
+
() => grant.state,
|
|
39
|
+
);
|
|
40
|
+
const [signing, setSigning] = useState(false);
|
|
41
|
+
|
|
42
|
+
const signIn = useCallback(async () => {
|
|
43
|
+
if (!siwe) throw new Error("useDirectLogin must be used inside a SIWEProvider");
|
|
44
|
+
if (siwe.onSign !== grant.onSign) {
|
|
45
|
+
throw new Error("useDirectLogin: the SIWEProvider's onSign is not this grant's");
|
|
46
|
+
}
|
|
47
|
+
setSigning(true);
|
|
48
|
+
try {
|
|
49
|
+
const result = await siwe.signIn();
|
|
50
|
+
return result === false ? null : grant.tokens;
|
|
51
|
+
} finally {
|
|
52
|
+
setSigning(false);
|
|
53
|
+
}
|
|
54
|
+
}, [siwe, grant]);
|
|
55
|
+
|
|
56
|
+
const status: DirectLoginStatus = signing
|
|
57
|
+
? "signing"
|
|
58
|
+
: state.tokens
|
|
59
|
+
? "signed-in"
|
|
60
|
+
: state.error instanceof ProfileIncompleteError
|
|
61
|
+
? "profile-incomplete"
|
|
62
|
+
: state.error
|
|
63
|
+
? "error"
|
|
64
|
+
: "idle";
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
status,
|
|
68
|
+
tokens: state.tokens,
|
|
69
|
+
error: state.error,
|
|
70
|
+
signIn,
|
|
71
|
+
refresh: useCallback(() => grant.refresh(), [grant]),
|
|
72
|
+
signOut: useCallback(() => grant.logout(), [grant]),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UseWalletLoginOptions, UseWalletLoginResult } from "./useWalletLogin";
|
|
2
|
+
|
|
3
|
+
export type { UseWalletLoginOptions, UseWalletLoginResult };
|
|
4
|
+
|
|
5
|
+
export function useWalletLogin(_opts?: UseWalletLoginOptions): UseWalletLoginResult {
|
|
6
|
+
throw new Error("useWalletLogin is web only; a native app signs in with useDirectLogin");
|
|
7
|
+
}
|