@abtnode/core 1.16.11-beta-0ae58a71 → 1.16.11-beta-f74663ac
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/node.js +1 -1
- package/lib/api/team.js +5 -5
- package/lib/blocklet/downloader/blocklet-downloader.js +34 -19
- package/lib/blocklet/downloader/bundle-downloader.js +2 -2
- package/lib/blocklet/hooks.js +1 -1
- package/lib/blocklet/manager/base.js +9 -9
- package/lib/blocklet/manager/disk.js +286 -172
- package/lib/blocklet/manager/helper/install-application-from-backup.js +1 -1
- package/lib/blocklet/manager/helper/install-application-from-general.js +18 -3
- package/lib/blocklet/manager/helper/install-component-from-dev.js +0 -1
- package/lib/blocklet/manager/helper/install-component-from-upload.js +8 -4
- package/lib/blocklet/manager/helper/install-component-from-url.js +4 -2
- package/lib/blocklet/manager/helper/upgrade-components.js +7 -7
- package/lib/blocklet/manager/pm2-events.js +3 -3
- package/lib/blocklet/migration.js +2 -2
- package/lib/blocklet/storage/backup/base.js +1 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +1 -1
- package/lib/blocklet/storage/backup/blocklet.js +1 -1
- package/lib/blocklet/storage/backup/disk.js +1 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +1 -1
- package/lib/blocklet/storage/restore/blocklet.js +1 -1
- package/lib/blocklet/storage/restore/disk.js +1 -1
- package/lib/blocklet/storage/restore/spaces.js +1 -1
- package/lib/cert.js +5 -5
- package/lib/crons/rotate-pm2-logs/index.js +1 -1
- package/lib/event.js +11 -1
- package/lib/index.js +8 -2
- package/lib/migrations/1.16.11-component-status.js +22 -0
- package/lib/migrations/index.js +3 -3
- package/lib/monitor/blocklet-runtime-monitor.js +7 -2
- package/lib/processes/updater.js +1 -1
- package/lib/router/helper.js +5 -5
- package/lib/states/audit-log.js +1 -1
- package/lib/states/backup.js +5 -4
- package/lib/states/blocklet-extras.js +11 -1
- package/lib/states/blocklet.js +46 -39
- package/lib/states/cache.js +1 -1
- package/lib/states/node.js +6 -2
- package/lib/states/notification.js +1 -1
- package/lib/states/routing-snapshot.js +1 -1
- package/lib/states/site.js +1 -1
- package/lib/states/user.js +12 -6
- package/lib/team/manager.js +6 -6
- package/lib/util/blocklet.js +130 -118
- package/lib/util/launcher.js +238 -0
- package/lib/util/reset-node.js +19 -16
- package/lib/util/rpc.js +1 -1
- package/lib/util/store.js +1 -1
- package/lib/validators/space-gateway.js +3 -0
- package/lib/validators/util.js +3 -0
- package/lib/webhook/sender/api/index.js +1 -1
- package/lib/webhook/sender/slack/index.js +1 -1
- package/package.json +17 -17
package/lib/util/blocklet.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
-
{
|
|
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 =
|
|
627
|
-
|
|
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 = (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 =
|
|
676
|
+
const reloadBlockletProcess = (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}
|
|
@@ -740,7 +714,7 @@ const getProcessInfo = (processId, { throwOnNotExist = true } = {}) =>
|
|
|
740
714
|
|
|
741
715
|
const deleteProcess = (processId) =>
|
|
742
716
|
new Promise((resolve, reject) => {
|
|
743
|
-
pm2.delete(processId,
|
|
717
|
+
pm2.delete(processId, (err) => {
|
|
744
718
|
if (isUsefulError(err)) {
|
|
745
719
|
logger.error('blocklet process delete failed', { error: err });
|
|
746
720
|
return reject(err);
|
|
@@ -751,7 +725,7 @@ const deleteProcess = (processId) =>
|
|
|
751
725
|
|
|
752
726
|
const reloadProcess = (processId) =>
|
|
753
727
|
new Promise((resolve, reject) => {
|
|
754
|
-
pm2.reload(processId,
|
|
728
|
+
pm2.reload(processId, (err) => {
|
|
755
729
|
if (err) {
|
|
756
730
|
if (isUsefulError(err)) {
|
|
757
731
|
logger.error('blocklet reload failed', { error: err });
|
|
@@ -885,7 +859,7 @@ const parseComponents = async (component, context = {}) => {
|
|
|
885
859
|
};
|
|
886
860
|
|
|
887
861
|
const validateBlocklet = (blocklet) =>
|
|
888
|
-
forEachBlocklet(blocklet,
|
|
862
|
+
forEachBlocklet(blocklet, (b) => {
|
|
889
863
|
isRequirementsSatisfied(b.meta.requirements);
|
|
890
864
|
validateEngine(getBlockletEngineNameByPlatform(b.meta));
|
|
891
865
|
});
|
|
@@ -905,19 +879,28 @@ 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
|
|
913
|
-
|
|
886
|
+
if (b.meta.group === BlockletGroup.gateway) {
|
|
887
|
+
return;
|
|
914
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;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const logToTerminal = [blocklet.mode, b.mode].includes(BLOCKLET_MODES.DEVELOPMENT);
|
|
896
|
+
|
|
897
|
+
await _checkProcessHealthy(b, { minConsecutiveTime, timeout, logToTerminal });
|
|
915
898
|
},
|
|
916
899
|
{ parallel: true }
|
|
917
900
|
);
|
|
918
901
|
};
|
|
919
902
|
|
|
920
|
-
const _checkProcessHealthy = async (blocklet, { minConsecutiveTime, timeout }) => {
|
|
903
|
+
const _checkProcessHealthy = async (blocklet, { minConsecutiveTime, timeout, logToTerminal }) => {
|
|
921
904
|
const { meta, ports, env } = blocklet;
|
|
922
905
|
const { name } = meta;
|
|
923
906
|
const { processId } = env;
|
|
@@ -953,6 +936,14 @@ const _checkProcessHealthy = async (blocklet, { minConsecutiveTime, timeout }) =
|
|
|
953
936
|
}
|
|
954
937
|
|
|
955
938
|
const port = findInterfacePortByName({ meta, ports }, webInterface.name);
|
|
939
|
+
if (logToTerminal) {
|
|
940
|
+
// eslint-disable-next-line no-console
|
|
941
|
+
console.log(
|
|
942
|
+
`Checking endpoint healthy for ${meta.title}, port: ${port}, minConsecutiveTime: ${
|
|
943
|
+
minConsecutiveTime / 1000
|
|
944
|
+
}s, timeout: ${timeout / 1000}s`
|
|
945
|
+
);
|
|
946
|
+
}
|
|
956
947
|
try {
|
|
957
948
|
await ensureEndpointHealthy({
|
|
958
949
|
port,
|
|
@@ -1538,51 +1529,6 @@ const getBlockletURLForLauncher = async ({ blocklet, nodeInfo }) => {
|
|
|
1538
1529
|
const didDomain = getDidDomainForBlocklet({ appPid: blocklet.appPid, didDomain: nodeInfo.didDomain });
|
|
1539
1530
|
return `https://${didDomain}`;
|
|
1540
1531
|
};
|
|
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
1532
|
const createDataArchive = (dataDir, fileName) => {
|
|
1587
1533
|
const zipPath = path.join(os.tmpdir(), fileName);
|
|
1588
1534
|
if (fs.existsSync(zipPath)) {
|
|
@@ -1952,13 +1898,77 @@ const getBlockletDidDomainList = (blocklet, nodeInfo) => {
|
|
|
1952
1898
|
return domainAliases;
|
|
1953
1899
|
};
|
|
1954
1900
|
|
|
1901
|
+
const getBlockletStatus = (blocklet) => {
|
|
1902
|
+
const fallbackStatus = BlockletStatus.stopped;
|
|
1903
|
+
|
|
1904
|
+
if (!blocklet) {
|
|
1905
|
+
return fallbackStatus;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
if (!blocklet.children?.length) {
|
|
1909
|
+
if (blocklet.meta?.group === BlockletGroup.gateway) {
|
|
1910
|
+
return blocklet.status;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
if (blocklet.status === BlockletStatus.added) {
|
|
1914
|
+
return BlockletStatus.added;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// for backward compatibility
|
|
1918
|
+
if (!blocklet.structVersion) {
|
|
1919
|
+
return blocklet.status;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
return fallbackStatus;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
let inProgressStatus;
|
|
1926
|
+
let runningStatus;
|
|
1927
|
+
let status;
|
|
1928
|
+
|
|
1929
|
+
forEachComponentV2Sync(blocklet, (component) => {
|
|
1930
|
+
if (component.meta?.group === BlockletGroup.gateway) {
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
if (isInProgress(component.status)) {
|
|
1935
|
+
if (!inProgressStatus) {
|
|
1936
|
+
inProgressStatus = component.status;
|
|
1937
|
+
}
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
if (isRunning(component.status)) {
|
|
1942
|
+
runningStatus = component.status;
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
status = component.status;
|
|
1947
|
+
});
|
|
1948
|
+
|
|
1949
|
+
return inProgressStatus || runningStatus || status;
|
|
1950
|
+
};
|
|
1951
|
+
|
|
1952
|
+
const shouldSkipComponent = (componentDid, whiteList) => {
|
|
1953
|
+
if (!whiteList || !Array.isArray(whiteList)) {
|
|
1954
|
+
return false;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
const arr = whiteList.filter(Boolean);
|
|
1958
|
+
|
|
1959
|
+
if (!arr.length) {
|
|
1960
|
+
return false;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
return !arr.includes(componentDid);
|
|
1964
|
+
};
|
|
1965
|
+
|
|
1955
1966
|
const exceedRedemptionPeriod = (expirationDate) => {
|
|
1956
1967
|
return dayjs().diff(dayjs(expirationDate), 'day') > EXPIRED_BLOCKLET_DATA_RETENTION_DAYS;
|
|
1957
1968
|
};
|
|
1958
1969
|
|
|
1959
1970
|
module.exports = {
|
|
1960
1971
|
updateBlockletFallbackLogo,
|
|
1961
|
-
consumeServerlessNFT,
|
|
1962
1972
|
forEachBlocklet,
|
|
1963
1973
|
getBlockletMetaFromUrl: (url) => getBlockletMetaFromUrl(url, { logger }),
|
|
1964
1974
|
parseComponents,
|
|
@@ -1975,7 +1985,6 @@ module.exports = {
|
|
|
1975
1985
|
stopBlockletProcess,
|
|
1976
1986
|
deleteBlockletProcess,
|
|
1977
1987
|
reloadBlockletProcess,
|
|
1978
|
-
getBlockletStatusFromProcess,
|
|
1979
1988
|
checkBlockletProcessHealthy,
|
|
1980
1989
|
getHealthyCheckTimeout,
|
|
1981
1990
|
getProcessInfo,
|
|
@@ -2014,6 +2023,9 @@ module.exports = {
|
|
|
2014
2023
|
getFixedBundleSource,
|
|
2015
2024
|
ensureAppLogo,
|
|
2016
2025
|
getBlockletDidDomainList,
|
|
2026
|
+
getProcessState,
|
|
2027
|
+
getBlockletStatus,
|
|
2028
|
+
shouldSkipComponent,
|
|
2017
2029
|
getBlockletURLForLauncher,
|
|
2018
2030
|
exceedRedemptionPeriod,
|
|
2019
2031
|
};
|
|
@@ -0,0 +1,238 @@
|
|
|
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: await 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.setBlockletOwner({ did: appDid, owner: { did: ownerDid, pk: ownerPk } });
|
|
188
|
+
await node.endSession({ id: sessionId });
|
|
189
|
+
logger.info('Complete install for blocklet', { appDid, ownerDid, sessionId });
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
session,
|
|
193
|
+
blocklet,
|
|
194
|
+
setupToken,
|
|
195
|
+
passport: vc,
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const getLauncherSession = async ({ launcherUrl, launcherSessionId, external = true }) => {
|
|
200
|
+
const info = await states.node.read();
|
|
201
|
+
const result = await getLauncherSessionRaw(info.sk, { launcherUrl, launcherSessionId });
|
|
202
|
+
|
|
203
|
+
// strip sensitive data if call from external
|
|
204
|
+
if (external && result.launcherSession) {
|
|
205
|
+
result.launcherSession = pick(result.launcherSession, ['_id', 'status']);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return result;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Check local first, then remote, FIXME: @wangshijun should we check on chain nft?
|
|
212
|
+
const isLauncherSessionConsumed = async (params) => {
|
|
213
|
+
let consumed = await states.blockletExtras.isLauncherSessionConsumed(params.launcherSessionId);
|
|
214
|
+
logger.info('Launcher session consumed at local?', { params, consumed });
|
|
215
|
+
if (consumed) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { error, launcherSession } = await getLauncherSession(params);
|
|
220
|
+
if (error) {
|
|
221
|
+
throw new Error(error);
|
|
222
|
+
}
|
|
223
|
+
if (!launcherSession) {
|
|
224
|
+
throw new Error('Launcher session not found');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
consumed = launcherSession.status > 40;
|
|
228
|
+
logger.info('Launcher session consumed at remote?', { params, consumed });
|
|
229
|
+
return consumed;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
consumeServerlessNFT,
|
|
234
|
+
consumeLauncherSession,
|
|
235
|
+
setupAppOwner,
|
|
236
|
+
getLauncherSession,
|
|
237
|
+
isLauncherSessionConsumed,
|
|
238
|
+
};
|