@arcblock/did-connect-react 3.1.28 → 3.1.31

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.
@@ -28,8 +28,9 @@ import bridge from '@arcblock/bridge';
28
28
  import { getFederatedEnabled } from '@arcblock/ux/lib/Util/federated';
29
29
  import noop from 'lodash/noop';
30
30
  import isNil from 'lodash/isNil';
31
+ import { ReactGA } from '@arcblock/ux/lib/withTracker';
31
32
 
32
- import { useState } from 'react';
33
+ import { useEffect, useState } from 'react';
33
34
  import createStorage from '../Storage';
34
35
  import {
35
36
  getAppId,
@@ -68,6 +69,14 @@ import { getWalletDid } from '../User/use-did';
68
69
  import useDIDSpacesGuide from './did-spaces-guide';
69
70
  import { FederatedProvider, useFederatedContext } from '../Federated';
70
71
  import useVerify from './hooks/use-verify';
72
+ import {
73
+ gaBindWalletFailedHandler,
74
+ gaBindWalletSuccessHandler,
75
+ gaLoginFailedHandler,
76
+ gaLoginSuccessHandler,
77
+ gaSwitchPassportFailedHandler,
78
+ gaSwitchPassportSuccessHandler,
79
+ } from './handler';
71
80
 
72
81
  export * from './libs';
73
82
 
@@ -261,6 +270,24 @@ function createSessionContext(
261
270
  });
262
271
 
263
272
  const events = useCreation(() => new EventEmitter(), []);
