Skip to content

Commit

Permalink
Merge pull request #66 from luniehq/fabo/56-bind-requests-to-tabs
Browse files Browse the repository at this point in the history
Fabo/56 bind requests to tabs
  • Loading branch information
jbibla authored Jul 18, 2019
2 parents 261c4fb + 6479c11 commit 01b6506
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 34 deletions.
1 change: 1 addition & 0 deletions pending/fabo_56-bind-requests-to-tabs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Changed] Remove pending sign requests if a tab closes or changes the URL @faboweb
8 changes: 7 additions & 1 deletion src/background.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'babel-polyfill'
import { signMessageHandler, walletMessageHandler } from './messageHandlers'
import SignRequestQueue from './requests'
import { bindRequestsToTabs } from './tabsHandler'

global.browser = require('webextension-polyfill')

Expand All @@ -9,6 +11,9 @@ if (process.env.NODE_ENV === 'development') {
whitelisted.push('https://localhost')
}

const signRequestQueue = new SignRequestQueue()
signRequestQueue.unqueueSignRequest('')

// main message handler
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (!senderAllowed(sender)) {
Expand All @@ -17,7 +22,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
}

try {
signMessageHandler(message, sender, sendResponse)
signMessageHandler(signRequestQueue, message, sender, sendResponse)
walletMessageHandler(message, sender, sendResponse)
} catch (error) {
// Return this as rejected
Expand All @@ -27,6 +32,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {

return true
})
bindRequestsToTabs(signRequestQueue, whitelisted)

// only allow whitelisted websites to send us messages
function senderAllowed(sender) {
Expand Down
3 changes: 3 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,8 @@
"contentScript.js"
]
}
],
"permissions": [
"tabs"
]
}
42 changes: 14 additions & 28 deletions src/messageHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,31 @@ const {
getSeed
} = require('@lunie/cosmos-keys')

let signRequestQueue = []
unqueueSignRequest('') // restart icons on restart

