fix: carry EIP-1193 error codes through to the page (closes #274)
All checks were successful
check / check (push) Successful in 30s

src/content/inpage.js rebuilt every failure as `new Error(error.message)`,
so the `code` the background produced and the content script relayed intact
was dropped in the last hop. A dApp checking `err.code === 4001` — the
standard way to tell "the user said no" from "something broke" — saw
`undefined`, which makes a wallet the user deliberately declined
indistinguishable from a wallet that failed.

The provider now rejects with an EIP-1193 `ProviderRpcError` carrying the
`code` and, where the boundary sent one, `data`. A class rather than
properties attached to an `Error`: the object crosses no boundary after
construction — it is built in the page's own realm and handed straight to
the caller's catch — so the prototype survives and `name` is a stable thing
for a dApp to see.

Whatever code arrived is passed through verbatim rather than matched
against a list. The background emits 4001, 4100 and 4902 on the RPC path
today; a code added later must reach the page without editing the provider.
An error the background sent with no code stays a plain `Error` with no
`code` property at all — a `ProviderRpcError` whose `code` is `undefined`
would advertise a conformance it does not have. `message` is unchanged in
every case, and nothing the background produces changes.

Every entry point the provider exposes funnels through the one response
listener, so `request`, `enable`, `send` and `sendAsync` are all covered;
tests/inpageErrors.test.js loads the real inpage.js against a stub window
and asserts each of them, both codes and the untouched messages.

The e2e probe added for #183, which printed the missing code on all four
rejected flows rather than asserting it, now requires code 4001 on the
page's Error as well as on the wire. Without the provider change it fails
on all four with "reached the page as an error with no code property at
all".
This commit is contained in:
2026-08-12 11:34:22 +00:00
parent c755a5e944
commit 9317d4386e
5 changed files with 394 additions and 18 deletions

View File

@@ -86,9 +86,11 @@ const DAPP_URL = DAPP_ORIGIN + "/";
// never drive the popup that has to settle it; start() files the promise
// under a key and settle() collects it once the prompt has been dealt with.
//
// The rejection branch records `code` as it arrives. EIP-1193 says a user
// rejection is a ProviderRpcError carrying code 4001; what the page can
// actually see is recorded here rather than assumed, and asserted in run.js.
// The rejection branch records the whole observable shape of the error as it
// arrives — name, message, and whether a `code` is present at all as distinct
// from its value. EIP-1193 says a user rejection is a ProviderRpcError
// carrying code 4001; what the page can actually see is recorded here rather
// than assumed, and asserted in run.js.
//
// The message log is the page's half of the boundary observation: every
// AUTISTMASK_* message that crosses between this page and the content
@@ -120,6 +122,7 @@ const DAPP_HTML = [
" return {",
" settled: 'rejected',",
" message: String((error && error.message) || error),",
" name: error ? error.name : undefined,",
" hasCode: !!error && 'code' in Object(error),",
" code: error ? error.code : undefined,",
" };",