@abtnode/core 1.16.11-next-069c3537 → 1.16.11-next-3d2b39f7

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.
@@ -3,10 +3,8 @@
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const dayjs = require('dayjs');
6
- const joinURL = require('url-join');
7
6
  const shelljs = require('shelljs');
8
7
  const os = require('os');
9
- const pRetry = require('p-retry');
10
8
  const tar = require('tar');
11
9
  const get = require('lodash/get');
12
10
  const uniq = require('lodash/uniq');
@@ -19,19 +17,16 @@ const diff = require('deep-diff');
19
17
  const createArchive = require('archiver');
20
18
  const isUrl = require('is-url');
21
19
  const semver = require('semver');
22
- const axios = require('@abtnode/util/lib/axios');
23
- const { stableStringify } = require('@arcblock/vc');
24
20
  const { chainInfo: chainInfoSchema } = require('@arcblock/did-auth/lib/schema');
25
21
 
26
22
  const { fromSecretKey } = require('@ocap/wallet');
27
- const { toHex, toBase58, isHex, toDid, toAddress } = require('@ocap/util');
23
+ const { toHex, isHex, toDid, toAddress } = require('@ocap/util');
28
24
  const { isValid: isValidDid, isEthereumDid } = require('@arcblock/did');
29
25
  const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
30
26
  const pm2 = require('@abtnode/util/lib/async-pm2');
31
27
  const sleep = require('@abtnode/util/lib/sleep');
32
28
  const getPm2ProcessInfo = require('@abtnode/util/lib/get-pm2-process-info');
33
29
  const killProcessOccupiedPorts = require('@abtnode/util/lib/kill-process-occupied-ports');
34
- const getNodeWallet = require('@abtnode/util/lib/get-app-wallet');
35
30
  const { formatEnv } = require('@abtnode/util/lib/security');
36
31
  const ensureEndpointHealthy = require('@abtnode/util/lib/ensure-endpoint-healthy');
37
32
  const CustomError = require('@abtnode/util/lib/custom-error');
