@quiltt/core 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +19 -12
  3. package/dist/api/browser.cjs +14 -0
  4. package/dist/api/browser.d.ts +128 -0
  5. package/dist/api/browser.js +12 -0
  6. package/dist/api/graphql/SubscriptionLink-12s-ufJBKwu1.js +149 -0
  7. package/dist/api/graphql/SubscriptionLink-12s-wjkChfxO.cjs +150 -0
  8. package/dist/api/graphql/index.cjs +218 -0
  9. package/dist/api/graphql/index.d.ts +82 -0
  10. package/dist/api/graphql/index.js +184 -0
  11. package/dist/api/index.cjs +26 -0
  12. package/dist/api/index.d.ts +3 -0
  13. package/dist/api/index.js +3 -0
  14. package/dist/api/rest/index.cjs +225 -0
  15. package/dist/api/rest/index.d.ts +128 -0
  16. package/dist/api/rest/index.js +217 -0
  17. package/dist/auth/index.cjs +21 -0
  18. package/dist/auth/index.d.ts +29 -0
  19. package/dist/auth/index.js +19 -0
  20. package/dist/config/index.cjs +44 -0
  21. package/dist/config/index.d.ts +9 -0
  22. package/dist/config/index.js +36 -0
  23. package/dist/index.cjs +61 -0
  24. package/dist/index.d.ts +8 -524
  25. package/dist/index.js +8 -449
  26. package/dist/observables/index.cjs +30 -0
  27. package/dist/observables/index.d.ts +21 -0
  28. package/dist/observables/index.js +28 -0
  29. package/dist/storage/index.cjs +272 -0
  30. package/dist/storage/index.d.ts +91 -0
  31. package/dist/{SubscriptionLink-12s-C2VbF8Tf.js → storage/index.js} +2 -139
  32. package/dist/timing/index.cjs +30 -0
  33. package/dist/timing/index.d.ts +15 -0
  34. package/dist/timing/index.js +28 -0
  35. package/dist/types.cjs +1 -0
  36. package/dist/types.d.ts +28 -0
  37. package/dist/types.js +1 -0
  38. package/dist/utils/index.cjs +61 -0
  39. package/dist/utils/index.d.ts +18 -0
  40. package/dist/utils/index.js +57 -0
  41. package/package.json +62 -6
  42. package/src/api/graphql/client.ts +1 -1
  43. package/src/api/graphql/links/ActionCableLink.ts +7 -6
  44. package/src/api/graphql/links/AuthLink.ts +13 -9
  45. package/src/api/graphql/links/BatchHttpLink.ts +1 -1
  46. package/src/api/graphql/links/ErrorLink.ts +4 -0
  47. package/src/api/graphql/links/HttpLink.ts +1 -1
  48. package/src/api/graphql/links/VersionLink.ts +1 -1
  49. package/src/api/rest/auth.ts +1 -1
  50. package/src/api/rest/connectors.ts +1 -1
  51. package/src/auth/index.ts +1 -0
  52. package/src/{JsonWebToken.ts → auth/json-web-token.ts} +1 -1
  53. package/src/{configuration.ts → config/configuration.ts} +1 -1
  54. package/src/config/index.ts +1 -0
  55. package/src/index.ts +5 -5
  56. package/src/observables/index.ts +1 -0
  57. package/src/{Observable.ts → observables/observable.ts} +1 -1
  58. package/src/storage/Local.ts +1 -1
  59. package/src/storage/Memory.ts +2 -2
  60. package/src/storage/Storage.ts +1 -1
  61. package/src/timing/index.ts +1 -0
  62. package/src/{Timeoutable.ts → timing/timeoutable.ts} +1 -1
  63. package/src/utils/index.ts +1 -0
  64. package/src/utils/token-validation.ts +67 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @quiltt/core
2
2
 
