@bananalink-test/agent 1.0.1
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 +481 -0
- package/dist/index.d.cts +175 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +175 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +439 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +47 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let jose = require("jose");
|
|
30
|
+
let _bananalink_test_sdk_core = require("@bananalink-test/sdk-core");
|
|
31
|
+
let qrcode = require("qrcode");
|
|
32
|
+
qrcode = __toESM(qrcode);
|
|
33
|
+
|
|
34
|
+
//#region src/errors/BananalinkError.ts
|
|
35
|
+
var BananalinkError = class extends Error {
|
|
36
|
+
constructor(message, options) {
|
|
37
|
+
super(message, options);
|
|
38
|
+
this.name = "BananalinkError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/errors/AgentApiError.ts
|
|
44
|
+
var AgentApiError = class extends BananalinkError {
|
|
45
|
+
constructor(message, statusCode) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.statusCode = statusCode;
|
|
48
|
+
this.name = "AgentApiError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/api/createAgentPairingRequest.ts
|
|
54
|
+
function isCreateAgentPairingResponse(body) {
|
|
55
|
+
return typeof body === "object" && body !== null && "pairingCode" in body;
|
|
56
|
+
}
|
|
57
|
+
async function createAgentPairingRequest(apiUrl, params) {
|
|
58
|
+
const response = await fetch(`${apiUrl}/agents/pairings`, {
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
method: "POST",
|
|
61
|
+
body: JSON.stringify(params)
|
|
62
|
+
});
|
|
63
|
+
if (response.ok) {
|
|
64
|
+
const body = await response.json().catch(() => {
|
|
65
|
+
throw new AgentApiError("Invalid JSON response from bananalink API", response.status);
|
|
66
|
+
});
|
|
67
|
+
if (!isCreateAgentPairingResponse(body)) throw new AgentApiError("Unexpected response shape from bananalink API", response.status);
|
|
68
|
+
return body;
|
|
69
|
+
}
|
|
70
|
+
throw new AgentApiError("Failed to create agent pairing request", response.status);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/errors/InvalidConfigError.ts
|
|
75
|
+
var InvalidConfigError = class extends BananalinkError {
|
|
76
|
+
constructor(message) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.name = "InvalidConfigError";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/errors/InvalidJwtError.ts
|
|
84
|
+
var InvalidJwtError = class extends BananalinkError {
|
|
85
|
+
constructor() {
|
|
86
|
+
super("Invalid Bananalink agent jwt");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/jwt/createBananalinkJwks.ts
|
|
92
|
+
function createBananalinkJwks(apiUrl) {
|
|
93
|
+
return (0, jose.createRemoteJWKSet)(new URL(`${apiUrl}/.well-known/jwks.json`));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/jwt/verifyJwt.ts
|
|
98
|
+
async function verifyJwt(jwt, jwks) {
|
|
99
|
+
try {
|
|
100
|
+
const { payload } = await (0, jose.jwtVerify)(jwt, jwks, {
|
|
101
|
+
algorithms: ["ES256"],
|
|
102
|
+
issuer: "bananalink",
|
|
103
|
+
requiredClaims: ["sub", "aud"]
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
agentId: Array.isArray(payload.aud) ? payload.aud[0] : payload.aud,
|
|
107
|
+
jwt
|
|
108
|
+
};
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/utils/isValidUrl.ts
|
|
116
|
+
function isValidUrl(url, protocols) {
|
|
117
|
+
try {
|
|
118
|
+
const parsed = new URL(url);
|
|
119
|
+
return protocols.includes(parsed.protocol.replace(":", ""));
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/errors/ConnectionRejectedError.ts
|
|
127
|
+
var ConnectionRejectedError = class extends BananalinkError {
|
|
128
|
+
constructor() {
|
|
129
|
+
super("Agent connection attempt was rejected by the hub");
|
|
130
|
+
this.name = "ConnectionRejectedError";
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/errors/ConnectionTimeoutError.ts
|
|
136
|
+
var ConnectionTimeoutError = class extends BananalinkError {
|
|
137
|
+
constructor() {
|
|
138
|
+
super("Agent connection attempt timed out");
|
|
139
|
+
this.name = "ConnectionTimeoutError";
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/errors/InvalidConnectionStateError.ts
|
|
145
|
+
var InvalidConnectionStateError = class extends BananalinkError {
|
|
146
|
+
constructor(message) {
|
|
147
|
+
super(message);
|
|
148
|
+
this.name = "InvalidConnectionStateError";
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/errors/PendingSessionAbortedError.ts
|
|
154
|
+
var PendingSessionAbortedError = class extends BananalinkError {
|
|
155
|
+
constructor() {
|
|
156
|
+
super("Pending session was intentionally aborted");
|
|
157
|
+
this.name = "PendingSessionAbortedError";
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/api/createAgentMessage.ts
|
|
163
|
+
function isCreateAgentMessageResponse(body) {
|
|
164
|
+
return typeof body === "object" && body !== null && "messageId" in body && typeof body.messageId === "string";
|
|
165
|
+
}
|
|
166
|
+
async function createAgentMessage(apiUrl, accessToken, params) {
|
|
167
|
+
const response = await fetch(`${apiUrl}/agents/messages`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: {
|
|
170
|
+
Authorization: `Bearer ${accessToken}`,
|
|
171
|
+
"Content-Type": "application/json"
|
|
172
|
+
},
|
|
173
|
+
body: JSON.stringify(params)
|
|
174
|
+
});
|
|
175
|
+
if (response.ok) {
|
|
176
|
+
const body = await response.json().catch(() => {
|
|
177
|
+
throw new AgentApiError("Invalid JSON response from bananalink API", response.status);
|
|
178
|
+
});
|
|
179
|
+
if (!isCreateAgentMessageResponse(body)) throw new AgentApiError("Unexpected response shape from bananalink API", response.status);
|
|
180
|
+
return body;
|
|
181
|
+
}
|
|
182
|
+
throw new AgentApiError("Failed to create agent message", response.status);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/errors/RequestRejectedError.ts
|
|
187
|
+
var RequestRejectedError = class extends BananalinkError {
|
|
188
|
+
constructor(messageId) {
|
|
189
|
+
super(`Request rejected by hub (messageId: ${messageId})`);
|
|
190
|
+
this.messageId = messageId;
|
|
191
|
+
this.name = "RequestRejectedError";
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/errors/RequestTimeoutError.ts
|
|
197
|
+
var RequestTimeoutError = class extends BananalinkError {
|
|
198
|
+
constructor(method) {
|
|
199
|
+
super(`Timeout reached for request ${method}`);
|
|
200
|
+
this.method = method;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/errors/SessionClosedError.ts
|
|
206
|
+
var SessionClosedError = class extends BananalinkError {
|
|
207
|
+
constructor() {
|
|
208
|
+
super("Session is closed");
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/core/BananalinkSession.ts
|
|
214
|
+
var BananalinkSession = class BananalinkSession {
|
|
215
|
+
static REQUEST_TIMEOUT_MS = 600 * 1e3;
|
|
216
|
+
static CREATE_DAPP_MESSAGE_MAX_ATTEMPTS = 3;
|
|
217
|
+
static CREATE_DAPP_MESSAGE_BASE_DELAY_MS = 1e3;
|
|
218
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
219
|
+
stopListening;
|
|
220
|
+
stopListeningOnClose;
|
|
221
|
+
closed;
|
|
222
|
+
static RESULT_HANDLERS = { eth_sendTransaction: (result) => {
|
|
223
|
+
if (typeof result === "object" && result !== null && "txHash" in result && typeof result.txHash === "string") return result.txHash;
|
|
224
|
+
throw new BananalinkError(`Unexpected eth_sendTransaction result: ${result}`);
|
|
225
|
+
} };
|
|
226
|
+
constructor(sessionClaims, ws, apiUrl) {
|
|
227
|
+
this.sessionClaims = sessionClaims;
|
|
228
|
+
this.ws = ws;
|
|
229
|
+
this.apiUrl = apiUrl;
|
|
230
|
+
this.stopListening = this.ws.onMessage((payload) => {
|
|
231
|
+
if ((0, _bananalink_test_sdk_core.isMessageResponseMessage)(payload)) this.handleMessageResponse(payload);
|
|
232
|
+
});
|
|
233
|
+
this.stopListeningOnClose = this.ws.onClose(() => {
|
|
234
|
+
this.cleanup();
|
|
235
|
+
this.closed = true;
|
|
236
|
+
});
|
|
237
|
+
this.closed = false;
|
|
238
|
+
}
|
|
239
|
+
async request({ method, params, timeoutMs = BananalinkSession.REQUEST_TIMEOUT_MS }) {
|
|
240
|
+
if (this.closed) throw new SessionClosedError();
|
|
241
|
+
const { messageId } = await (0, _bananalink_test_sdk_core.withBackoff)(async () => await createAgentMessage(this.apiUrl, this.sessionClaims.accessToken, {
|
|
242
|
+
method,
|
|
243
|
+
payload: params
|
|
244
|
+
}), BananalinkSession.CREATE_DAPP_MESSAGE_MAX_ATTEMPTS, BananalinkSession.CREATE_DAPP_MESSAGE_BASE_DELAY_MS);
|
|
245
|
+
if (this.closed) throw new SessionClosedError();
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
let timeout;
|
|
248
|
+
if (timeoutMs > 0) timeout = setTimeout(() => {
|
|
249
|
+
this.pendingRequests.delete(messageId);
|
|
250
|
+
reject(new RequestTimeoutError(method));
|
|
251
|
+
}, timeoutMs);
|
|
252
|
+
this.pendingRequests.set(messageId, {
|
|
253
|
+
timeout,
|
|
254
|
+
handle: (messageResponse) => {
|
|
255
|
+
if (messageResponse.status === "rejected") {
|
|
256
|
+
reject(new RequestRejectedError(messageId));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const resultHandler = BananalinkSession.RESULT_HANDLERS[method];
|
|
260
|
+
try {
|
|
261
|
+
resolve(resultHandler(messageResponse.payloadResponse));
|
|
262
|
+
} catch (error) {
|
|
263
|
+
reject(new BananalinkError(`Failed resolving request response, ${error}`));
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
reject
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
close() {
|
|
271
|
+
this.cleanup();
|
|
272
|
+
if (!this.ws.closed) this.ws.close();
|
|
273
|
+
this.closed = true;
|
|
274
|
+
}
|
|
275
|
+
handleMessageResponse(messageResponseMessage) {
|
|
276
|
+
const pending = this.pendingRequests.get(messageResponseMessage.msgId);
|
|
277
|
+
if (!pending) return;
|
|
278
|
+
this.pendingRequests.delete(messageResponseMessage.msgId);
|
|
279
|
+
clearTimeout(pending.timeout);
|
|
280
|
+
pending.handle(messageResponseMessage);
|
|
281
|
+
}
|
|
282
|
+
cleanup() {
|
|
283
|
+
this.rejectAllPendingRequests();
|
|
284
|
+
this.stopListening();
|
|
285
|
+
this.stopListeningOnClose();
|
|
286
|
+
}
|
|
287
|
+
rejectAllPendingRequests() {
|
|
288
|
+
for (const [, pending] of this.pendingRequests) {
|
|
289
|
+
clearTimeout(pending.timeout);
|
|
290
|
+
pending.reject(/* @__PURE__ */ new Error("Pending requests rejected"));
|
|
291
|
+
}
|
|
292
|
+
this.pendingRequests.clear();
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/core/BananalinkConnection.ts
|
|
298
|
+
var BananalinkConnection = class BananalinkConnection {
|
|
299
|
+
static AUTH_TIMEOUT_MILLIS = 600 * 1e3;
|
|
300
|
+
static BIND_RESPONSE_TIMEOUT_MILLIS = 20 * 1e3;
|
|
301
|
+
apiUrl;
|
|
302
|
+
wsUrl;
|
|
303
|
+
jwt;
|
|
304
|
+
agentPairingCode;
|
|
305
|
+
pendingSessionPromise = null;
|
|
306
|
+
pendingSessionAbortController = null;
|
|
307
|
+
constructor(opts) {
|
|
308
|
+
this.apiUrl = opts.apiUrl;
|
|
309
|
+
this.wsUrl = opts.wsUrl;
|
|
310
|
+
this.jwt = opts.jwt;
|
|
311
|
+
this.agentPairingCode = opts.agentPairingCode;
|
|
312
|
+
if (!this.agentPairingCode && !this.jwt) throw new InvalidConnectionStateError("Cannot get session without authorizing agent or providing a valid JWT");
|
|
313
|
+
}
|
|
314
|
+
get connectionUrl() {
|
|
315
|
+
if (!this.agentPairingCode) throw new InvalidConnectionStateError("Cannot get connection URL without a pairing code");
|
|
316
|
+
return `bananalink://?code=${encodeURIComponent(this.agentPairingCode)}`;
|
|
317
|
+
}
|
|
318
|
+
abortPendingSession() {
|
|
319
|
+
this.pendingSessionAbortController?.abort();
|
|
320
|
+
}
|
|
321
|
+
async getSession() {
|
|
322
|
+
if (this.pendingSessionPromise) return this.pendingSessionPromise;
|
|
323
|
+
this.pendingSessionAbortController = new AbortController();
|
|
324
|
+
this.pendingSessionPromise = (this.jwt ? this.resumeSession(this.jwt, this.pendingSessionAbortController.signal) : this.waitForAuthorization(this.pendingSessionAbortController.signal)).finally(() => {
|
|
325
|
+
this.pendingSessionPromise = null;
|
|
326
|
+
this.pendingSessionAbortController = null;
|
|
327
|
+
});
|
|
328
|
+
return this.pendingSessionPromise;
|
|
329
|
+
}
|
|
330
|
+
async openSession(getSessionClaims, abortSignal) {
|
|
331
|
+
const transportHandle = await this.getTransportHandle(this.agentPairingCode);
|
|
332
|
+
const onAbort = () => transportHandle.close();
|
|
333
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
334
|
+
if (abortSignal.aborted) {
|
|
335
|
+
transportHandle.close();
|
|
336
|
+
throw new PendingSessionAbortedError();
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const sessionClaims = await getSessionClaims(transportHandle);
|
|
340
|
+
await this.bind(transportHandle, sessionClaims.accessToken, abortSignal);
|
|
341
|
+
return new BananalinkSession(sessionClaims, (0, _bananalink_test_sdk_core.createReconnectingTransport)(transportHandle, {
|
|
342
|
+
reconnectTransport: async () => this.getTransportHandle(null),
|
|
343
|
+
onReconnect: async (th) => this.bind(th, sessionClaims.accessToken),
|
|
344
|
+
maxReconnectAttempts: 5,
|
|
345
|
+
baseDelayReconnectMs: 1e3
|
|
346
|
+
}), this.apiUrl);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
transportHandle.close();
|
|
349
|
+
throw error;
|
|
350
|
+
} finally {
|
|
351
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async resumeSession(jwt, abortSignal) {
|
|
355
|
+
return await this.openSession(async () => ({
|
|
356
|
+
...jwt.jwtPayload,
|
|
357
|
+
accessToken: jwt.accessToken
|
|
358
|
+
}), abortSignal);
|
|
359
|
+
}
|
|
360
|
+
async waitForAuthorization(abortSignal) {
|
|
361
|
+
return await this.openSession(async (transportHandle) => await (0, _bananalink_test_sdk_core.waitForTransportEvent)(transportHandle, {
|
|
362
|
+
onMessage: (payload, resolve, reject) => {
|
|
363
|
+
if ((0, _bananalink_test_sdk_core.isAuthorizedMessage)(payload)) resolve(payload);
|
|
364
|
+
if ((0, _bananalink_test_sdk_core.isRejectedMessage)(payload)) reject(new ConnectionRejectedError());
|
|
365
|
+
},
|
|
366
|
+
timeoutMs: BananalinkConnection.AUTH_TIMEOUT_MILLIS,
|
|
367
|
+
createTimeoutError: () => new ConnectionTimeoutError(),
|
|
368
|
+
createClosedError: () => new BananalinkError("Connection closed unexpectedly"),
|
|
369
|
+
abortSignal,
|
|
370
|
+
createAbortError: () => new PendingSessionAbortedError()
|
|
371
|
+
}), abortSignal);
|
|
372
|
+
}
|
|
373
|
+
async getTransportHandle(agentPairingCode) {
|
|
374
|
+
const url = `${this.wsUrl}${agentPairingCode != null ? `?code=${encodeURIComponent(agentPairingCode)}` : ""}`;
|
|
375
|
+
try {
|
|
376
|
+
return await (0, _bananalink_test_sdk_core.connectWebSocket)({
|
|
377
|
+
url,
|
|
378
|
+
pingIntervalMs: 3e4,
|
|
379
|
+
pongTimeoutMs: 5e3,
|
|
380
|
+
maxReconnectAttempts: 0
|
|
381
|
+
});
|
|
382
|
+
} catch (error) {
|
|
383
|
+
throw new BananalinkError("Unable to establish agent websocket connection", { cause: error });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async bind(ws, accessToken, abortSignal) {
|
|
387
|
+
try {
|
|
388
|
+
await (0, _bananalink_test_sdk_core.bindTransport)(ws, accessToken, {
|
|
389
|
+
timeoutMs: BananalinkConnection.BIND_RESPONSE_TIMEOUT_MILLIS,
|
|
390
|
+
createTimeoutError: () => new ConnectionTimeoutError(),
|
|
391
|
+
createClosedError: () => new BananalinkError("Connection closed unexpectedly"),
|
|
392
|
+
abortSignal,
|
|
393
|
+
createAbortError: () => new PendingSessionAbortedError()
|
|
394
|
+
});
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (error instanceof _bananalink_test_sdk_core.BindTransportError) throw new InvalidJwtError();
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/core/BananalinkAgent.ts
|
|
404
|
+
var BananalinkAgent = class BananalinkAgent {
|
|
405
|
+
apiUrl;
|
|
406
|
+
wsUrl;
|
|
407
|
+
jwks;
|
|
408
|
+
agent;
|
|
409
|
+
static DEFAULT_BANANALINK_API_URL = "https://api.dev.banana.link";
|
|
410
|
+
static DEFAULT_BANANALINK_WS_URL = "wss://ws.dev.banana.link";
|
|
411
|
+
constructor(config) {
|
|
412
|
+
this.apiUrl = config.apiUrl ?? BananalinkAgent.DEFAULT_BANANALINK_API_URL;
|
|
413
|
+
this.wsUrl = config.wsUrl ?? BananalinkAgent.DEFAULT_BANANALINK_WS_URL;
|
|
414
|
+
if (!isValidUrl(this.apiUrl, ["http", "https"])) throw new InvalidConfigError(`Invalid apiUrl: "${this.apiUrl}". Must use http or https.`);
|
|
415
|
+
if (!isValidUrl(this.wsUrl, ["ws", "wss"])) throw new InvalidConfigError(`Invalid wsUrl: "${this.wsUrl}". Must use ws or wss.`);
|
|
416
|
+
this.agent = config.agent;
|
|
417
|
+
this.jwks = createBananalinkJwks(this.apiUrl);
|
|
418
|
+
}
|
|
419
|
+
async connect(opts) {
|
|
420
|
+
const accessToken = opts?.accessToken;
|
|
421
|
+
if (!accessToken) {
|
|
422
|
+
const { pairingCode } = await this.pairAgent();
|
|
423
|
+
return new BananalinkConnection({
|
|
424
|
+
jwt: void 0,
|
|
425
|
+
agentPairingCode: pairingCode,
|
|
426
|
+
apiUrl: this.apiUrl,
|
|
427
|
+
wsUrl: this.wsUrl
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
const jwtPayload = await this.getJwtPayload(accessToken);
|
|
431
|
+
if (!jwtPayload) throw new InvalidJwtError();
|
|
432
|
+
return new BananalinkConnection({
|
|
433
|
+
jwt: {
|
|
434
|
+
jwtPayload,
|
|
435
|
+
accessToken
|
|
436
|
+
},
|
|
437
|
+
agentPairingCode: null,
|
|
438
|
+
apiUrl: this.apiUrl,
|
|
439
|
+
wsUrl: this.wsUrl
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
async getJwtPayload(accessToken) {
|
|
443
|
+
return await verifyJwt(accessToken, this.jwks);
|
|
444
|
+
}
|
|
445
|
+
async pairAgent() {
|
|
446
|
+
return await createAgentPairingRequest(this.apiUrl, this.agent);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/errors/InvalidUrlError.ts
|
|
452
|
+
var InvalidUrlError = class extends BananalinkError {
|
|
453
|
+
constructor(url) {
|
|
454
|
+
super(`${url} is not a valid url`);
|
|
455
|
+
this.url = url;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/utils/displayBananalinkQR.ts
|
|
461
|
+
async function displayBananalinkQR(url) {
|
|
462
|
+
if (!isValidUrl(url, ["bananalink"])) throw new InvalidUrlError(url);
|
|
463
|
+
return qrcode.default.toDataURL(url);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
//#endregion
|
|
467
|
+
exports.AgentApiError = AgentApiError;
|
|
468
|
+
exports.BananalinkAgent = BananalinkAgent;
|
|
469
|
+
exports.BananalinkConnection = BananalinkConnection;
|
|
470
|
+
exports.BananalinkError = BananalinkError;
|
|
471
|
+
exports.BananalinkSession = BananalinkSession;
|
|
472
|
+
exports.ConnectionRejectedError = ConnectionRejectedError;
|
|
473
|
+
exports.ConnectionTimeoutError = ConnectionTimeoutError;
|
|
474
|
+
exports.InvalidConfigError = InvalidConfigError;
|
|
475
|
+
exports.InvalidConnectionStateError = InvalidConnectionStateError;
|
|
476
|
+
exports.InvalidJwtError = InvalidJwtError;
|
|
477
|
+
exports.PendingSessionAbortedError = PendingSessionAbortedError;
|
|
478
|
+
exports.RequestRejectedError = RequestRejectedError;
|
|
479
|
+
exports.RequestTimeoutError = RequestTimeoutError;
|
|
480
|
+
exports.SessionClosedError = SessionClosedError;
|
|
481
|
+
exports.displayBananalinkQR = displayBananalinkQR;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Agent, Agent as Agent$1, EthSendTransactionParams, EthSendTransactionParams as EthSendTransactionParams$1, EthSendTransactionResult, TransportHandle } from "@bananalink-test/sdk-core";
|
|
2
|
+
|
|
3
|
+
//#region src/types/ConnectOpts.d.ts
|
|
4
|
+
type ConnectOpts = {
|
|
5
|
+
accessToken?: string;
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/types/JwtPayload.d.ts
|
|
9
|
+
type JwtPayload = {
|
|
10
|
+
agentId: string;
|
|
11
|
+
jwt: string;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/types/RequestMap.d.ts
|
|
15
|
+
type RequestMap = {
|
|
16
|
+
eth_sendTransaction: {
|
|
17
|
+
params: [EthSendTransactionParams$1];
|
|
18
|
+
result: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/types/Request.d.ts
|
|
23
|
+
type Request = keyof RequestMap;
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/types/RequestParams.d.ts
|
|
26
|
+
type RequestParams<T extends keyof RequestMap> = RequestMap[T]['params'];
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/types/RequestResult.d.ts
|
|
29
|
+
type RequestResult<T extends keyof RequestMap> = RequestMap[T]['result'];
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/types/SessionClaims.d.ts
|
|
32
|
+
type SessionClaims = {
|
|
33
|
+
accessToken: string;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/core/BananalinkSession.d.ts
|
|
37
|
+
declare class BananalinkSession {
|
|
38
|
+
readonly sessionClaims: SessionClaims;
|
|
39
|
+
private readonly ws;
|
|
40
|
+
private readonly apiUrl;
|
|
41
|
+
private static readonly REQUEST_TIMEOUT_MS;
|
|
42
|
+
private static readonly CREATE_DAPP_MESSAGE_MAX_ATTEMPTS;
|
|
43
|
+
private static readonly CREATE_DAPP_MESSAGE_BASE_DELAY_MS;
|
|
44
|
+
private readonly pendingRequests;
|
|
45
|
+
private readonly stopListening;
|
|
46
|
+
private readonly stopListeningOnClose;
|
|
47
|
+
private closed;
|
|
48
|
+
private static readonly RESULT_HANDLERS;
|
|
49
|
+
constructor(sessionClaims: SessionClaims, ws: TransportHandle, apiUrl: string);
|
|
50
|
+
request<T extends Request>({
|
|
51
|
+
method,
|
|
52
|
+
params,
|
|
53
|
+
timeoutMs
|
|
54
|
+
}: {
|
|
55
|
+
method: T;
|
|
56
|
+
params?: RequestParams<T>;
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
}): Promise<RequestResult<T>>;
|
|
59
|
+
close(): void;
|
|
60
|
+
private handleMessageResponse;
|
|
61
|
+
private cleanup;
|
|
62
|
+
private rejectAllPendingRequests;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/core/BananalinkConnection.d.ts
|
|
66
|
+
type BananalinkAgentJwt = {
|
|
67
|
+
jwtPayload: JwtPayload;
|
|
68
|
+
accessToken: string;
|
|
69
|
+
} | undefined;
|
|
70
|
+
declare class BananalinkConnection {
|
|
71
|
+
private static readonly AUTH_TIMEOUT_MILLIS;
|
|
72
|
+
private static readonly BIND_RESPONSE_TIMEOUT_MILLIS;
|
|
73
|
+
private readonly apiUrl;
|
|
74
|
+
private readonly wsUrl;
|
|
75
|
+
private readonly jwt;
|
|
76
|
+
private readonly agentPairingCode;
|
|
77
|
+
private pendingSessionPromise;
|
|
78
|
+
private pendingSessionAbortController;
|
|
79
|
+
constructor(opts: {
|
|
80
|
+
apiUrl: string;
|
|
81
|
+
wsUrl: string;
|
|
82
|
+
jwt: BananalinkAgentJwt;
|
|
83
|
+
agentPairingCode: string | null;
|
|
84
|
+
});
|
|
85
|
+
get connectionUrl(): string;
|
|
86
|
+
abortPendingSession(): void;
|
|
87
|
+
getSession(): Promise<BananalinkSession>;
|
|
88
|
+
private openSession;
|
|
89
|
+
private resumeSession;
|
|
90
|
+
private waitForAuthorization;
|
|
91
|
+
private getTransportHandle;
|
|
92
|
+
private bind;
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/core/BananalinkAgent.d.ts
|
|
96
|
+
declare class BananalinkAgent {
|
|
97
|
+
private readonly apiUrl;
|
|
98
|
+
private readonly wsUrl;
|
|
99
|
+
private readonly jwks;
|
|
100
|
+
private readonly agent;
|
|
101
|
+
private static readonly DEFAULT_BANANALINK_API_URL;
|
|
102
|
+
private static readonly DEFAULT_BANANALINK_WS_URL;
|
|
103
|
+
constructor(config: {
|
|
104
|
+
apiUrl?: string;
|
|
105
|
+
wsUrl?: string;
|
|
106
|
+
agent: Agent$1;
|
|
107
|
+
});
|
|
108
|
+
connect(opts?: ConnectOpts): Promise<BananalinkConnection>;
|
|
109
|
+
private getJwtPayload;
|
|
110
|
+
private pairAgent;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/errors/BananalinkError.d.ts
|
|
114
|
+
declare class BananalinkError extends Error {
|
|
115
|
+
constructor(message: string, options?: ErrorOptions);
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/errors/AgentApiError.d.ts
|
|
119
|
+
declare class AgentApiError extends BananalinkError {
|
|
120
|
+
readonly statusCode: number;
|
|
121
|
+
constructor(message: string, statusCode: number);
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/errors/ConnectionRejectedError.d.ts
|
|
125
|
+
declare class ConnectionRejectedError extends BananalinkError {
|
|
126
|
+
constructor();
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/errors/ConnectionTimeoutError.d.ts
|
|
130
|
+
declare class ConnectionTimeoutError extends BananalinkError {
|
|
131
|
+
constructor();
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/errors/InvalidConfigError.d.ts
|
|
135
|
+
declare class InvalidConfigError extends BananalinkError {
|
|
136
|
+
constructor(message: string);
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/errors/InvalidConnectionStateError.d.ts
|
|
140
|
+
declare class InvalidConnectionStateError extends BananalinkError {
|
|
141
|
+
constructor(message: string);
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/errors/InvalidJwtError.d.ts
|
|
145
|
+
declare class InvalidJwtError extends BananalinkError {
|
|
146
|
+
constructor();
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/errors/PendingSessionAbortedError.d.ts
|
|
150
|
+
declare class PendingSessionAbortedError extends BananalinkError {
|
|
151
|
+
constructor();
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/errors/RequestRejectedError.d.ts
|
|
155
|
+
declare class RequestRejectedError extends BananalinkError {
|
|
156
|
+
readonly messageId: string;
|
|
157
|
+
constructor(messageId: string);
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/errors/RequestTimeoutError.d.ts
|
|
161
|
+
declare class RequestTimeoutError extends BananalinkError {
|
|
162
|
+
readonly method: Request;
|
|
163
|
+
constructor(method: Request);
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/errors/SessionClosedError.d.ts
|
|
167
|
+
declare class SessionClosedError extends BananalinkError {
|
|
168
|
+
constructor();
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/utils/displayBananalinkQR.d.ts
|
|
172
|
+
declare function displayBananalinkQR(url: string): Promise<string>;
|
|
173
|
+
//#endregion
|
|
174
|
+
export { type Agent, AgentApiError, BananalinkAgent, BananalinkConnection, BananalinkError, BananalinkSession, type ConnectOpts, ConnectionRejectedError, ConnectionTimeoutError, type EthSendTransactionParams, type EthSendTransactionResult, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, PendingSessionAbortedError, type Request, type RequestMap, type RequestParams, RequestRejectedError, type RequestResult, RequestTimeoutError, SessionClosedError, displayBananalinkQR };
|
|
175
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types/ConnectOpts.ts","../src/types/JwtPayload.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/BananalinkAgent.ts","../src/errors/BananalinkError.ts","../src/errors/AgentApiError.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.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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"}
|