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: wait for plan selection #1547

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 2 additions & 7 deletions packages/w3up-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,8 @@ const account = await client.login('[email protected]')
If your account doesn't have a payment plan yet, you'll be prompted to select one after verifying your email. A payment plan is required to provision a space. You can use the following loop to wait until a payment plan is selected:

```js
// wait for payment plan to be selected
while (true) {
const res = await account.plan.get()
if (res.ok) break
console.log('Waiting for payment plan to be selected...')
await new Promise((resolve) => setTimeout(resolve, 1000))
}
// Wait for a payment plan with a 1-second polling interval and 5-minute timeout
await account.plan.wait()
```

Spaces can be created using the [`createSpace` client method][docs-client#createSpace]:
Expand Down
40 changes: 40 additions & 0 deletions packages/w3up-client/src/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,46 @@
})
}

/**
* Waits for a payment plan to be selected.
* This method continuously checks the account's payment plan status
* at a specified interval until a valid plan is selected, or when the timeout is reached,
* or when the abort signal is aborted.
*
* @param {object} [options]
* @param {number} [options.interval=1000] - The polling interval in milliseconds (default is 1000ms).

Check warning on line 245 in packages/w3up-client/src/account.js

View workflow job for this annotation

GitHub Actions / Test (18)

Defaults are not permitted on @param

Check warning on line 245 in packages/w3up-client/src/account.js

View workflow job for this annotation

GitHub Actions / Test (20)

Defaults are not permitted on @param
* @param {number} [options.timeout=300000] - The maximum time to wait in milliseconds before throwing a timeout error (default is 5 minutes).

Check warning on line 246 in packages/w3up-client/src/account.js

View workflow job for this annotation

GitHub Actions / Test (18)

Defaults are not permitted on @param

Check warning on line 246 in packages/w3up-client/src/account.js

View workflow job for this annotation

GitHub Actions / Test (20)

Defaults are not permitted on @param
fforbeck marked this conversation as resolved.
Show resolved Hide resolved
* @param {AbortSignal} [options.signal] - An optional AbortSignal to cancel the waiting process.
* @returns {Promise<import('@web3-storage/access').PlanGetSuccess>} - Resolves once a payment plan is selected within the timeout.
* @throws {Error} - Throws an error if there is an issue retrieving the payment plan or if the timeout is exceeded.
*/
async wait(options) {
const startTime = Date.now()
const interval = options?.interval || 1000 // 1 second
const timeout = options?.timeout || 300000 // 5 minutes

// eslint-disable-next-line no-constant-condition
while (true) {
const res = await this.get()
if (res.ok) return res.ok

if (res.error) {
throw new Error(`Error retrieving payment plan: ${res.error}`)
}

if (Date.now() - startTime > timeout) {
throw new Error('Timeout: Payment plan selection took too long.')
}

if (options?.signal?.aborted) {
throw new Error('Aborted: Payment plan selection was aborted.')
}

console.log('Waiting for payment plan to be selected...')
await new Promise((resolve) => setTimeout(resolve, interval))
}
}

/**
*
* @param {import('@web3-storage/access').AccountDID} accountDID
Expand Down
85 changes: 85 additions & 0 deletions packages/w3up-client/test/account.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,91 @@ export const testAccount = Test.withContext({

assert.deepEqual(client.currentSpace()?.did(), space.did())
},

waitForPaymentPlan: {
'should wait for a payment plan to be selected': async (
assert,
{ client, mail, grantAccess }
) => {
const email = '[email protected]'
const login = Account.login(client, email)
await grantAccess(await mail.take())
const account = Result.try(await login)

let callCount = 0
// Mock the get method to simulate a plan being selected after some time
// @ts-expect-error
account.plan.get = async () => {
callCount++
if (callCount > 2) {
return { ok: { product: 'did:web:example.com' } }
}
return { ok: false }
}

const result = await account.plan.wait({ interval: 100, timeout: 1000 })
assert.deepEqual(result.product, 'did:web:example.com')
},

'should throw an error if there is an issue retrieving the payment plan':
async (assert, { client, mail, grantAccess }) => {
const email = '[email protected]'
const login = Account.login(client, email)
await grantAccess(await mail.take())
const account = Result.try(await login)

// @ts-expect-error
account.plan.get = async () => Promise.resolve({ error: 'Some error' })

await assert.rejects(
account.plan.wait({ interval: 100, timeout: 1000 }),
{
message: 'Error retrieving payment plan: Some error',
}
)
},

'should throw a timeout error if the payment plan selection takes too long':
async (assert, { client, mail, grantAccess }) => {
const email = '[email protected]'
const login = Account.login(client, email)
await grantAccess(await mail.take())
const account = Result.try(await login)

// @ts-expect-error
account.plan.get = async () => Promise.resolve({ ok: false })

await assert.rejects(
account.plan.wait({ interval: 100, timeout: 500 }),
{
message: 'Timeout: Payment plan selection took too long.',
}
)
},

'should throw an error when the abort signal is aborted': async (
assert,
{ client, mail, grantAccess }
) => {
const abortController = new AbortController()
const signal = abortController.signal

const email = '[email protected]'
const login = Account.login(client, email)
await grantAccess(await mail.take())
const account = Result.try(await login)

// @ts-expect-error
account.plan.get = async () => Promise.resolve({ ok: false })

// Abort the signal after a short delay
setTimeout(() => abortController.abort(), 100)

await assert.rejects(account.plan.wait({ signal }), {
message: 'Aborted: Payment plan selection was aborted.',
})
},
},
})

Test.test({ Account: testAccount })
Loading