@bananalink-test/client 0.1.0 → 0.3.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 +78 -70
- package/dist/index.d.cts +11 -25
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +11 -25
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +78 -71
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
|
|
28
28
|
//#endregion
|
|
29
29
|
let jose = require("jose");
|
|
30
|
+
let _bananalink_test_sdk_core = require("@bananalink-test/sdk-core");
|
|
30
31
|
let qrcode = require("qrcode");
|
|
31
32
|
qrcode = __toESM(qrcode);
|
|
32
33
|
|
|
@@ -78,6 +79,14 @@ var InvalidConfigError = class extends BananalinkError {
|
|
|
78
79
|
}
|
|
79
80
|
};
|
|
80
81
|
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/errors/InvalidJwtError.ts
|
|
84
|
+
var InvalidJwtError = class extends BananalinkError {
|
|
85
|
+
constructor() {
|
|
86
|
+
super("Invalid Bananalink dapp jwt");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
81
90
|
//#endregion
|
|
82
91
|
//#region src/jwt/createBananalinkJwks.ts
|
|
83
92
|
function createBananalinkJwks(apiUrl) {
|
|
@@ -150,50 +159,20 @@ var InvalidConnectionStateError = class extends BananalinkError {
|
|
|
150
159
|
|
|
151
160
|
//#endregion
|
|
152
161
|
//#region src/core/BananalinkSession.ts
|
|
153
|
-
var BananalinkSession = class
|
|
154
|
-
static PING_INTERVAL_MILLIS = 30 * 1e3;
|
|
155
|
-
static PONG_TIMEOUT_MILLIS = 5 * 1e3;
|
|
156
|
-
pingTimer = null;
|
|
157
|
-
pongTimer = null;
|
|
162
|
+
var BananalinkSession = class {
|
|
158
163
|
constructor(sessionClaims, ws) {
|
|
159
164
|
this.sessionClaims = sessionClaims;
|
|
160
165
|
this.ws = ws;
|
|
161
|
-
this.ws.addEventListener("message", (event) => {
|
|
162
|
-
const { type } = JSON.parse(event.data);
|
|
163
|
-
if (type === "pong" && this.pongTimer) {
|
|
164
|
-
clearTimeout(this.pongTimer);
|
|
165
|
-
this.pongTimer = null;
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
this.startPing();
|
|
169
166
|
}
|
|
170
167
|
close() {
|
|
171
|
-
this.stopTimers();
|
|
172
168
|
this.ws.close();
|
|
173
169
|
}
|
|
174
|
-
startPing() {
|
|
175
|
-
this.pingTimer = setInterval(() => this.ping(), BananalinkSession.PING_INTERVAL_MILLIS);
|
|
176
|
-
}
|
|
177
|
-
ping() {
|
|
178
|
-
if (this.ws.readyState !== WebSocket.OPEN) {
|
|
179
|
-
this.close();
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
183
|
-
this.pongTimer = setTimeout(() => this.close(), BananalinkSession.PONG_TIMEOUT_MILLIS);
|
|
184
|
-
}
|
|
185
|
-
stopTimers() {
|
|
186
|
-
if (this.pingTimer !== null) clearInterval(this.pingTimer);
|
|
187
|
-
if (this.pongTimer !== null) clearTimeout(this.pongTimer);
|
|
188
|
-
this.pingTimer = null;
|
|
189
|
-
this.pongTimer = null;
|
|
190
|
-
}
|
|
191
170
|
};
|
|
192
171
|
|
|
193
172
|
//#endregion
|
|
194
173
|
//#region src/core/BananalinkConnection.ts
|
|
195
174
|
var BananalinkConnection = class BananalinkConnection {
|
|
196
|
-
static AUTH_TIMEOUT_MILLIS =
|
|
175
|
+
static AUTH_TIMEOUT_MILLIS = 600 * 1e3;
|
|
197
176
|
wsUrl;
|
|
198
177
|
jwt;
|
|
199
178
|
dappId;
|
|
@@ -205,59 +184,86 @@ var BananalinkConnection = class BananalinkConnection {
|
|
|
205
184
|
this.dappId = opts.dappId;
|
|
206
185
|
this.dappInstanceId = opts.dappInstanceId;
|
|
207
186
|
this.nonce = opts.nonce;
|
|
187
|
+
if (!this.dappInstanceId && !this.jwt) throw new InvalidConnectionStateError("Cannot get session without authorizing a dapp instance or providing a valid JWT");
|
|
208
188
|
}
|
|
209
189
|
async getSession() {
|
|
210
|
-
if (this.jwt)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
190
|
+
if (this.jwt) {
|
|
191
|
+
const { jwtPayload, rawJwt } = this.jwt;
|
|
192
|
+
const ws = await this.getTransportHandle();
|
|
193
|
+
BananalinkConnection.bind(ws, rawJwt);
|
|
194
|
+
return new BananalinkSession({
|
|
195
|
+
address: jwtPayload.address,
|
|
196
|
+
message: jwtPayload.message,
|
|
197
|
+
signature: jwtPayload.signature,
|
|
198
|
+
accessToken: rawJwt
|
|
199
|
+
}, ws);
|
|
200
|
+
}
|
|
201
|
+
const ws = await this.getTransportHandle(this.dappInstanceId);
|
|
202
|
+
return await new Promise((resolve, reject) => {
|
|
203
|
+
let settled = false;
|
|
215
204
|
const authTimeout = setTimeout(() => {
|
|
216
|
-
|
|
217
|
-
reject(new ConnectionTimeoutError());
|
|
205
|
+
settleReject(new ConnectionTimeoutError());
|
|
218
206
|
}, BananalinkConnection.AUTH_TIMEOUT_MILLIS);
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
message: msg.message,
|
|
228
|
-
signature: msg.signature,
|
|
229
|
-
accessToken: msg.accessToken
|
|
207
|
+
const stopListeningToMessages = ws.onMessage((payload) => {
|
|
208
|
+
if ((0, _bananalink_test_sdk_core.isAuthorizedMessage)(payload)) {
|
|
209
|
+
BananalinkConnection.bind(ws, payload.accessToken);
|
|
210
|
+
settleResolve(new BananalinkSession({
|
|
211
|
+
address: payload.address,
|
|
212
|
+
message: payload.message,
|
|
213
|
+
signature: payload.signature,
|
|
214
|
+
accessToken: payload.accessToken
|
|
230
215
|
}, ws));
|
|
231
|
-
|
|
232
|
-
ws.close();
|
|
233
|
-
reject(new ConnectionRejectedError());
|
|
216
|
+
return;
|
|
234
217
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
ws.
|
|
238
|
-
|
|
218
|
+
if ((0, _bananalink_test_sdk_core.isRejectedMessage)(payload)) settleReject(new ConnectionRejectedError());
|
|
219
|
+
});
|
|
220
|
+
const stopListeningToClose = ws.onClose(() => {
|
|
221
|
+
settleReject(new BananalinkError("Connection closed before authorization completed"));
|
|
222
|
+
});
|
|
223
|
+
function cleanup() {
|
|
224
|
+
clearTimeout(authTimeout);
|
|
225
|
+
stopListeningToMessages();
|
|
226
|
+
stopListeningToClose();
|
|
227
|
+
}
|
|
228
|
+
function settleResolve(session) {
|
|
229
|
+
if (settled) return;
|
|
230
|
+
settled = true;
|
|
231
|
+
cleanup();
|
|
232
|
+
resolve(session);
|
|
233
|
+
}
|
|
234
|
+
function settleReject(error) {
|
|
235
|
+
if (settled) return;
|
|
236
|
+
settled = true;
|
|
237
|
+
cleanup();
|
|
238
|
+
ws.close();
|
|
239
|
+
reject(error);
|
|
240
|
+
}
|
|
239
241
|
});
|
|
240
242
|
}
|
|
243
|
+
async getTransportHandle(dappInstanceId) {
|
|
244
|
+
const url = `${this.wsUrl}${dappInstanceId != null ? `?dappInstanceId=${encodeURIComponent(dappInstanceId)}` : ""}`;
|
|
245
|
+
try {
|
|
246
|
+
return await (0, _bananalink_test_sdk_core.connectWebSocket)({
|
|
247
|
+
url,
|
|
248
|
+
pingIntervalMs: 3e4,
|
|
249
|
+
pongTimeoutMs: 5e3
|
|
250
|
+
});
|
|
251
|
+
} catch (error) {
|
|
252
|
+
throw new BananalinkError("Unable to establish dapp websocket connection", { cause: error });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
241
255
|
get connectionUrl() {
|
|
242
256
|
if (!this.dappInstanceId || !this.nonce) throw new InvalidConnectionStateError("Cannot get connection URL without a dapp instance");
|
|
243
257
|
const dappInstanceId = this.dappInstanceId;
|
|
244
258
|
const nonce = this.nonce;
|
|
245
259
|
return `bananalink://?dappId=${encodeURIComponent(this.dappId)}&dappInstanceId=${encodeURIComponent(dappInstanceId)}&nonce=${encodeURIComponent(nonce)}`;
|
|
246
260
|
}
|
|
247
|
-
get authorized() {
|
|
248
|
-
return this.jwt !== void 0;
|
|
249
|
-
}
|
|
250
261
|
static bind(ws, jwt) {
|
|
251
|
-
|
|
262
|
+
const bindMessage = {
|
|
252
263
|
type: "bind",
|
|
253
264
|
jwt
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
static isAuthorizedMessage(payload) {
|
|
257
|
-
return typeof payload === "object" && payload !== null && "type" in payload && payload.type === "authorized" && "accessToken" in payload && typeof payload.accessToken === "string" && "refreshToken" in payload && typeof payload.refreshToken === "string" && "message" in payload && typeof payload.message === "string" && "signature" in payload && typeof payload.signature === "string" && "address" in payload && typeof payload.address === "string";
|
|
258
|
-
}
|
|
259
|
-
static isRejectedMessage(payload) {
|
|
260
|
-
return typeof payload === "object" && payload !== null && "type" in payload && payload.type === "rejected";
|
|
265
|
+
};
|
|
266
|
+
ws.send(bindMessage);
|
|
261
267
|
}
|
|
262
268
|
};
|
|
263
269
|
|
|
@@ -280,8 +286,7 @@ var BananalinkClient = class BananalinkClient {
|
|
|
280
286
|
}
|
|
281
287
|
async connect(opts) {
|
|
282
288
|
const rawJwt = opts?.jwt;
|
|
283
|
-
|
|
284
|
-
if (!jwtPayload || !rawJwt) {
|
|
289
|
+
if (!rawJwt) {
|
|
285
290
|
const { dappInstanceId, nonce } = await this.connectDappInstance();
|
|
286
291
|
return new BananalinkConnection({
|
|
287
292
|
jwt: void 0,
|
|
@@ -291,6 +296,8 @@ var BananalinkClient = class BananalinkClient {
|
|
|
291
296
|
wsUrl: this.wsUrl
|
|
292
297
|
});
|
|
293
298
|
}
|
|
299
|
+
const jwtPayload = await this.getJwtPayload(rawJwt);
|
|
300
|
+
if (!jwtPayload) throw new InvalidJwtError();
|
|
294
301
|
return new BananalinkConnection({
|
|
295
302
|
jwt: {
|
|
296
303
|
jwtPayload,
|
|
@@ -336,4 +343,5 @@ exports.ConnectionTimeoutError = ConnectionTimeoutError;
|
|
|
336
343
|
exports.DappApiError = DappApiError;
|
|
337
344
|
exports.InvalidConfigError = InvalidConfigError;
|
|
338
345
|
exports.InvalidConnectionStateError = InvalidConnectionStateError;
|
|
346
|
+
exports.InvalidJwtError = InvalidJwtError;
|
|
339
347
|
exports.displayBananalinkQR = displayBananalinkQR;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
dappId: string;
|
|
4
|
-
dappName: string;
|
|
5
|
-
dappDescription?: string;
|
|
6
|
-
dappStatement: string;
|
|
7
|
-
domain: string;
|
|
8
|
-
uri: string;
|
|
9
|
-
icons?: string[];
|
|
10
|
-
chainId?: number;
|
|
11
|
-
};
|
|
12
|
-
//#endregion
|
|
1
|
+
import { Dapp, Dapp as Dapp$1, TransportHandle } from "@bananalink-test/sdk-core";
|
|
2
|
+
|
|
13
3
|
//#region src/types/JwtPayload.d.ts
|
|
14
4
|
type JwtPayload = {
|
|
15
5
|
address: string;
|
|
@@ -31,15 +21,8 @@ type SessionClaims = {
|
|
|
31
21
|
declare class BananalinkSession {
|
|
32
22
|
readonly sessionClaims: SessionClaims;
|
|
33
23
|
private readonly ws;
|
|
34
|
-
|
|
35
|
-
private static readonly PONG_TIMEOUT_MILLIS;
|
|
36
|
-
private pingTimer;
|
|
37
|
-
private pongTimer;
|
|
38
|
-
constructor(sessionClaims: SessionClaims, ws: WebSocket);
|
|
24
|
+
constructor(sessionClaims: SessionClaims, ws: TransportHandle);
|
|
39
25
|
close(): void;
|
|
40
|
-
private startPing;
|
|
41
|
-
private ping;
|
|
42
|
-
private stopTimers;
|
|
43
26
|
}
|
|
44
27
|
//#endregion
|
|
45
28
|
//#region src/core/BananalinkConnection.d.ts
|
|
@@ -62,11 +45,9 @@ declare class BananalinkConnection {
|
|
|
62
45
|
nonce: string | null;
|
|
63
46
|
});
|
|
64
47
|
getSession(): Promise<BananalinkSession>;
|
|
48
|
+
private getTransportHandle;
|
|
65
49
|
get connectionUrl(): string;
|
|
66
|
-
get authorized(): boolean;
|
|
67
50
|
private static bind;
|
|
68
|
-
private static isAuthorizedMessage;
|
|
69
|
-
private static isRejectedMessage;
|
|
70
51
|
}
|
|
71
52
|
//#endregion
|
|
72
53
|
//#region src/core/BananalinkClient.d.ts
|
|
@@ -80,7 +61,7 @@ declare class BananalinkClient {
|
|
|
80
61
|
constructor(config: {
|
|
81
62
|
apiUrl?: string;
|
|
82
63
|
wsUrl?: string;
|
|
83
|
-
dapp: Dapp;
|
|
64
|
+
dapp: Dapp$1;
|
|
84
65
|
});
|
|
85
66
|
connect(opts?: {
|
|
86
67
|
jwt: string;
|
|
@@ -120,8 +101,13 @@ declare class InvalidConnectionStateError extends BananalinkError {
|
|
|
120
101
|
constructor(message: string);
|
|
121
102
|
}
|
|
122
103
|
//#endregion
|
|
104
|
+
//#region src/errors/InvalidJwtError.d.ts
|
|
105
|
+
declare class InvalidJwtError extends BananalinkError {
|
|
106
|
+
constructor();
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
123
109
|
//#region src/utils/displayBananalinkQR.d.ts
|
|
124
110
|
declare function displayBananalinkQR(url: string): Promise<string>;
|
|
125
111
|
//#endregion
|
|
126
|
-
export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, DappApiError, InvalidConfigError, InvalidConnectionStateError, displayBananalinkQR };
|
|
112
|
+
export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, type Dapp, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, displayBananalinkQR };
|
|
127
113
|
//# 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/
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types/JwtPayload.ts","../src/types/SessionClaims.ts","../src/core/BananalinkSession.ts","../src/core/BananalinkConnection.ts","../src/core/BananalinkClient.ts","../src/errors/BananalinkError.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.ts","../src/errors/DappApiError.ts","../src/errors/InvalidConfigError.ts","../src/errors/InvalidConnectionStateError.ts","../src/errors/InvalidJwtError.ts","../src/utils/displayBananalinkQR.ts"],"mappings":";;;KAAY,UAAA;EAAe,OAAA;EAAiB,MAAA;EAAgB,OAAA;EAAiB,SAAA;EAAmB,GAAA;AAAA;;;KCApF,aAAA;EAAkB,OAAA;EAAiB,OAAA;EAAiB,SAAA;EAAmB,WAAA;AAAA;;;cCGtE,iBAAA;EAAA,SAEO,aAAA,EAAe,aAAA;EAAA,iBACd,EAAA;cADD,aAAA,EAAe,aAAA,EACd,EAAA,EAAI,eAAA;EAGhB,KAAA,CAAA;AAAA;;;KCKJ,iBAAA;EAAsB,UAAA,EAAY,UAAA;EAAY,MAAA;AAAA;AAAA,cAEtC,oBAAA;EAAA,wBACa,mBAAA;EAAA,iBACP,KAAA;EAAA,iBACA,GAAA;EAAA,iBACA,MAAA;EAAA,iBACA,cAAA;EAAA,iBACA,KAAA;cAEL,IAAA;IACV,KAAA;IACA,GAAA,EAAK,iBAAA;IACL,MAAA;IACA,cAAA;IACA,KAAA;EAAA;EAcW,UAAA,CAAA,GAAc,OAAA,CAAQ,iBAAA;EAAA,QAgFrB,kBAAA;EAAA,IAaH,aAAA,CAAA;EAAA,eASI,IAAA;AAAA;;;cCvIJ,gBAAA;EAAA,iBACM,MAAA;EAAA,iBACA,KAAA;EAAA,iBACA,IAAA;EAAA,iBACA,IAAA;EAAA,wBACO,0BAAA;EAAA,wBACA,yBAAA;cAEZ,MAAA;IACV,MAAA;IACA,KAAA;IACA,IAAA,EAAM,MAAA;EAAA;EAcK,OAAA,CAAQ,IAAA;IAAS,GAAA;EAAA,IAAgB,OAAA,CAAQ,oBAAA;EAAA,QA2BxC,aAAA;EAAA,QAIA,mBAAA;AAAA;;;cClEH,eAAA,SAAwB,KAAA;cACvB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;;;cCC5B,uBAAA,SAAgC,eAAA;EAAA,WAAA,CAAA;AAAA;;;cCAhC,sBAAA,SAA+B,eAAA;EAAA,WAAA,CAAA;AAAA;;;cCA/B,YAAA,SAAqB,eAAA;EAAA,SAGd,UAAA;cADhB,OAAA,UACgB,UAAA;AAAA;;;cCHP,kBAAA,SAA2B,eAAA;cAC1B,OAAA;AAAA;;;cCDD,2BAAA,SAAoC,eAAA;cACnC,OAAA;AAAA;;;cCDD,eAAA,SAAwB,eAAA;EAAA,WAAA,CAAA;AAAA;;;iBCEf,mBAAA,CAAoB,GAAA,WAAc,OAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
dappId: string;
|
|
4
|
-
dappName: string;
|
|
5
|
-
dappDescription?: string;
|
|
6
|
-
dappStatement: string;
|
|
7
|
-
domain: string;
|
|
8
|
-
uri: string;
|
|
9
|
-
icons?: string[];
|
|
10
|
-
chainId?: number;
|
|
11
|
-
};
|
|
12
|
-
//#endregion
|
|
1
|
+
import { Dapp, Dapp as Dapp$1, TransportHandle } from "@bananalink-test/sdk-core";
|
|
2
|
+
|
|
13
3
|
//#region src/types/JwtPayload.d.ts
|
|
14
4
|
type JwtPayload = {
|
|
15
5
|
address: string;
|
|
@@ -31,15 +21,8 @@ type SessionClaims = {
|
|
|
31
21
|
declare class BananalinkSession {
|
|
32
22
|
readonly sessionClaims: SessionClaims;
|
|
33
23
|
private readonly ws;
|
|
34
|
-
|
|
35
|
-
private static readonly PONG_TIMEOUT_MILLIS;
|
|
36
|
-
private pingTimer;
|
|
37
|
-
private pongTimer;
|
|
38
|
-
constructor(sessionClaims: SessionClaims, ws: WebSocket);
|
|
24
|
+
constructor(sessionClaims: SessionClaims, ws: TransportHandle);
|
|
39
25
|
close(): void;
|
|
40
|
-
private startPing;
|
|
41
|
-
private ping;
|
|
42
|
-
private stopTimers;
|
|
43
26
|
}
|
|
44
27
|
//#endregion
|
|
45
28
|
//#region src/core/BananalinkConnection.d.ts
|
|
@@ -62,11 +45,9 @@ declare class BananalinkConnection {
|
|
|
62
45
|
nonce: string | null;
|
|
63
46
|
});
|
|
64
47
|
getSession(): Promise<BananalinkSession>;
|
|
48
|
+
private getTransportHandle;
|
|
65
49
|
get connectionUrl(): string;
|
|
66
|
-
get authorized(): boolean;
|
|
67
50
|
private static bind;
|
|
68
|
-
private static isAuthorizedMessage;
|
|
69
|
-
private static isRejectedMessage;
|
|
70
51
|
}
|
|
71
52
|
//#endregion
|
|
72
53
|
//#region src/core/BananalinkClient.d.ts
|
|
@@ -80,7 +61,7 @@ declare class BananalinkClient {
|
|
|
80
61
|
constructor(config: {
|
|
81
62
|
apiUrl?: string;
|
|
82
63
|
wsUrl?: string;
|
|
83
|
-
dapp: Dapp;
|
|
64
|
+
dapp: Dapp$1;
|
|
84
65
|
});
|
|
85
66
|
connect(opts?: {
|
|
86
67
|
jwt: string;
|
|
@@ -120,8 +101,13 @@ declare class InvalidConnectionStateError extends BananalinkError {
|
|
|
120
101
|
constructor(message: string);
|
|
121
102
|
}
|
|
122
103
|
//#endregion
|
|
104
|
+
//#region src/errors/InvalidJwtError.d.ts
|
|
105
|
+
declare class InvalidJwtError extends BananalinkError {
|
|
106
|
+
constructor();
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
123
109
|
//#region src/utils/displayBananalinkQR.d.ts
|
|
124
110
|
declare function displayBananalinkQR(url: string): Promise<string>;
|
|
125
111
|
//#endregion
|
|
126
|
-
export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, DappApiError, InvalidConfigError, InvalidConnectionStateError, displayBananalinkQR };
|
|
112
|
+
export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, type Dapp, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, displayBananalinkQR };
|
|
127
113
|
//# 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/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/JwtPayload.ts","../src/types/SessionClaims.ts","../src/core/BananalinkSession.ts","../src/core/BananalinkConnection.ts","../src/core/BananalinkClient.ts","../src/errors/BananalinkError.ts","../src/errors/ConnectionRejectedError.ts","../src/errors/ConnectionTimeoutError.ts","../src/errors/DappApiError.ts","../src/errors/InvalidConfigError.ts","../src/errors/InvalidConnectionStateError.ts","../src/errors/InvalidJwtError.ts","../src/utils/displayBananalinkQR.ts"],"mappings":";;;KAAY,UAAA;EAAe,OAAA;EAAiB,MAAA;EAAgB,OAAA;EAAiB,SAAA;EAAmB,GAAA;AAAA;;;KCApF,aAAA;EAAkB,OAAA;EAAiB,OAAA;EAAiB,SAAA;EAAmB,WAAA;AAAA;;;cCGtE,iBAAA;EAAA,SAEO,aAAA,EAAe,aAAA;EAAA,iBACd,EAAA;cADD,aAAA,EAAe,aAAA,EACd,EAAA,EAAI,eAAA;EAGhB,KAAA,CAAA;AAAA;;;KCKJ,iBAAA;EAAsB,UAAA,EAAY,UAAA;EAAY,MAAA;AAAA;AAAA,cAEtC,oBAAA;EAAA,wBACa,mBAAA;EAAA,iBACP,KAAA;EAAA,iBACA,GAAA;EAAA,iBACA,MAAA;EAAA,iBACA,cAAA;EAAA,iBACA,KAAA;cAEL,IAAA;IACV,KAAA;IACA,GAAA,EAAK,iBAAA;IACL,MAAA;IACA,cAAA;IACA,KAAA;EAAA;EAcW,UAAA,CAAA,GAAc,OAAA,CAAQ,iBAAA;EAAA,QAgFrB,kBAAA;EAAA,IAaH,aAAA,CAAA;EAAA,eASI,IAAA;AAAA;;;cCvIJ,gBAAA;EAAA,iBACM,MAAA;EAAA,iBACA,KAAA;EAAA,iBACA,IAAA;EAAA,iBACA,IAAA;EAAA,wBACO,0BAAA;EAAA,wBACA,yBAAA;cAEZ,MAAA;IACV,MAAA;IACA,KAAA;IACA,IAAA,EAAM,MAAA;EAAA;EAcK,OAAA,CAAQ,IAAA;IAAS,GAAA;EAAA,IAAgB,OAAA,CAAQ,oBAAA;EAAA,QA2BxC,aAAA;EAAA,QAIA,mBAAA;AAAA;;;cClEH,eAAA,SAAwB,KAAA;cACvB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;;;cCC5B,uBAAA,SAAgC,eAAA;EAAA,WAAA,CAAA;AAAA;;;cCAhC,sBAAA,SAA+B,eAAA;EAAA,WAAA,CAAA;AAAA;;;cCA/B,YAAA,SAAqB,eAAA;EAAA,SAGd,UAAA;cADhB,OAAA,UACgB,UAAA;AAAA;;;cCHP,kBAAA,SAA2B,eAAA;cAC1B,OAAA;AAAA;;;cCDD,2BAAA,SAAoC,eAAA;cACnC,OAAA;AAAA;;;cCDD,eAAA,SAAwB,eAAA;EAAA,WAAA,CAAA;AAAA;;;iBCEf,mBAAA,CAAoB,GAAA,WAAc,OAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
2
|
+
import { connectWebSocket, isAuthorizedMessage, isRejectedMessage } from "@bananalink-test/sdk-core";
|
|
2
3
|
import QRCode from "qrcode";
|
|
3
4
|
|
|
4
5
|
//#region src/errors/BananalinkError.ts
|
|
@@ -49,6 +50,14 @@ var InvalidConfigError = class extends BananalinkError {
|
|
|
49
50
|
}
|
|
50
51
|
};
|
|
51
52
|
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/errors/InvalidJwtError.ts
|
|
55
|
+
var InvalidJwtError = class extends BananalinkError {
|
|
56
|
+
constructor() {
|
|
57
|
+
super("Invalid Bananalink dapp jwt");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
52
61
|
//#endregion
|
|
53
62
|
//#region src/jwt/createBananalinkJwks.ts
|
|
54
63
|
function createBananalinkJwks(apiUrl) {
|
|
@@ -121,50 +130,20 @@ var InvalidConnectionStateError = class extends BananalinkError {
|
|
|
121
130
|
|
|
122
131
|
//#endregion
|
|
123
132
|
//#region src/core/BananalinkSession.ts
|
|
124
|
-
var BananalinkSession = class
|
|
125
|
-
static PING_INTERVAL_MILLIS = 30 * 1e3;
|
|
126
|
-
static PONG_TIMEOUT_MILLIS = 5 * 1e3;
|
|
127
|
-
pingTimer = null;
|
|
128
|
-
pongTimer = null;
|
|
133
|
+
var BananalinkSession = class {
|
|
129
134
|
constructor(sessionClaims, ws) {
|
|
130
135
|
this.sessionClaims = sessionClaims;
|
|
131
136
|
this.ws = ws;
|
|
132
|
-
this.ws.addEventListener("message", (event) => {
|
|
133
|
-
const { type } = JSON.parse(event.data);
|
|
134
|
-
if (type === "pong" && this.pongTimer) {
|
|
135
|
-
clearTimeout(this.pongTimer);
|
|
136
|
-
this.pongTimer = null;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
this.startPing();
|
|
140
137
|
}
|
|
141
138
|
close() {
|
|
142
|
-
this.stopTimers();
|
|
143
139
|
this.ws.close();
|
|
144
140
|
}
|
|
145
|
-
startPing() {
|
|
146
|
-
this.pingTimer = setInterval(() => this.ping(), BananalinkSession.PING_INTERVAL_MILLIS);
|
|
147
|
-
}
|
|
148
|
-
ping() {
|
|
149
|
-
if (this.ws.readyState !== WebSocket.OPEN) {
|
|
150
|
-
this.close();
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
154
|
-
this.pongTimer = setTimeout(() => this.close(), BananalinkSession.PONG_TIMEOUT_MILLIS);
|
|
155
|
-
}
|
|
156
|
-
stopTimers() {
|
|
157
|
-
if (this.pingTimer !== null) clearInterval(this.pingTimer);
|
|
158
|
-
if (this.pongTimer !== null) clearTimeout(this.pongTimer);
|
|
159
|
-
this.pingTimer = null;
|
|
160
|
-
this.pongTimer = null;
|
|
161
|
-
}
|
|
162
141
|
};
|
|
163
142
|
|
|
164
143
|
//#endregion
|
|
165
144
|
//#region src/core/BananalinkConnection.ts
|
|
166
145
|
var BananalinkConnection = class BananalinkConnection {
|
|
167
|
-
static AUTH_TIMEOUT_MILLIS =
|
|
146
|
+
static AUTH_TIMEOUT_MILLIS = 600 * 1e3;
|
|
168
147
|
wsUrl;
|
|
169
148
|
jwt;
|
|
170
149
|
dappId;
|
|
@@ -176,59 +155,86 @@ var BananalinkConnection = class BananalinkConnection {
|
|
|
176
155
|
this.dappId = opts.dappId;
|
|
177
156
|
this.dappInstanceId = opts.dappInstanceId;
|
|
178
157
|
this.nonce = opts.nonce;
|
|
158
|
+
if (!this.dappInstanceId && !this.jwt) throw new InvalidConnectionStateError("Cannot get session without authorizing a dapp instance or providing a valid JWT");
|
|
179
159
|
}
|
|
180
160
|
async getSession() {
|
|
181
|
-
if (this.jwt)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
161
|
+
if (this.jwt) {
|
|
162
|
+
const { jwtPayload, rawJwt } = this.jwt;
|
|
163
|
+
const ws = await this.getTransportHandle();
|
|
164
|
+
BananalinkConnection.bind(ws, rawJwt);
|
|
165
|
+
return new BananalinkSession({
|
|
166
|
+
address: jwtPayload.address,
|
|
167
|
+
message: jwtPayload.message,
|
|
168
|
+
signature: jwtPayload.signature,
|
|
169
|
+
accessToken: rawJwt
|
|
170
|
+
}, ws);
|
|
171
|
+
}
|
|
172
|
+
const ws = await this.getTransportHandle(this.dappInstanceId);
|
|
173
|
+
return await new Promise((resolve, reject) => {
|
|
174
|
+
let settled = false;
|
|
186
175
|
const authTimeout = setTimeout(() => {
|
|
187
|
-
|
|
188
|
-
reject(new ConnectionTimeoutError());
|
|
176
|
+
settleReject(new ConnectionTimeoutError());
|
|
189
177
|
}, BananalinkConnection.AUTH_TIMEOUT_MILLIS);
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
message: msg.message,
|
|
199
|
-
signature: msg.signature,
|
|
200
|
-
accessToken: msg.accessToken
|
|
178
|
+
const stopListeningToMessages = ws.onMessage((payload) => {
|
|
179
|
+
if (isAuthorizedMessage(payload)) {
|
|
180
|
+
BananalinkConnection.bind(ws, payload.accessToken);
|
|
181
|
+
settleResolve(new BananalinkSession({
|
|
182
|
+
address: payload.address,
|
|
183
|
+
message: payload.message,
|
|
184
|
+
signature: payload.signature,
|
|
185
|
+
accessToken: payload.accessToken
|
|
201
186
|
}, ws));
|
|
202
|
-
|
|
203
|
-
ws.close();
|
|
204
|
-
reject(new ConnectionRejectedError());
|
|
187
|
+
return;
|
|
205
188
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
ws.
|
|
209
|
-
|
|
189
|
+
if (isRejectedMessage(payload)) settleReject(new ConnectionRejectedError());
|
|
190
|
+
});
|
|
191
|
+
const stopListeningToClose = ws.onClose(() => {
|
|
192
|
+
settleReject(new BananalinkError("Connection closed before authorization completed"));
|
|
193
|
+
});
|
|
194
|
+
function cleanup() {
|
|
195
|
+
clearTimeout(authTimeout);
|
|
196
|
+
stopListeningToMessages();
|
|
197
|
+
stopListeningToClose();
|
|
198
|
+
}
|
|
199
|
+
function settleResolve(session) {
|
|
200
|
+
if (settled) return;
|
|
201
|
+
settled = true;
|
|
202
|
+
cleanup();
|
|
203
|
+
resolve(session);
|
|
204
|
+
}
|
|
205
|
+
function settleReject(error) {
|
|
206
|
+
if (settled) return;
|
|
207
|
+
settled = true;
|
|
208
|
+
cleanup();
|
|
209
|
+
ws.close();
|
|
210
|
+
reject(error);
|
|
211
|
+
}
|
|
210
212
|
});
|
|
211
213
|
}
|
|
214
|
+
async getTransportHandle(dappInstanceId) {
|
|
215
|
+
const url = `${this.wsUrl}${dappInstanceId != null ? `?dappInstanceId=${encodeURIComponent(dappInstanceId)}` : ""}`;
|
|
216
|
+
try {
|
|
217
|
+
return await connectWebSocket({
|
|
218
|
+
url,
|
|
219
|
+
pingIntervalMs: 3e4,
|
|
220
|
+
pongTimeoutMs: 5e3
|
|
221
|
+
});
|
|
222
|
+
} catch (error) {
|
|
223
|
+
throw new BananalinkError("Unable to establish dapp websocket connection", { cause: error });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
212
226
|
get connectionUrl() {
|
|
213
227
|
if (!this.dappInstanceId || !this.nonce) throw new InvalidConnectionStateError("Cannot get connection URL without a dapp instance");
|
|
214
228
|
const dappInstanceId = this.dappInstanceId;
|
|
215
229
|
const nonce = this.nonce;
|
|
216
230
|
return `bananalink://?dappId=${encodeURIComponent(this.dappId)}&dappInstanceId=${encodeURIComponent(dappInstanceId)}&nonce=${encodeURIComponent(nonce)}`;
|
|
217
231
|
}
|
|
218
|
-
get authorized() {
|
|
219
|
-
return this.jwt !== void 0;
|
|
220
|
-
}
|
|
221
232
|
static bind(ws, jwt) {
|
|
222
|
-
|
|
233
|
+
const bindMessage = {
|
|
223
234
|
type: "bind",
|
|
224
235
|
jwt
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
static isAuthorizedMessage(payload) {
|
|
228
|
-
return typeof payload === "object" && payload !== null && "type" in payload && payload.type === "authorized" && "accessToken" in payload && typeof payload.accessToken === "string" && "refreshToken" in payload && typeof payload.refreshToken === "string" && "message" in payload && typeof payload.message === "string" && "signature" in payload && typeof payload.signature === "string" && "address" in payload && typeof payload.address === "string";
|
|
229
|
-
}
|
|
230
|
-
static isRejectedMessage(payload) {
|
|
231
|
-
return typeof payload === "object" && payload !== null && "type" in payload && payload.type === "rejected";
|
|
236
|
+
};
|
|
237
|
+
ws.send(bindMessage);
|
|
232
238
|
}
|
|
233
239
|
};
|
|
234
240
|
|
|
@@ -251,8 +257,7 @@ var BananalinkClient = class BananalinkClient {
|
|
|
251
257
|
}
|
|
252
258
|
async connect(opts) {
|
|
253
259
|
const rawJwt = opts?.jwt;
|
|
254
|
-
|
|
255
|
-
if (!jwtPayload || !rawJwt) {
|
|
260
|
+
if (!rawJwt) {
|
|
256
261
|
const { dappInstanceId, nonce } = await this.connectDappInstance();
|
|
257
262
|
return new BananalinkConnection({
|
|
258
263
|
jwt: void 0,
|
|
@@ -262,6 +267,8 @@ var BananalinkClient = class BananalinkClient {
|
|
|
262
267
|
wsUrl: this.wsUrl
|
|
263
268
|
});
|
|
264
269
|
}
|
|
270
|
+
const jwtPayload = await this.getJwtPayload(rawJwt);
|
|
271
|
+
if (!jwtPayload) throw new InvalidJwtError();
|
|
265
272
|
return new BananalinkConnection({
|
|
266
273
|
jwt: {
|
|
267
274
|
jwtPayload,
|
|
@@ -298,5 +305,5 @@ async function displayBananalinkQR(url) {
|
|
|
298
305
|
}
|
|
299
306
|
|
|
300
307
|
//#endregion
|
|
301
|
-
export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, DappApiError, InvalidConfigError, InvalidConnectionStateError, displayBananalinkQR };
|
|
308
|
+
export { BananalinkClient, BananalinkConnection, BananalinkError, BananalinkSession, ConnectionRejectedError, ConnectionTimeoutError, DappApiError, InvalidConfigError, InvalidConnectionStateError, InvalidJwtError, displayBananalinkQR };
|
|
302
309
|
//# 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/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 { DappApiError } from '../errors/DappApiError.js';\nimport type { CreateDappInstanceResponse } from '../types/CreateDappInstanceResponse.js';\nimport type { Dapp } from '../types/Dapp.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 { 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 { SessionClaims } from '../types/SessionClaims.js';\n\nexport class BananalinkSession {\n private static readonly PING_INTERVAL_MILLIS = 30 * 1000;\n private static readonly PONG_TIMEOUT_MILLIS = 5 * 1000;\n\n private pingTimer: ReturnType<typeof setInterval> | null = null;\n private pongTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n public readonly sessionClaims: SessionClaims,\n private readonly ws: WebSocket,\n ) {\n this.ws.addEventListener('message', (event) => {\n const { type } = JSON.parse(event.data);\n if (type === 'pong' && this.pongTimer) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n });\n this.startPing();\n }\n\n public close(): void {\n this.stopTimers();\n this.ws.close();\n }\n\n private startPing(): void {\n this.pingTimer = setInterval(() => this.ping(), BananalinkSession.PING_INTERVAL_MILLIS);\n }\n\n private ping(): void {\n if (this.ws.readyState !== WebSocket.OPEN) {\n this.close();\n return;\n }\n this.ws.send(JSON.stringify({ type: 'ping' }));\n this.pongTimer = setTimeout(() => this.close(), BananalinkSession.PONG_TIMEOUT_MILLIS);\n }\n\n private stopTimers(): void {\n if (this.pingTimer !== null) {\n clearInterval(this.pingTimer);\n }\n if (this.pongTimer !== null) {\n clearTimeout(this.pongTimer);\n }\n this.pingTimer = null;\n this.pongTimer = null;\n }\n}\n","import { 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 { AuthorizedMessage } from '../types/AuthorizedMessage.js';\nimport type { JwtPayload } from '../types/JwtPayload.js';\nimport type { RejectedMessage } from '../types/RejectedMessage.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 = 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 }\n\n public async getSession(): Promise<BananalinkSession> {\n if (this.jwt) {\n throw new BananalinkError('Recovering a lost dapp connection is not yet implemented');\n }\n\n const dappInstanceId = this.dappInstanceId;\n if (!dappInstanceId) {\n throw new InvalidConnectionStateError(\n 'Cannot get session without authorizing the dapp instance or providing a valid JWT',\n );\n }\n\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(`${this.wsUrl}?dappInstanceId=${encodeURIComponent(dappInstanceId)}`);\n\n const authTimeout = setTimeout(() => {\n ws.close();\n reject(new ConnectionTimeoutError());\n }, BananalinkConnection.AUTH_TIMEOUT_MILLIS);\n\n const onMessage = (event: MessageEvent): void => {\n const msg = JSON.parse(event.data);\n\n // cheating, but we don't expect any other type of message at this point\n clearTimeout(authTimeout);\n ws.removeEventListener('message', onMessage);\n\n if (BananalinkConnection.isAuthorizedMessage(msg)) {\n BananalinkConnection.bind(ws, msg.accessToken);\n resolve(\n new BananalinkSession(\n {\n address: msg.address,\n message: msg.message,\n signature: msg.signature,\n accessToken: msg.accessToken,\n },\n ws,\n ),\n );\n } else if (BananalinkConnection.isRejectedMessage(msg)) {\n ws.close();\n reject(new ConnectionRejectedError());\n }\n };\n\n // TODO: abstract in a transport interface\n ws.addEventListener('message', onMessage);\n ws.addEventListener('error', () => {});\n ws.addEventListener('close', () => {});\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 public get authorized(): boolean {\n // TODO: handle revocation\n return this.jwt !== undefined;\n }\n\n private static bind(ws: WebSocket, jwt: string) {\n ws.send(JSON.stringify({ type: 'bind', jwt: jwt }));\n }\n\n private static isAuthorizedMessage(payload: unknown): payload is AuthorizedMessage {\n return (\n typeof payload === 'object' &&\n payload !== null &&\n 'type' in payload &&\n payload.type === 'authorized' &&\n 'accessToken' in payload &&\n typeof payload.accessToken === 'string' &&\n 'refreshToken' in payload &&\n typeof payload.refreshToken === 'string' &&\n 'message' in payload &&\n typeof payload.message === 'string' &&\n 'signature' in payload &&\n typeof payload.signature === 'string' &&\n 'address' in payload &&\n typeof payload.address === 'string'\n );\n }\n\n private static isRejectedMessage(payload: unknown): payload is RejectedMessage {\n return typeof payload === 'object' && payload !== null && 'type' in payload && payload.type === 'rejected';\n }\n}\n","import { createDappInstance } from '../api/createDappInstance.js';\nimport { InvalidConfigError } from '../errors/InvalidConfigError.js';\nimport { createBananalinkJwks } from '../jwt/createBananalinkJwks.js';\nimport { verifyJwt } from '../jwt/verifyJwt.js';\nimport type { CreateDappInstanceResponse } from '../types/CreateDappInstanceResponse.js';\nimport type { Dapp } from '../types/Dapp.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 const jwtPayload = await (rawJwt ? this.getJwtPayload(rawJwt) : Promise.resolve(null));\n if (!jwtPayload || !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 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/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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananalink-test/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"jose": "^6.1.3",
|
|
36
|
-
"qrcode": "^1.5.4"
|
|
36
|
+
"qrcode": "^1.5.4",
|
|
37
|
+
"@bananalink-test/sdk-core": "0.2.0"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
40
|
"build": "tsdown",
|