• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperledger / identus-edge-agent-sdk-ts / 10899571717

17 Sep 2024 08:42AM UTC coverage: 74.24% (-0.3%) from 74.513%
10899571717

push

github

web-flow
feat: integrating error reporting protocol  (#289)

Signed-off-by: Francisco Javier Ribo Labrador <elribonazo@gmail.com>

1508 of 2244 branches covered (67.2%)

Branch coverage included in aggregate %.

7 of 22 new or added lines in 4 files covered. (31.82%)

1 existing line in 1 file now uncovered.

3325 of 4266 relevant lines covered (77.94%)

27.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

24.42
/src/edge-agent/mediator/BasicMediatorHandler.ts
1
import { WebSocket } from 'isows'
2
import { uuid } from "@stablelib/uuid";
3

4
import { DID, Mediator, Message } from "../../domain";
5
import { Mercury } from "../../domain/buildingBlocks/Mercury";
6
import { AgentError } from "../../domain/models/Errors";
7
import { MediationGrant } from "../protocols/mediation/MediationGrant";
8
import { MediationKeysUpdateList } from "../protocols/mediation/MediationKeysUpdateList";
9
import { MediationRequest } from "../protocols/mediation/MediationRequest";
10
import { PickupReceived } from "../protocols/pickup/PickupReceived";
11
import { PickupRequest } from "../protocols/pickup/PickupRequest";
12
import { PickupRunner } from "../protocols/pickup/PickupRunner";
13
import { MediatorHandler, MediatorStore } from "../types";
14
import { ProtocolType } from '../protocols/ProtocolTypes';
15

16
/**
17
 * A basic implementation of our MediatorHandler Interface which is mainly used
18
 * to establish mediation and get new messages using the mediation and pickup didcomm v2 protocols
19
 *
20
 * @class BasicMediatorHandler
21
 * @typedef {BasicMediatorHandler}
22
 */
23
export class BasicMediatorHandler implements MediatorHandler {
24
  /**
25
   * Optional instance of the mediator so that if the mediation was already
26
   * established and recorded we don't need to mediate again with the same mediator
27
   *
28
   * @public
29
   * @type {?Mediator}
30
   */
31
  public mediator?: Mediator;
32

33
  /**
34
   * Creates an instance of BasicMediatorHandler.
35
   *
36
   * @constructor
37
   * @param {DID} mediatorDID
38
   * @param {Mercury} mercury
39
   * @param {MediatorStore} store
40
   */
41
  constructor(
42
    public mediatorDID: DID,
41✔
43
    public mercury: Mercury,
41✔
44
    public store: MediatorStore
41✔
45
  ) { }
46

47
  /**
48
   * Secondary constructor for BasicMediation Handler, to instanciate if from an existing Mediator
49
   * instance.
50
   *
51
   * @static
52
   * @param {Mediator} mediator
53
   * @param {Mercury} mercury
54
   * @param {MediatorStore} store
55
   * @returns {BasicMediatorHandler}
56
   */
57
  static fromMediator(
58
    mediator: Mediator,
59
    mercury: Mercury,
60
    store: MediatorStore
61
  ): BasicMediatorHandler {
62
    const mediatorHandler = new BasicMediatorHandler(
×
63
      mediator.mediatorDID,
64
      mercury,
65
      store
66
    );
67
    mediatorHandler.mediator = mediator;
×
68
    return mediatorHandler;
×
69
  }
70

71
  /**
72
   * Will asyncronously fetch the first mediator stored in database and set it as default mediator.
73
   *
74
   * @async
75
   * @returns {Promise<Mediator | undefined>}
76
   */
77
  async bootRegisteredMediator(): Promise<Mediator | undefined> {
78
    if (!this.mediator) {
×
79
      const mediators = await this.store.getAllMediators();
×
80
      const mediator = mediators.slice(0, 1).at(0);
×
81
      if (mediator) {
×
82
        this.mediator = mediator;
×
83
      }
84
    }
85
    return this.mediator;
×
86
  }
87

88
  /**
89
   * Asyncronously achieve mediation by specifying the HOST DID, this will
90
   * exchange the mediation protocol messages between the user and the mediator until established
91
   *
92
   * @async
93
   * @param {DID} host
94
   * @returns {Promise<Mediator>}
95
   */
96
  async achieveMediation(host: DID): Promise<Mediator> {
97
    const mediator = await this.bootRegisteredMediator();
×
98
    if (!mediator) {
×
99
      try {
×
100
        const mediationRequest = new MediationRequest(
×
101
          host,
102
          this.mediatorDID
103
        ).makeMessage();
104

105
        const message: Message | undefined =
106
          await this.mercury.sendMessageParseMessage(mediationRequest);
×
107

108
        if (!message) {
×
109
          //TODO: Improve this error
110
          throw new Error("Trying to achieve mediation returned empty data");
×
111
        }
112

113
        const grandMessage = MediationGrant.fromMessage(message);
×
114
        const routingDID = DID.fromString(grandMessage.body.routing_did);
×
115

116
        const mediator: Mediator = {
×
117
          hostDID: host,
118
          routingDID: routingDID,
119
          mediatorDID: this.mediatorDID,
120
        };
121

122
        await this.store.storeMediator(mediator);
×
123

124
        this.mediator = mediator;
×
125

126
        return mediator;
×
127
      } catch (err) {
128
        if (err instanceof Error) {
×
129
          throw new AgentError.MediationRequestFailedError(err.message);
×
130
        } else {
131
          throw err;
×
132
        }
133
      }
134
    }
135
    return mediator;
×
136
  }
137

138
  /**
139
   * Asyncronously update the mediator with the new keyList, used during the mediation process or during DID Rotation
140
   *
141
   * @async
142
   * @param {DID[]} dids
143
   * @returns {Promise<void>}
144
   */
145
  async updateKeyListWithDIDs(dids: DID[]): Promise<void> {
146
    if (!this.mediator) {
×
147
      throw new AgentError.NoMediatorAvailableError();
×
148
    }
149
    const keyListUpdateMessage: Message = new MediationKeysUpdateList(
×
150
      this.mediator.hostDID,
151
      this.mediator.mediatorDID,
152
      dids
153
    ).makeMessage();
154
    await this.mercury.sendMessage(keyListUpdateMessage);
×
155
  }
156

157
  /**
158
   * Asyncronously pickup unread messages from the mediator
159
   * if new messages are found, because the messages from in form of attachments inside the pickup response
160
   * we need to parse those and return the user a list of messages it can read and decode, this is done inside the pickup runner.
161
   *
162
   * @async
163
   * @param {number} limit
164
   * @returns {Promise<Array<{ attachmentId: string; message: Message }>>}
165
   */
166
  async pickupUnreadMessages(
167
    limit: number
168
  ): Promise<Array<{ attachmentId: string; message: Message; }>> {
169
    if (!this.mediator) {
4!
170
      throw new AgentError.NoMediatorAvailableError();
×
171
    }
172
    const request = new PickupRequest(
4✔
173
      { limit: limit },
174
      this.mediator.hostDID,
175
      this.mediator.mediatorDID
176
    ).makeMessage();
177

178
    const message = await this.mercury.sendMessageParseMessage(request);
4✔
179
    if (!message) {
4!
180
      return [];
4✔
181
    }
UNCOV
182
    return new PickupRunner(message, this.mercury).run();
×
183
  }
184

185
  /**
186
   * Asyncronously create a websocket connection with the mediator
187
   * Establish a websocket connection and activate the live-mode with the mediator
188
   * and also listen for incomming unread messages with existing protocols.
189
   * 
190
   * @async
191
   * @param signal 
192
   * @param serviceEndpointUri 
193
   * @param onMessage 
194
   */
195
  listenUnreadMessages(
196
    signal: AbortSignal,
197
    serviceEndpointUri: string,
198
    onMessage: (messages: {
199
      attachmentId: string;
200
      message: Message;
201
    }[]) => void | Promise<void>
202
  ) {
203
    //Todo: we may want to abstract this to allow users to use their own native implementations for websockets
204
    //Or potentially be TCP sockets directly, this can be used in electron and nodejs can establish tcp connections directly.
205
    const socket = new WebSocket(serviceEndpointUri);
1✔
206
    signal.addEventListener("abort", () => {
1✔
207
      if (socket.readyState === socket.OPEN) {
1!
208
        socket.close()
1✔
209
      }
210
    });
211

212
    socket.addEventListener("open", async () => {
1✔
213
      const mediator = this.mediator;
×
214
      if (!mediator) {
×
215
        throw new Error("No mediator, establish mediation first!")
×
216
      }
217
      const message = new Message(
×
218
        JSON.stringify({
219
          live_delivery: true
220
        }),
221
        uuid(),
222
        ProtocolType.LiveDeliveryChange,
223
        this.mediator?.hostDID,
224
        this.mediator?.mediatorDID,
225
      );
226
      const packedMessage = await this.mercury.packMessage(message)
×
227
      socket.send(packedMessage)
×
228
    })
229

230
    socket.addEventListener("message", async (message) => {
1✔
231
      const decryptMessage = await this.mercury.unpackMessage(message.data);
×
232
      if (
×
233
        decryptMessage.piuri === ProtocolType.PickupStatus ||
×
234
        decryptMessage.piuri === ProtocolType.PickupDelivery) {
235
        const delivered = await new PickupRunner(decryptMessage, this.mercury).run()
×
236
        await onMessage(delivered)
×
237
      }
238
    })
239

240
  }
241

242

243
  /**
244
   * Asyncronously notify the current mediator that one or multiple message ID's have been read (or stored)
245
   *
246
   * @async
247
   * @param {string[]} ids
248
   * @returns {Promise<void>}
249
   */
250
  async registerMessagesAsRead(ids: string[]): Promise<void> {
251
    if (!this.mediator) {
1!
252
      throw new AgentError.NoMediatorAvailableError();
×
253
    }
254
    const message = new PickupReceived(
1✔
255
      {
256
        messageIdList: ids,
257
      },
258
      this.mediator.hostDID,
259
      this.mediator.mediatorDID
260
    ).makeMessage();
261
    await this.mercury.sendMessage(message);
1✔
262
  }
263
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc