@truenas/api-client 9.0.5 → 9.0.6
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 +15 -0
- package/dist/{chunk-A2RDZXMF.js → chunk-R4VP3HBB.js} +243 -6
- package/dist/chunk-R4VP3HBB.js.map +1 -0
- package/dist/{chunk-KME2PLIQ.cjs → chunk-UOTSHE2J.cjs} +243 -6
- package/dist/chunk-UOTSHE2J.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-DXwKN4R6.d.cts → truenas-auth-mechanism.enum-CtU2b15s.d.cts} +57 -4
- package/dist/{truenas-auth-mechanism.enum-DXwKN4R6.d.ts → truenas-auth-mechanism.enum-CtU2b15s.d.ts} +57 -4
- package/package.json +1 -1
- package/dist/chunk-A2RDZXMF.js.map +0 -1
- package/dist/chunk-KME2PLIQ.cjs.map +0 -1
|
@@ -3177,6 +3177,157 @@ function getWebSocketError(code) {
|
|
|
3177
3177
|
return "Network connection failed - Check IP address and connectivity";
|
|
3178
3178
|
}
|
|
3179
3179
|
}
|
|
3180
|
+
var CallLimiter = class {
|
|
3181
|
+
constructor(limit, write) {
|
|
3182
|
+
this.limit = limit;
|
|
3183
|
+
this.write = write;
|
|
3184
|
+
/**
|
|
3185
|
+
* Each request awaiting an answer, with the handle `write` gave for it. That
|
|
3186
|
+
* handle closes once the frame is on a socket: until then the connection is
|
|
3187
|
+
* still holding it for one, and giving up on it takes it back.
|
|
3188
|
+
*/
|
|
3189
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
3190
|
+
this.waiting = [];
|
|
3191
|
+
/**
|
|
3192
|
+
* Calls that were waiting when a socket was lost, kept apart from `waiting`
|
|
3193
|
+
* until `release()`. A call sent after the loss, the sign-in among them, then
|
|
3194
|
+
* never waits behind one: nothing would write it, and `release()` waits on it.
|
|
3195
|
+
*/
|
|
3196
|
+
this.heldCalls = [];
|
|
3197
|
+
}
|
|
3198
|
+
/** Requests written or being written, and not answered yet. */
|
|
3199
|
+
get inFlightCount() {
|
|
3200
|
+
return this.inFlight.size;
|
|
3201
|
+
}
|
|
3202
|
+
/** Requests waiting for a place, held ones included. */
|
|
3203
|
+
get waitingCount() {
|
|
3204
|
+
return this.waiting.length + this.heldCalls.length;
|
|
3205
|
+
}
|
|
3206
|
+
/** Whether calls are being kept from a lost socket until `release()`. */
|
|
3207
|
+
get held() {
|
|
3208
|
+
return this.heldCalls.length > 0;
|
|
3209
|
+
}
|
|
3210
|
+
/**
|
|
3211
|
+
* Sends `message` now if there is room, or queues it. Frames that are not
|
|
3212
|
+
* requests pass straight through. Unsubscribing the returned handle takes
|
|
3213
|
+
* the call back if it has not been written yet.
|
|
3214
|
+
*
|
|
3215
|
+
* New calls are not held back by a lost socket, only by the limit: the frames
|
|
3216
|
+
* that sign a new socket in come through here, and the held calls are
|
|
3217
|
+
* waiting on exactly those.
|
|
3218
|
+
*/
|
|
3219
|
+
send(message) {
|
|
3220
|
+
if (!isRequest(message)) {
|
|
3221
|
+
return this.write(message);
|
|
3222
|
+
}
|
|
3223
|
+
const call = { id: message.id, message, handle: new rxjs.Subscription() };
|
|
3224
|
+
if (this.inFlight.size < this.limit) {
|
|
3225
|
+
this.start(call);
|
|
3226
|
+
return call.handle;
|
|
3227
|
+
}
|
|
3228
|
+
this.waiting.push(call);
|
|
3229
|
+
call.handle.add(() => {
|
|
3230
|
+
remove(this.waiting, call);
|
|
3231
|
+
remove(this.heldCalls, call);
|
|
3232
|
+
});
|
|
3233
|
+
return call.handle;
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* Counts a request the connection wrote to a socket itself, such as its
|
|
3237
|
+
* keepalive `core.ping`, or returns `false` without counting it when there is
|
|
3238
|
+
* no room. A request written that way is not the limiter's to queue: the
|
|
3239
|
+
* caller decides what not sending it means.
|
|
3240
|
+
*/
|
|
3241
|
+
tryTrack(id) {
|
|
3242
|
+
if (this.inFlight.size >= this.limit) {
|
|
3243
|
+
return false;
|
|
3244
|
+
}
|
|
3245
|
+
this.inFlight.set(id, rxjs.Subscription.EMPTY);
|
|
3246
|
+
return true;
|
|
3247
|
+
}
|
|
3248
|
+
/** Frees the place of the request this frame answers, if it is one. */
|
|
3249
|
+
received(message) {
|
|
3250
|
+
if (message.id === void 0 || message.id === null || message.method) {
|
|
3251
|
+
return;
|
|
3252
|
+
}
|
|
3253
|
+
this.finish(String(message.id));
|
|
3254
|
+
}
|
|
3255
|
+
/**
|
|
3256
|
+
* What was written on the lost socket is lost with it, answers included, so
|
|
3257
|
+
* those places are freed. A frame the connection has not written yet is still
|
|
3258
|
+
* its to send on the next socket, so it stays counted. Calls still waiting are
|
|
3259
|
+
* held until `release()`: the new socket starts signed out, and a call sent on
|
|
3260
|
+
* it before the session is back is refused.
|
|
3261
|
+
*/
|
|
3262
|
+
socketLost() {
|
|
3263
|
+
this.inFlight.forEach((sending, id) => {
|
|
3264
|
+
if (sending.closed) {
|
|
3265
|
+
this.inFlight.delete(id);
|
|
3266
|
+
}
|
|
3267
|
+
});
|
|
3268
|
+
this.heldCalls.push(...this.waiting.splice(0));
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Lets held calls go out again, once the session they were made in is back.
|
|
3272
|
+
* They were sent before anything still waiting, so they go first.
|
|
3273
|
+
*/
|
|
3274
|
+
release() {
|
|
3275
|
+
this.waiting.unshift(...this.heldCalls.splice(0));
|
|
3276
|
+
this.startWaiting();
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* Takes back every call held from a lost socket, unsent, and returns their
|
|
3280
|
+
* ids so the caller can answer them. Does nothing while nothing is held.
|
|
3281
|
+
*/
|
|
3282
|
+
takeHeld() {
|
|
3283
|
+
return this.heldCalls.splice(0).map(({ id }) => id);
|
|
3284
|
+
}
|
|
3285
|
+
// The three below call each other in a ring.
|
|
3286
|
+
start({ id, message, handle }) {
|
|
3287
|
+
this.inFlight.set(id, rxjs.Subscription.EMPTY);
|
|
3288
|
+
let sending;
|
|
3289
|
+
try {
|
|
3290
|
+
sending = this.write(message);
|
|
3291
|
+
} catch (error) {
|
|
3292
|
+
this.finish(id);
|
|
3293
|
+
throw error;
|
|
3294
|
+
}
|
|
3295
|
+
if (!this.inFlight.has(id)) {
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
this.inFlight.set(id, sending);
|
|
3299
|
+
handle.add(() => {
|
|
3300
|
+
const isWritten = sending.closed;
|
|
3301
|
+
sending.unsubscribe();
|
|
3302
|
+
if (!isWritten && this.inFlight.get(id) === sending) {
|
|
3303
|
+
this.finish(id);
|
|
3304
|
+
}
|
|
3305
|
+
});
|
|
3306
|
+
}
|
|
3307
|
+
startWaiting() {
|
|
3308
|
+
while (this.inFlight.size < this.limit) {
|
|
3309
|
+
const next = this.waiting.shift();
|
|
3310
|
+
if (!next) {
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
this.start(next);
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
finish(id) {
|
|
3317
|
+
if (this.inFlight.delete(id)) {
|
|
3318
|
+
this.startWaiting();
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
function remove(calls, call) {
|
|
3323
|
+
const index = calls.indexOf(call);
|
|
3324
|
+
if (index >= 0) {
|
|
3325
|
+
calls.splice(index, 1);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
function isRequest(message) {
|
|
3329
|
+
return message.id !== void 0 && message.id !== null && Boolean(message.method);
|
|
3330
|
+
}
|
|
3180
3331
|
var TrueNasSocket = class {
|
|
3181
3332
|
constructor(config) {
|
|
3182
3333
|
this.config = config;
|
|
@@ -3205,7 +3356,7 @@ function socketScheme(protocol) {
|
|
|
3205
3356
|
var tenSeconds = 10 * 1e3;
|
|
3206
3357
|
var twentySeconds = 20 * 1e3;
|
|
3207
3358
|
var TrueNasConnection = class {
|
|
3208
|
-
constructor(initialEnabled, hostnames, systemUuid, websocketPath, systemName, retryDelay = tenSeconds, maxRetry = 3, logger = noopLogger, protocol = "https:", interceptor) {
|
|
3359
|
+
constructor(initialEnabled, hostnames, systemUuid, websocketPath, systemName, retryDelay = tenSeconds, maxRetry = 3, logger = noopLogger, protocol = "https:", interceptor, maxConcurrentCalls) {
|
|
3209
3360
|
this.systemUuid = systemUuid;
|
|
3210
3361
|
this.websocketPath = websocketPath;
|
|
3211
3362
|
this.systemName = systemName;
|
|
@@ -3213,6 +3364,7 @@ var TrueNasConnection = class {
|
|
|
3213
3364
|
this.maxRetry = maxRetry;
|
|
3214
3365
|
this.logger = logger;
|
|
3215
3366
|
this.interceptor = interceptor;
|
|
3367
|
+
this.maxConcurrentCalls = maxConcurrentCalls;
|
|
3216
3368
|
// compatibility properties
|
|
3217
3369
|
this.opened = new rxjs.BehaviorSubject(false);
|
|
3218
3370
|
this.closed = new rxjs.Subject();
|
|
@@ -3376,16 +3528,39 @@ var TrueNasConnection = class {
|
|
|
3376
3528
|
rxjs.catchError(() => rxjs.EMPTY)
|
|
3377
3529
|
))
|
|
3378
3530
|
);
|
|
3379
|
-
|
|
3531
|
+
/** Answers given here, to calls ended without being sent. See `refuseHeldCalls()`. */
|
|
3532
|
+
this.refusals$ = new rxjs.Subject();
|
|
3533
|
+
if (maxConcurrentCalls !== void 0 && !(Number.isInteger(maxConcurrentCalls) && maxConcurrentCalls > 0)) {
|
|
3534
|
+
throw new Error(`maxConcurrentCalls must be a positive integer, got ${String(maxConcurrentCalls)}`);
|
|
3535
|
+
}
|
|
3536
|
+
this.limiter = maxConcurrentCalls === void 0 ? null : new CallLimiter(maxConcurrentCalls, (message) => this.writeToNextSocket(message));
|
|
3537
|
+
this.messages$ = interceptor?.injected$ ? rxjs.merge(this.socketMessages$, this.refusals$, interceptor.injected$.pipe(rxjs.observeOn(rxjs.asapScheduler), rxjs.share())) : rxjs.merge(this.socketMessages$, this.refusals$);
|
|
3380
3538
|
this.endpoint$.next(resolveEndpoint(hostnames, protocol));
|
|
3381
3539
|
this.ws$.pipe(
|
|
3382
3540
|
rxjs.switchMap((ws) => ws ? rxjs.interval(twentySeconds).pipe(rxjs.map(() => ws)) : rxjs.EMPTY),
|
|
3383
3541
|
rxjs.takeUntil(this.closeConnection)
|
|
3384
3542
|
).subscribe((ws) => {
|
|
3385
3543
|
const ping = createJsonRpcMessage("core.ping");
|
|
3544
|
+
if (this.limiter && !this.limiter.tryTrack(ping.id ?? "")) {
|
|
3545
|
+
this.logger.debug("Skipping core.ping: every concurrent call place is taken");
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
3386
3548
|
this.interceptor?.outgoing?.(ping);
|
|
3387
3549
|
ws.next(ping);
|
|
3388
3550
|
});
|
|
3551
|
+
if (this.limiter) {
|
|
3552
|
+
const limiter = this.limiter;
|
|
3553
|
+
this.socketMessages$.pipe(rxjs.takeUntil(this.closeConnection)).subscribe((message) => {
|
|
3554
|
+
limiter.received(message);
|
|
3555
|
+
});
|
|
3556
|
+
let wasOpen = false;
|
|
3557
|
+
this.opened$.pipe(rxjs.takeUntil(this.closeConnection)).subscribe((isOpen) => {
|
|
3558
|
+
if (wasOpen && !isOpen) {
|
|
3559
|
+
limiter.socketLost();
|
|
3560
|
+
}
|
|
3561
|
+
wasOpen = isOpen;
|
|
3562
|
+
});
|
|
3563
|
+
}
|
|
3389
3564
|
if (interceptor?.incoming) {
|
|
3390
3565
|
const incoming = interceptor.incoming.bind(interceptor);
|
|
3391
3566
|
this.messages$.pipe(rxjs.takeUntil(this.closeConnection)).subscribe((message) => incoming(message));
|
|
@@ -3456,11 +3631,46 @@ var TrueNasConnection = class {
|
|
|
3456
3631
|
* sends a message over the current websocket OR queues it to send once
|
|
3457
3632
|
* the next websocket is opened. messages sent via `send` will not be lost until after
|
|
3458
3633
|
* the next `closed$` emission.
|
|
3634
|
+
*
|
|
3635
|
+
* With `maxConcurrentCalls` set, a request beyond the limit waits for an
|
|
3636
|
+
* answer to make room. Unsubscribing the returned handle takes back a frame
|
|
3637
|
+
* not written yet, waiting or queued.
|
|
3459
3638
|
*/
|
|
3460
3639
|
send(message) {
|
|
3461
3640
|
if (this.interceptor?.outgoing?.(message) === false) {
|
|
3462
3641
|
return rxjs.Subscription.EMPTY;
|
|
3463
3642
|
}
|
|
3643
|
+
return this.limiter ? this.limiter.send(message) : this.writeToNextSocket(message);
|
|
3644
|
+
}
|
|
3645
|
+
/**
|
|
3646
|
+
* Lets calls held from a lost socket go out again. The client calls this
|
|
3647
|
+
* whenever the session is authenticated, so a consumer does not need to;
|
|
3648
|
+
* a no-op without `maxConcurrentCalls`, or with nothing held.
|
|
3649
|
+
*
|
|
3650
|
+
* Calls waiting for a place when a socket is lost are held, not sent on the
|
|
3651
|
+
* next one: a new socket starts signed out, so middleware would refuse them
|
|
3652
|
+
* with `ENOTAUTHENTICATED`.
|
|
3653
|
+
*/
|
|
3654
|
+
releaseHeldCalls() {
|
|
3655
|
+
this.limiter?.release();
|
|
3656
|
+
}
|
|
3657
|
+
/**
|
|
3658
|
+
* Ends every call held from a lost socket without sending it, answering each
|
|
3659
|
+
* on `messages()` with the refusal middleware would have given it on a socket
|
|
3660
|
+
* that is not signed in: `ENOTAUTHENTICATED`. Returns how many it ended.
|
|
3661
|
+
*
|
|
3662
|
+
* For an app whose next sign-in may be a different user than the one who made
|
|
3663
|
+
* those calls, e.g. a password prompt after the session was lost. Call it
|
|
3664
|
+
* before that sign-in; otherwise the calls go out once it succeeds. A no-op
|
|
3665
|
+
* while nothing is held, so a healthy session's waiting calls are not touched.
|
|
3666
|
+
*/
|
|
3667
|
+
refuseHeldCalls() {
|
|
3668
|
+
const ids = this.limiter?.takeHeld() ?? [];
|
|
3669
|
+
ids.forEach((id) => this.refusals$.next(notAuthenticatedRefusal(id)));
|
|
3670
|
+
return ids.length;
|
|
3671
|
+
}
|
|
3672
|
+
/** Writes to the current socket, or to the next one once it opens. */
|
|
3673
|
+
writeToNextSocket(message) {
|
|
3464
3674
|
return this.ws$.pipe(
|
|
3465
3675
|
rxjs.filter((ws) => ws !== null),
|
|
3466
3676
|
rxjs.take(1)
|
|
@@ -3604,6 +3814,22 @@ var makeConnectionError = (message, hostname, closeCode, closeReason) => ({
|
|
|
3604
3814
|
var isTerminalClose = (err) => err?.closeCode === policyViolationCloseCode;
|
|
3605
3815
|
var resolveEndpoint = (hostnames, protocol) => Object.freeze({ hostnames: Object.freeze([...hostnames]), protocol });
|
|
3606
3816
|
var sameEndpoint = (a, b) => a.protocol === b.protocol && a.hostnames.length === b.hostnames.length && a.hostnames.every((hostname, i) => hostname === b.hostnames[i]);
|
|
3817
|
+
var notAuthenticatedRefusal = (id) => ({
|
|
3818
|
+
jsonrpc: "2.0",
|
|
3819
|
+
id,
|
|
3820
|
+
error: {
|
|
3821
|
+
code: -32001,
|
|
3822
|
+
message: "Method call error",
|
|
3823
|
+
data: {
|
|
3824
|
+
// `ErrnoMixin.ENOTAUTHENTICATED` in truenas_api_client.
|
|
3825
|
+
error: 207,
|
|
3826
|
+
errname: "ENOTAUTHENTICATED",
|
|
3827
|
+
reason: "The session this call was made in ended before it could be sent.",
|
|
3828
|
+
extra: null,
|
|
3829
|
+
trace: null
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
});
|
|
3607
3833
|
var closedConnection = { state: "closed" };
|
|
3608
3834
|
|
|
3609
3835
|
// src/client/truenas-api-client.ts
|
|
@@ -3621,6 +3847,11 @@ var TrueNasApiClient = class {
|
|
|
3621
3847
|
this.authenticator = this.createAuthenticator();
|
|
3622
3848
|
this.api = this.createApi();
|
|
3623
3849
|
this.ops = this.createOperations();
|
|
3850
|
+
this.authenticator.authenticated$.pipe(
|
|
3851
|
+
rxjs.distinctUntilChanged(),
|
|
3852
|
+
rxjs.filter(Boolean),
|
|
3853
|
+
rxjs.takeUntil(this.connection.closeConnection)
|
|
3854
|
+
).subscribe(() => this.connection.releaseHeldCalls());
|
|
3624
3855
|
}
|
|
3625
3856
|
/**
|
|
3626
3857
|
* Get current connection status.
|
|
@@ -3659,7 +3890,8 @@ var TrueNasApiClient = class {
|
|
|
3659
3890
|
this.reconnect.maxRetry,
|
|
3660
3891
|
this.logger,
|
|
3661
3892
|
this.protocol,
|
|
3662
|
-
this.reconnect.interceptor
|
|
3893
|
+
this.reconnect.interceptor,
|
|
3894
|
+
this.reconnect.maxConcurrentCalls
|
|
3663
3895
|
);
|
|
3664
3896
|
}
|
|
3665
3897
|
/**
|
|
@@ -4524,7 +4756,12 @@ function instantiateClientForVersion(version, opts, logger) {
|
|
|
4524
4756
|
systemName,
|
|
4525
4757
|
logger,
|
|
4526
4758
|
opts.protocol,
|
|
4527
|
-
{
|
|
4759
|
+
{
|
|
4760
|
+
retryDelay: opts.retryDelay,
|
|
4761
|
+
maxRetry: opts.maxRetry,
|
|
4762
|
+
interceptor: opts.interceptor,
|
|
4763
|
+
maxConcurrentCalls: opts.maxConcurrentCalls
|
|
4764
|
+
}
|
|
4528
4765
|
);
|
|
4529
4766
|
}
|
|
4530
4767
|
function errorMessageOrDefault(error, fallback) {
|
|
@@ -4586,5 +4823,5 @@ exports.v25_10_4_exports = v25_10_4_exports;
|
|
|
4586
4823
|
exports.v25_10_5_exports = v25_10_5_exports;
|
|
4587
4824
|
exports.v27_0_0_exports = v27_0_0_exports;
|
|
4588
4825
|
exports.v28_0_0_exports = v28_0_0_exports;
|
|
4589
|
-
//# sourceMappingURL=chunk-
|
|
4590
|
-
//# sourceMappingURL=chunk-
|
|
4826
|
+
//# sourceMappingURL=chunk-UOTSHE2J.cjs.map
|
|
4827
|
+
//# sourceMappingURL=chunk-UOTSHE2J.cjs.map
|