75 lines
2.8 KiB
TypeScript
75 lines
2.8 KiB
TypeScript
export { bodyHash, buildSignMessage, type SignedEnvelope } from '@trumpsignal/contracts/signing'
|
|
import { buildSignMessage, type SignedEnvelope } from '@trumpsignal/contracts/signing'
|
|
|
|
/**
|
|
* Produce {timestamp, signature} for a given action + optional body.
|
|
* Caller supplies wagmi's `signMessageAsync`.
|
|
*/
|
|
/**
|
|
* Get-or-create a cached "view" envelope for a wallet. Reused across page loads
|
|
* within a short window below the server's 5-minute skew allowance.
|
|
* Backend's replay-guard is disabled for the view action so the same sig can
|
|
* be used multiple times while it remains fresh.
|
|
*/
|
|
const VIEW_TTL_MS = 4 * 60 * 1000 // 4 min — stays safely inside backend's 5 min skew window
|
|
|
|
/**
|
|
* Read-only: returns a cached, not-yet-expired view envelope if one exists.
|
|
* Never signs — never triggers a wallet popup. Returns null if no fresh cache.
|
|
*/
|
|
export function getCachedViewEnvelope(action: string, wallet: string): SignedEnvelope | null {
|
|
if (typeof window === 'undefined') return null
|
|
const cacheKey = `ts:view:${action}:${wallet.toLowerCase()}`
|
|
const raw = sessionStorage.getItem(cacheKey)
|
|
if (!raw) return null
|
|
try {
|
|
const env = JSON.parse(raw) as SignedEnvelope
|
|
if (Date.now() - env.timestamp < VIEW_TTL_MS) return env
|
|
sessionStorage.removeItem(cacheKey)
|
|
} catch (err) {
|
|
console.warn('[signedRequest] corrupt cached envelope, ignoring', err)
|
|
sessionStorage.removeItem(cacheKey)
|
|
}
|
|
return null
|
|
}
|
|
|
|
export async function getOrCreateViewEnvelope(params: {
|
|
action: string
|
|
wallet: string
|
|
signMessageAsync: (args: { message: string }) => Promise<string>
|
|
}): Promise<SignedEnvelope> {
|
|
if (typeof window === 'undefined') throw new Error('view envelope requires browser')
|
|
const wallet = params.wallet.toLowerCase()
|
|
const cacheKey = `ts:view:${params.action}:${wallet}`
|
|
const raw = sessionStorage.getItem(cacheKey)
|
|
if (raw) {
|
|
try {
|
|
const env = JSON.parse(raw) as SignedEnvelope
|
|
if (Date.now() - env.timestamp < VIEW_TTL_MS) return env
|
|
} catch (err) {
|
|
console.warn('[signedRequest] corrupt cached envelope, ignoring', err)
|
|
sessionStorage.removeItem(cacheKey)
|
|
}
|
|
}
|
|
const env = await signRequest({
|
|
action: params.action,
|
|
wallet,
|
|
body: null,
|
|
signMessageAsync: params.signMessageAsync,
|
|
})
|
|
sessionStorage.setItem(cacheKey, JSON.stringify(env))
|
|
return env
|
|
}
|
|
|
|
export async function signRequest(params: {
|
|
action: string
|
|
wallet: string
|
|
body: unknown | null
|
|
signMessageAsync: (args: { message: string }) => Promise<string>
|
|
}): Promise<SignedEnvelope> {
|
|
const timestamp = Date.now()
|
|
const message = await buildSignMessage(params.action, params.wallet, timestamp, params.body)
|
|
const signature = await params.signMessageAsync({ message })
|
|
return { wallet: params.wallet.toLowerCase(), timestamp, signature }
|
|
}
|