Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import nostr.api.service.NoteService;
import nostr.base.IEvent;
import nostr.base.ISignable;
import nostr.client.springwebsocket.SpringWebSocketClient;
Expand All @@ -12,6 +13,7 @@
import nostr.event.message.ReqMessage;
import nostr.id.Identity;
import nostr.util.NostrUtil;
import nostr.api.service.impl.DefaultNoteService;

import java.io.IOException;
import java.util.HashMap;
Expand All @@ -27,12 +29,23 @@ public class NostrSpringWebSocketClient implements NostrIF {
@Getter
private Identity sender;

private NoteService noteService = new DefaultNoteService();
Copy link

Copilot AI Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The field is initialized with a default implementation but can be overridden by constructors. Consider using final modifier and proper constructor chaining to ensure consistent initialization.

Copilot uses AI. Check for mistakes.

private static volatile NostrSpringWebSocketClient INSTANCE;

public NostrSpringWebSocketClient(String relayName, String relayUri) {
setRelays(Map.of(relayName, relayUri));
}

public NostrSpringWebSocketClient(@NonNull NoteService noteService) {
this.noteService = noteService;
}

public NostrSpringWebSocketClient(@NonNull Identity sender, @NonNull NoteService noteService) {
this.sender = sender;
this.noteService = noteService;
}

public static NostrIF getInstance() {
if (INSTANCE == null) {
synchronized (NostrSpringWebSocketClient.class) {
Expand Down Expand Up @@ -78,8 +91,13 @@ public NostrIF setRelays(@NonNull Map<String, String> relays) {

@Override
public List<String> sendEvent(@NonNull IEvent event) {
return clientMap.values().stream().map(client ->
client.sendEvent(event)).flatMap(List::stream).distinct().toList();
if (event instanceof GenericEvent genericEvent) {
if (!verify(genericEvent)) {
Copy link

Copilot AI Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verify method is being called but is not defined in this class or imported. This will cause a compilation error.

Copilot uses AI. Check for mistakes.
throw new IllegalStateException("Event verification failed");
}
}

return noteService.send(event, clientMap);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected WebSocketClientHandler(@NonNull String relayName, @NonNull String rela
this.eventClient = new SpringWebSocketClient(relayUri);
}

protected List<String> sendEvent(@NonNull IEvent event) {
public List<String> sendEvent(@NonNull IEvent event) {
((GenericEvent) event).validate();
return eventClient.send(new EventMessage(event)).stream().toList();
}
Expand Down
12 changes: 12 additions & 0 deletions nostr-java-api/src/main/java/nostr/api/service/NoteService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package nostr.api.service;

import lombok.NonNull;
import nostr.api.WebSocketClientHandler;
import nostr.base.IEvent;

import java.util.List;
import java.util.Map;

public interface NoteService {
List<String> send(@NonNull IEvent event, @NonNull Map<String, WebSocketClientHandler> clients);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package nostr.api.service.impl;

import lombok.NonNull;
import nostr.api.service.NoteService;
import nostr.api.WebSocketClientHandler;
import nostr.base.IEvent;

import java.util.List;
import java.util.Map;

/**
* Default implementation that dispatches notes through all WebSocket clients.
*/
public class DefaultNoteService implements NoteService {
@Override
public List<String> send(@NonNull IEvent event, @NonNull Map<String, WebSocketClientHandler> clients) {
return clients.values().stream()
.map(client -> client.sendEvent(event))
.flatMap(List::stream)
.distinct()
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package nostr.api.unit;

import nostr.api.NostrSpringWebSocketClient;
import nostr.api.service.NoteService;
import org.mockito.Mockito;
import nostr.config.Constants;
import nostr.event.impl.GenericEvent;
import nostr.id.Identity;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

public class NostrSpringWebSocketClientEventVerificationTest {

@Test
void sendEventThrowsWhenUnsigned() {
GenericEvent event = new GenericEvent();
event.setPubKey(Identity.generateRandomIdentity().getPublicKey());
event.setKind(Constants.Kind.SHORT_TEXT_NOTE);
event.setContent("test");

NoteService service = Mockito.mock(NoteService.class);
Mockito.when(service.send(Mockito.any(), Mockito.any())).thenReturn(List.of());
NostrSpringWebSocketClient client = new NostrSpringWebSocketClient(service);
assertThrows(IllegalStateException.class, () -> client.sendEvent(event));
}

@Test
void sendEventReturnsEmptyListWhenSigned() {
Identity identity = Identity.generateRandomIdentity();
GenericEvent event = new GenericEvent(identity.getPublicKey(), Constants.Kind.SHORT_TEXT_NOTE);
event.setContent("signed");
identity.sign(event);

NoteService service = Mockito.mock(NoteService.class);
Mockito.when(service.send(Mockito.any(), Mockito.any())).thenReturn(List.of());
NostrSpringWebSocketClient client = new NostrSpringWebSocketClient(service);
List<String> responses = client.sendEvent(event);
assertTrue(responses.isEmpty());
}
}