Skip to content

Commit

Permalink
wallet-ext: ui hooks for qredo (MystenLabs#12018)
Browse files Browse the repository at this point in the history
* update bg client for new messages
* use hooks for api and data from bg service - use queries for that data
that invalidate using messages from bg service
* allow queries bypass persisted data using metadata

part of APPS-760
  • Loading branch information
pchrysochoidis authored May 17, 2023
1 parent 1c604c2 commit e4ab6d1
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 2 deletions.
83 changes: 83 additions & 0 deletions apps/wallet/src/ui/app/background-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import { setActiveOrigin, changeActiveNetwork } from '_redux/slices/app';
import { setPermissions } from '_redux/slices/permissions';
import { setTransactionRequests } from '_redux/slices/transaction-requests';
import { type SerializedLedgerAccount } from '_src/background/keyring/LedgerAccount';
import {
isQredoConnectPayload,
type QredoConnectPayload,
} from '_src/shared/messaging/messages/payloads/QredoConnect';

import type { SuiAddress, SuiTransactionBlockResponse } from '@mysten/sui.js';
import type { Message } from '_messages';
Expand Down Expand Up @@ -364,6 +368,85 @@ export class BackgroundClient {
);
}

public fetchPendingQredoConnectRequest(requestID: string) {
return lastValueFrom(
this.sendMessage(
createMessage<QredoConnectPayload<'getPendingRequest'>>({
type: 'qredo-connect',
method: 'getPendingRequest',
args: { requestID },
})
).pipe(
take(1),
map(({ payload }) => {
if (
isQredoConnectPayload(
payload,
'getPendingRequestResponse'
)
) {
return payload.args.request;
}
throw new Error(
'Error unknown response for fetch pending qredo requests message'
);
})
)
);
}

public getQredoConnectionInfo(qredoID: string, refreshAccessToken = false) {
return lastValueFrom(
this.sendMessage(
createMessage<QredoConnectPayload<'getQredoInfo'>>({
type: 'qredo-connect',
method: 'getQredoInfo',
args: { qredoID, refreshAccessToken },
})
).pipe(
take(1),
map(({ payload }) => {
if (
isQredoConnectPayload(payload, 'getQredoInfoResponse')
) {
return payload.args;
}
throw new Error(
'Error unknown response for get qredo info message'
);
})
)
);
}

public acceptQredoConnection(
args: QredoConnectPayload<'acceptQredoConnection'>['args']
) {
return lastValueFrom(
this.sendMessage(
createMessage<QredoConnectPayload<'acceptQredoConnection'>>({
type: 'qredo-connect',
method: 'acceptQredoConnection',
args,
})
).pipe(take(1))
);
}

public rejectQredoConnection(
args: QredoConnectPayload<'rejectQredoConnection'>['args']
) {
return lastValueFrom(
this.sendMessage(
createMessage<QredoConnectPayload<'rejectQredoConnection'>>({
type: 'qredo-connect',
method: 'rejectQredoConnection',
args,
})
).pipe(take(1))
);
}

