@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,331 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { fakeKeycloak, json, type RecordedRequest } from "../../tests/fakeKeycloak";
|
|
3
|
+
import { keycloakWallet } from "./keycloakWallet";
|
|
4
|
+
import { KeycloakWalletError, ProfileIncompleteError } from "./types";
|
|
5
|
+
|
|
6
|
+
const t0 = Date.parse("2026-09-26T21:00:00Z");
|
|
7
|
+
|
|
8
|
+
interface ServerOptions {
|
|
9
|
+
reject?: boolean;
|
|
10
|
+
/** Answers every password grant instead of issuing tokens. */
|
|
11
|
+
password?: () => Response;
|
|
12
|
+
/** Answers every refresh-token grant instead of issuing tokens. */
|
|
13
|
+
refresh?: () => Response | Promise<Response>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function tokenServer(opts: ServerOptions = {}) {
|
|
17
|
+
let n = 0;
|
|
18
|
+
const token = (req: RecordedRequest) => {
|
|
19
|
+
const form = new URLSearchParams(req.body);
|
|
20
|
+
if (form.get("grant_type") === "password" && opts.password) return opts.password();
|
|
21
|
+
if (opts.reject && form.get("grant_type") === "password") {
|
|
22
|
+
return json({ error: "invalid_grant", error_description: "Invalid user credentials" }, 401);
|
|
23
|
+
}
|
|
24
|
+
if (form.get("grant_type") === "refresh_token" && form.get("refresh_token") === "revoked") {
|
|
25
|
+
return json({ error: "invalid_grant", error_description: "Token is not active" }, 400);
|
|
26
|
+
}
|
|
27
|
+
if (form.get("grant_type") === "refresh_token" && opts.refresh) return opts.refresh();
|
|
28
|
+
n += 1;
|
|
29
|
+
return json({
|
|
30
|
+
access_token: `access-${n}`,
|
|
31
|
+
refresh_token: `refresh-${n}`,
|
|
32
|
+
id_token: `id-${n}`,
|
|
33
|
+
token_type: "Bearer",
|
|
34
|
+
scope: form.get("scope") ?? "openid profile",
|
|
35
|
+
expires_in: 300,
|
|
36
|
+
refresh_expires_in: 1800,
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
return fakeKeycloak({
|
|
40
|
+
"/nonce": () =>
|
|
41
|
+
new Response("nonce", { headers: { "set-cookie": "KEYCLOAK_SESSION_WALLET=abc; Path=/" } }),
|
|
42
|
+
"/protocol/openid-connect/token": token,
|
|
43
|
+
"/protocol/openid-connect/logout": () => new Response(null, { status: 204 }),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setup(opts: ServerOptions & { now?: () => number } = {}) {
|
|
48
|
+
const server = tokenServer(opts);
|
|
49
|
+
const kc = keycloakWallet({
|
|
50
|
+
baseUrl: "https://kc.example.com",
|
|
51
|
+
realm: "main",
|
|
52
|
+
resource: "wallet",
|
|
53
|
+
fetch: server.fetch,
|
|
54
|
+
});
|
|
55
|
+
const grant = kc.directGrant({
|
|
56
|
+
clientId: "wallet-app",
|
|
57
|
+
scope: ["openid", "email"],
|
|
58
|
+
now: opts.now ?? (() => t0),
|
|
59
|
+
});
|
|
60
|
+
return { server, kc, grant };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("directGrant", () => {
|
|
64
|
+
it("trades signature + base64(message) for tokens on the password grant, with the nonce cookie", async () => {
|
|
65
|
+
const { server, kc, grant } = setup();
|
|
66
|
+
await kc.getNonce();
|
|
67
|
+
const tokens = await grant.login({ username: "0xsig", password: "bWVzc2FnZQ==" });
|
|
68
|
+
const req = server.requests.find((r) => r.url.endsWith("/token"))!;
|
|
69
|
+
expect(req.url).toBe("https://kc.example.com/realms/main/protocol/openid-connect/token");
|
|
70
|
+
expect(req.method).toBe("POST");
|
|
71
|
+
expect(req.headers.get("content-type")).toBe("application/x-www-form-urlencoded");
|
|
72
|
+
expect(req.headers.get("cookie")).toBe("KEYCLOAK_SESSION_WALLET=abc");
|
|
73
|
+
expect(Object.fromEntries(new URLSearchParams(req.body))).toEqual({
|
|
74
|
+
client_id: "wallet-app",
|
|
75
|
+
grant_type: "password",
|
|
76
|
+
username: "0xsig",
|
|
77
|
+
password: "bWVzc2FnZQ==",
|
|
78
|
+
scope: "openid email",
|
|
79
|
+
});
|
|
80
|
+
expect(tokens).toEqual({
|
|
81
|
+
accessToken: "access-1",
|
|
82
|
+
refreshToken: "refresh-1",
|
|
83
|
+
idToken: "id-1",
|
|
84
|
+
tokenType: "Bearer",
|
|
85
|
+
scope: "openid email",
|
|
86
|
+
expiresAt: t0 + 300_000,
|
|
87
|
+
refreshExpiresAt: t0 + 1_800_000,
|
|
88
|
+
});
|
|
89
|
+
expect(grant.tokens).toEqual(tokens);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("onSign is the SIWEProvider/GpgProvider hook: resolves true and stores the tokens", async () => {
|
|
93
|
+
const { grant } = setup();
|
|
94
|
+
const seen: unknown[] = [];
|
|
95
|
+
grant.subscribe((state) => seen.push(state.tokens?.accessToken));
|
|
96
|
+
await expect(
|
|
97
|
+
grant.onSign({ username: "0xsig", password: "bWVzc2FnZQ==", message: "message" }),
|
|
98
|
+
).resolves.toBe(true);
|
|
99
|
+
expect(grant.tokens?.accessToken).toBe("access-1");
|
|
100
|
+
expect(seen).toEqual(["access-1"]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("a rejected login throws KeycloakWalletError with Keycloak's error, and records it", async () => {
|
|
104
|
+
const { grant } = setup({ reject: true });
|
|
105
|
+
const error = await grant.login({ username: "0xsig", password: "x" }).catch((e) => e);
|
|
106
|
+
expect(error).toBeInstanceOf(KeycloakWalletError);
|
|
107
|
+
expect(error).toMatchObject({
|
|
108
|
+
status: 401,
|
|
109
|
+
error: "invalid_grant",
|
|
110
|
+
message: "Invalid user credentials",
|
|
111
|
+
});
|
|
112
|
+
expect(grant.state.error).toBe(error);
|
|
113
|
+
expect(grant.tokens).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("an account with required actions left rejects with ProfileIncompleteError", async () => {
|
|
117
|
+
const { grant } = setup({
|
|
118
|
+
password: () =>
|
|
119
|
+
json({ error: "invalid_grant", error_description: "Account is not fully set up" }, 400),
|
|
120
|
+
});
|
|
121
|
+
const error = await grant.login({ username: "0xsig", password: "x" }).catch((e) => e);
|
|
122
|
+
expect(error).toBeInstanceOf(ProfileIncompleteError);
|
|
123
|
+
expect(error).toBeInstanceOf(KeycloakWalletError);
|
|
124
|
+
expect(error).toMatchObject({
|
|
125
|
+
name: "ProfileIncompleteError",
|
|
126
|
+
status: 400,
|
|
127
|
+
error: "invalid_grant",
|
|
128
|
+
message: "Account is not fully set up",
|
|
129
|
+
});
|
|
130
|
+
expect(grant.state.error).toBe(error);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("maps the resolve_required_actions error code the same way", async () => {
|
|
134
|
+
const { grant } = setup({
|
|
135
|
+
password: () => json({ error: "resolve_required_actions" }, 400),
|
|
136
|
+
});
|
|
137
|
+
await expect(grant.login({ username: "0xsig", password: "x" })).rejects.toBeInstanceOf(
|
|
138
|
+
ProfileIncompleteError,
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("other invalid_grant refusals stay plain KeycloakWalletErrors", async () => {
|
|
143
|
+
const { grant } = setup({
|
|
144
|
+
password: () =>
|
|
145
|
+
json({ error: "invalid_grant", error_description: "Invalid user credentials" }, 400),
|
|
146
|
+
});
|
|
147
|
+
const error = await grant.login({ username: "0xsig", password: "x" }).catch((e) => e);
|
|
148
|
+
expect(error).toBeInstanceOf(KeycloakWalletError);
|
|
149
|
+
expect(error).not.toBeInstanceOf(ProfileIncompleteError);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("every login round trip drops the memoized nonce, so the next sign-in gets a fresh one", async () => {
|
|
153
|
+
const { server, kc, grant } = setup({ reject: true });
|
|
154
|
+
await kc.getNonce();
|
|
155
|
+
await grant.login({ username: "0xsig", password: "x" }).catch(() => undefined);
|
|
156
|
+
await kc.getNonce();
|
|
157
|
+
expect(server.requests.filter((r) => r.url.endsWith("/nonce"))).toHaveLength(2);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("refresh uses the stored refresh token and replaces the tokens", async () => {
|
|
161
|
+
const { server, grant } = setup();
|
|
162
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
163
|
+
const refreshed = await grant.refresh();
|
|
164
|
+
const req = server.requests.filter((r) => r.url.endsWith("/token"))[1];
|
|
165
|
+
expect(Object.fromEntries(new URLSearchParams(req.body))).toEqual({
|
|
166
|
+
client_id: "wallet-app",
|
|
167
|
+
grant_type: "refresh_token",
|
|
168
|
+
refresh_token: "refresh-1",
|
|
169
|
+
});
|
|
170
|
+
expect(refreshed.accessToken).toBe("access-2");
|
|
171
|
+
expect(grant.tokens?.refreshToken).toBe("refresh-2");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("concurrent refreshes share one request", async () => {
|
|
175
|
+
const { server, grant } = setup();
|
|
176
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
177
|
+
const [a, b] = await Promise.all([grant.refresh(), grant.refresh()]);
|
|
178
|
+
expect(a).toBe(b);
|
|
179
|
+
expect(server.requests.filter((r) => r.url.endsWith("/token"))).toHaveLength(2);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("a refused refresh (400 invalid_grant) clears the session", async () => {
|
|
183
|
+
const { grant } = setup();
|
|
184
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
185
|
+
await expect(grant.refresh("revoked")).rejects.toBeInstanceOf(KeycloakWalletError);
|
|
186
|
+
expect(grant.tokens).toBeNull();
|
|
187
|
+
expect(grant.state.error?.message).toBe("Token is not active");
|
|
188
|
+
await expect(grant.refresh()).rejects.toThrow(/no refresh token/);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("a 401 on refresh clears the session", async () => {
|
|
192
|
+
const { grant } = setup({
|
|
193
|
+
refresh: () =>
|
|
194
|
+
json({ error: "unauthorized_client", error_description: "Invalid client" }, 401),
|
|
195
|
+
});
|
|
196
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
197
|
+
await expect(grant.refresh()).rejects.toMatchObject({ status: 401 });
|
|
198
|
+
expect(grant.tokens).toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it.each([502, 503])(
|
|
202
|
+
"a %i on refresh (Keycloak restarting) keeps the tokens and surfaces the error",
|
|
203
|
+
async (status) => {
|
|
204
|
+
const { grant } = setup({ refresh: () => new Response("Bad Gateway", { status }) });
|
|
205
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
206
|
+
const error = await grant.refresh().catch((e) => e);
|
|
207
|
+
expect(error).toBeInstanceOf(KeycloakWalletError);
|
|
208
|
+
expect(error.status).toBe(status);
|
|
209
|
+
expect(grant.tokens?.refreshToken).toBe("refresh-1");
|
|
210
|
+
expect(grant.state.error).toBe(error);
|
|
211
|
+
},
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
it("a 400 other than invalid_grant keeps the tokens", async () => {
|
|
215
|
+
const { grant } = setup({
|
|
216
|
+
refresh: () =>
|
|
217
|
+
json({ error: "invalid_request", error_description: "Missing parameter" }, 400),
|
|
218
|
+
});
|
|
219
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
220
|
+
await expect(grant.refresh()).rejects.toMatchObject({ status: 400, error: "invalid_request" });
|
|
221
|
+
expect(grant.tokens?.accessToken).toBe("access-1");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("a network failure on refresh keeps the tokens", async () => {
|
|
225
|
+
const { server, kc } = setup();
|
|
226
|
+
let down = false;
|
|
227
|
+
const flaky = keycloakWallet({
|
|
228
|
+
baseUrl: kc.baseUrl,
|
|
229
|
+
realm: kc.realm,
|
|
230
|
+
resource: kc.resource,
|
|
231
|
+
fetch: (input, init) =>
|
|
232
|
+
down ? Promise.reject(new TypeError("Failed to fetch")) : server.fetch(input, init),
|
|
233
|
+
}).directGrant({ clientId: "wallet-app", now: () => t0 });
|
|
234
|
+
await flaky.login({ username: "0xsig", password: "x" });
|
|
235
|
+
down = true;
|
|
236
|
+
await expect(flaky.refresh()).rejects.toThrow("Failed to fetch");
|
|
237
|
+
expect(flaky.tokens?.accessToken).toBe("access-1");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("the next refresh after a kept 503 succeeds with the same refresh token", async () => {
|
|
241
|
+
let fail = true;
|
|
242
|
+
const { server, grant } = setup({
|
|
243
|
+
refresh: () =>
|
|
244
|
+
fail ? new Response("", { status: 503 }) : json({ access_token: "access-9" }),
|
|
245
|
+
});
|
|
246
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
247
|
+
await expect(grant.refresh()).rejects.toMatchObject({ status: 503 });
|
|
248
|
+
fail = false;
|
|
249
|
+
await expect(grant.refresh()).resolves.toMatchObject({ accessToken: "access-9" });
|
|
250
|
+
const refreshes = server.requests
|
|
251
|
+
.filter((r) => r.url.endsWith("/token"))
|
|
252
|
+
.map((r) => new URLSearchParams(r.body))
|
|
253
|
+
.filter((form) => form.get("grant_type") === "refresh_token");
|
|
254
|
+
expect(refreshes.map((form) => form.get("refresh_token"))).toEqual(["refresh-1", "refresh-1"]);
|
|
255
|
+
expect(grant.state.error).toBeNull();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe("a login that lands while a refresh is in flight", () => {
|
|
259
|
+
function slowRefresh(answer: () => Response) {
|
|
260
|
+
let release!: () => void;
|
|
261
|
+
const gate = new Promise<void>((resolve) => {
|
|
262
|
+
release = resolve;
|
|
263
|
+
});
|
|
264
|
+
return {
|
|
265
|
+
release,
|
|
266
|
+
refresh: () => gate.then(answer),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
it("wins over the refresh's tokens", async () => {
|
|
271
|
+
const slow = slowRefresh(() => json({ access_token: "stale", refresh_token: "stale" }));
|
|
272
|
+
const { grant } = setup({ refresh: slow.refresh });
|
|
273
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
274
|
+
const pending = grant.refresh();
|
|
275
|
+
const newer = await grant.login({ username: "0xsig2", password: "y" });
|
|
276
|
+
slow.release();
|
|
277
|
+
await expect(pending).resolves.toBe(newer);
|
|
278
|
+
expect(grant.tokens).toBe(newer);
|
|
279
|
+
expect(grant.tokens?.accessToken).not.toBe("stale");
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("is not cleared by the old refresh token's refusal", async () => {
|
|
283
|
+
const slow = slowRefresh(() =>
|
|
284
|
+
json({ error: "invalid_grant", error_description: "Token is not active" }, 400),
|
|
285
|
+
);
|
|
286
|
+
const { grant } = setup({ refresh: slow.refresh });
|
|
287
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
288
|
+
const pending = grant.refresh();
|
|
289
|
+
const newer = await grant.login({ username: "0xsig2", password: "y" });
|
|
290
|
+
slow.release();
|
|
291
|
+
await expect(pending).resolves.toBe(newer);
|
|
292
|
+
expect(grant.tokens).toBe(newer);
|
|
293
|
+
expect(grant.state.error).toBeNull();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("a logout during the refresh stays signed out", async () => {
|
|
297
|
+
const slow = slowRefresh(() => json({ access_token: "stale", refresh_token: "stale" }));
|
|
298
|
+
const { grant } = setup({ refresh: slow.refresh });
|
|
299
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
300
|
+
const pending = grant.refresh();
|
|
301
|
+
await grant.logout();
|
|
302
|
+
slow.release();
|
|
303
|
+
await expect(pending).rejects.toThrow(/signed out/);
|
|
304
|
+
expect(grant.tokens).toBeNull();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("getAccessToken refreshes an access token about to expire", async () => {
|
|
309
|
+
let now = t0;
|
|
310
|
+
const { grant } = setup({ now: () => now });
|
|
311
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
312
|
+
expect(await grant.getAccessToken()).toBe("access-1");
|
|
313
|
+
now = t0 + 290_000;
|
|
314
|
+
expect(await grant.getAccessToken()).toBe("access-2");
|
|
315
|
+
expect(await grant.getAccessToken({ minValiditySeconds: 0 })).toBe("access-2");
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("logout ends the Keycloak session and clears the tokens", async () => {
|
|
319
|
+
const { server, grant } = setup();
|
|
320
|
+
await grant.login({ username: "0xsig", password: "x" });
|
|
321
|
+
await grant.logout();
|
|
322
|
+
const req = server.requests.at(-1)!;
|
|
323
|
+
expect(req.url).toBe("https://kc.example.com/realms/main/protocol/openid-connect/logout");
|
|
324
|
+
expect(Object.fromEntries(new URLSearchParams(req.body))).toEqual({
|
|
325
|
+
client_id: "wallet-app",
|
|
326
|
+
refresh_token: "refresh-1",
|
|
327
|
+
});
|
|
328
|
+
expect(grant.tokens).toBeNull();
|
|
329
|
+
expect(await grant.getAccessToken()).toBeNull();
|
|
330
|
+
});
|
|
331
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { KeycloakWallet } from "./keycloakWallet";
|
|
2
|
+
import {
|
|
3
|
+
KeycloakWalletError,
|
|
4
|
+
ProfileIncompleteError,
|
|
5
|
+
type KeycloakTokens,
|
|
6
|
+
type OnSign,
|
|
7
|
+
type WalletSignIn,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
export interface DirectGrantOptions {
|
|
11
|
+
clientId: string;
|
|
12
|
+
/** Default `openid`. */
|
|
13
|
+
scope?: string | string[];
|
|
14
|
+
/** Confidential clients only; a native or browser app is a public client. */
|
|
15
|
+
clientSecret?: string;
|
|
16
|
+
now?: () => number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DirectGrantState {
|
|
20
|
+
tokens: KeycloakTokens | null;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DirectGrant {
|
|
25
|
+
readonly state: DirectGrantState;
|
|
26
|
+
readonly tokens: KeycloakTokens | null;
|
|
27
|
+
/** `onSign` for `<SIWEProvider>` / `<GpgProvider>`: exchanges the credential for tokens. */
|
|
28
|
+
onSign: OnSign;
|
|
29
|
+
/**
|
|
30
|
+
* Password grant with `username` = signature (or GPG blob) and `password` =
|
|
31
|
+
* base64(message). Rejects with `ProfileIncompleteError` when the account
|
|
32
|
+
* still has required actions; see that class for the browser fallback.
|
|
33
|
+
*/
|
|
34
|
+
login(credentials: Pick<WalletSignIn, "username" | "password">): Promise<KeycloakTokens>;
|
|
35
|
+
/**
|
|
36
|
+
* Refresh-token grant; defaults to the stored refresh token. Keycloak
|
|
37
|
+
* refusing the token (400 invalid_grant, 401) clears the stored tokens; any
|
|
38
|
+
* other failure keeps them and only records the error. A login or logout
|
|
39
|
+
* that lands while the refresh is in flight wins: the refresh's answer is
|
|
40
|
+
* dropped and it resolves with the newer tokens (or rejects when signed out).
|
|
41
|
+
*/
|
|
42
|
+
refresh(refreshToken?: string): Promise<KeycloakTokens>;
|
|
43
|
+
/** A valid access token, refreshed first when it expires within `minValiditySeconds` (default 30). */
|
|
44
|
+
getAccessToken(opts?: { minValiditySeconds?: number }): Promise<string | null>;
|
|
45
|
+
/** Ends the Keycloak session behind the refresh token and clears the stored tokens. */
|
|
46
|
+
logout(): Promise<void>;
|
|
47
|
+
subscribe(listener: (state: DirectGrantState) => void): () => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface TokenResponse {
|
|
51
|
+
access_token?: string;
|
|
52
|
+
refresh_token?: string;
|
|
53
|
+
id_token?: string;
|
|
54
|
+
token_type?: string;
|
|
55
|
+
scope?: string;
|
|
56
|
+
expires_in?: number;
|
|
57
|
+
refresh_expires_in?: number;
|
|
58
|
+
error?: string;
|
|
59
|
+
error_description?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Keycloak refused the refresh token itself (revoked, expired, session gone)
|
|
64
|
+
* or the client: the stored tokens are dead. Anything else, a 5xx while
|
|
65
|
+
* Keycloak restarts or a dropped connection, leaves them worth retrying.
|
|
66
|
+
*/
|
|
67
|
+
function refreshRefused(error: unknown): boolean {
|
|
68
|
+
if (!(error instanceof KeycloakWalletError)) return false;
|
|
69
|
+
return error.status === 401 || (error.status === 400 && error.error === "invalid_grant");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Keycloak's answer when the credential is good but required actions are
|
|
74
|
+
* pending: 400 invalid_grant "Account is not fully set up". Older releases
|
|
75
|
+
* and some proxies surface the event name, resolve_required_actions, instead.
|
|
76
|
+
*/
|
|
77
|
+
function profileIncomplete(status: number, data: TokenResponse): boolean {
|
|
78
|
+
if (data.error === "resolve_required_actions") return true;
|
|
79
|
+
return (
|
|
80
|
+
status === 400 &&
|
|
81
|
+
data.error === "invalid_grant" &&
|
|
82
|
+
/not fully set up/i.test(data.error_description ?? "")
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createDirectGrant(kc: KeycloakWallet, opts: DirectGrantOptions): DirectGrant {
|
|
87
|
+
const now = opts.now ?? Date.now;
|
|
88
|
+
const scope = Array.isArray(opts.scope) ? opts.scope.join(" ") : (opts.scope ?? "openid");
|
|
89
|
+
const listeners = new Set<(state: DirectGrantState) => void>();
|
|
90
|
+
let state: DirectGrantState = { tokens: null, error: null };
|
|
91
|
+
let refreshing: Promise<KeycloakTokens> | undefined;
|
|
92
|
+
|
|
93
|
+
const setState = (next: DirectGrantState) => {
|
|
94
|
+
state = next;
|
|
95
|
+
for (const listener of listeners) listener(state);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const form = (params: Record<string, string>) =>
|
|
99
|
+
new URLSearchParams({
|
|
100
|
+
client_id: opts.clientId,
|
|
101
|
+
...params,
|
|
102
|
+
...(opts.clientSecret ? { client_secret: opts.clientSecret } : {}),
|
|
103
|
+
}).toString();
|
|
104
|
+
|
|
105
|
+
const tokenRequest = async (params: Record<string, string>): Promise<KeycloakTokens> => {
|
|
106
|
+
const res = await kc.request(kc.tokenUrl, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
109
|
+
body: form(params),
|
|
110
|
+
});
|
|
111
|
+
const data = (await res.json().catch(() => ({}))) as TokenResponse;
|
|
112
|
+
if (!res.ok || !data.access_token) {
|
|
113
|
+
const Refusal = profileIncomplete(res.status, data)
|
|
114
|
+
? ProfileIncompleteError
|
|
115
|
+
: KeycloakWalletError;
|
|
116
|
+
throw new Refusal(
|
|
117
|
+
data.error_description || data.error || `Keycloak token request failed (${res.status})`,
|
|
118
|
+
res.status,
|
|
119
|
+
{ error: data.error, errorDescription: data.error_description },
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const issuedAt = now();
|
|
123
|
+
return {
|
|
124
|
+
accessToken: data.access_token,
|
|
125
|
+
refreshToken: data.refresh_token,
|
|
126
|
+
idToken: data.id_token,
|
|
127
|
+
tokenType: data.token_type ?? "Bearer",
|
|
128
|
+
scope: data.scope,
|
|
129
|
+
expiresAt: issuedAt + (data.expires_in ?? 0) * 1000,
|
|
130
|
+
refreshExpiresAt: data.refresh_expires_in
|
|
131
|
+
? issuedAt + data.refresh_expires_in * 1000
|
|
132
|
+
: undefined,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const grant: DirectGrant = {
|
|
137
|
+
get state() {
|
|
138
|
+
return state;
|
|
139
|
+
},
|
|
140
|
+
get tokens() {
|
|
141
|
+
return state.tokens;
|
|
142
|
+
},
|
|
143
|
+
async login({ username, password }) {
|
|
144
|
+
try {
|
|
145
|
+
const tokens = await tokenRequest({ grant_type: "password", username, password, scope });
|
|
146
|
+
setState({ tokens, error: null });
|
|
147
|
+
return tokens;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
setState({ tokens: state.tokens, error: error as Error });
|
|
150
|
+
throw error;
|
|
151
|
+
} finally {
|
|
152
|
+
kc.resetNonce();
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
onSign: async (credentials) => {
|
|
156
|
+
await grant.login(credentials);
|
|
157
|
+
return true;
|
|
158
|
+
},
|
|
159
|
+
refresh(refreshToken) {
|
|
160
|
+
const token = refreshToken ?? state.tokens?.refreshToken;
|
|
161
|
+
if (!token) return Promise.reject(new Error("no refresh token"));
|
|
162
|
+
if (!refreshing) {
|
|
163
|
+
const startedWith = state.tokens;
|
|
164
|
+
const superseded = (error: Error) => {
|
|
165
|
+
if (state.tokens) return state.tokens;
|
|
166
|
+
throw error;
|
|
167
|
+
};
|
|
168
|
+
refreshing = tokenRequest({ grant_type: "refresh_token", refresh_token: token })
|
|
169
|
+
.then(
|
|
170
|
+
(tokens) => {
|
|
171
|
+
if (state.tokens !== startedWith) {
|
|
172
|
+
return superseded(new Error("signed out while the refresh was in flight"));
|
|
173
|
+
}
|
|
174
|
+
setState({ tokens, error: null });
|
|
175
|
+
return tokens;
|
|
176
|
+
},
|
|
177
|
+
(error: Error) => {
|
|
178
|
+
if (state.tokens !== startedWith) return superseded(error);
|
|
179
|
+
setState({ tokens: refreshRefused(error) ? null : state.tokens, error });
|
|
180
|
+
throw error;
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
.finally(() => {
|
|
184
|
+
refreshing = undefined;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return refreshing;
|
|
188
|
+
},
|
|
189
|
+
async getAccessToken({ minValiditySeconds = 30 } = {}) {
|
|
190
|
+
const tokens = state.tokens;
|
|
191
|
+
if (!tokens) return null;
|
|
192
|
+
if (tokens.expiresAt - minValiditySeconds * 1000 > now()) return tokens.accessToken;
|
|
193
|
+
if (!tokens.refreshToken) return null;
|
|
194
|
+
return (await grant.refresh()).accessToken;
|
|
195
|
+
},
|
|
196
|
+
async logout() {
|
|
197
|
+
const token = state.tokens?.refreshToken;
|
|
198
|
+
setState({ tokens: null, error: null });
|
|
199
|
+
if (!token) return;
|
|
200
|
+
await kc.request(`${kc.realmUrl}/protocol/openid-connect/logout`, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
203
|
+
body: form({ refresh_token: token }),
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
subscribe(listener) {
|
|
207
|
+
listeners.add(listener);
|
|
208
|
+
return () => {
|
|
209
|
+
listeners.delete(listener);
|
|
210
|
+
};
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
return grant;
|
|
214
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export {
|
|
2
|
+
keycloakWallet,
|
|
3
|
+
deriveKeycloakFromLocation,
|
|
4
|
+
toCredentials,
|
|
5
|
+
pkceChallenge,
|
|
6
|
+
type BrowserLogin,
|
|
7
|
+
type BrowserLoginUrlOptions,
|
|
8
|
+
type KeycloakWallet,
|
|
9
|
+
type KeycloakWalletOptions,
|
|
10
|
+
} from "./keycloakWallet";
|
|
11
|
+
export { browserFlow, type BrowserFlowOptions } from "./browserFlow";
|
|
12
|
+
export {
|
|
13
|
+
createDirectGrant,
|
|
14
|
+
type DirectGrant,
|
|
15
|
+
type DirectGrantOptions,
|
|
16
|
+
type DirectGrantState,
|
|
17
|
+
} from "./directGrant";
|
|
18
|
+
export {
|
|
19
|
+
createWalletRegistration,
|
|
20
|
+
type RegisterWalletOptions,
|
|
21
|
+
type WalletRegistration,
|
|
22
|
+
} from "./registerWallet";
|
|
23
|
+
export {
|
|
24
|
+
useDirectLogin,
|
|
25
|
+
type DirectLoginStatus,
|
|
26
|
+
type UseDirectLoginResult,
|
|
27
|
+
} from "./useDirectLogin";
|
|
28
|
+
export {
|
|
29
|
+
useWalletLogin,
|
|
30
|
+
type UseWalletLoginOptions,
|
|
31
|
+
type UseWalletLoginResult,
|
|
32
|
+
} from "./useWalletLogin";
|
|
33
|
+
export { KeycloakWalletLogin, type KeycloakWalletLoginProps } from "./KeycloakWalletLogin";
|
|
34
|
+
export { mount, type MountedKeycloakWalletLogin } from "./mount";
|
|
35
|
+
export { hasUserActivation, isMobileBrowser } from "./device";
|
|
36
|
+
export {
|
|
37
|
+
KeycloakWalletError,
|
|
38
|
+
ProfileIncompleteError,
|
|
39
|
+
type KeycloakTokens,
|
|
40
|
+
type KeycloakWalletResource,
|
|
41
|
+
type OnSign,
|
|
42
|
+
type WalletSignIn,
|
|
43
|
+
} from "./types";
|
|
44
|
+
export { StatusState } from "../connectkit/siwe/SIWEContext";
|