From 459d2a340836d3778a69fee77f080e7b13b346ff Mon Sep 17 00:00:00 2001 From: Alex Stelea Date: Thu, 13 Jun 2024 18:27:05 +0100 Subject: [PATCH] refactor: update rcfm flow --- .../wallet-request/crypto/curve25519.ts | 58 +-- .../identity/identity.module.ts | 19 +- .../wallet-request/session/session.module.ts | 103 ++---- .../radix-connect-relay/deep-link.module.ts | 81 +---- .../radix-connect-relay-api.service.ts | 159 +------- .../radix-connect-relay.module.ts | 339 +++++++----------- .../modules/wallet-request/wallet-request.ts | 4 +- 7 files changed, 235 insertions(+), 528 deletions(-) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts index 8ea3e638..0d19da3e 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts @@ -5,10 +5,15 @@ import { Result, err, ok } from 'neverthrow' const toHex = (input: Uint8Array) => Buffer.from(input).toString('hex') export type KeyPairProvider = (privateKeyHex?: string) => { - getPublicKey: () => string getPrivateKey: () => string - calculateSharedSecret: (publicKeyHex: string) => Result - sign: (messageHex: string) => Result + x25519: { + getPublicKey: () => string + calculateSharedSecret: (publicKeyHex: string) => Result + } + ed25519: { + getPublicKey: () => string + sign: (messageHex: string) => Result + } } export type Curve25519 = ReturnType @@ -17,26 +22,31 @@ export const Curve25519: KeyPairProvider = ( privateKeyHex = toHex(x25519.utils.randomPrivateKey()), ) => { const getPrivateKey = () => privateKeyHex - - const getPublicKey = () => toHex(x25519.getPublicKey(privateKeyHex)) - - const calculateSharedSecret = ( - publicKeyHex: string, - ): Result => { - try { - return ok(toHex(x25519.getSharedSecret(privateKeyHex, publicKeyHex))) - } catch (error) { - return err(error as Error) - } + const x25519Api = { + getPublicKey: () => toHex(x25519.getPublicKey(privateKeyHex)), + calculateSharedSecret: (publicKeyHex: string): Result => { + try { + return ok(toHex(x25519.getSharedSecret(privateKeyHex, publicKeyHex))) + } catch (error) { + return err(error as Error) + } + }, + } as const + + const ed25519Api = { + getPublicKey: () => toHex(ed25519.getPublicKey(privateKeyHex)), + sign: (messageHex: string): Result => { + try { + return ok(toHex(ed25519.sign(privateKeyHex, messageHex))) + } catch (error) { + return err(error as Error) + } + }, + } as const + + return { + getPrivateKey, + x25519: x25519Api, + ed25519: ed25519Api, } - - const sign = (messageHex: string): Result => { - try { - return ok(toHex(ed25519.sign(privateKeyHex, messageHex))) - } catch (error) { - return err(error as Error) - } - } - - return { getPublicKey, getPrivateKey, calculateSharedSecret, sign } } diff --git a/packages/dapp-toolkit/src/modules/wallet-request/identity/identity.module.ts b/packages/dapp-toolkit/src/modules/wallet-request/identity/identity.module.ts index a713ee5b..e52a3fdb 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/identity/identity.module.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/identity/identity.module.ts @@ -55,7 +55,7 @@ export const IdentityModule = (input: { .mapErr(() => ({ reason: 'couldNotDeriveSharedSecret' })) .andThen((identity) => identity - ? identity.calculateSharedSecret(publicKey).mapErr(() => ({ + ? identity.x25519.calculateSharedSecret(publicKey).mapErr(() => ({ reason: 'FailedToDeriveSharedSecret', })) : err({ reason: 'DappIdentityNotFound' }), @@ -72,7 +72,7 @@ export const IdentityModule = (input: { dAppDefinitionAddress: string origin: string }): ResultAsync< - string, + { signature: string; publicKey: string }, { reason: string jsError: Error @@ -84,10 +84,17 @@ export const IdentityModule = (input: { dAppDefinitionAddress, origin, }).andThen((message) => - identity.sign(message).mapErr((error) => ({ - reason: 'couldNotSignMessage', - jsError: error, - })), + identity.ed25519 + .sign(message) + .map((signature) => ({ + signature, + publicKey: identity.x25519.getPublicKey(), + identity: identity.ed25519.getPublicKey(), + })) + .mapErr((error) => ({ + reason: 'couldNotSignMessage', + jsError: error, + })), ), ) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/session/session.module.ts b/packages/dapp-toolkit/src/modules/wallet-request/session/session.module.ts index bf7686c9..85f11755 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/session/session.module.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/session/session.module.ts @@ -1,117 +1,70 @@ import type { ResultAsync } from 'neverthrow' -import { errAsync, okAsync } from 'neverthrow' -import type { IdentityModule } from '../identity/identity.module' +import { okAsync } from 'neverthrow' import { StorageModule } from '../../storage/local-storage.module' import { v4 as uuidV4 } from 'uuid' -type Status = (typeof Status)[keyof typeof Status] -const Status = { Pending: 'Pending', Active: 'Active' } as const - -export type PendingSession = { - status: typeof Status.Pending - createdAt: number +export type Session = { sessionId: string - sentToWallet: boolean -} - -export type ActiveSession = { - status: typeof Status.Active - walletIdentity: string createdAt: number - sharedSecret: string - sessionId: string } -export type Session = PendingSession | ActiveSession - export type SessionModule = ReturnType export const SessionModule = (input: { providers: { storageModule: StorageModule - identityModule: IdentityModule } }) => { const storageModule = input.providers.storageModule - const identityModule = input.providers.identityModule - const findActiveSession = (): ResultAsync< - ActiveSession | undefined, - { reason: string } + const getSession = (): ResultAsync< + Session | undefined, + { reason: string; jsError: Error } > => storageModule .getItems() - .mapErr(() => ({ reason: 'couldNotReadFromStore' })) - .map((sessions) => { - const activeSession = Object.values(sessions).find( - (session): session is ActiveSession => - session.status === Status.Active, - ) - return activeSession - }) + .mapErr((error) => ({ + reason: 'couldNotReadSessionFromStore', + jsError: error, + })) + .map((sessions) => sessions[0]) const getSessionById = (sessionId: string) => - storageModule.getItemById(sessionId) + storageModule + .getItemById(sessionId) + .mapErr((error) => ({ reason: 'couldNotGetSessionById', jsError: error })) - const createSession = (): ResultAsync => { + const createSession = (): ResultAsync< + Session, + { reason: string; jsError: Error } + > => { const sessionId = uuidV4() - const newSession: PendingSession = { + const newSession: Session = { sessionId, - status: Status.Pending, createdAt: Date.now(), - sentToWallet: false, } return storageModule .setItems({ [sessionId]: newSession }) .map(() => newSession) + .mapErr((error) => ({ reason: 'couldNotCreateSession', jsError: error })) } const patchSession = (sessionId: string, value: Partial) => - storageModule.patchItem(sessionId, value) - - const convertToActiveSession = ( - sessionId: string, - walletIdentity: string, - ): ResultAsync => storageModule - .getItemById(sessionId) - .mapErr(() => ({ reason: 'readFromStorageError' })) - .andThen((session) => - session && session.status === Status.Pending - ? identityModule - .deriveSharedSecret('dApp', walletIdentity) - .andThen((sharedSecret) => - storageModule - .setItems({ - [sessionId]: { - ...session, - status: Status.Active, - walletIdentity, - sharedSecret, - }, - }) - .map(() => ({ - ...session, - status: Status.Active, - walletIdentity, - sharedSecret, - })) - .mapErr(() => ({ reason: 'writeToStorageError' })), - ) - : errAsync({ reason: 'sessionNotPending' }), - ) + .patchItem(sessionId, value) + .mapErr((error) => ({ reason: 'couldNotPatchSession', jsError: error })) - const getCurrentSession = (): ResultAsync => - findActiveSession().andThen((activeSession) => - activeSession - ? okAsync(activeSession) - : createSession().mapErr(() => ({ reason: 'couldNotCreateSession' })), + const getCurrentSession = (): ResultAsync< + Session, + { reason: string; jsError: Error } + > => + getSession().andThen((session) => + session ? okAsync(session) : createSession(), ) return { getCurrentSession, - convertToActiveSession, - findActiveSession, + getSession, store: storageModule, getSessionById, patchSession, diff --git a/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/deep-link.module.ts b/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/deep-link.module.ts index e2f5b5be..8f3e9478 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/deep-link.module.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/deep-link.module.ts @@ -1,49 +1,19 @@ -import { Result, ResultAsync } from 'neverthrow' -import { errAsync, ok, okAsync } from 'neverthrow' +import { ResultAsync } from 'neverthrow' +import { errAsync, okAsync } from 'neverthrow' import { Logger } from '../../../../helpers' -import { ReplaySubject } from 'rxjs' import Bowser from 'bowser' import { SdkError } from '../../../../error' export type DeepLinkModule = ReturnType export const DeepLinkModule = (input: { logger?: Logger - callBackPath: string walletUrl: string }) => { - const { callBackPath, walletUrl } = input + const { walletUrl } = input const userAgent = Bowser.parse(window.navigator.userAgent) - const { platform, browser } = userAgent + const { platform } = userAgent const logger = input?.logger?.getSubLogger({ name: 'DeepLinkModule' }) - const getNavigator = (): Navigator | undefined => globalThis?.navigator - - // Only exists in Brave browser - const getBrave = (): { isBrave: () => Promise } | undefined => - (getNavigator() as any)?.brave - - const isBrave = () => { - const maybeBrave = getBrave() - return maybeBrave - ? ResultAsync.fromPromise(maybeBrave.isBrave(), (error) => error as Error) - : okAsync(false) - } - - isBrave().map((isBrave) => { - if (isBrave) { - browser.name = 'Brave' - } - - logger?.debug({ platform, browser }) - }) - - const walletResponseSubject = new ReplaySubject>(1) - - const isCallbackUrl = () => window.location.href.includes(callBackPath) - - const shouldHandleWalletCallback = () => - platform.type === 'mobile' && isCallbackUrl() - const deepLinkToWallet = ( values: Record, ): ResultAsync => { @@ -53,56 +23,21 @@ export const DeepLinkModule = (input: { outboundUrl.searchParams.append(key, value) }) - outboundUrl.searchParams.append('browser', browser.name ?? 'unknown') - logger?.debug({ method: 'deepLinkToWallet', - queryParams: outboundUrl.searchParams.toString(), - browser: browser.name ?? 'unknown', + data: { ...values }, }) - if (platform.type === 'mobile') { - window.location.href = outboundUrl.toString() + if (platform.type === 'mobile' && globalThis.location?.href) { + globalThis.location.href = outboundUrl.toString() return okAsync(undefined) } - return errAsync(SdkError('UnhandledOs', '')) - } - - const getWalletResponseFromUrl = (): Result< - Record, - { reason: string } - > => { - const url = new URL(window.location.href) - const values = Object.fromEntries([...url.searchParams.entries()]) - logger?.debug({ - method: 'getWalletResponseFromUrl', - values, - }) - return ok(values) - } - - const handleWalletCallback = () => { - if (shouldHandleWalletCallback()) - return getWalletResponseFromUrl() - .map((values) => { - walletResponseSubject.next(values) - - return errAsync({ reason: 'InvalidCallbackValues' }) - }) - .mapErr((error) => { - logger?.debug({ - method: 'handleWalletCallback.error', - reason: error.reason, - }) - return error - }) + return errAsync(SdkError('UnhandledEnvironment', '')) } return { deepLinkToWallet, - handleWalletCallback, - walletResponse$: walletResponseSubject.asObservable(), } } diff --git a/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay-api.service.ts b/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay-api.service.ts index 85ee9cf4..18d7c58d 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay-api.service.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay-api.service.ts @@ -1,34 +1,29 @@ -import { Ok, Result, ResultAsync, err, ok } from 'neverthrow' import { SdkError } from '../../../../error' import { Logger, fetchWrapper, parseJSON } from '../../../../helpers' -import { - WalletInteraction, - WalletInteractionResponse, -} from '../../../../schemas' -import { EncryptionModule, transformBufferToSealbox } from '../../encryption' -import { - Subject, - filter, - first, - firstValueFrom, - merge, - of, - switchMap, -} from 'rxjs' -import { Buffer } from 'buffer' -import { ActiveSession } from '../../session/session.module' export type RadixConnectRelayApiService = ReturnType< typeof RadixConnectRelayApiService > + +export type WalletSuccessResponse = { + sessionId: string + publicKey: string + data: string +} + +export type WalletErrorResponse = { + sessionId: string + error: string +} + +export type WalletResponse = WalletSuccessResponse | WalletErrorResponse + export const RadixConnectRelayApiService = (input: { baseUrl: string logger?: Logger - providers: { encryptionModule: EncryptionModule } }) => { const baseUrl = input.baseUrl const logger = input.logger?.getSubLogger({ name: 'RadixConnectRelayApi' }) - const encryptionModule = input.providers.encryptionModule const callApi = ( body: Record & { method: string }, @@ -43,6 +38,7 @@ export const RadixConnectRelayApiService = (input: { .map((response) => { logger?.debug({ method: `callApi.${body.method}.success`, + response, }) return response }) @@ -58,132 +54,13 @@ export const RadixConnectRelayApiService = (input: { }) } - const decryptResponse = ( - secretHex: string, - value: string, - ): ResultAsync => - transformBufferToSealbox(Buffer.from(value, 'hex')) - .asyncAndThen(({ ciphertextAndAuthTag, iv }) => - encryptionModule.decrypt( - ciphertextAndAuthTag, - Buffer.from(secretHex, 'hex'), - iv, - ), - ) - .andThen((decrypted) => - parseJSON(decrypted.toString('utf-8')), - ) - .mapErr(() => SdkError('FailedToDecrypt', '')) - - const encryptWalletInteraction = ( - walletInteraction: WalletInteraction, - secret: Buffer, - ): ResultAsync => - encryptionModule - .encrypt(Buffer.from(JSON.stringify(walletInteraction), 'utf-8'), secret) - .mapErr(() => - SdkError( - 'FailEncryptWalletInteraction', - walletInteraction.interactionId, - ), - ) - .map((sealedBoxProps) => sealedBoxProps.combined.toString('hex')) - - const sendRequest = ( - { sessionId, sharedSecret }: ActiveSession, - walletInteraction: WalletInteraction, - ): ResultAsync => - encryptWalletInteraction( - walletInteraction, - Buffer.from(sharedSecret, 'hex'), - ).andThen((encryptedWalletInteraction) => - callApi({ - method: 'sendRequest', - sessionId, - data: encryptedWalletInteraction, - }).map(() => undefined), - ) - - const getResponses = (session: ActiveSession) => - callApi({ + const getResponses = (sessionId: string) => + callApi({ method: 'getResponses', - sessionId: session.sessionId, - }).andThen((value) => - ResultAsync.combine( - value.data.map((encryptedWalletInteraction) => - decryptResponse(session.sharedSecret, encryptedWalletInteraction), - ), - ), - ) - - const sendHandshakeRequest = ( - sessionId: string, - publicKeyHex: string, - ): ResultAsync => - callApi({ - method: 'sendHandshakeRequest', sessionId, - data: publicKeyHex, - }).map(() => undefined) - - const getHandshakeResponse = ( - sessionId: string, - ): ResultAsync => { - const getPublicKeyFromData = (data: { - publicKey: string - }): Result => { - const publicKeyRaw = data?.publicKey - - if (!publicKeyRaw) err({ reason: 'NotFound' }) - - const publicKey = Buffer.from(publicKeyRaw, 'hex').toString('utf-8') - - return parseJSON(publicKey) - .mapErr(() => ({ reason: 'FailedToParsePublicKey' })) - .andThen((parsed): Result => { - logger?.debug({ parsed }) - return parsed?.publicKey - ? ok(parsed.publicKey) - : err({ reason: 'NotFound' }) - }) - } - - const sendApiRequest = () => - callApi<{ publicKey: string }>({ - method: 'getHandshakeResponse', - sessionId, - }).andThen(({ data }) => getPublicKeyFromData(data)) - - return ResultAsync.fromPromise( - firstValueFrom( - of(null).pipe( - switchMap(() => { - const trigger = new Subject() - return merge(trigger, of(0)).pipe( - switchMap((retry) => - sendApiRequest().mapErr((err) => { - trigger.next(retry + 1) - return err - }), - ), - filter((result): result is Ok => { - return result.isOk() - }), - first(), - ) - }), - ), - ), - () => { - return SdkError('FailedToGetHandshakeResponseToRadixConnectRelay', '') - }, - ).andThen((result) => result) - } + }).map((value) => value.data) return { - sendRequest, getResponses, - sendHandshakeRequest, - getHandshakeResponse, } } diff --git a/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay.module.ts b/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay.module.ts index 3f5d7ae8..b034b485 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay.module.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay.module.ts @@ -1,28 +1,25 @@ -import { ResultAsync, err, ok } from 'neverthrow' -import { Subject, Subscription, filter, switchMap } from 'rxjs' -import { EncryptionModule } from '../../encryption' -import { - ActiveSession, - PendingSession, - Session, - SessionModule, -} from '../../session/session.module' +import { ResultAsync, err, errAsync, ok } from 'neverthrow' +import { Subject, Subscription, share } from 'rxjs' +import { EncryptionModule, transformBufferToSealbox } from '../../encryption' +import { Session, SessionModule } from '../../session/session.module' import type { CallbackFns, WalletInteraction, WalletInteractionResponse, } from '../../../../schemas' -import { Logger, isMobile } from '../../../../helpers' +import { Logger, isMobile, parseJSON } from '../../../../helpers' import { SdkError } from '../../../../error' import { DeepLinkModule } from './deep-link.module' import { IdentityModule } from '../../identity/identity.module' import { RequestItemModule } from '../../request-items/request-item.module' import { StorageModule } from '../../../storage' -import { Curve25519 } from '../../crypto' -import { RadixConnectRelayApiService } from './radix-connect-relay-api.service' +import { Curve25519, KeyPairProvider } from '../../crypto' +import { + RadixConnectRelayApiService, + WalletResponse, +} from './radix-connect-relay-api.service' import { RequestItem } from 'radix-connect-common' import type { TransportProvider } from '../../../../_types' -import { RcfmPageModule, RcfmPageState } from './rcfm-page.module' import { base64urlEncode } from './helpers/base64url' export type RadixConnectRelayModule = ReturnType @@ -36,7 +33,6 @@ export const RadixConnectRelayModule = (input: { encryptionModule?: EncryptionModule identityModule?: IdentityModule sessionModule?: SessionModule - rcfmPageModule?: RcfmPageModule deepLinkModule?: DeepLinkModule } }): TransportProvider => { @@ -46,18 +42,13 @@ export const RadixConnectRelayModule = (input: { const encryptionModule = providers?.encryptionModule ?? EncryptionModule() - const sessionChangeSubject = new Subject() - const deepLinkModule = providers?.deepLinkModule ?? DeepLinkModule({ logger, walletUrl, - callBackPath: '/connect', }) - const rcfmPageModule = providers?.rcfmPageModule ?? RcfmPageModule({ logger }) - const identityModule = providers?.identityModule ?? IdentityModule({ @@ -72,39 +63,29 @@ export const RadixConnectRelayModule = (input: { SessionModule({ providers: { storageModule: storageModule.getPartition('sessions'), - identityModule, }, }) const radixConnectRelayApiService = RadixConnectRelayApiService({ baseUrl: `${baseUrl}/api/v1`, logger, - providers: { encryptionModule }, }) const subscriptions = new Subscription() - const sendWalletLinkingRequest = (session: PendingSession) => - identityModule - .get('dApp') - .mapErr(() => SdkError('FailedToReadIdentity', '')) - .andThen((dAppIdentity) => - sessionModule - .patchSession(session.sessionId, { sentToWallet: true }) - .mapErr(() => SdkError('FailedToUpdateSession', '')) - .andThen(() => - deepLinkModule.deepLinkToWallet({ - sessionId: session.sessionId, - origin, - publicKey: dAppIdentity.getPublicKey(), - }), - ), - ) - - const sendWalletInteractionRequest = ( - session: ActiveSession, - walletInteraction: WalletInteraction, - ) => + const sendWalletInteractionRequest = ({ + session, + walletInteraction, + signature, + publicKey, + identity, + }: { + session: Session + walletInteraction: WalletInteraction + signature: string + publicKey: string + identity: string + }) => requestItemModule .getById(walletInteraction.interactionId) .mapErr(() => @@ -117,14 +98,19 @@ export const RadixConnectRelayModule = (input: { SdkError('PendingItemNotFound', walletInteraction.interactionId), ), ) - .andThen((pendingItem) => + .andThen(() => requestItemModule .patch(walletInteraction.interactionId, { sentToWallet: true }) .andThen(() => deepLinkModule.deepLinkToWallet({ sessionId: session.sessionId, - interactionId: pendingItem.interactionId, - walletInteraction: base64urlEncode(walletInteraction), + request: base64urlEncode(walletInteraction), + signature, + publicKey, + identity: identity, + origin: walletInteraction.metadata.origin, + dAppDefinitionAddress: + walletInteraction.metadata.dAppDefinitionAddress, }), ), ) @@ -137,104 +123,77 @@ export const RadixConnectRelayModule = (input: { callbackFns: Partial, ): ResultAsync => ResultAsync.combine([ - identityModule - .get('dApp') - .mapErr(() => - SdkError('FailedToGetDappIdentity', walletInteraction.interactionId), - ), sessionModule .getCurrentSession() - .mapErr(() => - SdkError('FailedToReadSession', walletInteraction.interactionId), + .mapErr((error) => + SdkError(error.reason, walletInteraction.interactionId), ), - ]) - .andThen(([dAppIdentity, session]) => - (session.status === 'Pending' - ? sendWalletLinkingRequest(session) - : sendWalletInteractionRequest(session, walletInteraction) - ).map(() => session), - ) - .andThen((session) => - waitForWalletResponse({ - sessionId: session.sessionId, + identityModule + .get('dApp') + .mapErr((error) => + SdkError(error.reason, walletInteraction.interactionId), + ), + ]).andThen(([session, dAppIdentity]) => + identityModule + .createSignature({ + dAppDefinitionAddress: + walletInteraction.metadata.dAppDefinitionAddress, interactionId: walletInteraction.interactionId, - }), - ) + origin: walletInteraction.metadata.origin, + kind: 'dApp', + }) + .mapErr((error) => + SdkError(error.reason, walletInteraction.interactionId), + ) + .andThen(({ signature }) => + sendWalletInteractionRequest({ + session, + walletInteraction, + signature, + identity: dAppIdentity.ed25519.getPublicKey(), + publicKey: dAppIdentity.x25519.getPublicKey(), + }), + ) + .andThen(() => + waitForWalletResponse({ + session, + interactionId: walletInteraction.interactionId, + dAppIdentity, + }), + ), + ) - // check if session exists - // -- if not, send error message to wallet - // generate shared secret - // update session - // send encrypted wallet interaction to RCR - // deep link to wallet with sessionId, interactionId, browser - const handleWalletLinkingResponse = ( - sessionId: string, - walletPublicKey: string, - ) => - sessionModule - .getSessionById(sessionId) - .mapErr(() => SdkError('FailedToReadSession', '')) - .andThen((session) => - session ? ok(session) : err(SdkError('SessionNotFound', '')), - ) - .andThen((session) => - session.status === 'Active' - ? ok(session) - : sessionModule - .convertToActiveSession(sessionId, walletPublicKey) - .mapErr(() => SdkError('FailedToUpdateSession', '')), + const decryptWalletResponseData = ( + sharedSecretHex: string, + value: string, + ): ResultAsync< + WalletInteractionResponse, + { reason: string; jsError: Error } + > => + transformBufferToSealbox(Buffer.from(value, 'hex')) + .asyncAndThen(({ ciphertextAndAuthTag, iv }) => + encryptionModule.decrypt( + ciphertextAndAuthTag, + Buffer.from(sharedSecretHex, 'hex'), + iv, + ), ) - .andThen((activeSession) => { - sessionChangeSubject.next(activeSession) - return requestItemModule - .getPending() - .mapErr(() => SdkError('FailedToReadPendingItems', '')) - .map((items) => { - const [item] = items.filter( - (item) => !item.walletResponse && !item.sentToWallet, - ) - return item - }) - - .map((item) => ({ activeSession, pendingItem: item })) - }) - .andThen( - ({ - activeSession, - pendingItem, - }: { - activeSession: ActiveSession - pendingItem?: RequestItem - }) => - pendingItem - ? requestItemModule - .patch(pendingItem.interactionId, { - sentToWallet: true, - }) - .mapErr(() => - SdkError( - 'FailedToUpdateRequestItem', - pendingItem.interactionId, - ), - ) - .andThen(() => - deepLinkModule.deepLinkToWallet({ - sessionId, - interactionId: pendingItem.walletInteraction.interactionId, - walletInteraction: base64urlEncode( - pendingItem.walletInteraction, - ), - }), - ) - : ok(pendingItem), + .andThen((decrypted) => + parseJSON(decrypted.toString('utf-8')), ) + .mapErr((error) => ({ + reason: 'FailedToDecryptWalletResponseData', + jsError: error, + })) const waitForWalletResponse = ({ - sessionId, + session, interactionId, + dAppIdentity, }: { - sessionId: string + session: Session interactionId: string + dAppIdentity: Curve25519 }): ResultAsync => ResultAsync.fromPromise( new Promise(async (resolve, reject) => { @@ -246,56 +205,57 @@ export const RadixConnectRelayModule = (input: { logger?.debug({ method: 'waitForWalletResponse', - sessionId, + sessionId: session.sessionId, interactionId, }) - while (!response) { - const sessionResult = await sessionModule.getSessionById(sessionId) - - if (sessionResult.isErr()) - return reject(SdkError('FailedToReadSession', interactionId)) + const getEncryptedWalletResponses = () => + radixConnectRelayApiService.getResponses(session.sessionId) - if (!sessionResult.value) { - return reject(SdkError('SessionNotFound', interactionId)) + const decryptWalletResponse = ( + walletResponse: WalletResponse, + ): ResultAsync => { + if ('error' in walletResponse) { + return errAsync({ reason: walletResponse.error }) } + return dAppIdentity.x25519 + .calculateSharedSecret(walletResponse.publicKey) + .mapErr(() => ({ reason: 'FailedToDeriveSharedSecret' })) + .asyncAndThen((sharedSecret) => + decryptWalletResponseData(sharedSecret, walletResponse.data), + ) + } + + while (!response) { + const encryptedWalletResponsesResult = + await getEncryptedWalletResponses() - const session = sessionResult.value + if (encryptedWalletResponsesResult.isOk()) { + const encryptedWalletResponses = + encryptedWalletResponsesResult.value - if (session.status === 'Active') { - await radixConnectRelayApiService - .getResponses(session) - .andThen((walletResponses) => - ResultAsync.combine( - walletResponses.map((walletResponse) => - requestItemModule.patch(walletResponse.interactionId, { - walletResponse, - }), - ), - ).map(() => walletResponses), + for (const encryptedWalletResponse of encryptedWalletResponses) { + const walletResponseResult = await decryptWalletResponse( + encryptedWalletResponse, ) - .andThen(() => requestItemModule.getById(interactionId)) - .map((walletInteraction) => { - if (walletInteraction) { - logger?.debug({ - method: 'waitForWalletResponse.success', - retry, - sessionId, - interactionId, - walletResponse: walletInteraction.walletResponse, - }) - response = walletInteraction.walletResponse - } - }) - .mapErr((error) => { - logger?.debug({ - method: 'waitForWalletResponse.error', - retry, - sessionId, + + if (walletResponseResult.isErr()) + logger?.error({ + method: 'waitForWalletResponse.decryptWalletResponse.error', + error: walletResponseResult.error, + sessionId: session.sessionId, interactionId, - error, }) - }) + + if (walletResponseResult.isOk()) { + const walletResponse = walletResponseResult.value + + if (walletResponse.interactionId === interactionId) { + response = walletResponse + break + } + } + } } if (!response) { @@ -309,47 +269,10 @@ export const RadixConnectRelayModule = (input: { (err) => err as SdkError, ) - const handleWalletCallback = async (values: Record) => { - const { sessionId, publicKey } = values - - const isLinkingResponse = sessionId && publicKey - - if (isLinkingResponse) { - return handleWalletLinkingResponse(sessionId, publicKey).mapErr( - (error) => { - logger?.debug({ method: 'handleWalletLinkingResponse.error', error }) - - deepLinkModule.deepLinkToWallet({ error: error.error }) - }, - ) - } - - logger?.debug({ method: 'handleWalletCallback.unhandled', values }) - } - - subscriptions.add( - deepLinkModule.walletResponse$ - .pipe( - filter((values) => Object.values(values).length > 0), - switchMap((item) => handleWalletCallback(item)), - ) - .subscribe(), - ) - - subscriptions.add( - sessionChangeSubject - .asObservable() - .pipe(filter((session) => session.status === 'Active')) - .subscribe(() => rcfmPageModule.show(RcfmPageState.dAppVerified)), - ) - - deepLinkModule.handleWalletCallback() - return { id: 'radix-connect-relay' as const, isSupported: () => isMobile(), send: sendToWallet, - sessionChange$: sessionChangeSubject.asObservable(), disconnect: () => {}, destroy: () => { subscriptions.unsubscribe() diff --git a/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts b/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts index 7da6ee7b..b4c4b6cc 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts @@ -102,7 +102,9 @@ export const WalletRequestModule = (input: { RadixConnectRelayModule({ logger, walletUrl: 'radixWallet://', - baseUrl: 'https://radix-connect-relay.radixdlt.com', + // baseUrl: 'https://radix-connect-relay.radixdlt.com', + baseUrl: + 'https://radix-connect-relay-dev.rdx-works-main.extratools.works', providers: { requestItemModule, storageModule,