@milaboratories/pl-client 3.11.5 → 3.12.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.
Files changed (193) hide show
  1. package/dist/core/PromiseTracker.cjs.map +1 -1
  2. package/dist/core/PromiseTracker.d.ts.map +1 -1
  3. package/dist/core/PromiseTracker.js.map +1 -1
  4. package/dist/core/StatefulPromise.cjs +2 -0
  5. package/dist/core/StatefulPromise.cjs.map +1 -1
  6. package/dist/core/StatefulPromise.js +2 -0
  7. package/dist/core/StatefulPromise.js.map +1 -1
  8. package/dist/core/abstract_stream.d.ts +1 -1
  9. package/dist/core/advisory_locks.cjs.map +1 -1
  10. package/dist/core/advisory_locks.js.map +1 -1
  11. package/dist/core/auth.cjs.map +1 -1
  12. package/dist/core/auth.d.ts.map +1 -1
  13. package/dist/core/auth.js.map +1 -1
  14. package/dist/core/cache.d.ts.map +1 -1
  15. package/dist/core/capabilities.cjs.map +1 -1
  16. package/dist/core/capabilities.d.ts.map +1 -1
  17. package/dist/core/capabilities.js.map +1 -1
  18. package/dist/core/client.cjs +3 -3
  19. package/dist/core/client.cjs.map +1 -1
  20. package/dist/core/client.d.ts.map +1 -1
  21. package/dist/core/client.js +2 -2
  22. package/dist/core/client.js.map +1 -1
  23. package/dist/core/config.cjs.map +1 -1
  24. package/dist/core/config.d.ts.map +1 -1
  25. package/dist/core/config.js.map +1 -1
  26. package/dist/core/default_client.cjs +5 -5
  27. package/dist/core/default_client.cjs.map +1 -1
  28. package/dist/core/default_client.d.ts.map +1 -1
  29. package/dist/core/default_client.js.map +1 -1
  30. package/dist/core/driver.cjs.map +1 -1
  31. package/dist/core/driver.d.ts.map +1 -1
  32. package/dist/core/driver.js.map +1 -1
  33. package/dist/core/error_resource.cjs.map +1 -1
  34. package/dist/core/error_resource.js.map +1 -1
  35. package/dist/core/errors.cjs +10 -11
  36. package/dist/core/errors.cjs.map +1 -1
  37. package/dist/core/errors.d.ts.map +1 -1
  38. package/dist/core/errors.js +10 -10
  39. package/dist/core/errors.js.map +1 -1
  40. package/dist/core/final.cjs +2 -1
  41. package/dist/core/final.cjs.map +1 -1
  42. package/dist/core/final.d.ts.map +1 -1
  43. package/dist/core/final.js.map +1 -1
  44. package/dist/core/ll_client.cjs +14 -9
  45. package/dist/core/ll_client.cjs.map +1 -1
  46. package/dist/core/ll_client.d.ts +1 -0
  47. package/dist/core/ll_client.d.ts.map +1 -1
  48. package/dist/core/ll_client.js +15 -9
  49. package/dist/core/ll_client.js.map +1 -1
  50. package/dist/core/ll_transaction.cjs +3 -1
  51. package/dist/core/ll_transaction.cjs.map +1 -1
  52. package/dist/core/ll_transaction.d.ts +1 -1
  53. package/dist/core/ll_transaction.d.ts.map +1 -1
  54. package/dist/core/ll_transaction.js +2 -1
  55. package/dist/core/ll_transaction.js.map +1 -1
  56. package/dist/core/stat.cjs.map +1 -1
  57. package/dist/core/stat.js.map +1 -1
  58. package/dist/core/transaction.cjs +11 -5
  59. package/dist/core/transaction.cjs.map +1 -1
  60. package/dist/core/transaction.d.ts.map +1 -1
  61. package/dist/core/transaction.js +11 -4
  62. package/dist/core/transaction.js.map +1 -1
  63. package/dist/core/tree_filter.cjs +73 -18
  64. package/dist/core/tree_filter.cjs.map +1 -1
  65. package/dist/core/tree_filter.d.ts +1 -1
  66. package/dist/core/tree_filter.d.ts.map +1 -1
  67. package/dist/core/tree_filter.js +72 -19
  68. package/dist/core/tree_filter.js.map +1 -1
  69. package/dist/core/type_conversion.cjs +19 -20
  70. package/dist/core/type_conversion.cjs.map +1 -1
  71. package/dist/core/type_conversion.js +19 -19
  72. package/dist/core/type_conversion.js.map +1 -1
  73. package/dist/core/types.cjs +2 -3
  74. package/dist/core/types.cjs.map +1 -1
  75. package/dist/core/types.d.ts.map +1 -1
  76. package/dist/core/types.js +2 -2
  77. package/dist/core/types.js.map +1 -1
  78. package/dist/core/unauth_client.cjs +7 -6
  79. package/dist/core/unauth_client.cjs.map +1 -1
  80. package/dist/core/unauth_client.d.ts +9 -0
  81. package/dist/core/unauth_client.d.ts.map +1 -1
  82. package/dist/core/unauth_client.js +7 -5
  83. package/dist/core/unauth_client.js.map +1 -1
  84. package/dist/core/user_resources.cjs +3 -1
  85. package/dist/core/user_resources.cjs.map +1 -1
  86. package/dist/core/user_resources.d.ts.map +1 -1
  87. package/dist/core/user_resources.js +3 -0
  88. package/dist/core/user_resources.js.map +1 -1
  89. package/dist/core/websocket_stream.cjs +17 -20
  90. package/dist/core/websocket_stream.cjs.map +1 -1
  91. package/dist/core/websocket_stream.js +16 -19
  92. package/dist/core/websocket_stream.js.map +1 -1
  93. package/dist/core/wire.d.ts.map +1 -1
  94. package/dist/helpers/pl.cjs +25 -0
  95. package/dist/helpers/pl.cjs.map +1 -1
  96. package/dist/helpers/pl.d.ts +1 -1
  97. package/dist/helpers/pl.d.ts.map +1 -1
  98. package/dist/helpers/pl.js +1 -1
  99. package/dist/helpers/pl.js.map +1 -1
  100. package/dist/helpers/poll.cjs +5 -1
  101. package/dist/helpers/poll.cjs.map +1 -1
  102. package/dist/helpers/poll.d.ts.map +1 -1
  103. package/dist/helpers/poll.js +4 -0
  104. package/dist/helpers/poll.js.map +1 -1
  105. package/dist/helpers/retry_strategy.cjs +1 -0
  106. package/dist/helpers/retry_strategy.cjs.map +1 -1
  107. package/dist/helpers/retry_strategy.js +1 -1
  108. package/dist/helpers/retry_strategy.js.map +1 -1
  109. package/dist/helpers/tx_helpers.cjs +0 -1
  110. package/dist/helpers/tx_helpers.cjs.map +1 -1
  111. package/dist/helpers/tx_helpers.d.ts.map +1 -1
  112. package/dist/helpers/tx_helpers.js.map +1 -1
  113. package/dist/index.cjs +0 -1
  114. package/dist/index.d.ts +1 -1
  115. package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs +0 -1
  116. package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs.map +1 -1
  117. package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.d.ts.map +1 -1
  118. package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.js.map +1 -1
  119. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +531 -77
  120. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
  121. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs +11 -4
  122. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs.map +1 -1
  123. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts +12 -6
  124. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts.map +1 -1
  125. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js +11 -3
  126. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js.map +1 -1
  127. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +106 -1
  128. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
  129. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +330 -77
  130. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
  131. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs +23 -7
  132. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs.map +1 -1
  133. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts +10 -3
  134. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts.map +1 -1
  135. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js +18 -7
  136. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js.map +1 -1
  137. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs +1 -2
  138. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs.map +1 -1
  139. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.d.ts.map +1 -1
  140. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js +1 -1
  141. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js.map +1 -1
  142. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.cjs +2 -2
  143. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.cjs.map +1 -1
  144. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts +1 -1
  145. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts.map +1 -1
  146. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.js +2 -2
  147. package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.js.map +1 -1
  148. package/dist/proto-grpc/google/protobuf/any.cjs +1 -2
  149. package/dist/proto-grpc/google/protobuf/any.cjs.map +1 -1
  150. package/dist/proto-grpc/google/protobuf/any.d.ts.map +1 -1
  151. package/dist/proto-grpc/google/protobuf/any.js +1 -1
  152. package/dist/proto-grpc/google/protobuf/any.js.map +1 -1
  153. package/dist/proto-grpc/google/protobuf/duration.cjs +0 -1
  154. package/dist/proto-grpc/google/protobuf/duration.cjs.map +1 -1
  155. package/dist/proto-grpc/google/protobuf/duration.d.ts.map +1 -1
  156. package/dist/proto-grpc/google/protobuf/duration.js.map +1 -1
  157. package/dist/proto-grpc/google/protobuf/timestamp.cjs +0 -1
  158. package/dist/proto-grpc/google/protobuf/timestamp.cjs.map +1 -1
  159. package/dist/proto-grpc/google/protobuf/timestamp.d.ts.map +1 -1
  160. package/dist/proto-grpc/google/protobuf/timestamp.js.map +1 -1
  161. package/dist/proto-grpc/google/rpc/code.d.ts.map +1 -1
  162. package/dist/proto-rest/index.cjs +1 -1
  163. package/dist/proto-rest/index.cjs.map +1 -1
  164. package/dist/proto-rest/index.d.ts +1 -1
  165. package/dist/proto-rest/index.d.ts.map +1 -1
  166. package/dist/proto-rest/index.js.map +1 -1
  167. package/dist/proto-rest/plapi.d.ts +30 -9
  168. package/dist/proto-rest/plapi.d.ts.map +1 -1
  169. package/dist/test/tcp-proxy.cjs +2 -2
  170. package/dist/test/tcp-proxy.cjs.map +1 -1
  171. package/dist/test/tcp-proxy.d.ts +1 -1
  172. package/dist/test/tcp-proxy.js.map +1 -1
  173. package/dist/test/test_config.cjs +8 -2
  174. package/dist/test/test_config.cjs.map +1 -1
  175. package/dist/test/test_config.d.ts +1 -1
  176. package/dist/test/test_config.d.ts.map +1 -1
  177. package/dist/test/test_config.js +1 -1
  178. package/dist/test/test_config.js.map +1 -1
  179. package/dist/util/pl.cjs.map +1 -1
  180. package/dist/util/pl.js.map +1 -1
  181. package/dist/util/util.cjs.map +1 -1
  182. package/dist/util/util.js.map +1 -1
  183. package/package.json +4 -4
  184. package/src/core/ll_client.ts +7 -2
  185. package/src/core/unauth_client.ts +17 -2
  186. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +16 -3
  187. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +326 -3
  188. package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts +21 -7
  189. package/src/proto-rest/plapi.ts +29 -8
  190. package/dist/proto-grpc/google/rpc/code.cjs +0 -224
  191. package/dist/proto-grpc/google/rpc/code.cjs.map +0 -1
  192. package/dist/proto-grpc/google/rpc/code.js +0 -224
  193. package/dist/proto-grpc/google/rpc/code.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"test_config.cjs","names":["path","fs","plAddressToConfig","inferAuthRefreshTime","UnauthenticatedPlClient","LLPlClient","PlClient","startTcpProxy","resourceIdToString"],"sources":["../../src/test/test_config.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport { LLPlClient } from \"../core/ll_client\";\nimport type { AuthInformation, AuthOps, PlClientConfig } from \"../core/config\";\nimport { plAddressToConfig } from \"../core/config\";\nimport { UnauthenticatedPlClient } from \"../core/unauth_client\";\nimport { PlClient } from \"../core/client\";\nimport { randomUUID } from \"node:crypto\";\nimport type { OptionalSignedResourceId } from \"../core/types\";\nimport { NullSignedResourceId, resourceIdToString } from \"../core/types\";\nimport { inferAuthRefreshTime } from \"../core/auth\";\nimport * as path from \"node:path\";\nimport type { TestTcpProxy } from \"./tcp-proxy\";\nimport { startTcpProxy } from \"./tcp-proxy\";\n\nexport { TestTcpProxy };\n\nexport interface TestConfig {\n address: string;\n test_proxy?: string;\n test_user?: string;\n test_password?: string;\n}\n\nconst CONFIG_FILE = \"test_config.json\";\n// const AUTH_DATA_FILE = '.test_auth.json';\n\nlet authDataFilePath: string | undefined;\n\nfunction getFullAuthDataFilePath() {\n if (authDataFilePath === undefined) authDataFilePath = path.resolve(\".test_auth.json\");\n return authDataFilePath;\n}\n\nexport function getTestConfig(): TestConfig {\n let conf: Partial<TestConfig> = {};\n if (fs.existsSync(CONFIG_FILE))\n conf = JSON.parse(fs.readFileSync(CONFIG_FILE, { encoding: \"utf-8\" })) as TestConfig;\n\n if (process.env.PL_ADDRESS !== undefined) conf.address = process.env.PL_ADDRESS;\n\n if (process.env.PL_TEST_USER !== undefined) conf.test_user = process.env.PL_TEST_USER;\n\n if (process.env.PL_TEST_PASSWORD !== undefined) conf.test_password = process.env.PL_TEST_PASSWORD;\n\n if (process.env.PL_TEST_PROXY !== undefined) conf.test_proxy = process.env.PL_TEST_PROXY;\n\n if (conf.address === undefined)\n throw new Error(\n `can't resolve platform address (checked ${CONFIG_FILE} file and PL_ADDRESS environment var)`,\n );\n\n return conf as TestConfig;\n}\n\n/** Default request timeout for tests (ms) */\nexport const TEST_REQUEST_TIMEOUT = 500;\n\n/** Returns PlClientConfig with reduced timeout for tests */\nexport function plAddressToTestConfig(address: string): PlClientConfig {\n const plConf = plAddressToConfig(address);\n plConf.defaultRequestTimeout = TEST_REQUEST_TIMEOUT;\n return plConf;\n}\n\ninterface AuthCache {\n /** To check if config changed */\n conf: TestConfig;\n /** Backend's instance ID at the moment the JWT was issued. Restarts that\n * reset state rotate this; the JWT's `iss` claim is bound to it and the\n * backend rejects tokens minted by a different instance. Without this\n * check, a cached JWT from a previous backend run would silently fail\n * the first authenticated call after restart. */\n instanceId: string;\n expiration: number;\n authInformation: AuthInformation;\n}\n\nfunction saveAuthInfoCallback(\n tConf: TestConfig,\n instanceId: string,\n): (authInformation: AuthInformation) => void {\n return (authInformation) => {\n const dst = getFullAuthDataFilePath();\n const tmpDst = getFullAuthDataFilePath() + randomUUID();\n fs.writeFileSync(\n tmpDst,\n Buffer.from(\n JSON.stringify({\n conf: tConf,\n instanceId,\n authInformation,\n expiration: inferAuthRefreshTime(authInformation, 24 * 60 * 60),\n } as AuthCache),\n ),\n \"utf8\",\n );\n fs.renameSync(tmpDst, dst);\n };\n}\n\nconst cleanAuthInfoCallback = () => {\n const p = getFullAuthDataFilePath();\n if (fs.existsSync(p)) {\n console.warn(`Removing: ${p}`);\n fs.rmSync(p);\n }\n};\n\nexport async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> {\n const tConf = getTestConfig();\n\n const plConf = plAddressToTestConfig(tConf.address);\n const uClient = await UnauthenticatedPlClient.build(plConf);\n // ll.serverInfo is populated by build()'s ping; instanceId rotates on a\n // backend restart that drops the persisted state.\n const instanceId = uClient.ll.serverInfo.instanceId;\n\n let authInformation: AuthInformation | undefined = undefined;\n\n // Try recover from cache. The cache is keyed by config AND backend\n // instanceId — a backend restart that rotates instanceId invalidates the\n // cached JWT (the token's `iss` claim no longer matches the live\n // instance), so we re-login instead of carrying the dead token through\n // to the first authenticated call.\n if (fs.existsSync(getFullAuthDataFilePath())) {\n try {\n const cache: AuthCache = JSON.parse(\n fs.readFileSync(getFullAuthDataFilePath(), { encoding: \"utf-8\" }),\n ) as AuthCache; // TODO runtime validation\n if (\n cache.conf.address === tConf.address &&\n cache.conf.test_user === tConf.test_user &&\n cache.conf.test_password === tConf.test_password &&\n cache.expiration > Date.now() &&\n cache.instanceId === instanceId\n )\n authInformation = cache.authInformation;\n } catch {\n // removing cache file on any error\n fs.rmSync(getFullAuthDataFilePath());\n }\n }\n\n const requireAuth = await uClient.requireAuth();\n\n if (!requireAuth && (tConf.test_user !== undefined || tConf.test_password !== undefined))\n throw new Error(\n `Server require no auth, but test user name or test password are provided via (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (requireAuth && (tConf.test_user === undefined || tConf.test_password === undefined))\n throw new Error(\n `No auth information found in config (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (authInformation === undefined) {\n if (requireAuth) authInformation = await uClient.login(tConf.test_user!, tConf.test_password!);\n // No authorization is required\n else authInformation = {};\n\n // saving cache\n saveAuthInfoCallback(tConf, instanceId)(authInformation);\n }\n\n return {\n conf: plConf,\n auth: {\n authInformation,\n onUpdate: saveAuthInfoCallback(tConf, instanceId),\n onAuthError: cleanAuthInfoCallback,\n onUpdateError: cleanAuthInfoCallback,\n },\n };\n}\n\nexport async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {\n const { conf, auth } = await getTestClientConf();\n return await LLPlClient.build({ ...conf, ...confOverrides }, { auth });\n}\n\nexport async function getTestClient(\n alternativeRoot?: string,\n confOverrides: Partial<PlClientConfig> = {},\n) {\n const { conf, auth } = await getTestClientConf();\n if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)\n throw new Error(\"test pl address configured with alternative root\");\n return await PlClient.init({ ...conf, ...confOverrides, alternativeRoot }, auth);\n}\n\nexport type WithTempRootOptions =\n | {\n /** If true and PL_ADDRESS is http://localhost or http://127.0.0.1:<port>,\n * a TCP proxy will be started and PL client will connect through it. */\n viaTcpProxy: true;\n /** Artificial latency for proxy (ms). Default 0 */\n proxyLatencyMs?: number;\n }\n | {\n viaTcpProxy?: undefined;\n };\n\nexport async function withTempRoot<T>(body: (pl: PlClient) => Promise<T>): Promise<T | void>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: Awaited<ReturnType<typeof startTcpProxy>>) => Promise<T>,\n options: {\n viaTcpProxy: true;\n proxyLatencyMs?: number;\n },\n): Promise<T>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: any) => Promise<T>,\n options: WithTempRootOptions = {},\n): Promise<T | undefined> {\n const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;\n let altRootId: OptionalSignedResourceId = NullSignedResourceId;\n // Proxy management\n let proxy: Awaited<ReturnType<typeof startTcpProxy>> | undefined;\n let confOverrides: Partial<PlClientConfig> = {};\n try {\n // Optionally start TCP proxy and rewrite PL_ADDRESS to point to proxy\n if (options.viaTcpProxy === true && process.env.PL_ADDRESS) {\n try {\n const url = new URL(process.env.PL_ADDRESS);\n const isHttp = url.protocol === \"http:\";\n const isLocal = url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\";\n const port = parseInt(url.port);\n if (isHttp && isLocal && Number.isFinite(port)) {\n proxy = await startTcpProxy({ targetPort: port, latency: options.proxyLatencyMs ?? 0 });\n // Override client connection host:port to proxy\n confOverrides = { hostAndPort: `127.0.0.1:${proxy.port}` } as Partial<PlClientConfig>;\n } else {\n console.warn(\n \"*** skipping proxy-based test, PL_ADDRESS is not localhost\",\n process.env.PL_ADDRESS,\n );\n return;\n }\n } catch {\n // ignore proxy setup errors; tests will run against original address\n }\n }\n\n const client = await getTestClient(alternativeRoot, confOverrides);\n altRootId = client.clientRoot;\n try {\n const value = await body(client, proxy);\n const rawClient = await getTestClient();\n try {\n await rawClient.deleteAlternativeRoot(alternativeRoot);\n } catch (cleanupErr: any) {\n // Cleanup may fail if test intentionally deleted resources\n console.warn(`Failed to clean up alternative root ${alternativeRoot}:`, cleanupErr.message);\n } finally {\n // Close the cleanup client to avoid dangling gRPC channels that can cause\n // segfaults during process exit\n await rawClient.close();\n }\n return value;\n } finally {\n // Close the test client to avoid dangling gRPC channels\n await client.close();\n }\n } catch (err: any) {\n console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);\n throw err;\n // throw new Error('withTempRoot error: ' + err.message, { cause: err });\n } finally {\n // Stop proxy if started\n if (proxy) {\n try {\n await proxy.disconnectAll();\n } catch {\n /* ignore */\n }\n try {\n await new Promise<void>((resolve) => proxy!.server.close(() => resolve()));\n } catch {\n /* ignore */\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"test_config.cjs","names":["path","fs","plAddressToConfig","inferAuthRefreshTime","UnauthenticatedPlClient","LLPlClient","PlClient","startTcpProxy","resourceIdToString"],"sources":["../../src/test/test_config.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport { LLPlClient } from \"../core/ll_client\";\nimport type { AuthInformation, AuthOps, PlClientConfig } from \"../core/config\";\nimport { plAddressToConfig } from \"../core/config\";\nimport { UnauthenticatedPlClient } from \"../core/unauth_client\";\nimport { PlClient } from \"../core/client\";\nimport { randomUUID } from \"node:crypto\";\nimport type { OptionalSignedResourceId } from \"../core/types\";\nimport { NullSignedResourceId, resourceIdToString } from \"../core/types\";\nimport { inferAuthRefreshTime } from \"../core/auth\";\nimport * as path from \"node:path\";\nimport type { TestTcpProxy } from \"./tcp-proxy\";\nimport { startTcpProxy } from \"./tcp-proxy\";\n\nexport { TestTcpProxy };\n\nexport interface TestConfig {\n address: string;\n test_proxy?: string;\n test_user?: string;\n test_password?: string;\n}\n\nconst CONFIG_FILE = \"test_config.json\";\n// const AUTH_DATA_FILE = '.test_auth.json';\n\nlet authDataFilePath: string | undefined;\n\nfunction getFullAuthDataFilePath() {\n if (authDataFilePath === undefined) authDataFilePath = path.resolve(\".test_auth.json\");\n return authDataFilePath;\n}\n\nexport function getTestConfig(): TestConfig {\n let conf: Partial<TestConfig> = {};\n if (fs.existsSync(CONFIG_FILE))\n conf = JSON.parse(fs.readFileSync(CONFIG_FILE, { encoding: \"utf-8\" })) as TestConfig;\n\n if (process.env.PL_ADDRESS !== undefined) conf.address = process.env.PL_ADDRESS;\n\n if (process.env.PL_TEST_USER !== undefined) conf.test_user = process.env.PL_TEST_USER;\n\n if (process.env.PL_TEST_PASSWORD !== undefined) conf.test_password = process.env.PL_TEST_PASSWORD;\n\n if (process.env.PL_TEST_PROXY !== undefined) conf.test_proxy = process.env.PL_TEST_PROXY;\n\n if (conf.address === undefined)\n throw new Error(\n `can't resolve platform address (checked ${CONFIG_FILE} file and PL_ADDRESS environment var)`,\n );\n\n return conf as TestConfig;\n}\n\n/** Default request timeout for tests (ms) */\nexport const TEST_REQUEST_TIMEOUT = 500;\n\n/** Returns PlClientConfig with reduced timeout for tests */\nexport function plAddressToTestConfig(address: string): PlClientConfig {\n const plConf = plAddressToConfig(address);\n plConf.defaultRequestTimeout = TEST_REQUEST_TIMEOUT;\n return plConf;\n}\n\ninterface AuthCache {\n /** To check if config changed */\n conf: TestConfig;\n /** Backend's instance ID at the moment the JWT was issued. Restarts that\n * reset state rotate this; the JWT's `iss` claim is bound to it and the\n * backend rejects tokens minted by a different instance. Without this\n * check, a cached JWT from a previous backend run would silently fail\n * the first authenticated call after restart. */\n instanceId: string;\n expiration: number;\n authInformation: AuthInformation;\n}\n\nfunction saveAuthInfoCallback(\n tConf: TestConfig,\n instanceId: string,\n): (authInformation: AuthInformation) => void {\n return (authInformation) => {\n const dst = getFullAuthDataFilePath();\n const tmpDst = getFullAuthDataFilePath() + randomUUID();\n fs.writeFileSync(\n tmpDst,\n Buffer.from(\n JSON.stringify({\n conf: tConf,\n instanceId,\n authInformation,\n expiration: inferAuthRefreshTime(authInformation, 24 * 60 * 60),\n } as AuthCache),\n ),\n \"utf8\",\n );\n fs.renameSync(tmpDst, dst);\n };\n}\n\nconst cleanAuthInfoCallback = () => {\n const p = getFullAuthDataFilePath();\n if (fs.existsSync(p)) {\n console.warn(`Removing: ${p}`);\n fs.rmSync(p);\n }\n};\n\nexport async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> {\n const tConf = getTestConfig();\n\n const plConf = plAddressToTestConfig(tConf.address);\n const uClient = await UnauthenticatedPlClient.build(plConf);\n // ll.serverInfo is populated by build()'s ping; instanceId rotates on a\n // backend restart that drops the persisted state.\n const instanceId = uClient.ll.serverInfo.instanceId;\n\n let authInformation: AuthInformation | undefined = undefined;\n\n // Try recover from cache. The cache is keyed by config AND backend\n // instanceId — a backend restart that rotates instanceId invalidates the\n // cached JWT (the token's `iss` claim no longer matches the live\n // instance), so we re-login instead of carrying the dead token through\n // to the first authenticated call.\n if (fs.existsSync(getFullAuthDataFilePath())) {\n try {\n const cache: AuthCache = JSON.parse(\n fs.readFileSync(getFullAuthDataFilePath(), { encoding: \"utf-8\" }),\n ) as AuthCache; // TODO runtime validation\n if (\n cache.conf.address === tConf.address &&\n cache.conf.test_user === tConf.test_user &&\n cache.conf.test_password === tConf.test_password &&\n cache.expiration > Date.now() &&\n cache.instanceId === instanceId\n )\n authInformation = cache.authInformation;\n } catch {\n // removing cache file on any error\n fs.rmSync(getFullAuthDataFilePath());\n }\n }\n\n const requireAuth = await uClient.requireAuth();\n\n if (!requireAuth && (tConf.test_user !== undefined || tConf.test_password !== undefined))\n throw new Error(\n `Server require no auth, but test user name or test password are provided via (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (requireAuth && (tConf.test_user === undefined || tConf.test_password === undefined))\n throw new Error(\n `No auth information found in config (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (authInformation === undefined) {\n if (requireAuth) authInformation = await uClient.login(tConf.test_user!, tConf.test_password!);\n // No authorization is required\n else authInformation = {};\n\n // saving cache\n saveAuthInfoCallback(tConf, instanceId)(authInformation);\n }\n\n return {\n conf: plConf,\n auth: {\n authInformation,\n onUpdate: saveAuthInfoCallback(tConf, instanceId),\n onAuthError: cleanAuthInfoCallback,\n onUpdateError: cleanAuthInfoCallback,\n },\n };\n}\n\nexport async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {\n const { conf, auth } = await getTestClientConf();\n return await LLPlClient.build({ ...conf, ...confOverrides }, { auth });\n}\n\nexport async function getTestClient(\n alternativeRoot?: string,\n confOverrides: Partial<PlClientConfig> = {},\n) {\n const { conf, auth } = await getTestClientConf();\n if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)\n throw new Error(\"test pl address configured with alternative root\");\n return await PlClient.init({ ...conf, ...confOverrides, alternativeRoot }, auth);\n}\n\nexport type WithTempRootOptions =\n | {\n /** If true and PL_ADDRESS is http://localhost or http://127.0.0.1:<port>,\n * a TCP proxy will be started and PL client will connect through it. */\n viaTcpProxy: true;\n /** Artificial latency for proxy (ms). Default 0 */\n proxyLatencyMs?: number;\n }\n | {\n viaTcpProxy?: undefined;\n };\n\nexport async function withTempRoot<T>(body: (pl: PlClient) => Promise<T>): Promise<T | void>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: Awaited<ReturnType<typeof startTcpProxy>>) => Promise<T>,\n options: {\n viaTcpProxy: true;\n proxyLatencyMs?: number;\n },\n): Promise<T>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: any) => Promise<T>,\n options: WithTempRootOptions = {},\n): Promise<T | undefined> {\n const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;\n let altRootId: OptionalSignedResourceId = NullSignedResourceId;\n // Proxy management\n let proxy: Awaited<ReturnType<typeof startTcpProxy>> | undefined;\n let confOverrides: Partial<PlClientConfig> = {};\n try {\n // Optionally start TCP proxy and rewrite PL_ADDRESS to point to proxy\n if (options.viaTcpProxy === true && process.env.PL_ADDRESS) {\n try {\n const url = new URL(process.env.PL_ADDRESS);\n const isHttp = url.protocol === \"http:\";\n const isLocal = url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\";\n const port = parseInt(url.port);\n if (isHttp && isLocal && Number.isFinite(port)) {\n proxy = await startTcpProxy({ targetPort: port, latency: options.proxyLatencyMs ?? 0 });\n // Override client connection host:port to proxy\n confOverrides = { hostAndPort: `127.0.0.1:${proxy.port}` } as Partial<PlClientConfig>;\n } else {\n console.warn(\n \"*** skipping proxy-based test, PL_ADDRESS is not localhost\",\n process.env.PL_ADDRESS,\n );\n return;\n }\n } catch {\n // ignore proxy setup errors; tests will run against original address\n }\n }\n\n const client = await getTestClient(alternativeRoot, confOverrides);\n altRootId = client.clientRoot;\n try {\n const value = await body(client, proxy);\n const rawClient = await getTestClient();\n try {\n await rawClient.deleteAlternativeRoot(alternativeRoot);\n } catch (cleanupErr: any) {\n // Cleanup may fail if test intentionally deleted resources\n console.warn(`Failed to clean up alternative root ${alternativeRoot}:`, cleanupErr.message);\n } finally {\n // Close the cleanup client to avoid dangling gRPC channels that can cause\n // segfaults during process exit\n await rawClient.close();\n }\n return value;\n } finally {\n // Close the test client to avoid dangling gRPC channels\n await client.close();\n }\n } catch (err: any) {\n console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);\n throw err;\n // throw new Error('withTempRoot error: ' + err.message, { cause: err });\n } finally {\n // Stop proxy if started\n if (proxy) {\n try {\n await proxy.disconnectAll();\n } catch {\n /* ignore */\n }\n try {\n await new Promise<void>((resolve) => proxy!.server.close(() => resolve()));\n } catch {\n /* ignore */\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -38,5 +38,5 @@ declare function withTempRoot<T>(body: (pl: PlClient, proxy: Awaited<ReturnType<
38
38
  proxyLatencyMs?: number;
39
39
  }): Promise<T>;
