@abtnode/core 1.17.8-beta-20260109-075740-5f484e08 → 1.17.8-beta-20260111-112953-aed5ff39
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/team/access-key-manager.js +104 -0
- package/lib/api/team/invitation-manager.js +461 -0
- package/lib/api/team/notification-manager.js +189 -0
- package/lib/api/team/oauth-manager.js +60 -0
- package/lib/api/team/org-crud-manager.js +202 -0
- package/lib/api/team/org-manager.js +56 -0
- package/lib/api/team/org-member-manager.js +403 -0
- package/lib/api/team/org-query-manager.js +126 -0
- package/lib/api/team/org-resource-manager.js +186 -0
- package/lib/api/team/passport-manager.js +670 -0
- package/lib/api/team/rbac-manager.js +335 -0
- package/lib/api/team/session-manager.js +540 -0
- package/lib/api/team/store-manager.js +198 -0
- package/lib/api/team/tag-manager.js +230 -0
- package/lib/api/team/user-auth-manager.js +132 -0
- package/lib/api/team/user-manager.js +78 -0
- package/lib/api/team/user-query-manager.js +299 -0
- package/lib/api/team/user-social-manager.js +354 -0
- package/lib/api/team/user-update-manager.js +224 -0
- package/lib/api/team/verify-code-manager.js +161 -0
- package/lib/api/team.js +439 -3287
- package/lib/blocklet/manager/disk/auth-manager.js +68 -0
- package/lib/blocklet/manager/disk/backup-manager.js +288 -0
- package/lib/blocklet/manager/disk/cleanup-manager.js +157 -0
- package/lib/blocklet/manager/disk/component-manager.js +83 -0
- package/lib/blocklet/manager/disk/config-manager.js +191 -0
- package/lib/blocklet/manager/disk/controller-manager.js +64 -0
- package/lib/blocklet/manager/disk/delete-reset-manager.js +328 -0
- package/lib/blocklet/manager/disk/download-manager.js +96 -0
- package/lib/blocklet/manager/disk/env-config-manager.js +311 -0
- package/lib/blocklet/manager/disk/federated-manager.js +651 -0
- package/lib/blocklet/manager/disk/hook-manager.js +124 -0
- package/lib/blocklet/manager/disk/install-component-manager.js +95 -0
- package/lib/blocklet/manager/disk/install-core-manager.js +448 -0
- package/lib/blocklet/manager/disk/install-download-manager.js +313 -0
- package/lib/blocklet/manager/disk/install-manager.js +36 -0
- package/lib/blocklet/manager/disk/install-upgrade-manager.js +340 -0
- package/lib/blocklet/manager/disk/job-manager.js +467 -0
- package/lib/blocklet/manager/disk/lifecycle-manager.js +26 -0
- package/lib/blocklet/manager/disk/notification-manager.js +343 -0
- package/lib/blocklet/manager/disk/query-manager.js +562 -0
- package/lib/blocklet/manager/disk/settings-manager.js +507 -0
- package/lib/blocklet/manager/disk/start-manager.js +611 -0
- package/lib/blocklet/manager/disk/stop-restart-manager.js +292 -0
- package/lib/blocklet/manager/disk/update-manager.js +153 -0
- package/lib/blocklet/manager/disk.js +669 -5796
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +5 -0
- package/lib/blocklet/manager/lock.js +18 -0
- package/lib/event/index.js +28 -24
- package/lib/util/blocklet/app-utils.js +192 -0
- package/lib/util/blocklet/blocklet-loader.js +258 -0
- package/lib/util/blocklet/config-manager.js +232 -0
- package/lib/util/blocklet/did-document.js +240 -0
- package/lib/util/blocklet/environment.js +555 -0
- package/lib/util/blocklet/health-check.js +449 -0
- package/lib/util/blocklet/install-utils.js +365 -0
- package/lib/util/blocklet/logo.js +57 -0
- package/lib/util/blocklet/meta-utils.js +269 -0
- package/lib/util/blocklet/port-manager.js +141 -0
- package/lib/util/blocklet/process-manager.js +504 -0
- package/lib/util/blocklet/runtime-info.js +105 -0
- package/lib/util/blocklet/validation.js +418 -0
- package/lib/util/blocklet.js +98 -3066
- package/lib/util/wallet-app-notification.js +40 -0
- package/package.json +22 -22
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const get = require('lodash/get');
|
|
2
|
+
const { forEachBlocklet, forEachComponentV2, isExternalBlocklet } = require('@blocklet/meta/lib/util');
|
|
3
|
+
|
|
4
|
+
const states = require('../../../states');
|
|
5
|
+
const {
|
|
6
|
+
getRuntimeEnvironments,
|
|
7
|
+
getConfigFromPreferences,
|
|
8
|
+
getAppConfigsFromComponent,
|
|
9
|
+
getHookArgs,
|
|
10
|
+
} = require('../../../util/blocklet');
|
|
11
|
+
const hooks = require('../../hooks');
|
|
12
|
+
const checkNeedRunDocker = require('../../../util/docker/check-need-run-docker');
|
|
13
|
+
const { dockerExec } = require('../../../util/docker/docker-exec');
|
|
14
|
+
const { installExternalDependencies } = require('../../../util/install-external-dependencies');
|
|
15
|
+
const { getBlocklet } = require('../../../util/blocklet');
|
|
16
|
+
|
|
17
|
+
const afterRunScripts = new Set(['postStart', 'preStop', 'preConfig', 'preUninstall']);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run user hook
|
|
21
|
+
* @param {Object} manager - BlockletManager instance
|
|
22
|
+
* @param {string} name - Hook name
|
|
23
|
+
* @param {Object} blocklet - Blocklet state
|
|
24
|
+
* @param {Object} context - Context
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
async function _runUserHook(manager, name, blocklet, context) {
|
|
28
|
+
const nodeInfo = await states.node.read();
|
|
29
|
+
const nodeEnvironments = await states.node.getEnvironments(nodeInfo);
|
|
30
|
+
const hookFn = async (b, { ancestors }) => {
|
|
31
|
+
const env = getRuntimeEnvironments(b, nodeEnvironments, ancestors);
|
|
32
|
+
const needRunDocker = await checkNeedRunDocker(b.meta, env, nodeInfo, isExternalBlocklet(blocklet));
|
|
33
|
+
const hookArgs = getHookArgs(b);
|
|
34
|
+
|
|
35
|
+
if (name === 'preInstall') {
|
|
36
|
+
await installExternalDependencies({
|
|
37
|
+
appDir: b.env.appDir,
|
|
38
|
+
nodeInfo,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (needRunDocker) {
|
|
43
|
+
const script = b.meta.scripts?.[name];
|
|
44
|
+
if (script && afterRunScripts.has(name)) {
|
|
45
|
+
return dockerExec({
|
|
46
|
+
blocklet,
|
|
47
|
+
meta: b.meta,
|
|
48
|
+
script,
|
|
49
|
+
hookName: name,
|
|
50
|
+
nodeInfo,
|
|
51
|
+
env,
|
|
52
|
+
...hookArgs,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return hooks[name](b.meta.title, {
|
|
59
|
+
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
60
|
+
env,
|
|
61
|
+
appDir: b.env.appDir,
|
|
62
|
+
did: blocklet.meta.did, // root blocklet did
|
|
63
|
+
notification: states.notification,
|
|
64
|
+
context,
|
|
65
|
+
teamManager: manager.teamManager,
|
|
66
|
+
...hookArgs,
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
await forEachBlocklet(blocklet, hookFn, { parallel: true, concurrencyLimit: 4 });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set configs from meta
|
|
75
|
+
* @param {Object} manager - BlockletManager instance
|
|
76
|
+
* @param {string} did - Blocklet DID
|
|
77
|
+
* @param {string} [childDid] - Child blocklet DID
|
|
78
|
+
* @returns {Promise<void>}
|
|
79
|
+
*/
|
|
80
|
+
async function _setConfigsFromMeta(manager, did, childDid) {
|
|
81
|
+
const blocklet = await getBlocklet({ states, dataDirs: manager.dataDirs, did });
|
|
82
|
+
|
|
83
|
+
const setConfig = async (app, component) => {
|
|
84
|
+
const b = component || app;
|
|
85
|
+
const environments = [...get(b.meta, 'environments', []), ...getConfigFromPreferences(b)];
|
|
86
|
+
|
|
87
|
+
// write configs to db
|
|
88
|
+
await states.blockletExtras.setConfigs([app.meta.did, component?.meta?.did].filter(Boolean), environments);
|
|
89
|
+
|
|
90
|
+
if (component) {
|
|
91
|
+
const envsInApp = await states.blockletExtras.getConfigs([blocklet.meta.did]);
|
|
92
|
+
const envsInComponent = await states.blockletExtras.getConfigs([app.meta.did, component.meta.did]);
|
|
93
|
+
|
|
94
|
+
const configs = getAppConfigsFromComponent({ environments }, envsInApp, envsInComponent);
|
|
95
|
+
if (configs.length) {
|
|
96
|
+
await states.blockletExtras.setConfigs(app.meta.did, configs);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// chain config
|
|
101
|
+
await manager._ensureAppChainConfig(
|
|
102
|
+
blocklet.meta.did,
|
|
103
|
+
environments.map((x) => ({ key: x.name, value: x.default })),
|
|
104
|
+
{ force: false }
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (!childDid) {
|
|
109
|
+
await setConfig(blocklet);
|
|
110
|
+
await forEachComponentV2(blocklet, async (component) => {
|
|
111
|
+
await setConfig(blocklet, component);
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
const component = blocklet.children.find((x) => x.meta.did === childDid);
|
|
115
|
+
|
|
116
|
+
await setConfig(blocklet, component);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
_runUserHook,
|
|
122
|
+
_setConfigsFromMeta,
|
|
123
|
+
afterRunScripts,
|
|
124
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager:install-component');
|
|
2
|
+
const { isInServerlessMode } = require('@abtnode/util/lib/serverless');
|
|
3
|
+
const { updateComponentDid } = require('@abtnode/util/lib/upload-component');
|
|
4
|
+
|
|
5
|
+
const states = require('../../../states');
|
|
6
|
+
const { installComponentFromUpload } = require('../helper/install-component-from-upload');
|
|
7
|
+
const { installComponentFromUrl } = require('../helper/install-component-from-url');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Install component
|
|
11
|
+
* @param {Object} manager - BlockletManager instance
|
|
12
|
+
* @param {Object} params
|
|
13
|
+
* @param {Object} context
|
|
14
|
+
* @returns {Promise<Object>}
|
|
15
|
+
*/
|
|
16
|
+
async function installComponent(
|
|
17
|
+
manager,
|
|
18
|
+
{
|
|
19
|
+
rootDid,
|
|
20
|
+
mountPoint,
|
|
21
|
+
url,
|
|
22
|
+
file,
|
|
23
|
+
did,
|
|
24
|
+
diffVersion,
|
|
25
|
+
deleteSet,
|
|
26
|
+
title,
|
|
27
|
+
name,
|
|
28
|
+
configs,
|
|
29
|
+
sync,
|
|
30
|
+
downloadTokenList,
|
|
31
|
+
onlyRequired,
|
|
32
|
+
dist,
|
|
33
|
+
},
|
|
34
|
+
context = {}
|
|
35
|
+
) {
|
|
36
|
+
logger.debug('start install component', { rootDid, mountPoint, url });
|
|
37
|
+
if (file) {
|
|
38
|
+
// TODO: 如何触发这种场景?
|
|
39
|
+
const info = await states.node.read();
|
|
40
|
+
if (isInServerlessMode(info)) {
|
|
41
|
+
throw new Error("Can't install component in serverless-mode server via upload");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return installComponentFromUpload({
|
|
45
|
+
rootDid,
|
|
46
|
+
mountPoint,
|
|
47
|
+
file,
|
|
48
|
+
did,
|
|
49
|
+
diffVersion,
|
|
50
|
+
deleteSet,
|
|
51
|
+
context,
|
|
52
|
+
states,
|
|
53
|
+
manager,
|
|
54
|
+
dist,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (url) {
|
|
59
|
+
// 如果是 file 协议的 URI,那么就认为是上传的文件
|
|
60
|
+
const isUploadFile = url.startsWith('file://');
|
|
61
|
+
// 需要判断是否是上传的文件,如果是上传的文件,需要判断是否要更新 did,走升级的逻辑
|
|
62
|
+
// 不需要阻塞流程,如果失败了,就新建一个组件
|
|
63
|
+
try {
|
|
64
|
+
if (isUploadFile && did) {
|
|
65
|
+
await updateComponentDid(url, did);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logger.error('update component did failed', { error });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return installComponentFromUrl({
|
|
72
|
+
rootDid,
|
|
73
|
+
mountPoint,
|
|
74
|
+
url,
|
|
75
|
+
context,
|
|
76
|
+
title,
|
|
77
|
+
did,
|
|
78
|
+
name,
|
|
79
|
+
configs,
|
|
80
|
+
sync,
|
|
81
|
+
downloadTokenList,
|
|
82
|
+
states,
|
|
83
|
+
manager,
|
|
84
|
+
onlyRequired,
|
|
85
|
+
isUploadFile,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// should not be here
|
|
90
|
+
throw new Error('Unknown source');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
installComponent,
|
|
95
|
+
};
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const omit = require('lodash/omit');
|
|
5
|
+
const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager:install-core');
|
|
6
|
+
const { generateRandomString } = require('@abtnode/models/lib/util');
|
|
7
|
+
const { sign } = require('@arcblock/jwt');
|
|
8
|
+
const { BLOCKLET_INSTALL_TYPE, INSTALL_ACTIONS, WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
|
|
9
|
+
const { BlockletStatus, BlockletEvents, fromBlockletSource } = require('@blocklet/constant');
|
|
10
|
+
const { isGatewayBlocklet } = require('@blocklet/meta/lib/util');
|
|
11
|
+
const {
|
|
12
|
+
BlockletGroup,
|
|
13
|
+
BlockletSource,
|
|
14
|
+
BLOCKLET_MODES,
|
|
15
|
+
BLOCKLET_INTERFACE_TYPE_WEB,
|
|
16
|
+
BLOCKLET_INTERFACE_PUBLIC,
|
|
17
|
+
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
18
|
+
BLOCKLET_DEFAULT_PORT_NAME,
|
|
19
|
+
BLOCKLET_INTERFACE_PROTOCOL_HTTP,
|
|
20
|
+
BLOCKLET_DEFAULT_PATH_REWRITE,
|
|
21
|
+
BLOCKLET_DEFAULT_VERSION,
|
|
22
|
+
BLOCKLET_LATEST_SPEC_VERSION,
|
|
23
|
+
BLOCKLET_META_FILE,
|
|
24
|
+
BLOCKLET_CONFIGURABLE_KEY,
|
|
25
|
+
} = require('@blocklet/constant');
|
|
26
|
+
const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
|
|
27
|
+
const { validateBlocklet, filterRequiredComponents, getBundleDir } = require('../../../util/blocklet');
|
|
28
|
+
|
|
29
|
+
const states = require('../../../states');
|
|
30
|
+
const launcher = require('../../../util/launcher');
|
|
31
|
+
const {
|
|
32
|
+
ensureAppLogo,
|
|
33
|
+
updateBlockletFallbackLogo,
|
|
34
|
+
getPackConfig,
|
|
35
|
+
getPackComponent,
|
|
36
|
+
copyPackImages,
|
|
37
|
+
getTypeFromInstallParams,
|
|
38
|
+
} = require('../../../util/blocklet');
|
|
39
|
+
const { removeBackup } = require('../../storage/utils/disk');
|
|
40
|
+
const { getWalletAppNotification } = require('../../../util/wallet-app-notification');
|
|
41
|
+
const { installApplicationFromBackup } = require('../helper/install-application-from-backup');
|
|
42
|
+
const { installApplicationFromGeneral } = require('../helper/install-application-from-general');
|
|
43
|
+
const util = require('../../../util');
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* On install callback
|
|
47
|
+
*/
|
|
48
|
+
async function _onInstall(manager, { blocklet, componentDids, context, oldBlocklet }) {
|
|
49
|
+
const { meta } = blocklet;
|
|
50
|
+
const { did, version } = meta;
|
|
51
|
+
logger.info('do install blocklet', { did, version });
|
|
52
|
+
try {
|
|
53
|
+
const installedBlocklet = await _installBlocklet(manager, {
|
|
54
|
+
did,
|
|
55
|
+
componentDids,
|
|
56
|
+
context,
|
|
57
|
+
oldBlocklet,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (context.startImmediately && installedBlocklet?.settings.initialized) {
|
|
61
|
+
try {
|
|
62
|
+
logger.info('start blocklet after installed', { did });
|
|
63
|
+
await manager.start({ did, checkHealthImmediately: true, componentDids });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.warn('attempt to start immediately failed', { did, error });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return installedBlocklet;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.error('blocklet onInstall error', { error });
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Install blocklet
|
|
78
|
+
*/
|
|
79
|
+
async function _installBlocklet(manager, { did, oldBlocklet, componentDids, context, createNotification = true }) {
|
|
80
|
+
try {
|
|
81
|
+
// should ensure blocklet integrity
|
|
82
|
+
let blocklet = await manager.ensureBlocklet(did);
|
|
83
|
+
const { meta, source, deployedFrom } = blocklet;
|
|
84
|
+
|
|
85
|
+
// ensure delete process
|
|
86
|
+
try {
|
|
87
|
+
await manager.deleteProcess({ did, shouldUpdateBlockletStatus: false }, context);
|
|
88
|
+
logger.info('ensure delete blocklet process for installing', { did, name: meta.name });
|
|
89
|
+
} catch (err) {
|
|
90
|
+
logger.error('ensure delete blocklet process failed for installing', { did, name: meta.name, error: err });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add environments
|
|
94
|
+
await manager._updateBlockletEnvironment(meta.did);
|
|
95
|
+
blocklet = await manager.getBlocklet(did);
|
|
96
|
+
|
|
97
|
+
// Add initialize authentication settings
|
|
98
|
+
await manager.migrateBlockletAuthentication({ did });
|
|
99
|
+
|
|
100
|
+
// pre install
|
|
101
|
+
await manager._runUserHook('preInstall', blocklet, context);
|
|
102
|
+
|
|
103
|
+
// post install
|
|
104
|
+
await manager._runUserHook('postInstall', blocklet, context);
|
|
105
|
+
|
|
106
|
+
// pre flight
|
|
107
|
+
await manager._runUserHook('preFlight', blocklet, context);
|
|
108
|
+
|
|
109
|
+
await states.blocklet.setInstalledAt(did);
|
|
110
|
+
|
|
111
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.installed, { componentDids });
|
|
112
|
+
blocklet = await manager.getBlocklet(did);
|
|
113
|
+
logger.info('blocklet installed', { source, did: meta.did });
|
|
114
|
+
|
|
115
|
+
// logo
|
|
116
|
+
await ensureAppLogo(blocklet, manager.installDir);
|
|
117
|
+
await updateBlockletFallbackLogo(blocklet);
|
|
118
|
+
|
|
119
|
+
// Init db
|
|
120
|
+
await manager.teamManager.initTeam(blocklet.meta.did);
|
|
121
|
+
|
|
122
|
+
// Update dependents
|
|
123
|
+
await manager._updateDependents(did);
|
|
124
|
+
blocklet = await manager.getBlocklet(did);
|
|
125
|
+
|
|
126
|
+
// Inject pack configs
|
|
127
|
+
const packConfig = await getPackConfig(blocklet);
|
|
128
|
+
if (packConfig) {
|
|
129
|
+
const packComponent = getPackComponent(blocklet);
|
|
130
|
+
|
|
131
|
+
// 同步图片资源
|
|
132
|
+
await copyPackImages({
|
|
133
|
+
appDataDir: path.join(manager.dataDirs.data, blocklet.meta.name),
|
|
134
|
+
packDir: packComponent.env.appDir,
|
|
135
|
+
packConfig,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 同步 App 导航配置
|
|
139
|
+
await manager.configNavigations({ did, navigations: packConfig.navigations });
|
|
140
|
+
|
|
141
|
+
// 同步 App 品牌相关图片配置
|
|
142
|
+
const configObj = packConfig.configObj || {};
|
|
143
|
+
for (const key of util.APP_CONFIG_IMAGE_KEYS) {
|
|
144
|
+
const value = configObj[key];
|
|
145
|
+
if (value) {
|
|
146
|
+
await manager.config({
|
|
147
|
+
did: [blocklet.meta.did],
|
|
148
|
+
configs: [{ key, value }],
|
|
149
|
+
skipHook: true,
|
|
150
|
+
skipDidDocument: true,
|
|
151
|
+
skipEmitEvents: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 同步各子组件配置
|
|
157
|
+
for (const { did: componentDid, configs } of packConfig.components || []) {
|
|
158
|
+
if (blocklet.children.some((x) => x.meta.did === componentDid)) {
|
|
159
|
+
await manager.config({
|
|
160
|
+
did: [blocklet.meta.did, componentDid],
|
|
161
|
+
configs,
|
|
162
|
+
skipHook: true,
|
|
163
|
+
skipDidDocument: true,
|
|
164
|
+
skipEmitEvents: true,
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
logger.error('component not found', { did: componentDid });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
manager.emit(BlockletEvents.installed, { blocklet, context });
|
|
173
|
+
|
|
174
|
+
// Update dynamic component meta in blocklet settings
|
|
175
|
+
await _ensureDeletedChildrenInSettings(manager, blocklet);
|
|
176
|
+
|
|
177
|
+
if (context?.downloadTokenList?.length) {
|
|
178
|
+
await states.blocklet.updateBlocklet(did, {
|
|
179
|
+
tokens: {
|
|
180
|
+
downloadTokenList: context.downloadTokenList,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (blocklet.controller && process.env.NODE_ENV !== 'test') {
|
|
186
|
+
let isNFTConsumed = false;
|
|
187
|
+
if (blocklet.controller.launcherSessionId && blocklet.controller.launcherUrl) {
|
|
188
|
+
await launcher.consumeLauncherSession({ params: blocklet.controller, blocklet });
|
|
189
|
+
isNFTConsumed = true;
|
|
190
|
+
} else if (blocklet.controller.nftId) {
|
|
191
|
+
await launcher.consumeServerlessNFT({ nftId: blocklet.controller.nftId, blocklet });
|
|
192
|
+
isNFTConsumed = true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (isNFTConsumed) {
|
|
196
|
+
await states.blockletExtras.updateByDid(did, {
|
|
197
|
+
controller: { ...blocklet.controller, consumedAt: new Date().toISOString() },
|
|
198
|
+
});
|
|
199
|
+
manager.emit(BlockletEvents.nftConsumed, { blocklet, context });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (createNotification) {
|
|
204
|
+
// 发送通知不阻塞后续安装流程
|
|
205
|
+
try {
|
|
206
|
+
const walletExtra = await getWalletAppNotification(blocklet);
|
|
207
|
+
|
|
208
|
+
manager._createNotification(did, {
|
|
209
|
+
title: 'Blocklet Installed',
|
|
210
|
+
description: `Blocklet ${meta.title} is installed successfully. (Source: ${
|
|
211
|
+
deployedFrom || fromBlockletSource(source)
|
|
212
|
+
})`,
|
|
213
|
+
action: `/blocklets/${did}/overview`,
|
|
214
|
+
blockletDashboardAction: `${WELLKNOWN_SERVICE_PATH_PREFIX}/admin/blocklets`,
|
|
215
|
+
entityType: 'blocklet',
|
|
216
|
+
entityId: did,
|
|
217
|
+
severity: 'success',
|
|
218
|
+
extra: { wallet: walletExtra },
|
|
219
|
+
});
|
|
220
|
+
} catch (error) {
|
|
221
|
+
logger.error('create installed notification failed', { error, did });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await manager._rollbackCache.remove({ did: blocklet.meta.did });
|
|
226
|
+
removeBackup(manager.dataDirs.data, blocklet.appDid);
|
|
227
|
+
return blocklet;
|
|
228
|
+
} catch (err) {
|
|
229
|
+
const { meta } = await states.blocklet.getBlocklet(did);
|
|
230
|
+
const { name, version } = meta;
|
|
231
|
+
logger.error('failed to install blocklet', { name, did, version, error: err });
|
|
232
|
+
try {
|
|
233
|
+
await manager._rollback(INSTALL_ACTIONS.INSTALL, did, oldBlocklet);
|
|
234
|
+
manager.emit(BlockletEvents.installFailed, {
|
|
235
|
+
meta: { did },
|
|
236
|
+
error: {
|
|
237
|
+
message: err.message,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
manager._createNotification(did, {
|
|
241
|
+
title: 'Blocklet Install Failed',
|
|
242
|
+
description: `Blocklet ${meta.title} install failed with error: ${err.message}`,
|
|
243
|
+
entityType: 'blocklet',
|
|
244
|
+
entityId: did,
|
|
245
|
+
severity: 'error',
|
|
246
|
+
});
|
|
247
|
+
} catch (e) {
|
|
248
|
+
logger.error('failed to remove blocklet on install error', { did: meta.did, error: e });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Ensure deleted children in settings (imported from install-upgrade-manager)
|
|
257
|
+
*/
|
|
258
|
+
async function _ensureDeletedChildrenInSettings(manager, blocklet) {
|
|
259
|
+
const { did } = blocklet.meta;
|
|
260
|
+
|
|
261
|
+
// TODO 不从 settings 中取值, 直接存在 extra 中
|
|
262
|
+
let deletedChildren = await states.blockletExtras.getSettings(did, 'children', []);
|
|
263
|
+
deletedChildren = deletedChildren.filter(
|
|
264
|
+
(x) => x.status === BlockletStatus.deleted && !blocklet.children.some((y) => y.meta.did === x.meta.did)
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
await states.blockletExtras.setSettings(did, { children: deletedChildren });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Add blocklet
|
|
272
|
+
*/
|
|
273
|
+
async function _addBlocklet(
|
|
274
|
+
manager,
|
|
275
|
+
{
|
|
276
|
+
component,
|
|
277
|
+
mode = BLOCKLET_MODES.PRODUCTION,
|
|
278
|
+
name,
|
|
279
|
+
did,
|
|
280
|
+
title,
|
|
281
|
+
description,
|
|
282
|
+
skSource = '',
|
|
283
|
+
folder,
|
|
284
|
+
onlyRequired,
|
|
285
|
+
requirements,
|
|
286
|
+
context,
|
|
287
|
+
}
|
|
288
|
+
) {
|
|
289
|
+
const environments = component?.meta?.environments || [];
|
|
290
|
+
|
|
291
|
+
const meta = {
|
|
292
|
+
name,
|
|
293
|
+
did,
|
|
294
|
+
title: title || component?.meta?.title || '',
|
|
295
|
+
description: description || component?.meta?.description || '',
|
|
296
|
+
version: BLOCKLET_DEFAULT_VERSION,
|
|
297
|
+
group: BlockletGroup.gateway,
|
|
298
|
+
interfaces: [
|
|
299
|
+
{
|
|
300
|
+
type: BLOCKLET_INTERFACE_TYPE_WEB,
|
|
301
|
+
name: BLOCKLET_INTERFACE_PUBLIC,
|
|
302
|
+
path: BLOCKLET_DEFAULT_PATH_REWRITE,
|
|
303
|
+
prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
304
|
+
port: BLOCKLET_DEFAULT_PORT_NAME,
|
|
305
|
+
protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
specVersion: BLOCKLET_LATEST_SPEC_VERSION,
|
|
309
|
+
environments,
|
|
310
|
+
timeout: {
|
|
311
|
+
start: process.env.NODE_ENV === 'test' ? 10 : 60,
|
|
312
|
+
},
|
|
313
|
+
...(requirements ? { requirements } : {}),
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
let children;
|
|
317
|
+
if (mode === BLOCKLET_MODES.DEVELOPMENT) {
|
|
318
|
+
children = isGatewayBlocklet(component.meta) ? [] : [component];
|
|
319
|
+
} else {
|
|
320
|
+
children = component ? await manager._getChildrenForInstallation(component) : [];
|
|
321
|
+
if (onlyRequired) {
|
|
322
|
+
children = filterRequiredComponents(component, children);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// FIXME @linchen
|
|
327
|
+
// 当应用本身是容器时, 下载这个容器, 因为容器可能除 blocklet.yml 额外的文件
|
|
328
|
+
// 本身就是容器的应用, 在容器中添加额外文件可能不是合理的做法
|
|
329
|
+
// 容器只在安装时有效, 安装后容器无法升级
|
|
330
|
+
const containerSourceUrl =
|
|
331
|
+
component && component.meta.group === BlockletGroup.gateway && component.meta.dist && component.bundleSource?.url;
|
|
332
|
+
|
|
333
|
+
if (containerSourceUrl) {
|
|
334
|
+
meta.bundleDid = component.meta.did;
|
|
335
|
+
meta.bundleName = component.meta.name;
|
|
336
|
+
meta.version = component.meta.version;
|
|
337
|
+
meta.dist = component.meta.dist;
|
|
338
|
+
meta.logo = component.meta.logo;
|
|
339
|
+
} else if (children[0]?.meta?.logo) {
|
|
340
|
+
meta.logo = children[0].meta.logo;
|
|
341
|
+
} else if (mode === BLOCKLET_MODES.DEVELOPMENT && isGatewayBlocklet(component.meta)) {
|
|
342
|
+
meta.logo = component.meta.logo;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
await validateBlocklet({ meta, children });
|
|
346
|
+
|
|
347
|
+
// fake install bundle
|
|
348
|
+
if (!containerSourceUrl) {
|
|
349
|
+
const bundleDir = getBundleDir(manager.installDir, meta);
|
|
350
|
+
fs.mkdirSync(bundleDir, { recursive: true });
|
|
351
|
+
updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// add blocklet to db
|
|
355
|
+
const params = {
|
|
356
|
+
meta,
|
|
357
|
+
source: BlockletSource.custom,
|
|
358
|
+
children,
|
|
359
|
+
mode,
|
|
360
|
+
externalSkSource: skSource,
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
if (containerSourceUrl) {
|
|
364
|
+
params.source = BlockletSource.url;
|
|
365
|
+
params.deployedFrom = containerSourceUrl;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (mode === BLOCKLET_MODES.DEVELOPMENT && isGatewayBlocklet(component.meta)) {
|
|
369
|
+
params.source = BlockletSource.local;
|
|
370
|
+
params.deployedFrom = folder;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const blocklet = await states.blocklet.addBlocklet(params);
|
|
374
|
+
|
|
375
|
+
// set chain type
|
|
376
|
+
const chainTypeEnv = {
|
|
377
|
+
key: BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_CHAIN_TYPE,
|
|
378
|
+
value: 'arcblock',
|
|
379
|
+
shared: true,
|
|
380
|
+
};
|
|
381
|
+
const customChainType = environments.find((x) => x.name === 'CHAIN_TYPE')?.default;
|
|
382
|
+
if (['eth', 'ethereum'].includes(customChainType)) {
|
|
383
|
+
chainTypeEnv.value = 'ethereum';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
await states.blockletExtras.setConfigs(blocklet.meta.did, [chainTypeEnv]);
|
|
387
|
+
await states.blockletExtras.setSettings(blocklet.meta.did, { session: { salt: generateRandomString(16) } });
|
|
388
|
+
|
|
389
|
+
manager.emit(BlockletEvents.added, { blocklet, context });
|
|
390
|
+
|
|
391
|
+
return blocklet;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Install blocklet
|
|
396
|
+
* @param {Object} manager - BlockletManager instance
|
|
397
|
+
* @param {Object} params - Install parameters
|
|
398
|
+
* @param {Object} context - Context
|
|
399
|
+
* @returns {Promise<Object>}
|
|
400
|
+
*/
|
|
401
|
+
async function install(manager, params, context = {}) {
|
|
402
|
+
const type = getTypeFromInstallParams(params);
|
|
403
|
+
logger.info('install blocklet', { type, params: omit(params, ['appSk']), user: context.user });
|
|
404
|
+
|
|
405
|
+
const { appSk } = params;
|
|
406
|
+
if (!appSk) {
|
|
407
|
+
throw new Error('appSk is required');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!params.controller && context?.user?.controller) {
|
|
411
|
+
params.controller = context.user.controller;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const info = await states.node.read();
|
|
415
|
+
|
|
416
|
+
// Note: if you added new header here, please change core/state/lib/blocklet/downloader/bundle-downloader.js to use that header
|
|
417
|
+
context.headers = Object.assign(context?.headers || {}, {
|
|
418
|
+
'x-server-did': info.did,
|
|
419
|
+
'x-server-public-key': info.pk,
|
|
420
|
+
'x-server-signature': await sign(info.did, info.sk, {
|
|
421
|
+
exp: (Date.now() + 5 * 60 * 1000) / 1000,
|
|
422
|
+
}),
|
|
423
|
+
});
|
|
424
|
+
context.downloadTokenList = params.downloadTokenList || [];
|
|
425
|
+
|
|
426
|
+
if (typeof context.startImmediately === 'undefined') {
|
|
427
|
+
context.startImmediately = !!params.startImmediately;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (type === BLOCKLET_INSTALL_TYPE.RESTORE) {
|
|
431
|
+
const { url } = params;
|
|
432
|
+
return installApplicationFromBackup({ url, appSk, context, manager, states });
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if ([BLOCKLET_INSTALL_TYPE.URL, BLOCKLET_INSTALL_TYPE.STORE, BLOCKLET_INSTALL_TYPE.CREATE].includes(type)) {
|
|
436
|
+
return installApplicationFromGeneral({ ...params, type, context, manager, states });
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// should not be here
|
|
440
|
+
throw new Error(`install from ${type} is not supported`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
module.exports = {
|
|
444
|
+
_onInstall,
|
|
445
|
+
_installBlocklet,
|
|
446
|
+
_addBlocklet,
|
|
447
|
+
install,
|
|
448
|
+
};
|