Skip to content

Commit

Permalink
feat (DEVEXP-148): Voice Callouts
Browse files Browse the repository at this point in the history
  • Loading branch information
JPPortier committed Dec 19, 2023
1 parent 4a53d76 commit b7876ec
Show file tree
Hide file tree
Showing 44 changed files with 2,442 additions and 30 deletions.
10 changes: 10 additions & 0 deletions client/resources/config-default.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,13 @@ numbers-server=https://numbers.api.sinch.com
sms-region=us
sms-server=https://zt.%s.sms.api.sinch.com
verification-server=https://verification.api.sinch.com

voice-region=global

# see https://developers.sinch.com/docs/voice/api-reference/#endpoints
voice-server-global=https://calling.api.sinch.com
voice-server-europe=https://calling-euc1.api.sinch.com
voice-server-north-america=https://calling-use1.api.sinch.com
voice-server-south-america=https://calling-sae1.api.sinch.com
voice-server-south-east-asia1=https://calling-apse1.api.sinch.com
voice-server-south-east-asia2=https://calling-apse2.api.sinch.com
88 changes: 77 additions & 11 deletions client/src/main/com/sinch/sdk/SinchClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import com.sinch.sdk.domains.numbers.NumbersService;
import com.sinch.sdk.domains.sms.SMSService;
import com.sinch.sdk.domains.verification.VerificationService;
import com.sinch.sdk.domains.voice.VoiceService;
import com.sinch.sdk.http.HttpClientApache;
import com.sinch.sdk.models.Configuration;
import com.sinch.sdk.models.SMSRegion;
import com.sinch.sdk.models.VoiceRegion;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
Expand All @@ -27,6 +29,9 @@ public class SinchClient {
private static final String NUMBERS_SERVER_KEY = "numbers-server";
private static final String SMS_REGION_KEY = "sms-region";
private static final String SMS_SERVER_KEY = "sms-server";

private static final String VOICE_REGION_KEY = "voice-region";

private static final String VERIFICATION_SERVER_KEY = "verification-server";

private static final String PROJECT_NAME_KEY = "project.name";
Expand All @@ -45,6 +50,7 @@ public class SinchClient {
private NumbersService numbers;
private SMSService sms;
private VerificationService verification;
private VoiceService voice;

private HttpClientApache httpClient;

Expand All @@ -57,34 +63,72 @@ public class SinchClient {
public SinchClient(Configuration configuration) {

Configuration.Builder builder = Configuration.builder(configuration);

Properties props = handlePropertiesFile(DEFAULT_PROPERTIES_FILE_NAME);

if (null == configuration.getOAuthUrl() && props.containsKey(OAUTH_URL_KEY)) {
builder.setOAuthUrl(props.getProperty(OAUTH_URL_KEY));
}

handleDefaultNumbersSettings(configuration, props, builder);
handleDefaultSmsSettings(configuration, props, builder);
handleDefaultVerificationSettings(configuration, props, builder);
handleDefaultVoiceSettings(configuration, props, builder);

Configuration newConfiguration = builder.build();
checkConfiguration(newConfiguration);
this.configuration = newConfiguration;

versionProperties = handlePropertiesFile(VERSION_PROPERTIES_FILE_NAME);
LOGGER.fine(
String.format(
"%s (%s) started with projectId '%s'",
versionProperties.getProperty(PROJECT_NAME_KEY),
versionProperties.getProperty(PROJECT_VERSION_KEY),
configuration.getProjectId()));
}

private void handleDefaultNumbersSettings(
Configuration configuration, Properties props, Configuration.Builder builder) {

if (null == configuration.getNumbersUrl() && props.containsKey(NUMBERS_SERVER_KEY)) {
builder.setNumbersUrl(props.getProperty(NUMBERS_SERVER_KEY));
}
}

private void handleDefaultSmsSettings(
Configuration configuration, Properties props, Configuration.Builder builder) {

if (null == configuration.getSmsUrl() && props.containsKey(SMS_SERVER_KEY)) {
builder.setSmsUrl(props.getProperty(SMS_SERVER_KEY));
}
if (null == configuration.getSmsRegion() && props.containsKey(SMS_REGION_KEY)) {
builder.setSmsRegion(SMSRegion.from(props.getProperty(SMS_REGION_KEY)));
}
}

private void handleDefaultVerificationSettings(
Configuration configuration, Properties props, Configuration.Builder builder) {

if (null == configuration.getVerificationUrl() && props.containsKey(VERIFICATION_SERVER_KEY)) {
builder.setVerificationUrl(props.getProperty(VERIFICATION_SERVER_KEY));
}
Configuration newConfiguration = builder.build();
checkConfiguration(newConfiguration);
this.configuration = newConfiguration;
}

versionProperties = handlePropertiesFile(VERSION_PROPERTIES_FILE_NAME);
LOGGER.fine(
String.format(
"%s (%s) started with projectId '%s'",
versionProperties.getProperty(PROJECT_NAME_KEY),
versionProperties.getProperty(PROJECT_VERSION_KEY),
configuration.getProjectId()));
private void handleDefaultVoiceSettings(
Configuration configuration, Properties props, Configuration.Builder builder) {
if (null == configuration.getVoiceRegion() && props.containsKey(VOICE_REGION_KEY)) {
builder.setVoiceRegion(VoiceRegion.from(props.getProperty(VOICE_REGION_KEY)));
}

// server is not defined: use the region to set to an existing one and use "global" as a default
// fallback
if (StringUtil.isEmpty(builder.voiceUrl)) {
VoiceRegion region =
StringUtil.isEmpty(builder.voiceRegion.value())
? VoiceRegion.GLOBAL
: builder.voiceRegion;
builder.setVoiceUrl(props.getProperty(String.format("voice-server-%s", region.value())));
}
}

/**
Expand Down Expand Up @@ -142,6 +186,21 @@ public VerificationService verification() {
return verification;
}

/**
* Get voice domain service
*
* @return Return instance onto Voice API service
* @see <a
* href="https://developers.sinch.com/docs/voice/api-reference">https://developers.sinch.com/docs/voice/api-reference</a>
* @since 1.0
*/
public VoiceService voice() {
if (null == voice) {
voice = voiceInit();
}
return voice;
}

private void checkConfiguration(Configuration configuration) throws NullPointerException {
Objects.requireNonNull(configuration.getKeyId(), "'keyId' cannot be null");
Objects.requireNonNull(configuration.getKeySecret(), "'keySecret' cannot be null");
Expand Down Expand Up @@ -176,6 +235,13 @@ private VerificationService verificationInit() {
getConfiguration(), getHttpClient());
}

private VoiceService voiceInit() {
LOGGER.fine(
"Activate voice API with server='" + getConfiguration().getVoiceServer().getUrl() + "'");
return new com.sinch.sdk.domains.voice.adapters.VoiceService(
getConfiguration(), getHttpClient());
}

private Properties handlePropertiesFile(String fileName) {

Properties prop = new Properties();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.sinch.sdk.auth.adapters;

// Voice and Verification are sharing same authentication mechanism
public class VoiceApplicationAuthManager extends VerificationApplicationAuthManager {

public VoiceApplicationAuthManager(String key, String base64Secret) {
super(key, base64Secret);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* SMS API related models
*
* @since 1.0
*/
package com.sinch.sdk.domains.sms.models;
24 changes: 24 additions & 0 deletions client/src/main/com/sinch/sdk/domains/voice/CalloutsService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.sinch.sdk.domains.voice;

import com.sinch.sdk.domains.voice.models.requests.CalloutRequestParameters;

/**
* A callout is a call made to a phone number or app using the API.
*
* @see <a
* href="https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callouts">https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callouts</a>
* @since 1.0
*/
public interface CalloutsService {

/**
* Makes a call out to a phone number. The types of callouts currently supported are conference
* callouts, text-to-speech callouts, and custom callouts. The custom callout is the most
* flexible, but text-to-speech and conference callouts are more convenient.
*
* @param parameters Callout type to be performed
* @return The returned call identifier
* @since 1.0
*/
String call(CalloutRequestParameters parameters);
}
19 changes: 19 additions & 0 deletions client/src/main/com/sinch/sdk/domains/voice/VoiceService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.sinch.sdk.domains.voice;

/**
* Voice service
*
* @see <a
* href="https://developers.sinch.com/docs/voice/">https://developers.sinch.com/docs/voice/</a>
* @since 1.0
*/
public interface VoiceService {

/**
* Callouts Service instance
*
* @return service instance for project
* @since 1.0
*/
CalloutsService callouts();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.sinch.sdk.domains.voice.adapters;

import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.core.http.HttpMapper;
import com.sinch.sdk.domains.voice.adapters.api.v1.CalloutsApi;
import com.sinch.sdk.domains.voice.adapters.converters.CalloutsDtoConverter;
import com.sinch.sdk.domains.voice.models.requests.CalloutRequestParameters;
import com.sinch.sdk.models.Configuration;
import java.util.Map;

public class CalloutsService implements com.sinch.sdk.domains.voice.CalloutsService {

private final CalloutsApi api;

public CalloutsService(
Configuration configuration, HttpClient httpClient, Map<String, AuthManager> authManagers) {
this.api =
new CalloutsApi(httpClient, configuration.getVoiceServer(), authManagers, new HttpMapper());
}

protected CalloutsApi getApi() {
return this.api;
}

public String call(CalloutRequestParameters parameters) {

return CalloutsDtoConverter.convert(
getApi().callouts(CalloutsDtoConverter.convert(parameters)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.sinch.sdk.domains.voice.adapters;

import com.sinch.sdk.auth.adapters.VoiceApplicationAuthManager;
import com.sinch.sdk.core.exceptions.ApiAuthException;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.domains.voice.CalloutsService;
import com.sinch.sdk.models.Configuration;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

public class VoiceService implements com.sinch.sdk.domains.voice.VoiceService {

private static final String SECURITY_SCHEME_KEYWORD = "Signed";

private final Configuration configuration;
private final HttpClient httpClient;
private CalloutsService callouts;

private Map<String, AuthManager> clientAuthManagers;

public VoiceService(Configuration configuration, HttpClient httpClient) {

// Currently, we are not supporting unified credentials: ensure application credentials are
// defined
Objects.requireNonNull(configuration.getApplicationKey(), "'applicationKey' cannot be null");
Objects.requireNonNull(
configuration.getApplicationSecret(), "'applicationSecret' cannot be null");

this.configuration = configuration;
this.httpClient = httpClient;
setApplicationCredentials(
configuration.getApplicationKey(), configuration.getApplicationSecret());
}

private void setApplicationCredentials(String key, String secret) {

AuthManager applicationAuthManager = new VoiceApplicationAuthManager(key, secret);

clientAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
clientAuthManagers.put(SECURITY_SCHEME_KEYWORD, applicationAuthManager);
}

public CalloutsService callouts() {
if (null == this.callouts) {
checkCredentials();
this.callouts =
new com.sinch.sdk.domains.voice.adapters.CalloutsService(
configuration, httpClient, clientAuthManagers);
}
return this.callouts;
}

private void checkCredentials() throws ApiAuthException {
if (null == clientAuthManagers || clientAuthManagers.isEmpty()) {
throw new ApiAuthException(
String.format(
"Service '%s' cannot be called without defined credentials",
this.getClass().getSimpleName()));
}
}
}
Loading

0 comments on commit b7876ec

Please sign in to comment.