@abtnode/util 1.16.43-beta-20250422-042711-c40bec75 → 1.16.43-beta-20250425-130658-8da18f4d

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.
@@ -0,0 +1,90 @@
1
+ const { fromPublicKey } = require('@ocap/wallet');
2
+ const { fromBase58 } = require('@ocap/util');
3
+ const { isSystemRole } = require('@abtnode/constant');
4
+
5
+ const checkAccessKeySource = async ({ node, keyId, info, blockletDid }) => {
6
+ let teamDid = info.did;
7
+ let accessKey = await node.getAccessKey({ teamDid, accessKeyId: keyId }).catch(() => null);
8
+ let isFromBlocklet;
9
+
10
+ if (blockletDid && !accessKey) {
11
+ isFromBlocklet = true;
12
+ teamDid = blockletDid;
13
+ accessKey = await node.getAccessKey({ teamDid, accessKeyId: keyId }).catch(() => null);
14
+ }
15
+
16
+ return { teamDid, isFromBlocklet, accessKey };
17
+ };
18
+
19
+ const isLoginToken = (token) => {
20
+ return !!token && typeof token === 'string' && token.split('.').length === 3;
21
+ };
22
+
23
+ const isAccessKey = (token) => {
24
+ return !!token && typeof token === 'string' && token.split('.').length === 1 && token.startsWith('blocklet-');
25
+ };
26
+
27
+ const authBySimpleAccessKey = async (token, node, blockletDid = '') => {
28
+ const info = await node.getNodeInfo({ useCache: true });
29
+
30
+ const secret = token.replace('blocklet-', '');
31
+
32
+ let publicKey = '';
33
+ try {
34
+ publicKey = fromBase58(secret);
35
+ } catch (error) {
36
+ publicKey = '';
37
+ }
38
+
39
+ if (!publicKey) {
40
+ throw new Error('Invalid access key secret');
41
+ }
42
+
43
+ const wallet = fromPublicKey(publicKey);
44
+ if (!wallet) {
45
+ throw new Error('Invalid access key secret');
46
+ }
47
+
48
+ const accessKeyId = wallet.address;
49
+
50
+ const { teamDid, isFromBlocklet, accessKey } = await checkAccessKeySource({
51
+ node,
52
+ keyId: accessKeyId,
53
+ info,
54
+ blockletDid,
55
+ });
56
+
57
+ const { passport, remark, expireAt, authType } = accessKey;
58
+
59
+ if (authType !== 'simple') {
60
+ throw new Error(`Access Key ${accessKeyId} is not a simple type`);
61
+ }
62
+
63
+ if (expireAt && new Date(expireAt).getTime() < new Date().getTime()) {
64
+ throw new Error(`Access Key ${accessKeyId} has expired`);
65
+ }
66
+
67
+ if (!accessKey.createdBy) {
68
+ throw new Error(`Access Key ${accessKeyId} is not created by a user`);
69
+ }
70
+
71
+ await node.refreshLastUsed({ teamDid, accessKeyId });
72
+
73
+ const role = passport;
74
+ const blockletRole = isSystemRole(role) ? `blocklet-${role}` : role;
75
+
76
+ return {
77
+ did: accessKey.createdBy,
78
+ role: isFromBlocklet ? blockletRole : role,
79
+ elevated: true,
80
+ blockletDid: teamDid,
81
+ fullName: remark || accessKeyId,
82
+ };
83
+ };
84
+
85
+ module.exports = {
86
+ checkAccessKeySource,
87
+ authBySimpleAccessKey,
88
+ isLoginToken,
89
+ isAccessKey,
90
+ };
@@ -0,0 +1,57 @@
1
+ const { minimatch } = require('minimatch');
2
+
3
+ const checkMatch = (file, match) => {
4
+ let m = match;
5
+ if (!m.includes('*')) {
6
+ return false;
7
+ }
8
+ if (!m.endsWith('*')) {
9
+ return minimatch(file, m);
10
+ }
11
+ if (!m.endsWith('**')) {
12
+ m = `${m}*`;
13
+ }
14
+
15
+ return minimatch(file, m);
16
+ };
17
+
18
+ const checkInclude = (list, f) =>
19
+ f === '.' ||
20
+ f === '' || // root folder maybe '.' and ''
21
+ list.some(
22
+ (x) =>
23
+ x.indexOf(f) === 0 || // f is 'website' && x is 'website/public'
24
+ f.indexOf(x) === 0 || // f is website/public/index.html && x is website/public
25
+ checkMatch(f, x) // f is hooks/pre-start.js && x is hooks/*
26
+ );
27
+
28
+ /**
29
+ * Whether a file should be deployed
30
+ * @param {string} file a file will be deployed
31
+ * @param {object} opts
32
+ * @param {array<string>} opts.diffList
33
+ * @param {BundleType} opts.bundleType
34
+ * @param {array<string>} opts.staticList
35
+ * @return {boolean} Should this file be deployed
36
+ */
37
+ const fileFilter = (file, opts = {}) => {
38
+ const { diffList, bundleType, staticList } = opts;
39
+ // only include diffList if has one
40
+ if (diffList) {
41
+ return checkInclude(diffList, file);
42
+ }
43
+ // only include staticList if bundleType is static
44
+ if (bundleType === 'static') {
45
+ if (!staticList) {
46
+ throw new Error('staticList should not be empty when bundleType is static');
47
+ }
48
+ return checkInclude(staticList, file);
49
+ }
50
+ // include all files
51
+ return true;
52
+ };
53
+
54
+ module.exports = {
55
+ fileFilter,
56
+ checkMatch,
57
+ };
@@ -0,0 +1,59 @@
1
+ const { joinURL } = require('ufo');
2
+ const { default: axios } = require('axios');
3
+ const { WELLKNOWN_DID_RESOLVER_PREFIX } = require('@abtnode/constant');
4
+ const { getDidDomainForBlocklet } = require('./get-domain-for-blocklet');
5
+
6
+ const BLOCKLET_JSON_PATH = '__blocklet__.js?type=json';
7
+
8
+ const ensureServerEndpoint = async (endpoint) => {
9
+ let serverBaseUrl;
10
+ let appPid;
11
+ let appName;
12
+ let appDescription;
13
+
14
+ const jsonPathUrl = joinURL(new URL(endpoint).origin, BLOCKLET_JSON_PATH);
15
+
16
+ const checkEndpoint = await axios(jsonPathUrl).catch(() => null);
17
+ const contentType = checkEndpoint && checkEndpoint.headers['content-type'];
18
+ if (contentType?.includes('application/json')) {
19
+ const url = getDidDomainForBlocklet({ did: checkEndpoint.data.serverDid });
20
+ // service endpoint
21
+ serverBaseUrl = new URL(`https://${url}`).origin;
22
+ appPid = checkEndpoint.data.appPid;
23
+ appName = checkEndpoint.data.appName;
24
+ appDescription = checkEndpoint.data.appDescription;
25
+ } else {
26
+ // maybe server endpoint
27
+ serverBaseUrl = new URL(endpoint).origin;
28
+ appPid = '';
29
+ }
30
+
31
+ if (!serverBaseUrl) {
32
+ throw new Error('Invalid endpoint');
33
+ }
34
+
35
+ // local debug
36
+ if (serverBaseUrl.includes('localhost:3000')) {
37
+ return {
38
+ endpoint: serverBaseUrl,
39
+ appPid,
40
+ };
41
+ }
42
+
43
+ const didJsonUrl = joinURL(serverBaseUrl, WELLKNOWN_DID_RESOLVER_PREFIX);
44
+ const didJson = await axios(didJsonUrl);
45
+
46
+ const didJsonContentType = didJson.headers['content-type'];
47
+ if (!didJsonContentType.includes('application/json')) {
48
+ throw new Error('Invalid endpoint');
49
+ }
50
+
51
+ return {
52
+ endpoint: joinURL(serverBaseUrl, didJson?.data?.services?.[0]?.path),
53
+ appPid,
54
+ appName,
55
+ appDescription,
56
+ };
57
+ };
58
+
59
+ module.exports = ensureServerEndpoint;
package/lib/get-origin.js CHANGED
@@ -2,11 +2,11 @@ const defaultLogger = {
2
2
  error: console.error,
3
3
  };
