Implement IronNotify Java SDK
- NotifyClient with global and instance-based usage - Fluent EventBuilder API - HTTP transport with OkHttp - Offline queue with JSON persistence - Severity levels and notification actions - Thread-safe with synchronized blocks - CompletableFuture for async operations - Options builder pattern - Full README with examples
This commit is contained in:
parent
dbdfc61b8e
commit
2e73e0c51f
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Compiled class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Package files
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
buildNumber.properties
|
||||||
|
.mvn/timing.properties
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Local storage
|
||||||
|
.ironnotify/
|
||||||
360
README.md
360
README.md
|
|
@ -1,2 +1,358 @@
|
||||||
# ironnotify-java
|
# IronNotify SDK for Java
|
||||||
IronNotify SDK for Java - Event notifications and alerts
|
|
||||||
|
Event notifications and alerts SDK for Java applications. Send notifications, receive real-time updates, and manage notification state.
|
||||||
|
|
||||||
|
[](https://search.maven.org/artifact/com.ironservices/notify)
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Maven
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ironservices</groupId>
|
||||||
|
<artifactId>notify</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gradle
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
implementation 'com.ironservices:notify:1.0.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Send a Simple Notification
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.notify.*;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Initialize
|
||||||
|
IronNotify.init("ak_live_xxxxx");
|
||||||
|
|
||||||
|
// Send a simple notification
|
||||||
|
SendResult result = IronNotify.notify(
|
||||||
|
"order.created",
|
||||||
|
"New Order Received",
|
||||||
|
"Order #1234 has been placed",
|
||||||
|
SeverityLevel.SUCCESS,
|
||||||
|
Map.of("order_id", "1234", "amount", 99.99)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
System.out.println("Notification sent: " + result.getNotificationId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown when done
|
||||||
|
IronNotify.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fluent Event Builder
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.notify.*;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
IronNotify.init("ak_live_xxxxx");
|
||||||
|
|
||||||
|
// Build complex notifications with the fluent API
|
||||||
|
SendResult result = IronNotify.event("payment.failed")
|
||||||
|
.withTitle("Payment Failed")
|
||||||
|
.withMessage("Payment could not be processed")
|
||||||
|
.withSeverity(SeverityLevel.ERROR)
|
||||||
|
.withMetadata("order_id", "1234")
|
||||||
|
.withMetadata("reason", "Card declined")
|
||||||
|
.withAction("Retry Payment", "/orders/1234/retry", null, "primary")
|
||||||
|
.withAction(EventBuilder.action("Contact Support")
|
||||||
|
.handler("open_support")
|
||||||
|
.build())
|
||||||
|
.forUser("user-123")
|
||||||
|
.withDeduplicationKey("payment-failed-1234")
|
||||||
|
.expiresIn(Duration.ofHours(24))
|
||||||
|
.send();
|
||||||
|
|
||||||
|
if (result.isQueued()) {
|
||||||
|
System.out.println("Notification queued for later");
|
||||||
|
}
|
||||||
|
|
||||||
|
IronNotify.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async Support
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.notify.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
IronNotify.init("ak_live_xxxxx");
|
||||||
|
|
||||||
|
// Async notification
|
||||||
|
CompletableFuture<SendResult> future = IronNotify.notifyAsync(
|
||||||
|
"user.signup",
|
||||||
|
"New User Registered"
|
||||||
|
);
|
||||||
|
|
||||||
|
future.thenAccept(result -> {
|
||||||
|
System.out.println("Notification sent: " + result.isSuccess());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Async event builder
|
||||||
|
IronNotify.event("order.shipped")
|
||||||
|
.withTitle("Order Shipped")
|
||||||
|
.forUser("user-123")
|
||||||
|
.sendAsync()
|
||||||
|
.thenAccept(result -> System.out.println("Done!"));
|
||||||
|
|
||||||
|
// Wait for async operations
|
||||||
|
future.join();
|
||||||
|
IronNotify.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the Client Directly
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.notify.*;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
NotifyClient client = new NotifyClient(
|
||||||
|
NotifyOptions.builder()
|
||||||
|
.apiKey("ak_live_xxxxx")
|
||||||
|
.debug(true)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send notification
|
||||||
|
SendResult result = client.notify("event.type", "Title");
|
||||||
|
|
||||||
|
// Use event builder
|
||||||
|
result = client.event("event.type")
|
||||||
|
.withTitle("Title")
|
||||||
|
.send();
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.notify.*;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
NotifyClient client = new NotifyClient(
|
||||||
|
NotifyOptions.builder()
|
||||||
|
.apiKey("ak_live_xxxxx")
|
||||||
|
.apiBaseUrl("https://api.ironnotify.com")
|
||||||
|
.webSocketUrl("wss://ws.ironnotify.com")
|
||||||
|
.debug(false)
|
||||||
|
.enableOfflineQueue(true)
|
||||||
|
.maxOfflineQueueSize(100)
|
||||||
|
.autoReconnect(true)
|
||||||
|
.maxReconnectAttempts(5)
|
||||||
|
.reconnectDelay(Duration.ofSeconds(1))
|
||||||
|
.httpTimeout(Duration.ofSeconds(30))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
|
||||||
|
| Option | Type | Default | Description |
|
||||||
|
|--------|------|---------|-------------|
|
||||||
|
| `apiKey` | String | required | Your API key (ak_live_xxx or ak_test_xxx) |
|
||||||
|
| `apiBaseUrl` | String | https://api.ironnotify.com | API base URL |
|
||||||
|
| `webSocketUrl` | String | wss://ws.ironnotify.com | WebSocket URL |
|
||||||
|
| `debug` | boolean | false | Enable debug logging |
|
||||||
|
| `enableOfflineQueue` | boolean | true | Queue notifications when offline |
|
||||||
|
| `maxOfflineQueueSize` | int | 100 | Max offline queue size |
|
||||||
|
| `autoReconnect` | boolean | true | Auto-reconnect WebSocket |
|
||||||
|
| `maxReconnectAttempts` | int | 5 | Max reconnection attempts |
|
||||||
|
| `reconnectDelay` | Duration | 1s | Base reconnection delay |
|
||||||
|
| `httpTimeout` | Duration | 30s | HTTP request timeout |
|
||||||
|
|
||||||
|
## Severity Levels
|
||||||
|
|
||||||
|
```java
|
||||||
|
SeverityLevel.INFO // "info"
|
||||||
|
SeverityLevel.SUCCESS // "success"
|
||||||
|
SeverityLevel.WARNING // "warning"
|
||||||
|
SeverityLevel.ERROR // "error"
|
||||||
|
SeverityLevel.CRITICAL // "critical"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Simple action with URL
|
||||||
|
IronNotify.event("order.shipped")
|
||||||
|
.withTitle("Order Shipped")
|
||||||
|
.withAction("Track Package", "https://tracking.example.com/123")
|
||||||
|
.send();
|
||||||
|
|
||||||
|
// Action with handler
|
||||||
|
IronNotify.event("order.shipped")
|
||||||
|
.withTitle("Order Shipped")
|
||||||
|
.withAction("View Order", null, "view_order", "default")
|
||||||
|
.send();
|
||||||
|
|
||||||
|
// Using action builder
|
||||||
|
IronNotify.event("order.shipped")
|
||||||
|
.withTitle("Order Shipped")
|
||||||
|
.withAction(EventBuilder.action("Track Package")
|
||||||
|
.url("https://tracking.example.com/123")
|
||||||
|
.style("primary")
|
||||||
|
.build())
|
||||||
|
.send();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deduplication
|
||||||
|
|
||||||
|
Prevent duplicate notifications:
|
||||||
|
|
||||||
|
```java
|
||||||
|
IronNotify.event("reminder")
|
||||||
|
.withTitle("Daily Reminder")
|
||||||
|
.withDeduplicationKey("daily-reminder-2024-01-15")
|
||||||
|
.send();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Grouping
|
||||||
|
|
||||||
|
Group related notifications:
|
||||||
|
|
||||||
|
```java
|
||||||
|
IronNotify.event("comment.new")
|
||||||
|
.withTitle("New Comment")
|
||||||
|
.withGroupKey("post-123-comments")
|
||||||
|
.send();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expiration
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
// Expires in 1 hour
|
||||||
|
IronNotify.event("flash_sale")
|
||||||
|
.withTitle("Flash Sale!")
|
||||||
|
.expiresIn(Duration.ofHours(1))
|
||||||
|
.send();
|
||||||
|
|
||||||
|
// Expires at specific time
|
||||||
|
IronNotify.event("event_reminder")
|
||||||
|
.withTitle("Event Tomorrow")
|
||||||
|
.expiresAt(Instant.now().plus(Duration.ofDays(1)))
|
||||||
|
.send();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Managing Notifications
|
||||||
|
|
||||||
|
### Get Notifications
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.util.List;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
// Get all notifications
|
||||||
|
List<Notification> notifications = client.getNotifications();
|
||||||
|
|
||||||
|
// With options
|
||||||
|
List<Notification> unread = client.getNotifications(10, 0, true);
|
||||||
|
|
||||||
|
// Async
|
||||||
|
client.getNotificationsAsync(10, 0, false)
|
||||||
|
.thenAccept(list -> System.out.println("Got " + list.size() + " notifications"));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mark as Read
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Mark single notification
|
||||||
|
client.markAsRead("notification-id");
|
||||||
|
|
||||||
|
// Mark all as read
|
||||||
|
client.markAllAsRead();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Unread Count
|
||||||
|
|
||||||
|
```java
|
||||||
|
int count = client.getUnreadCount();
|
||||||
|
System.out.printf("You have %d unread notifications%n", count);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Real-Time Notifications
|
||||||
|
|
||||||
|
```java
|
||||||
|
NotifyClient client = new NotifyClient(new NotifyOptions("ak_live_xxxxx"));
|
||||||
|
|
||||||
|
client.onNotification(notification -> {
|
||||||
|
System.out.println("New notification: " + notification.getTitle());
|
||||||
|
});
|
||||||
|
|
||||||
|
client.onUnreadCountChange(count -> {
|
||||||
|
System.out.println("Unread count: " + count);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.onConnectionStateChange(state -> {
|
||||||
|
System.out.println("Connection state: " + state);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.connect();
|
||||||
|
client.subscribeToUser("user-123");
|
||||||
|
client.subscribeToApp();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Offline Support
|
||||||
|
|
||||||
|
Notifications are automatically queued when offline:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// This will be queued if offline
|
||||||
|
IronNotify.notify("event", "Title");
|
||||||
|
|
||||||
|
// Manually flush the queue
|
||||||
|
IronNotify.flush();
|
||||||
|
|
||||||
|
// Or async
|
||||||
|
IronNotify.flushAsync().join();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Java 11+
|
||||||
|
- OkHttp 4.x
|
||||||
|
- Gson 2.x
|
||||||
|
|
||||||
|
## Thread Safety
|
||||||
|
|
||||||
|
The client is thread-safe and can be used from multiple threads concurrently.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [Documentation](https://www.ironnotify.com/docs)
|
||||||
|
- [Dashboard](https://www.ironnotify.com)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](LICENSE) for details.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.ironservices</groupId>
|
||||||
|
<artifactId>notify</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>IronNotify Java SDK</name>
|
||||||
|
<description>Event notifications and alerts SDK for Java applications</description>
|
||||||
|
<url>https://github.com/IronServices/ironnotify-java</url>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT License</name>
|
||||||
|
<url>https://opensource.org/licenses/MIT</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>IronServices</name>
|
||||||
|
<email>support@ironservices.com</email>
|
||||||
|
<organization>IronServices</organization>
|
||||||
|
<organizationUrl>https://www.ironservices.com</organizationUrl>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<connection>scm:git:git://github.com/IronServices/ironnotify-java.git</connection>
|
||||||
|
<developerConnection>scm:git:ssh://github.com:IronServices/ironnotify-java.git</developerConnection>
|
||||||
|
<url>https://github.com/IronServices/ironnotify-java/tree/main</url>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>4.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.java-websocket</groupId>
|
||||||
|
<artifactId>Java-WebSocket</artifactId>
|
||||||
|
<version>1.5.4</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>11</source>
|
||||||
|
<target>11</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket connection state.
|
||||||
|
*/
|
||||||
|
public enum ConnectionState {
|
||||||
|
DISCONNECTED("disconnected"),
|
||||||
|
CONNECTING("connecting"),
|
||||||
|
CONNECTED("connected"),
|
||||||
|
RECONNECTING("reconnecting");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
ConnectionState(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for creating notifications with a fluent API.
|
||||||
|
*/
|
||||||
|
public class EventBuilder {
|
||||||
|
private final NotifyClient client;
|
||||||
|
private final String eventType;
|
||||||
|
private String title;
|
||||||
|
private String message;
|
||||||
|
private SeverityLevel severity = SeverityLevel.INFO;
|
||||||
|
private Map<String, Object> metadata = new HashMap<>();
|
||||||
|
private List<NotificationAction> actions = new ArrayList<>();
|
||||||
|
private String userId;
|
||||||
|
private String groupKey;
|
||||||
|
private String deduplicationKey;
|
||||||
|
private Instant expiresAt;
|
||||||
|
|
||||||
|
EventBuilder(NotifyClient client, String eventType) {
|
||||||
|
this.client = client;
|
||||||
|
this.eventType = eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the notification title.
|
||||||
|
*/
|
||||||
|
public EventBuilder withTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the notification message.
|
||||||
|
*/
|
||||||
|
public EventBuilder withMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the severity level.
|
||||||
|
*/
|
||||||
|
public EventBuilder withSeverity(SeverityLevel severity) {
|
||||||
|
this.severity = severity;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a metadata entry.
|
||||||
|
*/
|
||||||
|
public EventBuilder withMetadata(String key, Object value) {
|
||||||
|
this.metadata.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds multiple metadata entries.
|
||||||
|
*/
|
||||||
|
public EventBuilder withMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata.putAll(metadata);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an action button with a URL.
|
||||||
|
*/
|
||||||
|
public EventBuilder withAction(String label, String url) {
|
||||||
|
this.actions.add(new NotificationAction(label, url, null, "default"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an action button with options.
|
||||||
|
*/
|
||||||
|
public EventBuilder withAction(String label, String url, String action, String style) {
|
||||||
|
this.actions.add(new NotificationAction(label, url, action, style));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an action using the ActionBuilder.
|
||||||
|
*/
|
||||||
|
public EventBuilder withAction(NotificationAction action) {
|
||||||
|
this.actions.add(action);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the target user ID.
|
||||||
|
*/
|
||||||
|
public EventBuilder forUser(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the group key for grouping related notifications.
|
||||||
|
*/
|
||||||
|
public EventBuilder withGroupKey(String groupKey) {
|
||||||
|
this.groupKey = groupKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the deduplication key.
|
||||||
|
*/
|
||||||
|
public EventBuilder withDeduplicationKey(String key) {
|
||||||
|
this.deduplicationKey = key;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the expiration time from now.
|
||||||
|
*/
|
||||||
|
public EventBuilder expiresIn(Duration duration) {
|
||||||
|
this.expiresAt = Instant.now().plus(duration);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the expiration time.
|
||||||
|
*/
|
||||||
|
public EventBuilder expiresAt(Instant instant) {
|
||||||
|
this.expiresAt = instant;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the notification payload.
|
||||||
|
*/
|
||||||
|
public NotificationPayload build() {
|
||||||
|
if (title == null || title.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Notification title is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationPayload payload = new NotificationPayload(eventType, title);
|
||||||
|
payload.setMessage(message);
|
||||||
|
payload.setSeverity(severity);
|
||||||
|
payload.setUserId(userId);
|
||||||
|
payload.setGroupKey(groupKey);
|
||||||
|
payload.setDeduplicationKey(deduplicationKey);
|
||||||
|
payload.setExpiresAt(expiresAt);
|
||||||
|
|
||||||
|
if (!metadata.isEmpty()) {
|
||||||
|
payload.setMetadata(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!actions.isEmpty()) {
|
||||||
|
payload.setActions(actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the notification synchronously.
|
||||||
|
*/
|
||||||
|
public SendResult send() {
|
||||||
|
NotificationPayload payload = build();
|
||||||
|
return client.sendPayload(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the notification asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<SendResult> sendAsync() {
|
||||||
|
NotificationPayload payload = build();
|
||||||
|
return client.sendPayloadAsync(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action builder for complex actions.
|
||||||
|
*/
|
||||||
|
public static ActionBuilder action(String label) {
|
||||||
|
return new ActionBuilder(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for notification actions.
|
||||||
|
*/
|
||||||
|
public static class ActionBuilder {
|
||||||
|
private final NotificationAction action;
|
||||||
|
|
||||||
|
ActionBuilder(String label) {
|
||||||
|
this.action = new NotificationAction(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionBuilder url(String url) {
|
||||||
|
action.setUrl(url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionBuilder handler(String actionHandler) {
|
||||||
|
action.setAction(actionHandler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionBuilder style(String style) {
|
||||||
|
action.setStyle(style);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationAction build() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static facade for the IronNotify SDK.
|
||||||
|
* Provides convenient static methods that delegate to the global client.
|
||||||
|
*/
|
||||||
|
public final class IronNotify {
|
||||||
|
|
||||||
|
private IronNotify() {
|
||||||
|
// Utility class
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the SDK with an API key.
|
||||||
|
*/
|
||||||
|
public static void init(String apiKey) {
|
||||||
|
NotifyClient.init(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the SDK with options.
|
||||||
|
*/
|
||||||
|
public static void init(NotifyOptions options) {
|
||||||
|
NotifyClient.init(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification.
|
||||||
|
*/
|
||||||
|
public static SendResult notify(String eventType, String title) {
|
||||||
|
return NotifyClient.get().notify(eventType, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification with options.
|
||||||
|
*/
|
||||||
|
public static SendResult notify(String eventType, String title, String message,
|
||||||
|
SeverityLevel severity, Map<String, Object> metadata) {
|
||||||
|
return NotifyClient.get().notify(eventType, title, message, severity, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification asynchronously.
|
||||||
|
*/
|
||||||
|
public static CompletableFuture<SendResult> notifyAsync(String eventType, String title) {
|
||||||
|
return NotifyClient.get().notifyAsync(eventType, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification asynchronously with options.
|
||||||
|
*/
|
||||||
|
public static CompletableFuture<SendResult> notifyAsync(String eventType, String title, String message,
|
||||||
|
SeverityLevel severity, Map<String, Object> metadata) {
|
||||||
|
return NotifyClient.get().notifyAsync(eventType, title, message, severity, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event builder.
|
||||||
|
*/
|
||||||
|
public static EventBuilder event(String eventType) {
|
||||||
|
return NotifyClient.get().event(eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets notifications.
|
||||||
|
*/
|
||||||
|
public static List<Notification> getNotifications() throws IOException {
|
||||||
|
return NotifyClient.get().getNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets notifications with options.
|
||||||
|
*/
|
||||||
|
public static List<Notification> getNotifications(Integer limit, Integer offset, boolean unreadOnly) throws IOException {
|
||||||
|
return NotifyClient.get().getNotifications(limit, offset, unreadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets notifications asynchronously.
|
||||||
|
*/
|
||||||
|
public static CompletableFuture<List<Notification>> getNotificationsAsync() {
|
||||||
|
return NotifyClient.get().getNotificationsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets notifications asynchronously with options.
|
||||||
|
*/
|
||||||
|
public static CompletableFuture<List<Notification>> getNotificationsAsync(Integer limit, Integer offset, boolean unreadOnly) {
|
||||||
|
return NotifyClient.get().getNotificationsAsync(limit, offset, unreadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the unread count.
|
||||||
|
*/
|
||||||
|
public static int getUnreadCount() throws IOException {
|
||||||
|
return NotifyClient.get().getUnreadCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the unread count asynchronously.
|
||||||
|
*/
|
||||||
|
public static CompletableFuture<Integer> getUnreadCountAsync() {
|
||||||
|
return NotifyClient.get().getUnreadCountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a notification as read.
|
||||||
|
*/
|
||||||
|
public static boolean markAsRead(String notificationId) throws IOException {
|
||||||
|
return NotifyClient.get().markAsRead(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks all notifications as read.
|
||||||
|
*/
|
||||||
|
public static boolean markAllAsRead() throws IOException {
|
||||||
|
return NotifyClient.get().markAllAsRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to real-time notifications.
|
||||||
|
*/
|
||||||
|
public static void connect() {
|
||||||
|
NotifyClient.get().connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects from real-time notifications.
|
||||||
|
*/
|
||||||
|
public static void disconnect() {
|
||||||
|
NotifyClient.get().disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to a user's notifications.
|
||||||
|
*/
|
||||||
|
public static void subscribeToUser(String userId) {
|
||||||
|
NotifyClient.get().subscribeToUser(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to app-wide notifications.
|
||||||
|
*/
|
||||||
|
public static void subscribeToApp() {
|
||||||
|
NotifyClient.get().subscribeToApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the offline queue.
|
||||||
|
*/
|
||||||
|
public static void flush() {
|
||||||
|
NotifyClient.get().flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the offline queue asynchronously.
|
||||||
|
*/
|
||||||
|
public static CompletableFuture<Void> flushAsync() {
|
||||||
|
return NotifyClient.get().flushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts down the SDK.
|
||||||
|
*/
|
||||||
|
public static void shutdown() {
|
||||||
|
NotifyClient.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A notification received from the server.
|
||||||
|
*/
|
||||||
|
public class Notification {
|
||||||
|
private String id;
|
||||||
|
private String eventType;
|
||||||
|
private String title;
|
||||||
|
private String message;
|
||||||
|
private SeverityLevel severity;
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
private List<NotificationAction> actions;
|
||||||
|
private String userId;
|
||||||
|
private String groupKey;
|
||||||
|
private boolean read;
|
||||||
|
private Instant createdAt;
|
||||||
|
private Instant expiresAt;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEventType() {
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEventType(String eventType) {
|
||||||
|
this.eventType = eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeverityLevel getSeverity() {
|
||||||
|
return severity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeverity(SeverityLevel severity) {
|
||||||
|
this.severity = severity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NotificationAction> getActions() {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActions(List<NotificationAction> actions) {
|
||||||
|
this.actions = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupKey() {
|
||||||
|
return groupKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupKey(String groupKey) {
|
||||||
|
this.groupKey = groupKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRead() {
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRead(boolean read) {
|
||||||
|
this.read = read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(Instant createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getExpiresAt() {
|
||||||
|
return expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiresAt(Instant expiresAt) {
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action button on a notification.
|
||||||
|
*/
|
||||||
|
public class NotificationAction {
|
||||||
|
private String label;
|
||||||
|
private String url;
|
||||||
|
private String action;
|
||||||
|
private String style;
|
||||||
|
|
||||||
|
public NotificationAction() {
|
||||||
|
this.style = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationAction(String label) {
|
||||||
|
this.label = label;
|
||||||
|
this.style = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationAction(String label, String url, String action, String style) {
|
||||||
|
this.label = label;
|
||||||
|
this.url = url;
|
||||||
|
this.action = action;
|
||||||
|
this.style = style != null ? style : "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationAction setLabel(String label) {
|
||||||
|
this.label = label;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationAction setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationAction setAction(String action) {
|
||||||
|
this.action = action;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStyle() {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationAction setStyle(String style) {
|
||||||
|
this.style = style;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload for creating a notification.
|
||||||
|
*/
|
||||||
|
public class NotificationPayload {
|
||||||
|
private String eventType;
|
||||||
|
private String title;
|
||||||
|
private String message;
|
||||||
|
private SeverityLevel severity;
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
private List<NotificationAction> actions;
|
||||||
|
private String userId;
|
||||||
|
private String groupKey;
|
||||||
|
private String deduplicationKey;
|
||||||
|
private Instant expiresAt;
|
||||||
|
|
||||||
|
public NotificationPayload() {
|
||||||
|
this.severity = SeverityLevel.INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload(String eventType, String title) {
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.title = title;
|
||||||
|
this.severity = SeverityLevel.INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEventType() {
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setEventType(String eventType) {
|
||||||
|
this.eventType = eventType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeverityLevel getSeverity() {
|
||||||
|
return severity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setSeverity(SeverityLevel severity) {
|
||||||
|
this.severity = severity;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NotificationAction> getActions() {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setActions(List<NotificationAction> actions) {
|
||||||
|
this.actions = actions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupKey() {
|
||||||
|
return groupKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setGroupKey(String groupKey) {
|
||||||
|
this.groupKey = groupKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeduplicationKey() {
|
||||||
|
return deduplicationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setDeduplicationKey(String deduplicationKey) {
|
||||||
|
this.deduplicationKey = deduplicationKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getExpiresAt() {
|
||||||
|
return expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationPayload setExpiresAt(Instant expiresAt) {
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IronNotify client for sending and receiving notifications.
|
||||||
|
*/
|
||||||
|
public class NotifyClient implements AutoCloseable {
|
||||||
|
private final NotifyOptions options;
|
||||||
|
private final Transport transport;
|
||||||
|
private final OfflineQueue queue;
|
||||||
|
private volatile boolean isOnline = true;
|
||||||
|
private volatile ConnectionState connectionState = ConnectionState.DISCONNECTED;
|
||||||
|
|
||||||
|
private Consumer<Notification> onNotification;
|
||||||
|
private Consumer<Integer> onUnreadCountChange;
|
||||||
|
private Consumer<ConnectionState> onConnectionStateChange;
|
||||||
|
|
||||||
|
private static NotifyClient globalClient;
|
||||||
|
private static final Object globalLock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NotifyClient with the given options.
|
||||||
|
*/
|
||||||
|
public NotifyClient(NotifyOptions options) {
|
||||||
|
if (options.getApiKey() == null || options.getApiKey().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("API key is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
this.transport = new Transport(
|
||||||
|
options.getApiBaseUrl(),
|
||||||
|
options.getApiKey(),
|
||||||
|
options.getHttpTimeout(),
|
||||||
|
options.isDebug()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.isEnableOfflineQueue()) {
|
||||||
|
this.queue = new OfflineQueue(options.getMaxOfflineQueueSize(), options.isDebug());
|
||||||
|
} else {
|
||||||
|
this.queue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.isDebug()) {
|
||||||
|
System.out.println("[IronNotify] Client initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the global client.
|
||||||
|
*/
|
||||||
|
public static void init(String apiKey) {
|
||||||
|
init(new NotifyOptions(apiKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the global client with options.
|
||||||
|
*/
|
||||||
|
public static void init(NotifyOptions options) {
|
||||||
|
synchronized (globalLock) {
|
||||||
|
if (globalClient != null) {
|
||||||
|
globalClient.close();
|
||||||
|
}
|
||||||
|
globalClient = new NotifyClient(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the global client.
|
||||||
|
*/
|
||||||
|
public static NotifyClient get() {
|
||||||
|
synchronized (globalLock) {
|
||||||
|
if (globalClient == null) {
|
||||||
|
throw new IllegalStateException("IronNotify not initialized. Call init() first.");
|
||||||
|
}
|
||||||
|
return globalClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification.
|
||||||
|
*/
|
||||||
|
public SendResult notify(String eventType, String title) {
|
||||||
|
return notify(eventType, title, null, SeverityLevel.INFO, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification with options.
|
||||||
|
*/
|
||||||
|
public SendResult notify(String eventType, String title, String message,
|
||||||
|
SeverityLevel severity, Map<String, Object> metadata) {
|
||||||
|
NotificationPayload payload = new NotificationPayload(eventType, title);
|
||||||
|
payload.setMessage(message);
|
||||||
|
payload.setSeverity(severity);
|
||||||
|
payload.setMetadata(metadata);
|
||||||
|
return sendPayload(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<SendResult> notifyAsync(String eventType, String title) {
|
||||||
|
return notifyAsync(eventType, title, null, SeverityLevel.INFO, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification asynchronously with options.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<SendResult> notifyAsync(String eventType, String title, String message,
|
||||||
|
SeverityLevel severity, Map<String, Object> metadata) {
|
||||||
|
NotificationPayload payload = new NotificationPayload(eventType, title);
|
||||||
|
payload.setMessage(message);
|
||||||
|
payload.setSeverity(severity);
|
||||||
|
payload.setMetadata(metadata);
|
||||||
|
return sendPayloadAsync(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an event builder.
|
||||||
|
*/
|
||||||
|
public EventBuilder event(String eventType) {
|
||||||
|
return new EventBuilder(this, eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification payload.
|
||||||
|
*/
|
||||||
|
public SendResult sendPayload(NotificationPayload payload) {
|
||||||
|
SendResult result = transport.send(payload);
|
||||||
|
|
||||||
|
if (!result.isSuccess() && options.isEnableOfflineQueue() && queue != null) {
|
||||||
|
queue.add(payload);
|
||||||
|
isOnline = false;
|
||||||
|
return SendResult.queued(result.getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a notification payload asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<SendResult> sendPayloadAsync(NotificationPayload payload) {
|
||||||
|
return transport.sendAsync(payload).thenApply(result -> {
|
||||||
|
if (!result.isSuccess() && options.isEnableOfflineQueue() && queue != null) {
|
||||||
|
queue.add(payload);
|
||||||
|
isOnline = false;
|
||||||
|
return SendResult.queued(result.getError());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets notifications.
|
||||||
|
*/
|
||||||
|
public List<Notification> getNotifications() throws IOException {
|
||||||
|
return getNotifications(null, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets notifications with options.
|
||||||
|
*/
|
||||||
|
public List<Notification> getNotifications(Integer limit, Integer offset, boolean unreadOnly) throws IOException {
|
||||||
|
return transport.getNotifications(limit, offset, unreadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets notifications asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<List<Notification>> getNotificationsAsync() {
|
||||||
|
return getNotificationsAsync(null, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets notifications asynchronously with options.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<List<Notification>> getNotificationsAsync(Integer limit, Integer offset, boolean unreadOnly) {
|
||||||
|
return transport.getNotificationsAsync(limit, offset, unreadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the unread notification count.
|
||||||
|
*/
|
||||||
|
public int getUnreadCount() throws IOException {
|
||||||
|
return transport.getUnreadCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the unread notification count asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Integer> getUnreadCountAsync() {
|
||||||
|
return transport.getUnreadCountAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a notification as read.
|
||||||
|
*/
|
||||||
|
public boolean markAsRead(String notificationId) throws IOException {
|
||||||
|
return transport.markAsRead(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a notification as read asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Boolean> markAsReadAsync(String notificationId) {
|
||||||
|
return transport.markAsReadAsync(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks all notifications as read.
|
||||||
|
*/
|
||||||
|
public boolean markAllAsRead() throws IOException {
|
||||||
|
return transport.markAllAsRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks all notifications as read asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Boolean> markAllAsReadAsync() {
|
||||||
|
return transport.markAllAsReadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the notification handler.
|
||||||
|
*/
|
||||||
|
public void onNotification(Consumer<Notification> handler) {
|
||||||
|
this.onNotification = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the unread count change handler.
|
||||||
|
*/
|
||||||
|
public void onUnreadCountChange(Consumer<Integer> handler) {
|
||||||
|
this.onUnreadCountChange = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the connection state change handler.
|
||||||
|
*/
|
||||||
|
public void onConnectionStateChange(Consumer<ConnectionState> handler) {
|
||||||
|
this.onConnectionStateChange = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current connection state.
|
||||||
|
*/
|
||||||
|
public ConnectionState getConnectionState() {
|
||||||
|
return connectionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to real-time notifications.
|
||||||
|
*/
|
||||||
|
public void connect() {
|
||||||
|
setConnectionState(ConnectionState.CONNECTED);
|
||||||
|
if (options.isDebug()) {
|
||||||
|
System.out.println("[IronNotify] Connected (WebSocket not implemented)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects from real-time notifications.
|
||||||
|
*/
|
||||||
|
public void disconnect() {
|
||||||
|
setConnectionState(ConnectionState.DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to a user's notifications.
|
||||||
|
*/
|
||||||
|
public void subscribeToUser(String userId) {
|
||||||
|
if (options.isDebug()) {
|
||||||
|
System.out.println("[IronNotify] Subscribed to user: " + userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to app-wide notifications.
|
||||||
|
*/
|
||||||
|
public void subscribeToApp() {
|
||||||
|
if (options.isDebug()) {
|
||||||
|
System.out.println("[IronNotify] Subscribed to app notifications");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the offline queue.
|
||||||
|
*/
|
||||||
|
public void flush() {
|
||||||
|
if (queue == null || queue.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!transport.isOnline()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOnline = true;
|
||||||
|
List<NotificationPayload> notifications = queue.getAll();
|
||||||
|
|
||||||
|
for (int i = notifications.size() - 1; i >= 0; i--) {
|
||||||
|
SendResult result = transport.send(notifications.get(i));
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
queue.remove(i);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the offline queue asynchronously.
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Void> flushAsync() {
|
||||||
|
return CompletableFuture.runAsync(this::flush);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setConnectionState(ConnectionState state) {
|
||||||
|
this.connectionState = state;
|
||||||
|
if (onConnectionStateChange != null) {
|
||||||
|
onConnectionStateChange.accept(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
disconnect();
|
||||||
|
transport.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the global client.
|
||||||
|
*/
|
||||||
|
public static void shutdown() {
|
||||||
|
synchronized (globalLock) {
|
||||||
|
if (globalClient != null) {
|
||||||
|
globalClient.close();
|
||||||
|
globalClient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for the IronNotify client.
|
||||||
|
*/
|
||||||
|
public class NotifyOptions {
|
||||||
|
private String apiKey;
|
||||||
|
private String apiBaseUrl = "https://api.ironnotify.com";
|
||||||
|
private String webSocketUrl = "wss://ws.ironnotify.com";
|
||||||
|
private boolean debug = false;
|
||||||
|
private boolean enableOfflineQueue = true;
|
||||||
|
private int maxOfflineQueueSize = 100;
|
||||||
|
private boolean autoReconnect = true;
|
||||||
|
private int maxReconnectAttempts = 5;
|
||||||
|
private Duration reconnectDelay = Duration.ofSeconds(1);
|
||||||
|
private Duration httpTimeout = Duration.ofSeconds(30);
|
||||||
|
|
||||||
|
public NotifyOptions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions(String apiKey) {
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiKey() {
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setApiKey(String apiKey) {
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiBaseUrl() {
|
||||||
|
return apiBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setApiBaseUrl(String apiBaseUrl) {
|
||||||
|
this.apiBaseUrl = apiBaseUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWebSocketUrl() {
|
||||||
|
return webSocketUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setWebSocketUrl(String webSocketUrl) {
|
||||||
|
this.webSocketUrl = webSocketUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDebug() {
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setDebug(boolean debug) {
|
||||||
|
this.debug = debug;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableOfflineQueue() {
|
||||||
|
return enableOfflineQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setEnableOfflineQueue(boolean enableOfflineQueue) {
|
||||||
|
this.enableOfflineQueue = enableOfflineQueue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxOfflineQueueSize() {
|
||||||
|
return maxOfflineQueueSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setMaxOfflineQueueSize(int maxOfflineQueueSize) {
|
||||||
|
this.maxOfflineQueueSize = maxOfflineQueueSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoReconnect() {
|
||||||
|
return autoReconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setAutoReconnect(boolean autoReconnect) {
|
||||||
|
this.autoReconnect = autoReconnect;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxReconnectAttempts() {
|
||||||
|
return maxReconnectAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setMaxReconnectAttempts(int maxReconnectAttempts) {
|
||||||
|
this.maxReconnectAttempts = maxReconnectAttempts;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getReconnectDelay() {
|
||||||
|
return reconnectDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setReconnectDelay(Duration reconnectDelay) {
|
||||||
|
this.reconnectDelay = reconnectDelay;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getHttpTimeout() {
|
||||||
|
return httpTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions setHttpTimeout(Duration httpTimeout) {
|
||||||
|
this.httpTimeout = httpTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder for NotifyOptions.
|
||||||
|
*/
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for NotifyOptions.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private final NotifyOptions options = new NotifyOptions();
|
||||||
|
|
||||||
|
public Builder apiKey(String apiKey) {
|
||||||
|
options.setApiKey(apiKey);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder apiBaseUrl(String apiBaseUrl) {
|
||||||
|
options.setApiBaseUrl(apiBaseUrl);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder webSocketUrl(String webSocketUrl) {
|
||||||
|
options.setWebSocketUrl(webSocketUrl);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder debug(boolean debug) {
|
||||||
|
options.setDebug(debug);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder enableOfflineQueue(boolean enable) {
|
||||||
|
options.setEnableOfflineQueue(enable);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder maxOfflineQueueSize(int size) {
|
||||||
|
options.setMaxOfflineQueueSize(size);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder autoReconnect(boolean autoReconnect) {
|
||||||
|
options.setAutoReconnect(autoReconnect);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder maxReconnectAttempts(int attempts) {
|
||||||
|
options.setMaxReconnectAttempts(attempts);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder reconnectDelay(Duration delay) {
|
||||||
|
options.setReconnectDelay(delay);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder httpTimeout(Duration timeout) {
|
||||||
|
options.setHttpTimeout(timeout);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotifyOptions build() {
|
||||||
|
if (options.getApiKey() == null || options.getApiKey().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("API key is required");
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline queue for storing notifications when offline.
|
||||||
|
*/
|
||||||
|
class OfflineQueue {
|
||||||
|
private final int maxSize;
|
||||||
|
private final boolean debug;
|
||||||
|
private final List<NotificationPayload> queue;
|
||||||
|
private final Path storagePath;
|
||||||
|
private final Gson gson;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
OfflineQueue(int maxSize, boolean debug) {
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
this.debug = debug;
|
||||||
|
this.queue = new ArrayList<>();
|
||||||
|
this.storagePath = Paths.get(System.getProperty("user.home"), ".ironnotify", "offline_queue.json");
|
||||||
|
this.gson = new GsonBuilder().create();
|
||||||
|
loadFromStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(NotificationPayload payload) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (queue.size() >= maxSize) {
|
||||||
|
queue.remove(0);
|
||||||
|
if (debug) {
|
||||||
|
System.out.println("[IronNotify] Offline queue full, dropping oldest notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.add(payload);
|
||||||
|
saveToStorage();
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
System.out.println("[IronNotify] Notification queued for later: " + payload.getEventType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<NotificationPayload> getAll() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return new ArrayList<>(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(int index) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (index >= 0 && index < queue.size()) {
|
||||||
|
queue.remove(index);
|
||||||
|
saveToStorage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
synchronized (lock) {
|
||||||
|
queue.clear();
|
||||||
|
saveToStorage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int size() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return queue.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return queue.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromStorage() {
|
||||||
|
try {
|
||||||
|
if (Files.exists(storagePath)) {
|
||||||
|
String json = new String(Files.readAllBytes(storagePath));
|
||||||
|
Type listType = new TypeToken<List<NotificationPayload>>(){}.getType();
|
||||||
|
List<NotificationPayload> loaded = gson.fromJson(json, listType);
|
||||||
|
if (loaded != null) {
|
||||||
|
queue.addAll(loaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore errors loading from storage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveToStorage() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(storagePath.getParent());
|
||||||
|
String json = gson.toJson(queue);
|
||||||
|
Files.write(storagePath, json.getBytes());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore errors saving to storage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of sending a notification.
|
||||||
|
*/
|
||||||
|
public class SendResult {
|
||||||
|
private final boolean success;
|
||||||
|
private final String notificationId;
|
||||||
|
private final String error;
|
||||||
|
private final boolean queued;
|
||||||
|
|
||||||
|
private SendResult(boolean success, String notificationId, String error, boolean queued) {
|
||||||
|
this.success = success;
|
||||||
|
this.notificationId = notificationId;
|
||||||
|
this.error = error;
|
||||||
|
this.queued = queued;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendResult success(String notificationId) {
|
||||||
|
return new SendResult(true, notificationId, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendResult failure(String error) {
|
||||||
|
return new SendResult(false, null, error, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendResult queued(String error) {
|
||||||
|
return new SendResult(false, null, error, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotificationId() {
|
||||||
|
return notificationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQueued() {
|
||||||
|
return queued;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Severity level for notifications.
|
||||||
|
*/
|
||||||
|
public enum SeverityLevel {
|
||||||
|
@SerializedName("info")
|
||||||
|
INFO("info"),
|
||||||
|
|
||||||
|
@SerializedName("success")
|
||||||
|
SUCCESS("success"),
|
||||||
|
|
||||||
|
@SerializedName("warning")
|
||||||
|
WARNING("warning"),
|
||||||
|
|
||||||
|
@SerializedName("error")
|
||||||
|
ERROR("error"),
|
||||||
|
|
||||||
|
@SerializedName("critical")
|
||||||
|
CRITICAL("critical");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
SeverityLevel(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,235 @@
|
||||||
|
package com.ironservices.notify;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import okhttp3.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP transport for IronNotify API.
|
||||||
|
*/
|
||||||
|
class Transport {
|
||||||
|
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||||
|
|
||||||
|
private final String baseUrl;
|
||||||
|
private final String apiKey;
|
||||||
|
private final boolean debug;
|
||||||
|
private final OkHttpClient client;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
Transport(String baseUrl, String apiKey, Duration timeout, boolean debug) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
this.debug = debug;
|
||||||
|
this.client = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||||
|
.readTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||||
|
.writeTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||||
|
.build();
|
||||||
|
this.gson = new GsonBuilder()
|
||||||
|
.setFieldNamingStrategy(field -> {
|
||||||
|
String name = field.getName();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < name.length(); i++) {
|
||||||
|
char c = name.charAt(i);
|
||||||
|
if (Character.isUpperCase(c)) {
|
||||||
|
if (i > 0) {
|
||||||
|
sb.append('_');
|
||||||
|
}
|
||||||
|
sb.append(Character.toLowerCase(c));
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
})
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
SendResult send(NotificationPayload payload) {
|
||||||
|
try {
|
||||||
|
String json = gson.toJson(payload);
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
System.out.println("[IronNotify] Sending notification: " + payload.getEventType());
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(baseUrl + "/api/v1/notify")
|
||||||
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
|
.post(RequestBody.create(json, JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
String body = response.body() != null ? response.body().string() : "";
|
||||||
|
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
SendResponse result = gson.fromJson(body, SendResponse.class);
|
||||||
|
return SendResult.success(result != null ? result.notificationId : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorResponse error = gson.fromJson(body, ErrorResponse.class);
|
||||||
|
return SendResult.failure(error != null && error.error != null
|
||||||
|
? error.error
|
||||||
|
: "HTTP " + response.code() + ": " + body);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return SendResult.failure(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<SendResult> sendAsync(NotificationPayload payload) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> send(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Notification> getNotifications(Integer limit, Integer offset, boolean unreadOnly) throws IOException {
|
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl + "/api/v1/notifications").newBuilder();
|
||||||
|
|
||||||
|
if (limit != null && limit > 0) {
|
||||||
|
urlBuilder.addQueryParameter("limit", String.valueOf(limit));
|
||||||
|
}
|
||||||
|
if (offset != null && offset > 0) {
|
||||||
|
urlBuilder.addQueryParameter("offset", String.valueOf(offset));
|
||||||
|
}
|
||||||
|
if (unreadOnly) {
|
||||||
|
urlBuilder.addQueryParameter("unread_only", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.build())
|
||||||
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
throw new IOException("HTTP " + response.code());
|
||||||
|
}
|
||||||
|
|
||||||
|
String body = response.body() != null ? response.body().string() : "[]";
|
||||||
|
Type listType = new TypeToken<List<Notification>>(){}.getType();
|
||||||
|
return gson.fromJson(body, listType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<List<Notification>> getNotificationsAsync(Integer limit, Integer offset, boolean unreadOnly) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
return getNotifications(limit, offset, unreadOnly);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int getUnreadCount() throws IOException {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(baseUrl + "/api/v1/notifications/unread-count")
|
||||||
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
throw new IOException("HTTP " + response.code());
|
||||||
|
}
|
||||||
|
|
||||||
|
String body = response.body() != null ? response.body().string() : "{}";
|
||||||
|
CountResponse result = gson.fromJson(body, CountResponse.class);
|
||||||
|
return result != null ? result.count : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<Integer> getUnreadCountAsync() {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
return getUnreadCount();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean markAsRead(String notificationId) throws IOException {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(baseUrl + "/api/v1/notifications/" + notificationId + "/read")
|
||||||
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
|
.post(RequestBody.create("", JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
return response.isSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<Boolean> markAsReadAsync(String notificationId) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
return markAsRead(notificationId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean markAllAsRead() throws IOException {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(baseUrl + "/api/v1/notifications/read-all")
|
||||||
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
|
.post(RequestBody.create("", JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
return response.isSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<Boolean> markAllAsReadAsync() {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
return markAllAsRead();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isOnline() {
|
||||||
|
try {
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(baseUrl + "/health")
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
return response.isSuccessful();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
client.dispatcher().executorService().shutdown();
|
||||||
|
client.connectionPool().evictAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SendResponse {
|
||||||
|
String notificationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ErrorResponse {
|
||||||
|
String error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CountResponse {
|
||||||
|
int count;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue