Compare commits
2 Commits
master
...
feature/pm
| Author | SHA1 | Date |
|---|---|---|
|
|
e9f8bf30d4 | |
|
|
5f6adc97fe |
|
|
@ -6,7 +6,7 @@ import org.springframework.stereotype.Component
|
|||
import ru.touchin.push.message.provider.dto.request.PushTokenCheck
|
||||
import ru.touchin.push.message.provider.dto.request.PushTokenMessage
|
||||
import ru.touchin.push.message.provider.dto.result.SendPushResult
|
||||
import ru.touchin.push.message.provider.dto.result.SendPushTokenMessageResult
|
||||
import ru.touchin.push.message.provider.dto.result.SendPushTokenMessageTraceableResult
|
||||
import ru.touchin.push.message.provider.enums.PushTokenStatus
|
||||
import ru.touchin.push.message.provider.exceptions.InvalidPushTokenException
|
||||
import ru.touchin.push.message.provider.exceptions.PushMessageProviderException
|
||||
|
|
@ -32,7 +32,7 @@ class FcmClient(
|
|||
fun check(request: PushTokenCheck): PushTokenStatus {
|
||||
val validationRequest = PushTokenMessage(
|
||||
token = request.pushToken,
|
||||
notification = null,
|
||||
pushMessageNotification = null,
|
||||
data = emptyMap()
|
||||
)
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ class FcmClient(
|
|||
return try {
|
||||
val messageId = firebaseMessaging.send(message, dryRun)
|
||||
|
||||
SendPushTokenMessageResult(messageId)
|
||||
SendPushTokenMessageTraceableResult(messageId)
|
||||
} catch (e: FirebaseMessagingException) {
|
||||
throw firebaseMessagingExceptionConverter(e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package ru.touchin.push.message.provider.fcm.converters
|
||||
|
||||
import com.google.firebase.messaging.Notification as FcmNotification
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.push.message.provider.dto.Notification
|
||||
|
||||
@Component
|
||||
class NotificationConverter {
|
||||
|
||||
operator fun invoke(notification: Notification): FcmNotification {
|
||||
return FcmNotification.builder()
|
||||
.setTitle(notification.title)
|
||||
.setBody(notification.description)
|
||||
.setImage(notification.imageUrl)
|
||||
.build()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package ru.touchin.push.message.provider.fcm.converters
|
||||
|
||||
import com.google.firebase.messaging.Notification as FcmNotification
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.push.message.provider.dto.PushMessageNotification
|
||||
|
||||
@Component
|
||||
class PushMessageNotificationConverter {
|
||||
|
||||
operator fun invoke(pushMessageNotification: PushMessageNotification): FcmNotification {
|
||||
return FcmNotification.builder()
|
||||
.setTitle(pushMessageNotification.title)
|
||||
.setBody(pushMessageNotification.description)
|
||||
.setImage(pushMessageNotification.imageUrl)
|
||||
.build()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,12 +6,12 @@ import com.google.firebase.messaging.ApnsConfig
|
|||
import com.google.firebase.messaging.Aps
|
||||
import com.google.firebase.messaging.Message
|
||||
import org.springframework.stereotype.Component
|
||||
import ru.touchin.push.message.provider.dto.Notification
|
||||
import ru.touchin.push.message.provider.dto.PushMessageNotification
|
||||
import ru.touchin.push.message.provider.dto.request.PushTokenMessage
|
||||
|
||||
@Component
|
||||
class PushTokenMessageConverter(
|
||||
private val notificationConverter: NotificationConverter
|
||||
private val pushMessageNotificationConverter: PushMessageNotificationConverter
|
||||
) {
|
||||
|
||||
private companion object {
|
||||
|
|
@ -26,14 +26,14 @@ class PushTokenMessageConverter(
|
|||
.setToken(request.token)
|
||||
.setupApns()
|
||||
.setupAndroid()
|
||||
.setIfExists(request.notification)
|
||||
.setIfExists(request.pushMessageNotification)
|
||||
.putAllData(request.data)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun Message.Builder.setIfExists(notification: Notification?): Message.Builder {
|
||||
return if (notification != null) {
|
||||
setNotification(notificationConverter(notification))
|
||||
private fun Message.Builder.setIfExists(pushMessageNotification: PushMessageNotification?): Message.Builder {
|
||||
return if (pushMessageNotification != null) {
|
||||
setNotification(pushMessageNotificationConverter(pushMessageNotification))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@ import org.junit.Assert
|
|||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import ru.touchin.push.message.provider.dto.Notification
|
||||
import ru.touchin.push.message.provider.dto.PushMessageNotification
|
||||
import com.google.firebase.messaging.Notification as FcmNotification
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
|
||||
@SpringBootTest
|
||||
class NotificationConverterTest {
|
||||
class PushMessageNotificationConverterTest {
|
||||
|
||||
@Autowired
|
||||
lateinit var notificationConverter: NotificationConverter
|
||||
lateinit var pushMessageNotificationConverter: PushMessageNotificationConverter
|
||||
|
||||
@Autowired
|
||||
lateinit var objectMapper: ObjectMapper
|
||||
|
|
@ -21,19 +21,19 @@ class NotificationConverterTest {
|
|||
@Test
|
||||
@DisplayName("Конвертация уведомления происходит корректно")
|
||||
fun invoke_basic() {
|
||||
val notification = Notification(
|
||||
val pushMessageNotification = PushMessageNotification(
|
||||
title = "title",
|
||||
description = "description",
|
||||
imageUrl = "imageUrl"
|
||||
)
|
||||
|
||||
val realResult = notificationConverter(notification)
|
||||
val realResult = pushMessageNotificationConverter(pushMessageNotification)
|
||||
val realResultJson = objectMapper.writeValueAsString(realResult)
|
||||
|
||||
val expectedResult = FcmNotification.builder()
|
||||
.setTitle(notification.title)
|
||||
.setBody(notification.description)
|
||||
.setImage(notification.imageUrl)
|
||||
.setTitle(pushMessageNotification.title)
|
||||
.setBody(pushMessageNotification.description)
|
||||
.setImage(pushMessageNotification.imageUrl)
|
||||
.build()
|
||||
|
||||
val expectedResultJson = objectMapper.writeValueAsString(expectedResult)
|
||||
|
|
@ -11,7 +11,7 @@ import org.junit.jupiter.api.DisplayName
|
|||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import ru.touchin.push.message.provider.dto.Notification
|
||||
import ru.touchin.push.message.provider.dto.PushMessageNotification
|
||||
import ru.touchin.push.message.provider.dto.request.PushTokenMessage
|
||||
|
||||
@SpringBootTest
|
||||
|
|
@ -21,7 +21,7 @@ class PushTokenMessageConverterTest {
|
|||
lateinit var pushTokenMessageConverter: PushTokenMessageConverter
|
||||
|
||||
@Autowired
|
||||
lateinit var notificationConverter: NotificationConverter
|
||||
lateinit var pushMessageNotificationConverter: PushMessageNotificationConverter
|
||||
|
||||
@Autowired
|
||||
lateinit var objectMapper: ObjectMapper
|
||||
|
|
@ -29,14 +29,14 @@ class PushTokenMessageConverterTest {
|
|||
@Test
|
||||
@DisplayName("Конвертация сообщения с уведомлением происходит корректно")
|
||||
fun invoke_withNotification() {
|
||||
val notification = Notification(
|
||||
val pushMessageNotification = PushMessageNotification(
|
||||
title = "title",
|
||||
description = "description",
|
||||
imageUrl = "imageUrl"
|
||||
)
|
||||
val pushTokenMessage = PushTokenMessage(
|
||||
token = "token",
|
||||
notification = notification,
|
||||
pushMessageNotification = pushMessageNotification,
|
||||
data = mapOf("testKey" to "testvalue")
|
||||
)
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ class PushTokenMessageConverterTest {
|
|||
|
||||
val expectedResult = Message.builder()
|
||||
.setToken(pushTokenMessage.token)
|
||||
.setNotification(notificationConverter(notification))
|
||||
.setNotification(pushMessageNotificationConverter(pushMessageNotification))
|
||||
.putAllData(pushTokenMessage.data)
|
||||
.setupApns()
|
||||
.setupAndroid()
|
||||
|
|
@ -65,7 +65,7 @@ class PushTokenMessageConverterTest {
|
|||
fun invoke_withoutNotification() {
|
||||
val pushTokenMessage = PushTokenMessage(
|
||||
token = "token",
|
||||
notification = null,
|
||||
pushMessageNotification = null,
|
||||
data = mapOf("testKey" to "testvalue")
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import org.springframework.boot.test.mock.mockito.MockBean
|
|||
import ru.touchin.push.message.provider.dto.request.PushTokenCheck
|
||||
import ru.touchin.push.message.provider.dto.request.PushTokenMessage
|
||||
import ru.touchin.push.message.provider.dto.result.CheckPushTokenResult
|
||||
import ru.touchin.push.message.provider.dto.result.SendPushTokenMessageResult
|
||||
import ru.touchin.push.message.provider.dto.result.SendPushTokenMessageTraceableResult
|
||||
import ru.touchin.push.message.provider.enums.PushTokenStatus
|
||||
import ru.touchin.push.message.provider.fcm.clients.FcmClient
|
||||
import ru.touchin.push.message.provider.services.PushMessageProviderService
|
||||
|
|
@ -29,11 +29,11 @@ class PushMessageProviderFcmServiceTest {
|
|||
fun send_basic() {
|
||||
val request = PushTokenMessage(
|
||||
token = "testToken",
|
||||
notification = null,
|
||||
pushMessageNotification = null,
|
||||
data = emptyMap()
|
||||
)
|
||||
|
||||
val expectedResult = SendPushTokenMessageResult("testMessageId")
|
||||
val expectedResult = SendPushTokenMessageTraceableResult("testMessageId")
|
||||
|
||||
Mockito.`when`(
|
||||
fcmClient.sendPushTokenMessage(request)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
id("kotlin-spring")
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||
|
||||
implementation(project(":logger-spring"))
|
||||
implementation(project(":common-spring-web"))
|
||||
implementation(project(":push-message-provider"))
|
||||
|
||||
testImplementation(project(":logger-spring-web"))
|
||||
|
||||
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.testcontainers:junit-jupiter")
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.push.message.provider.hpk
|
||||
|
||||
import org.springframework.context.annotation.Import
|
||||
import ru.touchin.push.message.provider.hpk.configurations.PushMessageProviderHpkConfiguration
|
||||
|
||||
@Import(value = [PushMessageProviderHpkConfiguration::class])
|
||||
annotation class EnablePushMessageProviderHpk
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package ru.touchin.push.message.provider.hpk.base.builders
|
||||
|
||||
internal interface Buildable
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package ru.touchin.push.message.provider.hpk.base.clients
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.springframework.web.reactive.function.client.ClientResponse
|
||||
import ru.touchin.push.message.provider.hpk.base.clients.dto.ConditionalResponse
|
||||
|
||||
internal open class ConditionalWebClientParser(
|
||||
private val objectMapper: ObjectMapper,
|
||||
) {
|
||||
|
||||
open fun isOkResponse(clientResponse: ClientResponse): Boolean {
|
||||
return clientResponse.statusCode().is2xxSuccessful
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
inline fun <reified S, reified F> parse(
|
||||
clientResponse: ClientResponse,
|
||||
body: String,
|
||||
): ConditionalResponse<S, F> {
|
||||
return if (isOkResponse(clientResponse)) {
|
||||
ConditionalResponse<S, F>(
|
||||
success = parseValue(body, S::class.java),
|
||||
failure = null
|
||||
)
|
||||
} else {
|
||||
ConditionalResponse(
|
||||
success = null,
|
||||
failure = parseValue(body, F::class.java)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> parseValue(source: String?, clazz: Class<T>): T {
|
||||
return if (clazz.canonicalName != String::class.java.canonicalName) {
|
||||
objectMapper.readValue(source, clazz)
|
||||
} else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
source as T // T is String
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package ru.touchin.push.message.provider.hpk.base.clients
|
||||
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.handler.ssl.SslContextBuilder
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector
|
||||
import org.springframework.web.reactive.function.client.ClientResponse
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import reactor.netty.http.client.HttpClient
|
||||
import ru.touchin.common.spring.web.webclient.BaseLogWebClient
|
||||
import ru.touchin.common.spring.web.webclient.dto.RequestLogData
|
||||
import ru.touchin.common.spring.web.webclient.logger.WebClientLogger
|
||||
import ru.touchin.push.message.provider.hpk.base.clients.dto.ConditionalResponse
|
||||
import ru.touchin.push.message.provider.hpk.properties.HpkProperties
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class ConfigurableWebClient(
|
||||
webClientLogger: WebClientLogger,
|
||||
webClientBuilder: WebClient.Builder,
|
||||
protected val webService: HpkProperties.WebService,
|
||||
) : BaseLogWebClient(webClientLogger, webClientBuilder) {
|
||||
|
||||
private val conditionalWebClientParser: Lazy<ConditionalWebClientParser> = lazy {
|
||||
ConditionalWebClientParser(
|
||||
objectMapper = getObjectMapper(),
|
||||
)
|
||||
}
|
||||
|
||||
protected fun WebClient.Builder.setTimeouts(): WebClient.Builder {
|
||||
val httpClient: HttpClient = HttpClient.create()
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, webService.http.connectionTimeout.toMillis().toInt())
|
||||
.doOnConnected { setup ->
|
||||
setup.addHandlerLast(ReadTimeoutHandler(webService.http.readTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||
setup.addHandlerLast(WriteTimeoutHandler(webService.http.writeTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||
}
|
||||
.let { httpClient ->
|
||||
webService.ssl?.let { ssl ->
|
||||
httpClient.secure { builder ->
|
||||
builder
|
||||
.sslContext(SslContextBuilder.forClient().build())
|
||||
.handshakeTimeout(ssl.handshakeTimeout)
|
||||
.closeNotifyFlushTimeout(ssl.notifyFlushTimeout)
|
||||
.closeNotifyReadTimeout(ssl.notifyReadTimeout)
|
||||
}
|
||||
} ?: httpClient
|
||||
}
|
||||
|
||||
return clientConnector(ReactorClientHttpConnector(httpClient))
|
||||
}
|
||||
|
||||
internal inline fun <reified S, reified F> WebClient.RequestHeadersSpec<*>.exchangeWithWrap(
|
||||
requestLogData: RequestLogData,
|
||||
): Mono<ConditionalResponse<S, F>> {
|
||||
return exchangeToMono { clientResponse ->
|
||||
parse<S, F>(clientResponse)
|
||||
}.doOnNext { responseWrapper ->
|
||||
getLogger().log(
|
||||
requestLogData.copy(
|
||||
responseBody = responseWrapper.success ?: responseWrapper.failure
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified S, reified F> parse(
|
||||
clientResponse: ClientResponse,
|
||||
): Mono<ConditionalResponse<S, F>> {
|
||||
val responseBody = clientResponse
|
||||
.bodyToMono(String::class.java)
|
||||
.defaultIfEmpty(String())
|
||||
.publishOn(Schedulers.parallel())
|
||||
|
||||
return responseBody
|
||||
.map { body ->
|
||||
conditionalWebClientParser.value.parse(
|
||||
clientResponse = clientResponse,
|
||||
body = body,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package ru.touchin.push.message.provider.hpk.base.clients.dto
|
||||
|
||||
internal open class ConditionalResponse<S, F>(
|
||||
val success: S?,
|
||||
val failure: F?,
|
||||
) {
|
||||
|
||||
init {
|
||||
// Only one value should be present
|
||||
check((success == null) != (failure == null))
|
||||
}
|
||||
|
||||
val isSuccess: Boolean = success != null
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.touchin.push.message.provider.hpk.base.enums
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue
|
||||
|
||||
internal interface ValueableSerializableEnum<T> {
|
||||
|
||||
val value: T
|
||||
|
||||
@JsonValue
|
||||
fun toValue(): T = value
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.push.message.provider.hpk.base.extensions
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.builders.Buildable
|
||||
|
||||
fun <V, B : Buildable> B.ifNotNull(value: V?, setter: B.(V) -> B): B {
|
||||
return value?.let { setter(it) } ?: this
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.dto.android
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import ru.touchin.push.message.provider.hpk.base.builders.Buildable
|
||||
|
||||
internal data class AndroidBadgeNotification private constructor(
|
||||
/** Accumulative badge number. */
|
||||
val addNum: Short?,
|
||||
/** Full path of the app entry activity class. */
|
||||
@JsonProperty("class")
|
||||
val clazz: String,
|
||||
/** Badge number. Overrides [addNum]. */
|
||||
val setNum: Short?,
|
||||
) {
|
||||
|
||||
class Validator {
|
||||
|
||||
fun check(androidBadgeNotification: AndroidBadgeNotification) {
|
||||
with(androidBadgeNotification) {
|
||||
if (addNum != null) {
|
||||
require(
|
||||
addNum in 1..99
|
||||
) { "add_num must locate between 0 and 100" }
|
||||
}
|
||||
|
||||
if (setNum != null) {
|
||||
require(
|
||||
setNum in 0..99
|
||||
) { "set_num must locate between -1 and 100" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Builder : Buildable {
|
||||
|
||||
private var addNum: Short? = null
|
||||
private var setNum: Short? = null
|
||||
|
||||
fun setAddNum(addNum: Short): Builder {
|
||||
this.addNum = addNum
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSetNum(setNum: Short): Builder {
|
||||
this.setNum = setNum
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(badgeClass: String): AndroidBadgeNotification {
|
||||
return AndroidBadgeNotification(
|
||||
addNum = addNum,
|
||||
clazz = badgeClass,
|
||||
setNum = setNum,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val validator = Validator()
|
||||
|
||||
fun builder() = Builder()
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.dto.android
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import ru.touchin.push.message.provider.hpk.base.builders.Buildable
|
||||
import ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android.AndroidActionType
|
||||
import ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android.AndroidIntentType
|
||||
|
||||
internal data class AndroidButton private constructor(
|
||||
/** Button name. */
|
||||
val name: String,
|
||||
/** Button action. */
|
||||
@JsonProperty("action_type")
|
||||
val androidActionType: AndroidActionType,
|
||||
/** Method of opening a custom app page. */
|
||||
@JsonProperty("intent_type")
|
||||
val androidIntentType: AndroidIntentType?,
|
||||
val intent: String?,
|
||||
/** Map of key-values. */
|
||||
val data: String?,
|
||||
) {
|
||||
|
||||
class Validator {
|
||||
|
||||
fun check(androidButton: AndroidButton) {
|
||||
with(androidButton) {
|
||||
require(
|
||||
name.length < 40
|
||||
) { "Button name length cannot exceed 40" }
|
||||
|
||||
if (androidActionType == AndroidActionType.SHARE_NOTIFICATION_MESSAGE) {
|
||||
require(!data.isNullOrEmpty()) { "Data is needed when actionType is $androidActionType" }
|
||||
require(data.length < 1024) { "Data length cannot exceed 1024 chars" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Builder : Buildable {
|
||||
|
||||
private var intent: String? = null
|
||||
private var data: String? = null
|
||||
|
||||
fun setIntent(intent: String): Builder {
|
||||
this.intent
|
||||
return this
|
||||
}
|
||||
|
||||
fun setData(data: String): Builder {
|
||||
this.data = data
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(name: String, androidActionType: AndroidActionType, androidIntentType: AndroidIntentType): AndroidButton {
|
||||
return AndroidButton(
|
||||
name = name,
|
||||
androidActionType = androidActionType,
|
||||
androidIntentType = androidIntentType,
|
||||
intent = intent,
|
||||
data = data
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val validator = Validator()
|
||||
|
||||
fun builder() = Builder()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.dto.android
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import ru.touchin.push.message.provider.hpk.base.builders.Buildable
|
||||
import ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android.AndroidClickActionType
|
||||
|
||||
internal data class AndroidClickAction private constructor(
|
||||
/** Message tapping action type. */
|
||||
@JsonProperty("type")
|
||||
val androidClickActionType: AndroidClickActionType,
|
||||
val intent: String?,
|
||||
/** URL to be opened. */
|
||||
val url: String?,
|
||||
/** Action corresponding to the activity of the page to be opened when the custom app page is opened through the action. */
|
||||
val action: String?,
|
||||
) {
|
||||
|
||||
class Validator {
|
||||
|
||||
fun check(androidClickAction: AndroidClickAction) {
|
||||
with(androidClickAction) {
|
||||
when (androidClickActionType) {
|
||||
AndroidClickActionType.CUSTOMIZE_ACTION -> require(
|
||||
!intent.isNullOrBlank() || !action.isNullOrBlank()
|
||||
) { "intent or action is required when click type is $androidClickActionType" }
|
||||
|
||||
AndroidClickActionType.OPEN_URL -> {
|
||||
require(!url.isNullOrBlank()) { "url is required when click type is $androidClickActionType" }
|
||||
require(url.matches(HTTPS_PATTERN)) { "url must start with https" }
|
||||
}
|
||||
|
||||
AndroidClickActionType.OPEN_APP -> {
|
||||
// no verification
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val HTTPS_PATTERN: Regex = Regex("^https.*")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Builder : Buildable {
|
||||
|
||||
private var intent: String? = null
|
||||
private var url: String? = null
|
||||
private var richResource: String? = null
|
||||
private var action: String? = null
|
||||
|
||||
fun setIntent(intent: String): Builder {
|
||||
this.intent = intent
|
||||
return this
|
||||
}
|
||||
|
||||
fun setUrl(url: String): Builder {
|
||||
this.url = url
|
||||
return this
|
||||
}
|
||||
|
||||
fun setRichResource(richResource: String): Builder {
|
||||
this.richResource = richResource
|
||||
return this
|
||||
}
|
||||
|
||||
fun setAction(action: String): Builder {
|
||||
this.action = action
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(androidClickActionType: AndroidClickActionType): AndroidClickAction {
|
||||
return AndroidClickAction(
|
||||
androidClickActionType = androidClickActionType,
|
||||
intent = intent.takeIf { androidClickActionType == AndroidClickActionType.CUSTOMIZE_ACTION },
|
||||
action = action.takeIf { androidClickActionType == AndroidClickActionType.CUSTOMIZE_ACTION },
|
||||
url = url.takeIf { androidClickActionType == AndroidClickActionType.OPEN_URL },
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val validator = Validator()
|
||||
|
||||
fun builder() = Builder()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.dto.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.builders.Buildable
|
||||
|
||||
internal data class AndroidColor private constructor(
|
||||
/** Alpha setting of the RGB color.*/
|
||||
val alpha: Float,
|
||||
/** Red setting of the RGB color. */
|
||||
val red: Float,
|
||||
/** Green setting of the RGB color. */
|
||||
val green: Float,
|
||||
/** Green setting of the RGB color. */
|
||||
val blue: Float,
|
||||
) {
|
||||
|
||||
class Validator {
|
||||
|
||||
fun check(androidColor: AndroidColor) {
|
||||
with(androidColor) {
|
||||
require(alpha > ZERO && alpha < ONE) { "Alpha must be locate between [0,1]" }
|
||||
require(red > ZERO && red < ONE) { "Red must be locate between [0,1]" }
|
||||
require(green > ZERO && green < ONE) { "Green must be locate between [0,1]" }
|
||||
require(blue > ZERO && blue < ONE) { "Blue must be locate between [0,1]" }
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val ZERO: Float = -0.000001f
|
||||
private const val ONE: Float = 1.000001f
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Builder : Buildable {
|
||||
|
||||
private var alpha: Float = 1.0f
|
||||
private var red: Float = 0.0f
|
||||
private var green: Float = 0.0f
|
||||
private var blue: Float = 0.0f
|
||||
|
||||
fun setAlpha(alpha: Float): Builder {
|
||||
this.alpha = alpha
|
||||
return this
|
||||
}
|
||||
|
||||
fun setRed(red: Float): Builder {
|
||||
this.red = red
|
||||
return this
|
||||
}
|
||||
|
||||
fun setGreen(green: Float): Builder {
|
||||
this.green = green
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBlue(blue: Float): Builder {
|
||||
this.blue = blue
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): AndroidColor {
|
||||
return AndroidColor(
|
||||
alpha = alpha,
|
||||
red = red,
|
||||
green = green,
|
||||
blue = blue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val validator = Validator()
|
||||
|
||||
fun builder() = Builder()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.dto.android
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import ru.touchin.push.message.provider.hpk.base.builders.Buildable
|
||||
|
||||
internal data class AndroidLightSettings private constructor(
|
||||
/** Breathing light color. */
|
||||
@JsonProperty("color")
|
||||
val androidColor: AndroidColor,
|
||||
/** Interval when a breathing light is on */
|
||||
val lightOnDuration: String,
|
||||
/** Interval when a breathing light is off */
|
||||
val lightOffDuration: String,
|
||||
) {
|
||||
|
||||
class Validator {
|
||||
|
||||
fun check(androidLightSettings: AndroidLightSettings) {
|
||||
with(androidLightSettings) {
|
||||
AndroidColor.validator.check(androidColor)
|
||||
|
||||
require(
|
||||
lightOnDuration.matches(LIGHT_DURATION_PATTERN)
|
||||
) { "light_on_duration format is wrong" }
|
||||
require(
|
||||
lightOffDuration.matches(LIGHT_DURATION_PATTERN)
|
||||
) { "light_off_duration format is wrong" }
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val LIGHT_DURATION_PATTERN: Regex = Regex("\\d+|\\d+[sS]|\\d+.\\d{1,9}|\\d+.\\d{1,9}[sS]")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Builder : Buildable {
|
||||
|
||||
fun build(
|
||||
color: AndroidColor,
|
||||
lightOnDuration: String,
|
||||
lightOffDuration: String
|
||||
): AndroidLightSettings {
|
||||
return AndroidLightSettings(
|
||||
androidColor = color,
|
||||
lightOnDuration = lightOnDuration,
|
||||
lightOffDuration = lightOffDuration,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val validator = Validator()
|
||||
|
||||
fun builder() = Builder()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,410 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.dto.android
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import ru.touchin.push.message.provider.hpk.base.builders.Buildable
|
||||
import ru.touchin.push.message.provider.hpk.clients.hms_hpk.dto.Notification
|
||||
import ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android.AndroidImportance
|
||||
import ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android.AndroidStyleType
|
||||
import ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android.AndroidVisibility
|
||||
|
||||
internal data class AndroidNotificationConfig private constructor(
|
||||
/**
|
||||
* Title of an Android notification message.
|
||||
* If the title parameter is set, the value of the [Notification.title] field is overwritten.
|
||||
* */
|
||||
val title: String?,
|
||||
/**
|
||||
* Body of an Android notification message.
|
||||
* If the body parameter is set, the value of the [Notification.body] field is overwritten.
|
||||
* */
|
||||
val body: String?,
|
||||
/**
|
||||
* Custom app icon on the left of a notification message.
|
||||
*/
|
||||
val icon: String?,
|
||||
/** Custom notification bar button color. */
|
||||
val color: String?,
|
||||
val sound: String?,
|
||||
/** Indicates whether to use the default ringtone. */
|
||||
val defaultSound: Boolean,
|
||||
/**
|
||||
* Message tag.
|
||||
* Messages that use the same message tag in the same app will be overwritten by the latest message.
|
||||
* */
|
||||
val tag: String?,
|
||||
@JsonProperty("click_action")
|
||||
val androidClickAction: AndroidClickAction?,
|
||||
val bodyLocKey: String?,
|
||||
val bodyLocArgs: Collection<String>?,
|
||||
val titleLocKey: String?,
|
||||
val titleLocArgs: Collection<String>?,
|
||||
val multiLangKey: Map<String, String>?,
|
||||
/** Custom channel for displaying notification messages. */
|
||||
val channelId: String?,
|
||||
/** Brief description of a notification message to an Android app. */
|
||||
val notifySummary: String?,
|
||||
/** URL of the custom small image on the right of a notification message. */
|
||||
val image: String?,
|
||||
/** Notification bar style. */
|
||||
@JsonProperty("style")
|
||||
val androidStyleType: AndroidStyleType?,
|
||||
/** Android notification message title in large text style. */
|
||||
val bigTitle: String?,
|
||||
val bigBody: String?,
|
||||
/**
|
||||
* Unique notification ID of a message.
|
||||
* If a message does not contain the ID or the ID is -1, the NC will generate a unique ID for the message.
|
||||
* Different notification messages can use the same notification ID, so that new messages can overwrite old messages.
|
||||
* */
|
||||
val notifyId: Int?,
|
||||
/**
|
||||
* Message group.
|
||||
* For example, if 10 messages that contain the same value of group are sent to a device,
|
||||
* the device displays only the latest message and the total number of messages received in the group,
|
||||
* but does not display these 10 messages.
|
||||
*/
|
||||
val group: String?,
|
||||
@JsonProperty("badge")
|
||||
val androidBadgeNotification: AndroidBadgeNotification? = null,
|
||||
val autoCancel: Boolean,
|
||||
/**
|
||||
* Time when Android notification messages are delivered, in the UTC timestamp format.
|
||||
* If you send multiple messages at the same time,
|
||||
* they will be sorted based on this value and displayed in the Android notification panel.
|
||||
* Example: 2014-10-02T15:01:23.045123456Z
|
||||
*/
|
||||
@JsonProperty("when")
|
||||
val sendAt: String?,
|
||||
val localOnly: Boolean? = null,
|
||||
/**
|
||||
* Android notification message priority, which determines the message notification behavior of a user device.
|
||||
*/
|
||||
@JsonProperty("importance")
|
||||
val androidImportance: AndroidImportance?,
|
||||
/** Indicates whether to use the default vibration mode. */
|
||||
val useDefaultVibrate: Boolean,
|
||||
/** Indicates whether to use the default breathing light. */
|
||||
val useDefaultLight: Boolean,
|
||||
val vibrateConfig: Collection<String>?,
|
||||
/** Android notification message visibility. */
|
||||
@JsonProperty("visibility")
|
||||
val androidVisibility: AndroidVisibility?,
|
||||
@JsonProperty("light_settings")
|
||||
val androidLightSettings: AndroidLightSettings?,
|
||||
/**
|
||||
* Indicates whether to display notification messages in the NC when your app is running in the foreground.
|
||||
* If this parameter is not set, the default value true will be used,
|
||||
* indicating that notification messages will be displayed in the NC when your app runs in the foreground.
|
||||
* */
|
||||
val foregroundShow: Boolean,
|
||||
val inboxContent: Collection<String>?,
|
||||
@JsonProperty("buttons")
|
||||
val androidButtons: Collection<AndroidButton>?,
|
||||
/** ID of the user-app relationship. */
|
||||
val profileId: String?,
|
||||
) {
|
||||
|
||||
class Validator {
|
||||
|
||||
fun check(androidNotificationConfig: AndroidNotificationConfig, notification: Notification?) {
|
||||
with(androidNotificationConfig) {
|
||||
androidBadgeNotification?.let { AndroidBadgeNotification.validator.check(it) }
|
||||
androidLightSettings?.also { AndroidLightSettings.validator.check(it) }
|
||||
androidClickAction?.also { AndroidClickAction.validator.check(it) }
|
||||
|
||||
require(!notification?.title.isNullOrBlank() || !title.isNullOrBlank()) { "title should be set" }
|
||||
require(!notification?.body.isNullOrBlank() || !body.isNullOrBlank()) { "body should be set" }
|
||||
|
||||
if (!color.isNullOrBlank()) {
|
||||
require(color.matches(COLOR_PATTERN)) { "Wrong color format, color must be in the form #RRGGBB" }
|
||||
}
|
||||
if (!image.isNullOrBlank()) {
|
||||
require(image.matches(HTTPS_URL_PATTERN)) { "notifyIcon must start with https" }
|
||||
}
|
||||
if (androidStyleType != null) {
|
||||
when (androidStyleType) {
|
||||
AndroidStyleType.DEFAULT -> {
|
||||
// no verification
|
||||
}
|
||||
|
||||
AndroidStyleType.BIG_TEXT -> {
|
||||
require(
|
||||
!bigTitle.isNullOrBlank() && !bigBody.isNullOrBlank()
|
||||
) { "title and body are required when style is $androidStyleType" }
|
||||
}
|
||||
|
||||
AndroidStyleType.INBOX -> {
|
||||
require(
|
||||
!inboxContent.isNullOrEmpty() && inboxContent.orEmpty().size <= 5
|
||||
) { "inboxContent is required when style is $androidStyleType and at most 5 inbox content allowed" }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (profileId != null) {
|
||||
require(profileId.length <= 64) { "profileId length cannot exceed 64 characters" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val COLOR_PATTERN: Regex = Regex("^#[0-9a-fA-F]{6}$")
|
||||
val HTTPS_URL_PATTERN: Regex = Regex("^https.*")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Builder : Buildable {
|
||||
|
||||
private var title: String? = null
|
||||
private val body: String? = null
|
||||
private var icon: String? = null
|
||||
private var color: String? = null
|
||||
private var sound: String? = null
|
||||
private var defaultSound = false
|
||||
private var tag: String? = null
|
||||
private var bodyLocKey: String? = null
|
||||
private val bodyLocArgs: MutableList<String> = mutableListOf()
|
||||
private var titleLocKey: String? = null
|
||||
private val titleLocArgs: MutableList<String> = mutableListOf()
|
||||
private var multiLangkey: Map<String, String>? = null
|
||||
private var channelId: String? = null
|
||||
private var notifySummary: String? = null
|
||||
private var image: String? = null
|
||||
private var androidStyleType: AndroidStyleType? = null
|
||||
private var bigTitle: String? = null
|
||||
private var bigBody: String? = null
|
||||
private var notifyId: Int? = null
|
||||
private var group: String? = null
|
||||
private var androidBadgeNotification: AndroidBadgeNotification? = null
|
||||
private var autoCancel = true
|
||||
private var sendAt: String? = null
|
||||
private var androidImportance: AndroidImportance? = null
|
||||
private var useDefaultVibrate = false
|
||||
private var useDefaultLight = false
|
||||
private val vibrateConfig: MutableList<String> = mutableListOf()
|
||||
private var androidVisibility: AndroidVisibility? = null
|
||||
private var androidLightSettings: AndroidLightSettings? = null
|
||||
private var foregroundShow = false
|
||||
private val inboxContent: MutableList<String> = mutableListOf()
|
||||
private val buttons: MutableList<AndroidButton> = mutableListOf()
|
||||
private var profileId: String? = null
|
||||
|
||||
fun setTitle(title: String): Builder {
|
||||
this.title = title
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBody(body: String): Builder {
|
||||
this.body
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIcon(icon: String): Builder {
|
||||
this.icon = icon
|
||||
return this
|
||||
}
|
||||
|
||||
fun setColor(color: String): Builder {
|
||||
this.color = color
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSound(sound: String): Builder {
|
||||
this.sound = sound
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDefaultSound(defaultSound: Boolean): Builder {
|
||||
this.defaultSound = defaultSound
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTag(tag: String): Builder {
|
||||
this.tag = tag
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBodyLocKey(bodyLocKey: String): Builder {
|
||||
this.bodyLocKey = bodyLocKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun addBodyLocArgs(vararg arg: String): Builder {
|
||||
bodyLocArgs.addAll(arg)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTitleLocKey(titleLocKey: String): Builder {
|
||||
this.titleLocKey = titleLocKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun addTitleLocArgs(vararg args: String): Builder {
|
||||
titleLocArgs.addAll(args)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setMultiLangkey(multiLangkey: Map<String, String>): Builder {
|
||||
this.multiLangkey = multiLangkey
|
||||
return this
|
||||
}
|
||||
|
||||
fun setChannelId(channelId: String): Builder {
|
||||
this.channelId = channelId
|
||||
return this
|
||||
}
|
||||
|
||||
fun setNotifySummary(notifySummary: String): Builder {
|
||||
this.notifySummary = notifySummary
|
||||
return this
|
||||
}
|
||||
|
||||
fun setImage(image: String): Builder {
|
||||
this.image = image
|
||||
return this
|
||||
}
|
||||
|
||||
fun setStyle(androidStyleType: AndroidStyleType): Builder {
|
||||
this.androidStyleType = androidStyleType
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBigTitle(bigTitle: String): Builder {
|
||||
this.bigTitle = bigTitle
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBigBody(bigBody: String): Builder {
|
||||
this.bigBody = bigBody
|
||||
return this
|
||||
}
|
||||
|
||||
fun setNotifyId(notifyId: Int): Builder {
|
||||
this.notifyId = notifyId
|
||||
return this
|
||||
}
|
||||
|
||||
fun setGroup(group: String): Builder {
|
||||
this.group = group
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBadge(androidBadgeNotification: AndroidBadgeNotification): Builder {
|
||||
this.androidBadgeNotification = androidBadgeNotification
|
||||
return this
|
||||
}
|
||||
|
||||
fun setAutoCancel(autoCancel: Boolean): Builder {
|
||||
this.autoCancel = autoCancel
|
||||
return this
|
||||
}
|
||||
|
||||
fun sendAt(sendAt: String): Builder {
|
||||
this.sendAt = sendAt
|
||||
return this
|
||||
}
|
||||
|
||||
fun setImportance(androidImportance: AndroidImportance): Builder {
|
||||
this.androidImportance = androidImportance
|
||||
return this
|
||||
}
|
||||
|
||||
fun setUseDefaultVibrate(useDefaultVibrate: Boolean): Builder {
|
||||
this.useDefaultVibrate = useDefaultVibrate
|
||||
return this
|
||||
}
|
||||
|
||||
fun setUseDefaultLight(useDefaultLight: Boolean): Builder {
|
||||
this.useDefaultLight = useDefaultLight
|
||||
return this
|
||||
}
|
||||
|
||||
fun addVibrateConfig(vararg vibrateTimings: String): Builder {
|
||||
vibrateConfig.addAll(vibrateTimings)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setAndroidVisibility(androidVisibility: AndroidVisibility): Builder {
|
||||
this.androidVisibility = androidVisibility
|
||||
return this
|
||||
}
|
||||
|
||||
fun setLightSettings(androidLightSettings: AndroidLightSettings): Builder {
|
||||
this.androidLightSettings = androidLightSettings
|
||||
return this
|
||||
}
|
||||
|
||||
fun setForegroundShow(foregroundShow: Boolean): Builder {
|
||||
this.foregroundShow = foregroundShow
|
||||
return this
|
||||
}
|
||||
|
||||
fun addInboxContent(vararg inboxContent: String): Builder {
|
||||
this.inboxContent.addAll(inboxContent)
|
||||
return this
|
||||
}
|
||||
|
||||
fun addButton(vararg button: AndroidButton): Builder {
|
||||
buttons.addAll(button)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setProfileId(profileId: String): Builder {
|
||||
this.profileId = profileId
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(
|
||||
androidClickAction: AndroidClickAction,
|
||||
): AndroidNotificationConfig {
|
||||
return AndroidNotificationConfig(
|
||||
title = title,
|
||||
body = body,
|
||||
icon = icon,
|
||||
color = color,
|
||||
sound = sound,
|
||||
defaultSound = defaultSound,
|
||||
tag = tag,
|
||||
androidClickAction = androidClickAction,
|
||||
bodyLocKey = bodyLocKey,
|
||||
bodyLocArgs = bodyLocArgs.takeIf(Collection<*>::isNotEmpty),
|
||||
titleLocKey = titleLocKey,
|
||||
titleLocArgs = titleLocArgs.takeIf(Collection<*>::isNotEmpty),
|
||||
multiLangKey = multiLangkey,
|
||||
channelId = channelId,
|
||||
notifySummary = notifySummary,
|
||||
image = image,
|
||||
androidStyleType = androidStyleType,
|
||||
bigTitle = bigTitle,
|
||||
bigBody = bigBody,
|
||||
notifyId = notifyId,
|
||||
group = group,
|
||||
androidBadgeNotification = androidBadgeNotification,
|
||||
autoCancel = autoCancel,
|
||||
sendAt = sendAt,
|
||||
androidImportance = androidImportance,
|
||||
useDefaultVibrate = useDefaultVibrate,
|
||||
useDefaultLight = useDefaultLight,
|
||||
vibrateConfig = vibrateConfig.takeIf(Collection<*>::isNotEmpty),
|
||||
androidVisibility = androidVisibility,
|
||||
androidLightSettings = androidLightSettings,
|
||||
foregroundShow = foregroundShow,
|
||||
inboxContent = inboxContent.takeIf(Collection<*>::isNotEmpty),
|
||||
androidButtons = buttons.takeIf(Collection<*>::isNotEmpty),
|
||||
profileId = profileId,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val validator = Validator()
|
||||
|
||||
fun builder() = Builder()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
internal enum class AndroidActionType(
|
||||
override val value: Short
|
||||
) : ValueableSerializableEnum<Short> {
|
||||
|
||||
OPEN_APP_HOME_PAGE(0),
|
||||
OPEN_CUSTOM_APP_PAGE(1),
|
||||
OPEN_WEB_PAGE(2),
|
||||
DELETE_NOTIFICATION_MESSAGE(3),
|
||||
|
||||
/** Only for Huawei devices */
|
||||
SHARE_NOTIFICATION_MESSAGE(4),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
internal enum class AndroidClickActionType(
|
||||
override val value: Short
|
||||
) : ValueableSerializableEnum<Short> {
|
||||
|
||||
CUSTOMIZE_ACTION(1),
|
||||
OPEN_URL(2),
|
||||
OPEN_APP(3),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
internal enum class AndroidFastAppTargetType(
|
||||
override val value: Short
|
||||
) : ValueableSerializableEnum<Short> {
|
||||
|
||||
DEVELOPMENT(1),
|
||||
PRODUCTION(2),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
internal enum class AndroidImportance(
|
||||
override val value: String
|
||||
) : ValueableSerializableEnum<String> {
|
||||
|
||||
LOW("LOW"),
|
||||
NORMAL("NORMAL"),
|
||||
HIGH("HIGH"), // TODO: check if this type is still supported by HMS HPK API
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
enum class AndroidIntentType(
|
||||
override val value: Short
|
||||
) : ValueableSerializableEnum<Short> {
|
||||
|
||||
INTENT(0),
|
||||
ACTION(1),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
enum class AndroidStyleType(
|
||||
override val value: Short
|
||||
) : ValueableSerializableEnum<Short> {
|
||||
|
||||
DEFAULT(0),
|
||||
BIG_TEXT(1),
|
||||
INBOX(3),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
internal enum class AndroidTargetUserType(
|
||||
override val value: Short
|
||||
) : ValueableSerializableEnum<Short> {
|
||||
|
||||
TEST_USER(1),
|
||||
FORMAL_USER(2),
|
||||
VOIP_USER(3),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
internal enum class AndroidTopicOperation(
|
||||
override val value: String
|
||||
) : ValueableSerializableEnum<String> {
|
||||
|
||||
SUBSCRIBE("subscribe"),
|
||||
UNSUBSCRIBE("unsubscribe"),
|
||||
LIST("list"),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
internal enum class AndroidUrgency(
|
||||
override val value: String
|
||||
) : ValueableSerializableEnum<String> {
|
||||
|
||||
HIGH("HIGH"),
|
||||
NORMAL("NORMAL"),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package ru.touchin.push.message.provider.hpk.clients.hms_hpk.enums.android
|
||||
|
||||
import ru.touchin.push.message.provider.hpk.base.enums.ValueableSerializableEnum
|
||||
|
||||
internal enum class AndroidVisibility(
|
||||
override val value: String
|
||||
) : ValueableSerializableEnum<String> {
|
||||
|
||||
/** The visibility is not specified. This value is equivalent to PRIVATE. */
|
||||
VISIBILITY_UNSPECIFIED("VISIBILITY_UNSPECIFIED"),
|
||||
|
||||
/**
|
||||
* If you have set a lock screen password and enabled Hide notification content under Settings > Notifications,
|
||||
* the content of a received notification message is hidden on the lock screen.
|
||||
* */
|
||||
PRIVATE("PRIVATE"),
|
||||
|
||||
/** The content of a received notification message is displayed on the lock screen. */
|
||||
PUBLIC("PUBLIC"),
|
||||
|
||||
/** A received notification message is not displayed on the lock screen. */
|
||||
SECRET("SECRET"),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package ru.touchin.push.message.provider.hpk.configurations
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||
import org.springframework.cache.CacheManager
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCache
|
||||
import org.springframework.cache.support.SimpleCacheManager
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.ComponentScan
|
||||
import org.springframework.context.annotation.Import
|
||||
import ru.touchin.push.message.provider.configurations.PushMessageProviderConfiguration
|
||||
import ru.touchin.push.message.provider.hpk.services.HmsOauthAccessTokenCacheServiceImpl.Companion.HMS_CLIENT_SERVICE_CACHE_KEY
|
||||
|
||||
@ComponentScan("ru.touchin.push.message.provider.hpk")
|
||||
@ConfigurationPropertiesScan(basePackages = ["ru.touchin.push.message.provider.hpk"])
|
||||
@Import(value = [PushMessageProviderConfiguration::class])
|
||||
class PushMessageProviderHpkConfiguration {
|
||||
|
||||
@Bean
|
||||
@Qualifier("push-message-provider.hpk.webclient-objectmapper")
|
||||
fun webclientObjectMapper(): ObjectMapper {
|
||||
return jacksonObjectMapper()
|
||||
.registerModule(JavaTimeModule())
|
||||
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("push-message-provider.hpk.client-objectmapper")
|
||||
fun clientObjectMapper(): ObjectMapper {
|
||||
return jacksonObjectMapper()
|
||||
.registerModule(JavaTimeModule())
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Qualifier("push-message-provider.hpk.webclient-cachemanager")
|
||||
fun cacheManager(): CacheManager {
|
||||
return SimpleCacheManager().also {
|
||||
it.setCaches(
|
||||
listOf(
|
||||
ConcurrentMapCache(HMS_CLIENT_SERVICE_CACHE_KEY)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package ru.touchin.push.message.provider.hpk.properties
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.ConstructorBinding
|
||||
import java.net.URL
|
||||
import java.time.Duration
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "push-message-provider.hpk")
|
||||
data class HpkProperties(
|
||||
val webServices: WebServices,
|
||||
) {
|
||||
|
||||
class WebServices(
|
||||
val clientId: String,
|
||||
val oauth: Oauth,
|
||||
val hpk: Hpk,
|
||||
)
|
||||
|
||||
class Oauth(
|
||||
val clientSecret: String,
|
||||
url: URL,
|
||||
http: Http,
|
||||
ssl: Ssl?,
|
||||
) : WebService(
|
||||
url = url,
|
||||
http = http,
|
||||
ssl = ssl,
|
||||
)
|
||||
|
||||
class Hpk(
|
||||
url: URL,
|
||||
http: Http,
|
||||
ssl: Ssl?,
|
||||
) : WebService(
|
||||
url = url,
|
||||
http = http,
|
||||
ssl = ssl,
|
||||
)
|
||||
|
||||
open class WebService(
|
||||
val url: URL,
|
||||
val http: Http,
|
||||
val ssl: Ssl?,
|
||||
)
|
||||
|
||||
class Http(
|
||||
val readTimeout: Duration,
|
||||
val writeTimeout: Duration,
|
||||
val connectionTimeout: Duration,
|
||||
)
|
||||
|
||||
class Ssl(
|
||||
val handshakeTimeout: Duration,
|
||||
val notifyFlushTimeout: Duration,
|
||||
val notifyReadTimeout: Duration,
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package ru.touchin.push.message.provider.dto
|
||||
|
||||
class Notification(
|
||||
class PushMessageNotification(
|
||||
val title: String?,
|
||||
val description: String?,
|
||||
val imageUrl: String?
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package ru.touchin.push.message.provider.dto.request
|
||||
|
||||
import ru.touchin.push.message.provider.dto.Notification
|
||||
import ru.touchin.push.message.provider.dto.PushMessageNotification
|
||||
|
||||
class PushTokenMessage(
|
||||
val token: String,
|
||||
override val notification: Notification?,
|
||||
override val pushMessageNotification: PushMessageNotification?,
|
||||
override val data: Map<String, String>
|
||||
) : SendPushRequest
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package ru.touchin.push.message.provider.dto.request
|
||||
|
||||
import ru.touchin.push.message.provider.dto.Notification
|
||||
import ru.touchin.push.message.provider.dto.PushMessageNotification
|
||||
|
||||
sealed interface SendPushRequest {
|
||||
|
||||
val notification: Notification?
|
||||
val pushMessageNotification: PushMessageNotification?
|
||||
val data: Map<String, String>
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ package ru.touchin.push.message.provider.dto.result
|
|||
import ru.touchin.push.message.provider.enums.PushTokenStatus
|
||||
|
||||
data class CheckPushTokenResult(
|
||||
val status: PushTokenStatus
|
||||
val status: PushTokenStatus,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
package ru.touchin.push.message.provider.dto.result
|
||||
|
||||
class SendPushTokenMessageResult(
|
||||
val messageId: String
|
||||
) : SendPushResult
|
||||
object SendPushTokenMessageResult : SendPushResult
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.touchin.push.message.provider.dto.result
|
||||
|
||||
data class SendPushTokenMessageTraceableResult(
|
||||
val messageId: String
|
||||
) : SendPushResult
|
||||
|
|
@ -3,6 +3,7 @@ package ru.touchin.push.message.provider.enums
|
|||
enum class PlatformType {
|
||||
|
||||
ANDROID_GOOGLE,
|
||||
ANDROID_HUAWEI,
|
||||
IOS
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package ru.touchin.push.message.provider.enums
|
|||
|
||||
enum class PushMessageProviderType {
|
||||
|
||||
FCM
|
||||
FCM,
|
||||
HPK,
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ include("validation-spring")
|
|||
include("version-spring-web")
|
||||
include("push-message-provider")
|
||||
include("push-message-provider-fcm")
|
||||
include("push-message-provider-hpk")
|
||||
include("response-wrapper-spring-web")
|
||||
include("settings-spring-jpa")
|
||||
include("security-authorization-server-core")
|
||||
|
|
|
|||
Loading…
Reference in New Issue