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

feat: add subintent polling #289

Merged
merged 7 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 14 additions & 4 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,23 @@ export type RadixButtonMode = keyof typeof RadixButtonMode
export type PersonaData = { field: string; value: string }

export const RequestStatus = {
fail: 'fail',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either we use past tense of stick with present

failed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just moved it up in this const, we do not use RequestStatus.fail everywhere but pure string as well so I'm hesitant to changing this to failed

ignored: 'ignored',
pending: 'pending',
success: 'success',
fail: 'fail',
timedOut: 'timedOut',
cancelled: 'cancelled',
ignored: 'ignored',
/**
* Pending commit status is for preauthorization which was signed but not yet successfully committed to the network
*/
pendingCommit: 'pendingCommit',
} as const

export const RequestItemType = {
loginRequest: 'loginRequest',
dataRequest: 'dataRequest',
sendTransaction: 'sendTransaction',
proofRequest: 'proofRequest',
loginRequest: 'loginRequest',
sendTransaction: 'sendTransaction',
preAuthorizationRequest: 'preAuthorizationRequest',
} as const

Expand All @@ -49,6 +54,10 @@ export type RequestItemTypes = keyof typeof RequestItemType

export type RequestStatusTypes = keyof typeof RequestStatus

/**
* Not used in the codebase. Will be removed in the next major release future.
* @deprecated
*/
export type WalletRequest<
RequestType extends RequestItemTypes,
Status extends RequestStatusTypes,
Expand Down Expand Up @@ -81,3 +90,4 @@ export type RequestItem = {
metadata?: Record<string, string | number | boolean>
walletData?: any
}

Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const Requests: Story = {
console.log('onIgnoreTransactionItem', event)
}}
?showCancel="${args.showCancel}"
transactionIntentHash="${args.transactionIntentHash}"
hash="${args.transactionIntentHash}"
></radix-request-card>
</radix-popover>
`,
Expand Down
85 changes: 63 additions & 22 deletions packages/connect-button/src/components/card/request-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class RadixRequestCard extends LitElement {
@property({
type: String,
})
transactionIntentHash: string = ''
hash: string = ''

render() {
const icon = this.getIconFromStatus()
Expand All @@ -57,8 +57,10 @@ export class RadixRequestCard extends LitElement {
cancelled: 'Transaction Cancelled',
ignored: 'Transaction Ignored',
success: 'Send transaction',
pendingCommit: '',
timedOut: '',
content: html`
${this.renderTxIntentHash()}
${this.renderHash()}
${this.status === 'pending'
? html`<div class="request-content">
Open your Radix Wallet app to review the transaction
Expand All @@ -73,11 +75,41 @@ export class RadixRequestCard extends LitElement {
: ''}
`,
},
preAuthorizationRequest: {
pending: 'Preauthorization Pending',
fail: 'Preauthorization Failed',
cancelled: 'Preauthorization Cancelled',
success: 'Preauthorization Request',
ignored: 'Preauthorization Ignored',
pendingCommit: 'Preauthorization Lookup',
timedOut: 'Preauthorization Timed Out',
content: html`
${this.renderHash()}
${this.status === 'pending'
? html`<div class="request-content">
Open your Radix Wallet app to review the preauthorization
${this.showCancel
? html`<div class="cancel" @click=${this.onCancel}>
Cancel
</div>`
: html`<div class="cancel" @click=${this.onIgnore}>
Ignore
</div>`}
</div>`
: this.status === RequestStatus.pendingCommit
? html`<div class="request-content">
<div class="cancel" @click=${this.onIgnore}>Ignore</div>
</div>`
: ''}
`,
},
dataRequest: {
pending: 'Data Request Pending',
fail: 'Data Request Rejected',
cancelled: 'Data Request Rejected',
ignored: '',
pendingCommit: '',
timedOut: '',
success: 'Data Request',
content: this.getRequestContentTemplate(
'Open Your Radix Wallet App to complete the request',
Expand All @@ -89,6 +121,8 @@ export class RadixRequestCard extends LitElement {
cancelled: 'Login Request Rejected',
success: 'Login Request',
ignored: '',
pendingCommit: '',
timedOut: '',
content: this.getRequestContentTemplate(
'Open Your Radix Wallet App to complete the request',
),
Expand All @@ -99,16 +133,8 @@ export class RadixRequestCard extends LitElement {
cancelled: 'Proof Request Rejected',
success: 'Proof Request',
ignored: '',
content: this.getRequestContentTemplate(
'Open Your Radix Wallet App to complete the request',
),
},
preAuthorizationRequest: {
pending: 'Preauthorization Request Pending',
fail: 'Preauthorization Request Rejected',
cancelled: 'Preauthorization Request Rejected',
success: 'Preauthorization Request',
ignored: '',
pendingCommit: '',
timedOut: '',
content: this.getRequestContentTemplate(
'Open Your Radix Wallet App to complete the request',
),
Expand Down Expand Up @@ -136,7 +162,7 @@ export class RadixRequestCard extends LitElement {
: ''
}

private isErrorStatus(status: RequestStatusTypes) {
private hasErrorIcon(status: RequestStatusTypes) {
return (
[
RequestStatus.cancelled,
Expand All @@ -146,19 +172,31 @@ export class RadixRequestCard extends LitElement {
).includes(status)
}

private hasPendingIcon(status: RequestStatusTypes) {
return ([RequestStatus.pending, RequestStatus.pendingCommit] as string[]).includes(
status,
)
}

private hasIgnoredIcon(status: RequestStatusTypes) {
return (
[RequestStatus.ignored, RequestStatus.timedOut] as string[]
).includes(status)
}

private getIconFromStatus() {
return this.status === RequestStatus.pending
return this.hasPendingIcon(this.status)
? 'pending'
: this.status === RequestStatus.ignored
: this.hasIgnoredIcon(this.status)
? 'ignored'
: this.isErrorStatus(this.status)
: this.hasErrorIcon(this.status)
? 'error'
: 'checked'
}

private getStylingFromStatus() {
return classMap({
dimmed: this.isErrorStatus(this.status),
dimmed: this.hasErrorIcon(this.status),
inverted: this.status === 'pending',
})
}
Expand Down Expand Up @@ -189,21 +227,24 @@ export class RadixRequestCard extends LitElement {
)
}

private renderTxIntentHash() {
return this.transactionIntentHash
private renderHash() {
return this.hash
? html`<div class="transaction">
<span class="text-dimmed">ID:</span>
<radix-link
displayText="${shortenAddress(this.transactionIntentHash)}"
displayText="${shortenAddress(this.hash)}"
@click=${(event: MouseEvent) => {
event.preventDefault()
this.dispatchEvent(
new CustomEvent('onLinkClick', {
bubbles: true,
composed: true,
detail: {
type: 'transaction',
data: this.transactionIntentHash,
type:
this.type === 'sendTransaction'
? 'transaction'
: 'subintent',
data: this.hash,
},
}),
)
Expand Down
2 changes: 1 addition & 1 deletion packages/connect-button/src/components/pages/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class RadixRequestsPage extends LitElement {
type="${requestItem.type}"
status="${requestItem.status}"
id="${requestItem.interactionId}"
transactionIntentHash="${requestItem.transactionIntentHash || ''}"
hash="${requestItem.transactionIntentHash || ''}"
?showCancel="${requestItem.showCancel}"
timestamp=${requestItem.createdAt}
></radix-request-card>`,
Expand Down
1 change: 1 addition & 0 deletions packages/dapp-toolkit/src/_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type Providers = {
export type ExplorerConfig = {
baseUrl: string
transactionPath: string
subintentPath: string
accountsPath: string
}

Expand Down
79 changes: 79 additions & 0 deletions packages/dapp-toolkit/src/helpers/exponential-backoff.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { afterAll, describe, expect, it, vi } from 'vitest'
import { ExponentialBackoff } from './exponential-backoff'
import { delayAsync } from '../test-helpers/delay-async'
import { Subscription } from 'rxjs'

describe('exponential backoff', () => {
const subscription = new Subscription()

it('should emit withBackoff$ observable', async () => {
const backoff = ExponentialBackoff({
maxDelayTime: 2000,
multiplier: 2,
interval: 1000,
})

const spy = vi.fn()

subscription.add(
backoff.withBackoff$.subscribe(() => {
spy()
backoff.trigger.next()
}),
)

await delayAsync(4500)

expect(spy).toHaveBeenCalledTimes(3)
})

it('should emit error after timeout', async () => {
const backoff = ExponentialBackoff({
maxDelayTime: 2000,
multiplier: 2,
interval: 2000,
timeout: new Date(Date.now() + 1000),
})
const spy = vi.fn()

subscription.add(
backoff.withBackoff$.subscribe((res) => {
spy(res.isOk() ? res.value : res.error)

backoff.trigger.next()
}),
)

await delayAsync(2000)

expect(spy).toHaveBeenCalledWith(0)
expect(spy).toHaveBeenCalledWith({
error: 'timeout',
})
})

it('should emit error after stop', async () => {
const backoff = ExponentialBackoff({})
const spy = vi.fn()

subscription.add(
backoff.withBackoff$.subscribe((res) => {
spy(res.isOk() ? res.value : res.error)
if (res.isOk()) {
backoff.trigger.next()
}
}),
)

await delayAsync(2000)
backoff.stop()
expect(spy).toHaveBeenCalledWith(0)
expect(spy).toHaveBeenCalledWith({
error: 'stopped',
})
})

afterAll(() => {
subscription.unsubscribe()
})
})
24 changes: 16 additions & 8 deletions packages/dapp-toolkit/src/helpers/exponential-backoff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { map, merge, of, Subject, switchMap, timer } from 'rxjs'
export type ExponentialBackoffInput = {
multiplier?: number
maxDelayTime?: number
timeout?: number
timeout?: number | Date
interval?: number
}
export type ExponentialBackoff = typeof ExponentialBackoff
Expand All @@ -17,6 +17,7 @@ export const ExponentialBackoff = ({
interval = 2_000,
}: ExponentialBackoffInput = {}) => {
const trigger = new Subject<void>()
const stop = new Subject<void>()
let numberOfRetries = 0

const backoff$ = merge(
Expand All @@ -36,12 +37,19 @@ export const ExponentialBackoff = ({
)

const withBackoffAndTimeout$: Observable<Result<number, { error: string }>> =
timeout
? merge(
backoff$,
timer(timeout).pipe(map(() => err({ error: 'timeout' }))),
)
: backoff$
merge(
stop.asObservable().pipe(map(() => err({ error: 'stopped' }))),
timeout
? merge(
backoff$,
timer(timeout).pipe(map(() => err({ error: 'timeout' }))),
)
: backoff$,
)

return { trigger, withBackoff$: withBackoffAndTimeout$ }
return {
trigger,
withBackoff$: withBackoffAndTimeout$,
stop: () => stop.next(),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,13 @@ export const ConnectButtonModule = (
const logger = input?.logger?.getSubLogger({ name: 'ConnectButtonModule' })
const subjects = input.subjects || ConnectButtonSubjects()
const dAppDefinitionAddress = input.dAppDefinitionAddress
const { baseUrl, accountsPath, transactionPath } = input.explorer ?? {
baseUrl: RadixNetworkConfigById[input.networkId].dashboardUrl,
transactionPath: '/transaction/',
accountsPath: '/account/',
}
const { baseUrl, accountsPath, transactionPath, subintentPath } =
input.explorer ?? {
baseUrl: RadixNetworkConfigById[input.networkId].dashboardUrl,
transactionPath: '/transaction/',
subintentPath: '/subintent/',
accountsPath: '/account/',
}
const statusStorage = input.providers.storageModule

const stateModule = input.providers.stateModule
Expand Down Expand Up @@ -287,11 +289,15 @@ export const ConnectButtonModule = (
subjects.onLinkClick
.pipe(
tap(({ type, data }) => {
if (['account', 'transaction'].includes(type)) {
if (['account', 'transaction', 'subintent'].includes(type)) {
if (!baseUrl || !window) return

const url = `${baseUrl}${
type === 'transaction' ? transactionPath : accountsPath
type === 'transaction'
? transactionPath
: type === 'subintent'
? subintentPath
: accountsPath
}${data}`

window.open(url)
Expand Down
Loading
Loading