-
Notifications
You must be signed in to change notification settings - Fork 66
Ntfy #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ntfy #20
Changes from all commits
c9bdb3e
b9fe889
a1c06e9
cca5d36
88f71c1
a00a88a
4a31c56
40c96db
3aeaad1
d3464f7
fc05207
5a6b509
72f9ef3
dbcb3bf
dc6cbd9
03df1f3
d081f82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| target/ | ||
| /.idea/ | ||
| .env |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.example; | ||
|
|
||
| import javafx.application.Application; | ||
| import javafx.fxml.FXMLLoader; | ||
| import javafx.scene.Parent; | ||
| import javafx.scene.Scene; | ||
| import javafx.stage.Stage; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| public class ChatApplication extends Application { | ||
|
|
||
| @Override | ||
| public void start(Stage stage) throws IOException { | ||
|
|
||
| NtfyConnection ntfyService = new NtfyConnectionImpl(); | ||
| ChatModel model = new ChatModel(ntfyService); | ||
|
|
||
| model.startReceiving(); | ||
|
|
||
| FXMLLoader fxmlLoader = new FXMLLoader( | ||
| ChatApplication.class.getResource("chat-view.fxml")); | ||
|
|
||
| ChatController controller = new ChatController(model); | ||
| fxmlLoader.setController(controller); | ||
|
|
||
| Scene scene = new Scene(fxmlLoader.load(), 600, 400); | ||
| stage.setTitle("JavaFX Ntfy Chat App"); | ||
| stage.setScene(scene); | ||
| stage.show(); | ||
| } | ||
|
|
||
| public static void main(String[] args) { | ||
| launch(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package com.example; | ||
|
|
||
| import io.github.cdimascio.dotenv.Dotenv; | ||
| import javafx.fxml.FXML; | ||
| import javafx.scene.control.Button; | ||
| import javafx.scene.control.Label; | ||
| import javafx.scene.control.ListView; | ||
| import javafx.scene.control.TextField; | ||
|
|
||
| import java.time.Instant; | ||
| import java.time.ZoneId; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.Objects; | ||
|
|
||
|
|
||
| public class ChatController { | ||
|
|
||
| @FXML private ListView<ChatMessage> messageListView; | ||
| @FXML private TextField inputTextField; | ||
| @FXML private Button sendButton; | ||
|
|
||
| private final ChatModel model; | ||
|
|
||
| private final DateTimeFormatter timeFormatter = | ||
| DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.systemDefault()); | ||
|
|
||
|
|
||
| public ChatController(ChatModel model) { | ||
| this.model = model; | ||
| } | ||
|
|
||
| @FXML | ||
| public void initialize() { | ||
| messageListView.setItems(model.getMessages()); | ||
|
|
||
| messageListView.setCellFactory(lv -> new javafx.scene.control.ListCell<ChatMessage>() { | ||
| @Override | ||
| protected void updateItem(ChatMessage msg, boolean empty) { | ||
| super.updateItem(msg, empty); | ||
| if (empty || msg == null) { | ||
| setText(null); | ||
| } else { | ||
| String formattedTime = timeFormatter.format(Instant.ofEpochSecond(msg.timestamp())); | ||
| setText("[" + formattedTime + "] " + msg.content()); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
|
|
||
| inputTextField.setOnAction(event -> sendMessageAction()); | ||
| sendButton.setOnAction(event -> sendMessageAction()); | ||
| } | ||
|
|
||
|
|
||
| private void sendMessageAction() { | ||
| String message = inputTextField.getText().trim(); | ||
| if (!message.isEmpty()) { | ||
|
|
||
| model.sendMessage(message); | ||
|
|
||
|
|
||
| inputTextField.clear(); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package com.example; | ||
|
|
||
| public record ChatMessage(String content, long timestamp) { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||||
| package com.example; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import javafx.application.Platform; | ||||||||||||||||||||||||||||
| import javafx.collections.FXCollections; | ||||||||||||||||||||||||||||
| import javafx.collections.ObservableList; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public class ChatModel { | ||||||||||||||||||||||||||||
| private final ObservableList<ChatMessage> messages = FXCollections.observableArrayList(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private final NtfyConnection ntfyConnection; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public ChatModel(NtfyConnection ntfyConnection) { | ||||||||||||||||||||||||||||
| this.ntfyConnection = ntfyConnection; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| public ObservableList<ChatMessage> getMessages() { | ||||||||||||||||||||||||||||
| return messages; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void sendMessage(String text) { | ||||||||||||||||||||||||||||
| ntfyConnection.send(text); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void startReceiving() { | ||||||||||||||||||||||||||||
| ntfyConnection.receive(ntfyDto -> { | ||||||||||||||||||||||||||||
| ChatMessage chatMsg = new ChatMessage(ntfyDto.message(), ntfyDto.time()); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| messages.add(chatMsg); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: JavaFX threading violation - ObservableList modified from background thread. Line 28 modifies the JavaFX You've imported Apply this diff to fix the threading issue: public void startReceiving() {
ntfyConnection.receive(ntfyDto -> {
ChatMessage chatMsg = new ChatMessage(ntfyDto.message(), ntfyDto.time());
-
- messages.add(chatMsg);
+ Platform.runLater(() -> messages.add(chatMsg));
});
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example; | ||
|
|
||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.function.Consumer; | ||
|
|
||
| public interface NtfyConnection { | ||
| CompletableFuture<Void> send(String message); | ||
| void receive(Consumer<NtfyMessageDto> messageHandler); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,83 @@ | ||||||
| package com.example; | ||||||
|
|
||||||
| import io.github.cdimascio.dotenv.Dotenv; | ||||||
| import tools.jackson.databind.ObjectMapper; | ||||||
|
|
||||||
| import java.net.URI; | ||||||
| import java.net.http.HttpClient; | ||||||
| import java.net.http.HttpRequest; | ||||||
| import java.net.http.HttpResponse; | ||||||
| import java.util.concurrent.CompletableFuture; | ||||||
| import java.util.function.Consumer; | ||||||
|
|
||||||
| public class NtfyConnectionImpl implements NtfyConnection { | ||||||
|
|
||||||
| private final HttpClient http = HttpClient.newHttpClient(); | ||||||
| private final String hostName; | ||||||
| private final ObjectMapper mapper = new ObjectMapper(); | ||||||
|
|
||||||
| public NtfyConnectionImpl() { | ||||||
| String loadedHostName = null; | ||||||
| try { | ||||||
| Dotenv dotenv = Dotenv.load(); | ||||||
| loadedHostName = dotenv.get("HOST_NAME"); | ||||||
| } catch (Exception e) { | ||||||
| System.err.println("WARNING: Could not load .env file for HOST_NAME. Using fallback."); | ||||||
| } | ||||||
|
|
||||||
| this.hostName = (loadedHostName != null) | ||||||
| ? loadedHostName | ||||||
| : "http://localhost:8080"; | ||||||
|
|
||||||
| if (this.hostName.equals("http://localhost:8080")) { | ||||||
| System.out.println("DEBUG: NtfyConnectionImpl running in test/fallback mode."); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| public NtfyConnectionImpl(String hostName) { | ||||||
| this.hostName = hostName; | ||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public CompletableFuture<Void> send(String message) { | ||||||
| HttpRequest httpRequest = HttpRequest.newBuilder() | ||||||
| .POST(HttpRequest.BodyPublishers.ofString(message)) | ||||||
| .uri(URI.create(hostName + "/mytopic")) | ||||||
| .build(); | ||||||
|
|
||||||
| return http.sendAsync(httpRequest, HttpResponse.BodyHandlers.discarding()) | ||||||
| .thenAccept(response -> { | ||||||
| if (response.statusCode() >= 200 && response.statusCode() < 300) { | ||||||
| System.out.println("Message sent successfully."); | ||||||
| } else { | ||||||
| System.err.println("Error while sending: " + response.statusCode()); | ||||||
| } | ||||||
| }) | ||||||
| .exceptionally(e -> { | ||||||
| System.err.println("Network issue: " + e.getMessage()); | ||||||
| return null; | ||||||
| }); | ||||||
| } | ||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
|
||||||
| @Override | ||||||
| public void receive(Consumer<NtfyMessageDto> messageHandler) { | ||||||
| HttpRequest httpRequest = HttpRequest.newBuilder() | ||||||
| .GET() | ||||||
| .uri(URI.create(hostName + "/mytopic/json")) | ||||||
| .build(); | ||||||
|
|
||||||
| http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines()) | ||||||
| .thenAccept(response -> response.body() | ||||||
| .map(s -> { | ||||||
| try { | ||||||
| return mapper.readValue(s, NtfyMessageDto.class); | ||||||
| } catch (Exception e) { | ||||||
| System.err.println("Failed to parse message: " + e.getMessage()); | ||||||
| return null; | ||||||
| } | ||||||
| }) | ||||||
| .filter(messageDto -> messageDto != null && messageDto.event().equals("message")) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid potential NPE in event comparison. If Apply this diff: - .filter(messageDto -> messageDto != null && messageDto.event().equals("message"))
+ .filter(messageDto -> messageDto != null && "message".equals(messageDto.event()))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| .peek(System.out::println) | ||||||
| .forEach(messageHandler)); | ||||||
| } | ||||||
|
Comment on lines
+62
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix compilation error and add error handling. This code has a critical compilation issue and lacks error handling:
Apply this diff to fix the compilation error and add proper error handling: @Override
public void receive(Consumer<NtfyMessageDto> messageHandler) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(hostName + "/mytopic/json"))
.build();
http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> response.body()
- .map(s ->
- mapper.readValue(s, NtfyMessageDto.class))
+ .map(s -> {
+ try {
+ return mapper.readValue(s, NtfyMessageDto.class);
+ } catch (IOException e) {
+ System.err.println("Error parsing message: " + e.getMessage());
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
.filter(message -> message.event().equals("message"))
.peek(System.out::println)
- .forEach(messageHandler));
+ .forEach(messageHandler))
+ .exceptionally(throwable -> {
+ System.err.println("Error receiving messages: " + throwable.getMessage());
+ return null;
+ });
} |
||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Pre-release versions in test dependencies.
Both WireMock (4.0.0-beta.15) and TestFX (4.0.16-alpha) are using pre-release versions. While acceptable for test dependencies, ensure these versions are stable enough for your testing needs.
🌐 Web query:
💡 Result:
Short answer: No — both are pre-release and should be used with caution, not considered “stable” for critical/testing without verification.
WireMock 4.0.0-beta.15 is explicitly a 4.x beta (WireMock v4 is still labelled beta and may contain breaking/unstable changes; Java 17 is the baseline). If you need stability, the WireMock 3.x line is the production release. [1][2]
TestFX 4.0.16-alpha is an alpha (pre‑release) artifact from 2019 and has known compatibility issues with newer Java/JavaFX releases; newer TestFX builds (e.g., 4.0.18) exist. TestFX alphas frequently require careful Java/JavaFX version matching and may need workarounds. [3][4]
If you want actionable guidance: use WireMock 3.x for stable CI tests or accept beta risk and pin Java 17 for v4 beta; for UI tests prefer a maintained TestFX release (or verify your exact JDK/OpenJFX/TestFX combination in CI) before relying on 4.0.16-alpha. References: WireMock v4 beta docs and downloads, TestFX Maven listing and GitHub issue describing JavaFX compatibility problems. [1][2][3][4]
Replace pre-release test dependencies with stable versions.
Verification confirms both are problematic for testing:
Consider pinning stable versions in
pom.xmllines 58–69 to avoid CI failures or unexpected behavior during test execution.🤖 Prompt for AI Agents