@@ -4,10 +4,27 @@ import {
4
4
RecordSource ,
5
5
Store ,
6
6
FetchFunction ,
7
+ SubscribeFunction ,
8
+ GraphQLResponse ,
9
+ Observable ,
7
10
} from "relay-runtime" ;
8
11
import keycloak from "./keycloak" ;
12
+ import { createClient } from "graphql-ws" ;
9
13
10
14
const HTTP_ENDPOINT = import . meta. env . VITE_GRAPH_URL ;
15
+ const WS_ENDPOINT = import . meta. env . VITE_GRAPH_WS_URL ;
16
+
17
+ let kcinitPromise : Promise < boolean > | null = null ;
18
+
19
+ // needed to prevent repeated refresh of page when using subscriptions
20
+ function ensureKeycloakInit ( ) : Promise < boolean > {
21
+ if ( ! kcinitPromise ) {
22
+ kcinitPromise = keycloak . init ( {
23
+ onLoad : "login-required" ,
24
+ } ) ;
25
+ }
26
+ return kcinitPromise ;
27
+ }
11
28
12
29
keycloak . onTokenExpired = ( ) => {
13
30
console . log ( "JWT expired" ) ;
@@ -25,23 +42,9 @@ keycloak.onTokenExpired = () => {
25
42
} ) ;
26
43
} ;
27
44
28
- const kcinit = keycloak
29
- . init ( {
30
- onLoad : "login-required" ,
31
- } )
32
- . then (
33
- ( auth ) => {
34
- console . info ( "Authenticated" ) ;
35
- console . log ( "auth" , auth ) ;
36
- } ,
37
- ( ) => {
38
- console . error ( "Authentication failed" ) ;
39
- } ,
40
- ) ;
41
-
42
45
const fetchFn : FetchFunction = async ( request , variables ) => {
43
46
if ( ! keycloak . authenticated ) {
44
- await kcinit ;
47
+ await ensureKeycloakInit ( ) ;
45
48
}
46
49
47
50
if ( keycloak . token ) {
@@ -66,11 +69,55 @@ const fetchFn: FetchFunction = async (request, variables) => {
66
69
}
67
70
} ;
68
71
69
- function createRelayEnvironment ( ) {
70
- return new Environment ( {
71
- network : Network . create ( fetchFn ) ,
72
- store : new Store ( new RecordSource ( ) ) ,
72
+ const wsClient = createClient ( {
73
+ url : WS_ENDPOINT ,
74
+ connectionParams : async ( ) => {
75
+ if ( ! keycloak . authenticated ) {
76
+ await ensureKeycloakInit ( ) ;
77
+ }
78
+ return {
79
+ Authorization : `Bearer ${ keycloak . token ?? "" } ` ,
80
+ } ;
81
+ } ,
82
+ } ) ;
83
+
84
+ const subscribeFn : SubscribeFunction = ( operation , variables ) => {
85
+ return Observable . create ( ( sink ) => {
86
+ const cleanup = wsClient . subscribe (
87
+ {
88
+ operationName : operation . name ,
89
+ query : operation . text ?? "" ,
90
+ variables,
91
+ } ,
92
+ {
93
+ next : ( response ) => {
94
+ const data = response . data ;
95
+ if ( data ) {
96
+ sink . next ( { data } as GraphQLResponse ) ;
97
+ } else if ( data == null ) {
98
+ console . warn ( "Data is null:" , response ) ;
99
+ } else {
100
+ console . error ( "Subscription error response:" , response ) ;
101
+ sink . error ( new Error ( "Subscription response missing data" ) ) ;
102
+ }
103
+ } ,
104
+ error : sink . error . bind ( sink ) ,
105
+ complete : sink . complete . bind ( sink ) ,
106
+ } ,
107
+ ) ;
108
+ return cleanup ;
73
109
} ) ;
74
- }
110
+ } ;
75
111
76
- export const RelayEnvironment = createRelayEnvironment ( ) ;
112
+ let RelayEnvironment : Environment | null = null ;
113
+
114
+ export async function getRelayEnvironment ( ) : Promise < Environment > {
115
+ if ( ! RelayEnvironment ) {
116
+ await ensureKeycloakInit ( ) ;
117
+ RelayEnvironment = new Environment ( {
118
+ network : Network . create ( fetchFn , subscribeFn ) ,
119
+ store : new Store ( new RecordSource ( ) ) ,
120
+ } ) ;
121
+ }
122
+ return RelayEnvironment ;
123
+ }
0 commit comments