@bananalink-test/client 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -157,15 +157,145 @@ var InvalidConnectionStateError = class extends BananalinkError {
157
157
  }
158
158
  };
159
159
 
160
+ //#endregion
161
+ //#region src/errors/PendingSessionAbortedError.ts
162
+ var PendingSessionAbortedError = class extends BananalinkError {
163
+ constructor() {
164
+ super("Pending session was intentionally aborted");
165
+ this.name = "PendingSessionAbortedError";
166
+ }
167
+ };
168
+
169
+ //#endregion
170
+ //#region src/api/createDappMessage.ts
171
+ function isCreateDappMessageResponse(body) {
172
+ return typeof body === "object" && body !== null && "messageId" in body && typeof body.messageId === "string";
173
+ }
174
+ async function createDappMessage(apiUrl, accessToken, params) {
175
+ const response = await fetch(`${apiUrl}/dapps/messages`, {
176
+ method: "POST",
177
+ headers: {
178
+ Authorization: `Bearer ${accessToken}`,
179
+ "Content-Type": "application/json"
180
+ },
181
+ body: JSON.stringify(params)
182
+ });
183
+ if (response.ok) {
184
+ const body = await response.json().catch(() => {
185
+ throw new DappApiError("Invalid JSON response from bananalink API", response.status);
186
+ });
187
+ if (!isCreateDappMessageResponse(body)) throw new DappApiError("Unexpected response shape from bananalink API", response.status);
188
+ return body;
189
+ }
190
+ throw new DappApiError("Failed to create dapp message", response.status);
191
+ }
192
+
193
+ //#endregion
194
+ //#region src/errors/RequestRejectedError.ts
195
+ var RequestRejectedError = class extends BananalinkError {
196
+ constructor(messageId) {
197
+ super(`Request rejected by wallet (messageId: ${messageId})`);
198
+ this.messageId = messageId;
199
+ this.name = "RequestRejectedError";
200
+ }
201
+ };
202
+
203
+ //#endregion
204
+ //#region src/errors/RequestTimeoutError.ts
205
+ var RequestTimeoutError = class extends BananalinkError {
206
+ constructor(method) {
207
+ super(`Timeout reached for request ${method}`);
208
+ this.method = method;
209
+ }
210
+ };
211
+
212
+ //#endregion
213
+ //#region src/errors/SessionClosedError.ts
214
+ var SessionClosedError = class extends BananalinkError {
215
+ constructor() {
216
+ super("Session is closed");
217
+ }
218
+ };
219
+
160
220
  //#endregion
161
221
  //#region src/core/BananalinkSession.ts
162
- var BananalinkSession = class {
163
- constructor(sessionClaims, ws) {
222
+ var BananalinkSession = class BananalinkSession {
223
+ static REQUEST_TIMEOUT_MS = 600 * 1e3;
224
+ pendingRequests = /* @__PURE__ */ new Map();
225
+ stopListening;
226
+ stopListeningOnClose;
227
+ closed;
228
+ static RESULT_HANDLERS = { eth_sendTransaction: (result) => {
229
+ if (typeof result === "object" && result !== null && "txHash" in result && typeof result.txHash === "string") return result.txHash;
230
+ throw new BananalinkError(`Unexpected eth_sendTransaction result: ${result}`);
231
+ } };
232
+ constructor(sessionClaims, ws, apiUrl) {
164
233
  this.sessionClaims = sessionClaims;
165
234
  this.ws = ws;
235
+ this.apiUrl = apiUrl;
236
+ this.stopListening = this.ws.onMessage((payload) => {
237
+ if ((0, _bananalink_test_sdk_core.isMessageResponseMessage)(payload)) this.handleMessageResponse(payload);
238
+ });
239
+ this.stopListeningOnClose = this.ws.onClose(() => {
240
+ this.cleanup();
241
+ this.closed = true;
242
+ });
243
+ this.closed = false;
244
+ }
245
+ async request({ method, params, timeoutMs = BananalinkSession.REQUEST_TIMEOUT_MS }) {
246
+ if (this.closed) throw new SessionClosedError();
247
+ const { messageId } = await createDappMessage(this.apiUrl, this.sessionClaims.accessToken, {
248
+ method,
249
+ payload: params
250
+ });
251
+ if (this.closed) throw new SessionClosedError();
252
+ return new Promise((resolve, reject) => {
253
+ let timeout;
254
+ if (timeoutMs > 0) timeout = setTimeout(() => {
255
+ this.pendingRequests.delete(messageId);
256
+ reject(new RequestTimeoutError(method));
257
+ }, timeoutMs);
258
+ this.pendingRequests.set(messageId, {
259
+ timeout,
260
+ handle: (messageResponse) => {
261
+ if (messageResponse.status === "rejected") {
262
+ reject(new RequestRejectedError(messageId));
263
+ return;
264
+ }
265
+ const resultHandler = BananalinkSession.RESULT_HANDLERS[method];
266
+ try {
267
+ resolve(resultHandler(messageResponse.payloadResponse));
268
+ } catch (error) {
269
+ reject(new BananalinkError(`Failed resolving request response, ${error}`));
270
+ }
271
+ },
272
+ reject
273
+ });
274
+ });
166
275
  }
167
276
  close() {
168
- this.ws.close();
277
+ this.cleanup();
278
+ if (!this.ws.closed) this.ws.close();
279
+ this.closed = true;
280
+ }
281
+ handleMessageResponse(messageResponseMessage) {
282
+ const pending = this.pendingRequests.get(messageResponseMessage.msgId);
283
+ if (!pending) return;
284
+ this.pendingRequests.delete(messageResponseMessage.msgId);
285
+ clearTimeout(pending.timeout);
286
+ pending.handle(messageResponseMessage);
287
+ }
288
+ cleanup() {
289
+ this.rejectAllPendingRequests();
290
+ this.stopListening();
291
+ this.stopListeningOnClose();
292
+ }
293
+ rejectAllPendingRequests() {
294
+ for (const [, pending] of this.pendingRequests) {
295
+ clearTimeout(pending.timeout);
296
+ pending.reject(/* @__PURE__ */ new Error("Pending requests rejected"));
297
+ }
298
+ this.pendingRequests.clear();
169
299
  }
170
300
  };
171
301
 
@@ -173,12 +303,17 @@ var BananalinkSession = class {
173
303
  //#region src/core/BananalinkConnection.ts
174
304
  var BananalinkConnection = class BananalinkConnection {
175
305
  static AUTH_TIMEOUT_MILLIS = 600 * 1e3;
306
+ apiUrl;
176
307
  wsUrl;
177
308
  jwt;
178
309
  dappId;
179
310
  dappInstanceId;
180
311
  nonce;
312
+ aborting = false;
313
+ abortPendingSessionRequest = null;
314
+ pendingSessionPromise = null;
181
315
  constructor(opts) {
316
+ this.apiUrl = opts.apiUrl;
182
317
  this.wsUrl = opts.wsUrl;
183
318
  this.jwt = opts.jwt;
184
319
  this.dappId = opts.dappId;
@@ -187,19 +322,30 @@ var BananalinkConnection = class BananalinkConnection {
187
322
  if (!this.dappInstanceId && !this.jwt) throw new InvalidConnectionStateError("Cannot get session without authorizing a dapp instance or providing a valid JWT");
188
323
  }
189
324
  async getSession() {
325
+ if (this.pendingSessionPromise) return this.pendingSessionPromise;
190
326
  if (this.jwt) {
191
327
  const { jwtPayload, rawJwt } = this.jwt;
192
328
  const ws = await this.getTransportHandle();
329
+ if (this.aborting) {
330
+ ws.close();
331
+ this.aborting = false;
332
+ throw new PendingSessionAbortedError();
333
+ }
193
334
  BananalinkConnection.bind(ws, rawJwt);
194
335
  return new BananalinkSession({
195
336
  address: jwtPayload.address,
196
337
  message: jwtPayload.message,
197
338
  signature: jwtPayload.signature,
198
339
  accessToken: rawJwt
199
- }, ws);
340
+ }, ws, this.apiUrl);
200
341
  }
201
342
  const ws = await this.getTransportHandle(this.dappInstanceId);
202
- return await new Promise((resolve, reject) => {
343
+ if (this.aborting) {
344
+ ws.close();
345
+ this.aborting = false;
346
+ throw new PendingSessionAbortedError();
347
+ }
348
+ this.pendingSessionPromise = new Promise((resolve, reject) => {
203
349
  let settled = false;
204
350
  const authTimeout = setTimeout(() => {
205
351
  settleReject(new ConnectionTimeoutError());
@@ -212,7 +358,7 @@ var BananalinkConnection = class BananalinkConnection {
212
358
  message: payload.message,
213
359
  signature: payload.signature,
214
360
  accessToken: payload.accessToken
215
- }, ws));
361
+ }, ws, this.apiUrl));
216
362
  return;
217
363
  }
218
364
  if ((0, _bananalink_test_sdk_core.isRejectedMessage)(payload)) settleReject(new ConnectionRejectedError());
@@ -220,11 +366,17 @@ var BananalinkConnection = class BananalinkConnection {
220
366
  const stopListeningToClose = ws.onClose(() => {
221
367
  settleReject(new BananalinkError("Connection closed before authorization completed"));
222
368
  });
223
- function cleanup() {
369
+ this.abortPendingSessionRequest = () => {
370
+ settleReject(new PendingSessionAbortedError());
371
+ };
372
+ const cleanup = () => {
373
+ this.abortPendingSessionRequest = null;
224
374
  clearTimeout(authTimeout);
225
375
  stopListeningToMessages();
226
376
  stopListeningToClose();
227
- }
377
+ this.aborting = false;
378
+ this.pendingSessionPromise = null;
379
+ };
228
380
  function settleResolve(session) {
229
381
  if (settled) return;
230
382
  settled = true;
@@ -239,6 +391,12 @@ var BananalinkConnection = class BananalinkConnection {
239
391
  reject(error);
240
392
  }
241
393
  });
394
+ return await this.pendingSessionPromise;
395
+ }
396
+ abortPendingSession() {
397
+ if (this.aborting) return;
398
+ this.aborting = true;
399
+ this.abortPendingSessionRequest?.();
242
400
  }
243
401
  async getTransportHandle(dappInstanceId) {
244
402
  const url = `${this.wsUrl}${dappInstanceId != null ? `?dappInstanceId=${encodeURIComponent(dappInstanceId)}` : ""}`;
@@ -293,6 +451,7 @@ var BananalinkClient = class BananalinkClient {
293
451
  dappId: this.dapp.dappId,
294
452
  dappInstanceId,
295
453
  nonce,
454
+ apiUrl: this.apiUrl,
296
455
  wsUrl: this.wsUrl
297
456
  });
298
457
  }
@@ -306,6 +465,7 @@ var BananalinkClient = class BananalinkClient {
306
465
  dappId: this.dapp.dappId,
307
466
  dappInstanceId: null,
308
467
  nonce: null,
468
+ apiUrl: this.apiUrl,
309
469
  wsUrl: this.wsUrl
310
470
  });
311
471
  }
