Skip to content

Commit 56dce9a

Browse files
committed
🦓 Improve API
- Drop the unnecessary OOCv1ApiClient<T>. - Use STJ source generation. - Bump version to 1.3.0.
1 parent d514cd1 commit 56dce9a

File tree

9 files changed

+52
-99
lines changed

9 files changed

+52
-99
lines changed

README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818

1919
``` cs
2020
// Serialize an API token.
21-
var token = new OOCv1ApiToken(1, "https://example.com", "8c1da4d8-8684-4a2c-9abb-57b9d5fa7e52", "a117460e-41df-4dbd-b2df-4bd0c16efd2f", null);
22-
var json = JsonSerializer.Serialize(token, JsonHelper.camelCaseMinifiedJsonSerializerOptions);
21+
OOCv1ApiToken token = new(1, "https://example.com", "8c1da4d8-8684-4a2c-9abb-57b9d5fa7e52", "a117460e-41df-4dbd-b2df-4bd0c16efd2f", null);
22+
string json = JsonSerializer.Serialize(token, OOCv1JsonSerializerContext.Default.OOCv1ApiToken);
2323
```
2424

2525
``` cs
2626
// Deserialize an API token.
27-
var json = "{\"version\":1,\"baseUrl\":\"https://example.com\",\"secret\":\"8c1da4d8-8684-4a2c-9abb-57b9d5fa7e52\",\"userId\":\"a117460e-41df-4dbd-b2df-4bd0c16efd2f\"}";
28-
var token = JsonSerializer.Deserialize<OOCv1ApiToken>(json, JsonHelper.camelCaseJsonDeserializerOptions);
27+
string json = "{\"version\":1,\"baseUrl\":\"https://example.com\",\"secret\":\"8c1da4d8-8684-4a2c-9abb-57b9d5fa7e52\",\"userId\":\"a117460e-41df-4dbd-b2df-4bd0c16efd2f\"}";
28+
OOCv1ApiToken? token = JsonSerializer.Deserialize(json, OOCv1JsonSerializerContext.Default.OOCv1ApiToken);
2929
```
3030

