@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 +172 -8
- package/dist/index.d.cts +74 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +74 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +170 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/index.d.cts.map
CHANGED
|
@@ -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;;;
|
|
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
|
-
|
|
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
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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;;;
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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":"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"}
|
|
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
|
+
"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.
|
|
37
|
+
"@bananalink-test/sdk-core": "0.3.1"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsdown",
|