@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 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 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,7 +1,7 @@
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
- import { ApolloLink, ApolloClient } from '@apollo/client/core/index.js';
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(initalState?: Maybe<T>);
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 with another
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
- constructor();
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
- subscribe: (key: string, observer: Observer<T>) => void;
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-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-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
- this.observers.push(callback);
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.2.3",
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.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,
@@ -58,10 +59,9 @@ export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
58
59
  */
59
60
 
60
61
  /** Client and Tooling */
61
- export { gql } from '@apollo/client/core/index.js'
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 type { NormalizedCacheObject } from '@apollo/client/cache'
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'
@@ -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'
@@ -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
  }