3131
### 2. Config Base
@@ -34,9 +34,7 @@ var token = JsonSerializer.Deserialize<OOCv1ApiToken>(json, JsonHelper.camelCase
3434

3535
### 3. API Client
3636

37-
`OOCv1ApiClient` is the general-purpose OOCv1 API client. Use this if you want to handle the response JSON yourself.
38-
39-
`OOCv1ApiClient<T>` is the generic OOCv1 API client. Use this if you have specific protocols in mind. `T` must be a subclass of `OOCv1ConfigBase`.
37+
`OOCv1ApiClient` is the general-purpose OOCv1 API client. Call `GetAsync<TValue>` to retrieve the online config. `TValue` must be a subclass of `OOCv1ConfigBase`.
4038

4139
## License
4240

src/OpenOnlineConfig/OpenOnlineConfig.Tests/DeserializationTests.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using OpenOnlineConfig.Utils;
21
using OpenOnlineConfig.v1;
32
using System.Text.Json;
43
using Xunit;
@@ -14,10 +13,10 @@ public class DeserializationTests
1413
1, "https://github.com", "8b7ef7fd-c2ac-47d9-8517-2b20c4fee5a7", "68720c88-e5e5-4b84-9db7-98fb4be109eb", "0ae384bfd4dde9d13e50c5857c05a442c93f8e01445ee4b34540d22bd1e37f1b")]
1514
public void ApiToken_Deserialization(string json, int expectedVersion, string expectedBaseUrl, string expectedSecret, string expectedUserId, string? expectedCertSha256)
1615
{
17-
var token = JsonSerializer.Deserialize<OOCv1ApiToken>(json, JsonHelper.camelCaseJsonDeserializerOptions);
16+
OOCv1ApiToken? token = JsonSerializer.Deserialize(json, OOCv1JsonSerializerContext.Default.OOCv1ApiToken);
1817

1918
Assert.NotNull(token);
20-
Assert.Equal(expectedVersion, token!.Version);
19+
Assert.Equal(expectedVersion, token.Version);
2120
Assert.Equal(expectedBaseUrl, token.BaseUrl);
2221
Assert.Equal(expectedSecret, token.Secret);
2322
Assert.Equal(expectedUserId, token.UserId);
@@ -31,10 +30,10 @@ public void ApiToken_Deserialization(string json, int expectedVersion, string ex
3130
"nobody", 274877906944UL, 824633720832UL, 1609459200L, new string[] { "shadowsocks", "vmess", "trojan-go", })]
3231
public void ConfigBase_Deserialization(string json, string? expectedUsername, ulong? expectedBytesUsed, ulong? expectedBytesRemaining, long? expectedExpiryDate, string[] expectedProtocols)
3332
{
34-
var config = JsonSerializer.Deserialize<OOCv1ConfigBase>(json, JsonHelper.camelCaseJsonDeserializerOptions);
33+
OOCv1ConfigBase? config = JsonSerializer.Deserialize(json, OOCv1JsonSerializerContext.Default.OOCv1ConfigBase);
3534

3635
Assert.NotNull(config);
37-
Assert.Equal(expectedUsername, config!.Username);
36+
Assert.Equal(expectedUsername, config.Username);
3837
Assert.Equal(expectedBytesUsed, config.BytesUsed);
3938
Assert.Equal(expectedBytesRemaining, config.BytesRemaining);
4039
Assert.Equal(expectedExpiryDate, config.ExpiryDate?.ToUnixTimeSeconds());

src/OpenOnlineConfig/OpenOnlineConfig/OpenOnlineConfig.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
<Authors>database64128</Authors>
77
<Product>OpenOnlineConfig.NET</Product>
88
<Description>.NET class library for Open Online Config (OOC) support.</Description>
9-
<Copyright2022 database64128</Copyright>
9+
<Copyright2025 database64128</Copyright>
1010
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1111
<PackageProjectUrl>https://github.com/Shadowsocks-NET/OpenOnlineConfig.NET</PackageProjectUrl>
1212
<RepositoryUrl>https://github.com/Shadowsocks-NET/OpenOnlineConfig.NET</RepositoryUrl>
1313
<RepositoryType>Public</RepositoryType>
1414
<PackageTags>open-online-config;censorship-circumvention</PackageTags>
1515
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1616
<NoWarn>CS1591</NoWarn>
17-
<Version>1.2.0</Version>
17+
<Version>1.3.0</Version>
1818
</PropertyGroup>
1919

2020
<ItemGroup>

src/OpenOnlineConfig/OpenOnlineConfig/Utils/JsonHelper.cs

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/OpenOnlineConfig/OpenOnlineConfig/v1/OOCv1ApiClient.cs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using System;
22
using System.Net.Http;
3-
using System.Net.Security;
3+
using System.Net.Http.Json;
44
using System.Security.Cryptography;
5-
using System.Security.Cryptography.X509Certificates;
5+
using System.Text.Json.Serialization.Metadata;
66
using System.Threading;
77
using System.Threading.Tasks;
88

@@ -13,8 +13,9 @@ namespace OpenOnlineConfig.v1
1313
/// </summary>
1414
public class OOCv1ApiClient : IDisposable
1515
{
16-
protected readonly OOCv1ApiToken _apiToken;
1716
protected readonly HttpClient _httpClient;
17+
private readonly Uri _uri;
18+
private readonly bool _disposeHttpClient;
1819
private bool disposedValue;
1920

2021
/// <summary>
@@ -25,7 +26,7 @@ public class OOCv1ApiClient : IDisposable
2526
/// <param name="httpClient">Supply an existing HttpClient instance.</param>
2627
public OOCv1ApiClient(OOCv1ApiToken apiToken, HttpClient? httpClient = null)
2728
{
28-
_apiToken = apiToken;
29+
_uri = apiToken.UserUrl;
2930

3031
if (apiToken.CertSha256 is null)
3132
{
@@ -37,14 +38,17 @@ public OOCv1ApiClient(OOCv1ApiToken apiToken, HttpClient? httpClient = null)
3738
{
3839
SslOptions = new()
3940
{
40-
RemoteCertificateValidationCallback = ValidateServerCertificate,
41+
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
42+
{
43+
string? sha256Fingerprint = certificate?.GetCertHashString(HashAlgorithmName.SHA256);
44+
return string.Equals(apiToken.CertSha256, sha256Fingerprint, StringComparison.OrdinalIgnoreCase);
45+
},
4146
},
4247
};
4348

4449
_httpClient = new(socketsHttpHandler);
50+
_disposeHttpClient = true;
4551
}
46-
47-
_httpClient.Timeout = TimeSpan.FromSeconds(30.0);
4852
}
4953

5054
/// <inheritdoc cref="HttpClient.Timeout"/>
@@ -54,15 +58,9 @@ public TimeSpan Timeout
5458
set => _httpClient.Timeout = value;
5559
}
5660

57-
private bool ValidateServerCertificate(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
58-
{
59-
var sha256Fingerprint = certificate?.GetCertHashString(HashAlgorithmName.SHA256);
60-
return string.Equals(_apiToken.CertSha256, sha256Fingerprint, StringComparison.OrdinalIgnoreCase);
61-
}
62-
6361
protected virtual void Dispose(bool disposing)
6462
{
65-
if (!disposedValue)
63+
if (_disposeHttpClient && !disposedValue)
6664
{
6765
if (disposing)
6866
{
@@ -80,11 +78,12 @@ public void Dispose()
8078
}
8179

8280
/// <summary>
83-
/// Retrieves the online config and returns the response JSON as a string in an asynchronous operation.
81+
/// Retrieves the online config and deserializes the response JSON as <typeparamref name="TValue"/> in an asynchronous operation.
8482
/// </summary>
83+
/// <param name="jsonTypeInfo">Metadata about the type to convert.</param>
8584
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
86-
/// <returns>The task object representing the asynchronous operation.</returns>
87-
public Task<string> GetAsync(CancellationToken cancellationToken = default)
88-
=> _httpClient.GetStringAsync($"{_apiToken.BaseUrl}/{_apiToken.Secret}/ooc/v1/{_apiToken.UserId}", cancellationToken);
85+
/// <returns>The <see cref="Task"/> object representing the asynchronous operation.</returns>
86+
public Task<TValue?> GetAsync<TValue>(JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default) where TValue : OOCv1ConfigBase =>
87+
_httpClient.GetFromJsonAsync(_uri, jsonTypeInfo, cancellationToken);
8988
}
9089
}

src/OpenOnlineConfig/OpenOnlineConfig/v1/OOCv1ApiClient{T}.cs

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
namespace OpenOnlineConfig.v1
1+
using System;
2+
3+
namespace OpenOnlineConfig.v1
24
{
35
/// <summary>
46
/// OOCv1 API access information.
57
/// Serialize and deserialize in camelCase.
68
/// </summary>
7-
public record OOCv1ApiToken(int Version, string BaseUrl, string Secret, string UserId, string? CertSha256);
9+
public record OOCv1ApiToken(int Version, string BaseUrl, string Secret, string UserId, string? CertSha256)
10+
{
11+
/// <summary>
12+
/// Gets the API URL for the user.
13+
/// </summary>
14+
public Uri UserUrl => new($"{BaseUrl.TrimEnd('/')}/{Secret}/ooc/v1/{UserId}");
15+
}
816
}

src/OpenOnlineConfig/OpenOnlineConfig/v1/OOCv1ConfigBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ public class OOCv1ConfigBase
4040
/// <summary>
4141
/// Gets or sets the protocols used in the configuration.
4242
/// </summary>
43-
public List<string> Protocols { get; set; } = new();
43+
public List<string> Protocols { get; set; } = [];
4444
}
4545
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace OpenOnlineConfig.v1;
4+
5+
[JsonSerializable(typeof(OOCv1ApiToken))]
6+
[JsonSerializable(typeof(OOCv1ConfigBase))]
7+
[JsonSourceGenerationOptions(
8+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
9+
IgnoreReadOnlyProperties = true,
10+
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
11+
public partial class OOCv1JsonSerializerContext : JsonSerializerContext
12+
{
13+
}

0 commit comments

Comments
 (0)