@maka/meteor-sdk 0.2.3 → 0.2.4
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/isopackets/combined/npm/node_modules/meteor/babel-compiler/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/boilerplate-generator/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/constraint-solver/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ddp-client/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ecmascript/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime-client/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime-server/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/fetch/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/inter-process-messaging/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/logging/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/logic-solver/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/meteor/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/modules/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/modules-runtime/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/npm-mongo/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/package-version-parser/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/promise/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/react-fast-refresh/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/socket-stream-client/node_modules/.package-lock.json +1 -1
- package/package.json +3 -3
- package/tools/utils/http-helpers.js +283 -285
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/meteor-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Programmatic Node API over the Meteor build tool (bundle/run/create/etc.) -- for driving Meteor's build system from plain Node code instead of shelling out to a meteor CLI (there isn't one anymore; see README.md). \"dependencies\" is generated by scripts/write-tool-package-json.js from scripts/dev-bundle-tool-package.js at the monorepo root -- do not hand-edit that field, edit that file and re-run the script instead.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"source-map-v01": "npm:source-map@0.1.32",
|
|
45
45
|
"@types/semver": "7.5.8",
|
|
46
46
|
"semver": "7.6.3",
|
|
47
|
-
"
|
|
47
|
+
"undici": "8.10.0",
|
|
48
48
|
"graceful-fs": "4.2.11",
|
|
49
49
|
"tar-fs": "2.1.1",
|
|
50
50
|
"source-map": "0.7.4",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"is-reachable": "3.1.0",
|
|
55
55
|
"wordwrap": "1.0.0",
|
|
56
56
|
"moment": "2.30.1",
|
|
57
|
-
"glob": "
|
|
57
|
+
"glob": "13.0.6",
|
|
58
58
|
"ignore": "7.0.5",
|
|
59
59
|
"commonmark": "0.15.0",
|
|
60
60
|
"escope": "4.0.0",
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
///
|
|
2
2
|
/// utility functions for dealing with urls and http
|
|
3
3
|
///
|
|
4
|
+
/// Implemented on undici's fetch (the engine behind Node's own global
|
|
5
|
+
/// fetch) rather than the long-deprecated `request` package -- request
|
|
6
|
+
/// dragged har-validator and uuid@3 (both deprecation-flagged on every
|
|
7
|
+
/// consumer install) and has been unmaintained since 2020. The public
|
|
8
|
+
/// surface here (request/getUrl/getUrlWithResuming and their option
|
|
9
|
+
/// shapes) is preserved for the nine in-tree callers; see the audit in
|
|
10
|
+
/// the git history of this rewrite for exactly which options each
|
|
11
|
+
/// caller uses.
|
|
12
|
+
///
|
|
4
13
|
|
|
5
14
|
var os = require('os');
|
|
6
15
|
var util = require('util');
|
|
@@ -38,6 +47,13 @@ class ConcatStream extends Writable {
|
|
|
38
47
|
return this.chunks[0];
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
// A resumed download whose server ignored our Range header restarts
|
|
51
|
+
// from byte 0 -- drop what we had so we don't concatenate duplicates.
|
|
52
|
+
reset() {
|
|
53
|
+
this.chunks = [];
|
|
54
|
+
this.size = 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
end(force = false) {
|
|
42
58
|
// Override the Writable#end method to ignore any .end() calls for
|
|
43
59
|
// this stream, since we likely want to resume the download later.
|
|
@@ -47,51 +63,6 @@ class ConcatStream extends Writable {
|
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
65
|
|
|
50
|
-
// Helper that tracks bytes written to a writable
|
|
51
|
-
var WritableWithProgress = function (writable, listener) {
|
|
52
|
-
var self = this;
|
|
53
|
-
self._inner = writable;
|
|
54
|
-
self._listener = listener;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
Object.assign(WritableWithProgress.prototype, {
|
|
58
|
-
write: function (chunk, encoding, callback) {
|
|
59
|
-
var self = this;
|
|
60
|
-
self._listener(chunk.length, false);
|
|
61
|
-
return self._inner.write(chunk, encoding);
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
end: function (chunk, encoding, callback) {
|
|
65
|
-
var self = this;
|
|
66
|
-
self._listener(chunk ? chunk.length : 0, true);
|
|
67
|
-
return self._inner.end(chunk, encoding);
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
_progress: function (n, done) {
|
|
71
|
-
var self = this;
|
|
72
|
-
|
|
73
|
-
var state = self._state;
|
|
74
|
-
state.current += n;
|
|
75
|
-
if (done) {
|
|
76
|
-
state.current.done = true;
|
|
77
|
-
}
|
|
78
|
-
self.progress.reportProgress(state);
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
on: function (name, callback) {
|
|
82
|
-
return this._inner.on(name, callback);
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
once: function () {
|
|
86
|
-
return this._inner.once(...arguments);
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
emit: function () {
|
|
90
|
-
return this._inner.emit(...arguments);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
|
|
95
66
|
// Compose a User-Agent header.
|
|
96
67
|
var getUserAgent = function () {
|
|
97
68
|
var version;
|
|
@@ -111,45 +82,70 @@ var getUserAgent = function () {
|
|
|
111
82
|
os.platform(), os.type(), os.release(), os.arch());
|
|
112
83
|
};
|
|
113
84
|
|
|
85
|
+
// Builds the undici dispatcher for this request: env-var proxy support
|
|
86
|
+
// (same env semantics the request package had, and the same explicit
|
|
87
|
+
// detection packages/socket-stream-client uses) and CAFILE support.
|
|
88
|
+
function buildDispatcher(url, undici) {
|
|
89
|
+
var proxy = process.env.HTTP_PROXY || process.env.http_proxy || null;
|
|
90
|
+
if (/^https/i.test(url)) {
|
|
91
|
+
proxy = process.env.HTTPS_PROXY || process.env.https_proxy || proxy;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var connect;
|
|
95
|
+
if (process.env.CAFILE) {
|
|
96
|
+
connect = { ca: files.readFile(process.env.CAFILE) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (proxy) {
|
|
100
|
+
return new undici.ProxyAgent(
|
|
101
|
+
connect ? { uri: proxy, connect } : { uri: proxy }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (connect) {
|
|
105
|
+
return new undici.Agent({ connect });
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Wraps a Node Readable request body in an async generator that reports
|
|
111
|
+
// upload progress per chunk.
|
|
112
|
+
async function* streamWithProgress(bodyStream, onChunk) {
|
|
113
|
+
for await (const chunk of bodyStream) {
|
|
114
|
+
onChunk(chunk.length);
|
|
115
|
+
yield chunk;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
114
118
|
|
|
115
119
|
var httpHelpers = exports;
|
|
116
120
|
Object.assign(exports, {
|
|
117
121
|
getUserAgent: getUserAgent,
|
|
118
122
|
|
|
119
|
-
// A
|
|
123
|
+
// A fetch-based replacement for the old request-package wrapper, with
|
|
124
|
+
// the same contract:
|
|
120
125
|
//
|
|
121
|
-
// -
|
|
122
|
-
//
|
|
126
|
+
// - Respects HTTP_PROXY/HTTPS_PROXY, sets a Meteor User-Agent, honors
|
|
127
|
+
// $CAFILE for a custom CA.
|
|
123
128
|
//
|
|
124
|
-
// -
|
|
129
|
+
// - Never follows redirects (fetch redirect: "manual"): redirects
|
|
130
|
+
// don't segregate cookies/headers by origin, and callers like the
|
|
131
|
+
// OAuth flow in auth.js depend on seeing the 302 themselves.
|
|
125
132
|
//
|
|
126
|
-
// -
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
133
|
+
// - Options: url (or a bare url string instead of the object), method,
|
|
134
|
+
// headers, qs (object appended to the url as a query string),
|
|
135
|
+
// json: true (parse the response body as JSON), encoding
|
|
136
|
+
// (null => body is a Buffer; anything else => utf8 string, the
|
|
137
|
+
// default), bodyStream (+ bodyStreamLength) for a streamed request
|
|
138
|
+
// body, outputStream to stream the response body into (body is not
|
|
139
|
+
// buffered then), progress (a progress tracker; incompatible with
|
|
140
|
+
// callback), timeout (idle ms between response chunks; default 60s,
|
|
141
|
+
// disable by passing a non-positive/non-number), useSessionHeader /
|
|
142
|
+
// useAuthHeader for the Meteor services session file.
|
|
130
143
|
//
|
|
131
|
-
// -
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// to parse it yourself. If there are multiple Set-Cookie headers
|
|
137
|
-
// for the same cookie it is unspecified which one you'll get.
|
|
138
|
-
//
|
|
139
|
-
// - You can provide a 'bodyStream' option which is a stream that
|
|
140
|
-
// will be used for the body of the request.
|
|
141
|
-
//
|
|
142
|
-
// - For authenticated Meteor Software services, you can set the
|
|
143
|
-
// 'useSessionHeader' and/or 'useAuthHeader' options (to true) to
|
|
144
|
-
// send X-Meteor-Session/X-Meteor-Auth headers using values from
|
|
145
|
-
// the session file.
|
|
146
|
-
//
|
|
147
|
-
// - forceSSL is always set to true. Always. And followRedirect is
|
|
148
|
-
// set to false since it doesn't understand origins (see comment
|
|
149
|
-
// in implementation).
|
|
150
|
-
//
|
|
151
|
-
// - An optional options.onRequest callback may be provided if the
|
|
152
|
-
// caller desires access to the request object.
|
|
144
|
+
// - Without a callback, returns a Promise of { response, body,
|
|
145
|
+
// setCookie }. response is a plain object: { statusCode, headers
|
|
146
|
+
// (lowercased plain object), request: { href } } -- the subset of
|
|
147
|
+
// the old request response our callers read. With a callback, calls
|
|
148
|
+
// callback(err, response, body) instead.
|
|
153
149
|
//
|
|
154
150
|
// NB: With useSessionHeader and useAuthHeader, this function will
|
|
155
151
|
// read *and possibly write to* the session file, so if you are
|
|
@@ -163,75 +159,41 @@ Object.assign(exports, {
|
|
|
163
159
|
options = _.clone(urlOrOptions);
|
|
164
160
|
}
|
|
165
161
|
|
|
166
|
-
var outputStream;
|
|
167
|
-
|
|
168
|
-
outputStream = options.outputStream;
|
|
169
|
-
delete options.outputStream;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
var bodyStream;
|
|
173
|
-
if (_.has(options, 'bodyStream')) {
|
|
174
|
-
bodyStream = options.bodyStream;
|
|
175
|
-
delete options.bodyStream;
|
|
176
|
-
}
|
|
162
|
+
var outputStream = options.outputStream;
|
|
163
|
+
var bodyStream = options.bodyStream;
|
|
177
164
|
|
|
178
165
|
// Body stream length for progress
|
|
179
166
|
var bodyStreamLength = 0;
|
|
180
167
|
if (_.has(options, 'bodyStreamLength')) {
|
|
181
168
|
bodyStreamLength = options.bodyStreamLength;
|
|
182
|
-
|
|
183
|
-
} else {
|
|
169
|
+
} else if (bodyStream) {
|
|
184
170
|
// Guess the body stream length as 1MB
|
|
185
171
|
// Hopefully if it's much bigger the caller will set it
|
|
186
172
|
// If it is much small, we will pleasantly surprise the user!
|
|
187
|
-
|
|
188
|
-
bodyStreamLength = 1024 * 1024;
|
|
189
|
-
}
|
|
173
|
+
bodyStreamLength = 1024 * 1024;
|
|
190
174
|
}
|
|
191
175
|
|
|
192
176
|
// Response length for progress
|
|
193
177
|
var responseLength = 128 * 1024;
|
|
194
178
|
if (_.has(options, 'responseLength')) {
|
|
195
179
|
responseLength = options.responseLength;
|
|
196
|
-
delete options.responseLength;
|
|
197
180
|
}
|
|
198
181
|
|
|
199
|
-
var progress = null;
|
|
200
|
-
if (
|
|
201
|
-
progress
|
|
202
|
-
delete options.progress;
|
|
203
|
-
|
|
204
|
-
if (callback) {
|
|
205
|
-
throw new Error("Not safe to use progress with callback");
|
|
206
|
-
}
|
|
182
|
+
var progress = options.progress || null;
|
|
183
|
+
if (progress && callback) {
|
|
184
|
+
throw new Error("Not safe to use progress with callback");
|
|
207
185
|
}
|
|
208
186
|
|
|
209
|
-
|
|
187
|
+
var headers = Object.assign({
|
|
210
188
|
'User-Agent': getUserAgent()
|
|
211
189
|
}, options.headers || {});
|
|
212
190
|
|
|
213
|
-
// This should never, ever be false, or else why are you using SSL?
|
|
214
|
-
options.forceSSL = true;
|
|
215
|
-
if (process.env.CAFILE) {
|
|
216
|
-
options.ca = files.readFile(process.env.CAFILE);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// followRedirect is very dangerous because request does not
|
|
220
|
-
// appear to segregate cookies by origin, so any cookies (and
|
|
221
|
-
// apparently headers as well, eg X-Meteor-Auth) sent on the
|
|
222
|
-
// original request could get forwarded to an unexpected domain in
|
|
223
|
-
// a redirect. This is almost certainly not something you ever
|
|
224
|
-
// want.
|
|
225
|
-
options.followRedirect = false;
|
|
226
|
-
|
|
227
191
|
var useSessionHeader = options.useSessionHeader;
|
|
228
|
-
delete options.useSessionHeader;
|
|
229
192
|
var useAuthHeader = options.useAuthHeader;
|
|
230
|
-
delete options.useAuthHeader;
|
|
231
193
|
if (useSessionHeader || useAuthHeader) {
|
|
232
194
|
var sessionHeader = auth.getSessionId(config.getAccountsDomain());
|
|
233
195
|
if (sessionHeader) {
|
|
234
|
-
|
|
196
|
+
headers['X-Meteor-Session'] = sessionHeader;
|
|
235
197
|
}
|
|
236
198
|
if (callback) {
|
|
237
199
|
throw new Error("session header can't be used with callback");
|
|
@@ -240,170 +202,197 @@ Object.assign(exports, {
|
|
|
240
202
|
if (useAuthHeader) {
|
|
241
203
|
var authHeader = auth.getSessionToken(config.getAccountsDomain());
|
|
242
204
|
if (authHeader) {
|
|
243
|
-
|
|
205
|
+
headers['X-Meteor-Auth'] = authHeader;
|
|
244
206
|
}
|
|
245
207
|
}
|
|
246
208
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
var setCookie = {};
|
|
257
|
-
_.each(response.headers["set-cookie"] || [], function (h) {
|
|
258
|
-
var match = h.match(/^([^=\s]+)=([^;\s]+)/);
|
|
259
|
-
if (match) {
|
|
260
|
-
setCookie[match[1]] = match[2];
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
if (useSessionHeader && _.has(response.headers, "x-meteor-session")) {
|
|
265
|
-
auth.setSessionId(config.getAccountsDomain(),
|
|
266
|
-
response.headers['x-meteor-session']);
|
|
267
|
-
}
|
|
209
|
+
// 60 seconds of idle time between response chunks; disable by
|
|
210
|
+
// passing anything other than a positive number, e.g. {timeout: null}.
|
|
211
|
+
var idleTimeoutMs = 60 * 1000 * timeoutScaleFactor;
|
|
212
|
+
if (_.has(options, "timeout")) {
|
|
213
|
+
idleTimeoutMs =
|
|
214
|
+
(typeof options.timeout === "number" && options.timeout > 0)
|
|
215
|
+
? options.timeout
|
|
216
|
+
: null;
|
|
217
|
+
}
|
|
268
218
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
219
|
+
var url = options.url;
|
|
220
|
+
if (options.qs && Object.keys(options.qs).length > 0) {
|
|
221
|
+
var search = new URLSearchParams();
|
|
222
|
+
Object.entries(options.qs).forEach(function ([k, v]) {
|
|
223
|
+
if (v !== undefined && v !== null) {
|
|
224
|
+
search.append(k, String(v));
|
|
225
|
+
}
|
|
275
226
|
});
|
|
227
|
+
var qsString = search.toString();
|
|
228
|
+
if (qsString) {
|
|
229
|
+
url += (url.includes('?') ? '&' : '?') + qsString;
|
|
230
|
+
}
|
|
276
231
|
}
|
|
277
232
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
233
|
+
var totalProgress = {
|
|
234
|
+
current: 0,
|
|
235
|
+
end: bodyStreamLength + responseLength,
|
|
236
|
+
done: false,
|
|
237
|
+
};
|
|
238
|
+
function reportProgress() {
|
|
239
|
+
if (progress && !totalProgress.done) {
|
|
240
|
+
progress.reportProgress(totalProgress);
|
|
241
|
+
}
|
|
287
242
|
}
|
|
288
243
|
|
|
289
|
-
|
|
290
|
-
// 60 seconds for timeout between initial response headers and data,
|
|
291
|
-
// and between chunks of data while reading the rest of the response.
|
|
292
|
-
options.timeout = 60 * 1000 * timeoutScaleFactor;
|
|
293
|
-
} else if (! (typeof options.timeout === "number" &&
|
|
294
|
-
options.timeout > 0)) {
|
|
295
|
-
// The timeout can be disabled by passing anything other than a
|
|
296
|
-
// positive number, e.g. { timeout: null }.
|
|
297
|
-
delete options.timeout;
|
|
298
|
-
}
|
|
244
|
+
Console.debug("Doing HTTP request: ", options.method || 'GET', url);
|
|
299
245
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
246
|
+
// undici, not Node's global fetch: we need dispatcher control
|
|
247
|
+
// (proxy/CA) and the same fetch implementation regardless of what
|
|
248
|
+
// the embedding process did to globalThis.fetch.
|
|
249
|
+
var undici = require('undici');
|
|
305
250
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
251
|
+
var promise = (async () => {
|
|
252
|
+
var controller = new AbortController();
|
|
253
|
+
var idleTimer = null;
|
|
254
|
+
function armIdleTimer() {
|
|
255
|
+
if (idleTimeoutMs == null) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (idleTimer) {
|
|
259
|
+
clearTimeout(idleTimer);
|
|
260
|
+
}
|
|
261
|
+
idleTimer = setTimeout(function () {
|
|
262
|
+
controller.abort(new Error(
|
|
263
|
+
"HTTP request idle-timed out after " + idleTimeoutMs + "ms: " + url
|
|
264
|
+
));
|
|
265
|
+
}, idleTimeoutMs);
|
|
266
|
+
// Don't hold the event loop open just for the timeout.
|
|
267
|
+
if (idleTimer.unref) {
|
|
268
|
+
idleTimer.unref();
|
|
323
269
|
}
|
|
324
270
|
}
|
|
325
271
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
var totalProgress = { current: 0, end: bodyStreamLength + responseLength, done: false };
|
|
334
|
-
|
|
335
|
-
if (bodyStream) {
|
|
336
|
-
var dest = req;
|
|
337
|
-
if (progress) {
|
|
338
|
-
dest = new WritableWithProgress(dest, function (n, done) {
|
|
339
|
-
if (!totalProgress.done) {
|
|
340
|
-
totalProgress.current += n;
|
|
341
|
-
progress.reportProgress(totalProgress);
|
|
342
|
-
}
|
|
272
|
+
var fetchBody;
|
|
273
|
+
if (bodyStream) {
|
|
274
|
+
var uploaded = 0;
|
|
275
|
+
fetchBody = streamWithProgress(bodyStream, function (n) {
|
|
276
|
+
uploaded += n;
|
|
277
|
+
totalProgress.current = Math.min(uploaded, bodyStreamLength);
|
|
278
|
+
reportProgress();
|
|
343
279
|
});
|
|
280
|
+
} else if (_.has(options, 'body')) {
|
|
281
|
+
fetchBody = options.body;
|
|
344
282
|
}
|
|
345
|
-
bodyStream.pipe(dest);
|
|
346
|
-
}
|
|
347
283
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
284
|
+
var res;
|
|
285
|
+
try {
|
|
286
|
+
armIdleTimer();
|
|
287
|
+
res = await undici.fetch(url, {
|
|
288
|
+
method: options.method || 'GET',
|
|
289
|
+
headers: headers,
|
|
290
|
+
body: fetchBody,
|
|
291
|
+
// See the redirect comment in this function's doc block.
|
|
292
|
+
redirect: 'manual',
|
|
293
|
+
signal: controller.signal,
|
|
294
|
+
dispatcher: buildDispatcher(url, undici),
|
|
295
|
+
...(bodyStream ? { duplex: 'half' } : null),
|
|
296
|
+
});
|
|
359
297
|
|
|
360
|
-
|
|
298
|
+
// Response shape: the subset of the old request-package response
|
|
299
|
+
// object our callers actually read.
|
|
300
|
+
var headerObj = {};
|
|
301
|
+
res.headers.forEach(function (value, key) {
|
|
302
|
+
headerObj[key.toLowerCase()] = value;
|
|
303
|
+
});
|
|
304
|
+
var setCookieList = typeof res.headers.getSetCookie === 'function'
|
|
305
|
+
? res.headers.getSetCookie()
|
|
306
|
+
: [];
|
|
307
|
+
if (setCookieList.length) {
|
|
308
|
+
headerObj['set-cookie'] = setCookieList;
|
|
361
309
|
}
|
|
362
|
-
|
|
363
|
-
|
|
310
|
+
var response = {
|
|
311
|
+
statusCode: res.status,
|
|
312
|
+
headers: headerObj,
|
|
313
|
+
request: { href: url },
|
|
314
|
+
};
|
|
364
315
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
316
|
+
// Stream the response body: to outputStream when given (body
|
|
317
|
+
// stays unbuffered), else into memory.
|
|
318
|
+
var collected = outputStream ? null : [];
|
|
319
|
+
var received = 0;
|
|
320
|
+
if (res.body) {
|
|
321
|
+
for await (const chunk of res.body) {
|
|
322
|
+
armIdleTimer();
|
|
323
|
+
var buf = Buffer.from(chunk);
|
|
324
|
+
received += buf.length;
|
|
325
|
+
if (outputStream) {
|
|
326
|
+
outputStream.write(buf);
|
|
327
|
+
} else {
|
|
328
|
+
collected.push(buf);
|
|
329
|
+
}
|
|
330
|
+
totalProgress.current = bodyStreamLength + received;
|
|
331
|
+
var contentLength = Number(headerObj['content-length']);
|
|
332
|
+
totalProgress.end = bodyStreamLength +
|
|
333
|
+
(contentLength > 0 ? contentLength : Math.max(received, responseLength));
|
|
334
|
+
reportProgress();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
totalProgress.done = true;
|
|
338
|
+
|
|
339
|
+
var body;
|
|
340
|
+
if (!outputStream) {
|
|
341
|
+
var bodyBuffer = Buffer.concat(collected);
|
|
342
|
+
var expected = Number(headerObj['content-length']);
|
|
343
|
+
if (expected > 0 && bodyBuffer.length < expected) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
"Expected " + expected + " bytes in request body " +
|
|
346
|
+
"but received only " + bodyBuffer.length);
|
|
347
|
+
}
|
|
348
|
+
body = options.encoding === null
|
|
349
|
+
? bodyBuffer
|
|
350
|
+
: bodyBuffer.toString('utf8');
|
|
351
|
+
if (options.json) {
|
|
352
|
+
body = body.length ? JSON.parse(body) : undefined;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
371
355
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
356
|
+
var setCookie = {};
|
|
357
|
+
setCookieList.forEach(function (h) {
|
|
358
|
+
var match = h.match(/^([^=\s]+)=([^;\s]+)/);
|
|
359
|
+
if (match) {
|
|
360
|
+
setCookie[match[1]] = match[2];
|
|
361
|
+
}
|
|
362
|
+
});
|
|
376
363
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
364
|
+
if (useSessionHeader && _.has(headerObj, "x-meteor-session")) {
|
|
365
|
+
auth.setSessionId(config.getAccountsDomain(),
|
|
366
|
+
headerObj['x-meteor-session']);
|
|
367
|
+
}
|
|
380
368
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
state.current = 0;
|
|
386
|
-
var contentLength = response.headers['content-length'];
|
|
387
|
-
if (contentLength) {
|
|
388
|
-
state.end = Number(contentLength);
|
|
369
|
+
return { response: response, body: body, setCookie: setCookie };
|
|
370
|
+
} finally {
|
|
371
|
+
if (idleTimer) {
|
|
372
|
+
clearTimeout(idleTimer);
|
|
389
373
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
374
|
+
if (progress) {
|
|
375
|
+
progress.reportProgressDone();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
})();
|
|
379
|
+
|
|
380
|
+
if (callback) {
|
|
381
|
+
promise.then(
|
|
382
|
+
function (result) {
|
|
383
|
+
callback(null, result.response, result.body);
|
|
384
|
+
},
|
|
385
|
+
function (err) {
|
|
386
|
+
callback(err);
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
return promise;
|
|
400
392
|
},
|
|
401
393
|
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
// (This has gone through a few refactors and it might be possible
|
|
406
|
-
// to fully roll it into httpHelpers.request() at this point.)
|
|
394
|
+
// Returns the response body or throws; network-level failures become
|
|
395
|
+
// files.OfflineError.
|
|
407
396
|
getUrl: async function (urlOrOptions) {
|
|
408
397
|
try {
|
|
409
398
|
var result = await httpHelpers.request(urlOrOptions);
|
|
@@ -426,10 +415,13 @@ Object.assign(exports, {
|
|
|
426
415
|
|
|
427
416
|
// More or less as above, except with support for multiple attempts per
|
|
428
417
|
// request and resuming on retries. This means if the connection is bad,
|
|
429
|
-
// we can sometimes complete a request, even if each individual attempt
|
|
430
|
-
// We only use this for package downloads.
|
|
431
|
-
//
|
|
432
|
-
//
|
|
418
|
+
// we can sometimes complete a request, even if each individual attempt
|
|
419
|
+
// fails. We only use this for package downloads.
|
|
420
|
+
//
|
|
421
|
+
// (Historical note: the request-package version of this had retry
|
|
422
|
+
// logic that could never fire -- its try/catch wrapped a Promise
|
|
423
|
+
// construction, so async rejections bypassed it entirely. This
|
|
424
|
+
// version actually retries.)
|
|
433
425
|
async getUrlWithResuming(urlOrOptions) {
|
|
434
426
|
const options = _.isObject(urlOrOptions) ? _.clone(urlOrOptions) : {
|
|
435
427
|
url: urlOrOptions,
|
|
@@ -446,50 +438,56 @@ Object.assign(exports, {
|
|
|
446
438
|
const masterProgress = options.progress;
|
|
447
439
|
const outputStream = new ConcatStream();
|
|
448
440
|
|
|
449
|
-
|
|
441
|
+
let triesRemaining = maxAttempts;
|
|
442
|
+
let result;
|
|
443
|
+
for (;;) {
|
|
444
|
+
const startAt = outputStream.size;
|
|
445
|
+
const attemptOptions = Object.assign({}, options, { outputStream });
|
|
450
446
|
if (startAt > 0) {
|
|
451
|
-
|
|
447
|
+
attemptOptions.headers = {
|
|
452
448
|
...options.headers,
|
|
453
|
-
Range: `bytes=${startAt}
|
|
449
|
+
Range: `bytes=${startAt}-`,
|
|
454
450
|
};
|
|
455
451
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
masterProgress.
|
|
459
|
-
options.progress = masterProgress.addChildTask({
|
|
460
|
-
title: masterProgress.title
|
|
452
|
+
if (masterProgress && masterProgress.addChildTask) {
|
|
453
|
+
attemptOptions.progress = masterProgress.addChildTask({
|
|
454
|
+
title: masterProgress.title,
|
|
461
455
|
});
|
|
462
456
|
}
|
|
463
457
|
|
|
464
458
|
try {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
459
|
+
result = await httpHelpers.request(attemptOptions);
|
|
460
|
+
// A server that ignored our Range restarted from byte 0 -- the
|
|
461
|
+
// stream got the full body appended after the partial prefix.
|
|
462
|
+
// Detect and keep only the fresh copy.
|
|
463
|
+
if (startAt > 0 && result.response.statusCode === 200) {
|
|
464
|
+
const full = outputStream.getBuffer().slice(startAt);
|
|
465
|
+
outputStream.reset();
|
|
466
|
+
outputStream.write(full);
|
|
467
|
+
}
|
|
468
|
+
break;
|
|
470
469
|
} catch (e) {
|
|
471
470
|
const size = outputStream.size;
|
|
472
|
-
const
|
|
473
|
-
const change = size - startAt;
|
|
471
|
+
const madeProgress = size > startAt;
|
|
474
472
|
|
|
475
|
-
if (
|
|
476
|
-
if (
|
|
477
|
-
|
|
473
|
+
if (madeProgress || triesRemaining > 1) {
|
|
474
|
+
if (!madeProgress) {
|
|
475
|
+
triesRemaining -= 1;
|
|
476
|
+
Console.debug(`Request failed, ${triesRemaining} attempts left`);
|
|
478
477
|
} else {
|
|
479
|
-
Console.debug(
|
|
478
|
+
Console.debug(
|
|
479
|
+
`Request failed after ${size - startAt} bytes, retrying`);
|
|
480
480
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
).then(() => attempt(triesRemaining - (useTry ? 1 : 0), size));
|
|
481
|
+
await new Promise(
|
|
482
|
+
resolve => setTimeout(resolve, retryDelaySecs * 1000));
|
|
483
|
+
continue;
|
|
485
484
|
}
|
|
486
485
|
|
|
487
486
|
Console.debug(`Request failed ${maxAttempts} times: failing`);
|
|
488
|
-
|
|
487
|
+
throw new files.OfflineError(e);
|
|
489
488
|
}
|
|
490
489
|
}
|
|
491
490
|
|
|
492
|
-
const result = await attempt();
|
|
493
491
|
const response = result.response;
|
|
494
492
|
if (response.statusCode >= 400 && response.statusCode < 600) {
|
|
495
493
|
const href = response.request.href;
|