Skip to content

Commit

Permalink
refactor: extract message queue logic from connector client
Browse files Browse the repository at this point in the history
  • Loading branch information
xstelea committed Apr 14, 2023
1 parent 1257645 commit b44dffa
Show file tree
Hide file tree
Showing 25 changed files with 488 additions and 379 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"start": "DEV_TOOLS=true vite --mode rcnet",
"start:local": "DEV_TOOLS=true vite --mode localhost",
"start:beta": "DEV_TOOLS=true vite --mode beta",
"start:development": "DEV_TOOLS=true vite --mode development",
"build:development": "tsc && vite build --mode development",
"build:beta": "tsc && vite build --mode beta",
"build:rcnet": "tsc && vite build --mode rcnet",
Expand Down
5 changes: 4 additions & 1 deletion src/chrome/background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const handleStorageChange = (changes: {
const messageHandler = MessageClient(
BackgroundMessageHandler({ logger: backgroundLogger }),
'background',
{}
{ logger: backgroundLogger }
)

const handleConnectionPasswordChange = (connectionPassword?: string) =>
Expand All @@ -61,5 +61,8 @@ chrome.runtime.onMessage.addListener((message, sender) => {
chrome.storage.onChanged.addListener(handleStorageChange)
chrome.action.onClicked.addListener(openParingPopup)
chrome.runtime.onInstalled.addListener(handleOnInstallExtension)
chrome.runtime.onStartup.addListener(() => {
backgroundLogger.debug('onStartup')
})

createOffscreen()
2 changes: 1 addition & 1 deletion src/chrome/background/message-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const BackgroundMessageHandler =
closePopup = closePopupFn,
openParingPopup = openParingPopupFn,
}: Partial<{
logger: AppLogger
logger?: AppLogger
getConnectionPassword: () => ResultAsync<any, Error>
closePopup: () => ResultAsync<any, Error>
openParingPopup: () => ResultAsync<any, Error>
Expand Down
4 changes: 1 addition & 3 deletions src/chrome/content-script/content-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const chromeDAppClient = ChromeDAppClient()
const sendMessageToDapp = (
message: Record<string, any>
): ResultAsync<undefined, ConfirmationMessageError['error']> => {
logger.debug('content-script: sendMessageToDapp', { message })
const result = chromeDAppClient.sendMessage(message)

return result.isErr()
Expand All @@ -39,14 +38,13 @@ const messageHandler = MessageClient(
logger: logger,
}),
'contentScript',
{}
{ logger }
)

chromeDAppClient.messageListener((message) => {
messageHandler.onMessage(createMessage.incomingDappMessage('dApp', message))
})

chrome.runtime.onMessage.addListener((message: Message) => {
logger.debug('content-script: received message', message)
messageHandler.onMessage(message)
})
7 changes: 2 additions & 5 deletions src/chrome/helpers/get-tab-by-id.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { err, ok, ResultAsync } from 'neverthrow'
import { ResultAsync } from 'neverthrow'

export const getTabById = (tabId: number) =>
ResultAsync.fromPromise(
chrome.tabs.get(tabId),
(err) => err as Error
).andThen((tab) => (tab ? ok(tab) : err(new Error('Tab not found'))))
ResultAsync.fromPromise(chrome.tabs.get(tabId), (err) => err as Error)
247 changes: 155 additions & 92 deletions src/chrome/messages/message-client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MessagesRouter } from 'message-router'
import { okAsync } from 'neverthrow'
import { errAsync, okAsync } from 'neverthrow'
import { filter, firstValueFrom } from 'rxjs'
import { Logger } from 'tslog'
import { BackgroundMessageHandler } from '../background/message-handler'
Expand All @@ -11,103 +11,115 @@ import { MessageSubjects } from './subjects'

const logger = new Logger()

const messageClientSubjects = MessageSubjects()

let backgroundMessageClient: MessageClient
let offScreenMessageClient: MessageClient
let contentScriptMessageClient: MessageClient
let messageRouter: MessagesRouter

const dAppRequestQueue = { add: () => okAsync(undefined) } as any

const mockIncomingDappMessage = (message: Record<string, any>) => {
contentScriptMessageClient.onMessage(
createMessage.incomingDappMessage('dApp', message)
)
}

const mockIncomingWalletMessage = (
message: Record<string, any>,
tabId?: number
) => {
offScreenMessageClient.onMessage(
createMessage.incomingWalletMessage('wallet', message),
tabId
)
}

describe('message client', () => {
beforeEach(() => {
messageRouter = MessagesRouter()

backgroundMessageClient = MessageClient(
BackgroundMessageHandler({
logger,
getConnectionPassword: () => okAsync(''),
openParingPopup: () => okAsync(undefined),
}),
'background',
{
subjects: messageClientSubjects,
sendMessage: (message, tabId?: number) => {
logger.debug('sendMessage', { message, tabId })
messageClientSubjects.messageSubject.next({ message, tabId })
const createTestHelper = ({
messageRouter = MessagesRouter({ logger }),
messageClientSubjects = MessageSubjects(),
backgroundMessageClient = MessageClient(
BackgroundMessageHandler({
logger,
getConnectionPassword: () => okAsync(''),
openParingPopup: () => okAsync(undefined),
}),
'background',
{
logger,
subjects: messageClientSubjects,
sendMessage: (message, tabId?: number) => {
logger.debug('sendMessage', { message, tabId })
messageClientSubjects.messageSubject.next({ message, tabId })

return okAsync(undefined)
},
}
return okAsync(undefined)
},
}
),
offScreenMessageClient = MessageClient(
OffscreenMessageHandler({
logger,
messageRouter,
dAppRequestQueue,
connectorClient: {
connect: () => {},
disconnect: () => {},
setConnectionPassword: () => okAsync(undefined),
},
} as any),
'offScreen',
{
logger,
subjects: messageClientSubjects,
sendMessage: (value, tabId?: number) => {
const message =
value.source !== 'background' && tabId
? createMessage.sendMessageToTab('offScreen', tabId, value)
: value
logger.debug('sendMessage', message)
messageClientSubjects.messageSubject.next({ message, tabId })
return okAsync(undefined)
},
}
),
contentScriptMessageClient = MessageClient(
ContentScriptMessageHandler({
sendMessageEventToDapp: () => okAsync(undefined),
sendMessageToDapp: () => okAsync(undefined),
logger,
}),
'contentScript',
{
logger,
subjects: messageClientSubjects,
sendMessage: (message, tabId?: number) => {
logger.debug('sendMessage', message)
messageClientSubjects.messageSubject.next({ message, tabId })
return okAsync(undefined)
},
}
),
}: Partial<{
messagesRouter: MessagesRouter
backgroundMessageClient: MessageClient
offScreenMessageClient: MessageClient
contentScriptMessageClient: MessageClient
messageRouter: MessagesRouter
messageClientSubjects: MessageSubjects
}>) => {
const mockIncomingDappMessage = (message: Record<string, any>) => {
contentScriptMessageClient.onMessage(
createMessage.incomingDappMessage('dApp', message)
)
}

offScreenMessageClient = MessageClient(
OffscreenMessageHandler({
logger,
messageRouter,
dAppRequestQueue,
connectorClient: {
connect: () => {},
disconnect: () => {},
setConnectionPassword: () => okAsync(undefined),
},
} as any),
'offScreen',
{
subjects: messageClientSubjects,
sendMessage: (value, tabId?: number) => {
const message =
value.source !== 'background' && tabId
? createMessage.sendMessageToTab('offScreen', tabId, value)
: value
logger.debug('sendMessage', message)
messageClientSubjects.messageSubject.next({ message, tabId })
return okAsync(undefined)
},
}
const mockIncomingWalletMessage = (
message: Record<string, any>,
tabId?: number
) => {
offScreenMessageClient.onMessage(
createMessage.incomingWalletMessage('wallet', message),
tabId
)
}

contentScriptMessageClient = MessageClient(
ContentScriptMessageHandler({
sendMessageEventToDapp: () => okAsync(undefined),
sendMessageToDapp: () => okAsync(undefined),
logger,
}),
'contentScript',
{
subjects: messageClientSubjects,
sendMessage: (message, tabId?: number) => {
logger.debug('sendMessage', message)
messageClientSubjects.messageSubject.next({ message, tabId })
return okAsync(undefined)
},
}
)
})
return {
subjects: messageClientSubjects,
backgroundMessageClient,
offScreenMessageClient,
contentScriptMessageClient,
mockIncomingDappMessage,
mockIncomingWalletMessage,
messageRouter,
}
}

describe('message client', () => {
it('should send dApp request to wallet', async () => {
mockIncomingDappMessage({ interactionId: '123' })
const testHelper = createTestHelper({})
testHelper.mockIncomingDappMessage({ interactionId: '123' })

await Promise.all([
firstValueFrom(
messageClientSubjects.messageSubject.pipe(
testHelper.subjects.messageSubject.pipe(
filter(
({ message }) =>
message.discriminator === 'confirmation' &&
Expand All @@ -116,7 +128,7 @@ describe('message client', () => {
)
),
firstValueFrom(
messageClientSubjects.messageSubject.pipe(
testHelper.subjects.messageSubject.pipe(
filter(({ message }) => message.discriminator === 'detectWalletLink')
)
),
Expand All @@ -126,22 +138,23 @@ describe('message client', () => {
// offScreenPage does not have have access to the chrome tabs API
// so it has to proxy the message through background message handler
it('should send wallet response to dApp', async () => {
messageRouter.add(1, '456')
mockIncomingWalletMessage({ interactionId: '456' }, 1)
const testHelper = createTestHelper({})
testHelper.messageRouter.add(1, '456')
testHelper.mockIncomingWalletMessage({ interactionId: '456' }, 1)

await Promise.all([
firstValueFrom(
messageClientSubjects.messageSubject.pipe(
testHelper.subjects.messageSubject.pipe(
filter(({ message }) => message.discriminator === 'sendMessageToTab')
)
),
firstValueFrom(
messageClientSubjects.messageSubject.pipe(
testHelper.subjects.messageSubject.pipe(
filter(({ message }) => message.discriminator === 'walletResponse')
)
),
firstValueFrom(
messageClientSubjects.messageSubject.pipe(
testHelper.subjects.messageSubject.pipe(
filter(
({ message }) =>
message.discriminator === 'confirmation' &&
Expand All @@ -151,4 +164,54 @@ describe('message client', () => {
),
])
})

it('should fail to send message to dApp if tab is missing', async () => {
const subjects = MessageSubjects()
const testHelper = createTestHelper({
messageClientSubjects: subjects,
backgroundMessageClient: MessageClient(
BackgroundMessageHandler({
logger,
getConnectionPassword: () => okAsync(''),
openParingPopup: () => okAsync(undefined),
}),
'background',
{
logger,
subjects,
sendMessage: (message, tabId?: number) => {
if (message.discriminator === 'walletResponse')
return errAsync({
reason: 'tabNotFound',
message: 'could not find tab, user may have closed it',
})
subjects.messageSubject.next({ message, tabId })
return okAsync(undefined)
},
}
),
})

testHelper.messageRouter.add(1, '456')
testHelper.mockIncomingWalletMessage({ interactionId: '456' }, 1)

await Promise.all([
firstValueFrom(
testHelper.subjects.messageSubject.pipe(
filter(({ message }) => message.discriminator === 'sendMessageToTab')
)
),
firstValueFrom(
testHelper.subjects.messageSubject.pipe(
filter(
({ message }) =>
message.discriminator === 'confirmation' &&
message.source === 'background' &&
message.success === false &&
message.error.reason === 'tabNotFound'
)
)
),
])
})
})
Loading

0 comments on commit b44dffa

Please sign in to comment.