Skip to content

Commit 50df99d

Browse files
committed
UnityHttpClient
1 parent 1a5b892 commit 50df99d

7 files changed

Lines changed: 237 additions & 2 deletions

File tree

Packages/com.walletconnect.web3modal/Runtime/Http.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Newtonsoft.Json;
7+
using UnityEngine.Networking;
8+
using WalletConnect.Web3Modal.Utils;
9+
10+
namespace WalletConnect.Web3Modal.Http
11+
{
12+
public abstract class HttpClientDecorator
13+
{
14+
public Task<HttpResponseContext> SendAsync(HttpRequestContext requestContext, CancellationToken cancellationToken, Func<HttpRequestContext, CancellationToken, Task<HttpResponseContext>> next)
15+
{
16+
if (requestContext == null)
17+
throw new ArgumentNullException(nameof(requestContext));
18+
19+
cancellationToken.ThrowIfCancellationRequested();
20+
21+
return SendAsyncCore(requestContext, cancellationToken, next);
22+
}
23+
24+
protected abstract Task<HttpResponseContext> SendAsyncCore(HttpRequestContext requestContext, CancellationToken cancellationToken, Func<HttpRequestContext, CancellationToken, Task<HttpResponseContext>> next);
25+
}
26+
27+
public class HttpRequestContext
28+
{
29+
private int _decoratorIndex;
30+
31+
public IDictionary<string, string> RequestHeaders { get; } = new Dictionary<string, string>();
32+
33+
public string Path { get; }
34+
public string Method { get; }
35+
public string Body { get; }
36+
public string ContentType { get; set; }
37+
public DateTimeOffset Timestamp { get; private set; }
38+
private HttpClientDecorator[] Decorators { get; }
39+
40+
public HttpRequestContext(string path, string method, string body, string contentType = null, HttpClientDecorator[] decorators = null)
41+
{
42+
Path = path;
43+
Method = method;
44+
Body = body;
45+
ContentType = contentType;
46+
Decorators = decorators;
47+
Timestamp = DateTimeOffset.UtcNow;
48+
}
49+
50+
public void Reset(HttpClientDecorator currentFilter)
51+
{
52+
_decoratorIndex = Array.IndexOf(Decorators, currentFilter);
53+
RequestHeaders?.Clear();
54+
Timestamp = DateTimeOffset.UtcNow;
55+
}
56+
57+
internal HttpClientDecorator GetNextDecorator()
58+
{
59+
return Decorators[_decoratorIndex++];
60+
}
61+
}
62+
63+
public class HttpResponseContext
64+
{
65+
public byte[] Bytes { get; }
66+
public long StatusCode { get; }
67+
public Dictionary<string, string> ResponseHeaders { get; }
68+
69+
public HttpResponseContext(byte[] bytes, long statusCode, Dictionary<string, string> responseHeaders)
70+
{
71+
Bytes = bytes;
72+
StatusCode = statusCode;
73+
ResponseHeaders = responseHeaders;
74+
}
75+
76+
public T GetResponseAs<T>()
77+
{
78+
var utf8String = Encoding.UTF8.GetString(Bytes);
79+
if (Type.GetTypeCode(typeof(T)) == TypeCode.String)
80+
return (T)Convert.ChangeType(utf8String, typeof(T));
81+
return JsonConvert.DeserializeObject<T>(utf8String);
82+
}
83+
}
84+
85+
public class UnityHttpClient : HttpClientDecorator
86+
{
87+
private readonly Uri _basePath;
88+
private readonly TimeSpan _timeout;
89+
private readonly HttpClientDecorator[] _decorators;
90+
private readonly Func<HttpRequestContext, CancellationToken, Task<HttpResponseContext>> _next;
91+
92+
public UnityHttpClient(Uri basePath, TimeSpan timeout, params HttpClientDecorator[] decorators)
93+
{
94+
_basePath = basePath;
95+
_timeout = timeout;
96+
_next = InvokeRecursive;
97+
_decorators = new HttpClientDecorator[decorators.Length + 1];
98+
Array.Copy(decorators, _decorators, decorators.Length);
99+
_decorators[^1] = this;
100+
}
101+
102+
public UnityHttpClient(TimeSpan timeout, params HttpClientDecorator[] decorators) : this(null, timeout, decorators)
103+
{
104+
}
105+
106+
private Task<HttpResponseContext> InvokeRecursive(HttpRequestContext context, CancellationToken cancellationToken)
107+
{
108+
return context
109+
.GetNextDecorator()
110+
.SendAsync(context, cancellationToken, _next);
111+
}
112+
113+
public async Task<T> PostAsync<T>(string path, string value, IDictionary<string, string> parameters = null)
114+
{
115+
path = path.AppendQueryString(parameters);
116+
117+
var request = new HttpRequestContext(path, "POST", value, "application/json", _decorators);
118+
var response = await InvokeRecursive(request, CancellationToken.None);
119+
return response.GetResponseAs<T>();
120+
}
121+
122+
public async Task<T> GetAsync<T>(string path, IDictionary<string, string> parameters = null)
123+
{
124+
path = path.AppendQueryString(parameters);
125+
126+
var request = new HttpRequestContext(path, "GET", null, null, _decorators);
127+
var response = await InvokeRecursive(request, CancellationToken.None);
128+
return response.GetResponseAs<T>();
129+
}
130+
131+
protected override Task<HttpResponseContext> SendAsyncCore(HttpRequestContext requestContext, CancellationToken cancellationToken, Func<HttpRequestContext, CancellationToken, Task<HttpResponseContext>> next)
132+
{
133+
var url = _basePath != null
134+
? new Uri(_basePath, requestContext.Path)
135+
: new Uri(requestContext.Path);
136+
137+
var uwr = new UnityWebRequest(url, requestContext.Method)
138+
{
139+
downloadHandler = new DownloadHandlerBuffer()
140+
};
141+
142+
if (requestContext.Method is UnityWebRequest.kHttpVerbPOST or UnityWebRequest.kHttpVerbPUT)
143+
{
144+
if (string.IsNullOrWhiteSpace(requestContext.Body))
145+
{
146+
uwr.SetRequestHeader("Content-Type", string.IsNullOrWhiteSpace(requestContext.ContentType)
147+
? "application/json"
148+
: requestContext.ContentType
149+
);
150+
}
151+
else
152+
{
153+
var bytes = Encoding.UTF8.GetBytes(requestContext.Body);
154+
uwr.uploadHandler = new UploadHandlerRaw(bytes);
155+
uwr.uploadHandler.contentType = requestContext.ContentType;
156+
}
157+
}
158+
159+
foreach (var (key, value) in requestContext.RequestHeaders)
160+
uwr.SetRequestHeader(key, value);
161+
162+
uwr.timeout = (int)_timeout.TotalSeconds;
163+
164+
var tcs = new TaskCompletionSource<HttpResponseContext>();
165+
cancellationToken.Register(
166+
callback => ((TaskCompletionSource<HttpResponseContext>)callback).SetCanceled(),
167+
tcs);
168+
169+
var handle = uwr.SendWebRequest();
170+
171+
handle.completed += _ =>
172+
{
173+
if (uwr.result != UnityWebRequest.Result.Success)
174+
{
175+
tcs.SetException(new Exception($"Failed to send web request: {uwr.error}")); // TODO: use custom ex type
176+
return;
177+
}
178+
179+
tcs.SetResult(new HttpResponseContext(uwr.downloadHandler.data, uwr.responseCode, uwr.GetResponseHeaders()));
180+
};
181+
182+
return tcs.Task;
183+
}
184+
}
185+
}

