@abtnode/blocklet-services 1.16.5 → 1.16.6-beta-4562aa60

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.
Files changed (44) hide show
  1. package/api/libs/connect/session.js +89 -35
  2. package/api/routes/blocklet.js +24 -0
  3. package/api/routes/oauth.js +2 -2
  4. package/api/services/auth/connect/bind-wallet.js +12 -2
  5. package/api/services/auth/connect/exchange-passport.js +58 -0
  6. package/api/services/auth/connect/login.js +2 -1
  7. package/api/services/auth/connect/pre-setup.js +2 -2
  8. package/api/services/auth/connect/receive-transfer-app-owner.js +392 -0
  9. package/api/services/auth/connect/transfer-app-owner.js +30 -0
  10. package/api/services/auth/index.js +6 -0
  11. package/api/services/oauth/index.js +30 -23
  12. package/api/socket/channel/app.js +2 -2
  13. package/api/socket/channel/did.js +2 -2
  14. package/api/socket/channel/relay.js +2 -2
  15. package/api/socket/util.js +2 -2
  16. package/build/asset-manifest.json +27 -21
  17. package/build/index.html +1 -1
  18. package/build/static/css/{204.1d1e88ad.chunk.css → 61.03a48b17.chunk.css} +1 -1
  19. package/build/static/js/162.58348adf.chunk.js +3 -0
  20. package/build/static/js/343.71c1d7f6.chunk.js +2 -0
  21. package/build/static/js/359.c47779c2.chunk.js +2 -0
  22. package/build/static/js/371.ccef728f.chunk.js +2 -0
  23. package/build/static/js/42.a0526d16.chunk.js +2 -0
  24. package/build/static/js/547.f8830a9e.chunk.js +2 -0
  25. package/build/static/js/573.2052cb80.chunk.js +2 -0
  26. package/build/static/js/61.3c903dd8.chunk.js +3 -0
  27. package/build/static/js/648.b5b229e5.chunk.js +3 -0
  28. package/build/static/js/716.bcea7c98.chunk.js +2 -0
  29. package/build/static/js/868.9c03fa86.chunk.js +2 -0
  30. package/build/static/js/main.b5f63929.js +3 -0
  31. package/package.json +35 -33
  32. package/build/static/js/204.df50af69.chunk.js +0 -3
  33. package/build/static/js/343.b31c2008.chunk.js +0 -2
  34. package/build/static/js/371.88127b62.chunk.js +0 -2
  35. package/build/static/js/573.2687bb44.chunk.js +0 -2
  36. package/build/static/js/648.7a2e44c8.chunk.js +0 -3
  37. package/build/static/js/712.9667cdcd.chunk.js +0 -3
  38. package/build/static/js/716.e1534c42.chunk.js +0 -2
  39. package/build/static/js/868.4d364267.chunk.js +0 -2
  40. package/build/static/js/main.3b61bb8b.js +0 -3
  41. /package/build/static/js/{712.9667cdcd.chunk.js.LICENSE.txt → 162.58348adf.chunk.js.LICENSE.txt} +0 -0
  42. /package/build/static/js/{204.df50af69.chunk.js.LICENSE.txt → 61.3c903dd8.chunk.js.LICENSE.txt} +0 -0
  43. /package/build/static/js/{648.7a2e44c8.chunk.js.LICENSE.txt → 648.b5b229e5.chunk.js.LICENSE.txt} +0 -0
  44. /package/build/static/js/{main.3b61bb8b.js.LICENSE.txt → main.b5f63929.js.LICENSE.txt} +0 -0
