Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intorduced FCM & HMS for push #175

Merged
merged 6 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
26 changes: 11 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,11 +95,11 @@ 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
}
kober32 marked this conversation as resolved.
Show resolved Hide resolved
} else {
// token retrieval failed
Expand All @@ -114,6 +109,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
Loading