Files
trumpsignal-monorepo/apps/web/lib/signedRequest.ts
T

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 }
}