@@ -344,4 +504,8 @@ exports.DappApiError = DappApiError;
344
504
  exports.InvalidConfigError = InvalidConfigError;
345
505
  exports.InvalidConnectionStateError = InvalidConnectionStateError;
346
506
  exports.InvalidJwtError = InvalidJwtError;
507
+ exports.PendingSessionAbortedError = PendingSessionAbortedError;
508
+ exports.RequestRejectedError = RequestRejectedError;
509
+ exports.RequestTimeoutError = RequestTimeoutError;
510
+ exports.SessionClosedError = SessionClosedError;
347
511
  exports.displayBananalinkQR = displayBananalinkQR;
package/dist/index.d.cts CHANGED
@@ -9,6 +9,31 @@ type JwtPayload = {
9
9
  jwt: string;
10
10
  };
11
11
  //#endregion
12
+ //#region src/types/TransactionRequest.d.ts
13
+ type TransactionRequest = {
14
+ to: string;
15
+ value?: string;
16
+ data?: string;
17
+ chainId?: number;
18
+ };
19
+ //#endregion
20
+ //#region src/types/RequestMap.d.ts
21
+ type RequestMap = {
22
+ eth_sendTransaction: {
23
+ params: [TransactionRequest];
24
+ result: string;
25
+ };
26
+ };
27
+ //#endregion
28
+ //#region src/types/Request.d.ts
29
+ type Request = keyof RequestMap;
30
+ //#endregion
31
+ //#region src/types/RequestParams.d.ts
32
+ type RequestParams<T extends keyof RequestMap> = RequestMap[T]['params'];
33
+ //#endregion
34
+ //#region src/types/RequestResult.d.ts
35
+ type RequestResult<T extends keyof RequestMap> = RequestMap[T]['result'];
36
+ //#endregion
12
37
  //#region src/types/SessionClaims.d.ts
