@monygroupcorp/micro-web3 1.3.5 → 1.3.8
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/micro-web3.cjs +2 -2
- package/dist/micro-web3.cjs.map +1 -1
- package/dist/micro-web3.esm.js +2 -2
- package/dist/micro-web3.esm.js.map +1 -1
- package/dist/micro-web3.umd.js +2 -2
- package/dist/micro-web3.umd.js.map +1 -1
- package/micro-web3-ipfs-gateway-rotation-spec.md +231 -0
- package/package.json +1 -1
- package/src/components/Wallet/WalletModal.js +17 -0
- package/src/services/IpfsService.js +359 -65
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# micro-web3 IpfsService: Intelligent Gateway Rotation
|
|
2
|
+
|
|
3
|
+
## Current State
|
|
4
|
+
|
|
5
|
+
IpfsService currently only provides:
|
|
6
|
+
- `setCustomGateway(url)` - Set a custom gateway URL
|
|
7
|
+
|
|
8
|
+
**Missing methods (apps need these now):**
|
|
9
|
+
- `getGateway()` - Get the current gateway URL
|
|
10
|
+
- `resolveUrl(ipfsUri)` - Convert `ipfs://` URI to gateway URL
|
|
11
|
+
- `fetchJson(cid)` - Fetch and parse JSON from IPFS
|
|
12
|
+
|
|
13
|
+
## Problem
|
|
14
|
+
|
|
15
|
+
Currently, IpfsService uses a single hardcoded gateway which fails when that gateway is down or slow. Users must manually switch gateways when issues occur.
|
|
16
|
+
|
|
17
|
+
## Proposed Solution
|
|
18
|
+
|
|
19
|
+
Implement dynamic gateway rotation with automatic failover and performance-based selection.
|
|
20
|
+
|
|
21
|
+
## Feature Requirements
|
|
22
|
+
|
|
23
|
+
### 1. Gateway Pool
|
|
24
|
+
|
|
25
|
+
Maintain a pool of known reliable IPFS gateways:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const GATEWAY_POOL = [
|
|
29
|
+
'https://nftstorage.link/ipfs/',
|
|
30
|
+
'https://dweb.link/ipfs/',
|
|
31
|
+
'https://cloudflare-ipfs.com/ipfs/',
|
|
32
|
+
'https://w3s.link/ipfs/',
|
|
33
|
+
'https://ipfs.io/ipfs/'
|
|
34
|
+
];
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Environment-Aware Gateway Prioritization
|
|
38
|
+
|
|
39
|
+
Detect development vs production and prioritize CORS-friendly gateways in dev:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
function getEnvironment() {
|
|
43
|
+
const hostname = window.location.hostname;
|
|
44
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname.includes('.local')
|
|
45
|
+
? 'development'
|
|
46
|
+
: 'production';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Gateways known to have proper CORS headers for localhost
|
|
50
|
+
const CORS_FRIENDLY_GATEWAYS = [
|
|
51
|
+
'https://cloudflare-ipfs.com/ipfs/',
|
|
52
|
+
'https://dweb.link/ipfs/',
|
|
53
|
+
'https://w3s.link/ipfs/'
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function getPrioritizedGateways() {
|
|
57
|
+
if (getEnvironment() === 'development') {
|
|
58
|
+
// In dev, put CORS-friendly gateways first
|
|
59
|
+
return [
|
|
60
|
+
...CORS_FRIENDLY_GATEWAYS,
|
|
61
|
+
...GATEWAY_POOL.filter(g => !CORS_FRIENDLY_GATEWAYS.includes(g))
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
return GATEWAY_POOL;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This ensures developers don't hit CORS issues during local development while still allowing the full gateway pool in production.
|
|
69
|
+
|
|
70
|
+
### 2. Initial Gateway Selection (Race Strategy)
|
|
71
|
+
|
|
72
|
+
When fetching the first batch of assets, race all gateways in parallel:
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
async function selectBestGateway(testCid) {
|
|
76
|
+
const results = await Promise.allSettled(
|
|
77
|
+
GATEWAY_POOL.map(async (gateway) => {
|
|
78
|
+
const start = Date.now();
|
|
79
|
+
const response = await fetch(`${gateway}${testCid}`, {
|
|
80
|
+
method: 'HEAD',
|
|
81
|
+
signal: AbortSignal.timeout(5000)
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) throw new Error('Failed');
|
|
84
|
+
return { gateway, latency: Date.now() - start };
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const successful = results
|
|
89
|
+
.filter(r => r.status === 'fulfilled')
|
|
90
|
+
.map(r => r.value)
|
|
91
|
+
.sort((a, b) => a.latency - b.latency);
|
|
92
|
+
|
|
93
|
+
return successful[0]?.gateway || GATEWAY_POOL[0];
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 3. Parallel First-Batch Loading
|
|
98
|
+
|
|
99
|
+
For the initial N assets (e.g., first 3), use different gateways simultaneously to determine the fastest:
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
async function loadWithGatewayDiscovery(cids) {
|
|
103
|
+
// Assign each of first N cids to different gateways
|
|
104
|
+
const gatewayStats = {};
|
|
105
|
+
|
|
106
|
+
const results = await Promise.allSettled(
|
|
107
|
+
cids.slice(0, GATEWAY_POOL.length).map((cid, i) => {
|
|
108
|
+
const gateway = GATEWAY_POOL[i % GATEWAY_POOL.length];
|
|
109
|
+
const start = Date.now();
|
|
110
|
+
return fetch(`${gateway}${cid}`)
|
|
111
|
+
.then(res => res.json())
|
|
112
|
+
.then(data => {
|
|
113
|
+
gatewayStats[gateway] = Date.now() - start;
|
|
114
|
+
return { cid, data, gateway };
|
|
115
|
+
});
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Select best gateway based on results
|
|
120
|
+
const bestGateway = Object.entries(gatewayStats)
|
|
121
|
+
.sort(([,a], [,b]) => a - b)[0]?.[0];
|
|
122
|
+
|
|
123
|
+
// Use best gateway for remaining assets
|
|
124
|
+
return { results, bestGateway };
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 4. Automatic Failover
|
|
129
|
+
|
|
130
|
+
If the selected gateway fails mid-session, automatically rotate to next best:
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
class IpfsService {
|
|
134
|
+
constructor() {
|
|
135
|
+
this.currentGateway = null;
|
|
136
|
+
this.gatewayLatencies = new Map();
|
|
137
|
+
this.failedGateways = new Set();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async fetch(cid) {
|
|
141
|
+
const gateway = this.currentGateway || await this.selectBestGateway(cid);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
return await this._fetchWithTimeout(`${gateway}${cid}`);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
this.failedGateways.add(gateway);
|
|
147
|
+
this.currentGateway = this._getNextBestGateway();
|
|
148
|
+
return this.fetch(cid); // Retry with new gateway
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_getNextBestGateway() {
|
|
153
|
+
return GATEWAY_POOL
|
|
154
|
+
.filter(g => !this.failedGateways.has(g))
|
|
155
|
+
.sort((a, b) =>
|
|
156
|
+
(this.gatewayLatencies.get(a) || Infinity) -
|
|
157
|
+
(this.gatewayLatencies.get(b) || Infinity)
|
|
158
|
+
)[0];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 5. Gateway Health Monitoring
|
|
164
|
+
|
|
165
|
+
Periodically test gateway health and update rankings:
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
async function monitorGatewayHealth() {
|
|
169
|
+
// Run every 60 seconds in background
|
|
170
|
+
for (const gateway of GATEWAY_POOL) {
|
|
171
|
+
try {
|
|
172
|
+
const start = Date.now();
|
|
173
|
+
await fetch(`${gateway}bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi`, {
|
|
174
|
+
method: 'HEAD',
|
|
175
|
+
signal: AbortSignal.timeout(3000)
|
|
176
|
+
});
|
|
177
|
+
this.gatewayLatencies.set(gateway, Date.now() - start);
|
|
178
|
+
this.failedGateways.delete(gateway);
|
|
179
|
+
} catch {
|
|
180
|
+
this.gatewayLatencies.set(gateway, Infinity);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## API Design
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
import { IpfsService } from '@monygroupcorp/micro-web3';
|
|
190
|
+
|
|
191
|
+
// Auto-selects best gateway
|
|
192
|
+
const ipfs = new IpfsService();
|
|
193
|
+
|
|
194
|
+
// Fetch with automatic gateway rotation
|
|
195
|
+
const metadata = await ipfs.fetchJson(cid);
|
|
196
|
+
const imageUrl = ipfs.resolveUrl(metadata.image);
|
|
197
|
+
|
|
198
|
+
// Manual gateway override (optional)
|
|
199
|
+
ipfs.setPreferredGateway('https://dweb.link/ipfs/');
|
|
200
|
+
|
|
201
|
+
// Get current gateway stats
|
|
202
|
+
const stats = ipfs.getGatewayStats();
|
|
203
|
+
// { current: 'https://nftstorage.link/ipfs/', latencies: {...}, failed: [...] }
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Configuration Options
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
const ipfs = new IpfsService({
|
|
210
|
+
gateways: [...customGateways], // Override default pool
|
|
211
|
+
timeout: 5000, // Request timeout (ms)
|
|
212
|
+
maxRetries: 3, // Retries before marking gateway failed
|
|
213
|
+
healthCheckInterval: 60000, // Health monitoring interval (ms)
|
|
214
|
+
parallelDiscovery: true, // Use parallel first-batch strategy
|
|
215
|
+
cacheResults: true // Cache successful responses
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Benefits
|
|
220
|
+
|
|
221
|
+
1. **Reliability**: Automatic failover means no manual intervention when gateways fail
|
|
222
|
+
2. **Performance**: Always uses the fastest available gateway
|
|
223
|
+
3. **Resilience**: Survives individual gateway outages transparently
|
|
224
|
+
4. **Developer Experience**: Simple API, complex logic handled internally
|
|
225
|
+
|
|
226
|
+
## Migration Path
|
|
227
|
+
|
|
228
|
+
1. Add new gateway rotation logic to IpfsService
|
|
229
|
+
2. Keep `setCustomGateway()` for backwards compatibility
|
|
230
|
+
3. Default to new auto-selection behavior
|
|
231
|
+
4. Apps using IpfsService get improvements automatically
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monygroupcorp/micro-web3",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"description": "A lean, reusable Web3 toolkit with components for wallet connection, IPFS, and common Web3 UI patterns.",
|
|
5
5
|
"main": "dist/micro-web3.cjs",
|
|
6
6
|
"module": "dist/micro-web3.esm.js",
|
|
@@ -65,6 +65,23 @@ export class WalletModal extends Component {
|
|
|
65
65
|
)
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
static get styles() {
|
|
70
|
+
return `
|
|
71
|
+
.wallet-option .wallet-icon {
|
|
72
|
+
width: 36px;
|
|
73
|
+
height: 36px;
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.wallet-option .wallet-icon svg {
|
|
80
|
+
width: 28px;
|
|
81
|
+
height: 28px;
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
export default WalletModal;
|