@quiltt/core 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +19 -12
  3. package/dist/api/browser.cjs +14 -0
  4. package/dist/api/browser.d.ts +128 -0
  5. package/dist/api/browser.js +12 -0
  6. package/dist/api/graphql/SubscriptionLink-12s-ufJBKwu1.js +149 -0
  7. package/dist/api/graphql/SubscriptionLink-12s-wjkChfxO.cjs +150 -0
  8. package/dist/api/graphql/index.cjs +218 -0
  9. package/dist/api/graphql/index.d.ts +82 -0
  10. package/dist/api/graphql/index.js +184 -0
  11. package/dist/api/index.cjs +26 -0
  12. package/dist/api/index.d.ts +3 -0
  13. package/dist/api/index.js +3 -0
  14. package/dist/api/rest/index.cjs +225 -0
  15. package/dist/api/rest/index.d.ts +128 -0
  16. package/dist/api/rest/index.js +217 -0
  17. package/dist/auth/index.cjs +21 -0
  18. package/dist/auth/index.d.ts +29 -0
  19. package/dist/auth/index.js +19 -0
  20. package/dist/config/index.cjs +44 -0
  21. package/dist/config/index.d.ts +9 -0
  22. package/dist/config/index.js +36 -0
  23. package/dist/index.cjs +61 -0
  24. package/dist/index.d.ts +8 -524
  25. package/dist/index.js +8 -449
  26. package/dist/observables/index.cjs +30 -0
  27. package/dist/observables/index.d.ts +21 -0
  28. package/dist/observables/index.js +28 -0
  29. package/dist/storage/index.cjs +272 -0
  30. package/dist/storage/index.d.ts +91 -0
  31. package/dist/{SubscriptionLink-12s-C2VbF8Tf.js → storage/index.js} +2 -139
  32. package/dist/timing/index.cjs +30 -0
  33. package/dist/timing/index.d.ts +15 -0
  34. package/dist/timing/index.js +28 -0
  35. package/dist/types.cjs +1 -0
  36. package/dist/types.d.ts +28 -0
  37. package/dist/types.js +1 -0
  38. package/dist/utils/index.cjs +61 -0
  39. package/dist/utils/index.d.ts +18 -0
  40. package/dist/utils/index.js +57 -0
  41. package/package.json +62 -6
  42. package/src/api/graphql/client.ts +1 -1
  43. package/src/api/graphql/links/ActionCableLink.ts +7 -6
  44. package/src/api/graphql/links/AuthLink.ts +13 -9
  45. package/src/api/graphql/links/BatchHttpLink.ts +1 -1
  46. package/src/api/graphql/links/ErrorLink.ts +4 -0
  47. package/src/api/graphql/links/HttpLink.ts +1 -1
  48. package/src/api/graphql/links/VersionLink.ts +1 -1
  49. package/src/api/rest/auth.ts +1 -1
  50. package/src/api/rest/connectors.ts +1 -1
  51. package/src/auth/index.ts +1 -0
  52. package/src/{JsonWebToken.ts → auth/json-web-token.ts} +1 -1
  53. package/src/{configuration.ts → config/configuration.ts} +1 -1
  54. package/src/config/index.ts +1 -0
  55. package/src/index.ts +5 -5
  56. package/src/observables/index.ts +1 -0
  57. package/src/{Observable.ts → observables/observable.ts} +1 -1
  58. package/src/storage/Local.ts +1 -1
  59. package/src/storage/Memory.ts +2 -2
  60. package/src/storage/Storage.ts +1 -1
  61. package/src/timing/index.ts +1 -0
  62. package/src/{Timeoutable.ts → timing/timeoutable.ts} +1 -1
  63. package/src/utils/index.ts +1 -0
  64. package/src/utils/token-validation.ts +67 -0
