Skip to content

Commit af545f3

Browse files
feat: replace WebSocket with REST + polling + webhooks (0.4.0)
Replaces the WebSocket-based PortalSDK with a REST HTTP client that mirrors the TypeScript SDK's async operation pattern. ## New architecture - PortalClient: HTTP client using java.net.http.HttpClient (Java 17) - AsyncOperation<T>: record { streamId, CompletableFuture<T> done } - PollOptions: builder for polling interval/timeout/per-event callback - StreamEvent: base event with type, index, timestamp, isTerminal() - WebhookPayload: extends StreamEvent with streamId field ## Async operations Methods that trigger a portal async operation return AsyncOperation<T> immediately. Events resolve the done future via: - Polling: client.pollUntilComplete(streamId, PollOptions.defaults()) - Webhooks: client.deliverWebhookPayload(rawBody, xPortalSignature) (HMAC-SHA256 verified, routed to matching CompletableFuture) ## New model classes AuthResponseData, AuthResponseStatus, KeyHandshakeResult, RecurringPaymentResponseContent, RecurringPaymentStatus, InvoicePaymentResponse, PayInvoiceResponse, EventsResponse, VersionResponse, InfoResponse, WalletInfoResponse, RequestInvoiceParams ## Removed - PortalSDK.java (WebSocket-based) - PortalWsClient.java - Response.java / ResponseDeserializer.java - command/ package (all WS request/response/notification classes) Aligns with lib PR#171 (refactor/portal-rest: REST + polling + webhooks)
1 parent dd18f1d commit af545f3

75 files changed

Lines changed: 1036 additions & 1469 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.MD

Lines changed: 100 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,132 @@
1-
# Portal Java SDK - Documentation
1+
# Portal Java SDK
22

