diff --git a/package-lock.json b/package-lock.json index a3658e47..d466ce9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@0xpolygonid/js-sdk", - "version": "1.13.0", + "version": "1.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@0xpolygonid/js-sdk", - "version": "1.13.0", + "version": "1.14.0", "license": "AGPL-3.0", "dependencies": { "@noble/curves": "^1.4.0", diff --git a/package.json b/package.json index 6fc18c60..5dab6016 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@0xpolygonid/js-sdk", - "version": "1.13.0", + "version": "1.14.0", "description": "SDK to work with Polygon ID", "main": "dist/node/cjs/index.js", "module": "dist/node/esm/index.js", diff --git a/src/iden3comm/handlers/fetch.ts b/src/iden3comm/handlers/fetch.ts index 83046c3c..dd5a197c 100644 --- a/src/iden3comm/handlers/fetch.ts +++ b/src/iden3comm/handlers/fetch.ts @@ -109,9 +109,16 @@ export class FetchHandler ctx: FetchMessageHandlerOptions ): Promise { switch (message.type) { - case PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE: - await this.handleOfferMessage(message as CredentialsOfferMessage, ctx); - return null; + case PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE: { + const result = await this.handleOfferMessage(message as CredentialsOfferMessage, ctx); + if (Array.isArray(result)) { + const credWallet = this.opts?.credentialWallet; + if (!credWallet) throw new Error('Credential wallet is not provided'); + await credWallet.saveAll(result); + return null; + } + return result as BasicMessage; + } case PROTOCOL_MESSAGE_TYPE.CREDENTIAL_FETCH_REQUEST_MESSAGE_TYPE: return this.handleFetchRequest(message as CredentialFetchRequestMessage); case PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ISSUANCE_RESPONSE_MESSAGE_TYPE: @@ -128,16 +135,14 @@ export class FetchHandler headers?: HeadersInit; packerOptions?: JWSPackerParams; } - ): Promise { + ): Promise { if (!ctx.mediaType) { ctx.mediaType = MediaType.ZKPMessage; } const credentials: W3CCredential[] = []; - for (let index = 0; index < offerMessage.body.credentials.length; index++) { - const credentialInfo = offerMessage.body.credentials[index]; - + for (const credentialInfo of offerMessage.body.credentials) { const guid = uuid.v4(); const fetchRequest: MessageFetchRequestMessage = { id: guid, @@ -167,7 +172,6 @@ export class FetchHandler ...packerOpts }) ); - let message: { body: { credential: W3CCredential } }; try { if (!offerMessage?.body?.url) { throw new Error(`could not fetch W3C credential, body url is missing`); @@ -180,16 +184,24 @@ export class FetchHandler }, body: token }); - if (resp.status !== 200) { - throw new Error(`could not fetch W3C credential, ${credentialInfo?.id}`); + const arrayBuffer = await resp.arrayBuffer(); + if (!arrayBuffer.byteLength) { + throw new Error(`could not fetch , ${credentialInfo?.id}, response is empty`); + } + const { unpackedMessage: message } = await this._packerMgr.unpack( + new Uint8Array(arrayBuffer) + ); + if (message.type !== PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ISSUANCE_RESPONSE_MESSAGE_TYPE) { + return message; } - message = await resp.json(); - credentials.push(W3CCredential.fromJSON(message.body.credential)); + credentials.push( + W3CCredential.fromJSON((message as CredentialIssuanceMessage).body.credential) + ); } catch (e: unknown) { throw new Error( - `could not fetch W3C credential, ${credentialInfo?.id}, error: ${ - (e as Error).message ?? e - }` + `could not fetch protocol message for credential offer id: , ${ + credentialInfo?.id + }, error: ${(e as Error).message ?? e}` ); } } @@ -219,11 +231,17 @@ export class FetchHandler PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE ); - return this.handleOfferMessage(offerMessage, { + const result = await this.handleOfferMessage(offerMessage, { mediaType: opts?.mediaType, headers: opts?.headers, packerOptions: opts?.packerOptions }); + + if (Array.isArray(result)) { + return result; + } + + throw new Error('invalid protocol message response'); } private async handleFetchRequest( @@ -242,7 +260,7 @@ export class FetchHandler const credId = msgRequest.body?.id; if (!credId) { - throw new Error('nvalid credential id in fetch request body'); + throw new Error('invalid credential id in fetch request body'); } if (!this.opts?.credentialWallet) { diff --git a/src/iden3comm/types/protocol/credentials.ts b/src/iden3comm/types/protocol/credentials.ts index 06aa8c7d..abc06b34 100644 --- a/src/iden3comm/types/protocol/credentials.ts +++ b/src/iden3comm/types/protocol/credentials.ts @@ -22,7 +22,7 @@ export type CredentialsOfferMessage = Required & { /** CredentialsOfferMessageBody is struct the represents offer message */ export type CredentialsOfferMessageBody = { url: string; - credentials: Array; + credentials: CredentialOffer[]; }; /** CredentialsOnchainOfferMessage represent Iden3message for credential onhcain offer message */ @@ -32,7 +32,7 @@ export type CredentialsOnchainOfferMessage = Required & { /** CredentialsOnchainOfferMessageBody is struct the represents onchain offer message body */ export type CredentialsOnchainOfferMessageBody = { - credentials: Array; + credentials: CredentialOffer[]; transaction_data: ContractInvokeTransactionData; }; @@ -44,7 +44,7 @@ export type CredentialOffer = { /** CredentialIssuanceMessage represent Iden3message for credential issuance */ export type CredentialIssuanceMessage = Required & { - body?: IssuanceMessageBody; + body: IssuanceMessageBody; }; /** IssuanceMessageBody is struct the represents message when credential is issued */ diff --git a/tests/handlers/fetch.test.ts b/tests/handlers/fetch.test.ts index 3285075d..2e896b9a 100644 --- a/tests/handlers/fetch.test.ts +++ b/tests/handlers/fetch.test.ts @@ -1,7 +1,6 @@ import { CredentialsOfferMessage, FetchHandler, - IFetchHandler, IPackageManager, IDataStorage, IdentityWallet, @@ -17,7 +16,8 @@ import { CredentialIssuanceMessage, FSCircuitStorage, ProofService, - CircuitId + CircuitId, + MessageHandler } from '../../src'; import { @@ -41,8 +41,9 @@ describe('fetch', () => { let credWallet: CredentialWallet; let dataStorage: IDataStorage; - let fetchHandler: IFetchHandler; + let fetchHandler: FetchHandler; let packageMgr: IPackageManager; + let msgHandler: MessageHandler; const agentUrl = 'https://testagent.com/'; const issuanceResponseMock = `{ @@ -134,6 +135,11 @@ describe('fetch', () => { fetchHandler = new FetchHandler(packageMgr, { credentialWallet: credWallet }); + + msgHandler = new MessageHandler({ + messageHandlers: [fetchHandler], + packageManager: packageMgr + }); }); it('fetch credential issued to genesis did', async () => { @@ -252,5 +258,33 @@ describe('fetch', () => { const cred2 = await credWallet.findById(newId); expect(cred2).not.to.be.undefined; + + const offer: CredentialsOfferMessage = { + id, + typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage, + type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE, + thid: id, + body: { + url: agentUrl, + credentials: [{ id: cred2?.id as string, description: 'kyc age credentials' }] + }, + from: issuerDID.string(), + to: userDID.string() + }; + + const bytes = await packageMgr.packMessage( + PROTOCOL_CONSTANTS.MediaType.PlainMessage, + offer, + {} + ); + fetchMock.restore(); + fetchMock.spy(); + fetchMock.post(agentUrl, issuanceResponseMock); + expect(await credWallet.list()).to.have.length(4); + + const response = await msgHandler.handleMessage(bytes, {}); + // credential saved after handleing message via msgHandler + expect(response).to.be.null; + expect(await credWallet.list()).to.have.length(5); }); });