@hardfin/cli 0.1.0-dev.25 → 0.1.0-dev.26

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/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ process.stdout.on("error", (error) => {
8
8
  });
9
9
  const answer = await toQuickAnswer(process.argv.slice(2));
10
10
  if (answer === void 0) {
11
- const { toCli } = await import("./program-b3uLLsYy.js");
11
+ const { toCli } = await import("./program-C_9qi3K1.js");
12
12
  await toCli().parseAsync(process.argv);
13
13
  } else process.stdout.write(`${answer}\n`);
14
14
  /**
@@ -435,6 +435,111 @@ function writeFile(held) {
435
435
  renameSync(pending, path);
436
436
  }
437
437
  //#endregion
438
+ //#region src/http/reach.ts
439
+ /** Unreachable is a request that never reached a server, so there is no answer to report. */
440
+ var Unreachable = class extends Error {
441
+ constructor(message) {
442
+ super(message);
443
+ this.name = "Unreachable";
444
+ }
445
+ };
446
+ const OFFLINE = "Couldn't connect to Hardfin. Check your internet connection and try again.";
447
+ const INSECURE = "Couldn't connect to Hardfin securely, so nothing was sent. A work network or security software may be blocking the connection. If this keeps happening, contact your IT team.";
448
+ /** The reasons Node gives, by the code on the error behind fetch's own. */
449
+ const REASONS = {
450
+ ECONNREFUSED: {
451
+ message: OFFLINE,
452
+ detail: "connection refused"
453
+ },
454
+ ENOTFOUND: {
455
+ message: OFFLINE,
456
+ detail: "address not found"
457
+ },
458
+ EAI_AGAIN: {
459
+ message: OFFLINE,
460
+ detail: "address lookup failed"
461
+ },
462
+ ETIMEDOUT: {
463
+ message: OFFLINE,
464
+ detail: "timed out"
465
+ },
466
+ UND_ERR_CONNECT_TIMEOUT: {
467
+ message: OFFLINE,
468
+ detail: "timed out"
469
+ },
470
+ EHOSTUNREACH: {
471
+ message: OFFLINE,
472
+ detail: "no route to host"
473
+ },
474
+ ENETUNREACH: {
475
+ message: OFFLINE,
476
+ detail: "network unreachable"
477
+ },
478
+ ECONNRESET: {
479
+ message: OFFLINE,
480
+ detail: "connection closed"
481
+ },
482
+ UND_ERR_SOCKET: {
483
+ message: OFFLINE,
484
+ detail: "connection closed"
485
+ },
486
+ DEPTH_ZERO_SELF_SIGNED_CERT: {
487
+ message: INSECURE,
488
+ detail: "certificate not trusted"
489
+ },
490
+ SELF_SIGNED_CERT_IN_CHAIN: {
491
+ message: INSECURE,
492
+ detail: "certificate not trusted"
493
+ },
494
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: {
495
+ message: INSECURE,
496
+ detail: "certificate not trusted"
497
+ },
498
+ UNABLE_TO_GET_ISSUER_CERT_LOCALLY: {
499
+ message: INSECURE,
500
+ detail: "certificate not trusted"
501
+ },
502
+ CERT_HAS_EXPIRED: {
503
+ message: INSECURE,
504
+ detail: "certificate expired"
505
+ },
506
+ ERR_TLS_CERT_ALTNAME_INVALID: {
507
+ message: INSECURE,
508
+ detail: "certificate is for another host"
509
+ },
510
+ ERR_SSL_WRONG_VERSION_NUMBER: {
511
+ message: OFFLINE,
512
+ detail: "not an HTTPS server"
513
+ },
514
+ EPROTO: {
515
+ message: OFFLINE,
516
+ detail: "not an HTTPS server"
517
+ }
518
+ };
519
+ /** send is fetch, with a failure to connect reported in words anyone can act on. */
520
+ async function send(url, init) {
521
+ try {
522
+ return await fetch(url, init);
523
+ } catch (error) {
524
+ throw toUnreachable(url, error);
525
+ }
526
+ }
527
+ /**
528
+ * toUnreachable describes a request that could not connect. Fetch reports every such failure
529
+ * as "fetch failed", and the reason is on the error it wraps. The person reads a plain
530
+ * message, and the line after it names the reason and host for whoever helps them.
531
+ */
532
+ function toUnreachable(url, error) {
533
+ const host = new URL(url).host;
534
+ const cause = error instanceof Error ? error.cause : void 0;
535
+ const code = cause instanceof Error ? cause.code : void 0;
536
+ const reason = (error instanceof Error && error.name === "TimeoutError" ? REASONS["ETIMEDOUT"] : code === void 0 ? void 0 : REASONS[code]) ?? {
537
+ message: OFFLINE,
538
+ detail: cause instanceof Error ? cause.message : error instanceof Error ? error.message : String(error)
539
+ };
540
+ return new Unreachable(`${reason.message}\nDetails: ${reason.detail} (${host})`);
541
+ }
542
+ //#endregion
438
543
  //#region src/auth/metadata.ts
