From eb682c4005ed66500e8838f26dbbc0ae5e863855 Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Fri, 6 Sep 2024 17:32:02 +0100 Subject: [PATCH] docs: Add KDocs to all public methods & classes (#11) * docs: Vonage (top-level) KDocs * Add top-level docs * SIM Swap KDocs * Conversion KDocs * Redact KDocs * Number Verification KDocs * Application KDocs * Users KDocs * Account KDocs & auth methods * Subaccounts KDocs * SMS KDocs * Messages KDocs * Verify v2 KDocs * Rename test clients, repurpose VonageTest * Number Insight KDocs * Verify v1 KDocs * Fix compilation error * Numbers KDocs * Video minor refactor & KDocs * All Video functions documented * Video KDocs throws clauses * Voice KDocs & small tweaks * Remove combine-only build * Fix compilation error * Revert testing with Java 8 * Fix failing test --- CHANGELOG.md | 27 +- pom.xml | 2 +- .../kotlin/com/vonage/client/kt/Account.kt | 83 +++ .../com/vonage/client/kt/Application.kt | 172 ++++++ .../kotlin/com/vonage/client/kt/Conversion.kt | 19 + .../com/vonage/client/kt/ExistingResource.kt | 6 + .../kotlin/com/vonage/client/kt/Messages.kt | 302 +++++++++-- .../com/vonage/client/kt/NumberInsight.kt | 55 ++ .../vonage/client/kt/NumberVerification.kt | 48 ++ .../kotlin/com/vonage/client/kt/Numbers.kt | 60 +++ .../kotlin/com/vonage/client/kt/Redact.kt | 33 ++ .../kotlin/com/vonage/client/kt/SimSwap.kt | 46 ++ src/main/kotlin/com/vonage/client/kt/Sms.kt | 76 +++ .../com/vonage/client/kt/Subaccounts.kt | 177 +++++++ src/main/kotlin/com/vonage/client/kt/Users.kt | 64 +++ .../kotlin/com/vonage/client/kt/Verify.kt | 167 +++++- .../com/vonage/client/kt/VerifyLegacy.kt | 82 ++- src/main/kotlin/com/vonage/client/kt/Video.kt | 500 +++++++++++++++++- src/main/kotlin/com/vonage/client/kt/Voice.kt | 328 +++++++++++- .../kotlin/com/vonage/client/kt/Vonage.kt | 144 ++++- .../com/vonage/client/kt/AbstractTest.kt | 10 +- .../com/vonage/client/kt/AccountTest.kt | 16 +- .../com/vonage/client/kt/ApplicationTest.kt | 14 +- .../com/vonage/client/kt/ConversionTest.kt | 10 +- .../com/vonage/client/kt/MessagesTest.kt | 7 +- .../com/vonage/client/kt/NumberInsightTest.kt | 18 +- .../client/kt/NumberVerificationTest.kt | 10 +- .../com/vonage/client/kt/SimSwapTest.kt | 6 +- .../kotlin/com/vonage/client/kt/SmsTest.kt | 18 +- .../kotlin/com/vonage/client/kt/UsersTest.kt | 6 +- .../com/vonage/client/kt/VerifyLegacyTest.kt | 14 +- .../kotlin/com/vonage/client/kt/VerifyTest.kt | 8 +- .../kotlin/com/vonage/client/kt/VideoTest.kt | 25 +- .../kotlin/com/vonage/client/kt/VoiceTest.kt | 34 +- .../kotlin/com/vonage/client/kt/VonageTest.kt | 7 +- 35 files changed, 2382 insertions(+), 212 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acfd3a5..ee9b375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [1.0.0] - 2024-09-12 +GA release! + +### [1.0.0-beta2] - 2024-09-06 + +### Added +- Documentation (KDocs) for all classes and methods + +### Changed +- Moved Video API's `connectToWebSocket`, `startRender` and `sipDial` methods to `ExistingSession` +- `CallsFilter.Builder.dateStart` and `dateEnd` extension functions now accept `Instant` instead of `String` +- `Voice.inputAction` requires body +- `Voice.connectToWebSocket` and `Call.Builder.toWebSocket` `contentType` parameter changed to (mandatory) enum + +### Removed +- `Voice.ExistingCall.transfer(URI)` method + ## [1.0.0-beta1] - 2024-09-02 +Feature-complete beta release ### Added - Video API ### Changed -- Standardised `Existing*` classes to extend `ExistingResource` for consistency +- Renamed `VerifyLegacy.ExistingRequest#search` to `info` for consistency with other APIs + +### Changed +- Standardised `Existing*` classes to extend `ExistingResource` for consistency. ## [0.9.0] - 2024-08-19 @@ -77,10 +98,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `authFromEnv` now checks for absent environment variables before attempting to set them ## [0.1.0] - 2024-06-25 - -Initial version. +Initial version ### Added - Messages API - Verify v2 API - diff --git a/pom.xml b/pom.xml index 528e716..9381708 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.vonage server-sdk-kotlin - 1.0.0-beta1 + 1.0.0-beta2 Vonage Kotlin Server SDK Kotlin client for Vonage APIs diff --git a/src/main/kotlin/com/vonage/client/kt/Account.kt b/src/main/kotlin/com/vonage/client/kt/Account.kt index 89ebf86..1c3fb4d 100644 --- a/src/main/kotlin/com/vonage/client/kt/Account.kt +++ b/src/main/kotlin/com/vonage/client/kt/Account.kt @@ -17,32 +17,115 @@ package com.vonage.client.kt import com.vonage.client.account.* +/** + * Implementation of the [Account API](https://developer.vonage.com/en/api/account). + * + * *Authentication method:* API key & secret. + */ class Account internal constructor(private val client: AccountClient) { + /** + * Obtains the current account remaining balance. + * + * @return A [BalanceResponse] object containing the current account balance. + * @throws [AccountResponseException] If the balance cannot be retrieved. + */ fun getBalance(): BalanceResponse = client.balance + /** + * You can top up your account using this API when you have enabled auto-reload in the dashboard. + * The amount added by the top-up operation will be the same amount as was added in the payment when + * auto-reload was enabled. Your account balance is checked every 5-10 minutes and if it falls below the + * threshold and auto-reload is enabled, then it will be topped up automatically. Use this method if you + * need to top up at times when your credit may be exhausted more quickly than the auto-reload may occur. + * + * @param transactionId The top-up transaction ID. + * + * @throws [AccountResponseException] If the top-up operation fails. + */ fun topUp(transactionId: String): Unit = client.topUp(transactionId) + /** + * Updates the top-level account settings. Namely, the URLs for incoming SMS and delivery receipts. + * + * @param incomingSmsUrl The URL to which incoming SMS messages are sent when using SMS API. + * @param deliverReceiptUrl The URL to which delivery receipts are sent when using SMS API. + * + * @return The updated account settings. + * + * @throws [AccountResponseException] If the account settings could not be updated. + */ fun updateSettings(incomingSmsUrl: String? = null, deliverReceiptUrl: String? = null): SettingsResponse = client.updateSettings(SettingsRequest(incomingSmsUrl, deliverReceiptUrl)) + /** + * Call this method to work with account secrets. + * + * @param apiKey (OPTIONAL) The account API key to manage secrets for. If not provided, + * the default API key (as supplied in the top-level [Vonage] client) will be used. + * + * @return A [Secrets] object with methods to interact with account secrets. + */ fun secrets(apiKey: String? = null): Secrets = Secrets(apiKey) + /** + * Class for working with account secrets. + * + * @property apiKey The account API key to manage secrets for. If not provided, + * the default API key (as supplied in the top-level [Vonage] client) will be used. + */ inner class Secrets internal constructor(val apiKey: String? = null) { + /** + * Retrieves secrets for the account. + * + * @return A list of secrets details. + * + * @throws [AccountResponseException] If the secrets cannot be retrieved. + */ fun list(): List = ( if (apiKey == null) client.listSecrets() else client.listSecrets(apiKey) ).secrets + /** + * Creates a new secret for the account. + * + * @param secret The secret value to associate with the API key, which must follow these rules: + * - Minimum 8 characters + * - Maximum 25 characters + * - Minimum 1 lower case character + * - Minimum 1 upper case character + * - Minimum 1 digit + * + * @return The created secret's metadata. + * + * @throws [AccountResponseException] If the secret cannot be created. + */ fun create(secret: String): SecretResponse = if (apiKey == null) client.createSecret(secret) else client.createSecret(apiKey, secret) + /** + * Retrieves a secret by its ID. + * + * @param secretId ID of the secret to retrieve. + * + * @return The secret's metadata. + * + * @throws [AccountResponseException] If the secret cannot be retrieved. + */ fun get(secretId: String): SecretResponse = if (apiKey == null) client.getSecret(secretId) else client.getSecret(apiKey, secretId) + /** + * Deletes a secret by its ID. + * + * @param secretId ID of the secret to delete. + * + * @throws [AccountResponseException] If the secret cannot be deleted. + */ fun delete(secretId: String): Unit = if (apiKey == null) client.revokeSecret(secretId) else client.revokeSecret(apiKey, secretId) diff --git a/src/main/kotlin/com/vonage/client/kt/Application.kt b/src/main/kotlin/com/vonage/client/kt/Application.kt index 2584cef..ef0aa44 100644 --- a/src/main/kotlin/com/vonage/client/kt/Application.kt +++ b/src/main/kotlin/com/vonage/client/kt/Application.kt @@ -24,23 +24,88 @@ import com.vonage.client.application.capabilities.Voice import com.vonage.client.common.Webhook import java.util.* +/** + * Implementation of the [Application API](https://developer.vonage.com/en/api/application.v2). + * + * *Authentication method:* API key & secret. + */ class Application internal constructor(private val client: ApplicationClient) { + /** + * Call this method to work with an existing application. + * + * @param applicationId The UUID of the application to work with. + * + * @return An [ExistingApplication] object with methods to interact with the application. + */ fun application(applicationId: String): ExistingApplication = ExistingApplication(applicationId) + + /** + * Call this method to work with an existing application. + * + * @param applicationId The UUID of the application to work with. + * + * @return An [ExistingApplication] object with methods to interact with the application. + */ fun application(applicationId: UUID) = application(applicationId.toString()) + /** + * Class for working with an existing application. + * + * @property id The application ID. + */ inner class ExistingApplication internal constructor(id: String): ExistingResource(id) { + + /** + * Retrieves the application details. + * + * @return The application details. + * + * @throws [ApplicationResponseException] If the application details could not be retrieved. + */ fun get(): Application = client.getApplication(id) + /** + * Updates the application details. + * + * @param properties A lambda function that takes an [Application.Builder] object as its receiver. + * Use this to specify properties to update on the application. + * + * @return The updated application details. + * + * @throws [ApplicationResponseException] If the application details could not be updated. + */ fun update(properties: Application.Builder.() -> Unit): Application = client.updateApplication(Application.builder(get()).apply(properties).build()) + /** + * Deletes the application. + * + * @throws [ApplicationResponseException] If the application could not be deleted. + */ fun delete(): Unit = client.deleteApplication(id) } + /** + * Retrieves a list of all applications associated with your Vonage account (up to the first 1000). + * + * @return A list containing all application details (maximum 1000). + * + * @throws [ApplicationResponseException] If the list of applications could not be retrieved. + */ fun listAll(): List = client.listAllApplications() + /** + * Retrieves a list of applications associated with your Vonage account. + * + * @param page (OPTIONAL) The page number to retrieve. + * @param pageSize (OPTIONAL) The maximum number of applications per page. + * + * @return A list containing application details. + * + * @throws [ApplicationResponseException] If the list of applications could not be retrieved. + */ fun list(page: Int? = null, pageSize: Int? = null): List { val filter = ListApplicationRequest.builder() if (page != null) filter.page(page.toLong()) @@ -48,6 +113,16 @@ class Application internal constructor(private val client: ApplicationClient) { return client.listApplications(filter.build()).applications } + /** + * Creates a new application. + * + * @param properties A lambda function that takes an [Application.Builder] object as its receiver. + * Use this to specify properties for the new application. + * + * @return The newly created application details. + * + * @throws [ApplicationResponseException] If the application could not be created. + */ fun create(properties: Application.Builder.() -> Unit): Application = client.createApplication(Application.builder().apply(properties).build()) } @@ -55,29 +130,93 @@ class Application internal constructor(private val client: ApplicationClient) { private fun webhookBuilder(properties: Webhook.Builder.() -> Unit): Webhook = Webhook.builder().apply(properties).build() +/** + * Adds a webhook to the application. This is an alias for [Webhook.Builder.address]. + * + * @param url The URL to send the webhook to as a string. + * + * @return The webhook builder. + */ fun Webhook.Builder.url(url: String): Webhook.Builder = address(url) +/** + * Adds an `answer_url` webhook to the [Voice] capability. + * + * @param properties A lambda function for setting additional properties on the webhook. + * + * @return The Voice capability builder. + */ fun Voice.Builder.answer(properties: Webhook.Builder.() -> Unit): Voice.Builder = addWebhook(Webhook.Type.ANSWER, webhookBuilder(properties)) +/** + * Adds an `fallback_answer_url` webhook to the [Voice] capability. + * + * @param properties A lambda function for setting additional properties on the webhook. + * + * @return The Voice capability builder. + */ fun Voice.Builder.fallbackAnswer(properties: Webhook.Builder.() -> Unit): Voice.Builder = addWebhook(Webhook.Type.FALLBACK_ANSWER, webhookBuilder(properties)) +/** + * Adds an `event_url` webhook to the [Voice] capability. + * + * @param properties A lambda function for setting additional properties on the webhook. + * + * @return The Voice capability builder. + */ fun Voice.Builder.event(properties: Webhook.Builder.() -> Unit): Voice.Builder = addWebhook(Webhook.Type.EVENT, webhookBuilder(properties)) +/** + * Adds an `event_url` webhook to the [Rtc] capability. + * + * @param properties A lambda function for setting additional properties on the webhook. + * + * @return The RTC capability builder. + */ fun Rtc.Builder.event(properties: Webhook.Builder.() -> Unit): Rtc.Builder = addWebhook(Webhook.Type.EVENT, webhookBuilder(properties)) +/** + * Adds a `status_url` webhook to the [Verify] capability. + * + * @param properties A lambda function for setting additional properties on the webhook. + * + * @return The Verify capability builder. + */ fun Verify.Builder.status(properties: Webhook.Builder.() -> Unit): Verify.Builder = addWebhook(Webhook.Type.STATUS, webhookBuilder(properties)) +/** + * Adds an `inbound_url` webhook to the [Messages] capability. + * + * @param properties A lambda function for setting additional properties on the webhook. + * + * @return The Messages capability builder. + */ fun Messages.Builder.inbound(properties: Webhook.Builder.() -> Unit): Messages.Builder = addWebhook(Webhook.Type.INBOUND, webhookBuilder(properties)) +/** + * Adds an `status_url` webhook to the [Messages] capability. + * + * @param properties A lambda function for setting additional properties on the webhook. + * + * @return The Messages capability builder. + */ fun Messages.Builder.status(properties: Webhook.Builder.() -> Unit): Messages.Builder = addWebhook(Webhook.Type.STATUS, webhookBuilder(properties)) +/** + * Removes one or more capabilities from the application, used in conjunction with + * [com.vonage.client.kt.Application.ExistingApplication#update]. + * + * @param capabilities The capability types to remove. + * + * @return The application builder. + */ fun Application.Builder.removeCapabilities(vararg capabilities: Capability.Type): Application.Builder { for (capability in capabilities) { removeCapability(capability) @@ -85,16 +224,49 @@ fun Application.Builder.removeCapabilities(vararg capabilities: Capability.Type) return this } +/** + * Adds a [Voice] capability to the application. + * + * @param capability A lambda function for setting additional properties on the capability. + * + * @return The application builder. + */ fun Application.Builder.voice(capability: Voice.Builder.() -> Unit): Application.Builder = addCapability(Voice.builder().apply(capability).build()) +/** + * Adds a [Messages] capability to the application. + * + * @param capability A lambda function for setting additional properties on the capability. + * + * @return The application builder. + */ fun Application.Builder.messages(capability: Messages.Builder.() -> Unit): Application.Builder = addCapability(Messages.builder().apply(capability).build()) +/** + * Adds a [Verify] capability to the application. + * + * @param capability A lambda function for setting additional properties on the capability. + * + * @return The application builder. + */ fun Application.Builder.verify(capability: Verify.Builder.() -> Unit): Application.Builder = addCapability(Verify.builder().apply(capability).build()) +/** + * Adds an [Rtc] capability to the application. + * + * @param capability A lambda function for setting additional properties on the capability. + * + * @return The application builder. + */ fun Application.Builder.rtc(capability: Rtc.Builder.() -> Unit): Application.Builder = addCapability(Rtc.builder().apply(capability).build()) +/** + * Adds a [Vbc] capability to the application. + * + * @return The application builder. + */ fun Application.Builder.vbc(): Application.Builder = addCapability(Vbc.builder().build()) diff --git a/src/main/kotlin/com/vonage/client/kt/Conversion.kt b/src/main/kotlin/com/vonage/client/kt/Conversion.kt index 40b32f5..fb3996b 100644 --- a/src/main/kotlin/com/vonage/client/kt/Conversion.kt +++ b/src/main/kotlin/com/vonage/client/kt/Conversion.kt @@ -19,6 +19,11 @@ import com.vonage.client.conversion.* import java.time.Instant import java.util.* +/** + * Implementation of the [Conversion API](https://developer.vonage.com/en/api/Conversion). + * + * *Authentication method:* API key & secret. + */ class Conversion internal constructor(private val client: ConversionClient) { private fun convert(type: ConversionRequest.Type, @@ -27,9 +32,23 @@ class Conversion internal constructor(private val client: ConversionClient) { if (timestamp != null) Date.from(timestamp) else null ) + /** + * Submit conversion for an SMS message. + * + * @param messageId The message ID. + * @param delivered `true` if the message was delivered, `false` otherwise. + * @param timestamp (OPTIONAL) Timestamp of the conversion. + */ fun convertSms(messageId: String, delivered: Boolean, timestamp: Instant? = null): Unit = convert(ConversionRequest.Type.SMS, messageId, delivered, timestamp) + /** + * Submit conversion for a voice call. + * + * @param callId The call UUID. + * @param delivered `true` if the call was received, `false` otherwise. + * @param timestamp (OPTIONAL) Timestamp of the conversion. + */ fun convertVoice(callId: String, delivered: Boolean, timestamp: Instant? = null): Unit = convert(ConversionRequest.Type.VOICE, callId, delivered, timestamp) } diff --git a/src/main/kotlin/com/vonage/client/kt/ExistingResource.kt b/src/main/kotlin/com/vonage/client/kt/ExistingResource.kt index e80ae63..fc24853 100644 --- a/src/main/kotlin/com/vonage/client/kt/ExistingResource.kt +++ b/src/main/kotlin/com/vonage/client/kt/ExistingResource.kt @@ -15,6 +15,12 @@ */ package com.vonage.client.kt +/** + * Base class for top-level endpoint domain objects with IDs. + * This factors out the ID field and provides a common `toString`, `equals`, and `hashCode` implementation. + * + * @param id The unique resource identifier. + */ open class ExistingResource internal constructor(val id: String) { @Override diff --git a/src/main/kotlin/com/vonage/client/kt/Messages.kt b/src/main/kotlin/com/vonage/client/kt/Messages.kt index 4447895..554d1e4 100644 --- a/src/main/kotlin/com/vonage/client/kt/Messages.kt +++ b/src/main/kotlin/com/vonage/client/kt/Messages.kt @@ -23,83 +23,285 @@ import com.vonage.client.messages.messenger.* import com.vonage.client.messages.viber.* import java.util.UUID +/** + * Implementation of the [Messages API](https://developer.vonage.com/en/api/messages-olympus). + * + * *Authentication method:* JWT (recommended), API key & secret (limited functionality). + */ class Messages internal constructor(private val client: MessagesClient) { + + /** + * Send a message. + * + * The details of its format, channel, sender, recipient etc. are specified entirely by + * the type and contents of the [MessageRequest]. This file contains utility functions for creating + * each valid combination of [Channel] and [com.vonage.client.common.MessageType]. Generally, the only + * required parameters are the `from` (sender), `to` (recipient), and the message content, which is + * typically either `text` (for text messages) or `url` (for media messages). + * + * @param message The message to send. Use one of the DSL functions to create the message. + * @param sandbox (OPTIONAL) Set to `true` to use the Messages API Sandbox endpoint. + * + * @return UUID of the message. + * + * @throws [MessageResponseException] If the message could not be sent. This may be for the following reasons: + * - **401**: Invalid credentials. + * - **402**: The account balance is too low. + * - **422**: Malformed request. + * - **429**: Too many requests. + * - **500**: Internal server error. + */ fun send(message: MessageRequest, sandbox: Boolean = false): UUID = (if (sandbox) client.useSandboxEndpoint() else client.useRegularEndpoint()).sendMessage(message).messageUuid } -fun smsText(init: SmsTextRequest.Builder.() -> Unit): SmsTextRequest = - SmsTextRequest.builder().apply(init).build() +/** + * Create an SMS text message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [SmsTextRequest] object with the specified properties. + */ +fun smsText(properties: SmsTextRequest.Builder.() -> Unit): SmsTextRequest = + SmsTextRequest.builder().apply(properties).build() -fun mmsVcard(init: MmsVcardRequest.Builder.() -> Unit): MmsVcardRequest = - MmsVcardRequest.builder().apply(init).build() +/** + * Creates an MMS vCard message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return An [MmsVcardRequest] object with the specified properties. + */ +fun mmsVcard(properties: MmsVcardRequest.Builder.() -> Unit): MmsVcardRequest = + MmsVcardRequest.builder().apply(properties).build() -fun mmsImage(init: MmsImageRequest.Builder.() -> Unit): MmsImageRequest = - MmsImageRequest.builder().apply(init).build() +/** + * Creates an MMS image message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return An [MmsImageRequest] object with the specified properties. + */ +fun mmsImage(properties: MmsImageRequest.Builder.() -> Unit): MmsImageRequest = + MmsImageRequest.builder().apply(properties).build() -fun mmsAudio(init: MmsAudioRequest.Builder.() -> Unit): MmsAudioRequest = - MmsAudioRequest.builder().apply(init).build() +/** + * Creates an MMS audio message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return An [MmsAudioRequest] object with the specified properties. + */ +fun mmsAudio(properties: MmsAudioRequest.Builder.() -> Unit): MmsAudioRequest = + MmsAudioRequest.builder().apply(properties).build() -fun mmsVideo(init: MmsVideoRequest.Builder.() -> Unit): MmsVideoRequest = - MmsVideoRequest.builder().apply(init).build() +/** + * Creates an MMS video message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return An [MmsVideoRequest] object with the specified properties. + */ +fun mmsVideo(properties: MmsVideoRequest.Builder.() -> Unit): MmsVideoRequest = + MmsVideoRequest.builder().apply(properties).build() -fun whatsappText(init: WhatsappTextRequest.Builder.() -> Unit): WhatsappTextRequest = - WhatsappTextRequest.builder().apply(init).build() +/** + * Creates a WhatsApp text message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappTextRequest] object with the specified properties. + */ +fun whatsappText(properties: WhatsappTextRequest.Builder.() -> Unit): WhatsappTextRequest = + WhatsappTextRequest.builder().apply(properties).build() -fun whatsappImage(init: WhatsappImageRequest.Builder.() -> Unit): WhatsappImageRequest = - WhatsappImageRequest.builder().apply(init).build() +/** + * Creates a WhatsApp image message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappImageRequest] object with the specified properties. + */ +fun whatsappImage(properties: WhatsappImageRequest.Builder.() -> Unit): WhatsappImageRequest = + WhatsappImageRequest.builder().apply(properties).build() -fun whatsappAudio(init: WhatsappAudioRequest.Builder.() -> Unit): WhatsappAudioRequest = - WhatsappAudioRequest.builder().apply(init).build() +/** + * Creates a WhatsApp audio message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappAudioRequest] object with the specified properties. + */ +fun whatsappAudio(properties: WhatsappAudioRequest.Builder.() -> Unit): WhatsappAudioRequest = + WhatsappAudioRequest.builder().apply(properties).build() -fun whatsappVideo(init: WhatsappVideoRequest.Builder.() -> Unit): WhatsappVideoRequest = - WhatsappVideoRequest.builder().apply(init).build() +/** + * Creates a WhatsApp video message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappVideoRequest] object with the specified properties. + */ +fun whatsappVideo(properties: WhatsappVideoRequest.Builder.() -> Unit): WhatsappVideoRequest = + WhatsappVideoRequest.builder().apply(properties).build() -fun whatsappFile(init: WhatsappFileRequest.Builder.() -> Unit): WhatsappFileRequest = - WhatsappFileRequest.builder().apply(init).build() +/** + * Creates a WhatsApp file message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappFileRequest] object with the specified properties. + */ +fun whatsappFile(properties: WhatsappFileRequest.Builder.() -> Unit): WhatsappFileRequest = + WhatsappFileRequest.builder().apply(properties).build() -fun whatsappSticker(init: WhatsappStickerRequest.Builder.() -> Unit): WhatsappStickerRequest = - WhatsappStickerRequest.builder().apply(init).build() +/** + * Creates a WhatsApp sticker message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappStickerRequest] object with the specified properties. + */ +fun whatsappSticker(properties: WhatsappStickerRequest.Builder.() -> Unit): WhatsappStickerRequest = + WhatsappStickerRequest.builder().apply(properties).build() -fun whatsappLocation(init: WhatsappLocationRequest.Builder.() -> Unit): WhatsappLocationRequest = - WhatsappLocationRequest.builder().apply(init).build() +/** + * Creates a WhatsApp location message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappLocationRequest] object with the specified properties. + */ +fun whatsappLocation(properties: WhatsappLocationRequest.Builder.() -> Unit): WhatsappLocationRequest = + WhatsappLocationRequest.builder().apply(properties).build() -fun whatsappSingleProduct(init: WhatsappSingleProductRequest.Builder.() -> Unit): WhatsappSingleProductRequest = - WhatsappSingleProductRequest.builder().apply(init).build() +/** + * Creates a WhatsApp single product message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappSingleProductRequest] object with the specified properties. + */ +fun whatsappSingleProduct(properties: WhatsappSingleProductRequest.Builder.() -> Unit): WhatsappSingleProductRequest = + WhatsappSingleProductRequest.builder().apply(properties).build() -fun whatsappMultiProduct(init: WhatsappMultiProductRequest.Builder.() -> Unit): WhatsappMultiProductRequest = - WhatsappMultiProductRequest.builder().apply(init).build() +/** + * Creates a WhatsApp multi product message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappMultiProductRequest] object with the specified properties. + */ +fun whatsappMultiProduct(properties: WhatsappMultiProductRequest.Builder.() -> Unit): WhatsappMultiProductRequest = + WhatsappMultiProductRequest.builder().apply(properties).build() -fun whatsappTemplate(init: WhatsappTemplateRequest.Builder.() -> Unit): WhatsappTemplateRequest = - WhatsappTemplateRequest.builder().apply(init).build() +/** + * Creates a WhatsApp template message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappTemplateRequest] object with the specified properties. + */ +fun whatsappTemplate(properties: WhatsappTemplateRequest.Builder.() -> Unit): WhatsappTemplateRequest = + WhatsappTemplateRequest.builder().apply(properties).build() -fun whatsappCustom(init: WhatsappCustomRequest.Builder.() -> Unit): WhatsappCustomRequest = - WhatsappCustomRequest.builder().apply(init).build() +/** + * Creates a WhatsApp custom message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [WhatsappCustomRequest] object with the specified properties. + */ +fun whatsappCustom(properties: WhatsappCustomRequest.Builder.() -> Unit): WhatsappCustomRequest = + WhatsappCustomRequest.builder().apply(properties).build() -fun messengerText(init: MessengerTextRequest.Builder.() -> Unit): MessengerTextRequest = - MessengerTextRequest.builder().apply(init).build() +/** + * Creates a Messenger text message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [MessengerTextRequest] object with the specified properties. + */ +fun messengerText(properties: MessengerTextRequest.Builder.() -> Unit): MessengerTextRequest = + MessengerTextRequest.builder().apply(properties).build() -fun messengerImage(init: MessengerImageRequest.Builder.() -> Unit): MessengerImageRequest = - MessengerImageRequest.builder().apply(init).build() +/** + * Creates a Messenger image message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [MessengerImageRequest] object with the specified properties. + */ +fun messengerImage(properties: MessengerImageRequest.Builder.() -> Unit): MessengerImageRequest = + MessengerImageRequest.builder().apply(properties).build() -fun messengerAudio(init: MessengerAudioRequest.Builder.() -> Unit): MessengerAudioRequest = - MessengerAudioRequest.builder().apply(init).build() +/** + * Creates a Messenger audio message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [MessengerAudioRequest] object with the specified properties. + */ +fun messengerAudio(properties: MessengerAudioRequest.Builder.() -> Unit): MessengerAudioRequest = + MessengerAudioRequest.builder().apply(properties).build() -fun messengerVideo(init: MessengerVideoRequest.Builder.() -> Unit): MessengerVideoRequest = - MessengerVideoRequest.builder().apply(init).build() +/** + * Creates a Messenger video message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [MessengerVideoRequest] object with the specified properties. + */ +fun messengerVideo(properties: MessengerVideoRequest.Builder.() -> Unit): MessengerVideoRequest = + MessengerVideoRequest.builder().apply(properties).build() -fun messengerFile(init: MessengerFileRequest.Builder.() -> Unit): MessengerFileRequest = - MessengerFileRequest.builder().apply(init).build() +/** + * Creates a Messenger file message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [MessengerFileRequest] object with the specified properties. + */ +fun messengerFile(properties: MessengerFileRequest.Builder.() -> Unit): MessengerFileRequest = + MessengerFileRequest.builder().apply(properties).build() -fun viberText(init: ViberTextRequest.Builder.() -> Unit): ViberTextRequest = - ViberTextRequest.builder().apply(init).build() +/** + * Creates a Viber text message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [ViberTextRequest] object with the specified properties. + */ +fun viberText(properties: ViberTextRequest.Builder.() -> Unit): ViberTextRequest = + ViberTextRequest.builder().apply(properties).build() -fun viberImage(init: ViberImageRequest.Builder.() -> Unit): ViberImageRequest = - ViberImageRequest.builder().apply(init).build() +/** + * Creates a Viber image message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [ViberImageRequest] object with the specified properties. + */ +fun viberImage(properties: ViberImageRequest.Builder.() -> Unit): ViberImageRequest = + ViberImageRequest.builder().apply(properties).build() -fun viberVideo(init: ViberVideoRequest.Builder.() -> Unit): ViberVideoRequest = - ViberVideoRequest.builder().apply(init).build() +/** + * Creates a Viber video message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [ViberVideoRequest] object with the specified properties. + */ +fun viberVideo(properties: ViberVideoRequest.Builder.() -> Unit): ViberVideoRequest = + ViberVideoRequest.builder().apply(properties).build() -fun viberFile(init: ViberFileRequest.Builder.() -> Unit): ViberFileRequest = - ViberFileRequest.builder().apply(init).build() \ No newline at end of file +/** + * Creates a Viber file message. + * + * @param properties A lambda function for setting the message's parameters. + * + * @return A [ViberFileRequest] object with the specified properties. + */ +fun viberFile(properties: ViberFileRequest.Builder.() -> Unit): ViberFileRequest = + ViberFileRequest.builder().apply(properties).build() diff --git a/src/main/kotlin/com/vonage/client/kt/NumberInsight.kt b/src/main/kotlin/com/vonage/client/kt/NumberInsight.kt index f3ccbfe..adc8a14 100644 --- a/src/main/kotlin/com/vonage/client/kt/NumberInsight.kt +++ b/src/main/kotlin/com/vonage/client/kt/NumberInsight.kt @@ -17,22 +17,77 @@ package com.vonage.client.kt import com.vonage.client.insight.* +/** + * Implementation of the [Number Insight API](https://developer.vonage.com/en/api/number-insight). + * + * *Authentication method:* API key & secret. + */ class NumberInsight internal constructor(private val client: InsightClient) { + /** + * Obtain basic insight about a number. + * + * @param number The phone number to look up in E.164 format. + * + * @param countryCode (OPTIONAL) The two-character country code in ISO 3166-1 alpha-2 format. + * + * @return Basic details about the number and insight metadata. + */ fun basic(number: String, countryCode: String? = null): BasicInsightResponse = client.getBasicNumberInsight(number, countryCode) + /** + * Obtain standard insight about a number. + * + * @param number The phone number to look up in E.164 format. + * + * @param countryCode (OPTIONAL) The two-character country code in ISO 3166-1 alpha-2 format. + * + * @param cnam (OPTIONAL) Whether the name of the person who owns the phone number should be looked up + * and returned in the response. Set to true to receive phone number owner name in the response. This + * feature is available for US numbers only and incurs an additional charge. + * + * @return Standard details about the number and insight metadata. + */ fun standard(number: String, countryCode: String? = null, cnam: Boolean? = null): StandardInsightResponse = client.getStandardNumberInsight(StandardInsightRequest.builder() .number(number).country(countryCode).cnam(cnam).build() ) + /** + * Obtain advanced insight about a number synchronously. This is not recommended due to potential timeouts. + * + * @param number The phone number to look up in E.164 format. + * + * @param countryCode (OPTIONAL) The two-character country code in ISO 3166-1 alpha-2 format. + * + * @param cnam (OPTIONAL) Whether the name of the person who owns the phone number should be looked up + * and returned in the response. Set to true to receive phone number owner name in the response. This + * feature is available for US numbers only and incurs an additional charge. + * + * @param realTimeData (OPTIONAL) Whether to receive real-time data back in the response. + * + * @return Advanced details about the number and insight metadata. + */ fun advanced(number: String, countryCode: String? = null, cnam: Boolean = false, realTimeData: Boolean = false): AdvancedInsightResponse = client.getAdvancedNumberInsight(AdvancedInsightRequest.builder().async(false) .number(number).country(countryCode).cnam(cnam).realTimeData(realTimeData).build() ) + /** + * Obtain advanced insight about a number asynchronously. This is recommended to avoid timeouts. + * + * @param number The phone number to look up in E.164 format. + * + * @param callbackUrl The URL to which the response will be sent. + * + * @param countryCode (OPTIONAL) The two-character country code in ISO 3166-1 alpha-2 format. + * + * @param cnam (OPTIONAL) Whether the name of the person who owns the phone number should be looked up + * and returned in the response. Set to true to receive phone number owner name in the response. This + * feature is available for US numbers only and incurs an additional charge. + */ fun advancedAsync(number: String, callbackUrl: String, countryCode: String? = null, cnam: Boolean = false) { client.getAdvancedNumberInsight( AdvancedInsightRequest.builder().async(true) diff --git a/src/main/kotlin/com/vonage/client/kt/NumberVerification.kt b/src/main/kotlin/com/vonage/client/kt/NumberVerification.kt index f79413b..b2635ff 100644 --- a/src/main/kotlin/com/vonage/client/kt/NumberVerification.kt +++ b/src/main/kotlin/com/vonage/client/kt/NumberVerification.kt @@ -16,16 +16,64 @@ package com.vonage.client.kt import com.vonage.client.camara.numberverification.* +import com.vonage.client.camara.CamaraResponseException +import com.vonage.client.auth.camara.NetworkAuthResponseException import java.net.URI +/** + * Implementation of the [Number Verification API](https://developer.vonage.com/en/api/camara/number-verification). + * + * *Authentication method:* JWT. + */ class NumberVerification internal constructor(private val client: NumberVerificationClient) { private var redirectUri: URI? = null + /** + * Sets up the client for verifying the given phone number. This method will cache the provided redirect URL + * for verification when calling #verifyNumberWithCode(String, String). The intended usage is to call this method first, + * and follow the returned URL on the target device which the phone number is supposed to be associated with. + * When the URL is followed, it will trigger an inbound request to the `redirectUrl` with two query parameters: + * `CODE` and `STATE` (if provided as a parameter to this method). The code should then be extracted from the + * query parameters and passed to the `verifyNumberWithCode` method. + * + * @param phoneNumber The MSISDN to verify. + * + * @param redirectUrl Redirect URL, as set in your Vonage application for Network APIs. + * + * @param state An optional string for identifying the request. + * For simplicity, this could be set to the same value as `phoneNumber`, or it may be null. + * + * @return A link with appropriate parameters which should be followed on the end user's device. The link should + * be followed when using the SIM card associated with the provided phone number. Therefore, on the target device, + * Wi-Fi should be disabled when doing this, otherwise the result of `verifyNumber(String)` will be false. + */ fun createVerificationUrl(phoneNumber: String, redirectUrl: String, state: String? = null): URI { redirectUri = URI.create(redirectUrl) return client.initiateVerification(phoneNumber, redirectUri, state) } + /** + * Verifies the given phone number with the provided code. This method should be called after the user has followed + * the URL returned by `createVerificationUrl(String, String, String)`. The code should be extracted from the + * query parameters of the URL and passed to this method. If `createVerificationUrl` has not been called first, + * then the `redirectUrl` parameter should be provided here. If it has, then it can be omitted. + * + * @param phoneNumber The MSISDN to verify. + * + * @param code The code extracted from the query parameters of the URL returned by `createVerificationUrl(String, String, String)`. + * + * @param redirectUrl Redirect URL, as set in your Vonage application for Network APIs. + * This defaults to the value passed in the `createVerificationUrl` method, but can be overridden here. + * + * @return `true` if the device that followed the link was using the SIM card associated with the phone number + * provided in `createVerificationUrl`, `false` otherwise (e.g. it was unknown, the link was not followed, + * the device that followed the link didn't use the SIM card with that phone number when doing so). + * + * @throws NetworkAuthResponseException If there was an error exchanging the code for an access token when + * using the Vonage Network Auth API. + * + * @throws CamaraResponseException If there was an error in communicating with the Number Verification API. + */ fun verifyNumberWithCode(phoneNumber: String, code: String, redirectUrl: String? = null): Boolean { if (redirectUrl != null) redirectUri = URI.create(redirectUrl) return client.verifyNumber(phoneNumber, redirectUri, code) diff --git a/src/main/kotlin/com/vonage/client/kt/Numbers.kt b/src/main/kotlin/com/vonage/client/kt/Numbers.kt index dff8e26..88dbb51 100644 --- a/src/main/kotlin/com/vonage/client/kt/Numbers.kt +++ b/src/main/kotlin/com/vonage/client/kt/Numbers.kt @@ -17,25 +17,85 @@ package com.vonage.client.kt import com.vonage.client.numbers.* +/** + * Implementation of the [Numbers API](https://developer.vonage.com/en/api/numbers). + * + * *Authentication method:* API key & secret. + */ class Numbers internal constructor(private val client: NumbersClient) { + /** + * Call this method to work with an existing number. + * + * @param countryCode The two-character country code of the number. + * @param msisdn The phone number in E.164 format. + * + * @return An [ExistingNumber] object with methods to interact with the number. + */ fun number(countryCode: String, msisdn: String) = ExistingNumber(countryCode, msisdn) + /** + * Class for working with an existing number. + * + * @property countryCode The two-character country code of the number. + * @property msisdn The phone number in E.164 format. + */ inner class ExistingNumber internal constructor(val countryCode: String, val msisdn: String) { + /** + * Purchase the number. + * + * @param targetApiKey (OPTIONAL) The API key of the subaccount to assign the number to. + * If unspecified (the default), the action will be performed on the main account. + * + * @throws [NumbersResponseException] If the number could not be purchased. + */ fun buy(targetApiKey: String? = null): Unit = client.buyNumber(countryCode, msisdn, targetApiKey) + /** + * Cancel the number. + * + * @param targetApiKey (OPTIONAL) The API key of the subaccount to cancel the number on. + * If unspecified (the default), the action will be performed on the main account. + * + * @throws [NumbersResponseException] If the number could not be cancelled. + */ fun cancel(targetApiKey: String? = null): Unit = client.cancelNumber(countryCode, msisdn, targetApiKey) + /** + * Update the number's assignment on your account. + * + * @param properties A lambda function for specifying the properties to update. + * + * @throws [NumbersResponseException] If the number could not be updated. + */ fun update(properties: UpdateNumberRequest.Builder.() -> Unit): Unit = client.updateNumber(UpdateNumberRequest.builder(msisdn, countryCode).apply(properties).build()) } + /** + * List numbers owned by the account. + * + * @param filter (OPTIONAL) A lambda function for specifying the filter properties. + * + * @return A [ListNumbersResponse] object containing the list of owned numbers. + * + * @throws [NumbersResponseException] If the list could not be retrieved. + */ fun listOwned(filter: ListNumbersFilter.Builder.() -> Unit = {}): ListNumbersResponse = client.listNumbers(ListNumbersFilter.builder().apply(filter).build()) + /** + * Search for numbers that are available to purchase. + * + * @param filter A lambda function for specifying the search filter properties. + * + * @return A [SearchNumbersResponse] object containing the list of available numbers. + * + * @throws [NumbersResponseException] If the search request could not be completed. + */ fun searchAvailable(filter: SearchNumbersFilter.Builder.() -> Unit): SearchNumbersResponse = client.searchNumbers(SearchNumbersFilter.builder().apply(filter).build()) } diff --git a/src/main/kotlin/com/vonage/client/kt/Redact.kt b/src/main/kotlin/com/vonage/client/kt/Redact.kt index 4b88237..864e164 100644 --- a/src/main/kotlin/com/vonage/client/kt/Redact.kt +++ b/src/main/kotlin/com/vonage/client/kt/Redact.kt @@ -17,20 +17,53 @@ package com.vonage.client.kt import com.vonage.client.redact.* +/** + * Implementation of the [Redact API](https://developer.vonage.com/en/api/redact). + * + * *Authentication method:* API key & secret. + */ class Redact internal constructor(private val client: RedactClient) { + /** + * Redact an SMS sent using the SMS API. + * + * @param messageId ID of the message to redact. + * @param direction Direction of the message to redact; either `INBOUND` or `OUTBOUND`. + */ fun redactSms(messageId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND): Unit = client.redactTransaction(messageId, RedactRequest.Product.SMS, direction) + /** + * Redact a message sent using the Messages API. + * + * @param messageId UUID of the message to redact. + * @param direction Direction of the message to redact; either `INBOUND` or `OUTBOUND`. + */ fun redactMessage(messageId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND): Unit = client.redactTransaction(messageId, RedactRequest.Product.MESSAGES, direction) + /** + * Redact a call made using the Voice API. + * + * @param callId UUID of the call to redact. + * @param direction Direction of the call to redact; either `INBOUND` or `OUTBOUND`. + */ fun redactCall(callId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND): Unit = client.redactTransaction(callId, RedactRequest.Product.VOICE, direction) + /** + * Redact a number insight request made using the Number Insight API. + * + * @param requestId ID of the insight request to redact. + */ fun redactInsight(requestId: String): Unit = client.redactTransaction(requestId, RedactRequest.Product.NUMBER_INSIGHTS) + /** + * Redact a verification request made using the Verify API. + * + * @param requestId UUID of the verification request to redact. + */ fun redactVerification(requestId: String): Unit = client.redactTransaction(requestId, RedactRequest.Product.VERIFY) } diff --git a/src/main/kotlin/com/vonage/client/kt/SimSwap.kt b/src/main/kotlin/com/vonage/client/kt/SimSwap.kt index 3a50b79..926f0a2 100644 --- a/src/main/kotlin/com/vonage/client/kt/SimSwap.kt +++ b/src/main/kotlin/com/vonage/client/kt/SimSwap.kt @@ -15,14 +15,60 @@ */ package com.vonage.client.kt +import com.vonage.client.auth.camara.NetworkAuthResponseException import com.vonage.client.camara.simswap.* +import com.vonage.client.camara.CamaraResponseException import java.time.Instant +/** + * Implementation of the [Sim Swap API](https://developer.vonage.com/en/api/camara/sim-swap). + * + * *Authentication method:* JWT. + */ class SimSwap internal constructor(private val client: SimSwapClient) { + /** + * Check if a SIM swap has been performed during the specified past period for the given phone number. + * + * @param phoneNumber Subscriber number in E.164 format (starting with country code). Optionally prefixed with '+'. + * + * @param maxAgeHours Period in hours to be checked for SIM swap. Must be between 1 and 2400. + * Default is 10 days (240 hours). + * + * @return `true` if the SIM card has been swapped during the period within the provided age. + * + * @throws NetworkAuthResponseException If an error was encountered in the OAuth 2 flow when + * using the Vonage Network Auth API to obtain the access token. + * + * @throws CamaraResponseException If the request was unsuccessful. This could be for the following reasons: + * - **400**: Invalid request arguments. + * - **401**: Request not authenticated due to missing, invalid, or expired credentials. + * - **403**: Client does not have sufficient permissions to perform this action. + * - **404**: SIM Swap can't be checked because the phone number is unknown. + * - **409**: Another request is created for the same MSISDN. + * - **502**: Bad gateway. + */ fun checkSimSwap(phoneNumber: String, maxAgeHours: Int = 240): Boolean = client.checkSimSwap(phoneNumber, maxAgeHours) + /** + * Get timestamp of last MSISDN to IMSI pairing change for a mobile user account. + * + * @param phoneNumber Subscriber number in E.164 format (starting with country code). Optionally prefixed with '+'. + * + * @return Time of the latest SIM swap performed as an Instant, or `null` if unknown / not applicable. + * + * @throws NetworkAuthResponseException If an error was encountered in the OAuth 2 flow when + * using the Vonage Network Auth API to obtain the access token. + * + * @throws CamaraResponseException If the request was unsuccessful. This could be for the following reasons: + * - **400**: Invalid request arguments. + * - **401**: Request not authenticated due to missing, invalid, or expired credentials. + * - **403**: Client does not have sufficient permissions to perform this action. + * - **404**: SIM Swap can't be checked because the phone number is unknown. + * - **409**: Another request is created for the same MSISDN. + * - **502**: Bad gateway. + */ fun retrieveSimSwapDate(phoneNumber: String): Instant? = client.retrieveSimSwapDate(phoneNumber) } diff --git a/src/main/kotlin/com/vonage/client/kt/Sms.kt b/src/main/kotlin/com/vonage/client/kt/Sms.kt index 803fbbf..31a03c7 100644 --- a/src/main/kotlin/com/vonage/client/kt/Sms.kt +++ b/src/main/kotlin/com/vonage/client/kt/Sms.kt @@ -18,6 +18,11 @@ package com.vonage.client.kt import com.vonage.client.sms.* import com.vonage.client.sms.messages.* +/** + * Implementation of the [SMS API](https://developer.vonage.com/en/api/sms). + * + * *Authentication method:* API key & secret or signature secret. + */ class Sms internal constructor(private val client: SmsClient) { private fun send(msgObj: Message, statusReport: Boolean?, ttl: Int?, @@ -34,6 +39,37 @@ class Sms internal constructor(private val client: SmsClient) { return client.submitMessage(msgObj).messages } + /** + * Send a text message. + * + * @param from The sender ID. This can be a phone number or a short alphanumeric string. + * + * @param to The recipient phone number in E.164 format. + * + * @param message Text of the message to send. + * + * @param unicode `true` if the message should be sent as Unicode, `false` for GSM (the default). + * + * @param statusReport (OPTIONAL) Whether to include a Delivery Receipt. + * + * @param ttl (OPTIONAL) The duration in milliseconds the delivery of an SMS will be attempted. By default, Vonage + * attempts delivery for 72 hours, however the maximum effective value depends on the operator and is typically + * 24 - 48 hours. We recommend this value should be kept at its default or at least 30 minutes. + * + * @param messageClass (OPTIONAL) Data Coding Scheme value of the message. + * + * @param clientRef (OPTIONAL) You can optionally include your own reference of up to 100 characters. + * + * @param contentId (OPTIONAL) This is to satisfy regulatory requirements when sending an SMS to specific countries. + * + * @param entityId (OPTIONAL) This is to satisfy regulatory requirements when sending an SMS to specific countries. + * + * @param callbackUrl (OPTIONAL) The URL to which delivery receipts for this message are sent. + * + * @return A list of [SmsSubmissionResponseMessage] objects, one for each message part sent. + * Multiple messages are sent if the text was too long. For convenience, you can use the + * [wasSuccessfullySent] method to check if all messages were sent successfully. + */ fun sendText(from: String, to: String, message: String, unicode: Boolean = false, statusReport: Boolean? = null, ttl: Int? = null, messageClass: Message.MessageClass? = null, clientRef: String? = null, @@ -44,6 +80,39 @@ class Sms internal constructor(private val client: SmsClient) { statusReport, ttl, messageClass, clientRef, contentId, entityId, callbackUrl ) + /** + * Send a binary (hex) message. + * + * @param from The sender ID. This can be a phone number or a short alphanumeric string. + * + * @param to The recipient phone number in E.164 format. + * + * @param body Hex encoded binary data message to send. + * + * @param udh Hex encoded User Data Header. + * + * @param protocolId (OPTIONAL) The value of the protocol identifier to use. Should be aligned with `udh`. + * + * @param statusReport (OPTIONAL) Whether to include a Delivery Receipt. + * + * @param ttl (OPTIONAL) The duration in milliseconds the delivery of an SMS will be attempted. By default, Vonage + * attempts delivery for 72 hours, however the maximum effective value depends on the operator and is typically + * 24 - 48 hours. We recommend this value should be kept at its default or at least 30 minutes. + * + * @param messageClass (OPTIONAL) Data Coding Scheme value of the message. + * + * @param clientRef (OPTIONAL) You can optionally include your own reference of up to 100 characters. + * + * @param contentId (OPTIONAL) This is to satisfy regulatory requirements when sending an SMS to specific countries. + * + * @param entityId (OPTIONAL) This is to satisfy regulatory requirements when sending an SMS to specific countries. + * + * @param callbackUrl (OPTIONAL) The URL to which delivery receipts for this message are sent. + * + * @return A list of [SmsSubmissionResponseMessage] objects, one for each message part sent. + * Multiple messages are sent if the body was too long. For convenience, you can use the + * [wasSuccessfullySent] method to check if all messages were sent successfully. + */ fun sendBinary(from: String, to: String, body: ByteArray, udh: ByteArray, protocolId: Int? = null, statusReport: Boolean? = null, ttl: Int? = null, messageClass: Message.MessageClass? = null, clientRef: String? = null, @@ -54,6 +123,13 @@ class Sms internal constructor(private val client: SmsClient) { return send(msgObj, statusReport, ttl, messageClass, clientRef, contentId, entityId, callbackUrl) } + /** + * Convenience method to check if all messages were sent successfully. + * + * @param response The list of [SmsSubmissionResponseMessage] objects returned when sending a message. + * + * @return `true` if all messages responses have a status of [MessageStatus.OK], `false` otherwise. + */ fun wasSuccessfullySent(response: List): Boolean = response.all { ssrm -> ssrm.status == MessageStatus.OK } } diff --git a/src/main/kotlin/com/vonage/client/kt/Subaccounts.kt b/src/main/kotlin/com/vonage/client/kt/Subaccounts.kt index fda755c..5fe8b89 100644 --- a/src/main/kotlin/com/vonage/client/kt/Subaccounts.kt +++ b/src/main/kotlin/com/vonage/client/kt/Subaccounts.kt @@ -19,10 +19,44 @@ import com.vonage.client.subaccounts.* import com.vonage.client.subaccounts.Account import java.time.Instant +/** + * Implementation of the [Subaccounts API](https://developer.vonage.com/en/api/subaccounts). + * + * *Authentication method:* API key & secret. + */ class Subaccounts internal constructor(private val client: SubaccountsClient) { + /** + * Retrieve all subaccounts owned by the primary account. + * + * @return The primary account details and list of subaccounts associated with it. + * + * @throws [SubaccountsResponseException] If the subaccounts could not be retrieved. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + */ fun listSubaccounts(): ListSubaccountsResponse = client.listSubaccounts() + /** + * Create a new subaccount. + * + * @param name Name of the subaccount. + * @param secret (OPTIONAL) Secret for the subaccount. If not provided, one will be generated. + * @param usePrimaryAccountBalance (OPTIONAL) Whether to use the primary account's balance. + * + * @return The newly created subaccount details. + * + * @throws [SubaccountsResponseException] If the subaccount could not be created. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + * - **422**: Validation error. + */ fun createSubaccount(name: String, secret: String? = null, usePrimaryAccountBalance: Boolean? = null): Account { val builder = CreateSubaccountRequest.builder().name(name).secret(secret) if (usePrimaryAccountBalance != null) { @@ -31,15 +65,71 @@ class Subaccounts internal constructor(private val client: SubaccountsClient) { return client.createSubaccount(builder.build()) } + /** + * Call this method to work with an existing subaccount. + * + * @param subaccountKey API key of the subaccount to work with. + * + * @return An [ExistingSubaccount] object with methods to interact with the subaccount. + */ fun subaccount(subaccountKey: String): ExistingSubaccount = ExistingSubaccount(subaccountKey) + /** + * Class for working with an existing subaccount. + * + * @property id The subaccount's API key. + */ inner class ExistingSubaccount internal constructor(key: String): ExistingResource(key) { + /** + * Retrieves the subaccount details. + * + * @return The subaccount details. + * + * @throws [SubaccountsResponseException] If the subaccount details could not be retrieved. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + */ fun get(): Account = client.getSubaccount(id) + /** + * Suspends or unsuspends the subaccount. Note that suspension will not delete it; + * only prevent it from being used to make API calls. + * + * @param suspend Set to `true` to suspend the subaccount or `false` to restore it. + * + * @return The updated subaccount details. + * + * @throws [SubaccountsResponseException] If the subaccount could not be suspended. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + * - **422**: Validation error. + */ fun suspended(suspend: Boolean): Account = client.updateSubaccount(UpdateSubaccountRequest.builder(id).suspended(suspend).build()) + /** + * Updates the subaccount details. At least one field must be provided. + * + * @param name (OPTIONAL) New name for the subaccount. + * @param usePrimaryAccountBalance (OPTIONAL) Whether to use the primary account's balance. + * + * @return The updated subaccount details. + * + * @throws [SubaccountsResponseException] If the subaccount details could not be updated. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + * - **422**: Validation error. + */ fun update(name: String? = null, usePrimaryAccountBalance: Boolean? = null): Account { val builder = UpdateSubaccountRequest.builder(id).name(name) if (usePrimaryAccountBalance != null) { @@ -49,24 +139,111 @@ class Subaccounts internal constructor(private val client: SubaccountsClient) { } } + /** + * Retrieve credit limit transfers that have occurred for the primary account within a specified time period. + * + * @param startDate (OPTIONAL) The start of the time period to retrieve transfers for. + * @param endDate (OPTIONAL) The end of the time period to retrieve transfers for. + * @param subaccount (OPTIONAL) The subaccount API key to retrieve transfers for. + * + * @return The list of credit transfers that have occurred which match the filter criteria. + * + * @throws [SubaccountsResponseException] If the transfers could not be retrieved. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + */ fun listCreditTransfers(startDate: Instant? = null, endDate: Instant? = null, subaccount: String? = null): List = client.listCreditTransfers(ListTransfersFilter.builder() .startDate(startDate).endDate(endDate).subaccount(subaccount).build() ) + /** + * Retrieve balance transfers that have occurred for the primary account within a specified time period. + * + * @param startDate (OPTIONAL) The start of the time period to retrieve transfers for. + * @param endDate (OPTIONAL) The end of the time period to retrieve transfers for. + * @param subaccount (OPTIONAL) The subaccount API key to retrieve transfers for. + * + * @return The list of balance transfers that have occurred which match the filter criteria. + * + * @throws [SubaccountsResponseException] If the transfers could not be retrieved. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + */ fun listBalanceTransfers(startDate: Instant? = null, endDate: Instant? = null, subaccount: String? = null): List = client.listBalanceTransfers(ListTransfersFilter.builder() .startDate(startDate).endDate(endDate).subaccount(subaccount).build() ) + /** + * Transfer credit limit between (sub)accounts. + * + * @param from The API key of the account to transfer credit from. + * @param to The API key of the subaccount to transfer credit to. + * @param amount The monetary amount of credit to transfer in Euros. + * @param ref (OPTIONAL) A reference to associate with the transfer. + * + * @return Details of the credit transfer. + * + * @throws [SubaccountsResponseException] If the credit could not be transferred. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + * - **422**: Validation error. + */ fun transferCredit(from: String, to: String, amount: Double, ref: String? = null): MoneyTransfer = client.transferCredit(MoneyTransfer.builder().from(from).to(to).amount(amount).reference(ref).build()) + /** + * Transfer monetary balance between (sub)accounts. + * + * @param from The API key of the account to transfer money from. + * @param to The API key of the subaccount to transfer money to. + * @param amount The monetary amount to transfer in Euros. + * @param ref (OPTIONAL) A reference to associate with the transfer. + * + * @return Details of the balance transfer. + * + * @throws [SubaccountsResponseException] If the balance could not be transferred. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + * - **422**: Validation error. + */ fun transferBalance(from: String, to: String, amount: Double, ref: String? = null): MoneyTransfer = client.transferBalance(MoneyTransfer.builder().from(from).to(to).amount(amount).reference(ref).build()) + /** + * Transfer a number from one account to another. + * + * @param from API key of the account to transfer the number from. + * @param to API key of the account to transfer the number to. + * @param number MSISDN of the number to transfer, in E.164 format. + * @param country Country the number is registered in, in ISO 3166-1 alpha-2 format. + * + * @return Details of the number transfer. + * + * @throws [SubaccountsResponseException] If the number could not be transferred. + * This may be for the following reasons: + * + * - **401**: Missing or invalid credentials. + * - **403**: Action is forbidden. + * - **404**: The account ID provided does not exist in the system, or you do not have access. + * - **409**: The number is already associated with the account you are trying to transfer it to. + * - **422**: Validation error. + */ fun transferNumber(from: String, to: String, number: String, country: String): NumberTransfer = client.transferNumber(NumberTransfer.builder().from(from).to(to).number(number).country(country).build()) diff --git a/src/main/kotlin/com/vonage/client/kt/Users.kt b/src/main/kotlin/com/vonage/client/kt/Users.kt index ef12970..adc5772 100644 --- a/src/main/kotlin/com/vonage/client/kt/Users.kt +++ b/src/main/kotlin/com/vonage/client/kt/Users.kt @@ -17,24 +17,88 @@ package com.vonage.client.kt import com.vonage.client.users.* +/** + * Implementation of the [Users API](https://developer.vonage.com/en/api/application.v2#User). + * + * *Authentication method:* JWT. + */ class Users internal constructor(private val client: UsersClient) { + /** + * Call this method to work with an existing user. + * + * @param userId The UUID of the user to work with. + * + * @return An [ExistingUser] object with methods to interact with the user. + */ fun user(userId: String): ExistingUser = ExistingUser(userId) + /** + * Call this method to work with an existing user. + * + * @param user The user object to work with. + * + * @return An [ExistingUser] object with methods to interact with the user. + */ fun user(user: BaseUser): ExistingUser = ExistingUser(user.id) + /** + * Class for working with an existing user. + * + * @property id The user ID. + */ inner class ExistingUser internal constructor(id: String): ExistingResource(id) { + + /** + * Retrieves the user details. + * + * @return The user details. + * + * @throws [UsersResponseException] If the user details cannot be retrieved. + */ fun get(): User = client.getUser(id) + /** + * Updates the user details. + * + * @param properties A lambda function to set the properties of the user. + * + * @return The updated user details. + * + * @throws [UsersResponseException] If the user details cannot be updated. + */ fun update(properties: User.Builder.() -> Unit): User = client.updateUser(id, User.builder().apply(properties).build()) + /** + * Deletes the user. + * + * @throws [UsersResponseException] If the user cannot be deleted. + */ fun delete(): Unit = client.deleteUser(id) } + /** + * Creates a new user. + * + * @param properties A lambda function to set the properties of the user. + * + * @return The created user details. + * + * @throws [UsersResponseException] If the user cannot be created. + */ fun create(properties: User.Builder.() -> Unit): User = client.createUser(User.builder().apply(properties).build()) + /** + * List users in your application. The response will only contain a subset of the user's properties. + * Call the [user] method to get the full details of a particular user. + * + * @param filter (OPTIONAL) A lambda function to set the request properties for pagination. + * If omitted, the maximum number of users is returned; which is the first 100. + * + * @return The HAL response page with a list of basic user details (ID and name). + */ fun list(filter: (ListUsersRequest.Builder.() -> Unit)? = null): ListUsersResponse { val request = ListUsersRequest.builder() if (filter == null) { diff --git a/src/main/kotlin/com/vonage/client/kt/Verify.kt b/src/main/kotlin/com/vonage/client/kt/Verify.kt index 5350a3c..1abf91b 100644 --- a/src/main/kotlin/com/vonage/client/kt/Verify.kt +++ b/src/main/kotlin/com/vonage/client/kt/Verify.kt @@ -18,24 +18,116 @@ package com.vonage.client.kt import com.vonage.client.verify2.* import java.util.* +/** + * Implementation of the [Verify v2 API](https://developer.vonage.com/en/api/verify.v2). + * + * *Authentication method:* JWT (recommended) and API key & secret (limited functionality). + */ class Verify(private val client: Verify2Client) { - fun sendVerification( - brand: String = "Vonage", - init: VerificationRequest.Builder.() -> Unit - ): VerificationResponse = client.sendVerification( - VerificationRequest.builder().brand(brand).apply(init).build() - ) + /** + * Initiate a verification request. + * + * @param brand The name of the company or app sending the verification request. + * This is what the user will see the sender as on their device. + * + * @param properties A lambda function for specifying the verification request properties. + * You must set at least one workflow and a recipient. See [VerificationRequest.Builder] methods for details. + * + * @return A [VerificationResponse] object containing the request ID. + * + * @throws [VerifyResponseException] If the request could not be sent. This may be for the following reasons: + * - **401**: Invalid credentials. + * - **402**: Account balance too low. + * - **409**: Verification already in progress. You must wait for it to be completed or aborted. + * - **422**: Invalid parameters. Check the error message for details. + * - **429**: Rate limit exceeded. + * - **500**: Internal server error. + */ + fun sendVerification(brand: String = "Vonage", + properties: VerificationRequest.Builder.() -> Unit): VerificationResponse = + client.sendVerification(VerificationRequest.builder().brand(brand).apply(properties).build()) + + + /** + * Call this method to work with an existing verification request. + * + * @param requestId UUID of the verification request to work with, + * as obtained from [VerificationResponse.getRequestId]. + * + * @return An [ExistingRequest] object with methods to interact with the request. + */ + fun request(requestId: UUID): ExistingRequest = ExistingRequest(requestId) + + /** + * Call this method to work with an existing verification request. + * + * @param requestId UUID of the verification request to work with. + */ + fun request(requestId: String): ExistingRequest = request(UUID.fromString(requestId)) + /** + * Call this method to work with an existing verification request. + * + * @param uuid UUID of the verification request to work with, as obtained from [VerificationResponse.getRequestId]. + */ inner class ExistingRequest internal constructor(private val uuid: UUID): ExistingResource(uuid.toString()) { + /** + * Cancels the verification request. + * + * @throws [VerifyResponseException] If the request could not be cancelled. + * This may be for the following reasons: + * - **401**: Invalid credentials. + * - **402**: Account balance too low. + * - **404**: Verification request not found. + * - **500**: Internal server error. + */ fun cancel(): Unit = client.cancelVerification(uuid) + /** + * Advanced the verification request to the next workflow. + * + * @throws [VerifyResponseException] If the request could not be moved to the next worklow. + * This could be for the following reasons: + * - **401**: Invalid credentials. + * - **402**: Account balance too low. + * - **404**: Verification request not found. + * - **409**: Verification already completed or cancelled. + * - **500**: Internal server error. + */ fun nextWorkflow(): Unit = client.nextWorkflow(uuid) + /** + * Checks the verification code. If successful (the code matches), this method will return normally. + * If the code does not match, an exception will be thrown. + * + * @param code The verification code to check, as entered by the user. + * + * @return A [VerifyCodeResponse] object containing the verification status. + * + * @throws [VerifyResponseException] If the code could not be checked. This could be for the following reasons: + * - **400**: Invalid code. + * - **401**: Invalid credentials. + * - **404**: Request was not found, or it has been verified already. + * - **409**: The current workflow does not support a code. + * - **410**: An incorrect code has been provided too many times. Workflow terminated. + * - **500**: Internal server error. + * + * @see isValidVerificationCode For a Boolean-returning version of this method. + */ fun checkVerificationCode(code: String): VerifyCodeResponse = client.checkVerificationCode(uuid, code) + /** + * Checks the verification code. If successful (the code matches), this method will return `true`. + * If the code does not match, the method will return `false. For any other case, an exception will be thrown. + * + * @param code The verification code to check, as entered by the user. + * + * @return `true` if the code is valid, `false` if it is not. + * @see checkVerificationCode For the original implementation which this method delegates to. + */ fun isValidVerificationCode(code: String): Boolean { try { checkVerificationCode(code) @@ -49,12 +141,21 @@ class Verify(private val client: Verify2Client) { } } } - - fun request(requestId: UUID): ExistingRequest = ExistingRequest(requestId) - - fun request(requestId: String): ExistingRequest = request(UUID.fromString(requestId)) } +/** + * Adds a Silent Authentication workflow to the verification request. Note that this must be the first workflow. + * + * @param number The recipient's phone number in E.164 format. + * + * @param sandbox (OPTIONAL) Whether to use the Vonage Sandbox (for testing purposes). Default is `false`. + * + * @param redirectUrl (OPTIONAL) Final redirect added at the end of the `check_url` request/response lifecycle. + * Will contain the `request_id` and `code` as a URL fragment. This can be used to redirect the user back + * to your application after following the verification link on their device. + * + * @return The verification request builder. + */ fun VerificationRequest.Builder.silentAuth( number: String, sandbox: Boolean? = null, redirectUrl: String? = null): VerificationRequest.Builder { val builder = SilentAuthWorkflow.builder(number) @@ -63,18 +164,60 @@ fun VerificationRequest.Builder.silentAuth( return addWorkflow(builder.build()) } +/** + * Adds an SMS workflow to the verification request. + * + * @param number The recipient's phone number in E.164 format. + * + * @param properties (OPTIONAL) A lambda function for specifying additional SMS workflow parameters. + * + * @return The verification request builder. + */ fun VerificationRequest.Builder.sms( - number: String, init: SmsWorkflow.Builder.() -> Unit = {}): VerificationRequest.Builder = - addWorkflow(SmsWorkflow.builder(number).apply(init).build()) + number: String, properties: SmsWorkflow.Builder.() -> Unit = {}): VerificationRequest.Builder = + addWorkflow(SmsWorkflow.builder(number).apply(properties).build()) +/** + * Adds a TTS (Text-to-Speech) workflow to the verification request. + * + * @param number The recipient's phone number in E.164 format. + * + * @return The verification request builder. + */ fun VerificationRequest.Builder.voice(number: String): VerificationRequest.Builder = addWorkflow(VoiceWorkflow(number)) +/** + * Adds an email workflow to the verification request. + * + * @param to The recipient's email address. + * + * @param from (OPTIONAL) The email address to send the request from. This is not available by default. + */ fun VerificationRequest.Builder.email(to: String, from: String? = null): VerificationRequest.Builder = addWorkflow(EmailWorkflow(to, from)) +/** + * Adds a WhatsApp text workflow to the verification request. + * + * @param to The recipient's phone number in E.164 format. + * + * @param from A WhatsApp Business Account connected sender number in E.164 format. + * + * @return The verification request builder. + */ fun VerificationRequest.Builder.whatsapp(to: String, from: String): VerificationRequest.Builder = addWorkflow(WhatsappWorkflow(to, from)) +/** + * Adds a WhatsApp Interactive (codeless) workflow to the verification request. + * The user will receive a Yes / No prompt instead of a PIN code. + * + * @param to The recipient's phone number in E.164 format. + * + * @param from A WhatsApp Business Account connected sender number in E.164 format. + * + * @return The verification request builder. + */ fun VerificationRequest.Builder.whatsappCodeless(to: String, from: String): VerificationRequest.Builder = addWorkflow(WhatsappCodelessWorkflow(to, from)) diff --git a/src/main/kotlin/com/vonage/client/kt/VerifyLegacy.kt b/src/main/kotlin/com/vonage/client/kt/VerifyLegacy.kt index 641262c..9e04c43 100644 --- a/src/main/kotlin/com/vonage/client/kt/VerifyLegacy.kt +++ b/src/main/kotlin/com/vonage/client/kt/VerifyLegacy.kt @@ -17,31 +17,101 @@ package com.vonage.client.kt import com.vonage.client.verify.* +/** + * Implementation of the [Verify v1 API](https://developer.vonage.com/en/api/verify). + * + * *Authentication method:* API key & secret or signature secret. + */ class VerifyLegacy internal constructor(private val client: VerifyClient) { + /** + * Initiate a verification request. + * + * @param number The phone number to verify in E.164 format. + * + * @param brand The name of the company or app sending the verification request. + * This is what the user will see the sender as on their device. + * + * @param properties A lambda function for specifying additional parameters of the request. + * + * @return A [VerifyResponse] object containing the request ID and status. + */ fun verify(number: String, brand: String, properties: (VerifyRequest.Builder.() -> Unit) = {}): VerifyResponse = client.verify(VerifyRequest.builder(number, brand).apply(properties).build()) + /** + * Generate and send a PIN to your user to authorize a payment, compliant with Payment Services Directive 2. + * + * @param number The phone number to send the request to, in E.164 format. + * + * @param amount The decimal amount of the payment to be confirmed, in Euros. + * + * @param payee Name of the recipient that the user is confirming a payment to. + * + * @param properties (OPTIONAL) A lambda function for specifying additional parameters of the request. + * + * @return A [VerifyResponse] object containing the request ID and status. + */ fun psd2Verify(number: String, amount: Double, payee: String, properties: (Psd2Request.Builder.() -> Unit) = {}): VerifyResponse = client.psd2Verify(Psd2Request.builder(number, amount, payee).apply(properties).build()) + /** + * Use this method to check the status of past or current verification requests. + * + * @param requestIds One or more request IDs to check. + * + * @return Information about the verification request(s). + * + * @see [ExistingRequest.info] An alias for this method. + */ fun search(vararg requestIds: String): SearchVerifyResponse = client.search(*requestIds) - fun request(requestId: String): ExistingRequest = ExistingRequest(requestId) - + /** + * Call this method to work with an existing verification request. + * + * @param response Response object containing the request ID. + */ fun request(response: VerifyResponse): ExistingRequest = request(response.requestId) + /** + * Call this method to work with an existing verification request. + * + * @param requestId ID of the verification request to work with. + */ + fun request(requestId: String): ExistingRequest = ExistingRequest(requestId) + + /** + * Call this method to work with an existing verification request. + * + * @param id ID of the verification request to work with. + */ inner class ExistingRequest internal constructor(id: String): ExistingResource(id) { + /** + * Retrieve details about the verification request. + * + * @return Information about the verification request. + * + * @see [search] An alias for this method. + */ + fun info(): SearchVerifyResponse = client.search(id) + + /** + * Cancel the verification request. + */ fun cancel(): ControlResponse = client.cancelVerification(id) + /** + * Trigger the next verification step. + */ fun advance(): ControlResponse = client.advanceVerification(id) + /** + * Check the verification code. + * + * @param code PIN code to check, as entered by the user. + */ fun check(code: String): CheckResponse = client.check(id, code) - - fun search(): SearchVerifyResponse = client.search(id) - } - } \ No newline at end of file diff --git a/src/main/kotlin/com/vonage/client/kt/Video.kt b/src/main/kotlin/com/vonage/client/kt/Video.kt index ded55be..d6b5c88 100644 --- a/src/main/kotlin/com/vonage/client/kt/Video.kt +++ b/src/main/kotlin/com/vonage/client/kt/Video.kt @@ -18,95 +18,405 @@ package com.vonage.client.kt import com.vonage.client.video.* import java.util.* +/** + * Implementation of the [Video API](https://developer.vonage.com/en/api/video). + * + * *Authentication method:* JWT. + */ class Video(private val client: VideoClient) { + /** + * Create a new session. + * + * @param properties (OPTIONAL) A lambda function to set the parameters of the session. + * + * @return The created session metadata. + * + * @throws [VideoResponseException] If the session could not be created. + */ fun createSession(properties: CreateSessionRequest.Builder.() -> Unit = {}): CreateSessionResponse = client.createSession(CreateSessionRequest.builder().apply(properties).build()) + /** + * Call this method to work with an existing session. + * + * @param sessionId ID of the session to work with. + * + * @return An [ExistingSession] object with methods to interact with the session. + */ fun session(sessionId: String): ExistingSession = ExistingSession(sessionId) + /** + * Class for working with an existing session. + * + * @property id The session ID. + */ inner class ExistingSession internal constructor(id: String): ExistingResource(id) { + /** + * Call this method to work with an existing stream. + * + * @param streamId UUID of the stream to work with. + */ fun stream(streamId: String): ExistingStream = ExistingStream(streamId) + /** + * Class for working with an existing stream. + * + * @property id The stream ID. + */ inner class ExistingStream internal constructor(id: String): ExistingResource(id) { + /** + * Retrieves the stream details. + * + * @return The stream metadata. + * + * @throws [VideoResponseException] If the stream details could not be retrieved. + */ fun info(): GetStreamResponse = client.getStream(this@ExistingSession.id, id) + /** + * Mute the stream. + * + * @throws [VideoResponseException] If the stream could not be muted. + */ fun mute(): Unit = client.muteStream(this@ExistingSession.id, id) + /** + * Update the stream's video layout. + * + * @param layoutClasses The layout class names to apply to the stream. + * + * @throws [VideoResponseException] If the stream layout could not be updated. + */ fun setLayout(vararg layoutClasses: String): Unit = client.setStreamLayout(this@ExistingSession.id, SessionStream.builder(id).layoutClassList(layoutClasses.toList()).build() ) } + /** + * Call this method to work with an existing client (participant). + * + * @param connectionId UUID of the connection to work with. + * + * @return An [ExistingConnection] object with methods to interact with the connection. + */ fun connection(connectionId: String): ExistingConnection = ExistingConnection(connectionId) + /** + * Class for working with an existing connection. + * + * @property id The connection ID. + */ inner class ExistingConnection internal constructor(id: String): ExistingResource(id) { + /** + * Force the client to disconnect from the session. + * + * @throws [VideoResponseException] If the client could not be disconnected. + */ fun disconnect(): Unit = client.forceDisconnect(this@ExistingSession.id, id) + /** + * Send a signal to the client. + * + * @param type Type of data that is being sent to the client. This cannot exceed 128 bytes. + * @param data Payload that is being sent to the client. This cannot exceed 8kb. + * + * @throws [VideoResponseException] If the signal could not be sent. + */ fun signal(type: String, data: String): Unit = client.signal(this@ExistingSession.id, id, signalRequest(type, data)) + /** + * Send DTMF tones to the client. Telephony events are negotiated over SDP and transmitted as + * RFC4733/RFC2833 digits to the remote endpoint. + * + * @param digits The string of DTMF digits to send. This can include 0-9, '*', '#', and 'p'. + * A 'p' indicates a pause of 500ms (if you need to add a delay in sending the digits). + * + * @throws [VideoResponseException] If the DTMF tones could not be sent. + */ fun sendDtmf(digits: String): Unit = client.sendDtmf(this@ExistingSession.id, id, digits) } + /** + * Mute or unmute all streams in the session, except for those specified. + * + * @param active Whether to mute streams in the session (`true`) and enable the mute state of the session, + * or to disable the mute state of the session (`false`). With the mute state enabled (true), all current + * and future streams published to the session (except streams in the `excludedStreamIds` array) are muted. + * When you call this method with the active property set to `false`, future streams published to the + * session are not muted (but any existing muted streams remain muted). + * + * @param excludedStreamIds (OPTIONAL) The stream IDs for streams that should not be muted. If you omit + * this property, all streams in the session will be muted. This property only applies when the active + * property is set to true. When the active property is set to false, it is ignored. + * + * @return The updated project details. + * + * @throws [VideoResponseException] If the streams could not be muted. + */ fun muteStreams(active: Boolean = true, vararg excludedStreamIds: String): ProjectDetails = client.muteSession(id, active, if (excludedStreamIds.isNotEmpty()) excludedStreamIds.toList() else null ) + /** + * List all streams in the session. This can be used to get information about layout classes used by a Vonage + * Video stream. The layout classes define how the stream is displayed in the layout of a broadcast stream. + * + * @return A list of streams and their layouts for this session. + * + * @throws [VideoResponseException] If the streams could not be retrieved. + */ fun listStreams(): List = client.listStreams(id) + /** + * List all Archives of this session. + * + * @param count (OPTIONAL) The number of Archives to return (maximum 1000). + * @param offset (OPTIONAL) Index of the first Archive to return (used for pagination). + * + * @return A list of Archives. + * + * @throws [VideoResponseException] If the archives could not be retrieved. + */ fun listArchives(count: Int = 1000, offset: Int = 0): List = client.listArchives(listCompositionsFilter(count, offset, id)) + /** + * List all Broadcasts in the application. + * + * @param count (OPTIONAL) The number of Broadcasts to return (maximum 1000). + * @param offset (OPTIONAL) Index of the first Broadcast to return (used for pagination). + * + * @return A list of Broadcasts. + * + * @throws [VideoResponseException] If the broadcasts could not be retrieved. + */ fun listBroadcasts(count: Int = 1000, offset: Int = 0): List = client.listBroadcasts(listCompositionsFilter(count, offset, id)) + /** + * Send a signal to all participants (clients) in the session. + * + * @param type Type of data that is being sent to the clients. This cannot exceed 128 bytes. + * @param data Payload that is being sent to the clients. This cannot exceed 8kb. + * + * @throws [VideoResponseException] If the signal could not be sent. + */ fun signalAll(type: String, data: String): Unit = client.signalAll(id, signalRequest(type, data)) + /** + * Send DTMF tones to all participants in the session. Telephony events are negotiated over SDP and transmitted + * as RFC4733/RFC2833 digits to the remote endpoint. + * + * @param digits The string of DTMF digits to send. This can include 0-9, '*', '#', and 'p'. + * A 'p' indicates a pause of 500ms (if you need to add a delay in sending the digits). + * + * @throws [VideoResponseException] If the DTMF tones could not be sent. + */ fun sendDtmf(digits: String): Unit = client.sendDtmf(id, digits) + /** + * Start real-time Live Captions for the session. + * + * The maximum allowed duration is 4 hours, after which the audio captioning will stop without any effect + * on the ongoing Vonage Video Session. An event will be posted to your callback URL if provided when + * starting the captions. Each Vonage Video Session supports only one audio captioning session. + * + * @param token A valid Vonage JWT with role set to Moderator. + * @param properties (OPTIONAL) A lambda function to set the parameters of the audio captioning session. + * + * @return Unique ID of the audio captioning session. + * + * @throws [VideoResponseException] If the captions could not be started. + */ fun startCaptions(token: String, properties: CaptionsRequest.Builder.() -> Unit = {}): UUID = client.startCaptions(CaptionsRequest.builder() .apply(properties).sessionId(id).token(token).build() ).captionsId + /** + * Stop Live Captions for the session. + * + * @param captionsId The unique ID of the audio captioning session. + * + * @throws [VideoResponseException] If the captions could not be stopped. + */ fun stopCaptions(captionsId: String): Unit = client.stopCaptions(captionsId) + /** + * Establish a SIP connection to this session. + * + * The audio from your end of the SIP call is added to the sessionnas an audio-only stream. The Vonage Video + * Media Router mixes audio from other streams in the session and sends the mixed audio to your SIP endpoint. + * The call ends when your SIP server sends a `BYE` message to terminate the call. You can also end a call + * using the [ExistingSession.ExistingConnection.disconnect] method. The Vonage Video SIP gateway automatically + * ends a call after 5 minutes of inactivity (5 minutes without media received). Also, as a security measure, + * the Vonage Video SIP gateway closes any SIP call that lasts longer than 6 hours. The SIP interconnect + * feature requires the session's media mode to be set to [MediaMode.ROUTED]. For more information, including + * technical details and security considerations, see the Vonage Video SIP interconnect developer guide. + * + * @param properties A lambda function to set the parameters of the SIP dial request. + * You need to provide a token and a SIP URI to establish the connection. + * + * @return Details of the SIP connection. + * + * @throws [VideoResponseException] If the SIP connection could not be established. + */ + fun sipDial(properties: SipDialRequest.Builder.() -> Unit): SipDialResponse = + client.sipDial(SipDialRequest.builder().sessionId(id).apply(properties).build()) + + /** + * Send audio from the session to a WebSocket. This feature is only supported in `routed` sessions. + * + * @param properties A lambda function to set the parameters of the WebSocket connection. + * + * @return Details of the WebSocket connection. + * + * @throws [VideoResponseException] If the WebSocket connection could not be established. + */ + fun connectToWebsocket(properties: ConnectRequest.Builder.() -> Unit): ConnectResponse = + client.connectToWebsocket(ConnectRequest.builder().sessionId(id).apply(properties).build()) + + /** + * Create a new Experience Composer for this session. + * + * @param properties A lambda function to set the parameters of the Experience Composer. + * + * @return Details of the created Experience Composer. + * + * @throws [VideoResponseException] If the Experience Composer could not be created. + */ + fun startRender(properties: RenderRequest.Builder.() -> Unit): RenderResponse = + client.startRender(RenderRequest.builder().sessionId(id).apply(properties).build()) + + /** + * Create a new Archive from this session. + * + * @param properties (OPTIONAL) A lambda function to set the parameters of the Archive. + * + * @return Details of the created Archive. + * + * @throws [VideoResponseException] If the Archive could not be created. + */ fun createArchive(properties: Archive.Builder.() -> Unit = {}): Archive = client.createArchive(Archive.builder(id).apply(properties).build()) + /** + * Start a live broadcast for the session to HLS (HTTP Live-Streaming) or RTMP streams. + * + * To successfully start broadcasting a session, at least one client must be connected to the session. + * The live-streaming broadcast can target one HLS endpoint and up to five RTMP servers simultaneously + * for a session. You can only start live-streaming for sessions that use the Vonage Video Media Router + * (with the media mode set to [MediaMode.ROUTED]); you cannot use live-streaming with sessions that have + * the media mode set to [MediaMode.RELAYED]. + * + * @param properties A lambda function to set the parameters of the broadcast. See [Broadcast.Builder] + * for details of required and optional parameters. + * + * @return Details of the created broadcast. + * + * @throws [VideoResponseException] If the broadcast could not be started. + */ fun startBroadcast(properties: Broadcast.Builder.() -> Unit): Broadcast = client.createBroadcast(Broadcast.builder(id).apply(properties).build()) + /** + * Generate a JWT for the session, which can be used in various other methods as required. + * + * @param options (OPTIONAL) A lambda function to set the parameters (claims) of the token. + * + * @return A new JWT with the specified claims. + * + * @throws [VideoResponseException] If the token could not be generated. + */ fun generateToken(options: TokenOptions.Builder.() -> Unit = {}): String = client.generateToken(id, TokenOptions.builder().apply(options).build()) } - fun sipDial(properties: SipDialRequest.Builder.() -> Unit): SipDialResponse = - client.sipDial(SipDialRequest.builder().apply(properties).build()) - - fun connectToWebsocket(properties: ConnectRequest.Builder.() -> Unit): ConnectResponse = - client.connectToWebsocket(ConnectRequest.builder().apply(properties).build()) - + /** + * List all Archives in the application. + * + * @param count (OPTIONAL) The number of Archives to return (maximum 1000). + * @param offset (OPTIONAL) Index of the first Archive to return (used for pagination). + * + * @return A list of Archives. + * + * @throws [VideoResponseException] If the archives could not be retrieved. + */ fun listArchives(count: Int = 1000, offset: Int = 0): List = client.listArchives(listCompositionsFilter(count, offset)) + /** + * Call this method to work with an existing archive. + * + * @param archiveId UUID of the archive to work with. + * + * @return An [ExistingArchive] object with methods to interact with the archive. + */ fun archive(archiveId: String): ExistingArchive = ExistingArchive(archiveId) + /** + * Class for working with an existing archive. + * + * @property id The archive ID. + */ inner class ExistingArchive internal constructor(id: String): ExistingResource(id) { + /** + * Retrieve archive information. + * + * @return Details of the archive. + * + * @throws [VideoResponseException] If the archive details could not be retrieved. + */ fun info(): Archive = client.getArchive(id) + /** + * Stop the archive. + * + * Archives stop recording after 4 hours (14,400 seconds), or 60 seconds after the last client disconnects + * from the session, or 60 minutes after the last client stops publishing. However, automatic archives continue + * recording to multiple consecutive files of up to 4 hours in length each. + * + * Calling this method for automatic archives has no effect. Automatic archives continue recording to multiple + * consecutive files of up to 4 hours (14,400 seconds) in length each, until 60 seconds after the last client + * disconnects from the session, or 60 minutes after the last client stops publishing a stream to the session. + * + * @return Details of the archive. + * + * @throws [VideoResponseException] If the archive could not be stopped. + */ fun stop(): Archive = client.stopArchive(id) + /** + * Delete the archive. + * + * @throws [VideoResponseException] If the archive could not be deleted. + */ fun delete(): Unit = client.deleteArchive(id) + /** + * Set the layout for the archive. This only applies if it is composed. + * + * @param initialLayout The layout type to use for the archive as an enum. If set to + * [ScreenLayoutType.CUSTOM], then you must also set the `stylesheet` property. + * + * @param screenshareType (OPTIONAL) The layout type to use when there is a screen-sharing stream in the + * session. Note if you set this property, then `initialLayout` must be set to [ScreenLayoutType.BEST_FIT] + * and you must leave the `stylesheet` property unset (null). + * + * @param stylesheet (OPTIONAL) The CSS stylesheet to use for the archive. If you set this property, + * then `initialLayout` must be set to [ScreenLayoutType.CUSTOM]. + * + * @throws [VideoResponseException] If the layout could not be set. + */ fun setLayout(initialLayout: ScreenLayoutType, screenshareType: ScreenLayoutType? = null, stylesheet: String? = null): Unit = @@ -117,23 +427,92 @@ class Video(private val client: VideoClient) { .build() ) - fun addStream(streamId: String, audio: Boolean = true, video: Boolean = true) = + /** + * Add a stream to the archive. This only applies if it's a composed archive that was started with + * the `streamMode` set to [StreamMode.MANUAL]. + * + * @param streamId UUID of the stream to add. + * @param audio (OPTIONAL) Whether the composed archive should include the stream's audio. + * @param video (OPTIONAL) Whether the composed archive should include the stream's video. + * + * @throws [VideoResponseException] If the stream could not be added. + */ + fun addStream(streamId: String, audio: Boolean = true, video: Boolean = true): Unit = client.addArchiveStream(id, streamId, audio, video) + /** + * Remove a stream from the archive. This only applies if it's a composed archive that was started with + * the `streamMode` set to [StreamMode.MANUAL]. + * + * @param streamId UUID of the stream to remove. + * + * @throws [VideoResponseException] If the stream could not be removed. + */ fun removeStream(streamId: String): Unit = client.removeArchiveStream(id, streamId) } + /** + * List all Broadcasts in the application. + * + * @param count (OPTIONAL) The number of Broadcasts to return (maximum 1000). + * @param offset (OPTIONAL) Index of the first Broadcast to return (used for pagination). + * + * @return A list of Broadcasts. + * + * @throws [VideoResponseException] If the broadcasts could not be retrieved. + */ fun listBroadcasts(count: Int = 1000, offset: Int = 0): List = client.listBroadcasts(listCompositionsFilter(count, offset)) + /** + * Call this method to work with an existing Broadcast. + * + * @param broadcastId UUID of the Broadcast to work with. + * + * @return An [ExistingBroadcast] object with methods to interact with the Broadcast. + */ fun broadcast(broadcastId: String): ExistingBroadcast = ExistingBroadcast(broadcastId) + /** + * Class for working with an existing broadcast. + * + * @property id The broadcast ID. + */ inner class ExistingBroadcast internal constructor(id: String): ExistingResource(id) { + /** + * Retrieves information about the broadcast in progress. + * + * @return Details of the broadcast. + * + * @throws [VideoResponseException] If the broadcast details could not be retrieved. + */ fun info(): Broadcast = client.getBroadcast(id) + /** + * Stops the broadcast. + * + * @return Details of the broadcast. + * + * @throws [VideoResponseException] If the broadcast could not be stopped. + */ fun stop(): Broadcast = client.stopBroadcast(id) + /** + * Set the layout for the broadcast. + * + * @param initialLayout The layout type to use for the broadcast as an enum. If set to + * [ScreenLayoutType.CUSTOM], then you must also set the `stylesheet` property. + * + * @param screenshareType (OPTIONAL) The layout type to use when there is a screen-sharing stream in the + * session. Note if you set this property, then `initialLayout` must be set to [ScreenLayoutType.BEST_FIT] + * and you must leave the `stylesheet` property unset (null). + * + * @param stylesheet (OPTIONAL) The CSS stylesheet to use for the broadcast. If you set this property, + * then `initialLayout` must be set to [ScreenLayoutType.CUSTOM]. + * + * @throws [VideoResponseException] If the layout could not be set. + */ fun setLayout(initialLayout: ScreenLayoutType, screenshareType: ScreenLayoutType? = null, stylesheet: String? = null): Unit = @@ -144,24 +523,71 @@ class Video(private val client: VideoClient) { .build() ) - fun addStream(streamId: String, audio: Boolean = true, video: Boolean = true) = + /** + * Add a stream to the broadcast. + * + * @param streamId UUID of the stream to add. + * @param audio (OPTIONAL) Whether the broadcast should include the stream's audio. + * @param video (OPTIONAL) Whether the broadcast should include the stream's video. + * + * @throws [VideoResponseException] If the stream could not be added. + */ + fun addStream(streamId: String, audio: Boolean = true, video: Boolean = true): Unit = client.addBroadcastStream(id, streamId, audio, video) + /** + * Remove a stream from the broadcast. + * + * @param streamId UUID of the stream to remove. + * + * @throws [VideoResponseException] If the stream could not be removed. + */ fun removeStream(streamId: String): Unit = client.removeBroadcastStream(id, streamId) } - fun startRender(properties: RenderRequest.Builder.() -> Unit): RenderResponse = - client.startRender(RenderRequest.builder().apply(properties).build()) - + /** + * List all Experience Composers in the application. + * + * @param count (OPTIONAL) The number of Experience Composers to return (maximum 1000). + * @param offset (OPTIONAL) Index of the first Experience Composer to return (used for pagination). + * + * @return A list of Experience Composers. + * + * @throws [VideoResponseException] If the Experience Composers could not be retrieved. + */ fun listRenders(count: Int = 1000, offset: Int = 0): List = client.listRenders(ListStreamCompositionsRequest.builder().count(count).offset(offset).build()) + /** + * Call this method to work with an existing Experience Composer. + * + * @param renderId UUID of the Experience Composer to work with. + * + * @return An [ExistingRender] object with methods to interact with the Experience Composer. + */ fun render(renderId: String): ExistingRender = ExistingRender(renderId) + /** + * Class for working with an existing Experience Composer. + * + * @property id The render ID. + */ inner class ExistingRender internal constructor(id: String): ExistingResource(id) { + /** + * Retrieves the Experience Composer. + * + * @return Details of the Experience Composer. + * + * @throws [VideoResponseException] If the Experience Composer details could not be retrieved. + */ fun info(): RenderResponse = client.getRender(id) + /** + * Stops the Experience Composer. + * + * @throws [VideoResponseException] If the Experience Composer could not be stopped. + */ fun stop(): Unit = client.stopRender(id) } } @@ -179,18 +605,64 @@ private fun streamCompositionLayout(initialLayout: ScreenLayoutType, StreamCompositionLayout.builder(initialLayout) .screenshareType(screenshareType).stylesheet(stylesheet).build() +/** + * Adds an RTMP stream to the broadcast builder. + * + * @param properties A lambda function to set the parameters of the RTMP stream. + * + * @return The updated broadcast builder. + */ fun Broadcast.Builder.addRtmpStream(properties: Rtmp.Builder.() -> Unit): Broadcast.Builder = addRtmpStream(Rtmp.builder().apply(properties).build()) -fun Broadcast.Builder.hls(hls: Hls.Builder.() -> Unit = {}): Broadcast.Builder = - hls(Hls.builder().apply(hls).build()) +/** + * Adds an HTTP Live Stream to the broadcast builder. + * + * @param properties (OPTIONAL) A lambda function to set the parameters of the HLS stream. + * + * @return The updated broadcast builder. + */ +fun Broadcast.Builder.hls(properties: Hls.Builder.() -> Unit = {}): Broadcast.Builder = + hls(Hls.builder().apply(properties).build()) +/** + * Sets the layout for a composed archive. If this option is specified, + * [Archive.Builder.outputMode] must be set to [OutputMode.COMPOSED]. + * + * @param initialLayout The layout type to use for the archive as an enum. If set to + * [ScreenLayoutType.CUSTOM], then you must also set the `stylesheet` property. + * + * @param screenshareType (OPTIONAL) The layout type to use when there is a screen-sharing stream in the + * session. Note if you set this property, then `initialLayout` must be set to [ScreenLayoutType.BEST_FIT] + * and you must leave the `stylesheet` property unset (null). + * + * @param stylesheet (OPTIONAL) The CSS stylesheet to use for the archive. If you set this property, + * then `initialLayout` must be set to [ScreenLayoutType.CUSTOM]. + * + * @return The updated archive builder. + */ fun Archive.Builder.layout(initialLayout: ScreenLayoutType, screenshareType: ScreenLayoutType? = null, stylesheet: String? = null): Archive.Builder = layout(streamCompositionLayout(initialLayout, screenshareType, stylesheet)) -fun Broadcast.Builder.layout(initialLayout: ScreenLayoutType, +/** + * Specify this to assign the initial layout type for the broadcast. If you do not specify an initial layout type, + * the broadcast stream uses [ScreenLayoutType.BEST_FIT] as the default layout type. + * + * @param initialLayout The layout type to use for the broadcast as an enum. If set to + * [ScreenLayoutType.CUSTOM], then you must also set the `stylesheet` property. + * + * @param screenshareType (OPTIONAL) The layout type to use when there is a screen-sharing stream in the + * session. Note if you set this property, then `initialLayout` must be set to [ScreenLayoutType.BEST_FIT] + * and you must leave the `stylesheet` property unset (null). + * + * @param stylesheet (OPTIONAL) The CSS stylesheet to use for the broadcast. If you set this property, + * then `initialLayout` must be set to [ScreenLayoutType.CUSTOM]. + * + * @return The updated broadcast builder. + */ +fun Broadcast.Builder.layout(initialLayout: ScreenLayoutType = ScreenLayoutType.BEST_FIT, screenshareType: ScreenLayoutType? = null, stylesheet: String? = null): Broadcast.Builder = layout(streamCompositionLayout(initialLayout, screenshareType, stylesheet)) diff --git a/src/main/kotlin/com/vonage/client/kt/Voice.kt b/src/main/kotlin/com/vonage/client/kt/Voice.kt index 6321dcb..61a9651 100644 --- a/src/main/kotlin/com/vonage/client/kt/Voice.kt +++ b/src/main/kotlin/com/vonage/client/kt/Voice.kt @@ -15,103 +15,328 @@ */ package com.vonage.client.kt +import com.vonage.client.users.channels.Websocket import com.vonage.client.voice.* import com.vonage.client.voice.ncco.* -import java.net.URI import java.time.Instant import java.util.* +/** + * Implementation of the [Voice API](https://developer.vonage.com/en/api/voice). + * + * *Authentication method:* JWT. + */ class Voice internal constructor(private val client: VoiceClient) { + /** + * Call this method to work with an existing call. + * + * @param callId UUID of the call to work with. + */ fun call(callId: String): ExistingCall = ExistingCall(callId) + /** + * Class for working with an existing call. + * + * @property id The call ID. + */ inner class ExistingCall internal constructor(id: String): ExistingResource(id) { + /** + * Get information about the call. + * + * @return Details of the call. + */ fun info(): CallInfo = client.getCallDetails(id) + /** + * End the call. + */ fun hangup(): Unit = client.terminateCall(id) + /** + * Mute the call. The other party will not be able to hear this call. + */ fun mute(): Unit = client.muteCall(id) + /** + * Unmute the call. The other party will be able to hear this call again. + */ fun unmute(): Unit = client.unmuteCall(id) + /** + * Earmuff the call. This call will not be able to hear audio. + */ fun earmuff(): Unit = client.earmuffCall(id) + /** + * Unearmuff the call. This call will be able to hear audio again. + */ fun unearmuff(): Unit = client.unearmuffCall(id) + /** + * Transfer the call using an NCCO. + * + * @param actions The actions to perform after the transfer. + */ fun transfer(vararg actions: Action): Unit = client.transferCall(id, Ncco(actions.asList())) + /** + * Transfer the call using an answer URL. + * + * @param nccoUrl URL of the endpoint that will return the NCCO to execute after the transfer. + */ fun transfer(nccoUrl: String): Unit = client.transferCall(id, nccoUrl) - fun transfer(nccoUrl: URI): Unit = transfer(nccoUrl.toString()) - + /** + * Play DTMF tones into the call. + * + * @param digits The digits to send. Valid characters are `0-9`, `#`, `*` and `p`, + * which indicates a short pause between tones. + * + * @return The DTMF status and call leg ID. + */ fun sendDtmf(digits: String): DtmfResponse = client.sendDtmf(id, digits) + /** + * Play an audio file to the call. + * + * @param url The publicly accessible URL of the audio file to play. + * @param loop The number of times to loop the audio file. + * @param level The volume level at which to play the audio file, between -1 (quietest) and 1 (loudest). + * + * @return The stream status and call leg ID. + */ fun streamAudio(streamUrl: String, loop: Int = 1, level: Double = 0.0): StreamResponse = client.startStream(id, streamUrl, loop, level) + /** + * Stop playing audio into the call. + * + * @return The stream status and call leg ID. + */ fun stopStream(): StreamResponse = client.stopStream(id) + /** + * Speak text into the call. + * + * @param text The text to speak. + * @param properties (OPTIONAL) Additional properties for the talk action. + * + * @return The talk status and call leg ID. + */ fun startTalk(text: String, properties: (TalkPayload.Builder.() -> Unit) = {}): TalkResponse = client.startTalk(id, TalkPayload.builder(text).apply(properties).build()) + /** + * Stop the Text-to-Speech (TTS). + * + * @return The talk status and call leg ID. + */ fun stopTalk(): TalkResponse = client.stopTalk(id) } + /** + * Retrieve details of your calls. + * + * @param filter (OPTIONAL) A lambda function for specifying the parameters to narrow down the results. + */ fun listCalls(filter: (CallsFilter.Builder.() -> Unit)? = null): CallInfoPage = if (filter == null) client.listCalls() else client.listCalls(CallsFilter.builder().apply(filter).build()) - fun createCall(call: Call.Builder.() -> Unit): CallEvent = - client.createCall(Call.builder().apply(call).build()) + /** + * Initiate an outbound call. + * + * @param properties A lambda function for specifying the call's parameters. + * + * @return Details of the created call. + */ + fun createCall(properties: Call.Builder.() -> Unit): CallEvent = + client.createCall(Call.builder().apply(properties).build()) } -fun CallsFilter.Builder.dateStart(dateStart: String): CallsFilter.Builder = - dateStart(Date.from(Instant.parse(dateStart))) +/** + * Sets the start date for the [Voice.listCalls] filter. + * + * @param dateStart ISO-8601 timestamp start retrieving calls from. + * + * @return The updated [CallsFilter.Builder]. + */ +fun CallsFilter.Builder.dateStart(dateStart: Instant): CallsFilter.Builder = dateStart(Date.from(dateStart)) -fun CallsFilter.Builder.dateEnd(dateEnd: String): CallsFilter.Builder = - dateEnd(Date.from(Instant.parse(dateEnd))) +/** + * Sets the end date for the [Voice.listCalls] filter. + * + * @param dateStart ISO-8601 timestamp to stop retrieving calls from. + * + * @return The updated [CallsFilter.Builder]. + */ +fun CallsFilter.Builder.dateEnd(dateEnd: Instant): CallsFilter.Builder = dateEnd(Date.from(dateEnd)) +/** + * Configure the behavior of Advanced Machine Detection. This overrides [Call.Builder#machineDetection] setting + * and is a premium feature, so you cannot set both. + * + * @param amd A lambda function for configuring the Advanced Machine Detection settings. + * + * @return The updated [Call.Builder]. + */ fun Call.Builder.advancedMachineDetection(amd: AdvancedMachineDetection.Builder.() -> Unit = {}): Call.Builder = advancedMachineDetection(AdvancedMachineDetection.builder().apply(amd).build()) +/** + * Configure the behavior of Automatic Speech Recognition to enable speech input. This setting is mutually + * exclusive with [Call.Builder#dtmf], so you must provide one or the other for receiving input from the callee. + * + * @param settings (OPTIONAL) A lambda function for configuring the speech recognition settings. + * + * @return The updated [InputAction.Builder]. + */ fun InputAction.Builder.speech(settings: SpeechSettings.Builder.() -> Unit = {}): InputAction.Builder = speech(SpeechSettings.builder().apply(settings).build()) +/** + * Configure the behavior of Dual-Tone Multi-Frequency (DTMF) input. This setting is mutually exclusive with + * [Call.Builder#speech], so you must provide one or the other for receiving input from the callee. + * + * @param settings (OPTIONAL) A lambda function for configuring the DTMF settings. + * + * @return The updated [InputAction.Builder]. + */ fun InputAction.Builder.dtmf(settings: DtmfSettings.Builder.() -> Unit = {}): InputAction.Builder = dtmf(DtmfSettings.builder().apply(settings).build()) +/** + * Configure the behaviour of call recording transcription. If present (even if all settings are default), + * transcription is activated. The [ConversationAction.Builder.record] parameter must also be set to `true`. + * + * @param settings (OPTIONAL) A lambda function for configuring the transcription settings. + * + * @return The updated [ConversationAction.Builder]. + */ fun ConversationAction.Builder.transcription(settings: TranscriptionSettings.Builder.() -> Unit = {}): ConversationAction.Builder = transcription(TranscriptionSettings.builder().apply(settings).build()) +/** + * Configure the behaviour of call recording transcription. Calling this method activates transcription. + * + * @param settings (OPTIONAL) A lambda function for configuring the transcription settings. + * + * @return The updated [RecordAction.Builder]. + */ fun RecordAction.Builder.transcription(settings: TranscriptionSettings.Builder.() -> Unit = {}): RecordAction.Builder = transcription(TranscriptionSettings.builder().apply(settings).build()) +/** + * Configure the behavior of Advanced Machine Detection. This overrides [ConnectAction.Builder#machineDetection] + * setting and is a premium feature, so you cannot set both. + * + * @param amd A lambda function for configuring the Advanced Machine Detection settings. + * + * @return The updated [ConnectAction.Builder]. + */ fun ConnectAction.Builder.advancedMachineDetection(amd: AdvancedMachineDetection.Builder.() -> Unit = {}): ConnectAction.Builder = advancedMachineDetection(AdvancedMachineDetection.builder().apply(amd).build()) +/** + * Builds an NCCO action to record the call. + * + * @param properties (OPTIONAL) A lambda function for configuring parameters of the record action. + * + * @return A new [RecordAction] with the specified properties. + */ fun recordAction(properties: RecordAction.Builder.() -> Unit = {}): RecordAction = RecordAction.builder().apply(properties).build() +/** + * Builds an NCCO action to play Text-to-Speech into the call. + * + * @param text A string of up to 1,500 characters (excluding SSML tags) containing the message to be synthesized in + * the Call or Conversation. A single comma in text adds a short pause to the synthesized speech. To add a longer + * pause a break tag needs to be used in SSML. To use SSML tags, you must enclose the text in a `speak` element. + * + * @param properties (OPTIONAL) A lambda function for configuring additional parameters of the TTS action. + * + * @return A new [TalkAction] with the specified properties. + */ fun talkAction(text: String, properties: TalkAction.Builder.() -> Unit = {}): TalkAction = TalkAction.builder(text).apply(properties).build() +/** + * Builds an NCCO action to play an audio stream into the call. + * + * @param streamUrl The URL of the audio stream to play. + * + * @param properties (OPTIONAL) A lambda function for configuring additional parameters of the stream action. + * + * @return A new [StreamAction] with the specified properties. + */ fun streamAction(streamUrl: String, properties: StreamAction.Builder.() -> Unit = {}): StreamAction = StreamAction.builder(streamUrl).apply(properties).build() +/** + * Builds an NCCO action to send custom events to a configured webhook. + * + * @param eventUrl The URL to send events to. If you return an NCCO when you receive a notification, + * it will replace the current NCCO. + * + * @param payload A map of key-value pairs to send as the event payload. + * + * @param eventMethod (OPTIONAL) The HTTP method to use when sending the event. + * + * @return A new [NotifyAction] with the specified properties. + */ fun notifyAction(eventUrl: String, payload: Map, eventMethod: EventMethod? = null): NotifyAction = NotifyAction.builder(payload, eventUrl).eventMethod(eventMethod).build() -fun inputAction(properties: InputAction.Builder.() -> Unit = {}): InputAction = +/** + * Builds an NCCO action for gathering input from the call via DTMF or speech recognition. + * + * @param properties A lambda function for configuring the input action parameters. + * + * @return A new [InputAction] with the specified properties. + */ +fun inputAction(properties: InputAction.Builder.() -> Unit): InputAction = InputAction.builder().apply(properties).build() +/** + * Builds an NCCO action to enable hosting conference calls, while preserving the communication context. + * + * @param name The name of the conversation. + * + * @param properties (OPTIONAL) A lambda function for configuring the Conversation action parameters. + * + * @return A new [ConversationAction] with the specified properties. + */ fun conversationAction(name: String, properties: ConversationAction.Builder.() -> Unit = {}): ConversationAction = ConversationAction.builder(name).apply(properties).build() +/** + * Builds an NCCO action to connect the call to another destination. This is the underlying method that other + * `connectTo*` methods in [Voice] use to build their actions. It is recommended to use those instead. + * + * @param endpoint The endpoint to connect the call to. + * + * @param properties (OPTIONAL) A lambda function for configuring the connect action parameters. + * + * @return A new [ConnectAction] with the specified properties. + */ fun connectAction(endpoint: com.vonage.client.voice.ncco.Endpoint, properties: ConnectAction.Builder.() -> Unit = {}): ConnectAction = ConnectAction.builder(endpoint).apply(properties).build() +/** + * Builds an NCCO action to connect the call to a PSTN number. + * + * @param number The MSISDN to connect the call to, in E.164 format. + * @param dtmfAnswer (OPTIONAL) The DTMF digits to send when the call is answered. + * @param onAnswerUrl (OPTIONAL) The URL to fetch a new NCCO from when the call is answered. + * @param onAnswerRingback (OPTIONAL) The URL to fetch a ringback tone from when the call is answered. + * @param properties (OPTIONAL) A lambda function for additional configuration of the connect action parameters. + * + * @return A new [ConnectAction] with the [com.vonage.client.voice.ncco.PhoneEndpoint] and specified properties. + */ fun connectToPstn(number: String, dtmfAnswer: String? = null, onAnswerUrl: String? = null, onAnswerRingback: String? = null, properties: ConnectAction.Builder.() -> Unit = {}) : ConnectAction = @@ -119,18 +344,55 @@ fun connectToPstn(number: String, dtmfAnswer: String? = null, .dtmfAnswer(dtmfAnswer).onAnswer(onAnswerUrl, onAnswerRingback).build(), properties ) +/** + * Builds an NCCO action to connect the call to a Vonage Business Communications (VBC) extension. + * + * @param extension The VBC extension number to connect the call to. + * + * @param properties (OPTIONAL) A lambda function for additional configuration of the connect action parameters. + * + * @return A new [ConnectAction] with the [com.vonage.client.voice.ncco.VbcEndpoint] and specified properties. + */ fun connectToVbc(extension: String, properties: ConnectAction.Builder.() -> Unit = {}) : ConnectAction = connectAction(com.vonage.client.voice.ncco.VbcEndpoint.builder(extension).build(), properties) +/** + * Builds an NCCO action to connect the call to an RTC capable application. + * + * @param user The username to connect the call to. + * @param properties (OPTIONAL) A lambda function for additional configuration of the connect action parameters. + * + * @return A new [ConnectAction] with the [com.vonage.client.voice.ncco.AppEndpoint] and specified properties. + */ fun connectToApp(user: String, properties: ConnectAction.Builder.() -> Unit = {}) : ConnectAction = connectAction(com.vonage.client.voice.ncco.AppEndpoint.builder(user).build(), properties) -fun connectToWebsocket(uri: String, contentType: String, headers: Map? = null, +/** + * Builds an NCCO action to connect the call to a WebSocket endpoint. + * + * @param uri The URI of the WebSocket to connect to. + * @param contentType The internet media type for the audio you are streaming. + * @param headers (OPTIONAL) A map of custom metadata to send with the WebSocket connection. + * @param properties (OPTIONAL) A lambda function for additional configuration of the connect action parameters. + * + * @return A new [ConnectAction] with the [com.vonage.client.voice.ncco.WebSocketEndpoint] and specified properties. + */ +fun connectToWebsocket(uri: String, contentType: Websocket.ContentType, headers: Map? = null, properties: ConnectAction.Builder.() -> Unit = {}) : ConnectAction = - connectAction(com.vonage.client.voice.ncco.WebSocketEndpoint.builder(uri, contentType) + connectAction(com.vonage.client.voice.ncco.WebSocketEndpoint.builder(uri, contentType.toString()) .headers(headers).build(), properties ) +/** + * Builds an NCCO action to connect the call to a SIP endpoint. + * + * @param uri The URI of the SIP endpoint to connect to. + * @param customHeaders (OPTIONAL) A map of custom metadata to send with the SIP connection. + * @param userToUserHeader (OPTIONAL) A string to send as the User-to-User header, as per RFC 7433. + * @param properties (OPTIONAL) A lambda function for additional configuration of the connect action parameters. + * + * @return A new [ConnectAction] with the [com.vonage.client.voice.ncco.SipEndpoint] and specified properties. + */ fun connectToSip(uri: String, customHeaders: Map? = null, userToUserHeader: String? = null, properties: ConnectAction.Builder.() -> Unit = {}) : ConnectAction { val builder = com.vonage.client.voice.ncco.SipEndpoint.builder(uri) @@ -143,19 +405,57 @@ fun connectToSip(uri: String, customHeaders: Map? = null, userToUse return connectAction(builder.build(), properties) } +/** + * Sets the call destination to a Public Switched Telephone Network (PSTN) or mobile number. + * + * @param number The MSISDN to call, in E.164 format. + * @param dtmfAnswer (OPTIONAL) The DTMF digits to send when the call is answered. + * + * @return The updated [Call.Builder]. + */ fun Call.Builder.toPstn(number: String, dtmfAnswer: String? = null): Call.Builder = to(com.vonage.client.voice.PhoneEndpoint(number, dtmfAnswer)) +/** + * Sets the call destination to a SIP URI. + * + * @param uri URI of the SIP endpoint to call. + * @param customHeaders (OPTIONAL) A map of custom metadata to send with the SIP connection. + * @param userToUserHeader (OPTIONAL) A string to send as the User-to-User header, as per RFC 7433. + * + * @return The updated [Call.Builder]. + */ fun Call.Builder.toSip(uri: String, customHeaders: Map? = null, userToUserHeader: String? = null): Call.Builder = to(com.vonage.client.voice.SipEndpoint(uri, customHeaders, userToUserHeader)) -fun Call.Builder.toWebSocket(uri: String, contentType: String? = null, +/** + * Sets the call destination to a WebSocket endpoint. + * + * @param uri The URI of the WebSocket to connect to. + * @param contentType The internet media type for the audio you are streaming. + * @param headers (OPTIONAL) A map of custom metadata to send with the WebSocket connection. + */ +fun Call.Builder.toWebSocket(uri: String, contentType: Websocket.ContentType, headers: Map? = null): Call.Builder = - to(com.vonage.client.voice.WebSocketEndpoint(uri, contentType, headers)) + to(com.vonage.client.voice.WebSocketEndpoint(uri, contentType.toString(), headers)) +/** + * Sets the call destination to a Vonage Business Communications (VBC) extension. + * + * @param extension The VBC extension number to call. + * + * @return The updated [Call.Builder]. + */ fun Call.Builder.toVbc(extension: String): Call.Builder = to(com.vonage.client.voice.VbcEndpoint(extension)) +/** + * Sets the call destination to an RTC capable application user. + * + * @param user The username of the application user to call. + * + * @return The updated [Call.Builder]. + */ fun Call.Builder.toApp(user: String): Call.Builder = to(com.vonage.client.voice.AppEndpoint(user)) diff --git a/src/main/kotlin/com/vonage/client/kt/Vonage.kt b/src/main/kotlin/com/vonage/client/kt/Vonage.kt index 5a5080a..5a38c78 100644 --- a/src/main/kotlin/com/vonage/client/kt/Vonage.kt +++ b/src/main/kotlin/com/vonage/client/kt/Vonage.kt @@ -18,32 +18,154 @@ package com.vonage.client.kt import com.vonage.client.HttpConfig import com.vonage.client.VonageClient -class Vonage(init: VonageClient.Builder.() -> Unit) { - private val client : VonageClient = VonageClient.builder().apply(init).build() +/** + * Entry point for the SDK. This class provides access to all the Vonage APIs via its properties. + * The constructor takes a lambda that configures the client. At a minimum, you must provide + * your account credentials (API key and secret) and/or application ID and private key depending + * on which APIs you plan to use. It is recommended that you set the parameters for both + * authentication methods to maximise compatibility. + * + * Every sub-client for interacting with each API is exposed as a property of this class. Those classes + * document their accepted / preferred authentication type(s). For JWT authentication, you will need to + * provide the application ID and private key path. For API key and secret authentication, you will need + * to provide the API key and secret. These are specified on the [VonageClient.Builder] when initialising + * this class using the provided lambda function. + * + * @param config The configuration lambda, where you provide your Vonage account credentials. + */ +class Vonage(config: VonageClient.Builder.() -> Unit) { + private val client : VonageClient = VonageClient.builder().apply(config).build() + + /** + * Access to the Vonage Account API. + * + * @return The [Account] client. + */ val account = Account(client.accountClient) + + /** + * Access to the Vonage Application API. + * + * @return The [Application] client. + */ val application = Application(client.applicationClient) + + /** + * Access to the Vonage Conversion API. + * + * @return The [Conversion] client. + */ val conversion = Conversion(client.conversionClient) + + /** + * Access to the Vonage Messages API. + * + * @return The [Messages] client. + */ val messages = Messages(client.messagesClient) + + /** + * Access to the Vonage Number Insight API. + * + * @return The [NumberInsight] client. + */ val numberInsight = NumberInsight(client.insightClient) + + /** + * Access to the Vonage Numbers API. + * + * @return The [Numbers] client. + */ val numbers = Numbers(client.numbersClient) + + /** + * Access to the CAMARA Number Verification API. + * + * @return The [NumberVerification] client. + */ val numberVerification = NumberVerification(client.numberVerificationClient) + + /** + * Access to the Vonage Redact API. + * + * @return The [Redact] client. + */ val redact = Redact(client.redactClient) + + /** + * Access to the CAMARA SIM Swap API. + * + * @return The [SimSwap] client. + */ val simSwap = SimSwap(client.simSwapClient) + + /** + * Access to the Vonage SMS API. For more channels and message types, + * we recommend using the Messages API instead. + * + * @return The [Sms] client. + */ val sms = Sms(client.smsClient) + + /** + * Access to the Vonage Subaccounts API. You must register explicitly to be able to use this. + * + * @return The [Subaccounts] client. + */ val subaccounts = Subaccounts(client.subaccountsClient) + + /** + * Access to the Vonage Users API. + * + * @return The [Users] client. + */ val users = Users(client.usersClient) + + /** + * Access to the Vonage Verify v2 API. + * + * @return The [Verify] client. + */ val verify = Verify(client.verify2Client) + + /** + * Access to the Vonage Verify v1 API. + * + * @return The [VerifyLegacy] client. + */ val verifyLegacy = VerifyLegacy(client.verifyClient) + + /** + * Access to the Vonage Video API. + * + * @return The [Video] client. + */ val video = Video(client.videoClient) + + /** + * Access to the Vonage Voice API. + * + * @return The [Voice] client. + */ val voice = Voice(client.voiceClient) } +/** + * Use environment variables to populate authentication parameters. + * This method will look for the following environment variables and set them on the builder if present: + * + * - **VONAGE_API_KEY**: Vonage account API key. + * - **VONAGE_API_SECRET**: Vonage account API secret. + * - **VONAGE_SIGNATURE_SECRET**: Vonage account signature secret. + * - **VONAGE_APPLICATION_ID**: Vonage application ID. + * - **VONAGE_PRIVATE_KEY_PATH**: Path to the private key file of the application. + */ fun VonageClient.Builder.authFromEnv(): VonageClient.Builder { - val apiKey = env("VONAGE_API_KEY") - val apiSecret = env("VONAGE_API_SECRET") - val signatureSecret = env("VONAGE_SIGNATURE_SECRET") - val applicationId = env("VONAGE_APPLICATION_ID") - val privateKeyPath = env("VONAGE_PRIVATE_KEY_PATH") + val apiKey = System.getenv("VONAGE_API_KEY") + val apiSecret = System.getenv("VONAGE_API_SECRET") + val signatureSecret = System.getenv("VONAGE_SIGNATURE_SECRET") + val applicationId = System.getenv("VONAGE_APPLICATION_ID") + val privateKeyPath = System.getenv("VONAGE_PRIVATE_KEY_PATH") if (apiKey != null) apiKey(apiKey) if (apiSecret != null) apiSecret(apiSecret) if (signatureSecret != null) signatureSecret(signatureSecret) @@ -52,7 +174,11 @@ fun VonageClient.Builder.authFromEnv(): VonageClient.Builder { return this } +/** + * Optional HTTP configuration options for the client. + * + * @param init The config options lambda. + * @return The builder. + */ fun VonageClient.Builder.httpConfig(init: HttpConfig.Builder.() -> Unit): VonageClient.Builder = httpConfig(HttpConfig.builder().apply(init).build()) - -private fun env(variable: String) : String? = System.getenv(variable) diff --git a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt index ff3488a..763380b 100644 --- a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt @@ -27,6 +27,7 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import com.fasterxml.jackson.databind.ObjectMapper +import com.vonage.client.users.channels.Websocket import java.net.URI import java.net.URLEncoder import java.nio.charset.StandardCharsets @@ -66,7 +67,8 @@ abstract class AbstractTest { protected val dtmf = "p*123#" protected val sipUri = "sip:rebekka@sip.example.com" protected val websocketUri = "wss://example.com/socket" - protected val wsContentType = "audio/l16;rate=8000" + protected val wsContentTypeStr = "audio/l16;rate=8000" + protected val wsContentType = Websocket.ContentType.AUDIO_L16_8K protected val clientRef = "my-personal-reference" protected val textHexEncoded = "48656c6c6f2c20576f726c6421" protected val entityId = "1101407360000017170" @@ -104,11 +106,11 @@ abstract class AbstractTest { ) val vonage = Vonage { - apiKey(apiKey); apiSecret(apiSecret); - applicationId(applicationId); privateKeyPath(privateKeyPath) - httpConfig { + authFromEnv(); httpConfig { baseUri("http://localhost:$port") } + apiKey(apiKey); apiSecret(apiSecret); signatureSecret(null) + applicationId(applicationId); privateKeyPath(privateKeyPath) } @BeforeEach diff --git a/src/test/kotlin/com/vonage/client/kt/AccountTest.kt b/src/test/kotlin/com/vonage/client/kt/AccountTest.kt index 378a8c8..1914763 100644 --- a/src/test/kotlin/com/vonage/client/kt/AccountTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/AccountTest.kt @@ -21,7 +21,7 @@ import org.junit.jupiter.api.assertThrows import kotlin.test.* class AccountTest : AbstractTest() { - private val account = vonage.account + private val client = vonage.account private val authType = AuthType.API_KEY_SECRET_HEADER private val secretId = "ad6dc56f-07b5-46e1-a527-85530e625800" private val trx = "8ef2447e69604f642ae59363aa5f781b" @@ -30,8 +30,8 @@ class AccountTest : AbstractTest() { private val secretsAltUrl = "${baseUrl}s/$apiKey2/secrets" private val secretUrl = "$secretsUrl/$secretId" private val altSecretUrl = "$secretsAltUrl/$secretId" - private val secretsNoApiKey = account.secrets() - private val secretsWithApiKey = account.secrets(apiKey2) + private val secretsNoApiKey = client.secrets() + private val secretsWithApiKey = client.secrets(apiKey2) private val errorCode = 401 private val secretResponse = linksSelfHref(secretUrl) + mapOf( "id" to secretId, @@ -60,7 +60,7 @@ class AccountTest : AbstractTest() { ) ) - val response = invocation(account) + val response = invocation(client) assertNotNull(response) assertEquals(moCallbackUrl, response.incomingSmsUrl) assertEquals(drCallbackUrl, response.deliveryReceiptUrl) @@ -159,7 +159,7 @@ class AccountTest : AbstractTest() { ) ) - val response = account.getBalance() + val response = client.getBalance() assertNotNull(response) assertEquals(value, response.value) assertEquals(autoReload, response.isAutoReload) @@ -172,7 +172,7 @@ class AccountTest : AbstractTest() { status = errorCode, authType = authType, expectedResponseParams = errorResponse ) - assertThrows { account.getBalance() } + assertThrows { client.getBalance() } } @Test @@ -186,7 +186,7 @@ class AccountTest : AbstractTest() { "error-code-label" to "success" ) ) - account.topUp(trx) + client.topUp(trx) } @Test @@ -198,7 +198,7 @@ class AccountTest : AbstractTest() { expectedResponseParams = errorResponse ) - assertThrows { account.topUp(trx) } + assertThrows { client.topUp(trx) } } @Test diff --git a/src/test/kotlin/com/vonage/client/kt/ApplicationTest.kt b/src/test/kotlin/com/vonage/client/kt/ApplicationTest.kt index 00a9a8e..eb5d98a 100644 --- a/src/test/kotlin/com/vonage/client/kt/ApplicationTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/ApplicationTest.kt @@ -24,9 +24,9 @@ import com.vonage.client.common.Webhook import kotlin.test.* class ApplicationTest : AbstractTest() { - private val ac = vonage.application + private val client = vonage.application private val authType = AuthType.API_KEY_SECRET_HEADER - private val existingApplication = ac.application(testUuid) + private val existingApplication = client.application(testUuid) private val baseUrl = "/v2/applications" private val appUrl = "$baseUrl/$testUuid" private val answerUrl = "$exampleUrlBase/answer" @@ -266,18 +266,18 @@ class ApplicationTest : AbstractTest() { @Test fun `list all applications`() { - assertListApplications { ac.listAll() } + assertListApplications { client.listAll() } } @Test fun `list applications no filter`() { - assertListApplications { ac.list() } + assertListApplications { client.list() } } @Test fun `list applications all filters`() { assertListApplications(mapOf("page" to page, "page_size" to pageSize)) { - ac.list(page, pageSize) + client.list(page, pageSize) } } @@ -316,7 +316,7 @@ class ApplicationTest : AbstractTest() { expectedRequestParams = advancedApplicationRequest, expectedResponseParams = fullApplicationResponse ) - assertEqualsFullApplication(ac.create { + assertEqualsFullApplication(client.create { name(name) publicKey(publicKey) voice { @@ -369,7 +369,7 @@ class ApplicationTest : AbstractTest() { }) assert401ApiResponseException(baseUrl, HttpMethod.POST) { - ac.create { name(name) } + client.create { name(name) } } } } \ No newline at end of file diff --git a/src/test/kotlin/com/vonage/client/kt/ConversionTest.kt b/src/test/kotlin/com/vonage/client/kt/ConversionTest.kt index b4596a4..3876bf8 100644 --- a/src/test/kotlin/com/vonage/client/kt/ConversionTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/ConversionTest.kt @@ -18,7 +18,7 @@ package com.vonage.client.kt import kotlin.test.* class ConversionTest : AbstractTest() { - private val conversionClient = vonage.conversion + private val client = vonage.conversion private val smsEndpoint = "sms" private val voiceEndpoint = "voice" @@ -33,27 +33,27 @@ class ConversionTest : AbstractTest() { fun `submit sms conversion with timestamp`() { val delivered = true mockSuccess(smsMessageId, smsEndpoint, delivered, true) - conversionClient.convertSms(smsMessageId, delivered, timestampDate.toInstant()) + client.convertSms(smsMessageId, delivered, timestampDate.toInstant()) } @Test fun `submit sms conversion without timestamp`() { val delivered = false mockSuccess(smsMessageId, smsEndpoint, delivered, false) - conversionClient.convertSms(smsMessageId, delivered) + client.convertSms(smsMessageId, delivered) } @Test fun `submit voice conversion with timestamp`() { val delivered = false mockSuccess(callIdStr, voiceEndpoint, delivered, true) - conversionClient.convertVoice(callIdStr, delivered, timestamp) + client.convertVoice(callIdStr, delivered, timestamp) } @Test fun `submit voice conversion without timestamp`() { val delivered = true mockSuccess(callIdStr, voiceEndpoint, delivered, false) - conversionClient.convertVoice(callIdStr, delivered) + client.convertVoice(callIdStr, delivered) } } \ No newline at end of file diff --git a/src/test/kotlin/com/vonage/client/kt/MessagesTest.kt b/src/test/kotlin/com/vonage/client/kt/MessagesTest.kt index 50cb21d..76c1321 100644 --- a/src/test/kotlin/com/vonage/client/kt/MessagesTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/MessagesTest.kt @@ -23,13 +23,12 @@ import com.vonage.client.messages.whatsapp.Policy import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.net.URI -import java.time.Instant import java.util.* import kotlin.test.assertEquals import kotlin.test.assertNotNull class MessagesTest : AbstractTest() { - private val messagesClient = vonage.messages + private val client = vonage.messages private val sendUrl = "/v1/messages" private val messageUuid = testUuid private val mmsChannel = "mms" @@ -48,7 +47,7 @@ class MessagesTest : AbstractTest() { expectedUrl = sendUrl, expectedRequestParams = expectedBodyParams, status = status, expectedResponseParams = expectedResponseParams ) - assertEquals(messageUuid, messagesClient.send(req)) + assertEquals(messageUuid, client.send(req)) } private fun baseBody(messageType: String, channel: String): Map = @@ -84,7 +83,7 @@ class MessagesTest : AbstractTest() { @Test fun `send message exception responses`() { assertApiResponseException(sendUrl, HttpMethod.POST) { - messagesClient.send(smsText { + client.send(smsText { from(altNumber); to(toNumber); text(text) }) } diff --git a/src/test/kotlin/com/vonage/client/kt/NumberInsightTest.kt b/src/test/kotlin/com/vonage/client/kt/NumberInsightTest.kt index 7841f09..a1e828d 100644 --- a/src/test/kotlin/com/vonage/client/kt/NumberInsightTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/NumberInsightTest.kt @@ -22,7 +22,7 @@ import java.math.BigDecimal import kotlin.test.* class NumberInsightTest : AbstractTest() { - private val niClient = vonage.numberInsight + private val client = vonage.numberInsight private val cnam = true private val realTimeData = true private val statusMessage = "Success" @@ -230,48 +230,48 @@ class NumberInsightTest : AbstractTest() { @Test fun `basic insight required params`() { mockInsight(InsightType.BASIC, false) - assertBasicResponse(niClient.basic(toNumber)) + assertBasicResponse(client.basic(toNumber)) } @Test fun `basic insight all params`() { mockInsight(InsightType.BASIC, true) - assertBasicResponse(niClient.basic(toNumber, countryCode)) + assertBasicResponse(client.basic(toNumber, countryCode)) } @Test fun `standard insight required params`() { mockInsight(InsightType.STANDARD, false) - assertStandardResponse(niClient.standard(toNumber)) + assertStandardResponse(client.standard(toNumber)) } @Test fun `standard insight all params`() { mockInsight(InsightType.STANDARD, true) - assertStandardResponse(niClient.standard(toNumber, countryCode, cnam)) + assertStandardResponse(client.standard(toNumber, countryCode, cnam)) } @Test fun `advanced insight required params`() { mockInsight(InsightType.ADVANCED, false) - assertAdvancedResponse(niClient.advanced(toNumber)) + assertAdvancedResponse(client.advanced(toNumber)) } @Test fun `advanced insight all params`() { mockInsight(InsightType.ADVANCED, true) - assertAdvancedResponse(niClient.advanced(toNumber, countryCode, cnam, realTimeData)) + assertAdvancedResponse(client.advanced(toNumber, countryCode, cnam, realTimeData)) } @Test fun `advanced async insight required params`() { mockInsight(InsightType.ADVANCED_ASYNC, false) - niClient.advancedAsync(toNumber, callbackUrl) + client.advancedAsync(toNumber, callbackUrl) } @Test fun `advanced async insight all params`() { mockInsight(InsightType.ADVANCED_ASYNC, true) - niClient.advancedAsync(toNumber, callbackUrl, countryCode, cnam) + client.advancedAsync(toNumber, callbackUrl, countryCode, cnam) } } \ No newline at end of file diff --git a/src/test/kotlin/com/vonage/client/kt/NumberVerificationTest.kt b/src/test/kotlin/com/vonage/client/kt/NumberVerificationTest.kt index 7ed0e7f..2b486ff 100644 --- a/src/test/kotlin/com/vonage/client/kt/NumberVerificationTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/NumberVerificationTest.kt @@ -20,7 +20,7 @@ import java.net.URLEncoder import kotlin.test.* class NumberVerificationTest : AbstractTest() { - private val nvClient = vonage.numberVerification + private val client = vonage.numberVerification private val nvCheckUrl = "/camara/number-verification/v031/verify" private val clientAuthUrl = "https://oidc.idp.vonage.com/oauth2/auth" private val redirectUrl = "$exampleUrlBase/nv/redirect" @@ -50,7 +50,7 @@ class NumberVerificationTest : AbstractTest() { expectedResponseParams = if (result != null) mapOf("devicePhoneNumberVerified" to result) else mapOf() ) - assertEquals(result ?: false, invocation(nvClient)) + assertEquals(result ?: false, invocation(client)) } } @@ -62,10 +62,10 @@ class NumberVerificationTest : AbstractTest() { URLEncoder.encode(redirectUrl, "UTF-8") }&response_type=code" - assertEquals(URI.create(expectedUrlStr), nvClient.createVerificationUrl(toNumber, redirectUrl)) + assertEquals(URI.create(expectedUrlStr), client.createVerificationUrl(toNumber, redirectUrl)) val expectedUrlWithState = URI.create("$expectedUrlStr&state=$state") - assertEquals(expectedUrlWithState, nvClient.createVerificationUrl(toNumber, redirectUrl, state)) + assertEquals(expectedUrlWithState, client.createVerificationUrl(toNumber, redirectUrl, state)) } @Test @@ -84,7 +84,7 @@ class NumberVerificationTest : AbstractTest() { @Test fun `verify number relying on cached redirect url`() { - nvClient.createVerificationUrl(altNumber, redirectUrl) + client.createVerificationUrl(altNumber, redirectUrl) assertVerifyNumber { verifyNumberWithCode(toNumber, code) diff --git a/src/test/kotlin/com/vonage/client/kt/SimSwapTest.kt b/src/test/kotlin/com/vonage/client/kt/SimSwapTest.kt index 2d651a0..55a9da4 100644 --- a/src/test/kotlin/com/vonage/client/kt/SimSwapTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/SimSwapTest.kt @@ -19,7 +19,7 @@ import com.vonage.client.auth.camara.FraudPreventionDetectionScope import kotlin.test.* class SimSwapTest : AbstractTest() { - private val simSwapClient = vonage.simSwap + private val client = vonage.simSwap private val baseSimSwapUrl = "/camara/sim-swap/v040" private val checkSimSwapUrl = "$baseSimSwapUrl/check" private val retrieveSimSwapDateUrl = "$baseSimSwapUrl/retrieve-date" @@ -66,7 +66,7 @@ class SimSwapTest : AbstractTest() { expectedRequestParams = phoneNumberMap + mapOf("maxAge" to maxAge), expectedResponseParams = if (result != null) mapOf("swapped" to result) else mapOf() ) - assertEquals(result == true, invocation(simSwapClient)) + assertEquals(result == true, invocation(client)) } } @@ -78,7 +78,7 @@ class SimSwapTest : AbstractTest() { expectedRequestParams = phoneNumberMap, expectedResponseParams = if (includeResponse) mapOf("latestSimChange" to timestampStr) else mapOf() ) - assertEquals(if (includeResponse) timestamp else null, simSwapClient.retrieveSimSwapDate(simSwapNumber)) + assertEquals(if (includeResponse) timestamp else null, client.retrieveSimSwapDate(simSwapNumber)) } @Test diff --git a/src/test/kotlin/com/vonage/client/kt/SmsTest.kt b/src/test/kotlin/com/vonage/client/kt/SmsTest.kt index 04c9514..01fb1d8 100644 --- a/src/test/kotlin/com/vonage/client/kt/SmsTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/SmsTest.kt @@ -22,7 +22,7 @@ import java.math.BigDecimal import kotlin.test.* class SmsTest : AbstractTest() { - private val smsClient = vonage.sms + private val client = vonage.sms private val sendUrl = "/sms/json" private val from = brand private val accountRef = "customer1234" @@ -70,7 +70,7 @@ class SmsTest : AbstractTest() { assertEquals(network, first.network) assertEquals(clientRef, first.clientRef) assertEquals(accountRef, first.accountRef) - assertTrue(smsClient.wasSuccessfullySent(response)) + assertTrue(client.wasSuccessfullySent(response)) } private fun errorStatus(code: Int, text: String) = mapOf("status" to code, "error-text" to text) @@ -78,14 +78,14 @@ class SmsTest : AbstractTest() { @Test fun `send regular text message success required parameters`() { testSuccessSingleMessage(mapOf("from" to from, "to" to toNumber, "text" to text, "type" to "unicode")) { - smsClient.sendText(from, toNumber, text, unicode = true) + client.sendText(from, toNumber, text, unicode = true) } } @Test fun `send unicode text message success required parameters`() { testSuccessSingleMessage(mapOf("from" to from, "to" to toNumber, "text" to text, "type" to "text")) { - smsClient.sendText(from, toNumber, text) + client.sendText(from, toNumber, text) } } @@ -104,7 +104,7 @@ class SmsTest : AbstractTest() { "entity-id" to entityId, "content-id" to contentId )) { - smsClient.sendText(from, toNumber, text, + client.sendText(from, toNumber, text, unicode = false, statusReport = statusReport, ttl = ttl, messageClass = Message.MessageClass.CLASS_1, clientRef = clientRef, contentId = contentId, entityId = entityId, @@ -119,7 +119,7 @@ class SmsTest : AbstractTest() { "from" to from, "to" to toNumber, "type" to "binary", "body" to textHexEncoded, "udh" to udhHex.lowercase() )) { - smsClient.sendBinary(from, toNumber, text.encodeToByteArray(), udhBinary) + client.sendBinary(from, toNumber, text.encodeToByteArray(), udhBinary) } } @@ -140,7 +140,7 @@ class SmsTest : AbstractTest() { "entity-id" to entityId, "content-id" to contentId )) { - smsClient.sendBinary(from, toNumber, text.encodeToByteArray(), udh = udhBinary, + client.sendBinary(from, toNumber, text.encodeToByteArray(), udh = udhBinary, protocolId = protocolId, statusReport = statusReport, ttl = ttl, messageClass = Message.MessageClass.CLASS_2, clientRef = clientRef, contentId = contentId, entityId = entityId, callbackUrl = moCallbackUrl @@ -183,9 +183,9 @@ class SmsTest : AbstractTest() { ) ) ) - val response = smsClient.sendText(from, toNumber, text) + val response = client.sendText(from, toNumber, text) assertNotNull(response) - assertFalse(smsClient.wasSuccessfullySent(response)) + assertFalse(client.wasSuccessfullySent(response)) assertEquals(24, response.size) var offset = 0 diff --git a/src/test/kotlin/com/vonage/client/kt/UsersTest.kt b/src/test/kotlin/com/vonage/client/kt/UsersTest.kt index a9659ca..0bc6960 100644 --- a/src/test/kotlin/com/vonage/client/kt/UsersTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/UsersTest.kt @@ -60,7 +60,7 @@ class UsersTest : AbstractTest() { )), "websocket" to listOf(mapOf( "uri" to websocketUri, - "content-type" to wsContentType, + "content-type" to wsContentTypeStr, "headers" to customData )), "mms" to listOf(mapOf( @@ -120,7 +120,7 @@ class UsersTest : AbstractTest() { assertNotNull(websocket) assertEquals(1, websocket.size) assertEquals(URI.create(websocketUri), websocket[0].uri) - assertEquals(Websocket.ContentType.fromString(wsContentType), websocket[0].contentType) + assertEquals(Websocket.ContentType.fromString(wsContentTypeStr), websocket[0].contentType) assertEquals(customData, websocket[0].headers) val sip = channels.sip @@ -273,7 +273,7 @@ class UsersTest : AbstractTest() { customData(customData) channels( Pstn(toNumber), Sip(sipUri, sipUser, sipPassword), - Websocket(websocketUri, Websocket.ContentType.fromString(wsContentType), customData), + Websocket(websocketUri, Websocket.ContentType.fromString(wsContentTypeStr), customData), Vbc(vbcExt.toInt()), Mms(toNumber), Whatsapp(altNumber), Viber(altNumber), Messenger(messengerId) ) diff --git a/src/test/kotlin/com/vonage/client/kt/VerifyLegacyTest.kt b/src/test/kotlin/com/vonage/client/kt/VerifyLegacyTest.kt index aa7adbc..86aa761 100644 --- a/src/test/kotlin/com/vonage/client/kt/VerifyLegacyTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/VerifyLegacyTest.kt @@ -21,9 +21,9 @@ import java.util.* import kotlin.test.* class VerifyLegacyTest : AbstractTest() { - private val verifyClient = vonage.verifyLegacy + private val client = vonage.verifyLegacy private val requestId = "abcdef0123456789abcdef0123456789" - private val existingRequest = verifyClient.request(requestId) + private val existingRequest = client.request(requestId) private val eventId = "0A00000012345678" private val accountId = "abcdef01" private val payee = "Acme Inc" @@ -45,11 +45,11 @@ class VerifyLegacyTest : AbstractTest() { "request_id" to requestId, "status" to "0" )) - val successParsed = invocation(verifyClient) + val successParsed = invocation(client) assertNotNull(successParsed) assertEquals(requestId, successParsed.requestId) assertEquals(VerifyStatus.OK, successParsed.status) - assertEquals(existingRequest, verifyClient.request(successParsed)) + assertEquals(existingRequest, client.request(successParsed)) val errorText = "Your request is incomplete and missing the mandatory parameter `number`" mockPostQueryParams(expectedUrl, params, expectedResponseParams = mapOf( @@ -58,7 +58,7 @@ class VerifyLegacyTest : AbstractTest() { "error_text" to errorText, "network" to networkCode )) - val failureParsed = invocation(verifyClient) + val failureParsed = invocation(client) assertNotNull(failureParsed) assertEquals(requestId, failureParsed.requestId) assertEquals(VerifyStatus.MISSING_PARAMS, failureParsed.status) @@ -137,7 +137,7 @@ class VerifyLegacyTest : AbstractTest() { ) ) ) - val response = if (single) existingRequest.search() else verifyClient.search(*requestIds) + val response = if (single) existingRequest.info() else client.search(*requestIds) assertNotNull(response) assertNull(response.errorText) assertEquals(3, response.verificationRequests.size) @@ -162,7 +162,7 @@ class VerifyLegacyTest : AbstractTest() { @Test fun `existing request hashCode is based on the requestId`() { assertEquals(requestId.hashCode(), existingRequest.hashCode()) - assertEquals(existingRequest, verifyClient.request(requestId)) + assertEquals(existingRequest, client.request(requestId)) assertEquals(existingRequest, existingRequest) assertFalse(existingRequest.equals(requestId)) } diff --git a/src/test/kotlin/com/vonage/client/kt/VerifyTest.kt b/src/test/kotlin/com/vonage/client/kt/VerifyTest.kt index 04c4dd2..401a3cb 100644 --- a/src/test/kotlin/com/vonage/client/kt/VerifyTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/VerifyTest.kt @@ -23,12 +23,12 @@ import java.util.UUID import kotlin.test.* class VerifyTest : AbstractTest() { - private val verifyClient = vonage.verify + private val client = vonage.verify private val baseUrl = "/v2/verify" private val requestIdStr = "c11236f4-00bf-4b89-84ba-88b25df97315" private val requestId = UUID.fromString(requestIdStr) private val requestIdUrl = "$baseUrl/$requestIdStr" - private val existingRequest = verifyClient.request(requestIdStr) + private val existingRequest = client.request(requestIdStr) private val timeout = 60 private val fraudCheck = false private val sandbox = true @@ -80,7 +80,7 @@ class VerifyTest : AbstractTest() { if (channel == Channel.SILENT_AUTH) mapOf("check_url" to checkUrl) else mapOf() ) - val response = verifyClient.sendVerification(brand) { + val response = client.sendVerification(brand) { when (channel) { Channel.VOICE -> voice(toNumber) Channel.SMS -> sms(toNumber) @@ -154,7 +154,7 @@ class VerifyTest : AbstractTest() { status = 202 ) - val response = verifyClient.sendVerification { + val response = client.sendVerification { brand(brand); clientRef(clientRef); channelTimeout(timeout) fraudCheck(fraudCheck); codeLength(codeLength); locale(locale) silentAuth(toNumber, sandbox, redirectUrl); voice(altNumber); sms(toNumber) { diff --git a/src/test/kotlin/com/vonage/client/kt/VideoTest.kt b/src/test/kotlin/com/vonage/client/kt/VideoTest.kt index 5fc5ef5..1c4ad37 100644 --- a/src/test/kotlin/com/vonage/client/kt/VideoTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/VideoTest.kt @@ -460,10 +460,9 @@ class VideoTest : AbstractTest() { ) ) - val response = client.connectToWebsocket { + val response = existingSession.connectToWebsocket { uri(websocketUri); headers(headers) - sessionId(sessionId); token(token) - streams(streamId, randomUuidStr) + streams(streamId, randomUuidStr); token(token) audioRate(Websocket.AudioRate.L16_16K) } assertNotNull(response) @@ -480,8 +479,8 @@ class VideoTest : AbstractTest() { expectedResponseParams = mapOf("id" to audioConnectorId) ) - val invocation = { client.connectToWebsocket { - uri(websocketUri); sessionId(sessionId); token(token) + val invocation = { existingSession.connectToWebsocket { + uri(websocketUri); token(token) }} val response = invocation() assertNotNull(response) @@ -576,11 +575,10 @@ class VideoTest : AbstractTest() { "streamId" to streamId ) ) - val response = client.sipDial { - sessionId(sessionId); token(token) + val response = existingSession.sipDial { uri(URI.create(sipUri), true) addHeaders(headers); secure(secure) - from(from); video(video) + from(from); video(video); token(token) observeForceMute(observeForceMute) username(userName); password(password) } @@ -599,7 +597,7 @@ class VideoTest : AbstractTest() { ), expectedResponseParams = mapOf("id" to sipCallId) ) - val invocation = { client.sipDial { + val invocation = { existingSession.sipDial { sessionId(sessionId); token(token); uri(URI.create(sipUri), false) } } val response = invocation() @@ -914,7 +912,7 @@ class VideoTest : AbstractTest() { multiBroadcastTag(multiBroadcastTag) maxDuration(maxDuration); maxBitrate(maxBitrate) resolution(broadcastResolution); streamMode(broadcastStreamMode) - layout(ScreenLayoutType.VERTICAL) // This is to get 100% coverage; override below + layout(); layout(ScreenLayoutType.VERTICAL) // This is to get 100% coverage; override below layout(ScreenLayoutType.BEST_FIT, ScreenLayoutType.PIP) hls { dvr(dvr); lowLatency(lowLatency) @@ -1139,9 +1137,8 @@ class VideoTest : AbstractTest() { expectedRequestParams = renderRequestMap, expectedResponseParams = mapOf() ) - val invocation = { client.startRender { - sessionId(sessionId); token(token) - url(mediaUrl); name(renderName) + val invocation = { existingSession.startRender { + token(token); url(mediaUrl); name(renderName) } } assertEqualsEmptyRender(invocation()) assertApiResponseException(renderBaseUrl, HttpMethod.POST, invocation) @@ -1156,7 +1153,7 @@ class VideoTest : AbstractTest() { ), expectedResponseParams = renderResponseMap ) - assertEqualsSampleRender(client.startRender { + assertEqualsSampleRender(existingSession.startRender { url(mediaUrl); maxDuration(maxDuration) resolution(Resolution.HD_LANDSCAPE) sessionId(sessionId); token(token); name(renderName) diff --git a/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt b/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt index d020586..2989418 100644 --- a/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt @@ -18,7 +18,6 @@ package com.vonage.client.kt import com.vonage.client.common.HttpMethod import com.vonage.client.voice.* import com.vonage.client.voice.ncco.* -import java.net.URI import java.util.* import kotlin.test.* @@ -204,7 +203,7 @@ class VoiceTest : AbstractTest() { ) ) - val callEvent = this@VoiceTest.client.createCall(call) + val callEvent = client.createCall(call) assertNotNull(callEvent) assertEquals(callIdStr, callEvent.uuid) assertEquals(callStatus, callEvent.status) @@ -300,7 +299,6 @@ class VoiceTest : AbstractTest() { @Test fun `transfer call with answer url`() { testModifyCall(nccoUrl = onAnswerUrl, invocation = { - existingCall.transfer(URI.create(onAnswerUrl)) existingCall.transfer(onAnswerUrl) }) } @@ -384,17 +382,17 @@ class VoiceTest : AbstractTest() { fun `list calls all filter parameters`() { mockGet(callsBaseUrl, expectedQueryParams = mapOf( "status" to "unanswered", - "date_start" to startTime, - "date_end" to endTime, + "date_start" to startTimeStr, + "date_end" to endTimeStr, "page_size" to pageSize, "record_index" to recordIndex, "order" to "desc", "conversation_uuid" to conversationId ), expectedResponseParams = listCallsResponse) - val callsPage = this@VoiceTest.client.listCalls { + val callsPage = client.listCalls { status(CallStatus.UNANSWERED) - dateStart(startTimeStr); dateEnd(endTimeStr) + dateStart(startTime); dateEnd(endTime) pageSize(pageSize); recordIndex(recordIndex) order(CallOrder.DESCENDING); conversationUuid(conversationId) } @@ -405,7 +403,7 @@ class VoiceTest : AbstractTest() { @Test fun `list calls no filter`() { mockGet(callsBaseUrl, expectedResponseParams = listCallsResponse) - assertEqualsSampleCallsPage(this@VoiceTest.client.listCalls()) + assertEqualsSampleCallsPage(client.listCalls()) } @Test @@ -469,13 +467,13 @@ class VoiceTest : AbstractTest() { @Test fun `create call to WebSocket`() { - val baseMap = mapOf("type" to "websocket", "uri" to websocketUri) + val baseMap = mapOf("type" to "websocket", "uri" to websocketUri, "content-type" to wsContentTypeStr) testCreateCallToSingleEndpoint(baseMap) { - toWebSocket(websocketUri) + toWebSocket(websocketUri, wsContentType) } testCreateCallToSingleEndpoint(baseMap + mapOf( - "content-type" to wsContentType, "headers" to customHeaders + "content-type" to wsContentTypeStr, "headers" to customHeaders )) { toWebSocket(websocketUri, wsContentType, customHeaders) } @@ -512,7 +510,7 @@ class VoiceTest : AbstractTest() { mapOf( "type" to "websocket", "uri" to websocketUri, - "content-type" to wsContentType, + "content-type" to wsContentTypeStr, "headers" to customHeaders ), mapOf( @@ -548,7 +546,7 @@ class VoiceTest : AbstractTest() { com.vonage.client.voice.AppEndpoint(userName), com.vonage.client.voice.PhoneEndpoint(toNumber, dtmf), com.vonage.client.voice.VbcEndpoint(vbcExt), - com.vonage.client.voice.WebSocketEndpoint(websocketUri, wsContentType, customHeaders), + com.vonage.client.voice.WebSocketEndpoint(websocketUri, wsContentTypeStr, customHeaders), com.vonage.client.voice.SipEndpoint(sipUri, customHeaders, userToUserHeader) ) } @@ -571,7 +569,11 @@ class VoiceTest : AbstractTest() { @Test fun `create call with input action required parameters only`() { - testSingleNcco(ncco = inputAction()) + val types = listOf("dtmf", "speech") + testSingleNcco( + additionalParams = mapOf("type" to types), + ncco = inputAction { type(types) } + ) } @Test @@ -645,12 +647,12 @@ class VoiceTest : AbstractTest() { @Test fun `create call with connect to WebSocket ncco`() { testSingleNccoConnect( - mapOf("uri" to websocketUri, "content-type" to wsContentType), + mapOf("uri" to websocketUri, "content-type" to wsContentTypeStr), connectToWebsocket(websocketUri, wsContentType) ) testSingleNccoConnect( - mapOf("uri" to websocketUri, "content-type" to wsContentType, "headers" to customHeaders), + mapOf("uri" to websocketUri, "content-type" to wsContentTypeStr, "headers" to customHeaders), connectToWebsocket(websocketUri, wsContentType, customHeaders) ) } diff --git a/src/test/kotlin/com/vonage/client/kt/VonageTest.kt b/src/test/kotlin/com/vonage/client/kt/VonageTest.kt index c07797d..58317d8 100644 --- a/src/test/kotlin/com/vonage/client/kt/VonageTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/VonageTest.kt @@ -15,14 +15,13 @@ */ package com.vonage.client.kt -import org.junit.jupiter.api.Test import kotlin.test.* class VonageTest { @Test - fun `auth from env`() { - val vonage = Vonage { authFromEnv() } - assertNotNull(vonage) + fun `live testing placeholder`() { + val client = Vonage { authFromEnv(); signatureSecret(null) } + println("Finished") // Place debug breakpoint here } } \ No newline at end of file