Skip to content

Commit

Permalink
feat: Asynchronous DTMF & Answer endpoint_type (#546)
Browse files Browse the repository at this point in the history
* feat: Add mode to InputAction

* feat: DTMF listener endpoints

* feat: Add endpoint_type to AnswerWebhook

* Updated 8.12.0 release date

* Bump Mockito version

* fix: DTMF listener eventUrl case

* Update changelog
  • Loading branch information
SMadani authored Oct 21, 2024
1 parent 41c6512 commit c21b5a2
Show file tree
Hide file tree
Showing 24 changed files with 509 additions and 98 deletions.
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
33 changes: 33 additions & 0 deletions src/main/java/com/vonage/client/voice/AddDtmfListenerRequest.java
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
4 changes: 1 addition & 3 deletions src/main/java/com/vonage/client/voice/WebSocketEndpoint.java
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

0 comments on commit c21b5a2

Please sign in to comment.