@ar.io/wayfinder-react 0.0.5-alpha.10 → 0.0.5-alpha.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.
package/README.md CHANGED
@@ -23,9 +23,8 @@ yarn add @ar.io/wayfinder-react @ar.io/wayfinder-core
23
23
  ```jsx
24
24
  import {
25
25
  WayfinderProvider,
26
+ useWayfinder,
26
27
  useWayfinderRequest,
27
- useWayfinderUrl,
28
- LocalStorageGatewaysProvider,
29
28
  } from '@ar.io/wayfinder-react';
30
29
  import { NetworkGatewaysProvider } from '@ar.io/wayfinder-core';
31
30
  import { ARIO } from '@ar.io/sdk';
@@ -36,14 +35,7 @@ function App() {
36
35
  <WayfinderProvider
37
36
  // pass in the wayfinder options
38
37
  // https://github.com/ar-io/wayfinder/tree/alpha/packages/core#custom-configuration
39
- gatewaysProvider={new LocalStorageGatewaysProvider({
40
- ttlSeconds: 3600, // cache the gateways locally for 1 hour to avoid unnecessary network requests
41
- gatewaysProvider: new NetworkGatewaysProvider({
42
- ario: ARIO.mainnet()
43
- limit: 10,
44
- sortBy: 'operatorStake',
45
- }),
46
- })}
38
+ gatewaysProvider={new NetworkGatewaysProvider({ ario: ARIO.mainnet() })}
47
39
  >
48
40
  <YourApp />
49
41
  </WayfinderProvider>
@@ -52,48 +44,24 @@ function App() {
52
44
 
53
45
  // Use components
54
46
  function YourComponent() {
55
- const txId = 'your-transaction-id'; // Replace with actual txId
56
-
57
- // Use custom hooks for URL resolution and data fetching
58
- const request = useWayfinderRequest();
59
- const { resolvedUrl, isLoading: urlLoading, error: urlError } = useWayfinderUrl({ txId });
47
+ const { wayfinder } = useWayfinder();
48
+ const [txData, setTxData] = useState<string | null>(null);
60
49
 
61
- // Use custom hooks for data fetching
62
- const [data, setData] = useState<any>(null);
63
- const [dataLoading, setDataLoading] = useState(false);
64
- const [dataError, setDataError] = useState<Error | null>(null);
50
+ // useMemo to get a resolution URL for a given txId
51
+ const wayfinderUrl = useMemo(() => wayfinder.resolveUrl(`ar://${txId}`), [txId, wayfinder]);
65
52
 
53
+ // request some data from arweave via wayfinder
66
54
  useEffect(() => {
67
55
  (async () => {
68
- try {
69
- setDataLoading(true);
70
- setDataError(null);
71
- // fetch the data for the txId using wayfinder
72
- const response = await request(`ar://${txId}`, {
73
- verificationSettings: {
74
- enabled: true, // enable verification on the request
75
- strict: true, // don't use the data if it's not verified
76
- },
77
- });
78
- const data = await response.arrayBuffer(); // or response.json() if you want to parse the data as JSON
79
- setData(data);
80
- } catch (error) {
81
- setDataError(error as Error);
82
- } finally {
83
- setDataLoading(false);
84
- }
56
+ const res = await wayfinder.request(`ar://${txId}`);
57
+ setTxData(await res.text());
85
58
  })();
86
- }, [request, txId]);
59
+ }, [txId, wayfinder]);
87
60
 
88
61
  return (
89
62
  <div>
90
- {urlLoading && <p>Resolving URL...</p>}
91
- {urlError && <p>Error resolving URL: {urlError.message}</p>}
92
- {resolvedUrl && <a href={resolvedUrl}>View on WayFinder</a>}
93
- <br />
94
- {dataLoading && <p>Loading data...</p>}
95
- {dataError && <p>Error loading data: {dataError.message}</p>}
96
- <pre>{data}</pre>
63
+ <a href={wayfinderUrl}>View on WayFinder</a>
64
+ <pre>{txData}</pre>
97
65
  </div>
98
66
  );
99
67
  }
@@ -14,28 +14,6 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { Wayfinder } from '@ar.io/wayfinder-core';
18
17
  import { type WayfinderContextValue } from '../components/wayfinder-provider.js';
19
- /**
20
- * Hook for getting the Wayfinder instance
21
- * @returns Wayfinder instance
22
- */
23
18
  export declare const useWayfinder: () => WayfinderContextValue;
24
- /**
25
- * Hook for getting the Wayfinder request function
26
- * @returns Wayfinder request function
27
- */
28
- export declare const useWayfinderRequest: () => Wayfinder["request"];
29
- /**
30
- * Hook for resolving a transaction ID to a WayFinder URL using the Wayfinder instance (e.g. txId -> https://<some-gateway>/txId)
31
- * @param txId - The transaction ID to resolve
32
- * @returns Object containing the resolved URL and loading state
33
- */
34
- export declare const useWayfinderUrl: ({ txId }: {
35
- txId: string;
36
- }) => {
37
- resolvedUrl: string | null;
38
- isLoading: boolean;
39
- error: Error | null;
40
- txId: string;
41
- };
19
+ export declare const useWayfinderRequest: () => typeof fetch;
@@ -14,12 +14,8 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { useContext, useEffect, useState } from 'react';
17
+ import { useContext } from 'react';
18
18
  import { WayfinderContext, } from '../components/wayfinder-provider.js';
