Skip to content

Commit

Permalink
Merge branch 'fix/acknowledge-message' into 'feat/beacon-v2'
Browse files Browse the repository at this point in the history
Fix/acknowledge message

See merge request papers/airgap/beacon-extension!22
  • Loading branch information
AndreasGassmann committed Dec 1, 2020
2 parents a671f57 + 110200f commit d142c77
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 18 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
"private": true,
"dependencies": {
"@airgap/beacon-sdk": "2.0.0-beta.16",
"@airgap/beacon-sdk": "2.0.0-beta.17",
"@angular/common": "^9.1.0",
"@angular/core": "^9.1.0",
"@angular/forms": "^9.1.0",
Expand Down
3 changes: 1 addition & 2 deletions src/extension/extension-client/Actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Network, P2PPairingRequest, PermissionInfo } from '@airgap/beacon-sdk'
import { ExtendedP2PPairingResponse } from '@airgap/beacon-sdk/dist/cjs/types/P2PPairingResponse'
import { ExtendedP2PPairingResponse, Network, P2PPairingRequest, PermissionInfo } from '@airgap/beacon-sdk'

export enum WalletType {
P2P = 'P2P',
Expand Down
8 changes: 3 additions & 5 deletions src/extension/extension-client/ExtensionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
ChromeStorage,
ConnectionContext,
DappP2PTransport,
ExtendedP2PPairingResponse,
ExtendedPostMessagePairingResponse,
ExtensionMessage,
ExtensionMessageTarget,
getAccountIdentifier,
Expand All @@ -19,8 +21,6 @@ import {
PermissionResponse,
Serializer
} from '@airgap/beacon-sdk'
import { ExtendedP2PPairingResponse } from '@airgap/beacon-sdk/dist/cjs/types/P2PPairingResponse'
import { ExtendedPostMessagePairingResponse } from '@airgap/beacon-sdk/dist/cjs/types/PostMessagePairingResponse'
import * as sodium from 'libsodium-wrappers'

import { AirGapOperationProvider, LocalSigner } from '../AirGapSigner'
Expand Down Expand Up @@ -327,9 +327,7 @@ export class ExtensionClient extends BeaconClient {
const peers: ExtendedPostMessagePairingResponse[] = ((await this.transport.getPeers()) as any) || []
const peer = peers.find(peerEl => peerEl.senderId === senderId)

if (peer) {
await this.transport.sendToTabs(peer.publicKey, serialized)
}
await this.transport.sendToTabs(peer ? peer.publicKey : undefined, serialized)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ChromeStorage, DappP2PTransport } from '@airgap/beacon-sdk'
import { ExtendedP2PPairingResponse } from '@airgap/beacon-sdk/dist/cjs/types/P2PPairingResponse'
import { ChromeStorage, DappP2PTransport, ExtendedP2PPairingResponse } from '@airgap/beacon-sdk'

import { Action, ExtensionMessageInputPayload, ExtensionMessageOutputPayload } from '../Actions'
import { ExtensionClient } from '../ExtensionClient'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
BeaconMessage,
BeaconMessageType,
ErrorResponse,
ErrorResponseInput,
getSenderId
} from '@airgap/beacon-sdk'
import { ErrorResponseInput } from '@airgap/beacon-sdk/dist/cjs/types/beacon/messages/BeaconResponseInputMessage'

