Skip to content

Commit

Permalink
discovery-protocol handler (#294)
Browse files Browse the repository at this point in the history
* init discovery-protocol handler
  • Loading branch information
volodymyr-basiuk authored Dec 20, 2024
1 parent 812ce46 commit 74069f7
Show file tree
Hide file tree
Showing 11 changed files with 689 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module.exports = {
...spellcheckerRule,
cspell: {
...cspellConfig,
ignoreWords: ['unmarshal', 'JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw', 'gdwj', 'fwor', 'multichain', "ETHWEI", "ETHGWEI"]
ignoreWords: ['unmarshal', 'JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw', 'gdwj', 'fwor', 'multichain', "ETHWEI", "ETHGWEI", "didcomm"]
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@0xpolygonid/js-sdk",
"version": "1.25.1",
"version": "1.26.0",
"description": "SDK to work with Polygon ID",
"main": "dist/node/cjs/index.js",
"module": "dist/node/esm/index.js",
Expand Down
9 changes: 8 additions & 1 deletion src/iden3comm/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AcceptProfile } from './types';

const IDEN3_PROTOCOL = 'https://iden3-communication.io/';
const DIDCOMM_PROTOCOL = 'https://didcomm.org/';
/**
* Constants for Iden3 protocol
*/
Expand Down Expand Up @@ -48,7 +49,13 @@ export const PROTOCOL_MESSAGE_TYPE = Object.freeze({
// PaymentRequestMessageType is type for payment-request message
PAYMENT_REQUEST_MESSAGE_TYPE: `${IDEN3_PROTOCOL}credentials/0.1/payment-request` as const,
// PaymentMessageType is type for payment message
PAYMENT_MESSAGE_TYPE: `${IDEN3_PROTOCOL}credentials/0.1/payment` as const
PAYMENT_MESSAGE_TYPE: `${IDEN3_PROTOCOL}credentials/0.1/payment` as const,
// DiscoveryProtocolQueriesMessageType is type for didcomm discovery protocol queries
DISCOVERY_PROTOCOL_QUERIES_MESSAGE_TYPE:
`${DIDCOMM_PROTOCOL}discover-features/2.0/queries` as const,
// DiscoveryProtocolDiscloseMessageType is type for didcomm discovery protocol disclose
DISCOVERY_PROTOCOL_DISCLOSE_MESSAGE_TYPE:
`${DIDCOMM_PROTOCOL}discover-features/2.0/disclose` as const
});

/**
Expand Down
275 changes: 275 additions & 0 deletions src/iden3comm/handlers/discovery-protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import { PROTOCOL_MESSAGE_TYPE } from '../constants';

import { BasicMessage, IPackageManager, ProtocolMessage } from '../types';

import * as uuid from 'uuid';
import {
DiscoverFeatureDiscloseMessage,
DiscoverFeatureDisclosure,
DiscoverFeatureQueriesMessage,
DiscoverFeatureQuery,
DiscoverFeatureQueryType,
DiscoveryProtocolFeatureType
} from '../types/protocol/discovery-protocol';
import {
AbstractMessageHandler,
BasicHandlerOptions,
IProtocolMessageHandler
} from './message-handler';
import { getUnixTimestamp } from '@iden3/js-iden3-core';
import { verifyExpiresTime } from './common';

/**
* @beta
* DiscoveryProtocolOptions contains options for DiscoveryProtocolHandler
* @public
* @interface DiscoveryProtocolOptions
*/
export interface DiscoveryProtocolOptions {
packageManager: IPackageManager;
protocols?: Array<ProtocolMessage>;
goalCodes?: Array<string>;
headers?: Array<string>;
}

/**
*
* Options to pass to discovery-protocol handler
*
* @beta
* @public
* @type DiscoveryProtocolHandlerOptions
*/
export type DiscoveryProtocolHandlerOptions = BasicHandlerOptions & {
disclosureExpiresDate?: Date;
};

/**
* @beta
* createDiscoveryFeatureQueryMessage is a function to create didcomm protocol discovery-feature query message
* @param opts - discovery-feature query options
* @returns `DiscoverFeatureQueriesMessage`
*/
export function createDiscoveryFeatureQueryMessage(
queries: DiscoverFeatureQuery[],
opts?: {
from?: string;
to?: string;
expires_time?: number;
}
): DiscoverFeatureQueriesMessage {
const uuidv4 = uuid.v4();
return {
id: uuidv4,
thid: uuidv4,
type: PROTOCOL_MESSAGE_TYPE.DISCOVERY_PROTOCOL_QUERIES_MESSAGE_TYPE,
body: {
queries
},
from: opts?.from,
to: opts?.to,
created_time: getUnixTimestamp(new Date()),
expires_time: opts?.expires_time
};
}

/**
* @beta
* createDiscoveryFeatureDiscloseMessage is a function to create didcomm protocol discovery-feature disclose message
* @param {DiscoverFeatureDisclosure[]} disclosures - array of disclosures
* @param opts - basic message options
* @returns `DiscoverFeatureQueriesMessage`
*/
export function createDiscoveryFeatureDiscloseMessage(
disclosures: DiscoverFeatureDisclosure[],
opts?: {
from?: string;
to?: string;
expires_time?: number;
}
): DiscoverFeatureDiscloseMessage {
const uuidv4 = uuid.v4();
return {
id: uuidv4,
thid: uuidv4,
type: PROTOCOL_MESSAGE_TYPE.DISCOVERY_PROTOCOL_DISCLOSE_MESSAGE_TYPE,
body: {
disclosures
},
from: opts?.from,
to: opts?.to,
created_time: getUnixTimestamp(new Date()),
expires_time: opts?.expires_time
};
}

/**
* Interface to work with discovery protocol handler
*
* @beta
* @public
* @interface IDiscoveryProtocolHandler
*/
export interface IDiscoveryProtocolHandler {
/**
* handle discovery query message
*
* @param {DiscoverFeatureQueriesMessage} message - discover feature queries message
* @param {{ expires_time?: number}} opts - discover feature handle options
* @returns {Promise<DiscoverFeatureDiscloseMessage>} - discover feature disclose message
*/
handleDiscoveryQuery(
message: DiscoverFeatureQueriesMessage,
opts?: DiscoveryProtocolHandlerOptions
): Promise<DiscoverFeatureDiscloseMessage>;
}

/**
*
* Handler for discovery protocol
*
* @public
* @beta
* @class DiscoveryProtocolHandler
* @implements implements DiscoveryProtocolHandler interface
*/
export class DiscoveryProtocolHandler
extends AbstractMessageHandler
implements IDiscoveryProtocolHandler, IProtocolMessageHandler
{
/**
* Creates an instance of DiscoveryProtocolHandler.
* @param {DiscoveryProtocolOptions} _options - discovery protocol options
*/
constructor(private readonly _options: DiscoveryProtocolOptions) {
super();
const headers = [
'id',
'typ',
'type',
'thid',
'body',
'from',
'to',
'created_time',
'expires_time'
];
if (!_options.headers) {
_options.headers = headers;
}
}

/**
* @inheritdoc IProtocolMessageHandler#handle
*/
public async handle(
message: BasicMessage,
context: { [key: string]: unknown }
): Promise<BasicMessage | null> {
switch (message.type) {
case PROTOCOL_MESSAGE_TYPE.DISCOVERY_PROTOCOL_QUERIES_MESSAGE_TYPE:
return await this.handleDiscoveryQuery(message as DiscoverFeatureQueriesMessage, context);
default:
return super.handle(message, context as { [key: string]: unknown });
}
}

/**
* @inheritdoc IDiscoveryProtocolHandler#handleDiscoveryQuery
*/
async handleDiscoveryQuery(
message: DiscoverFeatureQueriesMessage,
opts?: DiscoveryProtocolHandlerOptions
): Promise<DiscoverFeatureDiscloseMessage> {
if (!opts?.allowExpiredMessages) {
verifyExpiresTime(message);
}

const disclosures: DiscoverFeatureDisclosure[] = [];
for (const query of message.body.queries) {
disclosures.push(...this.handleQuery(query));
}

return Promise.resolve(
createDiscoveryFeatureDiscloseMessage(disclosures, {
to: message.from,
from: message.to,
expires_time: opts?.disclosureExpiresDate
? getUnixTimestamp(opts.disclosureExpiresDate)
: undefined
})
);
}

private handleQuery(query: DiscoverFeatureQuery): DiscoverFeatureDisclosure[] {
let result: DiscoverFeatureDisclosure[] = [];
switch (query[DiscoverFeatureQueryType.FeatureType]) {
case DiscoveryProtocolFeatureType.Accept:
result = this.handleAcceptQuery();
break;
case DiscoveryProtocolFeatureType.Protocol:
result = this.handleProtocolQuery();
break;
case DiscoveryProtocolFeatureType.GoalCode:
result = this.handleGoalCodeQuery();
break;
case DiscoveryProtocolFeatureType.Header:
result = this.handleHeaderQuery();
break;
}

return this.handleMatch(result, query.match);
}

private handleAcceptQuery(): DiscoverFeatureDisclosure[] {
const acceptProfiles = this._options.packageManager.getSupportedProfiles();
return acceptProfiles.map((profile) => ({
[DiscoverFeatureQueryType.FeatureType]: DiscoveryProtocolFeatureType.Accept,
id: profile
}));
}

private handleProtocolQuery(): DiscoverFeatureDisclosure[] {
return (
this._options.protocols?.map((protocol) => ({
[DiscoverFeatureQueryType.FeatureType]: DiscoveryProtocolFeatureType.Protocol,
id: protocol
})) ?? []
);
}

private handleGoalCodeQuery(): DiscoverFeatureDisclosure[] {
return (
this._options.goalCodes?.map((goalCode) => ({
[DiscoverFeatureQueryType.FeatureType]: DiscoveryProtocolFeatureType.GoalCode,
id: goalCode
})) ?? []
);
}

private handleHeaderQuery(): DiscoverFeatureDisclosure[] {
return (
this._options.headers?.map((header) => ({
[DiscoverFeatureQueryType.FeatureType]: DiscoveryProtocolFeatureType.Header,
id: header
})) ?? []
);
}

private handleMatch(
disclosures: DiscoverFeatureDisclosure[],
match?: string
): DiscoverFeatureDisclosure[] {
if (!match || match === '*') {
return disclosures;
}
const regExp = this.wildcardToRegExp(match);
return disclosures.filter((disclosure) => regExp.test(disclosure.id));
}

private wildcardToRegExp(match: string): RegExp {
// Escape special regex characters, then replace `*` with `.*`
const regexPattern = match.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
return new RegExp(`^${regexPattern}$`);
}
}
1 change: 1 addition & 0 deletions src/iden3comm/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './common';
export * from './credential-proposal';
export * from './message-handler';
export * from './payment';
export * from './discovery-protocol';
13 changes: 13 additions & 0 deletions src/iden3comm/packageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ export class PackageManager implements IPackageManager {
this.packers = new Map<MediaType, IPacker>();
}

/** {@inheritDoc IPackageManager.getSupportedProfiles} */
getSupportedProfiles(): string[] {
const acceptProfiles: string[] = [];
const mediaTypes = this.getSupportedMediaTypes();
for (const mediaType of mediaTypes) {
const p = this.packers.get(mediaType);
if (p) {
acceptProfiles.push(...p.getSupportedProfiles());
}
}
return [...new Set(acceptProfiles)];
}

/** {@inheritDoc IPackageManager.isProfileSupported} */
isProfileSupported(mediaType: MediaType, profile: string): boolean {
const p = this.packers.get(mediaType);
Expand Down
1 change: 1 addition & 0 deletions src/iden3comm/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './protocol/contract-request';
export * from './protocol/proposal-request';
export * from './protocol/payment';
export * from './protocol/accept-profile';
export * from './protocol/discovery-protocol';

export * from './packer';
export * from './models';
Expand Down
7 changes: 7 additions & 0 deletions src/iden3comm/types/packageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ export interface IPackageManager {
*/
getSupportedMediaTypes(): MediaType[];

/**
* gets supported accept profiles by packer manager
*
* @returns string[]
*/
getSupportedProfiles(): string[];

/**
* returns true if media type and algorithms supported by packer manager
*
Expand Down
Loading

0 comments on commit 74069f7

Please sign in to comment.