Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Asynchronous DTMF & Answer endpoint_type #546

Merged
merged 7 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

# [8.12.0] - 2024-10-??
# [8.12.0] - 2024-10-21
- Added `network_apis` capability to Application API
- Added `mode` property to `InputAction` NCCO
- Refactored `InputAction.Builder` and added constructor validation
- Added `endpoint_type` to `AnswerWebhook`
- Added `@JsonCreator` annotation to webhook classes' `fromJson(String)` method
- Added `app_id` to `com.vonage.client.numbers.OwnedNumber`
- Fixed Viber Video message TTL field being set incorrectly
- Added end-to-end encryption support for Video sessions
- Added `leg_persistence_time` to Application Voice capability
- Added `signed_callbacks` to Application RTC capability
- Added `name` field to `com.vonage.client.video.RenderResponse`

# [8.11.0] - 2024-09-25
- Added custom user agent property setting to `HttpConfig`
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.14.1</version>
<version>5.14.2</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2024 Vonage
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vonage.client.voice;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.vonage.client.JsonableBaseObject;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;

class AddDtmfListenerRequest extends JsonableBaseObject {
@JsonIgnore final String uuid;
@JsonProperty("event_url") final Collection<URI> eventUrl;

public AddDtmfListenerRequest(String uuid, URI eventUrl) {
this.uuid = uuid;
this.eventUrl = Collections.singletonList(eventUrl);
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/vonage/client/voice/AnswerWebhook.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* @since 8.2.0
*/
public class AnswerWebhook extends JsonableBaseObject {
@JsonProperty("endpoint_type") private EndpointType endpointType;
@JsonProperty("from") private String from;
@JsonProperty("from_user") private String fromUser;
@JsonProperty("to") private String to;
Expand All @@ -42,6 +43,16 @@ public class AnswerWebhook extends JsonableBaseObject {

protected AnswerWebhook() {}

/**
* The type of endpoint that answered the call.
*
* @return The endpoint type as an enum, or {@code null} if unknown.
* @since 8.12.0
*/
public EndpointType getEndpointType() {
return endpointType;
}

/**
* The user or number that answered the call. This is the virtual number linked to in your application.
*
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/com/vonage/client/voice/AppEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
* @since 7.3.0
*/
public class AppEndpoint extends JsonableBaseObject implements Endpoint {
private static final String TYPE = "app";
private String user;

protected AppEndpoint() {
Expand All @@ -49,9 +48,8 @@ public String toLog() {
return user;
}

@JsonProperty("type")
@Override
public String getType() {
return TYPE;
return EndpointType.APP.toString();
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/vonage/client/voice/Endpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.vonage.client.voice;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

Expand All @@ -37,6 +39,7 @@ public interface Endpoint {
*
* @return The type of endpoint as a string.
*/
@JsonProperty("type")
String getType();

/**
Expand All @@ -45,6 +48,7 @@ public interface Endpoint {
* @return String representation of the object.
* @deprecated This method will be removed in the next major release.
*/
@JsonIgnore
@Deprecated
String toLog();
}
44 changes: 44 additions & 0 deletions src/main/java/com/vonage/client/voice/EndpointType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2024 Vonage
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vonage.client.voice;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

/**
* Represents the various types of call endpoints supported by the Voice API.
*
* @since 8.12.0
*/
public enum EndpointType {
PHONE,
SIP,
WEBSOCKET,
APP,
VBC;

@JsonValue
@Override
public String toString() {
return name().toLowerCase();
}

@JsonCreator
public static EndpointType fromString(String name) {
if (name == null) return null;
return EndpointType.valueOf(name.toUpperCase());
}
}
4 changes: 1 addition & 3 deletions src/main/java/com/vonage/client/voice/PhoneEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.vonage.client.JsonableBaseObject;

public class PhoneEndpoint extends JsonableBaseObject implements Endpoint {
private static final String TYPE = "phone";
private String number, dtmfAnswer;

PhoneEndpoint() {}
Expand All @@ -46,10 +45,9 @@ public PhoneEndpoint(String number, String dtmfAnswer) {
this.dtmfAnswer = dtmfAnswer;
}

@JsonProperty("type")
@Override
public String getType() {
return TYPE;
return EndpointType.PHONE.toString();
}

@Override
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/com/vonage/client/voice/SipEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
* Endpoint for connecting to a SIP URI.
*/
public class SipEndpoint extends JsonableBaseObject implements Endpoint {
private static final String TYPE = "sip";
private String uri;
private Map<String, ?> headers;
private Map<SipHeader, String> standardHeaders;
Expand Down Expand Up @@ -92,10 +91,9 @@ public String getUri() {
return uri;
}

@JsonProperty("type")
@Override
public String getType() {
return TYPE;
return EndpointType.SIP.toString();
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/com/vonage/client/voice/VbcEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
* @since 7.3.0
*/
public class VbcEndpoint extends JsonableBaseObject implements Endpoint {
private static final String TYPE = "vbc";
private String extension;

protected VbcEndpoint() {
Expand Down Expand Up @@ -52,6 +51,6 @@ public String toLog() {
@JsonProperty("type")
@Override
public String getType() {
return TYPE;
return EndpointType.VBC.toString();
}
}
56 changes: 47 additions & 9 deletions src/main/java/com/vonage/client/voice/VoiceClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.vonage.client.*;
import com.vonage.client.auth.JWTAuthMethod;
import com.vonage.client.common.HttpMethod;
import com.vonage.client.voice.ncco.InputMode;
import com.vonage.client.voice.ncco.Ncco;
import com.vonage.jwt.Jwt;
import java.io.IOException;
Expand All @@ -41,6 +42,8 @@ public class VoiceClient {
final RestEndpoint<TalkPayload, TalkResponse> startTalk;
final RestEndpoint<String, TalkResponse> stopTalk;
final RestEndpoint<DtmfPayload, DtmfResponse> sendDtmf;
final RestEndpoint<AddDtmfListenerRequest, Void> addDtmfListener;
final RestEndpoint<String, Void> removeDtmfListener;
final RestEndpoint<String, byte[]> downloadRecording;

/**
Expand All @@ -58,15 +61,10 @@ class Endpoint<T, R> extends DynamicEndpoint<T, R> {
.requestMethod(method).wrapper(wrapper).pathGetter((de, req) -> {
String base = de.getHttpWrapper().getHttpConfig().getVersionedApiBaseUri("v1");
String path = pathGetter.apply(req);
if (path.isEmpty()) {
return base + "/calls";
}
else if (path.startsWith("http") && method == HttpMethod.GET) {
if (path.startsWith("http") && method == HttpMethod.GET) {
return path;
}
else {
return base + "/calls/" + pathGetter.apply(req);
}
return base + "/calls" + (path.isEmpty() ? "" : "/" + path);
})
);
}
Expand All @@ -81,6 +79,8 @@ else if (path.startsWith("http") && method == HttpMethod.GET) {
startTalk = new Endpoint<>(req -> req.uuid + "/talk", HttpMethod.PUT);
stopTalk = new Endpoint<>(uuid -> uuid + "/talk", HttpMethod.DELETE);
sendDtmf = new Endpoint<>(req -> req.uuid + "/dtmf", HttpMethod.PUT);
addDtmfListener = new Endpoint<>(req -> req.uuid + "/input/dtmf", HttpMethod.PUT);
removeDtmfListener = new Endpoint<>(uuid -> uuid + "/input/dtmf", HttpMethod.DELETE);
downloadRecording = new Endpoint<>(Function.identity(), HttpMethod.GET);
}

Expand Down Expand Up @@ -425,8 +425,8 @@ public TalkResponse startTalk(String uuid, String text, int loop) throws VonageR
/**
* Send a synthesized speech message to an ongoing call.
*
* @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}. This value
* can be obtained with {@link CallEvent#getUuid()}.
* @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
* This value can be obtained with {@link CallEvent#getUuid()}.
* @param text The message to be spoken to the call participants.
* @param language The language to use for the text-to-speech.
* @param style The language style to use for the text-to-speech.
Expand Down Expand Up @@ -481,6 +481,44 @@ public TalkResponse stopTalk(String uuid) throws VonageResponseParseException, V
return stopTalk.execute(validateUuid(uuid));
}

/**
* Register a listener for asynchronous DTMF events sent by a caller to an
* {@linkplain com.vonage.client.voice.ncco.InputAction} NCCO action, when the
* {@linkplain com.vonage.client.voice.ncco.InputAction.Builder#mode(InputMode)} is
* {@link com.vonage.client.voice.ncco.InputMode#ASYNCHRONOUS}.
*
* @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
* This value can be obtained with {@link CallEvent#getUuid()}.
*
* @param eventUrl The URL to send asynchronous DTMF user input events to.
*
* @throws VoiceResponseException If the call does not exist or the listener could not be added,
* for example if the call's state or input mode are incompatible.
*
* @since 8.12.0
*/
public void addDtmfListener(String uuid, String eventUrl) throws VoiceResponseException {
addDtmfListener.execute(new AddDtmfListenerRequest(validateUuid(uuid), URI.create(validateUrl(eventUrl))));
}

/**
* Stop receiving updates for asynchronous DTMF events sent by a caller to an
* {@linkplain com.vonage.client.voice.ncco.InputAction} NCCO, when the
* {@linkplain com.vonage.client.voice.ncco.InputAction.Builder#mode(InputMode)} is
* {@link com.vonage.client.voice.ncco.InputMode#ASYNCHRONOUS}. Calling this method
* stops sending DTMF events to the event URL set in {@link #addDtmfListener(String, String)}.
*
* @param uuid The UUID of the call, obtained from the object returned by {@link #createCall(Call)}.
* This value can be obtained with {@link CallEvent#getUuid()}.
*
* @throws VoiceResponseException If the call does not exist or have a listener attached.
*
* @since 8.12.0
*/
public void removeDtmfListener(String uuid) throws VoiceResponseException {
removeDtmfListener.execute(validateUuid(uuid));
}

/**
* Download a recording.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.util.Map;

public class WebSocketEndpoint extends JsonableBaseObject implements Endpoint {
private static final String TYPE = "websocket";
private String uri, contentType;
@JsonProperty("headers") private Map<String, Object> headers;

Expand All @@ -33,10 +32,9 @@ public WebSocketEndpoint(String uri, String contentType, Map<String, Object> hea
this.headers = headers;
}

@JsonProperty("type")
@Override
public String getType() {
return TYPE;
return EndpointType.WEBSOCKET.toString();
}

@Override
Expand Down
6 changes: 2 additions & 4 deletions src/main/java/com/vonage/client/voice/ncco/AppEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.fasterxml.jackson.annotation.JsonProperty;
import com.vonage.client.JsonableBaseObject;
import com.vonage.client.voice.EndpointType;

/**
* Represents an app endpoint used in a {@link ConnectAction}. See
Expand All @@ -26,18 +27,15 @@
* @since 5.4.0
*/
public class AppEndpoint extends JsonableBaseObject implements Endpoint {
private static final String TYPE = "app";

private final String user;

private AppEndpoint(Builder builder) {
this.user = builder.user;
}

@JsonProperty("type")
@Override
public String getType() {
return TYPE;
return EndpointType.APP.toString();
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/vonage/client/voice/ncco/Endpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
*/
package com.vonage.client.voice.ncco;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* An endpoint for a {@link ConnectAction} to connect to.
*/
public interface Endpoint {

@JsonProperty("type")
String getType();
}
Loading
Loading