@milaboratories/pl-client 3.10.2 → 3.11.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.
@@ -1,5 +1,6 @@
1
1
  require("../_virtual/_rolldown/runtime.cjs");
2
2
  const require_types = require("./types.cjs");
3
+ const require_timestamp = require("../proto-grpc/google/protobuf/timestamp.cjs");
3
4
  const require_api = require("../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs");
4
5
  const require_code = require("../proto-grpc/google/rpc/code.cjs");
5
6
  const require_errors = require("./errors.cjs");
@@ -412,6 +413,37 @@ var LLPlClient = class LLPlClient {
412
413
  requestedRole: role
413
414
  } })).data, "REST: empty response for login request").token;
414
415
  }
416
+ /** Login via SSO: forwards the raw IdP /token JSON body to the backend.
417
+ * Backend validates signature + nonce + audience and mints a Platforma JWT. */
418
+ async loginSSO(tokenResponse) {
419
+ const cl = this.clientProvider.get();
420
+ if (cl instanceof require_api_client.PlatformClient) return (await cl.login({ credentials: {
421
+ oneofKind: "sso",
422
+ sso: { tokenResponse }
423
+ } }).response).token;
424
+ else return (0, _milaboratories_ts_helpers.notEmpty)((await cl.POST("/v1/auth/login", { body: { sso: { tokenResponse: Buffer.from(tokenResponse).toString("base64") } } })).data, "REST: empty response for SSO login request").token;
425
+ }
426
+ /** Request a one-time PKCE nonce + its expiry from the backend. The desktop is
427
+ * expected to place the nonce verbatim into the OIDC auth-request before
428
+ * redirecting to the IdP; the backend enforces the expiry on {@link loginSSO}. */
429
+ async beginSSOLogin() {
430
+ const cl = this.clientProvider.get();
431
+ if (cl instanceof require_api_client.PlatformClient) {
432
+ const resp = (await cl.beginSSOLogin({}).response).flow;
433
+ if (resp.oneofKind !== "publicPkce") throw new Error(`beginSSOLogin: unsupported flow ${resp.oneofKind}`);
434
+ const exp = (0, _milaboratories_ts_helpers.notEmpty)(resp.publicPkce.expiresAt, "beginSSOLogin: missing expiresAt");
435
+ return {
436
+ nonce: resp.publicPkce.nonce,
437
+ expiresAt: require_timestamp.Timestamp.toDate(exp)
438
+ };
439
+ } else {
440
+ const pkce = (0, _milaboratories_ts_helpers.notEmpty)((0, _milaboratories_ts_helpers.notEmpty)((await cl.POST("/v1/auth/sso/begin-login", { body: {} })).data, "REST: empty response for beginSSOLogin request").publicPkce, "REST: missing publicPkce in beginSSOLogin");
441
+ return {
442
+ nonce: pkce.nonce,
443
+ expiresAt: new Date(pkce.expiresAt)
444
+ };
445
+ }
446
+ }
415
447
  /** Refresh the current JWT, preserving session id and role. */
