@blocklet/store 1.16.26-beta-c724c8e6
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/LICENSE +13 -0
- package/dist/index.cjs +365 -0
- package/dist/index.d.cts +34 -0
- package/dist/index.d.mts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.mjs +347 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2018-2020 ArcBlock
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const security = require('@abtnode/util/lib/security');
|
|
4
|
+
const lodash = require('lodash');
|
|
5
|
+
const tweetnacl = require('tweetnacl');
|
|
6
|
+
const SealedBox = require('tweetnacl-sealedbox-js');
|
|
7
|
+
const joinUrl = require('url-join');
|
|
8
|
+
const axios = require('@abtnode/util/lib/axios');
|
|
9
|
+
const pWaitFor = require('p-wait-for');
|
|
10
|
+
const wallet = require('@ocap/wallet');
|
|
11
|
+
const util$1 = require('@blocklet/meta/lib/util');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs$1 = require('fs-extra');
|
|
14
|
+
const did = require('@arcblock/did');
|
|
15
|
+
const v2 = require('@blocklet/meta/lib/payment/v2');
|
|
16
|
+
const FormData = require('form-data');
|
|
17
|
+
const Client = require('@ocap/client');
|
|
18
|
+
const constant$1 = require('@abtnode/constant');
|
|
19
|
+
const constant = require('@blocklet/constant');
|
|
20
|
+
const util = require('@ocap/util');
|
|
21
|
+
const stableStringify = require('json-stable-stringify');
|
|
22
|
+
const xbytes = require('xbytes');
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
|
|
25
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
26
|
+
|
|
27
|
+
const tweetnacl__default = /*#__PURE__*/_interopDefaultCompat(tweetnacl);
|
|
28
|
+
const SealedBox__default = /*#__PURE__*/_interopDefaultCompat(SealedBox);
|
|
29
|
+
const joinUrl__default = /*#__PURE__*/_interopDefaultCompat(joinUrl);
|
|
30
|
+
const axios__default = /*#__PURE__*/_interopDefaultCompat(axios);
|
|
31
|
+
const pWaitFor__default = /*#__PURE__*/_interopDefaultCompat(pWaitFor);
|
|
32
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
33
|
+
const fs__default$1 = /*#__PURE__*/_interopDefaultCompat(fs$1);
|
|
34
|
+
const FormData__default = /*#__PURE__*/_interopDefaultCompat(FormData);
|
|
35
|
+
const Client__default = /*#__PURE__*/_interopDefaultCompat(Client);
|
|
36
|
+
const stableStringify__default = /*#__PURE__*/_interopDefaultCompat(stableStringify);
|
|
37
|
+
const xbytes__default = /*#__PURE__*/_interopDefaultCompat(xbytes);
|
|
38
|
+
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
39
|
+
|
|
40
|
+
function request(options) {
|
|
41
|
+
return axios__default({
|
|
42
|
+
maxContentLength: Number.POSITIVE_INFINITY,
|
|
43
|
+
maxBodyLength: Number.POSITIVE_INFINITY,
|
|
44
|
+
...options
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const fetchConfigs = async ({
|
|
49
|
+
connectUrl,
|
|
50
|
+
sessionId,
|
|
51
|
+
fetchInterval,
|
|
52
|
+
fetchTimeout,
|
|
53
|
+
connectAction
|
|
54
|
+
}) => {
|
|
55
|
+
const ENDPOINT_CHECK_SESSION = `/api/did/${connectAction}/status`;
|
|
56
|
+
const ENDPOINT_INVALIDATE_SESSION = `/api/did/${connectAction}/timeout`;
|
|
57
|
+
const fetchSessionStatus = async () => {
|
|
58
|
+
const url = `${joinUrl__default(connectUrl, ENDPOINT_CHECK_SESSION)}?_t_=${sessionId}`;
|
|
59
|
+
const { data } = await request({
|
|
60
|
+
url,
|
|
61
|
+
method: "GET",
|
|
62
|
+
timeout: fetchTimeout
|
|
63
|
+
});
|
|
64
|
+
return data;
|
|
65
|
+
};
|
|
66
|
+
const condition = async () => {
|
|
67
|
+
const { status, error } = await fetchSessionStatus();
|
|
68
|
+
if (status === "error" || !!error) {
|
|
69
|
+
throw new Error(error);
|
|
70
|
+
}
|
|
71
|
+
return status === "succeed";
|
|
72
|
+
};
|
|
73
|
+
try {
|
|
74
|
+
await pWaitFor__default(condition, { interval: fetchInterval, timeout: fetchTimeout });
|
|
75
|
+
const { config } = await fetchSessionStatus();
|
|
76
|
+
return config;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
const err = e;
|
|
79
|
+
if (err.name === "TimeoutError") {
|
|
80
|
+
await request({
|
|
81
|
+
url: `${joinUrl__default(connectUrl, ENDPOINT_INVALIDATE_SESSION)}?_t_=${sessionId}`,
|
|
82
|
+
method: "GET",
|
|
83
|
+
timeout: fetchTimeout
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function baseWrapSpinner(_, waiting) {
|
|
91
|
+
return Promise.resolve(waiting());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function createConnect({
|
|
95
|
+
connectUrl = "https://store.blocklet.dev",
|
|
96
|
+
openPage,
|
|
97
|
+
fetchTimeout = 30 * 1e3,
|
|
98
|
+
fetchInterval = 3 * 1e3,
|
|
99
|
+
retry = 60,
|
|
100
|
+
connectAction = "connect-cli",
|
|
101
|
+
wrapSpinner = baseWrapSpinner,
|
|
102
|
+
enableEncrypt = false,
|
|
103
|
+
...restParams
|
|
104
|
+
} = {}) {
|
|
105
|
+
const ENDPOINT_CREATE_SESSION = `/api/did/${connectAction}/token`;
|
|
106
|
+
const DID_CONNECT_URL = `/${connectAction}`;
|
|
107
|
+
const keyPair = tweetnacl__default.box.keyPair();
|
|
108
|
+
const decrypt = (value) => {
|
|
109
|
+
const decrypted = SealedBox__default.open(
|
|
110
|
+
Uint8Array.from(Buffer.from(value, "base64")),
|
|
111
|
+
keyPair.publicKey,
|
|
112
|
+
keyPair.secretKey
|
|
113
|
+
);
|
|
114
|
+
return JSON.parse(Buffer.from(decrypted).toString("utf8"));
|
|
115
|
+
};
|
|
116
|
+
const BLOCKLET_JSON_PATH = "__blocklet__.js?type=json";
|
|
117
|
+
let masterSite;
|
|
118
|
+
try {
|
|
119
|
+
const { data: blocklet } = await request({
|
|
120
|
+
url: joinUrl__default(connectUrl, BLOCKLET_JSON_PATH),
|
|
121
|
+
method: "GET",
|
|
122
|
+
timeout: fetchTimeout
|
|
123
|
+
});
|
|
124
|
+
masterSite = blocklet?.settings?.federated?.master;
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const mergeParams = {
|
|
129
|
+
_ek_: security.encodeEncryptionKey(keyPair.publicKey),
|
|
130
|
+
sourceAppPid: masterSite?.appPid || void 0,
|
|
131
|
+
...restParams
|
|
132
|
+
};
|
|
133
|
+
const res = await request({
|
|
134
|
+
url: joinUrl__default(connectUrl, ENDPOINT_CREATE_SESSION),
|
|
135
|
+
params: mergeParams,
|
|
136
|
+
// for sensitive info encryption
|
|
137
|
+
method: "GET",
|
|
138
|
+
timeout: fetchTimeout
|
|
139
|
+
});
|
|
140
|
+
const { url, token } = res.data;
|
|
141
|
+
const encodedUrl = encodeURIComponent(Buffer.from(url).toString("base64"));
|
|
142
|
+
const pageUrl = `${joinUrl__default(connectUrl, DID_CONNECT_URL)}?__connect_url__=${encodedUrl}`;
|
|
143
|
+
openPage?.(pageUrl);
|
|
144
|
+
return await wrapSpinner(`Waiting for connection: ${connectUrl}`, async () => {
|
|
145
|
+
const fetchData = await fetchConfigs({
|
|
146
|
+
connectUrl,
|
|
147
|
+
sessionId: token,
|
|
148
|
+
connectAction,
|
|
149
|
+
fetchTimeout: retry * fetchInterval,
|
|
150
|
+
fetchInterval: retry
|
|
151
|
+
});
|
|
152
|
+
const decryptData = enableEncrypt ? decrypt(fetchData) : fetchData;
|
|
153
|
+
return decryptData;
|
|
154
|
+
});
|
|
155
|
+
} catch (e) {
|
|
156
|
+
const err = e;
|
|
157
|
+
const response = lodash.get(err, "response");
|
|
158
|
+
let errorMessage;
|
|
159
|
+
if (response) {
|
|
160
|
+
errorMessage = lodash.get(response, "data.error", response.statusText) || "";
|
|
161
|
+
errorMessage = `[${response.status}] ${errorMessage}`;
|
|
162
|
+
} else {
|
|
163
|
+
errorMessage = err.message;
|
|
164
|
+
}
|
|
165
|
+
throw new Error(`failed to connect to store (${errorMessage})`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const getStoreInfo = async (url) => {
|
|
170
|
+
const { data } = await axios__default.get(joinUrl__default(url, constant$1.BLOCKLET_STORE_META_PATH), { timeout: 20 * 1e3 });
|
|
171
|
+
return data;
|
|
172
|
+
};
|
|
173
|
+
const isValidChainEndpoint = async (endpoint) => {
|
|
174
|
+
if (!endpoint) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const client = new Client__default(endpoint);
|
|
178
|
+
try {
|
|
179
|
+
const { info } = await client.getChainInfo();
|
|
180
|
+
if (info.consensusVersion.split(" ").length === 2) {
|
|
181
|
+
return client;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
} catch (err) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const getShare = async ({
|
|
189
|
+
meta,
|
|
190
|
+
storeUrl,
|
|
191
|
+
developerDid
|
|
192
|
+
}) => {
|
|
193
|
+
const info = await getStoreInfo(storeUrl);
|
|
194
|
+
const shares = lodash.cloneDeep(lodash.get(meta, "payment.share", []));
|
|
195
|
+
if (shares.length === 0) {
|
|
196
|
+
shares.push({
|
|
197
|
+
name: "developer",
|
|
198
|
+
address: developerDid,
|
|
199
|
+
value: constant.BLOCKLET_FACTORY_SHARES.developer
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (!shares.find((x) => x.address === info.id)) {
|
|
203
|
+
shares.push({
|
|
204
|
+
name: "store",
|
|
205
|
+
address: info.id,
|
|
206
|
+
value: constant.BLOCKLET_FACTORY_SHARES.store
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return shares;
|
|
210
|
+
};
|
|
211
|
+
const ensureBlockletNftFactory = async ({
|
|
212
|
+
meta,
|
|
213
|
+
storeUrl
|
|
214
|
+
}) => {
|
|
215
|
+
const info = await getStoreInfo(storeUrl);
|
|
216
|
+
const endpoint = info.chainHost;
|
|
217
|
+
const ocapClient = await isValidChainEndpoint(endpoint);
|
|
218
|
+
if (ocapClient === false) {
|
|
219
|
+
throw new Error("Invalid chain endpoint fetched from store, please contact store administrator to fix");
|
|
220
|
+
}
|
|
221
|
+
const { itx } = await v2.createNftFactoryItx({
|
|
222
|
+
blockletMeta: meta,
|
|
223
|
+
ocapClient,
|
|
224
|
+
issuers: [info.id],
|
|
225
|
+
storeUrl
|
|
226
|
+
});
|
|
227
|
+
return itx.address;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const sign = (blockletMeta, wallet) => {
|
|
231
|
+
const walletJSON = wallet.toJSON();
|
|
232
|
+
const signatureData = {
|
|
233
|
+
type: walletJSON.type.pk,
|
|
234
|
+
name: blockletMeta.name,
|
|
235
|
+
signer: walletJSON.address,
|
|
236
|
+
pk: util.toBase58(walletJSON.pk),
|
|
237
|
+
created: (/* @__PURE__ */ new Date()).toISOString()
|
|
238
|
+
};
|
|
239
|
+
const signature = wallet.sign(stableStringify__default({ ...blockletMeta, signatures: [signatureData] }));
|
|
240
|
+
signatureData.sig = util.toBase58(signature);
|
|
241
|
+
return signatureData;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const verifyBundleSize = async ({
|
|
245
|
+
storeUrl,
|
|
246
|
+
tarballFilePath
|
|
247
|
+
}) => {
|
|
248
|
+
const { data: storeJson } = await axios__default({
|
|
249
|
+
url: `${storeUrl}/api/store.json`
|
|
250
|
+
}).catch(() => {
|
|
251
|
+
return {};
|
|
252
|
+
});
|
|
253
|
+
if (xbytes__default.isBytes(storeJson?.maxBundleSize) && fs__default.existsSync(tarballFilePath)) {
|
|
254
|
+
const { size: tarballFileBytes } = fs__default.statSync(tarballFilePath);
|
|
255
|
+
const maxBundleBytes = xbytes__default.parseSize(storeJson.maxBundleSize);
|
|
256
|
+
if (tarballFileBytes > maxBundleBytes) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`The release file ${path__default.basename(tarballFilePath)} size cannot exceed ${storeJson.maxBundleSize}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
async function upload({
|
|
265
|
+
storeUrl,
|
|
266
|
+
accessToken,
|
|
267
|
+
developerDid,
|
|
268
|
+
metaFile,
|
|
269
|
+
wrapSpinner = baseWrapSpinner,
|
|
270
|
+
printSuccess = () => {
|
|
271
|
+
},
|
|
272
|
+
printTar = async () => {
|
|
273
|
+
},
|
|
274
|
+
debug = () => {
|
|
275
|
+
}
|
|
276
|
+
}) {
|
|
277
|
+
if (!fs__default$1.existsSync(metaFile)) {
|
|
278
|
+
throw new Error(`Invalid release meta file ${metaFile} not exists`);
|
|
279
|
+
}
|
|
280
|
+
const meta = fs__default$1.readJSONSync(metaFile);
|
|
281
|
+
if (meta.payment === void 0) {
|
|
282
|
+
const { charging } = meta;
|
|
283
|
+
const payment = {};
|
|
284
|
+
if (charging && Array.isArray(charging.tokens)) {
|
|
285
|
+
payment.price = charging.tokens.map((x) => ({
|
|
286
|
+
address: x.address,
|
|
287
|
+
value: x.price
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
if (charging && Array.isArray(charging.shares)) {
|
|
291
|
+
payment.share = charging.shares.map((x) => ({
|
|
292
|
+
name: x.name,
|
|
293
|
+
address: x.address,
|
|
294
|
+
value: x.share
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
meta.payment = payment;
|
|
298
|
+
delete meta.charging;
|
|
299
|
+
}
|
|
300
|
+
const { tarball } = meta.dist;
|
|
301
|
+
const tarballFilePath = path__default.join(path__default.dirname(metaFile), tarball);
|
|
302
|
+
await verifyBundleSize({ storeUrl, tarballFilePath });
|
|
303
|
+
const wallet$1 = wallet.fromSecretKey(accessToken);
|
|
304
|
+
if (!wallet$1) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`[NO-ACCESS] Blocklet store accessToken is used to authorize developers when uploading blocklets, you can generate your own accessToken from ${storeUrl}`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
if (util$1.isFreeBlocklet(meta) === false) {
|
|
310
|
+
if (!developerDid || did.isValid(developerDid) === false) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
`developerDid is required to upload a paid blocklet, please get your developerDid from ${storeUrl}`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const typeInfo = did.toTypeInfo(meta.did);
|
|
317
|
+
const isNewDid = typeInfo.role === did.types.RoleType.ROLE_BLOCKLET;
|
|
318
|
+
meta.payment.share = await getShare({ meta, storeUrl, developerDid: isNewDid ? meta.did : developerDid });
|
|
319
|
+
const nftFactory = await ensureBlockletNftFactory({ meta, storeUrl });
|
|
320
|
+
meta.nftFactory = nftFactory;
|
|
321
|
+
printSuccess(`NFT Factory for ${meta.name}: ${nftFactory}`);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
throw new Error(`Can not determine NFT factory for ${meta.name}: ${err.message}`);
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
await v2.checkFreeBlocklet(meta);
|
|
327
|
+
}
|
|
328
|
+
const signature = sign(meta, wallet$1);
|
|
329
|
+
meta.signatures = [signature];
|
|
330
|
+
printSuccess("Blocklet release signed successfully, signature:", signature.sig);
|
|
331
|
+
await printTar(meta, tarballFilePath);
|
|
332
|
+
const data = new FormData__default();
|
|
333
|
+
data.append("blocklet-meta", Buffer.from(JSON.stringify(meta)), { filename: path__default.basename(metaFile) });
|
|
334
|
+
data.append("blocklet-tarball", fs__default$1.readFileSync(tarballFilePath), { filename: path__default.basename(tarballFilePath) });
|
|
335
|
+
let uploadResult = {};
|
|
336
|
+
try {
|
|
337
|
+
await wrapSpinner(`Uploading ${meta.name}@${meta.version}...`, async () => {
|
|
338
|
+
uploadResult = await axios__default({
|
|
339
|
+
url: joinUrl__default(storeUrl, "/api/blocklets/upload"),
|
|
340
|
+
method: "POST",
|
|
341
|
+
data,
|
|
342
|
+
headers: {
|
|
343
|
+
...data.getHeaders(),
|
|
344
|
+
"blocklet-version": meta.version,
|
|
345
|
+
"blocklet-did": meta.did
|
|
346
|
+
},
|
|
347
|
+
timeout: 10 * 60 * 1e3,
|
|
348
|
+
// 10 minute
|
|
349
|
+
maxContentLength: Number.POSITIVE_INFINITY,
|
|
350
|
+
maxBodyLength: Number.POSITIVE_INFINITY
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
debug("Upload result:", uploadResult.data);
|
|
354
|
+
} catch (err) {
|
|
355
|
+
if (err.response) {
|
|
356
|
+
const errorMessage = lodash.get(err.response, "data.error", err.response.statusText);
|
|
357
|
+
throw new Error(`Upload failed with error: [${err.response.status}] ${errorMessage}`);
|
|
358
|
+
}
|
|
359
|
+
throw new Error(`Upload failed with error: ${err.message}`);
|
|
360
|
+
}
|
|
361
|
+
return { ...meta, status: uploadResult.data?.status || "" };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
exports.createConnect = createConnect;
|
|
365
|
+
exports.upload = upload;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
declare function baseWrapSpinner(_: string, waiting: () => Promise<unknown>): Promise<unknown>;
|
|
2
|
+
|
|
3
|
+
interface CreateConnectOptions {
|
|
4
|
+
connectUrl?: string;
|
|
5
|
+
openPage?: (url: string) => void;
|
|
6
|
+
fetchTimeout?: number;
|
|
7
|
+
fetchInterval?: number;
|
|
8
|
+
retry?: number;
|
|
9
|
+
connectAction?: string;
|
|
10
|
+
enableEncrypt?: boolean;
|
|
11
|
+
wrapSpinner?: typeof baseWrapSpinner;
|
|
12
|
+
}
|
|
13
|
+
declare function createConnect({ connectUrl, openPage, fetchTimeout, fetchInterval, retry, connectAction, wrapSpinner, enableEncrypt, ...restParams }?: CreateConnectOptions): Promise<unknown>;
|
|
14
|
+
|
|
15
|
+
interface BlockletMeta {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
did: string;
|
|
19
|
+
status: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface UploadOptions {
|
|
23
|
+
storeUrl: string;
|
|
24
|
+
accessToken: string;
|
|
25
|
+
developerDid: string;
|
|
26
|
+
metaFile: string;
|
|
27
|
+
wrapSpinner?: typeof baseWrapSpinner;
|
|
28
|
+
printSuccess: (...args: unknown[]) => void;
|
|
29
|
+
printTar: (meta: unknown, metaPath: string) => Promise<void>;
|
|
30
|
+
debug: (...args: unknown[]) => void;
|
|
31
|
+
}
|
|
32
|
+
declare function upload({ storeUrl, accessToken, developerDid, metaFile, wrapSpinner, printSuccess, printTar, debug, }: UploadOptions): Promise<BlockletMeta | undefined>;
|
|
33
|
+
|
|
34
|
+
export { type UploadOptions, createConnect, upload };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
declare function baseWrapSpinner(_: string, waiting: () => Promise<unknown>): Promise<unknown>;
|
|
2
|
+
|
|
3
|
+
interface CreateConnectOptions {
|
|
4
|
+
connectUrl?: string;
|
|
5
|
+
openPage?: (url: string) => void;
|
|
6
|
+
fetchTimeout?: number;
|
|
7
|
+
fetchInterval?: number;
|
|
8
|
+
retry?: number;
|
|
9
|
+
connectAction?: string;
|
|
10
|
+
enableEncrypt?: boolean;
|
|
11
|
+
wrapSpinner?: typeof baseWrapSpinner;
|
|
12
|
+
}
|
|
13
|
+
declare function createConnect({ connectUrl, openPage, fetchTimeout, fetchInterval, retry, connectAction, wrapSpinner, enableEncrypt, ...restParams }?: CreateConnectOptions): Promise<unknown>;
|
|
14
|
+
|
|
15
|
+
interface BlockletMeta {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
did: string;
|
|
19
|
+
status: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface UploadOptions {
|
|
23
|
+
storeUrl: string;
|
|
24
|
+
accessToken: string;
|
|
25
|
+
developerDid: string;
|
|
26
|
+
metaFile: string;
|
|
27
|
+
wrapSpinner?: typeof baseWrapSpinner;
|
|
28
|
+
printSuccess: (...args: unknown[]) => void;
|
|
29
|
+
printTar: (meta: unknown, metaPath: string) => Promise<void>;
|
|
30
|
+
debug: (...args: unknown[]) => void;
|
|
31
|
+
}
|
|
32
|
+
declare function upload({ storeUrl, accessToken, developerDid, metaFile, wrapSpinner, printSuccess, printTar, debug, }: UploadOptions): Promise<BlockletMeta | undefined>;
|
|
33
|
+
|
|
34
|
+
export { type UploadOptions, createConnect, upload };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
declare function baseWrapSpinner(_: string, waiting: () => Promise<unknown>): Promise<unknown>;
|
|
2
|
+
|
|
3
|
+
interface CreateConnectOptions {
|
|
4
|
+
connectUrl?: string;
|
|
5
|
+
openPage?: (url: string) => void;
|
|
6
|
+
fetchTimeout?: number;
|
|
7
|
+
fetchInterval?: number;
|
|
8
|
+
retry?: number;
|
|
9
|
+
connectAction?: string;
|
|
10
|
+
enableEncrypt?: boolean;
|
|
11
|
+
wrapSpinner?: typeof baseWrapSpinner;
|
|
12
|
+
}
|
|
13
|
+
declare function createConnect({ connectUrl, openPage, fetchTimeout, fetchInterval, retry, connectAction, wrapSpinner, enableEncrypt, ...restParams }?: CreateConnectOptions): Promise<unknown>;
|
|
14
|
+
|
|
15
|
+
interface BlockletMeta {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
did: string;
|
|
19
|
+
status: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface UploadOptions {
|
|
23
|
+
storeUrl: string;
|
|
24
|
+
accessToken: string;
|
|
25
|
+
developerDid: string;
|
|
26
|
+
metaFile: string;
|
|
27
|
+
wrapSpinner?: typeof baseWrapSpinner;
|
|
28
|
+
printSuccess: (...args: unknown[]) => void;
|
|
29
|
+
printTar: (meta: unknown, metaPath: string) => Promise<void>;
|
|
30
|
+
debug: (...args: unknown[]) => void;
|
|
31
|
+
}
|
|
32
|
+
declare function upload({ storeUrl, accessToken, developerDid, metaFile, wrapSpinner, printSuccess, printTar, debug, }: UploadOptions): Promise<BlockletMeta | undefined>;
|
|
33
|
+
|
|
34
|
+
export { type UploadOptions, createConnect, upload };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { encodeEncryptionKey } from '@abtnode/util/lib/security';
|
|
2
|
+
import { get, cloneDeep } from 'lodash';
|
|
3
|
+
import tweetnacl from 'tweetnacl';
|
|
4
|
+
import SealedBox from 'tweetnacl-sealedbox-js';
|
|
5
|
+
import joinUrl from 'url-join';
|
|
6
|
+
import axios from '@abtnode/util/lib/axios';
|
|
7
|
+
import pWaitFor from 'p-wait-for';
|
|
8
|
+
import { fromSecretKey } from '@ocap/wallet';
|
|
9
|
+
import { isFreeBlocklet } from '@blocklet/meta/lib/util';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import fs$1 from 'fs-extra';
|
|
12
|
+
import { isValid, toTypeInfo, types } from '@arcblock/did';
|
|
13
|
+
import { createNftFactoryItx, checkFreeBlocklet } from '@blocklet/meta/lib/payment/v2';
|
|
14
|
+
import FormData from 'form-data';
|
|
15
|
+
import Client from '@ocap/client';
|
|
16
|
+
import { BLOCKLET_STORE_META_PATH } from '@abtnode/constant';
|
|
17
|
+
import { BLOCKLET_FACTORY_SHARES } from '@blocklet/constant';
|
|
18
|
+
import { toBase58 } from '@ocap/util';
|
|
19
|
+
import stableStringify from 'json-stable-stringify';
|
|
20
|
+
import xbytes from 'xbytes';
|
|
21
|
+
import fs from 'fs';
|
|
22
|
+
|
|
23
|
+
function request(options) {
|
|
24
|
+
return axios({
|
|
25
|
+
maxContentLength: Number.POSITIVE_INFINITY,
|
|
26
|
+
maxBodyLength: Number.POSITIVE_INFINITY,
|
|
27
|
+
...options
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fetchConfigs = async ({
|
|
32
|
+
connectUrl,
|
|
33
|
+
sessionId,
|
|
34
|
+
fetchInterval,
|
|
35
|
+
fetchTimeout,
|
|
36
|
+
connectAction
|
|
37
|
+
}) => {
|
|
38
|
+
const ENDPOINT_CHECK_SESSION = `/api/did/${connectAction}/status`;
|
|
39
|
+
const ENDPOINT_INVALIDATE_SESSION = `/api/did/${connectAction}/timeout`;
|
|
40
|
+
const fetchSessionStatus = async () => {
|
|
41
|
+
const url = `${joinUrl(connectUrl, ENDPOINT_CHECK_SESSION)}?_t_=${sessionId}`;
|
|
42
|
+
const { data } = await request({
|
|
43
|
+
url,
|
|
44
|
+
method: "GET",
|
|
45
|
+
timeout: fetchTimeout
|
|
46
|
+
});
|
|
47
|
+
return data;
|
|
48
|
+
};
|
|
49
|
+
const condition = async () => {
|
|
50
|
+
const { status, error } = await fetchSessionStatus();
|
|
51
|
+
if (status === "error" || !!error) {
|
|
52
|
+
throw new Error(error);
|
|
53
|
+
}
|
|
54
|
+
return status === "succeed";
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
await pWaitFor(condition, { interval: fetchInterval, timeout: fetchTimeout });
|
|
58
|
+
const { config } = await fetchSessionStatus();
|
|
59
|
+
return config;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
const err = e;
|
|
62
|
+
if (err.name === "TimeoutError") {
|
|
63
|
+
await request({
|
|
64
|
+
url: `${joinUrl(connectUrl, ENDPOINT_INVALIDATE_SESSION)}?_t_=${sessionId}`,
|
|
65
|
+
method: "GET",
|
|
66
|
+
timeout: fetchTimeout
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function baseWrapSpinner(_, waiting) {
|
|
74
|
+
return Promise.resolve(waiting());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function createConnect({
|
|
78
|
+
connectUrl = "https://store.blocklet.dev",
|
|
79
|
+
openPage,
|
|
80
|
+
fetchTimeout = 30 * 1e3,
|
|
81
|
+
fetchInterval = 3 * 1e3,
|
|
82
|
+
retry = 60,
|
|
83
|
+
connectAction = "connect-cli",
|
|
84
|
+
wrapSpinner = baseWrapSpinner,
|
|
85
|
+
enableEncrypt = false,
|
|
86
|
+
...restParams
|
|
87
|
+
} = {}) {
|
|
88
|
+
const ENDPOINT_CREATE_SESSION = `/api/did/${connectAction}/token`;
|
|
89
|
+
const DID_CONNECT_URL = `/${connectAction}`;
|
|
90
|
+
const keyPair = tweetnacl.box.keyPair();
|
|
91
|
+
const decrypt = (value) => {
|
|
92
|
+
const decrypted = SealedBox.open(
|
|
93
|
+
Uint8Array.from(Buffer.from(value, "base64")),
|
|
94
|
+
keyPair.publicKey,
|
|
95
|
+
keyPair.secretKey
|
|
96
|
+
);
|
|
97
|
+
return JSON.parse(Buffer.from(decrypted).toString("utf8"));
|
|
98
|
+
};
|
|
99
|
+
const BLOCKLET_JSON_PATH = "__blocklet__.js?type=json";
|
|
100
|
+
let masterSite;
|
|
101
|
+
try {
|
|
102
|
+
const { data: blocklet } = await request({
|
|
103
|
+
url: joinUrl(connectUrl, BLOCKLET_JSON_PATH),
|
|
104
|
+
method: "GET",
|
|
105
|
+
timeout: fetchTimeout
|
|
106
|
+
});
|
|
107
|
+
masterSite = blocklet?.settings?.federated?.master;
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const mergeParams = {
|
|
112
|
+
_ek_: encodeEncryptionKey(keyPair.publicKey),
|
|
113
|
+
sourceAppPid: masterSite?.appPid || void 0,
|
|
114
|
+
...restParams
|
|
115
|
+
};
|
|
116
|
+
const res = await request({
|
|
117
|
+
url: joinUrl(connectUrl, ENDPOINT_CREATE_SESSION),
|
|
118
|
+
params: mergeParams,
|
|
119
|
+
// for sensitive info encryption
|
|
120
|
+
method: "GET",
|
|
121
|
+
timeout: fetchTimeout
|
|
122
|
+
});
|
|
123
|
+
const { url, token } = res.data;
|
|
124
|
+
const encodedUrl = encodeURIComponent(Buffer.from(url).toString("base64"));
|
|
125
|
+
const pageUrl = `${joinUrl(connectUrl, DID_CONNECT_URL)}?__connect_url__=${encodedUrl}`;
|
|
126
|
+
openPage?.(pageUrl);
|
|
127
|
+
return await wrapSpinner(`Waiting for connection: ${connectUrl}`, async () => {
|
|
128
|
+
const fetchData = await fetchConfigs({
|
|
129
|
+
connectUrl,
|
|
130
|
+
sessionId: token,
|
|
131
|
+
connectAction,
|
|
132
|
+
fetchTimeout: retry * fetchInterval,
|
|
133
|
+
fetchInterval: retry
|
|
134
|
+
});
|
|
135
|
+
const decryptData = enableEncrypt ? decrypt(fetchData) : fetchData;
|
|
136
|
+
return decryptData;
|
|
137
|
+
});
|
|
138
|
+
} catch (e) {
|
|
139
|
+
const err = e;
|
|
140
|
+
const response = get(err, "response");
|
|
141
|
+
let errorMessage;
|
|
142
|
+
if (response) {
|
|
143
|
+
errorMessage = get(response, "data.error", response.statusText) || "";
|
|
144
|
+
errorMessage = `[${response.status}] ${errorMessage}`;
|
|
145
|
+
} else {
|
|
146
|
+
errorMessage = err.message;
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`failed to connect to store (${errorMessage})`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const getStoreInfo = async (url) => {
|
|
153
|
+
const { data } = await axios.get(joinUrl(url, BLOCKLET_STORE_META_PATH), { timeout: 20 * 1e3 });
|
|
154
|
+
return data;
|
|
155
|
+
};
|
|
156
|
+
const isValidChainEndpoint = async (endpoint) => {
|
|
157
|
+
if (!endpoint) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const client = new Client(endpoint);
|
|
161
|
+
try {
|
|
162
|
+
const { info } = await client.getChainInfo();
|
|
163
|
+
if (info.consensusVersion.split(" ").length === 2) {
|
|
164
|
+
return client;
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
} catch (err) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const getShare = async ({
|
|
172
|
+
meta,
|
|
173
|
+
storeUrl,
|
|
174
|
+
developerDid
|
|
175
|
+
}) => {
|
|
176
|
+
const info = await getStoreInfo(storeUrl);
|
|
177
|
+
const shares = cloneDeep(get(meta, "payment.share", []));
|
|
178
|
+
if (shares.length === 0) {
|
|
179
|
+
shares.push({
|
|
180
|
+
name: "developer",
|
|
181
|
+
address: developerDid,
|
|
182
|
+
value: BLOCKLET_FACTORY_SHARES.developer
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (!shares.find((x) => x.address === info.id)) {
|
|
186
|
+
shares.push({
|
|
187
|
+
name: "store",
|
|
188
|
+
address: info.id,
|
|
189
|
+
value: BLOCKLET_FACTORY_SHARES.store
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return shares;
|
|
193
|
+
};
|
|
194
|
+
const ensureBlockletNftFactory = async ({
|
|
195
|
+
meta,
|
|
196
|
+
storeUrl
|
|
197
|
+
}) => {
|
|
198
|
+
const info = await getStoreInfo(storeUrl);
|
|
199
|
+
const endpoint = info.chainHost;
|
|
200
|
+
const ocapClient = await isValidChainEndpoint(endpoint);
|
|
201
|
+
if (ocapClient === false) {
|
|
202
|
+
throw new Error("Invalid chain endpoint fetched from store, please contact store administrator to fix");
|
|
203
|
+
}
|
|
204
|
+
const { itx } = await createNftFactoryItx({
|
|
205
|
+
blockletMeta: meta,
|
|
206
|
+
ocapClient,
|
|
207
|
+
issuers: [info.id],
|
|
208
|
+
storeUrl
|
|
209
|
+
});
|
|
210
|
+
return itx.address;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const sign = (blockletMeta, wallet) => {
|
|
214
|
+
const walletJSON = wallet.toJSON();
|
|
215
|
+
const signatureData = {
|
|
216
|
+
type: walletJSON.type.pk,
|
|
217
|
+
name: blockletMeta.name,
|
|
218
|
+
signer: walletJSON.address,
|
|
219
|
+
pk: toBase58(walletJSON.pk),
|
|
220
|
+
created: (/* @__PURE__ */ new Date()).toISOString()
|
|
221
|
+
};
|
|
222
|
+
const signature = wallet.sign(stableStringify({ ...blockletMeta, signatures: [signatureData] }));
|
|
223
|
+
signatureData.sig = toBase58(signature);
|
|
224
|
+
return signatureData;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const verifyBundleSize = async ({
|
|
228
|
+
storeUrl,
|
|
229
|
+
tarballFilePath
|
|
230
|
+
}) => {
|
|
231
|
+
const { data: storeJson } = await axios({
|
|
232
|
+
url: `${storeUrl}/api/store.json`
|
|
233
|
+
}).catch(() => {
|
|
234
|
+
return {};
|
|
235
|
+
});
|
|
236
|
+
if (xbytes.isBytes(storeJson?.maxBundleSize) && fs.existsSync(tarballFilePath)) {
|
|
237
|
+
const { size: tarballFileBytes } = fs.statSync(tarballFilePath);
|
|
238
|
+
const maxBundleBytes = xbytes.parseSize(storeJson.maxBundleSize);
|
|
239
|
+
if (tarballFileBytes > maxBundleBytes) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`The release file ${path.basename(tarballFilePath)} size cannot exceed ${storeJson.maxBundleSize}`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
async function upload({
|
|
248
|
+
storeUrl,
|
|
249
|
+
accessToken,
|
|
250
|
+
developerDid,
|
|
251
|
+
metaFile,
|
|
252
|
+
wrapSpinner = baseWrapSpinner,
|
|
253
|
+
printSuccess = () => {
|
|
254
|
+
},
|
|
255
|
+
printTar = async () => {
|
|
256
|
+
},
|
|
257
|
+
debug = () => {
|
|
258
|
+
}
|
|
259
|
+
}) {
|
|
260
|
+
if (!fs$1.existsSync(metaFile)) {
|
|
261
|
+
throw new Error(`Invalid release meta file ${metaFile} not exists`);
|
|
262
|
+
}
|
|
263
|
+
const meta = fs$1.readJSONSync(metaFile);
|
|
264
|
+
if (meta.payment === void 0) {
|
|
265
|
+
const { charging } = meta;
|
|
266
|
+
const payment = {};
|
|
267
|
+
if (charging && Array.isArray(charging.tokens)) {
|
|
268
|
+
payment.price = charging.tokens.map((x) => ({
|
|
269
|
+
address: x.address,
|
|
270
|
+
value: x.price
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
if (charging && Array.isArray(charging.shares)) {
|
|
274
|
+
payment.share = charging.shares.map((x) => ({
|
|
275
|
+
name: x.name,
|
|
276
|
+
address: x.address,
|
|
277
|
+
value: x.share
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
meta.payment = payment;
|
|
281
|
+
delete meta.charging;
|
|
282
|
+
}
|
|
283
|
+
const { tarball } = meta.dist;
|
|
284
|
+
const tarballFilePath = path.join(path.dirname(metaFile), tarball);
|
|
285
|
+
await verifyBundleSize({ storeUrl, tarballFilePath });
|
|
286
|
+
const wallet = fromSecretKey(accessToken);
|
|
287
|
+
if (!wallet) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`[NO-ACCESS] Blocklet store accessToken is used to authorize developers when uploading blocklets, you can generate your own accessToken from ${storeUrl}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (isFreeBlocklet(meta) === false) {
|
|
293
|
+
if (!developerDid || isValid(developerDid) === false) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`developerDid is required to upload a paid blocklet, please get your developerDid from ${storeUrl}`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const typeInfo = toTypeInfo(meta.did);
|
|
300
|
+
const isNewDid = typeInfo.role === types.RoleType.ROLE_BLOCKLET;
|
|
301
|
+
meta.payment.share = await getShare({ meta, storeUrl, developerDid: isNewDid ? meta.did : developerDid });
|
|
302
|
+
const nftFactory = await ensureBlockletNftFactory({ meta, storeUrl });
|
|
303
|
+
meta.nftFactory = nftFactory;
|
|
304
|
+
printSuccess(`NFT Factory for ${meta.name}: ${nftFactory}`);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
throw new Error(`Can not determine NFT factory for ${meta.name}: ${err.message}`);
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
await checkFreeBlocklet(meta);
|
|
310
|
+
}
|
|
311
|
+
const signature = sign(meta, wallet);
|
|
312
|
+
meta.signatures = [signature];
|
|
313
|
+
printSuccess("Blocklet release signed successfully, signature:", signature.sig);
|
|
314
|
+
await printTar(meta, tarballFilePath);
|
|
315
|
+
const data = new FormData();
|
|
316
|
+
data.append("blocklet-meta", Buffer.from(JSON.stringify(meta)), { filename: path.basename(metaFile) });
|
|
317
|
+
data.append("blocklet-tarball", fs$1.readFileSync(tarballFilePath), { filename: path.basename(tarballFilePath) });
|
|
318
|
+
let uploadResult = {};
|
|
319
|
+
try {
|
|
320
|
+
await wrapSpinner(`Uploading ${meta.name}@${meta.version}...`, async () => {
|
|
321
|
+
uploadResult = await axios({
|
|
322
|
+
url: joinUrl(storeUrl, "/api/blocklets/upload"),
|
|
323
|
+
method: "POST",
|
|
324
|
+
data,
|
|
325
|
+
headers: {
|
|
326
|
+
...data.getHeaders(),
|
|
327
|
+
"blocklet-version": meta.version,
|
|
328
|
+
"blocklet-did": meta.did
|
|
329
|
+
},
|
|
330
|
+
timeout: 10 * 60 * 1e3,
|
|
331
|
+
// 10 minute
|
|
332
|
+
maxContentLength: Number.POSITIVE_INFINITY,
|
|
333
|
+
maxBodyLength: Number.POSITIVE_INFINITY
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
debug("Upload result:", uploadResult.data);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
if (err.response) {
|
|
339
|
+
const errorMessage = get(err.response, "data.error", err.response.statusText);
|
|
340
|
+
throw new Error(`Upload failed with error: [${err.response.status}] ${errorMessage}`);
|
|
341
|
+
}
|
|
342
|
+
throw new Error(`Upload failed with error: ${err.message}`);
|
|
343
|
+
}
|
|
344
|
+
return { ...meta, status: uploadResult.data?.status || "" };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export { createConnect, upload };
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blocklet/store",
|
|
3
|
+
"version": "1.16.26-beta-c724c8e6",
|
|
4
|
+
"description": "Connect Store and upload blocklet to Store",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"ts-check": "npx tsc --noemit --skipLibCheck --incremental --tsBuildInfoFile './node_modules/.tsbuildinfo'",
|
|
21
|
+
"lint": "npm run ts-check && eslint src",
|
|
22
|
+
"lint:fix": "npm run lint -- --fix",
|
|
23
|
+
"build": "unbuild",
|
|
24
|
+
"build:blocklet-store": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@abtnode/constant": "1.16.26-beta-c724c8e6",
|
|
31
|
+
"@abtnode/util": "1.16.26-beta-c724c8e6",
|
|
32
|
+
"@arcblock/did": "1.18.114",
|
|
33
|
+
"@blocklet/constant": "1.16.26-beta-c724c8e6",
|
|
34
|
+
"@blocklet/meta": "1.16.26-beta-c724c8e6",
|
|
35
|
+
"@ocap/client": "^1.18.114",
|
|
36
|
+
"@ocap/util": "1.18.114",
|
|
37
|
+
"@ocap/wallet": "1.18.114",
|
|
38
|
+
"form-data": "^4.0.0",
|
|
39
|
+
"fs-extra": "^11.2.0",
|
|
40
|
+
"json-stable-stringify": "^1.0.1",
|
|
41
|
+
"lodash": "^4.17.21",
|
|
42
|
+
"p-wait-for": "3.2.0",
|
|
43
|
+
"tweetnacl": "^1.0.3",
|
|
44
|
+
"tweetnacl-sealedbox-js": "^1.2.0",
|
|
45
|
+
"url-join": "^4.0.1",
|
|
46
|
+
"xbytes": "^1.8.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@arcblock/eslint-config-ts": "^0.3.0",
|
|
50
|
+
"@types/jest": "^29.5.11",
|
|
51
|
+
"@types/node": "^18.11.0",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
|
53
|
+
"@typescript-eslint/parser": "^5.40.1",
|
|
54
|
+
"dts-bundle-generator": "^9.2.3",
|
|
55
|
+
"eslint": "^8.25.0",
|
|
56
|
+
"jest": "^29.7.0",
|
|
57
|
+
"prettier": "^2.7.1",
|
|
58
|
+
"ts-jest": "^29.1.1",
|
|
59
|
+
"typescript": "^5.0.4",
|
|
60
|
+
"unbuild": "^2.0.0"
|
|
61
|
+
},
|
|
62
|
+
"gitHead": "f2b51533d934be041b1cc5b4b2fc8c56f4855a26"
|
|
63
|
+
}
|