@abtnode/blocklet-services 1.16.5 → 1.16.6-beta-8be2fe37

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 (39) hide show
  1. package/api/libs/connect/session.js +85 -31
  2. package/api/routes/blocklet.js +24 -0
  3. package/api/routes/oauth.js +2 -2
  4. package/api/services/auth/connect/exchange-passport.js +58 -0
  5. package/api/services/auth/connect/login.js +2 -1
  6. package/api/services/auth/connect/pre-setup.js +2 -2
  7. package/api/services/auth/connect/receive-transfer-app-owner.js +385 -0
  8. package/api/services/auth/connect/transfer-app-owner.js +30 -0
  9. package/api/services/auth/index.js +6 -0
  10. package/api/services/oauth/index.js +30 -23
  11. package/build/asset-manifest.json +27 -21
  12. package/build/index.html +1 -1
  13. package/build/static/css/{204.1d1e88ad.chunk.css → 61.03a48b17.chunk.css} +1 -1
  14. package/build/static/js/162.58348adf.chunk.js +3 -0
  15. package/build/static/js/343.54712c04.chunk.js +2 -0
  16. package/build/static/js/359.c47779c2.chunk.js +2 -0
  17. package/build/static/js/371.ccef728f.chunk.js +2 -0
  18. package/build/static/js/42.a0526d16.chunk.js +2 -0
  19. package/build/static/js/547.f8830a9e.chunk.js +2 -0
  20. package/build/static/js/573.2052cb80.chunk.js +2 -0
  21. package/build/static/js/61.20fc6264.chunk.js +3 -0
  22. package/build/static/js/648.b5b229e5.chunk.js +3 -0
  23. package/build/static/js/716.874040d8.chunk.js +2 -0
  24. package/build/static/js/868.9c03fa86.chunk.js +2 -0
  25. package/build/static/js/main.88eb4897.js +3 -0
  26. package/package.json +32 -30
  27. package/build/static/js/204.df50af69.chunk.js +0 -3
  28. package/build/static/js/343.b31c2008.chunk.js +0 -2
  29. package/build/static/js/371.88127b62.chunk.js +0 -2
  30. package/build/static/js/573.2687bb44.chunk.js +0 -2
  31. package/build/static/js/648.7a2e44c8.chunk.js +0 -3
  32. package/build/static/js/712.9667cdcd.chunk.js +0 -3
  33. package/build/static/js/716.e1534c42.chunk.js +0 -2
  34. package/build/static/js/868.4d364267.chunk.js +0 -2
  35. package/build/static/js/main.3b61bb8b.js +0 -3
  36. /package/build/static/js/{712.9667cdcd.chunk.js.LICENSE.txt → 162.58348adf.chunk.js.LICENSE.txt} +0 -0
  37. /package/build/static/js/{204.df50af69.chunk.js.LICENSE.txt → 61.20fc6264.chunk.js.LICENSE.txt} +0 -0
  38. /package/build/static/js/{648.7a2e44c8.chunk.js.LICENSE.txt → 648.b5b229e5.chunk.js.LICENSE.txt} +0 -0
  39. /package/build/static/js/{main.3b61bb8b.js.LICENSE.txt → main.88eb4897.js.LICENSE.txt} +0 -0