416
448
  async refreshToken(opts = {}) {
417
449
  const cl = this.clientProvider.get();
@@ -1 +1 @@
1
- {"version":3,"file":"ll_client.cjs","names":["plAddressToConfig","inferAuthRefreshTime","interceptors","GrpcPlApiClient","createClient","SUPPORTED_WIRE_PROTOCOLS","compressionAlgorithms","ChannelCredentials","GrpcTransport","parsePlJwt","parseResponseError","Code","InterceptingCall","GrpcStatus","AuthAPI_Role","hasCapability","isAbortedError","LLPlTransaction","WebSocketBiDiStream","TxAPI_ClientMessage","TxAPI_ServerMessage"],"sources":["../../src/core/ll_client.ts"],"sourcesContent":["import { PlatformClient as GrpcPlApiClient } from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client\";\nimport type { ClientOptions, Interceptor } from \"@grpc/grpc-js\";\nimport {\n ChannelCredentials,\n InterceptingCall,\n status as GrpcStatus,\n compressionAlgorithms,\n} from \"@grpc/grpc-js\";\nimport type {\n AuthInformation,\n AuthOps,\n PlClientConfig,\n PlConnectionStatus,\n PlConnectionStatusListener,\n} from \"./config\";\nimport { plAddressToConfig, type wireProtocol, SUPPORTED_WIRE_PROTOCOLS } from \"./config\";\nimport type { GrpcOptions } from \"@protobuf-ts/grpc-transport\";\nimport { GrpcTransport } from \"@protobuf-ts/grpc-transport\";\nimport { LLPlTransaction } from \"./ll_transaction\";\nimport { setResourceSignaturesRequired } from \"./types\";\nimport { parsePlJwt } from \"../util/pl\";\nimport { type Dispatcher, interceptors } from \"undici\";\nimport type { Middleware } from \"openapi-fetch\";\nimport { inferAuthRefreshTime } from \"./auth\";\nimport { hasCapability, type BackendCapability } from \"./capabilities\";\nimport { defaultHttpDispatcher } from \"@milaboratories/pl-http\";\nimport type { WireClientProvider, WireClientProviderFactory, WireConnection } from \"./wire\";\nimport { parseHttpAuth } from \"@milaboratories/pl-model-common\";\nimport type * as grpcTypes from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport {\n type PlApiPaths,\n type PlRestClientType,\n createClient,\n parseResponseError,\n} from \"../proto-rest\";\nimport { notEmpty, retry, withTimeout, type RetryOptions } from \"@milaboratories/ts-helpers\";\nimport { Code } from \"../proto-grpc/google/rpc/code\";\nimport { WebSocketBiDiStream } from \"./websocket_stream\";\nimport {\n AuthAPI_Role,\n TxAPI_ClientMessage,\n TxAPI_ServerMessage,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport type { MiLogger } from \"@milaboratories/ts-helpers\";\nimport { isAbortedError } from \"./errors\";\n\nexport interface PlCallOps {\n timeout?: number;\n abortSignal?: AbortSignal;\n}\n\n// Parses leading \"<major>.<minor>.<patch>\" from a version string like\n// \"3.1.1\" or \"3.1.1-rc1\" and returns true if the parsed version is >= target.\n// Returns false for unparseable versions (safer to assume an old backend).\nfunction isVersionAtLeast(version: string, target: [number, number, number]): boolean {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)/.exec(version);\n if (!match) return false;\n const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];\n for (let i = 0; i < 3; i++) {\n if (parsed[i] !== target[i]) return parsed[i] > target[i];\n }\n return true;\n}\n\n// Returns true iff `version` is strictly after the release tag `target`. Dev\n// builds (`git describe`-style, e.g. \"3.5.0-224-g0ca182\") are considered\n// AFTER the matching release tag — they include commits past the tag and so\n// have any change merged after it. Released versions with the same triplet\n// return false (we want the tag itself to be excluded).\n//\n// Examples for target [3,5,0]:\n// \"3.5.0\" → false (the tagged release)\n// \"3.5.0-224-g0ca182\" → true (dev build past the tag)\n// \"3.5.1\" → true\n// \"3.4.9\" → false\n// Returns false for unparseable versions.\nfunction isAfterVersion(version: string, target: [number, number, number]): boolean {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(.*)$/.exec(version);\n if (!match) return false;\n const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];\n const suffix = match[4];\n for (let i = 0; i < 3; i++) {\n if (parsed[i] !== target[i]) return parsed[i] > target[i];\n }\n return suffix !== \"\";\n}\n\nclass WireClientProviderImpl<Client> implements WireClientProvider<Client> {\n private client: Client | undefined = undefined;\n\n constructor(\n private readonly wireOpts: () => WireConnection,\n private readonly clientConstructor: (wireOpts: WireConnection) => Client,\n ) {}\n\n public reset(): void {\n this.client = undefined;\n }\n\n public get(): Client {\n if (this.client === undefined) this.client = this.clientConstructor(this.wireOpts());\n return this.client;\n }\n}\n\n/** Abstract out low level networking and authorization details */\nexport class LLPlClient implements WireClientProviderFactory {\n /** Initial authorization information */\n private authInformation?: AuthInformation;\n /** Will be executed by the client when it is required */\n private readonly onAuthUpdate?: (newInfo: AuthInformation) => void;\n /** Will be executed if auth-related error happens during normal client operation */\n private readonly onAuthError?: () => void;\n /** Will be executed by the client when it is required */\n private readonly onAuthRefreshProblem?: (error: unknown) => void;\n /** Threshold after which auth info refresh is required */\n private refreshTimestamp?: number;\n\n /** Cached Ping response. Populated by build() before it returns; refreshed by every later ping(). */\n private _serverInfo?: grpcTypes.MaintenanceAPI_Ping_Response;\n private _authMethodsSync?: grpcTypes.AuthAPI_ListMethods_Response;\n\n private _status: PlConnectionStatus = \"OK\";\n private readonly statusListener?: PlConnectionStatusListener;\n\n private _wireProto: wireProtocol = \"grpc\";\n private _wireConn!: WireConnection;\n\n private readonly _restInterceptors: Dispatcher.DispatcherComposeInterceptor[];\n private readonly _restMiddlewares: Middleware[];\n private readonly _grpcInterceptors: Interceptor[];\n private readonly providers: WeakRef<WireClientProviderImpl<any>>[] = [];\n\n public readonly clientProvider: WireClientProvider<PlRestClientType | GrpcPlApiClient>;\n\n public readonly httpDispatcher: Dispatcher;\n\n public static async build(\n configOrAddress: PlClientConfig | string,\n ops: {\n auth?: AuthOps;\n statusListener?: PlConnectionStatusListener;\n shouldUseGzip?: boolean;\n logger?: MiLogger;\n useAutoDetectWireProtocol?: boolean;\n } = {},\n ) {\n const conf =\n typeof configOrAddress === \"string\" ? plAddressToConfig(configOrAddress) : configOrAddress;\n\n const pl = new LLPlClient(conf, ops);\n\n // FIXME(rfiskov)[MILAB-5275]: Investigate why autodetect randomly fails; temporary turn it off.\n if (ops.useAutoDetectWireProtocol) {\n await pl.detectOptimalWireProtocol();\n }\n\n // Guarantee a ping happened so capability-gated paths (login, refresh) can branch synchronously.\n // In the autodetect path the loop's last successful ping already populated _serverInfo via the\n // side-effect in ping(); this fallback covers the path where autodetect is disabled.\n if (!pl._serverInfo) await pl.ping();\n\n // Install the process-global signature-strictness flag based on backend version.\n setResourceSignaturesRequired(pl.supportsResourceSignatures);\n\n // Guarantee authMethods happened so client can make weighted decision on which auth method to use.\n if (!pl._authMethodsSync) await pl.authMethods();\n\n return pl;\n }\n\n private constructor(\n public readonly conf: PlClientConfig,\n private readonly ops: {\n auth?: AuthOps;\n statusListener?: PlConnectionStatusListener;\n shouldUseGzip?: boolean;\n logger?: MiLogger;\n } = {},\n ) {\n const { auth, statusListener } = ops;\n\n if (auth !== undefined) {\n this.refreshTimestamp = inferAuthRefreshTime(\n auth.authInformation,\n this.conf.authMaxRefreshSeconds,\n );\n this.authInformation = auth.authInformation;\n this.onAuthUpdate = auth.onUpdate;\n this.onAuthRefreshProblem = auth.onUpdateError;\n this.onAuthError = auth.onAuthError;\n }\n\n this._restInterceptors = [];\n this._restMiddlewares = [];\n this._grpcInterceptors = [];\n\n if (auth !== undefined) {\n this._restInterceptors.push(this.createRestAuthInterceptor());\n this._grpcInterceptors.push(this.createGrpcAuthInterceptor());\n }\n this._restInterceptors.push(interceptors.retry({ statusCodes: [] })); // Handle errors with openapi-fetch middleware.\n this._restMiddlewares.push(this.createRestErrorMiddleware());\n this._grpcInterceptors.push(this.createGrpcErrorInterceptor());\n\n this.httpDispatcher = defaultHttpDispatcher(this.conf.httpProxy);\n if (this.conf.wireProtocol) {\n this._wireProto = this.conf.wireProtocol;\n }\n\n this.initWireConnection(this._wireProto);\n\n if (statusListener !== undefined) {\n this.statusListener = statusListener;\n statusListener(this._status);\n }\n\n this.clientProvider = this.createWireClientProvider((wireConn) => {\n if (wireConn.type === \"grpc\") {\n return new GrpcPlApiClient(wireConn.Transport);\n } else {\n return createClient<PlApiPaths>({\n hostAndPort: wireConn.Config.hostAndPort,\n ssl: wireConn.Config.ssl,\n dispatcher: wireConn.Dispatcher,\n middlewares: wireConn.Middlewares,\n });\n }\n });\n }\n\n private initWireConnection(protocol: wireProtocol) {\n switch (protocol) {\n case \"rest\":\n this.initRestConnection();\n return;\n case \"grpc\":\n this.initGrpcConnection(this.ops.shouldUseGzip ?? false);\n return;\n default:\n ((v: never) => {\n throw new Error(\n `Unsupported wire protocol '${v as string}'. Use one of: ${SUPPORTED_WIRE_PROTOCOLS.join(\", \")}`,\n );\n })(protocol);\n }\n }\n\n private initRestConnection(): void {\n const dispatcher = defaultHttpDispatcher(this.conf.grpcProxy, this._restInterceptors);\n this._replaceWireConnection({\n type: \"rest\",\n Config: this.conf,\n Dispatcher: dispatcher,\n Middlewares: this._restMiddlewares,\n });\n }\n\n /**\n * Initializes (or reinitializes) _grpcTransport\n * @param gzip - whether to enable gzip compression\n */\n private initGrpcConnection(gzip: boolean) {\n const clientOptions: ClientOptions = {\n \"grpc.keepalive_time_ms\": 30_000, // 30 seconds\n \"grpc.service_config_disable_resolution\": 1, // Disable DNS TXT lookups for service config\n interceptors: this._grpcInterceptors,\n };\n\n if (gzip) clientOptions[\"grpc.default_compression_algorithm\"] = compressionAlgorithms.gzip;\n\n //\n // Leaving it here for now\n // https://github.com/grpc/grpc-node/issues/2788\n //\n // We should implement message pooling algorithm to overcome hardcoded NO_DELAY behaviour\n // of HTTP/2 and allow our small messages to batch together.\n //\n const grpcOptions: GrpcOptions = {\n host: this.conf.hostAndPort,\n timeout: this.conf.defaultRequestTimeout,\n channelCredentials: this.conf.ssl\n ? ChannelCredentials.createSsl()\n : ChannelCredentials.createInsecure(),\n clientOptions,\n };\n\n const grpcProxy =\n typeof this.conf.grpcProxy === \"string\" ? { url: this.conf.grpcProxy } : this.conf.grpcProxy;\n\n if (grpcProxy?.url) {\n const url = new URL(grpcProxy.url);\n if (grpcProxy.auth) {\n const parsed = parseHttpAuth(grpcProxy.auth);\n if (parsed.scheme !== \"Basic\") {\n throw new Error(`Unsupported auth scheme: ${parsed.scheme as string}.`);\n }\n url.username = parsed.username;\n url.password = parsed.password;\n }\n process.env.grpc_proxy = url.toString();\n } else {\n delete process.env.grpc_proxy;\n }\n\n this._replaceWireConnection({ type: \"grpc\", Transport: new GrpcTransport(grpcOptions) });\n }\n\n private _replaceWireConnection(newConn: WireConnection): void {\n const oldConn = this._wireConn;\n this._wireConn = newConn;\n this._wireProto = newConn.type;\n\n // Reset all providers to let them reinitialize their clients\n for (let i = 0; i < this.providers.length; i++) {\n const provider = this.providers[i].deref();\n if (provider === undefined) {\n // at the same time we need to remove providers that are no longer valid\n this.providers.splice(i, 1);\n i--;\n } else {\n provider.reset();\n }\n }\n\n if (oldConn !== undefined && oldConn.type === \"grpc\") oldConn.Transport.close();\n }\n\n private providerCleanupCounter = 0;\n\n /**\n * Creates a provider for a grpc client. Returned provider will create fresh client whenever the underlying transport is reset.\n *\n * @param clientConstructor - a factory function that creates a grpc client\n */\n public createWireClientProvider<Client>(\n clientConstructor: (transport: WireConnection) => Client,\n ): WireClientProvider<Client> {\n // We need to cleanup providers periodically to avoid memory leaks.\n // This is a simple heuristic to avoid memory leaks.\n // We could use a more sophisticated algorithm, but this is good enough for now.\n this.providerCleanupCounter++;\n if (this.providerCleanupCounter >= 16) {\n for (let i = 0; i < this.providers.length; i++) {\n const provider = this.providers[i].deref();\n if (provider === undefined) {\n this.providers.splice(i, 1);\n i--;\n }\n }\n this.providerCleanupCounter = 0;\n }\n\n const provider = new WireClientProviderImpl<Client>(() => this._wireConn, clientConstructor);\n this.providers.push(new WeakRef(provider));\n return provider;\n }\n\n public get wireConnection(): WireConnection {\n return this._wireConn;\n }\n\n public get wireProtocol(): wireProtocol | undefined {\n return this._wireProto;\n }\n\n /** Returns true if client is authenticated. Even with anonymous auth information\n * connection is considered authenticated. Unauthenticated clients are used for\n * login and similar tasks, see {@link UnauthenticatedPlClient}. */\n public get authenticated(): boolean {\n return this.authInformation !== undefined;\n }\n\n /** null means anonymous connection */\n public get authUser(): string | null {\n if (!this.authenticated) throw new Error(\"Client is not authenticated\");\n if (this.authInformation?.jwtToken) {\n if (this.hasCapability(\"auth:v2\")) {\n return parsePlJwt(this.authInformation?.jwtToken).sub;\n }\n return parsePlJwt(this.authInformation?.jwtToken).user.login;\n } else return null;\n }\n\n private updateStatus(newStatus: PlConnectionStatus) {\n process.nextTick(() => {\n if (this._status !== newStatus) {\n this._status = newStatus;\n if (this.statusListener !== undefined) this.statusListener(this._status);\n if (newStatus === \"Unauthenticated\" && this.onAuthError !== undefined) this.onAuthError();\n }\n });\n }\n\n public get status(): PlConnectionStatus {\n return this._status;\n }\n\n private authRefreshInProgress: boolean = false;\n\n private refreshAuthInformationIfNeeded(): void {\n if (\n this.refreshTimestamp === undefined ||\n Date.now() < this.refreshTimestamp ||\n this.authRefreshInProgress ||\n this._status === \"Unauthenticated\"\n )\n return;\n\n // Running refresh in background`\n this.authRefreshInProgress = true;\n void (async () => {\n try {\n const ttl = BigInt(this.conf.authTTLSeconds);\n const token = this.hasCapability(\"auth:v2\")\n ? await this.refreshToken({ ttlSeconds: ttl })\n : await this.getJwtToken(ttl);\n this.authInformation = { jwtToken: token };\n this.refreshTimestamp = inferAuthRefreshTime(\n this.authInformation,\n this.conf.authMaxRefreshSeconds,\n );\n if (this.onAuthUpdate) this.onAuthUpdate(this.authInformation);\n } catch (e: unknown) {\n if (this.onAuthRefreshProblem) this.onAuthRefreshProblem(e);\n } finally {\n this.authRefreshInProgress = false;\n }\n })();\n }\n\n /**\n * Creates middleware that parses error responses and handles them centrally.\n * This middleware runs before openapi-fetch parses the response, so we need to\n * manually parse the response body for error responses.\n */\n private createRestErrorMiddleware(): Middleware {\n return {\n onResponse: async ({ request: _request, response, options: _options }) => {\n const { body, ...resOptions } = response;\n\n if ([502, 503, 504].includes(response.status)) {\n // Service unavailable, bad gateway, gateway timeout\n this.updateStatus(\"Disconnected\");\n return new Response(body, { ...resOptions, status: response.status });\n }\n\n const respErr = await parseResponseError(response);\n if (!respErr.error) {\n // No error: nice!\n return new Response(respErr.origBody ?? body, { ...resOptions, status: response.status });\n }\n\n if (typeof respErr.error === \"string\") {\n // Non-standard error or normal response: let later middleware to deal wit it.\n return new Response(respErr.error, { ...resOptions, status: response.status });\n }\n\n if (respErr.error.code === Code.UNAUTHENTICATED) {\n this.updateStatus(\"Unauthenticated\");\n }\n\n // Let later middleware to deal with standard gRPC error.\n return new Response(respErr.origBody, { ...resOptions, status: response.status });\n },\n };\n }\n\n /** Detects certain errors and update client status accordingly when using GRPC wire connection */\n private createGrpcErrorInterceptor(): Interceptor {\n return (options, nextCall) => {\n return new InterceptingCall(nextCall(options), {\n start: (metadata, listener, next) => {\n next(metadata, {\n onReceiveStatus: (status, next) => {\n if (status.code == GrpcStatus.UNAUTHENTICATED)\n // (!!!) don't change to \"===\"\n this.updateStatus(\"Unauthenticated\");\n if (status.code == GrpcStatus.UNAVAILABLE)\n // (!!!) don't change to \"===\"\n this.updateStatus(\"Disconnected\");\n next(status);\n },\n });\n },\n });\n };\n }\n\n private createRestAuthInterceptor(): Dispatcher.DispatcherComposeInterceptor {\n return (dispatch) => {\n return (options, handler) => {\n if (this.authInformation?.jwtToken !== undefined) {\n // TODO: check this magic really works and gets called\n options.headers = {\n ...options.headers,\n authorization: \"Bearer \" + this.authInformation.jwtToken,\n };\n this.refreshAuthInformationIfNeeded();\n }\n\n return dispatch(options, handler);\n };\n };\n }\n\n /** Injects authentication information if needed */\n private createGrpcAuthInterceptor(): Interceptor {\n return (options, nextCall) => {\n return new InterceptingCall(nextCall(options), {\n start: (metadata, listener, next) => {\n if (this.authInformation?.jwtToken !== undefined) {\n metadata.set(\"authorization\", \"Bearer \" + this.authInformation.jwtToken);\n this.refreshAuthInformationIfNeeded();\n next(metadata, listener);\n } else {\n next(metadata, listener);\n }\n },\n });\n };\n }\n\n public async getJwtToken(\n ttlSeconds: bigint,\n options?: { authorization?: string; role?: AuthAPI_Role },\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const role = options?.role ?? AuthAPI_Role.UNSPECIFIED;\n\n if (cl instanceof GrpcPlApiClient) {\n const meta: Record<string, string> = {};\n if (options?.authorization) meta.authorization = options.authorization;\n return (\n await cl.getJWTToken(\n {\n expiration: { seconds: ttlSeconds, nanos: 0 },\n requestedRole: role,\n },\n { meta },\n ).response\n ).token;\n } else {\n const headers: Record<string, string> = {};\n if (options?.authorization) headers.authorization = options.authorization;\n const resp = cl.POST(\"/v1/auth/jwt-token\", {\n body: { expiration: `${ttlSeconds}s`, requestedRole: role },\n headers,\n });\n return notEmpty((await resp).data, \"REST: empty response for JWT token request\").token;\n }\n }\n\n /** Login via username/password. Returns a fresh JWT. Backend creates a new session per call. */\n public async loginBasic(\n user: string,\n password: string,\n opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"basic\",\n basic: { login: user, password },\n },\n expiration: { seconds: ttl, nanos: 0 },\n requestedRole: role,\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n // openapi-typescript generated all body fields as required, but Login.Request\n // has a credentials oneof — only one of `basic`/`token` is sent. Cast around it.\n body: {\n basic: { login: user, password },\n expiration: `${ttl}s`,\n requestedRole: role,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for login request\").token;\n }\n }\n\n /** Login via opaque bearer token (controller pre-shared secret, OIDC id-token, etc.).\n * String input is UTF-8 encoded. Returns a fresh Platforma JWT. */\n public async loginWithToken(\n token: Uint8Array | string,\n opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;\n const bytes = typeof token === \"string\" ? Buffer.from(token, \"utf8\") : token;\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"token\",\n token: { token: bytes },\n },\n expiration: { seconds: ttl, nanos: 0 },\n requestedRole: role,\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n // openapi-typescript marks all body fields as required, but Login.Request has a oneof.\n // REST encodes `bytes` as a base64 string.\n body: {\n token: { token: Buffer.from(bytes).toString(\"base64\") },\n expiration: `${ttl}s`,\n requestedRole: role,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for login request\").token;\n }\n }\n\n /** Refresh the current JWT, preserving session id and role. */\n public async refreshToken(opts: { ttlSeconds?: bigint } = {}): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const currentToken = notEmpty(\n this.authInformation?.jwtToken,\n \"refreshToken called without a current JWT\",\n );\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.refreshToken({\n token: currentToken,\n expiration: { seconds: ttl, nanos: 0 },\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/refresh\", {\n body: { token: currentToken, expiration: `${ttl}s` },\n });\n return notEmpty((await resp).data, \"REST: empty response for refresh request\").token;\n }\n }\n\n public async ping(): Promise<grpcTypes.MaintenanceAPI_Ping_Response> {\n const cl = this.clientProvider.get();\n let resp: grpcTypes.MaintenanceAPI_Ping_Response;\n if (cl instanceof GrpcPlApiClient) {\n resp = (await cl.ping({})).response;\n } else {\n // The REST ping response predates the `capabilities` field (proto field 9).\n // Old servers omit it; treat absence as empty capability list.\n const pingData = notEmpty(\n (await cl.GET(\"/v1/ping\")).data,\n \"REST: empty response for ping request\",\n );\n resp = {\n ...(pingData as unknown as grpcTypes.MaintenanceAPI_Ping_Response),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n capabilities: (pingData as any).capabilities ?? [],\n };\n }\n this._serverInfo = resp;\n return resp;\n }\n\n /** Cached Ping response. Always populated post-build(); throws if accessed earlier. */\n public get serverInfo(): grpcTypes.MaintenanceAPI_Ping_Response {\n if (!this._serverInfo) {\n throw new Error(\"LLPlClient.serverInfo accessed before build() completed\");\n }\n return this._serverInfo;\n }\n\n /** Synchronous capability check against the cached Ping response. */\n public hasCapability(capability: BackendCapability): boolean {\n return hasCapability(this.serverInfo.capabilities, capability);\n }\n\n /** True if the backend produces signed resource ids (and accepts the\n * setDefaultColor TX request). Tagged at 3.3.0 — backends older than this\n * return resources without signatures and reject color-proof requests. */\n public get supportsResourceSignatures(): boolean {\n return isVersionAtLeast(this.serverInfo.coreVersion, [3, 3, 0]);\n }\n\n /**\n * True if the backend honors per-file `permissions` on workdir fill rules\n * (PR #1830 in milaboratory/pl). Backends before this change ignore the\n * requested mode and always land files at the canonical archive perm,\n * making `exec.builder().writeFile/addFile({ writable: true })` a no-op.\n *\n * Tagged at 3.5.0 cut without the change, so [3, 5, 0] excludes the tagged\n * release but includes dev builds past the tag (e.g. \"3.5.0-224-g0ca182\").\n */\n public get supportsWritableWorkdirFiles(): boolean {\n return isAfterVersion(this.serverInfo.coreVersion, [3, 5, 0]);\n }\n\n /**\n * Detects the best available wire protocol.\n * If wireProtocol is explicitly configured, does nothing.\n * Otherwise probes the current protocol via ping; if it fails, switches to the alternative.\n */\n private async detectOptimalWireProtocol() {\n if (this.conf.wireProtocol) {\n return;\n }\n\n // Each retry is:\n // - ping request timeout (100 to 3_000ms)\n // - backoff delay (30 to 500ms)\n //\n // 30 attempts are ~43 seconds of overall waiting time.\n // Think twice on overall time this thing takes to complete when changing these parameters.\n // It may block UI when connecting to the server and loading projects list.\n const pingTimeoutFactor = 1.3;\n const maxPingTimeoutMs = 3_000;\n const retryOptions: RetryOptions = {\n type: \"exponentialBackoff\",\n maxAttempts: 30,\n initialDelay: 30,\n backoffMultiplier: 1.3,\n jitter: 0.2,\n maxDelay: 500,\n };\n\n let attempt = 1;\n let pingTimeoutMs = 100;\n await retry(\n () => withTimeout(this.ping(), pingTimeoutMs),\n retryOptions,\n (e: unknown) => {\n if (isAbortedError(e)) {\n this.ops.logger?.info(\n `Wire proto autodetect: ping timed out after ${pingTimeoutMs}ms: attempt=${attempt}, wire=${this._wireProto}`,\n );\n\n if (attempt % 2 === 0) {\n // We have 2 wire protocols to check. Increase timeout each 2 attempts.\n pingTimeoutMs = Math.min(\n Math.round(pingTimeoutMs * pingTimeoutFactor),\n maxPingTimeoutMs,\n );\n }\n } else {\n this.ops.logger?.info(\n `Wire proto autodetect: ping failed: attempt=${attempt}, wire=${this._wireProto}, err=${String(e)}`,\n );\n }\n\n attempt++;\n const protocol = this._wireProto === \"grpc\" ? \"rest\" : \"grpc\";\n this.ops.logger?.info(\n `Wire protocol autodetect next attempt: will try wire '${protocol}' with timeout ${pingTimeoutMs}ms`,\n );\n this.initWireConnection(protocol);\n return true;\n },\n );\n }\n\n public async license(): Promise<grpcTypes.MaintenanceAPI_License_Response> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (await cl.license({})).response;\n } else {\n const resp = notEmpty(\n (await cl.GET(\"/v1/license\")).data,\n \"REST: empty response for license request\",\n );\n return {\n status: resp.status,\n isOk: resp.isOk,\n responseBody: Uint8Array.from(Buffer.from(resp.responseBody)),\n };\n }\n }\n\n public async authMethods(): Promise<grpcTypes.AuthAPI_ListMethods_Response> {\n const cl = this.clientProvider.get();\n let resp: grpcTypes.AuthAPI_ListMethods_Response;\n if (cl instanceof GrpcPlApiClient) {\n resp = (await cl.authMethods({})).response;\n } else {\n const wsResponse = notEmpty(\n (await cl.GET(\"/v1/auth/methods\")).data,\n \"REST: empty response for auth methods request\",\n );\n // OpenAPI schema flattens the protobuf oneof into `{ basic?, token? }`,\n // while protobuf-ts models it as a discriminated union. Reshape per item.\n resp = {\n methods: (wsResponse.methods ?? []).map((m): grpcTypes.AuthAPI_ListMethods_MethodInfo => {\n const base = { id: m.id, description: m.description };\n if (m.basic !== undefined) {\n return { ...base, method: { oneofKind: \"basic\", basic: m.basic } };\n }\n if (m.token !== undefined) {\n return { ...base, method: { oneofKind: \"token\", token: m.token } };\n }\n if (m.sso !== undefined) {\n return { ...base, method: { oneofKind: \"sso\", sso: m.sso } };\n }\n return { ...base, method: { oneofKind: undefined } };\n }),\n };\n }\n\n this._authMethodsSync = resp;\n return resp;\n }\n\n public get authMethodsSync(): grpcTypes.AuthAPI_ListMethods_Response {\n if (!this._authMethodsSync) {\n throw new Error(\"LLPlClient.authMethodsSync accessed before build() completed\");\n }\n return this._authMethodsSync;\n }\n\n public async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<grpcTypes.AuthAPI_GetUserRoot_Response> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.getUserRoot({\n login: opts.login ?? \"\",\n createIfNotExists: opts.createIfNotExists ?? false,\n })\n ).response;\n } else {\n const resp = notEmpty(\n (\n await cl.POST(\"/v1/auth/user-root\", {\n body: {\n login: opts.login ?? \"\",\n createIfNotExists: opts.createIfNotExists ?? false,\n },\n })\n ).data,\n \"REST: empty response for getUserRoot request\",\n );\n return {\n userRoot: resp.userRoot\n ? {\n resourceId: BigInt(resp.userRoot.resourceId),\n resourceSignature: Uint8Array.from(\n Buffer.from(resp.userRoot.resourceSignature, \"base64\"),\n ),\n }\n : undefined,\n };\n }\n }\n\n public async listUserResources(\n opts: { login?: string; startFrom?: bigint; limit?: number } = {},\n ): Promise<grpcTypes.AuthAPI_ListUserResources_Response[]> {\n const cl = this.clientProvider.get();\n\n if (!(cl instanceof GrpcPlApiClient)) {\n throw new Error(\"ListUserResources requires gRPC wire protocol; REST is not supported\");\n }\n\n const call = cl.listUserResources({\n login: opts.login ?? \"\",\n startFrom: opts.startFrom ?? 0n,\n limit: opts.limit ?? 0,\n });\n const responses: grpcTypes.AuthAPI_ListUserResources_Response[] = [];\n for await (const msg of call.responses) {\n responses.push(msg);\n }\n return responses;\n }\n\n public async txSync(txId: bigint): Promise<void> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n await cl.txSync({ txId: BigInt(txId) });\n } else {\n await cl.POST(\"/v1/tx-sync\", { body: { txId: txId.toString() } });\n }\n }\n\n createTx(rw: boolean, ops: PlCallOps = {}): LLPlTransaction {\n return new LLPlTransaction((abortSignal) => {\n let totalAbortSignal = abortSignal;\n if (ops.abortSignal) totalAbortSignal = AbortSignal.any([totalAbortSignal, ops.abortSignal]);\n\n const timeout =\n ops.timeout ??\n (rw ? this.conf.defaultRWTransactionTimeout : this.conf.defaultROTransactionTimeout);\n\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return cl.tx({\n abort: totalAbortSignal,\n timeout,\n });\n }\n\n const wireConn = this.wireConnection;\n if (wireConn.type === \"rest\") {\n // For REST/WebSocket protocol, timeout needs to be converted to AbortSignal\n if (timeout !== undefined) {\n totalAbortSignal = AbortSignal.any([totalAbortSignal, AbortSignal.timeout(timeout)]);\n }\n\n // The gRPC transport has the auth interceptor that already handles it, but here we need to refresh the auth information to be safe.\n this.refreshAuthInformationIfNeeded();\n\n const wsUrl = this.conf.ssl\n ? `wss://${this.conf.hostAndPort}/v1/ws/tx`\n : `ws://${this.conf.hostAndPort}/v1/ws/tx`;\n\n return new WebSocketBiDiStream(\n wsUrl,\n (msg) => TxAPI_ClientMessage.toBinary(msg),\n (data) => TxAPI_ServerMessage.fromBinary(new Uint8Array(data)),\n {\n abortSignal: totalAbortSignal,\n jwtToken: this.authInformation?.jwtToken,\n dispatcher: wireConn.Dispatcher,\n\n onComplete: async (stream) =>\n stream.requests.send({\n // Ask server to gracefully close the stream on its side, if not done yet.\n requestId: 0,\n request: { oneofKind: \"streamClose\", streamClose: {} },\n }),\n },\n );\n }\n\n throw new Error(`transactions are not supported for wire protocol ${this._wireProto}`);\n });\n }\n\n /** Closes underlying transport */\n public async close() {\n if (this.wireConnection.type === \"grpc\") {\n this.wireConnection.Transport.close();\n } else {\n // TODO: close all WS connections\n }\n await this.httpDispatcher.destroy();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ll_client.cjs","names":["plAddressToConfig","inferAuthRefreshTime","interceptors","GrpcPlApiClient","createClient","SUPPORTED_WIRE_PROTOCOLS","compressionAlgorithms","ChannelCredentials","GrpcTransport","parsePlJwt","parseResponseError","Code","InterceptingCall","GrpcStatus","AuthAPI_Role","Timestamp","hasCapability","isAbortedError","LLPlTransaction","WebSocketBiDiStream","TxAPI_ClientMessage","TxAPI_ServerMessage"],"sources":["../../src/core/ll_client.ts"],"sourcesContent":["import { PlatformClient as GrpcPlApiClient } from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client\";\nimport type { ClientOptions, Interceptor } from \"@grpc/grpc-js\";\nimport {\n ChannelCredentials,\n InterceptingCall,\n status as GrpcStatus,\n compressionAlgorithms,\n} from \"@grpc/grpc-js\";\nimport type {\n AuthInformation,\n AuthOps,\n PlClientConfig,\n PlConnectionStatus,\n PlConnectionStatusListener,\n} from \"./config\";\nimport { plAddressToConfig, type wireProtocol, SUPPORTED_WIRE_PROTOCOLS } from \"./config\";\nimport type { GrpcOptions } from \"@protobuf-ts/grpc-transport\";\nimport { GrpcTransport } from \"@protobuf-ts/grpc-transport\";\nimport { LLPlTransaction } from \"./ll_transaction\";\nimport { setResourceSignaturesRequired } from \"./types\";\nimport { parsePlJwt } from \"../util/pl\";\nimport { type Dispatcher, interceptors } from \"undici\";\nimport type { Middleware } from \"openapi-fetch\";\nimport { inferAuthRefreshTime } from \"./auth\";\nimport { hasCapability, type BackendCapability } from \"./capabilities\";\nimport { defaultHttpDispatcher } from \"@milaboratories/pl-http\";\nimport type { WireClientProvider, WireClientProviderFactory, WireConnection } from \"./wire\";\nimport { parseHttpAuth } from \"@milaboratories/pl-model-common\";\nimport type * as grpcTypes from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport {\n type PlApiPaths,\n type PlRestClientType,\n createClient,\n parseResponseError,\n} from \"../proto-rest\";\nimport { notEmpty, retry, withTimeout, type RetryOptions } from \"@milaboratories/ts-helpers\";\nimport { Code } from \"../proto-grpc/google/rpc/code\";\nimport { WebSocketBiDiStream } from \"./websocket_stream\";\nimport {\n AuthAPI_Role,\n TxAPI_ClientMessage,\n TxAPI_ServerMessage,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport type { MiLogger } from \"@milaboratories/ts-helpers\";\nimport { isAbortedError } from \"./errors\";\nimport { Timestamp } from \"../proto-grpc/google/protobuf/timestamp\";\n\nexport interface PlCallOps {\n timeout?: number;\n abortSignal?: AbortSignal;\n}\n\n// Parses leading \"<major>.<minor>.<patch>\" from a version string like\n// \"3.1.1\" or \"3.1.1-rc1\" and returns true if the parsed version is >= target.\n// Returns false for unparseable versions (safer to assume an old backend).\nfunction isVersionAtLeast(version: string, target: [number, number, number]): boolean {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)/.exec(version);\n if (!match) return false;\n const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];\n for (let i = 0; i < 3; i++) {\n if (parsed[i] !== target[i]) return parsed[i] > target[i];\n }\n return true;\n}\n\n// Returns true iff `version` is strictly after the release tag `target`. Dev\n// builds (`git describe`-style, e.g. \"3.5.0-224-g0ca182\") are considered\n// AFTER the matching release tag — they include commits past the tag and so\n// have any change merged after it. Released versions with the same triplet\n// return false (we want the tag itself to be excluded).\n//\n// Examples for target [3,5,0]:\n// \"3.5.0\" → false (the tagged release)\n// \"3.5.0-224-g0ca182\" → true (dev build past the tag)\n// \"3.5.1\" → true\n// \"3.4.9\" → false\n// Returns false for unparseable versions.\nfunction isAfterVersion(version: string, target: [number, number, number]): boolean {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(.*)$/.exec(version);\n if (!match) return false;\n const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];\n const suffix = match[4];\n for (let i = 0; i < 3; i++) {\n if (parsed[i] !== target[i]) return parsed[i] > target[i];\n }\n return suffix !== \"\";\n}\n\nclass WireClientProviderImpl<Client> implements WireClientProvider<Client> {\n private client: Client | undefined = undefined;\n\n constructor(\n private readonly wireOpts: () => WireConnection,\n private readonly clientConstructor: (wireOpts: WireConnection) => Client,\n ) {}\n\n public reset(): void {\n this.client = undefined;\n }\n\n public get(): Client {\n if (this.client === undefined) this.client = this.clientConstructor(this.wireOpts());\n return this.client;\n }\n}\n\n/** Abstract out low level networking and authorization details */\nexport class LLPlClient implements WireClientProviderFactory {\n /** Initial authorization information */\n private authInformation?: AuthInformation;\n /** Will be executed by the client when it is required */\n private readonly onAuthUpdate?: (newInfo: AuthInformation) => void;\n /** Will be executed if auth-related error happens during normal client operation */\n private readonly onAuthError?: () => void;\n /** Will be executed by the client when it is required */\n private readonly onAuthRefreshProblem?: (error: unknown) => void;\n /** Threshold after which auth info refresh is required */\n private refreshTimestamp?: number;\n\n /** Cached Ping response. Populated by build() before it returns; refreshed by every later ping(). */\n private _serverInfo?: grpcTypes.MaintenanceAPI_Ping_Response;\n private _authMethodsSync?: grpcTypes.AuthAPI_ListMethods_Response;\n\n private _status: PlConnectionStatus = \"OK\";\n private readonly statusListener?: PlConnectionStatusListener;\n\n private _wireProto: wireProtocol = \"grpc\";\n private _wireConn!: WireConnection;\n\n private readonly _restInterceptors: Dispatcher.DispatcherComposeInterceptor[];\n private readonly _restMiddlewares: Middleware[];\n private readonly _grpcInterceptors: Interceptor[];\n private readonly providers: WeakRef<WireClientProviderImpl<any>>[] = [];\n\n public readonly clientProvider: WireClientProvider<PlRestClientType | GrpcPlApiClient>;\n\n public readonly httpDispatcher: Dispatcher;\n\n public static async build(\n configOrAddress: PlClientConfig | string,\n ops: {\n auth?: AuthOps;\n statusListener?: PlConnectionStatusListener;\n shouldUseGzip?: boolean;\n logger?: MiLogger;\n useAutoDetectWireProtocol?: boolean;\n } = {},\n ) {\n const conf =\n typeof configOrAddress === \"string\" ? plAddressToConfig(configOrAddress) : configOrAddress;\n\n const pl = new LLPlClient(conf, ops);\n\n // FIXME(rfiskov)[MILAB-5275]: Investigate why autodetect randomly fails; temporary turn it off.\n if (ops.useAutoDetectWireProtocol) {\n await pl.detectOptimalWireProtocol();\n }\n\n // Guarantee a ping happened so capability-gated paths (login, refresh) can branch synchronously.\n // In the autodetect path the loop's last successful ping already populated _serverInfo via the\n // side-effect in ping(); this fallback covers the path where autodetect is disabled.\n if (!pl._serverInfo) await pl.ping();\n\n // Install the process-global signature-strictness flag based on backend version.\n setResourceSignaturesRequired(pl.supportsResourceSignatures);\n\n // Guarantee authMethods happened so client can make weighted decision on which auth method to use.\n if (!pl._authMethodsSync) await pl.authMethods();\n\n return pl;\n }\n\n private constructor(\n public readonly conf: PlClientConfig,\n private readonly ops: {\n auth?: AuthOps;\n statusListener?: PlConnectionStatusListener;\n shouldUseGzip?: boolean;\n logger?: MiLogger;\n } = {},\n ) {\n const { auth, statusListener } = ops;\n\n if (auth !== undefined) {\n this.refreshTimestamp = inferAuthRefreshTime(\n auth.authInformation,\n this.conf.authMaxRefreshSeconds,\n );\n this.authInformation = auth.authInformation;\n this.onAuthUpdate = auth.onUpdate;\n this.onAuthRefreshProblem = auth.onUpdateError;\n this.onAuthError = auth.onAuthError;\n }\n\n this._restInterceptors = [];\n this._restMiddlewares = [];\n this._grpcInterceptors = [];\n\n if (auth !== undefined) {\n this._restInterceptors.push(this.createRestAuthInterceptor());\n this._grpcInterceptors.push(this.createGrpcAuthInterceptor());\n }\n this._restInterceptors.push(interceptors.retry({ statusCodes: [] })); // Handle errors with openapi-fetch middleware.\n this._restMiddlewares.push(this.createRestErrorMiddleware());\n this._grpcInterceptors.push(this.createGrpcErrorInterceptor());\n\n this.httpDispatcher = defaultHttpDispatcher(this.conf.httpProxy);\n if (this.conf.wireProtocol) {\n this._wireProto = this.conf.wireProtocol;\n }\n\n this.initWireConnection(this._wireProto);\n\n if (statusListener !== undefined) {\n this.statusListener = statusListener;\n statusListener(this._status);\n }\n\n this.clientProvider = this.createWireClientProvider((wireConn) => {\n if (wireConn.type === \"grpc\") {\n return new GrpcPlApiClient(wireConn.Transport);\n } else {\n return createClient<PlApiPaths>({\n hostAndPort: wireConn.Config.hostAndPort,\n ssl: wireConn.Config.ssl,\n dispatcher: wireConn.Dispatcher,\n middlewares: wireConn.Middlewares,\n });\n }\n });\n }\n\n private initWireConnection(protocol: wireProtocol) {\n switch (protocol) {\n case \"rest\":\n this.initRestConnection();\n return;\n case \"grpc\":\n this.initGrpcConnection(this.ops.shouldUseGzip ?? false);\n return;\n default:\n ((v: never) => {\n throw new Error(\n `Unsupported wire protocol '${v as string}'. Use one of: ${SUPPORTED_WIRE_PROTOCOLS.join(\", \")}`,\n );\n })(protocol);\n }\n }\n\n private initRestConnection(): void {\n const dispatcher = defaultHttpDispatcher(this.conf.grpcProxy, this._restInterceptors);\n this._replaceWireConnection({\n type: \"rest\",\n Config: this.conf,\n Dispatcher: dispatcher,\n Middlewares: this._restMiddlewares,\n });\n }\n\n /**\n * Initializes (or reinitializes) _grpcTransport\n * @param gzip - whether to enable gzip compression\n */\n private initGrpcConnection(gzip: boolean) {\n const clientOptions: ClientOptions = {\n \"grpc.keepalive_time_ms\": 30_000, // 30 seconds\n \"grpc.service_config_disable_resolution\": 1, // Disable DNS TXT lookups for service config\n interceptors: this._grpcInterceptors,\n };\n\n if (gzip) clientOptions[\"grpc.default_compression_algorithm\"] = compressionAlgorithms.gzip;\n\n //\n // Leaving it here for now\n // https://github.com/grpc/grpc-node/issues/2788\n //\n // We should implement message pooling algorithm to overcome hardcoded NO_DELAY behaviour\n // of HTTP/2 and allow our small messages to batch together.\n //\n const grpcOptions: GrpcOptions = {\n host: this.conf.hostAndPort,\n timeout: this.conf.defaultRequestTimeout,\n channelCredentials: this.conf.ssl\n ? ChannelCredentials.createSsl()\n : ChannelCredentials.createInsecure(),\n clientOptions,\n };\n\n const grpcProxy =\n typeof this.conf.grpcProxy === \"string\" ? { url: this.conf.grpcProxy } : this.conf.grpcProxy;\n\n if (grpcProxy?.url) {\n const url = new URL(grpcProxy.url);\n if (grpcProxy.auth) {\n const parsed = parseHttpAuth(grpcProxy.auth);\n if (parsed.scheme !== \"Basic\") {\n throw new Error(`Unsupported auth scheme: ${parsed.scheme as string}.`);\n }\n url.username = parsed.username;\n url.password = parsed.password;\n }\n process.env.grpc_proxy = url.toString();\n } else {\n delete process.env.grpc_proxy;\n }\n\n this._replaceWireConnection({ type: \"grpc\", Transport: new GrpcTransport(grpcOptions) });\n }\n\n private _replaceWireConnection(newConn: WireConnection): void {\n const oldConn = this._wireConn;\n this._wireConn = newConn;\n this._wireProto = newConn.type;\n\n // Reset all providers to let them reinitialize their clients\n for (let i = 0; i < this.providers.length; i++) {\n const provider = this.providers[i].deref();\n if (provider === undefined) {\n // at the same time we need to remove providers that are no longer valid\n this.providers.splice(i, 1);\n i--;\n } else {\n provider.reset();\n }\n }\n\n if (oldConn !== undefined && oldConn.type === \"grpc\") oldConn.Transport.close();\n }\n\n private providerCleanupCounter = 0;\n\n /**\n * Creates a provider for a grpc client. Returned provider will create fresh client whenever the underlying transport is reset.\n *\n * @param clientConstructor - a factory function that creates a grpc client\n */\n public createWireClientProvider<Client>(\n clientConstructor: (transport: WireConnection) => Client,\n ): WireClientProvider<Client> {\n // We need to cleanup providers periodically to avoid memory leaks.\n // This is a simple heuristic to avoid memory leaks.\n // We could use a more sophisticated algorithm, but this is good enough for now.\n this.providerCleanupCounter++;\n if (this.providerCleanupCounter >= 16) {\n for (let i = 0; i < this.providers.length; i++) {\n const provider = this.providers[i].deref();\n if (provider === undefined) {\n this.providers.splice(i, 1);\n i--;\n }\n }\n this.providerCleanupCounter = 0;\n }\n\n const provider = new WireClientProviderImpl<Client>(() => this._wireConn, clientConstructor);\n this.providers.push(new WeakRef(provider));\n return provider;\n }\n\n public get wireConnection(): WireConnection {\n return this._wireConn;\n }\n\n public get wireProtocol(): wireProtocol | undefined {\n return this._wireProto;\n }\n\n /** Returns true if client is authenticated. Even with anonymous auth information\n * connection is considered authenticated. Unauthenticated clients are used for\n * login and similar tasks, see {@link UnauthenticatedPlClient}. */\n public get authenticated(): boolean {\n return this.authInformation !== undefined;\n }\n\n /** null means anonymous connection */\n public get authUser(): string | null {\n if (!this.authenticated) throw new Error(\"Client is not authenticated\");\n if (this.authInformation?.jwtToken) {\n if (this.hasCapability(\"auth:v2\")) {\n return parsePlJwt(this.authInformation?.jwtToken).sub;\n }\n return parsePlJwt(this.authInformation?.jwtToken).user.login;\n } else return null;\n }\n\n private updateStatus(newStatus: PlConnectionStatus) {\n process.nextTick(() => {\n if (this._status !== newStatus) {\n this._status = newStatus;\n if (this.statusListener !== undefined) this.statusListener(this._status);\n if (newStatus === \"Unauthenticated\" && this.onAuthError !== undefined) this.onAuthError();\n }\n });\n }\n\n public get status(): PlConnectionStatus {\n return this._status;\n }\n\n private authRefreshInProgress: boolean = false;\n\n private refreshAuthInformationIfNeeded(): void {\n if (\n this.refreshTimestamp === undefined ||\n Date.now() < this.refreshTimestamp ||\n this.authRefreshInProgress ||\n this._status === \"Unauthenticated\"\n )\n return;\n\n // Running refresh in background`\n this.authRefreshInProgress = true;\n void (async () => {\n try {\n const ttl = BigInt(this.conf.authTTLSeconds);\n const token = this.hasCapability(\"auth:v2\")\n ? await this.refreshToken({ ttlSeconds: ttl })\n : await this.getJwtToken(ttl);\n this.authInformation = { jwtToken: token };\n this.refreshTimestamp = inferAuthRefreshTime(\n this.authInformation,\n this.conf.authMaxRefreshSeconds,\n );\n if (this.onAuthUpdate) this.onAuthUpdate(this.authInformation);\n } catch (e: unknown) {\n if (this.onAuthRefreshProblem) this.onAuthRefreshProblem(e);\n } finally {\n this.authRefreshInProgress = false;\n }\n })();\n }\n\n /**\n * Creates middleware that parses error responses and handles them centrally.\n * This middleware runs before openapi-fetch parses the response, so we need to\n * manually parse the response body for error responses.\n */\n private createRestErrorMiddleware(): Middleware {\n return {\n onResponse: async ({ request: _request, response, options: _options }) => {\n const { body, ...resOptions } = response;\n\n if ([502, 503, 504].includes(response.status)) {\n // Service unavailable, bad gateway, gateway timeout\n this.updateStatus(\"Disconnected\");\n return new Response(body, { ...resOptions, status: response.status });\n }\n\n const respErr = await parseResponseError(response);\n if (!respErr.error) {\n // No error: nice!\n return new Response(respErr.origBody ?? body, { ...resOptions, status: response.status });\n }\n\n if (typeof respErr.error === \"string\") {\n // Non-standard error or normal response: let later middleware to deal wit it.\n return new Response(respErr.error, { ...resOptions, status: response.status });\n }\n\n if (respErr.error.code === Code.UNAUTHENTICATED) {\n this.updateStatus(\"Unauthenticated\");\n }\n\n // Let later middleware to deal with standard gRPC error.\n return new Response(respErr.origBody, { ...resOptions, status: response.status });\n },\n };\n }\n\n /** Detects certain errors and update client status accordingly when using GRPC wire connection */\n private createGrpcErrorInterceptor(): Interceptor {\n return (options, nextCall) => {\n return new InterceptingCall(nextCall(options), {\n start: (metadata, listener, next) => {\n next(metadata, {\n onReceiveStatus: (status, next) => {\n if (status.code == GrpcStatus.UNAUTHENTICATED)\n // (!!!) don't change to \"===\"\n this.updateStatus(\"Unauthenticated\");\n if (status.code == GrpcStatus.UNAVAILABLE)\n // (!!!) don't change to \"===\"\n this.updateStatus(\"Disconnected\");\n next(status);\n },\n });\n },\n });\n };\n }\n\n private createRestAuthInterceptor(): Dispatcher.DispatcherComposeInterceptor {\n return (dispatch) => {\n return (options, handler) => {\n if (this.authInformation?.jwtToken !== undefined) {\n // TODO: check this magic really works and gets called\n options.headers = {\n ...options.headers,\n authorization: \"Bearer \" + this.authInformation.jwtToken,\n };\n this.refreshAuthInformationIfNeeded();\n }\n\n return dispatch(options, handler);\n };\n };\n }\n\n /** Injects authentication information if needed */\n private createGrpcAuthInterceptor(): Interceptor {\n return (options, nextCall) => {\n return new InterceptingCall(nextCall(options), {\n start: (metadata, listener, next) => {\n if (this.authInformation?.jwtToken !== undefined) {\n metadata.set(\"authorization\", \"Bearer \" + this.authInformation.jwtToken);\n this.refreshAuthInformationIfNeeded();\n next(metadata, listener);\n } else {\n next(metadata, listener);\n }\n },\n });\n };\n }\n\n public async getJwtToken(\n ttlSeconds: bigint,\n options?: { authorization?: string; role?: AuthAPI_Role },\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const role = options?.role ?? AuthAPI_Role.UNSPECIFIED;\n\n if (cl instanceof GrpcPlApiClient) {\n const meta: Record<string, string> = {};\n if (options?.authorization) meta.authorization = options.authorization;\n return (\n await cl.getJWTToken(\n {\n expiration: { seconds: ttlSeconds, nanos: 0 },\n requestedRole: role,\n },\n { meta },\n ).response\n ).token;\n } else {\n const headers: Record<string, string> = {};\n if (options?.authorization) headers.authorization = options.authorization;\n const resp = cl.POST(\"/v1/auth/jwt-token\", {\n body: { expiration: `${ttlSeconds}s`, requestedRole: role },\n headers,\n });\n return notEmpty((await resp).data, \"REST: empty response for JWT token request\").token;\n }\n }\n\n /** Login via username/password. Returns a fresh JWT. Backend creates a new session per call. */\n public async loginBasic(\n user: string,\n password: string,\n opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"basic\",\n basic: { login: user, password },\n },\n expiration: { seconds: ttl, nanos: 0 },\n requestedRole: role,\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n // openapi-typescript generated all body fields as required, but Login.Request\n // has a credentials oneof — only one of `basic`/`token` is sent. Cast around it.\n body: {\n basic: { login: user, password },\n expiration: `${ttl}s`,\n requestedRole: role,\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for login request\").token;\n }\n }\n\n /** Login via opaque bearer token (controller pre-shared secret, OIDC id-token, etc.).\n * String input is UTF-8 encoded. Returns a fresh Platforma JWT. */\n public async loginWithToken(\n token: Uint8Array | string,\n opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;\n const bytes = typeof token === \"string\" ? Buffer.from(token, \"utf8\") : token;\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"token\",\n token: { token: bytes },\n },\n expiration: { seconds: ttl, nanos: 0 },\n requestedRole: role,\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n // openapi-typescript marks all body fields as required, but Login.Request has a oneof.\n // REST encodes `bytes` as a base64 string.\n body: {\n token: { token: Buffer.from(bytes).toString(\"base64\") },\n expiration: `${ttl}s`,\n requestedRole: role,\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for login request\").token;\n }\n }\n\n /** Login via SSO: forwards the raw IdP /token JSON body to the backend.\n * Backend validates signature + nonce + audience and mints a Platforma JWT. */\n public async loginSSO(tokenResponse: Uint8Array): Promise<string> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"sso\",\n sso: { tokenResponse },\n },\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n body: {\n sso: { tokenResponse: Buffer.from(tokenResponse).toString(\"base64\") },\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for SSO login request\").token;\n }\n }\n\n /** Request a one-time PKCE nonce + its expiry from the backend. The desktop is\n * expected to place the nonce verbatim into the OIDC auth-request before\n * redirecting to the IdP; the backend enforces the expiry on {@link loginSSO}. */\n public async beginSSOLogin(): Promise<{ nonce: string; expiresAt: Date }> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n const resp = (await cl.beginSSOLogin({}).response).flow;\n if (resp.oneofKind !== \"publicPkce\") {\n throw new Error(`beginSSOLogin: unsupported flow ${resp.oneofKind}`);\n }\n const exp = notEmpty(resp.publicPkce.expiresAt, \"beginSSOLogin: missing expiresAt\");\n return {\n nonce: resp.publicPkce.nonce,\n expiresAt: Timestamp.toDate(exp),\n };\n } else {\n const wsResponse = notEmpty(\n (await cl.POST(\"/v1/auth/sso/begin-login\", { body: {} })).data,\n \"REST: empty response for beginSSOLogin request\",\n );\n const pkce = notEmpty(wsResponse.publicPkce, \"REST: missing publicPkce in beginSSOLogin\");\n return { nonce: pkce.nonce, expiresAt: new Date(pkce.expiresAt) };\n }\n }\n\n /** Refresh the current JWT, preserving session id and role. */\n public async refreshToken(opts: { ttlSeconds?: bigint } = {}): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const currentToken = notEmpty(\n this.authInformation?.jwtToken,\n \"refreshToken called without a current JWT\",\n );\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.refreshToken({\n token: currentToken,\n expiration: { seconds: ttl, nanos: 0 },\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/refresh\", {\n body: { token: currentToken, expiration: `${ttl}s` },\n });\n return notEmpty((await resp).data, \"REST: empty response for refresh request\").token;\n }\n }\n\n public async ping(): Promise<grpcTypes.MaintenanceAPI_Ping_Response> {\n const cl = this.clientProvider.get();\n let resp: grpcTypes.MaintenanceAPI_Ping_Response;\n if (cl instanceof GrpcPlApiClient) {\n resp = (await cl.ping({})).response;\n } else {\n // The REST ping response predates the `capabilities` field (proto field 9).\n // Old servers omit it; treat absence as empty capability list.\n const pingData = notEmpty(\n (await cl.GET(\"/v1/ping\")).data,\n \"REST: empty response for ping request\",\n );\n resp = {\n ...(pingData as unknown as grpcTypes.MaintenanceAPI_Ping_Response),\n capabilities: (pingData as any).capabilities ?? [],\n };\n }\n this._serverInfo = resp;\n return resp;\n }\n\n /** Cached Ping response. Always populated post-build(); throws if accessed earlier. */\n public get serverInfo(): grpcTypes.MaintenanceAPI_Ping_Response {\n if (!this._serverInfo) {\n throw new Error(\"LLPlClient.serverInfo accessed before build() completed\");\n }\n return this._serverInfo;\n }\n\n /** Synchronous capability check against the cached Ping response. */\n public hasCapability(capability: BackendCapability): boolean {\n return hasCapability(this.serverInfo.capabilities, capability);\n }\n\n /** True if the backend produces signed resource ids (and accepts the\n * setDefaultColor TX request). Tagged at 3.3.0 — backends older than this\n * return resources without signatures and reject color-proof requests. */\n public get supportsResourceSignatures(): boolean {\n return isVersionAtLeast(this.serverInfo.coreVersion, [3, 3, 0]);\n }\n\n /**\n * True if the backend honors per-file `permissions` on workdir fill rules\n * (PR #1830 in milaboratory/pl). Backends before this change ignore the\n * requested mode and always land files at the canonical archive perm,\n * making `exec.builder().writeFile/addFile({ writable: true })` a no-op.\n *\n * Tagged at 3.5.0 cut without the change, so [3, 5, 0] excludes the tagged\n * release but includes dev builds past the tag (e.g. \"3.5.0-224-g0ca182\").\n */\n public get supportsWritableWorkdirFiles(): boolean {\n return isAfterVersion(this.serverInfo.coreVersion, [3, 5, 0]);\n }\n\n /**\n * Detects the best available wire protocol.\n * If wireProtocol is explicitly configured, does nothing.\n * Otherwise probes the current protocol via ping; if it fails, switches to the alternative.\n */\n private async detectOptimalWireProtocol() {\n if (this.conf.wireProtocol) {\n return;\n }\n\n // Each retry is:\n // - ping request timeout (100 to 3_000ms)\n // - backoff delay (30 to 500ms)\n //\n // 30 attempts are ~43 seconds of overall waiting time.\n // Think twice on overall time this thing takes to complete when changing these parameters.\n // It may block UI when connecting to the server and loading projects list.\n const pingTimeoutFactor = 1.3;\n const maxPingTimeoutMs = 3_000;\n const retryOptions: RetryOptions = {\n type: \"exponentialBackoff\",\n maxAttempts: 30,\n initialDelay: 30,\n backoffMultiplier: 1.3,\n jitter: 0.2,\n maxDelay: 500,\n };\n\n let attempt = 1;\n let pingTimeoutMs = 100;\n await retry(\n () => withTimeout(this.ping(), pingTimeoutMs),\n retryOptions,\n (e: unknown) => {\n if (isAbortedError(e)) {\n this.ops.logger?.info(\n `Wire proto autodetect: ping timed out after ${pingTimeoutMs}ms: attempt=${attempt}, wire=${this._wireProto}`,\n );\n\n if (attempt % 2 === 0) {\n // We have 2 wire protocols to check. Increase timeout each 2 attempts.\n pingTimeoutMs = Math.min(\n Math.round(pingTimeoutMs * pingTimeoutFactor),\n maxPingTimeoutMs,\n );\n }\n } else {\n this.ops.logger?.info(\n `Wire proto autodetect: ping failed: attempt=${attempt}, wire=${this._wireProto}, err=${String(e)}`,\n );\n }\n\n attempt++;\n const protocol = this._wireProto === \"grpc\" ? \"rest\" : \"grpc\";\n this.ops.logger?.info(\n `Wire protocol autodetect next attempt: will try wire '${protocol}' with timeout ${pingTimeoutMs}ms`,\n );\n this.initWireConnection(protocol);\n return true;\n },\n );\n }\n\n public async license(): Promise<grpcTypes.MaintenanceAPI_License_Response> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (await cl.license({})).response;\n } else {\n const resp = notEmpty(\n (await cl.GET(\"/v1/license\")).data,\n \"REST: empty response for license request\",\n );\n return {\n status: resp.status,\n isOk: resp.isOk,\n responseBody: Uint8Array.from(Buffer.from(resp.responseBody)),\n };\n }\n }\n\n public async authMethods(): Promise<grpcTypes.AuthAPI_ListMethods_Response> {\n const cl = this.clientProvider.get();\n let resp: grpcTypes.AuthAPI_ListMethods_Response;\n if (cl instanceof GrpcPlApiClient) {\n resp = (await cl.authMethods({})).response;\n } else {\n const wsResponse = notEmpty(\n (await cl.GET(\"/v1/auth/methods\")).data,\n \"REST: empty response for auth methods request\",\n );\n // OpenAPI schema flattens the protobuf oneof into `{ basic?, token? }`,\n // while protobuf-ts models it as a discriminated union. Reshape per item.\n resp = {\n methods: (wsResponse.methods ?? []).map((m): grpcTypes.AuthAPI_ListMethods_MethodInfo => {\n const base = { id: m.id, description: m.description };\n if (m.basic !== undefined) {\n return { ...base, method: { oneofKind: \"basic\", basic: m.basic } };\n }\n if (m.token !== undefined) {\n return { ...base, method: { oneofKind: \"token\", token: m.token } };\n }\n if (m.sso !== undefined) {\n return { ...base, method: { oneofKind: \"sso\", sso: m.sso } };\n }\n return { ...base, method: { oneofKind: undefined } };\n }),\n };\n }\n\n this._authMethodsSync = resp;\n return resp;\n }\n\n public get authMethodsSync(): grpcTypes.AuthAPI_ListMethods_Response {\n if (!this._authMethodsSync) {\n throw new Error(\"LLPlClient.authMethodsSync accessed before build() completed\");\n }\n return this._authMethodsSync;\n }\n\n public async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<grpcTypes.AuthAPI_GetUserRoot_Response> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.getUserRoot({\n login: opts.login ?? \"\",\n createIfNotExists: opts.createIfNotExists ?? false,\n })\n ).response;\n } else {\n const resp = notEmpty(\n (\n await cl.POST(\"/v1/auth/user-root\", {\n body: {\n login: opts.login ?? \"\",\n createIfNotExists: opts.createIfNotExists ?? false,\n },\n })\n ).data,\n \"REST: empty response for getUserRoot request\",\n );\n return {\n userRoot: resp.userRoot\n ? {\n resourceId: BigInt(resp.userRoot.resourceId),\n resourceSignature: Uint8Array.from(\n Buffer.from(resp.userRoot.resourceSignature, \"base64\"),\n ),\n }\n : undefined,\n };\n }\n }\n\n public async listUserResources(\n opts: { login?: string; startFrom?: bigint; limit?: number } = {},\n ): Promise<grpcTypes.AuthAPI_ListUserResources_Response[]> {\n const cl = this.clientProvider.get();\n\n if (!(cl instanceof GrpcPlApiClient)) {\n throw new Error(\"ListUserResources requires gRPC wire protocol; REST is not supported\");\n }\n\n const call = cl.listUserResources({\n login: opts.login ?? \"\",\n startFrom: opts.startFrom ?? 0n,\n limit: opts.limit ?? 0,\n });\n const responses: grpcTypes.AuthAPI_ListUserResources_Response[] = [];\n for await (const msg of call.responses) {\n responses.push(msg);\n }\n return responses;\n }\n\n public async txSync(txId: bigint): Promise<void> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n await cl.txSync({ txId: BigInt(txId) });\n } else {\n await cl.POST(\"/v1/tx-sync\", { body: { txId: txId.toString() } });\n }\n }\n\n createTx(rw: boolean, ops: PlCallOps = {}): LLPlTransaction {\n return new LLPlTransaction((abortSignal) => {\n let totalAbortSignal = abortSignal;\n if (ops.abortSignal) totalAbortSignal = AbortSignal.any([totalAbortSignal, ops.abortSignal]);\n\n const timeout =\n ops.timeout ??\n (rw ? this.conf.defaultRWTransactionTimeout : this.conf.defaultROTransactionTimeout);\n\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return cl.tx({\n abort: totalAbortSignal,\n timeout,\n });\n }\n\n const wireConn = this.wireConnection;\n if (wireConn.type === \"rest\") {\n // For REST/WebSocket protocol, timeout needs to be converted to AbortSignal\n if (timeout !== undefined) {\n totalAbortSignal = AbortSignal.any([totalAbortSignal, AbortSignal.timeout(timeout)]);\n }\n\n // The gRPC transport has the auth interceptor that already handles it, but here we need to refresh the auth information to be safe.\n this.refreshAuthInformationIfNeeded();\n\n const wsUrl = this.conf.ssl\n ? `wss://${this.conf.hostAndPort}/v1/ws/tx`\n : `ws://${this.conf.hostAndPort}/v1/ws/tx`;\n\n return new WebSocketBiDiStream(\n wsUrl,\n (msg) => TxAPI_ClientMessage.toBinary(msg),\n (data) => TxAPI_ServerMessage.fromBinary(new Uint8Array(data)),\n {\n abortSignal: totalAbortSignal,\n jwtToken: this.authInformation?.jwtToken,\n dispatcher: wireConn.Dispatcher,\n\n onComplete: async (stream) =>\n stream.requests.send({\n // Ask server to gracefully close the stream on its side, if not done yet.\n requestId: 0,\n request: { oneofKind: \"streamClose\", streamClose: {} },\n }),\n },\n );\n }\n\n throw new Error(`transactions are not supported for wire protocol ${this._wireProto}`);\n });\n }\n\n /** Closes underlying transport */\n public async close() {\n if (this.wireConnection.type === \"grpc\") {\n this.wireConnection.Transport.close();\n } else {\n // TODO: close all WS connections\n }\n await this.httpDispatcher.destroy();\n }\n}\n"],"mappings":"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"}
@@ -101,6 +101,16 @@ declare class LLPlClient implements WireClientProviderFactory {
101
101
  ttlSeconds?: bigint;
102
102
  role?: AuthAPI_Role;
103
103
  }): Promise<string>;
104
+ /** Login via SSO: forwards the raw IdP /token JSON body to the backend.
105
+ * Backend validates signature + nonce + audience and mints a Platforma JWT. */
106
+ loginSSO(tokenResponse: Uint8Array): Promise<string>;
107
+ /** Request a one-time PKCE nonce + its expiry from the backend. The desktop is
108
+ * expected to place the nonce verbatim into the OIDC auth-request before
109
+ * redirecting to the IdP; the backend enforces the expiry on {@link loginSSO}. */
110
+ beginSSOLogin(): Promise<{
111
+ nonce: string;
112
+ expiresAt: Date;
113
+ }>;
104
114
  /** Refresh the current JWT, preserving session id and role. */
105
115
  refreshToken(opts?: {
106
116
  ttlSeconds?: bigint;
@@ -1 +1 @@
1
- {"version":3,"file":"ll_client.d.ts","names":[],"sources":["../../src/core/ll_client.ts"],"mappings":"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"}
1
+ {"version":3,"file":"ll_client.d.ts","names":[],"sources":["../../src/core/ll_client.ts"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { setResourceSignaturesRequired } from "./types.js";
2
+ import { Timestamp } from "../proto-grpc/google/protobuf/timestamp.js";
2
3
  import { AuthAPI_Role, TxAPI_ClientMessage, TxAPI_ServerMessage } from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js";
3
4
  import { Code } from "../proto-grpc/google/rpc/code.js";
4
5
  import { isAbortedError } from "./errors.js";
@@ -411,6 +412,37 @@ var LLPlClient = class LLPlClient {
411
412
  requestedRole: role
412
413
  } })).data, "REST: empty response for login request").token;
413
414
  }
