Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

💉 Inject metadata into signer #3872

Merged
merged 23 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"graphql-tag": "^2.12.5",
"i18next": "^21.6.3",
"i18next-browser-languagedetector": "^6.1.2",
"injectweb3-connect": "^1.1.1",
"injectweb3-connect": "2.1.1",
"jsonpath": "^1.1.1",
"lodash": "^4.17.21",
"mime": "^2.4.4",
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { getPolkadotApiChainInfo } from 'injectweb3-connect'

import { Awaited } from '@/common/types/helpers'

export type MetadataDef = Awaited<ReturnType<typeof getPolkadotApiChainInfo>>
14 changes: 14 additions & 0 deletions packages/ui/src/api/utils/getChainMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApiRx } from '@polkadot/api'
import { getPolkadotApiChainInfo } from 'injectweb3-connect'

import { ProxyApi } from '@/proxyApi'

import { MetadataDef } from '../types'

export const getChainMetadata = async (api: ProxyApi | ApiRx): Promise<MetadataDef> => {
if ('_async' in api) {
return await api._async.chainMetadata
} else {
return await getPolkadotApiChainInfo(api)
}
}
1 change: 1 addition & 0 deletions packages/ui/src/app/pages/Settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const Settings = () => {
window.location.reload()
}
}