private setupAppStatusUpdateInterval() {
setInterval(() => {
this.sendAppStatus();
Expand Down
2 changes: 1 addition & 1 deletion apps/wallet/src/ui/app/helpers/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const queryClient = new QueryClient({
function createIDBPersister(idbValidKey: IDBValidKey) {
return {
persistClient: async (client: PersistedClient) => {
set(idbValidKey, client);
await set(idbValidKey, client);
},
restoreClient: async () => {
return await get<PersistedClient>(idbValidKey);
Expand Down
55 changes: 55 additions & 0 deletions apps/wallet/src/ui/app/hooks/useQredoAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useEffect, useState } from 'react';

import { useBackgroundClient } from './useBackgroundClient';
import { useQredoInfo } from './useQredoInfo';
import { QredoAPI } from '_src/shared/qredo-api';

const API_INSTANCES: Record<string, QredoAPI> = {};

export function useQredoAPI(qredoID?: string) {
const backgroundClient = useBackgroundClient();
const { data, isLoading, error } = useQredoInfo(qredoID);
const [api, setAPI] = useState(
() => (qredoID && API_INSTANCES[qredoID]) || null
);
useEffect(() => {
if (
data?.qredoInfo?.apiUrl &&
data?.qredoInfo?.accessToken &&
qredoID
) {
const instance = API_INSTANCES[qredoID];
if (
instance &&
instance.accessToken !== data.qredoInfo.accessToken
) {
instance.accessToken = data.qredoInfo.accessToken;
} else if (!instance) {
API_INSTANCES[qredoID] = new QredoAPI(
qredoID,
data.qredoInfo.apiUrl,
{
accessTokenRenewalFN: async (qredoID) =>
(
await backgroundClient.getQredoConnectionInfo(
qredoID,
true
)
).qredoInfo?.accessToken || null,
accessToken: data.qredoInfo.accessToken,
}
);
}
}
setAPI((qredoID && API_INSTANCES[qredoID]) || null);
}, [
backgroundClient,
data?.qredoInfo?.apiUrl,
data?.qredoInfo?.accessToken,
qredoID,
]);
return [api, isLoading, error] as const;
}
18 changes: 18 additions & 0 deletions apps/wallet/src/ui/app/hooks/useQredoInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useQuery } from '@tanstack/react-query';

import { useBackgroundClient } from './useBackgroundClient';

export function useQredoInfo(qredoID?: string) {
const backgroundClient = useBackgroundClient();
return useQuery({
queryKey: ['qredo', 'info', qredoID],
queryFn: async () => backgroundClient.getQredoConnectionInfo(qredoID!),
enabled: !!qredoID,
staleTime: 0,
refetchInterval: 1000,
meta: { skipPersistedCache: true },
});
}
45 changes: 45 additions & 0 deletions apps/wallet/src/ui/app/pages/qredo-connect/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useQuery } from '@tanstack/react-query';

import { useBackgroundClient } from '../../hooks/useBackgroundClient';
import { useQredoAPI } from '../../hooks/useQredoAPI';
import { type GetWalletsParams } from '_src/shared/qredo-api';

export function useQredoUIPendingRequest(requestID?: string) {
const backgroundClient = useBackgroundClient();
return useQuery({
queryKey: ['qredo-connect', 'pending-request', requestID],
queryFn: async () =>
await backgroundClient.fetchPendingQredoConnectRequest(requestID!),
staleTime: 0,
refetchInterval: 1000,
enabled: !!requestID,
meta: { skipPersistedCache: true },
});
}

export function useFetchQredoAccounts(
qredoID: string,
enabled?: boolean,
params?: GetWalletsParams
) {
const [api, isAPILoading, apiInitError] = useQredoAPI(qredoID);
return useQuery({
queryKey: ['qredo', 'fetch', 'accounts', qredoID, api, apiInitError],
queryFn: async () => {
if (api) {
return (await api.getWallets(params)).wallets;
}
throw apiInitError
? apiInitError
: new Error('Qredo API initialization failed');
},
enabled:
!!qredoID &&
(enabled ?? true) &&
!isAPILoading &&
!!(api || apiInitError),
});
}
18 changes: 18 additions & 0 deletions apps/wallet/src/ui/app/pages/qredo-connect/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { type UIQredoPendingRequest } from '_src/background/qredo/types';

export function isUntrustedQredoConnect({
apiUrl,
origin,
}: UIQredoPendingRequest) {
try {
return (
new URL(origin).protocol !== 'https:' ||
new URL(apiUrl).protocol !== 'https:'
);
} catch (e) {
return false;
}
}
8 changes: 7 additions & 1 deletion apps/wallet/src/ui/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ function AppWrapper() {
<Fragment key={network}>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
persistOptions={{
persister,
dehydrateOptions: {
shouldDehydrateQuery: ({ meta }) =>
!meta?.skipPersistedCache,
},
}}
>
<RpcClientContext.Provider
value={api.instance.fullNode}
Expand Down

0 comments on commit e4ab6d1

Please sign in to comment.