diff --git a/packages/ui/package.json b/packages/ui/package.json index 69d3f61411..cbd82f561a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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", diff --git a/packages/ui/src/api/types.ts b/packages/ui/src/api/types.ts new file mode 100644 index 0000000000..ad5150e350 --- /dev/null +++ b/packages/ui/src/api/types.ts @@ -0,0 +1,5 @@ +import { getPolkadotApiChainInfo } from 'injectweb3-connect' + +import { Awaited } from '@/common/types/helpers' + +export type MetadataDef = Awaited> diff --git a/packages/ui/src/api/utils/getChainMetadata.ts b/packages/ui/src/api/utils/getChainMetadata.ts new file mode 100644 index 0000000000..f1ef77e29f --- /dev/null +++ b/packages/ui/src/api/utils/getChainMetadata.ts @@ -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 => { + if ('_async' in api) { + return await api._async.chainMetadata + } else { + return await getPolkadotApiChainInfo(api) + } +} diff --git a/packages/ui/src/app/pages/Settings/Settings.tsx b/packages/ui/src/app/pages/Settings/Settings.tsx index 8db1960a02..fa20345d0a 100644 --- a/packages/ui/src/app/pages/Settings/Settings.tsx +++ b/packages/ui/src/app/pages/Settings/Settings.tsx @@ -42,6 +42,7 @@ export const Settings = () => { window.location.reload() } } + return ( 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) { diff --git a/packages/ui/src/common/types/helpers.ts b/packages/ui/src/common/types/helpers.ts index be8920e967..a12e4614ea 100644 --- a/packages/ui/src/common/types/helpers.ts +++ b/packages/ui/src/common/types/helpers.ts @@ -5,3 +5,5 @@ export type Defined = T extends undefined ? never : T export type EnumTypeString = { [key in string]: TEnum | string } export type KeysOfUnion = T extends T ? keyof T : never + +export type Awaited = T extends PromiseLike ? U : T diff --git a/packages/ui/src/forum/modals/PostReplyModal/helpers.ts b/packages/ui/src/forum/modals/PostReplyModal/helpers.ts index f54a377851..518ef0c4d1 100644 --- a/packages/ui/src/forum/modals/PostReplyModal/helpers.ts +++ b/packages/ui/src/forum/modals/PostReplyModal/helpers.ts @@ -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, diff --git a/packages/ui/src/proposals/modals/AddNewProposal/helpers.ts b/packages/ui/src/proposals/modals/AddNewProposal/helpers.ts index 1685564733..595fb3d33b 100644 --- a/packages/ui/src/proposals/modals/AddNewProposal/helpers.ts +++ b/packages/ui/src/proposals/modals/AddNewProposal/helpers.ts @@ -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 { @@ -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 = { @@ -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({ diff --git a/packages/ui/src/proxyApi/client/ProxyApi.ts b/packages/ui/src/proxyApi/client/ProxyApi.ts index faf54f35e0..b528418a2c 100644 --- a/packages/ui/src/proxyApi/client/ProxyApi.ts +++ b/packages/ui/src/proxyApi/client/ProxyApi.ts @@ -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' @@ -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' }) @@ -30,7 +32,10 @@ export class ProxyApi extends Events { share() ) const postMessage: PostMessage = (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 }) @@ -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 diff --git a/packages/ui/src/proxyApi/client/_async.ts b/packages/ui/src/proxyApi/client/_async.ts new file mode 100644 index 0000000000..3bf681a112 --- /dev/null +++ b/packages/ui/src/proxyApi/client/_async.ts @@ -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 +} + +export const _async = ( + messages: Observable, + postMessage: PostMessage +): AsyncProps => { + let chainMetadata: Promise + + return { + get chainMetadata(): Promise { + if (!chainMetadata) chainMetadata = getAsync('chain-metadata') + return chainMetadata + }, + } + + function getAsync(messageType: ClientAsyncMessage['messageType']): Promise { + postMessage({ messageType, payload: undefined }) + return firstValueFrom( + messages.pipe( + filter(({ data }) => data.messageType === 'chain-metadata'), + deserializeMessage(), + map(({ payload }) => payload) + ) + ) + } +} diff --git a/packages/ui/src/proxyApi/models/payload.ts b/packages/ui/src/proxyApi/models/payload.ts index d42a6f15f7..a9e2feb392 100644 --- a/packages/ui/src/proxyApi/models/payload.ts +++ b/packages/ui/src/proxyApi/models/payload.ts @@ -27,11 +27,13 @@ export interface ClientProxyMessage { payload: ProxyPromisePayload } -export const serializePayload = ( - payload: any, - messages?: Observable, +interface serializationOptions { + messages?: Observable postMessage?: PostMessage -): any => { + toJSON?: boolean +} + +export const serializePayload = (payload: any, { messages, postMessage, toJSON }: serializationOptions = {}): any => { const stack: AnyObject[] = [] const result = serializeValue(payload) @@ -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') { @@ -83,12 +85,16 @@ const serializeObject = (value: Record): Record => { return { ...value } } +interface deSerializationOptions { + messages?: Observable + postMessage?: PostMessage + transactionsRecord?: TransactionsRecord +} + // WARNING this mutate the serialized payload export const deserializePayload = ( payload: any, - messages?: Observable, - postMessage?: PostMessage, - transactionsRecord?: TransactionsRecord + { messages, postMessage, transactionsRecord }: deSerializationOptions = {} ): any => { const stack: AnyObject[] = [] const result = deserializeValue(payload) @@ -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)) } diff --git a/packages/ui/src/proxyApi/types.ts b/packages/ui/src/proxyApi/types.ts index 47517ff3aa..332a7cee5f 100644 --- a/packages/ui/src/proxyApi/types.ts +++ b/packages/ui/src/proxyApi/types.ts @@ -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' @@ -12,7 +13,7 @@ export interface ProxyPromisePayload { result?: T } -export type PostMessage = (message: Message) => void +export type PostMessage = (message: Message, asJSON?: boolean) => void export type ApiKinds = 'derive' | 'query' | 'rpc' | 'tx' @@ -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 diff --git a/packages/ui/src/proxyApi/worker/index.ts b/packages/ui/src/proxyApi/worker/index.ts index 287a4ed557..1c20063f66 100644 --- a/packages/ui/src/proxyApi/worker/index.ts +++ b/packages/ui/src/proxyApi/worker/index.ts @@ -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' @@ -13,8 +14,8 @@ import { transactionsRecord, tx } from './tx' const apiObserver = new BehaviorSubject(undefined) -const postMessage: PostMessage = (message) => - self.postMessage({ ...message, payload: serializePayload(message.payload) }) +const postMessage: PostMessage = (message, toJSON = false) => + self.postMessage({ ...message, payload: serializePayload(message.payload, { toJSON }) }) const messages = fromEvent(self, 'message') @@ -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') { @@ -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) } }) } diff --git a/packages/ui/test/_mocks/transactions.ts b/packages/ui/test/_mocks/transactions.ts index 89ce321f4d..e2f58d02b6 100644 --- a/packages/ui/test/_mocks/transactions.ts +++ b/packages/ui/test/_mocks/transactions.ts @@ -154,6 +154,7 @@ export const stubConst = (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, diff --git a/packages/ui/test/common/components/TransactionButton.test.tsx b/packages/ui/test/common/components/TransactionButton.test.tsx index 78478fbfe3..accf09dafd 100644 --- a/packages/ui/test/common/components/TransactionButton.test.tsx +++ b/packages/ui/test/common/components/TransactionButton.test.tsx @@ -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() }) diff --git a/packages/ui/test/setup.ts b/packages/ui/test/setup.ts index d3f293eb29..c43d1cdd50 100644 --- a/packages/ui/test/setup.ts +++ b/packages/ui/test/setup.ts @@ -13,6 +13,25 @@ import { UseTransaction } from '@/common/providers/transactionFees/context' configure({ testIdAttribute: 'id' }) +jest.mock('injectweb3-connect', () => { + const wallet = { + title: 'ExtraWallet', + extensionName: 'polkadot-js', + logo: { src: 'https://picsum.photos/100?grayscale&blur=2' }, + updateMetadata: jest.fn(() => Promise.resolve(true)), + } + const BaseDotsamaWallet = function () { + return + } + BaseDotsamaWallet.prototype = wallet + + return { + getWalletBySource: jest.fn(() => ({ ...wallet })), + getAllWallets: jest.fn(() => [{ ...wallet }]), + BaseDotsamaWallet, + } +}) + // Prevent jest from importing workers jest.mock('@/common/utils/crypto/worker', () => jest.requireActual('@/common/utils/crypto')) diff --git a/packages/ui/webpack.config.js b/packages/ui/webpack.config.js index 61897519b8..f9e689ae21 100644 --- a/packages/ui/webpack.config.js +++ b/packages/ui/webpack.config.js @@ -75,8 +75,7 @@ module.exports = (env, argv) => { exclude: [/node_modules/], }, { - test: /\.(png|jpg|gif|woff|woff2|eot|ttf|otf|svg)$/, - exclude: /node_modules/, + test: /\.(png|jpg|gif|woff|woff2|eot|ttf|otf|svg|webp)$/, use: ['file-loader'], }, { diff --git a/yarn.lock b/yarn.lock index 17019cb87c..7e298070dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3477,7 +3477,7 @@ __metadata: i18next: ^21.6.3 i18next-browser-languagedetector: ^6.1.2 i18next-json-csv-converter: ^0.2.0 - injectweb3-connect: ^1.1.1 + injectweb3-connect: 2.1.1 jest: ^27.2.5 jest-transform-stub: ^2.0.0 jsdom: ^18.0.0 @@ -3986,38 +3986,6 @@ __metadata: languageName: node linkType: hard -"@polkadot/extension-dapp@npm:^0.44.1": - version: 0.44.1 - resolution: "@polkadot/extension-dapp@npm:0.44.1" - dependencies: - "@babel/runtime": ^7.18.3 - "@polkadot/extension-inject": ^0.44.1 - "@polkadot/util": ^9.4.1 - "@polkadot/util-crypto": ^9.4.1 - peerDependencies: - "@polkadot/api": "*" - "@polkadot/util": "*" - "@polkadot/util-crypto": "*" - checksum: e759e8ea954e2be986ca16c7231d84306e2d5f37f444e936b18644011c4c15d765607ec5814eaeab2de65b655eb698b10619438ad58bb79b6c9aef478e93a193 - languageName: node - linkType: hard - -"@polkadot/extension-inject@npm:^0.44.1": - version: 0.44.1 - resolution: "@polkadot/extension-inject@npm:0.44.1" - dependencies: - "@babel/runtime": ^7.18.3 - "@polkadot/rpc-provider": ^8.7.1 - "@polkadot/types": ^8.7.1 - "@polkadot/util": ^9.4.1 - "@polkadot/util-crypto": ^9.4.1 - "@polkadot/x-global": ^9.4.1 - peerDependencies: - "@polkadot/api": "*" - checksum: cea75c32e702d344b4e1b4116ee3e98a09bb8fc97649677c22307063b6671b914e926bc8186d4e374d7686218cce3628583cd5dbfcb04ce6997e21d66024cd25 - languageName: node - linkType: hard - "@polkadot/extension-inject@npm:^0.44.2-4": version: 0.44.2-4 resolution: "@polkadot/extension-inject@npm:0.44.2-4" @@ -4402,7 +4370,7 @@ __metadata: languageName: node linkType: hard -"@polkadot/x-global@npm:9.7.2, @polkadot/x-global@npm:^9.4.1, @polkadot/x-global@npm:^9.5.1": +"@polkadot/x-global@npm:9.7.2, @polkadot/x-global@npm:^9.5.1": version: 9.7.2 resolution: "@polkadot/x-global@npm:9.7.2" dependencies: @@ -14644,15 +14612,12 @@ __metadata: languageName: node linkType: hard -"injectweb3-connect@npm:^1.1.1": - version: 1.1.1 - resolution: "injectweb3-connect@npm:1.1.1" - dependencies: - "@polkadot/api": ^8.8.2 - "@polkadot/extension-dapp": ^0.44.1 - "@polkadot/extension-inject": ^0.44.1 - "@polkadot/util-crypto": ^9.4.1 - checksum: b0ac017f918e2d752521081e88790d6679355a699c47927cff3ac1edfa891b02d2cd24bad5e786a29f935fab569dbc6e470fabd4250bf40b7c5fae67d7bf74c6 +"injectweb3-connect@npm:2.1.1": + version: 2.1.1 + resolution: "injectweb3-connect@npm:2.1.1" + peerDependencies: + "@polkadot/api": ^10.4.1 + checksum: a3e1eff7da5f603bade0dacc3f147a0a92581dd9e2ecb989819766be2fa2563e454232c18c69b485961a8556aacae9585aaef98f1876276f613aca79ffdc58e3 languageName: node linkType: hard