@abtnode/util 1.17.6-beta-20251217-011553-818fcc8e → 1.17.6-beta-20251218-120326-5b44dadf
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/did-document.js +200 -10
- package/lib/security.js +4 -4
- package/package.json +7 -7
package/lib/did-document.js
CHANGED
|
@@ -3,6 +3,7 @@ const { toBase64, toBase58, toDid } = require('@ocap/util');
|
|
|
3
3
|
const { joinURL } = require('ufo');
|
|
4
4
|
const pRetry = require('p-retry');
|
|
5
5
|
const debug = require('debug')('@abtnode/util:did-document');
|
|
6
|
+
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
6
7
|
|
|
7
8
|
const sleep = require('./sleep');
|
|
8
9
|
const axios = require('./axios');
|
|
@@ -20,8 +21,17 @@ const getDID = (address) => {
|
|
|
20
21
|
return `did:abt:${address}`;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
const update = async ({
|
|
24
|
-
|
|
24
|
+
const update = async ({
|
|
25
|
+
id,
|
|
26
|
+
services,
|
|
27
|
+
didRegistryUrl,
|
|
28
|
+
wallet,
|
|
29
|
+
alsoKnownAs = [],
|
|
30
|
+
blockletServerVersion,
|
|
31
|
+
name,
|
|
32
|
+
capabilities,
|
|
33
|
+
}) => {
|
|
34
|
+
debug('starting update did document', { didRegistryUrl });
|
|
25
35
|
|
|
26
36
|
const did = getDID(wallet.address);
|
|
27
37
|
const time = new Date().toISOString();
|
|
@@ -45,6 +55,14 @@ const update = async ({ id, services, didRegistryUrl, wallet, alsoKnownAs = [],
|
|
|
45
55
|
updated: time,
|
|
46
56
|
};
|
|
47
57
|
|
|
58
|
+
if (name) {
|
|
59
|
+
document.name = name;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (capabilities) {
|
|
63
|
+
document.capabilities = capabilities;
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
const proof = {
|
|
49
67
|
type: 'Ed25519Signature',
|
|
50
68
|
created: time,
|
|
@@ -63,6 +81,8 @@ const update = async ({ id, services, didRegistryUrl, wallet, alsoKnownAs = [],
|
|
|
63
81
|
};
|
|
64
82
|
|
|
65
83
|
const DEFAULT_RETRY_COUNT = 6;
|
|
84
|
+
const DEFAULT_DEBOUNCE_TIME = 3000;
|
|
85
|
+
|
|
66
86
|
const getRetryCount = () => {
|
|
67
87
|
try {
|
|
68
88
|
let retryCount = parseInt(process.env.ABT_NODE_UPDATE_DID_DOCUMENT_RETRY_COUNT, 10);
|
|
@@ -75,14 +95,60 @@ const getRetryCount = () => {
|
|
|
75
95
|
}
|
|
76
96
|
};
|
|
77
97
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
98
|
+
const getDebounceTime = () => {
|
|
99
|
+
try {
|
|
100
|
+
let debounceTime = parseInt(process.env.ABT_NODE_DID_DOCUMENT_DEBOUNCE_TIME, 10);
|
|
101
|
+
debounceTime = Number.isNaN(debounceTime) ? DEFAULT_DEBOUNCE_TIME : debounceTime;
|
|
102
|
+
return debounceTime;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return DEFAULT_DEBOUNCE_TIME;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const pendingUpdates = new Map();
|
|
109
|
+
const updateTimers = new Map();
|
|
110
|
+
|
|
111
|
+
const updateWithRetry = (...args) => {
|
|
112
|
+
const params = args[0];
|
|
113
|
+
const did = toDid(params.id);
|
|
114
|
+
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
if (updateTimers.has(did)) {
|
|
117
|
+
clearTimeout(updateTimers.get(did));
|
|
118
|
+
debug('cancelled pending update for did', { did });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pendingUpdates.set(did, { args, resolve, reject });
|
|
122
|
+
|
|
123
|
+
const timer = setTimeout(async () => {
|
|
124
|
+
const pending = pendingUpdates.get(did);
|
|
125
|
+
pendingUpdates.delete(did);
|
|
126
|
+
updateTimers.delete(did);
|
|
127
|
+
|
|
128
|
+
if (!pending) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
debug('executing debounced update for did', { did });
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const result = await pRetry(() => update(...pending.args), {
|
|
136
|
+
retries: getRetryCount(),
|
|
137
|
+
onFailedAttempt: async (error) => {
|
|
138
|
+
debug('update did document failed', error);
|
|
139
|
+
await sleep(10 * 1000);
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
pending.resolve(result);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
pending.reject(error);
|
|
145
|
+
}
|
|
146
|
+
}, getDebounceTime());
|
|
147
|
+
|
|
148
|
+
updateTimers.set(did, timer);
|
|
149
|
+
debug('scheduled update for did', { did, debounceTime: getDebounceTime() });
|
|
85
150
|
});
|
|
151
|
+
};
|
|
86
152
|
|
|
87
153
|
const getServerServices = ({ ips, wallet, domain }) => {
|
|
88
154
|
const records = ips.map((ip) => ({
|
|
@@ -148,6 +214,83 @@ const updateServerDocument = async ({ ips, wallet, didRegistryUrl, domain, block
|
|
|
148
214
|
return updateWithRetry({ id: getDID(wallet.address), services, didRegistryUrl, wallet, blockletServerVersion });
|
|
149
215
|
};
|
|
150
216
|
|
|
217
|
+
// Fetch existing DID document from registry
|
|
218
|
+
const getDidDocument = async ({ did, didRegistryUrl }) => {
|
|
219
|
+
const fullDid = getDID(did);
|
|
220
|
+
const resolveUrl = joinURL(didRegistryUrl, '/.well-known/did-resolver/resolve', fullDid);
|
|
221
|
+
|
|
222
|
+
debug('fetching did document', { did: fullDid, resolveUrl });
|
|
223
|
+
|
|
224
|
+
const response = await axios.get(resolveUrl, {
|
|
225
|
+
timeout: 10 * 1000,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return response.data?.didDocument || null;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Internal function to update blocklet state only
|
|
232
|
+
const updateBlockletStateOnlyInternal = async ({ did, state, didRegistryUrl, wallet, blockletServerVersion }) => {
|
|
233
|
+
if (['0', 'false', 0, false].includes(process.env.ABT_NODE_DID_DOCUMENT_UPDATE)) {
|
|
234
|
+
throw new Error('Did Document update is disabled');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
debug('updating blocklet state only', { did, state });
|
|
238
|
+
|
|
239
|
+
// Fetch current DID document from registry
|
|
240
|
+
const currentDoc = await getDidDocument({ did, didRegistryUrl });
|
|
241
|
+
|
|
242
|
+
if (!currentDoc) {
|
|
243
|
+
throw new Error(`Failed to fetch DID document for ${did} from registry`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Only update state if the path exists
|
|
247
|
+
if (currentDoc.capabilities?.blocklet?.metadata) {
|
|
248
|
+
currentDoc.capabilities.blocklet.metadata.state = state;
|
|
249
|
+
} else {
|
|
250
|
+
throw new Error(`DID document for ${did} does not have capabilities.blocklet.metadata structure`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Update timestamp
|
|
254
|
+
const time = new Date().toISOString();
|
|
255
|
+
currentDoc.updated = time;
|
|
256
|
+
|
|
257
|
+
// Remove old proof before re-signing
|
|
258
|
+
delete currentDoc.proof;
|
|
259
|
+
|
|
260
|
+
// Generate new proof with updated document
|
|
261
|
+
const controllerDid = getDID(wallet.address);
|
|
262
|
+
const proof = {
|
|
263
|
+
type: 'Ed25519Signature',
|
|
264
|
+
created: time,
|
|
265
|
+
verificationMethod: `${controllerDid}#key-1`,
|
|
266
|
+
jws: toBase64(await wallet.sign(stringify(currentDoc))),
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
currentDoc.proof = proof;
|
|
270
|
+
|
|
271
|
+
debug('update blocklet state only - sending to registry', { did, state, didRegistryUrl });
|
|
272
|
+
|
|
273
|
+
// Send directly to registry without reconstructing
|
|
274
|
+
return axios.post(joinURL(didRegistryUrl, '/.well-known/did-resolver/registries'), currentDoc, {
|
|
275
|
+
timeout: 10 * 1000,
|
|
276
|
+
headers: { 'X-Blocklet-Server-Version': blockletServerVersion },
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Update only blocklet state (e.g., to 'deleted') by fetching current document from registry with retry
|
|
281
|
+
// This function preserves the complete DID document structure and only modifies the state field
|
|
282
|
+
const updateBlockletStateOnly = (params) => {
|
|
283
|
+
debug('updateBlockletStateOnly with retry', { did: params.did });
|
|
284
|
+
|
|
285
|
+
return pRetry(() => updateBlockletStateOnlyInternal(params), {
|
|
286
|
+
retries: getRetryCount(),
|
|
287
|
+
onFailedAttempt: async (error) => {
|
|
288
|
+
debug('update blocklet state only failed', error);
|
|
289
|
+
await sleep(10 * 1000);
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
|
|
151
294
|
const updateBlockletDocument = ({
|
|
152
295
|
blocklet,
|
|
153
296
|
slpDid,
|
|
@@ -159,6 +302,11 @@ const updateBlockletDocument = ({
|
|
|
159
302
|
alsoKnownAs = [],
|
|
160
303
|
serverDid,
|
|
161
304
|
blockletServerVersion,
|
|
305
|
+
name,
|
|
306
|
+
state,
|
|
307
|
+
owner,
|
|
308
|
+
launcher,
|
|
309
|
+
domains,
|
|
162
310
|
}) => {
|
|
163
311
|
const { appPid } = blocklet;
|
|
164
312
|
if (['0', 'false', 0, false].includes(process.env.ABT_NODE_DID_DOCUMENT_UPDATE)) {
|
|
@@ -166,16 +314,58 @@ const updateBlockletDocument = ({
|
|
|
166
314
|
}
|
|
167
315
|
|
|
168
316
|
const services = getBlockletServices({ appPid, slpDid, daemonDidDomain, domain, slpDomain, serverDid });
|
|
169
|
-
|
|
317
|
+
|
|
318
|
+
const tagMap = new Set(blocklet.meta?.keywords || []);
|
|
319
|
+
tagMap.add('type:blocklet');
|
|
320
|
+
|
|
321
|
+
const metadata = {
|
|
322
|
+
name: blocklet.environments.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_NAME)?.value,
|
|
323
|
+
description: blocklet.environments.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_DESCRIPTION)?.value,
|
|
324
|
+
icon: blocklet.environments.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_LOGO)?.value,
|
|
325
|
+
tags: [],
|
|
326
|
+
state,
|
|
327
|
+
owner,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
if (launcher) {
|
|
331
|
+
tagMap.add('launcher:app');
|
|
332
|
+
metadata.launcher = launcher;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
metadata.tags = Array.from(tagMap);
|
|
336
|
+
|
|
337
|
+
const capabilities = {
|
|
338
|
+
blocklet: {
|
|
339
|
+
metadata,
|
|
340
|
+
domains,
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
debug('update blocklet document', { appPid, capabilities });
|
|
345
|
+
|
|
346
|
+
return updateWithRetry({
|
|
347
|
+
id: appPid,
|
|
348
|
+
services,
|
|
349
|
+
didRegistryUrl,
|
|
350
|
+
alsoKnownAs,
|
|
351
|
+
wallet,
|
|
352
|
+
blockletServerVersion,
|
|
353
|
+
name,
|
|
354
|
+
capabilities,
|
|
355
|
+
});
|
|
170
356
|
};
|
|
171
357
|
|
|
172
358
|
module.exports = {
|
|
173
359
|
DEFAULT_RETRY_COUNT,
|
|
360
|
+
DEFAULT_DEBOUNCE_TIME,
|
|
174
361
|
updateServerDocument,
|
|
175
362
|
updateBlockletDocument,
|
|
363
|
+
updateBlockletStateOnly,
|
|
364
|
+
getDidDocument,
|
|
176
365
|
getDID,
|
|
177
366
|
getServerServices,
|
|
178
367
|
getBlockletServices,
|
|
179
368
|
updateWithRetry,
|
|
180
369
|
getRetryCount,
|
|
370
|
+
getDebounceTime,
|
|
181
371
|
};
|
package/lib/security.js
CHANGED
|
@@ -266,7 +266,7 @@ function patchValueIntoDirectives(directives, key, value) {
|
|
|
266
266
|
return [...new Set(currentValue)];
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
async function patchResponseHeader(rawConfig, { node, blocklet, trustedDomains = [] }) {
|
|
269
|
+
async function patchResponseHeader(rawConfig, { node, blocklet, trustedDomains = [], info }) {
|
|
270
270
|
if (!rawConfig?.contentSecurityPolicy?.directives) {
|
|
271
271
|
return rawConfig;
|
|
272
272
|
}
|
|
@@ -295,7 +295,7 @@ async function patchResponseHeader(rawConfig, { node, blocklet, trustedDomains =
|
|
|
295
295
|
);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
const nodeInfo = await node.getNodeInfo({ useCache: true });
|
|
298
|
+
const nodeInfo = info || (await node.getNodeInfo({ useCache: true }));
|
|
299
299
|
// 添加 domain 的别名,需要放行 .well-known/ping 接口
|
|
300
300
|
let domainAliases = await node.getBlockletDomainAliases({ blocklet, nodeInfo });
|
|
301
301
|
domainAliases = (domainAliases || []).map((x) => x.value);
|
|
@@ -347,13 +347,13 @@ async function patchResponseHeader(rawConfig, { node, blocklet, trustedDomains =
|
|
|
347
347
|
return config;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
async function patchCors(rawConfig, { node, blocklet }) {
|
|
350
|
+
async function patchCors(rawConfig, { node, blocklet, info }) {
|
|
351
351
|
const config = cloneDeep(rawConfig);
|
|
352
352
|
if (config?.origin?.smart) {
|
|
353
353
|
// 获取当前应用自身的所有域名别名
|
|
354
354
|
const result = [];
|
|
355
355
|
result.push(...(config.origin?.value || []));
|
|
356
|
-
const nodeInfo = await node.getNodeInfo({ useCache: true });
|
|
356
|
+
const nodeInfo = info || (await node.getNodeInfo({ useCache: true }));
|
|
357
357
|
let domainAliases = await node.getBlockletDomainAliases({ blocklet, nodeInfo });
|
|
358
358
|
domainAliases = (domainAliases || []).map((x) => x.value);
|
|
359
359
|
result.push(...domainAliases.map((x) => withHttps(x)));
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.17.6-beta-
|
|
6
|
+
"version": "1.17.6-beta-20251218-120326-5b44dadf",
|
|
7
7
|
"description": "ArcBlock's JavaScript utility",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"author": "polunzh <polunzh@gmail.com> (http://github.com/polunzh)",
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@abtnode/constant": "1.17.6-beta-
|
|
22
|
-
"@abtnode/db-cache": "1.17.6-beta-
|
|
21
|
+
"@abtnode/constant": "1.17.6-beta-20251218-120326-5b44dadf",
|
|
22
|
+
"@abtnode/db-cache": "1.17.6-beta-20251218-120326-5b44dadf",
|
|
23
23
|
"@arcblock/did": "^1.27.15",
|
|
24
24
|
"@arcblock/event-hub": "^1.27.15",
|
|
25
25
|
"@arcblock/pm2": "^6.0.12",
|
|
26
|
-
"@blocklet/constant": "1.17.6-beta-
|
|
26
|
+
"@blocklet/constant": "1.17.6-beta-20251218-120326-5b44dadf",
|
|
27
27
|
"@blocklet/error": "^0.3.5",
|
|
28
|
-
"@blocklet/meta": "1.17.6-beta-
|
|
29
|
-
"@blocklet/xss": "^0.3.
|
|
28
|
+
"@blocklet/meta": "1.17.6-beta-20251218-120326-5b44dadf",
|
|
29
|
+
"@blocklet/xss": "^0.3.14",
|
|
30
30
|
"@ocap/client": "^1.27.15",
|
|
31
31
|
"@ocap/mcrypto": "^1.27.15",
|
|
32
32
|
"@ocap/util": "^1.27.15",
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"express": "^4.18.2",
|
|
91
91
|
"fs-extra": "^11.2.0"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "a07b4f70aa6dddea49ed794423b47d36dc206e53"
|
|
94
94
|
}
|