@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.
- package/CHANGELOG.md +20 -0
- package/README.md +19 -12
- package/dist/api/browser.cjs +14 -0
- package/dist/api/browser.d.ts +128 -0
- package/dist/api/browser.js +12 -0
- package/dist/api/graphql/SubscriptionLink-12s-ufJBKwu1.js +149 -0
- package/dist/api/graphql/SubscriptionLink-12s-wjkChfxO.cjs +150 -0
- package/dist/api/graphql/index.cjs +218 -0
- package/dist/api/graphql/index.d.ts +82 -0
- package/dist/api/graphql/index.js +184 -0
- package/dist/api/index.cjs +26 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.js +3 -0
- package/dist/api/rest/index.cjs +225 -0
- package/dist/api/rest/index.d.ts +128 -0
- package/dist/api/rest/index.js +217 -0
- package/dist/auth/index.cjs +21 -0
- package/dist/auth/index.d.ts +29 -0
- package/dist/auth/index.js +19 -0
- package/dist/config/index.cjs +44 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.js +36 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.ts +8 -524
- package/dist/index.js +8 -449
- package/dist/observables/index.cjs +30 -0
- package/dist/observables/index.d.ts +21 -0
- package/dist/observables/index.js +28 -0
- package/dist/storage/index.cjs +272 -0
- package/dist/storage/index.d.ts +91 -0
- package/dist/{SubscriptionLink-12s-C2VbF8Tf.js → storage/index.js} +2 -139
- package/dist/timing/index.cjs +30 -0
- package/dist/timing/index.d.ts +15 -0
- package/dist/timing/index.js +28 -0
- package/dist/types.cjs +1 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.js +1 -0
- package/dist/utils/index.cjs +61 -0
- package/dist/utils/index.d.ts +18 -0
- package/dist/utils/index.js +57 -0
- package/package.json +62 -6
- package/src/api/graphql/client.ts +1 -1
- package/src/api/graphql/links/ActionCableLink.ts +7 -6
- package/src/api/graphql/links/AuthLink.ts +13 -9
- package/src/api/graphql/links/BatchHttpLink.ts +1 -1
- package/src/api/graphql/links/ErrorLink.ts +4 -0
- package/src/api/graphql/links/HttpLink.ts +1 -1
- package/src/api/graphql/links/VersionLink.ts +1 -1
- package/src/api/rest/auth.ts +1 -1
- package/src/api/rest/connectors.ts +1 -1
- package/src/auth/index.ts +1 -0
- package/src/{JsonWebToken.ts → auth/json-web-token.ts} +1 -1
- package/src/{configuration.ts → config/configuration.ts} +1 -1
- package/src/config/index.ts +1 -0
- package/src/index.ts +5 -5
- package/src/observables/index.ts +1 -0
- package/src/{Observable.ts → observables/observable.ts} +1 -1
- package/src/storage/Local.ts +1 -1
- package/src/storage/Memory.ts +2 -2
- package/src/storage/Storage.ts +1 -1
- package/src/timing/index.ts +1 -0
- package/src/{Timeoutable.ts → timing/timeoutable.ts} +1 -1
- package/src/utils/index.ts +1 -0
- 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
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;
|