40
40
  //#endregion
41
- export { test_config_d_exports };
41
+ export { TEST_REQUEST_TIMEOUT, TestConfig, type TestTcpProxy, WithTempRootOptions, getTestClient, getTestClientConf, getTestConfig, getTestLLClient, plAddressToTestConfig, test_config_d_exports, withTempRoot };
42
42
  //# sourceMappingURL=test_config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test_config.d.ts","names":[],"sources":["../../src/test/test_config.ts"],"mappings":";;;;;;;;;UAgBiB,UAAA;EACf,OAAA;EACA,UAAA;EACA,SAAA;EACA,aAAA;AAAA;AAAA,iBAac,aAAA,CAAA,GAAiB,UAAA;;cAsBpB,oBAAA;;iBAGG,qBAAA,CAAsB,OAAA,WAAkB,cAAA;AAAA,iBAkDlC,iBAAA,CAAA,GAAqB,OAAA;EAAU,IAAA,EAAM,cAAA;EAAgB,IAAA,EAAM,OAAA;AAAA;AAAA,iBAmE3D,eAAA,CAAgB,aAAA,GAAe,OAAA,CAAQ,cAAA,IAAoB,OAAA,CAAA,UAAA;AAAA,iBAK3D,aAAA,CACpB,eAAA,WACA,aAAA,GAAe,OAAA,CAAQ,cAAA,IAAoB,OAAA,CAAA,QAAA;AAAA,KAQjC,mBAAA;EA5KV;;EAgLI,WAAA,QA9KS;EAgLT,cAAA;AAAA;EAGA,WAAA;AAAA;AAAA,iBAGgB,YAAA,GAAA,CAAgB,IAAA,GAAO,EAAA,EAAI,QAAA,KAAa,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;AAAA,iBAE7D,YAAA,GAAA,CACpB,IAAA,GAAO,EAAA,EAAI,QAAA,EAAU,KAAA,EAAO,OAAA,CAAQ,UAAA,QAAkB,aAAA,OAAoB,OAAA,CAAQ,CAAA,GAClF,OAAA;EACE,WAAA;EACA,cAAA;AAAA,IAED,OAAA,CAAQ,CAAA"}
