shogun-core 3.0.4 → 3.0.5
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/browser/shogun-core.js +92134 -0
- package/dist/browser/shogun-core.js.map +1 -0
- package/dist/config/simplified-config.js +230 -0
- package/dist/core.js +338 -0
- package/dist/gundb/crypto.js +268 -0
- package/dist/gundb/db.js +1833 -0
- package/dist/gundb/derive.js +229 -0
- package/dist/gundb/errors.js +66 -0
- package/dist/gundb/index.js +6 -0
- package/dist/gundb/restricted-put.js +81 -0
- package/dist/gundb/rxjs.js +445 -0
- package/dist/gundb/simple-api.js +438 -0
- package/dist/gundb/types.js +4 -0
- package/dist/index.js +16 -0
- package/dist/interfaces/common.js +1 -0
- package/dist/interfaces/events.js +36 -0
- package/dist/interfaces/plugin.js +1 -0
- package/dist/interfaces/shogun.js +34 -0
- package/dist/managers/AuthManager.js +225 -0
- package/dist/managers/CoreInitializer.js +240 -0
- package/dist/managers/EventManager.js +67 -0
- package/dist/managers/PluginManager.js +296 -0
- package/dist/migration-test.js +91 -0
- package/dist/plugins/base.js +47 -0
- package/dist/plugins/index.js +15 -0
- package/dist/plugins/nostr/index.js +4 -0
- package/dist/plugins/nostr/nostrConnector.js +413 -0
- package/dist/plugins/nostr/nostrConnectorPlugin.js +446 -0
- package/dist/plugins/nostr/nostrSigner.js +313 -0
- package/dist/plugins/nostr/types.js +1 -0
- package/dist/plugins/oauth/index.js +3 -0
- package/dist/plugins/oauth/oauthConnector.js +753 -0
- package/dist/plugins/oauth/oauthPlugin.js +396 -0
- package/dist/plugins/oauth/types.js +1 -0
- package/dist/plugins/web3/index.js +4 -0
- package/dist/plugins/web3/types.js +1 -0
- package/dist/plugins/web3/web3Connector.js +528 -0
- package/dist/plugins/web3/web3ConnectorPlugin.js +448 -0
- package/dist/plugins/web3/web3Signer.js +308 -0
- package/dist/plugins/webauthn/index.js +3 -0
- package/dist/plugins/webauthn/types.js +11 -0
- package/dist/plugins/webauthn/webauthn.js +478 -0
- package/dist/plugins/webauthn/webauthnPlugin.js +398 -0
- package/dist/plugins/webauthn/webauthnSigner.js +304 -0
- package/dist/storage/storage.js +147 -0
- package/dist/types/config/simplified-config.d.ts +114 -0
- package/dist/types/core.d.ts +305 -0
- package/dist/types/gundb/crypto.d.ts +95 -0
- package/dist/types/gundb/db.d.ts +404 -0
- package/dist/types/gundb/derive.d.ts +21 -0
- package/dist/types/gundb/errors.d.ts +42 -0
- package/dist/types/gundb/index.d.ts +3 -0
- package/dist/types/gundb/restricted-put.d.ts +15 -0
- package/dist/types/gundb/rxjs.d.ts +110 -0
- package/dist/types/gundb/simple-api.d.ts +90 -0
- package/dist/types/gundb/types.d.ts +264 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/interfaces/common.d.ts +85 -0
- package/dist/types/interfaces/events.d.ts +131 -0
- package/dist/types/interfaces/plugin.d.ts +162 -0
- package/dist/types/interfaces/shogun.d.ts +215 -0
- package/dist/types/managers/AuthManager.d.ts +72 -0
- package/dist/types/managers/CoreInitializer.d.ts +40 -0
- package/dist/types/managers/EventManager.d.ts +49 -0
- package/dist/types/managers/PluginManager.d.ts +145 -0
- package/dist/types/migration-test.d.ts +16 -0
- package/dist/types/plugins/base.d.ts +35 -0
- package/dist/types/plugins/index.d.ts +14 -0
- package/dist/types/plugins/nostr/index.d.ts +4 -0
- package/dist/types/plugins/nostr/nostrConnector.d.ts +119 -0
- package/dist/types/plugins/nostr/nostrConnectorPlugin.d.ts +163 -0
- package/dist/types/plugins/nostr/nostrSigner.d.ts +105 -0
- package/dist/types/plugins/nostr/types.d.ts +122 -0
- package/dist/types/plugins/oauth/index.d.ts +3 -0
- package/dist/types/plugins/oauth/oauthConnector.d.ts +110 -0
- package/dist/types/plugins/oauth/oauthPlugin.d.ts +91 -0
- package/dist/types/plugins/oauth/types.d.ts +114 -0
- package/dist/types/plugins/web3/index.d.ts +4 -0
- package/dist/types/plugins/web3/types.d.ts +107 -0
- package/dist/types/plugins/web3/web3Connector.d.ts +129 -0
- package/dist/types/plugins/web3/web3ConnectorPlugin.d.ts +160 -0
- package/dist/types/plugins/web3/web3Signer.d.ts +114 -0
- package/dist/types/plugins/webauthn/index.d.ts +3 -0
- package/dist/types/plugins/webauthn/types.d.ts +162 -0
- package/dist/types/plugins/webauthn/webauthn.d.ts +129 -0
- package/dist/types/plugins/webauthn/webauthnPlugin.d.ts +158 -0
- package/dist/types/plugins/webauthn/webauthnSigner.d.ts +91 -0
- package/dist/types/storage/storage.d.ts +50 -0
- package/dist/types/utils/errorHandler.d.ts +119 -0
- package/dist/types/utils/eventEmitter.d.ts +39 -0
- package/dist/types/utils/validation.d.ts +27 -0
- package/dist/utils/errorHandler.js +241 -0
- package/dist/utils/eventEmitter.js +76 -0
- package/dist/utils/validation.js +72 -0
- package/package.json +1 -1
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MetaMaskAuth class provides functionality for connecting, signing up, and logging in using MetaMask.
|
|
3
|
+
*/
|
|
4
|
+
import { ethers } from "ethers";
|
|
5
|
+
import { ErrorHandler, ErrorType } from "../../utils/errorHandler";
|
|
6
|
+
import { EventEmitter } from "../../utils/eventEmitter";
|
|
7
|
+
import derive from "../../gundb/derive";
|
|
8
|
+
/**
|
|
9
|
+
* Class for MetaMask connection
|
|
10
|
+
*/
|
|
11
|
+
class Web3Connector extends EventEmitter {
|
|
12
|
+
MESSAGE_TO_SIGN = "I Love Shogun!";
|
|
13
|
+
DEFAULT_CONFIG = {
|
|
14
|
+
cacheDuration: 30 * 60 * 1000, // 30 minutes
|
|
15
|
+
maxRetries: 3,
|
|
16
|
+
retryDelay: 1000,
|
|
17
|
+
timeout: 60000,
|
|
18
|
+
};
|
|
19
|
+
config;
|
|
20
|
+
signatureCache = new Map();
|
|
21
|
+
provider = null;
|
|
22
|
+
customProvider = null;
|
|
23
|
+
customWallet = null;
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
super();
|
|
26
|
+
this.config = { ...this.DEFAULT_CONFIG, ...config };
|
|
27
|
+
this.initProvider();
|
|
28
|
+
this.setupEventListeners();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the provider synchronously with fallback mechanisms
|
|
32
|
+
* to handle conflicts between multiple wallet providers
|
|
33
|
+
*/
|
|
34
|
+
initProvider() {
|
|
35
|
+
if (typeof window !== "undefined") {
|
|
36
|
+
try {
|
|
37
|
+
// Check if ethereum is available from any provider
|
|
38
|
+
const ethereumProvider = this.getAvailableEthereumProvider();
|
|
39
|
+
if (ethereumProvider) {
|
|
40
|
+
this.provider = new ethers.BrowserProvider(ethereumProvider);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.warn("No compatible Ethereum provider found");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error("Failed to initialize BrowserProvider", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.warn("Window object not available (non-browser environment)");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get available Ethereum provider from multiple possible sources
|
|
56
|
+
*/
|
|
57
|
+
getAvailableEthereumProvider() {
|
|
58
|
+
if (typeof window === "undefined")
|
|
59
|
+
return undefined;
|
|
60
|
+
// Define provider sources with priority order
|
|
61
|
+
const providerSources = [
|
|
62
|
+
// Check if we have providers in the _ethereumProviders registry (from index.html)
|
|
63
|
+
{
|
|
64
|
+
source: () => window._ethereumProviders && window._ethereumProviders[0],
|
|
65
|
+
name: "Registry Primary",
|
|
66
|
+
},
|
|
67
|
+
{ source: () => window.ethereum, name: "Standard ethereum" },
|
|
68
|
+
{
|
|
69
|
+
source: () => window.web3?.currentProvider,
|
|
70
|
+
name: "Legacy web3",
|
|
71
|
+
},
|
|
72
|
+
{ source: () => window.metamask, name: "MetaMask specific" },
|
|
73
|
+
{
|
|
74
|
+
source: () => window.ethereum?.providers?.find((p) => p.isMetaMask),
|
|
75
|
+
name: "MetaMask from providers array",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
source: () => window.ethereum?.providers?.[0],
|
|
79
|
+
name: "First provider in array",
|
|
80
|
+
},
|
|
81
|
+
// Try known provider names
|
|
82
|
+
{
|
|
83
|
+
source: () => window.enkrypt?.providers?.ethereum,
|
|
84
|
+
name: "Enkrypt",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
source: () => window.coinbaseWalletExtension,
|
|
88
|
+
name: "Coinbase",
|
|
89
|
+
},
|
|
90
|
+
{ source: () => window.trustWallet, name: "Trust Wallet" },
|
|
91
|
+
// Use special registry if available
|
|
92
|
+
{
|
|
93
|
+
source: () => Array.isArray(window._ethereumProviders)
|
|
94
|
+
? window._ethereumProviders.find((p) => !p._isProxy)
|
|
95
|
+
: undefined,
|
|
96
|
+
name: "Registry non-proxy",
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
// Try each provider source
|
|
100
|
+
for (const { source, name } of providerSources) {
|
|
101
|
+
try {
|
|
102
|
+
const provider = source();
|
|
103
|
+
if (provider && typeof provider.request === "function") {
|
|
104
|
+
return provider;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Continue to next provider source
|
|
109
|
+
console.warn(`Error checking provider ${name}:`, error);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// No provider found
|
|
114
|
+
console.warn("No compatible Ethereum provider found");
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Initialize the BrowserProvider (async method for explicit calls)
|
|
119
|
+
*/
|
|
120
|
+
async setupProvider() {
|
|
121
|
+
try {
|
|
122
|
+
if (typeof window !== "undefined") {
|
|
123
|
+
// Check if ethereum is available from any provider
|
|
124
|
+
const ethereumProvider = this.getAvailableEthereumProvider();
|
|
125
|
+
if (ethereumProvider) {
|
|
126
|
+
this.provider = new ethers.BrowserProvider(ethereumProvider);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.warn("No compatible Ethereum provider found");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.warn("Window object not available (non-browser environment)");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.error("Failed to initialize BrowserProvider", error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Setup MetaMask event listeners using BrowserProvider
|
|
142
|
+
*/
|
|
143
|
+
setupEventListeners() {
|
|
144
|
+
if (this.provider) {
|
|
145
|
+
// Listen for network changes through ethers provider
|
|
146
|
+
this.provider.on("network", (newNetwork, oldNetwork) => {
|
|
147
|
+
this.emit("chainChanged", newNetwork);
|
|
148
|
+
});
|
|
149
|
+
// Listen for account changes through the detected provider
|
|
150
|
+
try {
|
|
151
|
+
const ethereumProvider = this.getAvailableEthereumProvider();
|
|
152
|
+
if (ethereumProvider?.on) {
|
|
153
|
+
ethereumProvider.on("accountsChanged", (accounts) => {
|
|
154
|
+
this.emit("accountsChanged", accounts);
|
|
155
|
+
});
|
|
156
|
+
// Also listen for chainChanged events directly
|
|
157
|
+
ethereumProvider.on("chainChanged", (chainId) => {
|
|
158
|
+
this.emit("chainChanged", { chainId });
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.warn("Failed to setup account change listeners", error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Cleanup event listeners
|
|
169
|
+
*/
|
|
170
|
+
cleanup() {
|
|
171
|
+
if (this.provider) {
|
|
172
|
+
this.provider.removeAllListeners();
|
|
173
|
+
}
|
|
174
|
+
this.removeAllListeners();
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get cached signature if valid
|
|
178
|
+
*/
|
|
179
|
+
getCachedSignature(address) {
|
|
180
|
+
const cached = this.signatureCache.get(address);
|
|
181
|
+
if (!cached)
|
|
182
|
+
return null;
|
|
183
|
+
const now = Date.now();
|
|
184
|
+
if (now - cached.timestamp > this.config.cacheDuration) {
|
|
185
|
+
this.signatureCache.delete(address);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
// Check for invalid/empty signature
|
|
189
|
+
if (!cached.signature ||
|
|
190
|
+
typeof cached.signature !== "string" ||
|
|
191
|
+
cached.signature.length < 16) {
|
|
192
|
+
console.warn(`Invalid cached signature for address ${address} (length: ${cached.signature ? cached.signature.length : 0}), deleting from cache.`);
|
|
193
|
+
this.signatureCache.delete(address);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return cached.signature;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Cache signature
|
|
200
|
+
*/
|
|
201
|
+
cacheSignature(address, signature) {
|
|
202
|
+
this.signatureCache.set(address, {
|
|
203
|
+
signature,
|
|
204
|
+
timestamp: Date.now(),
|
|
205
|
+
address,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Validates that the address is valid
|
|
210
|
+
*/
|
|
211
|
+
validateAddress(address) {
|
|
212
|
+
if (!address) {
|
|
213
|
+
throw new Error("Address not provided");
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const normalizedAddress = String(address).trim().toLowerCase();
|
|
217
|
+
if (!ethers.isAddress(normalizedAddress)) {
|
|
218
|
+
throw new Error("Invalid address format");
|
|
219
|
+
}
|
|
220
|
+
return ethers.getAddress(normalizedAddress);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
ErrorHandler.handle(ErrorType.VALIDATION, "INVALID_ADDRESS", "Invalid Ethereum address provided", error);
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Connects to MetaMask with retry logic using BrowserProvider
|
|
229
|
+
*/
|
|
230
|
+
async connectMetaMask() {
|
|
231
|
+
try {
|
|
232
|
+
if (!this.provider) {
|
|
233
|
+
this.initProvider();
|
|
234
|
+
if (!this.provider) {
|
|
235
|
+
throw new Error("MetaMask is not available. Please install MetaMask extension.");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// First check if we can get the provider
|
|
239
|
+
const ethereumProvider = this.getAvailableEthereumProvider();
|
|
240
|
+
if (!ethereumProvider) {
|
|
241
|
+
throw new Error("No compatible Ethereum provider found");
|
|
242
|
+
}
|
|
243
|
+
// Richiedi esplicitamente l'accesso all'account MetaMask
|
|
244
|
+
let accounts = [];
|
|
245
|
+
// Try multiple methods of requesting accounts for compatibility
|
|
246
|
+
try {
|
|
247
|
+
// Try the provider we found first
|
|
248
|
+
accounts = await ethereumProvider.request({
|
|
249
|
+
method: "eth_requestAccounts",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (requestError) {
|
|
253
|
+
console.warn("First account request failed, trying window.ethereum:", requestError);
|
|
254
|
+
// Fallback to window.ethereum if available and different
|
|
255
|
+
if (window.ethereum && window.ethereum !== ethereumProvider) {
|
|
256
|
+
try {
|
|
257
|
+
accounts = await window.ethereum.request({
|
|
258
|
+
method: "eth_requestAccounts",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch (fallbackError) {
|
|
262
|
+
console.error("All account request methods failed", fallbackError);
|
|
263
|
+
throw new Error("User denied account access");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
throw new Error("User denied account access");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (!accounts || accounts.length === 0) {
|
|
271
|
+
}
|
|
272
|
+
for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {
|
|
273
|
+
try {
|
|
274
|
+
const signer = await this.provider.getSigner();
|
|
275
|
+
const address = await signer.getAddress();
|
|
276
|
+
if (!address) {
|
|
277
|
+
console.error("No address returned from signer");
|
|
278
|
+
throw new Error("No address returned from signer");
|
|
279
|
+
}
|
|
280
|
+
this.emit("connected", { address });
|
|
281
|
+
return {
|
|
282
|
+
success: true,
|
|
283
|
+
address,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
console.error(`Attempt ${attempt} failed:`, error);
|
|
288
|
+
if (attempt === this.config.maxRetries) {
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
// Wait before retrying
|
|
292
|
+
await new Promise((resolve) => setTimeout(resolve, this.config.retryDelay));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
throw new Error("Failed to get signer after all attempts");
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.error("Failed to connect to MetaMask:", error);
|
|
299
|
+
ErrorHandler.handle(ErrorType.WEBAUTHN, "METAMASK_CONNECTION_ERROR", error.message ?? "Unknown error while connecting to MetaMask", error);
|
|
300
|
+
return { success: false, error: error.message };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Generates credentials for the given address
|
|
305
|
+
*/
|
|
306
|
+
async generateCredentials(address) {
|
|
307
|
+
try {
|
|
308
|
+
const validAddress = this.validateAddress(address);
|
|
309
|
+
// Check if we have a cached signature
|
|
310
|
+
const cachedSignature = this.getCachedSignature(validAddress);
|
|
311
|
+
if (cachedSignature) {
|
|
312
|
+
return this.generateCredentialsFromSignature(validAddress, cachedSignature);
|
|
313
|
+
}
|
|
314
|
+
// Request signature with timeout
|
|
315
|
+
let signature;
|
|
316
|
+
try {
|
|
317
|
+
signature = await this.requestSignatureWithTimeout(validAddress, this.MESSAGE_TO_SIGN, this.config.timeout);
|
|
318
|
+
}
|
|
319
|
+
catch (signingError) {
|
|
320
|
+
// Gestione del fallimento di firma
|
|
321
|
+
console.warn(`Failed to get signature: ${signingError}. Using fallback method.`);
|
|
322
|
+
throw signingError;
|
|
323
|
+
}
|
|
324
|
+
// Cache the signature
|
|
325
|
+
this.cacheSignature(validAddress, signature);
|
|
326
|
+
return this.generateCredentialsFromSignature(validAddress, signature);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
ErrorHandler.handle(ErrorType.WEBAUTHN, "CREDENTIALS_GENERATION_ERROR", error.message ?? "Error generating MetaMask credentials", error);
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Generates credentials from a signature
|
|
335
|
+
*/
|
|
336
|
+
async generateCredentialsFromSignature(address, signature) {
|
|
337
|
+
const hashedAddress = ethers.keccak256(ethers.toUtf8Bytes(address));
|
|
338
|
+
const salt = `${address}_${signature}`;
|
|
339
|
+
return await derive(hashedAddress, salt, {
|
|
340
|
+
includeP256: true,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Generates fallback credentials (for testing/development)
|
|
345
|
+
*/
|
|
346
|
+
generateFallbackCredentials(address) {
|
|
347
|
+
console.warn("Using fallback credentials generation for address:", address);
|
|
348
|
+
// Generate a deterministic but insecure fallback
|
|
349
|
+
const fallbackSignature = ethers.keccak256(ethers.toUtf8Bytes(address + "fallback"));
|
|
350
|
+
return {
|
|
351
|
+
username: address.toLowerCase(),
|
|
352
|
+
password: fallbackSignature,
|
|
353
|
+
message: this.MESSAGE_TO_SIGN,
|
|
354
|
+
signature: fallbackSignature,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Checks if MetaMask is available
|
|
359
|
+
*/
|
|
360
|
+
static isMetaMaskAvailable() {
|
|
361
|
+
if (typeof window === "undefined") {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
// Check multiple possible sources
|
|
365
|
+
const sources = [
|
|
366
|
+
() => window.ethereum,
|
|
367
|
+
() => window.web3?.currentProvider,
|
|
368
|
+
() => window.metamask,
|
|
369
|
+
() => window._ethereumProviders?.[0],
|
|
370
|
+
];
|
|
371
|
+
for (const source of sources) {
|
|
372
|
+
try {
|
|
373
|
+
const provider = source();
|
|
374
|
+
if (provider && typeof provider.request === "function") {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
// Continue to next source
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Requests signature with timeout
|
|
386
|
+
*/
|
|
387
|
+
requestSignatureWithTimeout(address, message, timeout = 30000) {
|
|
388
|
+
return new Promise((resolve, reject) => {
|
|
389
|
+
const timeoutId = setTimeout(() => {
|
|
390
|
+
reject(new Error("Signature request timed out"));
|
|
391
|
+
}, timeout);
|
|
392
|
+
const cleanup = () => {
|
|
393
|
+
clearTimeout(timeoutId);
|
|
394
|
+
};
|
|
395
|
+
const errorHandler = (error) => {
|
|
396
|
+
cleanup();
|
|
397
|
+
reject(error);
|
|
398
|
+
};
|
|
399
|
+
const initializeAndSign = async () => {
|
|
400
|
+
try {
|
|
401
|
+
const signer = await this.provider.getSigner();
|
|
402
|
+
const signerAddress = await signer.getAddress();
|
|
403
|
+
// Verify the signer address matches the expected address
|
|
404
|
+
if (signerAddress.toLowerCase() !== address.toLowerCase()) {
|
|
405
|
+
throw new Error(`Signer address (${signerAddress}) does not match expected address (${address})`);
|
|
406
|
+
}
|
|
407
|
+
const signature = await signer.signMessage(message);
|
|
408
|
+
cleanup();
|
|
409
|
+
resolve(signature);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
console.error("Failed to request signature:", error);
|
|
413
|
+
errorHandler(error);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
initializeAndSign();
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Checks if the connector is available
|
|
421
|
+
*/
|
|
422
|
+
isAvailable() {
|
|
423
|
+
return Web3Connector.isMetaMaskAvailable();
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Sets a custom provider for testing/development
|
|
427
|
+
*/
|
|
428
|
+
setCustomProvider(rpcUrl, privateKey) {
|
|
429
|
+
try {
|
|
430
|
+
this.customProvider = new ethers.JsonRpcProvider(rpcUrl);
|
|
431
|
+
this.customWallet = new ethers.Wallet(privateKey, this.customProvider);
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
throw new Error(`Error configuring provider: ${error.message ?? "Unknown error"}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Get active signer instance using BrowserProvider
|
|
439
|
+
*/
|
|
440
|
+
async getSigner() {
|
|
441
|
+
try {
|
|
442
|
+
if (this.customWallet) {
|
|
443
|
+
return this.customWallet;
|
|
444
|
+
}
|
|
445
|
+
if (!this.provider) {
|
|
446
|
+
this.initProvider();
|
|
447
|
+
}
|
|
448
|
+
if (!this.provider) {
|
|
449
|
+
throw new Error("Provider not initialized");
|
|
450
|
+
}
|
|
451
|
+
return await this.provider.getSigner();
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
throw new Error(`Unable to get Ethereum signer: ${error.message || "Unknown error"}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Get active provider instance using BrowserProvider
|
|
459
|
+
*/
|
|
460
|
+
async getProvider() {
|
|
461
|
+
if (this.customProvider) {
|
|
462
|
+
return this.customProvider;
|
|
463
|
+
}
|
|
464
|
+
if (!this.provider) {
|
|
465
|
+
this.initProvider();
|
|
466
|
+
}
|
|
467
|
+
return this.provider;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Generate deterministic password from signature
|
|
471
|
+
* @param signature - Cryptographic signature
|
|
472
|
+
* @returns 64-character hex string
|
|
473
|
+
* @throws {Error} For invalid signature
|
|
474
|
+
*/
|
|
475
|
+
async generatePassword(signature) {
|
|
476
|
+
if (!signature) {
|
|
477
|
+
throw new Error("Invalid signature");
|
|
478
|
+
}
|
|
479
|
+
const hash = ethers.keccak256(ethers.toUtf8Bytes(signature));
|
|
480
|
+
return hash.slice(2, 66); // Remove 0x and use first 32 bytes
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Verify message signature
|
|
484
|
+
* @param message - Original signed message
|
|
485
|
+
* @param signature - Cryptographic signature
|
|
486
|
+
* @returns Recovered Ethereum address
|
|
487
|
+
* @throws {Error} For invalid inputs
|
|
488
|
+
*/
|
|
489
|
+
async verifySignature(message, signature) {
|
|
490
|
+
if (!message || !signature) {
|
|
491
|
+
throw new Error("Invalid message or signature");
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
return ethers.verifyMessage(message, signature);
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
throw new Error("Invalid message or signature");
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Get browser-based Ethereum signer
|
|
502
|
+
* @returns Browser provider signer
|
|
503
|
+
* @throws {Error} If MetaMask not detected
|
|
504
|
+
*/
|
|
505
|
+
async getEthereumSigner() {
|
|
506
|
+
if (!Web3Connector.isMetaMaskAvailable()) {
|
|
507
|
+
throw new Error("MetaMask not found. Please install MetaMask to continue.");
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
const ethereum = window.ethereum;
|
|
511
|
+
await ethereum.request({
|
|
512
|
+
method: "eth_requestAccounts",
|
|
513
|
+
});
|
|
514
|
+
const provider = new ethers.BrowserProvider(ethereum);
|
|
515
|
+
return provider.getSigner();
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
throw new Error(`Error accessing MetaMask: ${error.message ?? "Unknown error"}`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (typeof window !== "undefined") {
|
|
523
|
+
window.Web3Connector = Web3Connector;
|
|
524
|
+
}
|
|
525
|
+
else if (typeof global !== "undefined") {
|
|
526
|
+
global.Web3Connector = Web3Connector;
|
|
527
|
+
}
|
|
528
|
+
export { Web3Connector };
|