@quiltt/core 4.2.3 → 4.3.1
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/CHANGELOG.md +12 -0
- package/dist/{SubscriptionLink-client-BNmSIP63.js → SubscriptionLink-12s-C9j_SmFl.js} +159 -67
- package/dist/index.d.ts +67 -47
- package/dist/index.js +11 -15
- package/package.json +8 -8
- package/src/Observable.ts +6 -3
- package/src/Timeoutable.ts +2 -1
- package/src/api/browser.ts +9 -4
- package/src/api/graphql/client.ts +16 -5
- package/src/api/graphql/index.ts +1 -1
- package/src/api/graphql/links/ActionCableLink.ts +1 -2
- package/src/api/graphql/links/BatchHttpLink.ts +2 -2
- package/src/api/rest/institutions.ts +12 -12
- package/src/index.ts +1 -1
- package/src/storage/Local.ts +130 -30
- package/src/storage/Memory.ts +1 -1
- package/src/storage/Storage.ts +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @quiltt/core
|
|
2
2
|
|
|
3
|
+
## 4.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#366](https://github.com/quiltt/quiltt-js/pull/366) [`dc376b5`](https://github.com/quiltt/quiltt-js/commit/dc376b52dd824d7867ca74677bbfd5c54eff5cdc) Thanks [@sirwolfgang](https://github.com/sirwolfgang)! - Warn if useQuilttConnector is unmounted while in use
|
|
8
|
+
|
|
9
|
+
## 4.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#363](https://github.com/quiltt/quiltt-js/pull/363) [`641d766`](https://github.com/quiltt/quiltt-js/commit/641d76620ffbb99bc80fdc9998ac936883fe1d06) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Upgrade rails/actioncable to v8
|
|
14
|
+
|
|
3
15
|
## 4.2.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -4,75 +4,196 @@ import { Observable as Observable$1 } from '@apollo/client/utilities/index.js';
|
|
|
4
4
|
import { createConsumer } from '@rails/actioncable';
|
|
5
5
|
import { print } from 'graphql';
|
|
6
6
|
|
|
7
|
+
var name = "@quiltt/core";
|
|
8
|
+
var version$1 = "4.3.1";
|
|
9
|
+
|
|
10
|
+
const QUILTT_API_INSECURE = (()=>{
|
|
11
|
+
try {
|
|
12
|
+
return process.env.QUILTT_API_INSECURE === 'true';
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
const QUILTT_API_DOMAIN = (()=>{
|
|
18
|
+
try {
|
|
19
|
+
return process.env.QUILTT_API_DOMAIN;
|
|
20
|
+
} catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
const QUILTT_DEBUG = (()=>{
|
|
25
|
+
try {
|
|
26
|
+
return process.env.NODE_ENV !== 'production' && process.env.QUILTT_DEBUG === 'true';
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
})();
|
|
31
|
+
const domain = QUILTT_API_DOMAIN || 'quiltt.io';
|
|
32
|
+
const protocolHttp = `http${QUILTT_API_INSECURE ? '' : 's'}`;
|
|
33
|
+
const protocolWebsockets = `ws${QUILTT_API_INSECURE ? '' : 's'}`;
|
|
34
|
+
const debugging = QUILTT_DEBUG;
|
|
35
|
+
const version = `${name}: v${version$1}`;
|
|
36
|
+
const cdnBase = `${protocolHttp}://cdn.${domain}`;
|
|
37
|
+
const endpointAuth = `${protocolHttp}://auth.${domain}/v1/users/session`;
|
|
38
|
+
const endpointGraphQL = `${protocolHttp}://api.${domain}/v1/graphql`;
|
|
39
|
+
const endpointRest = `${protocolHttp}://api.${domain}/v1`;
|
|
40
|
+
const endpointWebsockets = `${protocolWebsockets}://api.${domain}/websockets`;
|
|
41
|
+
|
|
7
42
|
/**
|
|
8
43
|
* An error and type safe wrapper for localStorage.
|
|
9
44
|
* It allows you to subscribe to changes;
|
|
10
|
-
* but localStorage changes only fire
|
|
45
|
+
* but localStorage changes only fire when another
|
|
11
46
|
* window updates the record.
|
|
12
47
|
*/ class LocalStorage {
|
|
13
|
-
constructor(){
|
|
48
|
+
constructor(keyPrefix = 'quiltt'){
|
|
14
49
|
this.observers = {};
|
|
15
50
|
this.isEnabled = ()=>{
|
|
16
51
|
try {
|
|
17
|
-
|
|
18
|
-
localStorage.
|
|
52
|
+
const testKey = `${this.keyPrefix}.ping`;
|
|
53
|
+
localStorage.setItem(testKey, 'pong');
|
|
54
|
+
localStorage.removeItem(testKey);
|
|
19
55
|
return true;
|
|
20
|
-
} catch (
|
|
56
|
+
} catch (_error) {
|
|
21
57
|
return false;
|
|
22
58
|
}
|
|
23
59
|
};
|
|
24
60
|
this.isDisabled = ()=>!this.isEnabled();
|
|
25
61
|
this.get = (key)=>{
|
|
26
|
-
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined')
|
|
62
|
+
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const fullKey = this.getFullKey(key);
|
|
27
66
|
try {
|
|
28
|
-
const state = window.localStorage.getItem(
|
|
67
|
+
const state = window.localStorage.getItem(fullKey);
|
|
29
68
|
return state ? JSON.parse(state) : null;
|
|
30
69
|
} catch (error) {
|
|
31
|
-
console.warn(`localStorage Error: "
|
|
70
|
+
console.warn(`localStorage Error: "${fullKey}"`, error);
|
|
32
71
|
return undefined;
|
|
33
72
|
}
|
|
34
73
|
};
|
|
35
74
|
this.set = (key, state)=>{
|
|
36
75
|
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') return;
|
|
76
|
+
const fullKey = this.getFullKey(key);
|
|
37
77
|
try {
|
|
38
|
-
if (state) {
|
|
39
|
-
window.localStorage.setItem(
|
|
78
|
+
if (state !== null && state !== undefined) {
|
|
79
|
+
window.localStorage.setItem(fullKey, JSON.stringify(state));
|
|
40
80
|
} else {
|
|
41
|
-
window.localStorage.removeItem(
|
|
81
|
+
window.localStorage.removeItem(fullKey);
|
|
42
82
|
}
|
|
43
83
|
} catch (error) {
|
|
44
|
-
console.warn(`localStorage Error: "
|
|
84
|
+
console.warn(`localStorage Error: "${fullKey}"`, error);
|
|
45
85
|
}
|
|
46
86
|
};
|
|
47
87
|
this.remove = (key)=>{
|
|
88
|
+
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') return;
|
|
89
|
+
const fullKey = this.getFullKey(key);
|
|
90
|
+
try {
|
|
91
|
+
window.localStorage.removeItem(fullKey);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn(`localStorage Error: "${fullKey}"`, error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
this.has = (key)=>{
|
|
97
|
+
return this.get(key) !== null && this.get(key) !== undefined;
|
|
98
|
+
};
|
|
99
|
+
this.clear = ()=>{
|
|
48
100
|
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') return;
|
|
49
101
|
try {
|
|
50
|
-
|
|
102
|
+
const keysToRemove = [];
|
|
103
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
104
|
+
const key = localStorage.key(i);
|
|
105
|
+
if (key?.startsWith(`${this.keyPrefix}.`)) {
|
|
106
|
+
keysToRemove.push(key);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
keysToRemove.forEach((key)=>{
|
|
110
|
+
localStorage.removeItem(key);
|
|
111
|
+
});
|
|
51
112
|
} catch (error) {
|
|
52
|
-
console.warn(`localStorage Error
|
|
113
|
+
console.warn(`localStorage Error during clear`, error);
|
|
53
114
|
}
|
|
54
115
|
};
|
|
55
116
|
this.subscribe = (key, observer)=>{
|
|
56
|
-
|
|
57
|
-
this.observers[
|
|
117
|
+
const fullKey = this.getFullKey(key);
|
|
118
|
+
if (!this.observers[fullKey]) {
|
|
119
|
+
this.observers[fullKey] = [];
|
|
120
|
+
}
|
|
121
|
+
this.observers[fullKey].push(observer);
|
|
122
|
+
// Return unsubscribe function
|
|
123
|
+
return ()=>this.unsubscribe(key, observer);
|
|
58
124
|
};
|
|
59
125
|
this.unsubscribe = (key, observer)=>{
|
|
60
|
-
|
|
126
|
+
const fullKey = this.getFullKey(key);
|
|
127
|
+
if (this.observers[fullKey]) {
|
|
128
|
+
this.observers[fullKey] = this.observers[fullKey].filter((update)=>update !== observer);
|
|
129
|
+
// Clean up empty observer arrays
|
|
130
|
+
if (this.observers[fullKey].length === 0) {
|
|
131
|
+
delete this.observers[fullKey];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
61
134
|
};
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
135
|
+
// Get all keys that match quiltt prefix
|
|
136
|
+
this.keys = ()=>{
|
|
137
|
+
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
const keys = [];
|
|
141
|
+
try {
|
|
142
|
+
for(let i = 0; i < localStorage.length; i++){
|
|
143
|
+
const key = localStorage.key(i);
|
|
144
|
+
if (key?.startsWith(`${this.keyPrefix}.`)) {
|
|
145
|
+
keys.push(key.replace(`${this.keyPrefix}.`, ''));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.warn('localStorage Error getting keys', error);
|
|
150
|
+
}
|
|
151
|
+
return keys;
|
|
152
|
+
};
|
|
153
|
+
this.getFullKey = (key)=>{
|
|
154
|
+
return `${this.keyPrefix}.${key}`;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Handle storage events from other windows/tabs
|
|
158
|
+
* If there is a key, then trigger the related updates. If there is no key
|
|
159
|
+
* it means that a record has been removed and everything needs to be rechecked.
|
|
160
|
+
*/ this.handleStorageEvent = (event)=>{
|
|
161
|
+
const isQuilttKey = event.key?.startsWith(`${this.keyPrefix}.`);
|
|
162
|
+
if (isQuilttKey && event.key) {
|
|
163
|
+
// Parse the new value safely
|
|
164
|
+
let newState = null;
|
|
165
|
+
try {
|
|
166
|
+
newState = event.newValue ? JSON.parse(event.newValue) : null;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.warn(`Failed to parse storage event value for ${event.key}`, error);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
67
171
|
if (this.observers[event.key]) {
|
|
68
|
-
this.observers[event.key].forEach((
|
|
172
|
+
this.observers[event.key].forEach((observer)=>{
|
|
173
|
+
try {
|
|
174
|
+
observer(newState);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.warn(`Observer error for key ${event.key}`, error);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
69
179
|
}
|
|
70
|
-
} else {
|
|
71
|
-
|
|
72
|
-
|
|
180
|
+
} else if (!event.key) {
|
|
181
|
+
// Storage was cleared or changed in a way that doesn't specify a key
|
|
182
|
+
// Re-check all observed keys
|
|
183
|
+
Object.entries(this.observers).forEach(([fullKey, observers])=>{
|
|
184
|
+
const shortKey = fullKey.replace(`${this.keyPrefix}.`, '');
|
|
185
|
+
const currentValue = this.get(shortKey);
|
|
186
|
+
observers.forEach((observer)=>{
|
|
187
|
+
try {
|
|
188
|
+
observer(currentValue);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.warn(`Observer error for key ${fullKey}`, error);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
73
193
|
});
|
|
74
194
|
}
|
|
75
195
|
};
|
|
196
|
+
this.keyPrefix = keyPrefix;
|
|
76
197
|
if (typeof window !== 'undefined' && typeof window.addEventListener !== 'undefined') {
|
|
77
198
|
window.addEventListener('storage', this.handleStorageEvent.bind(this));
|
|
78
199
|
}
|
|
@@ -84,7 +205,7 @@ import { print } from 'graphql';
|
|
|
84
205
|
* instance of hooks to ensure that updates only process once, by storing a value
|
|
85
206
|
* then notifying all subscribers when it's updated.
|
|
86
207
|
*/ class Observable {
|
|
87
|
-
constructor(
|
|
208
|
+
constructor(initialState){
|
|
88
209
|
this.observers = [];
|
|
89
210
|
this.get = ()=>{
|
|
90
211
|
return this.state;
|
|
@@ -92,7 +213,9 @@ import { print } from 'graphql';
|
|
|
92
213
|
this.set = (nextState)=>{
|
|
93
214
|
if (this.state === nextState) return;
|
|
94
215
|
this.state = nextState;
|
|
95
|
-
this.observers.forEach((update)=>
|
|
216
|
+
this.observers.forEach((update)=>{
|
|
217
|
+
update(nextState);
|
|
218
|
+
});
|
|
96
219
|
};
|
|
97
220
|
this.subscribe = (observer)=>{
|
|
98
221
|
this.observers.push(observer);
|
|
@@ -100,7 +223,7 @@ import { print } from 'graphql';
|
|
|
100
223
|
this.unsubscribe = (observer)=>{
|
|
101
224
|
this.observers = this.observers.filter((update)=>update !== observer);
|
|
102
225
|
};
|
|
103
|
-
this.state =
|
|
226
|
+
this.state = initialState;
|
|
104
227
|
}
|
|
105
228
|
}
|
|
106
229
|
|
|
@@ -165,7 +288,9 @@ import { print } from 'graphql';
|
|
|
165
288
|
this.monitorLocalStorageChanges(key);
|
|
166
289
|
this.localStore.set(key, newState);
|
|
167
290
|
this.memoryStore.set(key, newState);
|
|
168
|
-
this.observers[key]?.forEach((update)=>
|
|
291
|
+
this.observers[key]?.forEach((update)=>{
|
|
292
|
+
update(newState);
|
|
293
|
+
});
|
|
169
294
|
};
|
|
170
295
|
/**
|
|
171
296
|
* Allows you to subscribe to all changes in memory or local storage as a
|
|
@@ -193,7 +318,9 @@ import { print } from 'graphql';
|
|
|
193
318
|
const prevValue = this.memoryStore.get(key);
|
|
194
319
|
const newState = nextState instanceof Function ? nextState(prevValue) : nextState;
|
|
195
320
|
this.memoryStore.set(key, newState);
|
|
196
|
-
this.observers[key]?.forEach((update)=>
|
|
321
|
+
this.observers[key]?.forEach((update)=>{
|
|
322
|
+
update(newState);
|
|
323
|
+
});
|
|
197
324
|
});
|
|
198
325
|
};
|
|
199
326
|
}
|
|
@@ -203,41 +330,6 @@ import { print } from 'graphql';
|
|
|
203
330
|
* basically acts like shared memory when there is no localStorage.
|
|
204
331
|
*/ const GlobalStorage = new Storage();
|
|
205
332
|
|
|
206
|
-
var name = "@quiltt/core";
|
|
207
|
-
var version$1 = "4.2.3";
|
|
208
|
-
|
|
209
|
-
const QUILTT_API_INSECURE = (()=>{
|
|
210
|
-
try {
|
|
211
|
-
return process.env.QUILTT_API_INSECURE === 'true';
|
|
212
|
-
} catch {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
})();
|
|
216
|
-
const QUILTT_API_DOMAIN = (()=>{
|
|
217
|
-
try {
|
|
218
|
-
return process.env.QUILTT_API_DOMAIN;
|
|
219
|
-
} catch {
|
|
220
|
-
return undefined;
|
|
221
|
-
}
|
|
222
|
-
})();
|
|
223
|
-
const QUILTT_DEBUG = (()=>{
|
|
224
|
-
try {
|
|
225
|
-
return process.env.NODE_ENV !== 'production' && process.env.QUILTT_DEBUG === 'true';
|
|
226
|
-
} catch {
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
})();
|
|
230
|
-
const domain = QUILTT_API_DOMAIN || 'quiltt.io';
|
|
231
|
-
const protocolHttp = `http${QUILTT_API_INSECURE ? '' : 's'}`;
|
|
232
|
-
const protocolWebsockets = `ws${QUILTT_API_INSECURE ? '' : 's'}`;
|
|
233
|
-
const debugging = QUILTT_DEBUG;
|
|
234
|
-
const version = `${name}: v${version$1}`;
|
|
235
|
-
const cdnBase = `${protocolHttp}://cdn.${domain}`;
|
|
236
|
-
const endpointAuth = `${protocolHttp}://auth.${domain}/v1/users/session`;
|
|
237
|
-
const endpointGraphQL = `${protocolHttp}://api.${domain}/v1/graphql`;
|
|
238
|
-
const endpointRest = `${protocolHttp}://api.${domain}/v1`;
|
|
239
|
-
const endpointWebsockets = `${protocolWebsockets}://api.${domain}/websockets`;
|
|
240
|
-
|
|
241
333
|
class ActionCableLink extends ApolloLink {
|
|
242
334
|
constructor(options){
|
|
243
335
|
super();
|
|
@@ -299,4 +391,4 @@ class SubscriptionLink extends ActionCableLink {
|
|
|
299
391
|
}
|
|
300
392
|
}
|
|
301
393
|
|
|
302
|
-
export { GlobalStorage as G, LocalStorage as L, MemoryStorage as M, Observable as O, SubscriptionLink as S, endpointAuth as a, endpointRest as b,
|
|
394
|
+
export { GlobalStorage as G, LocalStorage as L, MemoryStorage as M, Observable as O, SubscriptionLink as S, endpointAuth as a, endpointRest as b, cdnBase as c, debugging as d, endpointGraphQL as e, endpointWebsockets as f, Storage as g, version as v };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _apollo_client_core from '@apollo/client/core';
|
|
2
|
-
import {
|
|
2
|
+
import { ApolloClientOptions, NormalizedCacheObject, Operation, NextLink, FetchResult } from '@apollo/client/core';
|
|
3
3
|
export { ApolloError, OperationVariables } from '@apollo/client/core';
|
|
4
4
|
import { ApolloLink, ApolloClient } from '@apollo/client/core/index.js';
|
|
5
5
|
export { gql } from '@apollo/client/core/index.js';
|
|
@@ -10,8 +10,8 @@ import { RetryLink as RetryLink$1 } from '@apollo/client/link/retry/index.js';
|
|
|
10
10
|
import { Observable as Observable$2 } from '@apollo/client/utilities/index.js';
|
|
11
11
|
import { Consumer } from '@rails/actioncable';
|
|
12
12
|
import { Dispatch, SetStateAction } from 'react';
|
|
13
|
-
export { InMemoryCache } from '@apollo/client/cache/index.js';
|
|
14
13
|
export { NormalizedCacheObject } from '@apollo/client/cache';
|
|
14
|
+
export { InMemoryCache } from '@apollo/client/cache/index.js';
|
|
15
15
|
export { useMutation, useQuery, useSubscription } from '@apollo/client/react/hooks/index.js';
|
|
16
16
|
|
|
17
17
|
interface CallbackManager {
|
|
@@ -105,6 +105,10 @@ type ConnectorSDKCallbackMetadata = {
|
|
|
105
105
|
profileId?: string;
|
|
106
106
|
/** The ID of the Connection that was created or reconnected */
|
|
107
107
|
connectionId?: string;
|
|
108
|
+
/** The Connector Session Object */
|
|
109
|
+
connectorSession?: {
|
|
110
|
+
id: string;
|
|
111
|
+
};
|
|
108
112
|
};
|
|
109
113
|
/**
|
|
110
114
|
Options for the standard Connect flow
|
|
@@ -122,7 +126,11 @@ type ConnectorSDKReconnectOptions = ConnectorSDKCallbacks & {
|
|
|
122
126
|
/** The ID of the Connection to reconnect */
|
|
123
127
|
connectionId: string;
|
|
124
128
|
};
|
|
125
|
-
/** Options to initialize Connector
|
|
129
|
+
/** Options to initialize Connector
|
|
130
|
+
*
|
|
131
|
+
* @todo: refactor into a union type - it's either or.
|
|
132
|
+
* Union types only allow direct access to properties that exist on all branches, not properties unique to individual branches.
|
|
133
|
+
*/
|
|
126
134
|
type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
|
|
127
135
|
/** The Institution ID or search term to connect */
|
|
128
136
|
institution?: string;
|
|
@@ -132,6 +140,14 @@ type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
|
|
|
132
140
|
nonce?: string;
|
|
133
141
|
};
|
|
134
142
|
|
|
143
|
+
type QuilttClientOptions<T> = Omit<ApolloClientOptions<T>, 'link'> & {
|
|
144
|
+
/** An array of initial links to inject before the default Quiltt Links */
|
|
145
|
+
customLinks?: ApolloLink[];
|
|
146
|
+
};
|
|
147
|
+
declare class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
148
|
+
constructor(options: QuilttClientOptions<NormalizedCacheObject>);
|
|
149
|
+
}
|
|
150
|
+
|
|
135
151
|
/**
|
|
136
152
|
* unauthorizedCallback only triggers in the event the token is present, and
|
|
137
153
|
* returns the token; This allows sessions to be forgotten without race conditions
|
|
@@ -179,11 +195,6 @@ declare const TerminatingLink: ApolloLink;
|
|
|
179
195
|
|
|
180
196
|
declare const VersionLink: ApolloLink;
|
|
181
197
|
|
|
182
|
-
type QuilttClientOptions<T> = Omit<ApolloClientOptions<T>, 'link'>;
|
|
183
|
-
declare class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
184
|
-
constructor(options: QuilttClientOptions<NormalizedCacheObject>);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
198
|
type FetchResponse<T> = {
|
|
188
199
|
data: T;
|
|
189
200
|
status: number;
|
|
@@ -287,9 +298,16 @@ declare class InstitutionsAPI {
|
|
|
287
298
|
search: (token: string, connectorId: string, term: string, signal?: AbortSignal) => Promise<FetchResponse<Search>>;
|
|
288
299
|
private config;
|
|
289
300
|
private validateStatus;
|
|
290
|
-
private body;
|
|
291
301
|
}
|
|
292
302
|
|
|
303
|
+
declare const debugging: boolean;
|
|
304
|
+
declare const version: string;
|
|
305
|
+
declare const cdnBase: string;
|
|
306
|
+
declare const endpointAuth: string;
|
|
307
|
+
declare const endpointGraphQL: string;
|
|
308
|
+
declare const endpointRest: string;
|
|
309
|
+
declare const endpointWebsockets: string;
|
|
310
|
+
|
|
293
311
|
/** Utility types to extend default TS utilities */
|
|
294
312
|
type Maybe<T> = T | null;
|
|
295
313
|
type InputMaybe<T> = Maybe<T>;
|
|
@@ -317,6 +335,31 @@ type DeepReadonly<T> = T extends object ? {
|
|
|
317
335
|
[P in keyof T]: DeepReadonly<T[P]>;
|
|
318
336
|
} : T;
|
|
319
337
|
|
|
338
|
+
type RegisteredClaims = {
|
|
339
|
+
iss: string;
|
|
340
|
+
sub: string;
|
|
341
|
+
aud: string;
|
|
342
|
+
exp: number;
|
|
343
|
+
nbf: number;
|
|
344
|
+
iat: number;
|
|
345
|
+
jti: string;
|
|
346
|
+
};
|
|
347
|
+
type PrivateClaims = {
|
|
348
|
+
oid: string;
|
|
349
|
+
eid: string;
|
|
350
|
+
cid: string;
|
|
351
|
+
aid: string;
|
|
352
|
+
ver: number;
|
|
353
|
+
rol: 'basic' | 'core' | 'manager' | 'super-admin';
|
|
354
|
+
};
|
|
355
|
+
type Claims<T> = RegisteredClaims & T;
|
|
356
|
+
type JsonWebToken<T> = {
|
|
357
|
+
token: string;
|
|
358
|
+
claims: Claims<T>;
|
|
359
|
+
};
|
|
360
|
+
type QuilttJWT = JsonWebToken<PrivateClaims>;
|
|
361
|
+
declare const JsonWebTokenParse: <T>(token: Maybe<string> | undefined) => Maybe<JsonWebToken<T>> | undefined;
|
|
362
|
+
|
|
320
363
|
type Observer<T> = Dispatch<SetStateAction<Maybe<T> | undefined>>;
|
|
321
364
|
/**
|
|
322
365
|
* This is designed to support singletons to share the memory states across all
|
|
@@ -326,7 +369,7 @@ type Observer<T> = Dispatch<SetStateAction<Maybe<T> | undefined>>;
|
|
|
326
369
|
declare class Observable<T> {
|
|
327
370
|
private state?;
|
|
328
371
|
private observers;
|
|
329
|
-
constructor(
|
|
372
|
+
constructor(initialState?: Maybe<T>);
|
|
330
373
|
get: () => Maybe<T> | undefined;
|
|
331
374
|
set: (nextState: Maybe<T> | undefined) => void;
|
|
332
375
|
subscribe: (observer: Observer<T>) => void;
|
|
@@ -336,19 +379,29 @@ declare class Observable<T> {
|
|
|
336
379
|
/**
|
|
337
380
|
* An error and type safe wrapper for localStorage.
|
|
338
381
|
* It allows you to subscribe to changes;
|
|
339
|
-
* but localStorage changes only fire
|
|
382
|
+
* but localStorage changes only fire when another
|
|
340
383
|
* window updates the record.
|
|
341
384
|
*/
|
|
342
|
-
declare class LocalStorage<T> {
|
|
385
|
+
declare class LocalStorage<T = any> {
|
|
343
386
|
private observers;
|
|
344
|
-
|
|
387
|
+
private readonly keyPrefix;
|
|
388
|
+
constructor(keyPrefix?: string);
|
|
345
389
|
isEnabled: () => boolean;
|
|
346
390
|
isDisabled: () => boolean;
|
|
347
391
|
get: (key: string) => Maybe<T> | undefined;
|
|
348
392
|
set: (key: string, state: Maybe<T> | undefined) => void;
|
|
349
393
|
remove: (key: string) => void;
|
|
350
|
-
|
|
394
|
+
has: (key: string) => boolean;
|
|
395
|
+
clear: () => void;
|
|
396
|
+
subscribe: (key: string, observer: Observer<T>) => (() => void);
|
|
351
397
|
unsubscribe: (key: string, observer: Observer<T>) => void;
|
|
398
|
+
keys: () => string[];
|
|
399
|
+
private getFullKey;
|
|
400
|
+
/**
|
|
401
|
+
* Handle storage events from other windows/tabs
|
|
402
|
+
* If there is a key, then trigger the related updates. If there is no key
|
|
403
|
+
* it means that a record has been removed and everything needs to be rechecked.
|
|
404
|
+
*/
|
|
352
405
|
private handleStorageEvent;
|
|
353
406
|
}
|
|
354
407
|
|
|
@@ -410,39 +463,6 @@ declare class Storage<T> {
|
|
|
410
463
|
*/
|
|
411
464
|
declare const GlobalStorage: Storage<any>;
|
|
412
465
|
|
|
413
|
-
declare const debugging: boolean;
|
|
414
|
-
declare const version: string;
|
|
415
|
-
declare const cdnBase: string;
|
|
416
|
-
declare const endpointAuth: string;
|
|
417
|
-
declare const endpointGraphQL: string;
|
|
418
|
-
declare const endpointRest: string;
|
|
419
|
-
declare const endpointWebsockets: string;
|
|
420
|
-
|
|
421
|
-
type RegisteredClaims = {
|
|
422
|
-
iss: string;
|
|
423
|
-
sub: string;
|
|
424
|
-
aud: string;
|
|
425
|
-
exp: number;
|
|
426
|
-
nbf: number;
|
|
427
|
-
iat: number;
|
|
428
|
-
jti: string;
|
|
429
|
-
};
|
|
430
|
-
type PrivateClaims = {
|
|
431
|
-
oid: string;
|
|
432
|
-
eid: string;
|
|
433
|
-
cid: string;
|
|
434
|
-
aid: string;
|
|
435
|
-
ver: number;
|
|
436
|
-
rol: 'basic' | 'core' | 'manager' | 'super-admin';
|
|
437
|
-
};
|
|
438
|
-
type Claims<T> = RegisteredClaims & T;
|
|
439
|
-
type JsonWebToken<T> = {
|
|
440
|
-
token: string;
|
|
441
|
-
claims: Claims<T>;
|
|
442
|
-
};
|
|
443
|
-
type QuilttJWT = JsonWebToken<PrivateClaims>;
|
|
444
|
-
declare const JsonWebTokenParse: <T>(token: Maybe<string> | undefined) => Maybe<JsonWebToken<T>> | undefined;
|
|
445
|
-
|
|
446
466
|
/**
|
|
447
467
|
* This is designed to support singletons to timeouts that can broadcast
|
|
448
468
|
* to any observers, preventing race conditions with multiple timeouts.
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApolloLink, ApolloClient } from '@apollo/client/core/index.js';
|
|
2
2
|
export { gql } from '@apollo/client/core/index.js';
|
|
3
|
-
import { G as GlobalStorage, e as endpointGraphQL, v as version, d as debugging, S as SubscriptionLink, a as endpointAuth, b as endpointRest } from './SubscriptionLink-
|
|
4
|
-
export { L as LocalStorage, M as MemoryStorage, O as Observable,
|
|
3
|
+
import { G as GlobalStorage, e as endpointGraphQL, v as version, d as debugging, S as SubscriptionLink, a as endpointAuth, b as endpointRest } from './SubscriptionLink-12s-C9j_SmFl.js';
|
|
4
|
+
export { L as LocalStorage, M as MemoryStorage, O as Observable, g as Storage, c as cdnBase, f as endpointWebsockets } from './SubscriptionLink-12s-C9j_SmFl.js';
|
|
5
5
|
import { BatchHttpLink as BatchHttpLink$1 } from '@apollo/client/link/batch-http/index.js';
|
|
6
6
|
import crossfetch from 'cross-fetch';
|
|
7
7
|
import { onError } from '@apollo/client/link/error/index.js';
|
|
@@ -101,6 +101,9 @@ class QuilttClient extends ApolloClient {
|
|
|
101
101
|
enabled: options.devtools?.enabled ?? debugging
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
|
+
const initialLinks = options.customLinks ? [
|
|
105
|
+
...options.customLinks
|
|
106
|
+
] : [];
|
|
104
107
|
const isOperationDefinition = (def)=>def.kind === 'OperationDefinition';
|
|
105
108
|
const isSubscriptionOperation = (operation)=>{
|
|
106
109
|
return operation.query.definitions.some((definition)=>isOperationDefinition(definition) && definition.operation === 'subscription');
|
|
@@ -111,6 +114,7 @@ class QuilttClient extends ApolloClient {
|
|
|
111
114
|
const authLink = new AuthLink();
|
|
112
115
|
const subscriptionsLink = new SubscriptionLink();
|
|
113
116
|
const quilttLink = ApolloLink.from([
|
|
117
|
+
...initialLinks,
|
|
114
118
|
VersionLink,
|
|
115
119
|
authLink,
|
|
116
120
|
ErrorLink,
|
|
@@ -276,7 +280,7 @@ class InstitutionsAPI {
|
|
|
276
280
|
const headers = new Headers();
|
|
277
281
|
headers.set('Content-Type', 'application/json');
|
|
278
282
|
headers.set('Accept', 'application/json');
|
|
279
|
-
headers.set('
|
|
283
|
+
headers.set('Quiltt-SDK-Agent', this.agent);
|
|
280
284
|
headers.set('Authorization', `Bearer ${token}`);
|
|
281
285
|
return {
|
|
282
286
|
headers,
|
|
@@ -285,17 +289,6 @@ class InstitutionsAPI {
|
|
|
285
289
|
};
|
|
286
290
|
};
|
|
287
291
|
this.validateStatus = (status)=>status < 500 && status !== 429;
|
|
288
|
-
this.body = (payload)=>{
|
|
289
|
-
if (!this.clientId) {
|
|
290
|
-
console.error('Quiltt Client ID is not set. Unable to identify & authenticate');
|
|
291
|
-
}
|
|
292
|
-
return {
|
|
293
|
-
session: {
|
|
294
|
-
clientId: this.clientId,
|
|
295
|
-
...payload
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
};
|
|
299
292
|
this.clientId = clientId;
|
|
300
293
|
this.agent = agent;
|
|
301
294
|
}
|
|
@@ -329,7 +322,10 @@ const JsonWebTokenParse = (token)=>{
|
|
|
329
322
|
if (this.timeout) {
|
|
330
323
|
clearTimeout(this.timeout);
|
|
331
324
|
}
|
|
332
|
-
|
|
325
|
+
// Replace all observers with the new one
|
|
326
|
+
this.observers = [
|
|
327
|
+
callback
|
|
328
|
+
];
|
|
333
329
|
this.timeout = setTimeout(this.broadcast.bind(this), delay);
|
|
334
330
|
};
|
|
335
331
|
this.clear = (observer)=>{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiltt/core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"description": "Javascript API client and utilities for Quiltt",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"quiltt",
|
|
@@ -32,21 +32,21 @@
|
|
|
32
32
|
],
|
|
33
33
|
"main": "dist/index.js",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@apollo/client": "^3.
|
|
36
|
-
"@rails/actioncable": "^
|
|
35
|
+
"@apollo/client": "^3.14.0",
|
|
36
|
+
"@rails/actioncable": "^8.0.300",
|
|
37
37
|
"braces": "^3.0.3",
|
|
38
38
|
"cross-fetch": "^4.0.0",
|
|
39
39
|
"graphql": "^16.10.0",
|
|
40
40
|
"graphql-ruby-client": "^1.14.5"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@biomejs/biome": "
|
|
44
|
-
"@types/node": "22.18.
|
|
43
|
+
"@biomejs/biome": "2.2.4",
|
|
44
|
+
"@types/node": "22.18.6",
|
|
45
45
|
"@types/rails__actioncable": "6.1.11",
|
|
46
|
-
"@types/react": "18.3.
|
|
47
|
-
"bunchee": "6.
|
|
46
|
+
"@types/react": "18.3.23",
|
|
47
|
+
"bunchee": "6.6.0",
|
|
48
48
|
"rimraf": "6.0.1",
|
|
49
|
-
"typescript": "5.
|
|
49
|
+
"typescript": "5.9.2"
|
|
50
50
|
},
|
|
51
51
|
"tags": [
|
|
52
52
|
"quiltt"
|
package/src/Observable.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Dispatch, SetStateAction } from 'react'
|
|
2
|
+
|
|
2
3
|
import type { Maybe } from './types'
|
|
3
4
|
|
|
4
5
|
export type Observer<T> = Dispatch<SetStateAction<Maybe<T> | undefined>>
|
|
@@ -12,8 +13,8 @@ export class Observable<T> {
|
|
|
12
13
|
private state?: Maybe<T>
|
|
13
14
|
private observers: Observer<T>[] = []
|
|
14
15
|
|
|
15
|
-
constructor(
|
|
16
|
-
this.state =
|
|
16
|
+
constructor(initialState?: Maybe<T>) {
|
|
17
|
+
this.state = initialState
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
get = () => {
|
|
@@ -24,7 +25,9 @@ export class Observable<T> {
|
|
|
24
25
|
if (this.state === nextState) return
|
|
25
26
|
|
|
26
27
|
this.state = nextState
|
|
27
|
-
this.observers.forEach((update) =>
|
|
28
|
+
this.observers.forEach((update) => {
|
|
29
|
+
update(nextState)
|
|
30
|
+
})
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
subscribe = (observer: Observer<T>) => {
|
package/src/Timeoutable.ts
CHANGED
package/src/api/browser.ts
CHANGED
|
@@ -112,6 +112,10 @@ export type ConnectorSDKCallbackMetadata = {
|
|
|
112
112
|
profileId?: string
|
|
113
113
|
/** The ID of the Connection that was created or reconnected */
|
|
114
114
|
connectionId?: string
|
|
115
|
+
/** The Connector Session Object */
|
|
116
|
+
connectorSession?: {
|
|
117
|
+
id: string
|
|
118
|
+
}
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
/**
|
|
@@ -132,15 +136,16 @@ export type ConnectorSDKReconnectOptions = ConnectorSDKCallbacks & {
|
|
|
132
136
|
connectionId: string
|
|
133
137
|
}
|
|
134
138
|
|
|
135
|
-
/** Options to initialize Connector
|
|
136
|
-
|
|
139
|
+
/** Options to initialize Connector
|
|
140
|
+
*
|
|
141
|
+
* @todo: refactor into a union type - it's either or.
|
|
142
|
+
* Union types only allow direct access to properties that exist on all branches, not properties unique to individual branches.
|
|
143
|
+
*/
|
|
137
144
|
export type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
|
|
138
145
|
/** The Institution ID or search term to connect */
|
|
139
146
|
institution?: string
|
|
140
|
-
|
|
141
147
|
/** The ID of the Connection to reconnect */
|
|
142
148
|
connectionId?: string
|
|
143
|
-
|
|
144
149
|
/** The nonce to use for the script tag */
|
|
145
150
|
nonce?: string
|
|
146
151
|
}
|
|
@@ -3,6 +3,7 @@ import { ApolloClient, ApolloLink } from '@apollo/client/core/index.js'
|
|
|
3
3
|
import type { DefinitionNode, OperationDefinitionNode } from 'graphql'
|
|
4
4
|
|
|
5
5
|
import { debugging } from '@/configuration'
|
|
6
|
+
|
|
6
7
|
import {
|
|
7
8
|
AuthLink,
|
|
8
9
|
BatchHttpLink,
|
|
@@ -14,7 +15,10 @@ import {
|
|
|
14
15
|
VersionLink,
|
|
15
16
|
} from './links'
|
|
16
17
|
|
|
17
|
-
export type QuilttClientOptions<T> = Omit<ApolloClientOptions<T>, 'link'>
|
|
18
|
+
export type QuilttClientOptions<T> = Omit<ApolloClientOptions<T>, 'link'> & {
|
|
19
|
+
/** An array of initial links to inject before the default Quiltt Links */
|
|
20
|
+
customLinks?: ApolloLink[]
|
|
21
|
+
}
|
|
18
22
|
|
|
19
23
|
export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
20
24
|
constructor(options: QuilttClientOptions<NormalizedCacheObject>) {
|
|
@@ -25,6 +29,8 @@ export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
|
25
29
|
},
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
const initialLinks = options.customLinks ? [...options.customLinks] : []
|
|
33
|
+
|
|
28
34
|
const isOperationDefinition = (def: DefinitionNode): def is OperationDefinitionNode =>
|
|
29
35
|
def.kind === 'OperationDefinition'
|
|
30
36
|
|
|
@@ -41,7 +47,13 @@ export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
|
41
47
|
const authLink = new AuthLink()
|
|
42
48
|
const subscriptionsLink = new SubscriptionLink()
|
|
43
49
|
|
|
44
|
-
const quilttLink = ApolloLink.from([
|
|
50
|
+
const quilttLink = ApolloLink.from([
|
|
51
|
+
...initialLinks,
|
|
52
|
+
VersionLink,
|
|
53
|
+
authLink,
|
|
54
|
+
ErrorLink,
|
|
55
|
+
RetryLink,
|
|
56
|
+
])
|
|
45
57
|
.split(isSubscriptionOperation, subscriptionsLink, ForwardableLink)
|
|
46
58
|
.split(isBatchable, BatchHttpLink, HttpLink)
|
|
47
59
|
|
|
@@ -58,10 +70,9 @@ export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
|
58
70
|
*/
|
|
59
71
|
|
|
60
72
|
/** Client and Tooling */
|
|
61
|
-
export {
|
|
73
|
+
export type { NormalizedCacheObject } from '@apollo/client/cache'
|
|
62
74
|
export { InMemoryCache } from '@apollo/client/cache/index.js'
|
|
63
75
|
export type { ApolloError, OperationVariables } from '@apollo/client/core'
|
|
64
|
-
export
|
|
65
|
-
|
|
76
|
+
export { gql } from '@apollo/client/core/index.js'
|
|
66
77
|
/** React hooks used by @quiltt/react-native and @quiltt/react */
|
|
67
78
|
export { useMutation, useQuery, useSubscription } from '@apollo/client/react/hooks/index.js'
|
package/src/api/graphql/index.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { FetchResult, NextLink, Operation } from '@apollo/client/core'
|
|
2
2
|
import { ApolloLink } from '@apollo/client/core/index.js'
|
|
3
3
|
import { Observable } from '@apollo/client/utilities/index.js'
|
|
4
|
-
|
|
5
|
-
import { createConsumer } from '@rails/actioncable'
|
|
6
4
|
import type { Consumer } from '@rails/actioncable'
|
|
5
|
+
import { createConsumer } from '@rails/actioncable'
|
|
7
6
|
import { print } from 'graphql'
|
|
8
7
|
|
|
9
8
|
import { endpointWebsockets } from '@/configuration'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BatchHttpLink as
|
|
1
|
+
import { BatchHttpLink as ApolloBatchHttpLink } from '@apollo/client/link/batch-http/index.js'
|
|
2
2
|
import crossfetch from 'cross-fetch'
|
|
3
3
|
|
|
4
4
|
import { endpointGraphQL } from '@/configuration'
|
|
@@ -6,7 +6,7 @@ import { endpointGraphQL } from '@/configuration'
|
|
|
6
6
|
// Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
|
|
7
7
|
const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch
|
|
8
8
|
|
|
9
|
-
export const BatchHttpLink = new
|
|
9
|
+
export const BatchHttpLink = new ApolloBatchHttpLink({
|
|
10
10
|
uri: endpointGraphQL,
|
|
11
11
|
fetch: effectiveFetch,
|
|
12
12
|
})
|
|
@@ -43,7 +43,7 @@ export class InstitutionsAPI {
|
|
|
43
43
|
const headers = new Headers()
|
|
44
44
|
headers.set('Content-Type', 'application/json')
|
|
45
45
|
headers.set('Accept', 'application/json')
|
|
46
|
-
headers.set('
|
|
46
|
+
headers.set('Quiltt-SDK-Agent', this.agent)
|
|
47
47
|
headers.set('Authorization', `Bearer ${token}`)
|
|
48
48
|
|
|
49
49
|
return {
|
|
@@ -55,16 +55,16 @@ export class InstitutionsAPI {
|
|
|
55
55
|
|
|
56
56
|
private validateStatus = (status: number) => status < 500 && status !== 429
|
|
57
57
|
|
|
58
|
-
private body = (payload: any) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
// private body = (payload: any) => {
|
|
59
|
+
// if (!this.clientId) {
|
|
60
|
+
// console.error('Quiltt Client ID is not set. Unable to identify & authenticate')
|
|
61
|
+
// }
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
63
|
+
// return {
|
|
64
|
+
// session: {
|
|
65
|
+
// clientId: this.clientId,
|
|
66
|
+
// ...payload,
|
|
67
|
+
// },
|
|
68
|
+
// }
|
|
69
|
+
// }
|
|
70
70
|
}
|
package/src/index.ts
CHANGED
package/src/storage/Local.ts
CHANGED
|
@@ -4,13 +4,16 @@ import type { Maybe } from '@/types'
|
|
|
4
4
|
/**
|
|
5
5
|
* An error and type safe wrapper for localStorage.
|
|
6
6
|
* It allows you to subscribe to changes;
|
|
7
|
-
* but localStorage changes only fire
|
|
7
|
+
* but localStorage changes only fire when another
|
|
8
8
|
* window updates the record.
|
|
9
9
|
*/
|
|
10
|
-
export class LocalStorage<T> {
|
|
10
|
+
export class LocalStorage<T = any> {
|
|
11
11
|
private observers: { [key: string]: Observer<T>[] } = {}
|
|
12
|
+
private readonly keyPrefix: string
|
|
13
|
+
|
|
14
|
+
constructor(keyPrefix = 'quiltt') {
|
|
15
|
+
this.keyPrefix = keyPrefix
|
|
12
16
|
|
|
13
|
-
constructor() {
|
|
14
17
|
if (typeof window !== 'undefined' && typeof window.addEventListener !== 'undefined') {
|
|
15
18
|
window.addEventListener('storage', this.handleStorageEvent.bind(this))
|
|
16
19
|
}
|
|
@@ -18,10 +21,11 @@ export class LocalStorage<T> {
|
|
|
18
21
|
|
|
19
22
|
isEnabled = (): boolean => {
|
|
20
23
|
try {
|
|
21
|
-
|
|
22
|
-
localStorage.
|
|
24
|
+
const testKey = `${this.keyPrefix}.ping`
|
|
25
|
+
localStorage.setItem(testKey, 'pong')
|
|
26
|
+
localStorage.removeItem(testKey)
|
|
23
27
|
return true
|
|
24
|
-
} catch (
|
|
28
|
+
} catch (_error) {
|
|
25
29
|
return false
|
|
26
30
|
}
|
|
27
31
|
}
|
|
@@ -29,14 +33,16 @@ export class LocalStorage<T> {
|
|
|
29
33
|
isDisabled = (): boolean => !this.isEnabled()
|
|
30
34
|
|
|
31
35
|
get = (key: string): Maybe<T> | undefined => {
|
|
32
|
-
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined')
|
|
36
|
+
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
|
|
33
37
|
return undefined
|
|
38
|
+
}
|
|
34
39
|
|
|
40
|
+
const fullKey = this.getFullKey(key)
|
|
35
41
|
try {
|
|
36
|
-
const state = window.localStorage.getItem(
|
|
42
|
+
const state = window.localStorage.getItem(fullKey)
|
|
37
43
|
return state ? (JSON.parse(state) as T) : null
|
|
38
44
|
} catch (error) {
|
|
39
|
-
console.warn(`localStorage Error: "
|
|
45
|
+
console.warn(`localStorage Error: "${fullKey}"`, error)
|
|
40
46
|
return undefined
|
|
41
47
|
}
|
|
42
48
|
}
|
|
@@ -44,49 +50,143 @@ export class LocalStorage<T> {
|
|
|
44
50
|
set = (key: string, state: Maybe<T> | undefined): void => {
|
|
45
51
|
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') return
|
|
46
52
|
|
|
53
|
+
const fullKey = this.getFullKey(key)
|
|
47
54
|
try {
|
|
48
|
-
if (state) {
|
|
49
|
-
window.localStorage.setItem(
|
|
55
|
+
if (state !== null && state !== undefined) {
|
|
56
|
+
window.localStorage.setItem(fullKey, JSON.stringify(state))
|
|
50
57
|
} else {
|
|
51
|
-
window.localStorage.removeItem(
|
|
58
|
+
window.localStorage.removeItem(fullKey)
|
|
52
59
|
}
|
|
53
60
|
} catch (error) {
|
|
54
|
-
console.warn(`localStorage Error: "
|
|
61
|
+
console.warn(`localStorage Error: "${fullKey}"`, error)
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
remove = (key: string) => {
|
|
65
|
+
remove = (key: string): void => {
|
|
59
66
|
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') return
|
|
60
67
|
|
|
68
|
+
const fullKey = this.getFullKey(key)
|
|
61
69
|
try {
|
|
62
|
-
window.localStorage.removeItem(
|
|
70
|
+
window.localStorage.removeItem(fullKey)
|
|
63
71
|
} catch (error) {
|
|
64
|
-
console.warn(`localStorage Error: "
|
|
72
|
+
console.warn(`localStorage Error: "${fullKey}"`, error)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
has = (key: string): boolean => {
|
|
77
|
+
return this.get(key) !== null && this.get(key) !== undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
clear = (): void => {
|
|
81
|
+
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') return
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const keysToRemove: string[] = []
|
|
85
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
86
|
+
const key = localStorage.key(i)
|
|
87
|
+
if (key?.startsWith(`${this.keyPrefix}.`)) {
|
|
88
|
+
keysToRemove.push(key)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
keysToRemove.forEach((key) => {
|
|
92
|
+
localStorage.removeItem(key)
|
|
93
|
+
})
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.warn(`localStorage Error during clear`, error)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
subscribe = (key: string, observer: Observer<T>): (() => void) => {
|
|
100
|
+
const fullKey = this.getFullKey(key)
|
|
101
|
+
|
|
102
|
+
if (!this.observers[fullKey]) {
|
|
103
|
+
this.observers[fullKey] = []
|
|
65
104
|
}
|
|
105
|
+
|
|
106
|
+
this.observers[fullKey].push(observer)
|
|
107
|
+
|
|
108
|
+
// Return unsubscribe function
|
|
109
|
+
return () => this.unsubscribe(key, observer)
|
|
66
110
|
}
|
|
67
111
|
|
|
68
|
-
|
|
69
|
-
|
|
112
|
+
unsubscribe = (key: string, observer: Observer<T>): void => {
|
|
113
|
+
const fullKey = this.getFullKey(key)
|
|
70
114
|
|
|
71
|
-
this.observers[
|
|
115
|
+
if (this.observers[fullKey]) {
|
|
116
|
+
this.observers[fullKey] = this.observers[fullKey].filter((update) => update !== observer)
|
|
117
|
+
|
|
118
|
+
// Clean up empty observer arrays
|
|
119
|
+
if (this.observers[fullKey].length === 0) {
|
|
120
|
+
delete this.observers[fullKey]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
72
123
|
}
|
|
73
124
|
|
|
74
|
-
|
|
75
|
-
|
|
125
|
+
// Get all keys that match quiltt prefix
|
|
126
|
+
keys = (): string[] => {
|
|
127
|
+
if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
|
|
128
|
+
return []
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const keys: string[] = []
|
|
132
|
+
try {
|
|
133
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
134
|
+
const key = localStorage.key(i)
|
|
135
|
+
if (key?.startsWith(`${this.keyPrefix}.`)) {
|
|
136
|
+
keys.push(key.replace(`${this.keyPrefix}.`, ''))
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.warn('localStorage Error getting keys', error)
|
|
141
|
+
}
|
|
142
|
+
return keys
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private getFullKey = (key: string): string => {
|
|
146
|
+
return `${this.keyPrefix}.${key}`
|
|
76
147
|
}
|
|
77
148
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Handle storage events from other windows/tabs
|
|
151
|
+
* If there is a key, then trigger the related updates. If there is no key
|
|
152
|
+
* it means that a record has been removed and everything needs to be rechecked.
|
|
153
|
+
*/
|
|
154
|
+
private handleStorageEvent = (event: StorageEvent): void => {
|
|
155
|
+
const isQuilttKey = event.key?.startsWith(`${this.keyPrefix}.`)
|
|
156
|
+
|
|
157
|
+
if (isQuilttKey && event.key) {
|
|
158
|
+
// Parse the new value safely
|
|
159
|
+
let newState: T | null = null
|
|
160
|
+
try {
|
|
161
|
+
newState = event.newValue ? JSON.parse(event.newValue) : null
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.warn(`Failed to parse storage event value for ${event.key}`, error)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
83
166
|
|
|
84
167
|
if (this.observers[event.key]) {
|
|
85
|
-
this.observers[event.key].forEach((
|
|
168
|
+
this.observers[event.key].forEach((observer) => {
|
|
169
|
+
try {
|
|
170
|
+
observer(newState)
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.warn(`Observer error for key ${event.key}`, error)
|
|
173
|
+
}
|
|
174
|
+
})
|
|
86
175
|
}
|
|
87
|
-
} else {
|
|
88
|
-
|
|
89
|
-
|
|
176
|
+
} else if (!event.key) {
|
|
177
|
+
// Storage was cleared or changed in a way that doesn't specify a key
|
|
178
|
+
// Re-check all observed keys
|
|
179
|
+
Object.entries(this.observers).forEach(([fullKey, observers]) => {
|
|
180
|
+
const shortKey = fullKey.replace(`${this.keyPrefix}.`, '')
|
|
181
|
+
const currentValue = this.get(shortKey)
|
|
182
|
+
|
|
183
|
+
observers.forEach((observer) => {
|
|
184
|
+
try {
|
|
185
|
+
observer(currentValue)
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.warn(`Observer error for key ${fullKey}`, error)
|
|
188
|
+
}
|
|
189
|
+
})
|
|
90
190
|
})
|
|
91
191
|
}
|
|
92
192
|
}
|
package/src/storage/Memory.ts
CHANGED
package/src/storage/Storage.ts
CHANGED
|
@@ -41,7 +41,9 @@ export class Storage<T> {
|
|
|
41
41
|
this.localStore.set(key, newState)
|
|
42
42
|
this.memoryStore.set(key, newState)
|
|
43
43
|
|
|
44
|
-
this.observers[key]?.forEach((update) =>
|
|
44
|
+
this.observers[key]?.forEach((update) => {
|
|
45
|
+
update(newState)
|
|
46
|
+
})
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/**
|
|
@@ -77,7 +79,9 @@ export class Storage<T> {
|
|
|
77
79
|
const newState = nextState instanceof Function ? nextState(prevValue) : nextState
|
|
78
80
|
|
|
79
81
|
this.memoryStore.set(key, newState)
|
|
80
|
-
this.observers[key]?.forEach((update) =>
|
|
82
|
+
this.observers[key]?.forEach((update) => {
|
|
83
|
+
update(newState)
|
|
84
|
+
})
|
|
81
85
|
})
|
|
82
86
|
}
|
|
83
87
|
}
|