439
544
  const METADATA_PATH = "/.well-known/oauth-authorization-server";
440
545
  const AuthorizationServerMetadata = z.looseObject({
@@ -461,12 +566,7 @@ async function toMetadata(issuer) {
461
566
  const cached = toCached(issuer);
462
567
  if (cached) return cached;
463
568
  const url = `${issuer.replace(/\/+$/, "")}${METADATA_PATH}`;
464
- let response;
465
- try {
466
- response = await fetch(url, { headers: { Accept: "application/json" } });
467
- } catch (error) {
468
- throw new DiscoveryFailure(`${url} could not be reached: ${error instanceof Error ? error.message : String(error)}`);
469
- }
569
+ const response = await send(url, { headers: { Accept: "application/json" } });
470
570
  if (!response.ok) throw new DiscoveryFailure(`${url} answered ${response.status}, so this host publishes no authorization server`);
471
571
  const parsed = AuthorizationServerMetadata.safeParse(await response.json().catch(() => void 0));
472
572
  if (!parsed.success) throw new DiscoveryFailure(`${url} does not describe an authorization server`);
@@ -543,7 +643,7 @@ async function toTokensFromRefresh(tokenUrl, clientId, refreshToken) {
543
643
  }
544
644
  /** revoke tells the server to forget a token, so signing out reaches every machine. */
545
645
  async function revoke(revocationUrl, clientId, token) {
546
- await fetch(revocationUrl, {
646
+ await send(revocationUrl, {
547
647
  method: "POST",
548
648
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
549
649
  body: new URLSearchParams({
@@ -555,7 +655,7 @@ async function revoke(revocationUrl, clientId, token) {
555
655
  }
556
656
  /** toTokens asks the token endpoint, which answers a flat RFC 6749 body, not the envelope. */
557
657
  async function toTokens(url, form) {
558
- const response = await fetch(url, {
658
+ const response = await send(url, {
559
659
  method: "POST",
560
660
  headers: {
561
661
  "Content-Type": "application/x-www-form-urlencoded",
@@ -800,7 +900,7 @@ async function request(options) {
800
900
  Accept: options.downloads ? "*/*" : "application/json"
801
901
  };
802
902
  if (options.body !== void 0 && options.form === void 0) headers["Content-Type"] = "application/json";
803
- const response = await fetch(url, {
903
+ const response = await send(url, {
804
904
  method: options.method,
805
905
  headers,
806
906
  body: options.form ?? (options.body === void 0 ? void 0 : JSON.stringify(options.body))
@@ -1593,7 +1693,7 @@ const DeviceResponse = z.looseObject({
1593
1693
  });
1594
1694
  /** toDeviceAuthorization asks for a code a person can type on another machine. */
1595
1695
  async function toDeviceAuthorization(endpoint, clientId, scope) {
1596
- const response = await fetch(endpoint, {
1696
+ const response = await send(endpoint, {
1597
1697
  method: "POST",
1598
1698
  headers: {
1599
1699
  "Content-Type": "application/x-www-form-urlencoded",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardfin/cli",
3
- "version": "0.1.0-dev.25",
3
+ "version": "0.1.0-dev.26",
4
4
  "description": "Command line interface for the Hardfin API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hardfin, Inc.",