forked from NavidK0/SimpleGraphQL-For-Unity
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGraphQLClient.cs
358 lines (319 loc) · 12.3 KB
/
GraphQLClient.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json;
using UnityEngine;
namespace SimpleGraphQL
{
/// <summary>
/// This API object is meant to be reused, so keep an instance of it somewhere!
/// Multiple GraphQLClients can be used with different configs based on needs.
/// </summary>
[PublicAPI]
public class GraphQLClient
{
public readonly List<Query> SearchableQueries;
public readonly Dictionary<string, string> CustomHeaders;
public string Endpoint;
public string AuthScheme;
// track the running subscriptions ids
internal HashSet<string> RunningSubscriptions;
public GraphQLClient(
string endpoint,
IEnumerable<Query> queries = null,
Dictionary<string, string> headers = null,
string authScheme = null
)
{
Endpoint = endpoint;
AuthScheme = authScheme;
SearchableQueries = queries?.ToList();
CustomHeaders = headers;
RunningSubscriptions = new HashSet<string>();
}
public GraphQLClient(GraphQLConfig config)
{
Endpoint = config.Endpoint;
SearchableQueries = config.Files.SelectMany(x => x.Queries).ToList();
CustomHeaders = config.CustomHeaders.ToDictionary(header => header.Key, header => header.Value);
AuthScheme = config.AuthScheme;
RunningSubscriptions = new HashSet<string>();
}
/// <summary>
/// Send a query!
/// </summary>
/// <param name="request">The request you are sending.</param>
/// <param name="serializerSettings"></param>
/// <param name="headers">Any headers you want to pass</param>
/// <param name="authToken">The authToken</param>
/// <param name="authScheme">The authScheme to be used.</param>
/// <returns></returns>
public async Task<string> Send(
Request request,
JsonSerializerSettings serializerSettings = null,
Dictionary<string, string> headers = null,
string authToken = null,
string authScheme = null
)
{
if (CustomHeaders != null)
{
if (headers == null) headers = new Dictionary<string, string>();
foreach (KeyValuePair<string, string> header in CustomHeaders)
{
headers.Add(header.Key, header.Value);
}
}
if (authScheme == null)
{
authScheme = AuthScheme;
}
string postQueryAsync = await HttpUtils.PostRequest(
Endpoint,
request,
serializerSettings,
headers,
authToken,
authScheme
);
return postQueryAsync;
}
public async Task<Response<TResponse>> Send<TResponse>(
Request request,
JsonSerializerSettings serializerSettings = null,
Dictionary<string, string> headers = null,
string authToken = null,
string authScheme = null
)
{
string json = await Send(request, serializerSettings, headers, authToken, authScheme);
return JsonConvert.DeserializeObject<Response<TResponse>>(json);
}
public async Task<Response<TResponse>> Send<TResponse>(
Func<TResponse> responseTypeResolver,
Request request,
JsonSerializerSettings serializerSettings = null,
Dictionary<string, string> headers = null,
string authToken = null,
string authScheme = null)
{
return await Send<TResponse>(request, serializerSettings, headers, authToken, authScheme);
}
/// <summary>
/// Registers a listener for subscriptions.
/// </summary>
/// <param name="listener"></param>
public void RegisterListener(Action<string> listener)
{
HttpUtils.SubscriptionDataReceived += listener;
}
public void RegisterListener(string id, Action<string> listener)
{
if (!HttpUtils.SubscriptionDataReceivedPerChannel.ContainsKey(id))
{
HttpUtils.SubscriptionDataReceivedPerChannel[id] = null;
}
HttpUtils.SubscriptionDataReceivedPerChannel[id] += listener;
}
public void RegisterListener(Request request, Action<string> listener)
{
RegisterListener(request.Query.ToMurmur2Hash().ToString(), listener);
}
/// <summary>
/// Unregisters a listener for subscriptions.
/// </summary>
/// <param name="listener"></param>
public void UnregisterListener(Action<string> listener)
{
HttpUtils.SubscriptionDataReceived -= listener;
}
public void UnregisterListener(string id, Action<string> listener)
{
if (HttpUtils.SubscriptionDataReceivedPerChannel.ContainsKey(id))
{
HttpUtils.SubscriptionDataReceivedPerChannel[id] -= listener;
}
}
public void UnregisterListener(Request request, Action<string> listener)
{
UnregisterListener(request.Query.ToMurmur2Hash().ToString(), listener);
}
/// <summary>
/// Subscribe to a query in GraphQL.
/// </summary>
/// <param name="request">The request you are sending.</param>
/// <param name="headers"></param>
/// <param name="authToken"></param>
/// <param name="authScheme"></param>
/// <param name="protocol"></param>
/// <returns>True if successful</returns>
public async Task<bool> Subscribe(
Request request,
Dictionary<string, string> headers = null,
string authToken = null,
string authScheme = null,
string protocol = "graphql-ws"
)
{
return await Subscribe(request.Query.ToMurmur2Hash().ToString(), request, headers, authToken, authScheme, protocol);
}
/// <summary>
/// Subscribe to a query in GraphQL.
/// </summary>
/// <param name="id">A custom id to pass.</param>
/// <param name="request"></param>
/// <param name="headers"></param>
/// <param name="authToken"></param>
/// <param name="authScheme"></param>
/// <param name="protocol"></param>
/// <returns>True if successful</returns>
public async Task<bool> Subscribe(
string id,
Request request,
Dictionary<string, string> headers = null,
string authToken = null,
string authScheme = null,
string protocol = "graphql-ws"
)
{
if (CustomHeaders != null)
{
if (headers == null) headers = new Dictionary<string, string>();
foreach (KeyValuePair<string, string> header in CustomHeaders)
{
headers.Add(header.Key, header.Value);
}
}
if (authScheme == null)
{
authScheme = AuthScheme;
}
if (!HttpUtils.IsWebSocketReady())
{
Debug.Log("websocket not ready: open connection");
// Prepare the socket before continuing.
await HttpUtils.WebSocketConnect(Endpoint, headers, authToken, authScheme, protocol);
}
bool success = await HttpUtils.WebSocketSubscribe(id, request);
if (success)
{
RunningSubscriptions.Add(id);
}
else
{
// if no other subscriptions exist, close connection again
if (RunningSubscriptions.Count == 0)
{
Debug.Log("No running subscription remain: close connection");
await HttpUtils.WebSocketDisconnect();
}
}
return success;
}
/// <summary>
/// Unsubscribe from a request.
/// </summary>
/// <param name="request"></param>
public async Task Unsubscribe(Request request)
{
await Unsubscribe(request.Query.ToMurmur2Hash().ToString());
}
/// <summary>
/// Unsubscribe from an id.
/// </summary>
/// <param name="id"></param>
public async Task Unsubscribe(string id)
{
if (!HttpUtils.IsWebSocketReady())
{
// Socket is already apparently closed, so this wouldn't work anyways.
return;
}
// when unsubscribing an unexisting id (or already unsubscribed)
if (!RunningSubscriptions.Contains(id))
{
Debug.LogError("Attempted to unsubscribe to a query without subscribing first!");
return;
}
// TODO: what if this fails?
await HttpUtils.WebSocketUnsubscribe(id);
RunningSubscriptions.Remove(id);
// if no active subscriptions remain, stop the connection
// this will also stop the update loop
if (RunningSubscriptions.Count == 0)
{
Debug.Log("No running subscription remain: close connection");
await HttpUtils.WebSocketDisconnect();
Debug.Log("connection closed");
}
}
/// <summary>
/// Finds the first query located in a file.
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public Query FindQuery(string fileName)
{
return SearchableQueries?.FirstOrDefault(x => x.FileName == fileName);
}
/// <summary>
/// Finds the first query located in a file.
/// </summary>
/// <param name="operationName"></param>
/// <returns></returns>
public Query FindQueryByOperation(string operationName)
{
return SearchableQueries?.FirstOrDefault(x => x.OperationName == operationName);
}
/// <summary>
/// Finds a query by fileName and operationName.
/// </summary>
/// <param name="fileName"></param>
/// <param name="operationName"></param>
/// <returns></returns>
public Query FindQuery(string fileName, string operationName)
{
return SearchableQueries?.FirstOrDefault(x => x.FileName == fileName && x.OperationName == operationName);
}
/// <summary>
/// Finds a query by operationName and operationType.
/// </summary>
/// <param name="operationName"></param>
/// <param name="operationType"></param>
/// <returns></returns>
public Query FindQuery(string operationName, OperationType operationType)
{
return SearchableQueries?.FirstOrDefault(x =>
x.OperationName == operationName &&
x.OperationType == operationType
);
}
/// <summary>
/// Finds a query by fileName, operationName, and operationType.
/// </summary>
/// <param name="fileName"></param>
/// <param name="operationName"></param>
/// <param name="operationType"></param>
/// <returns></returns>
public Query FindQuery(string fileName, string operationName, OperationType operationType)
{
return SearchableQueries?.FirstOrDefault(
x => x.FileName == fileName &&
x.OperationName == operationName &&
x.OperationType == operationType
);
}
/// <summary>
/// Finds all queries with the given operation name.
/// You may need to do additional filtering to get the query you want since they will all have the same operation name.
/// </summary>
/// <param name="operation"></param>
/// <returns></returns>
public List<Query> FindQueriesByOperation(string operation)
{
return SearchableQueries?.FindAll(x => x.OperationName == operation);
}
}
}