peekinside 1.2.3 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peekinside",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "Debug Node.js from the inside: errors with the requests, queries, logs and jobs behind them. A free local dashboard, and encrypted monitoring for production",
5
5
  "main": "index.js",
6
6
  "types": "./index.d.ts",
@@ -56,7 +56,16 @@ process.stdin.on('data', (chunk) => { input += chunk; });
56
56
  process.stdin.on('end', () => {
57
57
  let job;
58
58
  try { job = JSON.parse(input); } catch (error) { process.exit(1); }
59
- fetch(job.url, { method: 'POST', headers: job.headers, body: job.body, signal: AbortSignal.timeout(${CRASH_SEND_TIMEOUT_MS - 500}) })
59
+ const signal = AbortSignal.timeout(${CRASH_SEND_TIMEOUT_MS - 500});
60
+ const post = (url) => fetch(url, { method: 'POST', headers: job.headers, body: job.body, redirect: 'manual', signal });
61
+ post(job.url)
62
+ .then((response) => {
63
+ const location = response.headers.get('location');
64
+ if ((response.status !== 307 && response.status !== 308) || !location) return response;
65
+ const target = new URL(location, job.url);
66
+ const ok = job.follow.hosts.includes(target.hostname) && job.follow.protocols.includes(target.protocol) && target.pathname === job.follow.path && (target.port === '' || target.port === job.follow.port);
67
+ return ok ? post(target.href) : response;
68
+ })
60
69
  .then((response) => process.exit(response.ok ? ${SENT} : response.status === 400 || response.status === 413 ? ${REFUSED} : 2))
61
70
  .catch(() => process.exit(1));
62
71
  });
@@ -287,13 +296,21 @@ export class CloudTransport extends BatchTransport {
287
296
  };
288
297
  }
289
298
 
290
- _post(body) {
291
- return fetch(this.endpoint, {
299
+ // Posts to the endpoint. A redirect to the site's twin (see redirectTarget) is followed here,
300
+ // with the API key, and remembered; fetch() would follow it but drop the key.
301
+ async _post(body) {
302
+ const post = (url) => fetch(url, {
292
303
  method: 'POST',
293
304
  headers: this._headers(),
294
305
  body,
306
+ redirect: 'manual',
295
307
  signal: AbortSignal.timeout(this.exiting ? EXIT_TIMEOUT_MS : REQUEST_TIMEOUT_MS)
296
308
  });
309
+ const response = await post(this.endpoint);
310
+ const moved = redirectTarget(response, this.endpoint);
311
+ if (!moved) return response;
312
+ this.endpoint = moved;
313
+ return post(moved);
297
314
  }
298
315
 
299
316
  // The newest queued records, sealed, within the crash budget; they leave the queue.
@@ -314,7 +331,7 @@ export class CloudTransport extends BatchTransport {
314
331
  // Posts from a child process and waits for it: the only way to send over the network
315
332
  // synchronously. The secret travels on stdin, never on the command line.
316
333
  _sendNow(body) {
317
- const input = JSON.stringify({ url: this.endpoint, headers: this._headers(), body });
334
+ const input = JSON.stringify({ url: this.endpoint, headers: this._headers(), body, follow: followRule(this.endpoint) });
318
335
  const env = process.platform === 'win32' && process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {};
319
336
  const result = spawnSync(process.execPath, ['-e', SENDER], {
320
337
  input,
@@ -395,3 +412,39 @@ function listFor(record) {
395
412
  if (record instanceof LogRecord) return 'logs';
396
413
  return 'events';
397
414
  }
415
+
416
+ /**
417
+ * Where a 307 or 308 answer from PeekInside Cloud moves its endpoint, when the API key may follow
418
+ * it there, or null. Hosting often sends a site's bare domain to its www name, or back (Vercel
419
+ * does by default), and fetch() drops the Authorization header on such a redirect, so it is
420
+ * followed by hand, and only to the same path on the same host or its www twin, never downgrading
421
+ * https. Anywhere else, the answer stands (and the batch is retried later).
422
+ *
423
+ * @param {Response} response
424
+ * @param {string} from - The URL that answered
425
+ * @returns {string|null}
426
+ */
427
+ export function redirectTarget(response, from) {
428
+ if (response.status !== 307 && response.status !== 308) return null;
429
+ const location = response.headers.get('location');
430
+ if (!location) return null;
431
+ let target;
432
+ try {
433
+ target = new URL(location, from);
434
+ } catch (error) {
435
+ return null;
436
+ }
437
+ const rule = followRule(from);
438
+ const ok = rule.hosts.includes(target.hostname) && rule.protocols.includes(target.protocol) && target.pathname === rule.path && (target.port === '' || target.port === rule.port);
439
+ return ok ? target.href : null;
440
+ }
441
+
442
+ // What redirectTarget accepts, as data, so the crash sender's child process applies the same rule.
443
+ function followRule(from) {
444
+ const url = new URL(from);
445
+ const host = url.hostname;
446
+ const named = host.includes('.') && !host.startsWith('[') && !/^[\d.]+$/.test(host) && host !== 'localhost' && !host.endsWith('.localhost');
447
+ const hosts = [host];
448
+ if (named) hosts.push(host.startsWith('www.') ? host.slice(4) : `www.${host}`);
449
+ return { hosts, protocols: url.protocol === 'http:' ? ['http:', 'https:'] : [url.protocol], path: url.pathname, port: url.port };
450
+ }