@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.
Files changed (22) hide show
  1. package/isopackets/combined/npm/node_modules/meteor/babel-compiler/node_modules/.package-lock.json +1 -1
  2. package/isopackets/combined/npm/node_modules/meteor/boilerplate-generator/node_modules/.package-lock.json +1 -1
  3. package/isopackets/combined/npm/node_modules/meteor/constraint-solver/node_modules/.package-lock.json +1 -1
  4. package/isopackets/combined/npm/node_modules/meteor/ddp-client/node_modules/.package-lock.json +1 -1
  5. package/isopackets/combined/npm/node_modules/meteor/ecmascript/node_modules/.package-lock.json +1 -1
  6. package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime/node_modules/.package-lock.json +1 -1
  7. package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime-client/node_modules/.package-lock.json +1 -1
  8. package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime-server/node_modules/.package-lock.json +1 -1
  9. package/isopackets/combined/npm/node_modules/meteor/fetch/node_modules/.package-lock.json +1 -1
  10. package/isopackets/combined/npm/node_modules/meteor/inter-process-messaging/node_modules/.package-lock.json +1 -1
  11. package/isopackets/combined/npm/node_modules/meteor/logging/node_modules/.package-lock.json +1 -1
  12. package/isopackets/combined/npm/node_modules/meteor/logic-solver/node_modules/.package-lock.json +1 -1
  13. package/isopackets/combined/npm/node_modules/meteor/meteor/node_modules/.package-lock.json +1 -1
  14. package/isopackets/combined/npm/node_modules/meteor/modules/node_modules/.package-lock.json +1 -1
  15. package/isopackets/combined/npm/node_modules/meteor/modules-runtime/node_modules/.package-lock.json +1 -1
  16. package/isopackets/combined/npm/node_modules/meteor/npm-mongo/node_modules/.package-lock.json +1 -1
  17. package/isopackets/combined/npm/node_modules/meteor/package-version-parser/node_modules/.package-lock.json +1 -1
  18. package/isopackets/combined/npm/node_modules/meteor/promise/node_modules/.package-lock.json +1 -1
  19. package/isopackets/combined/npm/node_modules/meteor/react-fast-refresh/node_modules/.package-lock.json +1 -1
  20. package/isopackets/combined/npm/node_modules/meteor/socket-stream-client/node_modules/.package-lock.json +1 -1
  21. package/package.json +3 -3
  22. package/tools/utils/http-helpers.js +283 -285
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-6xobneyfbqr",
2
+ "name": "package-new-1snkx7vjcuf",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-r3e8f88flkl",
2
+ "name": "package-new-4u9fx8mbxny",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-4pbying366l",
2
+ "name": "package-new-sm91z7t3z4o",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-w88s7v8a2tb",
2
+ "name": "package-new-gsvm5rcc2iq",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-f69i13ow6p7",
2
+ "name": "package-new-3gg4doz5i88",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-p1w5mkk0978",
2
+ "name": "package-new-1nb1rwir7xu",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1ib48dwuck5",
2
+ "name": "package-new-1jo8olhxkjm",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1157kjfj2gw",
2
+ "name": "package-new-2ybexym7dno",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1wg8ssdz0fmf",
2
+ "name": "package-new-i9b1vaovh2j",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1vnwfx4b68v",
2
+ "name": "package-new-dymdsziwayf",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-kid7k7quu7",
2
+ "name": "package-new-ffz4udz1rzl",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1a6dsn3qfc2",
2
+ "name": "package-new-1gj1i1e8erq",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-v6v3rjtfql",
2
+ "name": "package-new-1hynmr3gjvhf",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1lii7blq24r",
2
+ "name": "package-new-1ffagznc9qh",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1wg5bygb6ij",
2
+ "name": "package-new-e37v0zigxr6",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-uir4qgmp1sp",
2
+ "name": "package-new-wfebwy06fwm",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-461z05pva2w",
2
+ "name": "package-new-dbv0mrj9kh",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-23rno2x3x0o",
2
+ "name": "package-new-4rd6cn8q4z7",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1w81jifsunlg",
2
+ "name": "package-new-m44yzrtc8cq",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "package-new-1b9umwwq6uc",
2
+ "name": "package-new-c4g2z219k47",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maka/meteor-sdk",
3
- "version": "0.2.3",
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
- "request": "2.88.2",
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": "11.1.0",
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 wrapper around request with the following improvements:
123
+ // A fetch-based replacement for the old request-package wrapper, with
124
+ // the same contract:
120
125
  //
121
- // - It will respect proxy environment variables if present
122
- // (HTTP_PROXY or HTTPS_PROXY as appropriate).
126
+ // - Respects HTTP_PROXY/HTTPS_PROXY, sets a Meteor User-Agent, honors
127
+ // $CAFILE for a custom CA.
123
128
  //
