mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
A wallet whose prompt never answers no longer locks the sign-in: the other ways in stay open while it waits, any other press supersedes it, and after a minute it is let go and said so. signup_submitted is sent once the wallet has signed, not on the press. A second factor's header names the way in that holds it (the wallet, the number), never an address typed beside it. The gate's challenge names hanzo.ai, as IAM v1.34.171 does for a page at one of the client's registered origins. /login still sends no verify method: that needs @hanzo/iam 0.21.38 (loginWithWallet's `method`, hanzo-js/iam branch wallet-method).
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
/**
|
|
* A wallet, as the sign-in panel and Settings see it. The sign-in itself is
|
|
* @hanzo/iam's `loginWithWallet`: IAM's challenge, the EIP-4361 text rendered
|
|
* from it, `personal_sign` by the browser's own wallet, and IAM's verify, which
|
|
* answers as its login does. This file answers the three questions the page asks
|
|
* around that call, and is pure, so each is tested without a browser.
|
|
*/
|
|
|
|
/** The EIP-1193 request surface `loginWithWallet` signs through. */
|
|
type Provider = { request?: unknown }
|
|
|
|
/**
|
|
* Whether this browser holds an Ethereum wallet the sign-in can ask: the injected
|
|
* EIP-1193 provider (`window.ethereum`) `loginWithWallet` signs with when it is
|
|
* handed none. No wallet, no button: one that could not finish is worse than none.
|
|
*/
|
|
export function injected(w: { ethereum?: Provider } | undefined = typeof window === 'undefined' ? undefined : (window as { ethereum?: Provider })): boolean {
|
|
return typeof w?.ethereum?.request === 'function'
|
|
}
|
|
|
|
/**
|
|
* Whether the wallet declined to sign: the person said no in its prompt, which
|
|
* EIP-1193 states as error 4001. Said in the page's words, not the wallet's.
|
|
*/
|
|
export function declined(err: unknown): boolean {
|
|
return typeof err === 'object' && err !== null && (err as { code?: unknown }).code === 4001
|
|
}
|
|
|
|
/**
|
|
* How long the page waits on the wallet, in ms: its prompt open, the person
|
|
* unlocking it and reading the sign-in. A wallet that never answers (a prompt
|
|
* that never opened, an extension that hung) is let go after this.
|
|
*/
|
|
export const WAIT = 60_000
|
|
|
|
/** The wallet did not answer within `WAIT`. */
|
|
export class Silent extends Error {
|
|
constructor() {
|
|
super('the wallet did not answer')
|
|
this.name = 'Silent'
|
|
}
|
|
}
|
|
|
|
/** `p`, or a {@link Silent} once `ms` pass without it settling. */
|
|
export function within<T>(p: Promise<T>, ms: number = WAIT): Promise<T> {
|
|
return new Promise<T>((done, fail) => {
|
|
const late = setTimeout(() => fail(new Silent()), ms)
|
|
p.then(
|
|
(v) => {
|
|
clearTimeout(late)
|
|
done(v)
|
|
},
|
|
(err: unknown) => {
|
|
clearTimeout(late)
|
|
fail(err)
|
|
},
|
|
)
|
|
})
|
|
}
|
|
|
|
/** One wallet that signs an account in, as IAM names it: its chain family and its address. */
|
|
export type Wallet = { chain: string; address: string }
|
|
|
|
/**
|
|
* The wallets an access token says sign its account in: IAM's `wallets` claim, in
|
|
* IAM's order. Anything that is not a chain and an address is not one.
|
|
*/
|
|
export function wallets(claims: Record<string, unknown> | null | undefined): Wallet[] {
|
|
const set = claims?.wallets
|
|
if (!Array.isArray(set)) return []
|
|
return set.flatMap((w) => {
|
|
const r = (w ?? {}) as Record<string, unknown>
|
|
return typeof r.chain === 'string' && r.chain && typeof r.address === 'string' && r.address ? [{ chain: r.chain, address: r.address }] : []
|
|
})
|
|
}
|