@quiltt/core 4.2.3 → 4.3.0
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 +6 -0
- package/dist/{SubscriptionLink-client-BNmSIP63.js → SubscriptionLink-12s-DyFWSZbW.js} +159 -67
- package/dist/index.d.ts +65 -48
- package/dist/index.js +6 -14
- 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 +3 -3
- package/src/api/graphql/index.ts +1 -1
- package/src/api/graphql/links/ActionCableLink.ts +1 -2
- package/src/api/rest/institutions.ts +11 -11
- 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,11 @@
|
|
|
1
1
|
# @quiltt/core
|
|
2
2
|
|
|
3
|
+
## 4.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#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
|
|
8
|
+
|
|
3
9
|
## 4.2.3
|
|
4
10
|
|
|
5
11
|
### 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.0";
|
|
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,7 +1,7 @@
|
|
|
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
|
-
import {
|
|
4
|
+
import { ApolloClient, ApolloLink } from '@apollo/client/core/index.js';
|
|
5
5
|
export { gql } from '@apollo/client/core/index.js';
|
|
6
6
|
import { Observable as Observable$1 } from '@apollo/client/utilities';
|
|
7
7
|
import { BatchHttpLink as BatchHttpLink$1 } from '@apollo/client/link/batch-http/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,11 @@ type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
|
|
|
132
140
|
nonce?: string;
|
|
133
141
|
};
|
|
134
142
|
|
|
143
|
+
type QuilttClientOptions<T> = Omit<ApolloClientOptions<T>, 'link'>;
|
|
144
|
+
declare class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
145
|
+
constructor(options: QuilttClientOptions<NormalizedCacheObject>);
|
|
146
|
+
}
|
|
147
|
+
|
|
135
148
|
/**
|
|
136
149
|
* unauthorizedCallback only triggers in the event the token is present, and
|
|
137
150
|
* returns the token; This allows sessions to be forgotten without race conditions
|
|
@@ -179,11 +192,6 @@ declare const TerminatingLink: ApolloLink;
|
|
|
179
192
|
|
|
180
193
|
declare const VersionLink: ApolloLink;
|
|
181
194
|
|
|
182
|
-
type QuilttClientOptions<T> = Omit<ApolloClientOptions<T>, 'link'>;
|
|
183
|
-
declare class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
184
|
-
constructor(options: QuilttClientOptions<NormalizedCacheObject>);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
195
|
type FetchResponse<T> = {
|
|
188
196
|
data: T;
|
|
189
197
|
status: number;
|
|
@@ -287,9 +295,16 @@ declare class InstitutionsAPI {
|
|
|
287
295
|
search: (token: string, connectorId: string, term: string, signal?: AbortSignal) => Promise<FetchResponse<Search>>;
|
|
288
296
|
private config;
|
|
289
297
|
private validateStatus;
|
|
290
|
-
private body;
|
|
291
298
|
}
|
|
292
299
|
|
|
300
|
+
declare const debugging: boolean;
|
|
301
|
+
declare const version: string;
|
|
302
|
+
declare const cdnBase: string;
|
|
303
|
+
declare const endpointAuth: string;
|
|
304
|
+
declare const endpointGraphQL: string;
|
|
305
|
+
declare const endpointRest: string;
|
|
306
|
+
declare const endpointWebsockets: string;
|
|
307
|
+
|
|
293
308
|
/** Utility types to extend default TS utilities */
|
|
294
309
|
type Maybe<T> = T | null;
|
|
295
310
|
type InputMaybe<T> = Maybe<T>;
|
|
@@ -317,6 +332,31 @@ type DeepReadonly<T> = T extends object ? {
|
|
|
317
332
|
[P in keyof T]: DeepReadonly<T[P]>;
|
|
318
333
|
} : T;
|
|
319
334
|
|
|
335
|
+
type RegisteredClaims = {
|
|
336
|
+
iss: string;
|
|
337
|
+
sub: string;
|
|
338
|
+
aud: string;
|
|
339
|
+
exp: number;
|
|
340
|
+
nbf: number;
|
|
341
|
+
iat: number;
|
|
342
|
+
jti: string;
|
|
343
|
+
};
|
|
344
|
+
type PrivateClaims = {
|
|
345
|
+
oid: string;
|
|
346
|
+
eid: string;
|
|
347
|
+
cid: string;
|
|
348
|
+
aid: string;
|
|
349
|
+
ver: number;
|
|
350
|
+
rol: 'basic' | 'core' | 'manager' | 'super-admin';
|
|
351
|
+
};
|
|
352
|
+
type Claims<T> = RegisteredClaims & T;
|
|
353
|
+
type JsonWebToken<T> = {
|
|
354
|
+
token: string;
|
|
355
|
+
claims: Claims<T>;
|
|
356
|
+
};
|
|
357
|
+
type QuilttJWT = JsonWebToken<PrivateClaims>;
|
|
358
|
+
declare const JsonWebTokenParse: <T>(token: Maybe<string> | undefined) => Maybe<JsonWebToken<T>> | undefined;
|
|
359
|
+
|
|
320
360
|
type Observer<T> = Dispatch<SetStateAction<Maybe<T> | undefined>>;
|
|
321
361
|
/**
|
|
322
362
|
* This is designed to support singletons to share the memory states across all
|
|
@@ -326,7 +366,7 @@ type Observer<T> = Dispatch<SetStateAction<Maybe<T> | undefined>>;
|
|
|
326
366
|
declare class Observable<T> {
|
|
327
367
|
private state?;
|
|
328
368
|
private observers;
|
|
329
|
-
constructor(
|
|
369
|
+
constructor(initialState?: Maybe<T>);
|
|
330
370
|
get: () => Maybe<T> | undefined;
|
|
331
371
|
set: (nextState: Maybe<T> | undefined) => void;
|
|
332
372
|
subscribe: (observer: Observer<T>) => void;
|
|
@@ -336,19 +376,29 @@ declare class Observable<T> {
|
|
|
336
376
|
/**
|
|
337
377
|
* An error and type safe wrapper for localStorage.
|
|
338
378
|
* It allows you to subscribe to changes;
|
|
339
|
-
* but localStorage changes only fire
|
|
379
|
+
* but localStorage changes only fire when another
|
|
340
380
|
* window updates the record.
|
|
341
381
|
*/
|
|
342
|
-
declare class LocalStorage<T> {
|
|
382
|
+
declare class LocalStorage<T = any> {
|
|
343
383
|
private observers;
|
|
344
|
-
|
|
384
|
+
private readonly keyPrefix;
|
|
385
|
+
constructor(keyPrefix?: string);
|
|
345
386
|
isEnabled: () => boolean;
|
|
346
387
|
isDisabled: () => boolean;
|
|
347
388
|
get: (key: string) => Maybe<T> | undefined;
|
|
348
389
|
set: (key: string, state: Maybe<T> | undefined) => void;
|
|
349
390
|
remove: (key: string) => void;
|
|
350
|
-
|
|
391
|
+
has: (key: string) => boolean;
|
|
392
|
+
clear: () => void;
|
|
393
|
+
subscribe: (key: string, observer: Observer<T>) => (() => void);
|
|
351
394
|
unsubscribe: (key: string, observer: Observer<T>) => void;
|
|
395
|
+
keys: () => string[];
|
|
396
|
+
private getFullKey;
|
|
397
|
+
/**
|
|
398
|
+
* Handle storage events from other windows/tabs
|
|
399
|
+
* If there is a key, then trigger the related updates. If there is no key
|
|
400
|
+
* it means that a record has been removed and everything needs to be rechecked.
|
|
401
|
+
*/
|
|
352
402
|
private handleStorageEvent;
|
|
353
403
|
}
|
|
354
404
|
|
|
@@ -410,39 +460,6 @@ declare class Storage<T> {
|
|
|
410
460
|
*/
|
|
411
461
|
declare const GlobalStorage: Storage<any>;
|
|
412
462
|
|
|
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
463
|
/**
|
|
447
464
|
* This is designed to support singletons to timeouts that can broadcast
|
|
448
465
|
* 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-DyFWSZbW.js';
|
|
4
|
+
export { L as LocalStorage, M as MemoryStorage, O as Observable, g as Storage, c as cdnBase, f as endpointWebsockets } from './SubscriptionLink-12s-DyFWSZbW.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';
|
|
@@ -285,17 +285,6 @@ class InstitutionsAPI {
|
|
|
285
285
|
};
|
|
286
286
|
};
|
|
287
287
|
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
288
|
this.clientId = clientId;
|
|
300
289
|
this.agent = agent;
|
|
301
290
|
}
|
|
@@ -329,7 +318,10 @@ const JsonWebTokenParse = (token)=>{
|
|
|
329
318
|
if (this.timeout) {
|
|
330
319
|
clearTimeout(this.timeout);
|
|
331
320
|
}
|
|
332
|
-
|
|
321
|
+
// Replace all observers with the new one
|
|
322
|
+
this.observers = [
|
|
323
|
+
callback
|
|
324
|
+
];
|
|
333
325
|
this.timeout = setTimeout(this.broadcast.bind(this), delay);
|
|
334
326
|
};
|
|
335
327
|
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.0",
|
|
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,
|
|
@@ -58,10 +59,9 @@ export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
|
58
59
|
*/
|
|
59
60
|
|
|
60
61
|
/** Client and Tooling */
|
|
61
|
-
export {
|
|
62
|
+
export type { NormalizedCacheObject } from '@apollo/client/cache'
|
|
62
63
|
export { InMemoryCache } from '@apollo/client/cache/index.js'
|
|
63
64
|
export type { ApolloError, OperationVariables } from '@apollo/client/core'
|
|
64
|
-
export
|
|
65
|
-
|
|
65
|
+
export { gql } from '@apollo/client/core/index.js'
|
|
66
66
|
/** React hooks used by @quiltt/react-native and @quiltt/react */
|
|
67
67
|
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'
|
|
@@ -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
|
}
|