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

discovery-protocol handler #294

Merged
merged 13 commits into from
Dec 20, 2024
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.0",
"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
Loading