@@ -79,6 +74,7 @@ const {
79
74
  findWebInterface,
80
75
  forEachBlockletSync,
81
76
  forEachChildSync,
77
+ forEachComponentV2Sync,
82
78
  isComponentBlocklet,
83
79
  getSharedConfigObj,
84
80
  getComponentName,
@@ -86,6 +82,8 @@ const {
86
82
  getBlockletAppIdList,
87
83
  isBeforeInstalled,
88
84
  getChainInfo,
85
+ isInProgress,
86
+ isRunning,
89
87
  } = require('@blocklet/meta/lib/util');
90
88
  const toBlockletDid = require('@blocklet/meta/lib/did');
91
89
  const { titleSchema, descriptionSchema, logoSchema } = require('@blocklet/meta/lib/schema');
@@ -100,7 +98,7 @@ const { validate: validateEngine, get: getEngine } = require('../blocklet/manage
100
98
 
101
99
  const isRequirementsSatisfied = require('./requirement');
102
100
  const { getDidDomainForBlocklet } = require('./get-domain-for-blocklet');
103
- const { expandBundle, findInterfacePortByName, prettyURL, getNFTState, templateReplace } = require('./index');
101
+ const { expandBundle, findInterfacePortByName, prettyURL, templateReplace } = require('./index');
104
102
  const externalIpUtil = require('./get-accessible-external-node-ip');
105
103
  const { getIpDnsDomainForBlocklet } = require('./get-domain-for-blocklet');
106
104
  const { replaceDomainSlot } = require('./index');
@@ -373,8 +371,9 @@ const getComponentSystemEnvironments = (blocklet) => {
373
371
  }
374
372
 
375
373
  return {
376
- BLOCKLET_REAL_DID: blocklet.env.id,
374
+ BLOCKLET_REAL_DID: blocklet.env.id, // <appDid>/componentDid> e.g. xxxxx/xxxxx
377
375
  BLOCKLET_REAL_NAME: blocklet.env.name,
376
+ BLOCKLET_COMPONENT_DID: blocklet.meta.did, // component meta did e.g. xxxxxx
378
377
  BLOCKLET_DATA_DIR: blocklet.env.dataDir,
379
378
  BLOCKLET_LOG_DIR: blocklet.env.logsDir,
380
379
  BLOCKLET_CACHE_DIR: blocklet.env.cacheDir,
@@ -506,7 +505,15 @@ const getHealthyCheckTimeout = (blocklet, { checkHealthImmediately } = {}) => {
506
505
  */
507
506
  const startBlockletProcess = async (
508
507
  blocklet,
509
- { preStart = noop, postStart = noopAsync, nodeEnvironments, nodeInfo, e2eMode, skippedProcessIds = [] } = {}
508
+ {
509
+ preStart = noop,
510
+ postStart = noopAsync,
511
+ nodeEnvironments,
512
+ nodeInfo,
513
+ e2eMode,
514
+ skippedProcessIds = [],
515
+ componentDids,
516
+ } = {}
510
517
  ) => {
511
518
  if (!blocklet) {
512
519
  throw new Error('blocklet should not be empty');
@@ -522,7 +529,12 @@ const startBlockletProcess = async (
522
529
  const { appMain, processId, appCwd, logsDir } = b.env;
523
530
 
524
531
  if (skippedProcessIds.includes(processId)) {
525
- logger.info(`skip start process ${processId}`);
532
+ logger.info(`skip start skipped process ${processId}`);
533
+ return;
534
+ }
535
+
536
+ if (shouldSkipComponent(b.meta.did, componentDids)) {
537
+ logger.info(`skip start process not selected: ${b.meta.did}`, { processId });
526
538
  return;
527
539
  }
528
540
 
@@ -623,31 +635,15 @@ const startBlockletProcess = async (
623
635
  * Stop all precesses of a blocklet
624
636
  * @param {*} blocklet should contain env props
625
637
  */
626
- const stopBlockletProcess = async (blocklet, { preStop = noop, skippedProcessIds = [] } = {}) => {
627
- await forEachBlocklet(
628
- blocklet,
629
- async (b, { ancestors }) => {
630
- if (b.meta?.group === BlockletGroup.gateway) {
631
- return;
632
- }
633
-
634
- if (skippedProcessIds.includes(b.env.processId)) {
635
- logger.info(`skip stop process ${b.env.processId}`);
636
- return;
637
- }
638
-
639
- await preStop(b, { ancestors });
640
- await deleteProcess(b.env.processId);
641
- },
642
- { parallel: true }
643
- );
638
+ const stopBlockletProcess = async (blocklet, { preStop = noop, skippedProcessIds = [], componentDids } = {}) => {
639
+ return deleteBlockletProcess(blocklet, { preDelete: preStop, skippedProcessIds, componentDids });
644
640
  };
645
641
 
646
642
  /**
647
643
  * Delete all precesses of a blocklet
648
644
  * @param {*} blocklet should contain env props
649
645
  */
650
- const deleteBlockletProcess = async (blocklet, { preDelete = noop, skippedProcessIds = [] } = {}) => {
646
+ const deleteBlockletProcess = async (blocklet, { preDelete = noop, skippedProcessIds = [], componentDids } = {}) => {
651
647
  await forEachBlocklet(
652
648
  blocklet,
653
649
  async (b, { ancestors }) => {
@@ -657,7 +653,12 @@ const deleteBlockletProcess = async (blocklet, { preDelete = noop, skippedProces
657
653
  }
658
654
 
659
655
  if (skippedProcessIds.includes(b.env.processId)) {
660
- logger.info(`skip delete process ${b.env.processId}`);
656
+ logger.info(`skip delete skipped process ${b.env.processId}`);
657
+ return;
658
+ }
659
+
660
+ if (shouldSkipComponent(b.meta?.did, componentDids)) {
661
+ logger.info(`skip delete process not selected: ${b.meta.did}`, { processId: b.env.processId });
661
662
  return;
662
663
  }
663
664
 
@@ -672,52 +673,25 @@ const deleteBlockletProcess = async (blocklet, { preDelete = noop, skippedProces
672
673
  * Reload all precesses of a blocklet
673
674
  * @param {*} blocklet should contain env props
674
675
  */
675
- const reloadBlockletProcess = async (blocklet) =>
676
+ const reloadBlockletProcess = async (blocklet, { componentDids } = {}) =>
676
677
  forEachBlocklet(
677
678
  blocklet,
678
679
  async (b) => {
679
680
  if (b.meta.group === BlockletGroup.gateway) {
680
681
  return;
681
682
  }
683
+
684
+ if (shouldSkipComponent(b.meta.did, componentDids)) {
685
+ logger.info(`skip reload process not selected: ${b.meta.did}`, { processId: b.env.processId });
686
+ return;
687
+ }
688
+
682
689
  logger.info('reload process', { processId: b.env.processId });
683
690
  await reloadProcess(b.env.processId);
684
691
  },
685
692
  { parallel: false }
686
693
  );
687
694
 
688
- const getBlockletStatusFromProcess = async (blocklet) => {
689
- const tasks = [];
690
- await forEachBlocklet(blocklet, (b) => {
691
- if (b.meta.group !== BlockletGroup.gateway) {
692
- tasks.push(getProcessState(b.env.processId));
693
- }
694
- });
695
-
696
- const list = await Promise.all(tasks);
697
-
698
- if (!list.length) {
699
- return blocklet.status;
700
- }
701
-
702
- return getRootBlockletStatus(list);
703
- };
704
-
705
- /**
706
- * @param {Array<BlockletStatus>} statusList
707
- */
708
- const getRootBlockletStatus = (statusList = []) => {
709
- const noRunningStatus = statusList.find((x) => x !== BlockletStatus.running);
710
- if (noRunningStatus) {
711
- if (statusList.some((x) => x === BlockletStatus.error)) {
712
- return BlockletStatus.error;
713
- }
714
-
715
- return noRunningStatus;
716
- }
717
-
718
- return BlockletStatus.running;
719
- };
720
-
721
695
  /**
722
696
  * @param {*} processId
723
697
  * @returns {BlockletStatus}
@@ -905,13 +879,20 @@ const validateBlockletChainInfo = (blocklet) => {
905
879
  return chainInfo;
906
880
  };
907
881
 
908
- const checkBlockletProcessHealthy = async (blocklet, { minConsecutiveTime, timeout } = {}) => {
882
+ const checkBlockletProcessHealthy = async (blocklet, { minConsecutiveTime, timeout, componentDids } = {}) => {
909
883
  await forEachBlocklet(
910
884
  blocklet,
911
885
  async (b) => {
912
- if (b.meta.group !== BlockletGroup.gateway) {
913
- await _checkProcessHealthy(b, { minConsecutiveTime, timeout });
886
+ if (b.meta.group === BlockletGroup.gateway) {
887
+ return;
888
+ }
889
+
890
+ if (shouldSkipComponent(b.meta.did, componentDids)) {
891
+ logger.info('skip check component healthy not selected', { id: b.env.id, processId: b.env.processId });
892
+ return;
914
893
  }
894
+
895
+ await _checkProcessHealthy(b, { minConsecutiveTime, timeout });
915
896
  },
916
897
  { parallel: true }
917
898
  );
@@ -1538,51 +1519,6 @@ const getBlockletURLForLauncher = async ({ blocklet, nodeInfo }) => {
1538
1519
  const didDomain = getDidDomainForBlocklet({ appPid: blocklet.appPid, didDomain: nodeInfo.didDomain });
1539
1520
  return `https://${didDomain}`;
1540
1521
  };
1541
-
1542
- const consumeServerlessNFT = async ({ nftId, nodeInfo, blocklet }) => {
1543
- try {
1544
- const state = await getNFTState(blocklet.controller.chainHost, nftId);
1545
- if (!state) {
1546
- throw new Error(`get nft state failed, chainHost: ${blocklet.controller.chainHost}, nftId: ${nftId}`);
1547
- }
1548
-
1549
- const appURL = await getBlockletURLForLauncher({ blocklet, nodeInfo });
1550
-
1551
- logger.info('app url when consuming nft', { appURL, nftId });
1552
-
1553
- const body = { nftId, appURL };
1554
-
1555
- const { launcherUrl } = state.data.value;
1556
-
1557
- const wallet = getNodeWallet(nodeInfo.sk);
1558
- const func = async () => {
1559
- const { data } = await axios.post(joinURL(launcherUrl, '/api/serverless/consume'), body, {
1560
- headers: {
1561
- 'x-sig': toBase58(wallet.sign(stableStringify(body))),
1562
- },
1563
- });
1564
-
1565
- return data;
1566
- };
1567
-
1568
- const delay = 10 * 1000;
1569
- const result = await pRetry(func, {
1570
- retries: 3,
1571
- minTimeout: delay,
1572
- maxTimeout: delay,
1573
- onFailedAttempt: (error) => {
1574
- logger.error(`attempt consume nft ${nftId} failed`, { error });
1575
- },
1576
- });
1577
-
1578
- logger.info('consume serverless nft success', { nftId, hash: result.hash });
1579
- } catch (error) {
1580
- logger.error('consume serverless nft failed', { nftId, error });
1581
-
1582
- throw new Error(`consume nft ${nftId} failed`);
1583
- }
1584
- };
1585
-
1586
1522
  const createDataArchive = (dataDir, fileName) => {
1587
1523
  const zipPath = path.join(os.tmpdir(), fileName);
1588
1524
  if (fs.existsSync(zipPath)) {
@@ -1952,13 +1888,77 @@ const getBlockletDidDomainList = (blocklet, nodeInfo) => {
1952
1888
  return domainAliases;
1953
1889
  };
1954
1890
 
1891
+ const getBlockletStatus = (blocklet) => {
1892
+ const fallbackStatus = BlockletStatus.stopped;
1893
+
1894
+ if (!blocklet) {
1895
+ return fallbackStatus;
1896
+ }
1897
+
1898
+ if (!blocklet.children?.length) {
1899
+ if (blocklet.meta?.group === BlockletGroup.gateway) {
1900
+ return blocklet.status;
1901
+ }
1902
+
1903
+ if (blocklet.status === BlockletStatus.added) {
1904
+ return BlockletStatus.added;
1905
+ }
1906
+
1907
+ // for backward compatibility
1908
+ if (!blocklet.structVersion) {
1909
+ return blocklet.status;
1910
+ }
1911
+
1912
+ return fallbackStatus;
1913
+ }
1914
+
1915
+ let inProgressStatus;
1916
+ let runningStatus;
1917
+ let status;
1918
+
1919
+ forEachComponentV2Sync(blocklet, (component) => {
1920
+ if (component.meta?.group === BlockletGroup.gateway) {
1921
+ return;
1922
+ }
1923
+
1924
+ if (isInProgress(component.status)) {
1925
+ if (!inProgressStatus) {
1926
+ inProgressStatus = component.status;
1927
+ }
1928
+ return;
1929
+ }
1930
+
1931
+ if (isRunning(component.status)) {
1932
+ runningStatus = component.status;
1933
+ return;
1934
+ }
1935
+
1936
+ status = component.status;
1937
+ });
1938
+
1939
+ return inProgressStatus || runningStatus || status;
1940
+ };
1941
+
1942
+ const shouldSkipComponent = (componentDid, whiteList) => {
1943
+ if (!whiteList || !Array.isArray(whiteList)) {
1944
+ return false;
1945
+ }
1946
+
1947
+ const arr = whiteList.filter(Boolean);
1948
+
1949
+ if (!arr.length) {
1950
+ return false;
1951
+ }
1952
+
1953
+ return !arr.includes(componentDid);
1954
+ };
1955
+
1955
1956
  const shouldCleanExpiredBlocklet = (expirationDate) => {
1956
1957
  return dayjs().diff(dayjs(expirationDate), 'day') > EXPIRED_BLOCKLET_DATA_RETENTION_DAYS;
1957
1958
  };
1958
1959
 
1959
1960
  module.exports = {
1960
1961
  updateBlockletFallbackLogo,
1961
- consumeServerlessNFT,
1962
1962
  forEachBlocklet,
1963
1963
  getBlockletMetaFromUrl: (url) => getBlockletMetaFromUrl(url, { logger }),
1964
1964
  parseComponents,
@@ -1975,7 +1975,6 @@ module.exports = {
1975
1975
  stopBlockletProcess,
1976
1976
  deleteBlockletProcess,
1977
1977
  reloadBlockletProcess,
1978
- getBlockletStatusFromProcess,
1979
1978
  checkBlockletProcessHealthy,
1980
1979
  getHealthyCheckTimeout,
1981
1980
  getProcessInfo,
@@ -2014,6 +2013,9 @@ module.exports = {
2014
2013
  getFixedBundleSource,
2015
2014
  ensureAppLogo,
2016
2015
  getBlockletDidDomainList,
2016
+ getProcessState,
2017
+ getBlockletStatus,
2018
+ shouldSkipComponent,
2017
2019
  shouldCleanExpiredBlocklet,
2018
2020
  getBlockletURLForLauncher,
2019
2021
  };
@@ -0,0 +1,237 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const joinUrl = require('url-join');
4
+ const pick = require('lodash/pick');
5
+ const joinURL = require('url-join');
6
+ const { parseUserAvatar, extractUserAvatar, getAvatarFile, getAvatarByUrl } = require('@abtnode/util/lib/user');
7
+ const { Hasher } = require('@ocap/mcrypto');
8
+ const { getDisplayName } = require('@blocklet/meta/lib/util');
9
+ const getBlockletInfo = require('@blocklet/meta/lib/info');
10
+
11
+ const { getLauncherUser, getLauncherSession: getLauncherSessionRaw, doRequest } = require('@abtnode/auth/lib/launcher');
12
+ const { createAuthToken, getPassportStatusEndpoint } = require('@abtnode/auth/lib/auth');
13
+ const { createPassportVC, createPassport, createUserPassport } = require('@abtnode/auth/lib/passport');
14
+
15
+ const { BlockletStatus } = require('@blocklet/constant');
16
+ const {
17
+ WELLKNOWN_SERVICE_PATH_PREFIX,
18
+ NODE_DATA_DIR_NAME,
19
+ USER_AVATAR_URL_PREFIX,
20
+ ROLES,
21
+ } = require('@abtnode/constant');
22
+
23
+ const logger = require('@abtnode/logger')('@abtnode/core:util:launcher');
24
+
25
+ const { getNFTState } = require('./index');
26
+ const { getBlockletURLForLauncher } = require('./blocklet');
27
+ const states = require('../states');
28
+
29
+ const consumeServerlessNFT = async ({ nftId, blocklet }) => {
30
+ try {
31
+ const state = await getNFTState(blocklet.controller.chainHost, nftId);
32
+ if (!state) {
33
+ throw new Error(`Get nft state failed, chainHost: ${blocklet.controller.chainHost}, nftId: ${nftId}`);
34
+ }
35
+
36
+ const nodeInfo = await states.node.read();
37
+ const appURL = await getBlockletURLForLauncher({ blocklet, nodeInfo });
38
+ logger.info('Consuming serverless nft params', { appURL, nftId });
39
+
40
+ const { launcherUrl } = state.data.value;
41
+ const result = await doRequest(nodeInfo.sk, {
42
+ launcherUrl,
43
+ pathname: '/api/serverless/consume',
44
+ payload: { nftId, appURL },
45
+ method: 'post',
46
+ });
47
+
48
+ logger.info('Consume serverless nft success', { nftId, hash: result.hash });
49
+ } catch (error) {
50
+ logger.error('Consume serverless nft failed', { nftId, error });
51
+ throw new Error(`consume nft ${nftId} failed`);
52
+ }
53
+ };
54
+
55
+ const consumeLauncherSession = async ({ params, blocklet }) => {
56
+ try {
57
+ const info = await states.node.read();
58
+ const { appUrl, name } = getBlockletInfo(blocklet, info.sk);
59
+ const result = await doRequest(info.sk, {
60
+ launcherUrl: params.launcherUrl,
61
+ pathname: `/api/launches/${params.launcherSessionId}/installed`,
62
+ payload: { appDid: blocklet.appPid, appUrl, appName: name, ownerDid: params.ownerDid },
63
+ method: 'post',
64
+ });
65
+
66
+ logger.info('Consume launcher session success', { params, result });
67
+ } catch (error) {
68
+ logger.error('Consume launcher session failed', { params, error });
69
+ throw new Error(`Consume launcher session ${params.launcherSessionId} failed`);
70
+ }
71
+ };
72
+
73
+ const setupAppOwner = async (node, sessionId) => {
74
+ const session = await node.getSession({ id: sessionId });
75
+ if (!session) {
76
+ throw new Error(`Blocklet setup session not found in server: ${sessionId}`);
77
+ }
78
+
79
+ const { appDid, userDid, ownerDid, ownerPk, lastLoginIp, context, locale, launcherUrl, launcherSessionId } = session;
80
+ const [info, blocklet] = await Promise.all([states.node.read(), states.blocklet.getBlocklet(appDid)]);
81
+ if (!blocklet) {
82
+ throw new Error(`Blocklet not found in server: ${appDid}`);
83
+ }
84
+ if (blocklet.status !== BlockletStatus.installed) {
85
+ throw new Error(`Blocklet status not expected: ${appDid}`);
86
+ }
87
+
88
+ const { wallet } = getBlockletInfo(blocklet, info.sk);
89
+ const appUrl = blocklet.environments.find((x) => x.key === 'BLOCKLET_APP_URL').value;
90
+ const dataDir = blocklet.environments.find((x) => x.key === 'BLOCKLET_DATA_DIR').value;
91
+ const serverDataDir = path.join(node.dataDirs.data, NODE_DATA_DIR_NAME);
92
+
93
+ let user;
94
+ let appOwnerProfile;
95
+ // get user from launcher or server
96
+ if (launcherUrl && launcherSessionId) {
97
+ user = await getLauncherUser(info.sk, { launcherUrl, launcherSessionId });
98
+ if (!user) {
99
+ throw new Error(`Owner user not found from launcher: ${launcherUrl}`);
100
+ }
101
+ appOwnerProfile = pick(user, ['fullName', 'email', 'avatar']);
102
+ const avatarBase64 = await getAvatarByUrl(joinURL(launcherUrl, user.avatar));
103
+ appOwnerProfile.avatar = await extractUserAvatar(avatarBase64, { dataDir });
104
+ logger.info('Create owner from launcher for blocklet', { appDid, ownerDid, ownerPk, sessionId, appOwnerProfile });
105
+ } else {
106
+ user = await node.getUser({ teamDid: info.did, user: { did: userDid } });
107
+ if (!user) {
108
+ throw new Error(`Owner user not found in server: ${userDid}`);
109
+ }
110
+ appOwnerProfile = pick(user, ['fullName', 'email', 'avatar']);
111
+ if (user.avatar && user.avatar.startsWith(USER_AVATAR_URL_PREFIX)) {
112
+ const filename = user.avatar.split('/').pop();
113
+ const srcFile = getAvatarFile(serverDataDir, filename);
114
+ const destFile = getAvatarFile(dataDir, filename);
115
+ fs.mkdirpSync(path.dirname(destFile));
116
+ fs.copyFileSync(srcFile, destFile);
117
+ } else {
118
+ appOwnerProfile.avatar = extractUserAvatar(user.avatar, { dataDir });
119
+ }
120
+ logger.info('Create owner from session for blocklet', { appDid, ownerDid, ownerPk, sessionId, appOwnerProfile });
121
+ }
122
+
123
+ // create and send owner passport
124
+ const role = ROLES.OWNER;
125
+ const vc = createPassportVC({
126
+ issuerName: getDisplayName(blocklet),
127
+ issuerWallet: wallet,
128
+ ownerDid,
129
+ passport: await createPassport({
130
+ name: role,
131
+ node,
132
+ teamDid: appDid,
133
+ locale,
134
+ endpoint: appUrl,
135
+ }),
136
+ endpoint: getPassportStatusEndpoint({
137
+ baseUrl: joinUrl(appUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
138
+ userDid: ownerDid,
139
+ teamDid: appDid,
140
+ }),
141
+ ownerProfile: { ...appOwnerProfile, avatar: parseUserAvatar(user.avatar, { dataDir: serverDataDir }) },
142
+ preferredColor: 'default',
143
+ expirationDate: undefined,
144
+ });
145
+
146
+ // create app owner passport
147
+ const passport = createUserPassport(vc, { role });
148
+
149
+ // add app owner to database
150
+ const doc = await node.loginUser({
151
+ teamDid: appDid,
152
+ user: {
153
+ ...appOwnerProfile,
154
+ did: ownerDid,
155
+ pk: ownerPk,
156
+ passport,
157
+ locale,
158
+ lastLoginIp,
159
+ connectedAccount: {
160
+ provider: 'wallet',
161
+ did: ownerDid,
162
+ pk: ownerPk,
163
+ },
164
+ },
165
+ });
166
+ await node.createAuditLog(
167
+ {
168
+ action: 'addUser',
169
+ args: { teamDid: appDid, userDid: ownerDid, reason: 'launch blocklet' },
170
+ context: { ...context, user: doc },
171
+ result: doc,
172
+ },
173
+ node
174
+ );
175
+ logger.info('Create owner for blocklet', { appDid, ownerDid, sessionId });
176
+
177
+ // create owner login token that can be used to login on blocklet site
178
+ const appSecret = Hasher.SHA3.hash256(Buffer.concat([wallet.secretKey, wallet.address].map(Buffer.from)));
179
+ const setupToken = createAuthToken({
180
+ did: ownerDid,
181
+ passport,
182
+ role,
183
+ secret: appSecret,
184
+ expiresIn: '7d',
185
+ });
186
+
187
+ await node.endSession({ id: sessionId });
188
+ logger.info('Complete install for blocklet', { appDid, ownerDid, sessionId });
189
+
190
+ return {
191
+ session,
192
+ blocklet,
193
+ setupToken,
194
+ passport: vc,
195
+ };
196
+ };
197
+
198
+ const getLauncherSession = async ({ launcherUrl, launcherSessionId, external = true }) => {
199
+ const info = await states.node.read();
200
+ const result = await getLauncherSessionRaw(info.sk, { launcherUrl, launcherSessionId });
201
+
202
+ // strip sensitive data if call from external
203
+ if (external && result.launcherSession) {
204
+ result.launcherSession = pick(result.launcherSession, ['_id', 'status']);
205
+ }
206
+
207
+ return result;
208
+ };
209
+
210
+ // Check local first, then remote, FIXME: @wangshijun should we check on chain nft?
211
+ const isLauncherSessionConsumed = async (params) => {
212
+ let consumed = await states.blockletExtras.isLauncherSessionConsumed(params.launcherSessionId);
213
+ logger.info('Launcher session consumed at local?', { params, consumed });
214
+ if (consumed) {
215
+ return true;
216
+ }
217
+
218
+ const { error, launcherSession } = await getLauncherSession(params);
219
+ if (error) {
220
+ throw new Error(error);
221
+ }
222
+ if (!launcherSession) {
223
+ throw new Error('Launcher session not found');
224
+ }
225
+
226
+ consumed = launcherSession.status > 40;
227
+ logger.info('Launcher session consumed at remote?', { params, consumed });
228
+ return consumed;
229
+ };
230
+
231
+ module.exports = {
232
+ consumeServerlessNFT,
233
+ consumeLauncherSession,
234
+ setupAppOwner,
235
+ getLauncherSession,
236
+ isLauncherSessionConsumed,
237
+ };
@@ -15,6 +15,9 @@ const blockletController = Joi.object({
15
15
  nftOwner: Joi.DID().required(),
16
16
  chainHost: Joi.string().uri().required(),
17
17
  appMaxCount: Joi.number().required().min(1),
18
+ launcherUrl: Joi.string().uri().optional(),
19
+ launcherSessionId: Joi.string().optional(),
20
+ ownerDid: Joi.DID().optional(),
18
21
  }).options({ stripUnknown: true });
19
22
 
20
23
  const createValidator = (schema) => (entity) => schema.validateAsync(entity);
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.11-next-069c3537",
6
+ "version": "1.16.11-next-3d2b39f7",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,18 +19,18 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "Apache-2.0",
21
21
  "dependencies": {
22
- "@abtnode/auth": "1.16.11-next-069c3537",
23
- "@abtnode/certificate-manager": "1.16.11-next-069c3537",
24
- "@abtnode/constant": "1.16.11-next-069c3537",
25
- "@abtnode/cron": "1.16.11-next-069c3537",
26
- "@abtnode/logger": "1.16.11-next-069c3537",
27
- "@abtnode/models": "1.16.11-next-069c3537",
28
- "@abtnode/queue": "1.16.11-next-069c3537",
29
- "@abtnode/rbac": "1.16.11-next-069c3537",
30
- "@abtnode/router-provider": "1.16.11-next-069c3537",
31
- "@abtnode/static-server": "1.16.11-next-069c3537",
32
- "@abtnode/timemachine": "1.16.11-next-069c3537",
33
- "@abtnode/util": "1.16.11-next-069c3537",
22
+ "@abtnode/auth": "1.16.11-next-3d2b39f7",
23
+ "@abtnode/certificate-manager": "1.16.11-next-3d2b39f7",
24
+ "@abtnode/constant": "1.16.11-next-3d2b39f7",
25
+ "@abtnode/cron": "1.16.11-next-3d2b39f7",
26
+ "@abtnode/logger": "1.16.11-next-3d2b39f7",
27
+ "@abtnode/models": "1.16.11-next-3d2b39f7",
28
+ "@abtnode/queue": "1.16.11-next-3d2b39f7",
29
+ "@abtnode/rbac": "1.16.11-next-3d2b39f7",
30
+ "@abtnode/router-provider": "1.16.11-next-3d2b39f7",
31
+ "@abtnode/static-server": "1.16.11-next-3d2b39f7",
32
+ "@abtnode/timemachine": "1.16.11-next-3d2b39f7",
33
+ "@abtnode/util": "1.16.11-next-3d2b39f7",
34
34
  "@arcblock/did": "1.18.80",
35
35
  "@arcblock/did-auth": "1.18.80",
36
36
  "@arcblock/did-ext": "^1.18.80",
@@ -41,10 +41,10 @@
41
41
  "@arcblock/pm2-events": "^0.0.5",
42
42
  "@arcblock/validator": "^1.18.80",
43
43
  "@arcblock/vc": "1.18.80",
44
- "@blocklet/constant": "1.16.11-next-069c3537",
45
- "@blocklet/meta": "1.16.11-next-069c3537",
46
- "@blocklet/sdk": "1.16.11-next-069c3537",
47
- "@did-space/client": "^0.2.99",
44
+ "@blocklet/constant": "1.16.11-next-3d2b39f7",
45
+ "@blocklet/meta": "1.16.11-next-3d2b39f7",
46
+ "@blocklet/sdk": "1.16.11-next-3d2b39f7",
47
+ "@did-space/client": "^0.2.112",
48
48
  "@fidm/x509": "^1.2.1",
49
49
  "@ocap/mcrypto": "1.18.80",
50
50
  "@ocap/util": "1.18.80",
@@ -96,5 +96,5 @@
96
96
  "express": "^4.18.2",
97
97
  "jest": "^27.5.1"
98
98
  },
99
- "gitHead": "680dafcd7c2d446013abf25a370b8cfb70bfa7eb"
99
+ "gitHead": "d0142bd2d1e49b94dcb5542d78e294a7b2258faa"
100
100
  }