@abtnode/core 1.16.47-beta-20250721-073316-a6de1fa3 → 1.16.47-beta-20250723-114212-8da08071
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.js +5 -0
- package/lib/blocklet/manager/disk.js +72 -2
- package/lib/blocklet/manager/helper/install-application-from-general.js +12 -1
- package/lib/blocklet/migration-dist/migration.cjs +7 -4
- package/lib/blocklet/project/connect-to-aigne.js +61 -0
- package/lib/blocklet/project/create-connect.js +85 -0
- package/lib/blocklet/project/index.js +2 -0
- package/lib/event/index.js +1 -1
- package/lib/index.js +3 -0
- package/lib/router/helper.js +21 -13
- package/lib/router/index.js +7 -0
- package/lib/states/node.js +10 -5
- package/lib/util/aigne-verify.js +101 -0
- package/lib/util/blocklet.js +20 -1
- package/lib/util/check-dns.js +39 -9
- package/lib/util/docker/ensure-docker-postgres.js +1 -0
- package/lib/util/install-external-dependencies.js +2 -3
- package/lib/util/migration-sqlite-to-postgres.js +69 -5
- package/lib/validators/util.js +1 -0
- package/package.json +39 -38
package/lib/api/team.js
CHANGED
|
@@ -2085,6 +2085,11 @@ class TeamAPI extends EventEmitter {
|
|
|
2085
2085
|
if (!userDid) {
|
|
2086
2086
|
throw new Error('userDid is required');
|
|
2087
2087
|
}
|
|
2088
|
+
if (visitorId) {
|
|
2089
|
+
if (visitorId.length > 80) {
|
|
2090
|
+
throw new Error('visitorId should be less than 80 characters');
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2088
2093
|
|
|
2089
2094
|
const state = await this.getUserSessionState(teamDid);
|
|
2090
2095
|
let data;
|
|
@@ -27,7 +27,7 @@ const { getDidDomainForBlocklet } = require('@abtnode/util/lib/get-domain-for-bl
|
|
|
27
27
|
const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
|
|
28
28
|
const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
|
|
29
29
|
const { sanitizeTag } = require('@abtnode/util/lib/sanitize');
|
|
30
|
-
|
|
30
|
+
const { CustomError } = require('@blocklet/error');
|
|
31
31
|
const {
|
|
32
32
|
BLOCKLET_INSTALL_TYPE,
|
|
33
33
|
APP_STRUCT_VERSION,
|
|
@@ -224,6 +224,7 @@ const {
|
|
|
224
224
|
updateSelectedResources,
|
|
225
225
|
connectToStore,
|
|
226
226
|
connectToEndpoint,
|
|
227
|
+
connectToAigne,
|
|
227
228
|
publishToStore,
|
|
228
229
|
publishToEndpoint,
|
|
229
230
|
connectByStudio,
|
|
@@ -243,6 +244,7 @@ const { generateUserUpdateData } = require('../../util/user');
|
|
|
243
244
|
const { blockletThemeSchema } = require('../../validators/theme');
|
|
244
245
|
const { removeDockerNetwork } = require('../../util/docker/docker-network.js');
|
|
245
246
|
const parseDockerName = require('../../util/docker/parse-docker-name.js');
|
|
247
|
+
const { verifyAigneConfig, decryptValue } = require('../../util/aigne-verify');
|
|
246
248
|
|
|
247
249
|
const { formatEnvironments, getBlockletMeta, validateOwner, isCLI } = util;
|
|
248
250
|
|
|
@@ -2135,6 +2137,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
2135
2137
|
|
|
2136
2138
|
sessionConfig.email = validateConfig.email;
|
|
2137
2139
|
sessionConfig.phone = validateConfig.phone;
|
|
2140
|
+
sessionConfig.enableBlacklist = validateConfig.enableBlacklist;
|
|
2138
2141
|
|
|
2139
2142
|
// Do some final check
|
|
2140
2143
|
if (sessionConfig.email?.enabled && sessionConfig.email?.requireVerified && !getEmailServiceProvider(blocklet)) {
|
|
@@ -2779,7 +2782,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
2779
2782
|
return summary;
|
|
2780
2783
|
}
|
|
2781
2784
|
|
|
2782
|
-
async updateBlockletSettings({ did, enableSessionHardening, invite, gateway }, context) {
|
|
2785
|
+
async updateBlockletSettings({ did, enableSessionHardening, invite, gateway, aigne }, context) {
|
|
2783
2786
|
const params = {};
|
|
2784
2787
|
if (!isNil(enableSessionHardening)) {
|
|
2785
2788
|
params.enableSessionHardening = enableSessionHardening;
|
|
@@ -2793,6 +2796,21 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
2793
2796
|
params.gateway = gateway;
|
|
2794
2797
|
}
|
|
2795
2798
|
|
|
2799
|
+
if (!isNil(aigne)) {
|
|
2800
|
+
const { key, url } = aigne;
|
|
2801
|
+
if (url && !isUrl(url)) {
|
|
2802
|
+
throw new CustomError(400, 'The AIGNE API Url is either missing or incorrectly formatted');
|
|
2803
|
+
}
|
|
2804
|
+
if (!key) {
|
|
2805
|
+
throw new CustomError(400, 'The AIGNE API key is missing');
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
const decryptedKey = decryptValue(key, did);
|
|
2809
|
+
if (key && !decryptedKey) {
|
|
2810
|
+
throw new CustomError(400, 'Save failed, the API key is not encrypted');
|
|
2811
|
+
}
|
|
2812
|
+
params.aigne = aigne;
|
|
2813
|
+
}
|
|
2796
2814
|
const keys = Object.keys(params);
|
|
2797
2815
|
if (!keys.length) {
|
|
2798
2816
|
throw new Error('No settings to update');
|
|
@@ -2880,6 +2898,56 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
2880
2898
|
return connectToEndpoint({ ...params, context, manager: this });
|
|
2881
2899
|
}
|
|
2882
2900
|
|
|
2901
|
+
connectToAigne(params, context) {
|
|
2902
|
+
return connectToAigne({ ...params, context, manager: this });
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
async disconnectToAigne({ did, url, key }, context) {
|
|
2906
|
+
try {
|
|
2907
|
+
const blocklet = await this.getBlocklet(did);
|
|
2908
|
+
if (!blocklet) {
|
|
2909
|
+
throw new CustomError(400, 'blocklet did invalid, no blocklet found');
|
|
2910
|
+
}
|
|
2911
|
+
const aigneSetting = get(blocklet, 'settings.aigne', {});
|
|
2912
|
+
if (aigneSetting.key !== key && url !== aigneSetting.url) {
|
|
2913
|
+
throw new CustomError(400, 'Invalid key or url provided');
|
|
2914
|
+
}
|
|
2915
|
+
await states.blockletExtras.setSettings(did, {
|
|
2916
|
+
aigne: {
|
|
2917
|
+
...aigneSetting,
|
|
2918
|
+
key: '',
|
|
2919
|
+
accessKeyId: '',
|
|
2920
|
+
secretAccessKey: '',
|
|
2921
|
+
},
|
|
2922
|
+
});
|
|
2923
|
+
const newState = await this.getBlocklet(did);
|
|
2924
|
+
this.emit(BlockletInternalEvents.appSettingChanged, { appDid: did });
|
|
2925
|
+
this.emit(BlockletEvents.updated, { ...newState, context });
|
|
2926
|
+
|
|
2927
|
+
return newState;
|
|
2928
|
+
} catch (error) {
|
|
2929
|
+
logger.error('disconnectToAigne error', { did, url, key, error });
|
|
2930
|
+
throw error;
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
async verifyAigneConnection({ did }) {
|
|
2935
|
+
try {
|
|
2936
|
+
const blocklet = await this.getBlocklet(did);
|
|
2937
|
+
if (!blocklet) {
|
|
2938
|
+
throw new CustomError(400, 'blocklet did invalid, no blocklet found');
|
|
2939
|
+
}
|
|
2940
|
+
const aigne = get(blocklet, 'settings.aigne', {});
|
|
2941
|
+
const verified = await verifyAigneConfig(aigne, did);
|
|
2942
|
+
if (!verified.valid) {
|
|
2943
|
+
throw new CustomError(500, verified.error);
|
|
2944
|
+
}
|
|
2945
|
+
} catch (error) {
|
|
2946
|
+
logger.error('verify aigne connection error', { did, error });
|
|
2947
|
+
throw error;
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2883
2951
|
disconnectFromStore(params, context) {
|
|
2884
2952
|
return disconnectFromStore({ ...params, context, manager: this });
|
|
2885
2953
|
}
|
|
@@ -3912,6 +3980,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
3912
3980
|
skSource = '',
|
|
3913
3981
|
folder,
|
|
3914
3982
|
onlyRequired,
|
|
3983
|
+
requirements,
|
|
3915
3984
|
}) {
|
|
3916
3985
|
const environments = component?.meta?.environments || [];
|
|
3917
3986
|
|
|
@@ -3937,6 +4006,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
3937
4006
|
timeout: {
|
|
3938
4007
|
start: process.env.NODE_ENV === 'test' ? 10 : 60,
|
|
3939
4008
|
},
|
|
4009
|
+
...(requirements ? { requirements } : {}),
|
|
3940
4010
|
};
|
|
3941
4011
|
|
|
3942
4012
|
let children;
|
|
@@ -57,6 +57,7 @@ const installApplicationFromGeneral = async ({
|
|
|
57
57
|
|
|
58
58
|
// create component
|
|
59
59
|
let component;
|
|
60
|
+
let requirements;
|
|
60
61
|
if (componentSourceUrl) {
|
|
61
62
|
const meta = await getBlockletMetaFromUrl(componentSourceUrl);
|
|
62
63
|
|
|
@@ -92,6 +93,7 @@ const installApplicationFromGeneral = async ({
|
|
|
92
93
|
}
|
|
93
94
|
: { url: componentSourceUrl },
|
|
94
95
|
};
|
|
96
|
+
requirements = meta.requirements;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
// app wallet
|
|
@@ -113,7 +115,16 @@ const installApplicationFromGeneral = async ({
|
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
// create app
|
|
116
|
-
const blocklet = await manager._addBlocklet({
|
|
118
|
+
const blocklet = await manager._addBlocklet({
|
|
119
|
+
component,
|
|
120
|
+
name,
|
|
121
|
+
did,
|
|
122
|
+
title,
|
|
123
|
+
description,
|
|
124
|
+
skSource,
|
|
125
|
+
onlyRequired,
|
|
126
|
+
requirements,
|
|
127
|
+
});
|
|
117
128
|
logger.info('blocklet added to database', { did: blocklet.meta.did });
|
|
118
129
|
|
|
119
130
|
// create config
|
|
@@ -846,6 +846,7 @@ module.exports = Object.freeze({
|
|
|
846
846
|
'blocklet:read': 'Read blocklet information and database',
|
|
847
847
|
'blocklet:write': 'Manage blocklet components and configuration',
|
|
848
848
|
},
|
|
849
|
+
AIGNE_CONFIG_ENCRYPT_SALT: 'AIGNE_CONFIG_ENCRYPT_SALT',
|
|
849
850
|
});
|
|
850
851
|
|
|
851
852
|
|
|
@@ -7637,7 +7638,7 @@ function hookChildProcess(cp, parsed) {
|
|
|
7637
7638
|
// the command exists and emit an "error" instead
|
|
7638
7639
|
// See https://github.com/IndigoUnited/node-cross-spawn/issues/16
|
|
7639
7640
|
if (name === 'exit') {
|
|
7640
|
-
const err = verifyENOENT(arg1, parsed
|
|
7641
|
+
const err = verifyENOENT(arg1, parsed);
|
|
7641
7642
|
|
|
7642
7643
|
if (err) {
|
|
7643
7644
|
return originalEmit.call(cp, 'error', err);
|
|
@@ -7794,15 +7795,17 @@ function escapeArgument(arg, doubleEscapeMetaChars) {
|
|
|
7794
7795
|
arg = `${arg}`;
|
|
7795
7796
|
|
|
7796
7797
|
// Algorithm below is based on https://qntm.org/cmd
|
|
7798
|
+
// It's slightly altered to disable JS backtracking to avoid hanging on specially crafted input
|
|
7799
|
+
// Please see https://github.com/moxystudio/node-cross-spawn/pull/160 for more information
|
|
7797
7800
|
|
|
7798
7801
|
// Sequence of backslashes followed by a double quote:
|
|
7799
7802
|
// double up all the backslashes and escape the double quote
|
|
7800
|
-
arg = arg.replace(/(
|
|
7803
|
+
arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
|
|
7801
7804
|
|
|
7802
7805
|
// Sequence of backslashes followed by the end of the string
|
|
7803
7806
|
// (which will become a double quote later):
|
|
7804
7807
|
// double up all the backslashes
|
|
7805
|
-
arg = arg.replace(/(
|
|
7808
|
+
arg = arg.replace(/(?=(\\+?)?)\1$/, '$1$1');
|
|
7806
7809
|
|
|
7807
7810
|
// All other backslashes occur literally
|
|
7808
7811
|
|
|
@@ -38915,7 +38918,7 @@ module.exports = require("zlib");
|
|
|
38915
38918
|
/***/ ((module) => {
|
|
38916
38919
|
|
|
38917
38920
|
"use strict";
|
|
38918
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.46","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.46","@abtnode/auth":"1.16.46","@abtnode/certificate-manager":"1.16.46","@abtnode/client":"1.16.46","@abtnode/constant":"1.16.46","@abtnode/cron":"1.16.46","@abtnode/db-cache":"1.16.46","@abtnode/docker-utils":"1.16.46","@abtnode/logger":"1.16.46","@abtnode/models":"1.16.46","@abtnode/queue":"1.16.46","@abtnode/rbac":"1.16.46","@abtnode/router-provider":"1.16.46","@abtnode/static-server":"1.16.46","@abtnode/timemachine":"1.16.46","@abtnode/util":"1.16.46","@arcblock/did":"1.
|
|
38921
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.46","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.46","@abtnode/auth":"1.16.46","@abtnode/certificate-manager":"1.16.46","@abtnode/client":"1.16.46","@abtnode/constant":"1.16.46","@abtnode/cron":"1.16.46","@abtnode/db-cache":"1.16.46","@abtnode/docker-utils":"1.16.46","@abtnode/logger":"1.16.46","@abtnode/models":"1.16.46","@abtnode/queue":"1.16.46","@abtnode/rbac":"1.16.46","@abtnode/router-provider":"1.16.46","@abtnode/static-server":"1.16.46","@abtnode/timemachine":"1.16.46","@abtnode/util":"1.16.46","@aigne/aigne-hub":"^0.1.3","@arcblock/did":"1.21.0","@arcblock/did-auth":"1.21.0","@arcblock/did-ext":"1.21.0","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"1.21.0","@arcblock/event-hub":"1.21.0","@arcblock/jwt":"1.21.0","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.21.0","@arcblock/vc":"1.21.0","@blocklet/constant":"1.16.46","@blocklet/did-space-js":"^1.1.8","@blocklet/env":"1.16.46","@blocklet/error":"^0.2.5","@blocklet/meta":"1.16.46","@blocklet/resolver":"1.16.46","@blocklet/sdk":"1.16.46","@blocklet/store":"1.16.46","@blocklet/theme":"^3.0.30","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.21.0","@ocap/util":"1.21.0","@ocap/wallet":"1.21.0","@slack/webhook":"^5.0.4","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"^2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^11.1.0","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"expand-tilde":"^2.0.2","express":"^4.18.2","jest":"^29.7.0","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
|
|
38919
38922
|
|
|
38920
38923
|
/***/ }),
|
|
38921
38924
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const validUrl = require('valid-url');
|
|
2
|
+
|
|
3
|
+
const { encrypt } = require('@abtnode/util/lib/security');
|
|
4
|
+
const { joinURL, withHttps } = require('ufo');
|
|
5
|
+
const { WELLKNOWN_SERVICE_PATH_PREFIX, AIGNE_CONFIG_ENCRYPT_SALT } = require('@abtnode/constant');
|
|
6
|
+
const { getDisplayName, getAppUrl } = require('@blocklet/meta/lib/util');
|
|
7
|
+
const { CustomError } = require('@blocklet/error');
|
|
8
|
+
const logger = require('@abtnode/logger')('connect-to-aigne');
|
|
9
|
+
const { createConnect, fetchConfigs } = require('./create-connect');
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line require-await
|
|
12
|
+
const connectToAigne = async ({ did, baseUrl, provider, model, manager }) => {
|
|
13
|
+
if (!did) {
|
|
14
|
+
throw new CustomError(400, 'Invalid did');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!validUrl.isWebUri(baseUrl)) {
|
|
18
|
+
throw new CustomError(400, 'Invalid endpoint url:', baseUrl);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line no-async-promise-executor, consistent-return
|
|
22
|
+
return new Promise(async (resolve, reject) => {
|
|
23
|
+
try {
|
|
24
|
+
const blocklet = await manager.getBlocklet(did);
|
|
25
|
+
const appUrl = getAppUrl(blocklet);
|
|
26
|
+
|
|
27
|
+
const fetchData = await createConnect({
|
|
28
|
+
connectUrl: joinURL(new URL(baseUrl).origin, WELLKNOWN_SERVICE_PATH_PREFIX),
|
|
29
|
+
connectAction: 'gen-simple-access-key',
|
|
30
|
+
source: `Connect to AIGNE hub (${getDisplayName(blocklet)})`,
|
|
31
|
+
closeOnSuccess: true,
|
|
32
|
+
openPage: (pageUrl) => resolve(pageUrl),
|
|
33
|
+
intervalFetchConfig: fetchConfigs,
|
|
34
|
+
appUrl: withHttps(appUrl),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!fetchData.accessKeySecret) {
|
|
38
|
+
throw new CustomError(400, 'Failed to generate access key secret');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const encryptedKey = encrypt(fetchData.accessKeySecret, did || AIGNE_CONFIG_ENCRYPT_SALT, '');
|
|
42
|
+
|
|
43
|
+
await manager.updateBlockletSettings({
|
|
44
|
+
did,
|
|
45
|
+
aigne: {
|
|
46
|
+
key: encryptedKey,
|
|
47
|
+
url: baseUrl,
|
|
48
|
+
provider,
|
|
49
|
+
model,
|
|
50
|
+
accessKeyId: '',
|
|
51
|
+
secretAccessKey: '',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.error('connect to aigne error', { error, did, baseUrl });
|
|
56
|
+
reject(error);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
module.exports = connectToAigne;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const { joinURL } = require('ufo');
|
|
2
|
+
const { withQuery } = require('ufo');
|
|
3
|
+
const pWaitFor = require('p-wait-for');
|
|
4
|
+
const { encodeEncryptionKey, decrypt } = require('@abtnode/util/lib/security');
|
|
5
|
+
const axios = require('@abtnode/util/lib/axios');
|
|
6
|
+
|
|
7
|
+
const ACCESS_KEY_PREFIX = '/api/access-key/session';
|
|
8
|
+
|
|
9
|
+
const fetchConfigs = async ({ connectUrl, sessionId, fetchInterval, fetchTimeout }) => {
|
|
10
|
+
const url = withQuery(joinURL(connectUrl, ACCESS_KEY_PREFIX), { sid: sessionId });
|
|
11
|
+
|
|
12
|
+
const condition = async () => {
|
|
13
|
+
const { data: session } = await axios({ url });
|
|
14
|
+
return Boolean(session.accessKeyId && session.accessKeySecret);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
await pWaitFor(condition, { interval: fetchInterval, timeout: fetchTimeout });
|
|
18
|
+
|
|
19
|
+
const { data: session } = await axios({ url });
|
|
20
|
+
await axios({
|
|
21
|
+
url: withQuery(joinURL(connectUrl, ACCESS_KEY_PREFIX), { sid: sessionId }),
|
|
22
|
+
method: 'DELETE',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
...session,
|
|
27
|
+
accessKeyId: session.accessKeyId,
|
|
28
|
+
accessKeySecret: decrypt(session.accessKeySecret, session.accessKeyId, session.challenge),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function createConnect({
|
|
33
|
+
connectUrl,
|
|
34
|
+
openPage,
|
|
35
|
+
fetchInterval = 3 * 1000,
|
|
36
|
+
retry = 1500,
|
|
37
|
+
source = 'Blocklet CLI',
|
|
38
|
+
connectAction = 'connect-cli',
|
|
39
|
+
wrapSpinner = (_, waiting) => Promise.resolve(waiting()),
|
|
40
|
+
closeOnSuccess,
|
|
41
|
+
prettyUrl,
|
|
42
|
+
intervalFetchConfig,
|
|
43
|
+
appUrl,
|
|
44
|
+
}) {
|
|
45
|
+
try {
|
|
46
|
+
const { data: session } = await axios(joinURL(connectUrl, ACCESS_KEY_PREFIX), { method: 'POST' });
|
|
47
|
+
const token = session.id;
|
|
48
|
+
|
|
49
|
+
const pageUrl = withQuery(joinURL(connectUrl, connectAction), {
|
|
50
|
+
__token__: encodeEncryptionKey(token),
|
|
51
|
+
__url__: encodeEncryptionKey(appUrl),
|
|
52
|
+
source,
|
|
53
|
+
closeOnSuccess,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.info(
|
|
58
|
+
'If browser does not open automatically, please open the following link in your browser: ',
|
|
59
|
+
prettyUrl?.(pageUrl) || pageUrl
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
openPage?.(pageUrl);
|
|
63
|
+
|
|
64
|
+
return await wrapSpinner(`Waiting for connection: ${connectUrl}`, async () => {
|
|
65
|
+
const fn = intervalFetchConfig ?? fetchConfigs;
|
|
66
|
+
|
|
67
|
+
const fetchData = await fn({
|
|
68
|
+
connectUrl,
|
|
69
|
+
sessionId: token,
|
|
70
|
+
fetchTimeout: retry * fetchInterval,
|
|
71
|
+
fetchInterval: retry,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return fetchData;
|
|
75
|
+
});
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error(e);
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
createConnect,
|
|
84
|
+
fetchConfigs,
|
|
85
|
+
};
|
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
const createPackRelease = require('./create-pack-release');
|
|
25
25
|
const connectToStore = require('./connect-to-store');
|
|
26
26
|
const connectToEndpoint = require('./connect-to-endpoint');
|
|
27
|
+
const connectToAigne = require('./connect-to-aigne');
|
|
27
28
|
const publishToStore = require('./publish-to-store');
|
|
28
29
|
const publishToEndpoint = require('./publish-to-endpoint');
|
|
29
30
|
const connectByStudio = require('./connect-by-studio');
|
|
@@ -590,4 +591,5 @@ module.exports = {
|
|
|
590
591
|
publishToStore,
|
|
591
592
|
connectToEndpoint,
|
|
592
593
|
publishToEndpoint,
|
|
594
|
+
connectToAigne,
|
|
593
595
|
};
|
package/lib/event/index.js
CHANGED
|
@@ -171,7 +171,7 @@ module.exports = ({
|
|
|
171
171
|
{ message: `${routingSnapshotPrefix(blocklet)}Install blocklet ${blocklet.meta.name}`, dryRun: false },
|
|
172
172
|
context
|
|
173
173
|
);
|
|
174
|
-
logger.info('
|
|
174
|
+
logger.info('created.url.mapping', { event: name, did: blocklet.meta.did, hash });
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
await teamAPI.refreshBlockletInterfacePermissions(blocklet.meta);
|
package/lib/index.js
CHANGED
|
@@ -590,6 +590,9 @@ function ABTNode(options) {
|
|
|
590
590
|
addUploadEndpoint: teamAPI.addEndpoint.bind(teamAPI),
|
|
591
591
|
deleteUploadEndpoint: teamAPI.deleteEndpoint.bind(teamAPI),
|
|
592
592
|
connectToEndpoint: blockletManager.connectToEndpoint.bind(blockletManager),
|
|
593
|
+
connectToAigne: blockletManager.connectToAigne.bind(blockletManager),
|
|
594
|
+
disconnectToAigne: blockletManager.disconnectToAigne.bind(blockletManager),
|
|
595
|
+
verifyAigneConnection: blockletManager.verifyAigneConnection.bind(blockletManager),
|
|
593
596
|
disconnectFromEndpoint: blockletManager.disconnectFromEndpoint.bind(blockletManager),
|
|
594
597
|
publishToEndpoint: blockletManager.publishToEndpoint.bind(blockletManager),
|
|
595
598
|
|
package/lib/router/helper.js
CHANGED
|
@@ -1409,20 +1409,19 @@ module.exports = function getRouterHelpers({
|
|
|
1409
1409
|
return ruleChanged || siteChanged;
|
|
1410
1410
|
};
|
|
1411
1411
|
|
|
1412
|
-
async function readRoutingSites() {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
if (!snapshotHash) {
|
|
1412
|
+
async function readRoutingSites(snapshotHash) {
|
|
1413
|
+
let hash = snapshotHash;
|
|
1414
|
+
if (!hash) {
|
|
1416
1415
|
logger.debug('router.readRoutingSites read snapshot hash from snapshot db');
|
|
1417
|
-
|
|
1416
|
+
hash = await routingSnapshot.getLastSnapshot();
|
|
1418
1417
|
}
|
|
1419
1418
|
|
|
1420
|
-
if (!
|
|
1419
|
+
if (!hash) {
|
|
1421
1420
|
logger.error('Can not determine routing snapshot hash, there must be something wrong!');
|
|
1422
1421
|
return {};
|
|
1423
1422
|
}
|
|
1424
1423
|
|
|
1425
|
-
const result = await routingSnapshot.readSnapshot(
|
|
1424
|
+
const result = await routingSnapshot.readSnapshot(hash);
|
|
1426
1425
|
result.sites = await ensureLatestInfo(result.sites);
|
|
1427
1426
|
|
|
1428
1427
|
return result;
|
|
@@ -1531,7 +1530,9 @@ module.exports = function getRouterHelpers({
|
|
|
1531
1530
|
|
|
1532
1531
|
const handleRouting = async (nodeInfo) => {
|
|
1533
1532
|
const now = Date.now();
|
|
1534
|
-
logger.info('start handle routing'
|
|
1533
|
+
logger.info('start handle routing', {
|
|
1534
|
+
snapshotHash: nodeInfo?.routing?.snapshotHash,
|
|
1535
|
+
});
|
|
1535
1536
|
const providerName = get(nodeInfo, 'routing.provider', null);
|
|
1536
1537
|
const httpsEnabled = get(nodeInfo, 'routing.https', true);
|
|
1537
1538
|
logger.debug('handle routing', { providerName, httpsEnabled });
|
|
@@ -1551,8 +1552,10 @@ module.exports = function getRouterHelpers({
|
|
|
1551
1552
|
provider: createProviderInstance({ nodeInfo, routerDataDir: dataDirs.router }),
|
|
1552
1553
|
getRoutingParams: async () => {
|
|
1553
1554
|
try {
|
|
1554
|
-
const info = await nodeState.
|
|
1555
|
-
|
|
1555
|
+
const info = await nodeState._read();
|
|
1556
|
+
logger.info('router:getRoutingParams read routing params', { snapshotHash: info.routing?.snapshotHash });
|
|
1557
|
+
|
|
1558
|
+
let { sites } = await readRoutingSites(info.routing?.snapshotHash);
|
|
1556
1559
|
sites = await ensureLatestInfo(sites);
|
|
1557
1560
|
sites = await ensureServiceRule(sites);
|
|
1558
1561
|
sites = await ensureRootRule(sites);
|
|
@@ -1793,12 +1796,17 @@ module.exports = function getRouterHelpers({
|
|
|
1793
1796
|
|
|
1794
1797
|
const hash = await routingSnapshot.takeSnapshot(msg, dryRun);
|
|
1795
1798
|
if (!dryRun) {
|
|
1796
|
-
|
|
1797
|
-
|
|
1799
|
+
let nodeInfo = await nodeState.read();
|
|
1800
|
+
nodeInfo = await nodeState.updateNodeRouting({ ...nodeInfo.routing, snapshotHash: hash });
|
|
1798
1801
|
if (handle) {
|
|
1799
1802
|
await handleRouting(nodeInfo);
|
|
1800
1803
|
}
|
|
1801
|
-
logger.
|
|
1804
|
+
logger.info('takeRoutingSnapshot', {
|
|
1805
|
+
dryRun,
|
|
1806
|
+
handleRouting: handle,
|
|
1807
|
+
hash,
|
|
1808
|
+
dbSnapshotHash: nodeInfo?.routing?.snapshotHash,
|
|
1809
|
+
});
|
|
1802
1810
|
}
|
|
1803
1811
|
|
|
1804
1812
|
return hash;
|
package/lib/router/index.js
CHANGED
|
@@ -208,6 +208,9 @@ class Router {
|
|
|
208
208
|
outboundAnomalyScoreThreshold: 10,
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
+
logger.info('router: update routing table', {
|
|
212
|
+
snapshotHash: nodeInfo?.routing?.snapshotHash,
|
|
213
|
+
});
|
|
211
214
|
await this.provider.update({
|
|
212
215
|
routingTable: this.routingTable,
|
|
213
216
|
certificates,
|
|
@@ -223,12 +226,16 @@ class Router {
|
|
|
223
226
|
enableDefaultServer: nodeInfo.routing.enableDefaultServer ?? false,
|
|
224
227
|
enableIpServer: nodeInfo.routing.enableIpServer ?? false,
|
|
225
228
|
});
|
|
229
|
+
logger.info('router: update routing table success', {
|
|
230
|
+
snapshotHash: nodeInfo?.routing?.snapshotHash,
|
|
231
|
+
});
|
|
226
232
|
}
|
|
227
233
|
|
|
228
234
|
async update() {
|
|
229
235
|
logger.info('router: update');
|
|
230
236
|
await this.updateRoutingTable();
|
|
231
237
|
await this.provider.reload();
|
|
238
|
+
logger.info('router: reload provider success');
|
|
232
239
|
}
|
|
233
240
|
|
|
234
241
|
async start() {
|
package/lib/states/node.js
CHANGED
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
DEFAULT_NFT_DOMAIN_URL,
|
|
17
17
|
SERVER_CACHE_TTL,
|
|
18
18
|
} = require('@abtnode/constant');
|
|
19
|
-
|
|
19
|
+
const logger = require('@abtnode/logger')('@abtnode/core:states:node');
|
|
20
20
|
|
|
21
21
|
const BaseState = require('./base');
|
|
22
22
|
const { validateOwner } = require('../util');
|
|
@@ -69,7 +69,12 @@ class NodeState extends BaseState {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
deleteCache = async () => {
|
|
72
|
-
|
|
72
|
+
try {
|
|
73
|
+
await this.cache.del(this.cacheGroup);
|
|
74
|
+
logger.debug('deleteCache success', { cacheGroup: this.cacheGroup });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error('deleteCache failed', { error });
|
|
77
|
+
}
|
|
73
78
|
};
|
|
74
79
|
|
|
75
80
|
/**
|
|
@@ -172,7 +177,7 @@ class NodeState extends BaseState {
|
|
|
172
177
|
|
|
173
178
|
// FIXME: 这个接口比较危险,可能会修改一些本不应该修改的字段,后续需要考虑改进
|
|
174
179
|
async updateNodeInfo(entity = {}) {
|
|
175
|
-
this.deleteCache();
|
|
180
|
+
await this.deleteCache();
|
|
176
181
|
const old = await this.read();
|
|
177
182
|
const doc = await this.update({ $set: omit(entity, ['ownerNft', 'sk']) });
|
|
178
183
|
this.emit(EVENTS.NODE_UPDATED, doc, old);
|
|
@@ -338,10 +343,10 @@ class NodeState extends BaseState {
|
|
|
338
343
|
}
|
|
339
344
|
|
|
340
345
|
async update(updates) {
|
|
341
|
-
this.deleteCache();
|
|
346
|
+
await this.deleteCache();
|
|
342
347
|
await this.read(); // CAUTION: 这一行不要删,当 node state 不存在时,read() 会插入新数据
|
|
343
348
|
const [, [updated]] = await super.update({ did: this.config.nodeDid }, updates);
|
|
344
|
-
this.deleteCache();
|
|
349
|
+
await this.deleteCache();
|
|
345
350
|
return updated;
|
|
346
351
|
}
|
|
347
352
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用于验证 aigne 的配置是否正确
|
|
3
|
+
*/
|
|
4
|
+
const { AIGNEHubChatModel } = require('@aigne/aigne-hub');
|
|
5
|
+
const { AIGNE_CONFIG_ENCRYPT_SALT } = require('@abtnode/constant');
|
|
6
|
+
const { decrypt } = require('@abtnode/util/lib/security');
|
|
7
|
+
const axios = require('@abtnode/util/lib/axios');
|
|
8
|
+
const { joinURL } = require('ufo');
|
|
9
|
+
const logger = require('@abtnode/logger')('@abtnode/core:util:aigne-verify');
|
|
10
|
+
|
|
11
|
+
const decryptValue = (value, did) => {
|
|
12
|
+
return value ? decrypt(value, did || AIGNE_CONFIG_ENCRYPT_SALT, '') : value;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getAigneHubModelApi = async (url) => {
|
|
16
|
+
try {
|
|
17
|
+
const urlObj = new URL(url);
|
|
18
|
+
const appUrl = urlObj.origin;
|
|
19
|
+
const { data: blockletJson } = await axios.get(joinURL(appUrl, '__blocklet__.js?type=json'));
|
|
20
|
+
const { componentMountPoints = [] } = blockletJson || {};
|
|
21
|
+
|
|
22
|
+
const aigneHubMountPoint = componentMountPoints.find((item) => item.name === 'ai-kit');
|
|
23
|
+
if (!aigneHubMountPoint) {
|
|
24
|
+
throw new Error("The current application doesn't have the AIGNE Hub component installed");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return joinURL(appUrl, aigneHubMountPoint.mountPoint, 'api/v2/chat');
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error('Failed to establish connection to AIGNE Hub API endpoint at the specified URL');
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const verifyAigneHub = async (config, did) => {
|
|
34
|
+
try {
|
|
35
|
+
if (!config.url) {
|
|
36
|
+
return {
|
|
37
|
+
valid: false,
|
|
38
|
+
error: 'AIGNE Hub API URL must be provided in the configuration',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (!config.key) {
|
|
42
|
+
return {
|
|
43
|
+
valid: false,
|
|
44
|
+
error: 'AIGNE Hub API key must be provided in the configuration',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const baseUrl = await getAigneHubModelApi(config.url);
|
|
49
|
+
|
|
50
|
+
const modelConfig = {
|
|
51
|
+
accessKey: decryptValue(config.key, did),
|
|
52
|
+
model: !config.model || config.model === 'auto' ? undefined : config.model,
|
|
53
|
+
url: baseUrl,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const model = new AIGNEHubChatModel(modelConfig);
|
|
57
|
+
|
|
58
|
+
const result = await model.invoke({
|
|
59
|
+
messages: [{ role: 'user', content: 'Hello, who are you?' }],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return { valid: !!result };
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.error(`verify ${config.provider} config error`, { error });
|
|
65
|
+
return { valid: false, error: error.message };
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 主验证函数 - 根据配置中的 provider 执行对应的验证方法
|
|
71
|
+
* @param {Object} config - 配置对象
|
|
72
|
+
* @param {string} config.provider - 提供商名称
|
|
73
|
+
* @param {string} config.apiKey - API 密钥
|
|
74
|
+
* @param {string} [config.baseURL] - 基础 URL
|
|
75
|
+
* @param {string} [config.model] - 模型名称
|
|
76
|
+
* @param {Object} [config.options] - 其他选项
|
|
77
|
+
* @returns {Promise<{valid: boolean, error?: string}>}
|
|
78
|
+
*/
|
|
79
|
+
async function verifyAigneConfig(config, did) {
|
|
80
|
+
try {
|
|
81
|
+
if (!config || typeof config !== 'object') {
|
|
82
|
+
return {
|
|
83
|
+
valid: false,
|
|
84
|
+
error: 'AIGNE configuration must be a valid object',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return await verifyAigneHub(config, did);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
valid: false,
|
|
92
|
+
error: `Verification failed: ${error.message}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
verifyAigneConfig,
|
|
99
|
+
verifyAigneHub,
|
|
100
|
+
decryptValue,
|
|
101
|
+
};
|
package/lib/util/blocklet.js
CHANGED
|
@@ -7,6 +7,7 @@ const shelljs = require('shelljs');
|
|
|
7
7
|
const os = require('os');
|
|
8
8
|
const tar = require('tar');
|
|
9
9
|
const get = require('lodash/get');
|
|
10
|
+
const isNil = require('lodash/isNil');
|
|
10
11
|
const uniq = require('lodash/uniq');
|
|
11
12
|
const cloneDeep = require('lodash/cloneDeep');
|
|
12
13
|
const mergeWith = require('lodash/mergeWith');
|
|
@@ -30,7 +31,7 @@ const logger = require('@abtnode/logger')('@abtnode/core:util:blocklet');
|
|
|
30
31
|
const pm2 = require('@abtnode/util/lib/async-pm2');
|
|
31
32
|
const sleep = require('@abtnode/util/lib/sleep');
|
|
32
33
|
const getPm2ProcessInfo = require('@abtnode/util/lib/get-pm2-process-info');
|
|
33
|
-
const { formatEnv, getSecurityNodeOptions } = require('@abtnode/util/lib/security');
|
|
34
|
+
const { formatEnv, getSecurityNodeOptions, decrypt } = require('@abtnode/util/lib/security');
|
|
34
35
|
const ensureEndpointHealthy = require('@abtnode/util/lib/ensure-endpoint-healthy');
|
|
35
36
|
const getFolderSize = require('@abtnode/util/lib/get-folder-size');
|
|
36
37
|
const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
|
|
@@ -42,6 +43,7 @@ const {
|
|
|
42
43
|
BLOCKLET_INSTALL_TYPE,
|
|
43
44
|
APP_STRUCT_VERSION,
|
|
44
45
|
BLOCKLET_CACHE_TTL,
|
|
46
|
+
AIGNE_CONFIG_ENCRYPT_SALT,
|
|
45
47
|
} = require('@abtnode/constant');
|
|
46
48
|
const { BLOCKLET_THEME_LIGHT, BLOCKLET_THEME_DARK } = require('@blocklet/theme');
|
|
47
49
|
const {
|
|
@@ -527,6 +529,23 @@ const getRuntimeEnvironments = (blocklet, nodeEnvironments, ancestors) => {
|
|
|
527
529
|
...safeNodeEnvironments,
|
|
528
530
|
};
|
|
529
531
|
|
|
532
|
+
const aigne = get(root, 'settings.aigne', {});
|
|
533
|
+
const salt = root.meta.did || AIGNE_CONFIG_ENCRYPT_SALT;
|
|
534
|
+
if (!isNil(aigne) && aigne.provider) {
|
|
535
|
+
const { key, accessKeyId, secretAccessKey, provider } = aigne;
|
|
536
|
+
const selectedModel = !aigne.model || aigne.model === 'auto' ? undefined : aigne.model;
|
|
537
|
+
env.BLOCKLET_AIGNE_API_MODEL = selectedModel;
|
|
538
|
+
env.BLOCKLET_AIGNE_API_PROVIDER = aigne.provider;
|
|
539
|
+
const credential = {
|
|
540
|
+
apiKey: key ? decrypt(key, salt, '') : key || '',
|
|
541
|
+
accessKeyId: accessKeyId && provider === 'bedrock' ? decrypt(accessKeyId, salt, '') : accessKeyId || '',
|
|
542
|
+
secretAccessKey:
|
|
543
|
+
secretAccessKey && provider === 'bedrock' ? decrypt(secretAccessKey, salt, '') : secretAccessKey || '',
|
|
544
|
+
};
|
|
545
|
+
env.BLOCKLET_AIGNE_API_CREDENTIAL = JSON.stringify(credential);
|
|
546
|
+
env.BLOCKLET_AIGNE_API_URL = aigne.url || '';
|
|
547
|
+
}
|
|
548
|
+
|
|
530
549
|
if (root?.environmentObj?.BLOCKLET_APP_DATA_DIR) {
|
|
531
550
|
env.BLOCKLET_APP_SHARE_DIR = path.join(root.environmentObj.BLOCKLET_APP_DATA_DIR, '.share');
|
|
532
551
|
env.BLOCKLET_SHARE_DIR = path.join(root.environmentObj.BLOCKLET_APP_DATA_DIR, '.share', blocklet.meta.did);
|
package/lib/util/check-dns.js
CHANGED
|
@@ -1,26 +1,53 @@
|
|
|
1
|
-
const dns = require('dns');
|
|
2
|
-
const {
|
|
1
|
+
const dns = require('dns').promises;
|
|
2
|
+
const { Resolver } = require('dns').promises;
|
|
3
3
|
const logger = require('@abtnode/logger')('checkDNS');
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const
|
|
5
|
+
async function checkIsSameIp(domain1, domain2) {
|
|
6
|
+
const resolver = new Resolver();
|
|
7
|
+
resolver.setServers(['8.8.8.8']);
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const [ips1, ips2] = await Promise.all([resolver.resolve(domain1), resolver.resolve(domain2)]);
|
|
11
|
+
|
|
12
|
+
const set1 = new Set(ips1);
|
|
13
|
+
const set2 = new Set(ips2);
|
|
14
|
+
const hasIntersection = [...set1].some((ip) => set2.has(ip));
|
|
15
|
+
|
|
16
|
+
logger.info('checkIsSameIp', {
|
|
17
|
+
domain1: { domain: domain1, ips: ips1 },
|
|
18
|
+
domain2: { domain: domain2, ips: ips2 },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return hasIntersection;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
logger.error('DNS resolution error:', {
|
|
24
|
+
domain1,
|
|
25
|
+
domain2,
|
|
26
|
+
error: err.message,
|
|
27
|
+
});
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
7
31
|
|
|
8
32
|
async function checkDnsAndCname(domain, expectedCname = '') {
|
|
9
33
|
try {
|
|
10
|
-
await resolve(domain);
|
|
34
|
+
await dns.resolve(domain);
|
|
11
35
|
|
|
12
36
|
try {
|
|
13
|
-
const cnameRecords = await resolveCname(domain);
|
|
37
|
+
const cnameRecords = await dns.resolveCname(domain);
|
|
14
38
|
const isCnameMatch = cnameRecords.some((cname) => cname.toLowerCase() === expectedCname.toLowerCase());
|
|
15
39
|
|
|
16
40
|
return {
|
|
17
41
|
isDnsResolved: true,
|
|
18
42
|
hasCname: true,
|
|
19
43
|
cnameRecords,
|
|
20
|
-
isCnameMatch,
|
|
44
|
+
isCnameMatch: isCnameMatch || (await checkIsSameIp(domain, expectedCname)),
|
|
21
45
|
};
|
|
22
46
|
} catch (cnameError) {
|
|
23
|
-
logger.error('match resolve name error',
|
|
47
|
+
logger.error('match resolve name error', {
|
|
48
|
+
domain,
|
|
49
|
+
error: cnameError?.message,
|
|
50
|
+
});
|
|
24
51
|
|
|
25
52
|
return {
|
|
26
53
|
isDnsResolved: true,
|
|
@@ -31,7 +58,10 @@ async function checkDnsAndCname(domain, expectedCname = '') {
|
|
|
31
58
|
};
|
|
32
59
|
}
|
|
33
60
|
} catch (error) {
|
|
34
|
-
logger.error('resolve name error',
|
|
61
|
+
logger.error('resolve name error', {
|
|
62
|
+
domain,
|
|
63
|
+
error: error?.message,
|
|
64
|
+
});
|
|
35
65
|
|
|
36
66
|
return {
|
|
37
67
|
isDnsResolved: false,
|
|
@@ -71,6 +71,7 @@ async function _ensureDockerPostgres(dataDir, name = 'abtnode-postgres', port =
|
|
|
71
71
|
'-e POSTGRES_USER=postgres',
|
|
72
72
|
'-e POSTGRES_DB=postgres',
|
|
73
73
|
'postgres:17.5',
|
|
74
|
+
'-c max_connections=200',
|
|
74
75
|
].join(' ');
|
|
75
76
|
|
|
76
77
|
const url = `postgresql://postgres:postgres@localhost:${port}/postgres`;
|
|
@@ -2,14 +2,13 @@ const { spawn } = require('child_process');
|
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { ensureBun, getBunCacheDir } = require('./ensure-bun');
|
|
5
|
-
const checkDockerRunHistory = require('./docker/check-docker-run-history');
|
|
6
5
|
const { dockerInstallDependencies, saveLastInstallOs, isSameOs } = require('./docker/docker-install-dependenices');
|
|
7
6
|
|
|
8
7
|
function isDependencyInstalled(appDir, dependency) {
|
|
9
8
|
return fs.existsSync(path.resolve(appDir, 'node_modules', dependency));
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
async function installExternalDependencies({ appDir, forceInstall = false, nodeInfo } = {}) {
|
|
11
|
+
async function installExternalDependencies({ appDir, forceInstall = false, nodeInfo = {} } = {}) {
|
|
13
12
|
if (!appDir) {
|
|
14
13
|
throw new Error('appDir is required');
|
|
15
14
|
}
|
|
@@ -17,7 +16,7 @@ async function installExternalDependencies({ appDir, forceInstall = false, nodeI
|
|
|
17
16
|
throw new Error(`not a correct appDir directory: ${appDir}`);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
const isUseDocker =
|
|
19
|
+
const isUseDocker = nodeInfo.isDockerInstalled && nodeInfo.enableDocker;
|
|
21
20
|
|
|
22
21
|
// 读取 BLOCKLET_APP_DIR 的 package.json
|
|
23
22
|
const packageJsonPath = path.resolve(appDir, 'package.json');
|
|
@@ -10,6 +10,9 @@ const path = require('path');
|
|
|
10
10
|
|
|
11
11
|
const ignoreErrorTableNames = new Set(['runtime_insights', 'notification_receivers', 'notifications']);
|
|
12
12
|
const needCleanDataTableNames = new Set(['sessions']);
|
|
13
|
+
const notCheckPrimaryKeyTableNames = new Set(['tagging']);
|
|
14
|
+
|
|
15
|
+
const needBreakErrors = [];
|
|
13
16
|
|
|
14
17
|
function sortTableNames(tableNames, sort) {
|
|
15
18
|
return [...tableNames].sort((a, b) => {
|
|
@@ -49,10 +52,8 @@ async function migrateAllTablesNoModels(dbPath) {
|
|
|
49
52
|
// 把 tableNames 排序, 把被依赖的表放前面
|
|
50
53
|
tableNames = sortTableNames(tableNames, ['users', 'notification_receivers']);
|
|
51
54
|
|
|
52
|
-
console.log('Start migration database: ', dbPath);
|
|
53
|
-
|
|
54
55
|
for (const tableName of tableNames) {
|
|
55
|
-
console.log(`\n➡️ Starting migration for table: ${tableName}`);
|
|
56
|
+
console.log(`\n➡️ Starting migration for table: ${dbPath} ${tableName}`);
|
|
56
57
|
|
|
57
58
|
const colInfos = await sqliteDb.query(`PRAGMA TABLE_INFO("${tableName}")`, { type: QueryTypes.SELECT });
|
|
58
59
|
const sqliteSchema = {};
|
|
@@ -80,6 +81,14 @@ async function migrateAllTablesNoModels(dbPath) {
|
|
|
80
81
|
// Describe PG table to detect JSON/auto-inc
|
|
81
82
|
const pgSchema = await pgQI.describeTable(tableName);
|
|
82
83
|
|
|
84
|
+
const varcharLimits = {};
|
|
85
|
+
for (const [col, def] of Object.entries(pgSchema)) {
|
|
86
|
+
const m = def.type.match(/character varying\((\d+)\)/i);
|
|
87
|
+
if (m) {
|
|
88
|
+
varcharLimits[col] = parseInt(m[1], 10);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
83
92
|
// find JSON/JSONB
|
|
84
93
|
const jsonCols = Object.entries(pgSchema)
|
|
85
94
|
.filter(([, def]) => def.type && ['JSON', 'JSONB'].includes(def.type.toUpperCase()))
|
|
@@ -115,12 +124,22 @@ async function migrateAllTablesNoModels(dbPath) {
|
|
|
115
124
|
return jc ? `"${c}" = EXCLUDED."${c}"::${jc.type.toLowerCase()}` : `"${c}" = EXCLUDED."${c}"`;
|
|
116
125
|
})
|
|
117
126
|
.join(',');
|
|
118
|
-
|
|
127
|
+
if (notCheckPrimaryKeyTableNames.has(tableName)) {
|
|
128
|
+
console.log(' ❌ Not check primary key for, can not upsert only insert:', tableName);
|
|
129
|
+
// 只做普通插入
|
|
130
|
+
upsertSQL = `
|
|
131
|
+
INSERT INTO "${tableName}" (${insertColsList})
|
|
132
|
+
VALUES (${placeholders});
|
|
133
|
+
`;
|
|
134
|
+
} else {
|
|
135
|
+
// 正常的 upsert 逻辑
|
|
136
|
+
upsertSQL = `
|
|
119
137
|
INSERT INTO "${tableName}" (${insertColsList})
|
|
120
138
|
VALUES (${placeholders})
|
|
121
139
|
ON CONFLICT (${conflictKeys})
|
|
122
140
|
DO UPDATE SET ${updateSet};
|
|
123
141
|
`;
|
|
142
|
+
}
|
|
124
143
|
} else {
|
|
125
144
|
upsertSQL = `
|
|
126
145
|
INSERT INTO "${tableName}" (${insertColsList})
|
|
@@ -153,6 +172,18 @@ async function migrateAllTablesNoModels(dbPath) {
|
|
|
153
172
|
row.feedType = '';
|
|
154
173
|
}
|
|
155
174
|
|
|
175
|
+
// 对所有需插入的列, 操过长度的做截断
|
|
176
|
+
for (const col of insertCols) {
|
|
177
|
+
const val = row[col];
|
|
178
|
+
const limit = varcharLimits[col];
|
|
179
|
+
if (typeof val === 'string' && limit && val.length > limit) {
|
|
180
|
+
console.warn(`⚠️ Truncate "${col}" from length ${val.length} → ${limit}`);
|
|
181
|
+
console.log(' ❌ Old value:', val);
|
|
182
|
+
// 暂时不截断, 这样插入会失败, 后续会忽略这条数据的插入
|
|
183
|
+
// row[col] = val.slice(0, limit);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
156
187
|
for (const jc of jsonCols) {
|
|
157
188
|
const raw = row[jc.name];
|
|
158
189
|
let parsed = null;
|
|
@@ -265,7 +296,34 @@ async function migrateAllTablesNoModels(dbPath) {
|
|
|
265
296
|
}
|
|
266
297
|
continue;
|
|
267
298
|
}
|
|
268
|
-
|
|
299
|
+
if (err.message.includes('user_sessions_userDid_fkey')) {
|
|
300
|
+
const violationFilePath = dbPath.replace('.db', '.user_sessions_userDid_fkey.json');
|
|
301
|
+
console.log(
|
|
302
|
+
' ❌ Ignore error for user_sessions_userDid_fkey',
|
|
303
|
+
err.message,
|
|
304
|
+
'save to:',
|
|
305
|
+
violationFilePath,
|
|
306
|
+
'count: '
|
|
307
|
+
);
|
|
308
|
+
try {
|
|
309
|
+
await fsp.appendFile(
|
|
310
|
+
violationFilePath,
|
|
311
|
+
// eslint-disable-next-line prefer-template
|
|
312
|
+
JSON.stringify({ table: tableName, row, error: 'user_sessions_userDid_fkey' }) + '\n',
|
|
313
|
+
'utf8'
|
|
314
|
+
);
|
|
315
|
+
} catch (writeErr) {
|
|
316
|
+
console.warn(' ⚠️ Failed to write violation record:', writeErr);
|
|
317
|
+
}
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (needBreakErrors.length < 2000) {
|
|
322
|
+
console.error(' ❌ Error:', err.message);
|
|
323
|
+
needBreakErrors.push(err.message);
|
|
324
|
+
} else {
|
|
325
|
+
throw err;
|
|
326
|
+
}
|
|
269
327
|
}
|
|
270
328
|
}
|
|
271
329
|
|
|
@@ -418,6 +476,12 @@ async function migrationSqliteToPostgres(dataDir, dbPaths) {
|
|
|
418
476
|
await validateTableRowCounts(dbPath);
|
|
419
477
|
}
|
|
420
478
|
|
|
479
|
+
if (needBreakErrors.length > 0) {
|
|
480
|
+
console.error(' ❌ Has some errors, please check the violation files');
|
|
481
|
+
console.error(needBreakErrors.join('\n'));
|
|
482
|
+
throw new Error('Has some errors, please check the error log');
|
|
483
|
+
}
|
|
484
|
+
|
|
421
485
|
savePostgresLock(dataDir);
|
|
422
486
|
}
|
|
423
487
|
|
package/lib/validators/util.js
CHANGED
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.16.47-beta-
|
|
6
|
+
"version": "1.16.47-beta-20250723-114212-8da08071",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,45 +19,46 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "Apache-2.0",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/analytics": "1.16.47-beta-
|
|
23
|
-
"@abtnode/auth": "1.16.47-beta-
|
|
24
|
-
"@abtnode/certificate-manager": "1.16.47-beta-
|
|
25
|
-
"@abtnode/client": "1.16.47-beta-
|
|
26
|
-
"@abtnode/constant": "1.16.47-beta-
|
|
27
|
-
"@abtnode/cron": "1.16.47-beta-
|
|
28
|
-
"@abtnode/db-cache": "1.16.47-beta-
|
|
29
|
-
"@abtnode/docker-utils": "1.16.47-beta-
|
|
30
|
-
"@abtnode/logger": "1.16.47-beta-
|
|
31
|
-
"@abtnode/models": "1.16.47-beta-
|
|
32
|
-
"@abtnode/queue": "1.16.47-beta-
|
|
33
|
-
"@abtnode/rbac": "1.16.47-beta-
|
|
34
|
-
"@abtnode/router-provider": "1.16.47-beta-
|
|
35
|
-
"@abtnode/static-server": "1.16.47-beta-
|
|
36
|
-
"@abtnode/timemachine": "1.16.47-beta-
|
|
37
|
-
"@abtnode/util": "1.16.47-beta-
|
|
38
|
-
"@
|
|
39
|
-
"@arcblock/did
|
|
40
|
-
"@arcblock/did-
|
|
22
|
+
"@abtnode/analytics": "1.16.47-beta-20250723-114212-8da08071",
|
|
23
|
+
"@abtnode/auth": "1.16.47-beta-20250723-114212-8da08071",
|
|
24
|
+
"@abtnode/certificate-manager": "1.16.47-beta-20250723-114212-8da08071",
|
|
25
|
+
"@abtnode/client": "1.16.47-beta-20250723-114212-8da08071",
|
|
26
|
+
"@abtnode/constant": "1.16.47-beta-20250723-114212-8da08071",
|
|
27
|
+
"@abtnode/cron": "1.16.47-beta-20250723-114212-8da08071",
|
|
28
|
+
"@abtnode/db-cache": "1.16.47-beta-20250723-114212-8da08071",
|
|
29
|
+
"@abtnode/docker-utils": "1.16.47-beta-20250723-114212-8da08071",
|
|
30
|
+
"@abtnode/logger": "1.16.47-beta-20250723-114212-8da08071",
|
|
31
|
+
"@abtnode/models": "1.16.47-beta-20250723-114212-8da08071",
|
|
32
|
+
"@abtnode/queue": "1.16.47-beta-20250723-114212-8da08071",
|
|
33
|
+
"@abtnode/rbac": "1.16.47-beta-20250723-114212-8da08071",
|
|
34
|
+
"@abtnode/router-provider": "1.16.47-beta-20250723-114212-8da08071",
|
|
35
|
+
"@abtnode/static-server": "1.16.47-beta-20250723-114212-8da08071",
|
|
36
|
+
"@abtnode/timemachine": "1.16.47-beta-20250723-114212-8da08071",
|
|
37
|
+
"@abtnode/util": "1.16.47-beta-20250723-114212-8da08071",
|
|
38
|
+
"@aigne/aigne-hub": "^0.1.3",
|
|
39
|
+
"@arcblock/did": "1.21.0",
|
|
40
|
+
"@arcblock/did-auth": "1.21.0",
|
|
41
|
+
"@arcblock/did-ext": "1.21.0",
|
|
41
42
|
"@arcblock/did-motif": "^1.1.14",
|
|
42
|
-
"@arcblock/did-util": "1.
|
|
43
|
-
"@arcblock/event-hub": "1.
|
|
44
|
-
"@arcblock/jwt": "1.
|
|
43
|
+
"@arcblock/did-util": "1.21.0",
|
|
44
|
+
"@arcblock/event-hub": "1.21.0",
|
|
45
|
+
"@arcblock/jwt": "1.21.0",
|
|
45
46
|
"@arcblock/pm2-events": "^0.0.5",
|
|
46
|
-
"@arcblock/validator": "1.
|
|
47
|
-
"@arcblock/vc": "1.
|
|
48
|
-
"@blocklet/constant": "1.16.47-beta-
|
|
49
|
-
"@blocklet/did-space-js": "^1.1.
|
|
50
|
-
"@blocklet/env": "1.16.47-beta-
|
|
47
|
+
"@arcblock/validator": "1.21.0",
|
|
48
|
+
"@arcblock/vc": "1.21.0",
|
|
49
|
+
"@blocklet/constant": "1.16.47-beta-20250723-114212-8da08071",
|
|
50
|
+
"@blocklet/did-space-js": "^1.1.8",
|
|
51
|
+
"@blocklet/env": "1.16.47-beta-20250723-114212-8da08071",
|
|
51
52
|
"@blocklet/error": "^0.2.5",
|
|
52
|
-
"@blocklet/meta": "1.16.47-beta-
|
|
53
|
-
"@blocklet/resolver": "1.16.47-beta-
|
|
54
|
-
"@blocklet/sdk": "1.16.47-beta-
|
|
55
|
-
"@blocklet/store": "1.16.47-beta-
|
|
56
|
-
"@blocklet/theme": "^3.0.
|
|
53
|
+
"@blocklet/meta": "1.16.47-beta-20250723-114212-8da08071",
|
|
54
|
+
"@blocklet/resolver": "1.16.47-beta-20250723-114212-8da08071",
|
|
55
|
+
"@blocklet/sdk": "1.16.47-beta-20250723-114212-8da08071",
|
|
56
|
+
"@blocklet/store": "1.16.47-beta-20250723-114212-8da08071",
|
|
57
|
+
"@blocklet/theme": "^3.0.30",
|
|
57
58
|
"@fidm/x509": "^1.2.1",
|
|
58
|
-
"@ocap/mcrypto": "1.
|
|
59
|
-
"@ocap/util": "1.
|
|
60
|
-
"@ocap/wallet": "1.
|
|
59
|
+
"@ocap/mcrypto": "1.21.0",
|
|
60
|
+
"@ocap/util": "1.21.0",
|
|
61
|
+
"@ocap/wallet": "1.21.0",
|
|
61
62
|
"@slack/webhook": "^5.0.4",
|
|
62
63
|
"archiver": "^7.0.1",
|
|
63
64
|
"axios": "^1.7.9",
|
|
@@ -105,7 +106,7 @@
|
|
|
105
106
|
"transliteration": "^2.3.5",
|
|
106
107
|
"ua-parser-js": "^1.0.2",
|
|
107
108
|
"ufo": "^1.5.3",
|
|
108
|
-
"uuid": "^
|
|
109
|
+
"uuid": "^11.1.0",
|
|
109
110
|
"valid-url": "^1.0.9",
|
|
110
111
|
"which": "^2.0.2",
|
|
111
112
|
"xbytes": "^1.8.0"
|
|
@@ -116,5 +117,5 @@
|
|
|
116
117
|
"jest": "^29.7.0",
|
|
117
118
|
"unzipper": "^0.10.11"
|
|
118
119
|
},
|
|
119
|
-
"gitHead": "
|
|
120
|
+
"gitHead": "39a8ac5b8ca38755da47fd6f895d4fb0e19c0093"
|
|
120
121
|
}
|