@quiltt/core 4.5.1 → 5.0.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 +10 -0
- package/README.md +11 -3
- package/dist/{SubscriptionLink-12s-B8qxyDXC.js → SubscriptionLink-12s-C2VbF8Tf.js} +16 -6
- package/dist/index.d.ts +50 -26
- package/dist/index.js +112 -34
- package/package.json +11 -11
- package/src/api/graphql/client.ts +15 -15
- package/src/api/graphql/links/ActionCableLink.ts +28 -12
- package/src/api/graphql/links/AuthLink.ts +9 -5
- package/src/api/graphql/links/BatchHttpLink.ts +1 -1
- package/src/api/graphql/links/ErrorLink.ts +16 -9
- package/src/api/graphql/links/ForwardableLink.ts +1 -1
- package/src/api/graphql/links/HttpLink.ts +1 -1
- 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 +16 -11
- package/src/api/rest/connectors.ts +9 -5
- package/src/index.ts +1 -0
- package/src/utils/telemetry.ts +71 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @quiltt/core
|
|
2
2
|
|
|
3
|
+
## 5.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [#394](https://github.com/quiltt/quiltt-js/pull/394) [`2ba646a`](https://github.com/quiltt/quiltt-js/commit/2ba646a2efcb7bef7949dab74778ab1c3babdb84) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Migrate Apollo Client to v4
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- [#395](https://github.com/quiltt/quiltt-js/pull/395) [`f635500`](https://github.com/quiltt/quiltt-js/commit/f635500f17ab8a76aa0fb87ed7f4971e63a93f12) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Enhanced SDK telemetry with standardized User-Agent headers
|
|
12
|
+
|
|
3
13
|
## 4.5.1
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -41,10 +41,10 @@ import { AuthAPI } from '@quiltt/core'
|
|
|
41
41
|
const auth = new AuthAPI('{CONNECTOR_ID}')
|
|
42
42
|
|
|
43
43
|
// Check if a Session token is valid
|
|
44
|
-
auth.ping('{SESSION_TOKEN}')
|
|
44
|
+
await auth.ping('{SESSION_TOKEN}')
|
|
45
45
|
|
|
46
46
|
// Revoke a Session token
|
|
47
|
-
auth.revoke('{SESSION_TOKEN}')
|
|
47
|
+
await auth.revoke('{SESSION_TOKEN}')
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## Modules
|
|
@@ -76,7 +76,15 @@ The `types` module provides a collection of TypeScript type definitions and inte
|
|
|
76
76
|
## Usage
|
|
77
77
|
|
|
78
78
|
```javascript
|
|
79
|
-
import {
|
|
79
|
+
import {
|
|
80
|
+
AuthAPI,
|
|
81
|
+
JsonWebToken,
|
|
82
|
+
Observable,
|
|
83
|
+
Storage,
|
|
84
|
+
Timeoutable,
|
|
85
|
+
ConnectorSDK,
|
|
86
|
+
ConnectorSDKEventType
|
|
87
|
+
} from '@quiltt/core'
|
|
80
88
|
|
|
81
89
|
// Example usage of the library modules
|
|
82
90
|
// ...
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { ApolloLink } from '@apollo/client/core
|
|
3
|
-
import { Observable as Observable$1 } from '@apollo/client/utilities/index.js';
|
|
2
|
+
import { ApolloLink } from '@apollo/client/core';
|
|
4
3
|
import { createConsumer } from '@rails/actioncable';
|
|
5
4
|
import { print } from 'graphql';
|
|
5
|
+
import { Observable as Observable$1 } from 'rxjs';
|
|
6
6
|
|
|
7
7
|
var name = "@quiltt/core";
|
|
8
|
-
var version$1 = "
|
|
8
|
+
var version$1 = "5.0.0";
|
|
9
9
|
|
|
10
10
|
const QUILTT_API_INSECURE = (()=>{
|
|
11
11
|
try {
|
|
@@ -330,6 +330,7 @@ const endpointWebsockets = `${protocolWebsockets}://api.${domain}/websockets`;
|
|
|
330
330
|
* basically acts like shared memory when there is no localStorage.
|
|
331
331
|
*/ const GlobalStorage = new Storage();
|
|
332
332
|
|
|
333
|
+
// Adapted from https://github.com/rmosolgo/graphql-ruby/blob/master/javascript_client/src/subscriptions/ActionCableLink.ts
|
|
333
334
|
class ActionCableLink extends ApolloLink {
|
|
334
335
|
constructor(options){
|
|
335
336
|
super();
|
|
@@ -337,6 +338,7 @@ class ActionCableLink extends ApolloLink {
|
|
|
337
338
|
this.channelName = options.channelName || 'GraphqlChannel';
|
|
338
339
|
this.actionName = options.actionName || 'execute';
|
|
339
340
|
this.connectionParams = options.connectionParams || {};
|
|
341
|
+
this.callbacks = options.callbacks || {};
|
|
340
342
|
}
|
|
341
343
|
// Interestingly, this link does _not_ call through to `next` because
|
|
342
344
|
// instead, it sends the request to ActionCable.
|
|
@@ -344,7 +346,9 @@ class ActionCableLink extends ApolloLink {
|
|
|
344
346
|
const token = GlobalStorage.get('session');
|
|
345
347
|
if (!token) {
|
|
346
348
|
console.warn('QuilttClient attempted to send an unauthenticated Subscription');
|
|
347
|
-
return
|
|
349
|
+
return new Observable$1((observer)=>{
|
|
350
|
+
observer.error(new Error('No authentication token available'));
|
|
351
|
+
});
|
|
348
352
|
}
|
|
349
353
|
if (!this.cables[token]) {
|
|
350
354
|
this.cables[token] = createConsumer(endpointWebsockets + (token ? `?token=${token}` : ''));
|
|
@@ -353,11 +357,12 @@ class ActionCableLink extends ApolloLink {
|
|
|
353
357
|
const channelId = Math.round(Date.now() + Math.random() * 100000).toString(16);
|
|
354
358
|
const actionName = this.actionName;
|
|
355
359
|
const connectionParams = typeof this.connectionParams === 'function' ? this.connectionParams(operation) : this.connectionParams;
|
|
360
|
+
const callbacks = this.callbacks;
|
|
356
361
|
const channel = this.cables[token].subscriptions.create(Object.assign({}, {
|
|
357
362
|
channel: this.channelName,
|
|
358
363
|
channelId: channelId
|
|
359
364
|
}, connectionParams), {
|
|
360
|
-
connected: ()=>{
|
|
365
|
+
connected: (args)=>{
|
|
361
366
|
channel.perform(actionName, {
|
|
362
367
|
query: operation.query ? print(operation.query) : null,
|
|
363
368
|
variables: operation.variables,
|
|
@@ -365,6 +370,7 @@ class ActionCableLink extends ApolloLink {
|
|
|
365
370
|
operationId: operation.operationId,
|
|
366
371
|
operationName: operation.operationName
|
|
367
372
|
});
|
|
373
|
+
callbacks.connected?.(args);
|
|
368
374
|
},
|
|
369
375
|
received: (payload)=>{
|
|
370
376
|
if (payload?.result?.data || payload?.result?.errors) {
|
|
@@ -373,6 +379,10 @@ class ActionCableLink extends ApolloLink {
|
|
|
373
379
|
if (!payload.more) {
|
|
374
380
|
observer.complete();
|
|
375
381
|
}
|
|
382
|
+
callbacks.received?.(payload);
|
|
383
|
+
},
|
|
384
|
+
disconnected: ()=>{
|
|
385
|
+
callbacks.disconnected?.();
|
|
376
386
|
}
|
|
377
387
|
});
|
|
378
388
|
// Make the ActionCable subscription behave like an Apollo subscription
|
|
@@ -391,4 +401,4 @@ class SubscriptionLink extends ActionCableLink {
|
|
|
391
401
|
}
|
|
392
402
|
}
|
|
393
403
|
|
|
394
|
-
export { GlobalStorage as G, LocalStorage as L, MemoryStorage as M, Observable as O, SubscriptionLink as S, endpointAuth as a, endpointRest as b,
|
|
404
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { HttpLink as HttpLink$1 } from '@apollo/client/link/http/index.js';
|
|
9
|
-
import { RetryLink as RetryLink$1 } from '@apollo/client/link/retry/index.js';
|
|
10
|
-
import { Observable as Observable$2 } from '@apollo/client/utilities/index.js';
|
|
1
|
+
import { ApolloClient, ApolloLink } from '@apollo/client/core';
|
|
2
|
+
export { OperationVariables, gql } from '@apollo/client/core';
|
|
3
|
+
import { Observable as Observable$1 } from 'rxjs';
|
|
4
|
+
import { BatchHttpLink as BatchHttpLink$1 } from '@apollo/client/link/batch-http';
|
|
5
|
+
import { ErrorLink as ErrorLink$1 } from '@apollo/client/link/error';
|
|
6
|
+
import { HttpLink as HttpLink$1 } from '@apollo/client/link/http';
|
|
7
|
+
import { RetryLink as RetryLink$1 } from '@apollo/client/link/retry';
|
|
11
8
|
import { Consumer } from '@rails/actioncable';
|
|
12
9
|
import { Dispatch, SetStateAction } from 'react';
|
|
13
|
-
export { NormalizedCacheObject } from '@apollo/client/cache';
|
|
14
|
-
export {
|
|
15
|
-
export { useMutation, useQuery, useSubscription } from '@apollo/client/react/hooks/index.js';
|
|
10
|
+
export { InMemoryCache, NormalizedCacheObject } from '@apollo/client/cache';
|
|
11
|
+
export { useMutation, useQuery, useSubscription } from '@apollo/client/react';
|
|
16
12
|
|
|
17
13
|
interface CallbackManager {
|
|
18
14
|
onEvent(callback: ConnectorSDKOnEventCallback): void;
|
|
@@ -140,12 +136,14 @@ type ConnectorSDKConnectorOptions = ConnectorSDKCallbacks & {
|
|
|
140
136
|
nonce?: string;
|
|
141
137
|
};
|
|
142
138
|
|
|
143
|
-
type QuilttClientOptions
|
|
139
|
+
type QuilttClientOptions = Omit<ApolloClient.Options, 'link'> & {
|
|
144
140
|
/** An array of initial links to inject before the default Quiltt Links */
|
|
145
141
|
customLinks?: ApolloLink[];
|
|
142
|
+
/** Platform-specific version link (required) */
|
|
143
|
+
versionLink: ApolloLink;
|
|
146
144
|
};
|
|
147
|
-
declare class QuilttClient extends ApolloClient
|
|
148
|
-
constructor(options: QuilttClientOptions
|
|
145
|
+
declare class QuilttClient extends ApolloClient {
|
|
146
|
+
constructor(options: QuilttClientOptions);
|
|
149
147
|
}
|
|
150
148
|
|
|
151
149
|
/**
|
|
@@ -155,12 +153,12 @@ declare class QuilttClient extends ApolloClient<NormalizedCacheObject> {
|
|
|
155
153
|
* valid sessions during rotation and networking weirdness.
|
|
156
154
|
*/
|
|
157
155
|
declare class AuthLink extends ApolloLink {
|
|
158
|
-
request(operation: Operation, forward:
|
|
156
|
+
request(operation: ApolloLink.Operation, forward: ApolloLink.ForwardFunction): Observable$1<ApolloLink.Result>;
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
declare const BatchHttpLink: BatchHttpLink$1;
|
|
162
160
|
|
|
163
|
-
declare const ErrorLink:
|
|
161
|
+
declare const ErrorLink: ErrorLink$1;
|
|
164
162
|
|
|
165
163
|
declare const ForwardableLink: ApolloLink;
|
|
166
164
|
|
|
@@ -168,10 +166,17 @@ declare const HttpLink: HttpLink$1;
|
|
|
168
166
|
|
|
169
167
|
declare const RetryLink: RetryLink$1;
|
|
170
168
|
|
|
171
|
-
type RequestResult =
|
|
169
|
+
type RequestResult = ApolloLink.Result<{
|
|
172
170
|
[key: string]: unknown;
|
|
173
|
-
}
|
|
174
|
-
type ConnectionParams = object | ((operation: Operation) => object);
|
|
171
|
+
}>;
|
|
172
|
+
type ConnectionParams = object | ((operation: ApolloLink.Operation) => object);
|
|
173
|
+
type SubscriptionCallbacks = {
|
|
174
|
+
connected?: (args?: {
|
|
175
|
+
reconnected: boolean;
|
|
176
|
+
}) => void;
|
|
177
|
+
disconnected?: () => void;
|
|
178
|
+
received?: (payload: unknown) => void;
|
|
179
|
+
};
|
|
175
180
|
declare class ActionCableLink extends ApolloLink {
|
|
176
181
|
cables: {
|
|
177
182
|
[id: string]: Consumer;
|
|
@@ -179,12 +184,14 @@ declare class ActionCableLink extends ApolloLink {
|
|
|
179
184
|
channelName: string;
|
|
180
185
|
actionName: string;
|
|
181
186
|
connectionParams: ConnectionParams;
|
|
187
|
+
callbacks: SubscriptionCallbacks;
|
|
182
188
|
constructor(options: {
|
|
183
189
|
channelName?: string;
|
|
184
190
|
actionName?: string;
|
|
185
191
|
connectionParams?: ConnectionParams;
|
|
192
|
+
callbacks?: SubscriptionCallbacks;
|
|
186
193
|
});
|
|
187
|
-
request(operation: Operation, _next:
|
|
194
|
+
request(operation: ApolloLink.Operation, _next: ApolloLink.ForwardFunction): Observable$1<RequestResult>;
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
declare class SubscriptionLink extends ActionCableLink {
|
|
@@ -193,7 +200,7 @@ declare class SubscriptionLink extends ActionCableLink {
|
|
|
193
200
|
|
|
194
201
|
declare const TerminatingLink: ApolloLink;
|
|
195
202
|
|
|
196
|
-
declare const
|
|
203
|
+
declare const createVersionLink: (platformInfo: string) => ApolloLink;
|
|
197
204
|
|
|
198
205
|
type FetchResponse<T> = {
|
|
199
206
|
data: T;
|
|
@@ -292,8 +299,8 @@ type SearchResponse = FetchResponse<InstitutionsData>;
|
|
|
292
299
|
type ResolvableResponse = FetchResponse<ResolvableData>;
|
|
293
300
|
declare class ConnectorsAPI {
|
|
294
301
|
clientId: string;
|
|
295
|
-
|
|
296
|
-
constructor(clientId: string,
|
|
302
|
+
userAgent: string;
|
|
303
|
+
constructor(clientId: string, userAgent?: string);
|
|
297
304
|
/**
|
|
298
305
|
* Response Statuses:
|
|
299
306
|
* - 200: OK -> Institutions Found
|
|
@@ -496,5 +503,22 @@ declare class Timeoutable {
|
|
|
496
503
|
private broadcast;
|
|
497
504
|
}
|
|
498
505
|
|
|
499
|
-
|
|
506
|
+
/**
|
|
507
|
+
* Extracts version number from formatted version string
|
|
508
|
+
* @param formattedVersion - Formatted version like "@quiltt/core: v4.5.1"
|
|
509
|
+
* @returns Version number like "4.5.1" or "unknown" if not found
|
|
510
|
+
*/
|
|
511
|
+
declare const extractVersionNumber: (formattedVersion: string) => string;
|
|
512
|
+
/**
|
|
513
|
+
* Generates a User-Agent string following standard format
|
|
514
|
+
* Format: Quiltt/<version> (<platform-info>)
|
|
515
|
+
*/
|
|
516
|
+
declare const getUserAgent: (sdkVersion: string, platformInfo: string) => string;
|
|
517
|
+
/**
|
|
518
|
+
* Detects browser information from user agent string
|
|
519
|
+
* Returns browser name and version, or 'Unknown' if not detected
|
|
520
|
+
*/
|
|
521
|
+
declare const getBrowserInfo: () => string;
|
|
522
|
+
|
|
523
|
+
export { AuthAPI, AuthLink, AuthStrategies, BatchHttpLink, ConnectorSDKEventType, ConnectorsAPI, ErrorLink, ForwardableLink, GlobalStorage, HttpLink, JsonWebTokenParse, LocalStorage, MemoryStorage, Observable, QuilttClient, RetryLink, Storage, SubscriptionLink, TerminatingLink, Timeoutable, cdnBase, createVersionLink, debugging, endpointAuth, endpointGraphQL, endpointRest, endpointWebsockets, extractVersionNumber, getBrowserInfo, getUserAgent, version };
|
|
500
524
|
export type { BadRequestResponse, Claims, ConnectorSDK, ConnectorSDKCallbackMetadata, ConnectorSDKCallbacks, ConnectorSDKConnectOptions, ConnectorSDKConnector, ConnectorSDKConnectorOptions, ConnectorSDKOnEventCallback, ConnectorSDKOnEventExitCallback, ConnectorSDKOnExitAbortCallback, ConnectorSDKOnExitErrorCallback, ConnectorSDKOnExitSuccessCallback, ConnectorSDKOnLoadCallback, ConnectorSDKOnOpenCallback, ConnectorSDKReconnectOptions, DeepPartial, DeepReadonly, ErrorData, Exact, InputMaybe, InstitutionData, InstitutionsData, JsonWebToken, MakeMaybe, MakeOptional, Maybe, Mutable, NoContentData, Nullable, Observer, PasscodePayload, PrivateClaims, QuilttClientOptions, QuilttJWT, RegisteredClaims, ResolvableData, ResolvableResponse, SearchResponse, SessionResponse, UnauthorizedData, UnauthorizedResponse, UnprocessableData, UnprocessableResponse, UsernamePayload };
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { ApolloLink, ApolloClient } from '@apollo/client/core
|
|
2
|
-
export { gql } from '@apollo/client/core
|
|
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-
|
|
4
|
-
export { L as LocalStorage, M as MemoryStorage, O as Observable,
|
|
5
|
-
import {
|
|
1
|
+
import { ApolloLink, ApolloClient } from '@apollo/client/core';
|
|
2
|
+
export { gql } from '@apollo/client/core';
|
|
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-C2VbF8Tf.js';
|
|
4
|
+
export { L as LocalStorage, M as MemoryStorage, O as Observable, c as Storage, f as cdnBase, g as endpointWebsockets } from './SubscriptionLink-12s-C2VbF8Tf.js';
|
|
5
|
+
import { Observable } from 'rxjs';
|
|
6
|
+
import { BatchHttpLink as BatchHttpLink$1 } from '@apollo/client/link/batch-http';
|
|
6
7
|
import crossfetch from 'cross-fetch';
|
|
7
|
-
import {
|
|
8
|
-
import { HttpLink as HttpLink$1 } from '@apollo/client/link/http
|
|
9
|
-
import { RetryLink as RetryLink$1 } from '@apollo/client/link/retry
|
|
10
|
-
export { InMemoryCache } from '@apollo/client/cache
|
|
11
|
-
export { useMutation, useQuery, useSubscription } from '@apollo/client/react
|
|
8
|
+
import { ErrorLink as ErrorLink$1 } from '@apollo/client/link/error';
|
|
9
|
+
import { HttpLink as HttpLink$1 } from '@apollo/client/link/http';
|
|
10
|
+
import { RetryLink as RetryLink$1 } from '@apollo/client/link/retry';
|
|
11
|
+
export { InMemoryCache } from '@apollo/client/cache';
|
|
12
|
+
export { useMutation, useQuery, useSubscription } from '@apollo/client/react';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Enum representing the different types of events emitted by the Connector.
|
|
@@ -31,7 +32,9 @@ export { useMutation, useQuery, useSubscription } from '@apollo/client/react/hoo
|
|
|
31
32
|
const token = GlobalStorage.get('session');
|
|
32
33
|
if (!token) {
|
|
33
34
|
console.warn('QuilttLink attempted to send an unauthenticated Query');
|
|
34
|
-
return
|
|
35
|
+
return new Observable((observer)=>{
|
|
36
|
+
observer.error(new Error('No authentication token available'));
|
|
37
|
+
});
|
|
35
38
|
}
|
|
36
39
|
operation.setContext(({ headers = {} })=>({
|
|
37
40
|
headers: {
|
|
@@ -50,9 +53,12 @@ const BatchHttpLink = new BatchHttpLink$1({
|
|
|
50
53
|
fetch: effectiveFetch$2
|
|
51
54
|
});
|
|
52
55
|
|
|
53
|
-
const ErrorLink =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const ErrorLink = new ErrorLink$1(({ error, result })=>{
|
|
57
|
+
// In Apollo Client 4, errors are consolidated to the 'error' and 'result' properties
|
|
58
|
+
// Handle GraphQL errors from result
|
|
59
|
+
if (result?.errors) {
|
|
60
|
+
result.errors.forEach((graphQLError)=>{
|
|
61
|
+
const { message, path, extensions } = graphQLError;
|
|
56
62
|
const formattedPath = Array.isArray(path) ? path.join('.') : path ?? 'N/A';
|
|
57
63
|
const parts = [
|
|
58
64
|
`[Quiltt][GraphQL Error]: ${message}`,
|
|
@@ -65,12 +71,15 @@ const ErrorLink = onError(({ graphQLErrors, networkError })=>{
|
|
|
65
71
|
console.warn(parts.join(' | '));
|
|
66
72
|
});
|
|
67
73
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
// Handle network/server errors
|
|
75
|
+
if (error) {
|
|
76
|
+
if ('statusCode' in error && error.statusCode === 401) {
|
|
77
|
+
console.warn('[Quiltt][Authentication Error]:', error);
|
|
71
78
|
GlobalStorage.set('session', null);
|
|
79
|
+
} else if ('statusCode' in error) {
|
|
80
|
+
console.warn('[Quiltt][Server Error]:', error);
|
|
72
81
|
} else {
|
|
73
|
-
console.warn('[Quiltt][Network Error]:',
|
|
82
|
+
console.warn('[Quiltt][Network Error]:', error);
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
85
|
});
|
|
@@ -86,22 +95,91 @@ const HttpLink = new HttpLink$1({
|
|
|
86
95
|
|
|
87
96
|
const RetryLink = new RetryLink$1({
|
|
88
97
|
attempts: {
|
|
89
|
-
retryIf: (error, _operation)
|
|
98
|
+
retryIf: (error, _operation)=>{
|
|
99
|
+
if (!error) return false;
|
|
100
|
+
const statusCode = 'statusCode' in error ? error.statusCode : undefined;
|
|
101
|
+
return !statusCode || statusCode >= 500;
|
|
102
|
+
}
|
|
90
103
|
}
|
|
91
104
|
});
|
|
92
105
|
|
|
93
|
-
const TerminatingLink = new ApolloLink(()=>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
headers: {
|
|
98
|
-
...headers,
|
|
99
|
-
'Quiltt-Client-Version': version
|
|
100
|
-
}
|
|
101
|
-
}));
|
|
102
|
-
return forward(operation);
|
|
106
|
+
const TerminatingLink = new ApolloLink(()=>{
|
|
107
|
+
return new Observable((observer)=>{
|
|
108
|
+
observer.complete();
|
|
109
|
+
});
|
|
103
110
|
});
|
|
104
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Extracts version number from formatted version string
|
|
114
|
+
* @param formattedVersion - Formatted version like "@quiltt/core: v4.5.1"
|
|
115
|
+
* @returns Version number like "4.5.1" or "unknown" if not found
|
|
116
|
+
*/ const extractVersionNumber = (formattedVersion)=>{
|
|
117
|
+
// Find the 'v' prefix and extract version after it
|
|
118
|
+
const vIndex = formattedVersion.indexOf('v');
|
|
119
|
+
if (vIndex === -1) return 'unknown';
|
|
120
|
+
const versionPart = formattedVersion.substring(vIndex + 1);
|
|
121
|
+
const parts = versionPart.split('.');
|
|
122
|
+
// Validate we have at least major.minor.patch
|
|
123
|
+
if (parts.length < 3) return 'unknown';
|
|
124
|
+
// Extract numeric parts (handles cases like "4.5.1-beta")
|
|
125
|
+
const major = parts[0].match(/^\d+/)?.[0];
|
|
126
|
+
const minor = parts[1].match(/^\d+/)?.[0];
|
|
127
|
+
const patch = parts[2].match(/^\d+/)?.[0];
|
|
128
|
+
if (!major || !minor || !patch) return 'unknown';
|
|
129
|
+
return `${major}.${minor}.${patch}`;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Generates a User-Agent string following standard format
|
|
133
|
+
* Format: Quiltt/<version> (<platform-info>)
|
|
134
|
+
*/ const getUserAgent = (sdkVersion, platformInfo)=>{
|
|
135
|
+
return `Quiltt/${sdkVersion} (${platformInfo})`;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Detects browser information from user agent string
|
|
139
|
+
* Returns browser name and version, or 'Unknown' if not detected
|
|
140
|
+
*/ const getBrowserInfo = ()=>{
|
|
141
|
+
if (typeof navigator === 'undefined' || !navigator.userAgent) {
|
|
142
|
+
return 'Unknown';
|
|
143
|
+
}
|
|
144
|
+
const ua = navigator.userAgent;
|
|
145
|
+
// Edge (must be checked before Chrome)
|
|
146
|
+
if (ua.includes('Edg/')) {
|
|
147
|
+
const version = ua.match(/Edg\/(\d+)/)?.[1] || 'Unknown';
|
|
148
|
+
return `Edge/${version}`;
|
|
149
|
+
}
|
|
150
|
+
// Chrome
|
|
151
|
+
if (ua.includes('Chrome/') && !ua.includes('Edg/')) {
|
|
152
|
+
const version = ua.match(/Chrome\/(\d+)/)?.[1] || 'Unknown';
|
|
153
|
+
return `Chrome/${version}`;
|
|
154
|
+
}
|
|
155
|
+
// Safari (must be checked after Chrome)
|
|
156
|
+
if (ua.includes('Safari/') && !ua.includes('Chrome/')) {
|
|
157
|
+
const version = ua.match(/Version\/(\d+)/)?.[1] || 'Unknown';
|
|
158
|
+
return `Safari/${version}`;
|
|
159
|
+
}
|
|
160
|
+
// Firefox
|
|
161
|
+
if (ua.includes('Firefox/')) {
|
|
162
|
+
const version = ua.match(/Firefox\/(\d+)/)?.[1] || 'Unknown';
|
|
163
|
+
return `Firefox/${version}`;
|
|
164
|
+
}
|
|
165
|
+
return 'Unknown';
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const createVersionLink = (platformInfo)=>{
|
|
169
|
+
const versionNumber = extractVersionNumber(version);
|
|
170
|
+
const userAgent = getUserAgent(versionNumber, platformInfo);
|
|
171
|
+
return new ApolloLink((operation, forward)=>{
|
|
172
|
+
operation.setContext(({ headers = {} })=>({
|
|
173
|
+
headers: {
|
|
174
|
+
...headers,
|
|
175
|
+
'Quiltt-Client-Version': version,
|
|
176
|
+
'User-Agent': userAgent
|
|
177
|
+
}
|
|
178
|
+
}));
|
|
179
|
+
return forward(operation);
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
|
|
105
183
|
class QuilttClient extends ApolloClient {
|
|
106
184
|
constructor(options){
|
|
107
185
|
const finalOptions = {
|
|
@@ -124,7 +202,7 @@ class QuilttClient extends ApolloClient {
|
|
|
124
202
|
const subscriptionsLink = new SubscriptionLink();
|
|
125
203
|
const quilttLink = ApolloLink.from([
|
|
126
204
|
...initialLinks,
|
|
127
|
-
|
|
205
|
+
options.versionLink,
|
|
128
206
|
authLink,
|
|
129
207
|
ErrorLink,
|
|
130
208
|
RetryLink
|
|
@@ -268,7 +346,7 @@ class AuthAPI {
|
|
|
268
346
|
}
|
|
269
347
|
|
|
270
348
|
class ConnectorsAPI {
|
|
271
|
-
constructor(clientId,
|
|
349
|
+
constructor(clientId, userAgent = getUserAgent(extractVersionNumber(version), 'Unknown')){
|
|
272
350
|
/**
|
|
273
351
|
* Response Statuses:
|
|
274
352
|
* - 200: OK -> Institutions Found
|
|
@@ -309,7 +387,7 @@ class ConnectorsAPI {
|
|
|
309
387
|
const headers = new Headers();
|
|
310
388
|
headers.set('Content-Type', 'application/json');
|
|
311
389
|
headers.set('Accept', 'application/json');
|
|
312
|
-
headers.set('
|
|
390
|
+
headers.set('User-Agent', this.userAgent);
|
|
313
391
|
headers.set('Authorization', `Bearer ${token}`);
|
|
314
392
|
return {
|
|
315
393
|
headers,
|
|
@@ -319,7 +397,7 @@ class ConnectorsAPI {
|
|
|
319
397
|
};
|
|
320
398
|
this.validateStatus = (status)=>status < 500 && status !== 429;
|
|
321
399
|
this.clientId = clientId;
|
|
322
|
-
this.
|
|
400
|
+
this.userAgent = userAgent;
|
|
323
401
|
}
|
|
324
402
|
}
|
|
325
403
|
|
|
@@ -368,4 +446,4 @@ const JsonWebTokenParse = (token)=>{
|
|
|
368
446
|
}
|
|
369
447
|
}
|
|
370
448
|
|
|
371
|
-
export { AuthAPI, AuthLink, AuthStrategies, BatchHttpLink, ConnectorSDKEventType, ConnectorsAPI, ErrorLink, ForwardableLink, GlobalStorage, HttpLink, JsonWebTokenParse, QuilttClient, RetryLink, SubscriptionLink, TerminatingLink, Timeoutable,
|
|
449
|
+
export { AuthAPI, AuthLink, AuthStrategies, BatchHttpLink, ConnectorSDKEventType, ConnectorsAPI, ErrorLink, ForwardableLink, GlobalStorage, HttpLink, JsonWebTokenParse, QuilttClient, RetryLink, SubscriptionLink, TerminatingLink, Timeoutable, createVersionLink, debugging, endpointAuth, endpointGraphQL, endpointRest, extractVersionNumber, getBrowserInfo, getUserAgent, version };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiltt/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Javascript API client and utilities for Quiltt",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"quiltt",
|
|
@@ -32,19 +32,19 @@
|
|
|
32
32
|
],
|
|
33
33
|
"main": "dist/index.js",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@apollo/client": "^
|
|
36
|
-
"@rails/actioncable": "^8.
|
|
35
|
+
"@apollo/client": "^4.1.3",
|
|
36
|
+
"@rails/actioncable": "^8.1.200",
|
|
37
37
|
"braces": "^3.0.3",
|
|
38
|
-
"cross-fetch": "^4.
|
|
39
|
-
"graphql": "^16.
|
|
40
|
-
"
|
|
38
|
+
"cross-fetch": "^4.1.0",
|
|
39
|
+
"graphql": "^16.12.0",
|
|
40
|
+
"rxjs": "^7.8.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@biomejs/biome": "2.3.
|
|
44
|
-
"@types/node": "
|
|
45
|
-
"@types/rails__actioncable": "
|
|
46
|
-
"@types/react": "19.2.
|
|
47
|
-
"bunchee": "6.
|
|
43
|
+
"@biomejs/biome": "2.3.13",
|
|
44
|
+
"@types/node": "24.10.9",
|
|
45
|
+
"@types/rails__actioncable": "8.0.3",
|
|
46
|
+
"@types/react": "19.2.10",
|
|
47
|
+
"bunchee": "6.9.4",
|
|
48
48
|
"rimraf": "6.1.2",
|
|
49
49
|
"typescript": "5.9.3"
|
|
50
50
|
},
|
|
@@ -1,5 +1,4 @@
|
|
|
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
4
|
import { debugging } from '@/configuration'
|
|
@@ -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,46 +1,55 @@
|
|
|
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
8
|
import { endpointWebsockets } from '@/configuration'
|
|
9
9
|
import { GlobalStorage } from '@/storage'
|
|
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(
|
|
42
|
+
request(
|
|
43
|
+
operation: ApolloLink.Operation,
|
|
44
|
+
_next: ApolloLink.ForwardFunction
|
|
45
|
+
): Observable<RequestResult> {
|
|
39
46
|
const token = GlobalStorage.get('session')
|
|
40
47
|
|
|
41
48
|
if (!token) {
|
|
42
49
|
console.warn('QuilttClient attempted to send an unauthenticated Subscription')
|
|
43
|
-
return
|
|
50
|
+
return new Observable((observer) => {
|
|
51
|
+
observer.error(new Error('No authentication token available'))
|
|
52
|
+
})
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
if (!this.cables[token]) {
|
|
@@ -55,6 +64,7 @@ class ActionCableLink extends ApolloLink {
|
|
|
55
64
|
? this.connectionParams(operation)
|
|
56
65
|
: this.connectionParams
|
|
57
66
|
|
|
67
|
+
const callbacks = this.callbacks
|
|
58
68
|
const channel = this.cables[token].subscriptions.create(
|
|
59
69
|
Object.assign(
|
|
60
70
|
{},
|
|
@@ -65,7 +75,7 @@ class ActionCableLink extends ApolloLink {
|
|
|
65
75
|
connectionParams
|
|
66
76
|
),
|
|
67
77
|
{
|
|
68
|
-
connected: () => {
|
|
78
|
+
connected: (args?: { reconnected: boolean }) => {
|
|
69
79
|
channel.perform(actionName, {
|
|
70
80
|
query: operation.query ? print(operation.query) : null,
|
|
71
81
|
variables: operation.variables,
|
|
@@ -73,6 +83,7 @@ class ActionCableLink extends ApolloLink {
|
|
|
73
83
|
operationId: (operation as { operationId?: string }).operationId,
|
|
74
84
|
operationName: operation.operationName,
|
|
75
85
|
})
|
|
86
|
+
callbacks.connected?.(args)
|
|
76
87
|
},
|
|
77
88
|
|
|
78
89
|
received: (payload: { result: RequestResult; more: any }) => {
|
|
@@ -83,6 +94,11 @@ class ActionCableLink extends ApolloLink {
|
|
|
83
94
|
if (!payload.more) {
|
|
84
95
|
observer.complete()
|
|
85
96
|
}
|
|
97
|
+
|
|
98
|
+
callbacks.received?.(payload)
|
|
99
|
+
},
|
|
100
|
+
disconnected: () => {
|
|
101
|
+
callbacks.disconnected?.()
|
|
86
102
|
},
|
|
87
103
|
}
|
|
88
104
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
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
4
|
import { GlobalStorage } from '@/storage'
|
|
6
5
|
|
|
@@ -11,12 +10,17 @@ import { GlobalStorage } from '@/storage'
|
|
|
11
10
|
* valid sessions during rotation and networking weirdness.
|
|
12
11
|
*/
|
|
13
12
|
export class AuthLink extends ApolloLink {
|
|
14
|
-
request(
|
|
13
|
+
request(
|
|
14
|
+
operation: ApolloLink.Operation,
|
|
15
|
+
forward: ApolloLink.ForwardFunction
|
|
16
|
+
): Observable<ApolloLink.Result> {
|
|
15
17
|
const token = GlobalStorage.get('session')
|
|
16
18
|
|
|
17
19
|
if (!token) {
|
|
18
20
|
console.warn('QuilttLink attempted to send an unauthenticated Query')
|
|
19
|
-
return
|
|
21
|
+
return new Observable((observer) => {
|
|
22
|
+
observer.error(new Error('No authentication token available'))
|
|
23
|
+
})
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
operation.setContext(({ headers = {} }) => ({
|
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
import { endpointGraphQL } from '@/configuration'
|
|
@@ -1,11 +1,15 @@
|
|
|
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
|
|
|
@@ -18,12 +22,15 @@ export const ErrorLink = onError(({ graphQLErrors, networkError }) => {
|
|
|
18
22
|
})
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
// Handle network/server errors
|
|
26
|
+
if (error) {
|
|
27
|
+
if ('statusCode' in error && error.statusCode === 401) {
|
|
28
|
+
console.warn('[Quiltt][Authentication Error]:', error)
|
|
24
29
|
GlobalStorage.set('session', null)
|
|
30
|
+
} else if ('statusCode' in error) {
|
|
31
|
+
console.warn('[Quiltt][Server Error]:', error)
|
|
25
32
|
} else {
|
|
26
|
-
console.warn('[Quiltt][Network Error]:',
|
|
33
|
+
console.warn('[Quiltt][Network Error]:', error)
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
36
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
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
|
|
@@ -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
3
|
import { version } from '@/configuration'
|
|
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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { endpointRest } from '@/configuration'
|
|
1
|
+
import { endpointRest, version } from '@/configuration'
|
|
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 {
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
export const extractVersionNumber = (formattedVersion: string): string => {
|
|
7
|
+
// Find the 'v' prefix and extract version after it
|
|
8
|
+
const vIndex = formattedVersion.indexOf('v')
|
|
9
|
+
if (vIndex === -1) return 'unknown'
|
|
10
|
+
|
|
11
|
+
const versionPart = formattedVersion.substring(vIndex + 1)
|
|
12
|
+
const parts = versionPart.split('.')
|
|
13
|
+
|
|
14
|
+
// Validate we have at least major.minor.patch
|
|
15
|
+
if (parts.length < 3) return 'unknown'
|
|
16
|
+
|
|
17
|
+
// Extract numeric parts (handles cases like "4.5.1-beta")
|
|
18
|
+
const major = parts[0].match(/^\d+/)?.[0]
|
|
19
|
+
const minor = parts[1].match(/^\d+/)?.[0]
|
|
20
|
+
const patch = parts[2].match(/^\d+/)?.[0]
|
|
21
|
+
|
|
22
|
+
if (!major || !minor || !patch) return 'unknown'
|
|
23
|
+
|
|
24
|
+
return `${major}.${minor}.${patch}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates a User-Agent string following standard format
|
|
29
|
+
* Format: Quiltt/<version> (<platform-info>)
|
|
30
|
+
*/
|
|
31
|
+
export const getUserAgent = (sdkVersion: string, platformInfo: string): string => {
|
|
32
|
+
return `Quiltt/${sdkVersion} (${platformInfo})`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Detects browser information from user agent string
|
|
37
|
+
* Returns browser name and version, or 'Unknown' if not detected
|
|
38
|
+
*/
|
|
39
|
+
export const getBrowserInfo = (): string => {
|
|
40
|
+
if (typeof navigator === 'undefined' || !navigator.userAgent) {
|
|
41
|
+
return 'Unknown'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ua = navigator.userAgent
|
|
45
|
+
|
|
46
|
+
// Edge (must be checked before Chrome)
|
|
47
|
+
if (ua.includes('Edg/')) {
|
|
48
|
+
const version = ua.match(/Edg\/(\d+)/)?.[1] || 'Unknown'
|
|
49
|
+
return `Edge/${version}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Chrome
|
|
53
|
+
if (ua.includes('Chrome/') && !ua.includes('Edg/')) {
|
|
54
|
+
const version = ua.match(/Chrome\/(\d+)/)?.[1] || 'Unknown'
|
|
55
|
+
return `Chrome/${version}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Safari (must be checked after Chrome)
|
|
59
|
+
if (ua.includes('Safari/') && !ua.includes('Chrome/')) {
|
|
60
|
+
const version = ua.match(/Version\/(\d+)/)?.[1] || 'Unknown'
|
|
61
|
+
return `Safari/${version}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Firefox
|
|
65
|
+
if (ua.includes('Firefox/')) {
|
|
66
|
+
const version = ua.match(/Firefox\/(\d+)/)?.[1] || 'Unknown'
|
|
67
|
+
return `Firefox/${version}`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return 'Unknown'
|
|
71
|
+
}
|