Skip to content

Commit 99082e7

Browse files
feat(ui-kit): Read Only feature implementation
Read Only feature implementation
2 parents b7819c4 + 60a02e3 commit 99082e7

File tree

5 files changed

+246
-16
lines changed

5 files changed

+246
-16
lines changed

packages/ui-kit/src/lib/components/LoginModal/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type LoginModalProps = {
2929
handleUserLogOutEvent: () => void;
3030
toggleButtonRef: React.RefObject<HTMLButtonElement>;
3131
sendMessageToPushWallet: (message: any) => void;
32+
isReadOnly: boolean;
3233
};
3334

3435
const LoginModal: FC<LoginModalProps> = ({
@@ -45,7 +46,8 @@ const LoginModal: FC<LoginModalProps> = ({
4546
handleUserLogOutEvent,
4647
config,
4748
toggleButtonRef,
48-
sendMessageToPushWallet
49+
sendMessageToPushWallet,
50+
isReadOnly
4951
}) => {
5052
const { modal } = config;
5153
const { pushChainClient } = usePushChainClient(config?.uid || 'default');
@@ -109,7 +111,7 @@ const LoginModal: FC<LoginModalProps> = ({
109111
$modalDefaults={modal}
110112
$style={{ top, left }}
111113
>
112-
{isIframeLoading && (
114+
{isIframeLoading && !isReadOnly && (
113115
<FrameLoadingContainer>
114116
<CloseButtonContainer
115117
onClick={() => {

packages/ui-kit/src/lib/constants/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export enum WALLET_TO_APP_ACTION {
3333

3434
APP_CONNECTION_SUCCESS = 'appConnectionSuccess',
3535
APP_CONNECTION_REJECTED = 'appConnectionRejected',
36+
APP_CONNECTION_CANCELLED = 'appConnectionCancelled',
3637

3738
IS_LOGGED_IN = 'isLoggedIn',
3839
IS_LOGGED_OUT = 'loggedOut',
@@ -59,6 +60,8 @@ export enum APP_TO_WALLET_ACTION {
5960
WALLET_CONFIG = 'walletConfig',
6061

6162
PUSH_SEND_TRANSACTION_RESPONSE = 'pushSendTransactionResponse',
63+
READ_ONLY_CONNECTION_STATUS = 'readOnlyConnectionStatus',
64+
RECONNECT_WALLET = 'ReconnectWallet',
6265
}
6366

6467
export const CHAIN_LOGO: Record<string, React.FC | React.ComponentType> = {

packages/ui-kit/src/lib/context/WalletContext.tsx

Lines changed: 117 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export type WalletContextType = {
4848
handleSignMessage: (data: Uint8Array) => Promise<Uint8Array>;
4949
handleSignAndSendTransaction: (data: Uint8Array) => Promise<Uint8Array>;
5050
handleSignTypedData: (data: ITypedData) => Promise<Uint8Array>;
51+
handleExternalWalletConnection: (data: {
52+
chain: ChainType;
53+
provider: IWalletProvider["name"];
54+
}) => Promise<void>;
5155

5256
config: PushWalletProviderProps['config'];
5357
app?: PushWalletProviderProps['app'];
@@ -63,6 +67,10 @@ export type WalletContextType = {
6367

6468
toggleButtonRef: React.RefObject<HTMLButtonElement>;
6569
setProgress: React.Dispatch<React.SetStateAction<ProgressEvent | null>>;
70+
71+
isReadOnly: boolean;
72+
setIsReadOnly: React.Dispatch<React.SetStateAction<boolean>>;
73+
requestPushWalletConnection: () => Promise<{ chain: ChainType; provider: IWalletProvider["name"] }>;
6674
};
6775

6876
export const WalletContext = createContext<WalletContextType | null>(null);
@@ -84,6 +92,8 @@ export const WalletContextProvider: FC<PushWalletProviderProps> = ({
8492

8593
const [isIframeLoading, setIframeLoading] = useState(true);
8694

95+
const [isReadOnly, setIsReadOnly] = useState(false);
96+
8797
const [connectionStatus, setConnectionStatus] = useState<
8898
WalletContextType['connectionStatus']
8999
>(ConnectionStatus.NOT_CONNECTED);
@@ -163,6 +173,7 @@ export const WalletContextProvider: FC<PushWalletProviderProps> = ({
163173
setMinimiseWallet(false);
164174
setWalletVisibility(false);
165175
setIframeLoading(true);
176+
localStorage.removeItem("walletInfo");
166177
};
167178

168179
// sending events to wallet from dapp
@@ -196,9 +207,14 @@ export const WalletContextProvider: FC<PushWalletProviderProps> = ({
196207
const handleAppConnectionSuccess = (response: WalletEventRespoonse) => {
197208
setConnectionStatus(ConnectionStatus.CONNECTED);
198209
setMinimiseWallet(true);
210+
// setIsReadOnly(false);
199211
if (response.account) {
200212
setUniversalAccount(response.account);
201213
}
214+
localStorage.setItem(
215+
"walletInfo",
216+
JSON.stringify(response.account)
217+
);
202218
};
203219

204220
const handleAppConnectionRejection = () => {
@@ -239,6 +255,11 @@ export const WalletContextProvider: FC<PushWalletProviderProps> = ({
239255
chainType: data.chain,
240256
};
241257

258+
localStorage.setItem(
259+
"walletInfo",
260+
JSON.stringify(connectedWallet)
261+
);
262+
242263
setExternalWallet(connectedWallet);
243264

244265
sendMessageToPushWallet({
@@ -457,8 +478,97 @@ export const WalletContextProvider: FC<PushWalletProviderProps> = ({
457478
return signature;
458479
};
459480

460-
// eslint-disable-next-line @typescript-eslint/no-empty-function
461-
const handleCloseIFrame = useCallback(() => {}, [universalAccount]);
481+
const getAuthWindowConfig = () => {
482+
// Calculate the screen width and height
483+
const screenWidth = window.screen.width;
484+
const screenHeight = window.screen.height;
485+
486+
const width = 500;
487+
const height = 600;
488+
489+
// Calculate the position to center the window
490+
const left = (screenWidth - width) / 2;
491+
const top = (screenHeight - height) / 2;
492+
493+
// Open a new window with the calculated position
494+
const windowFeatures = `width=${width},height=${height},left=${left},top=${top},resizable,scrollbars`;
495+
496+
return windowFeatures;
497+
};
498+
499+
const requestPushWalletConnection = () => {
500+
setMinimiseWallet(false);
501+
sendMessageToPushWallet({
502+
type: APP_TO_WALLET_ACTION.RECONNECT_WALLET,
503+
});
504+
505+
// Wait for a response from the Push Wallet iframe
506+
return new Promise<{ chain: ChainType; provider: IWalletProvider["name"] }>((resolve, reject) => {
507+
const handleMessage = (event: MessageEvent) => {
508+
if (event.data.type === WALLET_TO_APP_ACTION.APP_CONNECTION_SUCCESS) {
509+
window.removeEventListener('message', handleMessage);
510+
if (event.data.error) {
511+
reject(new Error(event.data.error));
512+
} else {
513+
resolve(event.data);
514+
}
515+
}
516+
if (event.data.type === WALLET_TO_APP_ACTION.APP_CONNECTION_CANCELLED) {
517+
window.removeEventListener('message', handleMessage);
518+
reject(new Error('Push Wallet connection failed'));
519+
setMinimiseWallet(true);
520+
}
521+
};
522+
523+
window.addEventListener('message', handleMessage);
524+
525+
setTimeout(() => {
526+
window.removeEventListener('message', handleMessage);
527+
reject(new Error('Push Wallet connection timed out'));
528+
setMinimiseWallet(true);
529+
}, 100000);
530+
});
531+
}
532+
533+
useEffect(() => {
534+
const walletInfo = localStorage.getItem("walletInfo");
535+
const walletData = walletInfo ? JSON.parse(walletInfo) : null;
536+
if (!walletData) return;
537+
if (walletData.providerName) {
538+
setUniversalAccount(PushChain.utils.account.fromChainAgnostic(
539+
walletData.address
540+
));
541+
setExternalWallet(walletData);
542+
} else {
543+
setUniversalAccount(walletData);
544+
}
545+
setIsReadOnly(true);
546+
setMinimiseWallet(true);
547+
setWalletVisibility(true);
548+
setConnectionStatus(ConnectionStatus.CONNECTED);
549+
}, []);
550+
551+
useEffect(() => {
552+
if (isIframeLoading) return;
553+
if (!isReadOnly) return;
554+
if (externalWallet) {
555+
sendMessageToPushWallet({
556+
type: APP_TO_WALLET_ACTION.READ_ONLY_CONNECTION_STATUS,
557+
data: {
558+
status: 'successful',
559+
...externalWallet,
560+
},
561+
});
562+
} else if (universalAccount) {
563+
sendMessageToPushWallet({
564+
type: APP_TO_WALLET_ACTION.READ_ONLY_CONNECTION_STATUS,
565+
data: {
566+
status: 'successful',
567+
...universalAccount,
568+
},
569+
});
570+
}
571+
}, [isIframeLoading])
462572

463573
useEffect(() => {
464574
const messageHandler = (event: MessageEvent) => {
@@ -516,15 +626,6 @@ export const WalletContextProvider: FC<PushWalletProviderProps> = ({
516626

517627
const WalletContext = getWalletContext(config?.uid || 'default');
518628

519-
const dummyProgress: ProgressEvent = {
520-
id: PROGRESS_HOOK.SEND_TX_99_01,
521-
title: 'Push Chain Tx Success',
522-
message: '',
523-
level: 'SUCCESS',
524-
response: null,
525-
timestamp: '',
526-
};
527-
528629
return (
529630
<WalletContext.Provider
530631
value={{
@@ -547,6 +648,10 @@ export const WalletContextProvider: FC<PushWalletProviderProps> = ({
547648
handleSignTypedData,
548649
toggleButtonRef,
549650
setProgress,
651+
isReadOnly,
652+
setIsReadOnly,
653+
handleExternalWalletConnection,
654+
requestPushWalletConnection,
550655
}}
551656
>
552657
<LoginModal
@@ -564,6 +669,7 @@ export const WalletContextProvider: FC<PushWalletProviderProps> = ({
564669
handleUserLogOutEvent={handleUserLogOutEvent}
565670
toggleButtonRef={toggleButtonRef}
566671
sendMessageToPushWallet={sendMessageToPushWallet}
672+
isReadOnly={isReadOnly}
567673
/>
568674
{progress && (
569675
<PushWalletToast progress={progress} setProgress={setProgress} />
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { PushChain } from "@pushchain/core";
2+
import { UniversalSigner } from "@pushchain/core/src/lib/universal/universal.types";
3+
import { ChainType, IWalletProvider } from "../types";
4+
5+
export function createGuardedPushChain(
6+
baseClient: PushChain,
7+
handleExternalWalletConnection: (data: {
8+
chain: ChainType;
9+
provider: IWalletProvider["name"];
10+
}) => Promise<void>,
11+
requestPushWalletConnection: () => Promise<{
12+
chain: ChainType;
13+
provider: IWalletProvider["name"];
14+
}>,
15+
universalSigner: UniversalSigner,
16+
intializeProps: any,
17+
callback?: () => void,
18+
): PushChain {
19+
const clientRef: { current: PushChain } = { current: baseClient };
20+
21+
let promoting: Promise<void> | null = null;
22+
23+
const promoteIfNeeded = async () => {
24+
if (!clientRef.current.isReadMode) return;
25+
26+
if (!promoting) {
27+
promoting = (async () => {
28+
const walletInfo = localStorage.getItem("walletInfo");
29+
const walletData = walletInfo ? JSON.parse(walletInfo) : null;
30+
31+
if (!walletData) {
32+
return;
33+
}
34+
35+
if (walletData.providerName) {
36+
await handleExternalWalletConnection({
37+
chain: walletData.chainType,
38+
provider: walletData.providerName
39+
});
40+
} else {
41+
await requestPushWalletConnection();
42+
}
43+
44+
const pushChainClient = await clientRef.current.reinitialize(universalSigner, intializeProps);
45+
46+
callback?.();
47+
48+
clientRef.current = pushChainClient;
49+
50+
})().finally(() => {
51+
promoting = null;
52+
});
53+
}
54+
await promoting;
55+
};
56+
57+
const wrapWrite = <A extends unknown[], R>(
58+
getter: () => (...args: A) => Promise<R>
59+
) => {
60+
const wrapped = async (...args: A): Promise<R> => {
61+
await promoteIfNeeded();
62+
const fn = getter();
63+
return fn(...args);
64+
};
65+
return wrapped;
66+
};
67+
68+
const universalProxy = new Proxy({} as PushChain["universal"], {
69+
get(_t, p, _r) {
70+
const u = clientRef.current.universal;
71+
if (p === "sendTransaction") {
72+
return wrapWrite(() => clientRef.current.universal.sendTransaction);
73+
}
74+
if (p === "signMessage") {
75+
return wrapWrite(() => clientRef.current.universal.signMessage);
76+
}
77+
if (p === "signTypedData") {
78+
return wrapWrite(() => clientRef.current.universal.signTypedData);
79+
}
80+
// @ts-expect-error: index access for dynamic property
81+
return u[p];
82+
},
83+
});
84+
85+
const clientProxy = new Proxy(baseClient, {
86+
get(_target, prop, _receiver) {
87+
if (prop === "universal") return universalProxy;
88+
// @ts-expect-error: index access for dynamic property
89+
return clientRef.current[prop];
90+
},
91+
});
92+
93+
return clientProxy;
94+
}

0 commit comments

Comments
 (0)