19
- /**
20
- * Hook for getting the Wayfinder instance
21
- * @returns Wayfinder instance
22
- */
23
19
  export const useWayfinder = () => {
24
20
  const context = useContext(WayfinderContext);
25
21
  if (!context) {
@@ -27,46 +23,7 @@ export const useWayfinder = () => {
27
23
  }
28
24
  return context;
29
25
  };
30
- /**
31
- * Hook for getting the Wayfinder request function
32
- * @returns Wayfinder request function
33
- */
34
26
  export const useWayfinderRequest = () => {
35
27
  const { wayfinder } = useWayfinder();
36
28
  return wayfinder.request;
37
29
  };
38
- /**
39
- * Hook for resolving a transaction ID to a WayFinder URL using the Wayfinder instance (e.g. txId -> https://<some-gateway>/txId)
40
- * @param txId - The transaction ID to resolve
41
- * @returns Object containing the resolved URL and loading state
42
- */
43
- export const useWayfinderUrl = ({ txId }) => {
44
- const { wayfinder } = useWayfinder();
45
- const [resolvedUrl, setResolvedUrl] = useState(null);
46
- const [isLoading, setIsLoading] = useState(false);
47
- const [error, setError] = useState(null);
48
- useEffect(() => {
49
- if (!txId) {
50
- setResolvedUrl(null);
51
- setError(null);
52
- return;
53
- }
54
- setIsLoading(true);
55
- setError(null);
56
- (async () => {
57
- try {
58
- const resolved = await wayfinder.resolveUrl({
59
- originalUrl: `ar://${txId}`,
60
- });
61
- setResolvedUrl(resolved.toString());
62
- }
63
- catch (err) {
64
- setError(err instanceof Error ? err : new Error('Failed to resolve URL'));
65
- }
66
- finally {
67
- setIsLoading(false);
68
- }
69
- })();
70
- }, [txId, wayfinder]);
71
- return { resolvedUrl, isLoading, error, txId };
72
- };
package/dist/index.d.ts CHANGED
@@ -16,4 +16,3 @@
16
16
  */
17
17
  export * from './components/index.js';
18
18
  export * from './hooks/index.js';
19
- export * from './cache/index.js';
package/dist/index.js CHANGED
@@ -16,4 +16,3 @@
16
16
  */
17
17
  export * from './components/index.js';
18
18
  export * from './hooks/index.js';
19
- export * from './cache/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ar.io/wayfinder-react",
3
- "version": "0.0.5-alpha.10",
3
+ "version": "0.0.5-alpha.2",
4
4
  "description": "React components for WayFinder",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -10,7 +10,12 @@
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
- "files": ["dist", "package.json", "README.md", "LICENSE"],
13
+ "files": [
14
+ "dist",
15
+ "package.json",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
14
19
  "author": {
15
20
  "name": "Permanent Data Solutions Inc",
16
21
  "email": "info@ar.io",
@@ -23,14 +28,14 @@
23
28
  "scripts": {
24
29
  "build": "tsc",
25
30
  "clean": "rimraf dist",
26
- "test": "tsx --test 'src/**/*.test.ts'",
31
+ "test": "echo \"Passing\"",
27
32
  "lint:fix": "biome check --write --unsafe",
28
33
  "lint:check": "biome check --unsafe",
29
34
  "format:fix": "biome format --write",
30
35
  "format:check": "biome format"
31
36
  },
32
37
  "dependencies": {
33
- "@ar.io/wayfinder-core": "1.0.0-alpha.8",
38
+ "@ar.io/wayfinder-core": "0.0.5-alpha.1",
34
39
  "react": "^18.2.0",
35
40
  "react-dom": "^18.2.0"
36
41
  },
@@ -1,17 +0,0 @@
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';
@@ -1,17 +0,0 @@
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';
@@ -1,32 +0,0 @@
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
- }
@@ -1,69 +0,0 @@
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
- return cacheAge < ttlMs;
41
- }
42
- cacheGateways(gateways) {
43
- try {
44
- if (typeof window === 'undefined' || !window.localStorage) {
45
- return;
46
- }
47
- const cached = {
48
- gateways: gateways.map((gateway) => gateway.toString()),
49
- timestamp: Date.now(),
50
- ttlSeconds: this.ttlSeconds,
51
- };
52
- window.localStorage.setItem(this.storageKey, JSON.stringify(cached));
53
- }
54
- catch (error) {
55
- console.warn('Failed to cache gateways:', error);
56
- }
57
- }
58
- clearCache() {
59
- try {
60
- if (typeof window === 'undefined' || !window.localStorage) {
61
- return;
62
- }
63
- window.localStorage.removeItem(this.storageKey);
64
- }
65
- catch (error) {
66
- console.warn('Failed to clear gateway cache:', error);
67
- }
68
- }
69
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,372 +0,0 @@
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
- });