415
+ /** Login via SSO: forwards the raw IdP /token JSON body to the backend.
416
+ * Backend validates signature + nonce + audience and mints a Platforma JWT. */
417
+ async loginSSO(tokenResponse) {
418
+ const cl = this.clientProvider.get();
419
+ if (cl instanceof PlatformClient) return (await cl.login({ credentials: {
420
+ oneofKind: "sso",
421
+ sso: { tokenResponse }
422
+ } }).response).token;
423
+ else return notEmpty((await cl.POST("/v1/auth/login", { body: { sso: { tokenResponse: Buffer.from(tokenResponse).toString("base64") } } })).data, "REST: empty response for SSO login request").token;
424
+ }
425
+ /** Request a one-time PKCE nonce + its expiry from the backend. The desktop is
426
+ * expected to place the nonce verbatim into the OIDC auth-request before
427
+ * redirecting to the IdP; the backend enforces the expiry on {@link loginSSO}. */
428
+ async beginSSOLogin() {
429
+ const cl = this.clientProvider.get();
430
+ if (cl instanceof PlatformClient) {
431
+ const resp = (await cl.beginSSOLogin({}).response).flow;
432
+ if (resp.oneofKind !== "publicPkce") throw new Error(`beginSSOLogin: unsupported flow ${resp.oneofKind}`);
433
+ const exp = notEmpty(resp.publicPkce.expiresAt, "beginSSOLogin: missing expiresAt");
434
+ return {
435
+ nonce: resp.publicPkce.nonce,
436
+ expiresAt: Timestamp.toDate(exp)
437
+ };
438
+ } else {
439
+ const pkce = notEmpty(notEmpty((await cl.POST("/v1/auth/sso/begin-login", { body: {} })).data, "REST: empty response for beginSSOLogin request").publicPkce, "REST: missing publicPkce in beginSSOLogin");
440
+ return {
441
+ nonce: pkce.nonce,
442
+ expiresAt: new Date(pkce.expiresAt)
443
+ };
444
+ }
445
+ }
414
446
  /** Refresh the current JWT, preserving session id and role. */
415
447
  async refreshToken(opts = {}) {
416
448
  const cl = this.clientProvider.get();
@@ -1 +1 @@
1
- {"version":3,"file":"ll_client.js","names":["GrpcPlApiClient","status","GrpcStatus"],"sources":["../../src/core/ll_client.ts"],"sourcesContent":["import { PlatformClient as GrpcPlApiClient } from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client\";\nimport type { ClientOptions, Interceptor } from \"@grpc/grpc-js\";\nimport {\n ChannelCredentials,\n InterceptingCall,\n status as GrpcStatus,\n compressionAlgorithms,\n} from \"@grpc/grpc-js\";\nimport type {\n AuthInformation,\n AuthOps,\n PlClientConfig,\n PlConnectionStatus,\n PlConnectionStatusListener,\n} from \"./config\";\nimport { plAddressToConfig, type wireProtocol, SUPPORTED_WIRE_PROTOCOLS } from \"./config\";\nimport type { GrpcOptions } from \"@protobuf-ts/grpc-transport\";\nimport { GrpcTransport } from \"@protobuf-ts/grpc-transport\";\nimport { LLPlTransaction } from \"./ll_transaction\";\nimport { setResourceSignaturesRequired } from \"./types\";\nimport { parsePlJwt } from \"../util/pl\";\nimport { type Dispatcher, interceptors } from \"undici\";\nimport type { Middleware } from \"openapi-fetch\";\nimport { inferAuthRefreshTime } from \"./auth\";\nimport { hasCapability, type BackendCapability } from \"./capabilities\";\nimport { defaultHttpDispatcher } from \"@milaboratories/pl-http\";\nimport type { WireClientProvider, WireClientProviderFactory, WireConnection } from \"./wire\";\nimport { parseHttpAuth } from \"@milaboratories/pl-model-common\";\nimport type * as grpcTypes from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport {\n type PlApiPaths,\n type PlRestClientType,\n createClient,\n parseResponseError,\n} from \"../proto-rest\";\nimport { notEmpty, retry, withTimeout, type RetryOptions } from \"@milaboratories/ts-helpers\";\nimport { Code } from \"../proto-grpc/google/rpc/code\";\nimport { WebSocketBiDiStream } from \"./websocket_stream\";\nimport {\n AuthAPI_Role,\n TxAPI_ClientMessage,\n TxAPI_ServerMessage,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport type { MiLogger } from \"@milaboratories/ts-helpers\";\nimport { isAbortedError } from \"./errors\";\n\nexport interface PlCallOps {\n timeout?: number;\n abortSignal?: AbortSignal;\n}\n\n// Parses leading \"<major>.<minor>.<patch>\" from a version string like\n// \"3.1.1\" or \"3.1.1-rc1\" and returns true if the parsed version is >= target.\n// Returns false for unparseable versions (safer to assume an old backend).\nfunction isVersionAtLeast(version: string, target: [number, number, number]): boolean {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)/.exec(version);\n if (!match) return false;\n const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];\n for (let i = 0; i < 3; i++) {\n if (parsed[i] !== target[i]) return parsed[i] > target[i];\n }\n return true;\n}\n\n// Returns true iff `version` is strictly after the release tag `target`. Dev\n// builds (`git describe`-style, e.g. \"3.5.0-224-g0ca182\") are considered\n// AFTER the matching release tag — they include commits past the tag and so\n// have any change merged after it. Released versions with the same triplet\n// return false (we want the tag itself to be excluded).\n//\n// Examples for target [3,5,0]:\n// \"3.5.0\" → false (the tagged release)\n// \"3.5.0-224-g0ca182\" → true (dev build past the tag)\n// \"3.5.1\" → true\n// \"3.4.9\" → false\n// Returns false for unparseable versions.\nfunction isAfterVersion(version: string, target: [number, number, number]): boolean {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(.*)$/.exec(version);\n if (!match) return false;\n const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];\n const suffix = match[4];\n for (let i = 0; i < 3; i++) {\n if (parsed[i] !== target[i]) return parsed[i] > target[i];\n }\n return suffix !== \"\";\n}\n\nclass WireClientProviderImpl<Client> implements WireClientProvider<Client> {\n private client: Client | undefined = undefined;\n\n constructor(\n private readonly wireOpts: () => WireConnection,\n private readonly clientConstructor: (wireOpts: WireConnection) => Client,\n ) {}\n\n public reset(): void {\n this.client = undefined;\n }\n\n public get(): Client {\n if (this.client === undefined) this.client = this.clientConstructor(this.wireOpts());\n return this.client;\n }\n}\n\n/** Abstract out low level networking and authorization details */\nexport class LLPlClient implements WireClientProviderFactory {\n /** Initial authorization information */\n private authInformation?: AuthInformation;\n /** Will be executed by the client when it is required */\n private readonly onAuthUpdate?: (newInfo: AuthInformation) => void;\n /** Will be executed if auth-related error happens during normal client operation */\n private readonly onAuthError?: () => void;\n /** Will be executed by the client when it is required */\n private readonly onAuthRefreshProblem?: (error: unknown) => void;\n /** Threshold after which auth info refresh is required */\n private refreshTimestamp?: number;\n\n /** Cached Ping response. Populated by build() before it returns; refreshed by every later ping(). */\n private _serverInfo?: grpcTypes.MaintenanceAPI_Ping_Response;\n private _authMethodsSync?: grpcTypes.AuthAPI_ListMethods_Response;\n\n private _status: PlConnectionStatus = \"OK\";\n private readonly statusListener?: PlConnectionStatusListener;\n\n private _wireProto: wireProtocol = \"grpc\";\n private _wireConn!: WireConnection;\n\n private readonly _restInterceptors: Dispatcher.DispatcherComposeInterceptor[];\n private readonly _restMiddlewares: Middleware[];\n private readonly _grpcInterceptors: Interceptor[];\n private readonly providers: WeakRef<WireClientProviderImpl<any>>[] = [];\n\n public readonly clientProvider: WireClientProvider<PlRestClientType | GrpcPlApiClient>;\n\n public readonly httpDispatcher: Dispatcher;\n\n public static async build(\n configOrAddress: PlClientConfig | string,\n ops: {\n auth?: AuthOps;\n statusListener?: PlConnectionStatusListener;\n shouldUseGzip?: boolean;\n logger?: MiLogger;\n useAutoDetectWireProtocol?: boolean;\n } = {},\n ) {\n const conf =\n typeof configOrAddress === \"string\" ? plAddressToConfig(configOrAddress) : configOrAddress;\n\n const pl = new LLPlClient(conf, ops);\n\n // FIXME(rfiskov)[MILAB-5275]: Investigate why autodetect randomly fails; temporary turn it off.\n if (ops.useAutoDetectWireProtocol) {\n await pl.detectOptimalWireProtocol();\n }\n\n // Guarantee a ping happened so capability-gated paths (login, refresh) can branch synchronously.\n // In the autodetect path the loop's last successful ping already populated _serverInfo via the\n // side-effect in ping(); this fallback covers the path where autodetect is disabled.\n if (!pl._serverInfo) await pl.ping();\n\n // Install the process-global signature-strictness flag based on backend version.\n setResourceSignaturesRequired(pl.supportsResourceSignatures);\n\n // Guarantee authMethods happened so client can make weighted decision on which auth method to use.\n if (!pl._authMethodsSync) await pl.authMethods();\n\n return pl;\n }\n\n private constructor(\n public readonly conf: PlClientConfig,\n private readonly ops: {\n auth?: AuthOps;\n statusListener?: PlConnectionStatusListener;\n shouldUseGzip?: boolean;\n logger?: MiLogger;\n } = {},\n ) {\n const { auth, statusListener } = ops;\n\n if (auth !== undefined) {\n this.refreshTimestamp = inferAuthRefreshTime(\n auth.authInformation,\n this.conf.authMaxRefreshSeconds,\n );\n this.authInformation = auth.authInformation;\n this.onAuthUpdate = auth.onUpdate;\n this.onAuthRefreshProblem = auth.onUpdateError;\n this.onAuthError = auth.onAuthError;\n }\n\n this._restInterceptors = [];\n this._restMiddlewares = [];\n this._grpcInterceptors = [];\n\n if (auth !== undefined) {\n this._restInterceptors.push(this.createRestAuthInterceptor());\n this._grpcInterceptors.push(this.createGrpcAuthInterceptor());\n }\n this._restInterceptors.push(interceptors.retry({ statusCodes: [] })); // Handle errors with openapi-fetch middleware.\n this._restMiddlewares.push(this.createRestErrorMiddleware());\n this._grpcInterceptors.push(this.createGrpcErrorInterceptor());\n\n this.httpDispatcher = defaultHttpDispatcher(this.conf.httpProxy);\n if (this.conf.wireProtocol) {\n this._wireProto = this.conf.wireProtocol;\n }\n\n this.initWireConnection(this._wireProto);\n\n if (statusListener !== undefined) {\n this.statusListener = statusListener;\n statusListener(this._status);\n }\n\n this.clientProvider = this.createWireClientProvider((wireConn) => {\n if (wireConn.type === \"grpc\") {\n return new GrpcPlApiClient(wireConn.Transport);\n } else {\n return createClient<PlApiPaths>({\n hostAndPort: wireConn.Config.hostAndPort,\n ssl: wireConn.Config.ssl,\n dispatcher: wireConn.Dispatcher,\n middlewares: wireConn.Middlewares,\n });\n }\n });\n }\n\n private initWireConnection(protocol: wireProtocol) {\n switch (protocol) {\n case \"rest\":\n this.initRestConnection();\n return;\n case \"grpc\":\n this.initGrpcConnection(this.ops.shouldUseGzip ?? false);\n return;\n default:\n ((v: never) => {\n throw new Error(\n `Unsupported wire protocol '${v as string}'. Use one of: ${SUPPORTED_WIRE_PROTOCOLS.join(\", \")}`,\n );\n })(protocol);\n }\n }\n\n private initRestConnection(): void {\n const dispatcher = defaultHttpDispatcher(this.conf.grpcProxy, this._restInterceptors);\n this._replaceWireConnection({\n type: \"rest\",\n Config: this.conf,\n Dispatcher: dispatcher,\n Middlewares: this._restMiddlewares,\n });\n }\n\n /**\n * Initializes (or reinitializes) _grpcTransport\n * @param gzip - whether to enable gzip compression\n */\n private initGrpcConnection(gzip: boolean) {\n const clientOptions: ClientOptions = {\n \"grpc.keepalive_time_ms\": 30_000, // 30 seconds\n \"grpc.service_config_disable_resolution\": 1, // Disable DNS TXT lookups for service config\n interceptors: this._grpcInterceptors,\n };\n\n if (gzip) clientOptions[\"grpc.default_compression_algorithm\"] = compressionAlgorithms.gzip;\n\n //\n // Leaving it here for now\n // https://github.com/grpc/grpc-node/issues/2788\n //\n // We should implement message pooling algorithm to overcome hardcoded NO_DELAY behaviour\n // of HTTP/2 and allow our small messages to batch together.\n //\n const grpcOptions: GrpcOptions = {\n host: this.conf.hostAndPort,\n timeout: this.conf.defaultRequestTimeout,\n channelCredentials: this.conf.ssl\n ? ChannelCredentials.createSsl()\n : ChannelCredentials.createInsecure(),\n clientOptions,\n };\n\n const grpcProxy =\n typeof this.conf.grpcProxy === \"string\" ? { url: this.conf.grpcProxy } : this.conf.grpcProxy;\n\n if (grpcProxy?.url) {\n const url = new URL(grpcProxy.url);\n if (grpcProxy.auth) {\n const parsed = parseHttpAuth(grpcProxy.auth);\n if (parsed.scheme !== \"Basic\") {\n throw new Error(`Unsupported auth scheme: ${parsed.scheme as string}.`);\n }\n url.username = parsed.username;\n url.password = parsed.password;\n }\n process.env.grpc_proxy = url.toString();\n } else {\n delete process.env.grpc_proxy;\n }\n\n this._replaceWireConnection({ type: \"grpc\", Transport: new GrpcTransport(grpcOptions) });\n }\n\n private _replaceWireConnection(newConn: WireConnection): void {\n const oldConn = this._wireConn;\n this._wireConn = newConn;\n this._wireProto = newConn.type;\n\n // Reset all providers to let them reinitialize their clients\n for (let i = 0; i < this.providers.length; i++) {\n const provider = this.providers[i].deref();\n if (provider === undefined) {\n // at the same time we need to remove providers that are no longer valid\n this.providers.splice(i, 1);\n i--;\n } else {\n provider.reset();\n }\n }\n\n if (oldConn !== undefined && oldConn.type === \"grpc\") oldConn.Transport.close();\n }\n\n private providerCleanupCounter = 0;\n\n /**\n * Creates a provider for a grpc client. Returned provider will create fresh client whenever the underlying transport is reset.\n *\n * @param clientConstructor - a factory function that creates a grpc client\n */\n public createWireClientProvider<Client>(\n clientConstructor: (transport: WireConnection) => Client,\n ): WireClientProvider<Client> {\n // We need to cleanup providers periodically to avoid memory leaks.\n // This is a simple heuristic to avoid memory leaks.\n // We could use a more sophisticated algorithm, but this is good enough for now.\n this.providerCleanupCounter++;\n if (this.providerCleanupCounter >= 16) {\n for (let i = 0; i < this.providers.length; i++) {\n const provider = this.providers[i].deref();\n if (provider === undefined) {\n this.providers.splice(i, 1);\n i--;\n }\n }\n this.providerCleanupCounter = 0;\n }\n\n const provider = new WireClientProviderImpl<Client>(() => this._wireConn, clientConstructor);\n this.providers.push(new WeakRef(provider));\n return provider;\n }\n\n public get wireConnection(): WireConnection {\n return this._wireConn;\n }\n\n public get wireProtocol(): wireProtocol | undefined {\n return this._wireProto;\n }\n\n /** Returns true if client is authenticated. Even with anonymous auth information\n * connection is considered authenticated. Unauthenticated clients are used for\n * login and similar tasks, see {@link UnauthenticatedPlClient}. */\n public get authenticated(): boolean {\n return this.authInformation !== undefined;\n }\n\n /** null means anonymous connection */\n public get authUser(): string | null {\n if (!this.authenticated) throw new Error(\"Client is not authenticated\");\n if (this.authInformation?.jwtToken) {\n if (this.hasCapability(\"auth:v2\")) {\n return parsePlJwt(this.authInformation?.jwtToken).sub;\n }\n return parsePlJwt(this.authInformation?.jwtToken).user.login;\n } else return null;\n }\n\n private updateStatus(newStatus: PlConnectionStatus) {\n process.nextTick(() => {\n if (this._status !== newStatus) {\n this._status = newStatus;\n if (this.statusListener !== undefined) this.statusListener(this._status);\n if (newStatus === \"Unauthenticated\" && this.onAuthError !== undefined) this.onAuthError();\n }\n });\n }\n\n public get status(): PlConnectionStatus {\n return this._status;\n }\n\n private authRefreshInProgress: boolean = false;\n\n private refreshAuthInformationIfNeeded(): void {\n if (\n this.refreshTimestamp === undefined ||\n Date.now() < this.refreshTimestamp ||\n this.authRefreshInProgress ||\n this._status === \"Unauthenticated\"\n )\n return;\n\n // Running refresh in background`\n this.authRefreshInProgress = true;\n void (async () => {\n try {\n const ttl = BigInt(this.conf.authTTLSeconds);\n const token = this.hasCapability(\"auth:v2\")\n ? await this.refreshToken({ ttlSeconds: ttl })\n : await this.getJwtToken(ttl);\n this.authInformation = { jwtToken: token };\n this.refreshTimestamp = inferAuthRefreshTime(\n this.authInformation,\n this.conf.authMaxRefreshSeconds,\n );\n if (this.onAuthUpdate) this.onAuthUpdate(this.authInformation);\n } catch (e: unknown) {\n if (this.onAuthRefreshProblem) this.onAuthRefreshProblem(e);\n } finally {\n this.authRefreshInProgress = false;\n }\n })();\n }\n\n /**\n * Creates middleware that parses error responses and handles them centrally.\n * This middleware runs before openapi-fetch parses the response, so we need to\n * manually parse the response body for error responses.\n */\n private createRestErrorMiddleware(): Middleware {\n return {\n onResponse: async ({ request: _request, response, options: _options }) => {\n const { body, ...resOptions } = response;\n\n if ([502, 503, 504].includes(response.status)) {\n // Service unavailable, bad gateway, gateway timeout\n this.updateStatus(\"Disconnected\");\n return new Response(body, { ...resOptions, status: response.status });\n }\n\n const respErr = await parseResponseError(response);\n if (!respErr.error) {\n // No error: nice!\n return new Response(respErr.origBody ?? body, { ...resOptions, status: response.status });\n }\n\n if (typeof respErr.error === \"string\") {\n // Non-standard error or normal response: let later middleware to deal wit it.\n return new Response(respErr.error, { ...resOptions, status: response.status });\n }\n\n if (respErr.error.code === Code.UNAUTHENTICATED) {\n this.updateStatus(\"Unauthenticated\");\n }\n\n // Let later middleware to deal with standard gRPC error.\n return new Response(respErr.origBody, { ...resOptions, status: response.status });\n },\n };\n }\n\n /** Detects certain errors and update client status accordingly when using GRPC wire connection */\n private createGrpcErrorInterceptor(): Interceptor {\n return (options, nextCall) => {\n return new InterceptingCall(nextCall(options), {\n start: (metadata, listener, next) => {\n next(metadata, {\n onReceiveStatus: (status, next) => {\n if (status.code == GrpcStatus.UNAUTHENTICATED)\n // (!!!) don't change to \"===\"\n this.updateStatus(\"Unauthenticated\");\n if (status.code == GrpcStatus.UNAVAILABLE)\n // (!!!) don't change to \"===\"\n this.updateStatus(\"Disconnected\");\n next(status);\n },\n });\n },\n });\n };\n }\n\n private createRestAuthInterceptor(): Dispatcher.DispatcherComposeInterceptor {\n return (dispatch) => {\n return (options, handler) => {\n if (this.authInformation?.jwtToken !== undefined) {\n // TODO: check this magic really works and gets called\n options.headers = {\n ...options.headers,\n authorization: \"Bearer \" + this.authInformation.jwtToken,\n };\n this.refreshAuthInformationIfNeeded();\n }\n\n return dispatch(options, handler);\n };\n };\n }\n\n /** Injects authentication information if needed */\n private createGrpcAuthInterceptor(): Interceptor {\n return (options, nextCall) => {\n return new InterceptingCall(nextCall(options), {\n start: (metadata, listener, next) => {\n if (this.authInformation?.jwtToken !== undefined) {\n metadata.set(\"authorization\", \"Bearer \" + this.authInformation.jwtToken);\n this.refreshAuthInformationIfNeeded();\n next(metadata, listener);\n } else {\n next(metadata, listener);\n }\n },\n });\n };\n }\n\n public async getJwtToken(\n ttlSeconds: bigint,\n options?: { authorization?: string; role?: AuthAPI_Role },\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const role = options?.role ?? AuthAPI_Role.UNSPECIFIED;\n\n if (cl instanceof GrpcPlApiClient) {\n const meta: Record<string, string> = {};\n if (options?.authorization) meta.authorization = options.authorization;\n return (\n await cl.getJWTToken(\n {\n expiration: { seconds: ttlSeconds, nanos: 0 },\n requestedRole: role,\n },\n { meta },\n ).response\n ).token;\n } else {\n const headers: Record<string, string> = {};\n if (options?.authorization) headers.authorization = options.authorization;\n const resp = cl.POST(\"/v1/auth/jwt-token\", {\n body: { expiration: `${ttlSeconds}s`, requestedRole: role },\n headers,\n });\n return notEmpty((await resp).data, \"REST: empty response for JWT token request\").token;\n }\n }\n\n /** Login via username/password. Returns a fresh JWT. Backend creates a new session per call. */\n public async loginBasic(\n user: string,\n password: string,\n opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"basic\",\n basic: { login: user, password },\n },\n expiration: { seconds: ttl, nanos: 0 },\n requestedRole: role,\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n // openapi-typescript generated all body fields as required, but Login.Request\n // has a credentials oneof — only one of `basic`/`token` is sent. Cast around it.\n body: {\n basic: { login: user, password },\n expiration: `${ttl}s`,\n requestedRole: role,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for login request\").token;\n }\n }\n\n /** Login via opaque bearer token (controller pre-shared secret, OIDC id-token, etc.).\n * String input is UTF-8 encoded. Returns a fresh Platforma JWT. */\n public async loginWithToken(\n token: Uint8Array | string,\n opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;\n const bytes = typeof token === \"string\" ? Buffer.from(token, \"utf8\") : token;\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"token\",\n token: { token: bytes },\n },\n expiration: { seconds: ttl, nanos: 0 },\n requestedRole: role,\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n // openapi-typescript marks all body fields as required, but Login.Request has a oneof.\n // REST encodes `bytes` as a base64 string.\n body: {\n token: { token: Buffer.from(bytes).toString(\"base64\") },\n expiration: `${ttl}s`,\n requestedRole: role,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for login request\").token;\n }\n }\n\n /** Refresh the current JWT, preserving session id and role. */\n public async refreshToken(opts: { ttlSeconds?: bigint } = {}): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const currentToken = notEmpty(\n this.authInformation?.jwtToken,\n \"refreshToken called without a current JWT\",\n );\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.refreshToken({\n token: currentToken,\n expiration: { seconds: ttl, nanos: 0 },\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/refresh\", {\n body: { token: currentToken, expiration: `${ttl}s` },\n });\n return notEmpty((await resp).data, \"REST: empty response for refresh request\").token;\n }\n }\n\n public async ping(): Promise<grpcTypes.MaintenanceAPI_Ping_Response> {\n const cl = this.clientProvider.get();\n let resp: grpcTypes.MaintenanceAPI_Ping_Response;\n if (cl instanceof GrpcPlApiClient) {\n resp = (await cl.ping({})).response;\n } else {\n // The REST ping response predates the `capabilities` field (proto field 9).\n // Old servers omit it; treat absence as empty capability list.\n const pingData = notEmpty(\n (await cl.GET(\"/v1/ping\")).data,\n \"REST: empty response for ping request\",\n );\n resp = {\n ...(pingData as unknown as grpcTypes.MaintenanceAPI_Ping_Response),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n capabilities: (pingData as any).capabilities ?? [],\n };\n }\n this._serverInfo = resp;\n return resp;\n }\n\n /** Cached Ping response. Always populated post-build(); throws if accessed earlier. */\n public get serverInfo(): grpcTypes.MaintenanceAPI_Ping_Response {\n if (!this._serverInfo) {\n throw new Error(\"LLPlClient.serverInfo accessed before build() completed\");\n }\n return this._serverInfo;\n }\n\n /** Synchronous capability check against the cached Ping response. */\n public hasCapability(capability: BackendCapability): boolean {\n return hasCapability(this.serverInfo.capabilities, capability);\n }\n\n /** True if the backend produces signed resource ids (and accepts the\n * setDefaultColor TX request). Tagged at 3.3.0 — backends older than this\n * return resources without signatures and reject color-proof requests. */\n public get supportsResourceSignatures(): boolean {\n return isVersionAtLeast(this.serverInfo.coreVersion, [3, 3, 0]);\n }\n\n /**\n * True if the backend honors per-file `permissions` on workdir fill rules\n * (PR #1830 in milaboratory/pl). Backends before this change ignore the\n * requested mode and always land files at the canonical archive perm,\n * making `exec.builder().writeFile/addFile({ writable: true })` a no-op.\n *\n * Tagged at 3.5.0 cut without the change, so [3, 5, 0] excludes the tagged\n * release but includes dev builds past the tag (e.g. \"3.5.0-224-g0ca182\").\n */\n public get supportsWritableWorkdirFiles(): boolean {\n return isAfterVersion(this.serverInfo.coreVersion, [3, 5, 0]);\n }\n\n /**\n * Detects the best available wire protocol.\n * If wireProtocol is explicitly configured, does nothing.\n * Otherwise probes the current protocol via ping; if it fails, switches to the alternative.\n */\n private async detectOptimalWireProtocol() {\n if (this.conf.wireProtocol) {\n return;\n }\n\n // Each retry is:\n // - ping request timeout (100 to 3_000ms)\n // - backoff delay (30 to 500ms)\n //\n // 30 attempts are ~43 seconds of overall waiting time.\n // Think twice on overall time this thing takes to complete when changing these parameters.\n // It may block UI when connecting to the server and loading projects list.\n const pingTimeoutFactor = 1.3;\n const maxPingTimeoutMs = 3_000;\n const retryOptions: RetryOptions = {\n type: \"exponentialBackoff\",\n maxAttempts: 30,\n initialDelay: 30,\n backoffMultiplier: 1.3,\n jitter: 0.2,\n maxDelay: 500,\n };\n\n let attempt = 1;\n let pingTimeoutMs = 100;\n await retry(\n () => withTimeout(this.ping(), pingTimeoutMs),\n retryOptions,\n (e: unknown) => {\n if (isAbortedError(e)) {\n this.ops.logger?.info(\n `Wire proto autodetect: ping timed out after ${pingTimeoutMs}ms: attempt=${attempt}, wire=${this._wireProto}`,\n );\n\n if (attempt % 2 === 0) {\n // We have 2 wire protocols to check. Increase timeout each 2 attempts.\n pingTimeoutMs = Math.min(\n Math.round(pingTimeoutMs * pingTimeoutFactor),\n maxPingTimeoutMs,\n );\n }\n } else {\n this.ops.logger?.info(\n `Wire proto autodetect: ping failed: attempt=${attempt}, wire=${this._wireProto}, err=${String(e)}`,\n );\n }\n\n attempt++;\n const protocol = this._wireProto === \"grpc\" ? \"rest\" : \"grpc\";\n this.ops.logger?.info(\n `Wire protocol autodetect next attempt: will try wire '${protocol}' with timeout ${pingTimeoutMs}ms`,\n );\n this.initWireConnection(protocol);\n return true;\n },\n );\n }\n\n public async license(): Promise<grpcTypes.MaintenanceAPI_License_Response> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (await cl.license({})).response;\n } else {\n const resp = notEmpty(\n (await cl.GET(\"/v1/license\")).data,\n \"REST: empty response for license request\",\n );\n return {\n status: resp.status,\n isOk: resp.isOk,\n responseBody: Uint8Array.from(Buffer.from(resp.responseBody)),\n };\n }\n }\n\n public async authMethods(): Promise<grpcTypes.AuthAPI_ListMethods_Response> {\n const cl = this.clientProvider.get();\n let resp: grpcTypes.AuthAPI_ListMethods_Response;\n if (cl instanceof GrpcPlApiClient) {\n resp = (await cl.authMethods({})).response;\n } else {\n const wsResponse = notEmpty(\n (await cl.GET(\"/v1/auth/methods\")).data,\n \"REST: empty response for auth methods request\",\n );\n // OpenAPI schema flattens the protobuf oneof into `{ basic?, token? }`,\n // while protobuf-ts models it as a discriminated union. Reshape per item.\n resp = {\n methods: (wsResponse.methods ?? []).map((m): grpcTypes.AuthAPI_ListMethods_MethodInfo => {\n const base = { id: m.id, description: m.description };\n if (m.basic !== undefined) {\n return { ...base, method: { oneofKind: \"basic\", basic: m.basic } };\n }\n if (m.token !== undefined) {\n return { ...base, method: { oneofKind: \"token\", token: m.token } };\n }\n if (m.sso !== undefined) {\n return { ...base, method: { oneofKind: \"sso\", sso: m.sso } };\n }\n return { ...base, method: { oneofKind: undefined } };\n }),\n };\n }\n\n this._authMethodsSync = resp;\n return resp;\n }\n\n public get authMethodsSync(): grpcTypes.AuthAPI_ListMethods_Response {\n if (!this._authMethodsSync) {\n throw new Error(\"LLPlClient.authMethodsSync accessed before build() completed\");\n }\n return this._authMethodsSync;\n }\n\n public async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<grpcTypes.AuthAPI_GetUserRoot_Response> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.getUserRoot({\n login: opts.login ?? \"\",\n createIfNotExists: opts.createIfNotExists ?? false,\n })\n ).response;\n } else {\n const resp = notEmpty(\n (\n await cl.POST(\"/v1/auth/user-root\", {\n body: {\n login: opts.login ?? \"\",\n createIfNotExists: opts.createIfNotExists ?? false,\n },\n })\n ).data,\n \"REST: empty response for getUserRoot request\",\n );\n return {\n userRoot: resp.userRoot\n ? {\n resourceId: BigInt(resp.userRoot.resourceId),\n resourceSignature: Uint8Array.from(\n Buffer.from(resp.userRoot.resourceSignature, \"base64\"),\n ),\n }\n : undefined,\n };\n }\n }\n\n public async listUserResources(\n opts: { login?: string; startFrom?: bigint; limit?: number } = {},\n ): Promise<grpcTypes.AuthAPI_ListUserResources_Response[]> {\n const cl = this.clientProvider.get();\n\n if (!(cl instanceof GrpcPlApiClient)) {\n throw new Error(\"ListUserResources requires gRPC wire protocol; REST is not supported\");\n }\n\n const call = cl.listUserResources({\n login: opts.login ?? \"\",\n startFrom: opts.startFrom ?? 0n,\n limit: opts.limit ?? 0,\n });\n const responses: grpcTypes.AuthAPI_ListUserResources_Response[] = [];\n for await (const msg of call.responses) {\n responses.push(msg);\n }\n return responses;\n }\n\n public async txSync(txId: bigint): Promise<void> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n await cl.txSync({ txId: BigInt(txId) });\n } else {\n await cl.POST(\"/v1/tx-sync\", { body: { txId: txId.toString() } });\n }\n }\n\n createTx(rw: boolean, ops: PlCallOps = {}): LLPlTransaction {\n return new LLPlTransaction((abortSignal) => {\n let totalAbortSignal = abortSignal;\n if (ops.abortSignal) totalAbortSignal = AbortSignal.any([totalAbortSignal, ops.abortSignal]);\n\n const timeout =\n ops.timeout ??\n (rw ? this.conf.defaultRWTransactionTimeout : this.conf.defaultROTransactionTimeout);\n\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return cl.tx({\n abort: totalAbortSignal,\n timeout,\n });\n }\n\n const wireConn = this.wireConnection;\n if (wireConn.type === \"rest\") {\n // For REST/WebSocket protocol, timeout needs to be converted to AbortSignal\n if (timeout !== undefined) {\n totalAbortSignal = AbortSignal.any([totalAbortSignal, AbortSignal.timeout(timeout)]);\n }\n\n // The gRPC transport has the auth interceptor that already handles it, but here we need to refresh the auth information to be safe.\n this.refreshAuthInformationIfNeeded();\n\n const wsUrl = this.conf.ssl\n ? `wss://${this.conf.hostAndPort}/v1/ws/tx`\n : `ws://${this.conf.hostAndPort}/v1/ws/tx`;\n\n return new WebSocketBiDiStream(\n wsUrl,\n (msg) => TxAPI_ClientMessage.toBinary(msg),\n (data) => TxAPI_ServerMessage.fromBinary(new Uint8Array(data)),\n {\n abortSignal: totalAbortSignal,\n jwtToken: this.authInformation?.jwtToken,\n dispatcher: wireConn.Dispatcher,\n\n onComplete: async (stream) =>\n stream.requests.send({\n // Ask server to gracefully close the stream on its side, if not done yet.\n requestId: 0,\n request: { oneofKind: \"streamClose\", streamClose: {} },\n }),\n },\n );\n }\n\n throw new Error(`transactions are not supported for wire protocol ${this._wireProto}`);\n });\n }\n\n /** Closes underlying transport */\n public async close() {\n if (this.wireConnection.type === \"grpc\") {\n this.wireConnection.Transport.close();\n } else {\n // TODO: close all WS connections\n }\n await this.httpDispatcher.destroy();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ll_client.js","names":["GrpcPlApiClient","status","GrpcStatus"],"sources":["../../src/core/ll_client.ts"],"sourcesContent":["import { PlatformClient as GrpcPlApiClient } from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client\";\nimport type { ClientOptions, Interceptor } from \"@grpc/grpc-js\";\nimport {\n ChannelCredentials,\n InterceptingCall,\n status as GrpcStatus,\n compressionAlgorithms,\n} from \"@grpc/grpc-js\";\nimport type {\n AuthInformation,\n AuthOps,\n PlClientConfig,\n PlConnectionStatus,\n PlConnectionStatusListener,\n} from \"./config\";\nimport { plAddressToConfig, type wireProtocol, SUPPORTED_WIRE_PROTOCOLS } from \"./config\";\nimport type { GrpcOptions } from \"@protobuf-ts/grpc-transport\";\nimport { GrpcTransport } from \"@protobuf-ts/grpc-transport\";\nimport { LLPlTransaction } from \"./ll_transaction\";\nimport { setResourceSignaturesRequired } from \"./types\";\nimport { parsePlJwt } from \"../util/pl\";\nimport { type Dispatcher, interceptors } from \"undici\";\nimport type { Middleware } from \"openapi-fetch\";\nimport { inferAuthRefreshTime } from \"./auth\";\nimport { hasCapability, type BackendCapability } from \"./capabilities\";\nimport { defaultHttpDispatcher } from \"@milaboratories/pl-http\";\nimport type { WireClientProvider, WireClientProviderFactory, WireConnection } from \"./wire\";\nimport { parseHttpAuth } from \"@milaboratories/pl-model-common\";\nimport type * as grpcTypes from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport {\n type PlApiPaths,\n type PlRestClientType,\n createClient,\n parseResponseError,\n} from \"../proto-rest\";\nimport { notEmpty, retry, withTimeout, type RetryOptions } from \"@milaboratories/ts-helpers\";\nimport { Code } from \"../proto-grpc/google/rpc/code\";\nimport { WebSocketBiDiStream } from \"./websocket_stream\";\nimport {\n AuthAPI_Role,\n TxAPI_ClientMessage,\n TxAPI_ServerMessage,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport type { MiLogger } from \"@milaboratories/ts-helpers\";\nimport { isAbortedError } from \"./errors\";\nimport { Timestamp } from \"../proto-grpc/google/protobuf/timestamp\";\n\nexport interface PlCallOps {\n timeout?: number;\n abortSignal?: AbortSignal;\n}\n\n// Parses leading \"<major>.<minor>.<patch>\" from a version string like\n// \"3.1.1\" or \"3.1.1-rc1\" and returns true if the parsed version is >= target.\n// Returns false for unparseable versions (safer to assume an old backend).\nfunction isVersionAtLeast(version: string, target: [number, number, number]): boolean {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)/.exec(version);\n if (!match) return false;\n const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];\n for (let i = 0; i < 3; i++) {\n if (parsed[i] !== target[i]) return parsed[i] > target[i];\n }\n return true;\n}\n\n// Returns true iff `version` is strictly after the release tag `target`. Dev\n// builds (`git describe`-style, e.g. \"3.5.0-224-g0ca182\") are considered\n// AFTER the matching release tag — they include commits past the tag and so\n// have any change merged after it. Released versions with the same triplet\n// return false (we want the tag itself to be excluded).\n//\n// Examples for target [3,5,0]:\n// \"3.5.0\" → false (the tagged release)\n// \"3.5.0-224-g0ca182\" → true (dev build past the tag)\n// \"3.5.1\" → true\n// \"3.4.9\" → false\n// Returns false for unparseable versions.\nfunction isAfterVersion(version: string, target: [number, number, number]): boolean {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(.*)$/.exec(version);\n if (!match) return false;\n const parsed: [number, number, number] = [Number(match[1]), Number(match[2]), Number(match[3])];\n const suffix = match[4];\n for (let i = 0; i < 3; i++) {\n if (parsed[i] !== target[i]) return parsed[i] > target[i];\n }\n return suffix !== \"\";\n}\n\nclass WireClientProviderImpl<Client> implements WireClientProvider<Client> {\n private client: Client | undefined = undefined;\n\n constructor(\n private readonly wireOpts: () => WireConnection,\n private readonly clientConstructor: (wireOpts: WireConnection) => Client,\n ) {}\n\n public reset(): void {\n this.client = undefined;\n }\n\n public get(): Client {\n if (this.client === undefined) this.client = this.clientConstructor(this.wireOpts());\n return this.client;\n }\n}\n\n/** Abstract out low level networking and authorization details */\nexport class LLPlClient implements WireClientProviderFactory {\n /** Initial authorization information */\n private authInformation?: AuthInformation;\n /** Will be executed by the client when it is required */\n private readonly onAuthUpdate?: (newInfo: AuthInformation) => void;\n /** Will be executed if auth-related error happens during normal client operation */\n private readonly onAuthError?: () => void;\n /** Will be executed by the client when it is required */\n private readonly onAuthRefreshProblem?: (error: unknown) => void;\n /** Threshold after which auth info refresh is required */\n private refreshTimestamp?: number;\n\n /** Cached Ping response. Populated by build() before it returns; refreshed by every later ping(). */\n private _serverInfo?: grpcTypes.MaintenanceAPI_Ping_Response;\n private _authMethodsSync?: grpcTypes.AuthAPI_ListMethods_Response;\n\n private _status: PlConnectionStatus = \"OK\";\n private readonly statusListener?: PlConnectionStatusListener;\n\n private _wireProto: wireProtocol = \"grpc\";\n private _wireConn!: WireConnection;\n\n private readonly _restInterceptors: Dispatcher.DispatcherComposeInterceptor[];\n private readonly _restMiddlewares: Middleware[];\n private readonly _grpcInterceptors: Interceptor[];\n private readonly providers: WeakRef<WireClientProviderImpl<any>>[] = [];\n\n public readonly clientProvider: WireClientProvider<PlRestClientType | GrpcPlApiClient>;\n\n public readonly httpDispatcher: Dispatcher;\n\n public static async build(\n configOrAddress: PlClientConfig | string,\n ops: {\n auth?: AuthOps;\n statusListener?: PlConnectionStatusListener;\n shouldUseGzip?: boolean;\n logger?: MiLogger;\n useAutoDetectWireProtocol?: boolean;\n } = {},\n ) {\n const conf =\n typeof configOrAddress === \"string\" ? plAddressToConfig(configOrAddress) : configOrAddress;\n\n const pl = new LLPlClient(conf, ops);\n\n // FIXME(rfiskov)[MILAB-5275]: Investigate why autodetect randomly fails; temporary turn it off.\n if (ops.useAutoDetectWireProtocol) {\n await pl.detectOptimalWireProtocol();\n }\n\n // Guarantee a ping happened so capability-gated paths (login, refresh) can branch synchronously.\n // In the autodetect path the loop's last successful ping already populated _serverInfo via the\n // side-effect in ping(); this fallback covers the path where autodetect is disabled.\n if (!pl._serverInfo) await pl.ping();\n\n // Install the process-global signature-strictness flag based on backend version.\n setResourceSignaturesRequired(pl.supportsResourceSignatures);\n\n // Guarantee authMethods happened so client can make weighted decision on which auth method to use.\n if (!pl._authMethodsSync) await pl.authMethods();\n\n return pl;\n }\n\n private constructor(\n public readonly conf: PlClientConfig,\n private readonly ops: {\n auth?: AuthOps;\n statusListener?: PlConnectionStatusListener;\n shouldUseGzip?: boolean;\n logger?: MiLogger;\n } = {},\n ) {\n const { auth, statusListener } = ops;\n\n if (auth !== undefined) {\n this.refreshTimestamp = inferAuthRefreshTime(\n auth.authInformation,\n this.conf.authMaxRefreshSeconds,\n );\n this.authInformation = auth.authInformation;\n this.onAuthUpdate = auth.onUpdate;\n this.onAuthRefreshProblem = auth.onUpdateError;\n this.onAuthError = auth.onAuthError;\n }\n\n this._restInterceptors = [];\n this._restMiddlewares = [];\n this._grpcInterceptors = [];\n\n if (auth !== undefined) {\n this._restInterceptors.push(this.createRestAuthInterceptor());\n this._grpcInterceptors.push(this.createGrpcAuthInterceptor());\n }\n this._restInterceptors.push(interceptors.retry({ statusCodes: [] })); // Handle errors with openapi-fetch middleware.\n this._restMiddlewares.push(this.createRestErrorMiddleware());\n this._grpcInterceptors.push(this.createGrpcErrorInterceptor());\n\n this.httpDispatcher = defaultHttpDispatcher(this.conf.httpProxy);\n if (this.conf.wireProtocol) {\n this._wireProto = this.conf.wireProtocol;\n }\n\n this.initWireConnection(this._wireProto);\n\n if (statusListener !== undefined) {\n this.statusListener = statusListener;\n statusListener(this._status);\n }\n\n this.clientProvider = this.createWireClientProvider((wireConn) => {\n if (wireConn.type === \"grpc\") {\n return new GrpcPlApiClient(wireConn.Transport);\n } else {\n return createClient<PlApiPaths>({\n hostAndPort: wireConn.Config.hostAndPort,\n ssl: wireConn.Config.ssl,\n dispatcher: wireConn.Dispatcher,\n middlewares: wireConn.Middlewares,\n });\n }\n });\n }\n\n private initWireConnection(protocol: wireProtocol) {\n switch (protocol) {\n case \"rest\":\n this.initRestConnection();\n return;\n case \"grpc\":\n this.initGrpcConnection(this.ops.shouldUseGzip ?? false);\n return;\n default:\n ((v: never) => {\n throw new Error(\n `Unsupported wire protocol '${v as string}'. Use one of: ${SUPPORTED_WIRE_PROTOCOLS.join(\", \")}`,\n );\n })(protocol);\n }\n }\n\n private initRestConnection(): void {\n const dispatcher = defaultHttpDispatcher(this.conf.grpcProxy, this._restInterceptors);\n this._replaceWireConnection({\n type: \"rest\",\n Config: this.conf,\n Dispatcher: dispatcher,\n Middlewares: this._restMiddlewares,\n });\n }\n\n /**\n * Initializes (or reinitializes) _grpcTransport\n * @param gzip - whether to enable gzip compression\n */\n private initGrpcConnection(gzip: boolean) {\n const clientOptions: ClientOptions = {\n \"grpc.keepalive_time_ms\": 30_000, // 30 seconds\n \"grpc.service_config_disable_resolution\": 1, // Disable DNS TXT lookups for service config\n interceptors: this._grpcInterceptors,\n };\n\n if (gzip) clientOptions[\"grpc.default_compression_algorithm\"] = compressionAlgorithms.gzip;\n\n //\n // Leaving it here for now\n // https://github.com/grpc/grpc-node/issues/2788\n //\n // We should implement message pooling algorithm to overcome hardcoded NO_DELAY behaviour\n // of HTTP/2 and allow our small messages to batch together.\n //\n const grpcOptions: GrpcOptions = {\n host: this.conf.hostAndPort,\n timeout: this.conf.defaultRequestTimeout,\n channelCredentials: this.conf.ssl\n ? ChannelCredentials.createSsl()\n : ChannelCredentials.createInsecure(),\n clientOptions,\n };\n\n const grpcProxy =\n typeof this.conf.grpcProxy === \"string\" ? { url: this.conf.grpcProxy } : this.conf.grpcProxy;\n\n if (grpcProxy?.url) {\n const url = new URL(grpcProxy.url);\n if (grpcProxy.auth) {\n const parsed = parseHttpAuth(grpcProxy.auth);\n if (parsed.scheme !== \"Basic\") {\n throw new Error(`Unsupported auth scheme: ${parsed.scheme as string}.`);\n }\n url.username = parsed.username;\n url.password = parsed.password;\n }\n process.env.grpc_proxy = url.toString();\n } else {\n delete process.env.grpc_proxy;\n }\n\n this._replaceWireConnection({ type: \"grpc\", Transport: new GrpcTransport(grpcOptions) });\n }\n\n private _replaceWireConnection(newConn: WireConnection): void {\n const oldConn = this._wireConn;\n this._wireConn = newConn;\n this._wireProto = newConn.type;\n\n // Reset all providers to let them reinitialize their clients\n for (let i = 0; i < this.providers.length; i++) {\n const provider = this.providers[i].deref();\n if (provider === undefined) {\n // at the same time we need to remove providers that are no longer valid\n this.providers.splice(i, 1);\n i--;\n } else {\n provider.reset();\n }\n }\n\n if (oldConn !== undefined && oldConn.type === \"grpc\") oldConn.Transport.close();\n }\n\n private providerCleanupCounter = 0;\n\n /**\n * Creates a provider for a grpc client. Returned provider will create fresh client whenever the underlying transport is reset.\n *\n * @param clientConstructor - a factory function that creates a grpc client\n */\n public createWireClientProvider<Client>(\n clientConstructor: (transport: WireConnection) => Client,\n ): WireClientProvider<Client> {\n // We need to cleanup providers periodically to avoid memory leaks.\n // This is a simple heuristic to avoid memory leaks.\n // We could use a more sophisticated algorithm, but this is good enough for now.\n this.providerCleanupCounter++;\n if (this.providerCleanupCounter >= 16) {\n for (let i = 0; i < this.providers.length; i++) {\n const provider = this.providers[i].deref();\n if (provider === undefined) {\n this.providers.splice(i, 1);\n i--;\n }\n }\n this.providerCleanupCounter = 0;\n }\n\n const provider = new WireClientProviderImpl<Client>(() => this._wireConn, clientConstructor);\n this.providers.push(new WeakRef(provider));\n return provider;\n }\n\n public get wireConnection(): WireConnection {\n return this._wireConn;\n }\n\n public get wireProtocol(): wireProtocol | undefined {\n return this._wireProto;\n }\n\n /** Returns true if client is authenticated. Even with anonymous auth information\n * connection is considered authenticated. Unauthenticated clients are used for\n * login and similar tasks, see {@link UnauthenticatedPlClient}. */\n public get authenticated(): boolean {\n return this.authInformation !== undefined;\n }\n\n /** null means anonymous connection */\n public get authUser(): string | null {\n if (!this.authenticated) throw new Error(\"Client is not authenticated\");\n if (this.authInformation?.jwtToken) {\n if (this.hasCapability(\"auth:v2\")) {\n return parsePlJwt(this.authInformation?.jwtToken).sub;\n }\n return parsePlJwt(this.authInformation?.jwtToken).user.login;\n } else return null;\n }\n\n private updateStatus(newStatus: PlConnectionStatus) {\n process.nextTick(() => {\n if (this._status !== newStatus) {\n this._status = newStatus;\n if (this.statusListener !== undefined) this.statusListener(this._status);\n if (newStatus === \"Unauthenticated\" && this.onAuthError !== undefined) this.onAuthError();\n }\n });\n }\n\n public get status(): PlConnectionStatus {\n return this._status;\n }\n\n private authRefreshInProgress: boolean = false;\n\n private refreshAuthInformationIfNeeded(): void {\n if (\n this.refreshTimestamp === undefined ||\n Date.now() < this.refreshTimestamp ||\n this.authRefreshInProgress ||\n this._status === \"Unauthenticated\"\n )\n return;\n\n // Running refresh in background`\n this.authRefreshInProgress = true;\n void (async () => {\n try {\n const ttl = BigInt(this.conf.authTTLSeconds);\n const token = this.hasCapability(\"auth:v2\")\n ? await this.refreshToken({ ttlSeconds: ttl })\n : await this.getJwtToken(ttl);\n this.authInformation = { jwtToken: token };\n this.refreshTimestamp = inferAuthRefreshTime(\n this.authInformation,\n this.conf.authMaxRefreshSeconds,\n );\n if (this.onAuthUpdate) this.onAuthUpdate(this.authInformation);\n } catch (e: unknown) {\n if (this.onAuthRefreshProblem) this.onAuthRefreshProblem(e);\n } finally {\n this.authRefreshInProgress = false;\n }\n })();\n }\n\n /**\n * Creates middleware that parses error responses and handles them centrally.\n * This middleware runs before openapi-fetch parses the response, so we need to\n * manually parse the response body for error responses.\n */\n private createRestErrorMiddleware(): Middleware {\n return {\n onResponse: async ({ request: _request, response, options: _options }) => {\n const { body, ...resOptions } = response;\n\n if ([502, 503, 504].includes(response.status)) {\n // Service unavailable, bad gateway, gateway timeout\n this.updateStatus(\"Disconnected\");\n return new Response(body, { ...resOptions, status: response.status });\n }\n\n const respErr = await parseResponseError(response);\n if (!respErr.error) {\n // No error: nice!\n return new Response(respErr.origBody ?? body, { ...resOptions, status: response.status });\n }\n\n if (typeof respErr.error === \"string\") {\n // Non-standard error or normal response: let later middleware to deal wit it.\n return new Response(respErr.error, { ...resOptions, status: response.status });\n }\n\n if (respErr.error.code === Code.UNAUTHENTICATED) {\n this.updateStatus(\"Unauthenticated\");\n }\n\n // Let later middleware to deal with standard gRPC error.\n return new Response(respErr.origBody, { ...resOptions, status: response.status });\n },\n };\n }\n\n /** Detects certain errors and update client status accordingly when using GRPC wire connection */\n private createGrpcErrorInterceptor(): Interceptor {\n return (options, nextCall) => {\n return new InterceptingCall(nextCall(options), {\n start: (metadata, listener, next) => {\n next(metadata, {\n onReceiveStatus: (status, next) => {\n if (status.code == GrpcStatus.UNAUTHENTICATED)\n // (!!!) don't change to \"===\"\n this.updateStatus(\"Unauthenticated\");\n if (status.code == GrpcStatus.UNAVAILABLE)\n // (!!!) don't change to \"===\"\n this.updateStatus(\"Disconnected\");\n next(status);\n },\n });\n },\n });\n };\n }\n\n private createRestAuthInterceptor(): Dispatcher.DispatcherComposeInterceptor {\n return (dispatch) => {\n return (options, handler) => {\n if (this.authInformation?.jwtToken !== undefined) {\n // TODO: check this magic really works and gets called\n options.headers = {\n ...options.headers,\n authorization: \"Bearer \" + this.authInformation.jwtToken,\n };\n this.refreshAuthInformationIfNeeded();\n }\n\n return dispatch(options, handler);\n };\n };\n }\n\n /** Injects authentication information if needed */\n private createGrpcAuthInterceptor(): Interceptor {\n return (options, nextCall) => {\n return new InterceptingCall(nextCall(options), {\n start: (metadata, listener, next) => {\n if (this.authInformation?.jwtToken !== undefined) {\n metadata.set(\"authorization\", \"Bearer \" + this.authInformation.jwtToken);\n this.refreshAuthInformationIfNeeded();\n next(metadata, listener);\n } else {\n next(metadata, listener);\n }\n },\n });\n };\n }\n\n public async getJwtToken(\n ttlSeconds: bigint,\n options?: { authorization?: string; role?: AuthAPI_Role },\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const role = options?.role ?? AuthAPI_Role.UNSPECIFIED;\n\n if (cl instanceof GrpcPlApiClient) {\n const meta: Record<string, string> = {};\n if (options?.authorization) meta.authorization = options.authorization;\n return (\n await cl.getJWTToken(\n {\n expiration: { seconds: ttlSeconds, nanos: 0 },\n requestedRole: role,\n },\n { meta },\n ).response\n ).token;\n } else {\n const headers: Record<string, string> = {};\n if (options?.authorization) headers.authorization = options.authorization;\n const resp = cl.POST(\"/v1/auth/jwt-token\", {\n body: { expiration: `${ttlSeconds}s`, requestedRole: role },\n headers,\n });\n return notEmpty((await resp).data, \"REST: empty response for JWT token request\").token;\n }\n }\n\n /** Login via username/password. Returns a fresh JWT. Backend creates a new session per call. */\n public async loginBasic(\n user: string,\n password: string,\n opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"basic\",\n basic: { login: user, password },\n },\n expiration: { seconds: ttl, nanos: 0 },\n requestedRole: role,\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n // openapi-typescript generated all body fields as required, but Login.Request\n // has a credentials oneof — only one of `basic`/`token` is sent. Cast around it.\n body: {\n basic: { login: user, password },\n expiration: `${ttl}s`,\n requestedRole: role,\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for login request\").token;\n }\n }\n\n /** Login via opaque bearer token (controller pre-shared secret, OIDC id-token, etc.).\n * String input is UTF-8 encoded. Returns a fresh Platforma JWT. */\n public async loginWithToken(\n token: Uint8Array | string,\n opts: { ttlSeconds?: bigint; role?: AuthAPI_Role } = {},\n ): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const role = opts.role ?? AuthAPI_Role.UNSPECIFIED;\n const bytes = typeof token === \"string\" ? Buffer.from(token, \"utf8\") : token;\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"token\",\n token: { token: bytes },\n },\n expiration: { seconds: ttl, nanos: 0 },\n requestedRole: role,\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n // openapi-typescript marks all body fields as required, but Login.Request has a oneof.\n // REST encodes `bytes` as a base64 string.\n body: {\n token: { token: Buffer.from(bytes).toString(\"base64\") },\n expiration: `${ttl}s`,\n requestedRole: role,\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for login request\").token;\n }\n }\n\n /** Login via SSO: forwards the raw IdP /token JSON body to the backend.\n * Backend validates signature + nonce + audience and mints a Platforma JWT. */\n public async loginSSO(tokenResponse: Uint8Array): Promise<string> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.login({\n credentials: {\n oneofKind: \"sso\",\n sso: { tokenResponse },\n },\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/login\", {\n body: {\n sso: { tokenResponse: Buffer.from(tokenResponse).toString(\"base64\") },\n } as any,\n });\n return notEmpty((await resp).data, \"REST: empty response for SSO login request\").token;\n }\n }\n\n /** Request a one-time PKCE nonce + its expiry from the backend. The desktop is\n * expected to place the nonce verbatim into the OIDC auth-request before\n * redirecting to the IdP; the backend enforces the expiry on {@link loginSSO}. */\n public async beginSSOLogin(): Promise<{ nonce: string; expiresAt: Date }> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n const resp = (await cl.beginSSOLogin({}).response).flow;\n if (resp.oneofKind !== \"publicPkce\") {\n throw new Error(`beginSSOLogin: unsupported flow ${resp.oneofKind}`);\n }\n const exp = notEmpty(resp.publicPkce.expiresAt, \"beginSSOLogin: missing expiresAt\");\n return {\n nonce: resp.publicPkce.nonce,\n expiresAt: Timestamp.toDate(exp),\n };\n } else {\n const wsResponse = notEmpty(\n (await cl.POST(\"/v1/auth/sso/begin-login\", { body: {} })).data,\n \"REST: empty response for beginSSOLogin request\",\n );\n const pkce = notEmpty(wsResponse.publicPkce, \"REST: missing publicPkce in beginSSOLogin\");\n return { nonce: pkce.nonce, expiresAt: new Date(pkce.expiresAt) };\n }\n }\n\n /** Refresh the current JWT, preserving session id and role. */\n public async refreshToken(opts: { ttlSeconds?: bigint } = {}): Promise<string> {\n const cl = this.clientProvider.get();\n const ttl = opts.ttlSeconds ?? BigInt(this.conf.authTTLSeconds);\n const currentToken = notEmpty(\n this.authInformation?.jwtToken,\n \"refreshToken called without a current JWT\",\n );\n\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.refreshToken({\n token: currentToken,\n expiration: { seconds: ttl, nanos: 0 },\n }).response\n ).token;\n } else {\n const resp = cl.POST(\"/v1/auth/refresh\", {\n body: { token: currentToken, expiration: `${ttl}s` },\n });\n return notEmpty((await resp).data, \"REST: empty response for refresh request\").token;\n }\n }\n\n public async ping(): Promise<grpcTypes.MaintenanceAPI_Ping_Response> {\n const cl = this.clientProvider.get();\n let resp: grpcTypes.MaintenanceAPI_Ping_Response;\n if (cl instanceof GrpcPlApiClient) {\n resp = (await cl.ping({})).response;\n } else {\n // The REST ping response predates the `capabilities` field (proto field 9).\n // Old servers omit it; treat absence as empty capability list.\n const pingData = notEmpty(\n (await cl.GET(\"/v1/ping\")).data,\n \"REST: empty response for ping request\",\n );\n resp = {\n ...(pingData as unknown as grpcTypes.MaintenanceAPI_Ping_Response),\n capabilities: (pingData as any).capabilities ?? [],\n };\n }\n this._serverInfo = resp;\n return resp;\n }\n\n /** Cached Ping response. Always populated post-build(); throws if accessed earlier. */\n public get serverInfo(): grpcTypes.MaintenanceAPI_Ping_Response {\n if (!this._serverInfo) {\n throw new Error(\"LLPlClient.serverInfo accessed before build() completed\");\n }\n return this._serverInfo;\n }\n\n /** Synchronous capability check against the cached Ping response. */\n public hasCapability(capability: BackendCapability): boolean {\n return hasCapability(this.serverInfo.capabilities, capability);\n }\n\n /** True if the backend produces signed resource ids (and accepts the\n * setDefaultColor TX request). Tagged at 3.3.0 — backends older than this\n * return resources without signatures and reject color-proof requests. */\n public get supportsResourceSignatures(): boolean {\n return isVersionAtLeast(this.serverInfo.coreVersion, [3, 3, 0]);\n }\n\n /**\n * True if the backend honors per-file `permissions` on workdir fill rules\n * (PR #1830 in milaboratory/pl). Backends before this change ignore the\n * requested mode and always land files at the canonical archive perm,\n * making `exec.builder().writeFile/addFile({ writable: true })` a no-op.\n *\n * Tagged at 3.5.0 cut without the change, so [3, 5, 0] excludes the tagged\n * release but includes dev builds past the tag (e.g. \"3.5.0-224-g0ca182\").\n */\n public get supportsWritableWorkdirFiles(): boolean {\n return isAfterVersion(this.serverInfo.coreVersion, [3, 5, 0]);\n }\n\n /**\n * Detects the best available wire protocol.\n * If wireProtocol is explicitly configured, does nothing.\n * Otherwise probes the current protocol via ping; if it fails, switches to the alternative.\n */\n private async detectOptimalWireProtocol() {\n if (this.conf.wireProtocol) {\n return;\n }\n\n // Each retry is:\n // - ping request timeout (100 to 3_000ms)\n // - backoff delay (30 to 500ms)\n //\n // 30 attempts are ~43 seconds of overall waiting time.\n // Think twice on overall time this thing takes to complete when changing these parameters.\n // It may block UI when connecting to the server and loading projects list.\n const pingTimeoutFactor = 1.3;\n const maxPingTimeoutMs = 3_000;\n const retryOptions: RetryOptions = {\n type: \"exponentialBackoff\",\n maxAttempts: 30,\n initialDelay: 30,\n backoffMultiplier: 1.3,\n jitter: 0.2,\n maxDelay: 500,\n };\n\n let attempt = 1;\n let pingTimeoutMs = 100;\n await retry(\n () => withTimeout(this.ping(), pingTimeoutMs),\n retryOptions,\n (e: unknown) => {\n if (isAbortedError(e)) {\n this.ops.logger?.info(\n `Wire proto autodetect: ping timed out after ${pingTimeoutMs}ms: attempt=${attempt}, wire=${this._wireProto}`,\n );\n\n if (attempt % 2 === 0) {\n // We have 2 wire protocols to check. Increase timeout each 2 attempts.\n pingTimeoutMs = Math.min(\n Math.round(pingTimeoutMs * pingTimeoutFactor),\n maxPingTimeoutMs,\n );\n }\n } else {\n this.ops.logger?.info(\n `Wire proto autodetect: ping failed: attempt=${attempt}, wire=${this._wireProto}, err=${String(e)}`,\n );\n }\n\n attempt++;\n const protocol = this._wireProto === \"grpc\" ? \"rest\" : \"grpc\";\n this.ops.logger?.info(\n `Wire protocol autodetect next attempt: will try wire '${protocol}' with timeout ${pingTimeoutMs}ms`,\n );\n this.initWireConnection(protocol);\n return true;\n },\n );\n }\n\n public async license(): Promise<grpcTypes.MaintenanceAPI_License_Response> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (await cl.license({})).response;\n } else {\n const resp = notEmpty(\n (await cl.GET(\"/v1/license\")).data,\n \"REST: empty response for license request\",\n );\n return {\n status: resp.status,\n isOk: resp.isOk,\n responseBody: Uint8Array.from(Buffer.from(resp.responseBody)),\n };\n }\n }\n\n public async authMethods(): Promise<grpcTypes.AuthAPI_ListMethods_Response> {\n const cl = this.clientProvider.get();\n let resp: grpcTypes.AuthAPI_ListMethods_Response;\n if (cl instanceof GrpcPlApiClient) {\n resp = (await cl.authMethods({})).response;\n } else {\n const wsResponse = notEmpty(\n (await cl.GET(\"/v1/auth/methods\")).data,\n \"REST: empty response for auth methods request\",\n );\n // OpenAPI schema flattens the protobuf oneof into `{ basic?, token? }`,\n // while protobuf-ts models it as a discriminated union. Reshape per item.\n resp = {\n methods: (wsResponse.methods ?? []).map((m): grpcTypes.AuthAPI_ListMethods_MethodInfo => {\n const base = { id: m.id, description: m.description };\n if (m.basic !== undefined) {\n return { ...base, method: { oneofKind: \"basic\", basic: m.basic } };\n }\n if (m.token !== undefined) {\n return { ...base, method: { oneofKind: \"token\", token: m.token } };\n }\n if (m.sso !== undefined) {\n return { ...base, method: { oneofKind: \"sso\", sso: m.sso } };\n }\n return { ...base, method: { oneofKind: undefined } };\n }),\n };\n }\n\n this._authMethodsSync = resp;\n return resp;\n }\n\n public get authMethodsSync(): grpcTypes.AuthAPI_ListMethods_Response {\n if (!this._authMethodsSync) {\n throw new Error(\"LLPlClient.authMethodsSync accessed before build() completed\");\n }\n return this._authMethodsSync;\n }\n\n public async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<grpcTypes.AuthAPI_GetUserRoot_Response> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return (\n await cl.getUserRoot({\n login: opts.login ?? \"\",\n createIfNotExists: opts.createIfNotExists ?? false,\n })\n ).response;\n } else {\n const resp = notEmpty(\n (\n await cl.POST(\"/v1/auth/user-root\", {\n body: {\n login: opts.login ?? \"\",\n createIfNotExists: opts.createIfNotExists ?? false,\n },\n })\n ).data,\n \"REST: empty response for getUserRoot request\",\n );\n return {\n userRoot: resp.userRoot\n ? {\n resourceId: BigInt(resp.userRoot.resourceId),\n resourceSignature: Uint8Array.from(\n Buffer.from(resp.userRoot.resourceSignature, \"base64\"),\n ),\n }\n : undefined,\n };\n }\n }\n\n public async listUserResources(\n opts: { login?: string; startFrom?: bigint; limit?: number } = {},\n ): Promise<grpcTypes.AuthAPI_ListUserResources_Response[]> {\n const cl = this.clientProvider.get();\n\n if (!(cl instanceof GrpcPlApiClient)) {\n throw new Error(\"ListUserResources requires gRPC wire protocol; REST is not supported\");\n }\n\n const call = cl.listUserResources({\n login: opts.login ?? \"\",\n startFrom: opts.startFrom ?? 0n,\n limit: opts.limit ?? 0,\n });\n const responses: grpcTypes.AuthAPI_ListUserResources_Response[] = [];\n for await (const msg of call.responses) {\n responses.push(msg);\n }\n return responses;\n }\n\n public async txSync(txId: bigint): Promise<void> {\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n await cl.txSync({ txId: BigInt(txId) });\n } else {\n await cl.POST(\"/v1/tx-sync\", { body: { txId: txId.toString() } });\n }\n }\n\n createTx(rw: boolean, ops: PlCallOps = {}): LLPlTransaction {\n return new LLPlTransaction((abortSignal) => {\n let totalAbortSignal = abortSignal;\n if (ops.abortSignal) totalAbortSignal = AbortSignal.any([totalAbortSignal, ops.abortSignal]);\n\n const timeout =\n ops.timeout ??\n (rw ? this.conf.defaultRWTransactionTimeout : this.conf.defaultROTransactionTimeout);\n\n const cl = this.clientProvider.get();\n if (cl instanceof GrpcPlApiClient) {\n return cl.tx({\n abort: totalAbortSignal,\n timeout,\n });\n }\n\n const wireConn = this.wireConnection;\n if (wireConn.type === \"rest\") {\n // For REST/WebSocket protocol, timeout needs to be converted to AbortSignal\n if (timeout !== undefined) {\n totalAbortSignal = AbortSignal.any([totalAbortSignal, AbortSignal.timeout(timeout)]);\n }\n\n // The gRPC transport has the auth interceptor that already handles it, but here we need to refresh the auth information to be safe.\n this.refreshAuthInformationIfNeeded();\n\n const wsUrl = this.conf.ssl\n ? `wss://${this.conf.hostAndPort}/v1/ws/tx`\n : `ws://${this.conf.hostAndPort}/v1/ws/tx`;\n\n return new WebSocketBiDiStream(\n wsUrl,\n (msg) => TxAPI_ClientMessage.toBinary(msg),\n (data) => TxAPI_ServerMessage.fromBinary(new Uint8Array(data)),\n {\n abortSignal: totalAbortSignal,\n jwtToken: this.authInformation?.jwtToken,\n dispatcher: wireConn.Dispatcher,\n\n onComplete: async (stream) =>\n stream.requests.send({\n // Ask server to gracefully close the stream on its side, if not done yet.\n requestId: 0,\n request: { oneofKind: \"streamClose\", streamClose: {} },\n }),\n },\n );\n }\n\n throw new Error(`transactions are not supported for wire protocol ${this._wireProto}`);\n });\n }\n\n /** Closes underlying transport */\n public async close() {\n if (this.wireConnection.type === \"grpc\") {\n this.wireConnection.Transport.close();\n } else {\n // TODO: close all WS connections\n }\n await this.httpDispatcher.destroy();\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_api = require("../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs");
2
3
  const require_errors = require("./errors.cjs");
3
4
  const require_ll_client = require("./ll_client.cjs");
4
5
  let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
@@ -26,7 +27,7 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
26
27
  }
27
28
  /** Classifies the advertised authentication methods by credential scheme.
28
29
  * On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}
29
- * which uses the legacy GetJWTToken path. */
30
+ * which uses the legacy GetJWTToken path. SSO is surfaced via {@link ssoConfig}, not here. */
30
31
  get supportedAuthSchemes() {
31
32
  const result = {
32
33
  basic: false,
@@ -36,6 +37,29 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
36
37
  else if (m.method.oneofKind === "token") result.token = true;
37
38
  return result;
38
39
  }
40
+ /** Projection of the first advertised SSO method, derived from {@link authMethodsSync}.
41
+ * v1: at most one SSO method per deployment, so callers do not need to discriminate. */
42
+ ssoConfig() {
43
+ for (const method of this.ll.authMethodsSync.methods) {
44
+ if (method.method.oneofKind !== "sso") continue;
45
+ const sso = method.method.sso;
46
+ if (sso.flowType !== require_api.AuthAPI_ListMethods_SSOAuthMethod_FlowType.PUBLIC_PKCE) throw new Error(`ssoConfig: unsupported SSO flow type ${sso.flowType}`);
47
+ return {
48
+ id: method.id,
49
+ description: method.description,
50
+ issuer: sso.issuer,
51
+ clientId: sso.clientId,
52
+ scopes: sso.scopes,
53
+ resource: sso.resource,
54
+ prompt: sso.prompt,
55
+ redirectPorts: sso.redirectPorts,
56
+ subjectTokenSource: sso.subjectTokenSource,
57
+ userIdClaim: sso.userIdClaim,
58
+ groupsClaim: sso.groupsClaim,
59
+ flowType: "public_pkce"
60
+ };
61
+ }
62
+ }
39
63
  /** Login with username+password.
40
64
  *
41
65
  * On auth:v2 backends the client inspects the advertised AuthMethods:
@@ -63,6 +87,27 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
63
87
  throw new Error(e);
64
88
  }
65
89
  }
90
+ /** Request fresh server-issued login material. v1 only emits the public-PKCE flow;
91
+ * desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */
92
+ async beginSSOLogin() {
93
+ const { nonce, expiresAt } = await this.ll.beginSSOLogin();
94
+ return {
95
+ flow: "public_pkce",
96
+ nonce,
97
+ expiresAt
98
+ };
99
+ }
100
+ /** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */
101
+ async loginSSO(payload) {
102
+ try {
103
+ const jwtToken = await this.ll.loginSSO(payload.tokenResponse);
104
+ if (jwtToken === "") throw new Error("empty token");
105
+ return { jwtToken };
106
+ } catch (e) {
107
+ if (e.code === "UNAUTHENTICATED") throw new require_errors.UnauthenticatedError(e.message);
108
+ throw new Error(e);
109
+ }
110
+ }
66
111
  };
67
112
  //#endregion
68
113
  exports.UnauthenticatedPlClient = UnauthenticatedPlClient;
@@ -1 +1 @@
1
- {"version":3,"file":"unauth_client.cjs","names":["LLPlClient","UnauthenticatedError"],"sources":["../../src/core/unauth_client.ts"],"sourcesContent":["import type { AuthInformation, PlClientConfig } from \"./config\";\nimport type {\n AuthAPI_ListMethods_Response,\n MaintenanceAPI_Ping_Response,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport { LLPlClient } from \"./ll_client\";\nimport { type MiLogger, notEmpty } from \"@milaboratories/ts-helpers\";\nimport { UnauthenticatedError } from \"./errors\";\nimport type { BackendCapability } from \"./capabilities\";\n\n/** Primarily used for initial authentication (login) */\nexport class UnauthenticatedPlClient {\n public readonly ll: LLPlClient;\n\n private constructor(ll: LLPlClient) {\n this.ll = ll;\n }\n\n public static async build(\n configOrAddress: PlClientConfig | string,\n ops?: { logger?: MiLogger },\n ): Promise<UnauthenticatedPlClient> {\n const ll = await LLPlClient.build(configOrAddress, ops);\n return new UnauthenticatedPlClient(ll);\n }\n\n public async ping(): Promise<MaintenanceAPI_Ping_Response> {\n return await this.ll.ping();\n }\n\n public async authMethods(): Promise<AuthAPI_ListMethods_Response> {\n return await this.ll.authMethods();\n }\n\n public async requireAuth(): Promise<boolean> {\n return (await this.authMethods()).methods.length > 0;\n }\n\n public hasCapability(capability: BackendCapability): boolean {\n return this.ll.hasCapability(capability);\n }\n\n /** Classifies the advertised authentication methods by credential scheme.\n * On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}\n * which uses the legacy GetJWTToken path. */\n public get supportedAuthSchemes(): { basic: boolean; token: boolean } {\n const result = { basic: false, token: false };\n for (const m of this.ll.authMethodsSync.methods) {\n if (m.method.oneofKind === \"basic\") result.basic = true;\n else if (m.method.oneofKind === \"token\") result.token = true;\n }\n return result;\n }\n\n /** Login with username+password.\n *\n * On auth:v2 backends the client inspects the advertised AuthMethods:\n * - if basic auth is offered, sends {@link LLPlClient.loginBasic};\n * - if only token auth is offered, treats `password` as an opaque bearer token and\n * sends {@link LLPlClient.loginWithToken} (so deployments configured for static-token\n * auth still log in without the caller switching methods).\n *\n * On legacy backends (no auth:v2) it falls through to GetJWTToken with the Basic header,\n * preserving original behavior. */\n public async login(user: string, password: string): Promise<AuthInformation> {\n try {\n let token: string;\n if (this.ll.hasCapability(\"auth:v2\")) {\n const schemes = this.supportedAuthSchemes;\n if (schemes.basic) {\n token = await this.ll.loginBasic(user, password);\n } else if (schemes.token) {\n token = await this.ll.loginWithToken(password);\n } else {\n throw new Error(\"backend advertises no supported authentication methods\");\n }\n } else {\n token = await this.ll.getJwtToken(BigInt(this.ll.conf.authTTLSeconds), {\n authorization: \"Basic \" + Buffer.from(user + \":\" + password).toString(\"base64\"),\n });\n }\n const jwtToken = notEmpty(token);\n if (jwtToken === \"\") throw new Error(\"empty token\");\n return { jwtToken };\n } catch (e: any) {\n if (e.code === \"UNAUTHENTICATED\") throw new UnauthenticatedError(e.message);\n throw new Error(e);\n }\n }\n}\n"],"mappings":";;;;;;AAWA,IAAa,0BAAb,MAAa,wBAAwB;CACnC;CAEA,YAAoB,IAAgB;AAClC,OAAK,KAAK;;CAGZ,aAAoB,MAClB,iBACA,KACkC;AAElC,SAAO,IAAI,wBADA,MAAMA,kBAAAA,WAAW,MAAM,iBAAiB,IAAI,CACjB;;CAGxC,MAAa,OAA8C;AACzD,SAAO,MAAM,KAAK,GAAG,MAAM;;CAG7B,MAAa,cAAqD;AAChE,SAAO,MAAM,KAAK,GAAG,aAAa;;CAGpC,MAAa,cAAgC;AAC3C,UAAQ,MAAM,KAAK,aAAa,EAAE,QAAQ,SAAS;;CAGrD,cAAqB,YAAwC;AAC3D,SAAO,KAAK,GAAG,cAAc,WAAW;;;;;CAM1C,IAAW,uBAA2D;EACpE,MAAM,SAAS;GAAE,OAAO;GAAO,OAAO;GAAO;AAC7C,OAAK,MAAM,KAAK,KAAK,GAAG,gBAAgB,QACtC,KAAI,EAAE,OAAO,cAAc,QAAS,QAAO,QAAQ;WAC1C,EAAE,OAAO,cAAc,QAAS,QAAO,QAAQ;AAE1D,SAAO;;;;;;;;;;;;CAaT,MAAa,MAAM,MAAc,UAA4C;AAC3E,MAAI;GACF,IAAI;AACJ,OAAI,KAAK,GAAG,cAAc,UAAU,EAAE;IACpC,MAAM,UAAU,KAAK;AACrB,QAAI,QAAQ,MACV,SAAQ,MAAM,KAAK,GAAG,WAAW,MAAM,SAAS;aACvC,QAAQ,MACjB,SAAQ,MAAM,KAAK,GAAG,eAAe,SAAS;QAE9C,OAAM,IAAI,MAAM,yDAAyD;SAG3E,SAAQ,MAAM,KAAK,GAAG,YAAY,OAAO,KAAK,GAAG,KAAK,eAAe,EAAE,EACrE,eAAe,WAAW,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC,SAAS,SAAS,EAChF,CAAC;GAEJ,MAAM,YAAA,GAAA,2BAAA,UAAoB,MAAM;AAChC,OAAI,aAAa,GAAI,OAAM,IAAI,MAAM,cAAc;AACnD,UAAO,EAAE,UAAU;WACZ,GAAQ;AACf,OAAI,EAAE,SAAS,kBAAmB,OAAM,IAAIC,eAAAA,qBAAqB,EAAE,QAAQ;AAC3E,SAAM,IAAI,MAAM,EAAE"}
1
+ {"version":3,"file":"unauth_client.cjs","names":["LLPlClient","AuthAPI_ListMethods_SSOAuthMethod_FlowType","UnauthenticatedError"],"sources":["../../src/core/unauth_client.ts"],"sourcesContent":["import type { AuthInformation, PlClientConfig } from \"./config\";\nimport {\n type AuthAPI_ListMethods_Response,\n type MaintenanceAPI_Ping_Response,\n AuthAPI_ListMethods_SSOAuthMethod_FlowType,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport { LLPlClient } from \"./ll_client\";\nimport { type MiLogger, notEmpty } from \"@milaboratories/ts-helpers\";\nimport { UnauthenticatedError } from \"./errors\";\nimport type { BackendCapability } from \"./capabilities\";\n\n/** Login-flow shape advertised by the backend for a given SSO method. Extension\n * point: future flow variants (e.g. backend-driven) sit alongside `public_pkce`. */\nexport type SSOFlowType = \"public_pkce\";\n\n/** Typed projection of `AuthAPI.ListMethods.SSOAuthMethod` for desktop consumption.\n * Carries everything needed to drive the PKCE flow + diagnostics fields the renderer\n * may surface (`userIdClaim`, `groupsClaim`). */\nexport type SSOAuthMethod = {\n id: string;\n description: string;\n issuer: string;\n clientId: string;\n scopes: string;\n resource: string;\n prompt: string;\n redirectPorts: number[];\n subjectTokenSource: string;\n userIdClaim: string;\n groupsClaim: string;\n flowType: SSOFlowType;\n};\n\n/** Server-issued material returned by {@link UnauthenticatedPlClient.beginSSOLogin}.\n * Discriminated on `flow`; future flow variants extend the union. */\nexport type SSOLoginAttempt = {\n flow: \"public_pkce\";\n nonce: string;\n expiresAt: Date;\n};\n\n/** Primarily used for initial authentication (login) */\nexport class UnauthenticatedPlClient {\n public readonly ll: LLPlClient;\n\n private constructor(ll: LLPlClient) {\n this.ll = ll;\n }\n\n public static async build(\n configOrAddress: PlClientConfig | string,\n ops?: { logger?: MiLogger },\n ): Promise<UnauthenticatedPlClient> {\n const ll = await LLPlClient.build(configOrAddress, ops);\n return new UnauthenticatedPlClient(ll);\n }\n\n public async ping(): Promise<MaintenanceAPI_Ping_Response> {\n return await this.ll.ping();\n }\n\n public async authMethods(): Promise<AuthAPI_ListMethods_Response> {\n return await this.ll.authMethods();\n }\n\n public async requireAuth(): Promise<boolean> {\n return (await this.authMethods()).methods.length > 0;\n }\n\n public hasCapability(capability: BackendCapability): boolean {\n return this.ll.hasCapability(capability);\n }\n\n /** Classifies the advertised authentication methods by credential scheme.\n * On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}\n * which uses the legacy GetJWTToken path. SSO is surfaced via {@link ssoConfig}, not here. */\n public get supportedAuthSchemes(): { basic: boolean; token: boolean } {\n const result = { basic: false, token: false };\n for (const m of this.ll.authMethodsSync.methods) {\n if (m.method.oneofKind === \"basic\") result.basic = true;\n else if (m.method.oneofKind === \"token\") result.token = true;\n }\n return result;\n }\n\n /** Projection of the first advertised SSO method, derived from {@link authMethodsSync}.\n * v1: at most one SSO method per deployment, so callers do not need to discriminate. */\n public ssoConfig(): SSOAuthMethod | undefined {\n for (const method of this.ll.authMethodsSync.methods) {\n if (method.method.oneofKind !== \"sso\") continue;\n const sso = method.method.sso;\n if (sso.flowType !== AuthAPI_ListMethods_SSOAuthMethod_FlowType.PUBLIC_PKCE) {\n throw new Error(`ssoConfig: unsupported SSO flow type ${sso.flowType}`);\n }\n return {\n id: method.id,\n description: method.description,\n issuer: sso.issuer,\n clientId: sso.clientId,\n scopes: sso.scopes,\n resource: sso.resource,\n prompt: sso.prompt,\n redirectPorts: sso.redirectPorts,\n subjectTokenSource: sso.subjectTokenSource,\n userIdClaim: sso.userIdClaim,\n groupsClaim: sso.groupsClaim,\n flowType: \"public_pkce\",\n };\n }\n return undefined;\n }\n\n /** Login with username+password.\n *\n * On auth:v2 backends the client inspects the advertised AuthMethods:\n * - if basic auth is offered, sends {@link LLPlClient.loginBasic};\n * - if only token auth is offered, treats `password` as an opaque bearer token and\n * sends {@link LLPlClient.loginWithToken} (so deployments configured for static-token\n * auth still log in without the caller switching methods).\n *\n * On legacy backends (no auth:v2) it falls through to GetJWTToken with the Basic header,\n * preserving original behavior. */\n public async login(user: string, password: string): Promise<AuthInformation> {\n try {\n let token: string;\n if (this.ll.hasCapability(\"auth:v2\")) {\n const schemes = this.supportedAuthSchemes;\n if (schemes.basic) {\n token = await this.ll.loginBasic(user, password);\n } else if (schemes.token) {\n token = await this.ll.loginWithToken(password);\n } else {\n throw new Error(\"backend advertises no supported authentication methods\");\n }\n } else {\n token = await this.ll.getJwtToken(BigInt(this.ll.conf.authTTLSeconds), {\n authorization: \"Basic \" + Buffer.from(user + \":\" + password).toString(\"base64\"),\n });\n }\n const jwtToken = notEmpty(token);\n if (jwtToken === \"\") throw new Error(\"empty token\");\n return { jwtToken };\n } catch (e: any) {\n if (e.code === \"UNAUTHENTICATED\") throw new UnauthenticatedError(e.message);\n throw new Error(e);\n }\n }\n\n /** Request fresh server-issued login material. v1 only emits the public-PKCE flow;\n * desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */\n public async beginSSOLogin(): Promise<SSOLoginAttempt> {\n const { nonce, expiresAt } = await this.ll.beginSSOLogin();\n return { flow: \"public_pkce\", nonce, expiresAt };\n }\n\n /** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */\n public async loginSSO(payload: { tokenResponse: Uint8Array }): Promise<AuthInformation> {\n try {\n const jwtToken = await this.ll.loginSSO(payload.tokenResponse);\n if (jwtToken === \"\") throw new Error(\"empty token\");\n return { jwtToken };\n } catch (e: any) {\n if (e.code === \"UNAUTHENTICATED\") throw new UnauthenticatedError(e.message);\n throw new Error(e);\n }\n }\n}\n"],"mappings":";;;;;;;AA0CA,IAAa,0BAAb,MAAa,wBAAwB;CACnC;CAEA,YAAoB,IAAgB;AAClC,OAAK,KAAK;;CAGZ,aAAoB,MAClB,iBACA,KACkC;AAElC,SAAO,IAAI,wBADA,MAAMA,kBAAAA,WAAW,MAAM,iBAAiB,IAAI,CACjB;;CAGxC,MAAa,OAA8C;AACzD,SAAO,MAAM,KAAK,GAAG,MAAM;;CAG7B,MAAa,cAAqD;AAChE,SAAO,MAAM,KAAK,GAAG,aAAa;;CAGpC,MAAa,cAAgC;AAC3C,UAAQ,MAAM,KAAK,aAAa,EAAE,QAAQ,SAAS;;CAGrD,cAAqB,YAAwC;AAC3D,SAAO,KAAK,GAAG,cAAc,WAAW;;;;;CAM1C,IAAW,uBAA2D;EACpE,MAAM,SAAS;GAAE,OAAO;GAAO,OAAO;GAAO;AAC7C,OAAK,MAAM,KAAK,KAAK,GAAG,gBAAgB,QACtC,KAAI,EAAE,OAAO,cAAc,QAAS,QAAO,QAAQ;WAC1C,EAAE,OAAO,cAAc,QAAS,QAAO,QAAQ;AAE1D,SAAO;;;;CAKT,YAA8C;AAC5C,OAAK,MAAM,UAAU,KAAK,GAAG,gBAAgB,SAAS;AACpD,OAAI,OAAO,OAAO,cAAc,MAAO;GACvC,MAAM,MAAM,OAAO,OAAO;AAC1B,OAAI,IAAI,aAAaC,YAAAA,2CAA2C,YAC9D,OAAM,IAAI,MAAM,wCAAwC,IAAI,WAAW;AAEzE,UAAO;IACL,IAAI,OAAO;IACX,aAAa,OAAO;IACpB,QAAQ,IAAI;IACZ,UAAU,IAAI;IACd,QAAQ,IAAI;IACZ,UAAU,IAAI;IACd,QAAQ,IAAI;IACZ,eAAe,IAAI;IACnB,oBAAoB,IAAI;IACxB,aAAa,IAAI;IACjB,aAAa,IAAI;IACjB,UAAU;IACX;;;;;;;;;;;;;CAeL,MAAa,MAAM,MAAc,UAA4C;AAC3E,MAAI;GACF,IAAI;AACJ,OAAI,KAAK,GAAG,cAAc,UAAU,EAAE;IACpC,MAAM,UAAU,KAAK;AACrB,QAAI,QAAQ,MACV,SAAQ,MAAM,KAAK,GAAG,WAAW,MAAM,SAAS;aACvC,QAAQ,MACjB,SAAQ,MAAM,KAAK,GAAG,eAAe,SAAS;QAE9C,OAAM,IAAI,MAAM,yDAAyD;SAG3E,SAAQ,MAAM,KAAK,GAAG,YAAY,OAAO,KAAK,GAAG,KAAK,eAAe,EAAE,EACrE,eAAe,WAAW,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC,SAAS,SAAS,EAChF,CAAC;GAEJ,MAAM,YAAA,GAAA,2BAAA,UAAoB,MAAM;AAChC,OAAI,aAAa,GAAI,OAAM,IAAI,MAAM,cAAc;AACnD,UAAO,EAAE,UAAU;WACZ,GAAQ;AACf,OAAI,EAAE,SAAS,kBAAmB,OAAM,IAAIC,eAAAA,qBAAqB,EAAE,QAAQ;AAC3E,SAAM,IAAI,MAAM,EAAE;;;;;CAMtB,MAAa,gBAA0C;EACrD,MAAM,EAAE,OAAO,cAAc,MAAM,KAAK,GAAG,eAAe;AAC1D,SAAO;GAAE,MAAM;GAAe;GAAO;GAAW;;;CAIlD,MAAa,SAAS,SAAkE;AACtF,MAAI;GACF,MAAM,WAAW,MAAM,KAAK,GAAG,SAAS,QAAQ,cAAc;AAC9D,OAAI,aAAa,GAAI,OAAM,IAAI,MAAM,cAAc;AACnD,UAAO,EAAE,UAAU;WACZ,GAAQ;AACf,OAAI,EAAE,SAAS,kBAAmB,OAAM,IAAIA,eAAAA,qBAAqB,EAAE,QAAQ;AAC3E,SAAM,IAAI,MAAM,EAAE"}
@@ -5,6 +5,33 @@ import { LLPlClient } from "./ll_client.js";
5
5
  import { MiLogger } from "@milaboratories/ts-helpers";
6
6
 
7
7
  //#region src/core/unauth_client.d.ts
8
+ /** Login-flow shape advertised by the backend for a given SSO method. Extension
9
+ * point: future flow variants (e.g. backend-driven) sit alongside `public_pkce`. */
10
+ type SSOFlowType = "public_pkce";
11
+ /** Typed projection of `AuthAPI.ListMethods.SSOAuthMethod` for desktop consumption.
12
+ * Carries everything needed to drive the PKCE flow + diagnostics fields the renderer
13
+ * may surface (`userIdClaim`, `groupsClaim`). */
14
+ type SSOAuthMethod = {
15
+ id: string;
16
+ description: string;
17
+ issuer: string;
18
+ clientId: string;
19
+ scopes: string;
20
+ resource: string;
21
+ prompt: string;
22
+ redirectPorts: number[];
23
+ subjectTokenSource: string;
24
+ userIdClaim: string;
25
+ groupsClaim: string;
26
+ flowType: SSOFlowType;
27
+ };
28
+ /** Server-issued material returned by {@link UnauthenticatedPlClient.beginSSOLogin}.
29
+ * Discriminated on `flow`; future flow variants extend the union. */
30
+ type SSOLoginAttempt = {
31
+ flow: "public_pkce";
32
+ nonce: string;
33
+ expiresAt: Date;
34
+ };
8
35
  /** Primarily used for initial authentication (login) */
9
36
  declare class UnauthenticatedPlClient {
10
37
  readonly ll: LLPlClient;
@@ -18,11 +45,14 @@ declare class UnauthenticatedPlClient {
18
45
  hasCapability(capability: BackendCapability): boolean;
19
46
  /** Classifies the advertised authentication methods by credential scheme.
20
47
  * On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}
21
- * which uses the legacy GetJWTToken path. */
48
+ * which uses the legacy GetJWTToken path. SSO is surfaced via {@link ssoConfig}, not here. */
22
49
  get supportedAuthSchemes(): {
23
50
  basic: boolean;
24
51
  token: boolean;
25
52
  };
53
+ /** Projection of the first advertised SSO method, derived from {@link authMethodsSync}.
54
+ * v1: at most one SSO method per deployment, so callers do not need to discriminate. */
55
+ ssoConfig(): SSOAuthMethod | undefined;
26
56
  /** Login with username+password.
27
57
  *
28
58
  * On auth:v2 backends the client inspects the advertised AuthMethods:
@@ -34,7 +64,14 @@ declare class UnauthenticatedPlClient {
34
64
  * On legacy backends (no auth:v2) it falls through to GetJWTToken with the Basic header,
35
65
  * preserving original behavior. */
36
66
  login(user: string, password: string): Promise<AuthInformation>;
67
+ /** Request fresh server-issued login material. v1 only emits the public-PKCE flow;
68
+ * desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */
69
+ beginSSOLogin(): Promise<SSOLoginAttempt>;
70
+ /** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */
71
+ loginSSO(payload: {
72
+ tokenResponse: Uint8Array;
73
+ }): Promise<AuthInformation>;
37
74
  }
38
75
  //#endregion
39
- export { UnauthenticatedPlClient };
76
+ export { SSOAuthMethod, SSOFlowType, SSOLoginAttempt, UnauthenticatedPlClient };
40
77
  //# sourceMappingURL=unauth_client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"unauth_client.d.ts","names":[],"sources":["../../src/core/unauth_client.ts"],"mappings":";;;;;;;;cAWa,uBAAA;EAAA,SACK,EAAA,EAAI,UAAA;EAAA,QAEb,WAAA,CAAA;EAAA,OAIa,KAAA,CAClB,eAAA,EAAiB,cAAA,WACjB,GAAA;IAAQ,MAAA,GAAS,QAAA;EAAA,IAChB,OAAA,CAAQ,uBAAA;EAKE,IAAA,CAAA,GAAQ,OAAA,CAAQ,4BAAA;EAIhB,WAAA,CAAA,GAAe,OAAA,CAAQ,4BAAA;EAIvB,WAAA,CAAA,GAAe,OAAA;EAIrB,aAAA,CAAc,UAAA,EAAY,iBAAA;EAZZ;;;EAAA,IAmBV,oBAAA,CAAA;IAA0B,KAAA;IAAgB,KAAA;EAAA;EAmBM;;;;;;;;;;EAA9C,KAAA,CAAM,IAAA,UAAc,QAAA,WAAmB,OAAA,CAAQ,eAAA;AAAA"}
1
+ {"version":3,"file":"unauth_client.d.ts","names":[],"sources":["../../src/core/unauth_client.ts"],"mappings":";;;;;;;;;KAaY,WAAA;;;;KAKA,aAAA;EACV,EAAA;EACA,WAAA;EACA,MAAA;EACA,QAAA;EACA,MAAA;EACA,QAAA;EACA,MAAA;EACA,aAAA;EACA,kBAAA;EACA,WAAA;EACA,WAAA;EACA,QAAA,EAAU,WAAA;AAAA;;;KAKA,eAAA;EACV,IAAA;EACA,KAAA;EACA,SAAA,EAAW,IAAA;AAAA;;cAIA,uBAAA;EAAA,SACK,EAAA,EAAI,UAAA;EAAA,QAEb,WAAA,CAAA;EAAA,OAIa,KAAA,CAClB,eAAA,EAAiB,cAAA,WACjB,GAAA;IAAQ,MAAA,GAAS,QAAA;EAAA,IAChB,OAAA,CAAQ,uBAAA;EAKE,IAAA,CAAA,GAAQ,OAAA,CAAQ,4BAAA;EAIhB,WAAA,CAAA,GAAe,OAAA,CAAQ,4BAAA;EAIvB,WAAA,CAAA,GAAe,OAAA;EAIrB,aAAA,CAAc,UAAA,EAAY,iBAAA;;;;MAOtB,oBAAA,CAAA;IAA0B,KAAA;IAAgB,KAAA;EAAA;EAnBhC;;EA8Bd,SAAA,CAAA,GAAa,aAAA;EAtBQ;;;;;;;;;;EAyDf,KAAA,CAAM,IAAA,UAAc,QAAA,WAAmB,OAAA,CAAQ,eAAA;EAkCU;;EANzD,aAAA,CAAA,GAAiB,OAAA,CAAQ,eAAA;EAzG/B;EA+GM,QAAA,CAAS,OAAA;IAAW,aAAA,EAAe,UAAA;EAAA,IAAe,OAAA,CAAQ,eAAA;AAAA"}
@@ -1,3 +1,4 @@
1
+ import { AuthAPI_ListMethods_SSOAuthMethod_FlowType } from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js";
1
2
  import { UnauthenticatedError } from "./errors.js";
2
3
  import { LLPlClient } from "./ll_client.js";
3
4
  import { notEmpty } from "@milaboratories/ts-helpers";
@@ -25,7 +26,7 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
25
26
  }
26
27
  /** Classifies the advertised authentication methods by credential scheme.
27
28
  * On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}
28
- * which uses the legacy GetJWTToken path. */
29
+ * which uses the legacy GetJWTToken path. SSO is surfaced via {@link ssoConfig}, not here. */
29
30
  get supportedAuthSchemes() {
30
31
  const result = {
31
32
  basic: false,
@@ -35,6 +36,29 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
35
36
  else if (m.method.oneofKind === "token") result.token = true;
36
37
  return result;
37
38
  }
39
+ /** Projection of the first advertised SSO method, derived from {@link authMethodsSync}.
40
+ * v1: at most one SSO method per deployment, so callers do not need to discriminate. */
41
+ ssoConfig() {
42
+ for (const method of this.ll.authMethodsSync.methods) {
43
+ if (method.method.oneofKind !== "sso") continue;
44
+ const sso = method.method.sso;
45
+ if (sso.flowType !== AuthAPI_ListMethods_SSOAuthMethod_FlowType.PUBLIC_PKCE) throw new Error(`ssoConfig: unsupported SSO flow type ${sso.flowType}`);
46
+ return {
47
+ id: method.id,
48
+ description: method.description,
49
+ issuer: sso.issuer,
50
+ clientId: sso.clientId,
51
+ scopes: sso.scopes,
52
+ resource: sso.resource,
53
+ prompt: sso.prompt,
54
+ redirectPorts: sso.redirectPorts,
55
+ subjectTokenSource: sso.subjectTokenSource,
56
+ userIdClaim: sso.userIdClaim,
57
+ groupsClaim: sso.groupsClaim,
58
+ flowType: "public_pkce"
59
+ };
60
+ }
61
+ }
38
62
  /** Login with username+password.
39
63
  *
40
64
  * On auth:v2 backends the client inspects the advertised AuthMethods:
@@ -62,6 +86,27 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
62
86
  throw new Error(e);
63
87
  }
64
88
  }
89
+ /** Request fresh server-issued login material. v1 only emits the public-PKCE flow;
90
+ * desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */
91
+ async beginSSOLogin() {
92
+ const { nonce, expiresAt } = await this.ll.beginSSOLogin();
93
+ return {
94
+ flow: "public_pkce",
95
+ nonce,
96
+ expiresAt
97
+ };
98
+ }
99
+ /** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */
100
+ async loginSSO(payload) {
101
+ try {
102
+ const jwtToken = await this.ll.loginSSO(payload.tokenResponse);
103
+ if (jwtToken === "") throw new Error("empty token");
104
+ return { jwtToken };
105
+ } catch (e) {
106
+ if (e.code === "UNAUTHENTICATED") throw new UnauthenticatedError(e.message);
107
+ throw new Error(e);
108
+ }
109
+ }
65
110
  };
66
111
  //#endregion
67
112
  export { UnauthenticatedPlClient };
@@ -1 +1 @@
1
- {"version":3,"file":"unauth_client.js","names":[],"sources":["../../src/core/unauth_client.ts"],"sourcesContent":["import type { AuthInformation, PlClientConfig } from \"./config\";\nimport type {\n AuthAPI_ListMethods_Response,\n MaintenanceAPI_Ping_Response,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport { LLPlClient } from \"./ll_client\";\nimport { type MiLogger, notEmpty } from \"@milaboratories/ts-helpers\";\nimport { UnauthenticatedError } from \"./errors\";\nimport type { BackendCapability } from \"./capabilities\";\n\n/** Primarily used for initial authentication (login) */\nexport class UnauthenticatedPlClient {\n public readonly ll: LLPlClient;\n\n private constructor(ll: LLPlClient) {\n this.ll = ll;\n }\n\n public static async build(\n configOrAddress: PlClientConfig | string,\n ops?: { logger?: MiLogger },\n ): Promise<UnauthenticatedPlClient> {\n const ll = await LLPlClient.build(configOrAddress, ops);\n return new UnauthenticatedPlClient(ll);\n }\n\n public async ping(): Promise<MaintenanceAPI_Ping_Response> {\n return await this.ll.ping();\n }\n\n public async authMethods(): Promise<AuthAPI_ListMethods_Response> {\n return await this.ll.authMethods();\n }\n\n public async requireAuth(): Promise<boolean> {\n return (await this.authMethods()).methods.length > 0;\n }\n\n public hasCapability(capability: BackendCapability): boolean {\n return this.ll.hasCapability(capability);\n }\n\n /** Classifies the advertised authentication methods by credential scheme.\n * On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}\n * which uses the legacy GetJWTToken path. */\n public get supportedAuthSchemes(): { basic: boolean; token: boolean } {\n const result = { basic: false, token: false };\n for (const m of this.ll.authMethodsSync.methods) {\n if (m.method.oneofKind === \"basic\") result.basic = true;\n else if (m.method.oneofKind === \"token\") result.token = true;\n }\n return result;\n }\n\n /** Login with username+password.\n *\n * On auth:v2 backends the client inspects the advertised AuthMethods:\n * - if basic auth is offered, sends {@link LLPlClient.loginBasic};\n * - if only token auth is offered, treats `password` as an opaque bearer token and\n * sends {@link LLPlClient.loginWithToken} (so deployments configured for static-token\n * auth still log in without the caller switching methods).\n *\n * On legacy backends (no auth:v2) it falls through to GetJWTToken with the Basic header,\n * preserving original behavior. */\n public async login(user: string, password: string): Promise<AuthInformation> {\n try {\n let token: string;\n if (this.ll.hasCapability(\"auth:v2\")) {\n const schemes = this.supportedAuthSchemes;\n if (schemes.basic) {\n token = await this.ll.loginBasic(user, password);\n } else if (schemes.token) {\n token = await this.ll.loginWithToken(password);\n } else {\n throw new Error(\"backend advertises no supported authentication methods\");\n }\n } else {\n token = await this.ll.getJwtToken(BigInt(this.ll.conf.authTTLSeconds), {\n authorization: \"Basic \" + Buffer.from(user + \":\" + password).toString(\"base64\"),\n });\n }\n const jwtToken = notEmpty(token);\n if (jwtToken === \"\") throw new Error(\"empty token\");\n return { jwtToken };\n } catch (e: any) {\n if (e.code === \"UNAUTHENTICATED\") throw new UnauthenticatedError(e.message);\n throw new Error(e);\n }\n }\n}\n"],"mappings":";;;;;AAWA,IAAa,0BAAb,MAAa,wBAAwB;CACnC;CAEA,YAAoB,IAAgB;AAClC,OAAK,KAAK;;CAGZ,aAAoB,MAClB,iBACA,KACkC;AAElC,SAAO,IAAI,wBADA,MAAM,WAAW,MAAM,iBAAiB,IAAI,CACjB;;CAGxC,MAAa,OAA8C;AACzD,SAAO,MAAM,KAAK,GAAG,MAAM;;CAG7B,MAAa,cAAqD;AAChE,SAAO,MAAM,KAAK,GAAG,aAAa;;CAGpC,MAAa,cAAgC;AAC3C,UAAQ,MAAM,KAAK,aAAa,EAAE,QAAQ,SAAS;;CAGrD,cAAqB,YAAwC;AAC3D,SAAO,KAAK,GAAG,cAAc,WAAW;;;;;CAM1C,IAAW,uBAA2D;EACpE,MAAM,SAAS;GAAE,OAAO;GAAO,OAAO;GAAO;AAC7C,OAAK,MAAM,KAAK,KAAK,GAAG,gBAAgB,QACtC,KAAI,EAAE,OAAO,cAAc,QAAS,QAAO,QAAQ;WAC1C,EAAE,OAAO,cAAc,QAAS,QAAO,QAAQ;AAE1D,SAAO;;;;;;;;;;;;CAaT,MAAa,MAAM,MAAc,UAA4C;AAC3E,MAAI;GACF,IAAI;AACJ,OAAI,KAAK,GAAG,cAAc,UAAU,EAAE;IACpC,MAAM,UAAU,KAAK;AACrB,QAAI,QAAQ,MACV,SAAQ,MAAM,KAAK,GAAG,WAAW,MAAM,SAAS;aACvC,QAAQ,MACjB,SAAQ,MAAM,KAAK,GAAG,eAAe,SAAS;QAE9C,OAAM,IAAI,MAAM,yDAAyD;SAG3E,SAAQ,MAAM,KAAK,GAAG,YAAY,OAAO,KAAK,GAAG,KAAK,eAAe,EAAE,EACrE,eAAe,WAAW,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC,SAAS,SAAS,EAChF,CAAC;GAEJ,MAAM,WAAW,SAAS,MAAM;AAChC,OAAI,aAAa,GAAI,OAAM,IAAI,MAAM,cAAc;AACnD,UAAO,EAAE,UAAU;WACZ,GAAQ;AACf,OAAI,EAAE,SAAS,kBAAmB,OAAM,IAAI,qBAAqB,EAAE,QAAQ;AAC3E,SAAM,IAAI,MAAM,EAAE"}
1
+ {"version":3,"file":"unauth_client.js","names":[],"sources":["../../src/core/unauth_client.ts"],"sourcesContent":["import type { AuthInformation, PlClientConfig } from \"./config\";\nimport {\n type AuthAPI_ListMethods_Response,\n type MaintenanceAPI_Ping_Response,\n AuthAPI_ListMethods_SSOAuthMethod_FlowType,\n} from \"../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api\";\nimport { LLPlClient } from \"./ll_client\";\nimport { type MiLogger, notEmpty } from \"@milaboratories/ts-helpers\";\nimport { UnauthenticatedError } from \"./errors\";\nimport type { BackendCapability } from \"./capabilities\";\n\n/** Login-flow shape advertised by the backend for a given SSO method. Extension\n * point: future flow variants (e.g. backend-driven) sit alongside `public_pkce`. */\nexport type SSOFlowType = \"public_pkce\";\n\n/** Typed projection of `AuthAPI.ListMethods.SSOAuthMethod` for desktop consumption.\n * Carries everything needed to drive the PKCE flow + diagnostics fields the renderer\n * may surface (`userIdClaim`, `groupsClaim`). */\nexport type SSOAuthMethod = {\n id: string;\n description: string;\n issuer: string;\n clientId: string;\n scopes: string;\n resource: string;\n prompt: string;\n redirectPorts: number[];\n subjectTokenSource: string;\n userIdClaim: string;\n groupsClaim: string;\n flowType: SSOFlowType;\n};\n\n/** Server-issued material returned by {@link UnauthenticatedPlClient.beginSSOLogin}.\n * Discriminated on `flow`; future flow variants extend the union. */\nexport type SSOLoginAttempt = {\n flow: \"public_pkce\";\n nonce: string;\n expiresAt: Date;\n};\n\n/** Primarily used for initial authentication (login) */\nexport class UnauthenticatedPlClient {\n public readonly ll: LLPlClient;\n\n private constructor(ll: LLPlClient) {\n this.ll = ll;\n }\n\n public static async build(\n configOrAddress: PlClientConfig | string,\n ops?: { logger?: MiLogger },\n ): Promise<UnauthenticatedPlClient> {\n const ll = await LLPlClient.build(configOrAddress, ops);\n return new UnauthenticatedPlClient(ll);\n }\n\n public async ping(): Promise<MaintenanceAPI_Ping_Response> {\n return await this.ll.ping();\n }\n\n public async authMethods(): Promise<AuthAPI_ListMethods_Response> {\n return await this.ll.authMethods();\n }\n\n public async requireAuth(): Promise<boolean> {\n return (await this.authMethods()).methods.length > 0;\n }\n\n public hasCapability(capability: BackendCapability): boolean {\n return this.ll.hasCapability(capability);\n }\n\n /** Classifies the advertised authentication methods by credential scheme.\n * On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}\n * which uses the legacy GetJWTToken path. SSO is surfaced via {@link ssoConfig}, not here. */\n public get supportedAuthSchemes(): { basic: boolean; token: boolean } {\n const result = { basic: false, token: false };\n for (const m of this.ll.authMethodsSync.methods) {\n if (m.method.oneofKind === \"basic\") result.basic = true;\n else if (m.method.oneofKind === \"token\") result.token = true;\n }\n return result;\n }\n\n /** Projection of the first advertised SSO method, derived from {@link authMethodsSync}.\n * v1: at most one SSO method per deployment, so callers do not need to discriminate. */\n public ssoConfig(): SSOAuthMethod | undefined {\n for (const method of this.ll.authMethodsSync.methods) {\n if (method.method.oneofKind !== \"sso\") continue;\n const sso = method.method.sso;\n if (sso.flowType !== AuthAPI_ListMethods_SSOAuthMethod_FlowType.PUBLIC_PKCE) {\n throw new Error(`ssoConfig: unsupported SSO flow type ${sso.flowType}`);\n }\n return {\n id: method.id,\n description: method.description,\n issuer: sso.issuer,\n clientId: sso.clientId,\n scopes: sso.scopes,\n resource: sso.resource,\n prompt: sso.prompt,\n redirectPorts: sso.redirectPorts,\n subjectTokenSource: sso.subjectTokenSource,\n userIdClaim: sso.userIdClaim,\n groupsClaim: sso.groupsClaim,\n flowType: \"public_pkce\",\n };\n }\n return undefined;\n }\n\n /** Login with username+password.\n *\n * On auth:v2 backends the client inspects the advertised AuthMethods:\n * - if basic auth is offered, sends {@link LLPlClient.loginBasic};\n * - if only token auth is offered, treats `password` as an opaque bearer token and\n * sends {@link LLPlClient.loginWithToken} (so deployments configured for static-token\n * auth still log in without the caller switching methods).\n *\n * On legacy backends (no auth:v2) it falls through to GetJWTToken with the Basic header,\n * preserving original behavior. */\n public async login(user: string, password: string): Promise<AuthInformation> {\n try {\n let token: string;\n if (this.ll.hasCapability(\"auth:v2\")) {\n const schemes = this.supportedAuthSchemes;\n if (schemes.basic) {\n token = await this.ll.loginBasic(user, password);\n } else if (schemes.token) {\n token = await this.ll.loginWithToken(password);\n } else {\n throw new Error(\"backend advertises no supported authentication methods\");\n }\n } else {\n token = await this.ll.getJwtToken(BigInt(this.ll.conf.authTTLSeconds), {\n authorization: \"Basic \" + Buffer.from(user + \":\" + password).toString(\"base64\"),\n });\n }\n const jwtToken = notEmpty(token);\n if (jwtToken === \"\") throw new Error(\"empty token\");\n return { jwtToken };\n } catch (e: any) {\n if (e.code === \"UNAUTHENTICATED\") throw new UnauthenticatedError(e.message);\n throw new Error(e);\n }\n }\n\n /** Request fresh server-issued login material. v1 only emits the public-PKCE flow;\n * desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */\n public async beginSSOLogin(): Promise<SSOLoginAttempt> {\n const { nonce, expiresAt } = await this.ll.beginSSOLogin();\n return { flow: \"public_pkce\", nonce, expiresAt };\n }\n\n /** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */\n public async loginSSO(payload: { tokenResponse: Uint8Array }): Promise<AuthInformation> {\n try {\n const jwtToken = await this.ll.loginSSO(payload.tokenResponse);\n if (jwtToken === \"\") throw new Error(\"empty token\");\n return { jwtToken };\n } catch (e: any) {\n if (e.code === \"UNAUTHENTICATED\") throw new UnauthenticatedError(e.message);\n throw new Error(e);\n }\n }\n}\n"],"mappings":";;;;;;AA0CA,IAAa,0BAAb,MAAa,wBAAwB;CACnC;CAEA,YAAoB,IAAgB;AAClC,OAAK,KAAK;;CAGZ,aAAoB,MAClB,iBACA,KACkC;AAElC,SAAO,IAAI,wBADA,MAAM,WAAW,MAAM,iBAAiB,IAAI,CACjB;;CAGxC,MAAa,OAA8C;AACzD,SAAO,MAAM,KAAK,GAAG,MAAM;;CAG7B,MAAa,cAAqD;AAChE,SAAO,MAAM,KAAK,GAAG,aAAa;;CAGpC,MAAa,cAAgC;AAC3C,UAAQ,MAAM,KAAK,aAAa,EAAE,QAAQ,SAAS;;CAGrD,cAAqB,YAAwC;AAC3D,SAAO,KAAK,GAAG,cAAc,WAAW;;;;;CAM1C,IAAW,uBAA2D;EACpE,MAAM,SAAS;GAAE,OAAO;GAAO,OAAO;GAAO;AAC7C,OAAK,MAAM,KAAK,KAAK,GAAG,gBAAgB,QACtC,KAAI,EAAE,OAAO,cAAc,QAAS,QAAO,QAAQ;WAC1C,EAAE,OAAO,cAAc,QAAS,QAAO,QAAQ;AAE1D,SAAO;;;;CAKT,YAA8C;AAC5C,OAAK,MAAM,UAAU,KAAK,GAAG,gBAAgB,SAAS;AACpD,OAAI,OAAO,OAAO,cAAc,MAAO;GACvC,MAAM,MAAM,OAAO,OAAO;AAC1B,OAAI,IAAI,aAAa,2CAA2C,YAC9D,OAAM,IAAI,MAAM,wCAAwC,IAAI,WAAW;AAEzE,UAAO;IACL,IAAI,OAAO;IACX,aAAa,OAAO;IACpB,QAAQ,IAAI;IACZ,UAAU,IAAI;IACd,QAAQ,IAAI;IACZ,UAAU,IAAI;IACd,QAAQ,IAAI;IACZ,eAAe,IAAI;IACnB,oBAAoB,IAAI;IACxB,aAAa,IAAI;IACjB,aAAa,IAAI;IACjB,UAAU;IACX;;;;;;;;;;;;;CAeL,MAAa,MAAM,MAAc,UAA4C;AAC3E,MAAI;GACF,IAAI;AACJ,OAAI,KAAK,GAAG,cAAc,UAAU,EAAE;IACpC,MAAM,UAAU,KAAK;AACrB,QAAI,QAAQ,MACV,SAAQ,MAAM,KAAK,GAAG,WAAW,MAAM,SAAS;aACvC,QAAQ,MACjB,SAAQ,MAAM,KAAK,GAAG,eAAe,SAAS;QAE9C,OAAM,IAAI,MAAM,yDAAyD;SAG3E,SAAQ,MAAM,KAAK,GAAG,YAAY,OAAO,KAAK,GAAG,KAAK,eAAe,EAAE,EACrE,eAAe,WAAW,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC,SAAS,SAAS,EAChF,CAAC;GAEJ,MAAM,WAAW,SAAS,MAAM;AAChC,OAAI,aAAa,GAAI,OAAM,IAAI,MAAM,cAAc;AACnD,UAAO,EAAE,UAAU;WACZ,GAAQ;AACf,OAAI,EAAE,SAAS,kBAAmB,OAAM,IAAI,qBAAqB,EAAE,QAAQ;AAC3E,SAAM,IAAI,MAAM,EAAE;;;;;CAMtB,MAAa,gBAA0C;EACrD,MAAM,EAAE,OAAO,cAAc,MAAM,KAAK,GAAG,eAAe;AAC1D,SAAO;GAAE,MAAM;GAAe;GAAO;GAAW;;;CAIlD,MAAa,SAAS,SAAkE;AACtF,MAAI;GACF,MAAM,WAAW,MAAM,KAAK,GAAG,SAAS,QAAQ,cAAc;AAC9D,OAAI,aAAa,GAAI,OAAM,IAAI,MAAM,cAAc;AACnD,UAAO,EAAE,UAAU;WACZ,GAAQ;AACf,OAAI,EAAE,SAAS,kBAAmB,OAAM,IAAI,qBAAqB,EAAE,QAAQ;AAC3E,SAAM,IAAI,MAAM,EAAE"}
package/dist/index.d.ts CHANGED
@@ -12,10 +12,10 @@ import { StorageInfo, TxRunner, UserResources } from "./core/user_resources.js";
12
12
  import { PlClient, TxOps } from "./core/client.js";
13
13
  import { DisconnectedError, PermissionDeniedError, PlError, PlErrorCodeNotFound, RESTError, RecoverablePlError, UnauthenticatedError, UnrecoverablePlError, isAbortedError, isCancelError, isConnectionProblem, isNotFoundError, isPermissionDenied, isTimeoutError, isTimeoutOrCancelError, isUnauthenticated, isUnimplementedError, rethrowMeaningfulError, throwPlNotFoundError } from "./core/errors.js";
14
14
  import { defaultPlClient, tryGetFileConfig } from "./core/default_client.js";
15
- import { UnauthenticatedPlClient } from "./core/unauth_client.js";
15
+ import { SSOAuthMethod, SSOFlowType, SSOLoginAttempt, UnauthenticatedPlClient } from "./core/unauth_client.js";
16
16
  import { expirationFromAuthInformation, inferAuthRefreshTime } from "./core/auth.js";
17
17
  import { Filter, Property, treeFilter } from "./core/tree_filter.js";
18
18
  import { ValErr, valErr } from "./helpers/tx_helpers.js";
19
19
  import { ContinuePolling, DefaultPollingRetryOptions, PollFieldTraverseOps, PollResourceAccessor, PollTxAccessor, poll } from "./helpers/poll.js";
20
20
  import { test_config_d_exports } from "./test/test_config.js";
21
- export { AnonymousAuthInformation, AnyFieldId, AnyFieldRef, AnyRef, AnyResourceId, AnyResourceRef, AuthInformation, AuthOps, BackendCapability, BasicResourceData, ColorProof, ContinuePolling, DEFAULT_AUTH_MAX_REFRESH, DEFAULT_MAX_CACHE_BYTES, DEFAULT_REQUEST_TIMEOUT, DEFAULT_RETRY_BACKOFF_ALGORITHM, DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER, DEFAULT_RETRY_INITIAL_DELAY, DEFAULT_RETRY_JITTER, DEFAULT_RETRY_LINEAR_BACKOFF_STEP, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RO_TX_TIMEOUT, DEFAULT_RW_TX_TIMEOUT, DEFAULT_TOKEN_TTL_SECONDS, DefaultFinalResourceDataPredicate, DefaultPollingRetryOptions, DefaultRetryOptions, DisconnectedError, FieldData, FieldId, FieldRef, FieldStatus, FieldType, Filter, ResourceAPI_Tree_Filter_OperatorType as FilterOperatorType, ResourceAPI_Tree_Filter_Property as FilterProperty, FinalResourceDataPredicate, FutureFieldType, GlobalResourceId, GrpcConnection, KeyValue, KeyValueString, LocalFieldId, LocalResourceId, MaxLocalId, MaxTxId, NullResourceId, NullSignedResourceId, OptionalAnyResourceId, OptionalSignedResourceId, PermissionDeniedError, pl_d_exports as Pl, PlClient, PlClientConfig, PlConnectionStatus, PlConnectionStatusListener, PlDriver, PlDriverDefinition, PlError, PlErrorCodeNotFound, PlTransaction, PollFieldTraverseOps, PollResourceAccessor, PollTxAccessor, Property, RESTError, RecoverablePlError, ResourceAPI_Tree_Filter, ResourceData, ResourceIdWithSignature, ResourceKind, ResourceRef, ResourceSignature, ResourceTreeFrame, ResourceTreeItem, ResourceType, ResourceTypeName, ResourceTypePrefix, index_d_exports as RestAPI, RestConnection, SUPPORTED_WIRE_PROTOCOLS, SignedResourceId, StorageInfo, test_config_d_exports as TestHelpers, TxCommitConflict, TxOps, TxRunner, UnauthenticatedError, UnauthenticatedPlClient, UnrecoverablePlError, UserResources, ValErr, WireClientProvider, WireClientProviderFactory, WireConnection, addRTypeToMetadata, anyResourceIdToBigint, asSignedResourceId, base64UrlToSignature, checkLocalityOfResourceId, createGlobalResourceId, createLocalResourceId, createRTypeRoutingHeader, createSignedResourceId, defaultPlClient, ensureSignedResourceIdNotNull, expirationFromAuthInformation, extractBasicResourceData, extractTxId, field, getField, hasCapability, inferAuthRefreshTime, isAbortedError, isAnyResourceId, isCancelError, isConnectionProblem, isField, isFieldRef, isLocalResourceId, isNotFoundError, isNotNullSignedResourceId, isNullSignedResourceId, isPermissionDenied, isResource, isResourceId, isResourceRef, isRootResourceId, isSignedResourceId, isTimeoutError, isTimeoutOrCancelError, isUnauthenticated, isUnimplementedError, jsonToData, parseResourceType, parseSignedResourceId, plAddressToConfig, poll, resDataToJson, resourceIdFromString, resourceIdToString, resourceType, resourceTypeToString, resourceTypesEqual, rethrowMeaningfulError, setResourceSignaturesRequired, signatureToBase64Url, stringifyWithResourceId, throwPlNotFoundError, toFieldId, toGlobalFieldId, toGlobalResourceId, toResourceSignature, treeFilter, tryGetFileConfig, valErr, wireProtocol };
21
+ export { AnonymousAuthInformation, AnyFieldId, AnyFieldRef, AnyRef, AnyResourceId, AnyResourceRef, AuthInformation, AuthOps, BackendCapability, BasicResourceData, ColorProof, ContinuePolling, DEFAULT_AUTH_MAX_REFRESH, DEFAULT_MAX_CACHE_BYTES, DEFAULT_REQUEST_TIMEOUT, DEFAULT_RETRY_BACKOFF_ALGORITHM, DEFAULT_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER, DEFAULT_RETRY_INITIAL_DELAY, DEFAULT_RETRY_JITTER, DEFAULT_RETRY_LINEAR_BACKOFF_STEP, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RO_TX_TIMEOUT, DEFAULT_RW_TX_TIMEOUT, DEFAULT_TOKEN_TTL_SECONDS, DefaultFinalResourceDataPredicate, DefaultPollingRetryOptions, DefaultRetryOptions, DisconnectedError, FieldData, FieldId, FieldRef, FieldStatus, FieldType, Filter, ResourceAPI_Tree_Filter_OperatorType as FilterOperatorType, ResourceAPI_Tree_Filter_Property as FilterProperty, FinalResourceDataPredicate, FutureFieldType, GlobalResourceId, GrpcConnection, KeyValue, KeyValueString, LocalFieldId, LocalResourceId, MaxLocalId, MaxTxId, NullResourceId, NullSignedResourceId, OptionalAnyResourceId, OptionalSignedResourceId, PermissionDeniedError, pl_d_exports as Pl, PlClient, PlClientConfig, PlConnectionStatus, PlConnectionStatusListener, PlDriver, PlDriverDefinition, PlError, PlErrorCodeNotFound, PlTransaction, PollFieldTraverseOps, PollResourceAccessor, PollTxAccessor, Property, RESTError, RecoverablePlError, ResourceAPI_Tree_Filter, ResourceData, ResourceIdWithSignature, ResourceKind, ResourceRef, ResourceSignature, ResourceTreeFrame, ResourceTreeItem, ResourceType, ResourceTypeName, ResourceTypePrefix, index_d_exports as RestAPI, RestConnection, SSOAuthMethod, SSOFlowType, SSOLoginAttempt, SUPPORTED_WIRE_PROTOCOLS, SignedResourceId, StorageInfo, test_config_d_exports as TestHelpers, TxCommitConflict, TxOps, TxRunner, UnauthenticatedError, UnauthenticatedPlClient, UnrecoverablePlError, UserResources, ValErr, WireClientProvider, WireClientProviderFactory, WireConnection, addRTypeToMetadata, anyResourceIdToBigint, asSignedResourceId, base64UrlToSignature, checkLocalityOfResourceId, createGlobalResourceId, createLocalResourceId, createRTypeRoutingHeader, createSignedResourceId, defaultPlClient, ensureSignedResourceIdNotNull, expirationFromAuthInformation, extractBasicResourceData, extractTxId, field, getField, hasCapability, inferAuthRefreshTime, isAbortedError, isAnyResourceId, isCancelError, isConnectionProblem, isField, isFieldRef, isLocalResourceId, isNotFoundError, isNotNullSignedResourceId, isNullSignedResourceId, isPermissionDenied, isResource, isResourceId, isResourceRef, isRootResourceId, isSignedResourceId, isTimeoutError, isTimeoutOrCancelError, isUnauthenticated, isUnimplementedError, jsonToData, parseResourceType, parseSignedResourceId, plAddressToConfig, poll, resDataToJson, resourceIdFromString, resourceIdToString, resourceType, resourceTypeToString, resourceTypesEqual, rethrowMeaningfulError, setResourceSignaturesRequired, signatureToBase64Url, stringifyWithResourceId, throwPlNotFoundError, toFieldId, toGlobalFieldId, toGlobalResourceId, toResourceSignature, treeFilter, tryGetFileConfig, valErr, wireProtocol };
@@ -16109,6 +16109,7 @@ const Platform = new _protobuf_ts_runtime_rpc.ServiceType("MiLaboratories.PL.API
16109
16109
  }
16110
16110
  ]);
16111
16111
  //#endregion
16112
+ exports.AuthAPI_ListMethods_SSOAuthMethod_FlowType = AuthAPI_ListMethods_SSOAuthMethod_FlowType;
16112
16113
  exports.AuthAPI_Role = AuthAPI_Role;
16113
16114
  exports.MaintenanceAPI_Ping_Response_Compression = MaintenanceAPI_Ping_Response_Compression;
16114
16115
  exports.Platform = Platform;
@@ -16108,6 +16108,6 @@ const Platform = new ServiceType("MiLaboratories.PL.API.Platform", [
16108
16108
  }
16109
16109
  ]);
16110
16110
  //#endregion
16111
- export { AuthAPI_Role, MaintenanceAPI_Ping_Response_Compression, Platform, ResourceAPI_Tree_Filter_OperatorType, ResourceAPI_Tree_Filter_Property, TxAPI_ClientMessage, TxAPI_Open_Request_WritableTx, TxAPI_ServerMessage };
16111
+ export { AuthAPI_ListMethods_SSOAuthMethod_FlowType, AuthAPI_Role, MaintenanceAPI_Ping_Response_Compression, Platform, ResourceAPI_Tree_Filter_OperatorType, ResourceAPI_Tree_Filter_Property, TxAPI_ClientMessage, TxAPI_Open_Request_WritableTx, TxAPI_ServerMessage };
16112
16112
 
16113
16113
  //# sourceMappingURL=api.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-client",
3
- "version": "3.10.2",
3
+ "version": "3.11.0",
4
4
  "description": "New TS/JS client for Platform API",
5
5
  "files": [
6
6
  "./dist/**/*",
@@ -30,8 +30,8 @@
30
30
  "undici": "~7.16.0",
31
31
  "utility-types": "^3.11.0",
32
32
  "yaml": "^2.8.0",
33
- "@milaboratories/pl-model-common": "1.44.0",
34
33
  "@milaboratories/pl-http": "1.2.4",
34
+ "@milaboratories/pl-model-common": "1.44.0",
35
35
  "@milaboratories/ts-helpers": "1.8.2"
36
36
  },
37
37
  "devDependencies": {
@@ -41,8 +41,8 @@
41
41
  "openapi-typescript": "^7.10.0",
42
42
  "typescript": "~5.9.3",
43
43
  "vitest": "^4.1.3",
44
- "@milaboratories/ts-builder": "1.5.0",
45
44
  "@milaboratories/build-configs": "2.0.0",
45
+ "@milaboratories/ts-builder": "1.5.0",
46
46
  "@milaboratories/ts-configs": "1.2.3"
47
47
  },
48
48
  "engines": {
@@ -43,6 +43,7 @@ import {
43
43
  } from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api";
44
44
  import type { MiLogger } from "@milaboratories/ts-helpers";
45
45
  import { isAbortedError } from "./errors";
46
+ import { Timestamp } from "../proto-grpc/google/protobuf/timestamp";
46
47
 
47
48
  export interface PlCallOps {
48
49
  timeout?: number;
@@ -580,7 +581,6 @@ export class LLPlClient implements WireClientProviderFactory {
580
581
  basic: { login: user, password },
581
582
  expiration: `${ttl}s`,
582
583
  requestedRole: role,
583
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
584
584
  } as any,
585
585
  });
586
586
  return notEmpty((await resp).data, "REST: empty response for login request").token;
@@ -617,13 +617,60 @@ export class LLPlClient implements WireClientProviderFactory {
617
617
  token: { token: Buffer.from(bytes).toString("base64") },
618
618
  expiration: `${ttl}s`,
619
619
  requestedRole: role,
620
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
621
620
  } as any,
622
621
  });
623
622
  return notEmpty((await resp).data, "REST: empty response for login request").token;
624
623
  }
625
624
  }
626
625
 
626
+ /** Login via SSO: forwards the raw IdP /token JSON body to the backend.
627
+ * Backend validates signature + nonce + audience and mints a Platforma JWT. */
628
+ public async loginSSO(tokenResponse: Uint8Array): Promise<string> {
629
+ const cl = this.clientProvider.get();
630
+ if (cl instanceof GrpcPlApiClient) {
631
+ return (
632
+ await cl.login({
633
+ credentials: {
634
+ oneofKind: "sso",
635
+ sso: { tokenResponse },
636
+ },
637
+ }).response
638
+ ).token;
639
+ } else {
640
+ const resp = cl.POST("/v1/auth/login", {
641
+ body: {
642
+ sso: { tokenResponse: Buffer.from(tokenResponse).toString("base64") },
643
+ } as any,
644
+ });
645
+ return notEmpty((await resp).data, "REST: empty response for SSO login request").token;
646
+ }
647
+ }
648
+
649
+ /** Request a one-time PKCE nonce + its expiry from the backend. The desktop is
650
+ * expected to place the nonce verbatim into the OIDC auth-request before
651
+ * redirecting to the IdP; the backend enforces the expiry on {@link loginSSO}. */
652
+ public async beginSSOLogin(): Promise<{ nonce: string; expiresAt: Date }> {
653
+ const cl = this.clientProvider.get();
654
+ if (cl instanceof GrpcPlApiClient) {
655
+ const resp = (await cl.beginSSOLogin({}).response).flow;
656
+ if (resp.oneofKind !== "publicPkce") {
657
+ throw new Error(`beginSSOLogin: unsupported flow ${resp.oneofKind}`);
658
+ }
659
+ const exp = notEmpty(resp.publicPkce.expiresAt, "beginSSOLogin: missing expiresAt");
660
+ return {
661
+ nonce: resp.publicPkce.nonce,
662
+ expiresAt: Timestamp.toDate(exp),
663
+ };
664
+ } else {
665
+ const wsResponse = notEmpty(
666
+ (await cl.POST("/v1/auth/sso/begin-login", { body: {} })).data,
667
+ "REST: empty response for beginSSOLogin request",
668
+ );
669
+ const pkce = notEmpty(wsResponse.publicPkce, "REST: missing publicPkce in beginSSOLogin");
670
+ return { nonce: pkce.nonce, expiresAt: new Date(pkce.expiresAt) };
671
+ }
672
+ }
673
+
627
674
  /** Refresh the current JWT, preserving session id and role. */
