@gluwa/connect-kit 0.2.0-next.7 → 0.2.0-next.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/{chunk-EDNRW47A.js → chunk-VE5LB7BN.js} +4 -0
- package/dist/credit-connect.js +1 -1
- package/dist/index.css +33 -34
- package/dist/index.js +212 -82
- package/dist/package.json +1 -1
- package/package.json +6 -6
- package/src/components/qrArea/index.tsx +3 -1
- package/src/components/qrArea/style.scss +25 -33
- package/src/creditConnectConnector.ts +6 -0
- package/src/hooks/useConnectTimeout.ts +82 -0
- package/src/hooks/useMetaMaskExtension.ts +29 -0
- package/src/hooks/useWCState.ts +7 -8
- package/src/hooks/useWagmiConnect.ts +9 -7
- package/src/layout/allWallets/index.tsx +1 -1
- package/src/layout/allWallets/style.scss +2 -7
- package/src/layout/connectDialog/index.tsx +74 -11
- package/src/layout/connectDialog/style.scss +2 -1
- package/src/layout/detailPanel/index.tsx +4 -1
- package/src/layout/detailPanel/style.scss +5 -0
- package/src/utils/platform.ts +0 -4
- package/src/wallet-display-override.ts +15 -0
|
@@ -9,13 +9,6 @@
|
|
|
9
9
|
padding-bottom: 32px;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
// .ck-qr-group {
|
|
13
|
-
// display: flex;
|
|
14
|
-
// flex-direction: column;
|
|
15
|
-
// align-items: center;
|
|
16
|
-
// gap: 24px;
|
|
17
|
-
// }
|
|
18
|
-
|
|
19
12
|
// ── Text ──────────────────────────────────────────────────────────────────────
|
|
20
13
|
.ck-qr-text {
|
|
21
14
|
display: flex;
|
|
@@ -42,18 +35,16 @@
|
|
|
42
35
|
outline: none;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
.ck-qr-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.ck-qr-timeout {
|
|
38
|
+
.ck-qr-skeleton {
|
|
39
|
+
padding: 20px;
|
|
50
40
|
border: 1px solid var(--semantic-divider-divider-1);
|
|
41
|
+
border-radius: 20px;
|
|
51
42
|
}
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
width:
|
|
56
|
-
height:
|
|
44
|
+
.ck-qr-skeleton-inner {
|
|
45
|
+
display: block;
|
|
46
|
+
width: 246px;
|
|
47
|
+
height: 246px;
|
|
57
48
|
background: linear-gradient(
|
|
58
49
|
132deg,
|
|
59
50
|
var(--scale-gray-100) 36%,
|
|
@@ -64,8 +55,24 @@
|
|
|
64
55
|
animation: ck-qr-shimmer 1.5s linear infinite;
|
|
65
56
|
|
|
66
57
|
@include tablet {
|
|
67
|
-
width:
|
|
68
|
-
height:
|
|
58
|
+
width: 198px;
|
|
59
|
+
height: 198px;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Timeout ───────────────────────────────────────────────────────────────────
|
|
64
|
+
.ck-qr-timeout {
|
|
65
|
+
width: 288px;
|
|
66
|
+
height: 288px;
|
|
67
|
+
border: 1px solid var(--semantic-divider-divider-1);
|
|
68
|
+
background: var(--scale-gray-100);
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
|
|
73
|
+
@include tablet {
|
|
74
|
+
width: 240px;
|
|
75
|
+
height: 240px;
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -93,21 +100,6 @@
|
|
|
93
100
|
}
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
// ── Timeout ───────────────────────────────────────────────────────────────────
|
|
97
|
-
.ck-qr-timeout {
|
|
98
|
-
width: 248px;
|
|
99
|
-
height: 248px;
|
|
100
|
-
background: var(--scale-gray-100);
|
|
101
|
-
display: flex;
|
|
102
|
-
align-items: center;
|
|
103
|
-
justify-content: center;
|
|
104
|
-
|
|
105
|
-
@include tablet {
|
|
106
|
-
width: 200px;
|
|
107
|
-
height: 200px;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
103
|
.ck-qr-timeout-inner {
|
|
112
104
|
display: flex;
|
|
113
105
|
flex-direction: column;
|
|
@@ -107,6 +107,12 @@ export const creditConnectConnector = (
|
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
manager.on('sessionExpired', (payload) => {
|
|
111
|
+
// eslint-disable-next-line no-console
|
|
112
|
+
console.log('[CC connector] session expired → emit session_expired', payload);
|
|
113
|
+
emitWagmiMessage?.('session_expired', payload);
|
|
114
|
+
});
|
|
115
|
+
|
|
110
116
|
return manager;
|
|
111
117
|
};
|
|
112
118
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { type ConnectorId } from '../types';
|
|
3
|
+
|
|
4
|
+
// MM / CC 폴백용 기본 타임아웃. (CC 는 원칙적으로 서버 push 로 만료가 오지만,
|
|
5
|
+
// 서버 값이 없거나 신호가 유실된 경우를 대비한 폴백 용도로도 쓸 수 있음.)
|
|
6
|
+
export const DEFAULT_CONNECT_TIMEOUT_MS = 600_000;
|
|
7
|
+
|
|
8
|
+
interface UseConnectTimeoutParams {
|
|
9
|
+
/** 현재 연결 시도 중인 커넥터. null = 유휴 상태. */
|
|
10
|
+
connectorId: ConnectorId | null;
|
|
11
|
+
/** 타임아웃 감시 활성화 여부 — pending/QR 표시 중일 때 true. */
|
|
12
|
+
active: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* 외부 이벤트 기반 만료 신호 (MM 제외). 값이 증가할 때마다 "만료 발생" 으로 간주.
|
|
15
|
+
* - CC: dialog 가 connector 의 `session_expired`(서버 push) 를 받아 올림.
|
|
16
|
+
* - WC: dialog 가 WC connect promise reject(프로토콜 ~5분 만료 등) 를 받아 올림.
|
|
17
|
+
*/
|
|
18
|
+
expiredSignal?: number;
|
|
19
|
+
/** "Try Again" 시 실행할 재연결 로직 (dialog 가 주입). */
|
|
20
|
+
onRetry?: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface UseConnectTimeoutResult {
|
|
24
|
+
/** 타임아웃 발생 여부 — true 면 dialog 가 <Timeout> 을 렌더. */
|
|
25
|
+
timedOut: boolean;
|
|
26
|
+
/** "Try Again" 버튼에 연결할 함수 — 상태 리셋 + onRetry 실행. */
|
|
27
|
+
retry: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 커넥터별로 타임아웃 신호원을 분기해 하나의 `timedOut` 으로 수렴시키는 훅.
|
|
32
|
+
* - CC: 서버 push (`OnSessionExpired` → connector → expiredSignal).
|
|
33
|
+
* - MM: 네이티브 타임아웃 없음 → client 타이머(DEFAULT_CONNECT_TIMEOUT_MS).
|
|
34
|
+
* - WC: 프로토콜 자체 ~5분 proposal reject → connect promise reject → expiredSignal.
|
|
35
|
+
*/
|
|
36
|
+
export const useConnectTimeout = ({
|
|
37
|
+
connectorId,
|
|
38
|
+
active,
|
|
39
|
+
expiredSignal = 0,
|
|
40
|
+
onRetry,
|
|
41
|
+
}: UseConnectTimeoutParams): UseConnectTimeoutResult => {
|
|
42
|
+
const [timedOut, setTimedOut] = useState(false);
|
|
43
|
+
|
|
44
|
+
const fire = useCallback(() => {
|
|
45
|
+
setTimedOut(true);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
// 커넥터가 바뀌면(=새 연결 시도) 타임아웃 상태 리셋.
|
|
49
|
+
// active 가 false 로 떨어질 때는 리셋하지 않는다 — timeout UI 표시 중 상태가 날아가지 않도록.
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
setTimedOut(false);
|
|
52
|
+
}, [connectorId]);
|
|
53
|
+
|
|
54
|
+
// MM 만 client 타이머로 강제 (승인 대기에 네이티브 상한이 없음).
|
|
55
|
+
// CC(서버 push) / WC(connect reject) 는 아래 expiredSignal effect 에서 처리.
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!active || connectorId !== 'METAMASK') return;
|
|
58
|
+
const timer = setTimeout(fire, DEFAULT_CONNECT_TIMEOUT_MS);
|
|
59
|
+
return () => clearTimeout(timer);
|
|
60
|
+
}, [active, connectorId, fire]);
|
|
61
|
+
|
|
62
|
+
// 외부 만료 신호 처리 (CC 서버 push / WC connect reject) — MM 은 client 타이머라 제외.
|
|
63
|
+
// signal 값이 올라가면(새 만료 이벤트) 발화.
|
|
64
|
+
const prevSignal = useRef(expiredSignal);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!active || connectorId === 'METAMASK' || !connectorId) {
|
|
67
|
+
prevSignal.current = expiredSignal;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (expiredSignal !== prevSignal.current) {
|
|
71
|
+
prevSignal.current = expiredSignal;
|
|
72
|
+
fire();
|
|
73
|
+
}
|
|
74
|
+
}, [expiredSignal, connectorId, active, fire]);
|
|
75
|
+
|
|
76
|
+
const retry = useCallback(() => {
|
|
77
|
+
setTimedOut(false);
|
|
78
|
+
onRetry?.();
|
|
79
|
+
}, [onRetry]);
|
|
80
|
+
|
|
81
|
+
return { timedOut, retry };
|
|
82
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const METAMASK_RDNS = 'io.metamask';
|
|
4
|
+
|
|
5
|
+
// EIP-6963 응답의 rdns 로 MetaMask 확장을 식별.
|
|
6
|
+
const isMetaMaskAnnouncement = (event: Event): boolean => {
|
|
7
|
+
const detail = (event as CustomEvent<{ info?: { rdns?: string }; provider?: unknown }>).detail;
|
|
8
|
+
return detail?.info?.rdns?.toLowerCase() === METAMASK_RDNS && detail.provider != null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const useMetaMaskExtension = (): boolean => {
|
|
12
|
+
const [isInstalled, setIsInstalled] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleAnnouncement = (event: Event): void => {
|
|
16
|
+
if (isMetaMaskAnnouncement(event)) setIsInstalled(true);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// 응답 listener를 먼저 등록한 뒤 설치된 provider 목록을 요청.
|
|
20
|
+
window.addEventListener('eip6963:announceProvider', handleAnnouncement);
|
|
21
|
+
window.dispatchEvent(new Event('eip6963:requestProvider'));
|
|
22
|
+
|
|
23
|
+
return () => {
|
|
24
|
+
window.removeEventListener('eip6963:announceProvider', handleAnnouncement);
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return isInstalled;
|
|
29
|
+
};
|
package/src/hooks/useWCState.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
2
|
import type { WCWallet, WCSubView } from '../types';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// CONNECTOR_META.CREDIT_CONNECT.label 은 selector button 표시용 ('Continue with Credit Wallet')
|
|
6
|
-
// 이라 다름 — 별도 상수로 분리.
|
|
7
|
-
const WC_EXPLORER_CREDIT_WALLET_NAME = 'Credit Wallet';
|
|
3
|
+
// TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
|
|
4
|
+
import { CREDIT_WALLET_LISTING_ID, overrideWalletDisplay } from '../wallet-display-override';
|
|
8
5
|
|
|
9
6
|
const sortCreditWalletFirst = (list: WCWallet[]): WCWallet[] =>
|
|
10
7
|
[...list].sort((a, b) => {
|
|
11
|
-
|
|
12
|
-
if (
|
|
8
|
+
// TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
|
|
9
|
+
if (a.id === CREDIT_WALLET_LISTING_ID) return -1;
|
|
10
|
+
if (b.id === CREDIT_WALLET_LISTING_ID) return 1;
|
|
13
11
|
return 0;
|
|
14
12
|
});
|
|
15
13
|
|
|
@@ -115,7 +113,8 @@ export const useWCState = (): WCState => {
|
|
|
115
113
|
setWalletListLoading(true);
|
|
116
114
|
try {
|
|
117
115
|
const list = await fetchWCWalletList(projectId, controller.signal);
|
|
118
|
-
|
|
116
|
+
// TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
|
|
117
|
+
const sorted = sortCreditWalletFirst(list).map(overrideWalletDisplay);
|
|
119
118
|
// Credit Wallet 을 최상단으로 정렬한 상태로 state 저장 — view 측은 정렬 신경 X.
|
|
120
119
|
if (isMountedRef.current) setWalletList(sorted);
|
|
121
120
|
} catch (err) {
|
|
@@ -13,15 +13,10 @@ const hasMetaMaskKeyword = (value: string): boolean => value.toLowerCase().inclu
|
|
|
13
13
|
const hasWalletConnectKeyword = (value: string): boolean =>
|
|
14
14
|
value.toLowerCase().includes('walletconnect');
|
|
15
15
|
|
|
16
|
+
// 전역 provider 대신 wagmi connector 메타데이터로 MetaMask를 판별.
|
|
16
17
|
const isMetaMaskConnector = (connector: WagmiConnectorLike): boolean => {
|
|
17
18
|
if (connector.id === 'metaMaskSDK') return true;
|
|
18
19
|
|
|
19
|
-
if (connector.id === 'injected') {
|
|
20
|
-
if (typeof window === 'undefined') return false;
|
|
21
|
-
const eth = (window as Window & { ethereum?: { isMetaMask?: boolean } }).ethereum;
|
|
22
|
-
return eth?.isMetaMask === true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
20
|
if (hasMetaMaskKeyword(connector.id) || hasMetaMaskKeyword(connector.name)) return true;
|
|
26
21
|
|
|
27
22
|
if (connector.rdns) {
|
|
@@ -61,6 +56,7 @@ interface Options {
|
|
|
61
56
|
onConnect: (result: ConnectResult) => void;
|
|
62
57
|
onError: (error: Error, connectorId: ConnectorId) => void;
|
|
63
58
|
onQrUri: (uri: string | null) => void;
|
|
59
|
+
onSessionExpired?: () => void;
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
interface UseWagmiConnectResult {
|
|
@@ -73,6 +69,7 @@ export const useWagmiConnect = ({
|
|
|
73
69
|
onConnect,
|
|
74
70
|
onError,
|
|
75
71
|
onQrUri,
|
|
72
|
+
onSessionExpired,
|
|
76
73
|
}: Options): UseWagmiConnectResult => {
|
|
77
74
|
const config = useConfig();
|
|
78
75
|
const pendingConnectorId = useRef<ConnectorId | null>(null);
|
|
@@ -81,11 +78,13 @@ export const useWagmiConnect = ({
|
|
|
81
78
|
const onConnectRef = useRef(onConnect);
|
|
82
79
|
const onErrorRef = useRef(onError);
|
|
83
80
|
const onQrUriRef = useRef(onQrUri);
|
|
81
|
+
const onSessionExpiredRef = useRef(onSessionExpired);
|
|
84
82
|
useEffect(() => {
|
|
85
83
|
onConnectRef.current = onConnect;
|
|
86
84
|
onErrorRef.current = onError;
|
|
87
85
|
onQrUriRef.current = onQrUri;
|
|
88
|
-
|
|
86
|
+
onSessionExpiredRef.current = onSessionExpired;
|
|
87
|
+
}, [onConnect, onError, onQrUri, onSessionExpired]);
|
|
89
88
|
|
|
90
89
|
const { connectAsync, reset, isPending } = useConnect();
|
|
91
90
|
|
|
@@ -95,6 +94,9 @@ export const useWagmiConnect = ({
|
|
|
95
94
|
if (msg?.type === 'display_uri' && typeof msg.data === 'string') {
|
|
96
95
|
onQrUriRef.current(msg.data);
|
|
97
96
|
}
|
|
97
|
+
if (msg?.type === 'session_expired') {
|
|
98
|
+
onSessionExpiredRef.current?.();
|
|
99
|
+
}
|
|
98
100
|
};
|
|
99
101
|
|
|
100
102
|
const unsubscribers = config.connectors.map((connector) => {
|
|
@@ -42,7 +42,7 @@ export const AllWallets: FC<AllWalletsProps> = ({ wallets, isLoading, onSelectWa
|
|
|
42
42
|
</div>
|
|
43
43
|
{!isLoading && filtered.length === 0 ? (
|
|
44
44
|
<div className="ck-all-wallets-empty" role="status">
|
|
45
|
-
<p>No
|
|
45
|
+
<p>No Wallet found</p>
|
|
46
46
|
</div>
|
|
47
47
|
) : (
|
|
48
48
|
<WalletGrid wallets={filtered} isLoading={isLoading} onSelect={onSelectWallet} />
|
|
@@ -12,14 +12,11 @@ import { SelectorPanel } from '../selectorPanel';
|
|
|
12
12
|
import { DetailPanel } from '../detailPanel';
|
|
13
13
|
import { Snackbar } from '../../components/snackbar';
|
|
14
14
|
import { resolveConnectorId } from '../../connector-meta';
|
|
15
|
+
import { useMetaMaskExtension } from '../../hooks/useMetaMaskExtension';
|
|
15
16
|
import { useWagmiConnect } from '../../hooks/useWagmiConnect';
|
|
17
|
+
import { useConnectTimeout } from '../../hooks/useConnectTimeout';
|
|
16
18
|
import { useWCState } from '../../hooks/useWCState';
|
|
17
|
-
import {
|
|
18
|
-
detectMetaMaskExtension,
|
|
19
|
-
isMobileDevice,
|
|
20
|
-
tryOpenDeepLink,
|
|
21
|
-
tryOpenCreditWalletDeeplink,
|
|
22
|
-
} from '../../utils/platform';
|
|
19
|
+
import { isMobileDevice, tryOpenDeepLink, tryOpenCreditWalletDeeplink } from '../../utils/platform';
|
|
23
20
|
|
|
24
21
|
export type DialogStep = 'selector' | 'detail';
|
|
25
22
|
|
|
@@ -47,6 +44,13 @@ const getContentForConnector = (id: ConnectorId): DetailContent => {
|
|
|
47
44
|
return 'idle';
|
|
48
45
|
};
|
|
49
46
|
|
|
47
|
+
// MM 은 확장 감지 시 즉시 extension 연결, 아니면 QR. (select / retry 공통)
|
|
48
|
+
const getInitialContentForConnector = (
|
|
49
|
+
id: ConnectorId,
|
|
50
|
+
isMetaMaskExtensionInstalled: boolean,
|
|
51
|
+
): DetailContent =>
|
|
52
|
+
id === 'METAMASK' && !isMetaMaskExtensionInstalled ? 'metamask-qr' : getContentForConnector(id);
|
|
53
|
+
|
|
50
54
|
export const ConnectDialog: FC<ConnectDialogProps> = ({
|
|
51
55
|
open,
|
|
52
56
|
theme = 'dark',
|
|
@@ -83,6 +87,16 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
|
|
|
83
87
|
const [content, setContent] = useState<DetailContent>('idle');
|
|
84
88
|
const [showCopySnackbar, setShowCopySnackbar] = useState(false);
|
|
85
89
|
const [qrUri, setQrUri] = useState<string | null>(null);
|
|
90
|
+
// 외부 만료 신호 카운터 — CC(서버 push session_expired) / WC(connect reject) 수신 시 증가.
|
|
91
|
+
const [expiredSignal, setExpiredSignal] = useState(0);
|
|
92
|
+
// EIP-6963 기반 MetaMask 확장 설치 상태.
|
|
93
|
+
const isMetaMaskExtensionInstalled = useMetaMaskExtension();
|
|
94
|
+
|
|
95
|
+
// 현재 content 를 ref 로 추적 (effect deps 없이 최신값 참조용).
|
|
96
|
+
const contentRef = useRef<DetailContent>(content);
|
|
97
|
+
contentRef.current = content;
|
|
98
|
+
// 타임아웃 직전 화면 — retry 시 이 화면으로 복원 (예: WC 의 wallet-qr).
|
|
99
|
+
const preTimeoutContentRef = useRef<DetailContent>('idle');
|
|
86
100
|
|
|
87
101
|
const config = useConfig();
|
|
88
102
|
|
|
@@ -95,10 +109,61 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
|
|
|
95
109
|
setRecentConnector(result.connectorId);
|
|
96
110
|
onConnect?.(result);
|
|
97
111
|
},
|
|
98
|
-
onError: (err, connectorId) =>
|
|
112
|
+
onError: (err, connectorId) => {
|
|
113
|
+
// WC: connect reject(사용자 거절 / 프로토콜 ~5분 만료 / 네트워크 등)는
|
|
114
|
+
// 모두 timeout UI("Connection Failed")로 통일.
|
|
115
|
+
if (connectorId === 'WALLET_CONNECT') {
|
|
116
|
+
setExpiredSignal((n) => n + 1);
|
|
117
|
+
}
|
|
118
|
+
onError?.(err, connectorId);
|
|
119
|
+
},
|
|
99
120
|
onQrUri: (uri) => setQrUri(uri),
|
|
121
|
+
// CC connector 가 서버 OnSessionExpired 를 릴레이 → 만료 신호 증가.
|
|
122
|
+
onSessionExpired: () => setExpiredSignal((n) => n + 1),
|
|
100
123
|
});
|
|
101
124
|
|
|
125
|
+
// 연결 타임아웃 (CC=서버 push / MM=client 타이머 / WC=connect reject) → timedOut.
|
|
126
|
+
const timeoutActive = step === 'detail' && !!selectedConnector && content !== 'timeout';
|
|
127
|
+
const { timedOut, retry } = useConnectTimeout({
|
|
128
|
+
connectorId: selectedConnector,
|
|
129
|
+
active: timeoutActive,
|
|
130
|
+
expiredSignal,
|
|
131
|
+
onRetry: () => {
|
|
132
|
+
if (!selectedConnector) return;
|
|
133
|
+
cancelConnect();
|
|
134
|
+
setQrUri(null);
|
|
135
|
+
// 타임아웃 직전 화면으로 복원 (예: WC 의 wallet-qr → 선택 지갑 QR 유지).
|
|
136
|
+
// idle/timeout 처럼 복원 불가한 값이면 커넥터 초기 화면으로.
|
|
137
|
+
const restore = preTimeoutContentRef.current;
|
|
138
|
+
const nextContent: DetailContent =
|
|
139
|
+
restore !== 'idle' && restore !== 'timeout'
|
|
140
|
+
? restore
|
|
141
|
+
: getInitialContentForConnector(selectedConnector, isMetaMaskExtensionInstalled);
|
|
142
|
+
setContent(nextContent);
|
|
143
|
+
triggerConnect(selectedConnector);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// 타임아웃 발생 → 진행 중 연결 취소 + timeout 화면 표시.
|
|
148
|
+
// 전환 전 화면을 저장해 retry 시 복원 (WC wallet-qr 등).
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (!timedOut) return;
|
|
151
|
+
preTimeoutContentRef.current = contentRef.current;
|
|
152
|
+
cancelConnect();
|
|
153
|
+
setContent('timeout');
|
|
154
|
+
}, [timedOut, cancelConnect]);
|
|
155
|
+
|
|
156
|
+
// provider 응답이 늦게 도착하면 QR에서 확장 연결 화면으로 전환.
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (
|
|
159
|
+
isMetaMaskExtensionInstalled &&
|
|
160
|
+
selectedConnector === 'METAMASK' &&
|
|
161
|
+
content === 'metamask-qr'
|
|
162
|
+
) {
|
|
163
|
+
setContent('metamask-ext');
|
|
164
|
+
}
|
|
165
|
+
}, [isMetaMaskExtensionInstalled, selectedConnector, content]);
|
|
166
|
+
|
|
102
167
|
// 마운트 시 wagmi storage 의 recentConnectorId(연결 성공 시 wagmi 가 저장) 복원.
|
|
103
168
|
useEffect(() => {
|
|
104
169
|
const restoreRecent = async (): Promise<void> => {
|
|
@@ -115,10 +180,7 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
|
|
|
115
180
|
const handleSelect = (id: ConnectorId) => {
|
|
116
181
|
setSelectedConnector(id);
|
|
117
182
|
// recent 는 클릭이 아니라 연결 성공(onConnect) 시점에 기록 — 위 useWagmiConnect onConnect 참조.
|
|
118
|
-
|
|
119
|
-
const initialContent: DetailContent =
|
|
120
|
-
id === 'METAMASK' && !detectMetaMaskExtension() ? 'metamask-qr' : getContentForConnector(id);
|
|
121
|
-
setContent(initialContent);
|
|
183
|
+
setContent(getInitialContentForConnector(id, isMetaMaskExtensionInstalled));
|
|
122
184
|
setStep('detail');
|
|
123
185
|
// wagmi connect 시작 (display_uri 도착 시 setQrUri 자동 호출)
|
|
124
186
|
triggerConnect(id);
|
|
@@ -261,6 +323,7 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
|
|
|
261
323
|
walletCount={wc.walletList.length}
|
|
262
324
|
onBack={handleBack}
|
|
263
325
|
onClose={onClose}
|
|
326
|
+
onRetry={retry}
|
|
264
327
|
onShowAllWallets={handleShowAllWallets}
|
|
265
328
|
onSelectWallet={handleSelectWallet}
|
|
266
329
|
onCopyLink={() => {
|
|
@@ -37,8 +37,9 @@
|
|
|
37
37
|
width: 100%;
|
|
38
38
|
max-width: none;
|
|
39
39
|
grid-template-columns: 1fr;
|
|
40
|
+
grid-template-rows: minmax(0, 1fr);
|
|
40
41
|
max-height: 82dvh;
|
|
41
|
-
height:
|
|
42
|
+
height: auto;
|
|
42
43
|
border-radius: 12px 12px 0 0;
|
|
43
44
|
|
|
44
45
|
&[data-step='selector'] .ck-detail { display: none !important; }
|
|
@@ -40,6 +40,8 @@ interface DetailPanelProps {
|
|
|
40
40
|
onShowAllWallets?: () => void;
|
|
41
41
|
onSelectWallet?: (wallet: WCWallet) => void;
|
|
42
42
|
onCopyLink?: () => void;
|
|
43
|
+
/** timeout 화면의 "Try Again" 버튼 핸들러. */
|
|
44
|
+
onRetry?: () => void;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
const getTitle = (content: DetailContent, connector: ConnectorId | null): string => {
|
|
@@ -78,6 +80,7 @@ export const DetailPanel: FC<DetailPanelProps> = ({
|
|
|
78
80
|
onShowAllWallets,
|
|
79
81
|
onSelectWallet,
|
|
80
82
|
onCopyLink,
|
|
83
|
+
onRetry,
|
|
81
84
|
}) => {
|
|
82
85
|
// CONNECTOR_META 에서 활성 connector 의 logo / downloadUrl 가져옴.
|
|
83
86
|
const connectorMeta = selectedConnector ? CONNECTOR_META[selectedConnector] : null;
|
|
@@ -112,7 +115,7 @@ export const DetailPanel: FC<DetailPanelProps> = ({
|
|
|
112
115
|
{content === 'qr' && (
|
|
113
116
|
<QR uri={qrUri} logoUrl={connectorLogoUrl} downloadUrl={connectorDownloadUrl} />
|
|
114
117
|
)}
|
|
115
|
-
{content === 'timeout' && <Timeout />}
|
|
118
|
+
{content === 'timeout' && <Timeout onRetry={onRetry} />}
|
|
116
119
|
{(content === 'metamask-ext' || content === 'metamask-qr') && (
|
|
117
120
|
<MetaMask
|
|
118
121
|
variant={content === 'metamask-ext' ? 'extension' : 'qr'}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
.ck-detail {
|
|
4
4
|
display: flex;
|
|
5
5
|
flex-direction: column;
|
|
6
|
+
min-width: 0;
|
|
6
7
|
background: var(--semantic-paper-dialog);
|
|
7
8
|
min-height: 0;
|
|
8
9
|
position: relative;
|
|
@@ -61,4 +62,8 @@
|
|
|
61
62
|
overflow-y: auto;
|
|
62
63
|
padding: 20px;
|
|
63
64
|
@include themedScrollbar;
|
|
65
|
+
|
|
66
|
+
&:has(.ck-all-wallets) {
|
|
67
|
+
padding-bottom: 0;
|
|
68
|
+
}
|
|
64
69
|
}
|
package/src/utils/platform.ts
CHANGED
|
@@ -13,7 +13,3 @@ export const tryOpenDeepLink = (uri: string, deepLinkBase: string): void => {
|
|
|
13
13
|
export const tryOpenCreditWalletDeeplink = (payloadUrl: string): void => {
|
|
14
14
|
window.location.replace(`creditwallet://?uri=${encodeURIComponent(payloadUrl)}`);
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
export const detectMetaMaskExtension = (): boolean =>
|
|
18
|
-
typeof window !== 'undefined' &&
|
|
19
|
-
Boolean((window as Window & { ethereum?: { isMetaMask?: boolean } }).ethereum?.isMetaMask);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import penguinWalletIcon from '../assets/penguinwallet.png';
|
|
2
|
+
import type { WCWallet } from './types';
|
|
3
|
+
|
|
4
|
+
// TODO-CRCN-11: WC에 PenguinWallet 추가 시 삭제
|
|
5
|
+
export const CREDIT_WALLET_LISTING_ID =
|
|
6
|
+
'ce02f1ae74686234eabfa7d31876c442779b730dfe7924a3ca0e6559a7afa30f';
|
|
7
|
+
|
|
8
|
+
export const overrideWalletDisplay = (wallet: WCWallet): WCWallet =>
|
|
9
|
+
wallet.id === CREDIT_WALLET_LISTING_ID
|
|
10
|
+
? {
|
|
11
|
+
...wallet,
|
|
12
|
+
name: 'PenguinWallet',
|
|
13
|
+
imageUrl: penguinWalletIcon,
|
|
14
|
+
}
|
|
15
|
+
: wallet;
|