@cantonconnect/core 0.2.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/LICENSE +21 -0
- package/dist/index.d.mts +885 -0
- package/dist/index.d.ts +885 -0
- package/dist/index.js +786 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +756 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/types.ts
|
|
4
|
+
function toWalletId(id) {
|
|
5
|
+
return id;
|
|
6
|
+
}
|
|
7
|
+
function toPartyId(id) {
|
|
8
|
+
return id;
|
|
9
|
+
}
|
|
10
|
+
function toSessionId(id) {
|
|
11
|
+
return id;
|
|
12
|
+
}
|
|
13
|
+
function toTransactionHash(hash) {
|
|
14
|
+
return hash;
|
|
15
|
+
}
|
|
16
|
+
function toSignature(sig) {
|
|
17
|
+
return sig;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/errors.ts
|
|
21
|
+
var CantonConnectError = class _CantonConnectError extends Error {
|
|
22
|
+
code;
|
|
23
|
+
cause;
|
|
24
|
+
details;
|
|
25
|
+
isOperational;
|
|
26
|
+
constructor(message, code, options) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "CantonConnectError";
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.cause = options?.cause;
|
|
31
|
+
this.details = options?.details;
|
|
32
|
+
this.isOperational = options?.isOperational ?? true;
|
|
33
|
+
if (Error.captureStackTrace) {
|
|
34
|
+
Error.captureStackTrace(this, _CantonConnectError);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Serialize error to JSON for telemetry/logging
|
|
39
|
+
*/
|
|
40
|
+
toJSON() {
|
|
41
|
+
return {
|
|
42
|
+
name: this.name,
|
|
43
|
+
message: this.message,
|
|
44
|
+
code: this.code,
|
|
45
|
+
isOperational: this.isOperational,
|
|
46
|
+
details: this.details,
|
|
47
|
+
cause: this.cause instanceof Error ? {
|
|
48
|
+
name: this.cause.name,
|
|
49
|
+
message: this.cause.message,
|
|
50
|
+
stack: this.cause.stack
|
|
51
|
+
} : this.cause
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var WalletNotFoundError = class extends CantonConnectError {
|
|
56
|
+
constructor(walletId) {
|
|
57
|
+
super(`Wallet "${walletId}" not found`, "WALLET_NOT_FOUND", {
|
|
58
|
+
details: { walletId }
|
|
59
|
+
});
|
|
60
|
+
this.name = "WalletNotFoundError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var WalletNotInstalledError = class extends CantonConnectError {
|
|
64
|
+
constructor(walletId, reason) {
|
|
65
|
+
super(
|
|
66
|
+
`Wallet "${walletId}" is not installed${reason ? `: ${reason}` : ""}`,
|
|
67
|
+
"WALLET_NOT_INSTALLED",
|
|
68
|
+
{
|
|
69
|
+
details: { walletId, reason }
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
this.name = "WalletNotInstalledError";
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var UserRejectedError = class extends CantonConnectError {
|
|
76
|
+
constructor(operation, details) {
|
|
77
|
+
super(`User rejected ${operation}`, "USER_REJECTED", {
|
|
78
|
+
details: { operation, ...details }
|
|
79
|
+
});
|
|
80
|
+
this.name = "UserRejectedError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var OriginNotAllowedError = class extends CantonConnectError {
|
|
84
|
+
constructor(origin, allowedOrigins) {
|
|
85
|
+
super(
|
|
86
|
+
`Origin "${origin}" is not allowed`,
|
|
87
|
+
"ORIGIN_NOT_ALLOWED",
|
|
88
|
+
{
|
|
89
|
+
details: { origin, allowedOrigins }
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
this.name = "OriginNotAllowedError";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var SessionExpiredError = class extends CantonConnectError {
|
|
96
|
+
constructor(sessionId) {
|
|
97
|
+
super(`Session "${sessionId}" has expired`, "SESSION_EXPIRED", {
|
|
98
|
+
details: { sessionId }
|
|
99
|
+
});
|
|
100
|
+
this.name = "SessionExpiredError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var CapabilityNotSupportedError = class extends CantonConnectError {
|
|
104
|
+
constructor(walletId, capability) {
|
|
105
|
+
super(
|
|
106
|
+
`Wallet "${walletId}" does not support capability "${capability}"`,
|
|
107
|
+
"CAPABILITY_NOT_SUPPORTED",
|
|
108
|
+
{
|
|
109
|
+
details: { walletId, capability }
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
this.name = "CapabilityNotSupportedError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var TransportError = class extends CantonConnectError {
|
|
116
|
+
constructor(message, cause, details) {
|
|
117
|
+
super(message, "TRANSPORT_ERROR", {
|
|
118
|
+
cause,
|
|
119
|
+
details
|
|
120
|
+
});
|
|
121
|
+
this.name = "TransportError";
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var RegistryFetchFailedError = class extends CantonConnectError {
|
|
125
|
+
constructor(url, cause) {
|
|
126
|
+
super(`Failed to fetch registry from "${url}"`, "REGISTRY_FETCH_FAILED", {
|
|
127
|
+
cause,
|
|
128
|
+
details: { url }
|
|
129
|
+
});
|
|
130
|
+
this.name = "RegistryFetchFailedError";
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
var RegistryVerificationFailedError = class extends CantonConnectError {
|
|
134
|
+
constructor(reason, details) {
|
|
135
|
+
super(`Registry verification failed: ${reason}`, "REGISTRY_VERIFICATION_FAILED", {
|
|
136
|
+
details: { reason, ...details }
|
|
137
|
+
});
|
|
138
|
+
this.name = "RegistryVerificationFailedError";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var RegistrySchemaInvalidError = class extends CantonConnectError {
|
|
142
|
+
constructor(reason, details) {
|
|
143
|
+
super(`Registry schema invalid: ${reason}`, "REGISTRY_SCHEMA_INVALID", {
|
|
144
|
+
details: { reason, ...details }
|
|
145
|
+
});
|
|
146
|
+
this.name = "RegistrySchemaInvalidError";
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var InternalError = class extends CantonConnectError {
|
|
150
|
+
constructor(message, cause, details) {
|
|
151
|
+
super(message, "INTERNAL_ERROR", {
|
|
152
|
+
cause,
|
|
153
|
+
details,
|
|
154
|
+
isOperational: false
|
|
155
|
+
});
|
|
156
|
+
this.name = "InternalError";
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var TimeoutError = class extends CantonConnectError {
|
|
160
|
+
constructor(operation, timeoutMs) {
|
|
161
|
+
super(
|
|
162
|
+
`Operation "${operation}" timed out after ${timeoutMs}ms`,
|
|
163
|
+
"TIMEOUT",
|
|
164
|
+
{
|
|
165
|
+
details: { operation, timeoutMs }
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
this.name = "TimeoutError";
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
function mapUnknownErrorToCantonConnectError(err, context) {
|
|
172
|
+
if (err instanceof CantonConnectError) {
|
|
173
|
+
return err;
|
|
174
|
+
}
|
|
175
|
+
if (err instanceof Error) {
|
|
176
|
+
const message = err.message.toLowerCase();
|
|
177
|
+
if (message.includes("rejected") || message.includes("denied") || message.includes("cancelled") || message.includes("canceled") || err.name === "UserRejectedError") {
|
|
178
|
+
return new UserRejectedError(context.phase, {
|
|
179
|
+
walletId: context.walletId,
|
|
180
|
+
transport: context.transport,
|
|
181
|
+
originalMessage: err.message
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (message.includes("timeout") || message.includes("timed out") || err.name === "TimeoutError") {
|
|
185
|
+
let timeoutMs = context.timeoutMs ?? 0;
|
|
186
|
+
if (timeoutMs === 0) {
|
|
187
|
+
const msMatch = err.message.match(/(\d+)\s*ms/i);
|
|
188
|
+
const secMatch = err.message.match(/(\d+)\s*(?:sec|second)/i);
|
|
189
|
+
if (msMatch) {
|
|
190
|
+
timeoutMs = parseInt(msMatch[1], 10);
|
|
191
|
+
} else if (secMatch) {
|
|
192
|
+
timeoutMs = parseInt(secMatch[1], 10) * 1e3;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return new TimeoutError(context.phase, timeoutMs);
|
|
196
|
+
}
|
|
197
|
+
if (message.includes("network") || message.includes("fetch") || message.includes("connection") || err.name === "NetworkError" || err.name === "TypeError") {
|
|
198
|
+
return new TransportError(err.message, err, {
|
|
199
|
+
walletId: context.walletId,
|
|
200
|
+
phase: context.phase,
|
|
201
|
+
transport: context.transport
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return new TransportError(err.message, err, {
|
|
205
|
+
walletId: context.walletId,
|
|
206
|
+
phase: context.phase,
|
|
207
|
+
transport: context.transport,
|
|
208
|
+
originalError: err.name
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (typeof err === "string") {
|
|
212
|
+
return new TransportError(err, void 0, {
|
|
213
|
+
walletId: context.walletId,
|
|
214
|
+
phase: context.phase,
|
|
215
|
+
transport: context.transport
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return new InternalError(
|
|
219
|
+
`Unknown error in ${context.phase}`,
|
|
220
|
+
err,
|
|
221
|
+
{
|
|
222
|
+
walletId: context.walletId,
|
|
223
|
+
transport: context.transport,
|
|
224
|
+
errorType: typeof err
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/adapters.ts
|
|
230
|
+
function capabilityGuard(adapter, requiredCapabilities) {
|
|
231
|
+
const supported = adapter.getCapabilities();
|
|
232
|
+
const missing = requiredCapabilities.filter((cap) => !supported.includes(cap));
|
|
233
|
+
if (missing.length > 0) {
|
|
234
|
+
throw new CapabilityNotSupportedError(
|
|
235
|
+
adapter.walletId,
|
|
236
|
+
missing.join(", ")
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async function installGuard(adapter) {
|
|
241
|
+
const detect = await adapter.detectInstalled();
|
|
242
|
+
if (!detect.installed) {
|
|
243
|
+
throw new WalletNotInstalledError(adapter.walletId, detect.reason);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/session.ts
|
|
248
|
+
function generateSessionId() {
|
|
249
|
+
return toSessionId(`session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`);
|
|
250
|
+
}
|
|
251
|
+
function validateSession(session) {
|
|
252
|
+
if (typeof session !== "object" || session === null) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const s = session;
|
|
256
|
+
return typeof s.sessionId === "string" && typeof s.walletId === "string" && typeof s.partyId === "string" && typeof s.network === "string" && typeof s.createdAt === "number" && typeof s.origin === "string" && Array.isArray(s.capabilitiesSnapshot);
|
|
257
|
+
}
|
|
258
|
+
function isSessionExpired(session) {
|
|
259
|
+
if (!session.expiresAt) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return Date.now() >= session.expiresAt;
|
|
263
|
+
}
|
|
264
|
+
function createSession(walletId, partyId, network, origin, capabilities = [], expiresInMs) {
|
|
265
|
+
const now = Date.now();
|
|
266
|
+
return {
|
|
267
|
+
sessionId: generateSessionId(),
|
|
268
|
+
walletId,
|
|
269
|
+
partyId,
|
|
270
|
+
network,
|
|
271
|
+
createdAt: now,
|
|
272
|
+
expiresAt: expiresInMs ? now + expiresInMs : void 0,
|
|
273
|
+
origin,
|
|
274
|
+
capabilitiesSnapshot: capabilities
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/transport/deeplink.ts
|
|
279
|
+
var DeepLinkTransport = class {
|
|
280
|
+
/**
|
|
281
|
+
* Generate a random state nonce
|
|
282
|
+
*/
|
|
283
|
+
generateState() {
|
|
284
|
+
const array = new Uint8Array(32);
|
|
285
|
+
crypto.getRandomValues(array);
|
|
286
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Build deep link URL with query parameters
|
|
290
|
+
*/
|
|
291
|
+
buildDeepLinkUrl(baseUrl, request) {
|
|
292
|
+
const url = new URL(baseUrl);
|
|
293
|
+
Object.entries(request).forEach(([key, value]) => {
|
|
294
|
+
if (value !== void 0) {
|
|
295
|
+
if (Array.isArray(value)) {
|
|
296
|
+
url.searchParams.set(key, JSON.stringify(value));
|
|
297
|
+
} else if (typeof value === "object") {
|
|
298
|
+
url.searchParams.set(key, JSON.stringify(value));
|
|
299
|
+
} else {
|
|
300
|
+
url.searchParams.set(key, String(value));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return url.toString();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Validate callback origin
|
|
308
|
+
*/
|
|
309
|
+
validateOrigin(origin, options) {
|
|
310
|
+
if (options.allowedOrigins && options.allowedOrigins.length > 0) {
|
|
311
|
+
if (!options.allowedOrigins.includes(origin)) {
|
|
312
|
+
throw new Error(`Origin ${origin} not allowed`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Wait for callback via postMessage or redirect
|
|
318
|
+
*/
|
|
319
|
+
async waitForCallback(state, options, timeoutMs) {
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
const timeout = setTimeout(() => {
|
|
322
|
+
cleanup();
|
|
323
|
+
reject(new Error("Transport timeout"));
|
|
324
|
+
}, timeoutMs);
|
|
325
|
+
const messageHandler = (event) => {
|
|
326
|
+
try {
|
|
327
|
+
this.validateOrigin(event.origin, options);
|
|
328
|
+
} catch (err) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const data = event.data;
|
|
332
|
+
if (data && data.state === state) {
|
|
333
|
+
cleanup();
|
|
334
|
+
resolve(data);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
const redirectHandler = () => {
|
|
338
|
+
if (typeof window !== "undefined") {
|
|
339
|
+
const hash = window.location.hash.substring(1);
|
|
340
|
+
const params = new URLSearchParams(hash);
|
|
341
|
+
const callbackState = params.get("state");
|
|
342
|
+
if (callbackState === state) {
|
|
343
|
+
const data = {};
|
|
344
|
+
params.forEach((value, key) => {
|
|
345
|
+
data[key] = value;
|
|
346
|
+
});
|
|
347
|
+
cleanup();
|
|
348
|
+
resolve(data);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const cleanup = () => {
|
|
353
|
+
clearTimeout(timeout);
|
|
354
|
+
if (typeof window !== "undefined") {
|
|
355
|
+
window.removeEventListener("message", messageHandler);
|
|
356
|
+
window.removeEventListener("hashchange", redirectHandler);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
if (typeof window !== "undefined") {
|
|
360
|
+
window.addEventListener("message", messageHandler);
|
|
361
|
+
window.addEventListener("hashchange", redirectHandler);
|
|
362
|
+
redirectHandler();
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Open a connection request
|
|
368
|
+
*/
|
|
369
|
+
async openConnectRequest(url, request, options) {
|
|
370
|
+
if (!request.state) {
|
|
371
|
+
request.state = this.generateState();
|
|
372
|
+
}
|
|
373
|
+
const deepLinkUrl = this.buildDeepLinkUrl(url, request);
|
|
374
|
+
if (typeof window !== "undefined") {
|
|
375
|
+
window.location.href = deepLinkUrl;
|
|
376
|
+
const fallbackWindow = window.open(deepLinkUrl, "_blank");
|
|
377
|
+
if (!fallbackWindow) {
|
|
378
|
+
throw new Error("Failed to open deep link");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const timeout = options.timeoutMs || 6e4;
|
|
382
|
+
const response = await this.waitForCallback(
|
|
383
|
+
request.state,
|
|
384
|
+
options,
|
|
385
|
+
timeout
|
|
386
|
+
);
|
|
387
|
+
if (response.state !== request.state) {
|
|
388
|
+
throw new Error("State mismatch in callback");
|
|
389
|
+
}
|
|
390
|
+
return response;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Open a sign request
|
|
394
|
+
*/
|
|
395
|
+
async openSignRequest(url, request, options) {
|
|
396
|
+
if (!request.state) {
|
|
397
|
+
request.state = this.generateState();
|
|
398
|
+
}
|
|
399
|
+
const deepLinkUrl = this.buildDeepLinkUrl(url, request);
|
|
400
|
+
if (typeof window !== "undefined") {
|
|
401
|
+
window.location.href = deepLinkUrl;
|
|
402
|
+
}
|
|
403
|
+
const timeout = options.timeoutMs || 6e4;
|
|
404
|
+
const response = await this.waitForCallback(
|
|
405
|
+
request.state,
|
|
406
|
+
options,
|
|
407
|
+
timeout
|
|
408
|
+
);
|
|
409
|
+
if (response.state !== request.state) {
|
|
410
|
+
throw new Error("State mismatch in callback");
|
|
411
|
+
}
|
|
412
|
+
return response;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/transport/popup.ts
|
|
417
|
+
var PopupTransport = class {
|
|
418
|
+
/**
|
|
419
|
+
* Generate a random state nonce
|
|
420
|
+
*/
|
|
421
|
+
generateState() {
|
|
422
|
+
const array = new Uint8Array(32);
|
|
423
|
+
crypto.getRandomValues(array);
|
|
424
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Build URL with query parameters
|
|
428
|
+
*/
|
|
429
|
+
buildUrl(baseUrl, request) {
|
|
430
|
+
const url = new URL(baseUrl);
|
|
431
|
+
Object.entries(request).forEach(([key, value]) => {
|
|
432
|
+
if (value !== void 0) {
|
|
433
|
+
if (Array.isArray(value)) {
|
|
434
|
+
url.searchParams.set(key, JSON.stringify(value));
|
|
435
|
+
} else if (typeof value === "object") {
|
|
436
|
+
url.searchParams.set(key, JSON.stringify(value));
|
|
437
|
+
} else {
|
|
438
|
+
url.searchParams.set(key, String(value));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
return url.toString();
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Validate message origin
|
|
446
|
+
*/
|
|
447
|
+
validateOrigin(origin, options) {
|
|
448
|
+
if (options.allowedOrigins && options.allowedOrigins.length > 0) {
|
|
449
|
+
if (!options.allowedOrigins.includes(origin)) {
|
|
450
|
+
throw new Error(`Origin ${origin} not allowed`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Open popup window
|
|
456
|
+
*/
|
|
457
|
+
openPopup(url, _options) {
|
|
458
|
+
if (typeof window === "undefined") {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
const width = 500;
|
|
462
|
+
const height = 600;
|
|
463
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
464
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
465
|
+
return window.open(
|
|
466
|
+
url,
|
|
467
|
+
"CantonConnect",
|
|
468
|
+
`width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Wait for postMessage callback
|
|
473
|
+
*/
|
|
474
|
+
async waitForCallback(popup, state, options, timeoutMs) {
|
|
475
|
+
return new Promise((resolve, reject) => {
|
|
476
|
+
const timeout = setTimeout(() => {
|
|
477
|
+
cleanup();
|
|
478
|
+
popup.close();
|
|
479
|
+
reject(new Error("Transport timeout"));
|
|
480
|
+
}, timeoutMs);
|
|
481
|
+
const checkClosed = setInterval(() => {
|
|
482
|
+
if (popup.closed) {
|
|
483
|
+
cleanup();
|
|
484
|
+
reject(new Error("Popup closed by user"));
|
|
485
|
+
}
|
|
486
|
+
}, 500);
|
|
487
|
+
const messageHandler = (event) => {
|
|
488
|
+
try {
|
|
489
|
+
this.validateOrigin(event.origin, options);
|
|
490
|
+
} catch (err) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const data = event.data;
|
|
494
|
+
if (data && data.state === state) {
|
|
495
|
+
cleanup();
|
|
496
|
+
popup.close();
|
|
497
|
+
resolve(data);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
const cleanup = () => {
|
|
501
|
+
clearTimeout(timeout);
|
|
502
|
+
clearInterval(checkClosed);
|
|
503
|
+
if (typeof window !== "undefined") {
|
|
504
|
+
window.removeEventListener("message", messageHandler);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
if (typeof window !== "undefined") {
|
|
508
|
+
window.addEventListener("message", messageHandler);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Open a connection request
|
|
514
|
+
*/
|
|
515
|
+
async openConnectRequest(url, request, options) {
|
|
516
|
+
if (!request.state) {
|
|
517
|
+
request.state = this.generateState();
|
|
518
|
+
}
|
|
519
|
+
const fullUrl = this.buildUrl(url, request);
|
|
520
|
+
const popup = this.openPopup(fullUrl, options);
|
|
521
|
+
if (!popup) {
|
|
522
|
+
throw new Error("Failed to open popup window");
|
|
523
|
+
}
|
|
524
|
+
const timeout = options.timeoutMs || 6e4;
|
|
525
|
+
const response = await this.waitForCallback(
|
|
526
|
+
popup,
|
|
527
|
+
request.state,
|
|
528
|
+
options,
|
|
529
|
+
timeout
|
|
530
|
+
);
|
|
531
|
+
if (response.state !== request.state) {
|
|
532
|
+
throw new Error("State mismatch in callback");
|
|
533
|
+
}
|
|
534
|
+
return response;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Open a sign request
|
|
538
|
+
*/
|
|
539
|
+
async openSignRequest(url, request, options) {
|
|
540
|
+
if (!request.state) {
|
|
541
|
+
request.state = this.generateState();
|
|
542
|
+
}
|
|
543
|
+
const fullUrl = this.buildUrl(url, request);
|
|
544
|
+
const popup = this.openPopup(fullUrl, options);
|
|
545
|
+
if (!popup) {
|
|
546
|
+
throw new Error("Failed to open popup window");
|
|
547
|
+
}
|
|
548
|
+
const timeout = options.timeoutMs || 6e4;
|
|
549
|
+
const response = await this.waitForCallback(
|
|
550
|
+
popup,
|
|
551
|
+
request.state,
|
|
552
|
+
options,
|
|
553
|
+
timeout
|
|
554
|
+
);
|
|
555
|
+
if (response.state !== request.state) {
|
|
556
|
+
throw new Error("State mismatch in callback");
|
|
557
|
+
}
|
|
558
|
+
return response;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// src/transport/postmessage.ts
|
|
563
|
+
var PostMessageTransport = class {
|
|
564
|
+
/**
|
|
565
|
+
* Generate a random state nonce
|
|
566
|
+
*/
|
|
567
|
+
generateState() {
|
|
568
|
+
const array = new Uint8Array(32);
|
|
569
|
+
crypto.getRandomValues(array);
|
|
570
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Validate message origin
|
|
574
|
+
*/
|
|
575
|
+
validateOrigin(origin, options) {
|
|
576
|
+
if (options.allowedOrigins && options.allowedOrigins.length > 0) {
|
|
577
|
+
if (!options.allowedOrigins.includes(origin)) {
|
|
578
|
+
throw new Error(`Origin ${origin} not allowed`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Send message and wait for response
|
|
584
|
+
*/
|
|
585
|
+
async sendAndWait(target, targetOrigin, message, options, timeoutMs) {
|
|
586
|
+
return new Promise((resolve, reject) => {
|
|
587
|
+
const timeout = setTimeout(() => {
|
|
588
|
+
cleanup();
|
|
589
|
+
reject(new Error("Transport timeout"));
|
|
590
|
+
}, timeoutMs);
|
|
591
|
+
const messageHandler = (event) => {
|
|
592
|
+
try {
|
|
593
|
+
this.validateOrigin(event.origin, options);
|
|
594
|
+
} catch (err) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const data = event.data;
|
|
598
|
+
if (data && data.state === message.state) {
|
|
599
|
+
cleanup();
|
|
600
|
+
resolve(data);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const cleanup = () => {
|
|
604
|
+
clearTimeout(timeout);
|
|
605
|
+
if (typeof window !== "undefined") {
|
|
606
|
+
window.removeEventListener("message", messageHandler);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
if (typeof window !== "undefined") {
|
|
610
|
+
window.addEventListener("message", messageHandler);
|
|
611
|
+
target.postMessage(message, targetOrigin);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Open a connection request
|
|
617
|
+
*/
|
|
618
|
+
async openConnectRequest(url, request, options) {
|
|
619
|
+
if (typeof window === "undefined") {
|
|
620
|
+
throw new Error("PostMessage transport requires browser environment");
|
|
621
|
+
}
|
|
622
|
+
if (!request.state) {
|
|
623
|
+
request.state = this.generateState();
|
|
624
|
+
}
|
|
625
|
+
const targetUrl = new URL(url);
|
|
626
|
+
const targetOrigin = targetUrl.origin;
|
|
627
|
+
const target = window.opener || window.parent;
|
|
628
|
+
if (!target || target === window) {
|
|
629
|
+
throw new Error("No target window available for postMessage");
|
|
630
|
+
}
|
|
631
|
+
const timeout = options.timeoutMs || 6e4;
|
|
632
|
+
const response = await this.sendAndWait(
|
|
633
|
+
target,
|
|
634
|
+
targetOrigin,
|
|
635
|
+
request,
|
|
636
|
+
options,
|
|
637
|
+
timeout
|
|
638
|
+
);
|
|
639
|
+
if (response.state !== request.state) {
|
|
640
|
+
throw new Error("State mismatch in callback");
|
|
641
|
+
}
|
|
642
|
+
return response;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Open a sign request
|
|
646
|
+
*/
|
|
647
|
+
async openSignRequest(url, request, options) {
|
|
648
|
+
if (typeof window === "undefined") {
|
|
649
|
+
throw new Error("PostMessage transport requires browser environment");
|
|
650
|
+
}
|
|
651
|
+
if (!request.state) {
|
|
652
|
+
request.state = this.generateState();
|
|
653
|
+
}
|
|
654
|
+
const targetUrl = new URL(url);
|
|
655
|
+
const targetOrigin = targetUrl.origin;
|
|
656
|
+
const target = window.opener || window.parent;
|
|
657
|
+
if (!target || target === window) {
|
|
658
|
+
throw new Error("No target window available for postMessage");
|
|
659
|
+
}
|
|
660
|
+
const timeout = options.timeoutMs || 6e4;
|
|
661
|
+
const response = await this.sendAndWait(
|
|
662
|
+
target,
|
|
663
|
+
targetOrigin,
|
|
664
|
+
request,
|
|
665
|
+
options,
|
|
666
|
+
timeout
|
|
667
|
+
);
|
|
668
|
+
if (response.state !== request.state) {
|
|
669
|
+
throw new Error("State mismatch in callback");
|
|
670
|
+
}
|
|
671
|
+
return response;
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
// src/transport/mock.ts
|
|
676
|
+
var MockTransport = class {
|
|
677
|
+
mockResponses = /* @__PURE__ */ new Map();
|
|
678
|
+
mockJobs = /* @__PURE__ */ new Map();
|
|
679
|
+
/**
|
|
680
|
+
* Set mock response for a state
|
|
681
|
+
*/
|
|
682
|
+
setMockResponse(state, response) {
|
|
683
|
+
this.mockResponses.set(state, response);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Set mock job status
|
|
687
|
+
*/
|
|
688
|
+
setMockJob(jobId, status) {
|
|
689
|
+
this.mockJobs.set(jobId, status);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Clear all mocks
|
|
693
|
+
*/
|
|
694
|
+
clearMocks() {
|
|
695
|
+
this.mockResponses.clear();
|
|
696
|
+
this.mockJobs.clear();
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Open a connection request (mock)
|
|
700
|
+
*/
|
|
701
|
+
async openConnectRequest(_url, request, _options) {
|
|
702
|
+
const mockResponse = this.mockResponses.get(request.state);
|
|
703
|
+
if (mockResponse && "partyId" in mockResponse) {
|
|
704
|
+
return mockResponse;
|
|
705
|
+
}
|
|
706
|
+
return new Promise((resolve) => {
|
|
707
|
+
setTimeout(() => {
|
|
708
|
+
resolve({
|
|
709
|
+
state: request.state,
|
|
710
|
+
partyId: toPartyId("mock-party-" + Date.now()),
|
|
711
|
+
sessionToken: "mock-token",
|
|
712
|
+
expiresAt: Date.now() + 36e5,
|
|
713
|
+
// 1 hour
|
|
714
|
+
capabilities: request.requestedCapabilities || ["connect", "signMessage"]
|
|
715
|
+
});
|
|
716
|
+
}, 100);
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Open a sign request (mock)
|
|
721
|
+
*/
|
|
722
|
+
async openSignRequest(_url, request, _options) {
|
|
723
|
+
const mockResponse = this.mockResponses.get(request.state);
|
|
724
|
+
if (mockResponse && ("signature" in mockResponse || "jobId" in mockResponse)) {
|
|
725
|
+
return mockResponse;
|
|
726
|
+
}
|
|
727
|
+
return new Promise((resolve) => {
|
|
728
|
+
setTimeout(() => {
|
|
729
|
+
resolve({
|
|
730
|
+
state: request.state,
|
|
731
|
+
signature: "mock-signature-" + Date.now(),
|
|
732
|
+
transactionHash: request.transaction ? "mock-tx-hash" : void 0
|
|
733
|
+
});
|
|
734
|
+
}, 100);
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Poll job status (mock)
|
|
739
|
+
*/
|
|
740
|
+
async pollJobStatus(jobId, _statusUrl, _options) {
|
|
741
|
+
const mockJob = this.mockJobs.get(jobId);
|
|
742
|
+
if (mockJob) {
|
|
743
|
+
return mockJob;
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
jobId,
|
|
747
|
+
status: "approved",
|
|
748
|
+
result: {
|
|
749
|
+
signature: "mock-signature",
|
|
750
|
+
transactionHash: "mock-tx-hash"
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
exports.CantonConnectError = CantonConnectError;
|
|
757
|
+
exports.CapabilityNotSupportedError = CapabilityNotSupportedError;
|
|
758
|
+
exports.DeepLinkTransport = DeepLinkTransport;
|
|
759
|
+
exports.InternalError = InternalError;
|
|
760
|
+
exports.MockTransport = MockTransport;
|
|
761
|
+
exports.OriginNotAllowedError = OriginNotAllowedError;
|
|
762
|
+
exports.PopupTransport = PopupTransport;
|
|
763
|
+
exports.PostMessageTransport = PostMessageTransport;
|
|
764
|
+
exports.RegistryFetchFailedError = RegistryFetchFailedError;
|
|
765
|
+
exports.RegistrySchemaInvalidError = RegistrySchemaInvalidError;
|
|
766
|
+
exports.RegistryVerificationFailedError = RegistryVerificationFailedError;
|
|
767
|
+
exports.SessionExpiredError = SessionExpiredError;
|
|
768
|
+
exports.TimeoutError = TimeoutError;
|
|
769
|
+
exports.TransportError = TransportError;
|
|
770
|
+
exports.UserRejectedError = UserRejectedError;
|
|
771
|
+
exports.WalletNotFoundError = WalletNotFoundError;
|
|
772
|
+
exports.WalletNotInstalledError = WalletNotInstalledError;
|
|
773
|
+
exports.capabilityGuard = capabilityGuard;
|
|
774
|
+
exports.createSession = createSession;
|
|
775
|
+
exports.generateSessionId = generateSessionId;
|
|
776
|
+
exports.installGuard = installGuard;
|
|
777
|
+
exports.isSessionExpired = isSessionExpired;
|
|
778
|
+
exports.mapUnknownErrorToCantonConnectError = mapUnknownErrorToCantonConnectError;
|
|
779
|
+
exports.toPartyId = toPartyId;
|
|
780
|
+
exports.toSessionId = toSessionId;
|
|
781
|
+
exports.toSignature = toSignature;
|
|
782
|
+
exports.toTransactionHash = toTransactionHash;
|
|
783
|
+
exports.toWalletId = toWalletId;
|
|
784
|
+
exports.validateSession = validateSession;
|
|
785
|
+
//# sourceMappingURL=index.js.map
|
|
786
|
+
//# sourceMappingURL=index.js.map
|