1
+ {"version":3,"file":"test_config.d.ts","names":[],"sources":["../../src/test/test_config.ts"],"mappings":";;;;;;;;;UAgBiB,UAAA;EACf,OAAA;EACA,UAAA;EACA,SAAA;EACA,aAAA;AAAA;AAAA,iBAac,aAAA,IAAiB,UAAU;;cAsB9B,oBAAA;;iBAGG,qBAAA,CAAsB,OAAA,WAAkB,cAAc;AAAA,iBAkDhD,iBAAA,IAAqB,OAAA;EAAU,IAAA,EAAM,cAAA;EAAgB,IAAA,EAAM,OAAA;AAAA;AAAA,iBAmE3D,eAAA,CAAgB,aAAA,GAAe,OAAA,CAAQ,cAAA,IAAoB,OAAA,CAAA,UAAA;AAAA,iBAK3D,aAAA,CACpB,eAAA,WACA,aAAA,GAAe,OAAA,CAAQ,cAAA,IAAoB,OAAA,CAAA,QAAA;AAAA,KAQjC,mBAAA;EA5KV;;EAgLI,WAAA,QA9KS;EAgLT,cAAA;AAAA;EAGA,WAAA;AAAA;AAAA,iBAGgB,YAAA,IAAgB,IAAA,GAAO,EAAA,EAAI,QAAA,KAAa,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;AAAA,iBAE7D,YAAA,IACpB,IAAA,GAAO,EAAA,EAAI,QAAA,EAAU,KAAA,EAAO,OAAA,CAAQ,UAAA,QAAkB,aAAA,OAAoB,OAAA,CAAQ,CAAA,GAClF,OAAA;EACE,WAAA;EACA,cAAA;AAAA,IAED,OAAA,CAAQ,CAAA"}
@@ -160,6 +160,6 @@ async function withTempRoot(body, options = {}) {
160
160
  }