124
- // - It will set a reasonable User-Agent header.
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
- // - If you omit the callback it will run synchronously. The return
127
- // value will be an object with keys 'response' and 'body' (with
128
- // the same meaning as the arguments to request's normal
129
- // callback), or it will throw.
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
- // - If Set-Cookie headers are present on the response, *and* you
132
- // are using a callback, it will parse the cookies and include
133
- // them as a setCookie attribute on the object passed to the
134
- // callback. setCookie is a simple map from cookie name to cookie
135
- // value. If you want expiration time and attributes you'll have
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
- if (_.has(options, 'outputStream')) {
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
- delete options.bodyStreamLength;
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
- if (bodyStream) {
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 (_.has(options, 'progress')) {
201
- progress = options.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
- options.headers = Object.assign({
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
- options.headers['X-Meteor-Session'] = sessionHeader;
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
- options.headers['X-Meteor-Auth'] = authHeader;
205
+ headers['X-Meteor-Auth'] = authHeader;
244
206
  }
245
207
  }
246
208
 
247
- var promise;
248
- if (! callback) {
249
- promise = new Promise(function (resolve, reject) {
250
- callback = function (err, response, body) {
251
- if (err) {
252
- reject(err);
253
- return;
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
- resolve({
270
- response: response,
271
- body: body,
272
- setCookie: setCookie
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
- // try to get proxy from environment.
279
- // similar code is in packages/ddp/stream_client_nodejs.js
280
- var proxy = process.env.HTTP_PROXY || process.env.http_proxy || null;
281
- // if we're going to an https url, try the https_proxy env variable first.
282
- if (/^https/i.test(options.url)) {
283
- proxy = process.env.HTTPS_PROXY || process.env.https_proxy || proxy;
284
- }
285
- if (proxy && !options.proxy) {
286
- options.proxy = proxy;
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
- if (! _.has(options, "timeout")) {
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
- let onRequest;
301
- if (_.has(options, "onRequest")) {
302
- onRequest = options.onRequest;
303
- delete options.onRequest;
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
- // request is the most heavy-weight of the tool's npm dependencies; don't
307
- // require it until we definitely need it.
308
- Console.debug("Doing HTTP request: ", options.method || 'GET', options.url);
309
- var request = require('request');
310
- var req = request(options, function (error, response, body) {
311
- if (! error &&
312
- response &&
313
- (typeof body === "string" ||
314
- Buffer.isBuffer(body))) {
315
- const contentLength = Number(response.headers["content-length"]);
316
- const actualLength = Buffer.byteLength(body);
317
-
318
- if (contentLength > 0 &&
319
- actualLength < contentLength) {
320
- error = new Error(
321
- "Expected " + contentLength + " bytes in request body " +
322
- "but received only " + actualLength);
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
- return callback.call(this, error, response, body);
327
- });
328
-
329
- if (_.isFunction(onRequest)) {
330
- onRequest(req);
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
- if (outputStream) {
349
- req.pipe(outputStream);
350
- }
351
-
352
- if (progress) {
353
- httpHelpers._addProgressEvents(req);
354
- req.on('progress', function (state) {
355
- if (!totalProgress.done) {
356
- totalProgress.current = bodyStreamLength + state.current;
357
- totalProgress.end = bodyStreamLength + state.end;
358
- totalProgress.done = state.done;
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
- progress.reportProgress(totalProgress);
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
- if (promise) {
366
- return promise.finally(() => progress && progress.reportProgressDone());
367
- } else {
368
- return req;
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
- // Adds progress callbacks to a request
373
- // Based on request-progress
374
- _addProgressEvents: function (request) {
375
- var state = {};
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
- var emitProgress = function () {
378
- request.emit('progress', state);
379
- };
364
+ if (useSessionHeader && _.has(headerObj, "x-meteor-session")) {
365
+ auth.setSessionId(config.getAccountsDomain(),
366
+ headerObj['x-meteor-session']);
367
+ }
380
368
 
381
- request
382
- .on('response', function (response) {
383
- state.end = undefined;
384
- state.done = false;
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
- emitProgress();
391
- })
392
- .on('data', function (data) {
393
- state.current += data.length;
394
- emitProgress();
395
- })
396
- .on('end', function (data) {
397
- state.done = true;
398
- emitProgress();
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
- // A synchronous wrapper around request(...) that returns the response "body"
403
- // or throws.
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 fails.
430
- // We only use this for package downloads. In theory we could use it for
431
- // all requests but that seems like overkill and it isn't well tested in
432
- // other scenarioes.
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
- function attempt(triesRemaining = maxAttempts, startAt = 0) {
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
- options.headers = {
447
+ attemptOptions.headers = {
452
448
  ...options.headers,
453
- Range: `bytes=${startAt}-`
449
+ Range: `bytes=${startAt}-`,
454
450
  };
455
451
  }
456
-
457
- if (masterProgress &&
458
- masterProgress.addChildTask) {
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
- return Promise.resolve(httpHelpers.request({
466
- outputStream,
467
- ...options,
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 useTry = size === startAt;
473
- const change = size - startAt;
471
+ const madeProgress = size > startAt;
474
472
 
475
- if (!useTry || triesRemaining > 0) {
476
- if (useTry) {
477
- Console.debug(`Request failed, ${triesRemaining - 1} attempts left`);
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(`Request failed after ${change} bytes, retrying`);
478
+ Console.debug(
479
+ `Request failed after ${size - startAt} bytes, retrying`);
480
480
  }
481
-
482
- return new Promise(
483
- resolve => setTimeout(resolve, retryDelaySecs * 1000)
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
- return Promise.reject(new files.OfflineError(e));
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;