13
38
  type SessionClaims = {
14
39
  address: string;
@@ -21,8 +46,27 @@ type SessionClaims = {
21
46
  declare class BananalinkSession {
22
47
  readonly sessionClaims: SessionClaims;
23
48
  private readonly ws;
24
- constructor(sessionClaims: SessionClaims, ws: TransportHandle);
49
+ private readonly apiUrl;
50
+ private static readonly REQUEST_TIMEOUT_MS;
51
+ private readonly pendingRequests;
52
+ private readonly stopListening;
53
+ private readonly stopListeningOnClose;
54
+ private closed;
55
+ private static readonly RESULT_HANDLERS;
56
+ constructor(sessionClaims: SessionClaims, ws: TransportHandle, apiUrl: string);
57
+ request<T extends Request>({
58
+ method,
59
+ params,
60
+ timeoutMs
61
+ }: {
62
+ method: T;
63
+ params?: RequestParams<T>;
64
+ timeoutMs?: number;
65
+ }): Promise<RequestResult<T>>;
25
66
  close(): void;
67
+ private handleMessageResponse;
68
+ private cleanup;
69
+ private rejectAllPendingRequests;
26
70
  }
27
71
  //#endregion
28
72
  //#region src/core/BananalinkConnection.d.ts
@@ -32,12 +76,17 @@ type BananalinkDappJwt = {
32
76
  } | undefined;
33
77
  declare class BananalinkConnection {
34
78
  private static readonly AUTH_TIMEOUT_MILLIS;
79
+ private readonly apiUrl;
35
80
  private readonly wsUrl;
36
81
  private readonly jwt;
37
82
  private readonly dappId;
38
83
  private readonly dappInstanceId;
39
84
  private readonly nonce;
85
+ private aborting;
86
+ private abortPendingSessionRequest;
87
+ private pendingSessionPromise;
40
88
  constructor(opts: {
89
+ apiUrl: string;
41
90
  wsUrl: string;
42
91
  jwt: BananalinkDappJwt;
43
92
  dappId: string;
@@ -45,6 +94,7 @@ declare class BananalinkConnection {
45
94
  nonce: string | null;
46
95
  });
47
96
  getSession(): Promise<BananalinkSession>;
97
+ abortPendingSession(): void;
48
98
  private getTransportHandle;
49
99
  get connectionUrl(): string;
50
100
  private static bind;
@@ -106,8 +156,30 @@ declare class InvalidJwtError extends BananalinkError {
106
156
  constructor();
107
157
  }
108
158
  //#endregion
159
+ //#region src/errors/PendingSessionAbortedError.d.ts
160
+ declare class PendingSessionAbortedError extends BananalinkError {
161
+ constructor();
162
+ }
163
+ //#endregion
164
+ //#region src/errors/RequestRejectedError.d.ts
165
+ declare class RequestRejectedError extends BananalinkError {
166
+ readonly messageId: string;
167
+ constructor(messageId: string);
168
+ }
169
+ //#endregion
170
+ //#region src/errors/RequestTimeoutError.d.ts
171
+ declare class RequestTimeoutError extends BananalinkError {
172
+ readonly method: Request;
173
+ constructor(method: Request);
174
+ }
175
+ //#endregion
176
+ //#region src/errors/SessionClosedError.d.ts
177
+ declare class SessionClosedError extends BananalinkError {
178
+ constructor();
179
+ }
180
+ //#endregion
109
181
  //#region src/utils/displayBananalinkQR.d.ts
110
182
  declare function displayBananalinkQR(url: string): Promise<string>;
111
183
  //#endregion
112
- export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, type Dapp, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, displayBananalinkQR };
184
+ export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, type Dapp, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, PendingSessionAbortedError, type Request, type RequestMap, type RequestParams, RequestRejectedError, type RequestResult, RequestTimeoutError, SessionClosedError, type TransactionRequest, displayBananalinkQR };
113
185
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types/JwtPayload.ts","../src/types/SessionClaims.ts","../src/core/BananalinkSession.ts","../src/core/BananalinkConnection.ts","../src/core/BananalinkClient.ts","../src/errors/BananalinkError.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.ts","../src/errors/DappApiError.ts","../src/errors/InvalidConfigError.ts","../src/errors/InvalidConnectionStateError.ts","../src/errors/InvalidJwtError.ts","../src/utils/displayBananalinkQR.ts"],"mappings":";;;KAAY,UAAA;EAAe,OAAA;EAAiB,MAAA;EAAgB,OAAA;EAAiB,SAAA;EAAmB,GAAA;AAAA;;;KCApF,aAAA;EAAkB,OAAA;EAAiB,OAAA;EAAiB,SAAA;EAAmB,WAAA;AAAA;;;cCGtE,iBAAA;EAAA,SAEO,aAAA,EAAe,aAAA;EAAA,iBACd,EAAA;cADD,aAAA,EAAe,aAAA,EACd,EAAA,EAAI,eAAA;EAGhB,KAAA,CAAA;AAAA;;;KCKJ,iBAAA;EAAsB,UAAA,EAAY,UAAA;EAAY,MAAA;AAAA;AAAA,cAEtC,oBAAA;EAAA,wBACa,mBAAA;EAAA,iBACP,KAAA;EAAA,iBACA,GAAA;EAAA,iBACA,MAAA;EAAA,iBACA,cAAA;EAAA,iBACA,KAAA;cAEL,IAAA;IACV,KAAA;IACA,GAAA,EAAK,iBAAA;IACL,MAAA;IACA,cAAA;IACA,KAAA;EAAA;EAcW,UAAA,CAAA,GAAc,OAAA,CAAQ,iBAAA;EAAA,QAgFrB,kBAAA;EAAA,IAaH,aAAA,CAAA;EAAA,eASI,IAAA;AAAA;;;cCvIJ,gBAAA;EAAA,iBACM,MAAA;EAAA,iBACA,KAAA;EAAA,iBACA,IAAA;EAAA,iBACA,IAAA;EAAA,wBACO,0BAAA;EAAA,wBACA,yBAAA;cAEZ,MAAA;IACV,MAAA;IACA,KAAA;IACA,IAAA,EAAM,MAAA;EAAA;EAcK,OAAA,CAAQ,IAAA;IAAS,GAAA;EAAA,IAAgB,OAAA,CAAQ,oBAAA;EAAA,QA2BxC,aAAA;EAAA,QAIA,mBAAA;AAAA;;;cClEH,eAAA,SAAwB,KAAA;cACvB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;;;cCC5B,uBAAA,SAAgC,eAAA;EAAA,WAAA,CAAA;AAAA;;;cCAhC,sBAAA,SAA+B,eAAA;EAAA,WAAA,CAAA;AAAA;;;cCA/B,YAAA,SAAqB,eAAA;EAAA,SAGd,UAAA;cADhB,OAAA,UACgB,UAAA;AAAA;;;cCHP,kBAAA,SAA2B,eAAA;cAC1B,OAAA;AAAA;;;cCDD,2BAAA,SAAoC,eAAA;cACnC,OAAA;AAAA;;;cCDD,eAAA,SAAwB,eAAA;EAAA,WAAA,CAAA;AAAA;;;iBCEf,mBAAA,CAAoB,GAAA,WAAc,OAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types/JwtPayload.ts","../src/types/TransactionRequest.ts","../src/types/RequestMap.ts","../src/types/Request.ts","../src/types/RequestParams.ts","../src/types/RequestResult.ts","../src/types/SessionClaims.ts","../src/core/BananalinkSession.ts","../src/core/BananalinkConnection.ts","../src/core/BananalinkClient.ts","../src/errors/BananalinkError.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.ts","../src/errors/DappApiError.ts","../src/errors/InvalidConfigError.ts","../src/errors/InvalidConnectionStateError.ts","../src/errors/InvalidJwtError.ts","../src/errors/PendingSessionAbortedError.ts","../src/errors/RequestRejectedError.ts","../src/errors/RequestTimeoutError.ts","../src/errors/SessionClosedError.ts","../src/utils/displayBananalinkQR.ts"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -9,6 +9,31 @@ type JwtPayload = {
9
9
  jwt: string;
10
10
  };
11
11
  //#endregion
12
+ //#region src/types/TransactionRequest.d.ts
13
+ type TransactionRequest = {
14
+ to: string;
15
+ value?: string;
16
+ data?: string;
17
+ chainId?: number;
18
+ };
19
+ //#endregion
20
+ //#region src/types/RequestMap.d.ts
21
+ type RequestMap = {
22
+ eth_sendTransaction: {
23
+ params: [TransactionRequest];
24
+ result: string;
25
+ };
26
+ };
27
+ //#endregion
28
+ //#region src/types/Request.d.ts
29
+ type Request = keyof RequestMap;
30
+ //#endregion
31
+ //#region src/types/RequestParams.d.ts
32
+ type RequestParams<T extends keyof RequestMap> = RequestMap[T]['params'];
33
+ //#endregion
34
+ //#region src/types/RequestResult.d.ts
35
+ type RequestResult<T extends keyof RequestMap> = RequestMap[T]['result'];
36
+ //#endregion
12
37
  //#region src/types/SessionClaims.d.ts
13
38
  type SessionClaims = {
14
39
  address: string;
@@ -21,8 +46,27 @@ type SessionClaims = {
21
46
  declare class BananalinkSession {
22
47
  readonly sessionClaims: SessionClaims;
23
48
  private readonly ws;
24
- constructor(sessionClaims: SessionClaims, ws: TransportHandle);
49
+ private readonly apiUrl;
50
+ private static readonly REQUEST_TIMEOUT_MS;
51
+ private readonly pendingRequests;
52
+ private readonly stopListening;
53
+ private readonly stopListeningOnClose;
54
+ private closed;
55
+ private static readonly RESULT_HANDLERS;
56
+ constructor(sessionClaims: SessionClaims, ws: TransportHandle, apiUrl: string);
57
+ request<T extends Request>({
58
+ method,
59
+ params,
60
+ timeoutMs
61
+ }: {
62
+ method: T;
63
+ params?: RequestParams<T>;
64
+ timeoutMs?: number;
65
+ }): Promise<RequestResult<T>>;
25
66
  close(): void;
67
+ private handleMessageResponse;
68
+ private cleanup;
69
+ private rejectAllPendingRequests;
26
70
  }
27
71
  //#endregion
28
72
  //#region src/core/BananalinkConnection.d.ts
@@ -32,12 +76,17 @@ type BananalinkDappJwt = {
32
76
  } | undefined;
33
77
  declare class BananalinkConnection {
34
78
  private static readonly AUTH_TIMEOUT_MILLIS;
79
+ private readonly apiUrl;
35
80
  private readonly wsUrl;
36
81
  private readonly jwt;
37
82
  private readonly dappId;
38
83
  private readonly dappInstanceId;
39
84
  private readonly nonce;
85
+ private aborting;
86
+ private abortPendingSessionRequest;
87
+ private pendingSessionPromise;
40
88
  constructor(opts: {
89
+ apiUrl: string;
41
90
  wsUrl: string;
42
91
  jwt: BananalinkDappJwt;
43
92
  dappId: string;
@@ -45,6 +94,7 @@ declare class BananalinkConnection {
45
94
  nonce: string | null;
46
95
  });
47
96
  getSession(): Promise<BananalinkSession>;
97
+ abortPendingSession(): void;
48
98
  private getTransportHandle;
49
99
  get connectionUrl(): string;
50
100
  private static bind;
@@ -106,8 +156,30 @@ declare class InvalidJwtError extends BananalinkError {
106
156
  constructor();
107
157
  }
108
158
  //#endregion
159
+ //#region src/errors/PendingSessionAbortedError.d.ts
160
+ declare class PendingSessionAbortedError extends BananalinkError {
161
+ constructor();
162
+ }
163
+ //#endregion
164
+ //#region src/errors/RequestRejectedError.d.ts
165
+ declare class RequestRejectedError extends BananalinkError {
166
+ readonly messageId: string;
167
+ constructor(messageId: string);
168
+ }
169
+ //#endregion
170
+ //#region src/errors/RequestTimeoutError.d.ts
171
+ declare class RequestTimeoutError extends BananalinkError {
172
+ readonly method: Request;
173
+ constructor(method: Request);
174
+ }
175
+ //#endregion
176
+ //#region src/errors/SessionClosedError.d.ts
177
+ declare class SessionClosedError extends BananalinkError {
178
+ constructor();
179
+ }
180
+ //#endregion
109
181
  //#region src/utils/displayBananalinkQR.d.ts
110
182
  declare function displayBananalinkQR(url: string): Promise<string>;
111
183
  //#endregion
112
- export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, type Dapp, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, displayBananalinkQR };
184
+ export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, type Dapp, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, PendingSessionAbortedError, type Request, type RequestMap, type RequestParams, RequestRejectedError, type RequestResult, RequestTimeoutError, SessionClosedError, type TransactionRequest, displayBananalinkQR };
113
185
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/JwtPayload.ts","../src/types/SessionClaims.ts","../src/core/BananalinkSession.ts","../src/core/BananalinkConnection.ts","../src/core/BananalinkClient.ts","../src/errors/BananalinkError.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.ts","../src/errors/DappApiError.ts","../src/errors/InvalidConfigError.ts","../src/errors/InvalidConnectionStateError.ts","../src/errors/InvalidJwtError.ts","../src/utils/displayBananalinkQR.ts"],"mappings":";;;KAAY,UAAA;EAAe,OAAA;EAAiB,MAAA;EAAgB,OAAA;EAAiB,SAAA;EAAmB,GAAA;AAAA;;;KCApF,aAAA;EAAkB,OAAA;EAAiB,OAAA;EAAiB,SAAA;EAAmB,WAAA;AAAA;;;cCGtE,iBAAA;EAAA,SAEO,aAAA,EAAe,aAAA;EAAA,iBACd,EAAA;cADD,aAAA,EAAe,aAAA,EACd,EAAA,EAAI,eAAA;EAGhB,KAAA,CAAA;AAAA;;;KCKJ,iBAAA;EAAsB,UAAA,EAAY,UAAA;EAAY,MAAA;AAAA;AAAA,cAEtC,oBAAA;EAAA,wBACa,mBAAA;EAAA,iBACP,KAAA;EAAA,iBACA,GAAA;EAAA,iBACA,MAAA;EAAA,iBACA,cAAA;EAAA,iBACA,KAAA;cAEL,IAAA;IACV,KAAA;IACA,GAAA,EAAK,iBAAA;IACL,MAAA;IACA,cAAA;IACA,KAAA;EAAA;EAcW,UAAA,CAAA,GAAc,OAAA,CAAQ,iBAAA;EAAA,QAgFrB,kBAAA;EAAA,IAaH,aAAA,CAAA;EAAA,eASI,IAAA;AAAA;;;cCvIJ,gBAAA;EAAA,iBACM,MAAA;EAAA,iBACA,KAAA;EAAA,iBACA,IAAA;EAAA,iBACA,IAAA;EAAA,wBACO,0BAAA;EAAA,wBACA,yBAAA;cAEZ,MAAA;IACV,MAAA;IACA,KAAA;IACA,IAAA,EAAM,MAAA;EAAA;EAcK,OAAA,CAAQ,IAAA;IAAS,GAAA;EAAA,IAAgB,OAAA,CAAQ,oBAAA;EAAA,QA2BxC,aAAA;EAAA,QAIA,mBAAA;AAAA;;;cClEH,eAAA,SAAwB,KAAA;cACvB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;;;cCC5B,uBAAA,SAAgC,eAAA;EAAA,WAAA,CAAA;AAAA;;;cCAhC,sBAAA,SAA+B,eAAA;EAAA,WAAA,CAAA;AAAA;;;cCA/B,YAAA,SAAqB,eAAA;EAAA,SAGd,UAAA;cADhB,OAAA,UACgB,UAAA;AAAA;;;cCHP,kBAAA,SAA2B,eAAA;cAC1B,OAAA;AAAA;;;cCDD,2BAAA,SAAoC,eAAA;cACnC,OAAA;AAAA;;;cCDD,eAAA,SAAwB,eAAA;EAAA,WAAA,CAAA;AAAA;;;iBCEf,mBAAA,CAAoB,GAAA,WAAc,OAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/JwtPayload.ts","../src/types/TransactionRequest.ts","../src/types/RequestMap.ts","../src/types/Request.ts","../src/types/RequestParams.ts","../src/types/RequestResult.ts","../src/types/SessionClaims.ts","../src/core/BananalinkSession.ts","../src/core/BananalinkConnection.ts","../src/core/BananalinkClient.ts","../src/errors/BananalinkError.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.ts","../src/errors/DappApiError.ts","../src/errors/InvalidConfigError.ts","../src/errors/InvalidConnectionStateError.ts","../src/errors/InvalidJwtError.ts","../src/errors/PendingSessionAbortedError.ts","../src/errors/RequestRejectedError.ts","../src/errors/RequestTimeoutError.ts","../src/errors/SessionClosedError.ts","../src/utils/displayBananalinkQR.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRemoteJWKSet, jwtVerify } from "jose";
2
- import { connectWebSocket, isAuthorizedMessage, isRejectedMessage } from "@bananalink-test/sdk-core";
2
+ import { connectWebSocket, isAuthorizedMessage, isMessageResponseMessage, isRejectedMessage } from "@bananalink-test/sdk-core";
3
3
  import QRCode from "qrcode";
4
4
 
5
5
  //#region src/errors/BananalinkError.ts
@@ -128,15 +128,145 @@ var InvalidConnectionStateError = class extends BananalinkError {
128
128
  }
129
129
  };
130
130
 
131
+ //#endregion
132
+ //#region src/errors/PendingSessionAbortedError.ts
133
+ var PendingSessionAbortedError = class extends BananalinkError {
134
+ constructor() {
135
+ super("Pending session was intentionally aborted");
136
+ this.name = "PendingSessionAbortedError";
137
+ }
138
+ };
139
+
140
+ //#endregion
141
+ //#region src/api/createDappMessage.ts
142
+ function isCreateDappMessageResponse(body) {
143
+ return typeof body === "object" && body !== null && "messageId" in body && typeof body.messageId === "string";
144
+ }
145
+ async function createDappMessage(apiUrl, accessToken, params) {
146
+ const response = await fetch(`${apiUrl}/dapps/messages`, {
147
+ method: "POST",
148
+ headers: {
149
+ Authorization: `Bearer ${accessToken}`,
150
+ "Content-Type": "application/json"
151
+ },
152
+ body: JSON.stringify(params)
153
+ });
154
+ if (response.ok) {
155
+ const body = await response.json().catch(() => {
156
+ throw new DappApiError("Invalid JSON response from bananalink API", response.status);
157
+ });
158
+ if (!isCreateDappMessageResponse(body)) throw new DappApiError("Unexpected response shape from bananalink API", response.status);
159
+ return body;
160
+ }
161
+ throw new DappApiError("Failed to create dapp message", response.status);
162
+ }
163
+
164
+ //#endregion
165
+ //#region src/errors/RequestRejectedError.ts
166
+ var RequestRejectedError = class extends BananalinkError {
167
+ constructor(messageId) {
168
+ super(`Request rejected by wallet (messageId: ${messageId})`);
169
+ this.messageId = messageId;
170
+ this.name = "RequestRejectedError";
171
+ }
172
+ };
173
+
174
+ //#endregion
175
+ //#region src/errors/RequestTimeoutError.ts
176
+ var RequestTimeoutError = class extends BananalinkError {
177
+ constructor(method) {
178
+ super(`Timeout reached for request ${method}`);
179
+ this.method = method;
180
+ }
181
+ };
182
+
183
+ //#endregion
184
+ //#region src/errors/SessionClosedError.ts
185
+ var SessionClosedError = class extends BananalinkError {
186
+ constructor() {
187
+ super("Session is closed");
188
+ }
189
+ };
190
+
131
191
  //#endregion
132
192
  //#region src/core/BananalinkSession.ts
133
- var BananalinkSession = class {
134
- constructor(sessionClaims, ws) {
193
+ var BananalinkSession = class BananalinkSession {
194
+ static REQUEST_TIMEOUT_MS = 600 * 1e3;
195
+ pendingRequests = /* @__PURE__ */ new Map();
196
+ stopListening;
197
+ stopListeningOnClose;
198
+ closed;
199
+ static RESULT_HANDLERS = { eth_sendTransaction: (result) => {
200
+ if (typeof result === "object" && result !== null && "txHash" in result && typeof result.txHash === "string") return result.txHash;
201
+ throw new BananalinkError(`Unexpected eth_sendTransaction result: ${result}`);
202
+ } };
203
+ constructor(sessionClaims, ws, apiUrl) {
135
204
  this.sessionClaims = sessionClaims;
136
205
  this.ws = ws;
206
+ this.apiUrl = apiUrl;
207
+ this.stopListening = this.ws.onMessage((payload) => {
208
+ if (isMessageResponseMessage(payload)) this.handleMessageResponse(payload);
209
+ });
210
+ this.stopListeningOnClose = this.ws.onClose(() => {
211
+ this.cleanup();
212
+ this.closed = true;
213
+ });
214
+ this.closed = false;
215
+ }
216
+ async request({ method, params, timeoutMs = BananalinkSession.REQUEST_TIMEOUT_MS }) {
217
+ if (this.closed) throw new SessionClosedError();
218
+ const { messageId } = await createDappMessage(this.apiUrl, this.sessionClaims.accessToken, {
219
+ method,
220
+ payload: params
221
+ });
222
+ if (this.closed) throw new SessionClosedError();
223
+ return new Promise((resolve, reject) => {
224
+ let timeout;
225
+ if (timeoutMs > 0) timeout = setTimeout(() => {
226
+ this.pendingRequests.delete(messageId);
227
+ reject(new RequestTimeoutError(method));
228
+ }, timeoutMs);
229
+ this.pendingRequests.set(messageId, {
230
+ timeout,
231
+ handle: (messageResponse) => {
232
+ if (messageResponse.status === "rejected") {
233
+ reject(new RequestRejectedError(messageId));
234
+ return;
235
+ }
236
+ const resultHandler = BananalinkSession.RESULT_HANDLERS[method];
237
+ try {
238
+ resolve(resultHandler(messageResponse.payloadResponse));
239
+ } catch (error) {
240
+ reject(new BananalinkError(`Failed resolving request response, ${error}`));
241
+ }
242
+ },
243
+ reject
244
+ });
245
+ });
137
246
  }
138
247
  close() {
139
- this.ws.close();
248
+ this.cleanup();
249
+ if (!this.ws.closed) this.ws.close();
250
+ this.closed = true;
251
+ }
252
+ handleMessageResponse(messageResponseMessage) {
253
+ const pending = this.pendingRequests.get(messageResponseMessage.msgId);
254
+ if (!pending) return;
255
+ this.pendingRequests.delete(messageResponseMessage.msgId);
256
+ clearTimeout(pending.timeout);
257
+ pending.handle(messageResponseMessage);
258
+ }
259
+ cleanup() {
260
+ this.rejectAllPendingRequests();
261
+ this.stopListening();
262
+ this.stopListeningOnClose();
263
+ }
264
+ rejectAllPendingRequests() {
265
+ for (const [, pending] of this.pendingRequests) {
266
+ clearTimeout(pending.timeout);
267
+ pending.reject(/* @__PURE__ */ new Error("Pending requests rejected"));
268
+ }
269
+ this.pendingRequests.clear();
140
270
  }
141
271
  };
142
272
 
@@ -144,12 +274,17 @@ var BananalinkSession = class {
144
274
  //#region src/core/BananalinkConnection.ts
145
275
  var BananalinkConnection = class BananalinkConnection {
146
276
  static AUTH_TIMEOUT_MILLIS = 600 * 1e3;
277
+ apiUrl;
147
278
  wsUrl;
148
279
  jwt;
149
280
  dappId;
150
281
  dappInstanceId;
151
282
  nonce;
283
+ aborting = false;
284
+ abortPendingSessionRequest = null;
285
+ pendingSessionPromise = null;
152
286
  constructor(opts) {
287
+ this.apiUrl = opts.apiUrl;
153
288
  this.wsUrl = opts.wsUrl;
154
289
  this.jwt = opts.jwt;
155
290
  this.dappId = opts.dappId;
@@ -158,19 +293,30 @@ var BananalinkConnection = class BananalinkConnection {
158
293
  if (!this.dappInstanceId && !this.jwt) throw new InvalidConnectionStateError("Cannot get session without authorizing a dapp instance or providing a valid JWT");
159
294
  }
160
295
  async getSession() {
296
+ if (this.pendingSessionPromise) return this.pendingSessionPromise;
161
297
  if (this.jwt) {
162
298
  const { jwtPayload, rawJwt } = this.jwt;
163
299
  const ws = await this.getTransportHandle();
300
+ if (this.aborting) {
301
+ ws.close();
302
+ this.aborting = false;
303
+ throw new PendingSessionAbortedError();
304
+ }
164
305
  BananalinkConnection.bind(ws, rawJwt);
165
306
  return new BananalinkSession({
166
307
  address: jwtPayload.address,
167
308
  message: jwtPayload.message,
168
309
  signature: jwtPayload.signature,
169
310
  accessToken: rawJwt
170
- }, ws);
311
+ }, ws, this.apiUrl);
171
312
  }
172
313
  const ws = await this.getTransportHandle(this.dappInstanceId);
173
- return await new Promise((resolve, reject) => {
314
+ if (this.aborting) {
315
+ ws.close();
316
+ this.aborting = false;
317
+ throw new PendingSessionAbortedError();
318
+ }
319
+ this.pendingSessionPromise = new Promise((resolve, reject) => {
174
320
  let settled = false;
175
321
  const authTimeout = setTimeout(() => {
176
322
  settleReject(new ConnectionTimeoutError());
@@ -183,7 +329,7 @@ var BananalinkConnection = class BananalinkConnection {
183
329
  message: payload.message,
184
330
  signature: payload.signature,
185
331
  accessToken: payload.accessToken
186
- }, ws));
332
+ }, ws, this.apiUrl));
187
333
  return;
188
334
  }
189
335
  if (isRejectedMessage(payload)) settleReject(new ConnectionRejectedError());
@@ -191,11 +337,17 @@ var BananalinkConnection = class BananalinkConnection {
191
337
  const stopListeningToClose = ws.onClose(() => {
192
338
  settleReject(new BananalinkError("Connection closed before authorization completed"));
193
339
  });
194
- function cleanup() {
340
+ this.abortPendingSessionRequest = () => {
341
+ settleReject(new PendingSessionAbortedError());
342
+ };
343
+ const cleanup = () => {
344
+ this.abortPendingSessionRequest = null;
195
345
  clearTimeout(authTimeout);
196
346
  stopListeningToMessages();
197
347
  stopListeningToClose();
198
- }
348
+ this.aborting = false;
349
+ this.pendingSessionPromise = null;
350
+ };
199
351
  function settleResolve(session) {
200
352
  if (settled) return;
201
353
  settled = true;
@@ -210,6 +362,12 @@ var BananalinkConnection = class BananalinkConnection {
210
362
  reject(error);
211
363
  }
212
364
  });
365
+ return await this.pendingSessionPromise;
366
+ }
367
+ abortPendingSession() {
368
+ if (this.aborting) return;
369
+ this.aborting = true;
370
+ this.abortPendingSessionRequest?.();
213
371
  }
214
372
  async getTransportHandle(dappInstanceId) {
215
373
  const url = `${this.wsUrl}${dappInstanceId != null ? `?dappInstanceId=${encodeURIComponent(dappInstanceId)}` : ""}`;
@@ -264,6 +422,7 @@ var BananalinkClient = class BananalinkClient {
264
422
  dappId: this.dapp.dappId,
265
423
  dappInstanceId,
266
424
  nonce,
425
+ apiUrl: this.apiUrl,
267
426
  wsUrl: this.wsUrl
268
427
  });
269
428
  }
@@ -277,6 +436,7 @@ var BananalinkClient = class BananalinkClient {
277
436
  dappId: this.dapp.dappId,
278
437
  dappInstanceId: null,
279
438
  nonce: null,
439
+ apiUrl: this.apiUrl,
280
440
  wsUrl: this.wsUrl
281
441
  });
282
442
  }
@@ -305,5 +465,5 @@ async function displayBananalinkQR(url) {
305
465
  }
306
466
 
307
467
  //#endregion
308
- export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, displayBananalinkQR };
468
+ export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, PendingSessionAbortedError, RequestRejectedError, RequestTimeoutError, SessionClosedError, displayBananalinkQR };
309
469
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/errors/BananalinkError.ts","../src/errors/DappApiError.ts","../src/api/createDappInstance.ts","../src/errors/InvalidConfigError.ts","../src/errors/InvalidJwtError.ts","../src/jwt/createBananalinkJwks.ts","../src/jwt/verifyJwt.ts","../src/utils/isValidUrl.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.ts","../src/errors/InvalidConnectionStateError.ts","../src/core/BananalinkSession.ts","../src/core/BananalinkConnection.ts","../src/core/BananalinkClient.ts","../src/errors/InvalidUrlError.ts","../src/utils/displayBananalinkQR.ts"],"sourcesContent":["export class BananalinkError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'BananalinkError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class DappApiError extends BananalinkError {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message);\n this.name = 'DappApiError';\n }\n}\n","import type { CreateDappInstanceResponse, Dapp } from '@bananalink-test/sdk-core';\nimport { DappApiError } from '../errors/DappApiError.js';\n\nfunction isCreateDappInstanceResponse(body: unknown): body is CreateDappInstanceResponse {\n return (\n typeof body === 'object' &&\n body !== null &&\n 'dappInstanceId' in body &&\n typeof body.dappInstanceId === 'string' &&\n 'nonce' in body &&\n typeof body.nonce === 'string'\n );\n}\n\nexport async function createDappInstance(apiUrl: string, params: Dapp): Promise<CreateDappInstanceResponse> {\n const response = await fetch(`${apiUrl}/dapps`, {\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n body: JSON.stringify(params),\n });\n if (response.ok) {\n const body = await response.json().catch(() => {\n throw new DappApiError('Invalid JSON response from bananalink API', response.status);\n });\n if (!isCreateDappInstanceResponse(body)) {\n throw new DappApiError('Unexpected response shape from bananalink API', response.status);\n }\n return body;\n }\n throw new DappApiError('Failed to create dapp instance', response.status);\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class InvalidConfigError extends BananalinkError {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidConfigError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class InvalidJwtError extends BananalinkError {\n constructor() {\n super('Invalid Bananalink dapp jwt');\n }\n}\n","import { createRemoteJWKSet, type JWTVerifyGetKey } from 'jose';\n\nexport function createBananalinkJwks(apiUrl: string): JWTVerifyGetKey {\n const url = new URL(`${apiUrl}/.well-known/jwks.json`);\n return createRemoteJWKSet(url);\n}\n","import { type JWTVerifyGetKey, jwtVerify } from 'jose';\nimport type { JwtPayload } from '../types/JwtPayload.js';\n\nexport async function verifyJwt(jwt: string, jwks: JWTVerifyGetKey): Promise<JwtPayload | null> {\n try {\n const { payload } = await jwtVerify<{ message: string; signature: string }>(jwt, jwks, {\n algorithms: ['ES256'],\n issuer: 'bananalink',\n requiredClaims: ['sub', 'aud', 'message', 'signature'],\n });\n return {\n address: payload.sub as string,\n dappId: Array.isArray(payload.aud) ? payload.aud[0] : (payload.aud as string),\n message: payload.message,\n signature: payload.signature,\n jwt,\n };\n } catch {\n return null;\n }\n}\n","export function isValidUrl(url: string, protocols: string[]): boolean {\n try {\n const parsed = new URL(url);\n return protocols.includes(parsed.protocol.replace(':', ''));\n } catch {\n return false;\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class ConnectionRejectedError extends BananalinkError {\n constructor() {\n super('Dapp connection attempt was rejected by the wallet');\n this.name = 'ConnectionRejectedError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class ConnectionTimeoutError extends BananalinkError {\n constructor() {\n super('Dapp connection attempt timed out');\n this.name = 'ConnectionTimeoutError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class InvalidConnectionStateError extends BananalinkError {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidConnectionStateError';\n }\n}\n","import type { TransportHandle } from '@bananalink-test/sdk-core';\nimport type { SessionClaims } from '../types/SessionClaims.js';\n\nexport class BananalinkSession {\n constructor(\n public readonly sessionClaims: SessionClaims,\n private readonly ws: TransportHandle,\n ) {}\n\n public close(): void {\n this.ws.close();\n }\n}\n","import {\n type BindMessage,\n connectWebSocket,\n isAuthorizedMessage,\n isRejectedMessage,\n type TransportHandle,\n} from '@bananalink-test/sdk-core';\nimport { BananalinkError } from '../errors/BananalinkError.js';\nimport { ConnectionRejectedError } from '../errors/ConnectionRejectedError.js';\nimport { ConnectionTimeoutError } from '../errors/ConnectionTimeoutError.js';\nimport { InvalidConnectionStateError } from '../errors/InvalidConnectionStateError.js';\nimport type { JwtPayload } from '../types/JwtPayload.js';\nimport { BananalinkSession } from './BananalinkSession.js';\n\ntype BananalinkDappJwt = { jwtPayload: JwtPayload; rawJwt: string } | undefined;\n\nexport class BananalinkConnection {\n private static readonly AUTH_TIMEOUT_MILLIS = 10 * 60 * 1000;\n private readonly wsUrl: string;\n private readonly jwt: BananalinkDappJwt;\n private readonly dappId: string;\n private readonly dappInstanceId: string | null;\n private readonly nonce: string | null;\n\n constructor(opts: {\n wsUrl: string;\n jwt: BananalinkDappJwt;\n dappId: string;\n dappInstanceId: string | null;\n nonce: string | null;\n }) {\n this.wsUrl = opts.wsUrl;\n this.jwt = opts.jwt;\n this.dappId = opts.dappId;\n this.dappInstanceId = opts.dappInstanceId;\n this.nonce = opts.nonce;\n if (!this.dappInstanceId && !this.jwt) {\n throw new InvalidConnectionStateError(\n 'Cannot get session without authorizing a dapp instance or providing a valid JWT',\n );\n }\n }\n\n public async getSession(): Promise<BananalinkSession> {\n if (this.jwt) {\n const { jwtPayload, rawJwt } = this.jwt;\n const ws = await this.getTransportHandle();\n BananalinkConnection.bind(ws, rawJwt);\n return new BananalinkSession(\n {\n address: jwtPayload.address,\n message: jwtPayload.message,\n signature: jwtPayload.signature,\n accessToken: rawJwt,\n },\n ws,\n );\n }\n\n const ws = await this.getTransportHandle(this.dappInstanceId);\n\n return await new Promise((resolve, reject) => {\n let settled = false;\n\n const authTimeout = setTimeout(() => {\n settleReject(new ConnectionTimeoutError());\n }, BananalinkConnection.AUTH_TIMEOUT_MILLIS);\n\n const stopListeningToMessages = ws.onMessage((payload: unknown) => {\n if (isAuthorizedMessage(payload)) {\n BananalinkConnection.bind(ws, payload.accessToken);\n settleResolve(\n new BananalinkSession(\n {\n address: payload.address,\n message: payload.message,\n signature: payload.signature,\n accessToken: payload.accessToken,\n },\n ws,\n ),\n );\n return;\n }\n\n if (isRejectedMessage(payload)) {\n settleReject(new ConnectionRejectedError());\n }\n });\n\n const stopListeningToClose = ws.onClose(() => {\n settleReject(new BananalinkError('Connection closed before authorization completed'));\n });\n\n function cleanup(): void {\n clearTimeout(authTimeout);\n stopListeningToMessages();\n stopListeningToClose();\n }\n\n function settleResolve(session: BananalinkSession): void {\n if (settled) {\n return;\n }\n\n settled = true;\n cleanup();\n resolve(session);\n }\n\n function settleReject(error: Error): void {\n if (settled) {\n return;\n }\n\n settled = true;\n cleanup();\n ws.close();\n reject(error);\n }\n });\n }\n\n private async getTransportHandle(dappInstanceId?: string | null): Promise<TransportHandle> {\n const url = `${this.wsUrl}${dappInstanceId != null ? `?dappInstanceId=${encodeURIComponent(dappInstanceId)}` : ''}`;\n try {\n return await connectWebSocket({\n url,\n pingIntervalMs: 30_000,\n pongTimeoutMs: 5_000,\n });\n } catch (error) {\n throw new BananalinkError('Unable to establish dapp websocket connection', { cause: error });\n }\n }\n\n public get connectionUrl(): string {\n if (!this.dappInstanceId || !this.nonce) {\n throw new InvalidConnectionStateError('Cannot get connection URL without a dapp instance');\n }\n const dappInstanceId = this.dappInstanceId;\n const nonce = this.nonce;\n return `bananalink://?dappId=${encodeURIComponent(this.dappId)}&dappInstanceId=${encodeURIComponent(dappInstanceId)}&nonce=${encodeURIComponent(nonce)}`;\n }\n\n private static bind(ws: TransportHandle, jwt: string): void {\n const bindMessage: BindMessage = { type: 'bind', jwt };\n ws.send(bindMessage);\n }\n}\n","import type { CreateDappInstanceResponse, Dapp } from '@bananalink-test/sdk-core';\nimport { createDappInstance } from '../api/createDappInstance.js';\nimport { InvalidConfigError } from '../errors/InvalidConfigError.js';\nimport { InvalidJwtError } from '../errors/InvalidJwtError.js';\nimport { createBananalinkJwks } from '../jwt/createBananalinkJwks.js';\nimport { verifyJwt } from '../jwt/verifyJwt.js';\nimport type { JwtPayload } from '../types/JwtPayload.js';\nimport { isValidUrl } from '../utils/isValidUrl.js';\nimport { BananalinkConnection } from './BananalinkConnection.js';\n\nexport class BananalinkClient {\n private readonly apiUrl: string;\n private readonly wsUrl: string;\n private readonly jwks: ReturnType<typeof createBananalinkJwks>;\n private readonly dapp: Dapp;\n private static readonly DEFAULT_BANANALINK_API_URL = 'https://tfr9p2mn4i.execute-api.us-east-2.amazonaws.com';\n private static readonly DEFAULT_BANANALINK_WS_URL = 'wss://yb47qomkt3.execute-api.us-east-2.amazonaws.com/v1';\n\n constructor(config: {\n apiUrl?: string;\n wsUrl?: string;\n dapp: Dapp;\n }) {\n this.apiUrl = config.apiUrl ?? BananalinkClient.DEFAULT_BANANALINK_API_URL;\n this.wsUrl = config.wsUrl ?? BananalinkClient.DEFAULT_BANANALINK_WS_URL;\n if (!isValidUrl(this.apiUrl, ['http', 'https'])) {\n throw new InvalidConfigError(`Invalid apiUrl: \"${this.apiUrl}\". Must use http or https.`);\n }\n if (!isValidUrl(this.wsUrl, ['ws', 'wss'])) {\n throw new InvalidConfigError(`Invalid wsUrl: \"${this.wsUrl}\". Must use ws or wss.`);\n }\n this.dapp = config.dapp;\n this.jwks = createBananalinkJwks(this.apiUrl);\n }\n\n public async connect(opts?: { jwt: string }): Promise<BananalinkConnection> {\n const rawJwt = opts?.jwt;\n if (!rawJwt) {\n const { dappInstanceId, nonce } = await this.connectDappInstance();\n return new BananalinkConnection({\n jwt: undefined,\n dappId: this.dapp.dappId,\n dappInstanceId,\n nonce,\n wsUrl: this.wsUrl,\n });\n }\n\n const jwtPayload = await this.getJwtPayload(rawJwt);\n if (!jwtPayload) {\n throw new InvalidJwtError();\n }\n\n return new BananalinkConnection({\n jwt: { jwtPayload, rawJwt },\n dappId: this.dapp.dappId,\n dappInstanceId: null,\n nonce: null,\n wsUrl: this.wsUrl,\n });\n }\n\n private async getJwtPayload(rawJwt: string): Promise<JwtPayload | null> {\n return await verifyJwt(rawJwt, this.jwks);\n }\n\n private async connectDappInstance(): Promise<CreateDappInstanceResponse> {\n return await createDappInstance(this.apiUrl, this.dapp);\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class InvalidUrlError extends BananalinkError {\n constructor(public readonly url: string) {\n super(`${url} is not a valid url`);\n }\n}\n","import QRCode from 'qrcode';\nimport { InvalidUrlError } from '../errors/InvalidUrlError.js';\nimport { isValidUrl } from './isValidUrl.js';\n\nexport async function displayBananalinkQR(url: string): Promise<string> {\n if (!isValidUrl(url, ['bananalink'])) {\n throw new InvalidUrlError(url);\n }\n return QRCode.toDataURL(url);\n}\n"],"mappings":";;;;;AAAA,IAAa,kBAAb,cAAqC,MAAM;CACzC,YAAY,SAAiB,SAAwB;AACnD,QAAM,SAAS,QAAQ;AACvB,OAAK,OAAO;;;;;;ACDhB,IAAa,eAAb,cAAkC,gBAAgB;CAChD,YACE,SACA,AAAgB,YAChB;AACA,QAAM,QAAQ;EAFE;AAGhB,OAAK,OAAO;;;;;;ACLhB,SAAS,6BAA6B,MAAmD;AACvF,QACE,OAAO,SAAS,YAChB,SAAS,QACT,oBAAoB,QACpB,OAAO,KAAK,mBAAmB,YAC/B,WAAW,QACX,OAAO,KAAK,UAAU;;AAI1B,eAAsB,mBAAmB,QAAgB,QAAmD;CAC1G,MAAM,WAAW,MAAM,MAAM,GAAG,OAAO,SAAS;EAC9C,SAAS,EACP,gBAAgB,oBACjB;EACD,QAAQ;EACR,MAAM,KAAK,UAAU,OAAO;EAC7B,CAAC;AACF,KAAI,SAAS,IAAI;EACf,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY;AAC7C,SAAM,IAAI,aAAa,6CAA6C,SAAS,OAAO;IACpF;AACF,MAAI,CAAC,6BAA6B,KAAK,CACrC,OAAM,IAAI,aAAa,iDAAiD,SAAS,OAAO;AAE1F,SAAO;;AAET,OAAM,IAAI,aAAa,kCAAkC,SAAS,OAAO;;;;;AC7B3E,IAAa,qBAAb,cAAwC,gBAAgB;CACtD,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;ACHhB,IAAa,kBAAb,cAAqC,gBAAgB;CACnD,cAAc;AACZ,QAAM,8BAA8B;;;;;;ACFxC,SAAgB,qBAAqB,QAAiC;AAEpE,QAAO,mBADK,IAAI,IAAI,GAAG,OAAO,wBAAwB,CACxB;;;;;ACDhC,eAAsB,UAAU,KAAa,MAAmD;AAC9F,KAAI;EACF,MAAM,EAAE,YAAY,MAAM,UAAkD,KAAK,MAAM;GACrF,YAAY,CAAC,QAAQ;GACrB,QAAQ;GACR,gBAAgB;IAAC;IAAO;IAAO;IAAW;IAAY;GACvD,CAAC;AACF,SAAO;GACL,SAAS,QAAQ;GACjB,QAAQ,MAAM,QAAQ,QAAQ,IAAI,GAAG,QAAQ,IAAI,KAAM,QAAQ;GAC/D,SAAS,QAAQ;GACjB,WAAW,QAAQ;GACnB;GACD;SACK;AACN,SAAO;;;;;;AClBX,SAAgB,WAAW,KAAa,WAA8B;AACpE,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,SAAO,UAAU,SAAS,OAAO,SAAS,QAAQ,KAAK,GAAG,CAAC;SACrD;AACN,SAAO;;;;;;ACHX,IAAa,0BAAb,cAA6C,gBAAgB;CAC3D,cAAc;AACZ,QAAM,qDAAqD;AAC3D,OAAK,OAAO;;;;;;ACHhB,IAAa,yBAAb,cAA4C,gBAAgB;CAC1D,cAAc;AACZ,QAAM,oCAAoC;AAC1C,OAAK,OAAO;;;;;;ACHhB,IAAa,8BAAb,cAAiD,gBAAgB;CAC/D,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;ACFhB,IAAa,oBAAb,MAA+B;CAC7B,YACE,AAAgB,eAChB,AAAiB,IACjB;EAFgB;EACC;;CAGnB,AAAO,QAAc;AACnB,OAAK,GAAG,OAAO;;;;;;ACMnB,IAAa,uBAAb,MAAa,qBAAqB;CAChC,OAAwB,sBAAsB,MAAU;CACxD,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,YAAY,MAMT;AACD,OAAK,QAAQ,KAAK;AAClB,OAAK,MAAM,KAAK;AAChB,OAAK,SAAS,KAAK;AACnB,OAAK,iBAAiB,KAAK;AAC3B,OAAK,QAAQ,KAAK;AAClB,MAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,IAChC,OAAM,IAAI,4BACR,kFACD;;CAIL,MAAa,aAAyC;AACpD,MAAI,KAAK,KAAK;GACZ,MAAM,EAAE,YAAY,WAAW,KAAK;GACpC,MAAM,KAAK,MAAM,KAAK,oBAAoB;AAC1C,wBAAqB,KAAK,IAAI,OAAO;AACrC,UAAO,IAAI,kBACT;IACE,SAAS,WAAW;IACpB,SAAS,WAAW;IACpB,WAAW,WAAW;IACtB,aAAa;IACd,EACD,GACD;;EAGH,MAAM,KAAK,MAAM,KAAK,mBAAmB,KAAK,eAAe;AAE7D,SAAO,MAAM,IAAI,SAAS,SAAS,WAAW;GAC5C,IAAI,UAAU;GAEd,MAAM,cAAc,iBAAiB;AACnC,iBAAa,IAAI,wBAAwB,CAAC;MACzC,qBAAqB,oBAAoB;GAE5C,MAAM,0BAA0B,GAAG,WAAW,YAAqB;AACjE,QAAI,oBAAoB,QAAQ,EAAE;AAChC,0BAAqB,KAAK,IAAI,QAAQ,YAAY;AAClD,mBACE,IAAI,kBACF;MACE,SAAS,QAAQ;MACjB,SAAS,QAAQ;MACjB,WAAW,QAAQ;MACnB,aAAa,QAAQ;MACtB,EACD,GACD,CACF;AACD;;AAGF,QAAI,kBAAkB,QAAQ,CAC5B,cAAa,IAAI,yBAAyB,CAAC;KAE7C;GAEF,MAAM,uBAAuB,GAAG,cAAc;AAC5C,iBAAa,IAAI,gBAAgB,mDAAmD,CAAC;KACrF;GAEF,SAAS,UAAgB;AACvB,iBAAa,YAAY;AACzB,6BAAyB;AACzB,0BAAsB;;GAGxB,SAAS,cAAc,SAAkC;AACvD,QAAI,QACF;AAGF,cAAU;AACV,aAAS;AACT,YAAQ,QAAQ;;GAGlB,SAAS,aAAa,OAAoB;AACxC,QAAI,QACF;AAGF,cAAU;AACV,aAAS;AACT,OAAG,OAAO;AACV,WAAO,MAAM;;IAEf;;CAGJ,MAAc,mBAAmB,gBAA0D;EACzF,MAAM,MAAM,GAAG,KAAK,QAAQ,kBAAkB,OAAO,mBAAmB,mBAAmB,eAAe,KAAK;AAC/G,MAAI;AACF,UAAO,MAAM,iBAAiB;IAC5B;IACA,gBAAgB;IAChB,eAAe;IAChB,CAAC;WACK,OAAO;AACd,SAAM,IAAI,gBAAgB,iDAAiD,EAAE,OAAO,OAAO,CAAC;;;CAIhG,IAAW,gBAAwB;AACjC,MAAI,CAAC,KAAK,kBAAkB,CAAC,KAAK,MAChC,OAAM,IAAI,4BAA4B,oDAAoD;EAE5F,MAAM,iBAAiB,KAAK;EAC5B,MAAM,QAAQ,KAAK;AACnB,SAAO,wBAAwB,mBAAmB,KAAK,OAAO,CAAC,kBAAkB,mBAAmB,eAAe,CAAC,SAAS,mBAAmB,MAAM;;CAGxJ,OAAe,KAAK,IAAqB,KAAmB;EAC1D,MAAM,cAA2B;GAAE,MAAM;GAAQ;GAAK;AACtD,KAAG,KAAK,YAAY;;;;;;ACzIxB,IAAa,mBAAb,MAAa,iBAAiB;CAC5B,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,OAAwB,6BAA6B;CACrD,OAAwB,4BAA4B;CAEpD,YAAY,QAIT;AACD,OAAK,SAAS,OAAO,UAAU,iBAAiB;AAChD,OAAK,QAAQ,OAAO,SAAS,iBAAiB;AAC9C,MAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,QAAQ,QAAQ,CAAC,CAC7C,OAAM,IAAI,mBAAmB,oBAAoB,KAAK,OAAO,4BAA4B;AAE3F,MAAI,CAAC,WAAW,KAAK,OAAO,CAAC,MAAM,MAAM,CAAC,CACxC,OAAM,IAAI,mBAAmB,mBAAmB,KAAK,MAAM,wBAAwB;AAErF,OAAK,OAAO,OAAO;AACnB,OAAK,OAAO,qBAAqB,KAAK,OAAO;;CAG/C,MAAa,QAAQ,MAAuD;EAC1E,MAAM,SAAS,MAAM;AACrB,MAAI,CAAC,QAAQ;GACX,MAAM,EAAE,gBAAgB,UAAU,MAAM,KAAK,qBAAqB;AAClE,UAAO,IAAI,qBAAqB;IAC9B,KAAK;IACL,QAAQ,KAAK,KAAK;IAClB;IACA;IACA,OAAO,KAAK;IACb,CAAC;;EAGJ,MAAM,aAAa,MAAM,KAAK,cAAc,OAAO;AACnD,MAAI,CAAC,WACH,OAAM,IAAI,iBAAiB;AAG7B,SAAO,IAAI,qBAAqB;GAC9B,KAAK;IAAE;IAAY;IAAQ;GAC3B,QAAQ,KAAK,KAAK;GAClB,gBAAgB;GAChB,OAAO;GACP,OAAO,KAAK;GACb,CAAC;;CAGJ,MAAc,cAAc,QAA4C;AACtE,SAAO,MAAM,UAAU,QAAQ,KAAK,KAAK;;CAG3C,MAAc,sBAA2D;AACvE,SAAO,MAAM,mBAAmB,KAAK,QAAQ,KAAK,KAAK;;;;;;ACjE3D,IAAa,kBAAb,cAAqC,gBAAgB;CACnD,YAAY,AAAgB,KAAa;AACvC,QAAM,GAAG,IAAI,qBAAqB;EADR;;;;;;ACC9B,eAAsB,oBAAoB,KAA8B;AACtE,KAAI,CAAC,WAAW,KAAK,CAAC,aAAa,CAAC,CAClC,OAAM,IAAI,gBAAgB,IAAI;AAEhC,QAAO,OAAO,UAAU,IAAI"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/errors/BananalinkError.ts","../src/errors/DappApiError.ts","../src/api/createDappInstance.ts","../src/errors/InvalidConfigError.ts","../src/errors/InvalidJwtError.ts","../src/jwt/createBananalinkJwks.ts","../src/jwt/verifyJwt.ts","../src/utils/isValidUrl.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.ts","../src/errors/InvalidConnectionStateError.ts","../src/errors/PendingSessionAbortedError.ts","../src/api/createDappMessage.ts","../src/errors/RequestRejectedError.ts","../src/errors/RequestTimeoutError.ts","../src/errors/SessionClosedError.ts","../src/core/BananalinkSession.ts","../src/core/BananalinkConnection.ts","../src/core/BananalinkClient.ts","../src/errors/InvalidUrlError.ts","../src/utils/displayBananalinkQR.ts"],"sourcesContent":["export class BananalinkError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'BananalinkError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class DappApiError extends BananalinkError {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message);\n this.name = 'DappApiError';\n }\n}\n","import type { CreateDappInstanceResponse, Dapp } from '@bananalink-test/sdk-core';\nimport { DappApiError } from '../errors/DappApiError.js';\n\nfunction isCreateDappInstanceResponse(body: unknown): body is CreateDappInstanceResponse {\n return (\n typeof body === 'object' &&\n body !== null &&\n 'dappInstanceId' in body &&\n typeof body.dappInstanceId === 'string' &&\n 'nonce' in body &&\n typeof body.nonce === 'string'\n );\n}\n\nexport async function createDappInstance(apiUrl: string, params: Dapp): Promise<CreateDappInstanceResponse> {\n const response = await fetch(`${apiUrl}/dapps`, {\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n body: JSON.stringify(params),\n });\n if (response.ok) {\n const body = await response.json().catch(() => {\n throw new DappApiError('Invalid JSON response from bananalink API', response.status);\n });\n if (!isCreateDappInstanceResponse(body)) {\n throw new DappApiError('Unexpected response shape from bananalink API', response.status);\n }\n return body;\n }\n throw new DappApiError('Failed to create dapp instance', response.status);\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class InvalidConfigError extends BananalinkError {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidConfigError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class InvalidJwtError extends BananalinkError {\n constructor() {\n super('Invalid Bananalink dapp jwt');\n }\n}\n","import { createRemoteJWKSet, type JWTVerifyGetKey } from 'jose';\n\nexport function createBananalinkJwks(apiUrl: string): JWTVerifyGetKey {\n const url = new URL(`${apiUrl}/.well-known/jwks.json`);\n return createRemoteJWKSet(url);\n}\n","import { type JWTVerifyGetKey, jwtVerify } from 'jose';\nimport type { JwtPayload } from '../types/JwtPayload.js';\n\nexport async function verifyJwt(jwt: string, jwks: JWTVerifyGetKey): Promise<JwtPayload | null> {\n try {\n const { payload } = await jwtVerify<{ message: string; signature: string }>(jwt, jwks, {\n algorithms: ['ES256'],\n issuer: 'bananalink',\n requiredClaims: ['sub', 'aud', 'message', 'signature'],\n });\n return {\n address: payload.sub as string,\n dappId: Array.isArray(payload.aud) ? payload.aud[0] : (payload.aud as string),\n message: payload.message,\n signature: payload.signature,\n jwt,\n };\n } catch {\n return null;\n }\n}\n","export function isValidUrl(url: string, protocols: string[]): boolean {\n try {\n const parsed = new URL(url);\n return protocols.includes(parsed.protocol.replace(':', ''));\n } catch {\n return false;\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class ConnectionRejectedError extends BananalinkError {\n constructor() {\n super('Dapp connection attempt was rejected by the wallet');\n this.name = 'ConnectionRejectedError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class ConnectionTimeoutError extends BananalinkError {\n constructor() {\n super('Dapp connection attempt timed out');\n this.name = 'ConnectionTimeoutError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class InvalidConnectionStateError extends BananalinkError {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidConnectionStateError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class PendingSessionAbortedError extends BananalinkError {\n constructor() {\n super('Pending session was intentionally aborted');\n this.name = 'PendingSessionAbortedError';\n }\n}\n","import { DappApiError } from '../errors/DappApiError.js';\n\nfunction isCreateDappMessageResponse(body: unknown): body is { messageId: string } {\n return typeof body === 'object' && body !== null && 'messageId' in body && typeof body.messageId === 'string';\n}\n\nexport async function createDappMessage(\n apiUrl: string,\n accessToken: string,\n params: {\n method: string;\n payload: unknown;\n },\n): Promise<{ messageId: string }> {\n const response = await fetch(`${apiUrl}/dapps/messages`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(params),\n });\n if (response.ok) {\n const body = await response.json().catch(() => {\n throw new DappApiError('Invalid JSON response from bananalink API', response.status);\n });\n if (!isCreateDappMessageResponse(body)) {\n throw new DappApiError('Unexpected response shape from bananalink API', response.status);\n }\n return body;\n }\n throw new DappApiError('Failed to create dapp message', response.status);\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class RequestRejectedError extends BananalinkError {\n constructor(public readonly messageId: string) {\n super(`Request rejected by wallet (messageId: ${messageId})`);\n this.name = 'RequestRejectedError';\n }\n}\n","import type { Request } from '../types/Request.js';\nimport { BananalinkError } from './BananalinkError.js';\n\nexport class RequestTimeoutError extends BananalinkError {\n constructor(public readonly method: Request) {\n super(`Timeout reached for request ${method}`);\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class SessionClosedError extends BananalinkError {\n constructor() {\n super('Session is closed');\n }\n}\n","import { isMessageResponseMessage, type MessageResponseMessage, type TransportHandle } from '@bananalink-test/sdk-core';\nimport { createDappMessage } from '../api/createDappMessage.js';\nimport { BananalinkError } from '../errors/BananalinkError.js';\nimport { RequestRejectedError } from '../errors/RequestRejectedError.js';\nimport { RequestTimeoutError } from '../errors/RequestTimeoutError.js';\nimport { SessionClosedError } from '../errors/SessionClosedError.js';\nimport type { PendingMessageRequest } from '../types/PendingMessageRequest.js';\nimport type { Request } from '../types/Request.js';\nimport type { RequestParams } from '../types/RequestParams.js';\nimport type { RequestResult } from '../types/RequestResult.js';\nimport type { RequestResultHandler } from '../types/RequestResultHandler.js';\nimport type { SessionClaims } from '../types/SessionClaims.js';\n\nexport class BananalinkSession {\n private static readonly REQUEST_TIMEOUT_MS = 10 * 60 * 1000;\n private readonly pendingRequests = new Map<string, PendingMessageRequest>();\n private readonly stopListening: () => void;\n private readonly stopListeningOnClose: () => void;\n private closed: boolean;\n\n private static readonly RESULT_HANDLERS: { [T in Request]: RequestResultHandler<T> } = {\n eth_sendTransaction: (result) => {\n if (typeof result === 'object' && result !== null && 'txHash' in result && typeof result.txHash === 'string') {\n return result.txHash;\n }\n throw new BananalinkError(`Unexpected eth_sendTransaction result: ${result}`);\n },\n };\n\n constructor(\n public readonly sessionClaims: SessionClaims,\n private readonly ws: TransportHandle,\n private readonly apiUrl: string,\n ) {\n this.stopListening = this.ws.onMessage((payload) => {\n if (isMessageResponseMessage(payload)) {\n this.handleMessageResponse(payload);\n }\n });\n this.stopListeningOnClose = this.ws.onClose(() => {\n this.cleanup();\n this.closed = true;\n });\n this.closed = false;\n }\n\n public async request<T extends Request>({\n method,\n params,\n timeoutMs = BananalinkSession.REQUEST_TIMEOUT_MS,\n }: {\n method: T;\n params?: RequestParams<T>;\n timeoutMs?: number;\n }): Promise<RequestResult<T>> {\n if (this.closed) {\n throw new SessionClosedError();\n }\n const { messageId } = await createDappMessage(this.apiUrl, this.sessionClaims.accessToken, {\n method,\n payload: params,\n });\n\n if (this.closed) {\n throw new SessionClosedError();\n }\n\n return new Promise<RequestResult<T>>((resolve, reject) => {\n let timeout: ReturnType<typeof setTimeout> | undefined;\n if (timeoutMs > 0) {\n timeout = setTimeout(() => {\n this.pendingRequests.delete(messageId);\n reject(new RequestTimeoutError(method));\n }, timeoutMs);\n }\n\n this.pendingRequests.set(messageId, {\n timeout,\n handle: (messageResponse: MessageResponseMessage) => {\n if (messageResponse.status === 'rejected') {\n reject(new RequestRejectedError(messageId));\n return;\n }\n const resultHandler = BananalinkSession.RESULT_HANDLERS[method];\n try {\n resolve(resultHandler(messageResponse.payloadResponse));\n } catch (error: unknown) {\n reject(new BananalinkError(`Failed resolving request response, ${error}`));\n }\n },\n reject,\n });\n });\n }\n\n public close(): void {\n this.cleanup();\n if (!this.ws.closed) {\n this.ws.close();\n }\n this.closed = true;\n }\n\n private handleMessageResponse(messageResponseMessage: MessageResponseMessage): void {\n const pending = this.pendingRequests.get(messageResponseMessage.msgId);\n if (!pending) return;\n\n this.pendingRequests.delete(messageResponseMessage.msgId);\n clearTimeout(pending.timeout);\n pending.handle(messageResponseMessage);\n }\n\n private cleanup(): void {\n this.rejectAllPendingRequests();\n this.stopListening();\n this.stopListeningOnClose();\n }\n\n private rejectAllPendingRequests(): void {\n for (const [, pending] of this.pendingRequests) {\n clearTimeout(pending.timeout);\n // TODO: propagate error or throw generic one\n pending.reject(new Error('Pending requests rejected'));\n }\n this.pendingRequests.clear();\n }\n}\n","import {\n type BindMessage,\n connectWebSocket,\n isAuthorizedMessage,\n isRejectedMessage,\n type TransportHandle,\n} from '@bananalink-test/sdk-core';\nimport { BananalinkError } from '../errors/BananalinkError.js';\nimport { ConnectionRejectedError } from '../errors/ConnectionRejectedError.js';\nimport { ConnectionTimeoutError } from '../errors/ConnectionTimeoutError.js';\nimport { InvalidConnectionStateError } from '../errors/InvalidConnectionStateError.js';\nimport { PendingSessionAbortedError } from '../errors/PendingSessionAbortedError.js';\nimport type { JwtPayload } from '../types/JwtPayload.js';\nimport { BananalinkSession } from './BananalinkSession.js';\n\ntype BananalinkDappJwt = { jwtPayload: JwtPayload; rawJwt: string } | undefined;\n\nexport class BananalinkConnection {\n private static readonly AUTH_TIMEOUT_MILLIS = 10 * 60 * 1000;\n private readonly apiUrl: string;\n private readonly wsUrl: string;\n private readonly jwt: BananalinkDappJwt;\n private readonly dappId: string;\n private readonly dappInstanceId: string | null;\n private readonly nonce: string | null;\n private aborting: boolean = false;\n private abortPendingSessionRequest: (() => void) | null = null;\n private pendingSessionPromise: Promise<BananalinkSession> | null = null;\n\n constructor(opts: {\n apiUrl: string;\n wsUrl: string;\n jwt: BananalinkDappJwt;\n dappId: string;\n dappInstanceId: string | null;\n nonce: string | null;\n }) {\n this.apiUrl = opts.apiUrl;\n this.wsUrl = opts.wsUrl;\n this.jwt = opts.jwt;\n this.dappId = opts.dappId;\n this.dappInstanceId = opts.dappInstanceId;\n this.nonce = opts.nonce;\n if (!this.dappInstanceId && !this.jwt) {\n throw new InvalidConnectionStateError(\n 'Cannot get session without authorizing a dapp instance or providing a valid JWT',\n );\n }\n }\n\n public async getSession(): Promise<BananalinkSession> {\n if (this.pendingSessionPromise) {\n return this.pendingSessionPromise;\n }\n if (this.jwt) {\n const { jwtPayload, rawJwt } = this.jwt;\n const ws = await this.getTransportHandle();\n if (this.aborting) {\n ws.close();\n this.aborting = false;\n throw new PendingSessionAbortedError();\n }\n BananalinkConnection.bind(ws, rawJwt);\n return new BananalinkSession(\n {\n address: jwtPayload.address,\n message: jwtPayload.message,\n signature: jwtPayload.signature,\n accessToken: rawJwt,\n },\n ws,\n this.apiUrl,\n );\n }\n\n const ws = await this.getTransportHandle(this.dappInstanceId);\n if (this.aborting) {\n ws.close();\n this.aborting = false;\n throw new PendingSessionAbortedError();\n }\n\n this.pendingSessionPromise = new Promise((resolve, reject) => {\n let settled = false;\n\n const authTimeout = setTimeout(() => {\n settleReject(new ConnectionTimeoutError());\n }, BananalinkConnection.AUTH_TIMEOUT_MILLIS);\n\n const stopListeningToMessages = ws.onMessage((payload: unknown) => {\n if (isAuthorizedMessage(payload)) {\n BananalinkConnection.bind(ws, payload.accessToken);\n settleResolve(\n new BananalinkSession(\n {\n address: payload.address,\n message: payload.message,\n signature: payload.signature,\n accessToken: payload.accessToken,\n },\n ws,\n this.apiUrl,\n ),\n );\n return;\n }\n\n if (isRejectedMessage(payload)) {\n settleReject(new ConnectionRejectedError());\n }\n });\n\n const stopListeningToClose = ws.onClose(() => {\n settleReject(new BananalinkError('Connection closed before authorization completed'));\n });\n\n this.abortPendingSessionRequest = () => {\n settleReject(new PendingSessionAbortedError());\n };\n\n const cleanup = (): void => {\n this.abortPendingSessionRequest = null;\n clearTimeout(authTimeout);\n stopListeningToMessages();\n stopListeningToClose();\n this.aborting = false;\n this.pendingSessionPromise = null;\n };\n\n function settleResolve(session: BananalinkSession): void {\n if (settled) {\n return;\n }\n\n settled = true;\n cleanup();\n resolve(session);\n }\n\n function settleReject(error: Error): void {\n if (settled) {\n return;\n }\n\n settled = true;\n cleanup();\n ws.close();\n reject(error);\n }\n });\n return await this.pendingSessionPromise;\n }\n\n public abortPendingSession(): void {\n if (this.aborting) {\n return;\n }\n this.aborting = true;\n this.abortPendingSessionRequest?.();\n }\n\n private async getTransportHandle(dappInstanceId?: string | null): Promise<TransportHandle> {\n const url = `${this.wsUrl}${dappInstanceId != null ? `?dappInstanceId=${encodeURIComponent(dappInstanceId)}` : ''}`;\n try {\n return await connectWebSocket({\n url,\n pingIntervalMs: 30_000,\n pongTimeoutMs: 5_000,\n });\n } catch (error) {\n throw new BananalinkError('Unable to establish dapp websocket connection', { cause: error });\n }\n }\n\n public get connectionUrl(): string {\n if (!this.dappInstanceId || !this.nonce) {\n throw new InvalidConnectionStateError('Cannot get connection URL without a dapp instance');\n }\n const dappInstanceId = this.dappInstanceId;\n const nonce = this.nonce;\n return `bananalink://?dappId=${encodeURIComponent(this.dappId)}&dappInstanceId=${encodeURIComponent(dappInstanceId)}&nonce=${encodeURIComponent(nonce)}`;\n }\n\n private static bind(ws: TransportHandle, jwt: string): void {\n const bindMessage: BindMessage = { type: 'bind', jwt };\n ws.send(bindMessage);\n }\n}\n","import type { CreateDappInstanceResponse, Dapp } from '@bananalink-test/sdk-core';\nimport { createDappInstance } from '../api/createDappInstance.js';\nimport { InvalidConfigError } from '../errors/InvalidConfigError.js';\nimport { InvalidJwtError } from '../errors/InvalidJwtError.js';\nimport { createBananalinkJwks } from '../jwt/createBananalinkJwks.js';\nimport { verifyJwt } from '../jwt/verifyJwt.js';\nimport type { JwtPayload } from '../types/JwtPayload.js';\nimport { isValidUrl } from '../utils/isValidUrl.js';\nimport { BananalinkConnection } from './BananalinkConnection.js';\n\nexport class BananalinkClient {\n private readonly apiUrl: string;\n private readonly wsUrl: string;\n private readonly jwks: ReturnType<typeof createBananalinkJwks>;\n private readonly dapp: Dapp;\n private static readonly DEFAULT_BANANALINK_API_URL = 'https://tfr9p2mn4i.execute-api.us-east-2.amazonaws.com';\n private static readonly DEFAULT_BANANALINK_WS_URL = 'wss://yb47qomkt3.execute-api.us-east-2.amazonaws.com/v1';\n\n constructor(config: {\n apiUrl?: string;\n wsUrl?: string;\n dapp: Dapp;\n }) {\n this.apiUrl = config.apiUrl ?? BananalinkClient.DEFAULT_BANANALINK_API_URL;\n this.wsUrl = config.wsUrl ?? BananalinkClient.DEFAULT_BANANALINK_WS_URL;\n if (!isValidUrl(this.apiUrl, ['http', 'https'])) {\n throw new InvalidConfigError(`Invalid apiUrl: \"${this.apiUrl}\". Must use http or https.`);\n }\n if (!isValidUrl(this.wsUrl, ['ws', 'wss'])) {\n throw new InvalidConfigError(`Invalid wsUrl: \"${this.wsUrl}\". Must use ws or wss.`);\n }\n this.dapp = config.dapp;\n this.jwks = createBananalinkJwks(this.apiUrl);\n }\n\n public async connect(opts?: { jwt: string }): Promise<BananalinkConnection> {\n const rawJwt = opts?.jwt;\n if (!rawJwt) {\n const { dappInstanceId, nonce } = await this.connectDappInstance();\n return new BananalinkConnection({\n jwt: undefined,\n dappId: this.dapp.dappId,\n dappInstanceId,\n nonce,\n apiUrl: this.apiUrl,\n wsUrl: this.wsUrl,\n });\n }\n\n const jwtPayload = await this.getJwtPayload(rawJwt);\n if (!jwtPayload) {\n throw new InvalidJwtError();\n }\n\n return new BananalinkConnection({\n jwt: { jwtPayload, rawJwt },\n dappId: this.dapp.dappId,\n dappInstanceId: null,\n nonce: null,\n apiUrl: this.apiUrl,\n wsUrl: this.wsUrl,\n });\n }\n\n private async getJwtPayload(rawJwt: string): Promise<JwtPayload | null> {\n return await verifyJwt(rawJwt, this.jwks);\n }\n\n private async connectDappInstance(): Promise<CreateDappInstanceResponse> {\n return await createDappInstance(this.apiUrl, this.dapp);\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class InvalidUrlError extends BananalinkError {\n constructor(public readonly url: string) {\n super(`${url} is not a valid url`);\n }\n}\n","import QRCode from 'qrcode';\nimport { InvalidUrlError } from '../errors/InvalidUrlError.js';\nimport { isValidUrl } from './isValidUrl.js';\n\nexport async function displayBananalinkQR(url: string): Promise<string> {\n if (!isValidUrl(url, ['bananalink'])) {\n throw new InvalidUrlError(url);\n }\n return QRCode.toDataURL(url);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananalink-test/client",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "jose": "^6.1.3",
36
36
  "qrcode": "^1.5.4",
37
- "@bananalink-test/sdk-core": "0.2.0"
37
+ "@bananalink-test/sdk-core": "0.3.1"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsdown",