@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 +297 -0
- package/dist/index.d.cts +107 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +107 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +288 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +39 -0
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;
|
package/dist/index.d.cts
ADDED
|
@@ -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"}
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
+
}
|