@@ -0,0 +1,392 @@
1
+ const { get } = require('lodash');
2
+ const { toHex } = require('@ocap/util');
3
+ const pRetry = require('p-retry');
4
+ const { messages, getApplicationInfo, getPassportStatusEndpoint, getUser } = require('@abtnode/auth/lib/auth');
5
+ const { getKeyPairClaim, getAuthPrincipalForTransferAppOwnerShip } = require('@abtnode/auth/lib/server');
6
+ const sleep = require('@abtnode/util/lib/sleep');
7
+ const { getChainClient } = require('@abtnode/util/lib/get-chain-client');
8
+ const formatContext = require('@abtnode/util/lib/format-context');
9
+ const { extractUserAvatar } = require('@abtnode/util/lib/user-avatar');
10
+ const { ensureAccountOnMainChain } = require('@abtnode/util/lib/ensure-account-on-main-chain');
11
+ const {
12
+ createPassport,
13
+ createPassportVC,
14
+ createUserPassport,
15
+ getRoleFromLocalPassport,
16
+ } = require('@abtnode/auth/lib/passport');
17
+ const getBlockletWallet = require('@blocklet/meta/lib/wallet');
18
+ const { getBlockletChainInfo, isInProgress } = require('@blocklet/meta/lib/util');
19
+ const { ROLES } = require('@abtnode/constant');
20
+ const logger = require('@abtnode/logger')('blocklet-service:transfer-blocklet-owner');
21
+
22
+ const { mergeUserData } = require('../../oauth');
23
+
24
+ const migrateAppOnChain = async (blocklet, oldSk, newSk) => {
25
+ if (process.env.NODE_ENV === 'test') {
26
+ return;
27
+ }
28
+
29
+ logger.info('Preparing for on-chain migration', { did: blocklet.meta.did });
30
+ if (!oldSk) {
31
+ // should not be here
32
+ logger.error('on-chain migration aborted because oldSk is empty', { did: blocklet.meta.did });
33
+ return;
34
+ }
35
+
36
+ if (!newSk) {
37
+ // should not be here
38
+ logger.error('on-chain migration aborted because newSk is empty', { did: blocklet.meta.did });
39
+ return;
40
+ }
41
+
42
+ // ensure account changed
43
+ const type = blocklet.configObj?.BLOCKLET_WALLET_TYPE;
44
+ const oldWallet = getBlockletWallet(oldSk, undefined, type);
45
+ const newWallet = getBlockletWallet(newSk, undefined, type);
46
+ if (oldWallet.address === newWallet.address) {
47
+ // should not be here
48
+ logger.info('on-chain migration aborted because newSk same with oldSk', { did: blocklet.meta.did });
49
+ await ensureAccountOnMainChain(newWallet, blocklet.meta.title);
50
+ return;
51
+ }
52
+
53
+ // ensure chain host
54
+ const chainHost = getBlockletChainInfo(blocklet).host;
55
+ if (!chainHost || chainHost === 'none') {
56
+ logger.info('on-chain migration aborted because CHAIN_HOST is empty', { did: blocklet.meta.did });
57
+ await ensureAccountOnMainChain(newWallet, blocklet.meta.title);
58
+ return;
59
+ }
60
+
61
+ // migrate on chain
62
+ logger.info('on-chain migration for chain ', { did: blocklet.meta.did, host: chainHost });
63
+ const client = getChainClient(chainHost);
64
+ const oldResult = await client.getAccountState({ address: oldWallet.address });
65
+ const newResult = await client.getAccountState({ address: newWallet.address });
66
+
67
+ if (oldResult.state && !newResult.state) {
68
+ logger.info('on-chain migration for wallets', { oldAddress: oldWallet.address, newAddress: newWallet.address });
69
+
70
+ // migrate old account to new account
71
+ const tx = await client.signAccountMigrateTx({
72
+ tx: {
73
+ itx: {
74
+ address: newWallet.address,
75
+ pk: newWallet.publicKey,
76
+ },
77
+ },
78
+ wallet: oldWallet,
79
+ });
80
+ const hash = await client.sendAccountMigrateTx({ tx, wallet: oldWallet });
81
+ logger.info('on-chain migration done', { did: blocklet.meta.did, hash });
82
+ } else {
83
+ if (!oldResult.state) {
84
+ logger.info('on-chain migration aborted because oldSk not declared on chain', { did: blocklet.meta.did });
85
+ }
86
+
87
+ if (newResult.state) {
88
+ logger.info('on-chain migration aborted because newSk declared on chain', { did: blocklet.meta.did });
89
+ }
90
+ }
91
+
92
+ // should not throw error because signAccountMigrateTx is already happened
93
+ try {
94
+ await ensureAccountOnMainChain(newWallet, blocklet.meta.title);
95
+ } catch (error) {
96
+ logger.error('ensureAccountOnMainChain failed', { error });
97
+ }
98
+ };
99
+
100
+ module.exports = function createRoutes(node, _, createSessionToken) {
101
+ const getBlocklet = async (did) => {
102
+ const blocklet = await node.getBlocklet({ did, attachConfig: false });
103
+ if (!blocklet) {
104
+ throw new Error(`application not found: ${did}`);
105
+ }
106
+ return blocklet;
107
+ };
108
+
109
+ return {
110
+ action: 'receive-transfer-app-owner',
111
+ authPrincipal: false,
112
+ claims: [
113
+ {
114
+ authPrincipal: getAuthPrincipalForTransferAppOwnerShip(node),
115
+ },
116
+ {
117
+ keyPair: async (opts) => {
118
+ const {
119
+ extraParams: { appDid, transferId, locale },
120
+ } = opts;
121
+
122
+ const transfer = await node.checkTransferAppOwnerSession({ appDid, transferId });
123
+
124
+ if (transfer.appDid !== appDid) {
125
+ throw new Error(`Invalid appDid. Expect: ${transfer.appDid}, Actual: ${appDid}`);
126
+ }
127
+
128
+ const blocklet = await getBlocklet(appDid);
129
+
130
+ if (isInProgress(blocklet.status)) {
131
+ throw new Error(messages.appIsInProgress[locale]);
132
+ }
133
+
134
+ return getKeyPairClaim(node, { title: blocklet.meta.title, declare: false })(opts);
135
+ },
136
+ profile: async ({ extraParams: { locale = 'en' } }) => {
137
+ return {
138
+ fields: ['fullName', 'email', 'avatar'],
139
+ description: messages.requestProfile[locale],
140
+ };
141
+ },
142
+ },
143
+ ],
144
+ onAuth: async ({ userDid, userPk, claims, req, updateSession, extraParams, baseUrl }) => {
145
+ logger.info('transferAppOwnerHandler', extraParams);
146
+
147
+ // prepare data
148
+
149
+ const { locale = 'en', appDid, transferId } = extraParams;
150
+
151
+ if (!transferId) {
152
+ throw new Error('missing transferId in extraParams');
153
+ }
154
+
155
+ if (!appDid) {
156
+ throw new Error(messages.missingBlockletDid[locale]);
157
+ }
158
+
159
+ const profile = claims.find((x) => x.type === 'profile');
160
+ if (!profile) {
161
+ throw new Error('app keyPair must be provided');
162
+ }
163
+
164
+ const keyPair = claims.find((x) => x.type === 'keyPair');
165
+ if (!keyPair) {
166
+ throw new Error(messages.missingKeyPair[locale]);
167
+ }
168
+
169
+ const newSk = toHex(keyPair.secret);
170
+ if (!newSk) {
171
+ throw new Error(`keyPair.secret is empty: ${appDid}`);
172
+ }
173
+
174
+ const blocklet = await node.getBlocklet({ did: appDid, attachRuntimeInfo: false });
175
+ if (!blocklet) {
176
+ throw new Error(messages.invalidBlocklet[locale]);
177
+ }
178
+
179
+ if (blocklet.appDid !== appDid) {
180
+ throw new Error(`Invalid appDid. Expect: ${blocklet.appDid}, Actual: ${appDid}`);
181
+ }
182
+
183
+ const oldSk = (blocklet.environments || []).find((x) => x.key === 'BLOCKLET_APP_SK').value;
184
+
185
+ const initialized = !!blocklet.settings?.initialized;
186
+ if (!initialized) {
187
+ throw new Error(messages.appNotInitialized[locale]);
188
+ }
189
+
190
+ const oldOwnerDid = blocklet.settings?.owner?.did;
191
+
192
+ const { appPid } = blocklet;
193
+
194
+ const transfer = await node.checkTransferAppOwnerSession({ appDid, transferId });
195
+
196
+ if (transfer.appDid !== appDid) {
197
+ throw new Error(`Invalid appDid. Expect: ${transfer.appDid}, Actual: ${appDid}`);
198
+ }
199
+
200
+ const {
201
+ name: issuerName,
202
+ wallet: issuerWallet,
203
+ passportColor,
204
+ dataDir,
205
+ } = await getApplicationInfo({ node, teamDid: appPid });
206
+
207
+ const statusEndpointBaseUrl = baseUrl;
208
+ const endpoint = baseUrl;
209
+
210
+ const vcParams = {
211
+ issuerName,
212
+ issuerWallet,
213
+ ownerDid: userDid,
214
+ passport: await createPassport({
215
+ name: ROLES.OWNER,
216
+ node,
217
+ teamDid: appPid,
218
+ locale,
219
+ endpoint,
220
+ }),
221
+ endpoint: getPassportStatusEndpoint({
222
+ baseUrl: statusEndpointBaseUrl,
223
+ userDid,
224
+ teamDid: appPid,
225
+ }),
226
+ types: [],
227
+ ownerProfile: profile,
228
+ preferredColor: passportColor,
229
+ };
230
+
231
+ const vc = createPassportVC(vcParams);
232
+
233
+ const role = getRoleFromLocalPassport(get(vc, 'credentialSubject.passport'));
234
+ const passport = createUserPassport(vc, { role });
235
+
236
+ const user = await getUser(node, appPid, userDid);
237
+
238
+ // update state
239
+
240
+ await node.updateBlockletOwner({ did: appPid, owner: { did: userDid, pk: userPk } });
241
+
242
+ const avatar = await extractUserAvatar(get(profile, 'avatar'), {
243
+ dataDir,
244
+ });
245
+
246
+ const now = new Date().toISOString();
247
+
248
+ if (user) {
249
+ const doc = await node.updateUser({
250
+ teamDid: appPid,
251
+ user: {
252
+ ...mergeUserData(user, {
253
+ avatar,
254
+ locale,
255
+ lastLoginIp: get(req, 'headers[x-real-ip]') || '',
256
+ lastUsedPassport: passport,
257
+ connectedAccount: { provider: 'wallet' },
258
+ }),
259
+ ...profile,
260
+ avatar,
261
+ },
262
+ });
263
+ await node.createAuditLog(
264
+ {
265
+ action: 'updateUser',
266
+ args: { teamDid: appPid, userDid, passport, reason: 'transfer ownership' },
267
+ context: formatContext(Object.assign(req, { user })),
268
+ result: doc,
269
+ },
270
+ node
271
+ );
272
+ } else {
273
+ const doc = await node.addUser({
274
+ teamDid: appPid,
275
+ user: {
276
+ ...profile,
277
+ avatar,
278
+ did: userDid,
279
+ pk: userPk,
280
+ approved: true,
281
+ locale,
282
+ passports: [passport],
283
+ firstLoginAt: now,
284
+ lastLoginAt: now,
285
+ lastLoginIp: get(req, 'headers[x-real-ip]') || '',
286
+ extraConfigs: {
287
+ sourceProvider: 'wallet',
288
+ connectedAccounts: [
289
+ {
290
+ provider: 'wallet',
291
+ did: userDid,
292
+ pk: userPk,
293
+ lastLoginAt: now,
294
+ firstLoginAt: now,
295
+ },
296
+ ],
297
+ },
298
+ },
299
+ });
300
+
301
+ await node.createAuditLog(
302
+ {
303
+ action: 'addUser',
304
+ args: { teamDid: appPid, userDid, passport, reason: 'transfer ownership' },
305
+ context: formatContext(Object.assign(req, { user: doc })),
306
+ result: doc,
307
+ },
308
+ node
309
+ );
310
+ }
311
+
312
+ // NOTICE: must NOT use appDid here if update BLOCKLET_APP_SK
313
+ await node.configBlocklet(
314
+ {
315
+ did: blocklet.appPid,
316
+ configs: [{ key: 'BLOCKLET_APP_SK', value: newSk, secure: true }],
317
+ skipHook: true,
318
+ skipDidDocument: true,
319
+ },
320
+ formatContext(Object.assign(req, { user: { did: userDid, fullName: 'Owner', role: 'owner' } }))
321
+ );
322
+
323
+ try {
324
+ if (oldOwnerDid && oldOwnerDid !== userDid) {
325
+ const oldOwner = await getUser(node, appPid, oldOwnerDid);
326
+ if (oldOwner) {
327
+ const filteredPassports = (oldOwner.passports || []).filter((p) => p.role !== ROLES.OWNER);
328
+ if (filteredPassports.length !== (oldOwner.passports || []).length) {
329
+ logger.info('update old owner passport', {
330
+ oldOwnerDid,
331
+ filteredPassports: filteredPassports.map((x) => x.id),
332
+ });
333
+ await node.updateUser({
334
+ teamDid: appPid,
335
+ user: {
336
+ did: oldOwnerDid,
337
+ pk: oldOwner.pk,
338
+ passports: filteredPassports,
339
+ },
340
+ });
341
+ }
342
+ }
343
+ }
344
+ } catch (error) {
345
+ logger.error('remove old owner passport failed', { error });
346
+ }
347
+
348
+ try {
349
+ await node.closeTransferAppOwnerSession({
350
+ appPid: blocklet.appPid,
351
+ transferId,
352
+ status: 'success',
353
+ });
354
+ } catch (error) {
355
+ logger.error('closeTransferAppOwnerSession failed', { error });
356
+ }
357
+
358
+ // migrate on-chain account
359
+ pRetry(() => migrateAppOnChain(blocklet, oldSk, newSk), {
360
+ retries: 10,
361
+ onFailedAttempt: console.error,
362
+ }).catch((error) => {
363
+ logger.error('migrateAppOnChain failed', { error });
364
+ });
365
+
366
+ // restart blocklet (not use queue)
367
+ node
368
+ .stopBlocklet({ did: appPid })
369
+ .then(() => {
370
+ return node.startBlocklet({ did: appPid });
371
+ })
372
+ .catch((error) => {
373
+ logger.error('restart blocklet failed', { appPid, error });
374
+ });
375
+
376
+ await sleep(3000);
377
+ logger.info('transfer ownership success', { userDid });
378
+
379
+ // Generate new session token that client can save to localStorage
380
+ const sessionToken = await createSessionToken(userDid, { passport: vc, role });
381
+ await updateSession({ sessionToken }, true);
382
+ await updateSession({ passportId: vc?.id });
383
+ logger.info('invite.success', { userDid });
384
+
385
+ return {
386
+ disposition: 'attachment',
387
+ type: 'VerifiableCredential',
388
+ data: vc,
389
+ };
390
+ },
391
+ };
392
+ };
@@ -0,0 +1,30 @@
1
+ const { getAppDidOwnerClaims } = require('@abtnode/auth/lib/server');
2
+ const logger = require('@abtnode/logger')('blocklet-service:transfer-blocklet-owner');
3
+
4
+ module.exports = function createRoutes(node) {
5
+ const getBlocklet = async (did) => {
6
+ const blocklet = await node.getBlocklet({ did, attachConfig: false });
7
+ if (!blocklet) {
8
+ throw new Error(`application not found: ${did}`);
9
+ }
10
+ return blocklet;
11
+ };
12
+
13
+ return {
14
+ action: 'transfer-app-owner',
15
+ authPrincipal: false,
16
+ claims: getAppDidOwnerClaims(),
17
+ onAuth: async ({ userDid: appDid, updateSession }) => {
18
+ const blocklet = await getBlocklet(appDid);
19
+ if (blocklet.appDid !== appDid) {
20
+ throw new Error(`Invalid appDid. Expect: ${blocklet.appDid}, Actual: ${appDid}`);
21
+ }
22
+
23
+ const result = await node.createTransferAppOwnerSession({ appDid });
24
+
25
+ logger.info('create transfer app owner', result);
26
+
27
+ await updateSession({ result });
28
+ },
29
+ };
30
+ };
@@ -26,6 +26,7 @@ const createLoginRoutes = require('./connect/login');
26
26
  const createBindWallerRoutes = require('./connect/bind-wallet');
