@bananalink-test/wallet 0.0.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 ADDED
@@ -0,0 +1,297 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/errors/BananalinkError.ts
4
+ var BananalinkError = class extends Error {
5
+ constructor(message, options) {
6
+ super(message, options);
7
+ this.name = "BananalinkError";
8
+ }
9
+ };
10
+
11
+ //#endregion
12
+ //#region src/errors/ApiError.ts
13
+ var ApiError = class extends BananalinkError {
14
+ status;
15
+ body;
16
+ constructor(status, body) {
17
+ super(`API request failed with status ${status}`);
18
+ this.name = "ApiError";
19
+ this.status = status;
20
+ this.body = body;
21
+ }
22
+ };
23
+
24
+ //#endregion
25
+ //#region src/api/fetchDapp.ts
26
+ async function fetchDapp(apiUrl, dappId) {
27
+ const response = await fetch(`${apiUrl}/dapps/${encodeURIComponent(dappId)}`);
28
+ if (!response.ok) {
29
+ const body = await response.json().catch(() => null);
30
+ throw new ApiError(response.status, body);
31
+ }
32
+ const body = await response.json();
33
+ if (!isDappInfo(body)) throw new ApiError(response.status, body);
34
+ return body;
35
+ }
36
+ function isDappInfo(value) {
37
+ if (typeof value !== "object" || value === null) return false;
38
+ const record = value;
39
+ return typeof record.dappId === "string" && typeof record.dappName === "string" && (typeof record.dappDescription === "string" || typeof record.dappDescription === "undefined") && typeof record.dappStatement === "string" && typeof record.domain === "string" && typeof record.uri === "string" && Array.isArray(record.icons) && record.icons.every((icon) => typeof icon === "string") && typeof record.chainId === "number" && Number.isFinite(record.chainId);
40
+ }
41
+
42
+ //#endregion
43
+ //#region src/errors/NotConnectedError.ts
44
+ var NotConnectedError = class extends BananalinkError {
45
+ constructor() {
46
+ super("Wallet is not connected. Call connect() first.");
47
+ this.name = "NotConnectedError";
48
+ }
49
+ };
50
+
51
+ //#endregion
52
+ //#region src/errors/ConnectionError.ts
53
+ var ConnectionError = class extends BananalinkError {
54
+ constructor(message, options) {
55
+ super(message, options);
56
+ this.name = "ConnectionError";
57
+ }
58
+ };
59
+
60
+ //#endregion
61
+ //#region src/ws/connectWebSocket.ts
62
+ const HEARTBEAT_PING = "ping";
63
+ const HEARTBEAT_PONG = "pong";
64
+ function connectWebSocket(options) {
65
+ const { url, maxReconnectAttempts = 5, baseReconnectDelayMs = 1e3, pingIntervalMs = 3e5, pongTimeoutMs = 1e4 } = options;
66
+ if (pingIntervalMs > 0 && pongTimeoutMs >= pingIntervalMs) throw new ConnectionError(`pongTimeoutMs (${pongTimeoutMs}) must be less than pingIntervalMs (${pingIntervalMs})`);
67
+ return new Promise((resolveHandle, rejectHandle) => {
68
+ let ws;
69
+ let reconnectAttempts = 0;
70
+ let intentionallyClosed = false;
71
+ let connected = false;
72
+ let permanentlyClosed = false;
73
+ let settled = false;
74
+ let lastConnectError;
75
+ let pingTimer = null;
76
+ let pongTimer = null;
77
+ function resolveOnce() {
78
+ if (settled) return;
79
+ settled = true;
80
+ resolveHandle(handle);
81
+ }
82
+ function rejectOnce(error) {
83
+ if (settled) return;
84
+ settled = true;
85
+ rejectHandle(error);
86
+ }
87
+ function clearPongTimeout() {
88
+ if (pongTimer === null) return;
89
+ clearTimeout(pongTimer);
90
+ pongTimer = null;
91
+ }
92
+ function stopHeartbeat() {
93
+ if (pingTimer !== null) {
94
+ clearInterval(pingTimer);
95
+ pingTimer = null;
96
+ }
97
+ clearPongTimeout();
98
+ }
99
+ function startHeartbeat() {
100
+ stopHeartbeat();
101
+ if (pingIntervalMs <= 0) return;
102
+ pingTimer = setInterval(() => {
103
+ if (intentionallyClosed || permanentlyClosed || ws.readyState !== WebSocket.OPEN) return;
104
+ try {
105
+ ws.send(JSON.stringify({ type: HEARTBEAT_PING }));
106
+ } catch {
107
+ ws.close();
108
+ return;
109
+ }
110
+ clearPongTimeout();
111
+ pongTimer = setTimeout(() => {
112
+ if (!intentionallyClosed && !permanentlyClosed && ws.readyState === WebSocket.OPEN) ws.close();
113
+ }, pongTimeoutMs);
114
+ }, pingIntervalMs);
115
+ }
116
+ function connect() {
117
+ ws = new WebSocket(url);
118
+ ws.addEventListener("open", () => {
119
+ reconnectAttempts = 0;
120
+ permanentlyClosed = false;
121
+ startHeartbeat();
122
+ if (!connected) {
123
+ connected = true;
124
+ resolveOnce();
125
+ }
126
+ });
127
+ ws.addEventListener("message", (event) => {
128
+ if (typeof event.data === "string" && isPongMessage(event.data)) clearPongTimeout();
129
+ });
130
+ ws.addEventListener("close", () => {
131
+ stopHeartbeat();
132
+ if (intentionallyClosed) return;
133
+ attemptReconnect();
134
+ });
135
+ ws.addEventListener("error", (event) => {
136
+ if (!connected) lastConnectError = "message" in event ? event.message : `WebSocket error on ${url}`;
137
+ });
138
+ }
139
+ function attemptReconnect() {
140
+ if (reconnectAttempts >= maxReconnectAttempts) {
141
+ permanentlyClosed = true;
142
+ if (!connected) rejectOnce(new ConnectionError("Failed to connect after maximum reconnect attempts", { cause: lastConnectError }));
143
+ return;
144
+ }
145
+ const jitter = .5 + Math.random() * .5;
146
+ const delay = baseReconnectDelayMs * 2 ** reconnectAttempts * jitter;
147
+ reconnectAttempts++;
148
+ setTimeout(() => {
149
+ if (!intentionallyClosed) connect();
150
+ }, delay);
151
+ }
152
+ const handle = {
153
+ get closed() {
154
+ return intentionallyClosed || permanentlyClosed;
155
+ },
156
+ send(data) {
157
+ if (intentionallyClosed || permanentlyClosed) throw new ConnectionError("WebSocket is closed");
158
+ if (ws.readyState !== WebSocket.OPEN) throw new ConnectionError("WebSocket is not connected");
159
+ ws.send(JSON.stringify(data));
160
+ },
161
+ close() {
162
+ intentionallyClosed = true;
163
+ stopHeartbeat();
164
+ if (typeof ws !== "undefined") ws.close();
165
+ }
166
+ };
167
+ connect();
168
+ });
169
+ }
170
+ function isPongMessage(data) {
171
+ try {
172
+ const parsed = JSON.parse(data);
173
+ return typeof parsed === "object" && parsed !== null && "type" in parsed && parsed.type === HEARTBEAT_PONG;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+
179
+ //#endregion
180
+ //#region src/utils/buildSiweMessage.ts
181
+ function buildSiweMessage(params) {
182
+ const issuedAt = params.issuedAt ?? (/* @__PURE__ */ new Date()).toISOString();
183
+ return `${params.domain} wants you to sign in with your Ethereum account:\n${params.address}\n\n${params.statement}\n\nURI: ${params.uri}\nVersion: 1\nChain ID: ${params.chainId}\nNonce: ${params.nonce}\nIssued At: ${issuedAt}`;
184
+ }
185
+
186
+ //#endregion
187
+ //#region src/ws/createConnectionRequest.ts
188
+ function createConnectionRequest(params) {
189
+ return {
190
+ dappInstanceId: params.dappInstanceId,
191
+ nonce: params.nonce,
192
+ dapp: params.dapp,
193
+ async approve(signer) {
194
+ const message = buildSiweMessage({
195
+ domain: params.dapp.domain,
196
+ address: signer.address,
197
+ uri: params.dapp.uri,
198
+ statement: params.dapp.dappStatement,
199
+ nonce: params.nonce,
200
+ chainId: params.dapp.chainId
201
+ });
202
+ const signature = await signer.signMessage(message);
203
+ params.ws.send({
204
+ type: "authorize",
205
+ dappInstanceId: params.dappInstanceId,
206
+ message,
207
+ signature
208
+ });
209
+ },
210
+ async reject() {
211
+ params.ws.send({
212
+ type: "reject",
213
+ dappInstanceId: params.dappInstanceId
214
+ });
215
+ }
216
+ };
217
+ }
218
+
219
+ //#endregion
220
+ //#region src/core/BananalinkWallet.ts
221
+ var BananalinkWallet = class BananalinkWallet {
222
+ static DEFAULT_API_URL = "https://tfr9p2mn4i.execute-api.us-east-2.amazonaws.com";
223
+ static DEFAULT_WS_URL = "wss://yb47qomkt3.execute-api.us-east-2.amazonaws.com/v1";
224
+ apiUrl;
225
+ wsUrl;
226
+ ws = null;
227
+ constructor(config) {
228
+ this.apiUrl = stripTrailingSlash(config?.apiUrl ?? BananalinkWallet.DEFAULT_API_URL);
229
+ this.wsUrl = stripTrailingSlash(config?.wsUrl ?? BananalinkWallet.DEFAULT_WS_URL);
230
+ }
231
+ async connect(walletId) {
232
+ if (!walletId.trim()) throw new BananalinkError("walletId is required");
233
+ this.disconnect();
234
+ this.ws = await connectWebSocket({ url: `${this.wsUrl}?walletId=${encodeURIComponent(walletId)}` });
235
+ }
236
+ async getConnectionRequest(params) {
237
+ if (!this.ws) throw new NotConnectedError();
238
+ if (!params.dappInstanceId.trim()) throw new BananalinkError("dappInstanceId is required");
239
+ if (!params.dappId.trim()) throw new BananalinkError("dappId is required");
240
+ if (!params.nonce.trim()) throw new BananalinkError("nonce is required");
241
+ const dapp = await fetchDapp(this.apiUrl, params.dappId);
242
+ return createConnectionRequest({
243
+ dappInstanceId: params.dappInstanceId,
244
+ nonce: params.nonce,
245
+ dapp,
246
+ ws: this.ws
247
+ });
248
+ }
249
+ disconnect() {
250
+ this.ws?.close();
251
+ this.ws = null;
252
+ }
253
+ };
254
+ function stripTrailingSlash(url) {
255
+ return url.replace(/\/+$/, "");
256
+ }
257
+
258
+ //#endregion
259
+ //#region src/core/generateWalletId.ts
260
+ function generateWalletId() {
261
+ return crypto.randomUUID();
262
+ }
263
+
264
+ //#endregion
265
+ //#region src/utils/parseDeepLink.ts
266
+ function parseDeepLink(deepLink) {
267
+ let url;
268
+ try {
269
+ url = new URL(deepLink);
270
+ } catch (error) {
271
+ throw new BananalinkError("Invalid deep link URL", { cause: error });
272
+ }
273
+ const dappId = url.searchParams.get("dappId");
274
+ const dappInstanceId = url.searchParams.get("dappInstanceId");
275
+ const nonce = url.searchParams.get("nonce");
276
+ if (!dappId || !dappInstanceId || !nonce) throw new BananalinkError(`Invalid deep link: missing ${[
277
+ !dappId && "dappId",
278
+ !dappInstanceId && "dappInstanceId",
279
+ !nonce && "nonce"
280
+ ].filter(Boolean).join(", ")}`);
281
+ return {
282
+ dappId,
283
+ dappInstanceId,
284
+ nonce
285
+ };
286
+ }
287
+
288
+ //#endregion
289
+ exports.ApiError = ApiError;
290
+ exports.BananalinkError = BananalinkError;
291
+ exports.BananalinkWallet = BananalinkWallet;
292
+ exports.ConnectionError = ConnectionError;
293
+ exports.NotConnectedError = NotConnectedError;
294
+ exports.buildSiweMessage = buildSiweMessage;
295
+ exports.fetchDapp = fetchDapp;
296
+ exports.generateWalletId = generateWalletId;
297
+ exports.parseDeepLink = parseDeepLink;
@@ -0,0 +1,107 @@
1
+ //#region src/types/DappInfo.d.ts
2
+ interface DappInfo {
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
13
+ //#region src/api/fetchDapp.d.ts
14
+ declare function fetchDapp(apiUrl: string, dappId: string): Promise<DappInfo>;
15
+ //#endregion
16
+ //#region src/types/ConnectionRequestParams.d.ts
17
+ interface ConnectionRequestParams {
18
+ dappInstanceId: string;
19
+ dappId: string;
20
+ nonce: string;
21
+ }
22
+ //#endregion
23
+ //#region src/types/Signer.d.ts
24
+ interface Signer {
25
+ address: string;
26
+ signMessage: (message: string) => Promise<string>;
27
+ }
28
+ //#endregion
29
+ //#region src/ws/ConnectionRequest.d.ts
30
+ interface ConnectionRequest {
31
+ readonly dappInstanceId: string;
32
+ readonly nonce: string;
33
+ readonly dapp: DappInfo;
34
+ approve(signer: Signer): Promise<void>;
35
+ reject(): Promise<void>;
36
+ }
37
+ //#endregion
38
+ //#region src/core/BananalinkWalletConfig.d.ts
39
+ interface BananalinkWalletConfig {
40
+ apiUrl?: string;
41
+ wsUrl?: string;
42
+ }
43
+ //#endregion
44
+ //#region src/core/BananalinkWallet.d.ts
45
+ declare class BananalinkWallet {
46
+ private static readonly DEFAULT_API_URL;
47
+ private static readonly DEFAULT_WS_URL;
48
+ private readonly apiUrl;
49
+ private readonly wsUrl;
50
+ private ws;
51
+ constructor(config?: BananalinkWalletConfig);
52
+ connect(walletId: string): Promise<void>;
53
+ getConnectionRequest(params: ConnectionRequestParams): Promise<ConnectionRequest>;
54
+ disconnect(): void;
55
+ }
56
+ //#endregion
57
+ //#region src/core/generateWalletId.d.ts
58
+ declare function generateWalletId(): string;
59
+ //#endregion
60
+ //#region src/errors/BananalinkError.d.ts
61
+ declare class BananalinkError extends Error {
62
+ constructor(message: string, options?: ErrorOptions);
63
+ }
64
+ //#endregion
65
+ //#region src/errors/ApiError.d.ts
66
+ declare class ApiError extends BananalinkError {
67
+ readonly status: number;
68
+ readonly body: unknown;
69
+ constructor(status: number, body: unknown);
70
+ }
71
+ //#endregion
72
+ //#region src/errors/ConnectionError.d.ts
73
+ declare class ConnectionError extends BananalinkError {
74
+ constructor(message: string, options?: ErrorOptions);
75
+ }
76
+ //#endregion
77
+ //#region src/errors/NotConnectedError.d.ts
78
+ declare class NotConnectedError extends BananalinkError {
79
+ constructor();
80
+ }
81
+ //#endregion
82
+ //#region src/types/BuildSiweMessageParams.d.ts
83
+ interface BuildSiweMessageParams {
84
+ domain: string;
85
+ address: string;
86
+ uri: string;
87
+ statement: string;
88
+ nonce: string;
89
+ chainId: number;
90
+ issuedAt?: string;
91
+ }
92
+ //#endregion
93
+ //#region src/types/DeepLinkParams.d.ts
94
+ interface DeepLinkParams {
95
+ dappId: string;
96
+ dappInstanceId: string;
97
+ nonce: string;
98
+ }
99
+ //#endregion
100
+ //#region src/utils/buildSiweMessage.d.ts
101
+ declare function buildSiweMessage(params: BuildSiweMessageParams): string;
102
+ //#endregion
103
+ //#region src/utils/parseDeepLink.d.ts
104
+ declare function parseDeepLink(deepLink: string): DeepLinkParams;
105
+ //#endregion
106
+ export { ApiError, BananalinkError, BananalinkWallet, type BananalinkWalletConfig, type BuildSiweMessageParams, ConnectionError, type ConnectionRequest, type ConnectionRequestParams, type DappInfo, type DeepLinkParams, NotConnectedError, type Signer, buildSiweMessage, fetchDapp, generateWalletId, parseDeepLink };
107
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types/DappInfo.ts","../src/api/fetchDapp.ts","../src/types/ConnectionRequestParams.ts","../src/types/Signer.ts","../src/ws/ConnectionRequest.ts","../src/core/BananalinkWalletConfig.ts","../src/core/BananalinkWallet.ts","../src/core/generateWalletId.ts","../src/errors/BananalinkError.ts","../src/errors/ApiError.ts","../src/errors/ConnectionError.ts","../src/errors/NotConnectedError.ts","../src/types/BuildSiweMessageParams.ts","../src/types/DeepLinkParams.ts","../src/utils/buildSiweMessage.ts","../src/utils/parseDeepLink.ts"],"mappings":";UAAiB,QAAA;EACf,MAAA;EACA,QAAA;EACA,eAAA;EACA,aAAA;EACA,MAAA;EACA,GAAA;EACA,KAAA;EACA,OAAA;AAAA;;;iBCLoB,SAAA,CAAU,MAAA,UAAgB,MAAA,WAAiB,OAAA,CAAQ,QAAA;;;UCHxD,uBAAA;EACf,cAAA;EACA,MAAA;EACA,KAAA;AAAA;;;UCHe,MAAA;EACf,OAAA;EACA,WAAA,GAAc,OAAA,aAAoB,OAAA;AAAA;;;UCCnB,iBAAA;EAAA,SACN,cAAA;EAAA,SACA,KAAA;EAAA,SACA,IAAA,EAAM,QAAA;EACf,OAAA,CAAQ,MAAA,EAAQ,MAAA,GAAS,OAAA;EACzB,MAAA,IAAU,OAAA;AAAA;;;UCRK,sBAAA;EACf,MAAA;EACA,KAAA;AAAA;;;cCQW,gBAAA;EAAA,wBACa,eAAA;EAAA,wBACA,cAAA;EAAA,iBAEP,MAAA;EAAA,iBACA,KAAA;EAAA,QACT,EAAA;cAEI,MAAA,GAAS,sBAAA;EAKf,OAAA,CAAQ,QAAA,WAAmB,OAAA;EAW3B,oBAAA,CAAqB,MAAA,EAAQ,uBAAA,GAA0B,OAAA,CAAQ,iBAAA;EA2BrE,UAAA,CAAA;AAAA;;;iBC7Dc,gBAAA,CAAA;;;cCAH,eAAA,SAAwB,KAAA;cACvB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;;;cCC5B,QAAA,SAAiB,eAAA;EAAA,SACZ,MAAA;EAAA,SACA,IAAA;cAEJ,MAAA,UAAgB,IAAA;AAAA;;;cCJjB,eAAA,SAAwB,eAAA;cACvB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;;;cCD5B,iBAAA,SAA0B,eAAA;EAAA,WAAA,CAAA;AAAA;;;UCFtB,sBAAA;EACf,MAAA;EACA,OAAA;EACA,GAAA;EACA,SAAA;EACA,KAAA;EACA,OAAA;EACA,QAAA;AAAA;;;UCPe,cAAA;EACf,MAAA;EACA,cAAA;EACA,KAAA;AAAA;;;iBCDc,gBAAA,CAAiB,MAAA,EAAQ,sBAAA;;;iBCCzB,aAAA,CAAc,QAAA,WAAmB,cAAA"}
@@ -0,0 +1,107 @@
1
+ //#region src/types/DappInfo.d.ts
2
+ interface DappInfo {
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
13
+ //#region src/api/fetchDapp.d.ts
14
+ declare function fetchDapp(apiUrl: string, dappId: string): Promise<DappInfo>;
15
+ //#endregion
16
+ //#region src/types/ConnectionRequestParams.d.ts
17
+ interface ConnectionRequestParams {
18
+ dappInstanceId: string;
19
+ dappId: string;
20
+ nonce: string;
21
+ }
22
+ //#endregion
23
+ //#region src/types/Signer.d.ts
24
+ interface Signer {
25
+ address: string;
26
+ signMessage: (message: string) => Promise<string>;
27
+ }
28
+ //#endregion
29
+ //#region src/ws/ConnectionRequest.d.ts
30
+ interface ConnectionRequest {
31
+ readonly dappInstanceId: string;
32
+ readonly nonce: string;
33
+ readonly dapp: DappInfo;
34
+ approve(signer: Signer): Promise<void>;
35
+ reject(): Promise<void>;
36
+ }
37
+ //#endregion
38
+ //#region src/core/BananalinkWalletConfig.d.ts
39
+ interface BananalinkWalletConfig {
40
+ apiUrl?: string;
41
+ wsUrl?: string;
42
+ }
43
+ //#endregion
44
+ //#region src/core/BananalinkWallet.d.ts
45
+ declare class BananalinkWallet {
46
+ private static readonly DEFAULT_API_URL;
47
+ private static readonly DEFAULT_WS_URL;
48
+ private readonly apiUrl;
49
+ private readonly wsUrl;
50
+ private ws;
51
+ constructor(config?: BananalinkWalletConfig);
52
+ connect(walletId: string): Promise<void>;
53
+ getConnectionRequest(params: ConnectionRequestParams): Promise<ConnectionRequest>;
54
+ disconnect(): void;
55
+ }
56
+ //#endregion
57
+ //#region src/core/generateWalletId.d.ts
58
+ declare function generateWalletId(): string;
59
+ //#endregion
60
+ //#region src/errors/BananalinkError.d.ts
61
+ declare class BananalinkError extends Error {
62
+ constructor(message: string, options?: ErrorOptions);
63
+ }
64
+ //#endregion
65
+ //#region src/errors/ApiError.d.ts
66
+ declare class ApiError extends BananalinkError {
67
+ readonly status: number;
68
+ readonly body: unknown;
69
+ constructor(status: number, body: unknown);
70
+ }
71
+ //#endregion
72
+ //#region src/errors/ConnectionError.d.ts
73
+ declare class ConnectionError extends BananalinkError {
74
+ constructor(message: string, options?: ErrorOptions);
75
+ }
76
+ //#endregion
77
+ //#region src/errors/NotConnectedError.d.ts
78
+ declare class NotConnectedError extends BananalinkError {
79
+ constructor();
80
+ }
81
+ //#endregion
82
+ //#region src/types/BuildSiweMessageParams.d.ts
83
+ interface BuildSiweMessageParams {
84
+ domain: string;
85
+ address: string;
86
+ uri: string;
87
+ statement: string;
88
+ nonce: string;
89
+ chainId: number;
90
+ issuedAt?: string;
91
+ }
92
+ //#endregion
93
+ //#region src/types/DeepLinkParams.d.ts
94
+ interface DeepLinkParams {
95
+ dappId: string;
96
+ dappInstanceId: string;
97
+ nonce: string;
98
+ }
99
+ //#endregion
100
+ //#region src/utils/buildSiweMessage.d.ts
101
+ declare function buildSiweMessage(params: BuildSiweMessageParams): string;
102
+ //#endregion
103
+ //#region src/utils/parseDeepLink.d.ts
104
+ declare function parseDeepLink(deepLink: string): DeepLinkParams;
105
+ //#endregion
106
+ export { ApiError, BananalinkError, BananalinkWallet, type BananalinkWalletConfig, type BuildSiweMessageParams, ConnectionError, type ConnectionRequest, type ConnectionRequestParams, type DappInfo, type DeepLinkParams, NotConnectedError, type Signer, buildSiweMessage, fetchDapp, generateWalletId, parseDeepLink };
107
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/DappInfo.ts","../src/api/fetchDapp.ts","../src/types/ConnectionRequestParams.ts","../src/types/Signer.ts","../src/ws/ConnectionRequest.ts","../src/core/BananalinkWalletConfig.ts","../src/core/BananalinkWallet.ts","../src/core/generateWalletId.ts","../src/errors/BananalinkError.ts","../src/errors/ApiError.ts","../src/errors/ConnectionError.ts","../src/errors/NotConnectedError.ts","../src/types/BuildSiweMessageParams.ts","../src/types/DeepLinkParams.ts","../src/utils/buildSiweMessage.ts","../src/utils/parseDeepLink.ts"],"mappings":";UAAiB,QAAA;EACf,MAAA;EACA,QAAA;EACA,eAAA;EACA,aAAA;EACA,MAAA;EACA,GAAA;EACA,KAAA;EACA,OAAA;AAAA;;;iBCLoB,SAAA,CAAU,MAAA,UAAgB,MAAA,WAAiB,OAAA,CAAQ,QAAA;;;UCHxD,uBAAA;EACf,cAAA;EACA,MAAA;EACA,KAAA;AAAA;;;UCHe,MAAA;EACf,OAAA;EACA,WAAA,GAAc,OAAA,aAAoB,OAAA;AAAA;;;UCCnB,iBAAA;EAAA,SACN,cAAA;EAAA,SACA,KAAA;EAAA,SACA,IAAA,EAAM,QAAA;EACf,OAAA,CAAQ,MAAA,EAAQ,MAAA,GAAS,OAAA;EACzB,MAAA,IAAU,OAAA;AAAA;;;UCRK,sBAAA;EACf,MAAA;EACA,KAAA;AAAA;;;cCQW,gBAAA;EAAA,wBACa,eAAA;EAAA,wBACA,cAAA;EAAA,iBAEP,MAAA;EAAA,iBACA,KAAA;EAAA,QACT,EAAA;cAEI,MAAA,GAAS,sBAAA;EAKf,OAAA,CAAQ,QAAA,WAAmB,OAAA;EAW3B,oBAAA,CAAqB,MAAA,EAAQ,uBAAA,GAA0B,OAAA,CAAQ,iBAAA;EA2BrE,UAAA,CAAA;AAAA;;;iBC7Dc,gBAAA,CAAA;;;cCAH,eAAA,SAAwB,KAAA;cACvB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;;;cCC5B,QAAA,SAAiB,eAAA;EAAA,SACZ,MAAA;EAAA,SACA,IAAA;cAEJ,MAAA,UAAgB,IAAA;AAAA;;;cCJjB,eAAA,SAAwB,eAAA;cACvB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;;;cCD5B,iBAAA,SAA0B,eAAA;EAAA,WAAA,CAAA;AAAA;;;UCFtB,sBAAA;EACf,MAAA;EACA,OAAA;EACA,GAAA;EACA,SAAA;EACA,KAAA;EACA,OAAA;EACA,QAAA;AAAA;;;UCPe,cAAA;EACf,MAAA;EACA,cAAA;EACA,KAAA;AAAA;;;iBCDc,gBAAA,CAAiB,MAAA,EAAQ,sBAAA;;;iBCCzB,aAAA,CAAc,QAAA,WAAmB,cAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,288 @@
1
+ //#region src/errors/BananalinkError.ts
2
+ var BananalinkError = class extends Error {
3
+ constructor(message, options) {
4
+ super(message, options);
5
+ this.name = "BananalinkError";
6
+ }
7
+ };
8
+
9
+ //#endregion
10
+ //#region src/errors/ApiError.ts
11
+ var ApiError = class extends BananalinkError {
12
+ status;
13
+ body;
14
+ constructor(status, body) {
15
+ super(`API request failed with status ${status}`);
16
+ this.name = "ApiError";
17
+ this.status = status;
18
+ this.body = body;
19
+ }
20
+ };
21
+
22
+ //#endregion
23
+ //#region src/api/fetchDapp.ts
24
+ async function fetchDapp(apiUrl, dappId) {
25
+ const response = await fetch(`${apiUrl}/dapps/${encodeURIComponent(dappId)}`);
26
+ if (!response.ok) {
27
+ const body = await response.json().catch(() => null);
28
+ throw new ApiError(response.status, body);
29
+ }
30
+ const body = await response.json();
31
+ if (!isDappInfo(body)) throw new ApiError(response.status, body);
32
+ return body;
33
+ }
34
+ function isDappInfo(value) {
35
+ if (typeof value !== "object" || value === null) return false;
36
+ const record = value;
37
+ return typeof record.dappId === "string" && typeof record.dappName === "string" && (typeof record.dappDescription === "string" || typeof record.dappDescription === "undefined") && typeof record.dappStatement === "string" && typeof record.domain === "string" && typeof record.uri === "string" && Array.isArray(record.icons) && record.icons.every((icon) => typeof icon === "string") && typeof record.chainId === "number" && Number.isFinite(record.chainId);
38
+ }
39
+
40
+ //#endregion
41
+ //#region src/errors/NotConnectedError.ts
42
+ var NotConnectedError = class extends BananalinkError {
43
+ constructor() {
44
+ super("Wallet is not connected. Call connect() first.");
45
+ this.name = "NotConnectedError";
46
+ }
47
+ };
48
+
49
+ //#endregion
50
+ //#region src/errors/ConnectionError.ts
51
+ var ConnectionError = class extends BananalinkError {
52
+ constructor(message, options) {
53
+ super(message, options);
54
+ this.name = "ConnectionError";
55
+ }
56
+ };
57
+
58
+ //#endregion
59
+ //#region src/ws/connectWebSocket.ts
60
+ const HEARTBEAT_PING = "ping";
61
+ const HEARTBEAT_PONG = "pong";
62
+ function connectWebSocket(options) {
63
+ const { url, maxReconnectAttempts = 5, baseReconnectDelayMs = 1e3, pingIntervalMs = 3e5, pongTimeoutMs = 1e4 } = options;
64
+ if (pingIntervalMs > 0 && pongTimeoutMs >= pingIntervalMs) throw new ConnectionError(`pongTimeoutMs (${pongTimeoutMs}) must be less than pingIntervalMs (${pingIntervalMs})`);
65
+ return new Promise((resolveHandle, rejectHandle) => {
66
+ let ws;
67
+ let reconnectAttempts = 0;
68
+ let intentionallyClosed = false;
69
+ let connected = false;
70
+ let permanentlyClosed = false;
71
+ let settled = false;
72
+ let lastConnectError;
73
+ let pingTimer = null;
74
+ let pongTimer = null;
75
+ function resolveOnce() {
76
+ if (settled) return;
77
+ settled = true;
78
+ resolveHandle(handle);
79
+ }
80
+ function rejectOnce(error) {
81
+ if (settled) return;
82
+ settled = true;
83
+ rejectHandle(error);
84
+ }
85
+ function clearPongTimeout() {
86
+ if (pongTimer === null) return;
87
+ clearTimeout(pongTimer);
88
+ pongTimer = null;
89
+ }
90
+ function stopHeartbeat() {
91
+ if (pingTimer !== null) {
92
+ clearInterval(pingTimer);
93
+ pingTimer = null;
94
+ }
95
+ clearPongTimeout();
96
+ }
97
+ function startHeartbeat() {
98
+ stopHeartbeat();
99
+ if (pingIntervalMs <= 0) return;
100
+ pingTimer = setInterval(() => {
101
+ if (intentionallyClosed || permanentlyClosed || ws.readyState !== WebSocket.OPEN) return;
102
+ try {
103
+ ws.send(JSON.stringify({ type: HEARTBEAT_PING }));
104
+ } catch {
105
+ ws.close();
106
+ return;
107
+ }
108
+ clearPongTimeout();
109
+ pongTimer = setTimeout(() => {
110
+ if (!intentionallyClosed && !permanentlyClosed && ws.readyState === WebSocket.OPEN) ws.close();
111
+ }, pongTimeoutMs);
112
+ }, pingIntervalMs);
113
+ }
114
+ function connect() {
115
+ ws = new WebSocket(url);
116
+ ws.addEventListener("open", () => {
117
+ reconnectAttempts = 0;
118
+ permanentlyClosed = false;
119
+ startHeartbeat();
120
+ if (!connected) {
121
+ connected = true;
122
+ resolveOnce();
123
+ }
124
+ });
125
+ ws.addEventListener("message", (event) => {
126
+ if (typeof event.data === "string" && isPongMessage(event.data)) clearPongTimeout();
127
+ });
128
+ ws.addEventListener("close", () => {
129
+ stopHeartbeat();
130
+ if (intentionallyClosed) return;
131
+ attemptReconnect();
132
+ });
133
+ ws.addEventListener("error", (event) => {
134
+ if (!connected) lastConnectError = "message" in event ? event.message : `WebSocket error on ${url}`;
135
+ });
136
+ }
137
+ function attemptReconnect() {
138
+ if (reconnectAttempts >= maxReconnectAttempts) {
139
+ permanentlyClosed = true;
140
+ if (!connected) rejectOnce(new ConnectionError("Failed to connect after maximum reconnect attempts", { cause: lastConnectError }));
141
+ return;
142
+ }
143
+ const jitter = .5 + Math.random() * .5;
144
+ const delay = baseReconnectDelayMs * 2 ** reconnectAttempts * jitter;
145
+ reconnectAttempts++;
146
+ setTimeout(() => {
147
+ if (!intentionallyClosed) connect();
148
+ }, delay);
149
+ }
150
+ const handle = {
151
+ get closed() {
152
+ return intentionallyClosed || permanentlyClosed;
153
+ },
154
+ send(data) {
155
+ if (intentionallyClosed || permanentlyClosed) throw new ConnectionError("WebSocket is closed");
156
+ if (ws.readyState !== WebSocket.OPEN) throw new ConnectionError("WebSocket is not connected");
157
+ ws.send(JSON.stringify(data));
158
+ },
159
+ close() {
160
+ intentionallyClosed = true;
161
+ stopHeartbeat();
162
+ if (typeof ws !== "undefined") ws.close();
163
+ }
164
+ };
165
+ connect();
166
+ });
167
+ }
168
+ function isPongMessage(data) {
169
+ try {
170
+ const parsed = JSON.parse(data);
171
+ return typeof parsed === "object" && parsed !== null && "type" in parsed && parsed.type === HEARTBEAT_PONG;
172
+ } catch {
173
+ return false;
174
+ }
175
+ }
176
+
177
+ //#endregion
178
+ //#region src/utils/buildSiweMessage.ts
179
+ function buildSiweMessage(params) {
180
+ const issuedAt = params.issuedAt ?? (/* @__PURE__ */ new Date()).toISOString();
181
+ return `${params.domain} wants you to sign in with your Ethereum account:\n${params.address}\n\n${params.statement}\n\nURI: ${params.uri}\nVersion: 1\nChain ID: ${params.chainId}\nNonce: ${params.nonce}\nIssued At: ${issuedAt}`;
182
+ }
183
+
184
+ //#endregion
185
+ //#region src/ws/createConnectionRequest.ts
186
+ function createConnectionRequest(params) {
187
+ return {
188
+ dappInstanceId: params.dappInstanceId,
189
+ nonce: params.nonce,
190
+ dapp: params.dapp,
191
+ async approve(signer) {
192
+ const message = buildSiweMessage({
193
+ domain: params.dapp.domain,
194
+ address: signer.address,
195
+ uri: params.dapp.uri,
196
+ statement: params.dapp.dappStatement,
197
+ nonce: params.nonce,
198
+ chainId: params.dapp.chainId
199
+ });
200
+ const signature = await signer.signMessage(message);
201
+ params.ws.send({
202
+ type: "authorize",
203
+ dappInstanceId: params.dappInstanceId,
204
+ message,
205
+ signature
206
+ });
207
+ },
208
+ async reject() {
209
+ params.ws.send({
210
+ type: "reject",
211
+ dappInstanceId: params.dappInstanceId
212
+ });
213
+ }
214
+ };
215
+ }
216
+
217
+ //#endregion
218
+ //#region src/core/BananalinkWallet.ts
219
+ var BananalinkWallet = class BananalinkWallet {
220
+ static DEFAULT_API_URL = "https://tfr9p2mn4i.execute-api.us-east-2.amazonaws.com";
221
+ static DEFAULT_WS_URL = "wss://yb47qomkt3.execute-api.us-east-2.amazonaws.com/v1";
222
+ apiUrl;
223
+ wsUrl;
224
+ ws = null;
225
+ constructor(config) {
226
+ this.apiUrl = stripTrailingSlash(config?.apiUrl ?? BananalinkWallet.DEFAULT_API_URL);
227
+ this.wsUrl = stripTrailingSlash(config?.wsUrl ?? BananalinkWallet.DEFAULT_WS_URL);
228
+ }
229
+ async connect(walletId) {
230
+ if (!walletId.trim()) throw new BananalinkError("walletId is required");
231
+ this.disconnect();
232
+ this.ws = await connectWebSocket({ url: `${this.wsUrl}?walletId=${encodeURIComponent(walletId)}` });
233
+ }
234
+ async getConnectionRequest(params) {
235
+ if (!this.ws) throw new NotConnectedError();
236
+ if (!params.dappInstanceId.trim()) throw new BananalinkError("dappInstanceId is required");
237
+ if (!params.dappId.trim()) throw new BananalinkError("dappId is required");
238
+ if (!params.nonce.trim()) throw new BananalinkError("nonce is required");
239
+ const dapp = await fetchDapp(this.apiUrl, params.dappId);
240
+ return createConnectionRequest({
241
+ dappInstanceId: params.dappInstanceId,
242
+ nonce: params.nonce,
243
+ dapp,
244
+ ws: this.ws
245
+ });
246
+ }
247
+ disconnect() {
248
+ this.ws?.close();
249
+ this.ws = null;
250
+ }
251
+ };
252
+ function stripTrailingSlash(url) {
253
+ return url.replace(/\/+$/, "");
254
+ }
255
+
256
+ //#endregion
257
+ //#region src/core/generateWalletId.ts
258
+ function generateWalletId() {
259
+ return crypto.randomUUID();
260
+ }
261
+
262
+ //#endregion
263
+ //#region src/utils/parseDeepLink.ts
264
+ function parseDeepLink(deepLink) {
265
+ let url;
266
+ try {
267
+ url = new URL(deepLink);
268
+ } catch (error) {
269
+ throw new BananalinkError("Invalid deep link URL", { cause: error });
270
+ }
271
+ const dappId = url.searchParams.get("dappId");
272
+ const dappInstanceId = url.searchParams.get("dappInstanceId");
273
+ const nonce = url.searchParams.get("nonce");
274
+ if (!dappId || !dappInstanceId || !nonce) throw new BananalinkError(`Invalid deep link: missing ${[
275
+ !dappId && "dappId",
276
+ !dappInstanceId && "dappInstanceId",
277
+ !nonce && "nonce"
278
+ ].filter(Boolean).join(", ")}`);
279
+ return {
280
+ dappId,
281
+ dappInstanceId,
282
+ nonce
283
+ };
284
+ }
285
+
286
+ //#endregion
287
+ export { ApiError, BananalinkError, BananalinkWallet, ConnectionError, NotConnectedError, buildSiweMessage, fetchDapp, generateWalletId, parseDeepLink };
288
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/errors/BananalinkError.ts","../src/errors/ApiError.ts","../src/api/fetchDapp.ts","../src/errors/NotConnectedError.ts","../src/errors/ConnectionError.ts","../src/ws/connectWebSocket.ts","../src/utils/buildSiweMessage.ts","../src/ws/createConnectionRequest.ts","../src/core/BananalinkWallet.ts","../src/core/generateWalletId.ts","../src/utils/parseDeepLink.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 ApiError extends BananalinkError {\n public readonly status: number;\n public readonly body: unknown;\n\n constructor(status: number, body: unknown) {\n super(`API request failed with status ${status}`);\n this.name = 'ApiError';\n this.status = status;\n this.body = body;\n }\n}\n","import { ApiError } from '../errors/ApiError.js';\nimport type { DappInfo } from '../types/DappInfo.js';\n\nexport async function fetchDapp(apiUrl: string, dappId: string): Promise<DappInfo> {\n const response = await fetch(`${apiUrl}/dapps/${encodeURIComponent(dappId)}`);\n\n if (!response.ok) {\n const body = await response.json().catch(() => null);\n throw new ApiError(response.status, body);\n }\n\n const body = await response.json();\n\n if (!isDappInfo(body)) {\n throw new ApiError(response.status, body);\n }\n\n return body;\n}\n\nfunction isDappInfo(value: unknown): value is DappInfo {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n\n const record = value as Record<string, unknown>;\n\n return (\n typeof record.dappId === 'string' &&\n typeof record.dappName === 'string' &&\n (typeof record.dappDescription === 'string' || typeof record.dappDescription === 'undefined') &&\n typeof record.dappStatement === 'string' &&\n typeof record.domain === 'string' &&\n typeof record.uri === 'string' &&\n Array.isArray(record.icons) &&\n record.icons.every((icon) => typeof icon === 'string') &&\n typeof record.chainId === 'number' &&\n Number.isFinite(record.chainId)\n );\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class NotConnectedError extends BananalinkError {\n constructor() {\n super('Wallet is not connected. Call connect() first.');\n this.name = 'NotConnectedError';\n }\n}\n","import { BananalinkError } from './BananalinkError.js';\n\nexport class ConnectionError extends BananalinkError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ConnectionError';\n }\n}\n","import { ConnectionError } from '../errors/ConnectionError.js';\nimport type { ConnectWebSocketOptions } from './ConnectWebSocketOptions.js';\nimport type { WsHandle } from './WsHandle.js';\n\nconst HEARTBEAT_PING = 'ping' as const;\nconst HEARTBEAT_PONG = 'pong' as const;\n\nexport function connectWebSocket(options: ConnectWebSocketOptions): Promise<WsHandle> {\n const {\n url,\n maxReconnectAttempts = 5,\n baseReconnectDelayMs = 1000,\n pingIntervalMs = 300_000,\n pongTimeoutMs = 10_000,\n } = options;\n\n if (pingIntervalMs > 0 && pongTimeoutMs >= pingIntervalMs) {\n throw new ConnectionError(`pongTimeoutMs (${pongTimeoutMs}) must be less than pingIntervalMs (${pingIntervalMs})`);\n }\n\n return new Promise((resolveHandle, rejectHandle) => {\n let ws: WebSocket;\n let reconnectAttempts = 0;\n let intentionallyClosed = false;\n let connected = false;\n let permanentlyClosed = false;\n let settled = false;\n let lastConnectError: unknown;\n let pingTimer: ReturnType<typeof setInterval> | null = null;\n let pongTimer: ReturnType<typeof setTimeout> | null = null;\n\n function resolveOnce(): void {\n if (settled) {\n return;\n }\n\n settled = true;\n resolveHandle(handle);\n }\n\n function rejectOnce(error: ConnectionError): void {\n if (settled) {\n return;\n }\n\n settled = true;\n rejectHandle(error);\n }\n\n function clearPongTimeout(): void {\n if (pongTimer === null) {\n return;\n }\n\n clearTimeout(pongTimer);\n pongTimer = null;\n }\n\n function stopHeartbeat(): void {\n if (pingTimer !== null) {\n clearInterval(pingTimer);\n pingTimer = null;\n }\n\n clearPongTimeout();\n }\n\n function startHeartbeat(): void {\n stopHeartbeat();\n\n if (pingIntervalMs <= 0) {\n return;\n }\n\n pingTimer = setInterval(() => {\n if (intentionallyClosed || permanentlyClosed || ws.readyState !== WebSocket.OPEN) {\n return;\n }\n\n try {\n ws.send(JSON.stringify({ type: HEARTBEAT_PING }));\n } catch {\n ws.close();\n return;\n }\n\n clearPongTimeout();\n pongTimer = setTimeout(() => {\n if (!intentionallyClosed && !permanentlyClosed && ws.readyState === WebSocket.OPEN) {\n ws.close();\n }\n }, pongTimeoutMs);\n }, pingIntervalMs);\n }\n\n function connect() {\n ws = new WebSocket(url);\n\n ws.addEventListener('open', () => {\n reconnectAttempts = 0;\n permanentlyClosed = false;\n startHeartbeat();\n\n if (!connected) {\n connected = true;\n resolveOnce();\n }\n });\n\n ws.addEventListener('message', (event) => {\n if (typeof event.data === 'string' && isPongMessage(event.data)) {\n clearPongTimeout();\n }\n });\n\n ws.addEventListener('close', () => {\n stopHeartbeat();\n\n if (intentionallyClosed) return;\n attemptReconnect();\n });\n\n ws.addEventListener('error', (event) => {\n if (!connected) {\n lastConnectError = 'message' in event ? (event as ErrorEvent).message : `WebSocket error on ${url}`;\n }\n });\n }\n\n function attemptReconnect() {\n if (reconnectAttempts >= maxReconnectAttempts) {\n permanentlyClosed = true;\n\n if (!connected) {\n rejectOnce(\n new ConnectionError('Failed to connect after maximum reconnect attempts', {\n cause: lastConnectError,\n }),\n );\n }\n\n return;\n }\n\n const jitter = 0.5 + Math.random() * 0.5;\n const delay = baseReconnectDelayMs * 2 ** reconnectAttempts * jitter;\n reconnectAttempts++;\n\n setTimeout(() => {\n if (!intentionallyClosed) connect();\n }, delay);\n }\n\n const handle: WsHandle = {\n get closed() {\n return intentionallyClosed || permanentlyClosed;\n },\n\n send(data: unknown) {\n if (intentionallyClosed || permanentlyClosed) {\n throw new ConnectionError('WebSocket is closed');\n }\n\n if (ws.readyState !== WebSocket.OPEN) {\n throw new ConnectionError('WebSocket is not connected');\n }\n\n ws.send(JSON.stringify(data));\n },\n\n close() {\n intentionallyClosed = true;\n stopHeartbeat();\n\n if (typeof ws !== 'undefined') {\n ws.close();\n }\n },\n };\n\n connect();\n });\n}\n\nfunction isPongMessage(data: string): boolean {\n try {\n const parsed: unknown = JSON.parse(data);\n return (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'type' in parsed &&\n (parsed as { type: unknown }).type === HEARTBEAT_PONG\n );\n } catch {\n return false;\n }\n}\n","import type { BuildSiweMessageParams } from '../types/BuildSiweMessageParams.js';\n\nexport function buildSiweMessage(params: BuildSiweMessageParams): string {\n const issuedAt = params.issuedAt ?? new Date().toISOString();\n\n return (\n `${params.domain} wants you to sign in with your Ethereum account:\\n` +\n `${params.address}\\n` +\n `\\n` +\n `${params.statement}\\n` +\n `\\n` +\n `URI: ${params.uri}\\n` +\n `Version: 1\\n` +\n `Chain ID: ${params.chainId}\\n` +\n `Nonce: ${params.nonce}\\n` +\n `Issued At: ${issuedAt}`\n );\n}\n","import type { DappInfo } from '../types/DappInfo.js';\nimport type { Signer } from '../types/Signer.js';\nimport { buildSiweMessage } from '../utils/buildSiweMessage.js';\nimport type { ConnectionRequest } from './ConnectionRequest.js';\nimport type { WsHandle } from './WsHandle.js';\n\nexport function createConnectionRequest(params: {\n dappInstanceId: string;\n nonce: string;\n dapp: DappInfo;\n ws: WsHandle;\n}): ConnectionRequest {\n return {\n dappInstanceId: params.dappInstanceId,\n nonce: params.nonce,\n dapp: params.dapp,\n\n async approve(signer: Signer): Promise<void> {\n const message = buildSiweMessage({\n domain: params.dapp.domain,\n address: signer.address,\n uri: params.dapp.uri,\n statement: params.dapp.dappStatement,\n nonce: params.nonce,\n chainId: params.dapp.chainId,\n });\n\n const signature = await signer.signMessage(message);\n\n params.ws.send({\n type: 'authorize',\n dappInstanceId: params.dappInstanceId,\n message,\n signature,\n });\n },\n\n async reject(): Promise<void> {\n params.ws.send({\n type: 'reject',\n dappInstanceId: params.dappInstanceId,\n });\n },\n };\n}\n","import { fetchDapp } from '../api/fetchDapp.js';\nimport { BananalinkError } from '../errors/BananalinkError.js';\nimport { NotConnectedError } from '../errors/NotConnectedError.js';\nimport type { ConnectionRequestParams } from '../types/ConnectionRequestParams.js';\nimport type { ConnectionRequest } from '../ws/ConnectionRequest.js';\nimport { connectWebSocket } from '../ws/connectWebSocket.js';\nimport { createConnectionRequest } from '../ws/createConnectionRequest.js';\nimport type { WsHandle } from '../ws/WsHandle.js';\nimport type { BananalinkWalletConfig } from './BananalinkWalletConfig.js';\n\nexport class BananalinkWallet {\n private static readonly DEFAULT_API_URL = 'https://tfr9p2mn4i.execute-api.us-east-2.amazonaws.com';\n private static readonly DEFAULT_WS_URL = 'wss://yb47qomkt3.execute-api.us-east-2.amazonaws.com/v1';\n\n private readonly apiUrl: string;\n private readonly wsUrl: string;\n private ws: WsHandle | null = null;\n\n constructor(config?: BananalinkWalletConfig) {\n this.apiUrl = stripTrailingSlash(config?.apiUrl ?? BananalinkWallet.DEFAULT_API_URL);\n this.wsUrl = stripTrailingSlash(config?.wsUrl ?? BananalinkWallet.DEFAULT_WS_URL);\n }\n\n async connect(walletId: string): Promise<void> {\n if (!walletId.trim()) {\n throw new BananalinkError('walletId is required');\n }\n\n this.disconnect();\n\n const url = `${this.wsUrl}?walletId=${encodeURIComponent(walletId)}`;\n this.ws = await connectWebSocket({ url });\n }\n\n async getConnectionRequest(params: ConnectionRequestParams): Promise<ConnectionRequest> {\n if (!this.ws) {\n throw new NotConnectedError();\n }\n\n if (!params.dappInstanceId.trim()) {\n throw new BananalinkError('dappInstanceId is required');\n }\n\n if (!params.dappId.trim()) {\n throw new BananalinkError('dappId is required');\n }\n\n if (!params.nonce.trim()) {\n throw new BananalinkError('nonce is required');\n }\n\n const dapp = await fetchDapp(this.apiUrl, params.dappId);\n\n return createConnectionRequest({\n dappInstanceId: params.dappInstanceId,\n nonce: params.nonce,\n dapp,\n ws: this.ws,\n });\n }\n\n disconnect(): void {\n this.ws?.close();\n this.ws = null;\n }\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '');\n}\n","export function generateWalletId(): string {\n return crypto.randomUUID();\n}\n","import { BananalinkError } from '../errors/BananalinkError.js';\nimport type { DeepLinkParams } from '../types/DeepLinkParams.js';\n\nexport function parseDeepLink(deepLink: string): DeepLinkParams {\n let url: URL;\n\n try {\n url = new URL(deepLink);\n } catch (error) {\n throw new BananalinkError('Invalid deep link URL', { cause: error });\n }\n\n const dappId = url.searchParams.get('dappId');\n const dappInstanceId = url.searchParams.get('dappInstanceId');\n const nonce = url.searchParams.get('nonce');\n\n if (!dappId || !dappInstanceId || !nonce) {\n const missing = [!dappId && 'dappId', !dappInstanceId && 'dappInstanceId', !nonce && 'nonce'].filter(Boolean);\n throw new BananalinkError(`Invalid deep link: missing ${missing.join(', ')}`);\n }\n\n return { dappId, dappInstanceId, nonce };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@bananalink-test/wallet",
3
+ "version": "0.0.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "import": {
10
+ "types": "./dist/index.d.mts",
11
+ "default": "./dist/index.mjs"
12
+ },
13
+ "require": {
14
+ "types": "./dist/index.d.cts",
15
+ "default": "./dist/index.cjs"
16
+ }
17
+ }
18
+ },
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.mjs",
21
+ "types": "./dist/index.d.mts",
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "devDependencies": {
29
+ "@biomejs/biome": "2.3.15",
30
+ "tsdown": "^0.20.3",
31
+ "typescript": "^5.9.3"
32
+ },
33
+ "scripts": {
34
+ "build": "tsdown",
35
+ "type-check": "tsc --noEmit",
36
+ "lint": "biome check .",
37
+ "dev": "tsdown --watch"
38
+ }
39
+ }