Skip to content

Commit

Permalink
refactor: update rcfm flow
Browse files Browse the repository at this point in the history
  • Loading branch information
xstelea committed Jun 13, 2024
1 parent bcda653 commit 459d2a3
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 528 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Error>
sign: (messageHex: string) => Result<string, Error>
x25519: {
getPublicKey: () => string
calculateSharedSecret: (publicKeyHex: string) => Result<string, Error>
}
ed25519: {
getPublicKey: () => string
sign: (messageHex: string) => Result<string, Error>
}
}

export type Curve25519 = ReturnType<typeof Curve25519>
Expand All @@ -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<string, Error> => {
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<string, Error> => {
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<string, Error> => {
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<string, Error> => {
try {
return ok(toHex(ed25519.sign(privateKeyHex, messageHex)))
} catch (error) {
return err(error as Error)
}
}

return { getPublicKey, getPrivateKey, calculateSharedSecret, sign }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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' }),
Expand All @@ -72,7 +72,7 @@ export const IdentityModule = (input: {
dAppDefinitionAddress: string
origin: string
}): ResultAsync<
string,
{ signature: string; publicKey: string },
{
reason: string
jsError: Error
Expand All @@ -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,
})),
),
)

Expand Down
Original file line number Diff line number Diff line change
@@ -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<typeof SessionModule>
export const SessionModule = (input: {
providers: {
storageModule: StorageModule<Session>
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<PendingSession, Error> => {
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<Session>) =>
storageModule.patchItem(sessionId, value)

const convertToActiveSession = (
sessionId: string,
walletIdentity: string,
): ResultAsync<ActiveSession, { reason: string }> =>
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<Session, { reason: string }> =>
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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<typeof DeepLinkModule>
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<boolean> } | 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<Record<string, string>>(1)

const isCallbackUrl = () => window.location.href.includes(callBackPath)

const shouldHandleWalletCallback = () =>
platform.type === 'mobile' && isCallbackUrl()

const deepLinkToWallet = (
values: Record<string, string>,
): ResultAsync<undefined, SdkError> => {
Expand All @@ -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<string, string>,
{ 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(),
}
}
Loading

0 comments on commit 459d2a3

Please sign in to comment.