Skip to content

Commit

Permalink
Intorduced FCM & HMS for push (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
kober32 authored Nov 27, 2024
1 parent 5bf7a2c commit cd9cfc7
Show file tree
Hide file tree
Showing 22 changed files with 268 additions and 122 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ jobs:
ER_URL: ${{ secrets.TESTS_ER_URL }}
OP_URL: ${{ secrets.TESTS_OP_URL }}
IN_URL: ${{ secrets.TESTS_IN_URL }}
run: echo -e tests.sdk.cloudServerUrl="$CL_URL"\\ntests.sdk.cloudServerLogin="$CL_LGN"\\ntests.sdk.cloudServerPassword="$CL_PWD"\\ntests.sdk.cloudApplicationId="$CL_AID"\\ntests.sdk.enrollmentServerUrl="$ER_URL"\\ntests.sdk.operationsServerUrl="$OP_URL"\\ntests.sdk.inboxServerUrl="$IN_URL"\\ntests.sdk.sdkConfig="$SDK_CONFIG" > configs/integration-tests.properties
PU_URL: ${{ secrets.TESTS_PU_URL }}
run: echo -e tests.sdk.cloudServerUrl="$CL_URL"\\ntests.sdk.cloudServerLogin="$CL_LGN"\\ntests.sdk.cloudServerPassword="$CL_PWD"\\ntests.sdk.cloudApplicationId="$CL_AID"\\ntests.sdk.enrollmentServerUrl="$ER_URL"\\ntests.sdk.operationsServerUrl="$OP_URL"\\ntests.sdk.inboxServerUrl="$IN_URL"\\ntests.sdk.pushServerUrl="$PU_URL"\\ntests.sdk.sdkConfig="$SDK_CONFIG" > configs/integration-tests.properties
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/ProjectConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fun loadInstrumentationTestConfigProperties(project: Project, defaultConfig: Def
"tests.sdk.enrollmentServerUrl",
"tests.sdk.operationsServerUrl",
"tests.sdk.inboxServerUrl",
"tests.sdk.pushServerUrl",
"tests.sdk.sdkConfig"
)

Expand Down
1 change: 1 addition & 0 deletions configs/integration-tests.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ tests.sdk.cloudApplicationId=dev
tests.sdk.enrollmentServerUrl=http://localhost/enrollment-server
tests.sdk.operationsServerUrl=http://localhost/enrollment-server
tests.sdk.inboxServerUrl=http://localhost/enrollment-server
tests.sdk.pushServerUrl=http://localhost/enrollment-server

# Configuration of the PowerAuth server
tests.sdk.sdkConfig=
4 changes: 4 additions & 0 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## x.x.x (TBA)

- New push registration API that is using `FCM` and `HMS` instead of `ANDROID` and `HUAWEI`

## 1.12.0 (Oct, 2024)

- Upgraded to `PowerAuthSDK` `1.9.x` [(#165)](https://github.com/wultra/mtoken-sdk-ios/pull/165)
Expand Down
4 changes: 2 additions & 2 deletions docs/Using-Inbox-Service.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fun PowerAuthSDK.createInboxService(appContext: Context, baseURL: String, strate
```

- `appContext` - application context
- `baseURL` - address, where your operations server can be reached
- `baseURL` - address, where your operations server can be reached (ending with `/enrollment-server` in the default setup)
- `strategy` - a strategy used when validating HTTPS requests. The following strategies can be used:
- `SSLValidationStrategy.default`
- `SSLValidationStrategy.noValidation`
Expand All @@ -51,7 +51,7 @@ fun PowerAuthSDK.createInboxService(appContext: Context, baseURL: String, httpCl
```

- `appContext` - application context
- `baseURL`- address, where your operations server can be reached
- `baseURL`- address, where your operations server can be reached (ending with `/enrollment-server` in the default setup)
- `httpClient` - [`OkHttpClient`](https://square.github.io/okhttp/) instance used for API requests

__Optional parameters:__
Expand Down
6 changes: 3 additions & 3 deletions docs/Using-Operations-Service.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fun PowerAuthSDK.createOperationsService(appContext: Context, baseURL: String, s
```

- `appContext` - application context
- `baseURL` - address, where your operations server can be reached
- `baseURL` - address, where your operations server can be reached (ending with `/enrollment-server` in the default setup)
- `strategy` - a strategy used when validating HTTPS requests. The following strategies can be used:
- `SSLValidationStrategy.default`
- `SSLValidationStrategy.noValidation`
Expand All @@ -59,7 +59,7 @@ fun PowerAuthSDK.createOperationsService(appContext: Context, baseURL: String, h
```

- `appContext` - application context
- `baseURL`- address, where your operations server can be reached
- `baseURL`- address, where your operations server can be reached (ending with `/enrollment-server` in the default setup)
- `httpClient` - [`OkHttpClient`](https://square.github.io/okhttp/) instance used for API requests

__Optional parameters:__
Expand Down Expand Up @@ -631,7 +631,7 @@ When the `UserOperation` contains a `PreApprovalScreen.QR_SCAN`, the app should
When the app is launched via a deeplink, preserve the data from the deeplink and extract the relevant data. When operations are loaded compare the operation ID from the deeplink data to the operations within the app to find a match.

- Assign TOTP and Type to the Operation
Once the QR code is scanned or a match from the deeplink is found, create a `WMTProximityCheck` with:
Once the QR code is scanned or a match from the deeplink is found, create a `ProximityCheck` with:
- `totp`: The actual Time-Based One-Time Password.
- `type`: Set to `ProximityCheckType.QR_CODE` or `ProximityCheckType.DEEPLINK`.
- `timestampReceived`: The timestamp when the QR code was scanned (by default, it is created as the current timestamp when the object is instantiated).
Expand Down
27 changes: 12 additions & 15 deletions docs/Using-Push-Service.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fun PowerAuthSDK.createPushService(appContext: Context, baseURL: String, strateg
```

- `appContext` - application context
- `baseURL` - address, where your operations server can be reached
- `baseURL` - address, where your operations server can be reached (ending with `/enrollment-server` in the default setup)
- `strategy` - a strategy used when validating HTTPS requests. The following strategies can be used:
- `SSLValidationStrategy.default`
- `SSLValidationStrategy.noValidation`
Expand All @@ -44,7 +44,7 @@ __Optional parameters:__
fun PowerAuthSDK.createPushService(appContext: Context, baseURL: String, httpClient: OkHttpClient): IPushService
```
- `appContext` - application context
- `baseURL` - address, where your operations server can be reached
- `baseURL` - address, where your operations server can be reached (ending with `/enrollment-server` in the default setup)
- `httpClient` - [`OkHttpClient`](https://square.github.io/okhttp/) instance used for API requests

__Optional parameters:__
Expand All @@ -56,25 +56,20 @@ __Optional parameters:__
All available methods of the `IPushService` API are:

- `acceptLanguage` - Language settings, that will be sent along with each request.
- `register(fcmToken: String, callback: (result: Result<Unit>) -> Unit)` - Registers Firebase Cloud Messaging token on the backend
- `registerHuawei(hmsToken: String, callback: (result: Result<Unit>) -> Unit)` - Registers Huawei Mobile Services token on the backend

Messaging token on the backend

- `fcmToken` - Firebase Cloud Messaging token.
- `hmsToken` - Huawei Mobile Services token
- `callback` - Called when the request finishes.
- `register(data: PushData, callback: (result: Result<Unit>) -> Unit)` - Subscribes for WMT push notification on the server

## Registering to Push Notifications
### Android (with Google Play Services)

To register an app to push notifications, you can simply call the `register` method:

### Firebase Cloud Messaging

```kotlin
// first, retrieve Firebase token (do so in the background thread)
FirebaseInstanceId.getInstance().instanceId.addOnCompleteListener { task ->
if (task.isSuccessful) {
task.result?.token?.let { token ->
pushService.register(token) {
pushService.register(PushData.fcm(token)) {
it.onSuccess {
// push notification registered
}.onFailure {
Expand All @@ -90,7 +85,7 @@ FirebaseInstanceId.getInstance().instanceId.addOnCompleteListener { task ->

To be able to successfully process notifications, you need to register the app to receive push notifications in the first place. For more information visit [official documentation](https://firebase.google.com/docs/cloud-messaging/android/client).

### Huawei (HarmonyOS / EMUI)
### Huawei Messaging Service (HarmonyOS / EMUI)
For Huawei devices, you can also register your app to receive push notifications using Huawei Push Kit. To integrate Huawei Push Kit into your app, please refer to the Huawei Push Kit documentation.

```kotlin
Expand All @@ -100,12 +95,13 @@ try {
val token = HmsInstanceId.getInstance(appContext).getToken(appId, "HCM")

if (token.isNotEmpty()) {
pushService.registerHuawei(token) {
pushService.register(PushData.hms(token)) {
it.onSuccess {
// push notification registered
}.onFailure {
// push notification registration failed
// push notification registration failed
}
}
} else {
// token retrieval failed
}
Expand All @@ -114,6 +110,7 @@ try {
}
```
For more information visit [official documentation](https://developer.huawei.com/consumer/en/doc/hmscore-guides/android-client-dev-0000001050042041)

## Receiving WMT Push Notifications

To process the raw notification obtained from the Firebase Cloud Messaging service (FCM) or from the HUAWEI Mobile Services (HMS), you can use the `PushParser` helper class that will parse the notification into a `PushMessage` result.
Expand Down
2 changes: 1 addition & 1 deletion library/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
# and limitations under the License.
#

VERSION_NAME=1.12.0-SNAPSHOT
VERSION_NAME=1.12.1-SNAPSHOT
GROUP_ID=com.wultra.android.mtokensdk
ARTIFACT_ID=wultra-mtoken-sdk
6 changes: 3 additions & 3 deletions library/src/androidTest/java/InboxTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class InboxTests {
fun setup() {
try {
val result = IntegrationUtils.prepareActivation(pin)
pa = result.first
ops = result.second
inbox = result.third
pa = result.pa
ops = result.ops
inbox = result.inbox
} catch (e: Throwable) {
fail("Activation preparation failed: $e")
}
Expand Down
48 changes: 46 additions & 2 deletions library/src/androidTest/java/IntegrationTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import com.wultra.android.mtokensdk.api.operation.model.UserOperation
import com.wultra.android.mtokensdk.api.operation.model.UserOperationStatus
import com.wultra.android.mtokensdk.operation.*
import com.wultra.android.mtokensdk.operation.RejectionData
import com.wultra.android.mtokensdk.push.IPushService
import com.wultra.android.mtokensdk.push.PushData
import com.wultra.android.powerauth.networking.error.ApiError
import io.getlime.security.powerauth.sdk.PowerAuthAuthentication
import io.getlime.security.powerauth.sdk.PowerAuthSDK
Expand All @@ -40,15 +42,17 @@ import java.util.concurrent.TimeUnit
class IntegrationTests {

private lateinit var ops: IOperationsService
private lateinit var push: IPushService
private lateinit var pa: PowerAuthSDK
private val pin = "1234"

@Before
fun setup() {
try {
val result = IntegrationUtils.prepareActivation(pin)
pa = result.first
ops = result.second
pa = result.pa
ops = result.ops
push = result.push
} catch (e: Throwable) {
Assert.fail("Activation preparation failed: $e")
}
Expand Down Expand Up @@ -324,4 +328,44 @@ class IntegrationTests {
Assert.assertNotNull(opRecord)
Assert.assertTrue("${opRecord?.statusReason} should be PREARRANGED_REASON", opRecord?.statusReason == "PREARRANGED_REASON")
}

@Test
fun testRegisterPushLegacyAndroid() {
val future = CompletableFuture<Any?>()
push.register("testToken") { result ->
result.onSuccess { future.complete(null) }
.onFailure { future.completeExceptionally(it) }
}
Assert.assertNull(future.get(10, TimeUnit.SECONDS))
}

@Test
fun testRegisterPushLegacyHuawei() {
val future = CompletableFuture<Any?>()
push.registerHuawei("testToken") { result ->
result.onSuccess { future.complete(null) }
.onFailure { future.completeExceptionally(it) }
}
Assert.assertNull(future.get(10, TimeUnit.SECONDS))
}

@Test
fun testRegisterPushFCM() {
val future = CompletableFuture<Any?>()
push.register(PushData.fcm("testToken")) { result ->
result.onSuccess { future.complete(null) }
.onFailure { future.completeExceptionally(it) }
}
Assert.assertNull(future.get(10, TimeUnit.SECONDS))
}

@Test
fun testRegisterPushHMS() {
val future = CompletableFuture<Any?>()
push.register(PushData.hms("testToken")) { result ->
result.onSuccess { future.complete(null) }
.onFailure { future.completeExceptionally(it) }
}
Assert.assertNull(future.get(10, TimeUnit.SECONDS))
}
}
4 changes: 2 additions & 2 deletions library/src/androidTest/java/IntegrationTestsDeprecated.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class IntegrationTestsDeprecated {
fun setup() {
try {
val result = IntegrationUtils.prepareActivation(pin)
pa = result.first
ops = result.second
pa = result.pa
ops = result.ops
} catch (e: Throwable) {
Assert.fail("Activation preparation failed: $e")
}
Expand Down
12 changes: 9 additions & 3 deletions library/src/androidTest/java/IntegrationUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import com.wultra.android.mtokensdk.inbox.IInboxService
import com.wultra.android.mtokensdk.inbox.createInboxService
import com.wultra.android.mtokensdk.operation.IOperationsService
import com.wultra.android.mtokensdk.operation.createOperationsService
import com.wultra.android.mtokensdk.push.IPushService
import com.wultra.android.mtokensdk.push.createPushService
import com.wultra.android.powerauth.networking.ssl.SSLValidationStrategy
import io.getlime.security.powerauth.core.ActivationCodeUtil
import io.getlime.security.powerauth.networking.response.CreateActivationResult
Expand Down Expand Up @@ -68,6 +70,8 @@ class TimestampAdapter: TypeAdapter<Date>() {

class IntegrationUtils {

data class ActivationResult(val pa: PowerAuthSDK, val ops: IOperationsService, val inbox: IInboxService, val push: IPushService)

companion object {
val context: Context = ApplicationProvider.getApplicationContext()
private val client = OkHttpClient.Builder().build()
Expand All @@ -81,12 +85,13 @@ class IntegrationUtils {
private val enrollmentUrl = getInstrumentationParameter("enrollmentServerUrl")
private val operationsUrl = getInstrumentationParameter("operationsServerUrl")
private val inboxUrl = getInstrumentationParameter("inboxServerUrl")
private val pushUrl = getInstrumentationParameter("pushServerUrl")
private val sdkConfig = getInstrumentationParameter("sdkConfig")
private var activationName = "" // will be filled when activation is created
private var registrationId = "" // will be filled when activation is created

@Throws
fun prepareActivation(pin: String, userId: String? = null): Triple<PowerAuthSDK, IOperationsService, IInboxService> {
fun prepareActivation(pin: String, userId: String? = null): ActivationResult {

// Be sure that each activation has its own user
activationName = userId ?: UUID.randomUUID().toString()
Expand Down Expand Up @@ -150,10 +155,11 @@ class IntegrationUtils {
.trimIndent()
makeCall<CommitObject>(bodyCommit, "$cloudServerUrl/v2/registrations/${resp.registrationId}/commit")

return Triple(
return ActivationResult(
pa,
pa.createOperationsService(context, operationsUrl, SSLValidationStrategy.system()),
pa.createInboxService(context, inboxUrl, SSLValidationStrategy.system())
pa.createInboxService(context, inboxUrl, SSLValidationStrategy.system()),
pa.createPushService(context, pushUrl, SSLValidationStrategy.system())
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import com.wultra.android.mtokensdk.operation.JSONValue

open class PostApprovalScreen(
/**
* type of PostApprovalScreen is presented with different classes (Starting with `WMTPreApprovalScreen*`)
* type of PostApprovalScreen is presented with different classes (Starting with `PreApprovalScreen*`)
*/
val type: Type
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,24 @@ internal data class PushRegistrationRequestObject(
val token: String,

@SerializedName("platform")
val platform: Platform = Platform.ANDROID
)
/**
* Enum representing the platform for push registration.
*/
internal enum class Platform {
@SerializedName("android")
ANDROID,
val platform: Platform
) {
/**
* Enum representing the platform for push registration.
*/
internal enum class Platform {
@SerializedName("fcm")
FCM,

@SerializedName("hms")
HMS,

// legacy support for Android
@SerializedName("android")
ANDROID,

@SerializedName("huawei")
HUAWEI
// legacy support for Huawei
@SerializedName("huawei")
HUAWEI,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,30 @@ import com.wultra.android.powerauth.networking.tokens.IPowerAuthTokenProvider
import io.getlime.security.powerauth.sdk.PowerAuthSDK
import okhttp3.OkHttpClient

/**
* Convenience factory method to create an IInboxService instance
* from given PowerAuthSDK instance.
*
* @param appContext Application Context
* @param baseURL Base URL for inbox request (ending with `/enrollment-server` in the default setup)
* @param okHttpClient HTTP client instance for networking
* @param userAgent Default user agent for each request.
* @return IInboxService instance
*/
fun PowerAuthSDK.createInboxService(appContext: Context, baseURL: String, okHttpClient: OkHttpClient, userAgent: UserAgent? = null): IInboxService {
return InboxService(okHttpClient, baseURL, this, appContext, null, userAgent)
}

/**
* Convenience factory method to create an IInboxService instance
* from given PowerAuthSDK instance.
*
* @param appContext Application Context
* @param baseURL Base URL for inbox request (ending with `/enrollment-server` in the default setup)
* @param strategy SSL validation strategy for networking
* @param userAgent Default user agent for each request.
* @return IInboxService instance
*/
fun PowerAuthSDK.createInboxService(appContext: Context, baseURL: String, strategy: SSLValidationStrategy, userAgent: UserAgent? = null): IInboxService {
val builder = OkHttpClient.Builder()
strategy.configure(builder)
Expand Down
Loading

0 comments on commit cd9cfc7

Please sign in to comment.