@dignetwork/dig-sdk 0.0.1-alpha.100 → 0.0.1-alpha.101
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.
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
import { Readable } from "stream";
|
|
1
3
|
export declare class ContentServer {
|
|
2
4
|
private ipAddress;
|
|
3
5
|
private storeId;
|
|
4
6
|
private static certPath;
|
|
5
7
|
private static keyPath;
|
|
6
8
|
private static readonly port;
|
|
7
|
-
private static readonly inactivityTimeout;
|
|
8
9
|
constructor(ipAddress: string, storeId: string);
|
|
9
10
|
getKey(key: string, rootHash: string, challengeHex?: string): Promise<string>;
|
|
10
11
|
getPaymentAddress(): Promise<string | null>;
|
|
@@ -14,18 +15,19 @@ export declare class ContentServer {
|
|
|
14
15
|
getKeysIndex(rootHash?: string): Promise<any>;
|
|
15
16
|
headKey(key: string, rootHash?: string): Promise<{
|
|
16
17
|
success: boolean;
|
|
17
|
-
headers?:
|
|
18
|
+
headers?: http.IncomingHttpHeaders;
|
|
18
19
|
}>;
|
|
19
20
|
headStore(options?: {
|
|
20
21
|
hasRootHash: string;
|
|
21
22
|
}): Promise<{
|
|
22
23
|
success: boolean;
|
|
23
|
-
headers?:
|
|
24
|
+
headers?: http.IncomingHttpHeaders;
|
|
24
25
|
}>;
|
|
25
26
|
hasRootHash(rootHash: string): Promise<boolean>;
|
|
27
|
+
streamKey(key: string, rootHash?: string): Promise<Readable>;
|
|
26
28
|
private head;
|
|
27
29
|
private fetchJson;
|
|
30
|
+
private fetchWithRetries;
|
|
28
31
|
private fetch;
|
|
29
|
-
private getAxiosConfig;
|
|
30
32
|
}
|
|
31
33
|
//# sourceMappingURL=ContentServer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/ContentServer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ContentServer.d.ts","sourceRoot":"","sources":["../../src/DigNetwork/ContentServer.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGlC,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAS;IAChC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAQ;gBAExB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAYjC,MAAM,CACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC;IAaL,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAe3C,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC;IAM5B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAM9B,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAY7C,OAAO,CAClB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAA;KAAE,CAAC;IAYvD,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QACjE,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,IAAI,CAAC,mBAAmB,CAAC;KACpC,CAAC;IAUW,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YAgDrD,IAAI;YA0EJ,SAAS;YAMT,gBAAgB;YAiChB,KAAK;CA6GpB"}
|
|
@@ -4,9 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ContentServer = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
7
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const
|
|
8
|
+
const http_1 = __importDefault(require("http"));
|
|
9
|
+
const url_1 = require("url");
|
|
10
10
|
const ssl_1 = require("../utils/ssl");
|
|
11
11
|
class ContentServer {
|
|
12
12
|
constructor(ipAddress, storeId) {
|
|
@@ -20,11 +20,13 @@ class ContentServer {
|
|
|
20
20
|
}
|
|
21
21
|
// Method to get the content of a specified key from the peer, with optional challenge query
|
|
22
22
|
async getKey(key, rootHash, challengeHex) {
|
|
23
|
+
// Construct the base URL
|
|
23
24
|
let url = `https://${this.ipAddress}:${ContentServer.port}/chia.${this.storeId}.${rootHash}/${key}`;
|
|
25
|
+
// If a challenge is provided, append it as a query parameter
|
|
24
26
|
if (challengeHex) {
|
|
25
27
|
url += `?challenge=${challengeHex}`;
|
|
26
28
|
}
|
|
27
|
-
return this.
|
|
29
|
+
return this.fetchWithRetries(url);
|
|
28
30
|
}
|
|
29
31
|
// Method to get the payment address from the peer
|
|
30
32
|
async getPaymentAddress() {
|
|
@@ -88,89 +90,219 @@ class ContentServer {
|
|
|
88
90
|
}
|
|
89
91
|
return false;
|
|
90
92
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const response = await axios_1.default.head(url, config);
|
|
96
|
-
return {
|
|
97
|
-
success: response.status >= 200 && response.status < 300,
|
|
98
|
-
headers: response.headers,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
return { success: false };
|
|
93
|
+
streamKey(key, rootHash) {
|
|
94
|
+
let udi = `chia.${this.storeId}`;
|
|
95
|
+
if (rootHash) {
|
|
96
|
+
udi += `.${rootHash}`;
|
|
103
97
|
}
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const url = `https://${this.ipAddress}:${ContentServer.port}/${udi}/${key}`;
|
|
100
|
+
const urlObj = new url_1.URL(url);
|
|
101
|
+
const requestOptions = {
|
|
102
|
+
hostname: urlObj.hostname,
|
|
103
|
+
port: urlObj.port || ContentServer.port,
|
|
104
|
+
path: urlObj.pathname + urlObj.search,
|
|
105
|
+
method: "GET",
|
|
106
|
+
};
|
|
107
|
+
const request = http_1.default.request(requestOptions, (response) => {
|
|
108
|
+
if (response.statusCode === 200) {
|
|
109
|
+
resolve(response); // Resolve with the readable stream
|
|
110
|
+
}
|
|
111
|
+
else if (response.statusCode === 301 || response.statusCode === 302) {
|
|
112
|
+
// Handle redirects
|
|
113
|
+
const redirectUrl = response.headers.location;
|
|
114
|
+
if (redirectUrl) {
|
|
115
|
+
this.streamKey(redirectUrl).then(resolve).catch(reject);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
reject(new Error("Redirected without a location header"));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
request.on("error", (error) => {
|
|
126
|
+
console.error(`GET Request error for ${url}:`, error);
|
|
127
|
+
reject(error);
|
|
128
|
+
});
|
|
129
|
+
request.end();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// Helper method to perform HEAD requests
|
|
133
|
+
async head(url, maxRedirects = 5) {
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
try {
|
|
136
|
+
// Parse the input URL
|
|
137
|
+
const urlObj = new url_1.URL(url);
|
|
138
|
+
const requestOptions = {
|
|
139
|
+
hostname: urlObj.hostname,
|
|
140
|
+
port: urlObj.port ||
|
|
141
|
+
(urlObj.protocol === "http:" ? 80 : ContentServer.port),
|
|
142
|
+
path: urlObj.pathname + urlObj.search,
|
|
143
|
+
method: "HEAD",
|
|
144
|
+
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
145
|
+
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
146
|
+
rejectUnauthorized: false,
|
|
147
|
+
};
|
|
148
|
+
const request = http_1.default.request(requestOptions, (response) => {
|
|
149
|
+
const { statusCode, headers } = response;
|
|
150
|
+
// If status code is 2xx, return success
|
|
151
|
+
if (statusCode && statusCode >= 200 && statusCode < 300) {
|
|
152
|
+
resolve({ success: true, headers });
|
|
153
|
+
}
|
|
154
|
+
// Handle 3xx redirection
|
|
155
|
+
else if (statusCode &&
|
|
156
|
+
statusCode >= 300 &&
|
|
157
|
+
statusCode < 400 &&
|
|
158
|
+
headers.location) {
|
|
159
|
+
if (maxRedirects > 0) {
|
|
160
|
+
let redirectUrl = headers.location;
|
|
161
|
+
// Check if the redirect URL is relative
|
|
162
|
+
if (!/^https?:\/\//i.test(redirectUrl)) {
|
|
163
|
+
// Resolve the relative URL based on the original URL
|
|
164
|
+
redirectUrl = new url_1.URL(redirectUrl, url).toString();
|
|
165
|
+
console.log(`Resolved relative redirect to: ${redirectUrl}`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(`Redirecting to: ${redirectUrl}`);
|
|
169
|
+
}
|
|
170
|
+
// Recursively follow the redirection
|
|
171
|
+
this.head(redirectUrl, maxRedirects - 1)
|
|
172
|
+
.then(resolve)
|
|
173
|
+
.catch(reject);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
reject({ success: false, message: "Too many redirects" });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// For other status codes, consider it a failure
|
|
181
|
+
resolve({ success: false });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
request.on("error", (error) => {
|
|
185
|
+
console.error(`HEAD ${url}:`, error.message);
|
|
186
|
+
reject({ success: false });
|
|
187
|
+
});
|
|
188
|
+
request.end();
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
console.error(`Invalid URL: ${url}`, err);
|
|
192
|
+
reject({ success: false, message: "Invalid URL" });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
104
195
|
}
|
|
105
196
|
// Helper method to fetch JSON data from a URL
|
|
106
197
|
async fetchJson(url) {
|
|
107
|
-
const response = await this.
|
|
198
|
+
const response = await this.fetchWithRetries(url);
|
|
108
199
|
return JSON.parse(response);
|
|
109
200
|
}
|
|
110
|
-
//
|
|
111
|
-
async
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
201
|
+
// Helper method to fetch content with retries and redirection handling
|
|
202
|
+
async fetchWithRetries(url) {
|
|
203
|
+
let attempt = 0;
|
|
204
|
+
const maxRetries = 1;
|
|
205
|
+
const initialDelay = 2000; // 2 seconds
|
|
206
|
+
const maxDelay = 10000; // 10 seconds
|
|
207
|
+
const delayMultiplier = 1.5;
|
|
208
|
+
let delay = initialDelay;
|
|
209
|
+
while (attempt < maxRetries) {
|
|
210
|
+
try {
|
|
211
|
+
return await this.fetch(url);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (attempt < maxRetries - 1) {
|
|
215
|
+
console.warn(`Attempt ${attempt + 1} failed: ${error.message}. Retrying in ${delay / 1000} seconds...`);
|
|
216
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
217
|
+
delay = Math.min(maxDelay, delay * delayMultiplier);
|
|
120
218
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
219
|
+
else {
|
|
220
|
+
console.error(`Failed to retrieve data from ${url}. Aborting.`);
|
|
221
|
+
throw new Error(`Failed to retrieve data: ${error.message}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
attempt++;
|
|
225
|
+
}
|
|
226
|
+
throw new Error(`Failed to retrieve data from ${url} after ${maxRetries} attempts.`);
|
|
227
|
+
}
|
|
228
|
+
// Core method to fetch content from a URL with a 5-second inactivity timeout
|
|
229
|
+
async fetch(url, maxRedirects = 5) {
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
const urlObj = new url_1.URL(url);
|
|
232
|
+
const timeoutDuration = 5000; // 5 seconds
|
|
233
|
+
let timeout = null; // Initialize timeout
|
|
234
|
+
const requestOptions = {
|
|
235
|
+
hostname: urlObj.hostname,
|
|
236
|
+
port: urlObj.port || ContentServer.port,
|
|
237
|
+
path: urlObj.pathname + urlObj.search, // Include query params
|
|
238
|
+
method: "GET",
|
|
239
|
+
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
240
|
+
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
241
|
+
rejectUnauthorized: false,
|
|
124
242
|
};
|
|
125
|
-
|
|
126
|
-
.get(url, {
|
|
127
|
-
...config,
|
|
128
|
-
cancelToken: source.token,
|
|
129
|
-
responseType: "stream", // Use stream to track data transfer
|
|
130
|
-
maxRedirects: 5, // Axios follows up to 5 redirects by default
|
|
131
|
-
})
|
|
132
|
-
.then((response) => {
|
|
243
|
+
const request = http_1.default.request(requestOptions, (response) => {
|
|
133
244
|
let data = "";
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
245
|
+
// Set timeout for inactivity
|
|
246
|
+
timeout = setTimeout(() => {
|
|
247
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
248
|
+
request.destroy(); // Use destroy instead of abort
|
|
249
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
250
|
+
}, timeoutDuration);
|
|
251
|
+
const resetTimeout = () => {
|
|
139
252
|
if (timeout) {
|
|
140
|
-
clearTimeout(timeout);
|
|
253
|
+
clearTimeout(timeout);
|
|
141
254
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
255
|
+
timeout = setTimeout(() => {
|
|
256
|
+
console.error(`Request timeout: No data received for ${timeoutDuration / 1000} seconds.`);
|
|
257
|
+
request.destroy(); // Use destroy instead of abort
|
|
258
|
+
reject(new Error(`Request timed out after ${timeoutDuration / 1000} seconds of inactivity`));
|
|
259
|
+
}, timeoutDuration);
|
|
260
|
+
};
|
|
261
|
+
if (response.statusCode === 200) {
|
|
262
|
+
response.on("data", (chunk) => {
|
|
263
|
+
data += chunk;
|
|
264
|
+
resetTimeout(); // Reset the timeout every time data is received
|
|
265
|
+
});
|
|
266
|
+
response.on("end", () => {
|
|
267
|
+
if (timeout) {
|
|
268
|
+
clearTimeout(timeout);
|
|
269
|
+
}
|
|
270
|
+
resolve(data);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
else if ((response.statusCode === 301 || response.statusCode === 302) &&
|
|
274
|
+
response.headers.location) {
|
|
275
|
+
// Handle redirects
|
|
276
|
+
if (maxRedirects > 0) {
|
|
277
|
+
const redirectUrl = new url_1.URL(response.headers.location, url); // Resolve relative URLs based on the original URL
|
|
278
|
+
if (timeout) {
|
|
279
|
+
clearTimeout(timeout);
|
|
280
|
+
}
|
|
281
|
+
this.fetch(redirectUrl.toString(), maxRedirects - 1)
|
|
282
|
+
.then(resolve)
|
|
283
|
+
.catch(reject);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
reject(new Error("Too many redirects"));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
145
290
|
if (timeout) {
|
|
146
291
|
clearTimeout(timeout);
|
|
147
292
|
}
|
|
148
|
-
reject(
|
|
149
|
-
});
|
|
150
|
-
})
|
|
151
|
-
.catch((error) => {
|
|
152
|
-
if (axios_1.default.isCancel(error)) {
|
|
153
|
-
console.error("Request canceled:", error.message);
|
|
293
|
+
reject(new Error(`Failed to retrieve data from ${url}. Status code: ${response.statusCode}`));
|
|
154
294
|
}
|
|
155
|
-
|
|
156
|
-
|
|
295
|
+
});
|
|
296
|
+
request.on("error", (error) => {
|
|
297
|
+
if (timeout) {
|
|
298
|
+
clearTimeout(timeout);
|
|
157
299
|
}
|
|
300
|
+
console.error(`GET ${url}:`, error.message);
|
|
158
301
|
reject(error);
|
|
159
302
|
});
|
|
303
|
+
request.end();
|
|
160
304
|
});
|
|
161
305
|
}
|
|
162
|
-
// Helper method to configure axios with HTTPS settings
|
|
163
|
-
getAxiosConfig() {
|
|
164
|
-
return {
|
|
165
|
-
httpsAgent: new https_1.default.Agent({
|
|
166
|
-
cert: fs_1.default.readFileSync(ContentServer.certPath),
|
|
167
|
-
key: fs_1.default.readFileSync(ContentServer.keyPath),
|
|
168
|
-
rejectUnauthorized: false,
|
|
169
|
-
}),
|
|
170
|
-
timeout: 0, // Disable axios timeout (we're using inactivity timeout)
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
306
|
}
|
|
174
307
|
exports.ContentServer = ContentServer;
|
|
175
308
|
ContentServer.port = 4161;
|
|
176
|
-
ContentServer.inactivityTimeout = 5000; // Inactivity timeout in milliseconds (5 seconds)
|