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 - Event notifications and alerts
|
||||
# IronNotify SDK for Java
|
||||
|
||||
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