@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 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({ component, name, did, title, description, skSource, onlyRequired });
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, 'spawn');
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(/(\\*)"/g, '$1$1\\"');
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(/(\\*)$/, '$1$1');
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.20.16","@arcblock/did-auth":"1.20.16","@arcblock/did-ext":"1.20.16","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"1.20.16","@arcblock/event-hub":"1.20.16","@arcblock/jwt":"1.20.16","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.20.16","@arcblock/vc":"1.20.16","@blocklet/constant":"1.16.46","@blocklet/did-space-js":"^1.1.7","@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.26","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.20.16","@ocap/util":"1.20.16","@ocap/wallet":"1.20.16","@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":"^9.0.1","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"}');
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
  };
@@ -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('create.url.mapping', { event: name, did: blocklet.meta.did, hash });
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
 
@@ -1409,20 +1409,19 @@ module.exports = function getRouterHelpers({
1409
1409
  return ruleChanged || siteChanged;
1410
1410
  };
1411
1411
 
1412
- async function readRoutingSites() {
1413
- const info = await nodeState.read();
1414
- let snapshotHash = get(info, 'routing.snapshotHash', null);
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
- snapshotHash = await routingSnapshot.getLastSnapshot();
1416
+ hash = await routingSnapshot.getLastSnapshot();
1418
1417
  }
1419
1418
 
1420
- if (!snapshotHash) {
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(snapshotHash);
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.read();
1555
- let { sites } = await readRoutingSites();
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
- const nodeInfo = await nodeState.read();
1797
- const result = await nodeState.updateNodeRouting({ ...nodeInfo.routing, snapshotHash: hash });
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.debug('takeRoutingSnapshot', { dryRun, result, handleRouting: handle });
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;
@@ -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() {
@@ -16,7 +16,7 @@ const {
16
16
  DEFAULT_NFT_DOMAIN_URL,
17
17
  SERVER_CACHE_TTL,
18
18
  } = require('@abtnode/constant');
19
- // const logger = require('@abtnode/logger')('@abtnode/core:states:node');
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
- await this.cache.del(this.cacheGroup);
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
+ };
@@ -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);
@@ -1,26 +1,53 @@
1
- const dns = require('dns');
2
- const { promisify } = require('util');
1
+ const dns = require('dns').promises;
2
+ const { Resolver } = require('dns').promises;
3
3
  const logger = require('@abtnode/logger')('checkDNS');
4
4
 
5
- const resolveCname = promisify(dns.resolveCname);
6
- const resolve = promisify(dns.resolve);
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', cnameError?.message);
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', error?.message);
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 = checkDockerRunHistory(nodeInfo);
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
- upsertSQL = `
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
- throw err;
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
 
@@ -73,6 +73,7 @@ const sessionConfigSchema = Joi.object({
73
73
  .optional()
74
74
  .default([]),
75
75
  }),
76
+ enableBlacklist: Joi.boolean().default(false),
76
77
  });
77
78
 
78
79
  const passportDisplaySchema = Joi.object({
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.47-beta-20250721-073316-a6de1fa3",
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-20250721-073316-a6de1fa3",
23
- "@abtnode/auth": "1.16.47-beta-20250721-073316-a6de1fa3",
24
- "@abtnode/certificate-manager": "1.16.47-beta-20250721-073316-a6de1fa3",
25
- "@abtnode/client": "1.16.47-beta-20250721-073316-a6de1fa3",
26
- "@abtnode/constant": "1.16.47-beta-20250721-073316-a6de1fa3",
27
- "@abtnode/cron": "1.16.47-beta-20250721-073316-a6de1fa3",
28
- "@abtnode/db-cache": "1.16.47-beta-20250721-073316-a6de1fa3",
29
- "@abtnode/docker-utils": "1.16.47-beta-20250721-073316-a6de1fa3",
30
- "@abtnode/logger": "1.16.47-beta-20250721-073316-a6de1fa3",
31
- "@abtnode/models": "1.16.47-beta-20250721-073316-a6de1fa3",
32
- "@abtnode/queue": "1.16.47-beta-20250721-073316-a6de1fa3",
33
- "@abtnode/rbac": "1.16.47-beta-20250721-073316-a6de1fa3",
34
- "@abtnode/router-provider": "1.16.47-beta-20250721-073316-a6de1fa3",
35
- "@abtnode/static-server": "1.16.47-beta-20250721-073316-a6de1fa3",
36
- "@abtnode/timemachine": "1.16.47-beta-20250721-073316-a6de1fa3",
37
- "@abtnode/util": "1.16.47-beta-20250721-073316-a6de1fa3",
38
- "@arcblock/did": "1.20.16",
39
- "@arcblock/did-auth": "1.20.16",
40
- "@arcblock/did-ext": "1.20.16",
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.20.16",
43
- "@arcblock/event-hub": "1.20.16",
44
- "@arcblock/jwt": "1.20.16",
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.20.16",
47
- "@arcblock/vc": "1.20.16",
48
- "@blocklet/constant": "1.16.47-beta-20250721-073316-a6de1fa3",
49
- "@blocklet/did-space-js": "^1.1.7",
50
- "@blocklet/env": "1.16.47-beta-20250721-073316-a6de1fa3",
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-20250721-073316-a6de1fa3",
53
- "@blocklet/resolver": "1.16.47-beta-20250721-073316-a6de1fa3",
54
- "@blocklet/sdk": "1.16.47-beta-20250721-073316-a6de1fa3",
55
- "@blocklet/store": "1.16.47-beta-20250721-073316-a6de1fa3",
56
- "@blocklet/theme": "^3.0.26",
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.20.16",
59
- "@ocap/util": "1.20.16",
60
- "@ocap/wallet": "1.20.16",
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": "^9.0.1",
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": "2163d72cfb533e6dbcedd7082c45d7f3dbe3cd90"
120
+ "gitHead": "39a8ac5b8ca38755da47fd6f895d4fb0e19c0093"
120
121
  }