@abtnode/util 1.17.6-beta-20251216-223535-283b9ffe → 1.17.6-beta-20251217-144034-62fafb94

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.
Files changed (2) hide show
  1. package/lib/did-document.js +200 -10
  2. package/package.json +7 -7
@@ -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 ({ id, services, didRegistryUrl, wallet, alsoKnownAs = [], blockletServerVersion }) => {
24
- debug('update did document', { didRegistryUrl });
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 updateWithRetry = (...args) =>
79
- pRetry(() => update(...args), {
80
- retries: getRetryCount(),
81
- onFailedAttempt: async (error) => {
82
- debug('update did document failed', error);
83
- await sleep(10 * 1000);
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
- return updateWithRetry({ id: appPid, services, didRegistryUrl, alsoKnownAs, wallet, blockletServerVersion });
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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.17.6-beta-20251216-223535-283b9ffe",
6
+ "version": "1.17.6-beta-20251217-144034-62fafb94",
7
7
  "description": "ArcBlock's JavaScript utility",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -18,14 +18,14 @@
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-20251216-223535-283b9ffe",
22
- "@abtnode/db-cache": "1.17.6-beta-20251216-223535-283b9ffe",
21
+ "@abtnode/constant": "1.17.6-beta-20251217-144034-62fafb94",
22
+ "@abtnode/db-cache": "1.17.6-beta-20251217-144034-62fafb94",
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-20251216-223535-283b9ffe",
26
+ "@blocklet/constant": "1.17.6-beta-20251217-144034-62fafb94",
27
27
  "@blocklet/error": "^0.3.5",
28
- "@blocklet/meta": "1.17.6-beta-20251216-223535-283b9ffe",
28
+ "@blocklet/meta": "1.17.6-beta-20251217-144034-62fafb94",
29
29
  "@blocklet/xss": "^0.3.13",
30
30
  "@ocap/client": "^1.27.15",
31
31
  "@ocap/mcrypto": "^1.27.15",
@@ -79,7 +79,7 @@
79
79
  "through2-filter": "^3.0.0",
80
80
  "through2-map": "^3.0.0",
81
81
  "to-semver": "^3.0.0",
82
- "transliteration": "^2.3.5",
82
+ "transliteration": "2.3.5",
83
83
  "ufo": "^1.5.3",
84
84
  "which": "^2.0.2"
85
85
  },
@@ -90,5 +90,5 @@
90
90
  "express": "^4.18.2",
91
91
  "fs-extra": "^11.2.0"
92
92
  },
93
- "gitHead": "00f2aa2d19ce17e3e15b8ab7a200830f633e9030"
93
+ "gitHead": "9f47bf4e66af87c562f846e5470f45491506ab36"
94
94
  }