import { ExtensionClient } from '../ExtensionClient'
import { Logger } from '../Logger'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BeaconMessage,
BeaconMessageType,
getSenderId,
SigningType,
SignPayloadRequestOutput,
SignPayloadResponse,
SignPayloadResponseInput
Expand Down Expand Up @@ -83,6 +84,7 @@ export const signPayloadRequestHandler: (client: ExtensionClient, logger: Logger
const responseInput: SignPayloadResponseInput = {
id: signRequest.id,
type: BeaconMessageType.SignPayloadResponse,
signingType: SigningType.RAW,
signature: signature.res
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
EncryptedExtensionMessage,
ExtendedPostMessagePairingResponse,
ExtensionMessage,
ExtensionMessageTarget,
MessageBasedClient,
PostMessagePairingRequest
} from '@airgap/beacon-sdk'

export class ChromeMessageClient extends MessageBasedClient {
protected readonly activeListeners: Map<
string,
(
message: ExtensionMessage<string> | EncryptedExtensionMessage,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: unknown) => void
) => void
> = new Map()

public async init(): Promise<void> {
this.subscribeToMessages().catch(console.error)
}

public async listenForEncryptedMessage(
senderPublicKey: string,
messageCallback: (
message: ExtensionMessage<string>,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: unknown) => void
) => void
): Promise<void> {
if (this.activeListeners.has(senderPublicKey)) {
return
}

const callbackFunction = async (
message: ExtensionMessage<string> | EncryptedExtensionMessage,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: unknown) => void
): Promise<void> => {
if (message.hasOwnProperty('encryptedPayload')) {
const encryptedMessage: EncryptedExtensionMessage = message as EncryptedExtensionMessage

try {
const decrypted = await this.decryptMessage(senderPublicKey, encryptedMessage.encryptedPayload)
const decryptedMessage: ExtensionMessage<string> = {
payload: decrypted,
target: encryptedMessage.target,
sender: encryptedMessage.sender
}
messageCallback(decryptedMessage, sender, sendResponse)
} catch (decryptionError) {
/* NO-OP. We try to decode every message, but some might not be addressed to us. */
}
}
}

this.activeListeners.set(senderPublicKey, callbackFunction)
}

public async sendMessage(
message: string,
peer?: PostMessagePairingRequest | ExtendedPostMessagePairingResponse
): Promise<void> {
let msg: EncryptedExtensionMessage | ExtensionMessage<string> = {
target: ExtensionMessageTarget.PAGE,
payload: message
}

// If no recipient public key is provided, we respond with an unencrypted message
if (peer && peer.publicKey) {
const payload = await this.encryptMessage(peer.publicKey, message)

msg = {
target: ExtensionMessageTarget.PAGE,
encryptedPayload: payload
}
}

chrome.tabs.query({}, (tabs: chrome.tabs.Tab[]) => {
// TODO: Find way to have direct communication with tab
tabs.forEach(({ id }: chrome.tabs.Tab) => {
if (id) {
chrome.tabs.sendMessage(id, msg)
}
}) // Send message to all tabs
})
}

public async sendPairingResponse(pairingRequest: PostMessagePairingRequest): Promise<void> {
const pairingResponse = await this.getPairingResponseInfo(pairingRequest)

const encryptedMessage: string = await this.encryptMessageAsymmetric(
pairingRequest.publicKey,
JSON.stringify(pairingResponse)
)

const message: ExtensionMessage<string> = {
target: ExtensionMessageTarget.PAGE,
payload: encryptedMessage
}
chrome.tabs.query({}, (tabs: chrome.tabs.Tab[]) => {
// TODO: Find way to have direct communication with tab
tabs.forEach(({ id }: chrome.tabs.Tab) => {
if (id) {
chrome.tabs.sendMessage(id, message)
}
}) // Send message to all tabs
})
}

