Skip to content

Commit

Permalink
v2.0 - tags should be strings
Browse files Browse the repository at this point in the history
  • Loading branch information
Marfusios committed Nov 16, 2023
1 parent 24a5ad2 commit a889135
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 40 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- Project defaults -->

<PropertyGroup>
<Version>1.4.3</Version>
<Version>2.0.0</Version>
</PropertyGroup>

</Project>
2 changes: 1 addition & 1 deletion apps/nostr-bot/NostrBot.Web/Utils/TaskUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public static async Task DelaySafely(TimeSpan delay, CancellationToken? cancella
{
await Task.Delay(delay, cancellationToken ?? CancellationToken.None);
}
catch (OperationCanceledException e)
catch (OperationCanceledException)
{
// ignore
}
Expand Down
17 changes: 15 additions & 2 deletions apps/nostr-debug/NostrDebug.Web/Events/EventStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ public class EventStorage
{
private readonly Dictionary<string, NostrEventResponse> _responses = new();
private readonly Dictionary<string, NostrEvent> _events = new();
private readonly Dictionary<string, HashSet<string>> _eventToCommunicators = new();

public void Store(NostrEventResponse? response)
{
var eventId = response?.Event?.Id;
if (eventId == null)
return;
_responses[eventId] = response!;

if (!_eventToCommunicators.ContainsKey(eventId))
_eventToCommunicators[eventId] = new HashSet<string>();
_eventToCommunicators[eventId].Add(response!.CommunicatorName);
}

public void Store(NostrEvent? ev)
Expand All @@ -34,11 +39,19 @@ public void Store(NostrEvent? ev)

public NostrEvent? FindEvent(string eventId)
{
return _events.ContainsKey(eventId) ?
_events[eventId] :
return _events.TryGetValue(eventId, out var @event) ?
@event :
null;
}

public string[] FindCommunicators(string? eventId)
{
var key = eventId ?? string.Empty;
return _eventToCommunicators.TryGetValue(key, out var communicators) ?
communicators.ToArray() :
Array.Empty<string>();
}

public void Clear()
{
_events.Clear();
Expand Down
11 changes: 6 additions & 5 deletions apps/nostr-debug/NostrDebug.Web/Events/NostrEventTable.razor
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@
base.OnInitialized();
}

private void HandleEvent(IList<NostrEventResponse> response)
private void HandleEvent(IList<NostrEventResponse> responses)
{
var events = response
.Where(x => x.Event != null);
foreach (var evView in events)
foreach (var response in responses)
{
_receivedData.Add(evView.Event!);
if(response.Event == null)
continue;
EventStorage.Store(response);
_receivedData.Add(response.Event!);
}
StateHasChanged();
}
Expand Down
52 changes: 37 additions & 15 deletions apps/nostr-debug/NostrDebug.Web/Events/NostrEventView.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@using Nostr.Client.Messages.Zaps
@inject ClipboardService Clipboard;
@inject IJSRuntime JsRuntime
@inject EventStorage EventStorage;

<div>
<PageHeader Title3="@_displayedEvent?.Kind.ToString()" DisplayBrowserTitle="false"></PageHeader>
Expand All @@ -26,25 +27,39 @@

@foreach (var tag in _displayedEvent.Tags.GroupBy(x => x.TagIdentifier))
{
var data = tag
.SelectMany(x => x.AdditionalData)
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToArray();
<FluentTreeItem>
<span>@tag.Key <small>(@tag.SelectMany(x => x.AdditionalData).Count())</small></span>
<span>@tag.Key <small>(@data.Length)</small></span>

@foreach (var otherTag in tag.SelectMany(x => x.AdditionalData))
@foreach (var otherTag in data)
{
<FluentTreeItem>
@switch (tag.Key)
{
case NostrEventTag.EventIdentifier:
<div><a class="clear-link" href="@ExternalLinks.GetLinkToEvent(otherTag?.ToString())" target="_blank">@ExternalLinks.FormatToNote(otherTag?.ToString())</a></div>
break;
case NostrEventTag.ProfileIdentifier:
<div><a class="clear-link" href="@ExternalLinks.GetLinkToProfile(otherTag?.ToString())" target="_blank">@ExternalLinks.FormatToNpub(otherTag?.ToString())</a></div>
break;
default:
if (!string.IsNullOrWhiteSpace(otherTag))
{
<FluentTreeItem>
@if (NostrConverter.IsHex(otherTag))
{
@switch (tag.Key)
{
case NostrEventTag.EventIdentifier:
<div><a class="clear-link" href="@ExternalLinks.GetLinkToEvent(otherTag)" target="_blank">@ExternalLinks.FormatToNote(otherTag)</a></div>
break;
case NostrEventTag.ProfileIdentifier:
<div><a class="clear-link" href="@ExternalLinks.GetLinkToProfile(otherTag)" target="_blank">@ExternalLinks.FormatToNpub(otherTag)</a></div>
break;
default:
<div><span>@otherTag</span></div>
break;
}
}
else
{
<div><span>@otherTag</span></div>
break;
}
</FluentTreeItem>
}
</FluentTreeItem>
}
}

</FluentTreeItem>
Expand Down Expand Up @@ -75,6 +90,13 @@
<div>@_displayedEvent?.Pubkey</div>
<div><a class="clear-link" href="@ExternalLinks.GetLinkToProfile(_displayedEvent?.Pubkey)" target="_blank">@ExternalLinks.FormatToNpub(_displayedEvent?.Pubkey)</a></div>
</p>
<p>
<strong>Relays</strong>
@foreach (var communicator in EventStorage.FindCommunicators(_displayedEvent?.Id).OrderBy(x => x))
{
<div>@communicator</div>
}
</p>

@if (_displayedEvent is NostrZapReceiptEvent zapEvent)
{
Expand Down
17 changes: 17 additions & 0 deletions src/Nostr.Client/Json/ArrayConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer
writer.WriteValue(additional);
}
}

if (value is IHaveAdditionalStringData valueWithStringData)
{
foreach (var additional in valueWithStringData.AdditionalData)
{
writer.WriteValue(additional);
}
}

writer.WriteEndArray();
}
Expand Down Expand Up @@ -192,6 +200,15 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer
.ToArray();
resultWithData.SetAdditionalData(unhandledData!);
}

