@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.
- package/lib/api/node.js +1 -1
- package/lib/blocklet/downloader/blocklet-downloader.js +3 -3
- package/lib/blocklet/manager/disk.js +213 -136
- 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/event.js +11 -1
- package/lib/index.js +7 -3
- package/lib/migrations/1.16.11-component-status.js +22 -0
- package/lib/monitor/blocklet-runtime-monitor.js +3 -1
- package/lib/states/blocklet-extras.js +10 -0
- package/lib/states/blocklet.js +45 -38
- package/lib/states/node.js +1 -0
- package/lib/util/blocklet.js +116 -114
- package/lib/util/launcher.js +237 -0
- package/lib/validators/util.js +3 -0
- package/package.json +18 -18
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 = async (blocklet, { preStop = noop, skippedProcessIds = [] } = {}) => {
|
|
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 = 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
|
|
913
|
-
|
|
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
|
+
};
|
package/lib/validators/util.js
CHANGED
|
@@ -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-
|
|
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-
|
|
23
|
-
"@abtnode/certificate-manager": "1.16.11-next-
|
|
24
|
-
"@abtnode/constant": "1.16.11-next-
|
|
25
|
-
"@abtnode/cron": "1.16.11-next-
|
|
26
|
-
"@abtnode/logger": "1.16.11-next-
|
|
27
|
-
"@abtnode/models": "1.16.11-next-
|
|
28
|
-
"@abtnode/queue": "1.16.11-next-
|
|
29
|
-
"@abtnode/rbac": "1.16.11-next-
|
|
30
|
-
"@abtnode/router-provider": "1.16.11-next-
|
|
31
|
-
"@abtnode/static-server": "1.16.11-next-
|
|
32
|
-
"@abtnode/timemachine": "1.16.11-next-
|
|
33
|
-
"@abtnode/util": "1.16.11-next-
|
|
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-
|
|
45
|
-
"@blocklet/meta": "1.16.11-next-
|
|
46
|
-
"@blocklet/sdk": "1.16.11-next-
|
|
47
|
-
"@did-space/client": "^0.2.
|
|
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": "
|
|
99
|
+
"gitHead": "d0142bd2d1e49b94dcb5542d78e294a7b2258faa"
|
|
100
100
|
}
|