@@ -0,0 +1,385 @@
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, isRunning } = 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) || isRunning(blocklet.status)) {
131
+ throw new Error(messages.appIsRunning[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
+ if (userDid === oldOwnerDid) {
192
+ throw new Error(messages.notAllowedTransferToSelf[locale]);
193
+ }
194
+
195
+ const { appPid } = blocklet;
196
+
197
+ const transfer = await node.checkTransferAppOwnerSession({ appDid, transferId });
198
+
199
+ if (transfer.appDid !== appDid) {
200
+ throw new Error(`Invalid appDid. Expect: ${transfer.appDid}, Actual: ${appDid}`);
201
+ }
202
+
203
+ const {
204
+ name: issuerName,
205
+ wallet: issuerWallet,
206
+ passportColor,
207
+ dataDir,
208
+ } = await getApplicationInfo({ node, teamDid: appPid });
209
+
210
+ const statusEndpointBaseUrl = baseUrl;
211
+ const endpoint = baseUrl;
212
+
213
+ const vcParams = {
214
+ issuerName,
215
+ issuerWallet,
216
+ ownerDid: userDid,
217
+ passport: await createPassport({
218
+ name: ROLES.OWNER,
219
+ node,
220
+ teamDid: appPid,
221
+ locale,
222
+ endpoint,
223
+ }),
224
+ endpoint: getPassportStatusEndpoint({
225
+ baseUrl: statusEndpointBaseUrl,
226
+ userDid,
227
+ teamDid: appPid,
228
+ }),
229
+ types: [],
230
+ ownerProfile: profile,
231
+ preferredColor: passportColor,
232
+ };
233
+
234
+ const vc = createPassportVC(vcParams);
235
+
236
+ const role = getRoleFromLocalPassport(get(vc, 'credentialSubject.passport'));
237
+ const passport = createUserPassport(vc, { role });
238
+
239
+ const user = await getUser(node, appPid, userDid);
240
+
241
+ // update state
242
+
243
+ await node.updateBlockletOwner({ did: appPid, owner: { did: userDid, pk: userPk } });
244
+
245
+ const avatar = await extractUserAvatar(get(profile, 'avatar'), {
246
+ dataDir,
247
+ });
248
+
249
+ const now = new Date().toISOString();
250
+
251
+ if (user) {
252
+ const doc = await node.updateUser({
253
+ teamDid: appPid,
254
+ user: {
255
+ ...mergeUserData(user, {
256
+ avatar,
257
+ locale,
258
+ lastLoginIp: get(req, 'headers[x-real-ip]') || '',
259
+ lastUsedPassport: passport,
260
+ connectedAccount: { provider: 'wallet' },
261
+ }),
262
+ ...profile,
263
+ avatar,
264
+ },
265
+ });
266
+ await node.createAuditLog(
267
+ {
268
+ action: 'updateUser',
269
+ args: { teamDid: appPid, userDid, passport, reason: 'transfer ownership' },
270
+ context: formatContext(Object.assign(req, { user })),
271
+ result: doc,
272
+ },
273
+ node
274
+ );
275
+ } else {
276
+ const doc = await node.addUser({
277
+ teamDid: appPid,
278
+ user: {
279
+ ...profile,
280
+ avatar,
281
+ did: userDid,
282
+ pk: userPk,
283
+ approved: true,
284
+ locale,
285
+ passports: [passport],
286
+ firstLoginAt: now,
287
+ lastLoginAt: now,
288
+ lastLoginIp: get(req, 'headers[x-real-ip]') || '',
289
+ extraConfigs: {
290
+ sourceProvider: 'wallet',
291
+ connectedAccounts: [
292
+ {
293
+ provider: 'wallet',
294
+ did: userDid,
295
+ pk: userPk,
296
+ lastLoginAt: now,
297
+ firstLoginAt: now,
298
+ },
299
+ ],
300
+ },
301
+ },
302
+ });
303
+
304
+ await node.createAuditLog(
305
+ {
306
+ action: 'addUser',
307
+ args: { teamDid: appPid, userDid, passport, reason: 'transfer ownership' },
308
+ context: formatContext(Object.assign(req, { user: doc })),
309
+ result: doc,
310
+ },
311
+ node
312
+ );
313
+ }
314
+
315
+ // NOTICE: must NOT use appDid here if update BLOCKLET_APP_SK
316
+ await node.configBlocklet(
317
+ {
318
+ did: blocklet.appPid,
319
+ configs: [{ key: 'BLOCKLET_APP_SK', value: newSk, secure: true }],
320
+ skipHook: true,
321
+ skipDidDocument: true,
322
+ },
323
+ formatContext(Object.assign(req, { user: { did: userDid, fullName: 'Owner', role: 'owner' } }))
324
+ );
325
+
326
+ try {
327
+ if (oldOwnerDid) {
328
+ const oldOwner = await getUser(node, appPid, oldOwnerDid);
329
+ if (oldOwner) {
330
+ const filteredPassports = (oldOwner.passports || []).filter((p) => p.role !== ROLES.OWNER);
331
+ if (filteredPassports.length !== (oldOwner.passports || []).length) {
332
+ logger.info('update old owner passport', {
333
+ oldOwnerDid,
334
+ filteredPassports: filteredPassports.map((x) => x.id),
335
+ });
336
+ await node.updateUser({
337
+ teamDid: appPid,
338
+ user: {
339
+ did: oldOwnerDid,
340
+ pk: oldOwner.pk,
341
+ passports: filteredPassports,
342
+ },
343
+ });
344
+ }
345
+ }
346
+ }
347
+ } catch (error) {
348
+ logger.error('remove old owner passport failed', { error });
349
+ }
350
+
351
+ try {
352
+ await node.closeTransferAppOwnerSession({
353
+ appPid: blocklet.appPid,
354
+ transferId,
355
+ status: 'success',
356
+ });
357
+ } catch (error) {
358
+ logger.error('closeTransferAppOwnerSession failed', { error });
359
+ }
360
+
361
+ // migrate on-chain account
362
+ pRetry(() => migrateAppOnChain(blocklet, oldSk, newSk), {
363
+ retries: 10,
364
+ onFailedAttempt: console.error,
365
+ }).catch((error) => {
366
+ logger.error('migrateAppOnChain failed', { error });
367
+ });
368
+
369
+ await sleep(3000);
370
+ logger.info('transfer ownership success', { userDid });
371
+
372
+ // Generate new session token that client can save to localStorage
373
+ const sessionToken = await createSessionToken(userDid, { passport: vc, role });
374
+ await updateSession({ sessionToken }, true);
375
+ await updateSession({ passportId: vc?.id });
376
+ logger.info('invite.success', { userDid });
377
+
378
+ return {
379
+ disposition: 'attachment',
380
+ type: 'VerifiableCredential',
381
+ data: vc,
382
+ };
383
+ },
384
+ };
385
+ };
@@ -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
 
