{"version":3,"file":"credentialUse.mjs","names":[],"sources":["../../src/utils/credentialUse.ts"],"sourcesContent":["import { AgentContext } from '../agent'\nimport { CredoError } from '../error/CredoError'\nimport { Mdoc } from '../modules/mdoc/Mdoc'\nimport { MdocRecord, type MdocRecordInstances, MdocRepository } from '../modules/mdoc/repository'\nimport { decodeSdJwtVc } from '../modules/sd-jwt-vc/decodeSdJwtVc'\nimport { SdJwtVcRecord, type SdJwtVcRecordInstances, SdJwtVcRepository } from '../modules/sd-jwt-vc/repository'\nimport type { SdJwtVc } from '../modules/sd-jwt-vc/SdJwtVcService'\nimport {\n  W3cJsonLdVerifiableCredential,\n  W3cJwtVerifiableCredential,\n  W3cV2JwtVerifiableCredential,\n  W3cV2SdJwtVerifiableCredential,\n  type W3cV2VerifiableCredential,\n  type W3cVerifiableCredential,\n} from '../modules/vc'\nimport {\n  W3cCredentialRecord,\n  type W3cCredentialRecordInstances,\n  W3cCredentialRepository,\n  W3cV2CredentialRecord,\n  type W3cV2CredentialRecordInstances,\n  W3cV2CredentialRepository,\n} from '../modules/vc/repository'\nimport {\n  CredentialMultiInstanceState,\n  CredentialMultiInstanceUseMode,\n  CredentialMultiInstanceUseUpdateMode,\n} from './credentialUseTypes'\n\nexport { CredentialMultiInstanceState, CredentialMultiInstanceUseMode, CredentialMultiInstanceUseUpdateMode }\n\ntype CredentialRecord = W3cCredentialRecord | SdJwtVcRecord | MdocRecord | W3cV2CredentialRecord\n\ntype CredentialInstanceReturnType<Record extends CredentialRecord> = Record extends W3cCredentialRecord\n  ? W3cVerifiableCredential\n  : Record extends W3cV2CredentialRecord\n    ? W3cV2VerifiableCredential\n    : Record extends SdJwtVcRecord\n      ? SdJwtVc\n      : Record extends MdocRecord\n        ? Mdoc\n        : CredentialRecord\n\ntype CredentialRepositoryForRecord<Record extends CredentialRecord> = Record extends W3cCredentialRecord\n  ? W3cCredentialRepository\n  : Record extends W3cV2CredentialRepository\n    ? W3cV2VerifiableCredential\n    : Record extends SdJwtVcRepository\n      ? SdJwtVc\n      : Record extends MdocRepository\n        ? Mdoc\n        : W3cCredentialRepository | W3cV2CredentialRepository | SdJwtVcRepository | MdocRepository\n\n/**\n * Checks whether an instance can be used from the provided record based on\n * the required credential use mode.\n */\nexport function canUseInstanceFromCredentialRecord({\n  credentialRecord,\n  useMode,\n}: {\n  credentialRecord: CredentialRecord\n  useMode: CredentialMultiInstanceUseMode\n}) {\n  // If we're not required to use a new instance, we can always return the first instance\n  if (\n    useMode === CredentialMultiInstanceUseMode.First ||\n    useMode === CredentialMultiInstanceUseMode.NewOrFirst ||\n    (useMode === CredentialMultiInstanceUseMode.NewIfReceivedInBatch &&\n      [CredentialMultiInstanceState.SingleInstanceUnused, CredentialMultiInstanceState.SingleInstanceUsed].includes(\n        credentialRecord.multiInstanceState\n      ))\n  ) {\n    return true\n  }\n\n  // Otherwise we return whether we can use a new instance\n  return (\n    credentialRecord.multiInstanceState === CredentialMultiInstanceState.MultiInstanceFirstUnused ||\n    credentialRecord.multiInstanceState === CredentialMultiInstanceState.SingleInstanceUnused\n  )\n}\n\nexport interface UseInstanceFromCredentialRecordOptions<Record extends CredentialRecord> {\n  agentContext: AgentContext\n\n  /**\n   * Which mode to use for usage of the credential instance. See {@link CredentialMultiInstanceUseMode} for\n   * more information on the available options\n   */\n  useMode: CredentialMultiInstanceUseMode\n  credentialRecord: Record\n\n  /**\n   * The update mode for the credential record when a new instance is used.\n   *\n   * @default `CredentialMultiInstanceUseUpdateMode.RefetchAndUpdateWithLock`\n   */\n  updateMode?: CredentialMultiInstanceUseUpdateMode\n}\n\nexport interface UseInstanceFromCredentialRecordReturn<Record extends CredentialRecord> {\n  /**\n   * The credential instance with the kms key id\n   */\n  credentialInstance: CredentialInstanceReturnType<Record>\n\n  /**\n   * If the first instance was used, this value will be `true`. The first time\n   * the first instance is used, technically the credential is not reused yet,\n   * but we make no distinction between this.\n   */\n  isReused: boolean\n\n  /**\n   * Whether the last new instance was used. If `isReused` is `true` this value will\n   * always be `false`.\n   *\n   * The last new instance is the second entry in the mdoc instances, since the first\n   * one is reserved for 'reused' usage.\n   */\n  isLastNewInstance: boolean\n}\n\n/**\n * Extract an instance with the correct kms key id from the credential instances\n * on the record. Note that if an instance is extracted (that is not reused) it\n * will remove the instance from the record, and the record should be updated in\n * storage after usage.\n *\n * Note that the last credential instance is never removed from the record. So if the\n * method indicates the last instance has been used, you should remove the credential\n * from storage if you don't want it to be used anymore in the future.\n */\nexport async function useInstanceFromCredentialRecord<Record extends CredentialRecord>({\n  credentialRecord,\n  useMode,\n  agentContext,\n  updateMode = CredentialMultiInstanceUseUpdateMode.RefetchAndUpdateWithLock,\n}: UseInstanceFromCredentialRecordOptions<Record>): Promise<UseInstanceFromCredentialRecordReturn<Record>> {\n  let extractResult = extractInstanceAndUpdateRecord({\n    credentialRecord,\n    useMode,\n  })\n\n  if (\n    (!extractResult.isReused || extractResult.isLastNewInstance) &&\n    updateMode === CredentialMultiInstanceUseUpdateMode.Update\n  ) {\n    await updateCredentialRecord(agentContext, credentialRecord)\n  }\n\n  if (\n    (!extractResult.isReused || extractResult.isLastNewInstance) &&\n    updateMode === CredentialMultiInstanceUseUpdateMode.RefetchAndUpdateWithLock\n  ) {\n    // the method is generic, but the types don't work well generically\n    const repository = repositoryForRecord(agentContext, credentialRecord) as SdJwtVcRepository\n\n    await repository.updateByIdWithLock(\n      agentContext,\n      credentialRecord.id,\n      async (freshCredentialRecord: SdJwtVcRecord) => {\n        extractResult = extractInstanceAndUpdateRecord({\n          credentialRecord: freshCredentialRecord,\n          useMode,\n        })\n\n        return freshCredentialRecord\n      }\n    )\n  }\n\n  let transformedCredentialInstance: W3cVerifiableCredential | Mdoc | SdJwtVc | W3cV2VerifiableCredential\n  if (credentialRecord instanceof MdocRecord) {\n    const { issuerSignedBase64Url, kmsKeyId } = extractResult.credentialInstance as MdocRecordInstances[0]\n    transformedCredentialInstance = Mdoc.fromBase64Url(issuerSignedBase64Url)\n    transformedCredentialInstance.deviceKeyId = kmsKeyId ?? transformedCredentialInstance.deviceKey.legacyKeyId\n  } else if (credentialRecord instanceof SdJwtVcRecord) {\n    const { compactSdJwtVc, kmsKeyId } = extractResult.credentialInstance as SdJwtVcRecordInstances[0]\n\n    transformedCredentialInstance = {\n      ...decodeSdJwtVc(compactSdJwtVc, credentialRecord.typeMetadata),\n      kmsKeyId,\n    }\n  } else if (credentialRecord instanceof W3cCredentialRecord) {\n    const { credential } = extractResult.credentialInstance as W3cCredentialRecordInstances[0]\n\n    transformedCredentialInstance =\n      typeof credential === 'string'\n        ? W3cJwtVerifiableCredential.fromSerializedJwt(credential)\n        : W3cJsonLdVerifiableCredential.fromJson(credential)\n  } else if (credentialRecord instanceof W3cV2CredentialRecord) {\n    const { credential } = extractResult.credentialInstance as W3cV2CredentialRecordInstances[0]\n\n    transformedCredentialInstance = credential.includes('~')\n      ? W3cV2SdJwtVerifiableCredential.fromCompact(credential)\n      : W3cV2JwtVerifiableCredential.fromCompact(credential)\n  } else {\n    throw new CredoError('Unsupported record type')\n  }\n\n  return {\n    credentialInstance: transformedCredentialInstance as CredentialInstanceReturnType<Record>,\n    isReused: extractResult.isReused,\n    isLastNewInstance: extractResult.isLastNewInstance,\n  }\n}\n\nfunction extractInstanceAndUpdateRecord<Record extends CredentialRecord>({\n  credentialRecord,\n  useMode,\n}: Pick<UseInstanceFromCredentialRecordOptions<Record>, 'credentialRecord' | 'useMode'>) {\n  if (credentialRecord.credentialInstances.length === 1 || useMode === CredentialMultiInstanceUseMode.First) {\n    const isFirstUnused =\n      credentialRecord.multiInstanceState === CredentialMultiInstanceState.MultiInstanceFirstUnused ||\n      credentialRecord.multiInstanceState === CredentialMultiInstanceState.SingleInstanceUnused\n\n    const isMultiInstance =\n      credentialRecord.multiInstanceState === CredentialMultiInstanceState.MultiInstanceFirstUnused ||\n      credentialRecord.multiInstanceState === CredentialMultiInstanceState.MultiInstanceFirstUsed\n\n    const allowsReuse =\n      // New does not allow reuse\n      useMode === CredentialMultiInstanceUseMode.New\n        ? false\n        : // NewIfReceivedInBatch only allows reuse if the credential was not multi instance\n          useMode === CredentialMultiInstanceUseMode.NewIfReceivedInBatch\n          ? !isMultiInstance\n          : // Otherwise we allow reuse (First or NewOrFirst)\n            true\n\n    if (isFirstUnused) {\n      credentialRecord.multiInstanceState =\n        credentialRecord.multiInstanceState === CredentialMultiInstanceState.SingleInstanceUnused\n          ? CredentialMultiInstanceState.SingleInstanceUsed\n          : CredentialMultiInstanceState.MultiInstanceFirstUsed\n    } else if (!allowsReuse) {\n      throw new CredoError(\n        `Unable to extract new credential instance from ${credentialRecord.type} with id '${credentialRecord.id}', since it only contains a single credential instance but using a new instance is required due to use mode '${useMode}'.`\n      )\n    }\n\n    return {\n      isReused: !isFirstUnused,\n      credentialInstance: credentialRecord.credentialInstances[0],\n      isLastNewInstance: isFirstUnused,\n    }\n  }\n\n  // We have multiple instances, so we pop the last one (never the first one)\n  const _credentialInstance = credentialRecord.credentialInstances.pop()\n  if (!_credentialInstance) {\n    throw new CredoError(\n      `Unable to extract credential instance from ${credentialRecord.type} with id '${credentialRecord.id}', since the credential record does not contain any credential instances.`\n    )\n  }\n\n  return {\n    credentialInstance: _credentialInstance,\n    isReused: false,\n    isLastNewInstance: false,\n  }\n}\n\nfunction repositoryForRecord<Record extends CredentialRecord>(\n  agentContext: AgentContext,\n  record: CredentialRecord\n): CredentialRepositoryForRecord<Record> {\n  if (record instanceof W3cCredentialRecord)\n    return agentContext.resolve(W3cCredentialRepository) as CredentialRepositoryForRecord<Record>\n  if (record instanceof W3cV2CredentialRecord)\n    return agentContext.resolve(W3cV2CredentialRepository) as CredentialRepositoryForRecord<Record>\n  if (record instanceof MdocRecord) return agentContext.resolve(MdocRepository) as CredentialRepositoryForRecord<Record>\n  return agentContext.resolve(SdJwtVcRepository) as CredentialRepositoryForRecord<Record>\n}\n\nfunction updateCredentialRecord(agentContext: AgentContext, record: CredentialRecord) {\n  if (record instanceof W3cCredentialRecord)\n    return agentContext.resolve(W3cCredentialRepository).update(agentContext, record)\n  if (record instanceof W3cV2CredentialRecord)\n    return agentContext.resolve(W3cV2CredentialRepository).update(agentContext, record)\n  if (record instanceof MdocRecord) return agentContext.resolve(MdocRepository).update(agentContext, record)\n  return agentContext.resolve(SdJwtVcRepository).update(agentContext, record)\n}\n"],"mappings":"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"}