@milaboratories/pl-client 3.6.0 → 3.8.0
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/capabilities.cjs +9 -0
- package/dist/core/capabilities.cjs.map +1 -0
- package/dist/core/capabilities.d.ts +24 -0
- package/dist/core/capabilities.d.ts.map +1 -0
- package/dist/core/capabilities.js +9 -0
- package/dist/core/capabilities.js.map +1 -0
- package/dist/core/client.cjs +7 -25
- package/dist/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +2 -3
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +7 -25
- package/dist/core/client.js.map +1 -1
- package/dist/core/ll_client.cjs +153 -7
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +26 -0
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +153 -7
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/transaction.cjs +4 -2
- package/dist/core/transaction.cjs.map +1 -1
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +4 -2
- package/dist/core/transaction.js.map +1 -1
- package/dist/core/unauth_client.cjs +33 -1
- package/dist/core/unauth_client.cjs.map +1 -1
- package/dist/core/unauth_client.d.ts +19 -0
- package/dist/core/unauth_client.d.ts.map +1 -1
- package/dist/core/unauth_client.js +33 -1
- package/dist/core/unauth_client.js.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs.map +1 -1
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +1101 -135
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs +49 -10
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts +61 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js +49 -10
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +423 -15
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +1101 -135
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.cjs.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.d.ts +8 -9
- package/dist/proto-grpc/google/protobuf/timestamp.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.js.map +1 -1
- package/dist/proto-grpc/google/rpc/code.cjs.map +1 -1
- package/dist/proto-grpc/google/rpc/code.js.map +1 -1
- package/dist/proto-rest/plapi.d.ts +250 -5
- package/dist/proto-rest/plapi.d.ts.map +1 -1
- package/dist/util/pl.cjs.map +1 -1
- package/dist/util/pl.js.map +1 -1
- package/package.json +5 -5
- package/src/core/capabilities.ts +26 -0
- package/src/core/client.ts +11 -29
- package/src/core/ll_client.test.ts +16 -3
- package/src/core/ll_client.ts +187 -8
- package/src/core/ll_transaction.test.ts +15 -9
- package/src/core/transaction.ts +2 -0
- package/src/core/unauth_client.ts +42 -3
- package/src/core/unauth_client_branch.test.ts +69 -0
- package/src/index.ts +1 -0
- package/src/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.ts +1 -1
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +85 -10
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +1322 -104
- package/src/proto-grpc/google/api/http.ts +1 -1
- package/src/proto-grpc/google/protobuf/descriptor.ts +7 -240
- package/src/proto-grpc/google/protobuf/timestamp.ts +8 -9
- package/src/proto-grpc/google/protobuf/wrappers.ts +4 -38
- package/src/proto-grpc/google/rpc/code.ts +1 -1
- package/src/proto-grpc/google/rpc/error_details.ts +5 -5
- package/src/proto-grpc/google/rpc/http.ts +1 -1
- package/src/proto-grpc/google/rpc/status.ts +1 -1
- package/src/proto-rest/plapi.ts +266 -5
- package/src/util/pl.ts +5 -0
package/src/core/ll_client.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { parsePlJwt } from "../util/pl";
|
|
|
21
21
|
import { type Dispatcher, interceptors } from "undici";
|
|
22
22
|
import type { Middleware } from "openapi-fetch";
|
|
23
23
|
import { inferAuthRefreshTime } from "./auth";
|
|
24
|
+
import { hasCapability, type BackendCapability } from "./capabilities";
|
|
24
25
|
import { defaultHttpDispatcher } from "@milaboratories/pl-http";
|
|
25
26
|
import type { WireClientProvider, WireClientProviderFactory, WireConnection } from "./wire";
|
|
26
27
|
import { parseHttpAuth } from "@milaboratories/pl-model-common";
|
|
@@ -47,6 +48,19 @@ export interface PlCallOps {
|
|
|
47
48
|
abortSignal?: AbortSignal;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// Parses leading "<major>.<minor>.<patch>" from a version string like
|
|
52
|
+
// "3.1.1" or "3.1.1-rc1" and returns true if the parsed version is >= target.
|
|
53
|
+
// Returns false for unparseable versions (safer to assume an old backend).
|
|
54
|
+
function isVersionAtLeast(version: string, target: [number, number, number]): boolean {
|
|
55
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version);
|
|
56
|
+
if (!match) return false;
|
|
57
|
+
const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
58
|
+
for (let i = 0; i < 3; i++) {
|
|
59
|
+
if (parsed[i] !== target[i]) return parsed[i] > target[i];
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
class WireClientProviderImpl<Client> implements WireClientProvider<Client> {
|
|
51
65
|
private client: Client | undefined = undefined;
|
|
52
66
|
|
|
@@ -78,6 +92,10 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
78
92
|
/** Threshold after which auth info refresh is required */
|
|
79
93
|
private refreshTimestamp?: number;
|
|
80
94
|
|
|
95
|
+
/** Cached Ping response. Populated by build() before it returns; refreshed by every later ping(). */
|
|
96
|
+
private _serverInfo?: grpcTypes.MaintenanceAPI_Ping_Response;
|
|
97
|
+
private _authMethodsSync?: grpcTypes.AuthAPI_ListMethods_Response;
|
|
98
|
+
|
|
81
99
|
private _status: PlConnectionStatus = "OK";
|
|
82
100
|
private readonly statusListener?: PlConnectionStatusListener;
|
|
83
101
|
|
|
@@ -112,6 +130,15 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
112
130
|
if (ops.useAutoDetectWireProtocol) {
|
|
113
131
|
await pl.detectOptimalWireProtocol();
|
|
114
132
|
}
|
|
133
|
+
|
|
134
|
+
// Guarantee a ping happened so capability-gated paths (login, refresh) can branch synchronously.
|
|
135
|
+
// In the autodetect path the loop's last successful ping already populated _serverInfo via the
|
|
136
|
+
// side-effect in ping(); this fallback covers the path where autodetect is disabled.
|
|
137
|
+
if (!pl._serverInfo) await pl.ping();
|
|
138
|
+
|
|
139
|
+
// Guarantee authMethods happened so client can make weighted decision on which auth method to use.
|
|
140
|
+
if (!pl._authMethodsSync) await pl.authMethods();
|
|
141
|
+
|
|
115
142
|
return pl;
|
|
116
143
|
}
|
|
117
144
|
|
|
@@ -320,9 +347,12 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
320
347
|
/** null means anonymous connection */
|
|
321
348
|
public get authUser(): string | null {
|
|
322
349
|
if (!this.authenticated) throw new Error("Client is not authenticated");
|
|
323
|
-
if (this.authInformation?.jwtToken)
|
|
350
|
+
if (this.authInformation?.jwtToken) {
|
|
351
|
+
if (this.hasCapability("auth:v2")) {
|
|
352
|
+
return parsePlJwt(this.authInformation?.jwtToken).sub;
|
|
353
|
+
}
|
|
324
354
|
return parsePlJwt(this.authInformation?.jwtToken).user.login;
|
|
325
|
-
else return null;
|
|
355
|
+
} else return null;
|
|
326
356
|
}
|
|
327
357
|
|
|
328
358
|
private updateStatus(newStatus: PlConnectionStatus) {
|
|
@@ -354,7 +384,10 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
354
384
|
this.authRefreshInProgress = true;
|
|
355
385
|
void (async () => {
|
|
356
386
|
try {
|
|
357
|
-
const
|
|
387
|
+
const ttl = BigInt(this.conf.authTTLSeconds);
|
|
388
|
+
const token = this.hasCapability("auth:v2")
|
|
389
|
+
? await this.refreshToken({ ttlSeconds: ttl })
|
|
390
|
+
: await this.getJwtToken(ttl);
|
|
358
391
|
this.authInformation = { jwtToken: token };
|
|
359
392
|
this.refreshTimestamp = inferAuthRefreshTime(
|
|
360
393
|
this.authInformation,
|
|
@@ -491,10 +524,108 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
491
524
|
}
|
|
492
525
|
}
|
|
493
526
|
|
|
527
|
+
/** Login via username/password. Returns a fresh JWT. Backend creates a new session per call. */
|
|
528
|
+
public async loginBasic(
|
|
529
|
+
user: string,
|
|
530
|
+
password: string,
|
|
531
|
+
opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},
|
|
532
|
+
): Promise<string> {
|
|
533
|
+
const cl = this.clientProvider.get();
|
|
534
|
+
const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);
|
|
535
|
+
const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;
|
|
536
|
+
|
|
537
|
+
if (cl instanceof GrpcPlApiClient) {
|
|
538
|
+
return (
|
|
539
|
+
await cl.login({
|
|
540
|
+
credentials: {
|
|
541
|
+
oneofKind: "basic",
|
|
542
|
+
basic: { login: user, password },
|
|
543
|
+
},
|
|
544
|
+
expiration: { seconds: ttl, nanos: 0 },
|
|
545
|
+
requestedRole: role,
|
|
546
|
+
}).response
|
|
547
|
+
).token;
|
|
548
|
+
} else {
|
|
549
|
+
const resp = cl.POST("/v1/auth/login", {
|
|
550
|
+
// openapi-typescript generated all body fields as required, but Login.Request
|
|
551
|
+
// has a credentials oneof — only one of `basic`/`token` is sent. Cast around it.
|
|
552
|
+
body: {
|
|
553
|
+
basic: { login: user, password },
|
|
554
|
+
expiration: `${ttl}s`,
|
|
555
|
+
requestedRole: role,
|
|
556
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
557
|
+
} as any,
|
|
558
|
+
});
|
|
559
|
+
return notEmpty((await resp).data, "REST: empty response for login request").token;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** Login via opaque bearer token (controller pre-shared secret, OIDC id-token, etc.).
|
|
564
|
+
* String input is UTF-8 encoded. Returns a fresh Platforma JWT. */
|
|
565
|
+
public async loginWithToken(
|
|
566
|
+
token: Uint8Array | string,
|
|
567
|
+
opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},
|
|
568
|
+
): Promise<string> {
|
|
569
|
+
const cl = this.clientProvider.get();
|
|
570
|
+
const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);
|
|
571
|
+
const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;
|
|
572
|
+
const bytes = typeof token === "string" ? Buffer.from(token, "utf8") : token;
|
|
573
|
+
|
|
574
|
+
if (cl instanceof GrpcPlApiClient) {
|
|
575
|
+
return (
|
|
576
|
+
await cl.login({
|
|
577
|
+
credentials: {
|
|
578
|
+
oneofKind: "token",
|
|
579
|
+
token: { token: bytes },
|
|
580
|
+
},
|
|
581
|
+
expiration: { seconds: ttl, nanos: 0 },
|
|
582
|
+
requestedRole: role,
|
|
583
|
+
}).response
|
|
584
|
+
).token;
|
|
585
|
+
} else {
|
|
586
|
+
const resp = cl.POST("/v1/auth/login", {
|
|
587
|
+
// openapi-typescript marks all body fields as required, but Login.Request has a oneof.
|
|
588
|
+
// REST encodes `bytes` as a base64 string.
|
|
589
|
+
body: {
|
|
590
|
+
token: { token: Buffer.from(bytes).toString("base64") },
|
|
591
|
+
expiration: `${ttl}s`,
|
|
592
|
+
requestedRole: role,
|
|
593
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
594
|
+
} as any,
|
|
595
|
+
});
|
|
596
|
+
return notEmpty((await resp).data, "REST: empty response for login request").token;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** Refresh the current JWT, preserving session id and role. */
|
|
601
|
+
public async refreshToken(opts: { ttlSeconds?: bigint } = {}): Promise<string> {
|
|
602
|
+
const cl = this.clientProvider.get();
|
|
603
|
+
const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);
|
|
604
|
+
const currentToken = notEmpty(
|
|
605
|
+
this.authInformation?.jwtToken,
|
|
606
|
+
"refreshToken called without a current JWT",
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
if (cl instanceof GrpcPlApiClient) {
|
|
610
|
+
return (
|
|
611
|
+
await cl.refreshToken({
|
|
612
|
+
token: currentToken,
|
|
613
|
+
expiration: { seconds: ttl, nanos: 0 },
|
|
614
|
+
}).response
|
|
615
|
+
).token;
|
|
616
|
+
} else {
|
|
617
|
+
const resp = cl.POST("/v1/auth/refresh", {
|
|
618
|
+
body: { token: currentToken, expiration: `${ttl}s` },
|
|
619
|
+
});
|
|
620
|
+
return notEmpty((await resp).data, "REST: empty response for refresh request").token;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
494
624
|
public async ping(): Promise<grpcTypes.MaintenanceAPI_Ping_Response> {
|
|
495
625
|
const cl = this.clientProvider.get();
|
|
626
|
+
let resp: grpcTypes.MaintenanceAPI_Ping_Response;
|
|
496
627
|
if (cl instanceof GrpcPlApiClient) {
|
|
497
|
-
|
|
628
|
+
resp = (await cl.ping({})).response;
|
|
498
629
|
} else {
|
|
499
630
|
// The REST ping response predates the `capabilities` field (proto field 9).
|
|
500
631
|
// Old servers omit it; treat absence as empty capability list.
|
|
@@ -502,12 +633,32 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
502
633
|
(await cl.GET("/v1/ping")).data,
|
|
503
634
|
"REST: empty response for ping request",
|
|
504
635
|
);
|
|
505
|
-
|
|
506
|
-
return {
|
|
636
|
+
resp = {
|
|
507
637
|
...(pingData as unknown as grpcTypes.MaintenanceAPI_Ping_Response),
|
|
638
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
508
639
|
capabilities: (pingData as any).capabilities ?? [],
|
|
509
640
|
};
|
|
510
641
|
}
|
|
642
|
+
this._serverInfo = resp;
|
|
643
|
+
return resp;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** Cached Ping response. Always populated post-build(); throws if accessed earlier. */
|
|
647
|
+
public get serverInfo(): grpcTypes.MaintenanceAPI_Ping_Response {
|
|
648
|
+
if (!this._serverInfo) {
|
|
649
|
+
throw new Error("LLPlClient.serverInfo accessed before build() completed");
|
|
650
|
+
}
|
|
651
|
+
return this._serverInfo;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** Synchronous capability check against the cached Ping response. */
|
|
655
|
+
public hasCapability(capability: BackendCapability): boolean {
|
|
656
|
+
return hasCapability(this.serverInfo.capabilities, capability);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** True if the backend implements the setDefaultColor TX request. */
|
|
660
|
+
public get supportsSetDefaultColor(): boolean {
|
|
661
|
+
return isVersionAtLeast(this.serverInfo.coreVersion, [3, 3, 0]);
|
|
511
662
|
}
|
|
512
663
|
|
|
513
664
|
/**
|
|
@@ -592,14 +743,42 @@ export class LLPlClient implements WireClientProviderFactory {
|
|
|
592
743
|
|
|
593
744
|
public async authMethods(): Promise<grpcTypes.AuthAPI_ListMethods_Response> {
|
|
594
745
|
const cl = this.clientProvider.get();
|
|
746
|
+
let resp: grpcTypes.AuthAPI_ListMethods_Response;
|
|
595
747
|
if (cl instanceof GrpcPlApiClient) {
|
|
596
|
-
|
|
748
|
+
resp = (await cl.authMethods({})).response;
|
|
597
749
|
} else {
|
|
598
|
-
|
|
750
|
+
const wsResponse = notEmpty(
|
|
599
751
|
(await cl.GET("/v1/auth/methods")).data,
|
|
600
752
|
"REST: empty response for auth methods request",
|
|
601
753
|
);
|
|
754
|
+
// OpenAPI schema flattens the protobuf oneof into `{ basic?, token? }`,
|
|
755
|
+
// while protobuf-ts models it as a discriminated union. Reshape per item.
|
|
756
|
+
resp = {
|
|
757
|
+
methods: (wsResponse.methods ?? []).map((m): grpcTypes.AuthAPI_ListMethods_MethodInfo => {
|
|
758
|
+
const base = { id: m.id, description: m.description };
|
|
759
|
+
if (m.basic !== undefined) {
|
|
760
|
+
return { ...base, method: { oneofKind: "basic", basic: m.basic } };
|
|
761
|
+
}
|
|
762
|
+
if (m.token !== undefined) {
|
|
763
|
+
return { ...base, method: { oneofKind: "token", token: m.token } };
|
|
764
|
+
}
|
|
765
|
+
if (m.sso !== undefined) {
|
|
766
|
+
return { ...base, method: { oneofKind: "sso", sso: m.sso } };
|
|
767
|
+
}
|
|
768
|
+
return { ...base, method: { oneofKind: undefined } };
|
|
769
|
+
}),
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
this._authMethodsSync = resp;
|
|
774
|
+
return resp;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
public get authMethodsSync(): grpcTypes.AuthAPI_ListMethods_Response {
|
|
778
|
+
if (!this._authMethodsSync) {
|
|
779
|
+
throw new Error("LLPlClient.authMethodsSync accessed before build() completed");
|
|
602
780
|
}
|
|
781
|
+
return this._authMethodsSync;
|
|
603
782
|
}
|
|
604
783
|
|
|
605
784
|
public async getUserRoot(
|
|
@@ -113,11 +113,14 @@ test("check timeout error type (active)", async () => {
|
|
|
113
113
|
);
|
|
114
114
|
expect(openResponse.txOpen.tx?.isValid).toBeTruthy();
|
|
115
115
|
|
|
116
|
-
// Set default color so resource creation succeeds in strict mode
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
// Set default color so resource creation succeeds in strict mode.
|
|
117
|
+
// Skip on older backends that don't implement the setDefaultColor TX request.
|
|
118
|
+
if (client.supportsSetDefaultColor) {
|
|
119
|
+
await tx.send(
|
|
120
|
+
{ oneofKind: "setDefaultColor", setDefaultColor: { colorProof: rootSig } },
|
|
121
|
+
false,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
121
124
|
|
|
122
125
|
const rData = Uint8Array.from([
|
|
123
126
|
(Math.random() * 256) & 0xff,
|
|
@@ -185,10 +188,13 @@ test("check is abort error (active)", async () => {
|
|
|
185
188
|
expect(openResponse.txOpen.tx?.isValid).toBeTruthy();
|
|
186
189
|
|
|
187
190
|
// Set default color so resource creation succeeds in strict mode
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
// Skip on older backends that don't implement the setDefaultColor TX request.
|
|
192
|
+
if (client.supportsSetDefaultColor) {
|
|
193
|
+
await tx.send(
|
|
194
|
+
{ oneofKind: "setDefaultColor", setDefaultColor: { colorProof: rootSig } },
|
|
195
|
+
false,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
192
198
|
|
|
193
199
|
const rData = Uint8Array.from([
|
|
194
200
|
Math.random() & 0xff,
|
package/src/core/transaction.ts
CHANGED
|
@@ -702,6 +702,7 @@ export class PlTransaction {
|
|
|
702
702
|
resourceGet: {
|
|
703
703
|
...this.toSignedResourceId(rId),
|
|
704
704
|
loadFields: loadFields,
|
|
705
|
+
showSoftDeletes: false,
|
|
705
706
|
},
|
|
706
707
|
},
|
|
707
708
|
(r) => protoToResource(notEmpty(r.resourceGet.resource)),
|
|
@@ -1136,6 +1137,7 @@ export class PlTransaction {
|
|
|
1136
1137
|
traverseStopRules: opts?.traverseStopRules,
|
|
1137
1138
|
includeKv: opts?.includeKv ?? false,
|
|
1138
1139
|
maxDepth: opts?.maxDepth,
|
|
1140
|
+
showSoftDeletes: false,
|
|
1139
1141
|
},
|
|
1140
1142
|
});
|
|
1141
1143
|
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
import { LLPlClient } from "./ll_client";
|
|
7
7
|
import { type MiLogger, notEmpty } from "@milaboratories/ts-helpers";
|
|
8
8
|
import { UnauthenticatedError } from "./errors";
|
|
9
|
+
import type { BackendCapability } from "./capabilities";
|
|
9
10
|
|
|
10
11
|
/** Primarily used for initial authentication (login) */
|
|
11
12
|
export class UnauthenticatedPlClient {
|
|
@@ -35,11 +36,49 @@ export class UnauthenticatedPlClient {
|
|
|
35
36
|
return (await this.authMethods()).methods.length > 0;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
public hasCapability(capability: BackendCapability): boolean {
|
|
40
|
+
return this.ll.hasCapability(capability);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Classifies the advertised authentication methods by credential scheme.
|
|
44
|
+
* On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}
|
|
45
|
+
* which uses the legacy GetJWTToken path. */
|
|
46
|
+
public get supportedAuthSchemes(): { basic: boolean; token: boolean } {
|
|
47
|
+
const result = { basic: false, token: false };
|
|
48
|
+
for (const m of this.ll.authMethodsSync.methods) {
|
|
49
|
+
if (m.method.oneofKind === "basic") result.basic = true;
|
|
50
|
+
else if (m.method.oneofKind === "token") result.token = true;
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Login with username+password.
|
|
56
|
+
*
|
|
57
|
+
* On auth:v2 backends the client inspects the advertised AuthMethods:
|
|
58
|
+
* - if basic auth is offered, sends {@link LLPlClient.loginBasic};
|
|
59
|
+
* - if only token auth is offered, treats `password` as an opaque bearer token and
|
|
60
|
+
* sends {@link LLPlClient.loginWithToken} (so deployments configured for static-token
|
|
61
|
+
* auth still log in without the caller switching methods).
|
|
62
|
+
*
|
|
63
|
+
* On legacy backends (no auth:v2) it falls through to GetJWTToken with the Basic header,
|
|
64
|
+
* preserving original behavior. */
|
|
38
65
|
public async login(user: string, password: string): Promise<AuthInformation> {
|
|
39
66
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
67
|
+
let token: string;
|
|
68
|
+
if (this.ll.hasCapability("auth:v2")) {
|
|
69
|
+
const schemes = this.supportedAuthSchemes;
|
|
70
|
+
if (schemes.basic) {
|
|
71
|
+
token = await this.ll.loginBasic(user, password);
|
|
72
|
+
} else if (schemes.token) {
|
|
73
|
+
token = await this.ll.loginWithToken(password);
|
|
74
|
+
} else {
|
|
75
|
+
throw new Error("backend advertises no supported authentication methods");
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
token = await this.ll.getJwtToken(BigInt(this.ll.conf.authTTLSeconds), {
|
|
79
|
+
authorization: "Basic " + Buffer.from(user + ":" + password).toString("base64"),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
43
82
|
const jwtToken = notEmpty(token);
|
|
44
83
|
if (jwtToken === "") throw new Error("empty token");
|
|
45
84
|
return { jwtToken };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { test, expect, vi } from "vitest";
|
|
2
|
+
import { UnauthenticatedPlClient } from "./unauth_client";
|
|
3
|
+
import type { BackendCapability } from "./capabilities";
|
|
4
|
+
|
|
5
|
+
type Scheme = "basic" | "token" | "none";
|
|
6
|
+
|
|
7
|
+
function makeStub(opts: { hasAuthV2: boolean; scheme?: Scheme }) {
|
|
8
|
+
const scheme = opts.scheme ?? "basic";
|
|
9
|
+
const methods =
|
|
10
|
+
scheme === "basic"
|
|
11
|
+
? [{ id: "basic", method: { oneofKind: "basic", basic: {} } }]
|
|
12
|
+
: scheme === "token"
|
|
13
|
+
? [{ id: "token", method: { oneofKind: "token", token: {} } }]
|
|
14
|
+
: [];
|
|
15
|
+
const ll = {
|
|
16
|
+
hasCapability: vi.fn(
|
|
17
|
+
(capability: BackendCapability) => capability === "auth:v2" && opts.hasAuthV2,
|
|
18
|
+
),
|
|
19
|
+
loginBasic: vi.fn().mockResolvedValue("jwt-from-loginBasic"),
|
|
20
|
+
loginWithToken: vi.fn().mockResolvedValue("jwt-from-loginWithToken"),
|
|
21
|
+
getJwtToken: vi.fn().mockResolvedValue("jwt-from-getJwtToken"),
|
|
22
|
+
authMethodsSync: { methods },
|
|
23
|
+
conf: { authTTLSeconds: 100 },
|
|
24
|
+
};
|
|
25
|
+
const client = Object.assign(Object.create(UnauthenticatedPlClient.prototype) as object, { ll });
|
|
26
|
+
return { client: client as UnauthenticatedPlClient, ll };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("login routes to loginBasic when backend advertises auth:v2 + basic scheme", async () => {
|
|
30
|
+
const { client, ll } = makeStub({ hasAuthV2: true, scheme: "basic" });
|
|
31
|
+
|
|
32
|
+
const info = await client.login("alice", "pw");
|
|
33
|
+
|
|
34
|
+
expect(ll.loginBasic).toHaveBeenCalledWith("alice", "pw");
|
|
35
|
+
expect(ll.loginWithToken).not.toHaveBeenCalled();
|
|
36
|
+
expect(ll.getJwtToken).not.toHaveBeenCalled();
|
|
37
|
+
expect(info.jwtToken).toBe("jwt-from-loginBasic");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("login routes to loginWithToken when backend advertises auth:v2 + token-only scheme", async () => {
|
|
41
|
+
const { client, ll } = makeStub({ hasAuthV2: true, scheme: "token" });
|
|
42
|
+
|
|
43
|
+
const info = await client.login("alice", "opaque-token");
|
|
44
|
+
|
|
45
|
+
expect(ll.loginWithToken).toHaveBeenCalledWith("opaque-token");
|
|
46
|
+
expect(ll.loginBasic).not.toHaveBeenCalled();
|
|
47
|
+
expect(ll.getJwtToken).not.toHaveBeenCalled();
|
|
48
|
+
expect(info.jwtToken).toBe("jwt-from-loginWithToken");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("login falls back to getJwtToken when backend lacks auth:v2", async () => {
|
|
52
|
+
const { client, ll } = makeStub({ hasAuthV2: false });
|
|
53
|
+
|
|
54
|
+
const info = await client.login("alice", "pw");
|
|
55
|
+
|
|
56
|
+
expect(ll.getJwtToken).toHaveBeenCalledWith(BigInt(100), {
|
|
57
|
+
authorization: expect.stringMatching(/^Basic /),
|
|
58
|
+
});
|
|
59
|
+
expect(ll.loginBasic).not.toHaveBeenCalled();
|
|
60
|
+
expect(ll.loginWithToken).not.toHaveBeenCalled();
|
|
61
|
+
expect(info.jwtToken).toBe("jwt-from-getJwtToken");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("hasCapability proxies to underlying LLPlClient", () => {
|
|
65
|
+
const { client, ll } = makeStub({ hasAuthV2: true });
|
|
66
|
+
expect(client.hasCapability("auth:v2")).toBe(true);
|
|
67
|
+
expect(client.hasCapability("treeFilter:v1")).toBe(false);
|
|
68
|
+
expect(ll.hasCapability).toHaveBeenCalledWith("auth:v2");
|
|
69
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./core/errors";
|
|
|
8
8
|
export * from "./core/default_client";
|
|
9
9
|
export * from "./core/unauth_client";
|
|
10
10
|
export * from "./core/auth";
|
|
11
|
+
export * from "./core/capabilities";
|
|
11
12
|
export * from "./core/final";
|
|
12
13
|
export * from "./core/tree_filter";
|
|
13
14
|
export * from "./core/user_resources";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @generated from protobuf file "github.com/googleapis/googleapis/google/rpc/status.proto" (package "google.rpc", syntax proto3)
|
|
3
3
|
// tslint:disable
|
|
4
4
|
//
|
|
5
|
-
// Copyright
|
|
5
|
+
// Copyright 2025 Google LLC
|
|
6
6
|
//
|
|
7
7
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
// you may not use this file except in compliance with the License.
|
|
@@ -25,6 +25,12 @@ import type { AuthAPI_GrantAccess_Response } from "./api";
|
|
|
25
25
|
import type { AuthAPI_GrantAccess_Request } from "./api";
|
|
26
26
|
import type { AuthAPI_GetSessionInfo_Response } from "./api";
|
|
27
27
|
import type { AuthAPI_GetSessionInfo_Request } from "./api";
|
|
28
|
+
import type { AuthAPI_RefreshToken_Response } from "./api";
|
|
29
|
+
import type { AuthAPI_RefreshToken_Request } from "./api";
|
|
30
|
+
import type { AuthAPI_BeginSSOLogin_Response } from "./api";
|
|
31
|
+
import type { AuthAPI_BeginSSOLogin_Request } from "./api";
|
|
32
|
+
import type { AuthAPI_Login_Response } from "./api";
|
|
33
|
+
import type { AuthAPI_Login_Request } from "./api";
|
|
28
34
|
import type { AuthAPI_GetJWTToken_Response } from "./api";
|
|
29
35
|
import type { AuthAPI_GetJWTToken_Request } from "./api";
|
|
30
36
|
import type { AuthAPI_ListMethods_Response } from "./api";
|
|
@@ -213,9 +219,39 @@ export interface IPlatformClient {
|
|
|
213
219
|
*/
|
|
214
220
|
authMethods(input: AuthAPI_ListMethods_Request, options?: RpcOptions): UnaryCall<AuthAPI_ListMethods_Request, AuthAPI_ListMethods_Response>;
|
|
215
221
|
/**
|
|
222
|
+
* Deprecated: Use Login for session creation and role transitions,
|
|
223
|
+
* and RefreshToken for token renewal. Backends implementing this API always return
|
|
224
|
+
* codes.Unimplemented. Kept here so clients can still call old backends.
|
|
225
|
+
*
|
|
226
|
+
* @deprecated
|
|
216
227
|
* @generated from protobuf rpc: GetJWTToken
|
|
217
228
|
*/
|
|
218
229
|
getJWTToken(input: AuthAPI_GetJWTToken_Request, options?: RpcOptions): UnaryCall<AuthAPI_GetJWTToken_Request, AuthAPI_GetJWTToken_Response>;
|
|
230
|
+
/**
|
|
231
|
+
* Login authenticates with the given credentials and returns a new Platforma JWT.
|
|
232
|
+
* Every Login call creates a new session. Use RefreshToken to renew an existing one.
|
|
233
|
+
* This method is public: no Authorization header is required.
|
|
234
|
+
*
|
|
235
|
+
* @generated from protobuf rpc: Login
|
|
236
|
+
*/
|
|
237
|
+
login(input: AuthAPI_Login_Request, options?: RpcOptions): UnaryCall<AuthAPI_Login_Request, AuthAPI_Login_Response>;
|
|
238
|
+
/**
|
|
239
|
+
* BeginSSOLogin returns a fresh one-time nonce that the desktop must place
|
|
240
|
+
* into the OIDC auth-request before redirecting to the IdP. Used by the SSO
|
|
241
|
+
* login flow. This method is public: no Authorization header is required.
|
|
242
|
+
*
|
|
243
|
+
* @generated from protobuf rpc: BeginSSOLogin
|
|
244
|
+
*/
|
|
245
|
+
beginSSOLogin(input: AuthAPI_BeginSSOLogin_Request, options?: RpcOptions): UnaryCall<AuthAPI_BeginSSOLogin_Request, AuthAPI_BeginSSOLogin_Response>;
|
|
246
|
+
/**
|
|
247
|
+
* RefreshToken accepts a valid Platforma JWT and re-issues it with the same
|
|
248
|
+
* session ID and role. Only the token expiration may be changed.
|
|
249
|
+
* Workflow-scoped tokens cannot be refreshed; call Login instead.
|
|
250
|
+
* This method is public: no Authorization header is required.
|
|
251
|
+
*
|
|
252
|
+
* @generated from protobuf rpc: RefreshToken
|
|
253
|
+
*/
|
|
254
|
+
refreshToken(input: AuthAPI_RefreshToken_Request, options?: RpcOptions): UnaryCall<AuthAPI_RefreshToken_Request, AuthAPI_RefreshToken_Response>;
|
|
219
255
|
/**
|
|
220
256
|
* @generated from protobuf rpc: GetSessionInfo
|
|
221
257
|
*/
|
|
@@ -479,38 +515,77 @@ export class PlatformClient implements IPlatformClient, ServiceInfo {
|
|
|
479
515
|
return stackIntercept<AuthAPI_ListMethods_Request, AuthAPI_ListMethods_Response>("unary", this._transport, method, opt, input);
|
|
480
516
|
}
|
|
481
517
|
/**
|
|
518
|
+
* Deprecated: Use Login for session creation and role transitions,
|
|
519
|
+
* and RefreshToken for token renewal. Backends implementing this API always return
|
|
520
|
+
* codes.Unimplemented. Kept here so clients can still call old backends.
|
|
521
|
+
*
|
|
522
|
+
* @deprecated
|
|
482
523
|
* @generated from protobuf rpc: GetJWTToken
|
|
483
524
|
*/
|
|
484
525
|
getJWTToken(input: AuthAPI_GetJWTToken_Request, options?: RpcOptions): UnaryCall<AuthAPI_GetJWTToken_Request, AuthAPI_GetJWTToken_Response> {
|
|
485
526
|
const method = this.methods[23], opt = this._transport.mergeOptions(options);
|
|
486
527
|
return stackIntercept<AuthAPI_GetJWTToken_Request, AuthAPI_GetJWTToken_Response>("unary", this._transport, method, opt, input);
|
|
487
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Login authenticates with the given credentials and returns a new Platforma JWT.
|
|
531
|
+
* Every Login call creates a new session. Use RefreshToken to renew an existing one.
|
|
532
|
+
* This method is public: no Authorization header is required.
|
|
533
|
+
*
|
|
534
|
+
* @generated from protobuf rpc: Login
|
|
535
|
+
*/
|
|
536
|
+
login(input: AuthAPI_Login_Request, options?: RpcOptions): UnaryCall<AuthAPI_Login_Request, AuthAPI_Login_Response> {
|
|
537
|
+
const method = this.methods[24], opt = this._transport.mergeOptions(options);
|
|
538
|
+
return stackIntercept<AuthAPI_Login_Request, AuthAPI_Login_Response>("unary", this._transport, method, opt, input);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* BeginSSOLogin returns a fresh one-time nonce that the desktop must place
|
|
542
|
+
* into the OIDC auth-request before redirecting to the IdP. Used by the SSO
|
|
543
|
+
* login flow. This method is public: no Authorization header is required.
|
|
544
|
+
*
|
|
545
|
+
* @generated from protobuf rpc: BeginSSOLogin
|
|
546
|
+
*/
|
|
547
|
+
beginSSOLogin(input: AuthAPI_BeginSSOLogin_Request, options?: RpcOptions): UnaryCall<AuthAPI_BeginSSOLogin_Request, AuthAPI_BeginSSOLogin_Response> {
|
|
548
|
+
const method = this.methods[25], opt = this._transport.mergeOptions(options);
|
|
549
|
+
return stackIntercept<AuthAPI_BeginSSOLogin_Request, AuthAPI_BeginSSOLogin_Response>("unary", this._transport, method, opt, input);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* RefreshToken accepts a valid Platforma JWT and re-issues it with the same
|
|
553
|
+
* session ID and role. Only the token expiration may be changed.
|
|
554
|
+
* Workflow-scoped tokens cannot be refreshed; call Login instead.
|
|
555
|
+
* This method is public: no Authorization header is required.
|
|
556
|
+
*
|
|
557
|
+
* @generated from protobuf rpc: RefreshToken
|
|
558
|
+
*/
|
|
559
|
+
refreshToken(input: AuthAPI_RefreshToken_Request, options?: RpcOptions): UnaryCall<AuthAPI_RefreshToken_Request, AuthAPI_RefreshToken_Response> {
|
|
560
|
+
const method = this.methods[26], opt = this._transport.mergeOptions(options);
|
|
561
|
+
return stackIntercept<AuthAPI_RefreshToken_Request, AuthAPI_RefreshToken_Response>("unary", this._transport, method, opt, input);
|
|
562
|
+
}
|
|
488
563
|
/**
|
|
489
564
|
* @generated from protobuf rpc: GetSessionInfo
|
|
490
565
|
*/
|
|
491
566
|
getSessionInfo(input: AuthAPI_GetSessionInfo_Request, options?: RpcOptions): UnaryCall<AuthAPI_GetSessionInfo_Request, AuthAPI_GetSessionInfo_Response> {
|
|
492
|
-
const method = this.methods[
|
|
567
|
+
const method = this.methods[27], opt = this._transport.mergeOptions(options);
|
|
493
568
|
return stackIntercept<AuthAPI_GetSessionInfo_Request, AuthAPI_GetSessionInfo_Response>("unary", this._transport, method, opt, input);
|
|
494
569
|
}
|
|
495
570
|
/**
|
|
496
571
|
* @generated from protobuf rpc: GrantAccess
|
|
497
572
|
*/
|
|
498
573
|
grantAccess(input: AuthAPI_GrantAccess_Request, options?: RpcOptions): UnaryCall<AuthAPI_GrantAccess_Request, AuthAPI_GrantAccess_Response> {
|
|
499
|
-
const method = this.methods[
|
|
574
|
+
const method = this.methods[28], opt = this._transport.mergeOptions(options);
|
|
500
575
|
return stackIntercept<AuthAPI_GrantAccess_Request, AuthAPI_GrantAccess_Response>("unary", this._transport, method, opt, input);
|
|
501
576
|
}
|
|
502
577
|
/**
|
|
503
578
|
* @generated from protobuf rpc: RevokeAccess
|
|
504
579
|
*/
|
|
505
580
|
revokeAccess(input: AuthAPI_RevokeAccess_Request, options?: RpcOptions): UnaryCall<AuthAPI_RevokeAccess_Request, AuthAPI_RevokeAccess_Response> {
|
|
506
|
-
const method = this.methods[
|
|
581
|
+
const method = this.methods[29], opt = this._transport.mergeOptions(options);
|
|
507
582
|
return stackIntercept<AuthAPI_RevokeAccess_Request, AuthAPI_RevokeAccess_Response>("unary", this._transport, method, opt, input);
|
|
508
583
|
}
|
|
509
584
|
/**
|
|
510
585
|
* @generated from protobuf rpc: ListGrants
|
|
511
586
|
*/
|
|
512
587
|
listGrants(input: AuthAPI_ListGrants_Request, options?: RpcOptions): ServerStreamingCall<AuthAPI_ListGrants_Request, AuthAPI_ListGrants_Response> {
|
|
513
|
-
const method = this.methods[
|
|
588
|
+
const method = this.methods[30], opt = this._transport.mergeOptions(options);
|
|
514
589
|
return stackIntercept<AuthAPI_ListGrants_Request, AuthAPI_ListGrants_Response>("serverStreaming", this._transport, method, opt, input);
|
|
515
590
|
}
|
|
516
591
|
/**
|
|
@@ -521,21 +596,21 @@ export class PlatformClient implements IPlatformClient, ServiceInfo {
|
|
|
521
596
|
* @generated from protobuf rpc: MintSignature
|
|
522
597
|
*/
|
|
523
598
|
mintSignature(input: AuthAPI_MintSignature_Request, options?: RpcOptions): UnaryCall<AuthAPI_MintSignature_Request, AuthAPI_MintSignature_Response> {
|
|
524
|
-
const method = this.methods[
|
|
599
|
+
const method = this.methods[31], opt = this._transport.mergeOptions(options);
|
|
525
600
|
return stackIntercept<AuthAPI_MintSignature_Request, AuthAPI_MintSignature_Response>("unary", this._transport, method, opt, input);
|
|
526
601
|
}
|
|
527
602
|
/**
|
|
528
603
|
* @generated from protobuf rpc: GetUserRoot
|
|
529
604
|
*/
|
|
530
605
|
getUserRoot(input: AuthAPI_GetUserRoot_Request, options?: RpcOptions): UnaryCall<AuthAPI_GetUserRoot_Request, AuthAPI_GetUserRoot_Response> {
|
|
531
|
-
const method = this.methods[
|
|
606
|
+
const method = this.methods[32], opt = this._transport.mergeOptions(options);
|
|
532
607
|
return stackIntercept<AuthAPI_GetUserRoot_Request, AuthAPI_GetUserRoot_Response>("unary", this._transport, method, opt, input);
|
|
533
608
|
}
|
|
534
609
|
/**
|
|
535
610
|
* @generated from protobuf rpc: ListUserResources
|
|
536
611
|
*/
|
|
537
612
|
listUserResources(input: AuthAPI_ListUserResources_Request, options?: RpcOptions): ServerStreamingCall<AuthAPI_ListUserResources_Request, AuthAPI_ListUserResources_Response> {
|
|
538
|
-
const method = this.methods[
|
|
613
|
+
const method = this.methods[33], opt = this._transport.mergeOptions(options);
|
|
539
614
|
return stackIntercept<AuthAPI_ListUserResources_Request, AuthAPI_ListUserResources_Response>("serverStreaming", this._transport, method, opt, input);
|
|
540
615
|
}
|
|
541
616
|
/**
|
|
@@ -546,7 +621,7 @@ export class PlatformClient implements IPlatformClient, ServiceInfo {
|
|
|
546
621
|
* @generated from protobuf rpc: ListResourceTypes
|
|
547
622
|
*/
|
|
548
623
|
listResourceTypes(input: MiscAPI_ListResourceTypes_Request, options?: RpcOptions): UnaryCall<MiscAPI_ListResourceTypes_Request, MiscAPI_ListResourceTypes_Response> {
|
|
549
|
-
const method = this.methods[
|
|
624
|
+
const method = this.methods[34], opt = this._transport.mergeOptions(options);
|
|
550
625
|
return stackIntercept<MiscAPI_ListResourceTypes_Request, MiscAPI_ListResourceTypes_Response>("unary", this._transport, method, opt, input);
|
|
551
626
|
}
|
|
552
627
|
/**
|
|
@@ -557,14 +632,14 @@ export class PlatformClient implements IPlatformClient, ServiceInfo {
|
|
|
557
632
|
* @generated from protobuf rpc: Ping
|
|
558
633
|
*/
|
|
559
634
|
ping(input: MaintenanceAPI_Ping_Request, options?: RpcOptions): UnaryCall<MaintenanceAPI_Ping_Request, MaintenanceAPI_Ping_Response> {
|
|
560
|
-
const method = this.methods[
|
|
635
|
+
const method = this.methods[35], opt = this._transport.mergeOptions(options);
|
|
561
636
|
return stackIntercept<MaintenanceAPI_Ping_Request, MaintenanceAPI_Ping_Response>("unary", this._transport, method, opt, input);
|
|
562
637
|
}
|
|
563
638
|
/**
|
|
564
639
|
* @generated from protobuf rpc: License
|
|
565
640
|
*/
|
|
566
641
|
license(input: MaintenanceAPI_License_Request, options?: RpcOptions): UnaryCall<MaintenanceAPI_License_Request, MaintenanceAPI_License_Response> {
|
|
567
|
-
const method = this.methods[
|
|
642
|
+
const method = this.methods[36], opt = this._transport.mergeOptions(options);
|
|
568
643
|
return stackIntercept<MaintenanceAPI_License_Request, MaintenanceAPI_License_Response>("unary", this._transport, method, opt, input);
|
|
569
644
|
}
|
|
570
645
|
}
|