161
161
  }
162
162
  //#endregion
163
- export { test_config_exports };
163
+ export { getTestClient, getTestClientConf, getTestConfig, getTestLLClient, plAddressToTestConfig, test_config_exports, withTempRoot };
164
164
 
165
165
  //# sourceMappingURL=test_config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"test_config.js","names":["fs"],"sources":["../../src/test/test_config.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport { LLPlClient } from \"../core/ll_client\";\nimport type { AuthInformation, AuthOps, PlClientConfig } from \"../core/config\";\nimport { plAddressToConfig } from \"../core/config\";\nimport { UnauthenticatedPlClient } from \"../core/unauth_client\";\nimport { PlClient } from \"../core/client\";\nimport { randomUUID } from \"node:crypto\";\nimport type { OptionalSignedResourceId } from \"../core/types\";\nimport { NullSignedResourceId, resourceIdToString } from \"../core/types\";\nimport { inferAuthRefreshTime } from \"../core/auth\";\nimport * as path from \"node:path\";\nimport type { TestTcpProxy } from \"./tcp-proxy\";\nimport { startTcpProxy } from \"./tcp-proxy\";\n\nexport { TestTcpProxy };\n\nexport interface TestConfig {\n address: string;\n test_proxy?: string;\n test_user?: string;\n test_password?: string;\n}\n\nconst CONFIG_FILE = \"test_config.json\";\n// const AUTH_DATA_FILE = '.test_auth.json';\n\nlet authDataFilePath: string | undefined;\n\nfunction getFullAuthDataFilePath() {\n if (authDataFilePath === undefined) authDataFilePath = path.resolve(\".test_auth.json\");\n return authDataFilePath;\n}\n\nexport function getTestConfig(): TestConfig {\n let conf: Partial<TestConfig> = {};\n if (fs.existsSync(CONFIG_FILE))\n conf = JSON.parse(fs.readFileSync(CONFIG_FILE, { encoding: \"utf-8\" })) as TestConfig;\n\n if (process.env.PL_ADDRESS !== undefined) conf.address = process.env.PL_ADDRESS;\n\n if (process.env.PL_TEST_USER !== undefined) conf.test_user = process.env.PL_TEST_USER;\n\n if (process.env.PL_TEST_PASSWORD !== undefined) conf.test_password = process.env.PL_TEST_PASSWORD;\n\n if (process.env.PL_TEST_PROXY !== undefined) conf.test_proxy = process.env.PL_TEST_PROXY;\n\n if (conf.address === undefined)\n throw new Error(\n `can't resolve platform address (checked ${CONFIG_FILE} file and PL_ADDRESS environment var)`,\n );\n\n return conf as TestConfig;\n}\n\n/** Default request timeout for tests (ms) */\nexport const TEST_REQUEST_TIMEOUT = 500;\n\n/** Returns PlClientConfig with reduced timeout for tests */\nexport function plAddressToTestConfig(address: string): PlClientConfig {\n const plConf = plAddressToConfig(address);\n plConf.defaultRequestTimeout = TEST_REQUEST_TIMEOUT;\n return plConf;\n}\n\ninterface AuthCache {\n /** To check if config changed */\n conf: TestConfig;\n /** Backend's instance ID at the moment the JWT was issued. Restarts that\n * reset state rotate this; the JWT's `iss` claim is bound to it and the\n * backend rejects tokens minted by a different instance. Without this\n * check, a cached JWT from a previous backend run would silently fail\n * the first authenticated call after restart. */\n instanceId: string;\n expiration: number;\n authInformation: AuthInformation;\n}\n\nfunction saveAuthInfoCallback(\n tConf: TestConfig,\n instanceId: string,\n): (authInformation: AuthInformation) => void {\n return (authInformation) => {\n const dst = getFullAuthDataFilePath();\n const tmpDst = getFullAuthDataFilePath() + randomUUID();\n fs.writeFileSync(\n tmpDst,\n Buffer.from(\n JSON.stringify({\n conf: tConf,\n instanceId,\n authInformation,\n expiration: inferAuthRefreshTime(authInformation, 24 * 60 * 60),\n } as AuthCache),\n ),\n \"utf8\",\n );\n fs.renameSync(tmpDst, dst);\n };\n}\n\nconst cleanAuthInfoCallback = () => {\n const p = getFullAuthDataFilePath();\n if (fs.existsSync(p)) {\n console.warn(`Removing: ${p}`);\n fs.rmSync(p);\n }\n};\n\nexport async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> {\n const tConf = getTestConfig();\n\n const plConf = plAddressToTestConfig(tConf.address);\n const uClient = await UnauthenticatedPlClient.build(plConf);\n // ll.serverInfo is populated by build()'s ping; instanceId rotates on a\n // backend restart that drops the persisted state.\n const instanceId = uClient.ll.serverInfo.instanceId;\n\n let authInformation: AuthInformation | undefined = undefined;\n\n // Try recover from cache. The cache is keyed by config AND backend\n // instanceId — a backend restart that rotates instanceId invalidates the\n // cached JWT (the token's `iss` claim no longer matches the live\n // instance), so we re-login instead of carrying the dead token through\n // to the first authenticated call.\n if (fs.existsSync(getFullAuthDataFilePath())) {\n try {\n const cache: AuthCache = JSON.parse(\n fs.readFileSync(getFullAuthDataFilePath(), { encoding: \"utf-8\" }),\n ) as AuthCache; // TODO runtime validation\n if (\n cache.conf.address === tConf.address &&\n cache.conf.test_user === tConf.test_user &&\n cache.conf.test_password === tConf.test_password &&\n cache.expiration > Date.now() &&\n cache.instanceId === instanceId\n )\n authInformation = cache.authInformation;\n } catch {\n // removing cache file on any error\n fs.rmSync(getFullAuthDataFilePath());\n }\n }\n\n const requireAuth = await uClient.requireAuth();\n\n if (!requireAuth && (tConf.test_user !== undefined || tConf.test_password !== undefined))\n throw new Error(\n `Server require no auth, but test user name or test password are provided via (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (requireAuth && (tConf.test_user === undefined || tConf.test_password === undefined))\n throw new Error(\n `No auth information found in config (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (authInformation === undefined) {\n if (requireAuth) authInformation = await uClient.login(tConf.test_user!, tConf.test_password!);\n // No authorization is required\n else authInformation = {};\n\n // saving cache\n saveAuthInfoCallback(tConf, instanceId)(authInformation);\n }\n\n return {\n conf: plConf,\n auth: {\n authInformation,\n onUpdate: saveAuthInfoCallback(tConf, instanceId),\n onAuthError: cleanAuthInfoCallback,\n onUpdateError: cleanAuthInfoCallback,\n },\n };\n}\n\nexport async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {\n const { conf, auth } = await getTestClientConf();\n return await LLPlClient.build({ ...conf, ...confOverrides }, { auth });\n}\n\nexport async function getTestClient(\n alternativeRoot?: string,\n confOverrides: Partial<PlClientConfig> = {},\n) {\n const { conf, auth } = await getTestClientConf();\n if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)\n throw new Error(\"test pl address configured with alternative root\");\n return await PlClient.init({ ...conf, ...confOverrides, alternativeRoot }, auth);\n}\n\nexport type WithTempRootOptions =\n | {\n /** If true and PL_ADDRESS is http://localhost or http://127.0.0.1:<port>,\n * a TCP proxy will be started and PL client will connect through it. */\n viaTcpProxy: true;\n /** Artificial latency for proxy (ms). Default 0 */\n proxyLatencyMs?: number;\n }\n | {\n viaTcpProxy?: undefined;\n };\n\nexport async function withTempRoot<T>(body: (pl: PlClient) => Promise<T>): Promise<T | void>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: Awaited<ReturnType<typeof startTcpProxy>>) => Promise<T>,\n options: {\n viaTcpProxy: true;\n proxyLatencyMs?: number;\n },\n): Promise<T>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: any) => Promise<T>,\n options: WithTempRootOptions = {},\n): Promise<T | undefined> {\n const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;\n let altRootId: OptionalSignedResourceId = NullSignedResourceId;\n // Proxy management\n let proxy: Awaited<ReturnType<typeof startTcpProxy>> | undefined;\n let confOverrides: Partial<PlClientConfig> = {};\n try {\n // Optionally start TCP proxy and rewrite PL_ADDRESS to point to proxy\n if (options.viaTcpProxy === true && process.env.PL_ADDRESS) {\n try {\n const url = new URL(process.env.PL_ADDRESS);\n const isHttp = url.protocol === \"http:\";\n const isLocal = url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\";\n const port = parseInt(url.port);\n if (isHttp && isLocal && Number.isFinite(port)) {\n proxy = await startTcpProxy({ targetPort: port, latency: options.proxyLatencyMs ?? 0 });\n // Override client connection host:port to proxy\n confOverrides = { hostAndPort: `127.0.0.1:${proxy.port}` } as Partial<PlClientConfig>;\n } else {\n console.warn(\n \"*** skipping proxy-based test, PL_ADDRESS is not localhost\",\n process.env.PL_ADDRESS,\n );\n return;\n }\n } catch {\n // ignore proxy setup errors; tests will run against original address\n }\n }\n\n const client = await getTestClient(alternativeRoot, confOverrides);\n altRootId = client.clientRoot;\n try {\n const value = await body(client, proxy);\n const rawClient = await getTestClient();\n try {\n await rawClient.deleteAlternativeRoot(alternativeRoot);\n } catch (cleanupErr: any) {\n // Cleanup may fail if test intentionally deleted resources\n console.warn(`Failed to clean up alternative root ${alternativeRoot}:`, cleanupErr.message);\n } finally {\n // Close the cleanup client to avoid dangling gRPC channels that can cause\n // segfaults during process exit\n await rawClient.close();\n }\n return value;\n } finally {\n // Close the test client to avoid dangling gRPC channels\n await client.close();\n }\n } catch (err: any) {\n console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);\n throw err;\n // throw new Error('withTempRoot error: ' + err.message, { cause: err });\n } finally {\n // Stop proxy if started\n if (proxy) {\n try {\n await proxy.disconnectAll();\n } catch {\n /* ignore */\n }\n try {\n await new Promise<void>((resolve) => proxy!.server.close(() => resolve()));\n } catch {\n /* ignore */\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"test_config.js","names":["fs"],"sources":["../../src/test/test_config.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport { LLPlClient } from \"../core/ll_client\";\nimport type { AuthInformation, AuthOps, PlClientConfig } from \"../core/config\";\nimport { plAddressToConfig } from \"../core/config\";\nimport { UnauthenticatedPlClient } from \"../core/unauth_client\";\nimport { PlClient } from \"../core/client\";\nimport { randomUUID } from \"node:crypto\";\nimport type { OptionalSignedResourceId } from \"../core/types\";\nimport { NullSignedResourceId, resourceIdToString } from \"../core/types\";\nimport { inferAuthRefreshTime } from \"../core/auth\";\nimport * as path from \"node:path\";\nimport type { TestTcpProxy } from \"./tcp-proxy\";\nimport { startTcpProxy } from \"./tcp-proxy\";\n\nexport { TestTcpProxy };\n\nexport interface TestConfig {\n address: string;\n test_proxy?: string;\n test_user?: string;\n test_password?: string;\n}\n\nconst CONFIG_FILE = \"test_config.json\";\n// const AUTH_DATA_FILE = '.test_auth.json';\n\nlet authDataFilePath: string | undefined;\n\nfunction getFullAuthDataFilePath() {\n if (authDataFilePath === undefined) authDataFilePath = path.resolve(\".test_auth.json\");\n return authDataFilePath;\n}\n\nexport function getTestConfig(): TestConfig {\n let conf: Partial<TestConfig> = {};\n if (fs.existsSync(CONFIG_FILE))\n conf = JSON.parse(fs.readFileSync(CONFIG_FILE, { encoding: \"utf-8\" })) as TestConfig;\n\n if (process.env.PL_ADDRESS !== undefined) conf.address = process.env.PL_ADDRESS;\n\n if (process.env.PL_TEST_USER !== undefined) conf.test_user = process.env.PL_TEST_USER;\n\n if (process.env.PL_TEST_PASSWORD !== undefined) conf.test_password = process.env.PL_TEST_PASSWORD;\n\n if (process.env.PL_TEST_PROXY !== undefined) conf.test_proxy = process.env.PL_TEST_PROXY;\n\n if (conf.address === undefined)\n throw new Error(\n `can't resolve platform address (checked ${CONFIG_FILE} file and PL_ADDRESS environment var)`,\n );\n\n return conf as TestConfig;\n}\n\n/** Default request timeout for tests (ms) */\nexport const TEST_REQUEST_TIMEOUT = 500;\n\n/** Returns PlClientConfig with reduced timeout for tests */\nexport function plAddressToTestConfig(address: string): PlClientConfig {\n const plConf = plAddressToConfig(address);\n plConf.defaultRequestTimeout = TEST_REQUEST_TIMEOUT;\n return plConf;\n}\n\ninterface AuthCache {\n /** To check if config changed */\n conf: TestConfig;\n /** Backend's instance ID at the moment the JWT was issued. Restarts that\n * reset state rotate this; the JWT's `iss` claim is bound to it and the\n * backend rejects tokens minted by a different instance. Without this\n * check, a cached JWT from a previous backend run would silently fail\n * the first authenticated call after restart. */\n instanceId: string;\n expiration: number;\n authInformation: AuthInformation;\n}\n\nfunction saveAuthInfoCallback(\n tConf: TestConfig,\n instanceId: string,\n): (authInformation: AuthInformation) => void {\n return (authInformation) => {\n const dst = getFullAuthDataFilePath();\n const tmpDst = getFullAuthDataFilePath() + randomUUID();\n fs.writeFileSync(\n tmpDst,\n Buffer.from(\n JSON.stringify({\n conf: tConf,\n instanceId,\n authInformation,\n expiration: inferAuthRefreshTime(authInformation, 24 * 60 * 60),\n } as AuthCache),\n ),\n \"utf8\",\n );\n fs.renameSync(tmpDst, dst);\n };\n}\n\nconst cleanAuthInfoCallback = () => {\n const p = getFullAuthDataFilePath();\n if (fs.existsSync(p)) {\n console.warn(`Removing: ${p}`);\n fs.rmSync(p);\n }\n};\n\nexport async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> {\n const tConf = getTestConfig();\n\n const plConf = plAddressToTestConfig(tConf.address);\n const uClient = await UnauthenticatedPlClient.build(plConf);\n // ll.serverInfo is populated by build()'s ping; instanceId rotates on a\n // backend restart that drops the persisted state.\n const instanceId = uClient.ll.serverInfo.instanceId;\n\n let authInformation: AuthInformation | undefined = undefined;\n\n // Try recover from cache. The cache is keyed by config AND backend\n // instanceId — a backend restart that rotates instanceId invalidates the\n // cached JWT (the token's `iss` claim no longer matches the live\n // instance), so we re-login instead of carrying the dead token through\n // to the first authenticated call.\n if (fs.existsSync(getFullAuthDataFilePath())) {\n try {\n const cache: AuthCache = JSON.parse(\n fs.readFileSync(getFullAuthDataFilePath(), { encoding: \"utf-8\" }),\n ) as AuthCache; // TODO runtime validation\n if (\n cache.conf.address === tConf.address &&\n cache.conf.test_user === tConf.test_user &&\n cache.conf.test_password === tConf.test_password &&\n cache.expiration > Date.now() &&\n cache.instanceId === instanceId\n )\n authInformation = cache.authInformation;\n } catch {\n // removing cache file on any error\n fs.rmSync(getFullAuthDataFilePath());\n }\n }\n\n const requireAuth = await uClient.requireAuth();\n\n if (!requireAuth && (tConf.test_user !== undefined || tConf.test_password !== undefined))\n throw new Error(\n `Server require no auth, but test user name or test password are provided via (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (requireAuth && (tConf.test_user === undefined || tConf.test_password === undefined))\n throw new Error(\n `No auth information found in config (${CONFIG_FILE}) or env variables: PL_TEST_USER and PL_TEST_PASSWORD`,\n );\n\n if (authInformation === undefined) {\n if (requireAuth) authInformation = await uClient.login(tConf.test_user!, tConf.test_password!);\n // No authorization is required\n else authInformation = {};\n\n // saving cache\n saveAuthInfoCallback(tConf, instanceId)(authInformation);\n }\n\n return {\n conf: plConf,\n auth: {\n authInformation,\n onUpdate: saveAuthInfoCallback(tConf, instanceId),\n onAuthError: cleanAuthInfoCallback,\n onUpdateError: cleanAuthInfoCallback,\n },\n };\n}\n\nexport async function getTestLLClient(confOverrides: Partial<PlClientConfig> = {}) {\n const { conf, auth } = await getTestClientConf();\n return await LLPlClient.build({ ...conf, ...confOverrides }, { auth });\n}\n\nexport async function getTestClient(\n alternativeRoot?: string,\n confOverrides: Partial<PlClientConfig> = {},\n) {\n const { conf, auth } = await getTestClientConf();\n if (alternativeRoot !== undefined && conf.alternativeRoot !== undefined)\n throw new Error(\"test pl address configured with alternative root\");\n return await PlClient.init({ ...conf, ...confOverrides, alternativeRoot }, auth);\n}\n\nexport type WithTempRootOptions =\n | {\n /** If true and PL_ADDRESS is http://localhost or http://127.0.0.1:<port>,\n * a TCP proxy will be started and PL client will connect through it. */\n viaTcpProxy: true;\n /** Artificial latency for proxy (ms). Default 0 */\n proxyLatencyMs?: number;\n }\n | {\n viaTcpProxy?: undefined;\n };\n\nexport async function withTempRoot<T>(body: (pl: PlClient) => Promise<T>): Promise<T | void>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: Awaited<ReturnType<typeof startTcpProxy>>) => Promise<T>,\n options: {\n viaTcpProxy: true;\n proxyLatencyMs?: number;\n },\n): Promise<T>;\n\nexport async function withTempRoot<T>(\n body: (pl: PlClient, proxy: any) => Promise<T>,\n options: WithTempRootOptions = {},\n): Promise<T | undefined> {\n const alternativeRoot = `test_${Date.now()}_${randomUUID()}`;\n let altRootId: OptionalSignedResourceId = NullSignedResourceId;\n // Proxy management\n let proxy: Awaited<ReturnType<typeof startTcpProxy>> | undefined;\n let confOverrides: Partial<PlClientConfig> = {};\n try {\n // Optionally start TCP proxy and rewrite PL_ADDRESS to point to proxy\n if (options.viaTcpProxy === true && process.env.PL_ADDRESS) {\n try {\n const url = new URL(process.env.PL_ADDRESS);\n const isHttp = url.protocol === \"http:\";\n const isLocal = url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\";\n const port = parseInt(url.port);\n if (isHttp && isLocal && Number.isFinite(port)) {\n proxy = await startTcpProxy({ targetPort: port, latency: options.proxyLatencyMs ?? 0 });\n // Override client connection host:port to proxy\n confOverrides = { hostAndPort: `127.0.0.1:${proxy.port}` } as Partial<PlClientConfig>;\n } else {\n console.warn(\n \"*** skipping proxy-based test, PL_ADDRESS is not localhost\",\n process.env.PL_ADDRESS,\n );\n return;\n }\n } catch {\n // ignore proxy setup errors; tests will run against original address\n }\n }\n\n const client = await getTestClient(alternativeRoot, confOverrides);\n altRootId = client.clientRoot;\n try {\n const value = await body(client, proxy);\n const rawClient = await getTestClient();\n try {\n await rawClient.deleteAlternativeRoot(alternativeRoot);\n } catch (cleanupErr: any) {\n // Cleanup may fail if test intentionally deleted resources\n console.warn(`Failed to clean up alternative root ${alternativeRoot}:`, cleanupErr.message);\n } finally {\n // Close the cleanup client to avoid dangling gRPC channels that can cause\n // segfaults during process exit\n await rawClient.close();\n }\n return value;\n } finally {\n // Close the test client to avoid dangling gRPC channels\n await client.close();\n }\n } catch (err: any) {\n console.log(`ALTERNATIVE ROOT: ${alternativeRoot} (${resourceIdToString(altRootId)})`);\n throw err;\n // throw new Error('withTempRoot error: ' + err.message, { cause: err });\n } finally {\n // Stop proxy if started\n if (proxy) {\n try {\n await proxy.disconnectAll();\n } catch {\n /* ignore */\n }\n try {\n await new Promise<void>((resolve) => proxy!.server.close(() => resolve()));\n } catch {\n /* ignore */\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"pl.cjs","names":[],"sources":["../../src/util/pl.ts"],"sourcesContent":["export type PlJWTPayload = {\n sub: string; // user ID\n iss: string; // backend instance ID\n\n // deprecated. Prior backend capability auth:v2. Use uid instead.\n user: {\n login: string;\n };\n\n exp: number;\n iat: number;\n};\n\nexport function parsePlJwt(token: string): PlJWTPayload {\n return JSON.parse(Buffer.from(token.split(\".\")[1], \"base64\").toString());\n}\n"],"mappings":";AAaA,SAAgB,WAAW,OAA6B;AACtD,QAAO,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,IAAI,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC"}