@@ -0,0 +1,61 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ /**
4
+ * Extracts version number from formatted version string
5
+ * @param formattedVersion - Formatted version like "@quiltt/core: v4.5.1"
6
+ * @returns Version number like "4.5.1" or "unknown" if not found
7
+ */ const extractVersionNumber = (formattedVersion)=>{
8
+ // Find the 'v' prefix and extract version after it
9
+ const vIndex = formattedVersion.indexOf('v');
10
+ if (vIndex === -1) return 'unknown';
11
+ const versionPart = formattedVersion.substring(vIndex + 1);
12
+ const parts = versionPart.split('.');
13
+ // Validate we have at least major.minor.patch
14
+ if (parts.length < 3) return 'unknown';
15
+ // Extract numeric parts (handles cases like "4.5.1-beta")
16
+ const major = parts[0].match(/^\d+/)?.[0];
17
+ const minor = parts[1].match(/^\d+/)?.[0];
18
+ const patch = parts[2].match(/^\d+/)?.[0];
19
+ if (!major || !minor || !patch) return 'unknown';
20
+ return `${major}.${minor}.${patch}`;
21
+ };
22
+ /**
23
+ * Generates a User-Agent string following standard format
24
+ * Format: Quiltt/<version> (<platform-info>)
25
+ */ const getUserAgent = (sdkVersion, platformInfo)=>{
26
+ return `Quiltt/${sdkVersion} (${platformInfo})`;
27
+ };
28
+ /**
29
+ * Detects browser information from user agent string
30
+ * Returns browser name and version, or 'Unknown' if not detected
31
+ */ const getBrowserInfo = ()=>{
32
+ if (typeof navigator === 'undefined' || !navigator.userAgent) {
33
+ return 'Unknown';
34
+ }
35
+ const ua = navigator.userAgent;
36
+ // Edge (must be checked before Chrome)
37
+ if (ua.includes('Edg/')) {
38
+ const version = ua.match(/Edg\/(\d+)/)?.[1] || 'Unknown';
39
+ return `Edge/${version}`;
40
+ }
41
+ // Chrome
42
+ if (ua.includes('Chrome/') && !ua.includes('Edg/')) {
43
+ const version = ua.match(/Chrome\/(\d+)/)?.[1] || 'Unknown';
44
+ return `Chrome/${version}`;
45
+ }
46
+ // Safari (must be checked after Chrome)
47
+ if (ua.includes('Safari/') && !ua.includes('Chrome/')) {
48
+ const version = ua.match(/Version\/(\d+)/)?.[1] || 'Unknown';
49
+ return `Safari/${version}`;
50
+ }
51
+ // Firefox
52
+ if (ua.includes('Firefox/')) {
53
+ const version = ua.match(/Firefox\/(\d+)/)?.[1] || 'Unknown';
54
+ return `Firefox/${version}`;
55
+ }
56
+ return 'Unknown';
57
+ };
58
+
59
+ exports.extractVersionNumber = extractVersionNumber;
60
+ exports.getBrowserInfo = getBrowserInfo;
61
+ exports.getUserAgent = getUserAgent;
@@ -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": "5.0.0",
3
+ "version": "5.0.2",
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
- "import": "./dist/index.js",
24
- "types": "./dist/index.d.ts"
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",
@@ -40,10 +96,10 @@
40
96
  "rxjs": "^7.8.2"
41
97
  },
