|
| 1 | +import { PROTOCOL_MESSAGE_TYPE } from '../constants'; |
| 2 | + |
| 3 | +import { BasicMessage, IPackageManager, ProtocolMessage } from '../types'; |
| 4 | + |
| 5 | +import * as uuid from 'uuid'; |
| 6 | +import { |
| 7 | + DiscoverFeatureDiscloseMessage, |
| 8 | + DiscoverFeatureDisclosure, |
| 9 | + DiscoverFeatureQueriesMessage, |
| 10 | + DiscoverFeatureQuery, |
| 11 | + DiscoverFeatureQueryType, |
| 12 | + DiscoveryProtocolFeatureType |
| 13 | +} from '../types/protocol/discovery-protocol'; |
| 14 | +import { |
| 15 | + AbstractMessageHandler, |
| 16 | + BasicHandlerOptions, |
| 17 | + IProtocolMessageHandler |
| 18 | +} from './message-handler'; |
| 19 | +import { getUnixTimestamp } from '@iden3/js-iden3-core'; |
| 20 | +import { verifyExpiresTime } from './common'; |
| 21 | + |
| 22 | +/** |
| 23 | + * @beta |
| 24 | + * DiscoveryProtocolOptions contains options for DiscoveryProtocolHandler |
| 25 | + * @public |
| 26 | + * @interface DiscoveryProtocolOptions |
| 27 | + */ |
| 28 | +export interface DiscoveryProtocolOptions { |
| 29 | + packageManager: IPackageManager; |
| 30 | + protocols?: Array<ProtocolMessage>; |
| 31 | + goalCodes?: Array<string>; |
| 32 | + headers?: Array<string>; |
| 33 | +} |
| 34 | + |
| 35 | +/** |
| 36 | + * |
| 37 | + * Options to pass to discovery-protocol handler |
| 38 | + * |
| 39 | + * @beta |
| 40 | + * @public |
| 41 | + * @type DiscoveryProtocolHandlerOptions |
| 42 | + */ |
| 43 | +export type DiscoveryProtocolHandlerOptions = BasicHandlerOptions & { |
| 44 | + disclosureExpiresDate?: Date; |
| 45 | +}; |
| 46 | + |
| 47 | +/** |
| 48 | + * @beta |
| 49 | + * createDiscoveryFeatureQueryMessage is a function to create didcomm protocol discovery-feature query message |
| 50 | + * @param opts - discovery-feature query options |
| 51 | + * @returns `DiscoverFeatureQueriesMessage` |
| 52 | + */ |
| 53 | +export function createDiscoveryFeatureQueryMessage( |
| 54 | + queries: DiscoverFeatureQuery[], |
| 55 | + opts?: { |
| 56 | + from?: string; |
| 57 | + to?: string; |
| 58 | + expires_time?: number; |
| 59 | + } |
| 60 | +): DiscoverFeatureQueriesMessage { |
| 61 | + const uuidv4 = uuid.v4(); |
| 62 | + return { |
| 63 | + id: uuidv4, |
| 64 | + thid: uuidv4, |
| 65 | + type: PROTOCOL_MESSAGE_TYPE.DISCOVERY_PROTOCOL_QUERIES_MESSAGE_TYPE, |
| 66 | + body: { |
| 67 | + queries |
| 68 | + }, |
| 69 | + from: opts?.from, |
| 70 | + to: opts?.to, |
| 71 | + created_time: getUnixTimestamp(new Date()), |
| 72 | + expires_time: opts?.expires_time |
| 73 | + }; |
| 74 | +} |
| 75 | + |
| 76 | +/** |
| 77 | + * @beta |
| 78 | + * createDiscoveryFeatureDiscloseMessage is a function to create didcomm protocol discovery-feature disclose message |
| 79 | + * @param {DiscoverFeatureDisclosure[]} disclosures - array of disclosures |
| 80 | + * @param opts - basic message options |
| 81 | + * @returns `DiscoverFeatureQueriesMessage` |
| 82 | + */ |
| 83 | +export function createDiscoveryFeatureDiscloseMessage( |
| 84 | + disclosures: DiscoverFeatureDisclosure[], |
| 85 | + opts?: { |
| 86 | + from?: string; |
| 87 | + to?: string; |
| 88 | + expires_time?: number; |
| 89 | + } |
| 90 | +): DiscoverFeatureDiscloseMessage { |
| 91 | + const uuidv4 = uuid.v4(); |
| 92 | + return { |
| 93 | + id: uuidv4, |
| 94 | + thid: uuidv4, |
| 95 | + type: PROTOCOL_MESSAGE_TYPE.DISCOVERY_PROTOCOL_DISCLOSE_MESSAGE_TYPE, |
| 96 | + body: { |
| 97 | + disclosures |
| 98 | + }, |
| 99 | + from: opts?.from, |
| 100 | + to: opts?.to, |
| 101 | + created_time: getUnixTimestamp(new Date()), |
| 102 | + expires_time: opts?.expires_time |
| 103 | + }; |
| 104 | +} |
| 105 | + |
| 106 | +/** |
| 107 | + * Interface to work with discovery protocol handler |
| 108 | + * |
| 109 | + * @beta |
| 110 | + * @public |
| 111 | + * @interface IDiscoveryProtocolHandler |
| 112 | + */ |
| 113 | +export interface IDiscoveryProtocolHandler { |
| 114 | + /** |
| 115 | + * handle discovery query message |
| 116 | + * |
| 117 | + * @param {DiscoverFeatureQueriesMessage} message - discover feature queries message |
| 118 | + * @param {{ expires_time?: number}} opts - discover feature handle options |
| 119 | + * @returns {Promise<DiscoverFeatureDiscloseMessage>} - discover feature disclose message |
| 120 | + */ |
| 121 | + handleDiscoveryQuery( |
| 122 | + message: DiscoverFeatureQueriesMessage, |
| 123 | + opts?: DiscoveryProtocolHandlerOptions |
| 124 | + ): Promise<DiscoverFeatureDiscloseMessage>; |
| 125 | +} |
| 126 | + |
| 127 | +/** |
| 128 | + * |
| 129 | + * Handler for discovery protocol |
| 130 | + * |
| 131 | + * @public |
| 132 | + * @beta |
| 133 | + * @class DiscoveryProtocolHandler |
| 134 | + * @implements implements DiscoveryProtocolHandler interface |
| 135 | + */ |
| 136 | +export class DiscoveryProtocolHandler |
| 137 | + extends AbstractMessageHandler |
| 138 | + implements IDiscoveryProtocolHandler, IProtocolMessageHandler |
| 139 | +{ |
| 140 | + /** |
| 141 | + * Creates an instance of DiscoveryProtocolHandler. |
| 142 | + * @param {DiscoveryProtocolOptions} _options - discovery protocol options |
| 143 | + */ |
| 144 | + constructor(private readonly _options: DiscoveryProtocolOptions) { |
| 145 | + super(); |
| 146 | + const headers = [ |
| 147 | + 'id', |
| 148 | + 'typ', |
| 149 | + 'type', |
| 150 | + 'thid', |
| 151 | + 'body', |
| 152 | + 'from', |
| 153 | + 'to', |
| 154 | + 'created_time', |
| 155 | + 'expires_time' |
| 156 | + ]; |
| 157 | + if (!_options.headers) { |
| 158 | + _options.headers = headers; |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + /** |
| 163 | + * @inheritdoc IProtocolMessageHandler#handle |
| 164 | + */ |
| 165 | + public async handle( |
| 166 | + message: BasicMessage, |
| 167 | + context: { [key: string]: unknown } |
| 168 | + ): Promise<BasicMessage | null> { |
| 169 | + switch (message.type) { |
| 170 | + case PROTOCOL_MESSAGE_TYPE.DISCOVERY_PROTOCOL_QUERIES_MESSAGE_TYPE: |
| 171 | + return await this.handleDiscoveryQuery(message as DiscoverFeatureQueriesMessage, context); |
| 172 | + default: |
| 173 | + return super.handle(message, context as { [key: string]: unknown }); |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + /** |
| 178 | + * @inheritdoc IDiscoveryProtocolHandler#handleDiscoveryQuery |
| 179 | + */ |
| 180 | + async handleDiscoveryQuery( |
| 181 | + message: DiscoverFeatureQueriesMessage, |
| 182 | + opts?: DiscoveryProtocolHandlerOptions |
| 183 | + ): Promise<DiscoverFeatureDiscloseMessage> { |
| 184 | + if (!opts?.allowExpiredMessages) { |
| 185 | + verifyExpiresTime(message); |
| 186 | + } |
| 187 | + |
| 188 | + const disclosures: DiscoverFeatureDisclosure[] = []; |
| 189 | + for (const query of message.body.queries) { |
| 190 | + disclosures.push(...this.handleQuery(query)); |
| 191 | + } |
| 192 | + |
| 193 | + return Promise.resolve( |
| 194 | + createDiscoveryFeatureDiscloseMessage(disclosures, { |
| 195 | + to: message.from, |
| 196 | + from: message.to, |
| 197 | + expires_time: opts?.disclosureExpiresDate |
| 198 | + ? getUnixTimestamp(opts.disclosureExpiresDate) |
| 199 | + : undefined |
| 200 | + }) |
| 201 | + ); |
| 202 | + } |
| 203 | + |
| 204 | + private handleQuery(query: DiscoverFeatureQuery): DiscoverFeatureDisclosure[] { |
| 205 | + let result: DiscoverFeatureDisclosure[] = []; |
| 206 | + switch (query[DiscoverFeatureQueryType.FeatureType]) { |
| 207 | + case DiscoveryProtocolFeatureType.Accept: |
| 208 | + result = this.handleAcceptQuery(); |
| 209 | + break; |
| 210 | + case DiscoveryProtocolFeatureType.Protocol: |
| 211 | + result = this.handleProtocolQuery(); |
| 212 | + break; |
| 213 | + case DiscoveryProtocolFeatureType.GoalCode: |
| 214 | + result = this.handleGoalCodeQuery(); |
| 215 | + break; |
| 216 | + case DiscoveryProtocolFeatureType.Header: |
| 217 | + result = this.handleHeaderQuery(); |
| 218 | + break; |
| 219 | + } |
| 220 | + |
| 221 | + return this.handleMatch(result, query.match); |
| 222 | + } |
| 223 | + |
| 224 | + private handleAcceptQuery(): DiscoverFeatureDisclosure[] { |
| 225 | + const acceptProfiles = this._options.packageManager.getSupportedProfiles(); |
| 226 | + return acceptProfiles.map((profile) => ({ |
| 227 | + [DiscoverFeatureQueryType.FeatureType]: DiscoveryProtocolFeatureType.Accept, |
| 228 | + id: profile |
| 229 | + })); |
| 230 | + } |
| 231 | + |
| 232 | + private handleProtocolQuery(): DiscoverFeatureDisclosure[] { |
| 233 | + return ( |
| 234 | + this._options.protocols?.map((protocol) => ({ |
| 235 | + [DiscoverFeatureQueryType.FeatureType]: DiscoveryProtocolFeatureType.Protocol, |
| 236 | + id: protocol |
| 237 | + })) ?? [] |
| 238 | + ); |
| 239 | + } |
| 240 | + |
| 241 | + private handleGoalCodeQuery(): DiscoverFeatureDisclosure[] { |
| 242 | + return ( |
| 243 | + this._options.goalCodes?.map((goalCode) => ({ |
| 244 | + [DiscoverFeatureQueryType.FeatureType]: DiscoveryProtocolFeatureType.GoalCode, |
| 245 | + id: goalCode |
| 246 | + })) ?? [] |
| 247 | + ); |
| 248 | + } |
| 249 | + |
| 250 | + private handleHeaderQuery(): DiscoverFeatureDisclosure[] { |
| 251 | + return ( |
| 252 | + this._options.headers?.map((header) => ({ |
| 253 | + [DiscoverFeatureQueryType.FeatureType]: DiscoveryProtocolFeatureType.Header, |
| 254 | + id: header |
| 255 | + })) ?? [] |
| 256 | + ); |
| 257 | + } |
| 258 | + |
| 259 | + private handleMatch( |
| 260 | + disclosures: DiscoverFeatureDisclosure[], |
| 261 | + match?: string |
| 262 | + ): DiscoverFeatureDisclosure[] { |
| 263 | + if (!match || match === '*') { |
| 264 | + return disclosures; |
| 265 | + } |
| 266 | + const regExp = this.wildcardToRegExp(match); |
| 267 | + return disclosures.filter((disclosure) => regExp.test(disclosure.id)); |
| 268 | + } |
| 269 | + |
| 270 | + private wildcardToRegExp(match: string): RegExp { |
| 271 | + // Escape special regex characters, then replace `*` with `.*` |
| 272 | + const regexPattern = match.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*'); |
| 273 | + return new RegExp(`^${regexPattern}$`); |
| 274 | + } |
| 275 | +} |
0 commit comments