@milaboratories/pl-client 3.11.5 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/PromiseTracker.cjs.map +1 -1
- package/dist/core/PromiseTracker.d.ts.map +1 -1
- package/dist/core/PromiseTracker.js.map +1 -1
- package/dist/core/StatefulPromise.cjs +2 -0
- package/dist/core/StatefulPromise.cjs.map +1 -1
- package/dist/core/StatefulPromise.js +2 -0
- package/dist/core/StatefulPromise.js.map +1 -1
- package/dist/core/abstract_stream.d.ts +1 -1
- package/dist/core/advisory_locks.cjs.map +1 -1
- package/dist/core/advisory_locks.js.map +1 -1
- package/dist/core/auth.cjs.map +1 -1
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js.map +1 -1
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/capabilities.cjs.map +1 -1
- package/dist/core/capabilities.d.ts +2 -3
- package/dist/core/capabilities.d.ts.map +1 -1
- package/dist/core/capabilities.js.map +1 -1
- package/dist/core/client.cjs +23 -6
- package/dist/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +14 -4
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +22 -5
- package/dist/core/client.js.map +1 -1
- package/dist/core/config.cjs.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/default_client.cjs +5 -5
- package/dist/core/default_client.cjs.map +1 -1
- package/dist/core/default_client.d.ts.map +1 -1
- package/dist/core/default_client.js.map +1 -1
- package/dist/core/driver.cjs.map +1 -1
- package/dist/core/driver.d.ts.map +1 -1
- package/dist/core/driver.js.map +1 -1
- package/dist/core/error_resource.cjs.map +1 -1
- package/dist/core/error_resource.js.map +1 -1
- package/dist/core/errors.cjs +11 -11
- package/dist/core/errors.cjs.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +11 -10
- package/dist/core/errors.js.map +1 -1
- package/dist/core/final.cjs +6 -2
- package/dist/core/final.cjs.map +1 -1
- package/dist/core/final.d.ts.map +1 -1
- package/dist/core/final.js +4 -1
- package/dist/core/final.js.map +1 -1
- package/dist/core/ll_client.cjs +60 -16
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +23 -7
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +62 -17
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/ll_transaction.cjs +3 -1
- package/dist/core/ll_transaction.cjs.map +1 -1
- package/dist/core/ll_transaction.d.ts +1 -1
- package/dist/core/ll_transaction.d.ts.map +1 -1
- package/dist/core/ll_transaction.js +2 -1
- package/dist/core/ll_transaction.js.map +1 -1
- package/dist/core/stat.cjs.map +1 -1
- package/dist/core/stat.js.map +1 -1
- package/dist/core/transaction.cjs +91 -4
- package/dist/core/transaction.cjs.map +1 -1
- package/dist/core/transaction.d.ts +52 -2
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +90 -5
- package/dist/core/transaction.js.map +1 -1
- package/dist/core/tree_filter.cjs +73 -18
- package/dist/core/tree_filter.cjs.map +1 -1
- package/dist/core/tree_filter.d.ts +1 -1
- package/dist/core/tree_filter.d.ts.map +1 -1
- package/dist/core/tree_filter.js +72 -19
- package/dist/core/tree_filter.js.map +1 -1
- package/dist/core/type_conversion.cjs +19 -20
- package/dist/core/type_conversion.cjs.map +1 -1
- package/dist/core/type_conversion.js +19 -19
- package/dist/core/type_conversion.js.map +1 -1
- package/dist/core/types.cjs +2 -3
- package/dist/core/types.cjs.map +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +2 -2
- package/dist/core/types.js.map +1 -1
- package/dist/core/unauth_client.cjs +7 -6
- package/dist/core/unauth_client.cjs.map +1 -1
- package/dist/core/unauth_client.d.ts +9 -0
- package/dist/core/unauth_client.d.ts.map +1 -1
- package/dist/core/unauth_client.js +7 -5
- package/dist/core/unauth_client.js.map +1 -1
- package/dist/core/user_resources.cjs +57 -1
- package/dist/core/user_resources.cjs.map +1 -1
- package/dist/core/user_resources.d.ts +55 -1
- package/dist/core/user_resources.d.ts.map +1 -1
- package/dist/core/user_resources.js +57 -0
- package/dist/core/user_resources.js.map +1 -1
- package/dist/core/websocket_stream.cjs +17 -20
- package/dist/core/websocket_stream.cjs.map +1 -1
- package/dist/core/websocket_stream.js +16 -19
- package/dist/core/websocket_stream.js.map +1 -1
- package/dist/core/wire.d.ts.map +1 -1
- package/dist/helpers/pl.cjs +25 -0
- package/dist/helpers/pl.cjs.map +1 -1
- package/dist/helpers/pl.d.ts +1 -1
- package/dist/helpers/pl.d.ts.map +1 -1
- package/dist/helpers/pl.js +1 -1
- package/dist/helpers/pl.js.map +1 -1
- package/dist/helpers/poll.cjs +5 -1
- package/dist/helpers/poll.cjs.map +1 -1
- package/dist/helpers/poll.d.ts.map +1 -1
- package/dist/helpers/poll.js +4 -0
- package/dist/helpers/poll.js.map +1 -1
- package/dist/helpers/retry_strategy.cjs +1 -0
- package/dist/helpers/retry_strategy.cjs.map +1 -1
- package/dist/helpers/retry_strategy.js +1 -1
- package/dist/helpers/retry_strategy.js.map +1 -1
- package/dist/helpers/tx_helpers.cjs +0 -1
- package/dist/helpers/tx_helpers.cjs.map +1 -1
- package/dist/helpers/tx_helpers.d.ts.map +1 -1
- package/dist/helpers/tx_helpers.js.map +1 -1
- package/dist/index.cjs +3 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs +0 -1
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.cjs.map +1 -1
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +537 -83
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs +11 -4
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts +12 -6
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js +11 -3
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +112 -7
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +336 -83
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs +23 -7
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts +10 -3
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js +18 -7
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs +1 -2
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.cjs +2 -2
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.js +2 -2
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.js.map +1 -1
- package/dist/proto-grpc/google/protobuf/any.cjs +1 -2
- package/dist/proto-grpc/google/protobuf/any.cjs.map +1 -1
- package/dist/proto-grpc/google/protobuf/any.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/any.js +1 -1
- package/dist/proto-grpc/google/protobuf/any.js.map +1 -1
- package/dist/proto-grpc/google/protobuf/duration.cjs +0 -1
- package/dist/proto-grpc/google/protobuf/duration.cjs.map +1 -1
- package/dist/proto-grpc/google/protobuf/duration.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/duration.js.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.cjs +0 -1
- package/dist/proto-grpc/google/protobuf/timestamp.cjs.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.js.map +1 -1
- package/dist/proto-grpc/google/rpc/code.d.ts.map +1 -1
- package/dist/proto-rest/index.cjs +1 -1
- package/dist/proto-rest/index.cjs.map +1 -1
- package/dist/proto-rest/index.d.ts +1 -1
- package/dist/proto-rest/index.d.ts.map +1 -1
- package/dist/proto-rest/index.js.map +1 -1
- package/dist/proto-rest/plapi.d.ts +30 -9
- package/dist/proto-rest/plapi.d.ts.map +1 -1
- package/dist/test/tcp-proxy.cjs +2 -2
- package/dist/test/tcp-proxy.cjs.map +1 -1
- package/dist/test/tcp-proxy.d.ts +1 -1
- package/dist/test/tcp-proxy.js.map +1 -1
- package/dist/test/test_config.cjs +8 -2
- package/dist/test/test_config.cjs.map +1 -1
- package/dist/test/test_config.d.ts +1 -1
- package/dist/test/test_config.d.ts.map +1 -1
- package/dist/test/test_config.js +1 -1
- package/dist/test/test_config.js.map +1 -1
- package/dist/util/pl.cjs.map +1 -1
- package/dist/util/pl.js.map +1 -1
- package/dist/util/util.cjs.map +1 -1
- package/dist/util/util.js.map +1 -1
- package/package.json +4 -4
- package/src/core/capabilities.ts +10 -3
- package/src/core/client.ts +30 -3
- package/src/core/errors.ts +2 -0
- package/src/core/final.ts +4 -0
- package/src/core/ll_client.test.ts +15 -2
- package/src/core/ll_client.ts +66 -11
- package/src/core/transaction.ts +103 -3
- package/src/core/unauth_client.ts +17 -2
- package/src/core/user_resources.ts +85 -0
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +16 -3
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +335 -12
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts +21 -7
- package/src/proto-rest/plapi.ts +29 -8
- package/dist/proto-grpc/google/rpc/code.cjs +0 -224
- package/dist/proto-grpc/google/rpc/code.cjs.map +0 -1
- package/dist/proto-grpc/google/rpc/code.js +0 -224
- package/dist/proto-grpc/google/rpc/code.js.map +0 -1
package/dist/core/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/core/types.ts"],"sourcesContent":["/* eslint-disable eslint-js/no-restricted-syntax -- this file is the canonical place to construct SignedResourceId values; outside callers must use asSignedResourceId(). */\n\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport { cachedDeserialize, notEmpty } from \"@milaboratories/ts-helpers\";\n\n/** Null resource id */\nexport type NullResourceId = Branded<bigint, \"null\", \"__resource_id__\">;\n\n/** Global resource id */\nexport type GlobalResourceId = Branded<bigint, \"global\", \"__resource_id__\">;\n\n/** Local resource id */\nexport type LocalResourceId = Branded<bigint, \"local\", \"__resource_id__\">;\n\n/** Any non-null resource id */\nexport type AnyResourceId = GlobalResourceId | LocalResourceId;\n\n/** All possible resource flavours */\nexport type OptionalAnyResourceId = NullResourceId | GlobalResourceId | LocalResourceId;\n\nexport const NullResourceId = 0n as NullResourceId;\n\nfunction isNullResourceId(resourceId: bigint | string): resourceId is NullResourceId {\n return resourceId === NullResourceId;\n}\n\nexport function isAnyResourceId(resourceId: bigint): resourceId is AnyResourceId {\n return resourceId !== 0n;\n}\n\n// see local / global resource logic below...\n\nexport type ResourceKind = \"Structural\" | \"Value\";\n\nexport type FieldType = \"Input\" | \"Output\" | \"Service\" | \"OTW\" | \"Dynamic\" | \"MTW\";\n\nexport type FutureFieldType = \"Output\" | \"Input\" | \"Service\";\n\nexport type FieldStatus = \"Empty\" | \"Assigned\" | \"Resolved\";\n\nexport interface ResourceType {\n readonly name: string;\n readonly version: string;\n}\n\nexport function resourceType(name: string, version: string): ResourceType {\n return { name, version };\n}\n\nexport function resourceTypeToString(rt: ResourceType): string {\n return `${rt.name}:${rt.version}`;\n}\n\nexport function parseResourceType(str: string): ResourceType {\n const [name, version] = str.split(\":\");\n return { name, version };\n}\n\nexport function resourceTypesEqual(type1: ResourceType, type2: ResourceType): boolean {\n return type1.name === type2.name && type1.version === type2.version;\n}\n\n/** Color proof used for resource creation requests (alias for ResourceSignature). */\nexport type ColorProof = ResourceSignature;\n\n/** Readonly fields here marks properties of resource that can't change according to pl's state machine. */\nexport type BasicResourceData = {\n readonly id: SignedResourceId;\n readonly originalResourceId: OptionalSignedResourceId;\n\n readonly kind: ResourceKind;\n readonly type: ResourceType;\n\n readonly data?: Uint8Array;\n\n readonly error: OptionalSignedResourceId;\n\n readonly inputsLocked: boolean;\n readonly outputsLocked: boolean;\n readonly resourceReady: boolean;\n\n /** This value is derived from resource state by the server and can be used as\n * a robust criteria to determine resource is in final state. */\n readonly final: boolean;\n};\n\nexport function extractBasicResourceData(rd: ResourceData): BasicResourceData {\n const {\n id,\n originalResourceId,\n kind,\n type,\n data,\n error,\n inputsLocked,\n outputsLocked,\n resourceReady,\n final,\n } = rd;\n return {\n id,\n originalResourceId,\n kind,\n type,\n data,\n error,\n inputsLocked,\n outputsLocked,\n resourceReady,\n final,\n };\n}\n\nexport const jsonToData = (data: unknown) => Buffer.from(JSON.stringify(data));\n\nexport const resDataToJson = (res: ResourceData) => cachedDeserialize(notEmpty(res.data));\n\nexport type ResourceData = BasicResourceData & {\n readonly fields: FieldData[];\n};\n\nexport function getField(r: ResourceData, name: string): FieldData {\n return notEmpty(r.fields.find((f) => f.name === name));\n}\n\nexport type FieldData = {\n readonly name: string;\n readonly type: FieldType;\n readonly status: FieldStatus;\n readonly value: OptionalSignedResourceId;\n readonly error: OptionalSignedResourceId;\n\n /** True if value the fields points to is in final state. */\n readonly valueIsFinal: boolean;\n};\n\n//\n// Local / Global ResourceId arithmetics\n//\n\n// Note: txId and other numerical values are made numbers but not bigint intentionally,\n// after implementing security model based on signed resource ids this will make\n// much more sense\n\nconst ResourceIdRootMask = 1n << 63n;\nconst ResourceIdLocalMask = 1n << 62n;\nconst NoFlagsIdMask = 0x3fffffffffffffffn;\nconst LocalResourceIdTxIdOffset = 24n;\nexport const MaxLocalId = 0xffffff;\nexport const MaxTxId = 0xffffffff;\n/** Mask valid after applying shift */\nconst TxIdMask = BigInt(MaxTxId);\nconst LocalIdMask = BigInt(MaxLocalId);\n\n// /** Basically removes embedded tx id */\n// const LocalIdCleanMask = 0xFF00000000FFFFFFn;\n\nexport function isRootResourceId(id: bigint) {\n return (id & ResourceIdRootMask) !== 0n;\n}\n\nexport function isLocalResourceId(id: bigint | string): id is LocalResourceId {\n if (typeof id === \"string\") {\n return false;\n }\n\n return (id & ResourceIdLocalMask) !== 0n;\n}\n\nexport function createLocalResourceId(\n isRoot: boolean,\n localCounterValue: number,\n localTxId: number,\n): LocalResourceId {\n if (\n localCounterValue > MaxLocalId ||\n localTxId > MaxTxId ||\n localCounterValue < 0 ||\n localTxId <= 0\n )\n throw Error(\"wrong local id or tx id\");\n return ((isRoot ? ResourceIdRootMask : 0n) |\n ResourceIdLocalMask |\n BigInt(localCounterValue) |\n (BigInt(localTxId) << LocalResourceIdTxIdOffset)) as LocalResourceId;\n}\n\nexport function createGlobalResourceId(isRoot: boolean, unmaskedId: bigint): GlobalResourceId {\n return ((isRoot ? ResourceIdRootMask : 0n) | unmaskedId) as GlobalResourceId;\n}\n\nexport function extractTxId(localResourceId: LocalResourceId): number {\n return Number((localResourceId >> LocalResourceIdTxIdOffset) & TxIdMask);\n}\n\nexport function checkLocalityOfResourceId(resourceId: AnyResourceId, expectedTxId: number): void {\n if (!isLocalResourceId(resourceId)) return;\n if (extractTxId(resourceId) !== expectedTxId)\n throw Error(\n \"local id from another transaction, globalize id before leaking it from the transaction\",\n );\n}\n\nexport function resourceIdToString(\n resourceId: OptionalAnyResourceId | OptionalSignedResourceId,\n): string {\n if (isSignedResourceId(resourceId)) {\n // Strip signature\n resourceId = anyResourceIdToBigint(resourceId) as GlobalResourceId;\n }\n\n if (isNullSignedResourceId(resourceId)) return \"XX:0x0\";\n if (isNullResourceId(resourceId)) return \"XX:0x0\";\n\n if (isLocalResourceId(resourceId))\n return (\n (isRootResourceId(resourceId) ? \"R\" : \"N\") +\n \"L:0x\" +\n (LocalIdMask & resourceId).toString(16) +\n \"[0x\" +\n extractTxId(resourceId).toString(16) +\n \"]\"\n );\n else\n return (\n (isRootResourceId(resourceId) ? \"R\" : \"N\") +\n \"G:0x\" +\n (NoFlagsIdMask & resourceId).toString(16)\n );\n}\n\nconst resourceIdRegexp =\n /^(?:(?<xx>XX)|(?<rn>[XRN])(?<lg>[XLG])):0x(?<rid>[0-9a-fA-F]+)(?:\\[0x(?<txid>[0-9a-fA-F]+)])?$/;\n\nexport function resourceIdFromString(str: string): OptionalAnyResourceId | undefined {\n const match = str.match(resourceIdRegexp);\n if (match === null) return undefined;\n const { xx, rn, lg, rid, txid } = match.groups!;\n if (xx) return NullResourceId;\n if (lg === \"L\")\n return createLocalResourceId(rn === \"R\", Number.parseInt(rid, 16), Number.parseInt(txid, 16));\n else return createGlobalResourceId(rn === \"R\", BigInt(\"0x\" + rid));\n}\n\nexport function anyResourceIdToBigint(resourceId: bigint | SignedResourceId): bigint {\n if (typeof resourceId !== \"string\") {\n return resourceId;\n }\n\n const parsed = parseSignedResourceId(resourceId);\n return parsed.globalId as bigint;\n}\n\nexport function stringifyWithResourceId(object: unknown): string {\n return JSON.stringify(object, (key, value) => {\n if (typeof value === \"bigint\") return resourceIdToString(value as OptionalAnyResourceId);\n if (isSignedResourceId(value)) return resourceIdToString(value);\n return value;\n });\n}\n\n/** Opaque authorization signature attached to a resource. */\nexport type ResourceSignature = Branded<Uint8Array, \"ResourceSignature\">;\n\n/**\n * Signed resource id is \"<global ID>|<resource signature hex>\", encoded as string\n * (e.g. \"NG:0x123EC|1234567890abcdef\")\n */\nexport type SignedResourceId = Branded<string, \"signed\", \"__signed_resource_id__\">;\n\nexport type NullSignedResourceId = Branded<string, \"null\", \"__signed_resource_id__\">;\n\nexport const NullSignedResourceId = \"\" as NullSignedResourceId;\n\n/** Nullable signed resource ID */\nexport type OptionalSignedResourceId = NullSignedResourceId | SignedResourceId;\n\nexport function isNullSignedResourceId(\n resourceId: bigint | string,\n): resourceId is NullSignedResourceId {\n return resourceId === NullSignedResourceId;\n}\n\nexport function isNotNullSignedResourceId(\n resourceId: OptionalSignedResourceId,\n): resourceId is SignedResourceId {\n return resourceId !== NullSignedResourceId;\n}\n\nexport function ensureSignedResourceIdNotNull(\n resourceId: OptionalSignedResourceId,\n): SignedResourceId {\n if (!isNotNullSignedResourceId(resourceId)) throw new Error(\"null resource id\");\n return resourceId;\n}\n\nexport function isSignedResourceId(resourceId: bigint | string): resourceId is SignedResourceId {\n return typeof resourceId === \"string\" && resourceId.includes(\"|\");\n}\n\n// Process-global switch. Set to false by LLPlClient.build() when the connected\n// backend predates resource signatures (~< 3.3.0). When false, asSignedResourceId\n// accepts ids minted with an empty signature. Single-backend-per-process only:\n// a Node process talking to mixed signed/unsigned backends will share last-wins\n// state. Desktop App is single-backend-per-process, so this is acceptable.\nlet resourceSignaturesRequired = true;\n\nexport function setResourceSignaturesRequired(required: boolean): void {\n resourceSignaturesRequired = required;\n}\n\n/** Validate a string as a SignedResourceId and return it with the branded type.\n * Requires the format \"<globalId>|<signatureHex>\". A non-empty signature is\n * required only when {@link areResourceSignaturesRequired} is true. */\nexport function asSignedResourceId(str: string): SignedResourceId {\n const pipeIdx = str.indexOf(\"|\");\n if (pipeIdx < 0) throw new Error(`Not a signed resource id (no '|' separator): ${str}`);\n if (pipeIdx === 0) throw new Error(`Signed resource id has empty globalId: ${str}`);\n if (resourceSignaturesRequired && pipeIdx === str.length - 1)\n throw new Error(`Signed resource id has empty signature: ${str}`);\n return str as SignedResourceId;\n}\n\n/** Encode resource signature to base64url for embedding in URL-based handles. */\nexport function signatureToBase64Url(sig?: ResourceSignature): string {\n return sig && sig.length > 0 ? Buffer.from(sig).toString(\"base64url\") : \"\";\n}\n\n/** Cast raw bytes to a branded ResourceSignature, returning undefined for empty/missing input. */\nexport function toResourceSignature(raw?: Uint8Array): ResourceSignature {\n return raw && raw.length > 0\n ? (raw as ResourceSignature)\n : (new Uint8Array(0) as ResourceSignature);\n}\n\n/** Decode base64url-encoded string back to a branded ResourceSignature. */\nexport function base64UrlToSignature(str: string): ResourceSignature {\n return toResourceSignature(Buffer.from(str, \"base64url\"))!;\n}\n\n/** Converts bigint global resource id and signature to a SignedResourceId string.\n * Format: \"<globalIdString>|<signatureHex>\" */\nexport function createSignedResourceId(\n globalId: bigint,\n signature?: ResourceSignature,\n): SignedResourceId {\n if (isLocalResourceId(globalId))\n throw new Error(`Local resource id: ${resourceIdToString(globalId)}`);\n if (isNullResourceId(globalId)) throw new Error(`Null resource id.`);\n\n const sigHex = signature ? Buffer.from(signature).toString(\"hex\") : \"\";\n return `${String(globalId)}|${sigHex}` as SignedResourceId;\n}\n\nexport function parseSignedResourceId(resourceId: SignedResourceId): {\n globalId: GlobalResourceId;\n signature: ResourceSignature;\n} {\n if (typeof resourceId !== \"string\") {\n throw new Error(`Not a signed resource id: ${resourceId}`);\n }\n\n const pipeIdx = resourceId.indexOf(\"|\");\n if (pipeIdx < 0) throw new Error(`Malformed signed resource id (no '|'): ${resourceId}`);\n\n const globalIdStr = resourceId.substring(0, pipeIdx);\n const signatureHex = resourceId.substring(pipeIdx + 1);\n\n const globalId = BigInt(globalIdStr);\n if (isNullSignedResourceId(globalId) || isLocalResourceId(globalId))\n throw new Error(`Invalid global id portion in signed resource id: ${globalIdStr}`);\n\n const signature: ResourceSignature = (\n signatureHex.length > 0 ? Buffer.from(signatureHex, \"hex\") : new Uint8Array(0)\n ) as ResourceSignature;\n\n return { globalId: globalId as GlobalResourceId, signature };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/core/types.ts"],"sourcesContent":["/* eslint-disable eslint-js/no-restricted-syntax -- this file is the canonical place to construct SignedResourceId values; outside callers must use asSignedResourceId(). */\n\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport { cachedDeserialize, notEmpty } from \"@milaboratories/ts-helpers\";\n\n/** Null resource id */\nexport type NullResourceId = Branded<bigint, \"null\", \"__resource_id__\">;\n\n/** Global resource id */\nexport type GlobalResourceId = Branded<bigint, \"global\", \"__resource_id__\">;\n\n/** Local resource id */\nexport type LocalResourceId = Branded<bigint, \"local\", \"__resource_id__\">;\n\n/** Any non-null resource id */\nexport type AnyResourceId = GlobalResourceId | LocalResourceId;\n\n/** All possible resource flavours */\nexport type OptionalAnyResourceId = NullResourceId | GlobalResourceId | LocalResourceId;\n\nexport const NullResourceId = 0n as NullResourceId;\n\nfunction isNullResourceId(resourceId: bigint | string): resourceId is NullResourceId {\n return resourceId === NullResourceId;\n}\n\nexport function isAnyResourceId(resourceId: bigint): resourceId is AnyResourceId {\n return resourceId !== 0n;\n}\n\n// see local / global resource logic below...\n\nexport type ResourceKind = \"Structural\" | \"Value\";\n\nexport type FieldType = \"Input\" | \"Output\" | \"Service\" | \"OTW\" | \"Dynamic\" | \"MTW\";\n\nexport type FutureFieldType = \"Output\" | \"Input\" | \"Service\";\n\nexport type FieldStatus = \"Empty\" | \"Assigned\" | \"Resolved\";\n\nexport interface ResourceType {\n readonly name: string;\n readonly version: string;\n}\n\nexport function resourceType(name: string, version: string): ResourceType {\n return { name, version };\n}\n\nexport function resourceTypeToString(rt: ResourceType): string {\n return `${rt.name}:${rt.version}`;\n}\n\nexport function parseResourceType(str: string): ResourceType {\n const [name, version] = str.split(\":\");\n return { name, version };\n}\n\nexport function resourceTypesEqual(type1: ResourceType, type2: ResourceType): boolean {\n return type1.name === type2.name && type1.version === type2.version;\n}\n\n/** Color proof used for resource creation requests (alias for ResourceSignature). */\nexport type ColorProof = ResourceSignature;\n\n/** Readonly fields here marks properties of resource that can't change according to pl's state machine. */\nexport type BasicResourceData = {\n readonly id: SignedResourceId;\n readonly originalResourceId: OptionalSignedResourceId;\n\n readonly kind: ResourceKind;\n readonly type: ResourceType;\n\n readonly data?: Uint8Array;\n\n readonly error: OptionalSignedResourceId;\n\n readonly inputsLocked: boolean;\n readonly outputsLocked: boolean;\n readonly resourceReady: boolean;\n\n /** This value is derived from resource state by the server and can be used as\n * a robust criteria to determine resource is in final state. */\n readonly final: boolean;\n};\n\nexport function extractBasicResourceData(rd: ResourceData): BasicResourceData {\n const {\n id,\n originalResourceId,\n kind,\n type,\n data,\n error,\n inputsLocked,\n outputsLocked,\n resourceReady,\n final,\n } = rd;\n return {\n id,\n originalResourceId,\n kind,\n type,\n data,\n error,\n inputsLocked,\n outputsLocked,\n resourceReady,\n final,\n };\n}\n\nexport const jsonToData = (data: unknown) => Buffer.from(JSON.stringify(data));\n\nexport const resDataToJson = (res: ResourceData) => cachedDeserialize(notEmpty(res.data));\n\nexport type ResourceData = BasicResourceData & {\n readonly fields: FieldData[];\n};\n\nexport function getField(r: ResourceData, name: string): FieldData {\n return notEmpty(r.fields.find((f) => f.name === name));\n}\n\nexport type FieldData = {\n readonly name: string;\n readonly type: FieldType;\n readonly status: FieldStatus;\n readonly value: OptionalSignedResourceId;\n readonly error: OptionalSignedResourceId;\n\n /** True if value the fields points to is in final state. */\n readonly valueIsFinal: boolean;\n};\n\n//\n// Local / Global ResourceId arithmetics\n//\n\n// Note: txId and other numerical values are made numbers but not bigint intentionally,\n// after implementing security model based on signed resource ids this will make\n// much more sense\n\nconst ResourceIdRootMask = 1n << 63n;\nconst ResourceIdLocalMask = 1n << 62n;\nconst NoFlagsIdMask = 0x3fffffffffffffffn;\nconst LocalResourceIdTxIdOffset = 24n;\nexport const MaxLocalId = 0xffffff;\nexport const MaxTxId = 0xffffffff;\n/** Mask valid after applying shift */\nconst TxIdMask = BigInt(MaxTxId);\nconst LocalIdMask = BigInt(MaxLocalId);\n\n// /** Basically removes embedded tx id */\n// const LocalIdCleanMask = 0xFF00000000FFFFFFn;\n\nexport function isRootResourceId(id: bigint) {\n return (id & ResourceIdRootMask) !== 0n;\n}\n\nexport function isLocalResourceId(id: bigint | string): id is LocalResourceId {\n if (typeof id === \"string\") {\n return false;\n }\n\n return (id & ResourceIdLocalMask) !== 0n;\n}\n\nexport function createLocalResourceId(\n isRoot: boolean,\n localCounterValue: number,\n localTxId: number,\n): LocalResourceId {\n if (\n localCounterValue > MaxLocalId ||\n localTxId > MaxTxId ||\n localCounterValue < 0 ||\n localTxId <= 0\n )\n throw Error(\"wrong local id or tx id\");\n return ((isRoot ? ResourceIdRootMask : 0n) |\n ResourceIdLocalMask |\n BigInt(localCounterValue) |\n (BigInt(localTxId) << LocalResourceIdTxIdOffset)) as LocalResourceId;\n}\n\nexport function createGlobalResourceId(isRoot: boolean, unmaskedId: bigint): GlobalResourceId {\n return ((isRoot ? ResourceIdRootMask : 0n) | unmaskedId) as GlobalResourceId;\n}\n\nexport function extractTxId(localResourceId: LocalResourceId): number {\n return Number((localResourceId >> LocalResourceIdTxIdOffset) & TxIdMask);\n}\n\nexport function checkLocalityOfResourceId(resourceId: AnyResourceId, expectedTxId: number): void {\n if (!isLocalResourceId(resourceId)) return;\n if (extractTxId(resourceId) !== expectedTxId)\n throw Error(\n \"local id from another transaction, globalize id before leaking it from the transaction\",\n );\n}\n\nexport function resourceIdToString(\n resourceId: OptionalAnyResourceId | OptionalSignedResourceId,\n): string {\n if (isSignedResourceId(resourceId)) {\n // Strip signature\n resourceId = anyResourceIdToBigint(resourceId) as GlobalResourceId;\n }\n\n if (isNullSignedResourceId(resourceId)) return \"XX:0x0\";\n if (isNullResourceId(resourceId)) return \"XX:0x0\";\n\n if (isLocalResourceId(resourceId))\n return (\n (isRootResourceId(resourceId) ? \"R\" : \"N\") +\n \"L:0x\" +\n (LocalIdMask & resourceId).toString(16) +\n \"[0x\" +\n extractTxId(resourceId).toString(16) +\n \"]\"\n );\n else\n return (\n (isRootResourceId(resourceId) ? \"R\" : \"N\") +\n \"G:0x\" +\n (NoFlagsIdMask & resourceId).toString(16)\n );\n}\n\nconst resourceIdRegexp =\n /^(?:(?<xx>XX)|(?<rn>[XRN])(?<lg>[XLG])):0x(?<rid>[0-9a-fA-F]+)(?:\\[0x(?<txid>[0-9a-fA-F]+)])?$/;\n\nexport function resourceIdFromString(str: string): OptionalAnyResourceId | undefined {\n const match = str.match(resourceIdRegexp);\n if (match === null) return undefined;\n const { xx, rn, lg, rid, txid } = match.groups!;\n if (xx) return NullResourceId;\n if (lg === \"L\")\n return createLocalResourceId(rn === \"R\", Number.parseInt(rid, 16), Number.parseInt(txid, 16));\n else return createGlobalResourceId(rn === \"R\", BigInt(\"0x\" + rid));\n}\n\nexport function anyResourceIdToBigint(resourceId: bigint | SignedResourceId): bigint {\n if (typeof resourceId !== \"string\") {\n return resourceId;\n }\n\n const parsed = parseSignedResourceId(resourceId);\n return parsed.globalId as bigint;\n}\n\nexport function stringifyWithResourceId(object: unknown): string {\n return JSON.stringify(object, (key, value) => {\n if (typeof value === \"bigint\") return resourceIdToString(value as OptionalAnyResourceId);\n if (isSignedResourceId(value)) return resourceIdToString(value);\n return value;\n });\n}\n\n/** Opaque authorization signature attached to a resource. */\nexport type ResourceSignature = Branded<Uint8Array, \"ResourceSignature\">;\n\n/**\n * Signed resource id is \"<global ID>|<resource signature hex>\", encoded as string\n * (e.g. \"NG:0x123EC|1234567890abcdef\")\n */\nexport type SignedResourceId = Branded<string, \"signed\", \"__signed_resource_id__\">;\n\nexport type NullSignedResourceId = Branded<string, \"null\", \"__signed_resource_id__\">;\n\nexport const NullSignedResourceId = \"\" as NullSignedResourceId;\n\n/** Nullable signed resource ID */\nexport type OptionalSignedResourceId = NullSignedResourceId | SignedResourceId;\n\nexport function isNullSignedResourceId(\n resourceId: bigint | string,\n): resourceId is NullSignedResourceId {\n return resourceId === NullSignedResourceId;\n}\n\nexport function isNotNullSignedResourceId(\n resourceId: OptionalSignedResourceId,\n): resourceId is SignedResourceId {\n return resourceId !== NullSignedResourceId;\n}\n\nexport function ensureSignedResourceIdNotNull(\n resourceId: OptionalSignedResourceId,\n): SignedResourceId {\n if (!isNotNullSignedResourceId(resourceId)) throw new Error(\"null resource id\");\n return resourceId;\n}\n\nexport function isSignedResourceId(resourceId: bigint | string): resourceId is SignedResourceId {\n return typeof resourceId === \"string\" && resourceId.includes(\"|\");\n}\n\n// Process-global switch. Set to false by LLPlClient.build() when the connected\n// backend predates resource signatures (~< 3.3.0). When false, asSignedResourceId\n// accepts ids minted with an empty signature. Single-backend-per-process only:\n// a Node process talking to mixed signed/unsigned backends will share last-wins\n// state. Desktop App is single-backend-per-process, so this is acceptable.\nlet resourceSignaturesRequired = true;\n\nexport function setResourceSignaturesRequired(required: boolean): void {\n resourceSignaturesRequired = required;\n}\n\n/** Validate a string as a SignedResourceId and return it with the branded type.\n * Requires the format \"<globalId>|<signatureHex>\". A non-empty signature is\n * required only when {@link areResourceSignaturesRequired} is true. */\nexport function asSignedResourceId(str: string): SignedResourceId {\n const pipeIdx = str.indexOf(\"|\");\n if (pipeIdx < 0) throw new Error(`Not a signed resource id (no '|' separator): ${str}`);\n if (pipeIdx === 0) throw new Error(`Signed resource id has empty globalId: ${str}`);\n if (resourceSignaturesRequired && pipeIdx === str.length - 1)\n throw new Error(`Signed resource id has empty signature: ${str}`);\n return str as SignedResourceId;\n}\n\n/** Encode resource signature to base64url for embedding in URL-based handles. */\nexport function signatureToBase64Url(sig?: ResourceSignature): string {\n return sig && sig.length > 0 ? Buffer.from(sig).toString(\"base64url\") : \"\";\n}\n\n/** Cast raw bytes to a branded ResourceSignature, returning undefined for empty/missing input. */\nexport function toResourceSignature(raw?: Uint8Array): ResourceSignature {\n return raw && raw.length > 0\n ? (raw as ResourceSignature)\n : (new Uint8Array(0) as ResourceSignature);\n}\n\n/** Decode base64url-encoded string back to a branded ResourceSignature. */\nexport function base64UrlToSignature(str: string): ResourceSignature {\n return toResourceSignature(Buffer.from(str, \"base64url\"))!;\n}\n\n/** Converts bigint global resource id and signature to a SignedResourceId string.\n * Format: \"<globalIdString>|<signatureHex>\" */\nexport function createSignedResourceId(\n globalId: bigint,\n signature?: ResourceSignature,\n): SignedResourceId {\n if (isLocalResourceId(globalId))\n throw new Error(`Local resource id: ${resourceIdToString(globalId)}`);\n if (isNullResourceId(globalId)) throw new Error(`Null resource id.`);\n\n const sigHex = signature ? Buffer.from(signature).toString(\"hex\") : \"\";\n return `${String(globalId)}|${sigHex}` as SignedResourceId;\n}\n\nexport function parseSignedResourceId(resourceId: SignedResourceId): {\n globalId: GlobalResourceId;\n signature: ResourceSignature;\n} {\n if (typeof resourceId !== \"string\") {\n throw new Error(`Not a signed resource id: ${resourceId}`);\n }\n\n const pipeIdx = resourceId.indexOf(\"|\");\n if (pipeIdx < 0) throw new Error(`Malformed signed resource id (no '|'): ${resourceId}`);\n\n const globalIdStr = resourceId.substring(0, pipeIdx);\n const signatureHex = resourceId.substring(pipeIdx + 1);\n\n const globalId = BigInt(globalIdStr);\n if (isNullSignedResourceId(globalId) || isLocalResourceId(globalId))\n throw new Error(`Invalid global id portion in signed resource id: ${globalIdStr}`);\n\n const signature: ResourceSignature = (\n signatureHex.length > 0 ? Buffer.from(signatureHex, \"hex\") : new Uint8Array(0)\n ) as ResourceSignature;\n\n return { globalId: globalId as GlobalResourceId, signature };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
require("../
|
|
2
|
-
const require_api = require("../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs");
|
|
1
|
+
require("../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs");
|
|
3
2
|
const require_errors = require("./errors.cjs");
|
|
4
3
|
const require_ll_client = require("./ll_client.cjs");
|
|
5
4
|
let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
|
|
@@ -43,7 +42,7 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
|
|
|
43
42
|
for (const method of this.ll.authMethodsSync.methods) {
|
|
44
43
|
if (method.method.oneofKind !== "sso") continue;
|
|
45
44
|
const sso = method.method.sso;
|
|
46
|
-
if (sso.flowType !==
|
|
45
|
+
if (sso.flowType !== 0) throw new Error(`ssoConfig: unsupported SSO flow type ${sso.flowType}`);
|
|
47
46
|
return {
|
|
48
47
|
id: method.id,
|
|
49
48
|
description: method.description,
|
|
@@ -56,6 +55,7 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
|
|
|
56
55
|
subjectTokenSource: sso.subjectTokenSource,
|
|
57
56
|
userIdClaim: sso.userIdClaim,
|
|
58
57
|
groupsClaim: sso.groupsClaim,
|
|
58
|
+
accessType: sso.accessType || void 0,
|
|
59
59
|
flowType: "public_pkce"
|
|
60
60
|
};
|
|
61
61
|
}
|
|
@@ -90,11 +90,12 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
|
|
|
90
90
|
/** Request fresh server-issued login material. v1 only emits the public-PKCE flow;
|
|
91
91
|
* desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */
|
|
92
92
|
async beginSSOLogin() {
|
|
93
|
-
const
|
|
93
|
+
const attempt = await this.ll.beginSSOLogin();
|
|
94
94
|
return {
|
|
95
95
|
flow: "public_pkce",
|
|
96
|
-
nonce,
|
|
97
|
-
expiresAt
|
|
96
|
+
nonce: attempt.nonce,
|
|
97
|
+
expiresAt: attempt.expiresAt,
|
|
98
|
+
clientSecret: attempt.clientSecret
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
101
|
/** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unauth_client.cjs","names":["LLPlClient","
|
|
1
|
+
{"version":3,"file":"unauth_client.cjs","names":["LLPlClient","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 /** OIDC `access_type` auth-request param (\"online\" | \"offline\"). Google-specific:\n * \"offline\" makes Google issue a refresh token; other IdPs ignore it. Absent when\n * the server does not set it — never send an empty string to the IdP. */\n accessType?: string;\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 /** Confidential-client secret for the IdP token exchange; absent for public clients.\n * Google's OIDC has no public-client mode — it requires a client_secret at the token\n * endpoint even with PKCE — so for Google the backend forwards its secret here and the\n * desktop exchanges the code as a confidential client. */\n clientSecret?: string;\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 accessType: sso.accessType || undefined,\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 attempt = await this.ll.beginSSOLogin();\n return {\n flow: \"public_pkce\",\n nonce: attempt.nonce,\n expiresAt: attempt.expiresAt,\n clientSecret: attempt.clientSecret,\n };\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":"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"}
|
|
@@ -24,6 +24,10 @@ type SSOAuthMethod = {
|
|
|
24
24
|
userIdClaim: string;
|
|
25
25
|
groupsClaim: string;
|
|
26
26
|
flowType: SSOFlowType;
|
|
27
|
+
/** OIDC `access_type` auth-request param ("online" | "offline"). Google-specific:
|
|
28
|
+
* "offline" makes Google issue a refresh token; other IdPs ignore it. Absent when
|
|
29
|
+
* the server does not set it — never send an empty string to the IdP. */
|
|
30
|
+
accessType?: string;
|
|
27
31
|
};
|
|
28
32
|
/** Server-issued material returned by {@link UnauthenticatedPlClient.beginSSOLogin}.
|
|
29
33
|
* Discriminated on `flow`; future flow variants extend the union. */
|
|
@@ -31,6 +35,11 @@ type SSOLoginAttempt = {
|
|
|
31
35
|
flow: "public_pkce";
|
|
32
36
|
nonce: string;
|
|
33
37
|
expiresAt: Date;
|
|
38
|
+
/** Confidential-client secret for the IdP token exchange; absent for public clients.
|
|
39
|
+
* Google's OIDC has no public-client mode — it requires a client_secret at the token
|
|
40
|
+
* endpoint even with PKCE — so for Google the backend forwards its secret here and the
|
|
41
|
+
* desktop exchanges the code as a confidential client. */
|
|
42
|
+
clientSecret?: string;
|
|
34
43
|
};
|
|
35
44
|
/** Primarily used for initial authentication (login) */
|
|
36
45
|
declare class UnauthenticatedPlClient {
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,WAAW;EAHrB;;;EAOA,UAAA;AAAA;;;KAKU,eAAA;EACV,IAAA;EACA,KAAA;EACA,SAAA,EAAW,IAAI;EAAA;;;;EAKf,YAAA;AAAA;;cAIW,uBAAA;EAAA,SACK,EAAA,EAAI,UAAA;EAAA,QAEb,WAAA;EAAA,OAIa,KAAA,CAClB,eAAA,EAAiB,cAAA,WACjB,GAAA;IAAQ,MAAA,GAAS,QAAA;EAAA,IAChB,OAAA,CAAQ,uBAAA;EAKE,IAAA,IAAQ,OAAA,CAAQ,4BAAA;EAIhB,WAAA,IAAe,OAAA,CAAQ,4BAAA;EAIvB,WAAA,IAAe,OAAA;EAIrB,aAAA,CAAc,UAAA,EAAY,iBAAA;EAZZ;;;EAAA,IAmBV,oBAAA;IAA0B,KAAA;IAAgB,KAAA;EAAA;EA+CD;;EApC7C,SAAA,IAAa,aAAA;EA2E4B;;;;;;;;;;EAvCnC,KAAA,CAAM,IAAA,UAAc,QAAA,WAAmB,OAAA,CAAQ,eAAA;EAxElD;;EAoGG,aAAA,IAAiB,OAAA,CAAQ,eAAA;EAnGnC;EA8GU,QAAA,CAAS,OAAA;IAAW,aAAA,EAAe,UAAA;EAAA,IAAe,OAAA,CAAQ,eAAA;AAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js";
|
|
2
2
|
import { UnauthenticatedError } from "./errors.js";
|
|
3
3
|
import { LLPlClient } from "./ll_client.js";
|
|
4
4
|
import { notEmpty } from "@milaboratories/ts-helpers";
|
|
@@ -42,7 +42,7 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
|
|
|
42
42
|
for (const method of this.ll.authMethodsSync.methods) {
|
|
43
43
|
if (method.method.oneofKind !== "sso") continue;
|
|
44
44
|
const sso = method.method.sso;
|
|
45
|
-
if (sso.flowType !==
|
|
45
|
+
if (sso.flowType !== 0) throw new Error(`ssoConfig: unsupported SSO flow type ${sso.flowType}`);
|
|
46
46
|
return {
|
|
47
47
|
id: method.id,
|
|
48
48
|
description: method.description,
|
|
@@ -55,6 +55,7 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
|
|
|
55
55
|
subjectTokenSource: sso.subjectTokenSource,
|
|
56
56
|
userIdClaim: sso.userIdClaim,
|
|
57
57
|
groupsClaim: sso.groupsClaim,
|
|
58
|
+
accessType: sso.accessType || void 0,
|
|
58
59
|
flowType: "public_pkce"
|
|
59
60
|
};
|
|
60
61
|
}
|
|
@@ -89,11 +90,12 @@ var UnauthenticatedPlClient = class UnauthenticatedPlClient {
|
|
|
89
90
|
/** Request fresh server-issued login material. v1 only emits the public-PKCE flow;
|
|
90
91
|
* desktop MUST place the returned `nonce` verbatim into the OIDC auth-request. */
|
|
91
92
|
async beginSSOLogin() {
|
|
92
|
-
const
|
|
93
|
+
const attempt = await this.ll.beginSSOLogin();
|
|
93
94
|
return {
|
|
94
95
|
flow: "public_pkce",
|
|
95
|
-
nonce,
|
|
96
|
-
expiresAt
|
|
96
|
+
nonce: attempt.nonce,
|
|
97
|
+
expiresAt: attempt.expiresAt,
|
|
98
|
+
clientSecret: attempt.clientSecret
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
/** Forward the verbatim IdP `/token` response body and receive a Platforma JWT. */
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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 /** OIDC `access_type` auth-request param (\"online\" | \"offline\"). Google-specific:\n * \"offline\" makes Google issue a refresh token; other IdPs ignore it. Absent when\n * the server does not set it — never send an empty string to the IdP. */\n accessType?: string;\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 /** Confidential-client secret for the IdP token exchange; absent for public clients.\n * Google's OIDC has no public-client mode — it requires a client_secret at the token\n * endpoint even with PKCE — so for Google the backend forwards its secret here and the\n * desktop exchanges the code as a confidential client. */\n clientSecret?: string;\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 accessType: sso.accessType || undefined,\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 attempt = await this.ll.beginSSOLogin();\n return {\n flow: \"public_pkce\",\n nonce: attempt.nonce,\n expiresAt: attempt.expiresAt,\n clientSecret: attempt.clientSecret,\n };\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":";;;;;;AAmDA,IAAa,0BAAb,MAAa,wBAAwB;CACnC;CAEA,YAAoB,IAAgB;EAClC,KAAK,KAAK;CACZ;CAEA,aAAoB,MAClB,iBACA,KACkC;EAElC,OAAO,IAAI,wBAAwB,MADlB,WAAW,MAAM,iBAAiB,GAAG,CACjB;CACvC;CAEA,MAAa,OAA8C;EACzD,OAAO,MAAM,KAAK,GAAG,KAAK;CAC5B;CAEA,MAAa,cAAqD;EAChE,OAAO,MAAM,KAAK,GAAG,YAAY;CACnC;CAEA,MAAa,cAAgC;EAC3C,QAAQ,MAAM,KAAK,YAAY,EAAA,CAAG,QAAQ,SAAS;CACrD;CAEA,cAAqB,YAAwC;EAC3D,OAAO,KAAK,GAAG,cAAc,UAAU;CACzC;;;;CAKA,IAAW,uBAA2D;EACpE,MAAM,SAAS;GAAE,OAAO;GAAO,OAAO;EAAM;EAC5C,KAAK,MAAM,KAAK,KAAK,GAAG,gBAAgB,SACtC,IAAI,EAAE,OAAO,cAAc,SAAS,OAAO,QAAQ;OAC9C,IAAI,EAAE,OAAO,cAAc,SAAS,OAAO,QAAQ;EAE1D,OAAO;CACT;;;CAIA,YAA8C;EAC5C,KAAK,MAAM,UAAU,KAAK,GAAG,gBAAgB,SAAS;GACpD,IAAI,OAAO,OAAO,cAAc,OAAO;GACvC,MAAM,MAAM,OAAO,OAAO;GAC1B,IAAI,IAAI,aAAA,GACN,MAAM,IAAI,MAAM,wCAAwC,IAAI,UAAU;GAExE,OAAO;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,YAAY,IAAI,cAAc,KAAA;IAC9B,UAAU;GACZ;EACF;CAEF;;;;;;;;;;;CAYA,MAAa,MAAM,MAAc,UAA4C;EAC3E,IAAI;GACF,IAAI;GACJ,IAAI,KAAK,GAAG,cAAc,SAAS,GAAG;IACpC,MAAM,UAAU,KAAK;IACrB,IAAI,QAAQ,OACV,QAAQ,MAAM,KAAK,GAAG,WAAW,MAAM,QAAQ;SAC1C,IAAI,QAAQ,OACjB,QAAQ,MAAM,KAAK,GAAG,eAAe,QAAQ;SAE7C,MAAM,IAAI,MAAM,wDAAwD;GAE5E,OACE,QAAQ,MAAM,KAAK,GAAG,YAAY,OAAO,KAAK,GAAG,KAAK,cAAc,GAAG,EACrE,eAAe,WAAW,OAAO,KAAK,OAAO,MAAM,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAChF,CAAC;GAEH,MAAM,WAAW,SAAS,KAAK;GAC/B,IAAI,aAAa,IAAI,MAAM,IAAI,MAAM,aAAa;GAClD,OAAO,EAAE,SAAS;EACpB,SAAS,GAAQ;GACf,IAAI,EAAE,SAAS,mBAAmB,MAAM,IAAI,qBAAqB,EAAE,OAAO;GAC1E,MAAM,IAAI,MAAM,CAAC;EACnB;CACF;;;CAIA,MAAa,gBAA0C;EACrD,MAAM,UAAU,MAAM,KAAK,GAAG,cAAc;EAC5C,OAAO;GACL,MAAM;GACN,OAAO,QAAQ;GACf,WAAW,QAAQ;GACnB,cAAc,QAAQ;EACxB;CACF;;CAGA,MAAa,SAAS,SAAkE;EACtF,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,GAAG,SAAS,QAAQ,aAAa;GAC7D,IAAI,aAAa,IAAI,MAAM,IAAI,MAAM,aAAa;GAClD,OAAO,EAAE,SAAS;EACpB,SAAS,GAAQ;GACf,IAAI,EAAE,SAAS,mBAAmB,MAAM,IAAI,qBAAqB,EAAE,OAAO;GAC1E,MAAM,IAAI,MAAM,CAAC;EACnB;CACF;AACF"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require("../_virtual/_rolldown/runtime.cjs");
|
|
2
1
|
const require_types = require("./types.cjs");
|
|
3
2
|
const require_errors = require("./errors.cjs");
|
|
4
3
|
const require_transaction = require("./transaction.cjs");
|
|
@@ -18,6 +17,9 @@ const LsProviderFieldPrefix = "storage/";
|
|
|
18
17
|
* 3. Named resource lookup/creation via transaction (legacy)
|
|
19
18
|
*/
|
|
20
19
|
var UserResources = class {
|
|
20
|
+
ll;
|
|
21
|
+
runTx;
|
|
22
|
+
authUser;
|
|
21
23
|
backendCapability;
|
|
22
24
|
constructor(ll, runTx, authUser) {
|
|
23
25
|
this.ll = ll;
|
|
@@ -70,6 +72,60 @@ var UserResources = class {
|
|
|
70
72
|
if (this.backendCapability !== "legacy") return await this.getDataLibrariesViaList(opts);
|
|
71
73
|
return await this.getDataLibrariesViaLegacy();
|
|
72
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Discovers shared resources of a given type granted to the user, as signed resource ids.
|
|
77
|
+
*
|
|
78
|
+
* The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the
|
|
79
|
+
* `ListUserResources` gRPC stream and returns every `sharedResource` entry whose
|
|
80
|
+
* `resourceType.name` matches `resourceTypeName`. Matching is by name only — the
|
|
81
|
+
* version is ignored (permissive, survives schema bumps).
|
|
82
|
+
*
|
|
83
|
+
* gRPC-only: `ListUserResources` throws on a REST-connected client
|
|
84
|
+
* ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.
|
|
85
|
+
*/
|
|
86
|
+
async listSharedResourcesByType(resourceTypeName, opts = {}) {
|
|
87
|
+
const responses = await this.ll.listUserResources({ login: opts.login });
|
|
88
|
+
const result = [];
|
|
89
|
+
for (const msg of responses) {
|
|
90
|
+
if (msg.entry.oneofKind !== "sharedResource") continue;
|
|
91
|
+
const sr = msg.entry.sharedResource;
|
|
92
|
+
if (!sr.resourceType) continue;
|
|
93
|
+
if (sr.resourceType.name !== resourceTypeName) continue;
|
|
94
|
+
result.push(require_types.createSignedResourceId(sr.resourceId, require_types.toResourceSignature(sr.resourceSignature)));
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Enumerates the recipients of a single resource — the donor-side "who did I
|
|
100
|
+
* share with" view.
|
|
101
|
+
*
|
|
102
|
+
* Takes a signed, writable resource handle: the backend gates grant listing on a
|
|
103
|
+
* signed resource id with writable permission, so only the resource's owner can
|
|
104
|
+
* call it. An everyone-grant surfaces with `user` matching the backend's
|
|
105
|
+
* everyone-sentinel — test with {@link isEveryoneUserLogin} (from "./transaction");
|
|
106
|
+
* the caller maps that to "*".
|
|
107
|
+
*
|
|
108
|
+
* Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on
|
|
109
|
+
* every wire protocol — including REST, where the standalone server-streaming
|
|
110
|
+
* `ListGrants` RPC has no binding.
|
|
111
|
+
*/
|
|
112
|
+
async listGrants(resourceId) {
|
|
113
|
+
return (await this.runTx("ListGrants", false, "", (tx) => tx.listGrants(resourceId))).map((grant) => ({
|
|
114
|
+
user: grant.user,
|
|
115
|
+
writable: grant.permissions?.writable ?? false,
|
|
116
|
+
grantedBy: grant.grantedBy,
|
|
117
|
+
grantedAt: Number(grant.grantedAt)
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Lists the logins of users known to the server, for the recipient picker. A user becomes
|
|
122
|
+
* known on first login; provisioned users who have never logged in do not appear. gRPC-only
|
|
123
|
+
* (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a
|
|
124
|
+
* REST-connected client, so callers gate on the `userListing:v1` capability.
|
|
125
|
+
*/
|
|
126
|
+
async listUsers() {
|
|
127
|
+
return (await this.ll.listUsers()).map((user) => ({ login: user.login }));
|
|
128
|
+
}
|
|
73
129
|
async getUserRootViaRpc(opts = {}) {
|
|
74
130
|
const resp = await this.ll.getUserRoot({
|
|
75
131
|
login: opts.login,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user_resources.cjs","names":["isUnimplementedError","createSignedResourceId","toResourceSignature","ClientRoot","toGlobalResourceId","isNotNullSignedResourceId"],"sources":["../../src/core/user_resources.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { LLPlClient } from \"./ll_client\";\nimport type { PlTransaction } from \"./transaction\";\nimport { toGlobalResourceId } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId, ResourceType } from \"./types\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n toResourceSignature,\n} from \"./types\";\nimport { isUnimplementedError } from \"./errors\";\nimport { ClientRoot } from \"../helpers/pl\";\n\nconst AnonymousClientRoot = \"AnonymousRoot\";\nconst LsStorageTypePrefix = \"LS/\"; // implements ls API in particular storage\nconst LsProviderFieldPrefix = \"storage/\"; // provides access to storages list\n\n/** Information about a single data library (LS storage). */\nexport interface StorageInfo {\n /** Machine-stable identifier, e.g. \"library\". Used for filtering and map keys. */\n readonly storageId: string;\n /** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */\n readonly storageName: string;\n /** Signed resource ID for this storage resource. */\n readonly resourceId: SignedResourceId;\n /** Full resource type including correct version (\"1\" or \"2\"). */\n readonly resourceType: ResourceType;\n}\n\n/** V2 LsStorage resource JSON data shape (contract with backend). */\ninterface LsStorageV2Data {\n storageName: string;\n storageID: string;\n}\n\n/**\n * Callback type for running transactions. Matches PlClient._withTx signature\n * so the index can run transactions before PlClient is fully initialized.\n */\nexport type TxRunner = <T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalSignedResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n) => Promise<T>;\n\ntype BackendCapability = \"getUserRoot\" | \"listUserResources\" | \"legacy\";\n\n/**\n * Abstracts user resource discovery with backward compatibility.\n *\n * Detects backend capability on the first getUserRoot() call and remembers\n * the result. Three-tier fallback:\n * 1. getUserRoot RPC (newest, supports createIfNotExists)\n * 2. listUserResources RPC (streams all resources, picks userRoot)\n * 3. Named resource lookup/creation via transaction (legacy)\n */\nexport class UserResources {\n private backendCapability: BackendCapability | undefined;\n\n constructor(\n private readonly ll: LLPlClient,\n private readonly runTx: TxRunner,\n public readonly authUser: string | null,\n ) {}\n\n /**\n * Returns the user's root resource ID.\n *\n * On first call, detects backend capability by trying methods in order:\n * 1. getUserRoot RPC (newest)\n * 2. listUserResources RPC\n * 3. Named resource lookup/creation via transaction (legacy)\n */\n async getUserRoot(opts: { login?: string; createIfNotExists: true }): Promise<SignedResourceId>;\n async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n if (this.backendCapability === undefined) {\n return await this.detectAndGetUserRoot(opts);\n }\n return await this.getUserRootWith(this.backendCapability, opts);\n }\n\n private async detectAndGetUserRoot(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async detectAndGetUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async detectAndGetUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n // 1. Try getUserRoot RPC\n try {\n const root = await this.getUserRootViaRpc(opts);\n this.backendCapability = \"getUserRoot\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 2. Try listUserResources\n try {\n const root = await this.getUserRootViaList(opts);\n this.backendCapability = \"listUserResources\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 3. Legacy fallback\n this.backendCapability = \"legacy\";\n return await this.getUserRootViaLegacy(opts);\n }\n\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists: true },\n ): Promise<SignedResourceId>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts?: { login?: string; createIfNotExists?: boolean },\n ): Promise<SignedResourceId | undefined>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n switch (capability) {\n case \"getUserRoot\":\n return await this.getUserRootViaRpc(opts);\n case \"listUserResources\":\n return await this.getUserRootViaList(opts);\n case \"legacy\":\n return await this.getUserRootViaLegacy(opts);\n }\n }\n\n /**\n * Returns all data libraries the user has access to.\n * Always fetches fresh from the server (no caching).\n */\n async getDataLibraries(\n opts: { login?: string; createUserRootIfNotExists?: boolean } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n if (this.backendCapability === undefined) {\n // First call — detect backend capability\n try {\n const libs = await this.getDataLibrariesViaList(opts);\n // getUserRoot RPC doesn't return libraries, but listUserResources does;\n // record at least \"listUserResources\" so future getUserRoot calls don't re-detect.\n this.backendCapability = \"listUserResources\";\n return libs;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n this.backendCapability = \"legacy\";\n return await this.getDataLibrariesViaLegacy();\n }\n }\n\n // A server that supports getUserRoot definitely supports listUserResources.\n if (this.backendCapability !== \"legacy\") {\n return await this.getDataLibrariesViaList(opts);\n }\n return await this.getDataLibrariesViaLegacy();\n }\n\n private async getUserRootViaRpc(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaRpc(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaRpc(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const resp = await this.ll.getUserRoot({\n login: opts.login,\n createIfNotExists: opts.createIfNotExists,\n });\n if (resp.userRoot === undefined) {\n if (!opts.createIfNotExists) return undefined;\n throw new Error(\"getUserRoot returned no userRoot entry\");\n }\n return createSignedResourceId(\n resp.userRoot.resourceId,\n toResourceSignature(resp.userRoot.resourceSignature),\n );\n }\n\n private async getUserRootViaList(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaList(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaList(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const responses = await this.ll.listUserResources({ login: opts.login, limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return createSignedResourceId(\n msg.entry.userRoot.resourceId,\n toResourceSignature(msg.entry.userRoot.resourceSignature),\n );\n }\n }\n\n if (opts.createIfNotExists) {\n throw new Error(\"listUserResources returned no userRoot entry\");\n }\n\n return undefined;\n }\n\n private async getDataLibrariesViaList(\n opts: { login?: string } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n // Collect all LS/* shared resources, separating V1 and V2\n const v1Entries: StorageInfo[] = [];\n const v2ResourceIds: { resourceId: SignedResourceId; resourceType: ResourceType }[] = [];\n\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n\n if (!sr.resourceType) continue;\n const typeName = sr.resourceType.name;\n const typeVersion = sr.resourceType.version;\n if (!typeName.startsWith(LsStorageTypePrefix)) continue;\n\n const rId = createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature));\n const rType: ResourceType = { name: typeName, version: typeVersion };\n\n if (typeVersion === \"2\") {\n v2ResourceIds.push({ resourceId: rId, resourceType: rType });\n } else {\n // V1 or unknown version: derive storageId from type name\n const storageId = typeName.substring(LsStorageTypePrefix.length);\n v1Entries.push({\n storageId,\n storageName: storageId,\n resourceId: rId,\n resourceType: rType,\n });\n }\n }\n\n // Read V2 resource data in a single transaction\n let v2Entries: StorageInfo[] = [];\n if (v2ResourceIds.length > 0) {\n v2Entries = await this.runTx(\n \"ReadLsStorageV2Data\",\n false,\n NullSignedResourceId,\n async (tx) => {\n const entries: StorageInfo[] = [];\n for (const { resourceId, resourceType } of v2ResourceIds) {\n const rd = await tx.getResourceData(resourceId, false);\n if (rd.data) {\n const v2Data = JSON.parse(Buffer.from(rd.data).toString(\"utf-8\")) as LsStorageV2Data;\n entries.push({\n storageId: v2Data.storageID,\n storageName: v2Data.storageName,\n resourceId,\n resourceType,\n });\n }\n }\n return entries;\n },\n );\n }\n\n const result = new Map<string, StorageInfo>();\n for (const entry of [...v1Entries, ...v2Entries]) {\n result.set(entry.storageId, entry);\n }\n return result;\n }\n\n private async getUserRootViaLegacy(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaLegacy(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaLegacy(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const login = opts.login ?? this.authUser;\n const mainRootName =\n login === null ? AnonymousClientRoot : createHash(\"sha256\").update(login).digest(\"hex\");\n\n return await this.runTx(\"initialization\", true, NullSignedResourceId, async (tx) => {\n if (await tx.checkResourceNameExists(mainRootName)) {\n return await tx.getResourceByName(mainRootName);\n }\n\n if (!opts.createIfNotExists) {\n return undefined;\n }\n\n const mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n });\n }\n\n private async getDataLibrariesViaLegacy(): Promise<ReadonlyMap<string, StorageInfo>> {\n return await this.runTx(\"GetAvailableStorageIds\", false, NullSignedResourceId, async (tx) => {\n const lsProviderId = await tx.getResourceByName(\"LSProvider\");\n const provider = await tx.getResourceData(lsProviderId, true);\n\n const result = new Map<string, StorageInfo>();\n for (const field of provider.fields) {\n if (field.type !== \"Dynamic\" || !isNotNullSignedResourceId(field.value)) continue;\n if (!field.name.startsWith(LsProviderFieldPrefix)) continue;\n\n const storageId = field.name.substring(LsProviderFieldPrefix.length);\n result.set(storageId, {\n storageId,\n storageName: storageId,\n resourceId: field.value,\n resourceType: { name: `${LsStorageTypePrefix}${storageId}`, version: \"1\" },\n });\n }\n return result;\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"user_resources.cjs","names":["isUnimplementedError","createSignedResourceId","toResourceSignature","ClientRoot","toGlobalResourceId","isNotNullSignedResourceId"],"sources":["../../src/core/user_resources.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { LLPlClient } from \"./ll_client\";\nimport type { PlTransaction } from \"./transaction\";\nimport { toGlobalResourceId } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId, ResourceType } from \"./types\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n toResourceSignature,\n} from \"./types\";\nimport { isUnimplementedError } from \"./errors\";\nimport { ClientRoot } from \"../helpers/pl\";\n\nconst AnonymousClientRoot = \"AnonymousRoot\";\nconst LsStorageTypePrefix = \"LS/\"; // implements ls API in particular storage\nconst LsProviderFieldPrefix = \"storage/\"; // provides access to storages list\n\n/** One recipient of a resource, as returned by {@link UserResources.listGrants}. */\nexport interface GrantEntry {\n /** Login the grant targets. An everyone-grant surfaces here as the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\"); callers\n * map it to \"*\". */\n readonly user: string;\n /** True for a writable grant (copy / collaboration), false for read-only. */\n readonly writable: boolean;\n /** Login of the user who created the grant. */\n readonly grantedBy: string;\n /** When the grant was created (ms epoch). */\n readonly grantedAt: number;\n}\n\n/** One user known to the server, as returned by {@link UserResources.listUsers}. */\nexport interface UserEntry {\n /** Stable identifier of the user; the grant target and `GetUserRoot` key. */\n readonly login: string;\n}\n\n/** Information about a single data library (LS storage). */\nexport interface StorageInfo {\n /** Machine-stable identifier, e.g. \"library\". Used for filtering and map keys. */\n readonly storageId: string;\n /** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */\n readonly storageName: string;\n /** Signed resource ID for this storage resource. */\n readonly resourceId: SignedResourceId;\n /** Full resource type including correct version (\"1\" or \"2\"). */\n readonly resourceType: ResourceType;\n}\n\n/** V2 LsStorage resource JSON data shape (contract with backend). */\ninterface LsStorageV2Data {\n storageName: string;\n storageID: string;\n}\n\n/**\n * Callback type for running transactions. Matches PlClient._withTx signature\n * so the index can run transactions before PlClient is fully initialized.\n */\nexport type TxRunner = <T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalSignedResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n) => Promise<T>;\n\ntype BackendCapability = \"getUserRoot\" | \"listUserResources\" | \"legacy\";\n\n/**\n * Abstracts user resource discovery with backward compatibility.\n *\n * Detects backend capability on the first getUserRoot() call and remembers\n * the result. Three-tier fallback:\n * 1. getUserRoot RPC (newest, supports createIfNotExists)\n * 2. listUserResources RPC (streams all resources, picks userRoot)\n * 3. Named resource lookup/creation via transaction (legacy)\n */\nexport class UserResources {\n private backendCapability: BackendCapability | undefined;\n\n constructor(\n private readonly ll: LLPlClient,\n private readonly runTx: TxRunner,\n public readonly authUser: string | null,\n ) {}\n\n /**\n * Returns the user's root resource ID.\n *\n * On first call, detects backend capability by trying methods in order:\n * 1. getUserRoot RPC (newest)\n * 2. listUserResources RPC\n * 3. Named resource lookup/creation via transaction (legacy)\n */\n async getUserRoot(opts: { login?: string; createIfNotExists: true }): Promise<SignedResourceId>;\n async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n if (this.backendCapability === undefined) {\n return await this.detectAndGetUserRoot(opts);\n }\n return await this.getUserRootWith(this.backendCapability, opts);\n }\n\n private async detectAndGetUserRoot(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async detectAndGetUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async detectAndGetUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n // 1. Try getUserRoot RPC\n try {\n const root = await this.getUserRootViaRpc(opts);\n this.backendCapability = \"getUserRoot\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 2. Try listUserResources\n try {\n const root = await this.getUserRootViaList(opts);\n this.backendCapability = \"listUserResources\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 3. Legacy fallback\n this.backendCapability = \"legacy\";\n return await this.getUserRootViaLegacy(opts);\n }\n\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists: true },\n ): Promise<SignedResourceId>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts?: { login?: string; createIfNotExists?: boolean },\n ): Promise<SignedResourceId | undefined>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n switch (capability) {\n case \"getUserRoot\":\n return await this.getUserRootViaRpc(opts);\n case \"listUserResources\":\n return await this.getUserRootViaList(opts);\n case \"legacy\":\n return await this.getUserRootViaLegacy(opts);\n }\n }\n\n /**\n * Returns all data libraries the user has access to.\n * Always fetches fresh from the server (no caching).\n */\n async getDataLibraries(\n opts: { login?: string; createUserRootIfNotExists?: boolean } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n if (this.backendCapability === undefined) {\n // First call — detect backend capability\n try {\n const libs = await this.getDataLibrariesViaList(opts);\n // getUserRoot RPC doesn't return libraries, but listUserResources does;\n // record at least \"listUserResources\" so future getUserRoot calls don't re-detect.\n this.backendCapability = \"listUserResources\";\n return libs;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n this.backendCapability = \"legacy\";\n return await this.getDataLibrariesViaLegacy();\n }\n }\n\n // A server that supports getUserRoot definitely supports listUserResources.\n if (this.backendCapability !== \"legacy\") {\n return await this.getDataLibrariesViaList(opts);\n }\n return await this.getDataLibrariesViaLegacy();\n }\n\n /**\n * Discovers shared resources of a given type granted to the user, as signed resource ids.\n *\n * The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the\n * `ListUserResources` gRPC stream and returns every `sharedResource` entry whose\n * `resourceType.name` matches `resourceTypeName`. Matching is by name only — the\n * version is ignored (permissive, survives schema bumps).\n *\n * gRPC-only: `ListUserResources` throws on a REST-connected client\n * ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.\n */\n async listSharedResourcesByType(\n resourceTypeName: string,\n opts: { login?: string } = {},\n ): Promise<SignedResourceId[]> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n const result: SignedResourceId[] = [];\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n if (!sr.resourceType) continue;\n if (sr.resourceType.name !== resourceTypeName) continue;\n result.push(createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature)));\n }\n return result;\n }\n\n /**\n * Enumerates the recipients of a single resource — the donor-side \"who did I\n * share with\" view.\n *\n * Takes a signed, writable resource handle: the backend gates grant listing on a\n * signed resource id with writable permission, so only the resource's owner can\n * call it. An everyone-grant surfaces with `user` matching the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\");\n * the caller maps that to \"*\".\n *\n * Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on\n * every wire protocol — including REST, where the standalone server-streaming\n * `ListGrants` RPC has no binding.\n */\n async listGrants(resourceId: SignedResourceId): Promise<GrantEntry[]> {\n const grants = await this.runTx(\"ListGrants\", false, NullSignedResourceId, (tx) =>\n tx.listGrants(resourceId),\n );\n return grants.map((grant) => ({\n user: grant.user,\n writable: grant.permissions?.writable ?? false,\n grantedBy: grant.grantedBy,\n grantedAt: Number(grant.grantedAt),\n }));\n }\n\n /**\n * Lists the logins of users known to the server, for the recipient picker. A user becomes\n * known on first login; provisioned users who have never logged in do not appear. gRPC-only\n * (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a\n * REST-connected client, so callers gate on the `userListing:v1` capability.\n */\n async listUsers(): Promise<UserEntry[]> {\n const users = await this.ll.listUsers();\n return users.map((user) => ({ login: user.login }));\n }\n\n private async getUserRootViaRpc(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaRpc(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaRpc(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const resp = await this.ll.getUserRoot({\n login: opts.login,\n createIfNotExists: opts.createIfNotExists,\n });\n if (resp.userRoot === undefined) {\n if (!opts.createIfNotExists) return undefined;\n throw new Error(\"getUserRoot returned no userRoot entry\");\n }\n return createSignedResourceId(\n resp.userRoot.resourceId,\n toResourceSignature(resp.userRoot.resourceSignature),\n );\n }\n\n private async getUserRootViaList(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaList(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaList(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const responses = await this.ll.listUserResources({ login: opts.login, limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return createSignedResourceId(\n msg.entry.userRoot.resourceId,\n toResourceSignature(msg.entry.userRoot.resourceSignature),\n );\n }\n }\n\n if (opts.createIfNotExists) {\n throw new Error(\"listUserResources returned no userRoot entry\");\n }\n\n return undefined;\n }\n\n private async getDataLibrariesViaList(\n opts: { login?: string } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n // Collect all LS/* shared resources, separating V1 and V2\n const v1Entries: StorageInfo[] = [];\n const v2ResourceIds: { resourceId: SignedResourceId; resourceType: ResourceType }[] = [];\n\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n\n if (!sr.resourceType) continue;\n const typeName = sr.resourceType.name;\n const typeVersion = sr.resourceType.version;\n if (!typeName.startsWith(LsStorageTypePrefix)) continue;\n\n const rId = createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature));\n const rType: ResourceType = { name: typeName, version: typeVersion };\n\n if (typeVersion === \"2\") {\n v2ResourceIds.push({ resourceId: rId, resourceType: rType });\n } else {\n // V1 or unknown version: derive storageId from type name\n const storageId = typeName.substring(LsStorageTypePrefix.length);\n v1Entries.push({\n storageId,\n storageName: storageId,\n resourceId: rId,\n resourceType: rType,\n });\n }\n }\n\n // Read V2 resource data in a single transaction\n let v2Entries: StorageInfo[] = [];\n if (v2ResourceIds.length > 0) {\n v2Entries = await this.runTx(\n \"ReadLsStorageV2Data\",\n false,\n NullSignedResourceId,\n async (tx) => {\n const entries: StorageInfo[] = [];\n for (const { resourceId, resourceType } of v2ResourceIds) {\n const rd = await tx.getResourceData(resourceId, false);\n if (rd.data) {\n const v2Data = JSON.parse(Buffer.from(rd.data).toString(\"utf-8\")) as LsStorageV2Data;\n entries.push({\n storageId: v2Data.storageID,\n storageName: v2Data.storageName,\n resourceId,\n resourceType,\n });\n }\n }\n return entries;\n },\n );\n }\n\n const result = new Map<string, StorageInfo>();\n for (const entry of [...v1Entries, ...v2Entries]) {\n result.set(entry.storageId, entry);\n }\n return result;\n }\n\n private async getUserRootViaLegacy(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaLegacy(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaLegacy(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const login = opts.login ?? this.authUser;\n const mainRootName =\n login === null ? AnonymousClientRoot : createHash(\"sha256\").update(login).digest(\"hex\");\n\n return await this.runTx(\"initialization\", true, NullSignedResourceId, async (tx) => {\n if (await tx.checkResourceNameExists(mainRootName)) {\n return await tx.getResourceByName(mainRootName);\n }\n\n if (!opts.createIfNotExists) {\n return undefined;\n }\n\n const mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n });\n }\n\n private async getDataLibrariesViaLegacy(): Promise<ReadonlyMap<string, StorageInfo>> {\n return await this.runTx(\"GetAvailableStorageIds\", false, NullSignedResourceId, async (tx) => {\n const lsProviderId = await tx.getResourceByName(\"LSProvider\");\n const provider = await tx.getResourceData(lsProviderId, true);\n\n const result = new Map<string, StorageInfo>();\n for (const field of provider.fields) {\n if (field.type !== \"Dynamic\" || !isNotNullSignedResourceId(field.value)) continue;\n if (!field.name.startsWith(LsProviderFieldPrefix)) continue;\n\n const storageId = field.name.substring(LsProviderFieldPrefix.length);\n result.set(storageId, {\n storageId,\n storageName: storageId,\n resourceId: field.value,\n resourceType: { name: `${LsStorageTypePrefix}${storageId}`, version: \"1\" },\n });\n }\n return result;\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,6 +3,24 @@ import { PlTransaction } from "./transaction.js";
|
|
|
3
3
|
import { LLPlClient } from "./ll_client.js";
|
|
4
4
|
|
|
5
5
|
//#region src/core/user_resources.d.ts
|
|
6
|
+
/** One recipient of a resource, as returned by {@link UserResources.listGrants}. */
|
|
7
|
+
interface GrantEntry {
|
|
8
|
+
/** Login the grant targets. An everyone-grant surfaces here as the backend's
|
|
9
|
+
* everyone-sentinel — test with {@link isEveryoneUserLogin} (from "./transaction"); callers
|
|
10
|
+
* map it to "*". */
|
|
11
|
+
readonly user: string;
|
|
12
|
+
/** True for a writable grant (copy / collaboration), false for read-only. */
|
|
13
|
+
readonly writable: boolean;
|
|
14
|
+
/** Login of the user who created the grant. */
|
|
15
|
+
readonly grantedBy: string;
|
|
16
|
+
/** When the grant was created (ms epoch). */
|
|
17
|
+
readonly grantedAt: number;
|
|
18
|
+
}
|
|
19
|
+
/** One user known to the server, as returned by {@link UserResources.listUsers}. */
|
|
20
|
+
interface UserEntry {
|
|
21
|
+
/** Stable identifier of the user; the grant target and `GetUserRoot` key. */
|
|
22
|
+
readonly login: string;
|
|
23
|
+
}
|
|
6
24
|
/** Information about a single data library (LS storage). */
|
|
7
25
|
interface StorageInfo {
|
|
8
26
|
/** Machine-stable identifier, e.g. "library". Used for filtering and map keys. */
|
|
@@ -60,6 +78,42 @@ declare class UserResources {
|
|
|
60
78
|
login?: string;
|
|
61
79
|
createUserRootIfNotExists?: boolean;
|
|
62
80
|
}): Promise<ReadonlyMap<string, StorageInfo>>;
|
|
81
|
+
/**
|
|
82
|
+
* Discovers shared resources of a given type granted to the user, as signed resource ids.
|
|
83
|
+
*
|
|
84
|
+
* The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the
|
|
85
|
+
* `ListUserResources` gRPC stream and returns every `sharedResource` entry whose
|
|
86
|
+
* `resourceType.name` matches `resourceTypeName`. Matching is by name only — the
|
|
87
|
+
* version is ignored (permissive, survives schema bumps).
|
|
88
|
+
*
|
|
89
|
+
* gRPC-only: `ListUserResources` throws on a REST-connected client
|
|
90
|
+
* ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.
|
|
91
|
+
*/
|
|
92
|
+
listSharedResourcesByType(resourceTypeName: string, opts?: {
|
|
93
|
+
login?: string;
|
|
94
|
+
}): Promise<SignedResourceId[]>;
|
|
95
|
+
/**
|
|
96
|
+
* Enumerates the recipients of a single resource — the donor-side "who did I
|
|
97
|
+
* share with" view.
|
|
98
|
+
*
|
|
99
|
+
* Takes a signed, writable resource handle: the backend gates grant listing on a
|
|
100
|
+
* signed resource id with writable permission, so only the resource's owner can
|
|
101
|
+
* call it. An everyone-grant surfaces with `user` matching the backend's
|
|
102
|
+
* everyone-sentinel — test with {@link isEveryoneUserLogin} (from "./transaction");
|
|
103
|
+
* the caller maps that to "*".
|
|
104
|
+
*
|
|
105
|
+
* Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on
|
|
106
|
+
* every wire protocol — including REST, where the standalone server-streaming
|
|
107
|
+
* `ListGrants` RPC has no binding.
|
|
108
|
+
*/
|
|
109
|
+
listGrants(resourceId: SignedResourceId): Promise<GrantEntry[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Lists the logins of users known to the server, for the recipient picker. A user becomes
|
|
112
|
+
* known on first login; provisioned users who have never logged in do not appear. gRPC-only
|
|
113
|
+
* (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a
|
|
114
|
+
* REST-connected client, so callers gate on the `userListing:v1` capability.
|
|
115
|
+
*/
|
|
116
|
+
listUsers(): Promise<UserEntry[]>;
|
|
63
117
|
private getUserRootViaRpc;
|
|
64
118
|
private getUserRootViaList;
|
|
65
119
|
private getDataLibrariesViaList;
|
|
@@ -67,5 +121,5 @@ declare class UserResources {
|
|
|
67
121
|
private getDataLibrariesViaLegacy;
|
|
68
122
|
}
|
|
69
123
|
//#endregion
|
|
70
|
-
export { StorageInfo, TxRunner, UserResources };
|
|
124
|
+
export { GrantEntry, StorageInfo, TxRunner, UserEntry, UserResources };
|
|
71
125
|
//# sourceMappingURL=user_resources.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user_resources.d.ts","names":[],"sources":["../../src/core/user_resources.ts"],"mappings":";;;;;;UAmBiB,
|
|
1
|
+
{"version":3,"file":"user_resources.d.ts","names":[],"sources":["../../src/core/user_resources.ts"],"mappings":";;;;;;UAmBiB,UAAA;EAAA;;;EAAA,SAIN,IAAA;EAAA;EAAA,SAEA,QAAA;EAEA;EAAA,SAAA,SAAA;EAES;EAAA,SAAT,SAAA;AAAA;;UAIM,SAAA;EAED;EAAA,SAAL,KAAK;AAAA;;UAIC,WAAA;EAQoB;EAAA,SAN1B,SAAA;EAEA;EAAA,SAAA,WAAA;EAEY;EAAA,SAAZ,UAAA,EAAY,gBAAA;EAEE;EAAA,SAAd,YAAA,EAAc,YAAY;AAAA;AAarC;;;;AAAA,KAAY,QAAA,OACV,IAAA,UACA,QAAA,WACA,UAAA,EAAY,wBAAA,EACZ,IAAA,GAAO,EAAA,EAAI,aAAA,KAAkB,OAAA,CAAQ,CAAA,MAClC,OAAA,CAAQ,CAAA;;;;;;;;;;cAaA,aAAA;EAAA,iBAIQ,EAAA;EAAA,iBACA,KAAA;EAAA,SACD,QAAA;EAAA,QALV,iBAAA;cAGW,EAAA,EAAI,UAAA,EACJ,KAAA,EAAO,QAAA,EACR,QAAA;EApBlB;;;;AACY;AAad;;;EAiBQ,WAAA,CAAY,IAAA;IAAQ,KAAA;IAAgB,iBAAA;EAAA,IAA4B,OAAA,CAAQ,gBAAA;EACxE,WAAA,CAAY,IAAA;IAChB,KAAA;IACA,iBAAA;EAAA,IACE,OAAA,CAAQ,gBAAA;EAAA,QAUE,oBAAA;EAAA,QAkCA,eAAA;EAiEX;;;;EAvCG,gBAAA,CACJ,IAAA;IAAQ,KAAA;IAAgB,yBAAA;EAAA,IACvB,OAAA,CAAQ,WAAA,SAAoB,WAAA;EAzFZ;;;;;;;;;;;EA2Hb,yBAAA,CACJ,gBAAA,UACA,IAAA;IAAQ,KAAA;EAAA,IACP,OAAA,CAAQ,gBAAA;EAjH2D;;;;;;;;;;;;;;EA6IhE,UAAA,CAAW,UAAA,EAAY,gBAAA,GAAmB,OAAA,CAAQ,UAAA;EAjE7C;;;;;;EAmFL,SAAA,IAAa,OAAA,CAAQ,SAAA;EAAA,QAKb,iBAAA;EAAA,QAyBA,kBAAA;EAAA,QA4BA,uBAAA;EAAA,QAoEA,oBAAA;EAAA,QA+BA,yBAAA;AAAA"}
|