@@ -1,25 +1,28 @@
1
1
  {
2
2
  "files": {
3
3
  "main.css": "/.blocklet/proxy/blocklet-service/static/css/main.632501d5.css",
4
- "main.js": "/.blocklet/proxy/blocklet-service/static/js/main.3b61bb8b.js",
5
- "static/js/716.e1534c42.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/716.e1534c42.chunk.js",
4
+ "main.js": "/.blocklet/proxy/blocklet-service/static/js/main.88eb4897.js",
5
+ "static/js/716.874040d8.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/716.874040d8.chunk.js",
6
+ "static/js/359.c47779c2.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/359.c47779c2.chunk.js",
6
7
  "static/js/255.279b1bca.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/255.279b1bca.chunk.js",
7
- "static/js/371.88127b62.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/371.88127b62.chunk.js",
8
+ "static/js/371.ccef728f.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/371.ccef728f.chunk.js",
8
9
  "static/js/737.ed53dec5.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/737.ed53dec5.chunk.js",
9
10
  "static/js/460.97b02ba6.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/460.97b02ba6.chunk.js",
10
- "static/js/868.4d364267.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/868.4d364267.chunk.js",
11
- "static/js/343.b31c2008.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/343.b31c2008.chunk.js",
11
+ "static/js/868.9c03fa86.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/868.9c03fa86.chunk.js",
12
+ "static/js/547.f8830a9e.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/547.f8830a9e.chunk.js",
13
+ "static/js/343.54712c04.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/343.54712c04.chunk.js",
12
14
  "static/js/682.c64ae291.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/682.c64ae291.chunk.js",
13
15
  "static/js/711.6c22b7c7.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/711.6c22b7c7.chunk.js",
14
16
  "static/js/437.d815f0c0.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/437.d815f0c0.chunk.js",
15
17
  "static/js/690.e2a01dd2.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/690.e2a01dd2.chunk.js",
16
- "static/js/648.7a2e44c8.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/648.7a2e44c8.chunk.js",
17
- "static/js/712.9667cdcd.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/712.9667cdcd.chunk.js",
18
+ "static/js/42.a0526d16.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/42.a0526d16.chunk.js",
19
+ "static/js/648.b5b229e5.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/648.b5b229e5.chunk.js",
20
+ "static/js/162.58348adf.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/162.58348adf.chunk.js",
18
21
  "static/js/610.40349d57.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/610.40349d57.chunk.js",
19
- "static/css/204.1d1e88ad.chunk.css": "/.blocklet/proxy/blocklet-service/static/css/204.1d1e88ad.chunk.css",
20
- "static/js/204.df50af69.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/204.df50af69.chunk.js",
22
+ "static/css/61.03a48b17.chunk.css": "/.blocklet/proxy/blocklet-service/static/css/61.03a48b17.chunk.css",
23
+ "static/js/61.20fc6264.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/61.20fc6264.chunk.js",
21
24
  "static/js/199.3d76c24b.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/199.3d76c24b.chunk.js",
22
- "static/js/573.2687bb44.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/573.2687bb44.chunk.js",
25
+ "static/js/573.2052cb80.chunk.js": "/.blocklet/proxy/blocklet-service/static/js/573.2052cb80.chunk.js",
23
26
  "static/media/ubuntu-mono-all-400-normal.woff": "/.blocklet/proxy/blocklet-service/static/media/ubuntu-mono-all-400-normal.c879328bc62e9c68268f.woff",
24
27
  "static/media/lato-all-400-normal.woff": "/.blocklet/proxy/blocklet-service/static/media/lato-all-400-normal.3dc1eff492ab1f598560.woff",
25
28
  "static/media/iconify.cjs": "/.blocklet/proxy/blocklet-service/static/media/iconify.32b54549e843e448ee9b.cjs",
@@ -42,28 +45,31 @@
42
45
  "router-template-styles/styles.css": "/.blocklet/proxy/blocklet-service/router-template-styles/styles.css",
43
46
  "index.html": "/.blocklet/proxy/blocklet-service/index.html",
44
47
  "main.632501d5.css.map": "/.blocklet/proxy/blocklet-service/static/css/main.632501d5.css.map",
45
- "main.3b61bb8b.js.map": "/.blocklet/proxy/blocklet-service/static/js/main.3b61bb8b.js.map",
46
- "716.e1534c42.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/716.e1534c42.chunk.js.map",
48
+ "main.88eb4897.js.map": "/.blocklet/proxy/blocklet-service/static/js/main.88eb4897.js.map",
49
+ "716.874040d8.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/716.874040d8.chunk.js.map",
50
+ "359.c47779c2.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/359.c47779c2.chunk.js.map",
47
51
  "255.279b1bca.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/255.279b1bca.chunk.js.map",
48
- "371.88127b62.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/371.88127b62.chunk.js.map",
52
+ "371.ccef728f.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/371.ccef728f.chunk.js.map",
49
53
  "737.ed53dec5.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/737.ed53dec5.chunk.js.map",
50
54
  "460.97b02ba6.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/460.97b02ba6.chunk.js.map",
51
- "868.4d364267.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/868.4d364267.chunk.js.map",
52
- "343.b31c2008.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/343.b31c2008.chunk.js.map",
55
+ "868.9c03fa86.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/868.9c03fa86.chunk.js.map",
56
+ "547.f8830a9e.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/547.f8830a9e.chunk.js.map",
57
+ "343.54712c04.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/343.54712c04.chunk.js.map",
53
58
  "682.c64ae291.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/682.c64ae291.chunk.js.map",
54
59
  "711.6c22b7c7.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/711.6c22b7c7.chunk.js.map",
55
60
  "437.d815f0c0.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/437.d815f0c0.chunk.js.map",
56
61
  "690.e2a01dd2.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/690.e2a01dd2.chunk.js.map",
57
- "648.7a2e44c8.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/648.7a2e44c8.chunk.js.map",
58
- "712.9667cdcd.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/712.9667cdcd.chunk.js.map",
62
+ "42.a0526d16.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/42.a0526d16.chunk.js.map",
63
+ "648.b5b229e5.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/648.b5b229e5.chunk.js.map",
64
+ "162.58348adf.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/162.58348adf.chunk.js.map",
59
65
  "610.40349d57.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/610.40349d57.chunk.js.map",
60
- "204.1d1e88ad.chunk.css.map": "/.blocklet/proxy/blocklet-service/static/css/204.1d1e88ad.chunk.css.map",
61
- "204.df50af69.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/204.df50af69.chunk.js.map",
66
+ "61.03a48b17.chunk.css.map": "/.blocklet/proxy/blocklet-service/static/css/61.03a48b17.chunk.css.map",
67
+ "61.20fc6264.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/61.20fc6264.chunk.js.map",
62
68
  "199.3d76c24b.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/199.3d76c24b.chunk.js.map",
63
- "573.2687bb44.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/573.2687bb44.chunk.js.map"
69
+ "573.2052cb80.chunk.js.map": "/.blocklet/proxy/blocklet-service/static/js/573.2052cb80.chunk.js.map"
64
70
  },
65
71
  "entrypoints": [
66
72
  "static/css/main.632501d5.css",
67
- "static/js/main.3b61bb8b.js"
73
+ "static/js/main.88eb4897.js"
68
74
  ]
69
75
  }
package/build/index.html CHANGED
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#000000"/><title>Blocklet Service</title><script src=".well-known/service/api/env"></script><script src="/__blocklet__.js"></script><script defer="defer" src="/.blocklet/proxy/blocklet-service/static/js/main.3b61bb8b.js"></script><link href="/.blocklet/proxy/blocklet-service/static/css/main.632501d5.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#000000"/><title>Blocklet Service</title><script src=".well-known/service/api/env"></script><script src="/__blocklet__.js"></script><script defer="defer" src="/.blocklet/proxy/blocklet-service/static/js/main.88eb4897.js"></script><link href="/.blocklet/proxy/blocklet-service/static/css/main.632501d5.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>