Skip to content

Commit

Permalink
Merge pull request #1548 from aeternity/feature/wallet-ts
Browse files Browse the repository at this point in the history
refactor(wallet-rpc)!: rewrite to TypeScript
  • Loading branch information
davidyuk authored Jun 7, 2022
2 parents 5c74af3 + 930e7d7 commit b1b3336
Show file tree
Hide file tree
Showing 13 changed files with 476 additions and 667 deletions.
95 changes: 44 additions & 51 deletions docs/guides/build-wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const accounts = [
MemoryAccount({ keypair: generateKeyPair() }), // generate keypair for account1
MemoryAccount({ keypair: generateKeyPair() }) // generate keypair for account2
]
const aeppInfo = {}

async function init () {
// Init extension stamp from sdk
Expand All @@ -63,42 +64,37 @@ async function init () {
// The `ExtensionProvider` uses the first account by default. You can change active account using `selectAccount(address)` function
accounts,
// Hook for sdk registration
onConnection (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to connect`)) {
action.accept()
} else {
action.deny()
onConnection (aeppId, params) {
if (!confirm(`Aepp ${params.name} with id ${aeppId} wants to connect`)) {
throw new RpcConnectionDenyError()
}
aeppInfo[aeppId] = params
},
onDisconnect (msg, client) {
console.log('Disconnect client: ', client)
},
onSubscription (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to subscribe for accounts`)) {
action.accept()
} else {
action.deny()
onSubscription (aeppId) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to subscribe for accounts`)) {
throw new RpcRejectedByUserError()
}
},
onSign (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to sign tx ${action.params.tx}`)) {
action.accept()
} else {
action.deny()
onSign (aeppId, params) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to sign tx ${params.tx}`)) {
throw new RpcRejectedByUserError()
}
},
onAskAccounts (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to get accounts`)) {
action.accept()
} else {
action.deny()
onAskAccounts (aeppId) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to get accounts`)) {
throw new RpcRejectedByUserError()
}
},
onMessageSign (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to sign msg ${action.params.message}`)) {
action.accept()
} else {
action.deny()
onMessageSign (aeppId, params) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to sign msg ${params.message}`)) {
throw new RpcRejectedByUserError()
}
}
}).then(wallet => {
Expand Down Expand Up @@ -142,46 +138,43 @@ async function init () {
// The `ExtensionProvider` uses the first account by default. You can change active account using `selectAccount(address)` function
accounts,
// Hook for sdk registration
onConnection (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to connect`)) {
onConnection (aeppId, params, origin) {
if (confirm(`Aepp ${params.name} with id ${aeppId} wants to connect`)) {
// Whitelist aepp domains for node connection
const aepps = ['https://test', 'https://aepp.aeternity.com']
if (aepp.info.connectNode && aepps.includes(aepp.info.origin)) action.accept()
else action.deny()
action.accept() // Accept wallet connection without sharing node URLs
if (params.connectNode && aepps.includes(origin)) {}
else throw new RpcConnectionDenyError()
// Connect to aepp without sharing node URLs
} else {
action.deny()
throw new RpcConnectionDenyError()
}
aeppInfo[aeppId] = params
},
onDisconnect (msg, client) {
console.log('Disconnect client: ', client)
},
onSubscription (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to subscribe for accounts`)) {
action.accept()
} else {
action.deny()
onSubscription (aeppId) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to subscribe for accounts`)) {
throw new RpcRejectedByUserError()
}
},
onSign (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to sign tx ${action.params.tx}`)) {
action.accept()
} else {
action.deny()
onSign (aeppId, params) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to sign tx ${params.tx}`)) {
throw new RpcRejectedByUserError()
}
},
onAskAccounts (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to get accounts`)) {
action.accept()
} else {
action.deny()
onAskAccounts (aeppId) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to get accounts`)) {
throw new RpcRejectedByUserError()
}
},
onMessageSign (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to sign msg ${action.params.message}`)) {
action.accept()
} else {
action.deny()
onMessageSign (aeppId, params) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to sign msg ${params.message}`)) {
throw new RpcRejectedByUserError()
}
}
}).then(wallet => {
Expand Down
18 changes: 13 additions & 5 deletions examples/browser/wallet-iframe/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
<script>
import {
MemoryAccount, RpcWallet, Node, generateKeyPair,
BrowserWindowMessageConnection, METHODS, WALLET_TYPE
BrowserWindowMessageConnection, METHODS, WALLET_TYPE,
RpcConnectionDenyError, RpcRejectedByUserError
} from '@aeternity/aepp-sdk'
import Value from './Value'
Expand Down Expand Up @@ -71,9 +72,11 @@ export default {
}
},
async mounted () {
const genConfirmCallback = getActionName => (aepp, { accept, deny, params }) => {
if (confirm(`Client ${aepp.info.name} with id ${aepp.id} want to ${getActionName(params)}`)) accept()
else deny()
const aeppInfo = {}
const genConfirmCallback = (getActionName) => (aeppId, params) => {
if (!confirm(`Client ${aeppInfo[aeppId].name} with id ${aeppId} want to ${getActionName(params)}`)) {
throw new RpcRejectedByUserError()
}
}
this.aeSdk = await RpcWallet({
id: window.origin,
Expand All @@ -93,7 +96,12 @@ export default {
MemoryAccount({ keypair: generateKeyPair() })
],
name: 'Wallet Iframe',
onConnection: genConfirmCallback(() => 'connect'),
onConnection: (aeppId, params) => {
if (!confirm(`Client ${params.name} with id ${aeppId} want to connect`)) {
throw new RpcConnectionDenyError()
}
aeppInfo[aeppId] = params
},
onSubscription: genConfirmCallback(() => 'subscription'),
onSign: genConfirmCallback(({ returnSigned, tx }) => `${returnSigned ? 'sign' : 'sign and broadcast'} ${JSON.stringify(tx)}`),
onMessageSign: genConfirmCallback(() => 'message sign'),
Expand Down
50 changes: 24 additions & 26 deletions examples/browser/wallet-web-extension/src/background.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import browser from 'webextension-polyfill'
import {
RpcWallet, Node, MemoryAccount, generateKeyPair, BrowserRuntimeConnection, WALLET_TYPE
RpcWallet, Node, MemoryAccount, generateKeyPair, BrowserRuntimeConnection, WALLET_TYPE,
RpcConnectionDenyError, RpcRejectedByUserError
} from '@aeternity/aepp-sdk'

(async () => {
const aeppInfo = {}

const aeSdk = await RpcWallet({
compilerUrl: 'https://compiler.aepps.com',
nodes: [{
Expand All @@ -25,42 +28,37 @@ import {
MemoryAccount({ keypair: generateKeyPair() })
],
// Hook for sdk registration
onConnection (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to connect`)) {
action.accept()
} else {
action.deny()
onConnection (aeppId, params) {
if (!confirm(`Aepp ${params.name} with id ${aeppId} wants to connect`)) {
throw new RpcConnectionDenyError()
}
aeppInfo[aeppId] = params
},
onDisconnect (msg, client) {
console.log('Client disconnected:', client)
},
onSubscription (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to subscribe for accounts`)) {
action.accept()
} else {
action.deny()
onSubscription (aeppId) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to subscribe for accounts`)) {
throw new RpcRejectedByUserError()
}
},
onSign (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to sign tx ${action.params.tx}`)) {
action.accept()
} else {
action.deny()
onSign (aeppId, params) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to sign tx ${params.tx}`)) {
throw new RpcRejectedByUserError()
}
},
onAskAccounts (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to get accounts`)) {
action.accept()
} else {
action.deny()
onAskAccounts (aeppId) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to get accounts`)) {
throw new RpcRejectedByUserError()
}
},
onMessageSign (aepp, action) {
if (confirm(`Aepp ${aepp.info.name} with id ${aepp.id} wants to sign msg ${action.params.message}`)) {
action.accept()
} else {
action.deny()
onMessageSign (aeppId, params) {
const { name } = aeppInfo[aeppId]
if (!confirm(`Aepp ${name} with id ${aeppId} wants to sign msg ${params.message}`)) {
throw new RpcRejectedByUserError()
}
}
})
Expand Down
8 changes: 4 additions & 4 deletions src/account/multiple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import AccountResolver, { _AccountResolver, Account } from './resolver'
import { UnavailableAccountError } from '../utils/errors'
import type stampit from '@stamp/it' // eslint-disable-line @typescript-eslint/no-unused-vars

class _AccountMultiple extends _AccountResolver {
export class _AccountMultiple extends _AccountResolver {
accounts: { [key: EncodedData<'ak'>]: _AccountBase }
selectedAddress?: string
selectedAddress?: EncodedData<'ak'>

async init (
{ accounts = [], address }: { accounts: _AccountBase[], address: EncodedData<'ak'> } & Parameters<_AccountBase['init']>[0]
Expand Down Expand Up @@ -60,8 +60,8 @@ class _AccountMultiple extends _AccountResolver {
* @return {String[]}
* @example addresses()
*/
addresses (): string[] {
return Object.keys(this.accounts)
addresses (): Array<EncodedData<'ak'>> {
return Object.keys(this.accounts) as Array<EncodedData<'ak'>>
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/node-pool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ interface NodePool {
readonly api: Node['api']
}

class _NodePool implements NodePool {
export class _NodePool implements NodePool {
readonly api: Node['api']
pool: Map<string, Node>
selectedNode?: Node
Expand Down Expand Up @@ -159,7 +159,7 @@ export default stampit<NodePool>({
selectNode: _NodePool.prototype.selectNode,
getNodeInfo: _NodePool.prototype.getNodeInfo,
isNodeConnected: _NodePool.prototype.isNodeConnected,
getNetworkId: _NodePool.prototype.getNetworkId,
getNetworkId,
getNodesInPool: _NodePool.prototype.getNodesInPool
}
})
11 changes: 7 additions & 4 deletions src/utils/aepp-wallet-communication/rpc/RpcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ interface JsonRpcResponse {
}
}

type RpcApiHandler = (p?: any, origin?: string) => any | undefined
type RpcApi<Keys> = { [k in keyof Keys]: RpcApiHandler }
type RpcApiHandler = (p?: any) => any | undefined
type RpcApi<Api> = { [k in keyof Api]: RpcApiHandler }
type WithOrigin<Api extends RpcApi<Api>> = {
[k in keyof Api]: (p: Parameters<Api[k]>[0], origin: string) => ReturnType<Api[k]>
}

/**
* Contain functionality for using RPC conection
Expand All @@ -36,12 +39,12 @@ export default class RpcClient <
connection: BrowserConnection
#callbacks = new Map<number, { resolve: (v: any) => void, reject: (e: Error) => void }>()
#messageId = 0
#methods: LocalApi
#methods: WithOrigin<LocalApi>

constructor (
connection: BrowserConnection,
onDisconnect: () => void,
methods: LocalApi
methods: WithOrigin<LocalApi>
) {
this.connection = connection
this.#methods = methods
Expand Down
14 changes: 7 additions & 7 deletions src/utils/aepp-wallet-communication/rpc/aepp-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import AccountRpc from '../../../account/rpc'
import { decode, EncodedData } from '../../encoder'
import { Accounts, WalletInfo, Network, WalletApi, AeppApi } from './types'
import RpcClient from './RpcClient'
import { METHODS, VERSION } from '../schema'
import { METHODS, VERSION, SUBSCRIPTION_TYPES } from '../schema'
import {
AlreadyConnectedError,
NoWalletConnectedError,
Expand Down Expand Up @@ -108,16 +108,16 @@ abstract class _AeppRpc extends _AccountResolver {
delete this._accounts
this.onDisconnect(disconnectParams)
}, {
[METHODS.updateAddress]: (params: Accounts) => {
[METHODS.updateAddress]: (params) => {
this._accounts = params
this.onAddressChange(params)
},
[METHODS.updateNetwork]: async (params: Network) => {
[METHODS.updateNetwork]: async (params) => {
const { node } = params
if (node != null) this.addNode(node.name, await Node(node), true)
this.onNetworkChange(params)
},
[METHODS.closeConnection]: (params: any) => {
[METHODS.closeConnection]: (params) => {
disconnectParams = params
client.connection.disconnect()
},
Expand Down Expand Up @@ -155,12 +155,12 @@ abstract class _AeppRpc extends _AccountResolver {

/**
* Subscribe for addresses from wallet
* @param type Should be one of 'current' (the selected account), 'connected' (all)
* @param value Subscription action
* @param type Subscription type
* @param value Should be one of 'current' (the selected account), 'connected' (all)
* @return Accounts from wallet
*/
async subscribeAddress (
type: 'current' | 'connected', value: 'subscribe' | 'unsubscribe'
type: SUBSCRIPTION_TYPES, value: 'current' | 'connected'
): Promise<ReturnType<WalletApi[METHODS.subscribeAddress]>> {
this._ensureConnected()
const result = await this.rpcClient.request(METHODS.subscribeAddress, { type, value })
Expand Down
Loading

0 comments on commit b1b3336

Please sign in to comment.