Skip to content

Commit

Permalink
feat!: store both recurring/one-time grants & spents; add state (#379)
Browse files Browse the repository at this point in the history
  • Loading branch information
sidvishnoi authored Jul 2, 2024
1 parent 68f23f5 commit f511b5d
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 81 deletions.
3 changes: 2 additions & 1 deletion cspell-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ crossorigin
iframes
data-testid

# scripts
# scripts and 3rd party terms
typecheck
prettiercache
corepack
linkcode

# packages and 3rd party tools/libraries
awilix
Expand Down
24 changes: 19 additions & 5 deletions src/background/services/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class Background {
this.bindOnInstalled()
this.bindMessageHandler()
this.bindPermissionsHandler()
this.bindStateHandler()
this.bindTabHandlers()
this.bindWindowHandlers()
}
Expand Down Expand Up @@ -154,18 +155,30 @@ export class Background {
bindPermissionsHandler() {
this.browser.permissions.onAdded.addListener(this.checkPermissions)
this.browser.permissions.onRemoved.addListener(this.checkPermissions)
this.events.on('storage.host_permissions_update', async ({ status }) => {
this.logger.info('permission changed', { status })
}

bindStateHandler() {
this.events.on('storage.state_update', async ({ state, prevState }) => {
this.logger.info('state changed', { state, prevState })
// TODO: change icon here in future
})
}

bindOnInstalled() {
this.browser.runtime.onInstalled.addListener(async (details) => {
this.logger.info(await this.storage.get())
const data = await this.storage.get()
this.logger.info(data)
if (details.reason === 'install') {
await this.storage.populate()
await this.openPaymentsService.generateKeys()
} else if (details.reason === 'update') {
const migrated = await this.storage.migrate()
if (migrated) {
const prevVersion = data.version ?? 1
this.logger.info(
`Migrated from ${prevVersion} to ${migrated.version}`
)
}
}
await this.checkPermissions()
})
Expand All @@ -174,8 +187,9 @@ export class Background {
checkPermissions = async () => {
try {
this.logger.debug('checking hosts permission')
const status = await this.browser.permissions.contains(PERMISSION_HOSTS)
this.storage.setHostPermissionStatus(status)
const hasPermissions =
await this.browser.permissions.contains(PERMISSION_HOSTS)
this.storage.setState({ missing_host_permissions: !hasPermissions })
} catch (error) {
this.logger.error(error)
}
Expand Down
6 changes: 5 additions & 1 deletion src/background/services/events.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { EventEmitter } from 'events'
import type { Storage } from '@/shared/types'

interface BackgroundEvents {
'storage.rate_of_pay_update': { rate: string }
'storage.host_permissions_update': { status: boolean }
'storage.state_update': {
state: Storage['state']
prevState: Storage['state']
}
}

export class EventsService extends EventEmitter {
Expand Down
6 changes: 3 additions & 3 deletions src/background/services/monetization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,14 @@ export class MonetizationService {
const storedData = await this.storage.get([
'enabled',
'connected',
'hasHostPermissions',
'amount',
'state',
'rateOfPay',
'minRateOfPay',
'maxRateOfPay',
'walletAddress',
'publicKey'
])

const balance = await this.storage.getBalance()
const tab = await getCurrentActiveTab(this.browser)

let url
Expand All @@ -279,6 +278,7 @@ export class MonetizationService {

return {
...storedData,
balance: balance.total.toString(),
url,
isSiteMonetized
}
Expand Down
106 changes: 69 additions & 37 deletions src/background/services/openPayments.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// cSpell:ignore keyid
import { AccessToken, WalletAmount } from 'shared/types'
import type { AccessToken, GrantDetails, WalletAmount } from 'shared/types'
import {
type AuthenticatedClient,
createAuthenticatedClient
Expand Down Expand Up @@ -92,6 +92,7 @@ export class OpenPaymentsService {
client?: AuthenticatedClient

private token: AccessToken
private grant: GrantDetails | null

constructor(
private browser: Browser,
Expand All @@ -102,15 +103,22 @@ export class OpenPaymentsService {
}

private async initialize() {
const { token, connected, walletAddress } = await this.storage.get([
'connected',
'walletAddress',
'token'
])

if (connected === true && walletAddress && token) {
const { connected, walletAddress, oneTimeGrant, recurringGrant } =
await this.storage.get([
'connected',
'walletAddress',
'oneTimeGrant',
'recurringGrant'
])

if (
connected === true &&
walletAddress &&
(recurringGrant || oneTimeGrant)
) {
this.grant = recurringGrant || oneTimeGrant!
this.token = this.grant.accessToken
await this.initClient(walletAddress.id)
this.token = token
}
}

Expand Down Expand Up @@ -330,25 +338,41 @@ export class OpenPaymentsService {
throw new Error('Expected finalized grant. Received non-finalized grant.')
}

const token = {
value: continuation.access_token.value,
manage: continuation.access_token.manage
const grantDetails: GrantDetails = {
type: recurring ? 'recurring' : 'one-time',
amount: transformedAmount as Required<WalletAmount>,
accessToken: {
value: continuation.access_token.value,
manageUrl: continuation.access_token.manage
},
continue: {
accessToken: continuation.continue.access_token.value,
url: continuation.continue.uri
}
}

await this.storage.set({
const data = {
walletAddress,
rateOfPay,
minRateOfPay,
maxRateOfPay,
amount: transformedAmount,
token,
grant: {
accessToken: continuation.continue.access_token.value,
continueUri: continuation.continue.uri
},
connected: true
})
this.token = token
}
if (grantDetails.type === 'recurring') {
await this.storage.set({
...data,
recurringGrant: grantDetails,
recurringGrantSpentAmount: '0'
})
} else {
await this.storage.set({
...data,
oneTimeGrant: grantDetails,
oneTimeGrantSpentAmount: '0'
})
}
this.grant = grantDetails
this.token = this.grant.accessToken
}

private async createOutgoingPaymentGrant({
Expand Down Expand Up @@ -459,18 +483,20 @@ export class OpenPaymentsService {
}

async disconnectWallet() {
const { grant } = await this.storage.get(['grant'])
const { recurringGrant, oneTimeGrant } = await this.storage.get([
'recurringGrant',
'oneTimeGrant'
])
// TODO: When both types of grant can co-exist, make sure to revoke them
// correctly (either specific grant or all grants). See
// https://github.com/interledger/web-monetization-extension/pull/379#discussion_r1660447849
const grant = recurringGrant || oneTimeGrant

if (grant) {
await this.client!.grant.cancel({
url: grant.continueUri,
accessToken: grant.accessToken
})
await this.client!.grant.cancel(grant.continue)
await this.storage.clear()
this.token = {
value: '',
manage: ''
}
this.grant = null
this.token = { value: '', manageUrl: '' }
}
}

Expand Down Expand Up @@ -514,18 +540,24 @@ export class OpenPaymentsService {
}

async rotateToken() {
if (!this.grant) {
throw new Error('No grant to rotate token for')
}
const rotate = this.deduplicator.dedupe(this.client!.token.rotate)
const newToken = await rotate({
url: this.token.manage,
url: this.token.manageUrl,
accessToken: this.token.value
})
const token = {
const accessToken: AccessToken = {
value: newToken.access_token.value,
manage: newToken.access_token.manage
manageUrl: newToken.access_token.manage
}
await this.storage.set({
token
})
this.token = token
if (this.grant.type === 'recurring') {
this.storage.set({ recurringGrant: { ...this.grant, accessToken } })
} else {
this.storage.set({ oneTimeGrant: { ...this.grant, accessToken } })
}
this.grant.accessToken = accessToken
this.token = accessToken
}
}
Loading

0 comments on commit f511b5d

Please sign in to comment.