private async subscribeToMessages(): Promise<void> {
chrome.runtime.onMessage.addListener(
(
message: ExtensionMessage<string> | EncryptedExtensionMessage,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: unknown) => void
) => {
this.activeListeners.forEach(listener => {
listener(message, sender, sendResponse)
})

// return true from the event listener to indicate you wish to send a response asynchronously
// (this will keep the message channel open to the other end until sendResponse is called).
return true
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// eslint-disable-next-line spaced-comment
/// <reference types="chrome"/>

import {
ConnectionContext,
ExtendedPostMessagePairingResponse,
ExtensionMessage,
ExtensionMessageTarget,
getSenderId,
Origin,
PeerManager,
PostMessagePairingRequest,
Serializer,
Storage,
StorageKey,
Transport,
TransportStatus,
TransportType
} from '@airgap/beacon-sdk'
import * as sodium from 'libsodium-wrappers'

import { Logger } from '../Logger'

import { ChromeMessageClient } from './ChromeMessageClient'

const logger = new Logger('ChromeMessageTransport')

export class ChromeMessageTransport<
T extends PostMessagePairingRequest | ExtendedPostMessagePairingResponse,
K extends StorageKey.TRANSPORT_POSTMESSAGE_PEERS_DAPP | StorageKey.TRANSPORT_POSTMESSAGE_PEERS_WALLET
> extends Transport<T, K, ChromeMessageClient> {
public readonly type: TransportType = TransportType.CHROME_MESSAGE

constructor(name: string, keyPair: sodium.KeyPair, storage: Storage, storageKey: K) {
super(name, new ChromeMessageClient(name, keyPair, false), new PeerManager(storage, storageKey))
this.init().catch(error => console.error(error))
this.connect().catch(error => console.error(error))
}

public static async isAvailable(): Promise<boolean> {
const isAvailable: boolean = Boolean(window.chrome && chrome.runtime && chrome.runtime.id)

return Promise.resolve(isAvailable)
}

public async connect(): Promise<void> {
logger.log('connect')
this._isConnected = TransportStatus.CONNECTING

const knownPeers = await this.getPeers()

if (knownPeers.length > 0) {
logger.log('connect', `connecting to ${knownPeers.length} peers`)
const connectionPromises = knownPeers.map(async peer => this.listen(peer.publicKey))
await Promise.all(connectionPromises)
}

await super.connect()
}

public async send(payload: string | Record<string, unknown>): Promise<void> {
const message: ExtensionMessage<string | Record<string, unknown>> = {
target: ExtensionMessageTarget.PAGE,
payload
}
chrome.runtime.sendMessage(message, (data?: unknown): void => {
logger.log('send', 'got response', data)
})
}

public async sendToTabs(publicKey: string | undefined, payload: string): Promise<void> {
const peers = await this.getPeers()
const peer = peers.find(peerEl => peerEl.publicKey === publicKey)

return this.client.sendMessage(payload, peer)
}

public async addPeer(newPeer: T): Promise<void> {
await super.addPeer(newPeer)
}

public async listen(publicKey: string): Promise<void> {
await this.client
.listenForEncryptedMessage(
publicKey,
async (
message: ExtensionMessage<string>,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: unknown) => void
) => {
const connectionContext: ConnectionContext = {
origin: Origin.WEBSITE,
id: sender.url ? sender.url : '',
extras: { sender, sendResponse }
}

this.notifyListeners(message, connectionContext).catch(error => {
throw error
})
}
)
.catch(error => {
throw error
})
}

private async init(): Promise<void> {
chrome.runtime.onMessage.addListener(
(
message: ExtensionMessage<string>,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: unknown) => void
) => {
logger.log('init', 'receiving chrome message', message, sender)

if (message && message.payload && typeof message.payload === 'string') {
// Handling PairingRequest and connect peer
new Serializer()
.deserialize(message.payload)
.then(async deserialized => {
// TODO: Add check if it's a peer
if ((deserialized as any).publicKey) {
const peer = deserialized as any
this.addPeer({ ...peer, senderId: await getSenderId(peer.publicKey) }).catch(console.error)
} else {
// V1 does not support encryption, so we handle the message directly
if ((deserialized as any).version === '1') {
this.notify(message, sender, sendResponse).catch(error => {
throw error
})
}
}
})
.catch(undefined)
} else if (message && message.payload) {
// Most likely an internal, unencrypted message
this.notify(message, sender, sendResponse).catch(error => {
throw error
})
}

// return true from the event listener to indicate you wish to send a response asynchronously
// (this will keep the message channel open to the other end until sendResponse is called).

// return true
}
)
}

private async notify(
message: ExtensionMessage<string>,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: unknown) => void
): Promise<void> {
const connectionContext: ConnectionContext = {
origin: Origin.WEBSITE,
id: sender.url ? sender.url : '',
extras: { sender, sendResponse }
}

this.notifyListeners(message, connectionContext).catch(error => {
throw error
})
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ChromeMessageTransport, Storage, StorageKey } from '@airgap/beacon-sdk'
import { PostMessagePairingRequest } from '@airgap/beacon-sdk/dist/cjs/types/PostMessagePairingRequest'
import { PostMessagePairingRequest, Storage, StorageKey } from '@airgap/beacon-sdk'
import * as sodium from 'libsodium-wrappers'

import { ChromeMessageTransport } from './ChromeMessageTransport'

// const logger = new Logger('DappP2PTransport')

export class WalletChromeMessageTransport extends ChromeMessageTransport<
Expand Down
Loading

0 comments on commit d142c77

Please sign in to comment.