Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into feat/image-url
Browse files Browse the repository at this point in the history
  • Loading branch information
TimoGlastra committed Oct 6, 2021
2 parents ce5e630 + e316e04 commit 54aee66
Show file tree
Hide file tree
Showing 68 changed files with 753 additions and 316 deletions.
10 changes: 6 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

You are encouraged to contribute to the repository by **forking and submitting a pull request**.

If you would like to propose a significant change, please open an issue first to discuss the work with the community to avoid re-work.
For significant changes, please open an issue first to discuss the proposed changes to avoid re-work.

(If you are new to GitHub, you might want to start with a [basic tutorial](https://help.github.com/articles/set-up-git) and check out a more detailed guide to [pull requests](https://help.github.com/articles/using-pull-requests/).)
(If you are new to GitHub, you might start with a [basic tutorial](https://help.github.com/articles/set-up-git) and check out a more detailed guide to [pull requests](https://help.github.com/articles/using-pull-requests/).)

Pull requests will be evaluated by the repository guardians on a schedule and if deemed beneficial will be committed to the `main` branch. Pull requests should have a descriptive name and include an summary of all changes made in the pull request description.
Pull requests will be evaluated by the repository guardians on a schedule and if deemed beneficial will be committed to the main branch. Pull requests should have a descriptive name and include an summary of all changes made in the pull request description.

All contributors retain the original copyright to their stuff, but by contributing to this project, you grant a world-wide, royalty-free, perpetual, irrevocable, non-exclusive, transferable license to all users **under the terms of the license under which this project is distributed.**
If you would like to propose a significant change, please open an issue first to discuss the work with the community.

Contributions are made pursuant to the Developer's Certificate of Origin, available at [https://developercertificate.org](https://developercertificate.org), and licensed under the Apache License, version 2.0 (Apache-2.0).
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ Now that your project is setup and everything seems to be working, it is time to

If you would like to contribute to the framework, please read the [Framework Developers README](/DEVREADME.md) and the [CONTRIBUTING](/CONTRIBUTING.md) guidelines. These documents will provide more information to get you started!

The Aries Framework JavaScript call takes place every week at Thursday, 14:00 UTC via [Zoom](https://zoom.us/j/92215586249?pwd=Vm5ZTGV4T0cwVEl4blh3MjBzYjVYZz09). This meeting is for contributors to groom and plan the backlog, and discuss issues. Feel free to join!
The Aries Framework JavaScript call takes place every week at Thursday, 14:00 UTC via [Zoom](https://zoom.us/j/92215586249?pwd=Vm5ZTGV4T0cwVEl4blh3MjBzYjVYZz09).
This meeting is for contributors to groom and plan the backlog, and discuss issues.
Meeting agendas and recordings can be found [here](https://wiki.hyperledger.org/display/ARIES/Framework+JS+Meetings).
Feel free to join!

## License

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/agent/BaseMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Matches } from 'class-validator'
import { uuid } from '../utils/uuid'

export const MessageIdRegExp = /[-_./a-zA-Z0-9]{8,64}/
export const MessageTypeRegExp = /(.*?)([a-z0-9._-]+)\/(\d[^/]*)\/([a-z0-9._-]+)$/
export const MessageTypeRegExp = /(.*?)([a-zA-Z0-9._-]+)\/(\d[^/]*)\/([a-zA-Z0-9._-]+)$/

export type BaseMessageConstructor = Constructor<BaseMessage>

Expand Down
22 changes: 20 additions & 2 deletions packages/core/src/agent/Dispatcher.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { Logger } from '../logger'
import type { OutboundMessage, OutboundServiceMessage } from '../types'
import type { AgentMessage } from './AgentMessage'
import type { AgentMessageProcessedEvent } from './Events'
import type { Handler } from './Handler'
import type { InboundMessageContext } from './models/InboundMessageContext'

import { Lifecycle, scoped } from 'tsyringe'

import { AgentConfig } from '../agent/AgentConfig'
import { AriesFrameworkError } from '../error/AriesFrameworkError'

import { EventEmitter } from './EventEmitter'
Expand All @@ -17,10 +20,12 @@ class Dispatcher {
private handlers: Handler[] = []
private messageSender: MessageSender
private eventEmitter: EventEmitter
private logger: Logger

public constructor(messageSender: MessageSender, eventEmitter: EventEmitter) {
public constructor(messageSender: MessageSender, eventEmitter: EventEmitter, agentConfig: AgentConfig) {
this.messageSender = messageSender
this.eventEmitter = eventEmitter
this.logger = agentConfig.logger
}

public registerHandler(handler: Handler) {
Expand All @@ -35,7 +40,20 @@ class Dispatcher {
throw new AriesFrameworkError(`No handler for message type "${message.type}" found`)
}

const outboundMessage = await handler.handle(messageContext)
let outboundMessage: OutboundMessage<AgentMessage> | OutboundServiceMessage<AgentMessage> | void

try {
outboundMessage = await handler.handle(messageContext)
} catch (error) {
this.logger.error(`Error handling message with type ${message.type}`, {
message: message.toJSON(),
senderVerkey: messageContext.senderVerkey,
recipientVerkey: messageContext.recipientVerkey,
connectionId: messageContext.connection?.id,
})

throw error
}

if (outboundMessage && isOutboundServiceMessage(outboundMessage)) {
await this.messageSender.sendMessageToService({
Expand Down
46 changes: 32 additions & 14 deletions packages/core/src/agent/MessageReceiver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Logger } from '../logger'
import type { ConnectionRecord } from '../modules/connections'
import type { InboundTransport } from '../transport'
import type { UnpackedMessageContext, UnpackedMessage, WireMessage } from '../types'
import type { AgentMessage } from './AgentMessage'
Expand All @@ -9,6 +10,7 @@ import { Lifecycle, scoped } from 'tsyringe'
import { AriesFrameworkError } from '../error'
import { ConnectionService } from '../modules/connections/services/ConnectionService'
import { JsonTransformer } from '../utils/JsonTransformer'
import { MessageValidator } from '../utils/MessageValidator'
import { replaceLegacyDidSovPrefixOnMessage } from '../utils/messageType'

import { AgentConfig } from './AgentConfig'
Expand Down Expand Up @@ -63,18 +65,16 @@ export class MessageReceiver {
const senderKey = unpackedMessage.senderVerkey
const recipientKey = unpackedMessage.recipientVerkey

let connection = undefined
let connection: ConnectionRecord | null = null

// Only fetch connection if recipientKey and senderKey are present (AuthCrypt)
if (senderKey && recipientKey) {
// TODO: only attach if theirKey is present. Otherwise a connection that may not be complete, validated or correct will
// be attached to the message context. See #76
connection = (await this.connectionService.findByVerkey(recipientKey)) || undefined

// We check whether the sender key is the same as the key we have stored in the connection
// otherwise everyone could send messages to our key and we would just accept
// it as if it was send by the key of the connection.
if (connection && connection.theirKey != null && connection.theirKey != senderKey) {
connection = await this.connectionService.findByVerkey(recipientKey)

// Throw error if the recipient key (ourKey) does not match the key of the connection record
if (connection && connection.theirKey !== null && connection.theirKey !== senderKey) {
throw new AriesFrameworkError(
`Inbound message 'sender_key' ${senderKey} is different from connection.theirKey ${connection.theirKey}`
`Inbound message senderKey '${senderKey}' is different from connection.theirKey '${connection.theirKey}'`
)
}
}
Expand All @@ -85,8 +85,22 @@ export class MessageReceiver {
)

const message = await this.transformMessage(unpackedMessage)
try {
await MessageValidator.validate(message)
} catch (error) {
this.logger.error(`Error validating message ${message.type}`, {
errors: error,
message: message.toJSON(),
})

throw error
}

const messageContext = new InboundMessageContext(message, {
connection,
// Only make the connection available in message context if the connection is ready
// To prevent unwanted usage of unready connections. Connections can still be retrieved from
// Storage if the specific protocol allows an unready connection to be used.
connection: connection?.isReady ? connection : undefined,
senderVerkey: senderKey,
recipientVerkey: recipientKey,
})
Expand All @@ -95,14 +109,18 @@ export class MessageReceiver {
// That can happen when inbound message has `return_route` set to `all` or `thread`.
// If `return_route` defines just `thread`, we decide later whether to use session according to outbound message `threadId`.
if (senderKey && recipientKey && message.hasAnyReturnRoute() && session) {
this.logger.debug(`Storing session for inbound message '${message.id}'`)
const keys = {
recipientKeys: [senderKey],
routingKeys: [],
senderKey: recipientKey,
}
session.keys = keys
session.inboundMessage = message
session.connection = connection
// We allow unready connections to be attached to the session as we want to be able to
// use return routing to make connections. This is especially useful for creating connections
// with mediators when you don't have a public endpoint yet.
session.connection = connection ?? undefined
this.transportService.saveSession(session)
}

Expand All @@ -125,7 +143,7 @@ export class MessageReceiver {
this.logger.error('error while unpacking message', {
error,
packedMessage,
errorMessage: error.message,
errorMessage: error instanceof Error ? error.message : error,
})
throw error
}
Expand Down Expand Up @@ -156,7 +174,7 @@ export class MessageReceiver {
const MessageClass = this.dispatcher.getMessageClassForType(messageType)

if (!MessageClass) {
throw new AriesFrameworkError(`No handler for message type "${messageType}" found`)
throw new AriesFrameworkError(`No message class found for message type "${messageType}"`)
}

// Cast the plain JSON object to specific instance of Message extended from AgentMessage
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/agent/MessageSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export class MessageSender {
// Try to send to already open session
const session = this.transportService.findSessionByConnectionId(connection.id)
if (session?.inboundMessage?.hasReturnRouting(payload.threadId)) {
this.logger.debug(`Found session with return routing for message '${payload.id}' (connection '${connection.id}'`)
try {
await this.sendMessageToSession(session, payload)
return
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/decorators/ack/AckDecoratorExtension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BaseMessageConstructor } from '../../agent/BaseMessage'

import { Expose, Type } from 'class-transformer'
import { ValidateNested } from 'class-validator'
import { IsInstance, IsOptional, ValidateNested } from 'class-validator'

import { AckDecorator } from './AckDecorator'

Expand All @@ -10,6 +10,8 @@ export function AckDecorated<T extends BaseMessageConstructor>(Base: T) {
@Expose({ name: '~please_ack' })
@Type(() => AckDecorator)
@ValidateNested()
@IsInstance(AckDecorator)
@IsOptional()
public pleaseAck?: AckDecorator

public setPleaseAck() {
Expand Down
13 changes: 12 additions & 1 deletion packages/core/src/decorators/attachment/Attachment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { Expose, Type } from 'class-transformer'
import { IsBase64, IsDate, IsHash, IsInt, IsMimeType, IsOptional, IsString, ValidateNested } from 'class-validator'
import {
IsBase64,
IsDate,
IsHash,
IsInstance,
IsInt,
IsMimeType,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator'

import { uuid } from '../../utils/uuid'

Expand Down Expand Up @@ -130,5 +140,6 @@ export class Attachment {

@Type(() => AttachmentData)
@ValidateNested()
@IsInstance(AttachmentData)
public data!: AttachmentData
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BaseMessageConstructor } from '../../agent/BaseMessage'

import { Expose, Type } from 'class-transformer'
import { IsArray, IsOptional, ValidateNested } from 'class-validator'
import { IsInstance, IsOptional, ValidateNested } from 'class-validator'

import { Attachment } from './Attachment'

Expand All @@ -13,7 +13,7 @@ export function AttachmentDecorated<T extends BaseMessageConstructor>(Base: T) {
@Expose({ name: '~attach' })
@Type(() => Attachment)
@ValidateNested()
@IsArray()
@IsInstance(Attachment, { each: true })
@IsOptional()
public attachments?: Attachment[]

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/decorators/l10n/L10nDecoratorExtension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BaseMessageConstructor } from '../../agent/BaseMessage'

import { Expose, Type } from 'class-transformer'
import { ValidateNested } from 'class-validator'
import { IsInstance, IsOptional, ValidateNested } from 'class-validator'

import { L10nDecorator } from './L10nDecorator'

Expand All @@ -10,6 +10,8 @@ export function L10nDecorated<T extends BaseMessageConstructor>(Base: T) {
@Expose({ name: '~l10n' })
@Type(() => L10nDecorator)
@ValidateNested()
@IsOptional()
@IsInstance(L10nDecorator)
public l10n?: L10nDecorator

public addLocale(locale: string) {
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/decorators/signature/SignatureDecorator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Expose } from 'class-transformer'
import { Matches } from 'class-validator'
import { IsString, Matches } from 'class-validator'

import { MessageTypeRegExp } from '../../agent/BaseMessage'

Expand All @@ -22,11 +22,14 @@ export class SignatureDecorator {
public signatureType!: string

@Expose({ name: 'sig_data' })
@IsString()
public signatureData!: string

@Expose({ name: 'signer' })
@IsString()
public signer!: string

@Expose({ name: 'signature' })
@IsString()
public signature!: string
}
7 changes: 6 additions & 1 deletion packages/core/src/decorators/thread/ThreadDecorator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Expose } from 'class-transformer'
import { Matches } from 'class-validator'
import { IsInt, IsOptional, Matches } from 'class-validator'

import { MessageIdRegExp } from '../../agent/BaseMessage'

Expand All @@ -20,25 +20,30 @@ export class ThreadDecorator {
*/
@Expose({ name: 'thid' })
@Matches(MessageIdRegExp)
@IsOptional()
public threadId?: string

/**
* An optional parent `thid`. Used when branching or nesting a new interaction off of an existing one.
*/
@Expose({ name: 'pthid' })
@Matches(MessageIdRegExp)
@IsOptional()
public parentThreadId?: string

/**
* A number that tells where this message fits in the sequence of all messages that the current sender has contributed to this thread.
*/
@Expose({ name: 'sender_order' })
@IsOptional()
@IsInt()
public senderOrder?: number

/**
* Reports the highest `sender_order` value that the sender has seen from other sender(s) on the thread.
* This value is often missing if it is the first message in an interaction, but should be used otherwise, as it provides an implicit ACK.
*/
@Expose({ name: 'received_orders' })
@IsOptional()
public receivedOrders?: { [key: string]: number }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BaseMessageConstructor } from '../../agent/BaseMessage'

import { Expose, Type } from 'class-transformer'
import { ValidateNested } from 'class-validator'
import { IsInstance, IsOptional, ValidateNested } from 'class-validator'

import { ThreadDecorator } from './ThreadDecorator'

Expand All @@ -11,8 +11,10 @@ export function ThreadDecorated<T extends BaseMessageConstructor>(Base: T) {
* The ~thread decorator is generally required on any type of response, since this is what connects it with the original request.
*/
@Expose({ name: '~thread' })
@IsOptional()
@Type(() => ThreadDecorator)
@ValidateNested()
@IsInstance(ThreadDecorator)
public thread?: ThreadDecorator

public get threadId(): string {
Expand Down
Loading

0 comments on commit 54aee66

Please sign in to comment.