42
98
  "devDependencies": {
43
- "@biomejs/biome": "2.3.13",
44
- "@types/node": "24.10.9",
99
+ "@biomejs/biome": "2.3.14",
100
+ "@types/node": "24.10.10",
45
101
  "@types/rails__actioncable": "8.0.3",
46
- "@types/react": "19.2.10",
102
+ "@types/react": "19.2.11",
47
103
  "bunchee": "6.9.4",
48
104
  "rimraf": "6.1.2",
49
105
  "typescript": "5.9.3"
@@ -1,7 +1,7 @@
1
1
  import { ApolloClient, ApolloLink } from '@apollo/client/core'
2
2
  import type { DefinitionNode, OperationDefinitionNode } from 'graphql'
3
3
 
4
- import { debugging } from '@/configuration'
4
+ import { debugging } from '@/config'
5
5
 
6
6
  import {
7
7
  AuthLink,
@@ -5,8 +5,8 @@ import { createConsumer } from '@rails/actioncable'
5
5
  import { print } from 'graphql'
6
6
  import { Observable } from 'rxjs'
7
7
 
8
- import { endpointWebsockets } from '@/configuration'
9
- import { GlobalStorage } from '@/storage'
8
+ import { endpointWebsockets } from '@/config'
9
+ import { validateSessionToken } from '@/utils/token-validation'
10
10
 
11
11
  type RequestResult = ApolloLink.Result<{ [key: string]: unknown }>
12
12
  type ConnectionParams = object | ((operation: ApolloLink.Operation) => object)
@@ -43,15 +43,16 @@ class ActionCableLink extends ApolloLink {
43
43
  operation: ApolloLink.Operation,
44
44
  _next: ApolloLink.ForwardFunction
45
45
  ): Observable<RequestResult> {
46
- const token = GlobalStorage.get('session')
46
+ const validation = validateSessionToken('for subscription')
47
47
 
48
- if (!token) {
49
- console.warn('QuilttClient attempted to send an unauthenticated Subscription')
48
+ if (!validation.valid) {
50
49
  return new Observable((observer) => {
51
- observer.error(new Error('No authentication token available'))
50
+ observer.error(validation.error)
52
51
  })
53
52
  }
54
53
 
54
+ const { token } = validation
55
+
55
56
  if (!this.cables[token]) {
56
57
  this.cables[token] = createConsumer(endpointWebsockets + (token ? `?token=${token}` : ''))
57
58
  }
@@ -1,28 +1,32 @@
1
1
  import { ApolloLink } from '@apollo/client/core'
2
2
  import { Observable } from 'rxjs'
3
3
 
4
- import { GlobalStorage } from '@/storage'
4
+ import { validateSessionToken } from '@/utils/token-validation'
5
5
 
6
6
  /**
7
- * unauthorizedCallback only triggers in the event the token is present, and
8
- * returns the token; This allows sessions to be forgotten without race conditions
9
- * causing null sessions to kill valid sessions, or invalid sessions for killing
10
- * valid sessions during rotation and networking weirdness.
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
11
14
  */
12
15
  export class AuthLink extends ApolloLink {
13
16
  request(
14
17
  operation: ApolloLink.Operation,
15
18
  forward: ApolloLink.ForwardFunction
16
19
  ): Observable<ApolloLink.Result> {
17
- const token = GlobalStorage.get('session')
20
+ const validation = validateSessionToken()
18
21
 
19
- if (!token) {
20
- console.warn('QuilttLink attempted to send an unauthenticated Query')
22
+ if (!validation.valid) {
21
23
  return new Observable((observer) => {
22
- observer.error(new Error('No authentication token available'))
24
+ observer.error(validation.error)
23
25
  })
24
26
  }
25
27
 
28
+ const { token } = validation
29
+
26
30
  operation.setContext(({ headers = {} }) => ({
27
31
  headers: {
28
32
  ...headers,
@@ -1,7 +1,7 @@
1
1
  import { BatchHttpLink as ApolloBatchHttpLink } from '@apollo/client/link/batch-http'
2
2
  import crossfetch from 'cross-fetch'
3
3
 
4
- import { endpointGraphQL } from '@/configuration'
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
@@ -16,6 +16,10 @@ export const ErrorLink = new ApolloErrorLink(({ error, result }) => {
16
16
  if (extensions) {
17
17
  if (extensions.code) parts.push(`Code: ${extensions.code}`)
18
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
+ }
19
23
  }
20
24
 
21
25
  console.warn(parts.join(' | '))
@@ -4,7 +4,7 @@ import crossfetch from 'cross-fetch'
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 '@/configuration'
7
+ import { endpointGraphQL } from '@/config'
8
8
 
9
9
  export const HttpLink = new ApolloHttpLink({
10
10
  uri: endpointGraphQL,
@@ -1,6 +1,6 @@
1
1
  import { ApolloLink } from '@apollo/client/core'
2
2
 
3
- import { version } from '@/configuration'
3
+ import { version } from '@/config'
4
4
  import { extractVersionNumber, getUserAgent } from '@/utils/telemetry'
5
5
 
6
6
  export const createVersionLink = (platformInfo: string) => {
@@ -1,4 +1,4 @@
1
- import { endpointAuth } from '@/configuration'
1
+ import { endpointAuth } from '@/config'
2
2
 
3
3
  import type { FetchResponse } from './fetchWithRetry'
4
4
  import { fetchWithRetry } from './fetchWithRetry'
@@ -1,4 +1,4 @@
1
- import { endpointRest, version } from '@/configuration'
1
+ import { endpointRest, version } from '@/config'
2
2
  import { extractVersionNumber, getUserAgent } from '@/utils/telemetry'
3
3
 
4
4
  import type { FetchResponse } from './fetchWithRetry'
@@ -0,0 +1 @@
1
+ export * from './json-web-token'
@@ -1,4 +1,4 @@
1
- import type { Maybe } from './types'
1
+ import type { Maybe } from '../types'
2
2
 
3
3
  export type RegisteredClaims = {
4
4
  iss: string // (issuer): Issuer of the JWT
@@ -1,4 +1,4 @@
1
- import { name as packageName, version as packageVersion } from '../package.json'
1
+ import { name as packageName, version as packageVersion } from '../../package.json'
2
2
 
3
3
  const QUILTT_API_INSECURE = (() => {
4
4
  try {
@@ -0,0 +1 @@
1
+ export * from './configuration'
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export * from './api'
2
- export * from './configuration'
3
- export * from './JsonWebToken'
4
- export * from './Observable'
2
+ export * from './auth'
3
+ export * from './config'
4
+ export * from './observables'
5
5
  export * from './storage'
6
- export * from './Timeoutable'
6
+ export * from './timing'
7
7
  export * from './types'
8
- export * from './utils/telemetry'
8
+ export * from './utils'
@@ -0,0 +1 @@
1
+ export * from './observable'
@@ -1,6 +1,6 @@
1
1
  import type { Dispatch, SetStateAction } from 'react'
2
2
 
3
- import type { Maybe } from './types'
3
+ import type { Maybe } from '../types'
4
4
 
5
5
  export type Observer<T> = Dispatch<SetStateAction<Maybe<T> | undefined>>
6
6
 
@@ -1,4 +1,4 @@
1
- import type { Observer } from '@/Observable'
1
+ import type { Observer } from '@/observables'
2
2
  import type { Maybe } from '@/types'
3
3
 
4
4
  /**
@@ -1,5 +1,5 @@
1
- import type { Observer } from '@/Observable'
2
- import { Observable } from '@/Observable'
1
+ import type { Observer } from '@/observables'
2
+ import { Observable } from '@/observables'
3
3
  import type { Maybe } from '@/types'
4
4
 
5
5
  /**
@@ -1,4 +1,4 @@
1
- import type { Observer } from '@/Observable'
1
+ import type { Observer } from '@/observables'
2
2
  import { LocalStorage } from '@/storage/Local'
3
3
  import { MemoryStorage } from '@/storage/Memory'
4
4
  import type { Maybe } from '@/types'
@@ -0,0 +1 @@
1
+ export * from './timeoutable'
@@ -1,4 +1,4 @@
1
- import type { Observer } from './Observable'
1
+ import type { Observer } from '../observables'
2
2
 
3
3
  /**
4
4
  * This is designed to support singletons to timeouts that can broadcast
@@ -0,0 +1 @@
1
+ export * from './telemetry'
@@ -0,0 +1,67 @@
1
+ import { GraphQLError } from 'graphql'
2
+
3
+ import { JsonWebTokenParse } from '@/auth/json-web-token'
4
+ import { GlobalStorage } from '@/storage'
5
+
6
+ /**
7
+ * Result of token validation
8
+ */
9
+ export type TokenValidationResult =
10
+ | { valid: true; token: string }
11
+ | { valid: false; error: GraphQLError }
12
+
13
+ /**
14
+ * Validates the session token from GlobalStorage.
15
+ *
16
+ * This function:
17
+ * - Checks if a session token exists
18
+ * - Validates token expiration
19
+ * - Clears expired tokens from storage (triggers observers and React re-renders)
20
+ * - Returns appropriate GraphQL errors for authentication failures
21
+ *
22
+ * @param errorMessagePrefix - Optional prefix for error messages (e.g., "for subscription")
23
+ * @returns TokenValidationResult indicating whether the token is valid or providing an error
24
+ */
25
+ export function validateSessionToken(errorMessagePrefix = ''): TokenValidationResult {
26
+ const token = GlobalStorage.get('session')
27
+
28
+ if (!token) {
29
+ return {
30
+ valid: false,
31
+ error: new GraphQLError(
32
+ `No session token available${errorMessagePrefix ? ` ${errorMessagePrefix}` : ''}`,
33
+ {
34
+ extensions: {
35
+ code: 'UNAUTHENTICATED',
36
+ reason: 'NO_TOKEN',
37
+ documentationUrl: 'https://www.quiltt.dev/authentication#session-tokens',
38
+ },
39
+ }
40
+ ),
41
+ }
42
+ }
43
+
44
+ // Check if token is expired
45
+ const jwt = JsonWebTokenParse(token)
46
+ if (jwt?.claims.exp) {
47
+ const nowInSeconds = Math.floor(Date.now() / 1000)
48
+ if (jwt.claims.exp < nowInSeconds) {
49
+ // Clear expired token - this triggers observers and React re-renders
50
+ GlobalStorage.set('session', null)
51
+
52
+ return {
53
+ valid: false,
54
+ error: new GraphQLError('Session token has expired', {
55
+ extensions: {
56
+ code: 'UNAUTHENTICATED',
57
+ reason: 'TOKEN_EXPIRED',
58
+ expiredAt: jwt.claims.exp,
59
+ documentationUrl: 'https://www.quiltt.dev/authentication#session-tokens',
60
+ },
61
+ }),
62
+ }
63
+ }
64
+ }
65
+
66
+ return { valid: true, token }
67
+ }