@ar.io/wayfinder-react 1.0.2-alpha.2 → 1.0.2
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.
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WayFinder
|
|
3
|
+
* Copyright (C) 2022-2025 Permanent Data Solutions, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
export * from './local-storage.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WayFinder
|
|
3
|
+
* Copyright (C) 2022-2025 Permanent Data Solutions, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
export * from './local-storage.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WayFinder
|
|
3
|
+
* Copyright (C) 2022-2025 Permanent Data Solutions, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { GatewaysProvider } from '@ar.io/wayfinder-core';
|
|
18
|
+
export declare class LocalStorageGatewaysProvider implements GatewaysProvider {
|
|
19
|
+
private readonly storageKey;
|
|
20
|
+
private readonly defaultTtlSeconds;
|
|
21
|
+
private readonly gatewaysProvider;
|
|
22
|
+
private readonly ttlSeconds;
|
|
23
|
+
constructor({ ttlSeconds, gatewaysProvider, }: {
|
|
24
|
+
ttlSeconds?: number;
|
|
25
|
+
gatewaysProvider: GatewaysProvider;
|
|
26
|
+
});
|
|
27
|
+
getGateways(): Promise<URL[]>;
|
|
28
|
+
private getCachedGateways;
|
|
29
|
+
private isCacheValid;
|
|
30
|
+
private cacheGateways;
|
|
31
|
+
clearCache(): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export class LocalStorageGatewaysProvider {
|
|
2
|
+
storageKey = 'wayfinder-gateways-cache';
|
|
3
|
+
defaultTtlSeconds = 3600; // 1 hour default
|
|
4
|
+
gatewaysProvider;
|
|
5
|
+
ttlSeconds;
|
|
6
|
+
constructor({ ttlSeconds = 300, gatewaysProvider, }) {
|
|
7
|
+
this.gatewaysProvider = gatewaysProvider;
|
|
8
|
+
this.ttlSeconds = ttlSeconds;
|
|
9
|
+
this.gatewaysProvider = gatewaysProvider;
|
|
10
|
+
}
|
|
11
|
+
async getGateways() {
|
|
12
|
+
const cached = this.getCachedGateways();
|
|
13
|
+
if (cached && this.isCacheValid(cached)) {
|
|
14
|
+
return cached.gateways.map((gateway) => new URL(gateway));
|
|
15
|
+
}
|
|
16
|
+
const gateways = await this.gatewaysProvider.getGateways();
|
|
17
|
+
this.cacheGateways(gateways);
|
|
18
|
+
return gateways;
|
|
19
|
+
}
|
|
20
|
+
getCachedGateways() {
|
|
21
|
+
try {
|
|
22
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const cached = window.localStorage.getItem(this.storageKey);
|
|
26
|
+
if (!cached) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return JSON.parse(cached);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.warn('Failed to retrieve cached gateways:', error);
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
isCacheValid(cached) {
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
const cacheAge = now - cached.timestamp;
|
|
39
|
+
const ttlMs = (cached.ttlSeconds || this.defaultTtlSeconds) * 1000;
|
|
40
|
+
const gatewaysCount = cached.gateways.length;
|
|
41
|
+
return cacheAge < ttlMs && gatewaysCount > 0;
|
|
42
|
+
}
|
|
43
|
+
cacheGateways(gateways) {
|
|
44
|
+
try {
|
|
45
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const cached = {
|
|
49
|
+
gateways: gateways.map((gateway) => gateway.toString()),
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
ttlSeconds: this.ttlSeconds,
|
|
52
|
+
};
|
|
53
|
+
window.localStorage.setItem(this.storageKey, JSON.stringify(cached));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.warn('Failed to cache gateways:', error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
clearCache() {
|
|
60
|
+
try {
|
|
61
|
+
if (typeof window === 'undefined' || !window.localStorage) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
window.localStorage.removeItem(this.storageKey);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.warn('Failed to clear gateway cache:', error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WayFinder
|
|
3
|
+
* Copyright (C) 2022-2025 Permanent Data Solutions, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import assert from 'node:assert';
|
|
18
|
+
import { beforeEach, describe, it } from 'node:test';
|
|
19
|
+
import { LocalStorageGatewaysProvider } from './local-storage.js';
|
|
20
|
+
class MockGatewaysProvider {
|
|
21
|
+
gateways;
|
|
22
|
+
constructor(gateways = ['https://gateway1.com', 'https://gateway2.com']) {
|
|
23
|
+
this.gateways = gateways.map((url) => new URL(url));
|
|
24
|
+
}
|
|
25
|
+
async getGateways() {
|
|
26
|
+
return this.gateways;
|
|
27
|
+
}
|
|
28
|
+
setGateways(gateways) {
|
|
29
|
+
this.gateways = gateways.map((url) => new URL(url));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function setupBrowserEnvironment() {
|
|
33
|
+
const mockLocalStorage = {
|
|
34
|
+
store: {},
|
|
35
|
+
get length() {
|
|
36
|
+
return Object.keys(this.store).length;
|
|
37
|
+
},
|
|
38
|
+
getItem: function (key) {
|
|
39
|
+
return this.store[key] || null;
|
|
40
|
+
},
|
|
41
|
+
setItem: function (key, value) {
|
|
42
|
+
this.store[key] = value;
|
|
43
|
+
},
|
|
44
|
+
removeItem: function (key) {
|
|
45
|
+
delete this.store[key];
|
|
46
|
+
},
|
|
47
|
+
clear: function () {
|
|
48
|
+
this.store = {};
|
|
49
|
+
},
|
|
50
|
+
key: function (index) {
|
|
51
|
+
const keys = Object.keys(this.store);
|
|
52
|
+
return keys[index] || null;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
// @ts-expect-error - Mock global window object
|
|
56
|
+
global.window = {
|
|
57
|
+
localStorage: mockLocalStorage,
|
|
58
|
+
};
|
|
59
|
+
return mockLocalStorage;
|
|
60
|
+
}
|
|
61
|
+
function teardownBrowserEnvironment() {
|
|
62
|
+
// @ts-expect-error - Remove mock global window object
|
|
63
|
+
delete global.window;
|
|
64
|
+
}
|
|
65
|
+
describe('LocalStorageGatewaysProvider', () => {
|
|
66
|
+
let mockLocalStorage;
|
|
67
|
+
let mockGatewaysProvider;
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
mockLocalStorage = setupBrowserEnvironment();
|
|
70
|
+
mockGatewaysProvider = new MockGatewaysProvider();
|
|
71
|
+
});
|
|
72
|
+
describe('constructor', () => {
|
|
73
|
+
it('should initialize with default TTL when not provided', () => {
|
|
74
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
75
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
76
|
+
});
|
|
77
|
+
assert.ok(provider instanceof LocalStorageGatewaysProvider);
|
|
78
|
+
});
|
|
79
|
+
it('should initialize with custom TTL when provided', () => {
|
|
80
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
81
|
+
ttlSeconds: 600,
|
|
82
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
83
|
+
});
|
|
84
|
+
assert.ok(provider instanceof LocalStorageGatewaysProvider);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('getGateways', () => {
|
|
88
|
+
describe('cache hit scenarios', () => {
|
|
89
|
+
it('should return cached gateways when cache is valid', async () => {
|
|
90
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
91
|
+
ttlSeconds: 300,
|
|
92
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
93
|
+
});
|
|
94
|
+
const cachedGateways = {
|
|
95
|
+
gateways: ['https://cached1.com', 'https://cached2.com'],
|
|
96
|
+
timestamp: Date.now(),
|
|
97
|
+
ttlSeconds: 300,
|
|
98
|
+
};
|
|
99
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', JSON.stringify(cachedGateways));
|
|
100
|
+
const result = await provider.getGateways();
|
|
101
|
+
assert.strictEqual(result.length, 2);
|
|
102
|
+
assert.strictEqual(result[0].toString(), 'https://cached1.com/');
|
|
103
|
+
assert.strictEqual(result[1].toString(), 'https://cached2.com/');
|
|
104
|
+
});
|
|
105
|
+
it('should return URLs as URL objects from cache', async () => {
|
|
106
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
107
|
+
ttlSeconds: 300,
|
|
108
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
109
|
+
});
|
|
110
|
+
const cachedGateways = {
|
|
111
|
+
gateways: ['https://example.com'],
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
ttlSeconds: 300,
|
|
114
|
+
};
|
|
115
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', JSON.stringify(cachedGateways));
|
|
116
|
+
const result = await provider.getGateways();
|
|
117
|
+
assert.ok(result[0] instanceof URL);
|
|
118
|
+
assert.strictEqual(result[0].toString(), 'https://example.com/');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('cache miss scenarios', () => {
|
|
122
|
+
it('should fetch from provider when no cache exists', async () => {
|
|
123
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
124
|
+
ttlSeconds: 300,
|
|
125
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
126
|
+
});
|
|
127
|
+
const result = await provider.getGateways();
|
|
128
|
+
assert.strictEqual(result.length, 2);
|
|
129
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
130
|
+
assert.strictEqual(result[1].toString(), 'https://gateway2.com/');
|
|
131
|
+
const cached = mockLocalStorage.getItem('wayfinder-gateways-cache');
|
|
132
|
+
assert.ok(cached);
|
|
133
|
+
const parsedCache = JSON.parse(cached);
|
|
134
|
+
assert.strictEqual(parsedCache.gateways.length, 2);
|
|
135
|
+
assert.strictEqual(parsedCache.ttlSeconds, 300);
|
|
136
|
+
});
|
|
137
|
+
it('should fetch from provider when cache is expired', async () => {
|
|
138
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
139
|
+
ttlSeconds: 300,
|
|
140
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
141
|
+
});
|
|
142
|
+
const expiredCache = {
|
|
143
|
+
gateways: ['https://expired.com'],
|
|
144
|
+
timestamp: Date.now() - 400000, // 400 seconds ago (expired)
|
|
145
|
+
ttlSeconds: 300,
|
|
146
|
+
};
|
|
147
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', JSON.stringify(expiredCache));
|
|
148
|
+
const result = await provider.getGateways();
|
|
149
|
+
assert.strictEqual(result.length, 2);
|
|
150
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
151
|
+
assert.strictEqual(result[1].toString(), 'https://gateway2.com/');
|
|
152
|
+
});
|
|
153
|
+
it('should use default TTL from cache when cache TTL is missing', async () => {
|
|
154
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
155
|
+
ttlSeconds: 300,
|
|
156
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
157
|
+
});
|
|
158
|
+
const cacheWithoutTTL = {
|
|
159
|
+
gateways: ['https://cached.com'],
|
|
160
|
+
timestamp: Date.now() - 3700000, // Older than default TTL (3600s)
|
|
161
|
+
};
|
|
162
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', JSON.stringify(cacheWithoutTTL));
|
|
163
|
+
const result = await provider.getGateways();
|
|
164
|
+
assert.strictEqual(result.length, 2);
|
|
165
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe('error handling', () => {
|
|
169
|
+
it('should handle JSON parse errors gracefully', async () => {
|
|
170
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
171
|
+
ttlSeconds: 300,
|
|
172
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
173
|
+
});
|
|
174
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', 'invalid-json');
|
|
175
|
+
const result = await provider.getGateways();
|
|
176
|
+
assert.strictEqual(result.length, 2);
|
|
177
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
178
|
+
});
|
|
179
|
+
it('should handle localStorage getItem errors', async () => {
|
|
180
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
181
|
+
ttlSeconds: 300,
|
|
182
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
183
|
+
});
|
|
184
|
+
mockLocalStorage.getItem = () => {
|
|
185
|
+
throw new Error('Storage error');
|
|
186
|
+
};
|
|
187
|
+
const result = await provider.getGateways();
|
|
188
|
+
assert.strictEqual(result.length, 2);
|
|
189
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
190
|
+
});
|
|
191
|
+
it('should handle localStorage setItem errors gracefully', async () => {
|
|
192
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
193
|
+
ttlSeconds: 300,
|
|
194
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
195
|
+
});
|
|
196
|
+
mockLocalStorage.setItem = () => {
|
|
197
|
+
throw new Error('Storage quota exceeded');
|
|
198
|
+
};
|
|
199
|
+
const result = await provider.getGateways();
|
|
200
|
+
assert.strictEqual(result.length, 2);
|
|
201
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
describe('non-browser environment', () => {
|
|
205
|
+
it('should work when window is undefined', async () => {
|
|
206
|
+
teardownBrowserEnvironment();
|
|
207
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
208
|
+
ttlSeconds: 300,
|
|
209
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
210
|
+
});
|
|
211
|
+
const result = await provider.getGateways();
|
|
212
|
+
assert.strictEqual(result.length, 2);
|
|
213
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
214
|
+
setupBrowserEnvironment();
|
|
215
|
+
});
|
|
216
|
+
it('should work when localStorage is undefined', async () => {
|
|
217
|
+
// @ts-expect-error - Mock window without localStorage
|
|
218
|
+
global.window = {};
|
|
219
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
220
|
+
ttlSeconds: 300,
|
|
221
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
222
|
+
});
|
|
223
|
+
const result = await provider.getGateways();
|
|
224
|
+
assert.strictEqual(result.length, 2);
|
|
225
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe('clearCache', () => {
|
|
230
|
+
it('should remove cache from localStorage', () => {
|
|
231
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
232
|
+
ttlSeconds: 300,
|
|
233
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
234
|
+
});
|
|
235
|
+
const cachedGateways = {
|
|
236
|
+
gateways: ['https://cached.com'],
|
|
237
|
+
timestamp: Date.now(),
|
|
238
|
+
ttlSeconds: 300,
|
|
239
|
+
};
|
|
240
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', JSON.stringify(cachedGateways));
|
|
241
|
+
assert.ok(mockLocalStorage.getItem('wayfinder-gateways-cache'));
|
|
242
|
+
provider.clearCache();
|
|
243
|
+
assert.strictEqual(mockLocalStorage.getItem('wayfinder-gateways-cache'), null);
|
|
244
|
+
});
|
|
245
|
+
it('should handle localStorage removeItem errors gracefully', () => {
|
|
246
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
247
|
+
ttlSeconds: 300,
|
|
248
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
249
|
+
});
|
|
250
|
+
mockLocalStorage.removeItem = () => {
|
|
251
|
+
throw new Error('Storage error');
|
|
252
|
+
};
|
|
253
|
+
assert.doesNotThrow(() => {
|
|
254
|
+
provider.clearCache();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
it('should handle non-browser environment gracefully', () => {
|
|
258
|
+
teardownBrowserEnvironment();
|
|
259
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
260
|
+
ttlSeconds: 300,
|
|
261
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
262
|
+
});
|
|
263
|
+
assert.doesNotThrow(() => {
|
|
264
|
+
provider.clearCache();
|
|
265
|
+
});
|
|
266
|
+
setupBrowserEnvironment();
|
|
267
|
+
});
|
|
268
|
+
it('should handle missing localStorage gracefully', () => {
|
|
269
|
+
// @ts-expect-error - Mock window without localStorage
|
|
270
|
+
global.window = {};
|
|
271
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
272
|
+
ttlSeconds: 300,
|
|
273
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
274
|
+
});
|
|
275
|
+
assert.doesNotThrow(() => {
|
|
276
|
+
provider.clearCache();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
describe('cache validation', () => {
|
|
281
|
+
it('should consider cache valid when within TTL', async () => {
|
|
282
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
283
|
+
ttlSeconds: 300,
|
|
284
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
285
|
+
});
|
|
286
|
+
const recentCache = {
|
|
287
|
+
gateways: ['https://recent.com'],
|
|
288
|
+
timestamp: Date.now() - 100000, // 100 seconds ago
|
|
289
|
+
ttlSeconds: 300,
|
|
290
|
+
};
|
|
291
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', JSON.stringify(recentCache));
|
|
292
|
+
const result = await provider.getGateways();
|
|
293
|
+
assert.strictEqual(result[0].toString(), 'https://recent.com/');
|
|
294
|
+
});
|
|
295
|
+
it('should consider cache invalid when beyond TTL', async () => {
|
|
296
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
297
|
+
ttlSeconds: 300,
|
|
298
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
299
|
+
});
|
|
300
|
+
const oldCache = {
|
|
301
|
+
gateways: ['https://old.com'],
|
|
302
|
+
timestamp: Date.now() - 400000, // 400 seconds ago
|
|
303
|
+
ttlSeconds: 300,
|
|
304
|
+
};
|
|
305
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', JSON.stringify(oldCache));
|
|
306
|
+
const result = await provider.getGateways();
|
|
307
|
+
assert.strictEqual(result[0].toString(), 'https://gateway1.com/');
|
|
308
|
+
});
|
|
309
|
+
it('should handle edge case when cache timestamp equals current time', async () => {
|
|
310
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
311
|
+
ttlSeconds: 300,
|
|
312
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
313
|
+
});
|
|
314
|
+
const currentTime = Date.now();
|
|
315
|
+
const exactCache = {
|
|
316
|
+
gateways: ['https://exact.com'],
|
|
317
|
+
timestamp: currentTime,
|
|
318
|
+
ttlSeconds: 300,
|
|
319
|
+
};
|
|
320
|
+
mockLocalStorage.setItem('wayfinder-gateways-cache', JSON.stringify(exactCache));
|
|
321
|
+
const result = await provider.getGateways();
|
|
322
|
+
assert.strictEqual(result[0].toString(), 'https://exact.com/');
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
describe('integration scenarios', () => {
|
|
326
|
+
it('should cache results after fetching from provider', async () => {
|
|
327
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
328
|
+
ttlSeconds: 600,
|
|
329
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
330
|
+
});
|
|
331
|
+
await provider.getGateways();
|
|
332
|
+
const cached = mockLocalStorage.getItem('wayfinder-gateways-cache');
|
|
333
|
+
assert.ok(cached);
|
|
334
|
+
const parsedCache = JSON.parse(cached);
|
|
335
|
+
assert.strictEqual(parsedCache.gateways.length, 2);
|
|
336
|
+
assert.strictEqual(parsedCache.gateways[0], 'https://gateway1.com/');
|
|
337
|
+
assert.strictEqual(parsedCache.gateways[1], 'https://gateway2.com/');
|
|
338
|
+
assert.strictEqual(parsedCache.ttlSeconds, 600);
|
|
339
|
+
assert.ok(typeof parsedCache.timestamp === 'number');
|
|
340
|
+
});
|
|
341
|
+
it('should work with different gateway providers', async () => {
|
|
342
|
+
const customProvider = new MockGatewaysProvider([
|
|
343
|
+
'https://custom1.com',
|
|
344
|
+
'https://custom2.com',
|
|
345
|
+
'https://custom3.com',
|
|
346
|
+
]);
|
|
347
|
+
const provider = new LocalStorageGatewaysProvider({
|
|
348
|
+
ttlSeconds: 300,
|
|
349
|
+
gatewaysProvider: customProvider,
|
|
350
|
+
});
|
|
351
|
+
const result = await provider.getGateways();
|
|
352
|
+
assert.strictEqual(result.length, 3);
|
|
353
|
+
assert.strictEqual(result[0].toString(), 'https://custom1.com/');
|
|
354
|
+
assert.strictEqual(result[1].toString(), 'https://custom2.com/');
|
|
355
|
+
assert.strictEqual(result[2].toString(), 'https://custom3.com/');
|
|
356
|
+
});
|
|
357
|
+
it('should maintain separate cache instances', async () => {
|
|
358
|
+
const provider1 = new LocalStorageGatewaysProvider({
|
|
359
|
+
ttlSeconds: 300,
|
|
360
|
+
gatewaysProvider: mockGatewaysProvider,
|
|
361
|
+
});
|
|
362
|
+
const provider2 = new LocalStorageGatewaysProvider({
|
|
363
|
+
ttlSeconds: 600,
|
|
364
|
+
gatewaysProvider: new MockGatewaysProvider(['https://different.com']),
|
|
365
|
+
});
|
|
366
|
+
await provider1.getGateways();
|
|
367
|
+
provider1.clearCache();
|
|
368
|
+
const result2 = await provider2.getGateways();
|
|
369
|
+
assert.strictEqual(result2[0].toString(), 'https://different.com/');
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ar.io/wayfinder-react",
|
|
3
|
-
"version": "1.0.2
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "React components for WayFinder",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"format:check": "biome format"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@ar.io/wayfinder-core": "1.0.1
|
|
33
|
+
"@ar.io/wayfinder-core": "1.0.1",
|
|
34
34
|
"react": "^18.2.0",
|
|
35
35
|
"react-dom": "^18.2.0"
|
|
36
36
|
},
|