Skip to content

Commit

Permalink
Storage download upload (#4)
Browse files Browse the repository at this point in the history
* Storage download upload

* Update dotnet-ci.yml
  • Loading branch information
0xFirekeeper authored Apr 5, 2024
1 parent 8d643e7 commit 8fb4114
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/dotnet-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ jobs:
shell: bash
env:
THIRDWEB_SECRET_KEY: ${{ secrets.THIRDWEB_SECRET_KEY }}
THIRDWEB_CLIENT_ID_BUNDLE_ID_ONLY: ${{ secrets.THIRDWEB_CLIENT_ID_BUNDLE_ID_ONLY }}
THIRDWEB_BUNDLE_ID_BUNDLE_ID_ONLY: ${{ secrets.THIRDWEB_BUNDLE_ID_BUNDLE_ID_ONLY }}
4 changes: 4 additions & 0 deletions Thirdweb.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ private static async Task Main(string[] args)
await privateKeyAccount.Connect();
await embeddedAccount.Connect();

// Reset embedded account
if (await embeddedAccount.IsConnected())
await embeddedAccount.Disconnect();

// Relog if embedded account not logged in
if (!await embeddedAccount.IsConnected())
{
Expand Down
4 changes: 4 additions & 0 deletions Thirdweb.Tests/BaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ public class BaseTests
{
protected readonly ITestOutputHelper _output;
protected readonly string? _secretKey;
protected readonly string? _clientIdBundleIdOnly;
protected readonly string? _bundleIdBundleIdOnly;

public BaseTests(ITestOutputHelper output)
{
DotEnv.Load();
_output = output;
_secretKey = Environment.GetEnvironmentVariable("THIRDWEB_SECRET_KEY");
_clientIdBundleIdOnly = Environment.GetEnvironmentVariable("THIRDWEB_CLIENT_ID_BUNDLE_ID_ONLY");
_bundleIdBundleIdOnly = Environment.GetEnvironmentVariable("THIRDWEB_BUNDLE_ID_BUNDLE_ID_ONLY");
}

[Fact]
Expand Down
File renamed without changes.
File renamed without changes.
43 changes: 43 additions & 0 deletions Thirdweb.Tests/Thirdweb.Storage.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Thirdweb.Tests;

public class StorageTests : BaseTests
{
public StorageTests(ITestOutputHelper output)
: base(output) { }

[Fact]
public async Task DownloadTest_SecretKey()
{
var client = new ThirdwebClient(secretKey: _secretKey);
var res = await ThirdwebStorage.Download<string>(client, "https://1.rpc.thirdweb.com/providers");
Assert.NotNull(res);
}

[Fact]
public async Task DownloadTest_Client_BundleId()
{
var client = new ThirdwebClient(clientId: _clientIdBundleIdOnly, bundleId: _bundleIdBundleIdOnly);
var res = await ThirdwebStorage.Download<string>(client, "https://1.rpc.thirdweb.com/providers");
Assert.NotNull(res);
}

[Fact]
public async Task UploadTest_SecretKey()
{
var client = new ThirdwebClient(secretKey: _secretKey);
var path = Path.Combine(Path.GetTempPath(), "testJson.json");
File.WriteAllText(path, "{\"test\": \"test\"}");
var res = await ThirdwebStorage.Upload(client, path);
Assert.StartsWith($"https://{client.ClientId}.ipfscdn.io/ipfs/", res.PreviewUrl);
}

[Fact]
public async Task UploadTest_Client_BundleId()
{
var client = new ThirdwebClient(clientId: _clientIdBundleIdOnly, bundleId: _bundleIdBundleIdOnly);
var path = Path.Combine(Path.GetTempPath(), "testJson.json");
File.WriteAllText(path, "{\"test\": \"test\"}");
var res = await ThirdwebStorage.Upload(client, path);
Assert.StartsWith($"https://{client.ClientId}.ipfscdn.io/ipfs/", res.PreviewUrl);
}
}
1 change: 1 addition & 0 deletions Thirdweb/Thirdweb.Client/ThirdwebClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Thirdweb.Tests")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Thirdweb.Console")]

namespace Thirdweb
{
Expand Down
11 changes: 11 additions & 0 deletions Thirdweb/Thirdweb.Storage/StorageTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Thirdweb
{
[System.Serializable]
public struct IPFSUploadResult
{
public string IpfsHash;
public string PinSize;
public string Timestamp;
public string PreviewUrl;
}
}
77 changes: 77 additions & 0 deletions Thirdweb/Thirdweb.Storage/ThirdwebStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Newtonsoft.Json;

namespace Thirdweb
{
public class ThirdwebStorage
{
public static async Task<T> Download<T>(ThirdwebClient client, string uri, int? requestTimeout = null)
{
if (string.IsNullOrEmpty(uri))
{
throw new ArgumentNullException(nameof(uri));
}

uri = uri.ReplaceIPFS(string.IsNullOrEmpty(client.ClientId) ? Constants.FALLBACK_IPFS_GATEWAY : $"https://{client.ClientId}.ipfscdn.io/ipfs/");

using var httpClient = new HttpClient();

var isThirdwebRequest = new Uri(uri).Host.EndsWith(".ipfscdn.io");
if (isThirdwebRequest)
{
var headers = Utils.GetThirdwebHeaders(client);
foreach (var header in headers)
{
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
}

requestTimeout ??= client.FetchTimeoutOptions.GetTimeout(TimeoutType.Storage);

var response = await httpClient.GetAsync(uri, new CancellationTokenSource(requestTimeout.Value).Token);

if (!response.IsSuccessStatusCode)
{
throw new Exception($"Failed to download {uri}: {response.StatusCode} | {response.ReasonPhrase} | {await response.Content.ReadAsStringAsync()}");
}

var content = await response.Content.ReadAsStringAsync();

return typeof(T) == typeof(string) ? (T)(object)content : JsonConvert.DeserializeObject<T>(content);
}

public static async Task<IPFSUploadResult> Upload(ThirdwebClient client, string path)
{
if (string.IsNullOrEmpty(client.ClientId))
{
throw new UnauthorizedAccessException("You cannot use Upload features without setting a Client ID.");
}

if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
}

using var httpClient = new HttpClient();
using var form = new MultipartFormDataContent { { new ByteArrayContent(File.ReadAllBytes(path)), "file", Path.GetFileName(path) } };

var headers = Utils.GetThirdwebHeaders(client);
foreach (var header in headers)
{
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}

var response = await httpClient.PostAsync(Constants.PIN_URI, form);

if (!response.IsSuccessStatusCode)
{
throw new Exception($"Failed to upload {path}: {response.StatusCode} | {response.ReasonPhrase} | {await response.Content.ReadAsStringAsync()}");
}

var result = await response.Content.ReadAsStringAsync();

var res = JsonConvert.DeserializeObject<IPFSUploadResult>(result);
res.PreviewUrl = $"https://{client.ClientId}.ipfscdn.io/ipfs/{res.IpfsHash}";
return res;
}
}
}
2 changes: 2 additions & 0 deletions Thirdweb/Thirdweb.Utils/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ public static class Constants
internal const string DUMMY_SIG = "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c";
internal const string DUMMY_PAYMASTER_AND_DATA_HEX =
"0x0101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000001010101010100000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101";
internal const string FALLBACK_IPFS_GATEWAY = "https://ipfs.io/ipfs/";
internal const string PIN_URI = "https://storage.thirdweb.com/ipfs/upload";
}
}
34 changes: 34 additions & 0 deletions Thirdweb/Thirdweb.Utils/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,39 @@ public static long GetUnixTimeStampIn10Years()
{
return DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60 * 60 * 24 * 365 * 10;
}

public static string ReplaceIPFS(this string uri, string gateway = null)
{
gateway ??= Constants.FALLBACK_IPFS_GATEWAY;
return !string.IsNullOrEmpty(uri) && uri.StartsWith("ipfs://") ? uri.Replace("ipfs://", gateway) : uri;
}

public static Dictionary<string, string> GetThirdwebHeaders(ThirdwebClient client)
{
var headers = new Dictionary<string, string>
{
{ "x-sdk-name", "Thirdweb.NET" },
{ "x-sdk-os", System.Runtime.InteropServices.RuntimeInformation.OSDescription },
{ "x-sdk-platform", "dotnet" },
{ "x-sdk-version", Constants.VERSION }
};

if (!string.IsNullOrEmpty(client.ClientId))
{
headers.Add("x-client-id", client.ClientId);
}

if (!string.IsNullOrEmpty(client.SecretKey))
{
headers.Add("x-secret-key", client.SecretKey);
}

if (!string.IsNullOrEmpty(client.BundleId))
{
headers.Add("x-bundle-id", client.BundleId);
}

return headers;
}
}
}

0 comments on commit 8fb4114

Please sign in to comment.