export function signMessageHandler(message, sender, sendResponse) {
export function signMessageHandler(
signRequestQueue,
message,
sender,
sendResponse
) {
switch (message.type) {
case 'LUNIE_SIGN_REQUEST': {
const { signMessage, senderAddress } = message.payload
const wallet = getWalletFromIndex(getWalletIndex(), senderAddress)
if (!wallet) {
throw new Error('No wallet found matching the sender address.')
}
queueSignRequest({ signMessage, senderAddress, tabID: sender.tab.id })
signRequestQueue.queueSignRequest({
signMessage,
senderAddress,
tabID: sender.tab.id
})
break
}
case 'SIGN': {
const { signMessage, senderAddress, password, id } = message.payload
const wallet = getStoredWallet(senderAddress, password)

const { tabID } = unqueueSignRequest(id)
const { tabID } = signRequestQueue.unqueueSignRequest(id)
const signature = signWithPrivateKey(
signMessage,
Buffer.from(wallet.privateKey, 'hex')
Expand All @@ -43,9 +49,7 @@ export function signMessageHandler(message, sender, sendResponse) {
break
}
case 'GET_SIGN_REQUEST': {
sendResponse(
signRequestQueue.length > 0 ? signRequestQueue[0] : undefined
)
sendResponse(signRequestQueue.getSignRequest())
break
}
case 'REJECT_SIGN_REQUEST': {
Expand All @@ -54,7 +58,7 @@ export function signMessageHandler(message, sender, sendResponse) {
type: 'LUNIE_SIGN_REQUEST_RESPONSE',
payload: { rejected: true }
})
unqueueSignRequest(id)
signRequestQueue.unqueueSignRequest(id)
sendResponse() // to popup
break
}
Expand Down Expand Up @@ -107,21 +111,3 @@ function getWalletFromIndex(walletIndex, address) {
({ address: storedAddress }) => storedAddress === address
)
}

function queueSignRequest({ signMessage, senderAddress, tabID }) {
signRequestQueue.push({ signMessage, senderAddress, id: Date.now(), tabID })
chrome.browserAction.setIcon({ path: 'icons/128x128-alert.png' })
}

function unqueueSignRequest(id) {
const signRequest = signRequestQueue.find(
({ id: storedId }) => storedId === id
)
signRequestQueue = signRequestQueue.filter(
({ id: storedId }) => storedId !== id
)
if (signRequestQueue.length === 0) {
chrome.browserAction.setIcon({ path: 'icons/128x128.png' })
}
return signRequest
}
32 changes: 32 additions & 0 deletions src/requests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export default class SignRequestQueue {
constructor() {
this.queue = []
this.unqueueSignRequest('') // to reset the icon in the beginning
}

queueSignRequest({ signMessage, senderAddress, tabID }) {
this.queue.push({ signMessage, senderAddress, id: Date.now(), tabID })
chrome.browserAction.setIcon({ path: 'icons/128x128-alert.png' })
}

unqueueSignRequest(id) {
const signRequest = this.queue.find(({ id: storedId }) => storedId === id)
this.queue = this.queue.filter(({ id: storedId }) => storedId !== id)
if (this.queue.length === 0) {
chrome.browserAction.setIcon({ path: 'icons/128x128.png' })
}
return signRequest
}

unqueueSignRequestForTab(tabID) {
this.queue
.filter(({ tabID: storedtabID }) => storedtabID === tabID)
.map(({ id }) => {
this.unqueueSignRequest(id)
})
}

getSignRequest() {
return this.queue.length > 0 ? this.queue[0] : undefined
}
}
22 changes: 22 additions & 0 deletions src/tabsHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// requests always reference a tab so that a response finds the right listener
// if a tab is killed or it's url changes the request is not useful anymore
export function bindRequestsToTabs(signRequestQueue, whitelisted) {
// check if tab got removed
chrome.tabs.onRemoved.addListener(function(tabID) {
signRequestQueue.unqueueSignRequestForTab(tabID)
})
// check if url changed
chrome.tabs.onUpdated.addListener(function(tabID, changeInfo) {
// if the url doesn't change, ignore the update
if (!changeInfo.url) {
return
}
if (
!whitelisted.find(whitelistedUrl =>
changeInfo.url.startsWith(whitelistedUrl)
)
) {
signRequestQueue.unqueueSignRequestForTab(tabID)
}
})
}
59 changes: 59 additions & 0 deletions test/unit/requests.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import SignRequestQueue from '../../src/requests'

const mockSignRequest = {
signMessage: 'HALLOOOOO',
senderAddress: 'cosmos1234',
tabID: 42,
id: expect.any(Number)
}

describe('Sign request queue', () => {
let instance

beforeEach(() => {
global.chrome = {
browserAction: {
setIcon: jest.fn()
}
}
instance = new SignRequestQueue()
global.chrome.browserAction.setIcon.mockClear()
})

it('should queue a sign request', () => {
instance.queueSignRequest(mockSignRequest)
expect(instance.getSignRequest()).toEqual(mockSignRequest)
})

it('should unqueue a sign request', () => {
instance.queueSignRequest(mockSignRequest)
const { id } = instance.queue[0]
instance.unqueueSignRequest(id)

expect(instance.getSignRequest()).toEqual(undefined)
})

it('should unqueueSignRequestForTab', () => {
instance.queueSignRequest(mockSignRequest)
instance.unqueueSignRequestForTab(42)

expect(instance.getSignRequest()).toEqual(undefined)
})

describe('icons', () => {
it('shows a pending sign request icon', () => {
instance.queueSignRequest(mockSignRequest)
expect(global.chrome.browserAction.setIcon).toHaveBeenCalled()
})

it('removed the pending sign request icon', () => {
instance.queueSignRequest(mockSignRequest)
expect(global.chrome.browserAction.setIcon).toHaveBeenCalled()

global.chrome.browserAction.setIcon.mockClear()

instance.unqueueSignRequestForTab(42)
expect(global.chrome.browserAction.setIcon).toHaveBeenCalled()
})
})
})
48 changes: 48 additions & 0 deletions test/unit/tabsHandler.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { bindRequestsToTabs } from '../../src/tabsHandler'

describe('Sign request queue', () => {
let signRequestQueue

beforeEach(() => {
signRequestQueue = {
unqueueSignRequestForTab: jest.fn()
}
})

it('kills on tab removal', () => {
global.chrome = {
tabs: {
onRemoved: { addListener: callback => callback(42) },
onUpdated: { addListener: () => {} }
}
}
bindRequestsToTabs(signRequestQueue, [])

expect(signRequestQueue.unqueueSignRequestForTab).toHaveBeenCalledWith(42)
expect(signRequestQueue.unqueueSignRequestForTab).toHaveBeenCalledTimes(1)
})

it('kills on tab url not accepted', () => {
global.chrome = {
tabs: {
onRemoved: { addListener: () => {} },
onUpdated: { addListener: callback => callback(42, {}) }
}
}
bindRequestsToTabs(signRequestQueue, ['https://lunie.io'])
expect(signRequestQueue.unqueueSignRequestForTab).not.toHaveBeenCalled()

global.chrome = {
tabs: {
onRemoved: { addListener: () => {} },
onUpdated: {
addListener: callback => callback(42, { url: 'https://funkytown.io' })
}
}
}
bindRequestsToTabs(signRequestQueue, ['https://lunie.io'])

expect(signRequestQueue.unqueueSignRequestForTab).toHaveBeenCalledWith(42)
expect(signRequestQueue.unqueueSignRequestForTab).toHaveBeenCalledTimes(1)
})
})
5 changes: 0 additions & 5 deletions test/unit/test.spec.js

This file was deleted.

0 comments on commit 01b6506

Please sign in to comment.