273
+ // Register event listeners with cleanup to prevent memory leaks
274
+ useEffect(() => {
275
+ events.on(EVENTS.LOGIN, gaLoginSuccessHandler);
276
+ events.on(EVENTS.LOGIN_FAILED, gaLoginFailedHandler);
277
+ events.on(EVENTS.SWITCH_PASSPORT, gaSwitchPassportSuccessHandler);
278
+ events.on(EVENTS.SWITCH_PASSPORT_FAILED, gaSwitchPassportFailedHandler);
279
+ events.on(EVENTS.BIND_WALLET, gaBindWalletSuccessHandler);
280
+ events.on(EVENTS.BIND_WALLET_FAILED, gaBindWalletFailedHandler);
281
+ return () => {
282
+ events.off(EVENTS.LOGIN, gaLoginSuccessHandler);
283
+ events.off(EVENTS.LOGIN_FAILED, gaLoginFailedHandler);
284
+ events.off(EVENTS.SWITCH_PASSPORT, gaSwitchPassportSuccessHandler);
285
+ events.off(EVENTS.SWITCH_PASSPORT_FAILED, gaSwitchPassportFailedHandler);
286
+ events.off(EVENTS.BIND_WALLET, gaBindWalletSuccessHandler);
287
+ events.off(EVENTS.BIND_WALLET_FAILED, gaBindWalletFailedHandler);
288
+ };
289
+ }, [events]);
290
+
264
291
  /**
265
292
  * 用于包装 did-connect 的 open 和 close 事件,添加一些默认的行为
266
293
  * 1. 在 open 中添加的事件订阅,did-connect close 时需要取消订阅
@@ -554,6 +581,10 @@ function createSessionContext(
554
581
  state.loading = false;
555
582
  events.emit('logout');
556
583
 
584
+ ReactGA.set({
585
+ user_id: undefined,
586
+ });
587
+
557
588
  if (typeof done === 'function') {
558
589
  done();
559
590
  }
@@ -623,12 +654,17 @@ function createSessionContext(
623
654
  );
624
655
  wrapOnceEmit('switch-did', done);
625
656
  handleLoginResult({ ...result, encrypted: false });
626
- handleRefreshUser({ showProgress: true }).then(async () => {
627
- await sleep(200);
628
- events.emit(EVENTS.LOGIN, result, decrypt, session.current);
629
- notifyBridge(session.current);
630
- await connectToDidSpaceForFullAccess();
631
- });
657
+ handleRefreshUser({ showProgress: true })
658
+ .then(async () => {
659
+ await sleep(200);
660
+ events.emit(EVENTS.LOGIN, result, decrypt, session.current);
661
+ notifyBridge(session.current);
662
+ await connectToDidSpaceForFullAccess();
663
+ })
664
+ .catch((error) => {
665
+ events.emit(EVENTS.LOGIN_FAILED, error);
666
+ throw error;
667
+ });
632
668
  });
633
669
  } else {
634
670
  if (isUndefined(extraParams?.forceConnected)) {
@@ -802,6 +838,10 @@ function createSessionContext(
802
838
  }
803
839
  });
804
840
 
841
+ const onSwitchPassportFailed = useMemoizedFn((error) => {
842
+ events.emit(EVENTS.SWITCH_PASSPORT_FAILED, error);
843
+ });
844
+
805
845
  const onLogin = useMemoizedFn(async (result) => {
806
846
  if (state.action === 'switch-passport') {
807
847
  onSwitchPassport(result);
@@ -823,6 +863,10 @@ function createSessionContext(
823
863
  }
824
864
  });
825
865
 
866
+ const onLoginFailed = useMemoizedFn((error) => {
867
+ events.emit(EVENTS.LOGIN_FAILED, error);
868
+ });
869
+
826
870
  const onBindWallet = useMemoizedFn((result) => {
827
871
  pageState.extraParams = {};
828
872
  pageState.options = {};
@@ -839,6 +883,10 @@ function createSessionContext(
839
883
  // NOTICE: auth0 绑定 wallet 在新的版本中,不会再要求切换 login_token,这里的处理是为了兼容有无 sessionToken 的两种情况,都可以顺利完成当前绑定操作
840
884
  });
841
885
 
886
+ const onBindWalletFailed = useMemoizedFn((error) => {
887
+ events.emit(EVENTS.BIND_WALLET_FAILED, error, session);
888
+ });
889
+
842
890
  const onSwitchProfile = useMemoizedFn((result) => {
843
891
  debug('onSwitchProfile', { result });
844
892
  pageState.extraParams = {};
@@ -888,9 +936,14 @@ function createSessionContext(
888
936
  args,
889
937
  err,
890
938
  });
939
+ callbacks[`${action}-failed`]?.(err, ...args);
891
940
  }
892
941
  });
893
942
 
943
+ const onError = useMemoizedFn((action, err, ...args) => {
944
+ callbacks[`${action}-failed`]?.(err, ...args);
945
+ });
946
+
894
947
  const {
895
948
  federatedMaster,
896
949
  federatedEnabled,
@@ -1011,7 +1064,8 @@ function createSessionContext(
1011
1064
  onClose(state.action, ...args);
1012
1065
  },
1013
1066
  onError(err) {
1014
- console.error(err);
1067
+ console.error(state.action, err);
1068
+ onError(state.action, err, session.current);
1015
1069
  },
1016
1070
  };
1017
1071
  state.open = true;
@@ -1148,12 +1202,17 @@ function createSessionContext(
1148
1202
  true
1149
1203
  );
1150
1204
  handleLoginResult({ ...result, encrypted: false });
1151
- handleRefreshUser({ showProgress: true }).then(async () => {
1152
- await sleep(200);
1153
- events.emit(EVENTS.LOGIN, result, decrypt, session.current);
1154
- notifyBridge(session.current);
1155
- await connectToDidSpaceForFullAccess();
1156
- });
1205
+ handleRefreshUser({ showProgress: true })
1206
+ .then(async () => {
1207
+ await sleep(200);
1208
+ events.emit(EVENTS.LOGIN, result, decrypt, session.current);
1209
+ notifyBridge(session.current);
1210
+ await connectToDidSpaceForFullAccess();
1211
+ })
1212
+ .catch((error) => {
1213
+ events.emit(EVENTS.LOGIN_FAILED, error);
1214
+ throw error;
1215
+ });
1157
1216
  });
1158
1217
  close();
1159
1218
  return;
@@ -1253,7 +1312,7 @@ function createSessionContext(
1253
1312
  resolve();
1254
1313
  },
1255
1314
  onError(err) {
1256
- console.error(err);
1315
+ events.emit(EVENTS.LOGIN_FAILED, err);
1257
1316
  reject(err);
1258
1317
  },
1259
1318
  });
@@ -1293,22 +1352,27 @@ function createSessionContext(
1293
1352
  WrapDid,
1294
1353
  getUserSessions,
1295
1354
  async loginUserSession(userSessionItem) {
1296
- const loginResult = await loginUserSession(userSessionItem);
1297
- updateConnectedInfo(
1298
- {
1299
- connected_did: userSessionItem.user.did,
1300
- connected_pk: userSessionItem.user.pk,
1301
- connected_wallet_os: userSessionItem.extra.walletOS,
1302
- connected_app: getAppId(),
1303
- },
1304
- true
1305
- );
1306
- handleLoginResult({ ...loginResult, encrypted: false });
1307
- await handleRefreshUser({ showProgress: true });
1308
- await sleep(200);
1309
- events.emit(EVENTS.LOGIN, loginResult, decrypt, session.current);
1310
- notifyBridge(session.current);
1311
- await connectToDidSpaceForFullAccess();
1355
+ try {
1356
+ const loginResult = await loginUserSession(userSessionItem);
1357
+ updateConnectedInfo(
1358
+ {
1359
+ connected_did: userSessionItem.user.did,
1360
+ connected_pk: userSessionItem.user.pk,
1361
+ connected_wallet_os: userSessionItem.extra.walletOS,
1362
+ connected_app: getAppId(),
1363
+ },
1364
+ true
1365
+ );
1366
+ handleLoginResult({ ...loginResult, encrypted: false });
1367
+ await handleRefreshUser({ showProgress: true });
1368
+ await sleep(200);
1369
+ events.emit(EVENTS.LOGIN, loginResult, decrypt, session.current);
1370
+ notifyBridge(session.current);
1371
+ await connectToDidSpaceForFullAccess();
1372
+ } catch (err) {
1373
+ events.emit(EVENTS.LOGIN_FAILED, err);
1374
+ throw err;
1375
+ }
1312
1376
  },
1313
1377
  unReadCount,
1314
1378
  setUnReadCount,
@@ -1332,7 +1396,10 @@ function createSessionContext(
1332
1396
  login: onLogin,
1333
1397
  'switch-profile': onSwitchProfile,
1334
1398
  'switch-passport': onSwitchPassport,
1399
+ 'switch-passport-failed': onSwitchPassportFailed,
1335
1400
  'bind-wallet': onBindWallet,
1401
+ 'bind-wallet-failed': onBindWalletFailed,
1402
+ 'login-failed': onLoginFailed,
1336
1403
  };
1337
1404
 
1338
1405
  // user change 事件
@@ -1455,6 +1522,9 @@ function createSessionContext(
1455
1522
  state.initialized = true;
1456
1523
  return;
1457
1524
  }
1525
+ } catch (err) {
1526
+ events.emit(EVENTS.LOGIN_FAILED, err);
1527
+ throw err;
1458
1528
  } finally {
1459
1529
  state.loading = false;
1460
1530
  }
@@ -1540,6 +1610,7 @@ function createSessionContext(
1540
1610
  });
1541
1611
  }
1542
1612
  } catch (err) {
1613
+ events.emit(EVENTS.LOGIN_FAILED, err);
1543
1614
  logger.error('Failed to autoLogin ArcSphere', err);
1544
1615
  }
1545
1616
  }
@@ -1561,6 +1632,7 @@ function createSessionContext(
1561
1632
  onBindOAuth: () => handleRefreshUser(),
1562
1633
  onAddPasskey: () => handleRefreshUser(),
1563
1634
  onRemovePasskey: () => handleRefreshUser(),
1635
+ session: session.current,
1564
1636
  };
1565
1637
 
1566
1638
  return (
@@ -2,13 +2,16 @@
2
2
 
3
3
  export const EVENTS = {
4
4
  LOGIN: Symbol('login'),
5
+ LOGIN_FAILED: Symbol('loginFailed'),
5
6
  CANCEL_LOGIN: Symbol('cancelLogin'),
6
7
  LOGOUT: Symbol('logout'),
7
8
  SWITCH_PROFILE: Symbol('switchProfile'),
8
9
  CANCEL_SWITCH_PROFILE: Symbol('cancelSwitchProfile'),
9
10
  SWITCH_PASSPORT: Symbol('switchPassport'),
11
+ SWITCH_PASSPORT_FAILED: Symbol('switchPassportFailed'),
10
12
  CANCEL_SWITCH_PASSPORT: Symbol('cancelSwitchPassport'),
11
13
  BIND_WALLET: Symbol('bindWallet'),
14
+ BIND_WALLET_FAILED: Symbol('bindWalletFailed'),
12
15
  CANCEL_BIND_WALLET: Symbol('cancelBindWallet'),
13
16
  DID_SPACE_CONNECTED: Symbol('didSpaceConnected'),
14
17
  };
@@ -0,0 +1,160 @@
1
+ /* eslint-disable react/prop-types */
2
+ import pick from 'lodash/pick';
3
+ import { Confirm } from '@arcblock/ux/lib/Dialog';
4
+ import { Box } from '@mui/material';
5
+ import Toast from '@arcblock/ux/lib/Toast';
6
+ import CardSelector from '@arcblock/ux/lib/CardSelector';
7
+ import { translate } from '@arcblock/ux/lib/Locale/util';
8
+ import { useCreation, useMemoizedFn } from 'ahooks';
9
+ import { createPassportSvg } from '@arcblock/ux/lib/Util/passport';
10
+
11
+ // eslint-disable-next-line import/no-unresolved
12
+ import { ReactGA } from '@arcblock/ux/lib/withTracker';
13
+ // eslint-disable-next-line import/no-unresolved
14
+ import Guest from '../OAuth/guest.svg?react';
15
+
16
+ export const parseResponse = (data) => {
17
+ if (!data) {
18
+ return {};
19
+ }
20
+
21
+ // for backward compatibility
22
+ if (typeof data === 'string') {
23
+ return { sessionToken: data };
24
+ }
25
+
26
+ return pick(data, ['sessionToken', 'refreshToken', 'visitorId', 'destroySessionId']);
27
+ };
28
+
29
+ const translations = {
30
+ zh: {
31
+ switchPassport: '切换通行证',
32
+ switchPassportFailed: '切换通行证失败',
33
+ switchPassportSucceed: '切换通行证成功',
34
+ selectedPassport: '请选择一个通行证',
35
+ switch: '切换',
36
+ cancel: '取消',
37
+ currentRole: '当前角色:',
38
+ passport: '通行证',
39
+ getPassportFailed: '获取通行证失败',
40
+ },
41
+ en: {
42
+ switchPassport: 'Switch passport',
43
+ switchPassportFailed: 'Switch passport failed',
44
+ switchPassportSucceed: 'Switch passport succeed',
45
+ selectedPassport: 'Select a passport to switch',
46
+ switch: 'Switch',
47
+ cancel: 'Cancel',
48
+ currentRole: 'Current role: ',
49
+ passport: 'Passport',
50
+ getPassportFailed: 'Get passports failed',
51
+ },
52
+ };
53
+
54
+ export function PassportSwitcher({ api, locale, switchState, onSwitchPassport, session }) {
55
+ const t = useMemoizedFn((key, data = {}) => {
56
+ return translate(translations, key, locale, 'en', data);
57
+ });
58
+
59
+ const visiblePassports = useCreation(() => {
60
+ return switchState.passports.filter((x) => x.role !== switchState.currentUser?.role);
61
+ }, [switchState.passports, switchState.currentUser?.role]);
62
+
63
+ const onSwitch = async () => {
64
+ const passportId = switchState.selectedPassport;
65
+ const fromRole = session?.user?.role;
66
+ const toRole = session?.user?.passports?.find((x) => x.id === passportId)?.role ?? 'guest';
67
+ const change = `${fromRole} -> ${toRole}`;
68
+ try {
69
+ const { data } = await api.post('/switch', { passportId });
70
+ const { sessionToken, refreshToken } = parseResponse(data);
71
+ onSwitchPassport({ sessionToken, refreshToken });
72
+ Toast.success(t('switchPassportSucceed'));
73
+
74
+ /**
75
+ * @type {import('@arcblock/ux/lib/withTracker/action/switch-passport').SwitchPassportSuccessEvent}
76
+ */
77
+ const switchPassportSuccessEvent = {
78
+ action: 'switchPassportSuccess',
79
+ change,
80
+ success: true,
81
+ };
82
+ ReactGA.event(switchPassportSuccessEvent.action, switchPassportSuccessEvent);
83
+ } catch (err) {
84
+ const errorMessage = err.message || t('switchPassportFailed');
85
+ /**
86
+ * @type {import('@arcblock/ux/lib/withTracker/action/switch-passport').SwitchPassportFailedEvent}
87
+ */
88
+ const switchPassportFailedEvent = {
89
+ action: 'switchPassportFailed',
90
+ success: false,
91
+ change,
92
+ errorMessage,
93
+ };
94
+ ReactGA.event(switchPassportFailedEvent.action, switchPassportFailedEvent);
95
+ Toast.error(errorMessage);
96
+ }
97
+ };
98
+
99
+ return (
100
+ <Confirm
101
+ open={switchState.open}
102
+ title={t('switchPassport')}
103
+ onConfirm={onSwitch}
104
+ onCancel={switchState.reset}
105
+ confirmButton={{
106
+ text: t('switch'),
107
+ props: {
108
+ variant: 'contained',
109
+ color: 'primary',
110
+ },
111
+ }}
112
+ cancelButton={{
113
+ text: t('cancel'),
114
+ props: {
115
+ color: 'inherit',
116
+ },
117
+ }}
118
+ PaperProps={{ style: { width: 600 } }}>
119
+ <Box
120
+ sx={{
121
+ mb: 2,
122
+ }}>
123
+ {t('currentRole')}
124
+ {switchState.currentUser?.role}
125
+ </Box>
126
+ <CardSelector
127
+ width={160}
128
+ height={240}
129
+ cardSpace={24}
130
+ list={[
131
+ // eslint-disable-next-line react/no-unstable-nested-components
132
+ () => <Guest />,
133
+ ...visiblePassports.map((x) => {
134
+ if (x.display) {
135
+ return {
136
+ src: x.display,
137
+ name: x.title,
138
+ };
139
+ }
140
+
141
+ // eslint-disable-next-line react/no-unstable-nested-components, react/function-component-definition
142
+ return () => (
143
+ <Box
144
+ key={x.id}
145
+ sx={{ width: '100%' }}
146
+ dangerouslySetInnerHTML={{
147
+ __html: createPassportSvg(x),
148
+ }}
149
+ />
150
+ );
151
+ }),
152
+ ]}
153
+ onSelect={(index) => {
154
+ switchState.selectedPassport = index > 0 ? visiblePassports[index - 1].id : undefined;
155
+ }}
156
+ defaultIndex={0}
157
+ />
158
+ </Confirm>
159
+ );
160
+ }