diff --git a/package.json b/package.json index 983d2b10e..e8cca134c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@babel/preset-typescript": "^7.10.4", "@babel/runtime": "^7.11.2", "@jolocom/local-resolver-registrar": "^1.0.1", - "@jolocom/sdk-storage-typeorm": "^4.0.0", + "@jolocom/sdk-storage-typeorm": "^4.1.0", "@types/jest": "^26.0.10", "@types/node": "^13.9.8", "@types/node-fetch": "^2.5.5", @@ -56,7 +56,7 @@ "ts-node": "^9.0.0", "tslib": "^1.7.1", "typedoc": "^0.19.2", - "typeorm": "^0.2.25", + "typeorm": "0.2.24", "typescript": "^3.7.5", "yarn": "^1.22.0" }, diff --git a/src/interactionManager/authenticationFlow.ts b/src/interactionManager/authenticationFlow.ts index 51f9aacab..85c0f75e0 100644 --- a/src/interactionManager/authenticationFlow.ts +++ b/src/interactionManager/authenticationFlow.ts @@ -6,7 +6,8 @@ import { isAuthenticationRequest } from './guards' export class AuthenticationFlow extends Flow { public state: AuthenticationFlowState = { description: '' } - public type = FlowType.Authentication + public static type = FlowType.Authentication + public static firstMessageType = InteractionType.Authentication // TODO InteractionType.AuthenticaitonResponse should exist public async handleInteractionToken(token: Authentication, interactionType: string) { diff --git a/src/interactionManager/authorizationFlow.ts b/src/interactionManager/authorizationFlow.ts index 0dd7d0e6d..99f76bc10 100644 --- a/src/interactionManager/authorizationFlow.ts +++ b/src/interactionManager/authorizationFlow.ts @@ -12,10 +12,11 @@ import { isAuthorizationRequest, isAuthorizationResponse } from './guards' export class AuthorizationFlow extends Flow< AuthorizationResponse | AuthorizationRequest > { - public type = FlowType.Authorization + public static type = FlowType.Authorization public state: AuthorizationFlowState = { description: '', } + public static firstMessageType = AuthorizationType.AuthorizationRequest public async handleInteractionToken( token: AuthorizationRequest | AuthorizationResponse, diff --git a/src/interactionManager/credentialOfferFlow.ts b/src/interactionManager/credentialOfferFlow.ts index 34e2d3fe8..4a9c4b87a 100644 --- a/src/interactionManager/credentialOfferFlow.ts +++ b/src/interactionManager/credentialOfferFlow.ts @@ -25,7 +25,8 @@ export class CredentialOfferFlow extends Flow< credentialsValidity: [], credentialsAllValid: true, } - public type = FlowType.CredentialOffer + public static type = FlowType.CredentialOffer + public static firstMessageType = InteractionType.CredentialOfferRequest public constructor(ctx: Interaction) { super(ctx) diff --git a/src/interactionManager/credentialRequestFlow.ts b/src/interactionManager/credentialRequestFlow.ts index 4d6f5ed1e..b7556d11a 100644 --- a/src/interactionManager/credentialRequestFlow.ts +++ b/src/interactionManager/credentialRequestFlow.ts @@ -13,7 +13,8 @@ export class CredentialRequestFlow extends Flow< constraints: [], providedCredentials: [], } - public type = FlowType.CredentialShare + public static type = FlowType.CredentialShare + public static firstMessageType = InteractionType.CredentialRequest constructor(ctx: Interaction) { super(ctx) diff --git a/src/interactionManager/decryptionFlow.ts b/src/interactionManager/decryptionFlow.ts index f65b3e4f4..ca741db4e 100644 --- a/src/interactionManager/decryptionFlow.ts +++ b/src/interactionManager/decryptionFlow.ts @@ -16,8 +16,9 @@ export interface DecryptionFlowState extends FlowState { export class DecryptionFlow extends Flow< DecryptionRequest | DecryptionResponse > { - public type = FlowType.Decrypt + public static type = FlowType.Decrypt public state: DecryptionFlowState = {} + public static firstMessageType = DecryptionType.DecryptionRequest public constructor(ctx: Interaction) { super(ctx) diff --git a/src/interactionManager/encryptionFlow.ts b/src/interactionManager/encryptionFlow.ts index c2576dee8..16522b9a3 100644 --- a/src/interactionManager/encryptionFlow.ts +++ b/src/interactionManager/encryptionFlow.ts @@ -16,8 +16,9 @@ export interface EncryptionFlowState extends FlowState { export class EncryptionFlow extends Flow< EncryptionRequest | EncryptionResponse > { - public type = FlowType.Encrypt + public static type = FlowType.Encrypt public state: EncryptionFlowState = {} + public static firstMessageType = EncryptionType.EncryptionRequest public constructor(ctx: Interaction) { super(ctx) diff --git a/src/interactionManager/establishChannelFlow.ts b/src/interactionManager/establishChannelFlow.ts index fd5ec1477..deb1bf085 100644 --- a/src/interactionManager/establishChannelFlow.ts +++ b/src/interactionManager/establishChannelFlow.ts @@ -16,7 +16,8 @@ export class EstablishChannelFlow extends Flow< description: '', established: false, } - public type = FlowType.EstablishChannel + public static type = FlowType.EstablishChannel + public static firstMessageType = EstablishChannelType.EstablishChannelRequest public constructor(ctx: Interaction) { super(ctx) diff --git a/src/interactionManager/flow.ts b/src/interactionManager/flow.ts index 494ed7d53..62afd7a07 100644 --- a/src/interactionManager/flow.ts +++ b/src/interactionManager/flow.ts @@ -1,5 +1,6 @@ import { Interaction } from './interaction' import { FlowType } from './types' +import { InteractionType } from '@jolocom/protocol-ts/dist/lib/interactionTokens' // FIXME why is this exported? export interface FlowState { } @@ -7,13 +8,19 @@ export interface FlowState { } export abstract class Flow { protected ctx: Interaction - public abstract type: FlowType + public static type: FlowType public abstract state: FlowState + public static firstMessageType: InteractionType | string constructor(ctx: Interaction) { this.ctx = ctx } + get type() { + // @ts-ignore + return this.constructor.type + } + public getState() { return this.state } diff --git a/src/interactionManager/interaction.ts b/src/interactionManager/interaction.ts index b7fcb1b22..ceb677e79 100644 --- a/src/interactionManager/interaction.ts +++ b/src/interactionManager/interaction.ts @@ -1,4 +1,3 @@ -import { CredentialOfferFlow } from './credentialOfferFlow' import { InteractionType, CredentialOfferResponseSelection, @@ -22,10 +21,8 @@ import { SigningResponse, SigningType, } from './types' -import { CredentialRequestFlow } from './credentialRequestFlow' import { Flow } from './flow' import { CredentialOfferRequest } from 'jolocom-lib/js/interactionTokens/credentialOfferRequest' -import { AuthenticationFlow } from './authenticationFlow' import { CredentialRequest } from 'jolocom-lib/js/interactionTokens/credentialRequest' import { SDKError, ErrorCode } from '../errors' import { Authentication } from 'jolocom-lib/js/interactionTokens/authentication' @@ -36,8 +33,6 @@ import { AuthorizationRequest, AuthorizationFlowState, } from './types' -import { AuthorizationFlow } from './authorizationFlow' -import { EstablishChannelFlow } from './establishChannelFlow' import { InteractionManager, @@ -46,32 +41,42 @@ import { import { SignedCredential } from 'jolocom-lib/js/credentials/signedCredential/signedCredential' import { CredentialOfferResponse } from 'jolocom-lib/js/interactionTokens/credentialOfferResponse' +import { CredentialOfferFlow } from './credentialOfferFlow' +import { CredentialRequestFlow } from './credentialRequestFlow' +import { AuthenticationFlow } from './authenticationFlow' +import { AuthorizationFlow } from './authorizationFlow' +import { EstablishChannelFlow } from './establishChannelFlow' import { EncryptionFlow } from './encryptionFlow' import { DecryptionFlow } from './decryptionFlow' import { SigningFlow } from './signingFlow' -import { generateIdentitySummary } from '../util' - import { ResolutionType, ResolutionFlow, ResolutionFlowState, ResolutionRequest, } from './resolutionFlow' + +import { generateIdentitySummary } from '../util' import { last } from 'ramda' import { TransportAPI, TransportDesc, InteractionTransportType } from '../types' import { Transportable } from '../transports' -const interactionFlowForMessage = { - [InteractionType.CredentialOfferRequest]: CredentialOfferFlow, - [InteractionType.CredentialRequest]: CredentialRequestFlow, - [InteractionType.Authentication]: AuthenticationFlow, - [AuthorizationType.AuthorizationRequest]: AuthorizationFlow, - [EstablishChannelType.EstablishChannelRequest]: EstablishChannelFlow, - [EncryptionType.EncryptionRequest]: EncryptionFlow, - [DecryptionType.DecryptionRequest]: DecryptionFlow, - [ResolutionType.ResolutionRequest]: ResolutionFlow, - [SigningType.SigningRequest]: SigningFlow, -} +export const flows = [ + AuthenticationFlow, + AuthorizationFlow, + CredentialOfferFlow, + CredentialRequestFlow, + EstablishChannelFlow, + SigningFlow, + EncryptionFlow, + DecryptionFlow, + ResolutionFlow +] + +const interactionFlowForMessage = {} +flows.forEach(f => { + interactionFlowForMessage[f.firstMessageType] = f +}) /** * This class is instantiated by the {@link InteractionManager} when it needs to diff --git a/src/interactionManager/interactionManager.ts b/src/interactionManager/interactionManager.ts index fdfee5293..9a722b33e 100644 --- a/src/interactionManager/interactionManager.ts +++ b/src/interactionManager/interactionManager.ts @@ -1,9 +1,15 @@ import { JSONWebToken } from 'jolocom-lib/js/interactionTokens/JSONWebToken' -import { Interaction } from './interaction' +import { Interaction, flows } from './interaction' import { Agent } from '../agent' import { Flow } from './flow' import { TransportAPI } from '../types' import { SDKError, ErrorCode } from '../errors' +import { FlowType } from './types' + +const firstMessageForFlowType = {} +flows.forEach(f => { + firstMessageForFlowType[f.type] = f.firstMessageType +}) /** * The {@link InteractionManager} is an entry point to dealing with {@link @@ -72,9 +78,38 @@ export class InteractionManager { // interaction messages const messages = await this.ctx.storage.get.interactionTokens({ nonce: id }) if (messages.length === 0) throw new SDKError(ErrorCode.NoSuchInteraction) + const interxn = await Interaction.fromMessages(messages, this, id, transportAPI) this.interactions[id] = interxn + return interxn } + + /** + * Returns a list of {@link Interaction} instances given filtering and + * pagination criteria + * + * @param flows - a list of {@link FlowType}s or Flow classes + * @param take - number of results to return (pagination limit) + * @param skip - number of results to skip (pagination offset) + * @param reverse - if true, return the list in reverse storage order + */ + public async listInteractions(opts?: { + flows?: Array, + take?: number, + skip?: number, + reverse?: boolean + }): Promise { + let queryOpts = opts && { + take: opts.take, + skip: opts.skip, + ...(opts.reverse && { order: { id: 'DESC' as 'DESC' } }) + } + const attrs = opts && opts.flows && opts.flows.map(f => ({ + type: typeof f === "string" ? firstMessageForFlowType[f] : f.firstMessageType + })) + const ids = await this.ctx.storage.get.interactionIds(attrs, queryOpts) + return Promise.all(ids.map((id: string) => this.getInteraction(id))) + } } diff --git a/src/interactionManager/resolutionFlow.ts b/src/interactionManager/resolutionFlow.ts index 6bfc42def..6d117a7e8 100644 --- a/src/interactionManager/resolutionFlow.ts +++ b/src/interactionManager/resolutionFlow.ts @@ -30,8 +30,9 @@ export const isResolutionResponse = ( ): t is ResolutionResult => typ === ResolutionType.ResolutionResponse export class ResolutionFlow extends Flow { - public type = FlowType.Resolution + public static type = FlowType.Resolution public state: ResolutionFlowState = {} + public static firstMessageType = ResolutionType.ResolutionRequest public async handleInteractionToken( token: ResolutionRequest | ResolutionResult, diff --git a/src/interactionManager/signingFlow.ts b/src/interactionManager/signingFlow.ts index 5af1a35c0..119ced68f 100644 --- a/src/interactionManager/signingFlow.ts +++ b/src/interactionManager/signingFlow.ts @@ -11,7 +11,8 @@ export interface SigningFlowState extends FlowState { export class SigningFlow extends Flow { public state: SigningFlowState = {} - public type = FlowType.Sign + public static type = FlowType.Sign + public static firstMessageType = SigningType.SigningRequest public constructor(ctx: Interaction) { super(ctx) diff --git a/src/storage/index.ts b/src/storage/index.ts index df6ddbdd1..b016b3b0f 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -18,7 +18,8 @@ export interface EncryptedWalletAttributes { export interface FindOptions { skip?: number - take: number + take?: number + order?: { [k: string]: 'ASC' | 'DESC' } } /** @@ -40,6 +41,12 @@ export interface IStorageStore { interactionToken(token: JSONWebToken): Promise } +export interface InteractionTokenAttrs { + nonce?: string + type?: string + issuer?: string +} + export interface IStorageGet { settingsObject(): Promise<{ [key: string]: any }> setting(key: string): Promise @@ -54,13 +61,13 @@ export interface IStorageGet { publicProfile(did: string): Promise identity(did: string): Promise interactionTokens( - attrs: { - nonce?: string - type?: string - issuer?: string - }, + attrs?: InteractionTokenAttrs, findOptions?: FindOptions, ): Promise>> + interactionIds( + attrs?: InteractionTokenAttrs | InteractionTokenAttrs[], + findOptions?: FindOptions, + ): Promise> } export interface IStorageDelete { diff --git a/tests/agent.test.ts b/tests/agent.test.ts index ac0b0aac7..19a9c93a3 100644 --- a/tests/agent.test.ts +++ b/tests/agent.test.ts @@ -1,5 +1,7 @@ import { destroyAgent, createAgent, meetAgent } from './util' -import { Agent } from 'src' +import { Agent, FlowType } from '../src' +import { AuthenticationFlow } from '../src/interactionManager/authenticationFlow' +import { AuthorizationFlow } from '../src/interactionManager/authorizationFlow' const conn1Name = 'agentTest1' const conn2Name = 'agentTest2' @@ -62,6 +64,77 @@ describe('processJWT', () => { }) }) +describe('listInteractions', () => { + it('lists the requested number of interactions', async () => { + for (let t in [1, 2, 3, 4]) { + const aliceRequest = await alice.authRequestToken({ + callbackURL: 'no', + description: 'authntest'+t + }) + const bobInterxn = await bob.processJWT(aliceRequest.encode()) + const bobResponse = ( + await bobInterxn.createAuthenticationResponse() + ).encode() + await alice.processJWT(bobResponse) + } + for (let t in [1, 2]) { + const aliceRequest = await alice.authorizationRequestToken({ + callbackURL: 'no', + description: 'authztest'+t + }) + const bobInterxn = await bob.processJWT(aliceRequest.encode()) + const bobResponse = ( + await bobInterxn.createAuthorizationResponse() + ).encode() + await alice.processJWT(bobResponse) + } + + const authnInterxns = await alice.interactionManager.listInteractions({ + flows: [AuthenticationFlow], + take: 2 + }) + const authzInterxns = await alice.interactionManager.listInteractions({ + flows: [AuthorizationFlow], + take: 2 + }) + const allInterxns = await alice.interactionManager.listInteractions({ skip: 3 }) + expect(authnInterxns).toHaveLength(2) + expect(authzInterxns).toHaveLength(2) + expect(allInterxns).toHaveLength(3) + }) + + it('lists the requested interactions in the requested order', async () => { + const ids = [] + for (let t in [1, 2, 3, 4]) { + const aliceRequest = await alice.authRequestToken({ + callbackURL: 'no', + description: 'authntest'+t + }) + const bobInterxn = await bob.processJWT(aliceRequest.encode()) + ids.push(bobInterxn.id) + const bobResponse = ( + await bobInterxn.createAuthenticationResponse() + ).encode() + await alice.processJWT(bobResponse) + } + + const authnInterxns = await alice.interactionManager.listInteractions({ + flows: [FlowType.Authentication], + take: 2 + }) + expect(authnInterxns).toHaveLength(2) + expect(authnInterxns[0].id).toEqual(ids[0]) + + const reversedAuthnInterxns = await alice.interactionManager.listInteractions({ + flows: [FlowType.Authentication], + take: 2, + reverse: true + }) + expect(reversedAuthnInterxns).toHaveLength(2) + expect(reversedAuthnInterxns[0].id).toEqual(ids[ids.length-1]) + }) +}) + describe('findInteraction', () => { it('finds an interaction based on a JSONWebToken', async () => { const jwt = await alice.authRequestToken({ callbackURL: '', description: 'test' }) diff --git a/yarn.lock b/yarn.lock index 6a095ba8d..284799f3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1559,10 +1559,10 @@ dependencies: ethers "5.0.5" -"@jolocom/sdk-storage-typeorm@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@jolocom/sdk-storage-typeorm/-/sdk-storage-typeorm-4.0.0.tgz#02185ab15d32e436d47e36bf73beea3e656d0e8f" - integrity sha512-nLY0Km+RvCPy0W7OAhQacV0zJn+oGlV5nRwSxXPM9M+m+cIVPKhcOjaw3D/k1YAC9CoT4MNK5ZT+nEyoEnnGPA== +"@jolocom/sdk-storage-typeorm@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@jolocom/sdk-storage-typeorm/-/sdk-storage-typeorm-4.1.0.tgz#008ddad8756e39cdd37b9c6c6ecea9fa9fee0a38" + integrity sha512-OxyGGk6D5DxSoAWmaNZSvGyyEkuLnbxLTnILuVJPbq1WGxUjKaqIoI/U6bfyIy+2DcL0mZflClkGaiQoIENkbA== dependencies: class-transformer "^0.3.1" ramda "^0.27.1" @@ -5191,7 +5191,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@1.x, mkdirp@^1.0.3: +mkdirp@1.x: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -6975,10 +6975,10 @@ typedoc@^0.19.2: shelljs "^0.8.4" typedoc-default-themes "^0.11.4" -typeorm@^0.2.25: - version "0.2.25" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.25.tgz#1a33513b375b78cc7740d2405202208b918d7dde" - integrity sha512-yzQ995fyDy5wolSLK9cmjUNcmQdixaeEm2TnXB5HN++uKbs9TiR6Y7eYAHpDlAE8s9J1uniDBgytecCZVFergQ== +typeorm@0.2.24: + version "0.2.24" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.24.tgz#cd0fbd907326873a96c98e290fca49c589f0ffa8" + integrity sha512-L9tQv6nNLRyh+gex/qc8/CyLs8u0kXKqk1OjYGF13k/KOg6N2oibwkuGgv0FuoTGYx2ta2NmqvuMUAMrHIY5ew== dependencies: app-root-path "^3.0.0" buffer "^5.1.0" @@ -6988,7 +6988,7 @@ typeorm@^0.2.25: dotenv "^6.2.0" glob "^7.1.2" js-yaml "^3.13.1" - mkdirp "^1.0.3" + mkdirp "^0.5.1" reflect-metadata "^0.1.13" sha.js "^2.4.11" tslib "^1.9.0"