4
4
 
5
- async function getOrigin({ req }, { printError = defaultLogger.error } = {}) {
5
+ async function getOrigin({ req, blockletInfo }, { printError = defaultLogger.error } = {}) {
6
6
  try {
7
7
  if (req.getBlockletInfo) {
8
- const blockletInfo = await req.getBlockletInfo();
9
- return blockletInfo.appUrl;
8
+ const _blockletInfo = blockletInfo || (await req.getBlockletInfo());
9
+ return _blockletInfo.appUrl;
10
10
  }
11
11
  const host = req.get('host');
12
12
  return `https://${host}`;
@@ -0,0 +1,62 @@
1
+ const fs = require('fs');
2
+ const FormData = require('form-data');
3
+
4
+ const makeFormData = ({ tarFile: file, hasDiff, did, serverVersion, deleteSet, rootDid, mountPoint }) => {
5
+ let varFields = hasDiff
6
+ ? '$file: Upload!, $did: String, $diffVersion: String, $deleteSet: [String!]'
7
+ : '$file: Upload!';
8
+ let inputFields = hasDiff
9
+ ? 'file: $file, did: $did, diffVersion: $diffVersion, deleteSet: $deleteSet'
10
+ : 'file: $file';
11
+
12
+ varFields = `${varFields}, $rootDid: String, $mountPoint: String`;
13
+ inputFields = `${inputFields}, rootDid: $rootDid, mountPoint: $mountPoint`;
14
+
15
+ const variables = hasDiff
16
+ ? {
17
+ file: null,
18
+ did,
19
+ diffVersion: serverVersion,
20
+ deleteSet,
21
+ }
22
+ : {
23
+ file: null,
24
+ };
25
+
26
+ variables.rootDid = rootDid;
27
+ variables.mountPoint = mountPoint;
28
+
29
+ const apiName = 'installComponent';
30
+ const query = `
31
+ mutation (${varFields}) {
32
+ ${apiName}(input: { ${inputFields} } ) {
33
+ code
34
+ blocklet {
35
+ meta {
36
+ did
37
+ name
38
+ title
39
+ version
40
+ description
41
+ }
42
+ status
43
+ source
44
+ }
45
+ }
46
+ }
47
+ `;
48
+ const gql = {
49
+ query,
50
+ variables,
51
+ };
52
+ const map = {
53
+ file0: ['variables.file'],
54
+ };
55
+ const form = new FormData();
56
+ form.append('operations', JSON.stringify(gql));
57
+ form.append('map', JSON.stringify(map));
58
+ form.append('file0', fs.createReadStream(file));
59
+ return { form, apiName };
60
+ };
61
+
62
+ module.exports = makeFormData;
@@ -0,0 +1,80 @@
1
+ const { LRUCache } = require('lru-cache');
2
+ const pWaitFor = require('p-wait-for');
3
+
4
+ const PADDING_VALUE = '__padding__';
5
+
6
+ // Single flight lru cache
7
+ // ref: https://www.codingexplorations.com/blog/understanding-singleflight-in-golang-a-solution-for-eliminating-redundant-work
8
+ class SingleFlightLRUCache extends LRUCache {
9
+ paddingInterval = 100;
10
+
11
+ paddingTimeout = 10 * 1000;
12
+
13
+ setPadding(key) {
14
+ this.set(key, PADDING_VALUE);
15
+ }
16
+
17
+ isPadding(key) {
18
+ return this.get(key) === PADDING_VALUE;
19
+ }
20
+
21
+ async waitPending(key) {
22
+ if (this.get(key) === PADDING_VALUE) {
23
+ try {
24
+ await pWaitFor(() => this.get(key) !== PADDING_VALUE, {
25
+ interval: this.paddingInterval,
26
+ timeout: this.paddingTimeout,
27
+ });
28
+ } catch (_) {
29
+ if (this.get(key) === PADDING_VALUE) {
30
+ this.delete(key);
31
+ }
32
+ }
33
+ }
34
+ return this.get(key);
35
+ }
36
+
37
+ async wrapPending(key, fn) {
38
+ if (!key) {
39
+ return Promise.resolve(fn());
40
+ }
41
+ const old = this.get(key);
42
+ if (old) {
43
+ if (old === PADDING_VALUE) {
44
+ const result = await this.waitPending(key);
45
+ if (result && result !== PADDING_VALUE) {
46
+ return result;
47
+ }
48
+ } else {
49
+ return old;
50
+ }
51
+ }
52
+ this.setPadding(key);
53
+ try {
54
+ const result = await Promise.resolve(fn());
55
+ return result;
56
+ } finally {
57
+ if (this.get(key) === PADDING_VALUE) {
58
+ this.delete(key);
59
+ }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * autoCache will set the result to the cache if the key is not null
65
+ * if the key is null, it will return the result of the function
66
+ * if the key is not null, it will return the cached result
67
+ * if padding, it will wait for the padding to finish
68
+ * if the key is not in the cache, it will set the result to the cache
69
+ * if the key is in the cache, it will return the cached result
70
+ */
71
+ async autoCache(key, fn) {
72
+ const result = await this.wrapPending(key, fn);
73
+ if (key) {
74
+ this.set(key, result);
75
+ }
76
+ return result;
77
+ }
78
+ }
79
+
80
+ module.exports = SingleFlightLRUCache;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.43-beta-20250422-042711-c40bec75",
6
+ "version": "1.16.43-beta-20250425-130658-8da18f4d",
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.16.43-beta-20250422-042711-c40bec75",
22
- "@arcblock/did": "^1.20.1",
21
+ "@abtnode/constant": "1.16.43-beta-20250425-130658-8da18f4d",
22
+ "@arcblock/did": "^1.20.2",
23
23
  "@arcblock/pm2": "^5.4.0",
24
- "@blocklet/constant": "1.16.43-beta-20250422-042711-c40bec75",
25
- "@blocklet/meta": "1.16.43-beta-20250422-042711-c40bec75",
26
- "@ocap/client": "1.20.1",
27
- "@ocap/mcrypto": "1.20.1",
28
- "@ocap/util": "1.20.1",
29
- "@ocap/wallet": "1.20.1",
24
+ "@blocklet/constant": "1.16.43-beta-20250425-130658-8da18f4d",
25
+ "@blocklet/meta": "1.16.43-beta-20250425-130658-8da18f4d",
26
+ "@ocap/client": "1.20.2",
27
+ "@ocap/mcrypto": "1.20.2",
28
+ "@ocap/util": "1.20.2",
29
+ "@ocap/wallet": "1.20.2",
30
30
  "archiver": "^7.0.1",
31
31
  "axios": "^1.7.9",
32
32
  "axios-mock-adapter": "^2.1.0",
@@ -43,6 +43,7 @@
43
43
  "fkill": "^7.2.1",
44
44
  "flush-write-stream": "^2.0.0",
45
45
  "folder-walker": "^3.2.0",
46
+ "form-data": "^4.0.2",
46
47
  "get-folder-size": "^2.0.1",
47
48
  "hasha": "^5.2.2",
48
49
  "hpagent": "^1.1.0",
@@ -52,9 +53,12 @@
52
53
  "is-url": "^1.2.4",
53
54
  "json-stable-stringify": "^1.0.1",
54
55
  "lodash": "^4.17.21",
56
+ "lru-cache": "^11.0.2",
57
+ "minimatch": "^10.0.1",
55
58
  "multiformats": "9.9.0",
56
59
  "npm-packlist": "^7.0.4",
57
60
  "p-retry": "^4.6.2",
61
+ "p-wait-for": "^3.2.0",
58
62
  "parallel-transform": "^1.2.0",
59
63
  "public-ip": "^4.0.4",
60
64
  "pump": "^3.0.0",
@@ -82,5 +86,5 @@
82
86
  "fs-extra": "^11.2.0",
83
87
  "jest": "^29.7.0"
84
88
  },
85
- "gitHead": "836a400c1f953f421a8e95dc9a32ba883ac2586d"
89
+ "gitHead": "b5195a0290d5ced00bb716a709548b1a56356134"
86
90
  }