@truenas/api-client 3.0.3 → 3.0.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/README.md CHANGED
@@ -82,6 +82,49 @@ client.api.events('app.query').subscribe(event => {
82
82
  });
83
83
  ```
84
84
 
85
+ ### Reaching an appliance over http
86
+
87
+ By default the client discovers over `https://` and connects over `wss://`,
88
+ which is what an appliance serves. An appliance reached without TLS needs
89
+ `protocol`:
90
+
91
+ ```typescript
92
+ import type { ApplianceProtocol } from '@truenas/api-client';
93
+
94
+ const protocol: ApplianceProtocol =
95
+ location.protocol === 'http:' ? 'http:' : 'https:';
96
+
97
+ const client = await createTrueNasClient({
98
+ uuid, hostnames: [location.host], enabled: true, protocol,
99
+ });
100
+ ```
101
+
102
+ It selects both halves of the transport — `https:` gives `https` discovery and a
103
+ `wss` socket, `http:` gives `http` and `ws` — and defaults to `https:`, so
104
+ existing callers are unaffected.
105
+
106
+ `protocol` describes the **appliance**, not the page. Reading it from
107
+ `location.protocol` is right when the appliance serves the page, which is the
108
+ same-origin case this exists for. A page served from somewhere else — a dev
109
+ server on `http://localhost:5173` talking to an https appliance — must pass what
110
+ the *appliance* uses. Getting it wrong breaks both halves but reports only one:
111
+ discovery's `fetch` follows the redirect and looks fine, while the socket opens
112
+ `ws://`, meets the same redirect, and fails the handshake without naming the
113
+ scheme.
114
+
115
+ Omitting it against a plaintext appliance fails the other way, and more quietly.
116
+ Discovery tries `https://`, `fetch` rejects, and the factory cannot tell that
117
+ apart from the CORS block that v25.10.0 has on `/api/versions` — so it takes the
118
+ fallback and hands back a client pinned to `v25.10.0` on `/api/v25.10.0`, with
119
+ only a `logger.warn` to say so. Against a v26 or v27 box that is a wrong-version
120
+ client that looks configured. If the appliance is plaintext, say so.
121
+
122
+ Narrow rather than cast: `location.protocol` is a `string`, and it is genuinely
123
+ `file:` for a locally-opened page or `chrome-extension:` in an extension. Both
124
+ halves fall back to the encrypted scheme for anything off-contract, so a bad
125
+ value cannot downgrade the transport — but the compiler will not stop you
126
+ asserting one into this option, and it will not be the value you meant.
127
+
85
128
  ### Naming a version
86
129
 
87
130
  By default the version is discovered at runtime while the types are fixed at
package/dist/index.cjs CHANGED
@@ -694,11 +694,19 @@ var TrueNasSocket = class {
694
694
  }
695
695
  };
696
696
 
697
+ // src/types/transport.type.ts
698
+ function httpScheme(protocol) {
699
+ return protocol === "http:" ? "http:" : "https:";
700
+ }
701
+ function socketScheme(protocol) {
702
+ return protocol === "http:" ? "ws:" : "wss:";
703
+ }
704
+
697
705
  // src/connection/truenas-connection.ts
698
706
  var tenSeconds = 10 * 1e3;
699
707
  var twentySeconds = 20 * 1e3;
