@truenas/api-client 9.0.2 → 9.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 +47 -0
- package/dist/{chunk-PJVDHM2S.js → chunk-PEOXWWBS.js} +33 -15
- package/dist/chunk-PEOXWWBS.js.map +1 -0
- package/dist/{chunk-MJFFBJJE.cjs → chunk-WAVCPALL.cjs} +32 -14
- package/dist/chunk-WAVCPALL.cjs.map +1 -0
- package/dist/index.cjs +37 -37
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/testing/index.cjs +13 -13
- package/dist/testing/index.d.cts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -2
- package/dist/{truenas-auth-mechanism.enum-o8dxrPXL.d.cts → truenas-auth-mechanism.enum-Djr9ulY7.d.cts} +999 -17
- package/dist/{truenas-auth-mechanism.enum-o8dxrPXL.d.ts → truenas-auth-mechanism.enum-Djr9ulY7.d.ts} +999 -17
- package/package.json +2 -2
- package/dist/chunk-MJFFBJJE.cjs.map +0 -1
- package/dist/chunk-PJVDHM2S.js.map +0 -1
package/README.md
CHANGED
|
@@ -171,6 +171,30 @@ client you get. A named `version` skips discovery and also selects the
|
|
|
171
171
|
WebSocket endpoint, so name the version the appliance actually runs. The types
|
|
172
172
|
follow it when the version is a literal.
|
|
173
173
|
|
|
174
|
+
To be written against one version but run against whatever the appliance
|
|
175
|
+
serves, as an appliance's own UI is, keep the `version` and move the socket:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const client = await createTrueNasClient({
|
|
179
|
+
...options,
|
|
180
|
+
version: 'v28.0.0', // the surface you compile against
|
|
181
|
+
websocketPath: '/api/current', // the appliance's newest version, whichever it is
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The client then works against an older or newer release call for call; a
|
|
186
|
+
method or field that release lacks fails when it is called rather than at
|
|
187
|
+
compile time.
|
|
188
|
+
|
|
189
|
+
Each version's models are under its namespace, and its enums are values there
|
|
190
|
+
as well as types, so a request can be built from them:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { v28_0_0 } from '@truenas/api-client';
|
|
194
|
+
|
|
195
|
+
const type: v28_0_0.KeychainCredentialEntryType = v28_0_0.KeychainCredentialEntryType.SshKeyPair;
|
|
196
|
+
```
|
|
197
|
+
|
|
174
198
|
For code that must run against any supported release, use `client.ops`. It
|
|
175
199
|
offers the same calls on every version and maps them onto whatever that version
|
|
176
200
|
provides:
|
|
@@ -234,6 +258,29 @@ version stays the same, so a different version needs a new client:
|
|
|
234
258
|
client.connection.setEndpoint({ hostnames: ['10.0.0.5'], protocol: 'http:' });
|
|
235
259
|
```
|
|
236
260
|
|
|
261
|
+
An `interceptor` sees the connection's traffic, for a debug panel or for
|
|
262
|
+
mocking calls in a running app. `outgoing` gets every frame before it is
|
|
263
|
+
written, including the client's own `core.set_options` and `core.ping`, and
|
|
264
|
+
returning `false` keeps a frame from `send` off the socket. `injected$` frames
|
|
265
|
+
are read as if the appliance had sent them, one tick later. `incoming` gets
|
|
266
|
+
every frame read:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const mocked$ = new Subject<TrueNasMessage>();
|
|
270
|
+
const client = await createTrueNasClient({
|
|
271
|
+
uuid, hostnames, enabled: true,
|
|
272
|
+
interceptor: {
|
|
273
|
+
outgoing: (frame) => {
|
|
274
|
+
if (frame.method !== 'system.info') return;
|
|
275
|
+
mocked$.next({ jsonrpc: '2.0', id: frame.id, result: fakeInfo });
|
|
276
|
+
return false;
|
|
277
|
+
},
|
|
278
|
+
incoming: (frame) => console.debug('<-', frame),
|
|
279
|
+
injected$: mocked$,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
237
284
|
An appliance served without TLS needs `protocol: 'http:'`, which switches
|
|
238
285
|
discovery to `http` and the socket to `ws`. It describes the appliance, not the
|
|
239
286
|
page, and defaults to `https:`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BehaviorSubject, filter, switchMap, catchError, EMPTY, tap, map, finalize, take, Subject, distinctUntilChanged, of, startWith, concat, throwError, retry, race, timer, skip, takeUntil, shareReplay, interval, Observable, timeout, TimeoutError, toArray, from, defer, firstValueFrom,
|
|
1
|
+
import { BehaviorSubject, filter, switchMap, catchError, EMPTY, tap, map, finalize, take, Subject, distinctUntilChanged, of, startWith, concat, throwError, retry, race, timer, skip, takeUntil, shareReplay, merge, observeOn, asapScheduler, share, interval, Subscription, Observable, timeout, TimeoutError, toArray, from, defer, firstValueFrom, ignoreElements, takeWhile, skipWhile, pairwise } from 'rxjs';
|
|
2
2
|
import { webSocket } from 'rxjs/webSocket';
|
|
3
3
|
import { map as map$1, catchError as catchError$1, shareReplay as shareReplay$1 } from 'rxjs/operators';
|
|
4
4
|
|
|
@@ -3203,13 +3203,14 @@ function socketScheme(protocol) {
|
|
|
3203
3203
|
var tenSeconds = 10 * 1e3;
|
|
3204
3204
|
var twentySeconds = 20 * 1e3;
|
|
3205
3205
|
var TrueNasConnection = class {
|
|
3206
|
-
constructor(initialEnabled, hostnames, systemUuid, websocketPath, systemName, retryDelay = tenSeconds, maxRetry = 3, logger = noopLogger, protocol = "https:") {
|
|
3206
|
+
constructor(initialEnabled, hostnames, systemUuid, websocketPath, systemName, retryDelay = tenSeconds, maxRetry = 3, logger = noopLogger, protocol = "https:", interceptor) {
|
|
3207
3207
|
this.systemUuid = systemUuid;
|
|
3208
3208
|
this.websocketPath = websocketPath;
|
|
3209
3209
|
this.systemName = systemName;
|
|
3210
3210
|
this.retryDelay = retryDelay;
|
|
3211
3211
|
this.maxRetry = maxRetry;
|
|
3212
3212
|
this.logger = logger;
|
|
3213
|
+
this.interceptor = interceptor;
|
|
3213
3214
|
// compatibility properties
|
|
3214
3215
|
this.opened = new BehaviorSubject(false);
|
|
3215
3216
|
this.closed = new Subject();
|
|
@@ -3267,6 +3268,7 @@ var TrueNasConnection = class {
|
|
|
3267
3268
|
const initMessage = createJsonRpcMessage("core.set_options", [
|
|
3268
3269
|
{ legacy_jobs: false }
|
|
3269
3270
|
]);
|
|
3271
|
+
this.interceptor?.outgoing?.(initMessage);
|
|
3270
3272
|
connection.ws.next(initMessage);
|
|
3271
3273
|
return connection.ws.messages().pipe(
|
|
3272
3274
|
filter((msg) => msg.id === initMessage.id),
|
|
@@ -3365,22 +3367,27 @@ var TrueNasConnection = class {
|
|
|
3365
3367
|
map((conn) => conn.state === "error"),
|
|
3366
3368
|
takeUntil(this.closeConnection)
|
|
3367
3369
|
);
|
|
3368
|
-
/**
|
|
3369
|
-
|
|
3370
|
-
*/
|
|
3371
|
-
this.messages$ = this.ws$.pipe(
|
|
3370
|
+
/** What the appliance sends, on whichever socket is current. */
|
|
3371
|
+
this.socketMessages$ = this.ws$.pipe(
|
|
3372
3372
|
filter((ws) => ws !== null),
|
|
3373
3373
|
switchMap((ws) => ws.messages().pipe(
|
|
3374
3374
|
catchError(() => EMPTY)
|
|
3375
3375
|
))
|
|
3376
3376
|
);
|
|
3377
|
+
this.messages$ = interceptor?.injected$ ? merge(this.socketMessages$, interceptor.injected$.pipe(observeOn(asapScheduler), share())) : this.socketMessages$;
|
|
3377
3378
|
this.endpoint$.next(resolveEndpoint(hostnames, protocol));
|
|
3378
3379
|
this.ws$.pipe(
|
|
3379
3380
|
switchMap((ws) => ws ? interval(twentySeconds).pipe(map(() => ws)) : EMPTY),
|
|
3380
3381
|
takeUntil(this.closeConnection)
|
|
3381
3382
|
).subscribe((ws) => {
|
|
3382
|
-
|
|
3383
|
+
const ping = createJsonRpcMessage("core.ping");
|
|
3384
|
+
this.interceptor?.outgoing?.(ping);
|
|
3385
|
+
ws.next(ping);
|
|
3383
3386
|
});
|
|
3387
|
+
if (interceptor?.incoming) {
|
|
3388
|
+
const incoming = interceptor.incoming.bind(interceptor);
|
|
3389
|
+
this.messages$.pipe(takeUntil(this.closeConnection)).subscribe((message) => incoming(message));
|
|
3390
|
+
}
|
|
3384
3391
|
this.opened$.subscribe((val) => this.opened.next(val));
|
|
3385
3392
|
this.closed$.subscribe(() => this.closed.next());
|
|
3386
3393
|
this.hostname$.pipe(filter(Boolean)).subscribe((name) => this.hostname.next(name));
|
|
@@ -3449,6 +3456,9 @@ var TrueNasConnection = class {
|
|
|
3449
3456
|
* the next `closed$` emission.
|
|
3450
3457
|
*/
|
|
3451
3458
|
send(message) {
|
|
3459
|
+
if (this.interceptor?.outgoing?.(message) === false) {
|
|
3460
|
+
return Subscription.EMPTY;
|
|
3461
|
+
}
|
|
3452
3462
|
return this.ws$.pipe(
|
|
3453
3463
|
filter((ws) => ws !== null),
|
|
3454
3464
|
take(1)
|
|
@@ -3646,7 +3656,8 @@ var TrueNasApiClient = class {
|
|
|
3646
3656
|
this.reconnect.retryDelay,
|
|
3647
3657
|
this.reconnect.maxRetry,
|
|
3648
3658
|
this.logger,
|
|
3649
|
-
this.protocol
|
|
3659
|
+
this.protocol,
|
|
3660
|
+
this.reconnect.interceptor
|
|
3650
3661
|
);
|
|
3651
3662
|
}
|
|
3652
3663
|
/**
|
|
@@ -4306,6 +4317,11 @@ async function createTrueNasClient(opts) {
|
|
|
4306
4317
|
`Cannot create client for system ${uuid}: hostnames array is empty`
|
|
4307
4318
|
);
|
|
4308
4319
|
}
|
|
4320
|
+
if (opts.websocketPath !== void 0 && !opts.websocketPath.startsWith("/")) {
|
|
4321
|
+
throw new Error(
|
|
4322
|
+
`Cannot create client for system ${uuid}: websocketPath must be an absolute path, got '${opts.websocketPath}'.`
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4309
4325
|
logger.info("Creating versioned API client", {
|
|
4310
4326
|
uuid: uuid.slice(0, 8),
|
|
4311
4327
|
hostnames: hostnames.join(", "),
|
|
@@ -4335,7 +4351,7 @@ async function createTrueNasClient(opts) {
|
|
|
4335
4351
|
logger.info("API version supplied by the caller, skipping discovery", {
|
|
4336
4352
|
uuid: uuid.slice(0, 8),
|
|
4337
4353
|
version: known.version,
|
|
4338
|
-
websocketPath: known.websocketPath
|
|
4354
|
+
websocketPath: opts.websocketPath ?? known.websocketPath
|
|
4339
4355
|
});
|
|
4340
4356
|
return instantiateClientForVersion(known, opts, logger);
|
|
4341
4357
|
}
|
|
@@ -4354,7 +4370,7 @@ async function createTrueNasClient(opts) {
|
|
|
4354
4370
|
// of them anyway.
|
|
4355
4371
|
hostname: winner.hostname,
|
|
4356
4372
|
version: version.version,
|
|
4357
|
-
websocketPath: version.websocketPath
|
|
4373
|
+
websocketPath: opts.websocketPath ?? version.websocketPath
|
|
4358
4374
|
});
|
|
4359
4375
|
} catch (error) {
|
|
4360
4376
|
const errorMessage = errorMessageOrDefault(error, "Unknown error");
|
|
@@ -4480,6 +4496,7 @@ function clientClassFor(version) {
|
|
|
4480
4496
|
function instantiateClientForVersion(version, opts, logger) {
|
|
4481
4497
|
const { uuid, hostnames, enabled, systemName } = opts;
|
|
4482
4498
|
const versionKey = clientVersionKey(version);
|
|
4499
|
+
const endpoint = opts.websocketPath === void 0 ? version : { ...version, websocketPath: opts.websocketPath };
|
|
4483
4500
|
const Client = CLIENT_BY_VERSION_KEY[versionKey];
|
|
4484
4501
|
if (!Client) {
|
|
4485
4502
|
logger.error("No client implementation for version", {
|
|
@@ -4494,17 +4511,18 @@ function instantiateClientForVersion(version, opts, logger) {
|
|
|
4494
4511
|
logger.info(`Instantiating ${Client.name}`, {
|
|
4495
4512
|
uuid: uuid.slice(0, 8),
|
|
4496
4513
|
version: version.version,
|
|
4497
|
-
versionKey
|
|
4514
|
+
versionKey,
|
|
4515
|
+
websocketPath: endpoint.websocketPath
|
|
4498
4516
|
});
|
|
4499
4517
|
return new Client(
|
|
4500
4518
|
uuid,
|
|
4501
4519
|
hostnames,
|
|
4502
|
-
|
|
4520
|
+
endpoint,
|
|
4503
4521
|
enabled,
|
|
4504
4522
|
systemName,
|
|
4505
4523
|
logger,
|
|
4506
4524
|
opts.protocol,
|
|
4507
|
-
{ retryDelay: opts.retryDelay, maxRetry: opts.maxRetry }
|
|
4525
|
+
{ retryDelay: opts.retryDelay, maxRetry: opts.maxRetry, interceptor: opts.interceptor }
|
|
4508
4526
|
);
|
|
4509
4527
|
}
|
|
4510
4528
|
function errorMessageOrDefault(error, fallback) {
|
|
@@ -4524,5 +4542,5 @@ var UserRole = /* @__PURE__ */ ((UserRole2) => {
|
|
|
4524
4542
|
})(UserRole || {});
|
|
4525
4543
|
|
|
4526
4544
|
export { AppState, AuthError, AuthErrorCode, AuthResponseType, InvalidVersionResponseError, JobState, NoCompatibleVersionsError, SUPPORTED_API_VERSIONS, TrueNasApiClient, TrueNasApiClientV2510, TrueNasApiClientV27, TrueNasApiClientV28, TrueNasApiError, TrueNasAuthMechanism, TrueNasAuthenticator, TrueNasConnection, UserRole, VersionCompatibility, VersionDiscovery, VersionDiscoveryError, VersionDiscoveryNetworkError, VersionDiscoveryTimeoutError, VersionEndpointNotFoundError, VersionTooNewError, VersionTooOldError, clientClassFor, consoleLogger, createTrueNasClient, getApiErrorMessage, getCloseMessage, isJobFinished, noopLogger, parseApiVersion, policyViolationCloseCode, subscriptionName, v25_10_0_exports, v25_10_1_exports, v25_10_2_exports, v25_10_3_exports, v25_10_4_exports, v25_10_5_exports, v27_0_0_exports, v28_0_0_exports };
|
|
4527
|
-
//# sourceMappingURL=chunk-
|
|
4528
|
-
//# sourceMappingURL=chunk-
|
|
4545
|
+
//# sourceMappingURL=chunk-PEOXWWBS.js.map
|
|
4546
|
+
//# sourceMappingURL=chunk-PEOXWWBS.js.map
|