27
27
  const createInviteRoutes = require('./connect/invite');
28
28
  const createIssuePassportAuth = require('./connect/issue-passport');
29
+ const createExchangePassportAuth = require('./connect/exchange-passport');
29
30
  const createLostPassportListAuth = require('./connect/lost-passport-list');
30
31
  const createLostPassportIssueAuth = require('./connect/lost-passport-issue');
31
32
  const createPreSetupAuth = require('./connect/pre-setup');
@@ -35,6 +36,8 @@ const createSwitchPassportAuth = require('./connect/switch-passport');
35
36
  const createRotateKeyPairAuth = require('./connect/rotate-key-pair');
36
37
  const createFuelAuth = require('./connect/fuel');
37
38
  const createMigrateToStructV2Routes = require('./connect/migrate-app-to-struct-v2');
39
+ const createTransferAppOwnerRoutes = require('./connect/transfer-app-owner');
40
+ const createReceiveTransferAppOwnerRoutes = require('./connect/receive-transfer-app-owner');
38
41
  const createSessionRoutes = require('./session');
39
42
  const createPassportRoutes = require('./passport');
40
43
  const { getRedirectUrl } = require('../../util');
@@ -169,6 +172,7 @@ const init = ({ node, options }) => {
169
172
  handler.attach(Object.assign({ app }, createBindWallerRoutes(node, authenticator, createSessionToken)));
170
173
  handler.attach(Object.assign({ app }, createInviteRoutes(node, authenticator, createSessionToken)));
171
174
  handler.attach(Object.assign({ app }, createIssuePassportAuth(node, authenticator, createSessionToken)));
175
+ handler.attach(Object.assign({ app }, createExchangePassportAuth(node, authenticator, createSessionToken)));
172
176
  handler.attach(Object.assign({ app }, createLostPassportListAuth(node, authenticator, createSessionToken)));
173
177
  handler.attach(Object.assign({ app }, createLostPassportIssueAuth(node, authenticator, createSessionToken)));
174
178
  handler.attach(Object.assign({ app }, createPreSetupAuth(node, authenticator, createSessionToken)));
@@ -178,6 +182,8 @@ const init = ({ node, options }) => {
178
182
  handler.attach(Object.assign({ app }, createRotateKeyPairAuth(node, authenticator, createSessionToken)));
179
183
  handler.attach(Object.assign({ app }, createFuelAuth(authenticator, createSessionToken)));
180
184
  handler.attach(Object.assign({ app }, createMigrateToStructV2Routes(node, options)));
185
+ handler.attach(Object.assign({ app }, createTransferAppOwnerRoutes(node, options)));
186
+ handler.attach(Object.assign({ app }, createReceiveTransferAppOwnerRoutes(node, options, createSessionToken)));
181
187
  });