700
708
  var TrueNasConnection = class {
701
- constructor(initialEnabled, hostnames, systemUuid, websocketPath, systemName, retryDelay = tenSeconds, maxRetry = 3, logger = noopLogger) {
709
+ constructor(initialEnabled, hostnames, systemUuid, websocketPath, systemName, retryDelay = tenSeconds, maxRetry = 3, logger = noopLogger, protocol = "https:") {
702
710
  this.hostnames = hostnames;
703
711
  this.systemUuid = systemUuid;
704
712
  this.websocketPath = websocketPath;
@@ -706,6 +714,7 @@ var TrueNasConnection = class {
706
714
  this.retryDelay = retryDelay;
707
715
  this.maxRetry = maxRetry;
708
716
  this.logger = logger;
717
+ this.protocol = protocol;
709
718
  // compatibility properties
710
719
  this.opened = new rxjs.BehaviorSubject(false);
711
720
  this.closed = new rxjs.Subject();
@@ -902,7 +911,7 @@ var TrueNasConnection = class {
902
911
  * error if the connection is never established and will not complete until unsubscribed from or closed.
903
912
  */
904
913
  createSocket(hostname) {
905
- const url = `wss://${hostname}${this.websocketPath}`;
914
+ const url = `${socketScheme(this.protocol)}//${hostname}${this.websocketPath}`;
906
915
  let hasOpened = false;
907
916
  return new rxjs.Observable((subscriber) => {
908
917
  const ws = new TrueNasSocket({
@@ -3001,13 +3010,14 @@ function getWebSocketPath(version) {
3001
3010
 
3002
3011
  // src/client/truenas-api-client.ts
3003
3012
  var TrueNasApiClient = class {
3004
- constructor(uuid, hostnames, version, enabled, systemName, logger = noopLogger) {
3013
+ constructor(uuid, hostnames, version, enabled, systemName, logger = noopLogger, protocol = "https:") {
3005
3014
  this.uuid = uuid;
3006
3015
  this.hostnames = hostnames;
3007
3016
  this.version = version;
3008
3017
  this.enabled = enabled;
3009
3018
  this.systemName = systemName;
3010
3019
  this.logger = logger;
3020
+ this.protocol = protocol;
3011
3021
  this.connection = this.createConnection();
3012
3022
  this.authenticator = this.createAuthenticator();
3013
3023
  this.api = this.createApi();
@@ -3050,7 +3060,8 @@ var TrueNasApiClient = class {
3050
3060
  // retryDelay (use default)
3051
3061
  void 0,
3052
3062
  // maxRetry (use default)
3053
- this.logger
3063
+ this.logger,
3064
+ this.protocol
3054
3065
  );
3055
3066
  }
3056
3067
  /**
@@ -3444,14 +3455,18 @@ function hasErrorName(error, expected) {
3444
3455
  return typeof error === "object" && error !== null && "name" in error && error.name === expected;
3445
3456
  }
3446
3457
  var VersionDiscovery = class {
3447
- constructor(logger = noopLogger) {
3458
+ constructor(logger = noopLogger, protocol = "https:") {
3448
3459
  this.logger = logger;
3460
+ this.protocol = protocol;
3449
3461
  this.versionCache = /* @__PURE__ */ new Map();
3450
3462
  }
3463
+ versionsUrl(hostname) {
3464
+ return `${httpScheme(this.protocol)}//${hostname}/api/versions`;
3465
+ }
3451
3466
  /**
3452
3467
  * Discovers the API version for a given hostname.
3453
3468
  *
3454
- * Makes a GET request to `https://{hostname}/api/versions` and returns the latest
3469
+ * Makes a GET request to `{protocol}//{hostname}/api/versions` and returns the latest
3455
3470
  * compatible version. Results are cached per hostname; the cache entry is removed
3456
3471
  * on failure so the next call retries.
3457
3472
  *
@@ -3465,8 +3480,10 @@ var VersionDiscovery = class {
3465
3480
  this.logger.info("Version discovery cache hit", { hostname });
3466
3481
  return cached;
3467
3482
  }
3468
- const url = `https://${hostname}/api/versions`;
3469
- this.logger.info("Starting version discovery", { hostname, url });
3483
+ this.logger.info("Starting version discovery", {
3484
+ hostname,
3485
+ url: this.versionsUrl(hostname)
3486
+ });
3470
3487
  const discovery$ = rxjs.defer(() => rxjs.from(this.fetchVersions(hostname))).pipe(
3471
3488
  operators.map((versionStrings) => this.selectVersion(hostname, versionStrings)),
3472
3489
  operators.catchError((error) => {
@@ -3504,7 +3521,7 @@ var VersionDiscovery = class {
3504
3521
  * misfile as a network error. Validating here keeps it an `InvalidVersionResponseError`.
3505
3522
  */
3506
3523
  async fetchVersions(hostname) {
3507
- const url = `https://${hostname}/api/versions`;
3524
+ const url = this.versionsUrl(hostname);
3508
3525
  const controller = new AbortController();
3509
3526
  const timer2 = setTimeout(() => controller.abort(), discoveryTimeoutMs);
3510
3527
  try {
@@ -3677,7 +3694,7 @@ async function createTrueNasClient(opts) {
3677
3694
  });
3678
3695
  return instantiateClientForVersion(known, opts, logger);
3679
3696
  }
3680
- const versionDiscovery = new VersionDiscovery(logger);
3697
+ const versionDiscovery = new VersionDiscovery(logger, opts.protocol);
3681
3698
  let version;
3682
3699
  try {
3683
3700
  const winner = await discoverVersionFromAnyHostname(
@@ -3786,7 +3803,8 @@ function instantiateClientForVersion(version, opts, logger) {
3786
3803
  version,
3787
3804
  enabled,
3788
3805
  systemName,
3789
- logger
3806
+ logger,
3807
+ opts.protocol
3790
3808
  );
3791
3809
  }
3792
3810
  function errorMessageOrDefault(error, fallback) {