@quiltt/core 4.5.1 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +21 -6
- 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 -500
- package/dist/index.js +8 -371
- 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-B8qxyDXC.js → storage/index.js} +2 -129
- 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 +69 -13
- package/src/api/graphql/client.ts +16 -16
- package/src/api/graphql/links/ActionCableLink.ts +34 -17
- package/src/api/graphql/links/AuthLink.ts +21 -13
- package/src/api/graphql/links/BatchHttpLink.ts +2 -2
- package/src/api/graphql/links/ErrorLink.ts +20 -9
- package/src/api/graphql/links/ForwardableLink.ts +1 -1
- package/src/api/graphql/links/HttpLink.ts +2 -2
- package/src/api/graphql/links/RetryLink.ts +6 -2
- package/src/api/graphql/links/TerminatingLink.ts +7 -2
- package/src/api/graphql/links/VersionLink.ts +17 -12
- package/src/api/rest/auth.ts +1 -1
- package/src/api/rest/connectors.ts +9 -5
- 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 -4
- 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/telemetry.ts +71 -0
- package/src/utils/token-validation.ts +67 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts version number from formatted version string
|
|
3
|
+
* @param formattedVersion - Formatted version like "@quiltt/core: v4.5.1"
|
|
4
|
+
* @returns Version number like "4.5.1" or "unknown" if not found
|
|
5
|
+
*/
|
|
6
|
+
declare const extractVersionNumber: (formattedVersion: string) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Generates a User-Agent string following standard format
|
|
9
|
+
* Format: Quiltt/<version> (<platform-info>)
|
|
10
|
+
*/
|
|
11
|
+
declare const getUserAgent: (sdkVersion: string, platformInfo: string) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Detects browser information from user agent string
|
|
14
|
+
* Returns browser name and version, or 'Unknown' if not detected
|
|
15
|
+
*/
|
|
16
|
+
declare const getBrowserInfo: () => string;
|
|
17
|
+
|
|
18
|
+
export { extractVersionNumber, getBrowserInfo, getUserAgent };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts version number from formatted version string
|
|
3
|
+
* @param formattedVersion - Formatted version like "@quiltt/core: v4.5.1"
|
|
4
|
+
* @returns Version number like "4.5.1" or "unknown" if not found
|
|
5
|
+
*/ const extractVersionNumber = (formattedVersion)=>{
|
|
6
|
+
// Find the 'v' prefix and extract version after it
|
|
7
|
+
const vIndex = formattedVersion.indexOf('v');
|
|
8
|
+
if (vIndex === -1) return 'unknown';
|
|
9
|
+
const versionPart = formattedVersion.substring(vIndex + 1);
|
|
10
|
+
const parts = versionPart.split('.');
|
|
11
|
+
// Validate we have at least major.minor.patch
|
|
12
|
+
if (parts.length < 3) return 'unknown';
|
|
13
|
+
// Extract numeric parts (handles cases like "4.5.1-beta")
|
|
14
|
+
const major = parts[0].match(/^\d+/)?.[0];
|
|
15
|
+
const minor = parts[1].match(/^\d+/)?.[0];
|
|
16
|
+
const patch = parts[2].match(/^\d+/)?.[0];
|
|
17
|
+
if (!major || !minor || !patch) return 'unknown';
|
|
18
|
+
return `${major}.${minor}.${patch}`;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Generates a User-Agent string following standard format
|
|
22
|
+
* Format: Quiltt/<version> (<platform-info>)
|
|
23
|
+
*/ const getUserAgent = (sdkVersion, platformInfo)=>{
|
|
24
|
+
return `Quiltt/${sdkVersion} (${platformInfo})`;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Detects browser information from user agent string
|
|
28
|
+
* Returns browser name and version, or 'Unknown' if not detected
|
|
29
|
+
*/ const getBrowserInfo = ()=>{
|
|
30
|
+
if (typeof navigator === 'undefined' || !navigator.userAgent) {
|
|
31
|
+
return 'Unknown';
|
|
32
|
+
}
|
|
33
|
+
const ua = navigator.userAgent;
|
|
34
|
+
// Edge (must be checked before Chrome)
|
|
35
|
+
if (ua.includes('Edg/')) {
|
|
36
|
+
const version = ua.match(/Edg\/(\d+)/)?.[1] || 'Unknown';
|
|
37
|
+
return `Edge/${version}`;
|
|
38
|
+
}
|
|
39
|
+
// Chrome
|
|
40
|
+
if (ua.includes('Chrome/') && !ua.includes('Edg/')) {
|
|
41
|
+
const version = ua.match(/Chrome\/(\d+)/)?.[1] || 'Unknown';
|
|
42
|
+
return `Chrome/${version}`;
|
|
43
|
+
}
|
|
44
|
+
// Safari (must be checked after Chrome)
|
|
45
|
+
if (ua.includes('Safari/') && !ua.includes('Chrome/')) {
|
|
46
|
+
const version = ua.match(/Version\/(\d+)/)?.[1] || 'Unknown';
|
|
47
|
+
return `Safari/${version}`;
|
|
48
|
+
}
|
|
49
|
+
// Firefox
|
|
50
|
+
if (ua.includes('Firefox/')) {
|
|
51
|
+
const version = ua.match(/Firefox\/(\d+)/)?.[1] || 'Unknown';
|
|
52
|
+
return `Firefox/${version}`;
|
|
53
|
+
}
|
|
54
|
+
return 'Unknown';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export { extractVersionNumber, getBrowserInfo, getUserAgent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiltt/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Javascript API client and utilities for Quiltt",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"quiltt",
|
|
@@ -20,8 +20,64 @@
|
|
|
20
20
|
"type": "module",
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"require": "./dist/index.cjs",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./api": {
|
|
28
|
+
"types": "./dist/api/index.d.ts",
|
|
29
|
+
"require": "./dist/api/index.cjs",
|
|
30
|
+
"import": "./dist/api/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./api/browser": {
|
|
33
|
+
"types": "./dist/api/browser.d.ts",
|
|
34
|
+
"require": "./dist/api/browser.cjs",
|
|
35
|
+
"import": "./dist/api/browser.js"
|
|
36
|
+
},
|
|
37
|
+
"./api/graphql": {
|
|
38
|
+
"types": "./dist/api/graphql/index.d.ts",
|
|
39
|
+
"require": "./dist/api/graphql/index.cjs",
|
|
40
|
+
"import": "./dist/api/graphql/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./api/rest": {
|
|
43
|
+
"types": "./dist/api/rest/index.d.ts",
|
|
44
|
+
"require": "./dist/api/rest/index.cjs",
|
|
45
|
+
"import": "./dist/api/rest/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./auth": {
|
|
48
|
+
"types": "./dist/auth/index.d.ts",
|
|
49
|
+
"require": "./dist/auth/index.cjs",
|
|
50
|
+
"import": "./dist/auth/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./config": {
|
|
53
|
+
"types": "./dist/config/index.d.ts",
|
|
54
|
+
"require": "./dist/config/index.cjs",
|
|
55
|
+
"import": "./dist/config/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./observables": {
|
|
58
|
+
"types": "./dist/observables/index.d.ts",
|
|
59
|
+
"require": "./dist/observables/index.cjs",
|
|
60
|
+
"import": "./dist/observables/index.js"
|
|
61
|
+
},
|
|
62
|
+
"./storage": {
|
|
63
|
+
"types": "./dist/storage/index.d.ts",
|
|
64
|
+
"require": "./dist/storage/index.cjs",
|
|
65
|
+
"import": "./dist/storage/index.js"
|
|
66
|
+
},
|
|
67
|
+
"./timing": {
|
|
68
|
+
"types": "./dist/timing/index.d.ts",
|
|
69
|
+
"require": "./dist/timing/index.cjs",
|
|
70
|
+
"import": "./dist/timing/index.js"
|
|
71
|
+
},
|
|
72
|
+
"./types": {
|
|
73
|
+
"types": "./dist/types.d.ts",
|
|
74
|
+
"require": "./dist/types.cjs",
|
|
75
|
+
"import": "./dist/types.js"
|
|
76
|
+
},
|
|
77
|
+
"./utils": {
|
|
78
|
+
"types": "./dist/utils/index.d.ts",
|
|
79
|
+
"require": "./dist/utils/index.cjs",
|
|
80
|
+
"import": "./dist/utils/index.js"
|
|
25
81
|
}
|
|
26
82
|
},
|
|
27
83
|
"types": "./dist/index.d.ts",
|
|
@@ -32,19 +88,19 @@
|
|
|
32
88
|
],
|
|
33
89
|
"main": "dist/index.js",
|
|
34
90
|
"dependencies": {
|
|
35
|
-
"@apollo/client": "^
|
|
36
|
-
"@rails/actioncable": "^8.
|
|
91
|
+
"@apollo/client": "^4.1.3",
|
|
92
|
+
"@rails/actioncable": "^8.1.200",
|
|
37
93
|
"braces": "^3.0.3",
|
|
38
|
-
"cross-fetch": "^4.
|
|
39
|
-
"graphql": "^16.
|
|
40
|
-
"
|
|
94
|
+
"cross-fetch": "^4.1.0",
|
|
95
|
+
"graphql": "^16.12.0",
|
|
96
|
+
"rxjs": "^7.8.2"
|
|
41
97
|
},
|
|
42
98
|
"devDependencies": {
|
|
43
|
-
"@biomejs/biome": "2.3.
|
|
44
|
-
"@types/node": "
|
|
45
|
-
"@types/rails__actioncable": "
|
|
46
|
-
"@types/react": "19.2.
|
|
47
|
-
"bunchee": "6.
|
|
99
|
+
"@biomejs/biome": "2.3.14",
|
|
100
|
+
"@types/node": "24.10.10",
|
|
101
|
+
"@types/rails__actioncable": "8.0.3",
|
|
102
|
+
"@types/react": "19.2.11",
|
|
103
|
+
"bunchee": "6.9.4",
|
|
48
104
|
"rimraf": "6.1.2",
|
|
49
105
|
"typescript": "5.9.3"
|
|
50
106
|
},
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ApolloClient, ApolloLink } from '@apollo/client/core/index.js'
|
|
1
|
+
import { ApolloClient, ApolloLink } from '@apollo/client/core'
|
|
3
2
|
import type { DefinitionNode, OperationDefinitionNode } from 'graphql'
|
|
4
3
|
|
|
5
|
-
import { debugging } from '@/
|
|
4
|
+
import { debugging } from '@/config'
|
|
6
5
|
|
|
7
6
|
import {
|
|
8
7
|
AuthLink,
|
|
@@ -12,16 +11,17 @@ import {
|
|
|
12
11
|
HttpLink,
|
|
13
12
|
RetryLink,
|
|
14
13
|
SubscriptionLink,
|
|
15
|
-
VersionLink,
|
|
16
14
|
} from './links'
|
|
17
15
|
|
|
18
|
-
export type QuilttClientOptions
|
|
16
|
+
export type QuilttClientOptions = Omit<ApolloClient.Options, 'link'> & {
|
|
19
17
|
/** An array of initial links to inject before the default Quiltt Links */
|
|
20
18
|
customLinks?: ApolloLink[]
|
|
19
|
+
/** Platform-specific version link (required) */
|
|
20
|
+
versionLink: ApolloLink
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export class QuilttClient extends ApolloClient
|
|
24
|
-
constructor(options: QuilttClientOptions
|
|
23
|
+
export class QuilttClient extends ApolloClient {
|
|
24
|
+
constructor(options: QuilttClientOptions) {
|
|
25
25
|
const finalOptions = {
|
|
26
26
|
...options,
|
|
27
27
|
devtools: {
|
|
@@ -34,13 +34,13 @@ export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
|
34
34
|
const isOperationDefinition = (def: DefinitionNode): def is OperationDefinitionNode =>
|
|
35
35
|
def.kind === 'OperationDefinition'
|
|
36
36
|
|
|
37
|
-
const isSubscriptionOperation = (operation: Operation) => {
|
|
37
|
+
const isSubscriptionOperation = (operation: ApolloLink.Operation) => {
|
|
38
38
|
return operation.query.definitions.some(
|
|
39
39
|
(definition) => isOperationDefinition(definition) && definition.operation === 'subscription'
|
|
40
40
|
)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const isBatchable = (operation: Operation) => {
|
|
43
|
+
const isBatchable = (operation: ApolloLink.Operation) => {
|
|
44
44
|
return operation.getContext().batchable ?? true
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -49,12 +49,12 @@ export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
|
49
49
|
|
|
50
50
|
const quilttLink = ApolloLink.from([
|
|
51
51
|
...initialLinks,
|
|
52
|
-
|
|
52
|
+
options.versionLink,
|
|
53
53
|
authLink,
|
|
54
54
|
ErrorLink,
|
|
55
55
|
RetryLink,
|
|
56
|
-
])
|
|
57
|
-
.split(isSubscriptionOperation, subscriptionsLink, ForwardableLink)
|
|
56
|
+
] as ApolloLink[])
|
|
57
|
+
.split(isSubscriptionOperation, subscriptionsLink as ApolloLink, ForwardableLink)
|
|
58
58
|
.split(isBatchable, BatchHttpLink, HttpLink)
|
|
59
59
|
|
|
60
60
|
super({
|
|
@@ -71,8 +71,8 @@ export class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
|
71
71
|
|
|
72
72
|
/** Client and Tooling */
|
|
73
73
|
export type { NormalizedCacheObject } from '@apollo/client/cache'
|
|
74
|
-
export { InMemoryCache } from '@apollo/client/cache
|
|
75
|
-
export type {
|
|
76
|
-
export { gql } from '@apollo/client/core
|
|
74
|
+
export { InMemoryCache } from '@apollo/client/cache'
|
|
75
|
+
export type { OperationVariables } from '@apollo/client/core'
|
|
76
|
+
export { gql } from '@apollo/client/core'
|
|
77
77
|
/** React hooks used by @quiltt/react-native and @quiltt/react */
|
|
78
|
-
export { useMutation, useQuery, useSubscription } from '@apollo/client/react
|
|
78
|
+
export { useMutation, useQuery, useSubscription } from '@apollo/client/react'
|
|
@@ -1,48 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
import { ApolloLink } from '@apollo/client/core
|
|
3
|
-
import { Observable } from '@apollo/client/utilities/index.js'
|
|
1
|
+
// Adapted from https://github.com/rmosolgo/graphql-ruby/blob/master/javascript_client/src/subscriptions/ActionCableLink.ts
|
|
2
|
+
import { ApolloLink } from '@apollo/client/core'
|
|
4
3
|
import type { Consumer } from '@rails/actioncable'
|
|
5
4
|
import { createConsumer } from '@rails/actioncable'
|
|
6
5
|
import { print } from 'graphql'
|
|
6
|
+
import { Observable } from 'rxjs'
|
|
7
7
|
|
|
8
|
-
import { endpointWebsockets } from '@/
|
|
9
|
-
import {
|
|
8
|
+
import { endpointWebsockets } from '@/config'
|
|
9
|
+
import { validateSessionToken } from '@/utils/token-validation'
|
|
10
10
|
|
|
11
|
-
type RequestResult =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
type RequestResult = ApolloLink.Result<{ [key: string]: unknown }>
|
|
12
|
+
type ConnectionParams = object | ((operation: ApolloLink.Operation) => object)
|
|
13
|
+
type SubscriptionCallbacks = {
|
|
14
|
+
connected?: (args?: { reconnected: boolean }) => void
|
|
15
|
+
disconnected?: () => void
|
|
16
|
+
received?: (payload: unknown) => void
|
|
17
|
+
}
|
|
17
18
|
|
|
18
19
|
class ActionCableLink extends ApolloLink {
|
|
19
20
|
cables: { [id: string]: Consumer }
|
|
20
21
|
channelName: string
|
|
21
22
|
actionName: string
|
|
22
23
|
connectionParams: ConnectionParams
|
|
24
|
+
callbacks: SubscriptionCallbacks
|
|
23
25
|
|
|
24
26
|
constructor(options: {
|
|
25
27
|
channelName?: string
|
|
26
28
|
actionName?: string
|
|
27
29
|
connectionParams?: ConnectionParams
|
|
30
|
+
callbacks?: SubscriptionCallbacks
|
|
28
31
|
}) {
|
|
29
32
|
super()
|
|
30
33
|
this.cables = {}
|
|
31
34
|
this.channelName = options.channelName || 'GraphqlChannel'
|
|
32
35
|
this.actionName = options.actionName || 'execute'
|
|
33
36
|
this.connectionParams = options.connectionParams || {}
|
|
37
|
+
this.callbacks = options.callbacks || {}
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
// Interestingly, this link does _not_ call through to `next` because
|
|
37
41
|
// instead, it sends the request to ActionCable.
|
|
38
|
-
request(
|
|
39
|
-
|
|
42
|
+
request(
|
|
43
|
+
operation: ApolloLink.Operation,
|
|
44
|
+
_next: ApolloLink.ForwardFunction
|
|
45
|
+
): Observable<RequestResult> {
|
|
46
|
+
const validation = validateSessionToken('for subscription')
|
|
40
47
|
|
|
41
|
-
if (!
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
if (!validation.valid) {
|
|
49
|
+
return new Observable((observer) => {
|
|
50
|
+
observer.error(validation.error)
|
|
51
|
+
})
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
const { token } = validation
|
|
55
|
+
|
|
46
56
|
if (!this.cables[token]) {
|
|
47
57
|
this.cables[token] = createConsumer(endpointWebsockets + (token ? `?token=${token}` : ''))
|
|
48
58
|
}
|
|
@@ -55,6 +65,7 @@ class ActionCableLink extends ApolloLink {
|
|
|
55
65
|
? this.connectionParams(operation)
|
|
56
66
|
: this.connectionParams
|
|
57
67
|
|
|
68
|
+
const callbacks = this.callbacks
|
|
58
69
|
const channel = this.cables[token].subscriptions.create(
|
|
59
70
|
Object.assign(
|
|
60
71
|
{},
|
|
@@ -65,7 +76,7 @@ class ActionCableLink extends ApolloLink {
|
|
|
65
76
|
connectionParams
|
|
66
77
|
),
|
|
67
78
|
{
|
|
68
|
-
connected: () => {
|
|
79
|
+
connected: (args?: { reconnected: boolean }) => {
|
|
69
80
|
channel.perform(actionName, {
|
|
70
81
|
query: operation.query ? print(operation.query) : null,
|
|
71
82
|
variables: operation.variables,
|
|
@@ -73,6 +84,7 @@ class ActionCableLink extends ApolloLink {
|
|
|
73
84
|
operationId: (operation as { operationId?: string }).operationId,
|
|
74
85
|
operationName: operation.operationName,
|
|
75
86
|
})
|
|
87
|
+
callbacks.connected?.(args)
|
|
76
88
|
},
|
|
77
89
|
|
|
78
90
|
received: (payload: { result: RequestResult; more: any }) => {
|
|
@@ -83,6 +95,11 @@ class ActionCableLink extends ApolloLink {
|
|
|
83
95
|
if (!payload.more) {
|
|
84
96
|
observer.complete()
|
|
85
97
|
}
|
|
98
|
+
|
|
99
|
+
callbacks.received?.(payload)
|
|
100
|
+
},
|
|
101
|
+
disconnected: () => {
|
|
102
|
+
callbacks.disconnected?.()
|
|
86
103
|
},
|
|
87
104
|
}
|
|
88
105
|
)
|
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { Observable } from '@apollo/client/utilities'
|
|
1
|
+
import { ApolloLink } from '@apollo/client/core'
|
|
2
|
+
import { Observable } from 'rxjs'
|
|
4
3
|
|
|
5
|
-
import {
|
|
4
|
+
import { validateSessionToken } from '@/utils/token-validation'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Apollo Link that handles authentication and session expiration for GraphQL requests.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Automatically adds Bearer token to request headers
|
|
11
|
+
* - Detects expired tokens and triggers proper error handling
|
|
12
|
+
* - Clears expired sessions from storage (triggers React re-renders via observers)
|
|
13
|
+
* - Emits GraphQL errors for consistent Apollo error handling
|
|
12
14
|
*/
|
|
13
15
|
export class AuthLink extends ApolloLink {
|
|
14
|
-
request(
|
|
15
|
-
|
|
16
|
+
request(
|
|
17
|
+
operation: ApolloLink.Operation,
|
|
18
|
+
forward: ApolloLink.ForwardFunction
|
|
19
|
+
): Observable<ApolloLink.Result> {
|
|
20
|
+
const validation = validateSessionToken()
|
|
16
21
|
|
|
17
|
-
if (!
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
if (!validation.valid) {
|
|
23
|
+
return new Observable((observer) => {
|
|
24
|
+
observer.error(validation.error)
|
|
25
|
+
})
|
|
20
26
|
}
|
|
21
27
|
|
|
28
|
+
const { token } = validation
|
|
29
|
+
|
|
22
30
|
operation.setContext(({ headers = {} }) => ({
|
|
23
31
|
headers: {
|
|
24
32
|
...headers,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BatchHttpLink as ApolloBatchHttpLink } from '@apollo/client/link/batch-http
|
|
1
|
+
import { BatchHttpLink as ApolloBatchHttpLink } from '@apollo/client/link/batch-http'
|
|
2
2
|
import crossfetch from 'cross-fetch'
|
|
3
3
|
|
|
4
|
-
import { endpointGraphQL } from '@/
|
|
4
|
+
import { endpointGraphQL } from '@/config'
|
|
5
5
|
|
|
6
6
|
// Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
|
|
7
7
|
const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch
|
|
@@ -1,29 +1,40 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { ErrorLink as ApolloErrorLink } from '@apollo/client/link/error'
|
|
2
|
+
import type { GraphQLFormattedError } from 'graphql'
|
|
3
3
|
|
|
4
4
|
import { GlobalStorage } from '@/storage'
|
|
5
5
|
|
|
6
|
-
export const ErrorLink =
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
export const ErrorLink = new ApolloErrorLink(({ error, result }) => {
|
|
7
|
+
// In Apollo Client 4, errors are consolidated to the 'error' and 'result' properties
|
|
8
|
+
|
|
9
|
+
// Handle GraphQL errors from result
|
|
10
|
+
if (result?.errors) {
|
|
11
|
+
result.errors.forEach((graphQLError: GraphQLFormattedError) => {
|
|
12
|
+
const { message, path, extensions } = graphQLError
|
|
9
13
|
const formattedPath = Array.isArray(path) ? path.join('.') : (path ?? 'N/A')
|
|
10
14
|
const parts = [`[Quiltt][GraphQL Error]: ${message}`, `Path: ${formattedPath}`]
|
|
11
15
|
|
|
12
16
|
if (extensions) {
|
|
13
17
|
if (extensions.code) parts.push(`Code: ${extensions.code}`)
|
|
14
18
|
if (extensions.errorId) parts.push(`Error ID: ${extensions.errorId}`)
|
|
19
|
+
if (extensions.instruction) parts.push(`Instruction: ${extensions.instruction}`)
|
|
20
|
+
if (extensions.documentationUrl) {
|
|
21
|
+
parts.push(`Docs: ${extensions.documentationUrl}`)
|
|
22
|
+
}
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
console.warn(parts.join(' | '))
|
|
18
26
|
})
|
|
19
27
|
}
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
// Handle network/server errors
|
|
30
|
+
if (error) {
|
|
31
|
+
if ('statusCode' in error && error.statusCode === 401) {
|
|
32
|
+
console.warn('[Quiltt][Authentication Error]:', error)
|
|
24
33
|
GlobalStorage.set('session', null)
|
|
34
|
+
} else if ('statusCode' in error) {
|
|
35
|
+
console.warn('[Quiltt][Server Error]:', error)
|
|
25
36
|
} else {
|
|
26
|
-
console.warn('[Quiltt][Network Error]:',
|
|
37
|
+
console.warn('[Quiltt][Network Error]:', error)
|
|
27
38
|
}
|
|
28
39
|
}
|
|
29
40
|
})
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { HttpLink as ApolloHttpLink } from '@apollo/client/link/http
|
|
1
|
+
import { HttpLink as ApolloHttpLink } from '@apollo/client/link/http'
|
|
2
2
|
import crossfetch from 'cross-fetch'
|
|
3
3
|
|
|
4
4
|
// Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
|
|
5
5
|
const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch
|
|
6
6
|
|
|
7
|
-
import { endpointGraphQL } from '@/
|
|
7
|
+
import { endpointGraphQL } from '@/config'
|
|
8
8
|
|
|
9
9
|
export const HttpLink = new ApolloHttpLink({
|
|
10
10
|
uri: endpointGraphQL,
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { RetryLink as ApolloRetryLink } from '@apollo/client/link/retry
|
|
1
|
+
import { RetryLink as ApolloRetryLink } from '@apollo/client/link/retry'
|
|
2
2
|
|
|
3
3
|
export const RetryLink = new ApolloRetryLink({
|
|
4
4
|
attempts: {
|
|
5
|
-
retryIf: (error, _operation) =>
|
|
5
|
+
retryIf: (error, _operation) => {
|
|
6
|
+
if (!error) return false
|
|
7
|
+
const statusCode = 'statusCode' in error ? (error as any).statusCode : undefined
|
|
8
|
+
return !statusCode || statusCode >= 500
|
|
9
|
+
},
|
|
6
10
|
},
|
|
7
11
|
})
|
|
8
12
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { ApolloLink } from '@apollo/client/core
|
|
1
|
+
import { ApolloLink } from '@apollo/client/core'
|
|
2
|
+
import { Observable } from 'rxjs'
|
|
2
3
|
|
|
3
|
-
export const TerminatingLink = new ApolloLink(() =>
|
|
4
|
+
export const TerminatingLink = new ApolloLink(() => {
|
|
5
|
+
return new Observable((observer) => {
|
|
6
|
+
observer.complete()
|
|
7
|
+
})
|
|
8
|
+
})
|
|
4
9
|
|
|
5
10
|
export default TerminatingLink
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { ApolloLink } from '@apollo/client/core
|
|
1
|
+
import { ApolloLink } from '@apollo/client/core'
|
|
2
2
|
|
|
3
|
-
import { version } from '@/
|
|
3
|
+
import { version } from '@/config'
|
|
4
|
+
import { extractVersionNumber, getUserAgent } from '@/utils/telemetry'
|
|
4
5
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
...headers,
|
|
9
|
-
'Quiltt-Client-Version': version,
|
|
10
|
-
},
|
|
11
|
-
}))
|
|
12
|
-
return forward(operation)
|
|
13
|
-
})
|
|
6
|
+
export const createVersionLink = (platformInfo: string) => {
|
|
7
|
+
const versionNumber = extractVersionNumber(version)
|
|
8
|
+
const userAgent = getUserAgent(versionNumber, platformInfo)
|
|
14
9
|
|
|
15
|
-
|
|
10
|
+
return new ApolloLink((operation, forward) => {
|
|
11
|
+
operation.setContext(({ headers = {} }) => ({
|
|
12
|
+
headers: {
|
|
13
|
+
...headers,
|
|
14
|
+
'Quiltt-Client-Version': version,
|
|
15
|
+
'User-Agent': userAgent,
|
|
16
|
+
},
|
|
17
|
+
}))
|
|
18
|
+
return forward(operation)
|
|
19
|
+
})
|
|
20
|
+
}
|
package/src/api/rest/auth.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { endpointRest } from '@/
|
|
1
|
+
import { endpointRest, version } from '@/config'
|
|
2
|
+
import { extractVersionNumber, getUserAgent } from '@/utils/telemetry'
|
|
2
3
|
|
|
3
4
|
import type { FetchResponse } from './fetchWithRetry'
|
|
4
5
|
import { fetchWithRetry } from './fetchWithRetry'
|
|
@@ -17,11 +18,14 @@ export type ResolvableResponse = FetchResponse<ResolvableData>
|
|
|
17
18
|
|
|
18
19
|
export class ConnectorsAPI {
|
|
19
20
|
clientId: string
|
|
20
|
-
|
|
21
|
+
userAgent: string
|
|
21
22
|
|
|
22
|
-
constructor(
|
|
23
|
+
constructor(
|
|
24
|
+
clientId: string,
|
|
25
|
+
userAgent: string = getUserAgent(extractVersionNumber(version), 'Unknown')
|
|
26
|
+
) {
|
|
23
27
|
this.clientId = clientId
|
|
24
|
-
this.
|
|
28
|
+
this.userAgent = userAgent
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -87,7 +91,7 @@ export class ConnectorsAPI {
|
|
|
87
91
|
const headers = new Headers()
|
|
88
92
|
headers.set('Content-Type', 'application/json')
|
|
89
93
|
headers.set('Accept', 'application/json')
|
|
90
|
-
headers.set('
|
|
94
|
+
headers.set('User-Agent', this.userAgent)
|
|
91
95
|
headers.set('Authorization', `Bearer ${token}`)
|
|
92
96
|
|
|
93
97
|
return {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './json-web-token'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './configuration'
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * from './api'
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
2
|
+
export * from './auth'
|
|
3
|
+
export * from './config'
|
|
4
|
+
export * from './observables'
|
|
5
5
|
export * from './storage'
|
|
6
|
-
export * from './
|
|
6
|
+
export * from './timing'
|
|
7
7
|
export * from './types'
|
|
8
|
+
export * from './utils'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './observable'
|