diff --git a/src/components/Wallets.test.jsx b/src/components/Wallets.test.jsx
index 0e75c8c1c..6c1f8f5f6 100644
--- a/src/components/Wallets.test.jsx
+++ b/src/components/Wallets.test.jsx
@@ -202,7 +202,12 @@ describe('', () => {
})
apiMock.postWalletUnlock.mockResolvedValueOnce({
ok: true,
- json: () => Promise.resolve({ walletname: dummyWalletName, token: dummyToken }),
+ json: () =>
+ Promise.resolve({
+ walletname: dummyWalletName,
+ token: dummyToken,
+ refresh_token: dummyToken,
+ }),
})
await act(async () => setup({}))
@@ -223,7 +228,10 @@ describe('', () => {
await waitFor(() => screen.findByText('wallets.wallet_preview.button_unlock'))
})
- expect(mockStartWallet).toHaveBeenCalledWith(dummyWalletName, dummyToken)
+ expect(mockStartWallet).toHaveBeenCalledWith(dummyWalletName, {
+ token: dummyToken,
+ refresh_token: dummyToken,
+ })
expect(mockedNavigate).toHaveBeenCalledWith('/wallet')
})
diff --git a/src/constants/config.ts b/src/constants/config.ts
index 632e5dde9..c67165669 100644
--- a/src/constants/config.ts
+++ b/src/constants/config.ts
@@ -17,3 +17,5 @@ export const JM_MINIMUM_MAKERS_DEFAULT = 4
export const CJ_STATE_TAKER_RUNNING = 0
export const CJ_STATE_MAKER_RUNNING = 1
export const CJ_STATE_NONE_RUNNING = 2
+
+export const JM_API_AUTH_TOKEN_EXPIRY: Milliseconds = Math.round(0.5 * 60 * 60 * 1_000)
diff --git a/src/context/ServiceInfoContext.tsx b/src/context/ServiceInfoContext.tsx
index ebb80f5ec..fb50031ba 100644
--- a/src/context/ServiceInfoContext.tsx
+++ b/src/context/ServiceInfoContext.tsx
@@ -17,8 +17,8 @@ import { CJ_STATE_TAKER_RUNNING, CJ_STATE_MAKER_RUNNING } from '../constants/con
import * as Api from '../libs/JmWalletApi'
-// interval in milliseconds for periodic session requests
-const SESSION_REQUEST_INTERVAL = 10_000
+// interval for periodic session requests
+const SESSION_REQUEST_INTERVAL: Milliseconds = 10_000
type AmountFraction = number
type AmountCounterparties = number
diff --git a/src/context/WalletContext.tsx b/src/context/WalletContext.tsx
index ea253ba18..a1bd47854 100644
--- a/src/context/WalletContext.tsx
+++ b/src/context/WalletContext.tsx
@@ -1,10 +1,11 @@
import { createContext, useEffect, useCallback, useState, useContext, PropsWithChildren, useMemo } from 'react'
-import { getSession } from '../session'
+import { getSession, setSession } from '../session'
import * as fb from '../components/fb/utils'
import * as Api from '../libs/JmWalletApi'
import { WalletBalanceSummary, toBalanceSummary } from './BalanceSummary'
+import { JM_API_AUTH_TOKEN_EXPIRY } from '../constants/config'
export interface CurrentWallet {
name: Api.WalletName
@@ -276,6 +277,44 @@ const WalletProvider = ({ children }: PropsWithChildren) => {
}
}, [currentWallet])
+ useEffect(() => {
+ if (!currentWallet) return
+
+ const abortCtrl = new AbortController()
+
+ const refreshToken = () => {
+ const session = getSession()
+ if (!session?.auth?.refresh_token) return
+
+ Api.postToken(
+ { token: session.auth.token, signal: abortCtrl.signal },
+ {
+ grant_type: 'refresh_token',
+ refresh_token: session.auth.refresh_token,
+ },
+ )
+ .then((res) => (res.ok ? res.json() : Api.Helper.throwError(res)))
+ .then((body) => {
+ const auth = {
+ token: body.token,
+ token_type: body.token_type,
+ expires_in: body.expires_in,
+ scope: body.scope,
+ refresh_token: body.refresh_token,
+ }
+ setSession({ name: currentWallet.name, auth })
+ setCurrentWallet({ ...currentWallet, token: auth.token })
+ })
+ .catch((err) => console.error(err))
+ }
+
+ const interval = setInterval(refreshToken, JM_API_AUTH_TOKEN_EXPIRY / 4)
+ return () => {
+ clearInterval(interval)
+ abortCtrl.abort()
+ }
+ }, [currentWallet, setCurrentWallet])
+
return (
`${window.JM.PUBLIC_PATH}/api`
-export type ApiToken = string
-export type WalletName = `${string}.jmdat`
+type ApiToken = string
+type WalletName = `${string}.jmdat`
-export type Mixdepth = number
-export type AmountSats = number // TODO: should be BigInt! Remove once every caller migrated to TypeScript.
-export type BitcoinAddress = string
+type Mixdepth = number
+type AmountSats = number // TODO: should be BigInt! Remove once every caller migrated to TypeScript.
+type BitcoinAddress = string
type Vout = number
-export type TxId = string
-export type UtxoId = `${TxId}:${Vout}`
+type TxId = string
+type UtxoId = `${TxId}:${Vout}`
// for JM versions <0.9.11
-export type SingleTokenAuthContext = {
+type SingleTokenAuthContext = {
token: ApiToken
+ refresh_token: undefined
}
// for JM versions >=0.9.11
-export type RefreshTokenAuthContext = SingleTokenAuthContext & {
+type RefreshTokenAuthContext = {
+ token: ApiToken
token_type: string // "bearer"
expires_in: Seconds // 1800
scope: string
refresh_token: ApiToken
}
-export type ApiAuthContext = SingleTokenAuthContext | RefreshTokenAuthContext
+type ApiAuthContext = SingleTokenAuthContext | RefreshTokenAuthContext
type WithWalletName = {
walletName: WalletName
@@ -48,7 +50,7 @@ type WithMixdepth = {
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type YYYY = `2${Digit}${Digit}${Digit}`
type MM = '01' | '02' | '03' | '04' | '05' | '06' | '07' | '08' | '09' | '10' | '11' | '12'
-export type Lockdate = `${YYYY}-${MM}`
+type Lockdate = `${YYYY}-${MM}`
type WithLockdate = {
lockdate: Lockdate
}
@@ -64,7 +66,7 @@ interface AuthApiRequestContext extends ApiRequestContext {
token: ApiToken
}
-export type WalletRequestContext = AuthApiRequestContext & WithWalletName
+type WalletRequestContext = AuthApiRequestContext & WithWalletName
interface ApiError {
message: string
@@ -135,12 +137,12 @@ interface ConfigGetRequest {
field: string
}
-export interface StartSchedulerRequest {
+interface StartSchedulerRequest {
destination_addresses: BitcoinAddress[]
tumbler_options?: TumblerOptions
}
-export interface TumblerOptions {
+interface TumblerOptions {
restart?: boolean
schedulefile?: string
addrcount?: number
@@ -490,7 +492,7 @@ const getRescanBlockchain = async ({
})
}
-export class JmApiError extends Error {
+class JmApiError extends Error {
public response: Response
constructor(response: Response, message: string) {
@@ -526,4 +528,16 @@ export {
getSchedule,
getRescanBlockchain,
Helper,
+ JmApiError,
+ ApiAuthContext,
+ StartSchedulerRequest,
+ WalletRequestContext,
+ ApiToken,
+ WalletName,
+ Lockdate,
+ TxId,
+ UtxoId,
+ Mixdepth,
+ AmountSats,
+ BitcoinAddress,
}