return (
<Container>
<PageLayout
Expand Down
15 changes: 14 additions & 1 deletion packages/ui/src/common/hooks/useSignAndSendTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { useCallback, useEffect, useState } from 'react'
import { ActorRef } from 'xstate'

import { useBalance } from '@/accounts/hooks/useBalance'
import { useMyAccounts } from '@/accounts/hooks/useMyAccounts'
import { useApi } from '@/api/hooks/useApi'
import { getChainMetadata } from '@/api/utils/getChainMetadata'
import { BN_ZERO } from '@/common/constants'
import { getFeeSpendableBalance } from '@/common/providers/transactionFees/provider'

Expand Down Expand Up @@ -44,8 +47,18 @@ export const useSignAndSendTransaction = ({
setBlockHash,
})
const queryNodeStatus = useQueryNodeTransactionStatus(isProcessing, blockHash, skipQueryNode)
const { wallet } = useMyAccounts()
const { api } = useApi()

const sign = useCallback(() => send('SIGN'), [service])
const sign = useCallback(() => {
if (wallet && api) {
return getChainMetadata(api).then(async (metadata) => {
await wallet.updateMetadata(metadata)
send('SIGN')
})
}
send('SIGN')
}, [service, wallet])

useEffect(() => {
if (skipQueryNode && isProcessing) {
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/common/types/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export type Defined<T> = T extends undefined ? never : T
export type EnumTypeString<TEnum extends string> = { [key in string]: TEnum | string }

export type KeysOfUnion<T> = T extends T ? keyof T : never

export type Awaited<T> = T extends PromiseLike<infer U> ? U : T
4 changes: 2 additions & 2 deletions packages/ui/src/forum/modals/PostReplyModal/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ForumPostMetadata } from '@joystream/metadata-protobuf'

import { Api } from '@/api'
import { createType } from '@/common/model/createType'
import { metadataToBytes } from '@/common/model/JoystreamNode'
import { ForumPost } from '@/forum/types'
import { ProxyApi } from '@/proxyApi'

export const transactionFactory = (
api: ProxyApi,
api: Api,
module: 'forum' | 'proposalsDiscussion',
text: string,
isEditable: boolean,
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/proposals/modals/AddNewProposal/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BN from 'bn.js'
import * as Yup from 'yup'

import { Account } from '@/accounts/types'
import { Api } from '@/api'
import { CurrencyName } from '@/app/constants/currency'
import { QuestionValueProps } from '@/common/components/EditableInputList/EditableInputList'
import {
Expand All @@ -17,7 +18,6 @@ import {
import { AccountSchema, StakingAccountSchema } from '@/memberships/model/validation'
import { Member } from '@/memberships/types'
import { ProposalType } from '@/proposals/types'
import { ProxyApi } from '@/proxyApi'
import { GroupIdName } from '@/working-groups/types'

export const defaultProposalValues = {
Expand Down Expand Up @@ -168,7 +168,7 @@ export interface AddNewProposalForm {
}
}

export const schemaFactory = (api?: ProxyApi) => {
export const schemaFactory = (api?: Api) => {
return Yup.object().shape({
groupId: Yup.string(),
proposalType: Yup.object().shape({
Expand Down
8 changes: 7 additions & 1 deletion packages/ui/src/proxyApi/client/ProxyApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { firstWhere } from '@/common/utils/rx'
import { deserializeMessage, serializePayload, WorkerProxyMessage } from '../models/payload'
import { ClientMessage, PostMessage, RawWorkerMessageEvent, WorkerConnectMessage, WorkerInitMessage } from '../types'

import { AsyncProps, _async } from './_async'
import { query } from './query'
import { tx } from './tx'

Expand All @@ -18,6 +19,7 @@ export class ProxyApi extends Events {
rpc: ApiRx['rpc']
tx: ApiRx['tx']
consts: ApiRx['consts']
_async: AsyncProps

static create(providerEndpoint: string) {
const worker = new Worker(new URL('../worker', import.meta.url), { type: 'module' })
Expand All @@ -30,7 +32,10 @@ export class ProxyApi extends Events {
share()
)
const postMessage: PostMessage<ClientMessage> = (message) =>
worker.postMessage({ ...message, payload: serializePayload(message.payload, workerProxyMessages, postMessage) })
worker.postMessage({
...message,
payload: serializePayload(message.payload, { messages: workerProxyMessages, postMessage }),
})

postMessage({ messageType: 'init', payload: providerEndpoint })

Expand All @@ -54,6 +59,7 @@ export class ProxyApi extends Events {
this.query = query('query', messages, postMessage)
this.rpc = query('rpc', messages, postMessage)
this.tx = tx(messages, postMessage)
this._async = _async(messages, postMessage)
}

messages
Expand Down
45 changes: 45 additions & 0 deletions packages/ui/src/proxyApi/client/_async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { filter, firstValueFrom, map, Observable } from 'rxjs'

import { MetadataDef } from '@/api/types'

import { deserializeMessage } from '../models/payload'
import { PostMessage, RawWorkerMessageEvent } from '../types'

export type ClientAsyncMessage = {
messageType: 'chain-metadata'
payload: undefined
}

export type WorkerAsyncMessage = {
messageType: 'chain-metadata'
payload: MetadataDef
}

export interface AsyncProps {
chainMetadata: Promise<MetadataDef>
}

export const _async = (
messages: Observable<RawWorkerMessageEvent>,
postMessage: PostMessage<ClientAsyncMessage>
): AsyncProps => {
let chainMetadata: Promise<MetadataDef>

return {
get chainMetadata(): Promise<MetadataDef> {
if (!chainMetadata) chainMetadata = getAsync('chain-metadata')
return chainMetadata
},
}

function getAsync(messageType: ClientAsyncMessage['messageType']): Promise<WorkerAsyncMessage['payload']> {
postMessage({ messageType, payload: undefined })
return firstValueFrom(
messages.pipe(
filter(({ data }) => data.messageType === 'chain-metadata'),
deserializeMessage<WorkerAsyncMessage>(),
map(({ payload }) => payload)
)
)
}
}
31 changes: 21 additions & 10 deletions packages/ui/src/proxyApi/models/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ export interface ClientProxyMessage {
payload: ProxyPromisePayload
}

export const serializePayload = (
payload: any,
messages?: Observable<WorkerProxyMessage>,
interface serializationOptions {
messages?: Observable<WorkerProxyMessage>
postMessage?: PostMessage<ClientProxyMessage>
): any => {
toJSON?: boolean
}

export const serializePayload = (payload: any, { messages, postMessage, toJSON }: serializationOptions = {}): any => {
const stack: AnyObject[] = []
const result = serializeValue(payload)

Expand All @@ -56,7 +58,7 @@ export const serializePayload = (
} else if (typeof value !== 'object' || value === null) {
return value
} else if (isCodec(value)) {
return serializeCodec(value)
return toJSON ? value.toJSON() : serializeCodec(value)
} else if (value instanceof BN) {
return { kind: 'BN', value: value.toArray() }
} else if (value.kind === 'SubmittableExtrinsicProxy') {
Expand All @@ -83,12 +85,16 @@ const serializeObject = (value: Record<any, any>): Record<string, any> => {
return { ...value }
}

interface deSerializationOptions {
messages?: Observable<ClientProxyMessage>
postMessage?: PostMessage<WorkerProxyMessage>
transactionsRecord?: TransactionsRecord
}

// WARNING this mutate the serialized payload
export const deserializePayload = (
payload: any,
messages?: Observable<ClientProxyMessage>,
postMessage?: PostMessage<WorkerProxyMessage>,
transactionsRecord?: TransactionsRecord
{ messages, postMessage, transactionsRecord }: deSerializationOptions = {}
): any => {
const stack: AnyObject[] = []
const result = deserializeValue(payload)
Expand Down Expand Up @@ -225,8 +231,13 @@ const serializeCodec = (codec: Codec): SerializedCodec => {
}

const properties = (Object.getOwnPropertyNames(Object.getPrototypeOf(codec)) as (keyof Codec)[])
.map((key) => [key, codec[key]])
.filter(([, prop]) => !isFunction(prop))
.map<[keyof Codec, Codec[keyof Codec]]>((key) => [key, codec[key]])
.filter(
([key, prop]) =>
!['encodedLength', 'hash', 'initialU8aLength', 'isEmpty', 'registry', 'createdAtHash'].includes(key) &&
!isFunction(prop) &&
!Object.getOwnPropertyDescriptor(codec, key)
)

return properties.length > 0
? { ...serializedCodec, properties: serializePayload(Object.fromEntries(properties)) }
Expand Down
13 changes: 10 additions & 3 deletions packages/ui/src/proxyApi/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SubmittableExtrinsic } from '@polkadot/api/types'

import { ProxyApi } from '.'
import { ClientAsyncMessage, WorkerAsyncMessage } from './client/_async'
import { ClientQueryMessage, WorkerQueryMessage } from './client/query'
import { ClientTxMessage, WorkerTxMessage } from './client/tx'
import { ClientProxyMessage, WorkerProxyMessage } from './models/payload'
Expand All @@ -12,7 +13,7 @@ export interface ProxyPromisePayload<T = any> {
result?: T
}

export type PostMessage<Message extends AnyMessage = AnyMessage> = (message: Message) => void
export type PostMessage<Message extends AnyMessage = AnyMessage> = (message: Message, asJSON?: boolean) => void

export type ApiKinds = 'derive' | 'query' | 'rpc' | 'tx'

Expand All @@ -38,7 +39,13 @@ export type WorkerMessage =
| WorkerQueryMessage
| WorkerTxMessage
| WorkerProxyMessage

export type ClientMessage = ClientInitMessage | ClientQueryMessage | ClientTxMessage | ClientProxyMessage
| WorkerAsyncMessage

export type ClientMessage =
| ClientInitMessage
| ClientQueryMessage
| ClientTxMessage
| ClientProxyMessage
| ClientAsyncMessage

export type AnyMessage = WorkerMessage | ClientMessage
23 changes: 14 additions & 9 deletions packages/ui/src/proxyApi/worker/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@joystream/types'
import { ApiRx, WsProvider } from '@polkadot/api'
import { getPolkadotApiChainInfo } from 'injectweb3-connect'
import { BehaviorSubject, filter, first, fromEvent, share } from 'rxjs'

import { isDefined } from '@/common/utils'
Expand All @@ -13,8 +14,8 @@ import { transactionsRecord, tx } from './tx'

const apiObserver = new BehaviorSubject<ApiRx | undefined>(undefined)

const postMessage: PostMessage<WorkerMessage> = (message) =>
self.postMessage({ ...message, payload: serializePayload(message.payload) })
const postMessage: PostMessage<WorkerMessage> = (message, toJSON = false) =>
self.postMessage({ ...message, payload: serializePayload(message.payload, { toJSON }) })

const messages = fromEvent<RawClientMessageEvent>(self, 'message')

Expand All @@ -25,7 +26,7 @@ const clientProxyMessage = messages.pipe(
)

messages.subscribe(({ data }) => {
const payload = deserializePayload(data.payload, clientProxyMessage, postMessage, transactionsRecord)
const payload = deserializePayload(data.payload, { messages: clientProxyMessage, postMessage, transactionsRecord })
const message = { ...data, payload } as ClientMessage

if (message.messageType === 'init') {
Expand All @@ -35,26 +36,30 @@ messages.subscribe(({ data }) => {
.subscribe((api) => {
postMessage({ messageType: 'init', payload: { consts: api.consts } })
postMessage({ messageType: 'isConnected', payload: true })

api.on('connected', () => self.postMessage({ messageType: 'isConnected', payload: true }))
api.on('disconnected', () => self.postMessage({ messageType: 'isConnected', payload: false }))

apiObserver.next(api)
})
} else {
apiObserver.pipe(firstWhere(isDefined)).subscribe((api) => {
apiObserver.pipe(firstWhere(isDefined)).subscribe(async (api) => {
if (!api) return

switch (message.messageType) {
case 'derive':
return query('derive', api as ApiRx, message, postMessage)
return query('derive', api, message, postMessage)

case 'query':
return query('query', api as ApiRx, message, postMessage)
return query('query', api, message, postMessage)

case 'rpc':
return query('rpc', api as ApiRx, message, postMessage)
return query('rpc', api, message, postMessage)

case 'tx':
return tx(api as ApiRx, message, postMessage)
return tx(api, message, postMessage)

case 'chain-metadata':
return postMessage({ messageType: 'chain-metadata', payload: await getPolkadotApiChainInfo(api) }, true)
}
})
}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/test/_mocks/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export const stubConst = <T>(api: UseApi, constSubPath: string, value: T) => {
export const stubApi = () => {
const api: UseApi = {
api: {
_async: { chainMetadata: Promise.resolve({}) },
isConnected: true,
} as unknown as Api,
isConnected: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ describe('UI: TransactionButton', () => {
stubTransaction(api, txPath)

beforeAll(async () => {
stubAccounts([{ ...alice, name: 'Alice Account' }], { wallet: new BaseDotsamaWallet({ title: 'ExtraWallet' }) })
const wallet = new BaseDotsamaWallet({ title: 'ExtraWallet' })
stubAccounts([{ ...alice, name: 'Alice Account' }], {
wallet,
})
await cryptoWaitReady()
})

Expand Down
Loading
Loading