Implement EIP-1193 provider for dApp connectivity
All checks were successful
check / check (push) Successful in 13s

Three-part architecture:
- inpage.js: creates window.ethereum in page context with
  request(), on(), send(), sendAsync(), enable() methods.
  Sets isMetaMask=true for compatibility.
- content/index.js: bridge between page and extension via
  postMessage (page<->content) and runtime.sendMessage
  (content<->background).
- background/index.js: handles RPC routing. Proxies read-only
  methods (eth_call, eth_getBalance, etc.) to configured RPC.
  Handles eth_requestAccounts (auto-connect for now),
  wallet_switchEthereumChain (mainnet only), and returns
  informative errors for unimplemented signing methods.

Manifests updated with web_accessible_resources for inpage.js.
Build updated to bundle inpage.js as a separate output file.
This commit is contained in:
2026-02-25 18:36:27 +07:00
parent 98b5eef21b
commit 1c9d5a9f2d
6 changed files with 414 additions and 3 deletions

View File

@@ -1,2 +1,51 @@
// AutistMask content script
// TODO: inject window.ethereum provider into page context
// AutistMask content script — bridges between inpage (window.ethereum)
// and the background service worker via extension messaging.
// Inject the inpage script into the page's JS context
const script = document.createElement("script");
script.src = (typeof browser !== "undefined" ? browser : chrome).runtime.getURL(
"src/content/inpage.js",
);
script.onload = function () {
this.remove();
};
(document.head || document.documentElement).appendChild(script);
// Relay requests from the page to the background script
window.addEventListener("message", (event) => {
if (event.source !== window) return;
if (event.data?.type !== "AUTISTMASK_REQUEST") return;
const { id, method, params } = event.data;
const runtime =
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
runtime.sendMessage(
{ type: "AUTISTMASK_RPC", id, method, params, origin: location.origin },
(response) => {
if (response) {
window.postMessage(
{ type: "AUTISTMASK_RESPONSE", id, ...response },
"*",
);
}
},
);
});
// Listen for events pushed from the background (e.g. accountsChanged)
const runtime =
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
runtime.onMessage.addListener((msg) => {
if (msg.type === "AUTISTMASK_EVENT") {
window.postMessage(
{
type: "AUTISTMASK_EVENT",
eventName: msg.eventName,
data: msg.data,
},
"*",
);
}
});

128
src/content/inpage.js Normal file
View File

@@ -0,0 +1,128 @@
// AutistMask inpage script — injected into the page's JS context.
// Creates window.ethereum (EIP-1193 provider).
(function () {
if (typeof window.ethereum !== "undefined") return;
const CHAIN_ID = "0x1"; // Ethereum mainnet
const listeners = {};
let nextId = 1;
const pending = {};
// Listen for responses from the content script
window.addEventListener("message", (event) => {
if (event.source !== window) return;
if (event.data?.type !== "AUTISTMASK_RESPONSE") return;
const { id, result, error } = event.data;
const p = pending[id];
if (!p) return;
delete pending[id];
if (error) {
p.reject(new Error(error.message || "Request failed"));
} else {
p.resolve(result);
}
});
// Listen for events pushed from the extension
window.addEventListener("message", (event) => {
if (event.source !== window) return;
if (event.data?.type !== "AUTISTMASK_EVENT") return;
const { eventName, data } = event.data;
emit(eventName, data);
});
function emit(eventName, data) {
const cbs = listeners[eventName];
if (!cbs) return;
for (const cb of cbs) {
try {
cb(data);
} catch (e) {
// ignore listener errors
}
}
}
function request(args) {
return new Promise((resolve, reject) => {
const id = nextId++;
pending[id] = { resolve, reject };
window.postMessage(
{ type: "AUTISTMASK_REQUEST", id, ...args },
"*",
);
});
}
const provider = {
isAutistMask: true,
isMetaMask: true, // compatibility — many dApps check this
chainId: CHAIN_ID,
networkVersion: "1",
selectedAddress: null,
request(args) {
return request({ method: args.method, params: args.params || [] });
},
// Legacy methods (still used by some dApps)
enable() {
return this.request({ method: "eth_requestAccounts" });
},
send(methodOrPayload, paramsOrCallback) {
// Handle both send(method, params) and send({method, params})
if (typeof methodOrPayload === "string") {
return this.request({
method: methodOrPayload,
params: paramsOrCallback || [],
});
}
return this.request({
method: methodOrPayload.method,
params: methodOrPayload.params || [],
});
},
sendAsync(payload, callback) {
this.request({
method: payload.method,
params: payload.params || [],
})
.then((result) =>
callback(null, { id: payload.id, jsonrpc: "2.0", result }),
)
.catch((err) => callback(err));
},
on(event, cb) {
if (!listeners[event]) listeners[event] = [];
listeners[event].push(cb);
return this;
},
removeListener(event, cb) {
if (!listeners[event]) return this;
listeners[event] = listeners[event].filter((c) => c !== cb);
return this;
},
removeAllListeners(event) {
if (event) {
delete listeners[event];
} else {
for (const key of Object.keys(listeners)) {
delete listeners[key];
}
}
return this;
},
};
window.ethereum = provider;
// Announce via EIP-6963 (multi-wallet discovery)
window.dispatchEvent(new Event("ethereum#initialized"));
})();