@milaboratories/pl-client 2.16.15 → 2.16.17
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/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +2 -1
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js.map +1 -1
- package/dist/core/errors.cjs +14 -0
- package/dist/core/errors.cjs.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +14 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/ll_client.cjs +26 -2
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +2 -0
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +26 -2
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/unauth_client.cjs +2 -2
- package/dist/core/unauth_client.cjs.map +1 -1
- package/dist/core/unauth_client.d.ts +4 -1
- package/dist/core/unauth_client.d.ts.map +1 -1
- package/dist/core/unauth_client.js +2 -2
- package/dist/core/unauth_client.js.map +1 -1
- package/package.json +6 -6
- package/src/core/client.ts +3 -1
- package/src/core/errors.ts +14 -0
- package/src/core/ll_client.ts +34 -3
- package/src/core/unauth_client.ts +3 -3
package/src/core/errors.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Aborted } from '@milaboratories/ts-helpers';
|
|
|
3
3
|
import { Code } from '../proto-grpc/google/rpc/code';
|
|
4
4
|
|
|
5
5
|
export function isConnectionProblem(err: unknown, nested: boolean = false): boolean {
|
|
6
|
+
if (err === undefined || err === null) return false;
|
|
7
|
+
|
|
6
8
|
if (err instanceof DisconnectedError) return true;
|
|
7
9
|
if ((err as any).name == 'RpcError' && (err as any).code == 'UNAVAILABLE') return true;
|
|
8
10
|
if ((err as any).name == 'RESTError' && (err as any).status.code == Code.UNAVAILABLE) return true;
|
|
@@ -11,6 +13,8 @@ export function isConnectionProblem(err: unknown, nested: boolean = false): bool
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function isUnauthenticated(err: unknown, nested: boolean = false): boolean {
|
|
16
|
+
if (err === undefined || err === null) return false;
|
|
17
|
+
|
|
14
18
|
if (err instanceof UnauthenticatedError) return true;
|
|
15
19
|
if ((err as any).name == 'RpcError' && (err as any).code == 'UNAUTHENTICATED') return true;
|
|
16
20
|
if ((err as any).name == 'RESTError' && (err as any).status.code == Code.UNAUTHENTICATED) return true;
|
|
@@ -19,6 +23,8 @@ export function isUnauthenticated(err: unknown, nested: boolean = false): boolea
|
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export function isTimeoutError(err: unknown, nested: boolean = false): boolean {
|
|
26
|
+
if (err === undefined || err === null) return false;
|
|
27
|
+
|
|
22
28
|
if ((err as any).name == 'TimeoutError') return true;
|
|
23
29
|
if ((err as any).name == 'RpcError' && (err as any).code == 'DEADLINE_EXCEEDED') return true;
|
|
24
30
|
if ((err as any).name == 'RESTError' && (err as any).status.code == Code.DEADLINE_EXCEEDED) return true;
|
|
@@ -27,6 +33,8 @@ export function isTimeoutError(err: unknown, nested: boolean = false): boolean {
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
export function isCancelError(err: unknown, nested: boolean = false): boolean {
|
|
36
|
+
if (err === undefined || err === null) return false;
|
|
37
|
+
|
|
30
38
|
if ((err as any).name == 'RpcError' && (err as any).code == 'CANCELLED') return true;
|
|
31
39
|
if ((err as any).name == 'RESTError' && (err as any).status.code == Code.CANCELLED) return true;
|
|
32
40
|
if ((err as any).cause !== undefined && !nested) return isCancelError((err as any).cause, true);
|
|
@@ -34,6 +42,8 @@ export function isCancelError(err: unknown, nested: boolean = false): boolean {
|
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
export function isAbortedError(err: unknown, nested: boolean = false): boolean {
|
|
45
|
+
if (err === undefined || err === null) return false;
|
|
46
|
+
|
|
37
47
|
if (err instanceof Aborted || (err as any).name == 'AbortError') return true;
|
|
38
48
|
if ((err as any).code == 'ABORT_ERR') return true;
|
|
39
49
|
if (err instanceof DOMException && err.code === DOMException.ABORT_ERR) return true; // WebSocket error
|
|
@@ -44,6 +54,8 @@ export function isAbortedError(err: unknown, nested: boolean = false): boolean {
|
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
export function isTimeoutOrCancelError(err: unknown, nested: boolean = false): boolean {
|
|
57
|
+
if (err === undefined || err === null) return false;
|
|
58
|
+
|
|
47
59
|
if (isAbortedError(err, true)) return true;
|
|
48
60
|
if (isTimeoutError(err, true)) return true;
|
|
49
61
|
if (isCancelError(err, true)) return true;
|
|
@@ -52,6 +64,8 @@ export function isTimeoutOrCancelError(err: unknown, nested: boolean = false): b
|
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
export function isNotFoundError(err: unknown, nested: boolean = false): boolean {
|
|
67
|
+
if (err === undefined || err === null) return false;
|
|
68
|
+
|
|
55
69
|
if ((err as any).name == 'RpcError' && (err as any).code == 'NOT_FOUND') return true;
|
|
56
70
|
if ((err as any).name == 'RESTError' && (err as any).status.code == Code.NOT_FOUND) return true;
|
|
57
71
|
if ((err as any).cause !== undefined && !nested) return isNotFoundError((err as any).cause, true);
|
package/src/core/ll_client.ts
CHANGED
|
@@ -30,6 +30,8 @@ import { notEmpty, retry, withTimeout, type RetryOptions } from '@milaboratories
|
|
|
30
30
|
import { Code } from '../proto-grpc/google/rpc/code';
|
|
31
31
|
import { WebSocketBiDiStream } from './websocket_stream';
|
|
32
32
|
import { TxAPI_ClientMessage, TxAPI_ServerMessage } from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api';
|
|
33
|
+
import type { MiLogger } from '@milaboratories/ts-helpers';
|
|
34
|
+
import { isAbortedError } from './errors';
|
|
33
35
|
|
|
34
36
|
export interface PlCallOps {
|
|
35
37
|
timeout?: number;
|
|
@@ -86,6 +88,7 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
86
88
|
auth?: AuthOps;
|
|
87
89
|
statusListener?: PlConnectionStatusListener;
|
|
88
90
|
shouldUseGzip?: boolean;
|
|
91
|
+
logger?: MiLogger;
|
|
89
92
|
} = {},
|
|
90
93
|
) {
|
|
91
94
|
const conf = typeof configOrAddress === 'string' ? plAddressToConfig(configOrAddress) : configOrAddress;
|
|
@@ -101,6 +104,7 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
101
104
|
auth?: AuthOps;
|
|
102
105
|
statusListener?: PlConnectionStatusListener;
|
|
103
106
|
shouldUseGzip?: boolean;
|
|
107
|
+
logger?: MiLogger;
|
|
104
108
|
} = {},
|
|
105
109
|
) {
|
|
106
110
|
const { auth, statusListener } = ops;
|
|
@@ -466,20 +470,47 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
466
470
|
return;
|
|
467
471
|
}
|
|
468
472
|
|
|
473
|
+
// Each retry is:
|
|
474
|
+
// - ping request timeout (100 to 3_000ms)
|
|
475
|
+
// - backoff delay (30 to 500ms)
|
|
476
|
+
//
|
|
477
|
+
// 30 attempts are ~43 seconds of overall waiting time.
|
|
478
|
+
// Think twice on overall time this thing takes to complete when changing these parameters.
|
|
479
|
+
// It may block UI when connecting to the server and loading projects list.
|
|
480
|
+
const pingTimeoutFactor = 1.3;
|
|
481
|
+
const maxPingTimeoutMs = 3_000;
|
|
469
482
|
const retryOptions: RetryOptions = {
|
|
470
483
|
type: 'exponentialBackoff',
|
|
471
|
-
maxAttempts:
|
|
484
|
+
maxAttempts: 30,
|
|
472
485
|
initialDelay: 30,
|
|
473
486
|
backoffMultiplier: 1.3,
|
|
474
487
|
jitter: 0.2,
|
|
475
488
|
maxDelay: 500,
|
|
476
489
|
};
|
|
477
490
|
|
|
491
|
+
let attempt = 1;
|
|
492
|
+
let pingTimeoutMs = 100;
|
|
478
493
|
await retry(
|
|
479
|
-
() => withTimeout(this.ping(),
|
|
494
|
+
() => withTimeout(this.ping(), pingTimeoutMs),
|
|
480
495
|
retryOptions,
|
|
481
|
-
() => {
|
|
496
|
+
(e: unknown) => {
|
|
497
|
+
if (isAbortedError(e)) {
|
|
498
|
+
this.ops.logger?.info(`Wire proto autodetect: ping timed out after ${pingTimeoutMs}ms: attempt=${attempt}, wire=${this._wireProto}`);
|
|
499
|
+
|
|
500
|
+
if (attempt % 2 === 0) {
|
|
501
|
+
// We have 2 wire protocols to check. Increase timeout each 2 attempts.
|
|
502
|
+
pingTimeoutMs = Math.min(
|
|
503
|
+
Math.round(pingTimeoutMs * pingTimeoutFactor),
|
|
504
|
+
maxPingTimeoutMs,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
this.ops.logger?.info(`Wire proto autodetect: ping failed: attempt=${attempt}, wire=${this._wireProto}, err=${String(e)}`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
attempt++;
|
|
482
512
|
const protocol = this._wireProto === 'grpc' ? 'rest' : 'grpc';
|
|
513
|
+
this.ops.logger?.info(`Wire protocol autodetect next attempt: will try wire '${protocol}' with timeout ${pingTimeoutMs}ms`);
|
|
483
514
|
this.initWireConnection(protocol);
|
|
484
515
|
return true;
|
|
485
516
|
});
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
MaintenanceAPI_Ping_Response,
|
|
5
5
|
} from '../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api';
|
|
6
6
|
import { LLPlClient } from './ll_client';
|
|
7
|
-
import { notEmpty } from '@milaboratories/ts-helpers';
|
|
7
|
+
import { type MiLogger, notEmpty } from '@milaboratories/ts-helpers';
|
|
8
8
|
import { UnauthenticatedError } from './errors';
|
|
9
9
|
|
|
10
10
|
/** Primarily used for initial authentication (login) */
|
|
@@ -15,8 +15,8 @@ export class UnauthenticatedPlClient {
|
|
|
15
15
|
this.ll = ll;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
public static async build(configOrAddress: PlClientConfig | string): Promise<UnauthenticatedPlClient> {
|
|
19
|
-
const ll = await LLPlClient.build(configOrAddress);
|
|
18
|
+
public static async build(configOrAddress: PlClientConfig | string, ops?: { logger?: MiLogger }): Promise<UnauthenticatedPlClient> {
|
|
19
|
+
const ll = await LLPlClient.build(configOrAddress, ops);
|
|
20
20
|
return new UnauthenticatedPlClient(ll);
|
|
21
21
|
}
|
|
22
22
|
|