Packages/com.walletconnect.web3modal/Runtime/Http/UnityHttpClient.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using WalletConnectUnity.Core;
5+
6+
namespace WalletConnect.Web3Modal.Http
7+
{
8+
public class Web3ModalApiHeaderDecorator : HttpClientDecorator
9+
{
10+
protected override Task<HttpResponseContext> SendAsyncCore(HttpRequestContext requestContext, CancellationToken cancellationToken, Func<HttpRequestContext, CancellationToken, Task<HttpResponseContext>> next)
11+
{
12+
requestContext.RequestHeaders["x-project-id"] = ProjectConfiguration.Load().Id;
13+
requestContext.RequestHeaders["x-sdk-type"] = SdkMetadata.Type;
14+
requestContext.RequestHeaders["x-sdk-version"] = SdkMetadata.Version;
15+
16+
return next(requestContext, cancellationToken);
17+
}
18+
}
19+
}

Packages/com.walletconnect.web3modal/Runtime/Http/Web3ModalApiHeaderDecorator.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Packages/com.walletconnect.web3modal/Runtime/Utils/Extensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Collections.Generic;
2+
using System.Text;
13
using UnityEngine;
24
using UnityEngine.UIElements;
35

@@ -28,6 +30,25 @@ public static string FontWeight700(this string value)
2830
{
2931
return $"<font-weight=\"700\">{value}</font-weight>";
3032
}
33+
34+
public static string AppendQueryString(this string path, IDictionary<string, string> queryParameters)
35+
{
36+
if (queryParameters == null || queryParameters.Count == 0)
37+
{
38+
return path;
39+
}
40+
41+
var queryString = new StringBuilder();
42+
foreach (var param in queryParameters)
43+
{
44+
if (queryString.Length > 0)
45+
queryString.Append("&");
46+
47+
queryString.Append($"{param.Key}={param.Value}");
48+
}
49+
50+
return $"{path}?{queryString}";
51+
}
3152
}
3253

3354
public static class ScrollViewExtensions

Packages/com.walletconnect.web3modal/Runtime/Web3Modal.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Nethereum.Web3;
44
using UnityEngine;
55
using UnityEngine.Scripting;
6+
using WalletConnectUnity.Core;
67
using WalletConnectUnity.Core.Networking;
78
using WalletConnectUnity.Nethereum;
89

@@ -71,8 +72,8 @@ public static async Task InitializeAsync(Web3ModalConfig config)
7172
if (IsInitialized)
7273
throw new Exception("Already initialized"); // TODO: use custom ex type
7374

74-
UnityWebRequestExtensions.sdkType = "w3m";
75-
UnityWebRequestExtensions.sdkVersion = "unity-w3m-v0.3.1"; // TODO: update this from CI
75+
SdkMetadata.Type = "w3m";
76+
SdkMetadata.Version = "unity-w3m-v0.3.1"; // TODO: update this from CI
7677

7778
Config = config ?? throw new ArgumentNullException(nameof(config));
7879

0 commit comments

Comments
 (0)