182
188
  };
183
189
 
@@ -1,6 +1,6 @@
1
1
  const { upsertToPassports } = require('@abtnode/auth/lib/passport');
2
+ const { getChainClient } = require('@abtnode/util/lib/get-chain-client');
2
3
  const cloneDeep = require('lodash/cloneDeep');
3
- const Client = require('@ocap/client');
4
4
  const { default: urlFriendly } = require('@blocklet/meta/lib/url-friendly');
5
5
  const { slugify } = require('transliteration');
6
6
  const { MAIN_CHAIN_ENDPOINT } = require('@abtnode/constant');
@@ -10,7 +10,7 @@ const { types } = require('@arcblock/did');
10
10
  const logger = require('@abtnode/logger')('blocklet-services:oauth');
11
11
 
12
12
  function mergeUserData(oldUser, updateData) {
13
- const currentTime = new Date().toISOString();
13
+ const now = new Date().toISOString();
14
14
  const { extraConfigs = {} } = oldUser || {};
15
15
  const newUser = {
16
16
  did: oldUser.did,
@@ -19,14 +19,14 @@ function mergeUserData(oldUser, updateData) {
19
19
  oldUser.passports || [],
20
20
  updateData.lastUsedPassport && {
21
21
  ...updateData.lastUsedPassport,
22
- lastLoginAt: currentTime,
22
+ lastLoginAt: now,
23
23
  }
24
24
  ),
25
25
  extraConfigs: {
26
26
  ...extraConfigs,
27
27
  connectedAccounts: updateConnectedAccount(extraConfigs.connectedAccounts || [], updateData.connectedAccount),
28
28
  },
29
- lastLoginAt: currentTime,
29
+ lastLoginAt: now,
30
30
  };
31
31
  if (updateData.lastLoginIp) {
32
32
  newUser.lastLoginIp = updateData.lastLoginIp;
@@ -39,20 +39,23 @@ function mergeUserData(oldUser, updateData) {
39
39
  }
40
40
 
41
41
  function updateConnectedAccount(connectedAccounts = [], connectedAccount = {}) {
42
- const currentTime = new Date().toISOString();
43
- const copyAccounts = cloneDeep(connectedAccounts);
44
- const findAccountIndex = copyAccounts.findIndex((item) => item.provider === connectedAccount.provider);
45
- if (findAccountIndex > -1) {
46
- copyAccounts[findAccountIndex] = {
47
- ...connectedAccounts[findAccountIndex],
48
- ...connectedAccount,
49
- lastLoginAt: currentTime,
50
- };
51
- } else {
52
- copyAccounts.push(connectedAccount);
53
- }
42
+ const now = new Date().toISOString();
43
+ const updated = cloneDeep(connectedAccounts);
44
+ const updates = Array.isArray(connectedAccount) ? connectedAccount : [connectedAccount];
45
+ updates.forEach((x) => {
46
+ const findAccountIndex = updated.findIndex((item) => item.provider === x.provider && item.did === x.did);
47
+ if (findAccountIndex > -1) {
48
+ updated[findAccountIndex] = {
49
+ ...connectedAccounts[findAccountIndex],
50
+ ...x,
51
+ lastLoginAt: now,
52
+ };
53
+ } else {
54
+ updated.push(x);
55
+ }
56
+ });
54
57
 
55
- return copyAccounts;
58
+ return updated;
56
59
  }
57
60
 
58
61
  async function getRawUser(teamDid, userDid, { getUser, getUsers }) {
@@ -75,7 +78,7 @@ async function getRawUser(teamDid, userDid, { getUser, getUsers }) {
75
78
  }
76
79
 
77
80
  async function declareAccountByChain({ chainHost, wallet, moniker }) {
78
- const chainClient = new Client(chainHost);
81
+ const chainClient = getChainClient(chainHost);
79
82
  const { address } = wallet;
80
83
  const { state } = await chainClient.getAccountState({ address }, { ignoreFields: ['context'] });
81
84
  if (state) {
@@ -103,13 +106,15 @@ async function declareAccount({ wallet, moniker, blocklet }) {
103
106
  if (chainHost && chainHost !== 'none') {
104
107
  chainHostList.push(chainHost);
105
108
  }
106
- const waitingList = chainHostList.map((item) => declareAccountByChain({ chainHost: item, wallet, moniker }));
109
+ const waitingList = [...new Set(chainHostList)].map((item) =>
110
+ declareAccountByChain({ chainHost: item, wallet, moniker })
111
+ );
107
112
  await Promise.all(waitingList);
108
113
  }
109
114
 
110
115
  async function migrateAccountByChain({ chainHost, user, wallet }) {
111
116
  try {
112
- const client = new Client(chainHost);
117
+ const client = getChainClient(chainHost);
113
118
  const tx = await client.signAccountMigrateTx({
114
119
  tx: {
115
120
  itx: {
@@ -120,10 +125,10 @@ async function migrateAccountByChain({ chainHost, user, wallet }) {
120
125
  wallet,
121
126
  });
122
127
  const hash = await client.sendAccountMigrateTx({ tx, wallet });
123
- logger.info('migration account done', { did: user.did, hash });
128
+ logger.info('migration account done', { chain: chainHost, did: user.did, hash });
124
129
  return hash;
125
130
  } catch (error) {
126
- logger.error('migration account failed', { did: user.did, error });
131
+ logger.error('migration account failed', { chain: chainHost, did: user.did, error });
127
132
  return undefined;
128
133
  }
129
134
  }
@@ -134,7 +139,9 @@ async function migrateAccount({ wallet, blocklet, user }) {
134
139
  if (chainHost && chainHost !== 'none') {
135
140
  chainHostList.push(chainHost);
136
141
  }
137
- const waitingList = chainHostList.map((item) => migrateAccountByChain({ chainHost: item, wallet, user }));
142
+ const waitingList = [...new Set(chainHostList)].map((item) =>
143
+ migrateAccountByChain({ chainHost: item, wallet, user })
144
+ );
138
145
  await Promise.all(waitingList);
139
146
  }
140
147
 
@@ -4,7 +4,7 @@ const { CHANNEL_TYPE, parseChannel, getAppPublicChannel } = require('@blocklet/m
4
4
  const { validateNotification } = require('@blocklet/sdk/lib/validators/notification');
5
5
  const { NODE_MODES } = require('@abtnode/constant');
6
6
 
7
- const { ensureSender, parseNotification, broadcast, EVENTS } = require('../util');
7
+ const { ensureSenderApp, parseNotification, broadcast, EVENTS } = require('../util');
8
8
 
9
9
  /**
10
10
  *
@@ -54,7 +54,7 @@ const sendToAppChannel = async ({
54
54
 
55
55
  // parse sender
56
56
 
57
- const appInfo = await ensureSender({ sender, node, nodeInfo });
57
+ const appInfo = await ensureSenderApp({ sender, node, nodeInfo });
58
58
 
59
59
  if (![appInfo.wallet.address, appInfo.permanentWallet.address].includes(channelInfo.appDid)) {
60
60
  throw new Error('Cannot sent message to channel of other app');
@@ -8,7 +8,7 @@ const { getWalletDidFromUser } = require('@blocklet/sdk/lib/did');
8
8
 
9
9
  // eslint-disable-next-line global-require
10
10
  const logger = require('@abtnode/logger')(`${require('../../../package.json').name}:notification`);
11
- const { ensureSender, parseNotification, broadcast, EVENTS } = require('../util');
11
+ const { ensureSenderApp, parseNotification, broadcast, EVENTS } = require('../util');
12
12
  const states = require('../../state');
13
13
 
14
14
  /**
@@ -52,7 +52,7 @@ const sendToDid = async ({ sender, receiver: rawDid, notification, options, node
52
52
 
53
53
  // parse sender
54
54
 
55
- const senderInfo = await ensureSender({ sender, node, nodeInfo });
55
+ const senderInfo = await ensureSenderApp({ sender, node, nodeInfo });
56
56
 
57
57
  // parse notification
58
58
 
@@ -1,5 +1,5 @@
1
1
  const { CHANNEL_TYPE, parseChannel, getRelayChannel } = require('@blocklet/meta/lib/channel');
2
- const { ensureSender, broadcast, EVENTS } = require('../util');
2
+ const { ensureSenderApp, broadcast, EVENTS } = require('../util');
3
3
 
4
4
  // Only an application can send message to a relay channel
5
5
  // But multiple clients can subscribe to the same relay channel
@@ -12,7 +12,7 @@ const sendToRelay = async ({ sender, channel, event = EVENTS.MESSAGE, data, node
12
12
  const nodeInfo = await node.getNodeInfo();
13
13
 
14
14
  // Sender is appDid of application
15
- const appInfo = await ensureSender({ sender, node, nodeInfo });
15
+ const appInfo = await ensureSenderApp({ sender, node, nodeInfo });
16
16
 
17
17
  if (![appInfo.wallet.address, appInfo.permanentWallet.address].includes(channelInfo.appDid)) {
18
18
  throw new Error('Cannot sent message to relay channel of other app');
@@ -47,7 +47,7 @@ const broadcast = (...args) => {
47
47
  });
48
48
  };
49
49
 
50
- const ensureSender = async ({ sender, node, nodeInfo }) => {
50
+ const ensureSenderApp = async ({ sender, node, nodeInfo }) => {
51
51
  let appInfo;
52
52
  try {
53
53
  appInfo = await getApplicationInfo({ node, nodeInfo, teamDid: sender.appDid });
@@ -77,7 +77,7 @@ const EVENTS = {
77
77
  module.exports = {
78
78
  parseNotification,
79
79
  broadcast,
80
- ensureSender,
80
+ ensureSenderApp,
81
81
  getTokenInfo,
82
82
  EVENTS,
83
83
  };