《浏览器插件钱包(二) - 处理DApp请求》

浏览器插件钱包(二) - 处理DApp请求

前言:紧接上一篇,我们已经能够在Dapp上检测到我们的钱包插件了,那么接下来我们就需要处理来自于DApp的请求,以便更丰富的交互。

本系列将分为多次文章展示,敬请期待,也可以到github 仓库地址查看最新的代码更新。

请求失败

在上一篇文章的代码运行下,刷新网页会得到一个报错 ,如图:

并且点击connect按钮会报错,如图:

这是因为我们没有提供一个可以使用的provider给DApp,本篇文章主要就是解释如何提供provider。

架构图

首先我们需要理解chrome extension的content.js和注入到页面的injected.js以及background.js之间的关系:

  1. 首先我们需要三个主体:Content, injected provider, Background
  2. 使用Content将injected provider注入到页面声明eip6963的provider,并且监听provider发出的事件
  3. Content将监听的事件发送到Background进行处理,然后将处理的结果发送给injected provider

完成这个图上的消息传递是整个系统中的基础,完成之后我们就能够打通Dapp与插件钱包的消息传递以及交互。

主要代码片段

ContentProvider.ts

ts 复制代码
export class ContentProvider {

    constructor() {
        // 监听来自页面的消息
        window.addEventListener('message', this.handlePageMessage);

        // 监听来自扩展的消息
        chrome.runtime.onMessage.addListener(this.handleExtensionMessage);
    }

    // 处理来自页面的消息
    private handlePageMessage = (event: MessageEvent) => {
        // 忽略来自其他源的消息
        if (event.source !== window) return;

        const data = event.data;

        // 检查消息是否来自我们的注入脚本
        if (data && typeof data === 'object' && data.type === `${MESSAGE_PREFIX}${MessageType.REQUEST}`) {
            // 解析请求
            const request = data.payload as JsonRpcRequest;

            // 发送请求到扩展的后台脚本
            chrome.runtime.sendMessage(
                { type: `${MESSAGE_PREFIX}${MessageType.REQUEST}`, payload: request },
                (response: JsonRpcResponse) => {
                    // 将响应发送回页面
                    window.postMessage(
                        {
                            type: `${MESSAGE_PREFIX}${MessageType.RESPONSE}`,
                            payload: response
                        },
                        '*'
                    );
                }
            );
        }
    };

    // 处理来自扩展的消息
    private handleExtensionMessage = (
        message: any,
        sender: chrome.runtime.MessageSender,
        sendResponse: (response: any) => void
    ) => {
        // 检查消息类型
        if (message && typeof message === 'object' && message.type === `${MESSAGE_PREFIX}${MessageType.RESPONSE}`) {
            // 将响应发送回页面
            window.postMessage(message, '*');
            return true; // 保持消息通道打开以进行异步响应
        }
        return false;
    };

    // 清理方法
    public cleanup() {
        window.removeEventListener('message', this.handlePageMessage);
        // 注意:Chrome 扩展 API 不提供 removeListener 的标准方式
    }
}

injectedProvider.ts

ts 复制代码
class PageProvider {
    private requestIdCounter = 0;
    private pendingRequests = new Map<number | string, (response: any) => void>();

    constructor() {
        // 添加消息监听器,用于接收来自 content script 的响应
        window.addEventListener('message', this.handleContentScriptMessage);
    }

    // 处理来自 content script 的消息
    private handleContentScriptMessage = (event: MessageEvent) => {
        // 安全检查:确保消息来自当前窗口
        if (event.source !== window) return;

        const data = event.data;

        // 验证消息格式
        if (
            data &&
            typeof data === 'object' &&
            data.type === `${MESSAGE_PREFIX}${MessageType.RESPONSE}` &&
            data.payload
        ) {
            const response = data.payload;
            const callback = this.pendingRequests.get(response.id);

            if (callback) {
                callback(response);
                this.pendingRequests.delete(response.id);
            }
        }
    };

    request = (args: any): Promise<any> => {
        return new Promise((resolve, reject) => {
            const id = this.requestIdCounter++;

            // 创建 JSON-RPC 请求
            const request = {
                id,
                jsonrpc: '2.0',
                method: args.method,
                params: args.params
            };

            // 存储回调
            this.pendingRequests.set(id, (response) => {
                if (response.error) {
                    reject(new Error(response.error.message));
                } else {
                    resolve(response.result);
                }
            });

            // 发送消息到 content script
            window.postMessage(
                {
                    type: `${MESSAGE_PREFIX}${MessageType.REQUEST}`,
                    payload: request
                },
                '*'
            );
        });
    }

    // 当 PageProvider 销毁时清理监听器
    disconnect() {
        window.removeEventListener('message', this.handleContentScriptMessage);
    }
}

content.ts

ts 复制代码
import { defineContentScript } from 'wxt/sandbox';
import contentProvider from './provider';

export default defineContentScript({
    matches: ['<all_urls>'],
    runAt: 'document_start',
    main() {
        // 注册 content provider
        contentProvider;

        // Inject the script into the page
        const injectScript = () => {
            try {
                const script = document.createElement('script');
                script.src = chrome.runtime.getURL('injected.js');
                script.onload = () => script.remove();
                (document.head || document.documentElement).appendChild(script);
            } catch (error) {
                console.error('Error injecting script:', error);
            }
        };

        // Execute injection when DOM is ready
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', injectScript);
        } else {
            injectScript();
        }
    },
}); 

background.ts

ts 复制代码
import { defineBackground } from 'wxt/sandbox';
import { MESSAGE_PREFIX, MessageType } from '../../constants';

// 处理 RPC 请求
async function handleRpcRequest(request: any) {
    try {
        // 根据请求的方法执行相应的操作
        switch (request.method) {
            case 'eth_accounts':
                return ['0x0000000000000000000000000000000000000000'];

            case 'eth_chainId':
                return '0x1'; // Ethereum Mainnet

            case 'net_version':
                return '1'; // Ethereum Mainnet

            case 'eth_requestAccounts':
                // 这里应该触发钱包 UI 来请求用户授权
                // 现在我们简单返回一个固定地址
                return ['0x0000000000000000000000000000000000000000'];

            default:
                throw new Error(`Method not supported: ${request.method}`);
        }
    } catch (error) {
        console.error('RPC request error:', error);
        throw error;
    }
}

export default defineBackground({
    main() {
        // 监听来自 content script 的消息
        chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
            // 检查消息是否来自我们的 content script
            if (
                message &&
                typeof message === 'object' &&
                message.type === `${MESSAGE_PREFIX}${MessageType.REQUEST}`
            ) {
                const request = message.payload;

                // 处理 RPC 请求
                handleRpcRequest(request)
                    .then(result => {
                        // 发送成功响应
                        sendResponse({
                            id: request.id,
                            jsonrpc: request.jsonrpc,
                            result
                        });
                    })
                    .catch(error => {
                        // 发送错误响应
                        sendResponse({
                            id: request.id,
                            jsonrpc: request.jsonrpc,
                            error: {
                                code: -32603, // Internal error
                                message: error.message || 'Internal error'
                            }
                        });
                    });

                // 返回 true 表示我们将异步发送响应
                return true;
            }
        });
    },
}); 

完成之后我们就能与Dapp进行交互了,也不会报错了,只会报错我们目前不支持的方法,因为目前我们还没有一个钱包所以,下一篇文章《浏览器插件钱包(三) - 创建钱包》敬请期待。

下期预告

《浏览器插件钱包(三) - 创建钱包》

求打赏

文章总结不易,欢迎各位打赏,打赏越快,更新越快,谢谢~

相关推荐
兰德里的折磨5508 分钟前
基于若依和elementui实现文件上传(导入Excel表)
前端·elementui·excel
喝拿铁写前端11 分钟前
一个列表页面,初级中级高级前端之间的鸿沟就显出来了
前端·架构·代码规范
人类群星闪耀时17 分钟前
区块链点燃游戏行业新未来——技术变革与实践指南
游戏·区块链
magic 2451 小时前
ES6变量声明:let、var、const全面解析
前端·javascript·ecmascript·es6
M_chen_M1 小时前
es6学习02-let命令和const命令
前端·学习·es6
好_快1 小时前
Lodash源码阅读-dropWhile
前端·javascript·源码阅读
M_chen_M1 小时前
JS6(ES6)学习01-babel转码器
前端·学习·es6
好_快1 小时前
Lodash源码阅读-dropRightWhile
前端·javascript·源码阅读
二川bro2 小时前
Vue 项目中 package.json 文件的深度解析
前端·vue.js·json
寰宇视讯2 小时前
铼赛智能Edge mini斩获2025法国设计大奖 | 重新定义数字化齿科美学
前端·数据库·edge