if (arr.Count > maxIndex && result is IHaveAdditionalStringData resultWithStringData)
{
var unhandledData = arr.Skip(maxIndex)
.Select(x => x.Value<string?>())
.Where(x => x != null)
.ToArray();
resultWithStringData.SetAdditionalData(unhandledData!);
}

return result;
}
Expand Down
18 changes: 18 additions & 0 deletions src/Nostr.Client/Json/IHaveAdditionalStringData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Nostr.Client.Json
{
/// <summary>
/// Contains collection of additional unparsed string data
/// </summary>
public interface IHaveAdditionalStringData
{
/// <summary>
/// Data that wasn't parsed into properties
/// </summary>
string[] AdditionalData { get; }

/// <summary>
/// Set additional data, should not be used outside of this library
/// </summary>
void SetAdditionalData(string[] data);
}
}
8 changes: 4 additions & 4 deletions src/Nostr.Client/Messages/Mutable/NostrEventTagMutable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ public NostrEventTagMutable()
{
}

public NostrEventTagMutable(string? identifier, params object[] data)
public NostrEventTagMutable(string? identifier, params string[] data)
{
TagIdentifier = identifier;
TagIdentifier = identifier ?? string.Empty;
AdditionalData = data;
}

public string? TagIdentifier { get; set; }
public string TagIdentifier { get; set; } = string.Empty;

public object[] AdditionalData { get; set; } = Array.Empty<object>();
public string[] AdditionalData { get; set; } = Array.Empty<string>();