628
675
  public async refreshToken(opts: { ttlSeconds?: bigint } = {}): Promise<string> {
629
676
  const cl = this.clientProvider.get();
@@ -662,7 +709,6 @@ export class LLPlClient implements WireClientProviderFactory {
662
709
  );
663
710
  resp = {
664
711
  ...(pingData as unknown as grpcTypes.MaintenanceAPI_Ping_Response),
665
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
666
712
  capabilities: (pingData as any).capabilities ?? [],
667
713
  };
668
714
  }
@@ -1,13 +1,44 @@
1
1
  import type { AuthInformation, PlClientConfig } from "./config";
2
- import type {
3
- AuthAPI_ListMethods_Response,
4
- MaintenanceAPI_Ping_Response,
2
+ import {
3
+ type AuthAPI_ListMethods_Response,
4
+ type MaintenanceAPI_Ping_Response,
5
+ AuthAPI_ListMethods_SSOAuthMethod_FlowType,
5
6
  } from "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api";
6
7
  import { LLPlClient } from "./ll_client";
7
8
  import { type MiLogger, notEmpty } from "@milaboratories/ts-helpers";
8
9
  import { UnauthenticatedError } from "./errors";
9
10
  import type { BackendCapability } from "./capabilities";
10
11
 
12
+ /** Login-flow shape advertised by the backend for a given SSO method. Extension
13
+ * point: future flow variants (e.g. backend-driven) sit alongside `public_pkce`. */
14
+ export type SSOFlowType = "public_pkce";
15
+
16
+ /** Typed projection of `AuthAPI.ListMethods.SSOAuthMethod` for desktop consumption.
17
+ * Carries everything needed to drive the PKCE flow + diagnostics fields the renderer
18
+ * may surface (`userIdClaim`, `groupsClaim`). */
19
+ export type SSOAuthMethod = {
20
+ id: string;
21
+ description: string;
22
+ issuer: string;
23
+ clientId: string;
24
+ scopes: string;
25
+ resource: string;
26
+ prompt: string;
27
+ redirectPorts: number[];
28
+ subjectTokenSource: string;
29
+ userIdClaim: string;
30
+ groupsClaim: string;
31
+ flowType: SSOFlowType;
32
+ };
33
+
34
+ /** Server-issued material returned by {@link UnauthenticatedPlClient.beginSSOLogin}.
35
+ * Discriminated on `flow`; future flow variants extend the union. */
36
+ export type SSOLoginAttempt = {
37
+ flow: "public_pkce";
38
+ nonce: string;
39
+ expiresAt: Date;
40
+ };
41
+
11
42
  /** Primarily used for initial authentication (login) */
12
43
  export class UnauthenticatedPlClient {
13
44
  public readonly ll: LLPlClient;
@@ -42,7 +73,7 @@ export class UnauthenticatedPlClient {
42
73
 
43
74
  /** Classifies the advertised authentication methods by credential scheme.
44
75
  * On legacy backends (no auth:v2) the typed oneof is empty; callers fall through to {@link login}
45
- * which uses the legacy GetJWTToken path. */
76
+ * which uses the legacy GetJWTToken path. SSO is surfaced via {@link ssoConfig}, not here. */
46
77
  public get supportedAuthSchemes(): { basic: boolean; token: boolean } {
47
78
  const result = { basic: false, token: false };
48
79
  for (const m of this.ll.authMethodsSync.methods) {
@@ -52,6 +83,33 @@ export class UnauthenticatedPlClient {
52
83
  return result;
53
84
  }
54
85
 
86
+ /** Projection of the first advertised SSO method, derived from {@link authMethodsSync}.
87
+ * v1: at most one SSO method per deployment, so callers do not need to discriminate. */
88
+ public ssoConfig(): SSOAuthMethod | undefined {
89
+ for (const method of this.ll.authMethodsSync.methods) {
90
+ if (method.method.oneofKind !== "sso") continue;
91
+ const sso = method.method.sso;
92
+ if (sso.flowType !== AuthAPI_ListMethods_SSOAuthMethod_FlowType.PUBLIC_PKCE) {
93
+ throw new Error(`ssoConfig: unsupported SSO flow type ${sso.flowType}`);
94
+ }
95
+ return {
96
+ id: method.id,
97
+ description: method.description,
98
+ issuer: sso.issuer,
99
+ clientId: sso.clientId,
100
+ scopes: sso.scopes,
101
+ resource: sso.resource,
102
+ prompt: sso.prompt,
103
+ redirectPorts: sso.redirectPorts,
104
+ subjectTokenSource: sso.subjectTokenSource,
105
+ userIdClaim: sso.userIdClaim,
106
+ groupsClaim: sso.groupsClaim,
107
+ flowType: "public_pkce",
108
+ };
109
+ }
110
+ return undefined;
111
+ }
112
+
55
113
  /** Login with username+password.
56
114
  *
57
115
  * On auth:v2 backends the client inspects the advertised AuthMethods:
@@ -87,4 +145,23 @@ export class UnauthenticatedPlClient {
87
145
  throw new Error(e);
88
146
  }
89
147
  }
148
+
149
+ /** Request fresh server-issued login material. v1 only emits the public-PKCE flow;
150
+ * desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */
151
+ public async beginSSOLogin(): Promise<SSOLoginAttempt> {
152
+ const { nonce, expiresAt } = await this.ll.beginSSOLogin();
153
+ return { flow: "public_pkce", nonce, expiresAt };
154
+ }
155
+
156
+ /** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */
157
+ public async loginSSO(payload: { tokenResponse: Uint8Array }): Promise<AuthInformation> {
158
+ try {
159
+ const jwtToken = await this.ll.loginSSO(payload.tokenResponse);
160
+ if (jwtToken === "") throw new Error("empty token");
161
+ return { jwtToken };
162
+ } catch (e: any) {
163
+ if (e.code === "UNAUTHENTICATED") throw new UnauthenticatedError(e.message);
164
+ throw new Error(e);
165
+ }
166
+ }
90
167
  }