diff --git a/packages/extension-ui/src/messaging.ts b/packages/extension-ui/src/messaging.ts index d6b659a2..124d5e0d 100644 --- a/packages/extension-ui/src/messaging.ts +++ b/packages/extension-ui/src/messaging.ts @@ -6,19 +6,45 @@ import { AuthorizeRequest, MessageTypes, SigningRequest } from '@polkadot/extens import { KeyringJson } from '@polkadot/ui-keyring/types'; import { KeypairType } from '@polkadot/util-crypto/types'; +import extension from 'extensionizer'; +import { PORT_POPUP } from '@polkadot/extension/defaults'; + +type Handlers = { + [index: number]: { + resolve: (data: any) => void, + reject: (error: Error) => void + } +}; + +const port = extension.runtime.connect({ name: PORT_POPUP }); +const handlers: Handlers = {}; let idCounter = 0; +// setup a listener for messages, any incoming resolves the promise +port.onMessage.addListener((data) => { + const handler = handlers[data.id]; + + if (!handler) { + console.error(`Uknown response: ${JSON.stringify(data)}`); + return; + } + + delete handlers[data.id]; + + if (data.error) { + handler.reject(new Error(data.error)); + } else { + handler.resolve(data.response); + } +}); + function sendMessage (message: MessageTypes, request: any = {}): Promise { return new Promise((resolve, reject) => { const id = ++idCounter; - chrome.runtime.sendMessage({ id, message, request }, ({ error, response }) => { - if (error) { - reject(new Error(error)); - } else { - resolve(response); - } - }); + handlers[id] = { resolve, reject }; + + port.postMessage({ id, message, request }); }); } diff --git a/packages/extension/src/background/handlers/index.ts b/packages/extension/src/background/handlers/index.ts index 6d974767..3064d822 100644 --- a/packages/extension/src/background/handlers/index.ts +++ b/packages/extension/src/background/handlers/index.ts @@ -4,6 +4,7 @@ import { MessageRequest } from '../types'; +import { PORT_POPUP } from '../../defaults'; import Extension from './Extension'; import State from './State'; import Tabs from './Tabs'; @@ -12,27 +13,34 @@ const state = new State(); const extension = new Extension(state); const tabs = new Tabs(state); -export default function handler ({ id, message, request }: MessageRequest, { tab }: chrome.runtime.MessageSender, sendResponse: (response?: any) => void): boolean { - const source = `${tab ? tab.url : 'extension'}: ${id}: ${message}`; +export default function handler ({ id, message, request }: MessageRequest, port: chrome.runtime.Port): boolean { + const isPopup = port.name === PORT_POPUP; + const sender = port.sender as chrome.runtime.MessageSender; + const from = isPopup + ? 'popup' + : sender.tab + ? sender.tab.url + : ''; + const source = `${from}: ${id}: ${message}`; console.log(` [in] ${source}`); // :: ${JSON.stringify(request)}`); - const promise = tab - ? tabs.handle(message, request, tab.url) - : extension.handle(message, request); + const promise = isPopup + ? extension.handle(message, request) + : tabs.handle(message, request, from); promise .then((response) => { console.log(`[out] ${source}`); // :: ${JSON.stringify(response)}`); - sendResponse({ id, response }); + port.postMessage({ id, response }); }) .catch((error) => { console.log(`[err] ${source}:: ${error.message}`); - sendResponse({ id, error: error.message }); + port.postMessage({ id, error: error.message }); }); - // return true to indicate we are sending the respionse async + // return true to indicate we are sending the response async return true; } diff --git a/packages/extension/src/background/index.ts b/packages/extension/src/background/index.ts index d6bbfbfc..0ac43ef3 100644 --- a/packages/extension/src/background/index.ts +++ b/packages/extension/src/background/index.ts @@ -7,14 +7,27 @@ import extension from 'extensionizer'; // Runs in the extension background, handling all keyring access import keyring from '@polkadot/ui-keyring'; +import { assert } from '@polkadot/util'; import { cryptoWaitReady } from '@polkadot/util-crypto'; +import { PORT_CONTENT, PORT_POPUP } from '../defaults'; import ChromeStore from './ChromeStore'; import handlers from './handlers'; +// setup the notification extension.browserAction.setBadgeBackgroundColor({ color: '#ff0000' }); -extension.runtime.onMessage.addListener(handlers); +// listen to all messages and handle appropriately +extension.runtime.onConnect.addListener((port) => { + // shouldn't happen, however... only listen to what we know about + assert([PORT_CONTENT, PORT_POPUP].includes(port.name), `Unknown connection from ${port.name}`); + + // message and disconnect handlers + port.onMessage.addListener((data) => handlers(data, port)); + port.onDisconnect.addListener(() => console.log(`Disconnected from ${port.name}`)); +}); + +// initial setup cryptoWaitReady() .then(() => { console.log('crypto initialized'); diff --git a/packages/extension/src/content.ts b/packages/extension/src/content.ts index 45006394..9e8466df 100644 --- a/packages/extension/src/content.ts +++ b/packages/extension/src/content.ts @@ -4,27 +4,24 @@ import extension from 'extensionizer'; -function sendResponse (data: any): void { - window.postMessage({ ...data, origin: 'content' }, '*'); -} +import { PORT_CONTENT } from './defaults'; -// Handle all messages, passing messages to the extension +// connect to the extension +const port = extension.runtime.connect({ name: PORT_CONTENT }); + +// send any messages from the extension back to the page +port.onMessage.addListener((data) => { + window.postMessage({ ...data, origin: 'content' }, '*'); +}); + +// all messages from the page, pass them to the extension window.addEventListener('message', ({ data, source }) => { // only allow messages from our window, by the inject if (source !== window || data.origin !== 'page') { return; } - // pass the detail as-is as a message to the extension - extension.runtime.sendMessage(data, (response) => { - if (!response) { - // no data, send diconnected/unknown error - sendResponse({ id: data.id, error: 'Unknown response' }); - } else { - // handle the response, passing as a custom event - sendResponse(response); - } - }); + port.postMessage(data); }); // inject our data injector diff --git a/packages/extension/src/defaults.ts b/packages/extension/src/defaults.ts new file mode 100644 index 00000000..72ff7366 --- /dev/null +++ b/packages/extension/src/defaults.ts @@ -0,0 +1,11 @@ +// Copyright 2019 @polkadot/extension authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +const PORT_CONTENT = 'content'; +const PORT_POPUP = 'popup'; + +export { + PORT_CONTENT, + PORT_POPUP +}; diff --git a/packages/extension/src/page/index.ts b/packages/extension/src/page/index.ts index f29e48b7..bf7f8890 100644 --- a/packages/extension/src/page/index.ts +++ b/packages/extension/src/page/index.ts @@ -14,7 +14,7 @@ import Injected from './Injected'; // - this injector, reads listends on the events, maps it to the original // - resolves/rejects the promise with the result -type Callbacks = { +type Handlers = { [index: number]: { resolve: (data: any) => void, reject: (error: Error) => void @@ -23,7 +23,7 @@ type Callbacks = { // small helper with the typescript types, just cast window const windowInject = window as InjectedWindow; -const callbacks: Callbacks = {}; +const handlers: Handlers = {}; let idCounter = 0; // a generic message sender that creates an event, returning a promise that will @@ -32,7 +32,7 @@ function sendMessage (message: MessageTypes, request: any = null): Promise return new Promise((resolve, reject) => { const id = ++idCounter; - callbacks[id] = { resolve, reject }; + handlers[id] = { resolve, reject }; window.postMessage({ id, message, origin: 'page', request }, '*'); }); @@ -52,19 +52,19 @@ window.addEventListener('message', ({ data, source }) => { return; } - const promise = callbacks[data.id]; + const handler = handlers[data.id]; - if (!promise) { + if (!handler) { console.error(`Uknown response: ${JSON.stringify(data)}`); return; } - delete callbacks[data.id]; + delete handlers[data.id]; if (data.error) { - promise.reject(new Error(data.error)); + handler.reject(new Error(data.error)); } else { - promise.resolve(data.response); + handler.resolve(data.response); } });