public NostrEventTag ToTag()
{
Expand Down
16 changes: 8 additions & 8 deletions src/Nostr.Client/Messages/NostrEventTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Nostr.Client.Messages
{
[DebuggerDisplay("Tag {TagIdentifier} additional: {AdditionalData.Length}")]
[JsonConverter(typeof(ArrayConverter))]
public class NostrEventTag : IHaveAdditionalData
public class NostrEventTag : IHaveAdditionalStringData
{
public const string EventIdentifier = "e";
public const string ProfileIdentifier = "p";
Expand All @@ -17,25 +17,25 @@ public NostrEventTag()
{
}

public NostrEventTag(string? identifier, params object[] data)
public NostrEventTag(string? identifier, params string[] data)
{
TagIdentifier = identifier;
TagIdentifier = identifier ?? string.Empty;
AdditionalData = data;
}

[ArrayProperty(0)]
public string? TagIdentifier { get; init; }
[ArrayProperty(0)]
public string TagIdentifier { get; init; } = string.Empty;

/// <summary>
/// Additional unexpected data at higher indexes in the tags array
/// </summary>
public object[] AdditionalData { get; private set; } = Array.Empty<object>();
public string[] AdditionalData { get; private set; } = Array.Empty<string>();

/// <summary>
/// Set additional data, should not be used outside of this library.
/// Hidden behind explicit interface implementation to avoid accidental usage.
/// </summary>
void IHaveAdditionalData.SetAdditionalData(object[] data)
void IHaveAdditionalStringData.SetAdditionalData(string[] data)
{
AdditionalData = data;
}
Expand All @@ -45,7 +45,7 @@ public NostrEventTag DeepClone()
return new NostrEventTag
{
TagIdentifier = TagIdentifier,
AdditionalData = (object[])AdditionalData.Clone()
AdditionalData = (string[])AdditionalData.Clone()
};
}

Expand Down
16 changes: 16 additions & 0 deletions src/Nostr.Client/Utils/HexExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ public static byte[] ToByteArray(string hex)
return arr;
}

public static bool IsHex(string hex)
{
if (hex.Length % 2 == 1)
return false;
foreach(var c in hex.ToArray())
{
var isHex = (c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');

if(!isHex)
return false;
}
return true;
}

public static int GetHexValue(char hex)
{
var val = (int)hex;
Expand Down
24 changes: 23 additions & 1 deletion src/Nostr.Client/Utils/NostrConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ public static bool TryToHex(string? bech32, out string? hex, out string? hrp)
}
}

/// <summary>
/// Check whether input is valid hex string
/// </summary>
/// <param name="hexKey"></param>
/// <returns></returns>
public static bool IsHex(string? hexKey)
{
if (string.IsNullOrWhiteSpace(hexKey))
return false;

try
{
return HexExtensions.IsHex(hexKey);
}
catch (Exception)
{
// ignore
}

return false;
}

/// <summary>
/// Convert hex string to Bech32 format, you need to provide hrp (prefix)
/// </summary>
Expand Down Expand Up @@ -83,7 +105,7 @@ public static bool TryToBech32(string? hexKey, string hrp, out string? bech32)
bech32 = null;
try
{
bech32 = ToBech32(bech32, hrp);
bech32 = ToBech32(hexKey, hrp);
return !string.IsNullOrWhiteSpace(bech32);
}
catch (Exception)
Expand Down
17 changes: 17 additions & 0 deletions test/Nostr.Client.Tests/NostrConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,21 @@ public void ToNsec_ShouldConvertCorrectly(string hex, string expected)
var converted = NostrConverter.ToNsec(hex);
Assert.Equal(expected, converted);
}

[Theory]
[InlineData("6b75a3b4832f265989254ca560b700da3343d707d2319e7a45f4e01afe4a0c31", true)]
[InlineData("63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed", true)]
[InlineData("aa", true)]
[InlineData("AA", true)]
[InlineData("a", false)]
[InlineData("A", false)]
[InlineData(null, false)]
[InlineData("", false)]
[InlineData("root", false)]
[InlineData("reply", false)]
public void IsHex_ShouldReturnCorrectValue(string? hex, bool isValid)
{
var isHex = NostrConverter.IsHex(hex);
Assert.Equal(isValid, isHex);
}
}
6 changes: 3 additions & 3 deletions test/Nostr.Client.Tests/NostrMultiClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void Streams_ShouldForwardMessages()
client.Send(new NostrEventRequest(ev));

Assert.Equal(3, receivedEvents.Count);
Assert.True(receivedEvents.Any(x => x?.CommunicatorName == communicator2.Name));
Assert.Contains(receivedEvents, x => x?.CommunicatorName == communicator2.Name);
Assert.NotNull(client.FindClient(communicator3.Name));
}

Expand Down Expand Up @@ -99,7 +99,7 @@ public void RemoveRegistration_ShouldStopForwardingMessages()
client.Send(new NostrEventRequest(ev));

Assert.Equal(3, receivedEvents.Count);
Assert.True(receivedEvents.Any(x => x?.CommunicatorName == communicator2.Name));
Assert.Contains(receivedEvents, x => x?.CommunicatorName == communicator2.Name);
Assert.NotNull(client.FindClient(communicator2.Name));

client.RemoveRegistration(communicator2.Name);
Expand All @@ -108,7 +108,7 @@ public void RemoveRegistration_ShouldStopForwardingMessages()
client.Send(new NostrEventRequest(ev));

Assert.Equal(2, receivedEvents.Count);
Assert.False(receivedEvents.Any(x => x?.CommunicatorName == communicator2.Name));
Assert.DoesNotContain(receivedEvents, x => x?.CommunicatorName == communicator2.Name);
Assert.Null(client.FindClient(communicator2.Name));
}
}
Expand Down

0 comments on commit a889135

Please sign in to comment.