1
+ {"version":3,"file":"pl.cjs","names":[],"sources":["../../src/util/pl.ts"],"sourcesContent":["export type PlJWTPayload = {\n sub: string; // user ID\n iss: string; // backend instance ID\n\n // deprecated. Prior backend capability auth:v2. Use uid instead.\n user: {\n login: string;\n };\n\n exp: number;\n iat: number;\n};\n\nexport function parsePlJwt(token: string): PlJWTPayload {\n return JSON.parse(Buffer.from(token.split(\".\")[1], \"base64\").toString());\n}\n"],"mappings":";AAaA,SAAgB,WAAW,OAA6B;CACtD,OAAO,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,SAAS,CAAC;AACzE"}
@@ -1 +1 @@
1
- {"version":3,"file":"pl.js","names":[],"sources":["../../src/util/pl.ts"],"sourcesContent":["export type PlJWTPayload = {\n sub: string; // user ID\n iss: string; // backend instance ID\n\n // deprecated. Prior backend capability auth:v2. Use uid instead.\n user: {\n login: string;\n };\n\n exp: number;\n iat: number;\n};\n\nexport function parsePlJwt(token: string): PlJWTPayload {\n return JSON.parse(Buffer.from(token.split(\".\")[1], \"base64\").toString());\n}\n"],"mappings":";AAaA,SAAgB,WAAW,OAA6B;AACtD,QAAO,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,IAAI,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC"}
1
+ {"version":3,"file":"pl.js","names":[],"sources":["../../src/util/pl.ts"],"sourcesContent":["export type PlJWTPayload = {\n sub: string; // user ID\n iss: string; // backend instance ID\n\n // deprecated. Prior backend capability auth:v2. Use uid instead.\n user: {\n login: string;\n };\n\n exp: number;\n iat: number;\n};\n\nexport function parsePlJwt(token: string): PlJWTPayload {\n return JSON.parse(Buffer.from(token.split(\".\")[1], \"base64\").toString());\n}\n"],"mappings":";AAaA,SAAgB,WAAW,OAA6B;CACtD,OAAO,KAAK,MAAM,OAAO,KAAK,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,SAAS,CAAC;AACzE"}
@@ -1 +1 @@
1
- {"version":3,"file":"util.cjs","names":[],"sources":["../../src/util/util.ts"],"sourcesContent":["function isArrayBufferOrView(value: unknown): value is ArrayBufferLike {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\nexport function toBytes(value: string | Uint8Array): Uint8Array {\n if (typeof value === \"string\") return Buffer.from(value);\n else if (isArrayBufferOrView(value)) return value;\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n else throw new Error(`Unexpected type: ${value}`);\n}\n"],"mappings":";AAAA,SAAS,oBAAoB,OAA0C;AACrE,QAAO,iBAAiB,eAAe,YAAY,OAAO,MAAM;;AAGlE,SAAgB,QAAQ,OAAwC;AAC9D,KAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK,MAAM;UAC/C,oBAAoB,MAAM,CAAE,QAAO;KAEvC,OAAM,IAAI,MAAM,oBAAoB,QAAQ"}
1
+ {"version":3,"file":"util.cjs","names":[],"sources":["../../src/util/util.ts"],"sourcesContent":["function isArrayBufferOrView(value: unknown): value is ArrayBufferLike {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\nexport function toBytes(value: string | Uint8Array): Uint8Array {\n if (typeof value === \"string\") return Buffer.from(value);\n else if (isArrayBufferOrView(value)) return value;\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n else throw new Error(`Unexpected type: ${value}`);\n}\n"],"mappings":";AAAA,SAAS,oBAAoB,OAA0C;CACrE,OAAO,iBAAiB,eAAe,YAAY,OAAO,KAAK;AACjE;AAEA,SAAgB,QAAQ,OAAwC;CAC9D,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,KAAK,KAAK;MAClD,IAAI,oBAAoB,KAAK,GAAG,OAAO;MAEvC,MAAM,IAAI,MAAM,oBAAoB,OAAO;AAClD"}
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","names":[],"sources":["../../src/util/util.ts"],"sourcesContent":["function isArrayBufferOrView(value: unknown): value is ArrayBufferLike {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\nexport function toBytes(value: string | Uint8Array): Uint8Array {\n if (typeof value === \"string\") return Buffer.from(value);\n else if (isArrayBufferOrView(value)) return value;\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n else throw new Error(`Unexpected type: ${value}`);\n}\n"],"mappings":";AAAA,SAAS,oBAAoB,OAA0C;AACrE,QAAO,iBAAiB,eAAe,YAAY,OAAO,MAAM;;AAGlE,SAAgB,QAAQ,OAAwC;AAC9D,KAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK,MAAM;UAC/C,oBAAoB,MAAM,CAAE,QAAO;KAEvC,OAAM,IAAI,MAAM,oBAAoB,QAAQ"}
1
+ {"version":3,"file":"util.js","names":[],"sources":["../../src/util/util.ts"],"sourcesContent":["function isArrayBufferOrView(value: unknown): value is ArrayBufferLike {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\nexport function toBytes(value: string | Uint8Array): Uint8Array {\n if (typeof value === \"string\") return Buffer.from(value);\n else if (isArrayBufferOrView(value)) return value;\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n else throw new Error(`Unexpected type: ${value}`);\n}\n"],"mappings":";AAAA,SAAS,oBAAoB,OAA0C;CACrE,OAAO,iBAAiB,eAAe,YAAY,OAAO,KAAK;AACjE;AAEA,SAAgB,QAAQ,OAAwC;CAC9D,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,KAAK,KAAK;MAClD,IAAI,oBAAoB,KAAK,GAAG,OAAO;MAEvC,MAAM,IAAI,MAAM,oBAAoB,OAAO;AAClD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-client",
3
- "version": "3.11.5",
3
+ "version": "3.12.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-http": "1.2.4",
34
33
  "@milaboratories/pl-model-common": "1.46.2",
34
+ "@milaboratories/pl-http": "1.2.4",
35
35
  "@milaboratories/ts-helpers": "1.8.3"
36
36
  },
37
37
  "devDependencies": {
@@ -42,8 +42,8 @@
42
42
  "typescript": "~5.9.3",
43
43
  "vitest": "^4.1.3",
44
44
  "@milaboratories/build-configs": "2.0.0",
45
- "@milaboratories/ts-builder": "1.5.2",
46
- "@milaboratories/ts-configs": "1.2.3"
45
+ "@milaboratories/ts-builder": "1.6.0",
46
+ "@milaboratories/ts-configs": "1.3.0"
47
47
  },
48
48
  "engines": {
49
49
  "node": ">=22.19.0"
@@ -649,7 +649,7 @@ export class LLPlClient implements WireClientProviderFactory {
649
649
  /** Request a one-time PKCE nonce + its expiry from the backend. The desktop is
650
650
  * expected to place the nonce verbatim into the OIDC auth-request before
651
651
  * redirecting to the IdP; the backend enforces the expiry on {@link loginSSO}. */
652
- public async beginSSOLogin(): Promise<{ nonce: string; expiresAt: Date }> {
652
+ public async beginSSOLogin(): Promise<{ nonce: string; expiresAt: Date; clientSecret?: string }> {
653
653
  const cl = this.clientProvider.get();
654
654
  if (cl instanceof GrpcPlApiClient) {
655
655
  const resp = (await cl.beginSSOLogin({}).response).flow;
@@ -660,6 +660,7 @@ export class LLPlClient implements WireClientProviderFactory {
660
660
  return {
661
661
  nonce: resp.publicPkce.nonce,
662
662
  expiresAt: Timestamp.toDate(exp),
663
+ clientSecret: resp.publicPkce.clientSecret,
663
664
  };
664
665
  } else {
665
666
  const wsResponse = notEmpty(
@@ -667,7 +668,11 @@ export class LLPlClient implements WireClientProviderFactory {
667
668
  "REST: empty response for beginSSOLogin request",
668
669
  );
669
670
  const pkce = notEmpty(wsResponse.publicPkce, "REST: missing publicPkce in beginSSOLogin");
670
- return { nonce: pkce.nonce, expiresAt: new Date(pkce.expiresAt) };
671
+ return {
672
+ nonce: pkce.nonce,
673
+ expiresAt: new Date(pkce.expiresAt),
674
+ clientSecret: pkce.clientSecret,
675
+ };
671
676
  }
672
677
  }
673
678
 
@@ -29,6 +29,10 @@ export type SSOAuthMethod = {
29
29
  userIdClaim: string;
30
30
  groupsClaim: string;
31
31
  flowType: SSOFlowType;
32
+ /** OIDC `access_type` auth-request param ("online" | "offline"). Google-specific:
33
+ * "offline" makes Google issue a refresh token; other IdPs ignore it. Absent when
34
+ * the server does not set it — never send an empty string to the IdP. */
35
+ accessType?: string;
32
36
  };
33
37
 
34
38
  /** Server-issued material returned by {@link UnauthenticatedPlClient.beginSSOLogin}.
@@ -37,6 +41,11 @@ export type SSOLoginAttempt = {
37
41
  flow: "public_pkce";
38
42
  nonce: string;
39
43
  expiresAt: Date;
44
+ /** Confidential-client secret for the IdP token exchange; absent for public clients.
45
+ * Google's OIDC has no public-client mode — it requires a client_secret at the token
46
+ * endpoint even with PKCE — so for Google the backend forwards its secret here and the
47
+ * desktop exchanges the code as a confidential client. */
48
+ clientSecret?: string;
40
49
  };
41
50
 
42
51
  /** Primarily used for initial authentication (login) */
@@ -104,6 +113,7 @@ export class UnauthenticatedPlClient {
104
113
  subjectTokenSource: sso.subjectTokenSource,
105
114
  userIdClaim: sso.userIdClaim,
106
115
  groupsClaim: sso.groupsClaim,
116
+ accessType: sso.accessType || undefined,
107
117
  flowType: "public_pkce",
108
118
  };
109
119
  }
@@ -149,8 +159,13 @@ export class UnauthenticatedPlClient {
149
159
  /** Request fresh server-issued login material. v1 only emits the public-PKCE flow;
150
160
  * desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */
151
161
  public async beginSSOLogin(): Promise<SSOLoginAttempt> {
152
- const { nonce, expiresAt } = await this.ll.beginSSOLogin();
153
- return { flow: "public_pkce", nonce, expiresAt };
162
+ const attempt = await this.ll.beginSSOLogin();
163
+ return {
164
+ flow: "public_pkce",
165
+ nonce: attempt.nonce,
166
+ expiresAt: attempt.expiresAt,
167
+ clientSecret: attempt.clientSecret,
168
+ };
154
169
  }
155
170
 
156
171
  /** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */
@@ -10,6 +10,8 @@ import type { MaintenanceAPI_Ping_Response } from "./api";
10
10
  import type { MaintenanceAPI_Ping_Request } from "./api";
11
11
  import type { MiscAPI_ListResourceTypes_Response } from "./api";
12
12
  import type { MiscAPI_ListResourceTypes_Request } from "./api";
13
+ import type { AuthAPI_ListUsers_Response } from "./api";
14
+ import type { AuthAPI_ListUsers_Request } from "./api";
13
15
  import type { AuthAPI_ListUserResources_Response } from "./api";
14
16
  import type { AuthAPI_ListUserResources_Request } from "./api";
15
17
  import type { AuthAPI_GetUserRoot_Response } from "./api";
@@ -284,6 +286,10 @@ export interface IPlatformClient {
284
286
  * @generated from protobuf rpc: ListUserResources
285
287
  */
286
288
  listUserResources(input: AuthAPI_ListUserResources_Request, options?: RpcOptions): ServerStreamingCall<AuthAPI_ListUserResources_Request, AuthAPI_ListUserResources_Response>;
289
+ /**
290
+ * @generated from protobuf rpc: ListUsers
291
+ */
292
+ listUsers(input: AuthAPI_ListUsers_Request, options?: RpcOptions): UnaryCall<AuthAPI_ListUsers_Request, AuthAPI_ListUsers_Response>;
287
293
  /**
288
294
  *
289
295
  * Other stuff
@@ -613,6 +619,13 @@ export class PlatformClient implements IPlatformClient, ServiceInfo {
613
619
  const method = this.methods[33], opt = this._transport.mergeOptions(options);
614
620
  return stackIntercept<AuthAPI_ListUserResources_Request, AuthAPI_ListUserResources_Response>("serverStreaming", this._transport, method, opt, input);
615
621
  }
622
+ /**
623
+ * @generated from protobuf rpc: ListUsers
624
+ */
625
+ listUsers(input: AuthAPI_ListUsers_Request, options?: RpcOptions): UnaryCall<AuthAPI_ListUsers_Request, AuthAPI_ListUsers_Response> {
626
+ const method = this.methods[34], opt = this._transport.mergeOptions(options);
627
+ return stackIntercept<AuthAPI_ListUsers_Request, AuthAPI_ListUsers_Response>("unary", this._transport, method, opt, input);
628
+ }
616
629
  /**
617
630
  *
618
631
  * Other stuff
@@ -621,7 +634,7 @@ export class PlatformClient implements IPlatformClient, ServiceInfo {
621
634
  * @generated from protobuf rpc: ListResourceTypes
622
635
  */
623
636
  listResourceTypes(input: MiscAPI_ListResourceTypes_Request, options?: RpcOptions): UnaryCall<MiscAPI_ListResourceTypes_Request, MiscAPI_ListResourceTypes_Response> {
624
- const method = this.methods[34], opt = this._transport.mergeOptions(options);
637
+ const method = this.methods[35], opt = this._transport.mergeOptions(options);
625
638
  return stackIntercept<MiscAPI_ListResourceTypes_Request, MiscAPI_ListResourceTypes_Response>("unary", this._transport, method, opt, input);
626
639
  }
627
640
  /**
@@ -632,14 +645,14 @@ export class PlatformClient implements IPlatformClient, ServiceInfo {
632
645
  * @generated from protobuf rpc: Ping
633
646
  */
634
647
  ping(input: MaintenanceAPI_Ping_Request, options?: RpcOptions): UnaryCall<MaintenanceAPI_Ping_Request, MaintenanceAPI_Ping_Response> {
635
- const method = this.methods[35], opt = this._transport.mergeOptions(options);
648
+ const method = this.methods[36], opt = this._transport.mergeOptions(options);
636
649
  return stackIntercept<MaintenanceAPI_Ping_Request, MaintenanceAPI_Ping_Response>("unary", this._transport, method, opt, input);
637
650
  }
638
651
  /**
639
652
  * @generated from protobuf rpc: License
640
653
  */
641
654
  license(input: MaintenanceAPI_License_Request, options?: RpcOptions): UnaryCall<MaintenanceAPI_License_Request, MaintenanceAPI_License_Response> {
642
- const method = this.methods[36], opt = this._transport.mergeOptions(options);
655
+ const method = this.methods[37], opt = this._transport.mergeOptions(options);
643
656
  return stackIntercept<MaintenanceAPI_License_Request, MaintenanceAPI_License_Response>("unary", this._transport, method, opt, input);
644
657
  }
645
658
  }