Skip to content

Commit

Permalink
fix: stricter messenger communication
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Oct 4, 2023
1 parent 4e802c2 commit 22755d7
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/entries/background/extension-id.ts
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 2 additions & 1 deletion src/entries/inpage/injectProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 12 additions & 6 deletions src/messengers/transports/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,35 @@ export const createBridgeTransport = <TConnection extends string>(
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
})
}
10 changes: 9 additions & 1 deletion src/messengers/transports/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export const createExtensionTransport = <TConnection extends string>(
async send<TPayload, TResponse>(
topic: string,
payload: TPayload,
{ id }: { id?: number | string } = {},
{
connection: connection_,
id,
}: { connection?: string; id?: number | string } = {},
) {
return new Promise<TResponse>((resolve, reject) => {
const listener = (message: ReplyMessage<TResponse>) => {
Expand All @@ -33,6 +36,7 @@ export const createExtensionTransport = <TConnection extends string>(
chrome.runtime.onMessage.addListener(listener)

chrome.runtime.sendMessage({
connection: connection_ || connection,
topic: `> ${topic}`,
payload,
id,
Expand All @@ -47,17 +51,20 @@ export const createExtensionTransport = <TConnection extends string>(
message: SendMessage<TPayload>,
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,
Expand All @@ -71,6 +78,7 @@ export const createExtensionTransport = <TConnection extends string>(
error[key] = (<Error>error_)[<keyof Error>key]
}
chrome.runtime.sendMessage({
connection: message.connection,
topic: repliedTopic,
payload: { error },
id: message.id,
Expand Down
19 changes: 15 additions & 4 deletions src/messengers/transports/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const createTabTransport = <TConnection extends string>(
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<any>) => {
if (!isValidReply({ id, message, topic })) return
Expand All @@ -26,28 +26,38 @@ export const createTabTransport = <TConnection extends string>(
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<any>,
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,
})
.then((response) =>
sendMessage(
{
connection: message.connection,
topic: repliedTopic,
payload: { response },
id: message.id,
Expand All @@ -62,6 +72,7 @@ export const createTabTransport = <TConnection extends string>(
}
sendMessage(
{
connection: message.connection,
topic: repliedTopic,
payload: { error },
id: message.id,
Expand Down
10 changes: 10 additions & 0 deletions src/messengers/transports/types.ts
Original file line number Diff line number Diff line change
@@ -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. */
Expand Down Expand Up @@ -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
},
Expand All @@ -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<TPayload = unknown> = {
connection: string
topic: string
payload: TPayload
id?: number | string
}

export type ReplyMessage<TResponse = unknown> = {
connection: string
topic: string
id: number | string
payload: { response: TResponse; error: Error }
Expand Down
8 changes: 8 additions & 0 deletions src/messengers/transports/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@ export function isValidReply<TResponse>({
export function isValidSend({
topic,
message,
options,
}: {
topic: string
message: SendMessage<unknown>
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
Expand Down
18 changes: 14 additions & 4 deletions src/messengers/transports/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@ export const createWindowTransport = <TConnection extends string>(
): Transport<TConnection> => ({
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
Expand All @@ -26,9 +34,9 @@ export const createWindowTransport = <TConnection extends string>(
window.addEventListener('message', listener)
})
},
reply(topic, callback) {
reply(topic, callback, options) {
const listener = async (event: MessageEvent<SendMessage<any>>) => {
if (!isValidSend({ message: event.data, topic })) return
if (!isValidSend({ message: event.data, options, topic })) return

const sender = event.source
if (sender !== window) return
Expand All @@ -37,6 +45,7 @@ export const createWindowTransport = <TConnection extends string>(
let response
try {
response = await callback(event.data.payload, {
connection: event.data.connection,
topic: event.data.topic,
sender,
id: event.data.id,
Expand All @@ -47,6 +56,7 @@ export const createWindowTransport = <TConnection extends string>(

const repliedTopic = event.data.topic.replace('>', '<')
window.postMessage({
connection: event.data.connection,
topic: repliedTopic,
payload: { error, response },
id: event.data.id,
Expand Down
62 changes: 38 additions & 24 deletions src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,60 @@ import type { RpcRequest } from '~/messengers/schema'
const providerCache = new Map<string, EIP1193Provider>()
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()

// 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: {
Expand All @@ -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
}
3 changes: 2 additions & 1 deletion src/viem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down

0 comments on commit 22755d7

Please sign in to comment.