3-
## Introduction
4-
5-
A Java client for the Portal WebSocket Server, providing Nostr-based authentication and Lightning Network payment processing capabilities.
6-
7-
---
3+
Java SDK for the [Portal REST API](https://github.com/PortalTechnologiesInc/lib).
84

95
## Installation
106

11-
1. Add the Jitpack repository to your `build.gradle`:
12-
```groovy
13-
repositories {
14-
maven { url 'https://jitpack.io' }
15-
}
16-
```
17-
18-
Or if you are using Maven, add the following to your `pom.xml`:
19-
```xml
20-
<repository>
21-
<id>jitpack.io</id>
22-
<url>https://jitpack.io</url>
23-
</repository>
24-
```
25-
26-
2. Add the dependency to your `build.gradle`:
27-
```groovy
28-
dependencies {
29-
implementation 'com.github.PortalTechnologiesInc:java-sdk:0.3.0'
30-
}
31-
```
32-
33-
Or if you are using Maven, add the following to your `pom.xml`:
34-
```xml
35-
<dependency>
36-
<groupId>com.github.PortalTechnologiesInc</groupId>
37-
<artifactId>java-sdk</artifactId>
38-
<version>0.3.0</version>
39-
</dependency>
40-
```
41-
42-
3. Once you're done, you may now proceed integrating the SDK into your project.
43-
44-
---
45-
46-
## Versioning & Compatibility
47-
48-
The Java SDK version is kept in sync with the [Portal SDK Daemon](https://hub.docker.com/r/getportal/sdk-daemon) (`getportal/sdk-daemon`).
49-
50-
**Compatibility rule:** the `major.minor` version of the SDK must match the `major.minor` version of the SDK Daemon. The patch version (`x` in `0.3.x`) is independent and can differ — it only contains bug fixes.
51-
52-
| SDK version | SDK Daemon version |
53-
|-------------|-------------------|
54-
| `0.3.x` | `0.3.x` |
7+
Add via JitPack:
558

56-
**Example:** SDK `0.3.0` works with `getportal/sdk-daemon:0.3.1`, but not with `getportal/sdk-daemon:0.4.0`.
57-
58-
When upgrading to a new `major.minor`, update both the SDK dependency and the Docker image tag together.
59-
60-
61-
---
62-
63-
## Basic Usage
64-
65-
### Initialization
66-
67-
Create an instance of `PortalSDK` by passing the websocket endpoint of your portal server:
9+
```kotlin
10+
// settings.gradle.kts
11+
dependencyResolutionManagement {
12+
repositories {
13+
maven { url = uri("https://jitpack.io") }
14+
}
15+
}
6816

69-
```java
70-
var portalSDK = new PortalSDK(wsEndpoint);
17+
// build.gradle.kts
18+
dependencies {
19+
implementation("com.github.PortalTechnologiesInc:java-sdk:0.4.0")
20+
}
7121
```
7222

73-
### Connecting to the server
74-
75-
Establish the WebSocket connection, then authenticate with your token:
23+
## Quick Start
7624

7725
```java
78-
portalSDK.connect();
79-
portalSDK.authenticate(authToken);
26+
PortalClient client = new PortalClient(
27+
"http://localhost:3000", // portal-rest base URL
28+
"your-auth-token",
29+
"your-webhook-secret" // null if you only use polling
30+
);
31+
32+
// Check connectivity
33+
String status = client.health(); // "OK"
8034
```
8135

36+
## Async Operations
8237

83-
### Sending a command
84-
85-
You can send a command to the server by calling the `sendCommand` method.
38+
All async methods return `AsyncOperation<T>` immediately. The `done` future resolves
39+
when a terminal event arrives — either via webhook or polling.
8640

8741
```java
88-
portalSDK.sendCommand(request, (response, err) -> {
89-
if(err != null) {
90-
logger.error("error sending command: {}", err);
91-
return;
92-
}
93-
logger.info("command sent successfully: {}", response);
94-
});
42+
// Request a single payment
43+
AsyncOperation<StreamEvent> op = client.requestSinglePayment(
44+
mainKey,
45+
List.of(), // subkeys
46+
new SinglePaymentRequestContent("Coffee", 1000, Currency.MILLISATS, null, null, null)
47+
);
48+
49+
System.out.println("Stream ID: " + op.streamId());
9550
```
9651

97-
### Basic example
52+
### Option A — Polling (no inbound HTTP required)
9853

9954
```java
100-
portalSDK.sendCommand(new CalculateNextOccurrenceRequest("weekly", System.currentTimeMillis() / 1000), (res, err) -> {
101-
if(err != null) {
102-
logger.error("error calculating next occurrence: {}", err);
103-
return;
104-
}
105-
logger.info("next occurrence: {}", res.next_occurrence());
106-
});
55+
StreamEvent result = client.pollUntilComplete(
56+
op.streamId(),
57+
PollOptions.defaults()
58+
.intervalMs(500)
59+
.timeoutMs(60_000)
60+
.onEvent(event -> System.out.println("Event: " + event.type))
61+
);
62+
System.out.println("Terminal event: " + result.type);
10763
```
108-
---
109-
110-
## Available Commands
111-
112-
Commands are implemented as specific request classes in [`src/main/java/cc/getportal/command/request/`](./src/main/java/cc/getportal/command/request/), and used via the `sendCommand()` method of the [`PortalSDK`](./src/main/java/cc/getportal/PortalSDK.java) class.
11364

114-
Some key available commands include:
65+
### Option B — Webhooks (recommended for production)
11566

116-
- [`AuthRequest`](./src/main/java/cc/getportal/command/request/AuthRequest.java): Authenticate using a token.
117-
- [`KeyHandshakeUrlRequest`](./src/main/java/cc/getportal/command/request/KeyHandshakeUrlRequest.java): Get handshake URL for key and relays.
118-
- [`RequestSinglePaymentRequest`](./src/main/java/cc/getportal/command/request/RequestSinglePaymentRequest.java): Request a single payment.
119-
- [`MintCashuRequest`](./src/main/java/cc/getportal/command/request/MintCashuRequest.java): Mint Cashu tokens.
67+
Mount the webhook endpoint in your HTTP server. The SDK verifies the
68+
`X-Portal-Signature` HMAC-SHA256 header automatically.
12069

121-
> See [`src/main/java/cc/getportal/command/request/`](./src/main/java/cc/getportal/command/request/) for all available commands and additional details.
122-
123-
To use a command, instantiate its request class and pass it to `PortalSDK.sendCommand(...)`. The full list of commands may evolve; check the request folder for the latest options.
70+
```java
71+
// In your HTTP server's POST /portal/webhook handler:
72+
byte[] rawBody = request.getBody();
73+
String signature = request.getHeader("X-Portal-Signature");
74+
client.deliverWebhookPayload(rawBody, signature);
12475

125-
---
76+
// The done future resolves when the terminal event is delivered
77+
StreamEvent result = op.done().get(60, TimeUnit.SECONDS);
78+
```
12679

127-
## Example Integrations
80+
## Other Operations
12881

129-
- See [portal-demo](https://github.com/PortalTechnologiesInc/portal-demo) for a Kotlin example.
82+
```java
83+
// Key handshake
84+
AsyncOperation<KeyHandshakeResult> handshake = client.newKeyHandshakeUrl(null, false);
85+
KeyHandshakeResult r = client.pollUntilComplete(handshake.streamId(), PollOptions.defaults())
86+
.data.get... // or use handshake.done() with webhooks
87+
88+
// Recurring payment
89+
AsyncOperation<RecurringPaymentResponseContent> sub = client.requestRecurringPayment(
90+
mainKey, List.of(), content
91+
);
92+
93+
// Invoice request
94+
AsyncOperation<InvoicePaymentResponse> invoice = client.requestInvoice(
95+
recipientKey, List.of(),
96+
new RequestInvoiceParams(1000, Currency.MILLISATS, "Test invoice")
97+
);
98+
99+
// Pay a BOLT11 invoice
100+
PayInvoiceResponse paid = client.payInvoice("lnbc...");
101+
102+
// JWT
103+
String token = client.issueJwt(targetKey, 24);
104+
String key = client.verifyJwt(pubkey, token);
105+
106+
// Profile
107+
Profile profile = client.fetchProfile(mainKey);
108+
109+
// Wallet info
110+
WalletInfoResponse info = client.getWalletInfo();
111+
```
130112

131-
---
113+
## Versioning & Compatibility
132114

133-
## Main API
115+
The Java SDK version `major.minor` must match the portal-rest (sdk-daemon) version.
116+
Patch versions are independent.
134117

135-
- `PortalSDK` - Main client class
136-
- `PortalRequest` - Represents a request to the server
137-
- `PortalResponse` - Represents a response from the server
138-
- `PortalNotification` - Represents a notification from the server
118+
| Java SDK | sdk-daemon |
119+
|----------|------------|
120+
| 0.4.x | 0.4.x |
121+
| 0.3.x | 0.3.x |
139122

140-
---
123+
## Architecture
141124

142-
## Support
125+
The new REST + polling/webhooks architecture replaces the previous WebSocket API:
143126

144-
For questions or issues, see the official documentation or open an issue on the project's GitHub repository.
127+
- **Sync operations** return their result directly.
128+
- **Async operations** return `AsyncOperation<T>` with `streamId` (available immediately)
129+
and `done` (`CompletableFuture<T>` that resolves on terminal event).
130+
- **Polling**`pollUntilComplete(streamId, options)` loops on `GET /events/:streamId`.
131+
- **Webhooks**`deliverWebhookPayload(rawBody, signature)` verifies HMAC-SHA256 and
132+
resolves the matching `done` future.

build.gradle.kts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group = "cc.getportal"
7-
version = "0.3.0"
7+
version = "0.4.0"
88

99
java {
1010
toolchain {
@@ -23,7 +23,7 @@ publishing {
2323

2424
groupId = "cc.getportal"
2525
artifactId = "portal-java-sdk"
26-
version = "0.3.0"
26+
version = "0.4.0"
2727
}
2828
}
2929
}
@@ -40,9 +40,6 @@ dependencies {
4040
implementation("org.slf4j:slf4j-api:2.0.17")
4141
runtimeOnly("org.slf4j:slf4j-simple:2.0.17")
4242

43-
// WebSocket Client
44-
implementation("org.java-websocket:Java-WebSocket:1.6.0")
45-
4643
// Json serialization
4744
implementation("com.google.code.gson:gson:2.13.2")
4845

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package cc.getportal;
2+
3+
import java.util.concurrent.CompletableFuture;
4+
5+
/**
6+
* Wraps an async operation: the stream ID is available immediately,
7+
* while the {@code done} future resolves when a terminal event arrives.
8+
*/
9+
public record AsyncOperation<T>(String streamId, CompletableFuture<T> done) {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package cc.getportal;
2+
3+
import org.jetbrains.annotations.Nullable;
4+
5+
import java.util.function.Consumer;
6+
7+
/**
8+
* Builder-style options for polling a stream until completion.
9+
*/
10+
public class PollOptions {
11+
public long intervalMs = 1000;
12+
public long timeoutMs = 0; // 0 = no timeout
13+
@Nullable public Consumer<StreamEvent> onEvent;
14+
15+
public static PollOptions defaults() {
16+
return new PollOptions();
17+
}
18+
19+
public PollOptions intervalMs(long ms) {
20+
this.intervalMs = ms;
21+
return this;
22+
}
23+
24+
public PollOptions timeoutMs(long ms) {
25+
this.timeoutMs = ms;
26+
return this;
27+
}
28+
29+
public PollOptions onEvent(Consumer<StreamEvent> cb) {
30+
this.onEvent = cb;
31+
return this;
32+
}
33+
}

0 commit comments

Comments
 (0)