diff --git a/src/entries/background/extension-id.ts b/src/entries/background/extension-id.ts index 7a1a029..24b5d0a 100644 --- a/src/entries/background/extension-id.ts +++ b/src/entries/background/extension-id.ts @@ -1,6 +1,6 @@ import { getMessenger } from '~/messengers' export function setupExtensionId() { - const messenger = getMessenger('background:inpage') + const messenger = getMessenger('background:contentScript') messenger.reply('extensionId', async () => chrome.runtime.id) } diff --git a/src/entries/inpage/injectProvider.ts b/src/entries/inpage/injectProvider.ts index 6c47d98..b8737f2 100644 --- a/src/entries/inpage/injectProvider.ts +++ b/src/entries/inpage/injectProvider.ts @@ -10,7 +10,8 @@ const walletMessenger = getMessenger('wallet:inpage') export function injectProvider() { const provider = getProvider({ host: window.location.host, - messenger: backgroundMessenger, + eventMessenger: [walletMessenger, backgroundMessenger], + requestMessenger: backgroundMessenger, }) // Inject provider directly onto window diff --git a/src/messengers/transports/bridge.ts b/src/messengers/transports/bridge.ts index 405825b..727adb9 100644 --- a/src/messengers/transports/bridge.ts +++ b/src/messengers/transports/bridge.ts @@ -17,29 +17,35 @@ export const createBridgeTransport = ( available: transport.available, connection, async send(topic, payload, { id } = {}) { - return transport.send(topic, payload, { id }) + return transport.send(topic, payload, { connection, id }) }, reply(topic, callback) { - return transport.reply(topic, callback) + return transport.reply(topic, callback, { connection }) }, }) export function setupBridgeTransportRelay() { // inpage -> content script -> background - windowTransport.reply('*', async (payload, { topic, id }) => { + windowTransport.reply('*', async (payload, { connection, topic, id }) => { if (!topic) return const topic_ = topic.replace('> ', '') - const response = await tabTransport.send(topic_, payload, { id }) + const response = await tabTransport.send(topic_, payload, { + id, + connection, + }) return response }) // background -> content script -> inpage - tabTransport.reply('*', async (payload, { topic, id }) => { + tabTransport.reply('*', async (payload, { connection, topic, id }) => { if (!topic) return const topic_: string = topic.replace('> ', '') - const response = await windowTransport.send(topic_, payload, { id }) + const response = await windowTransport.send(topic_, payload, { + id, + connection, + }) return response }) } diff --git a/src/messengers/transports/extension.ts b/src/messengers/transports/extension.ts index 9c62fb5..aade350 100644 --- a/src/messengers/transports/extension.ts +++ b/src/messengers/transports/extension.ts @@ -18,7 +18,10 @@ export const createExtensionTransport = ( async send( topic: string, payload: TPayload, - { id }: { id?: number | string } = {}, + { + connection: connection_, + id, + }: { connection?: string; id?: number | string } = {}, ) { return new Promise((resolve, reject) => { const listener = (message: ReplyMessage) => { @@ -33,6 +36,7 @@ export const createExtensionTransport = ( chrome.runtime.onMessage.addListener(listener) chrome.runtime.sendMessage({ + connection: connection_ || connection, topic: `> ${topic}`, payload, id, @@ -47,17 +51,20 @@ export const createExtensionTransport = ( message: SendMessage, sender: chrome.runtime.MessageSender, ) => { + if (topic !== '*' && message.connection !== connection) return if (!isValidSend({ message, topic })) return const repliedTopic = message.topic.replace('>', '<') callback(message.payload, { + connection: message.connection, id: message.id, sender, topic: message.topic, }) .then((response) => chrome.runtime.sendMessage({ + connection: message.connection, topic: repliedTopic, payload: { response }, id: message.id, @@ -71,6 +78,7 @@ export const createExtensionTransport = ( error[key] = (error_)[key] } chrome.runtime.sendMessage({ + connection: message.connection, topic: repliedTopic, payload: { error }, id: message.id, diff --git a/src/messengers/transports/tab.ts b/src/messengers/transports/tab.ts index 86eca48..fe99602 100644 --- a/src/messengers/transports/tab.ts +++ b/src/messengers/transports/tab.ts @@ -12,7 +12,7 @@ export const createTabTransport = ( typeof chrome !== 'undefined' && chrome.runtime?.id && chrome.tabs, ), connection, - async send(topic, payload, { id } = {}) { + async send(topic, payload, { connection: connection_, id } = {}) { return new Promise((resolve, reject) => { const listener = (message: ReplyMessage) => { if (!isValidReply({ id, message, topic })) return @@ -26,21 +26,30 @@ export const createTabTransport = ( chrome.runtime.onMessage.addListener(listener) getActiveTabs().then(([tab]) => { - sendMessage({ topic: `> ${topic}`, payload, id }, { tabId: tab?.id }) + sendMessage( + { + connection: connection_ || connection, + topic: `> ${topic}`, + payload, + id, + }, + { tabId: tab?.id }, + ) }) }) }, - reply(topic, callback) { + reply(topic, callback, options) { const listener = ( message: SendMessage, sender: chrome.runtime.MessageSender, ) => { - if (!isValidSend({ message, topic })) return + if (!isValidSend({ message, options, topic })) return const repliedTopic = message.topic.replace('>', '<') getActiveTabs().then(([tab]) => { callback(message.payload, { + connection: message.connection, id: message.id, sender, topic: message.topic, @@ -48,6 +57,7 @@ export const createTabTransport = ( .then((response) => sendMessage( { + connection: message.connection, topic: repliedTopic, payload: { response }, id: message.id, @@ -62,6 +72,7 @@ export const createTabTransport = ( } sendMessage( { + connection: message.connection, topic: repliedTopic, payload: { error }, id: message.id, diff --git a/src/messengers/transports/types.ts b/src/messengers/transports/types.ts index 6011c9a..d2a0698 100644 --- a/src/messengers/transports/types.ts +++ b/src/messengers/transports/types.ts @@ -1,4 +1,6 @@ export type CallbackOptions = { + /** The connection of the messenger. */ + connection: string /** The sender of the message. */ sender: chrome.runtime.MessageSender /** The topic provided. */ @@ -31,6 +33,8 @@ export type Transport< /** The payload to send to the `reply` handler. */ payload: TSchema extends TransportSchema ? TSchema[TTopic][0] : unknown, options?: { + /** The connection of the messenger. */ + connection?: string /** Identify & scope the request via an ID. */ id?: string | number }, @@ -43,16 +47,22 @@ export type Transport< TSchema extends TransportSchema ? TSchema[TTopic][0] : unknown, TSchema extends TransportSchema ? TSchema[TTopic][1] : unknown >, + options?: { + /** The connection of the messenger. */ + connection?: string + }, ) => () => void } export type SendMessage = { + connection: string topic: string payload: TPayload id?: number | string } export type ReplyMessage = { + connection: string topic: string id: number | string payload: { response: TResponse; error: Error } diff --git a/src/messengers/transports/utils.ts b/src/messengers/transports/utils.ts index b4ce115..6ae13f3 100644 --- a/src/messengers/transports/utils.ts +++ b/src/messengers/transports/utils.ts @@ -18,11 +18,19 @@ export function isValidReply({ export function isValidSend({ topic, message, + options, }: { topic: string message: SendMessage + options?: { connection?: string } }) { if (!message.topic) return false + if ( + options?.connection && + message.connection && + message.connection !== options.connection + ) + return false if (topic !== '*' && message.topic !== `> ${topic}`) return false if (topic === '*' && message.topic.startsWith('<')) return false return true diff --git a/src/messengers/transports/window.ts b/src/messengers/transports/window.ts index c132f1c..2cbb362 100644 --- a/src/messengers/transports/window.ts +++ b/src/messengers/transports/window.ts @@ -10,8 +10,16 @@ export const createWindowTransport = ( ): Transport => ({ available: typeof window !== 'undefined', connection, - async send(topic, payload, { id } = {}) { - window.postMessage({ topic: `> ${topic}`, payload, id }, '*') + async send(topic, payload, { connection: connection_, id } = {}) { + window.postMessage( + { + connection: connection_ || connection, + topic: `> ${topic}`, + payload, + id, + }, + '*', + ) return new Promise((resolve, reject) => { const listener = (event: MessageEvent) => { if (!isValidReply({ id, message: event.data, topic })) return @@ -26,9 +34,9 @@ export const createWindowTransport = ( window.addEventListener('message', listener) }) }, - reply(topic, callback) { + reply(topic, callback, options) { const listener = async (event: MessageEvent>) => { - if (!isValidSend({ message: event.data, topic })) return + if (!isValidSend({ message: event.data, options, topic })) return const sender = event.source if (sender !== window) return @@ -37,6 +45,7 @@ export const createWindowTransport = ( let response try { response = await callback(event.data.payload, { + connection: event.data.connection, topic: event.data.topic, sender, id: event.data.id, @@ -47,6 +56,7 @@ export const createWindowTransport = ( const repliedTopic = event.data.topic.replace('>', '<') window.postMessage({ + connection: event.data.connection, topic: repliedTopic, payload: { error, response }, id: event.data.id, diff --git a/src/provider.ts b/src/provider.ts index 6f663a2..180c5a6 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -12,12 +12,23 @@ import type { RpcRequest } from '~/messengers/schema' const providerCache = new Map() export function getProvider({ host, - messenger, + eventMessenger: eventMessenger_, + requestMessenger, rpcUrl, -}: { host?: string; messenger: Messenger; rpcUrl?: string }): EIP1193Provider { - const cachedProvider = rpcUrl - ? providerCache.get(`${messenger.connection}.${rpcUrl}`) - : undefined +}: { + host?: string + eventMessenger: Messenger | Messenger[] + requestMessenger: Messenger + rpcUrl?: string +}): EIP1193Provider { + const eventMessenger = Array.isArray(eventMessenger_) + ? eventMessenger_ + : [eventMessenger_] + + const cacheKey = `${requestMessenger.connection}.${eventMessenger + .map((m) => m.connection) + .join('.')}.${rpcUrl}` + const cachedProvider = rpcUrl ? providerCache.get(cacheKey) : undefined if (cachedProvider) return cachedProvider const emitter = new EventEmitter() @@ -25,33 +36,36 @@ export function getProvider({ // Workaround for id conflicts between inpage & wallet. let _id = Math.floor(Math.random() * 10000) - messenger.reply('accountsChanged', async ({ accounts, sessions }) => { - if (host && !sessions.find((session) => session.host === host)) return - emitter.emit('accountsChanged', accounts) - }) + function listen(messenger: Messenger) { + messenger.reply('accountsChanged', async ({ accounts, sessions }) => { + if (host && !sessions.find((session) => session.host === host)) return + emitter.emit('accountsChanged', accounts) + }) - messenger.reply('chainChanged', async ({ chainId, sessions }) => { - if (host && !sessions.find((session) => session.host === host)) return - emitter.emit('chainChanged', chainId) - }) + messenger.reply('chainChanged', async ({ chainId, sessions }) => { + if (host && !sessions.find((session) => session.host === host)) return + emitter.emit('chainChanged', chainId) + }) - messenger.reply('connect', async ({ chainId }) => { - emitter.emit('connect', { chainId }) - }) + messenger.reply('connect', async ({ chainId }) => { + emitter.emit('connect', { chainId }) + }) - messenger.reply('disconnect', async () => { - emitter.emit( - 'disconnect', - new ProviderDisconnectedError(new Error('Disconnected.')), - ) - }) + messenger.reply('disconnect', async () => { + emitter.emit( + 'disconnect', + new ProviderDisconnectedError(new Error('Disconnected.')), + ) + }) + } + eventMessenger.forEach(listen) const provider: EIP1193Provider = { on: emitter.on.bind(emitter), removeListener: emitter.removeListener.bind(emitter), async request({ method, params }) { const id = _id++ - const { result, error, ...response } = await messenger.send( + const { result, error, ...response } = await requestMessenger.send( 'request', { request: { @@ -74,6 +88,6 @@ export function getProvider({ return result }, } - if (rpcUrl) providerCache.set(`${messenger.connection}.${rpcUrl}`, provider) + if (rpcUrl) providerCache.set(cacheKey, provider) return provider } diff --git a/src/viem.ts b/src/viem.ts index e2c8801..be6610b 100644 --- a/src/viem.ts +++ b/src/viem.ts @@ -57,7 +57,8 @@ export function getClient({ rpcUrl }: { rpcUrl: string }): Client { chain: buildChain({ rpcUrl }), transport: custom( getProvider({ - messenger, + eventMessenger: messenger, + requestMessenger: messenger, rpcUrl, }), { retryCount: 0 },