Skip to content

Commit 93684c2

Browse files
authored
Merge pull request #7 from PortalTechnologiesInc/feat/rest-polling-webhooks
feat: replace WebSocket with REST + polling + webhooks (0.4.0)
2 parents e2f1849 + f1b019a commit 93684c2

77 files changed

Lines changed: 1087 additions & 1470 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: 74 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,105 @@
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 17 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` |
55-
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.
7+
```kotlin
8+
// settings.gradle.kts
9+
dependencyResolutionManagement {
10+
repositories { maven { url = uri("https://jitpack.io") } }
11+
}
5912

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:
68-
69-
```java
70-
var portalSDK = new PortalSDK(wsEndpoint);
13+
// build.gradle.kts
14+
dependencies {
15+
implementation("com.github.PortalTechnologiesInc:java-sdk:0.4.0")
16+
}
7117
```
7218

73-
### Connecting to the server
19+
## Setup
7420

75-
Establish the WebSocket connection, then authenticate with your token:
21+
Choose how you want to receive async results:
7622

7723
```java
78-
portalSDK.connect();
79-
portalSDK.authenticate(authToken);
24+
// Manual polling — you call pollUntilComplete() yourself, no background threads
25+
PortalClient client = new PortalClient(
26+
PortalClientConfig.create("http://localhost:3000", "token")
27+
);
28+
29+
// Auto-polling — background scheduler, just use done()
30+
PortalClient client = new PortalClient(
31+
PortalClientConfig.create("http://localhost:3000", "token")
32+
.autoPolling(500) // poll every 500ms
33+
);
34+
35+
// Webhooks — portal-rest POSTs to your server, just use done()
36+
PortalClient client = new PortalClient(
37+
PortalClientConfig.create("http://localhost:3000", "token")
38+
.webhookSecret("my-secret")
39+
);
8040
```
8141

42+
## Async operations
8243

83-
### Sending a command
44+
All async methods return `AsyncOperation<T>` with `streamId` (available immediately)
45+
and `done` (`CompletableFuture<T>` that resolves when the operation completes).
8446

85-
You can send a command to the server by calling the `sendCommand` method.
47+
### Manual polling
8648

8749
```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-
});
50+
AsyncOperation<InvoiceStatus> op = client.requestSinglePayment(
51+
mainKey, List.of(),
52+
new SinglePaymentRequestContent("Coffee", 1000, Currency.MILLISATS, null, null, null)
53+
);
54+
55+
// blocks until paid/rejected/timeout
56+
InvoiceStatus result = client.pollUntilComplete(op, PollOptions.defaults().timeoutMs(60_000));
57+
System.out.println(result.status); // "paid", "timeout", "user_rejected", ...
9558
```
9659

97-
### Basic example
60+
### Auto-polling
9861

9962
```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-
});
63+
// client configured with .autoPolling(500)
64+
AsyncOperation<InvoiceStatus> op = client.requestSinglePayment(...);
65+
op.done().thenAccept(result -> System.out.println(result.status));
10766
```
108-
---
109-
110-
## Available Commands
11167

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.
68+
### Webhooks
11369

114-
Some key available commands include:
115-
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.
120-
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.
124-
125-
---
70+
```java
71+
// client configured with .webhookSecret("my-secret")
72+
AsyncOperation<InvoiceStatus> op = client.requestSinglePayment(...);
73+
op.done().thenAccept(result -> System.out.println(result.status));
12674

127-
## Example Integrations
75+
// in your HTTP server's POST /webhook handler:
76+
client.deliverWebhookPayload(rawBody, request.getHeader("X-Portal-Signature"));
77+
```
12878

129-
- See [portal-demo](https://github.com/PortalTechnologiesInc/portal-demo) for a Kotlin example.
79+
## Async methods
13080

131-
---
81+
| Method | Resolves to |
82+
|--------|-------------|
83+
| `requestSinglePayment(mainKey, subkeys, content)` | `AsyncOperation<InvoiceStatus>` |
84+
| `requestPaymentRaw(mainKey, subkeys, content)` | `AsyncOperation<InvoiceStatus>` |
85+
| `requestRecurringPayment(mainKey, subkeys, content)` | `AsyncOperation<RecurringPaymentResponseContent>` |
86+
| `requestInvoice(recipientKey, subkeys, params)` | `AsyncOperation<InvoicePaymentResponse>` |
87+
| `requestCashu(recipientKey, subkeys, mintUrl, unit, amount)` | `AsyncOperation<CashuResponseStatus>` |
88+
| `authenticateKey(mainKey, subkeys)` | `AsyncOperation<AuthResponseData>` |
89+
| `newKeyHandshakeUrl(staticToken, noRequest)` | `AsyncOperation<KeyHandshakeResult>` |
13290

133-
## Main API
91+
## Sync methods
13492

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
93+
`health()`, `version()`, `info()`, `fetchProfile()`, `payInvoice()`,
94+
`closeRecurringPayment()`, `issueJwt()`, `verifyJwt()`, `addRelay()`, `removeRelay()`,
95+
`mintCashu()`, `burnCashu()`, `sendCashuDirect()`, `calculateNextOccurrence()`,
96+
`fetchNip05Profile()`, `getWalletInfo()`
13997

140-
---
98+
## Versioning
14199

142-
## Support
100+
Java SDK `major.minor` must match the portal-rest (sdk-daemon) version.
143101

144-
For questions or issues, see the official documentation or open an issue on the project's GitHub repository.
102+
| Java SDK | sdk-daemon |
103+
|----------|------------|
104+
| 0.4.x | 0.4.x |
105+
| 0.3.x | 0.3.x |

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)