Skip to content

Commit

Permalink
Merge pull request #43 from sinch/DEVEXP-215-voice-webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
JPPortier authored Jan 31, 2024
2 parents 05a5406 + c43cb9b commit c5bddc7
Show file tree
Hide file tree
Showing 95 changed files with 4,575 additions and 1,515 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sinch.sdk.domains.common.adapters.converters;

import java.time.Instant;
import java.time.OffsetDateTime;

public class OffsetDateTimeDtoConverter {

public static Instant convert(OffsetDateTime dto) {
return null != dto ? dto.toInstant() : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ boolean validateAuthenticatedRequest(
VerificationEvent unserializeVerificationEvent(String jsonPayload) throws ApiMappingException;

/**
* This function can be called to serialize a verification response to be send as JSON
* This function can be called to serialize a verification response to be sent as JSON
*
* @param response The response to be serialized
* @return The JSON string to be sent
Expand Down
8 changes: 8 additions & 0 deletions client/src/main/com/sinch/sdk/domains/voice/VoiceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,12 @@ public interface VoiceService {
* @since 1.0
*/
ApplicationsService applications();

/**
* Webhooks helpers instance
*
* @return instance service related to webhooks helpers
* @since 1.0
*/
WebHooksService webhooks();
}
59 changes: 59 additions & 0 deletions client/src/main/com/sinch/sdk/domains/voice/WebHooksService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.sinch.sdk.domains.voice;

import com.sinch.sdk.core.exceptions.ApiMappingException;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;
import com.sinch.sdk.domains.voice.models.webhooks.WebhooksEvent;
import java.util.Map;

/**
* Webhooks service
*
* @see <a
* href="https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks">https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/</a>
* @since 1.0
*/
public interface WebHooksService {

/**
* The Sinch Platform can initiate callback requests to a URL you define (Callback URL) on request
* and result events. All callback requests are signed using your Application key and secret pair
* found on your dashboard. The signature is included in the Authorization header of the request
*
* <p>By using following function, you can ensure authentication according to received payload
* from your backend
*
* @param method The HTTP method used ot handle the callback
* @param path The path to you backend endpoint used for callback
* @param headers Received headers
* @param jsonPayload Received payload
* @return Is authentication is validated (true) or not (false)
* <p>see <a
* href="https://developers.sinch.com/docs/voice/api-reference/authentication/callback-signed-request">https://developers.sinch.com/docs/voice/api-reference/authentication/callback-signed-request/</a>
* @since 1.0
*/
boolean validateAuthenticatedRequest(
String method, String path, Map<String, String> headers, String jsonPayload);

/**
* This function can be called to deserialize received payload onto callback onto proper java
* Voice event class
*
* @param jsonPayload Received payload to be deserialized
* @return The Voice event instance class
* <p>see <a
* href="https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/">https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/</a>
* @since 1.0
*/
WebhooksEvent unserializeWebhooksEvent(String jsonPayload) throws ApiMappingException;

/**
* This function can be called to serialize a Voice response to be sent as JSON
*
* @param response The response to be serialized
* @return The JSON string to be sent
* <p>see <a
* href="https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/">https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/</a>
* @since 1.0
*/
String serializeWebhooksResponse(SVAMLControl response) throws ApiMappingException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.domains.voice.ApplicationsService;
import com.sinch.sdk.domains.voice.CalloutsService;
import com.sinch.sdk.domains.voice.WebHooksService;
import com.sinch.sdk.models.Configuration;
import java.util.Map;
import java.util.Objects;
Expand All @@ -26,6 +27,7 @@ public class VoiceService implements com.sinch.sdk.domains.voice.VoiceService {
private ConferencesService conferences;
private CallsService calls;
private ApplicationsService applications;
private WebHooksService webhooks;

private Map<String, AuthManager> clientAuthManagers;
private Map<String, AuthManager> webhooksAuthManagers;
Expand Down Expand Up @@ -99,6 +101,15 @@ public ApplicationsService applications() {
return this.applications;
}

public WebHooksService webhooks() {
checkCredentials();
if (null == this.webhooks) {
this.webhooks =
new com.sinch.sdk.domains.voice.adapters.WebHooksService(webhooksAuthManagers);
}
return this.webhooks;
}

private void checkCredentials() throws ApiAuthException {
if (null == clientAuthManagers || clientAuthManagers.isEmpty()) {
throw new ApiAuthException(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.sinch.sdk.domains.voice.adapters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.sinch.sdk.core.exceptions.ApiMappingException;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.utils.databind.Mapper;
import com.sinch.sdk.domains.voice.adapters.converters.CallsDtoConverter;
import com.sinch.sdk.domains.voice.adapters.converters.WebhooksEventDtoConverter;
import com.sinch.sdk.domains.voice.models.dto.v1.SVAMLRequestBodyDto;
import com.sinch.sdk.domains.voice.models.dto.v1.WebhooksEventDto;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;
import com.sinch.sdk.domains.voice.models.webhooks.WebhooksEvent;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

public class WebHooksService implements com.sinch.sdk.domains.voice.WebHooksService {
private static final Logger LOGGER = Logger.getLogger(WebHooksService.class.getName());

private final Map<String, AuthManager> authManagers;

public WebHooksService(Map<String, AuthManager> authManagers) {
this.authManagers = authManagers;
}

public boolean validateAuthenticatedRequest(
String method, String path, Map<String, String> headers, String jsonPayload) {

// convert header keys to use case-insensitive map keys
Map<String, String> caseInsensitiveHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
caseInsensitiveHeaders.putAll(headers);

String authorizationHeader = caseInsensitiveHeaders.get("Authorization");

// no authorization required
if (null == authorizationHeader) {
return true;
}

String[] split = authorizationHeader.split(" ");
String authorizationKeyword = split.length > 0 ? split[0] : "";

AuthManager authManager = authManagers.get(authorizationKeyword);
if (null == authManager) {
// unknown auth manager
LOGGER.severe(
String.format("Auth manager for authorization '%s' not found", authorizationKeyword));
return false;
}
return authManager.validateAuthenticatedRequest(method, path, headers, jsonPayload);
}

@Override
public WebhooksEvent unserializeWebhooksEvent(String jsonPayload) throws ApiMappingException {
try {
WebhooksEventDto o = Mapper.getInstance().readValue(jsonPayload, WebhooksEventDto.class);
return WebhooksEventDtoConverter.convert(o);
} catch (JsonProcessingException e) {
throw new ApiMappingException(jsonPayload, e);
}
}

@Override
public String serializeWebhooksResponse(SVAMLControl response) throws ApiMappingException {
SVAMLRequestBodyDto dto = CallsDtoConverter.convert(response);
try {
return Mapper.getInstance().writeValueAsString(dto);
} catch (JsonProcessingException e) {
throw new ApiMappingException(response.toString(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.sinch.sdk.domains.voice.models.dto.v1.ConferenceCalloutRequestConferenceDtmfOptionsDto;
import com.sinch.sdk.domains.voice.models.dto.v1.ConferenceCalloutRequestDto;
import com.sinch.sdk.domains.voice.models.dto.v1.CustomCalloutRequestDto;
import com.sinch.sdk.domains.voice.models.dto.v1.DomainDto;
import com.sinch.sdk.domains.voice.models.dto.v1.GetCalloutResponseObjDto;
import com.sinch.sdk.domains.voice.models.dto.v1.TtsCalloutRequestDto;
import com.sinch.sdk.domains.voice.models.requests.CalloutRequestParameters;
Expand Down Expand Up @@ -76,7 +77,9 @@ private static CalloutRequestDto convert(CalloutRequestParametersTTS client) {
client.getEnableDice().ifPresent(dto::setEnableDice);
client.getEnablePie().ifPresent(dto::setEnablePie);
client.getLocale().ifPresent(dto::setLocale);
client.getDomain().ifPresent(f -> dto.setDomain(EnumDynamicConverter.convert(f)));
client
.getDomain()
.ifPresent(f -> dto.setDomain(DomainDto.fromValue(EnumDynamicConverter.convert(f))));
client.getText().ifPresent(dto::setText);
client.getPrompts().ifPresent(dto::setPrompts);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.sinch.sdk.domains.voice.adapters.converters;

import com.sinch.sdk.domains.voice.models.CallReasonType;
import com.sinch.sdk.domains.voice.models.CallResultType;
import com.sinch.sdk.domains.voice.models.DomainType;
import com.sinch.sdk.domains.voice.models.dto.v1.GetCallResponseObjDto;
import com.sinch.sdk.domains.voice.models.dto.v1.SVAMLRequestBodyDto;
import com.sinch.sdk.domains.voice.models.response.CallInformation;
import com.sinch.sdk.domains.voice.models.response.CallReasonType;
import com.sinch.sdk.domains.voice.models.response.CallResultType;
import com.sinch.sdk.domains.voice.models.response.CallStatusType;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;

Expand All @@ -22,7 +22,7 @@ public static CallInformation convert(GetCallResponseObjDto dto) {
.setCallId(dto.getCallId())
.setDuration(dto.getDuration())
.setStatus(null != dto.getStatus() ? CallStatusType.from(dto.getStatus()) : null)
.setResult(null != dto.getResult() ? CallResultType.from(dto.getResult()) : null)
.setResult(null != dto.getResult() ? CallResultType.from(dto.getResult().getValue()) : null)
.setReason(null != dto.getReason() ? CallReasonType.from(dto.getReason()) : null)
.setTimeStamp(null != dto.getTimestamp() ? dto.getTimestamp().toInstant() : null)
.setCustom(null != dto.getCustom() ? dto.getCustom() : null)
Expand All @@ -39,8 +39,8 @@ public static SVAMLRequestBodyDto convert(SVAMLControl client) {

client
.getInstructions()
.ifPresent(f -> dto.instructions(SAVMLInstructionDtoConverter.convert(f)));
client.getAction().ifPresent(f -> dto.action(SAVMLActionDtoConverter.convert(f)));
.ifPresent(f -> dto.instructions(SVAMLInstructionDtoConverter.convert(f)));
client.getAction().ifPresent(f -> dto.action(SVAMLActionDtoConverter.convert(f)));
return dto;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

public class ControlDtoConverter {

private static final Logger LOGGER = Logger.getLogger(SAVMLActionDtoConverter.class.getName());
private static final Logger LOGGER = Logger.getLogger(SVAMLActionDtoConverter.class.getName());

public static String convert(Control client) {
if (null == client) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.sinch.sdk.domains.voice.models.Destination;
import com.sinch.sdk.domains.voice.models.DestinationNumber;
import com.sinch.sdk.domains.voice.models.DestinationNumberType;
import com.sinch.sdk.domains.voice.models.DestinationSip;
import com.sinch.sdk.domains.voice.models.DestinationUser;
import com.sinch.sdk.domains.voice.models.dto.v1.DestinationDto;
Expand All @@ -11,25 +12,42 @@
import java.util.logging.Logger;

public class DestinationDtoConverter {

private static final Logger LOGGER = Logger.getLogger(DestinationDtoConverter.class.getName());

public static DestinationDto convert(Destination client) {
DestinationDto dto = new DestinationDto();
if (null == client) {
return null;
}
DestinationTypeDto type = null;
String endpoint = null;
if (client instanceof DestinationNumber) {
DestinationNumber destination = (DestinationNumber) client;
dto.type(DestinationTypeDto.NUMBER).endpoint(destination.getPhoneNumber().stringValue());
DestinationNumberType clientType = destination.getType();
if (clientType == DestinationNumberType.DID) {
type = DestinationTypeDto.DID;
} else if (clientType == DestinationNumberType.PSTN) {
type = DestinationTypeDto.NUMBER;
} else {
LOGGER.severe(String.format("Unexpected type '%s'", destination.getType()));
return dto;
}
endpoint = destination.getPhoneNumber().stringValue();
} else if (client instanceof DestinationUser) {
DestinationUser destination = (DestinationUser) client;
dto.type(DestinationTypeDto.USERNAME).endpoint(destination.getUserName());
type = DestinationTypeDto.USERNAME;
endpoint = destination.getUserName();
} else if (client instanceof DestinationSip) {
DestinationSip destination = (DestinationSip) client;
dto.type(DestinationTypeDto.SIP).endpoint(destination.getSIPAddress());
type = DestinationTypeDto.SIP;
endpoint = destination.getSIPAddress();
} else {
LOGGER.severe(String.format("Unexpected class '%s'", client.getClass()));
}
if (null != type && endpoint != null) {
dto.type(type).endpoint(endpoint);
}
return dto;
}

Expand All @@ -38,10 +56,20 @@ public static Destination convert(DestinationDto dto) {
return null;
}
Destination destination = null;
if (Objects.equals(dto.getType(), DestinationTypeDto.NUMBER2)) {
destination = new DestinationNumber(E164PhoneNumber.valueOf(dto.getEndpoint()));
} else if (Objects.equals(dto.getType(), DestinationTypeDto.USERNAME2)) {
if (Objects.equals(dto.getType(), DestinationTypeDto.NUMBER)
|| Objects.equals(dto.getType(), DestinationTypeDto.NUMBER2)) {
destination =
new DestinationNumber(
E164PhoneNumber.valueOf(dto.getEndpoint()), DestinationNumberType.PSTN);
} else if (Objects.equals(dto.getType(), DestinationTypeDto.USERNAME)
|| Objects.equals(dto.getType(), DestinationTypeDto.USERNAME2)) {
destination = new DestinationUser(dto.getEndpoint());
} else if (Objects.equals(dto.getType(), DestinationTypeDto.SIP)) {
destination = new DestinationSip(dto.getEndpoint());
} else if (Objects.equals(dto.getType(), DestinationTypeDto.DID)) {
destination =
new DestinationNumber(
E164PhoneNumber.valueOf(dto.getEndpoint()), DestinationNumberType.DID);
} else {
LOGGER.severe(String.format("Unexpected type value '%s'", dto.getType()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.sinch.sdk.domains.voice.adapters.converters;

import com.sinch.sdk.domains.voice.models.DomainType;
import com.sinch.sdk.domains.voice.models.dto.v1.DomainDto;

public class DomainTypeDtoConverter {

public static DomainType convert(String dto) {
if (null == dto) {
return null;
}
return DomainType.from(dto.toLowerCase());
}

public static DomainType convert(DomainDto dto) {
if (null == dto) {
return null;
}
return DomainType.from(dto.getValue().toLowerCase());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;

public class SAVMLActionDtoConverter {
private static final Logger LOGGER = Logger.getLogger(SAVMLActionDtoConverter.class.getName());
public class SVAMLActionDtoConverter {
private static final Logger LOGGER = Logger.getLogger(SVAMLActionDtoConverter.class.getName());

public static SvamlActionDto convert(Action client) {
if (null == client) {
Expand Down Expand Up @@ -218,7 +218,7 @@ private static List<MenuDto> convertMenuCollection(Collection<Menu> client) {
if (null == client) {
return null;
}
return client.stream().map(SAVMLActionDtoConverter::convert).collect(Collectors.toList());
return client.stream().map(SVAMLActionDtoConverter::convert).collect(Collectors.toList());
}

private static MenuDto convert(Menu client) {
Expand Down
Loading

0 comments on commit c5bddc7

Please sign in to comment.