3
+ ## 5.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#407](https://github.com/quiltt/quiltt-js/pull/407) [`0262754`](https://github.com/quiltt/quiltt-js/commit/0262754fa680d39cea1426a09796783488a4f4d6) Thanks [@rubendinho](https://github.com/rubendinho)! - Add Institution Prop to QuilttContainer
8
+
9
+ ## 5.0.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [#403](https://github.com/quiltt/quiltt-js/pull/403) [`b714ff3`](https://github.com/quiltt/quiltt-js/commit/b714ff3b3ee878445ca8e09903153ac0b43d693b) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Prevent Infinite Re-Renders in QuilttProvider
14
+
15
+ - [#399](https://github.com/quiltt/quiltt-js/pull/399) [`77d11b6`](https://github.com/quiltt/quiltt-js/commit/77d11b69ceb950da9ca500aa73bae7a3abdfb3a2) Thanks [@CarltonHowell](https://github.com/CarltonHowell)! - Output commonJS
16
+
17
+ - [#402](https://github.com/quiltt/quiltt-js/pull/402) [`97eccbe`](https://github.com/quiltt/quiltt-js/commit/97eccbe6df42843307a11d28a0a8b5a36e43f3d9) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Remove Node.js Dependencies in React Native Builds
18
+
19
+ - [#401](https://github.com/quiltt/quiltt-js/pull/401) [`2bf07c3`](https://github.com/quiltt/quiltt-js/commit/2bf07c36bf8dc573f01db8f4c69a48e05d313b8b) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Improve expired auth token detection and clearing
20
+
21
+ - [#404](https://github.com/quiltt/quiltt-js/pull/404) [`45eb8c9`](https://github.com/quiltt/quiltt-js/commit/45eb8c907f51c0c8c5a3c069c35e0f301a4c374f) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Add Subpath Exports and Reorganize Core Modules
22
+
3
23
  ## 5.0.0
4
24
 
5
25
  ### Major Changes
package/README.md CHANGED
@@ -49,11 +49,22 @@ await auth.revoke('{SESSION_TOKEN}')
49
49
 
50
50
  ## Modules
51
51
 
52
- ### JsonWebToken
52
+ You can import from the main entry or use subpath imports for better tree-shaking.
53
+
54
+ ```ts
55
+ import { AuthAPI } from '@quiltt/core'
56
+ import { JsonWebTokenParse } from '@quiltt/core/auth'
57
+ import { Observable } from '@quiltt/core/observables'
58
+ import { Timeoutable } from '@quiltt/core/timing'
59
+ import { Storage } from '@quiltt/core/storage'
60
+ import { endpointAuth } from '@quiltt/core/config'
61
+ ```
62
+
63
+ ### Auth (JsonWebToken)
53
64
 
54
65
  The `JsonWebToken` module provides functionality related to JSON Web Tokens (JWT). It includes methods for generating, signing, and verifying JWTs. With this module, you can easily handle authentication and secure communication in your applications.
55
66
 
56
- ### Observable
67
+ ### Observables
57
68
 
58
69
  The `Observable` module implements the Observable pattern, allowing you to create and manage observable streams of data. It provides a powerful toolset for working with asynchronous events and data streams, enabling you to build reactive and event-driven applications.
59
70
 
@@ -61,7 +72,7 @@ The `Observable` module implements the Observable pattern, allowing you to creat
61
72
 
62
73
  The `Storage` module offers convenient wrappers and abstractions for working with different types of storage, such as local storage or session storage. It simplifies the process of storing and retrieving data in a secure and efficient manner.
63
74
 
64
- ### Timeoutable
75
+ ### Timing
65
76
 
66
77
  The `Timeoutable` module provides utilities for handling timeouts and delays in your application. It allows you to schedule and manage timeouts, ensuring precise control over time-sensitive operations.
67
78
 
@@ -76,15 +87,11 @@ The `types` module provides a collection of TypeScript type definitions and inte
76
87
  ## Usage
77
88
 
78
89
  ```javascript
79
- import {
80
- AuthAPI,
81
- JsonWebToken,
82
- Observable,
83
- Storage,
84
- Timeoutable,
85
- ConnectorSDK,
86
- ConnectorSDKEventType
87
- } from '@quiltt/core'
90
+ import { AuthAPI } from '@quiltt/core'
91
+ import { JsonWebTokenParse } from '@quiltt/core/auth'
92
+ import { Observable } from '@quiltt/core/observables'
93
+ import { Storage } from '@quiltt/core/storage'
94
+ import { Timeoutable } from '@quiltt/core/timing'
88
95
 
89
96
  // Example usage of the library modules
90
97
  // ...
@@ -0,0 +1,14 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ /**
4
+ * Enum representing the different types of events emitted by the Connector.
5
+ */ var ConnectorSDKEventType = /*#__PURE__*/ function(ConnectorSDKEventType) {
6
+ /** The Connector modal has been opened */ ConnectorSDKEventType["Open"] = "opened";
7
+ /** The Connector has loaded successfully */ ConnectorSDKEventType["Load"] = "loaded";
8
+ /** The end-user successfully completed the flow */ ConnectorSDKEventType["ExitSuccess"] = "exited.successful";
9
+ /** The end-user exited the Connector before completing the flow */ ConnectorSDKEventType["ExitAbort"] = "exited.aborted";
10
+ /** The end-user experienced an error during the flow */ ConnectorSDKEventType["ExitError"] = "exited.errored";
11
+ return ConnectorSDKEventType;
12
+ }({});
13
+
14
+ exports.ConnectorSDKEventType = ConnectorSDKEventType;
@@ -0,0 +1,128 @@
1
+ interface CallbackManager {
2
+ onEvent(callback: ConnectorSDKOnEventCallback): void;
3
+ onOpen(callback: ConnectorSDKOnOpenCallback): void;
4
+ onLoad(callback: ConnectorSDKOnLoadCallback): void;
5
+ onExit(callback: ConnectorSDKOnEventExitCallback): void;
6
+ onExitSuccess(callback: ConnectorSDKOnExitSuccessCallback): void;
7
+ onExitAbort(callback: ConnectorSDKOnExitAbortCallback): void;
8
+ onExitError(callback: ConnectorSDKOnExitErrorCallback): void;
9
+ offEvent(callback: ConnectorSDKOnEventCallback): void;
10
+ offOpen(callback: ConnectorSDKOnOpenCallback): void;
11
+ offLoad(callback: ConnectorSDKOnLoadCallback): void;
12
+ offExit(callback: ConnectorSDKOnEventExitCallback): void;
13
+ offExitSuccess(callback: ConnectorSDKOnExitSuccessCallback): void;
14
+ offExitAbort(callback: ConnectorSDKOnExitAbortCallback): void;
15
+ offExitError(callback: ConnectorSDKOnExitErrorCallback): void;
16
+ }
17
+ interface ConnectorSDK extends CallbackManager {
18
+ authenticate(token: string | null | undefined): void;
19
+ connect(connectorId: string, options?: ConnectorSDKConnectOptions): ConnectorSDKConnector;
20
+ reconnect(connectorId: string, options: ConnectorSDKReconnectOptions): ConnectorSDKConnector;
21
+ reset(): void;
22
+ resetConnector: (connectorId: string) => void;
23
+ }
24
+ interface ConnectorSDKConnector extends CallbackManager {
25
+ open(): void;
26
+ }
27
+ /**
28
+ * Types for optional callbacks in the ConnectorSDK.
29
+ *
30
+ * Leaf event nodes return only metadata.
31
+ * Internal nodes return both event type and metadata.
32
+ */
33
+ type ConnectorSDKCallbacks = {
34
+ onEvent?: ConnectorSDKOnEventCallback;
35
+ onOpen?: ConnectorSDKOnOpenCallback;
36
+ onLoad?: ConnectorSDKOnLoadCallback;
37
+ onExit?: ConnectorSDKOnEventExitCallback;
38
+ onExitSuccess?: ConnectorSDKOnExitSuccessCallback;
39
+ onExitAbort?: ConnectorSDKOnExitAbortCallback;
40
+ onExitError?: ConnectorSDKOnExitErrorCallback;
41
+ };
42
+ /**
43
+ * Callback function to handle all events from the Connector.
44
+ * @param type The type of event that was emitted
45
+ * @param metadata Metadata about the event that was emitted
46
+ */
47
+ type ConnectorSDKOnEventCallback = (
48
+ /** The type of event that was emitted */
49
+ type: ConnectorSDKEventType,
50
+ /** The metadata from the event */
51
+ metadata: ConnectorSDKCallbackMetadata) => void;
52
+ /** Callback function to handle the Open event */
53
+ type ConnectorSDKOnOpenCallback = (metadata: ConnectorSDKCallbackMetadata) => void;
54
+ /** Callback function to handle the Load event */
55
+ type ConnectorSDKOnLoadCallback = (metadata: ConnectorSDKCallbackMetadata) => void;
56
+ /** Callback function to handle all Exit events */
57
+ type ConnectorSDKOnEventExitCallback = (type: ConnectorSDKEventType, metadata: ConnectorSDKCallbackMetadata) => void;
58
+ /** Callback function to handle the ExitSuccess event */
59
+ type ConnectorSDKOnExitSuccessCallback = (metadata: ConnectorSDKCallbackMetadata) => void;
60
+ /** Callback function to handle the ExitAbort event */
61
+ type ConnectorSDKOnExitAbortCallback = (metadata: ConnectorSDKCallbackMetadata) => void;
62
+ /** Callback function to handle the ExitError event */
63
+ type ConnectorSDKOnExitErrorCallback = (metadata: ConnectorSDKCallbackMetadata) => void;
64
+ /**
65
+ * Enum representing the different types of events emitted by the Connector.
66
+ */
67
+ declare enum ConnectorSDKEventType {
68
+ /** The Connector modal has been opened */
69
+ Open = "opened",
70
+ /** The Connector has loaded successfully */
71
+ Load = "loaded",
72
+ /** The end-user successfully completed the flow */
73
+ ExitSuccess = "exited.successful",
74
+ /** The end-user exited the Connector before completing the flow */
75
+ ExitAbort = "exited.aborted",
76
+ /** The end-user experienced an error during the flow */
77
+ ExitError = "exited.errored"
78
+ }
79
+ /**
80
+ * Metadata about a Connector event
81
+ * @param connectorId The ID of the Connector that emitted the event
82
+ * @param profileId The ID of the authenticated Profile
83
+ * @param connectionId The ID of the Connection that was created or reconnected
84
+ */
85
+ type ConnectorSDKCallbackMetadata = {
86
+ /** The ID of the Connector that emitted the event */
87
+ connectorId: string;
88
+ /** The ID of the authenticated Profile */
89
+ profileId?: string;
90
+ /** The ID of the Connection that was created or reconnected */
91
+ connectionId?: string;
92
+ /** The Connector Session Object */
93
+ connectorSession?: {
94
+ id: string;
95
+ };
96
+ };
97
+ /**
98
+ Options for the standard Connect flow
99
+ @param institution The Institution ID or search term to connect
100
+ */
101
+ type ConnectorSDKConnectOptions = ConnectorSDKCallbacks & {
102
+ /** The Institution ID or search term to connect */
103
+ institution?: string;
104
+ };
105
+ /**
106
+ * Options for the Reconnect flow
107
+ * @param connectionId The ID of the Connection to reconnect
108
+ */
109
+ type ConnectorSDKReconnectOptions = ConnectorSDKCallbacks & {
110
+ /** The ID of the Connection to reconnect */
111
+ connectionId: string;
112
+ };
113
+ /** Options to initialize Connector
114
+ *
115
+ * @todo: refactor into a union type - it's either or.
116
+ * Union types only allow direct access to properties that exist on all branches, not properties unique to individual branches.
117
+ */
118
+ type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
119
+ /** The Institution ID or search term to connect */
120
+ institution?: string;
121
+ /** The ID of the Connection to reconnect */
122
+ connectionId?: string;
123
+ /** The nonce to use for the script tag */
124
+ nonce?: string;
125
+ };
126
+
127
+ export { ConnectorSDKEventType };
128
+ export type { ConnectorSDK, ConnectorSDKCallbackMetadata, ConnectorSDKCallbacks, ConnectorSDKConnectOptions, ConnectorSDKConnector, ConnectorSDKConnectorOptions, ConnectorSDKOnEventCallback, ConnectorSDKOnEventExitCallback, ConnectorSDKOnExitAbortCallback, ConnectorSDKOnExitErrorCallback, ConnectorSDKOnExitSuccessCallback, ConnectorSDKOnLoadCallback, ConnectorSDKOnOpenCallback, ConnectorSDKReconnectOptions };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Enum representing the different types of events emitted by the Connector.
3
+ */ var ConnectorSDKEventType = /*#__PURE__*/ function(ConnectorSDKEventType) {
4
+ /** The Connector modal has been opened */ ConnectorSDKEventType["Open"] = "opened";
5
+ /** The Connector has loaded successfully */ ConnectorSDKEventType["Load"] = "loaded";
6
+ /** The end-user successfully completed the flow */ ConnectorSDKEventType["ExitSuccess"] = "exited.successful";
7
+ /** The end-user exited the Connector before completing the flow */ ConnectorSDKEventType["ExitAbort"] = "exited.aborted";
8
+ /** The end-user experienced an error during the flow */ ConnectorSDKEventType["ExitError"] = "exited.errored";
9
+ return ConnectorSDKEventType;
10
+ }({});
11
+
12
+ export { ConnectorSDKEventType };
@@ -0,0 +1,149 @@
1
+ 'use client';
2
+ import { ApolloLink } from '@apollo/client/core';
3
+ import { createConsumer } from '@rails/actioncable';
4
+ import { GraphQLError, print } from 'graphql';
5
+ import { Observable } from 'rxjs';
6
+ import { endpointWebsockets } from '../../config/index.js';
7
+ import { GlobalStorage } from '../../storage/index.js';
8
+
9
+ const MATCHER = /^(?:[\w-]+\.){2}[\w-]+$/;
10
+ const JsonWebTokenParse = (token)=>{
11
+ if (typeof token === 'undefined' || token === null) return token;
12
+ if (!MATCHER.test(token)) {
13
+ console.error(`Invalid Session Token: ${token}`);
14
+ return;
15
+ }
16
+ const [_header, payload, _signature] = token.split('.');
17
+ try {
18
+ return {
19
+ token,
20
+ claims: JSON.parse(atob(payload))
21
+ };
22
+ } catch (error) {
23
+ console.error(`Invalid Session Token: ${error}`);
24
+ }
25
+ };
26
+
27
+ /**
28
+ * Validates the session token from GlobalStorage.
29
+ *
30
+ * This function:
31
+ * - Checks if a session token exists
32
+ * - Validates token expiration
33
+ * - Clears expired tokens from storage (triggers observers and React re-renders)
34
+ * - Returns appropriate GraphQL errors for authentication failures
35
+ *
36
+ * @param errorMessagePrefix - Optional prefix for error messages (e.g., "for subscription")
37
+ * @returns TokenValidationResult indicating whether the token is valid or providing an error
38
+ */ function validateSessionToken(errorMessagePrefix = '') {
39
+ const token = GlobalStorage.get('session');
40
+ if (!token) {
41
+ return {
42
+ valid: false,
43
+ error: new GraphQLError(`No session token available${errorMessagePrefix ? ` ${errorMessagePrefix}` : ''}`, {
44
+ extensions: {
45
+ code: 'UNAUTHENTICATED',
46
+ reason: 'NO_TOKEN',
47
+ documentationUrl: 'https://www.quiltt.dev/authentication#session-tokens'
48
+ }
49
+ })
50
+ };
51
+ }
52
+ // Check if token is expired
53
+ const jwt = JsonWebTokenParse(token);
54
+ if (jwt?.claims.exp) {
55
+ const nowInSeconds = Math.floor(Date.now() / 1000);
56
+ if (jwt.claims.exp < nowInSeconds) {
57
+ // Clear expired token - this triggers observers and React re-renders
58
+ GlobalStorage.set('session', null);
59
+ return {
60
+ valid: false,
61
+ error: new GraphQLError('Session token has expired', {
62
+ extensions: {
63
+ code: 'UNAUTHENTICATED',
64
+ reason: 'TOKEN_EXPIRED',
65
+ expiredAt: jwt.claims.exp,
66
+ documentationUrl: 'https://www.quiltt.dev/authentication#session-tokens'
67
+ }
68
+ })
69
+ };
70
+ }
71
+ }
72
+ return {
73
+ valid: true,
74
+ token
75
+ };
76
+ }
77
+
78
+ // Adapted from https://github.com/rmosolgo/graphql-ruby/blob/master/javascript_client/src/subscriptions/ActionCableLink.ts
79
+ class ActionCableLink extends ApolloLink {
80
+ constructor(options){
81
+ super();
82
+ this.cables = {};
83
+ this.channelName = options.channelName || 'GraphqlChannel';
84
+ this.actionName = options.actionName || 'execute';
85
+ this.connectionParams = options.connectionParams || {};
86
+ this.callbacks = options.callbacks || {};
87
+ }
88
+ // Interestingly, this link does _not_ call through to `next` because
89
+ // instead, it sends the request to ActionCable.
90
+ request(operation, _next) {
91
+ const validation = validateSessionToken('for subscription');
92
+ if (!validation.valid) {
93
+ return new Observable((observer)=>{
94
+ observer.error(validation.error);
95
+ });
96
+ }
97
+ const { token } = validation;
98
+ if (!this.cables[token]) {
99
+ this.cables[token] = createConsumer(endpointWebsockets + (token ? `?token=${token}` : ''));
100
+ }
101
+ return new Observable((observer)=>{
102
+ const channelId = Math.round(Date.now() + Math.random() * 100000).toString(16);
103
+ const actionName = this.actionName;
104
+ const connectionParams = typeof this.connectionParams === 'function' ? this.connectionParams(operation) : this.connectionParams;
105
+ const callbacks = this.callbacks;
106
+ const channel = this.cables[token].subscriptions.create(Object.assign({}, {
107
+ channel: this.channelName,
108
+ channelId: channelId
109
+ }, connectionParams), {
110
+ connected: (args)=>{
111
+ channel.perform(actionName, {
112
+ query: operation.query ? print(operation.query) : null,
113
+ variables: operation.variables,
114
+ // This is added for persisted operation support:
115
+ operationId: operation.operationId,
116
+ operationName: operation.operationName
117
+ });
118
+ callbacks.connected?.(args);
119
+ },
120
+ received: (payload)=>{
121
+ if (payload?.result?.data || payload?.result?.errors) {
122
+ observer.next(payload.result);
123
+ }
124
+ if (!payload.more) {
125
+ observer.complete();
126
+ }
127
+ callbacks.received?.(payload);
128
+ },
129
+ disconnected: ()=>{
130
+ callbacks.disconnected?.();
131
+ }
132
+ });
133
+ // Make the ActionCable subscription behave like an Apollo subscription
134
+ return Object.assign(channel, {
135
+ closed: false
136
+ });
137
+ });
138
+ }
139
+ }
140
+
141
+ class SubscriptionLink extends ActionCableLink {
142
+ constructor(){
143
+ super({
144
+ channelName: 'GraphQLChannel'
145
+ });
146
+ }
147
+ }
148
+
149
+ export { SubscriptionLink as S, validateSessionToken as v };
@@ -0,0 +1,150 @@
1
+ 'use client';
2
+ var core = require('@apollo/client/core');
3
+ var actioncable = require('@rails/actioncable');
4
+ var graphql = require('graphql');
5
+ var rxjs = require('rxjs');
6
+ var index_cjs$1 = require('../../config/index.cjs');
7
+ var index_cjs = require('../../storage/index.cjs');
8
+
9
+ const MATCHER = /^(?:[\w-]+\.){2}[\w-]+$/;
10
+ const JsonWebTokenParse = (token)=>{
11
+ if (typeof token === 'undefined' || token === null) return token;
12
+ if (!MATCHER.test(token)) {
13
+ console.error(`Invalid Session Token: ${token}`);
14
+ return;
15
+ }
16
+ const [_header, payload, _signature] = token.split('.');
17
+ try {
18
+ return {
19
+ token,
20
+ claims: JSON.parse(atob(payload))
21
+ };
22
+ } catch (error) {
23
+ console.error(`Invalid Session Token: ${error}`);
24
+ }
25
+ };
26
+
27
+ /**
28
+ * Validates the session token from GlobalStorage.
29
+ *
30
+ * This function:
31
+ * - Checks if a session token exists
32
+ * - Validates token expiration
33
+ * - Clears expired tokens from storage (triggers observers and React re-renders)
34
+ * - Returns appropriate GraphQL errors for authentication failures
35
+ *
36
+ * @param errorMessagePrefix - Optional prefix for error messages (e.g., "for subscription")
37
+ * @returns TokenValidationResult indicating whether the token is valid or providing an error
38
+ */ function validateSessionToken(errorMessagePrefix = '') {
39
+ const token = index_cjs.GlobalStorage.get('session');
40
+ if (!token) {
41
+ return {
42
+ valid: false,
43
+ error: new graphql.GraphQLError(`No session token available${errorMessagePrefix ? ` ${errorMessagePrefix}` : ''}`, {
44
+ extensions: {
45
+ code: 'UNAUTHENTICATED',
46
+ reason: 'NO_TOKEN',
47
+ documentationUrl: 'https://www.quiltt.dev/authentication#session-tokens'
48
+ }
49
+ })
50
+ };
51
+ }
52
+ // Check if token is expired
53
+ const jwt = JsonWebTokenParse(token);
54
+ if (jwt?.claims.exp) {
55
+ const nowInSeconds = Math.floor(Date.now() / 1000);
56
+ if (jwt.claims.exp < nowInSeconds) {
57
+ // Clear expired token - this triggers observers and React re-renders
58
+ index_cjs.GlobalStorage.set('session', null);
59
+ return {
60
+ valid: false,
61
+ error: new graphql.GraphQLError('Session token has expired', {
62
+ extensions: {
63
+ code: 'UNAUTHENTICATED',
64
+ reason: 'TOKEN_EXPIRED',
65
+ expiredAt: jwt.claims.exp,
66
+ documentationUrl: 'https://www.quiltt.dev/authentication#session-tokens'
67
+ }
68
+ })
69
+ };
70
+ }
71
+ }
72
+ return {
73
+ valid: true,
74
+ token
75
+ };
76
+ }
77
+
78
+ // Adapted from https://github.com/rmosolgo/graphql-ruby/blob/master/javascript_client/src/subscriptions/ActionCableLink.ts
79
+ class ActionCableLink extends core.ApolloLink {
80
+ constructor(options){
81
+ super();
82
+ this.cables = {};
83
+ this.channelName = options.channelName || 'GraphqlChannel';
84
+ this.actionName = options.actionName || 'execute';
85
+ this.connectionParams = options.connectionParams || {};
86
+ this.callbacks = options.callbacks || {};
87
+ }
88
+ // Interestingly, this link does _not_ call through to `next` because
89
+ // instead, it sends the request to ActionCable.
90
+ request(operation, _next) {
91
+ const validation = validateSessionToken('for subscription');
92
+ if (!validation.valid) {
93
+ return new rxjs.Observable((observer)=>{
94
+ observer.error(validation.error);
95
+ });
96
+ }
97
+ const { token } = validation;
98
+ if (!this.cables[token]) {
99
+ this.cables[token] = actioncable.createConsumer(index_cjs$1.endpointWebsockets + (token ? `?token=${token}` : ''));
100
+ }
101
+ return new rxjs.Observable((observer)=>{
102
+ const channelId = Math.round(Date.now() + Math.random() * 100000).toString(16);
103
+ const actionName = this.actionName;
104
+ const connectionParams = typeof this.connectionParams === 'function' ? this.connectionParams(operation) : this.connectionParams;
105
+ const callbacks = this.callbacks;
106
+ const channel = this.cables[token].subscriptions.create(Object.assign({}, {
107
+ channel: this.channelName,
108
+ channelId: channelId
109
+ }, connectionParams), {
110
+ connected: (args)=>{
111
+ channel.perform(actionName, {
112
+ query: operation.query ? graphql.print(operation.query) : null,
113
+ variables: operation.variables,
114
+ // This is added for persisted operation support:
115
+ operationId: operation.operationId,
116
+ operationName: operation.operationName
117
+ });
118
+ callbacks.connected?.(args);
119
+ },
120
+ received: (payload)=>{
121
+ if (payload?.result?.data || payload?.result?.errors) {
122
+ observer.next(payload.result);
123
+ }
124
+ if (!payload.more) {
125
+ observer.complete();
126
+ }
127
+ callbacks.received?.(payload);
128
+ },
129
+ disconnected: ()=>{
130
+ callbacks.disconnected?.();
131
+ }
132
+ });
133
+ // Make the ActionCable subscription behave like an Apollo subscription
134
+ return Object.assign(channel, {
135
+ closed: false
136
+ });
137
+ });
138
+ }
139
+ }
140
+
141
+ class SubscriptionLink extends ActionCableLink {
142
+ constructor(){
143
+ super({
144
+ channelName: 'GraphQLChannel'
145
+ });
146
+ }
147
+ }
148
+
149
+ exports.SubscriptionLink = SubscriptionLink;
150
+ exports.validateSessionToken = validateSessionToken;