Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: do not add ~service in createOOBOffer method #772

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions packages/core/src/modules/credentials/CredentialsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface CredentialsModule {
declineOffer(credentialRecordId: string): Promise<CredentialExchangeRecord>
negotiateOffer(options: NegotiateOfferOptions): Promise<CredentialExchangeRecord>
// out of band
createOutOfBandOffer(options: OfferCredentialOptions): Promise<{
createOffer(options: OfferCredentialOptions): Promise<{
message: AgentMessage
credentialRecord: CredentialExchangeRecord
}>
Expand Down Expand Up @@ -522,18 +522,14 @@ export class CredentialsModule implements CredentialsModule {
* @param options The credential options to use for the offer
* @returns The credential record and credential offer message
*/
public async createOutOfBandOffer(options: OfferCredentialOptions): Promise<{
public async createOffer(options: OfferCredentialOptions): Promise<{
message: AgentMessage
credentialRecord: CredentialExchangeRecord
}> {
// with version we can get the Service
if (!options.protocolVersion) {
throw new AriesFrameworkError('Missing protocol version in createOutOfBandOffer')
}
const service = this.getService(options.protocolVersion)

this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`)
const { message, credentialRecord } = await service.createOutOfBandOffer(options)
const { message, credentialRecord } = await service.createOffer(options)

this.logger.debug('Offer Message successfully created; message= ', message)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import { Lifecycle, scoped } from 'tsyringe'
import { AgentConfig } from '../../../../agent/AgentConfig'
import { Dispatcher } from '../../../../agent/Dispatcher'
import { EventEmitter } from '../../../../agent/EventEmitter'
import { ServiceDecorator } from '../../../../decorators/service/ServiceDecorator'
import { AriesFrameworkError } from '../../../../error'
import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage'
import { isLinkedAttachment } from '../../../../utils/attachment'
Expand Down Expand Up @@ -504,32 +503,30 @@ export class V1CredentialService extends CredentialService {
public async createOffer(
credentialOptions: OfferCredentialOptions
): Promise<CredentialProtocolMsgReturnType<V1OfferCredentialMessage>> {
if (!credentialOptions.connectionId) {
throw new AriesFrameworkError('Connection id missing from offer credential options')
}
if (!credentialOptions.credentialFormats.indy || Object.keys(credentialOptions.credentialFormats).length !== 1) {
// connection id can be undefined in connection-less scenario
const connection = credentialOptions.connectionId
? await this.connectionService.getById(credentialOptions.connectionId)
: undefined

const indy = credentialOptions.credentialFormats.indy

if (!indy || Object.keys(credentialOptions.credentialFormats).length !== 1) {
throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1')
}

const connection = await this.connectionService.getById(credentialOptions.connectionId)

if (
!credentialOptions?.credentialFormats.indy?.attributes ||
!credentialOptions?.credentialFormats.indy?.credentialDefinitionId
) {
if (!indy.attributes || !indy.credentialDefinitionId) {
throw new AriesFrameworkError('Missing properties from OfferCredentialOptions object: cannot create Offer!')
}

const preview: V1CredentialPreview = new V1CredentialPreview({
attributes: credentialOptions.credentialFormats.indy?.attributes,
attributes: indy.attributes,
})

const linkedAttachments = credentialOptions.credentialFormats.indy?.linkedAttachments

const template: CredentialOfferTemplate = {
...credentialOptions,
preview: preview,
credentialDefinitionId: credentialOptions?.credentialFormats.indy?.credentialDefinitionId,
linkedAttachments,
credentialDefinitionId: indy.credentialDefinitionId,
linkedAttachments: indy.linkedAttachments,
}

const { credentialRecord, message } = await this.createOfferProcessing(template, connection)
Expand All @@ -549,6 +546,7 @@ export class V1CredentialService extends CredentialService {
})
return { credentialRecord, message }
}

/**
* Process a received {@link OfferCredentialMessage}. This will not accept the credential offer
* or send a credential request. It will only create a new credential record with
Expand Down Expand Up @@ -714,50 +712,6 @@ export class V1CredentialService extends CredentialService {
return { message: offerMessage, credentialRecord }
}

public async createOutOfBandOffer(
credentialOptions: OfferCredentialOptions
): Promise<CredentialProtocolMsgReturnType<V1OfferCredentialMessage>> {
if (!credentialOptions.credentialFormats.indy || Object.keys(credentialOptions.credentialFormats).length !== 1) {
throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1')
}

if (!credentialOptions.credentialFormats.indy?.credentialDefinitionId) {
throw new AriesFrameworkError('Missing credential definition id for out of band credential')
}
const v1Preview = new V1CredentialPreview({
attributes: credentialOptions.credentialFormats.indy?.attributes,
})
const template: CredentialOfferTemplate = {
credentialDefinitionId: credentialOptions.credentialFormats.indy?.credentialDefinitionId,
comment: credentialOptions.comment,
preview: v1Preview,
autoAcceptCredential: credentialOptions.autoAcceptCredential,
}

const { credentialRecord, message } = await this.createOfferProcessing(template)

// Create and set ~service decorator
const routing = await this.mediationRecipientService.getRouting()
message.service = new ServiceDecorator({
serviceEndpoint: routing.endpoints[0],
recipientKeys: [routing.recipientKey.publicKeyBase58],
routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58),
})
await this.credentialRepository.save(credentialRecord)
await this.didCommMessageRepository.saveAgentMessage({
agentMessage: message,
role: DidCommMessageRole.Receiver,
associatedRecordId: credentialRecord.id,
})
this.eventEmitter.emit<CredentialStateChangedEvent>({
type: CredentialEventTypes.CredentialStateChanged,
payload: {
credentialRecord,
previousState: null,
},
})
return { credentialRecord, message }
}
/**
* Create a {@link RequestCredentialMessage} as response to a received credential offer.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { Lifecycle, scoped } from 'tsyringe'
import { AgentConfig } from '../../../../agent/AgentConfig'
import { Dispatcher } from '../../../../agent/Dispatcher'
import { EventEmitter } from '../../../../agent/EventEmitter'
import { ServiceDecorator } from '../../../../decorators/service/ServiceDecorator'
import { AriesFrameworkError } from '../../../../error'
import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage'
import { AckStatus } from '../../../common'
Expand Down Expand Up @@ -328,7 +327,8 @@ export class V2CredentialService extends CredentialService {
return { message: credentialProposalMessage, credentialRecord }
}
/**
* Create a {@link V2OfferCredentialMessage} as beginning of protocol process.
* Create a {@link V2OfferCredentialMessage} as beginning of protocol process. If no connectionId is provided, the
* exchange will be created without a connection for usage in oob and connection-less issuance.
*
* @param formatService {@link CredentialFormatService} the format service object containing format-specific logic
* @param options attributes of the original offer
Expand All @@ -338,18 +338,15 @@ export class V2CredentialService extends CredentialService {
public async createOffer(
options: OfferCredentialOptions
): Promise<CredentialProtocolMsgReturnType<V2OfferCredentialMessage>> {
if (!options.connectionId) {
throw new AriesFrameworkError('Connection id missing from offer credential options')
}
const connection = await this.connectionService.getById(options.connectionId)

const connection = options.connectionId ? await this.connectionService.getById(options.connectionId) : undefined
connection?.assertReady()

const formats: CredentialFormatService[] = this.getFormats(options.credentialFormats)
const formats = this.getFormats(options.credentialFormats)

if (!formats || formats.length === 0) {
if (formats.length === 0) {
throw new AriesFrameworkError(`Unable to create offer. No supported formats`)
}

// Create message
const { credentialRecord, message: credentialOfferMessage } = await this.credentialMessageBuilder.createOffer(
formats,
Expand All @@ -369,41 +366,6 @@ export class V2CredentialService extends CredentialService {
return { credentialRecord, message: credentialOfferMessage }
}

/**
* Create an offer message for an out-of-band (connectionless) credential
* @param credentialOptions the options (parameters) object for the offer
* @returns the credential record and the offer message
*/
public async createOutOfBandOffer(
credentialOptions: OfferCredentialOptions
): Promise<CredentialProtocolMsgReturnType<V2OfferCredentialMessage>> {
const formats: CredentialFormatService[] = this.getFormats(credentialOptions.credentialFormats)

if (!formats || formats.length === 0) {
throw new AriesFrameworkError(`Unable to create out of band offer. No supported formats`)
}
// Create message
const { credentialRecord, message: offerCredentialMessage } = await this.credentialMessageBuilder.createOffer(
formats,
credentialOptions
)

// Create and set ~service decorator
const routing = await this.mediationRecipientService.getRouting()
offerCredentialMessage.service = new ServiceDecorator({
serviceEndpoint: routing.endpoints[0],
recipientKeys: [routing.recipientKey.publicKeyBase58],
routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58),
})
await this.credentialRepository.save(credentialRecord)
await this.didCommMessageRepository.saveOrUpdateAgentMessage({
agentMessage: offerCredentialMessage,
role: DidCommMessageRole.Receiver,
associatedRecordId: credentialRecord.id,
})
await this.emitEvent(credentialRecord)
return { credentialRecord, message: offerCredentialMessage }
}
/**
* Create a {@link OfferCredentialMessage} as response to a received credential proposal.
* To create an offer not bound to an existing credential exchange, use {@link V2CredentialService#createOffer}.
Expand Down Expand Up @@ -1051,6 +1013,7 @@ export class V2CredentialService extends CredentialService {
},
})
}

/**
* Retrieve a credential record by connection id and thread id
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,15 @@ describe('credentials', () => {
connectionId: '',
}
// eslint-disable-next-line prefer-const
let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer(
offerOptions
)
let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions)

await aliceAgent.receiveMessage(message.toJSON())
const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({
recordId: faberCredentialRecord.id,
message,
domain: 'https://a-domain.com',
})

await aliceAgent.receiveMessage(offerMessage.toJSON())

let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, {
threadId: faberCredentialRecord.threadId,
Expand Down Expand Up @@ -190,12 +194,16 @@ describe('credentials', () => {
connectionId: '',
}
// eslint-disable-next-line prefer-const
let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer(
offerOptions
)
let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions)

const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({
recordId: faberCredentialRecord.id,
message,
domain: 'https://a-domain.com',
})

// Receive Message
await aliceAgent.receiveMessage(message.toJSON())
await aliceAgent.receiveMessage(offerMessage.toJSON())

// Wait for it to be processed
let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,15 @@ describe('credentials', () => {
connectionId: '',
}
// eslint-disable-next-line prefer-const
let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer(
offerOptions
)
let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions)

await aliceAgent.receiveMessage(message.toJSON())
const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({
recordId: faberCredentialRecord.id,
message,
domain: 'https://a-domain.com',
})

await aliceAgent.receiveMessage(offerMessage.toJSON())

let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, {
threadId: faberCredentialRecord.threadId,
Expand Down Expand Up @@ -187,12 +191,16 @@ describe('credentials', () => {
connectionId: '',
}
// eslint-disable-next-line prefer-const
let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer(
offerOptions
)
let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOffer(offerOptions)

const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({
recordId: faberCredentialRecord.id,
message,
domain: 'https://a-domain.com',
})

// Receive Message
await aliceAgent.receiveMessage(message.toJSON())
await aliceAgent.receiveMessage(offerMessage.toJSON())

// Wait for it to be processed
let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ export abstract class CredentialService {
abstract createOffer(options: OfferCredentialOptions): Promise<CredentialProtocolMsgReturnType<AgentMessage>>
abstract processOffer(messageContext: HandlerInboundMessage<Handler>): Promise<CredentialExchangeRecord>

abstract createOutOfBandOffer(options: OfferCredentialOptions): Promise<CredentialProtocolMsgReturnType<AgentMessage>>

// methods for request
abstract createRequest(
credentialRecord: CredentialExchangeRecord,
Expand Down
35 changes: 34 additions & 1 deletion packages/core/src/modules/oob/OutOfBandModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
ConnectionInvitationMessage,
ConnectionsModule,
} from '../../modules/connections'
import { JsonTransformer } from '../../utils'
import { DidCommMessageRepository, DidCommMessageRole } from '../../storage'
import { JsonEncoder, JsonTransformer } from '../../utils'
import { parseMessageType, supportsIncomingMessageType } from '../../utils/messageType'
import { DidKey } from '../dids'
import { didKeyToVerkey } from '../dids/helpers'
Expand Down Expand Up @@ -82,6 +83,7 @@ export class OutOfBandModule {
private outOfBandService: OutOfBandService
private mediationRecipientService: MediationRecipientService
private connectionsModule: ConnectionsModule
private didCommMessageRepository: DidCommMessageRepository
private dispatcher: Dispatcher
private messageSender: MessageSender
private eventEmitter: EventEmitter
Expand All @@ -94,6 +96,7 @@ export class OutOfBandModule {
outOfBandService: OutOfBandService,
mediationRecipientService: MediationRecipientService,
connectionsModule: ConnectionsModule,
didCommMessageRepository: DidCommMessageRepository,
messageSender: MessageSender,
eventEmitter: EventEmitter
) {
Expand All @@ -103,6 +106,7 @@ export class OutOfBandModule {
this.outOfBandService = outOfBandService
this.mediationRecipientService = mediationRecipientService
this.connectionsModule = connectionsModule
this.didCommMessageRepository = didCommMessageRepository
this.messageSender = messageSender
this.eventEmitter = eventEmitter
this.registerHandlers(dispatcher)
Expand Down Expand Up @@ -232,6 +236,35 @@ export class OutOfBandModule {
return { outOfBandRecord, invitation: convertToOldInvitation(outOfBandRecord.outOfBandInvitation) }
}

public async createLegacyConnectionlessInvitation<Message extends AgentMessage>(config: {
recordId: string
message: Message
domain: string
}): Promise<{ message: Message; invitationUrl: string }> {
// Create keys (and optionally register them at the mediator)
const routing = await this.mediationRecipientService.getRouting()

// Set the service on the message
config.message.service = new ServiceDecorator({
TimoGlastra marked this conversation as resolved.
Show resolved Hide resolved
serviceEndpoint: routing.endpoints[0],
recipientKeys: [routing.recipientKey].map((key) => key.publicKeyBase58),
routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58),
})

// We need to update the message with the new service, so we can
// retrieve it from storage later on.
await this.didCommMessageRepository.saveOrUpdateAgentMessage({
agentMessage: config.message,
associatedRecordId: config.recordId,
role: DidCommMessageRole.Sender,
})

return {
message: config.message,
invitationUrl: `${config.domain}?d_m=${JsonEncoder.toBase64URL(JsonTransformer.toJSON(config.message))}`,
}
}

/**
* Parses URL, decodes invitation and calls `receiveMessage` with parsed invitation message.
*
Expand Down
Loading