@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 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 with another
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
- localStorage.setItem('quiltt.ping', 'pong');
18
- localStorage.removeItem('quiltt.ping');
52
+ const testKey = `${this.keyPrefix}.ping`;
53
+ localStorage.setItem(testKey, 'pong');
54
+ localStorage.removeItem(testKey);
19
55
  return true;
20
- } catch (error) {
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') return 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(`quiltt.${key}`);
67
+ const state = window.localStorage.getItem(fullKey);
29
68
  return state ? JSON.parse(state) : null;
30
69
  } catch (error) {
31
- console.warn(`localStorage Error: "quiltt.${key}"`, 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(`quiltt.${key}`, JSON.stringify(state));
78
+ if (state !== null && state !== undefined) {
79
+ window.localStorage.setItem(fullKey, JSON.stringify(state));
40
80
  } else {
41
- window.localStorage.removeItem(`quiltt.${key}`);
81
+ window.localStorage.removeItem(fullKey);
42
82
  }
43
83
  } catch (error) {
44
- console.warn(`localStorage Error: "quiltt.${key}"`, 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
- window.localStorage.removeItem(`quiltt.${key}`);
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: "quiltt.${key}">`, error);
113
+ console.warn(`localStorage Error during clear`, error);
53
114
  }
54
115
  };
55
116
  this.subscribe = (key, observer)=>{
56
- if (!this.observers[key]) this.observers[key] = [];
57
- this.observers[key].push(observer);
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
- this.observers[key] = this.observers[key]?.filter((update)=>update !== observer);
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
- // if there is a key, then trigger the related updates. If there is not key
63
- // it means that a record has been removed and everything needs to be rechecked.
64
- this.handleStorageEvent = (event)=>{
65
- if (event.key?.includes('quiltt.')) {
66
- const newState = event.newValue ? JSON.parse(event.newValue) : null;
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((update)=>update(newState));
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
- Object.entries(this.observers).forEach(([key, observers])=>{
72
- observers.forEach((update)=>update(this.get(key)));
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(initalState){
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)=>update(nextState));
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 = initalState;
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)=>update(newState));
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)=>update(newState));
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, Storage as c, debugging as d, endpointGraphQL as e, cdnBase as f, endpointWebsockets as g, version as v };
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 { Operation, NextLink, FetchResult, ApolloClientOptions, NormalizedCacheObject } from '@apollo/client/core';
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(initalState?: Maybe<T>);
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 with another
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
- constructor();
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
- subscribe: (key: string, observer: Observer<T>) => void;
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-client-BNmSIP63.js';
4
- export { L as LocalStorage, M as MemoryStorage, O as Observable, c as Storage, f as cdnBase, g as endpointWebsockets } from './SubscriptionLink-client-BNmSIP63.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-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('X-Quiltt-SDK-Agent', this.agent);
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
- this.observers.push(callback);
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.2.3",
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.12.4",
36
- "@rails/actioncable": "^7.2.201",
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": "1.9.4",
44
- "@types/node": "22.18.1",
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.20",
47
- "bunchee": "6.3.4",
46
+ "@types/react": "18.3.23",
47
+ "bunchee": "6.6.0",
48
48
  "rimraf": "6.0.1",
49
- "typescript": "5.8.3"
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(initalState?: Maybe<T>) {
16
- this.state = initalState
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) => update(nextState))
28
+ this.observers.forEach((update) => {
29
+ update(nextState)
30
+ })
28
31
  }
29
32
 
30
33
  subscribe = (observer: Observer<T>) => {
@@ -13,7 +13,8 @@ export class Timeoutable {
13
13
  clearTimeout(this.timeout)
14
14
  }
15
15
 
16
- this.observers.push(callback)
16
+ // Replace all observers with the new one
17
+ this.observers = [callback]
17
18
  this.timeout = setTimeout(this.broadcast.bind(this), delay)
18
19
  }
19
20
 
@@ -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
- // @todo: refactor into a union type - it's either or.
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([VersionLink, authLink, ErrorLink, RetryLink])
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 { gql } from '@apollo/client/core/index.js'
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 type { NormalizedCacheObject } from '@apollo/client/cache'
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'
@@ -1,2 +1,2 @@
1
- export * from './links'
2
1
  export * from './client'
2
+ export * from './links'
@@ -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 ApolloHttpLink } from '@apollo/client/link/batch-http/index.js'
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 ApolloHttpLink({
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('X-Quiltt-SDK-Agent', this.agent)
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
- if (!this.clientId) {
60
- console.error('Quiltt Client ID is not set. Unable to identify & authenticate')
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
- return {
64
- session: {
65
- clientId: this.clientId,
66
- ...payload,
67
- },
68
- }
69
- }
63
+ // return {
64
+ // session: {
65
+ // clientId: this.clientId,
66
+ // ...payload,
67
+ // },
68
+ // }
69
+ // }
70
70
  }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './api'
2
- export * from './storage'
3
2
  export * from './configuration'
4
3
  export * from './JsonWebToken'
5
4
  export * from './Observable'
5
+ export * from './storage'
6
6
  export * from './Timeoutable'
7
7
  export * from './types'
@@ -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 with another
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
- localStorage.setItem('quiltt.ping', 'pong')
22
- localStorage.removeItem('quiltt.ping')
24
+ const testKey = `${this.keyPrefix}.ping`
25
+ localStorage.setItem(testKey, 'pong')
26
+ localStorage.removeItem(testKey)
23
27
  return true
24
- } catch (error) {
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(`quiltt.${key}`)
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: "quiltt.${key}"`, 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(`quiltt.${key}`, JSON.stringify(state))
55
+ if (state !== null && state !== undefined) {
56
+ window.localStorage.setItem(fullKey, JSON.stringify(state))
50
57
  } else {
51
- window.localStorage.removeItem(`quiltt.${key}`)
58
+ window.localStorage.removeItem(fullKey)
52
59
  }
53
60
  } catch (error) {
54
- console.warn(`localStorage Error: "quiltt.${key}"`, 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(`quiltt.${key}`)
70
+ window.localStorage.removeItem(fullKey)
63
71
  } catch (error) {
64
- console.warn(`localStorage Error: "quiltt.${key}">`, 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
- subscribe = (key: string, observer: Observer<T>) => {
69
- if (!this.observers[key]) this.observers[key] = []
112
+ unsubscribe = (key: string, observer: Observer<T>): void => {
113
+ const fullKey = this.getFullKey(key)
70
114
 
71
- this.observers[key].push(observer)
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
- unsubscribe = (key: string, observer: Observer<T>) => {
75
- this.observers[key] = this.observers[key]?.filter((update) => update !== observer)
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
- // if there is a key, then trigger the related updates. If there is not key
79
- // it means that a record has been removed and everything needs to be rechecked.
80
- private handleStorageEvent = (event: StorageEvent) => {
81
- if (event.key?.includes('quiltt.')) {
82
- const newState = event.newValue ? JSON.parse(event.newValue) : null
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((update) => update(newState))
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
- Object.entries(this.observers).forEach(([key, observers]) => {
89
- observers.forEach((update) => update(this.get(key)))
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
  }
@@ -1,5 +1,5 @@
1
- import { Observable } from '@/Observable'
2
1
  import type { Observer } from '@/Observable'
2
+ import { Observable } from '@/Observable'
3
3
  import type { Maybe } from '@/types'
4
4
 
5
5
  /**
@@ -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) => update(newState))
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) => update(newState))
82
+ this.observers[key]?.forEach((update) => {
83
+ update(newState)
84
+ })
81
85
  })
82
86
  }
83
87
  }