@onekeyfe/react-native-jpush 6.7.0-alpha.436 → 6.7.0-alpha.444
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/android/build.gradle +1 -0
- package/android/fcm/build.gradle +7 -0
- package/android/fcm/src/main/AndroidManifest.xml +10 -1
- package/android/fcm/src/main/java/com/onekey/jpush/fcm/FcmInitProvider.java +25 -0
- package/android/fcm/src/main/java/com/onekey/jpush/fcm/FcmMessagingService.java +14 -2
- package/android/fcm/src/test/java/com/onekey/jpush/fcm/FcmAdapterTest.java +684 -0
- package/android/fcm/src/test/java/com/onekey/jpush/fcm/FcmManifestTest.java +54 -0
- package/android/src/main/java/cn/jiguang/api/utils/JCollectionAuth.java +6 -1
- package/android/src/main/java/cn/jpush/android/local/ActionHelper.java +17 -1
- package/android/src/test/java/com/margelo/nitro/reactnativejpush/JPushCloudHandshakeTest.java +110 -0
- package/android/src/test/java/com/margelo/nitro/reactnativejpush/JPushHandshakeFailureTest.java +73 -0
- package/android/src/test/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushProtocolTest.java +0 -108
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,10 +19,17 @@ yarn workspace @onekeyfe/react-native-jpush typecheck
|
|
|
19
19
|
yarn workspace @onekeyfe/react-native-jpush lint
|
|
20
20
|
yarn workspace @onekeyfe/react-native-jpush test
|
|
21
21
|
|
|
22
|
-
#
|
|
22
|
+
# Resolve JUnit dependencies and run the core/FCM module suites
|
|
23
|
+
(cd example/react-native/android && ./gradlew :onekeyfe_react-native-jpush:testDebugUnitTest :onekey-jpush-fcm:testDebugUnitTest)
|
|
24
|
+
|
|
25
|
+
# macOS: native host build/protocol regressions (loopback servers and isolated RN/Android shims)
|
|
23
26
|
python3 native-modules/react-native-jpush/scripts/verify_clean_sdk.py
|
|
24
27
|
```
|
|
25
28
|
|
|
29
|
+
Java and Objective-C host fixtures live in `tests/android` and `tests/ios`; Python only builds, runs and checks artifacts. Android host tests use real JUnit 4.13.2 from the Gradle dependency cache. The FCM Gradle suite uses Robolectric and real Firebase message/task types, with mocked external token requests. These checks do not establish physical-device or cloud delivery.
|
|
30
|
+
|
|
31
|
+
The external Jiguang sandbox register/login test is explicitly skipped by default. Set `JPUSH_LIVE_TESTS=1` when running either Android test entry to enable it; then all network/protocol/assertion failures fail the test. CI runs both host suites plus Gradle FCM tests. A manual `native-tests` workflow can enable the same cloud check with `jpush_live_cloud`.
|
|
32
|
+
|
|
26
33
|
## Android Google / FCM source transport
|
|
27
34
|
|
|
28
35
|
The optional Android source library in `android/fcm` replaces the opaque Jiguang FCM plugin. It delegates the Google transport to the official Firebase Messaging SDK and forwards tokens and messages into this package's JCore/JPush pipeline. Link it alongside the project's autolinked JPush module (the alias below matches app-monorepo):
|
|
@@ -37,4 +44,14 @@ project(':onekey-jpush-fcm').projectDir = new File(project(':jpush-react-native'
|
|
|
37
44
|
googleImplementation project(':onekey-jpush-fcm')
|
|
38
45
|
```
|
|
39
46
|
|
|
40
|
-
The host retains its existing `google-services.json` and Google Services Gradle plugin. Firebase auto-init stays disabled; JPush initialization requests the token after authorization.
|
|
47
|
+
The host retains its existing `google-services.json` and Google Services Gradle plugin. Firebase auto-init stays disabled; JPush initialization requests the token after authorization. Granting authorization after a previously unauthorized init also requests registration without reconnecting. Cloud acceptance requires the matching Firebase credentials configured in the Jiguang sender.
|
|
48
|
+
|
|
49
|
+
### Client enforcement of stopped/authorization controls
|
|
50
|
+
|
|
51
|
+
Vendor payloads do not need to change. `FcmMessagingService.handleIntent` checks `stopPush()` and collection authorization before calling the pinned Firebase Messaging 25.0.1 receive handler. This covers Firebase's automatic background display of notification/combined payloads as well as data-only delivery. Authorized messages retain Firebase's normal dispatch and deduplication; callback-level guards remain. Blocked events are discarded and are not replayed on resume or authorization grant. Keep the receive-entry integration tests when upgrading Firebase because this override uses its internal public receive hook.
|
|
52
|
+
|
|
53
|
+
The optional Google module disables Firebase notification delegation and removes the unguarded fallback service. A private startup provider clears any existing Google Play services notification delegate on Android 10+ after an upgrade, without requesting a token or changing consent. Other delegates are preserved. The provider runs before JS initialization, including when push is stopped or consent is absent. Existing delegation is revoked when the updated app first starts; code in a never-started update cannot revoke an already-installed delegate. Direct-boot reception before user unlock is not enabled.
|
|
54
|
+
|
|
55
|
+
For vendor background notification/combined payloads, Firebase still owns display after the gate allows delivery. Their click action must remain `cn.jpush.android.intent.NOTIFICATION_OPENED` so the private JPush activity receives `JMessageExtra`. Data-only payloads continue through the native arrival/custom-message pipeline.
|
|
56
|
+
|
|
57
|
+
Device acceptance must cover background notification-only, combined and data-only payloads: normal authorized delivery, no display/callback after stop or authorization withdrawal, normal delivery after resume/regrant, and an upgrade from existing Play services delegation. Robolectric tests run Firebase's real Intent receive/display code with only its transport acknowledgement mocked; they do not establish vendor cloud or physical-device acceptance.
|
package/android/build.gradle
CHANGED
|
@@ -107,6 +107,7 @@ android {
|
|
|
107
107
|
testOptions {
|
|
108
108
|
unitTests.returnDefaultValues = true
|
|
109
109
|
unitTests.all {
|
|
110
|
+
inputs.property("jpushLiveTests", System.getenv("JPUSH_LIVE_TESTS") ?: "0")
|
|
110
111
|
testLogging {
|
|
111
112
|
events "passed", "skipped", "failed", "standardOut", "standardError"
|
|
112
113
|
outputs.upToDateWhen { false }
|
package/android/fcm/build.gradle
CHANGED
|
@@ -14,6 +14,10 @@ android {
|
|
|
14
14
|
sourceCompatibility JavaVersion.VERSION_1_8
|
|
15
15
|
targetCompatibility JavaVersion.VERSION_1_8
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
testOptions {
|
|
19
|
+
unitTests.includeAndroidResources = true
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
def coreProject = rootProject.subprojects.find {
|
|
@@ -26,4 +30,7 @@ if (coreProject == null) {
|
|
|
26
30
|
dependencies {
|
|
27
31
|
implementation project(coreProject.path)
|
|
28
32
|
implementation 'com.google.firebase:firebase-messaging:25.0.1'
|
|
33
|
+
testImplementation 'junit:junit:4.13.2'
|
|
34
|
+
testImplementation 'org.robolectric:robolectric:4.17'
|
|
35
|
+
testImplementation 'org.mockito:mockito-core:5.20.0'
|
|
29
36
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
xmlns:tools="http://schemas.android.com/tools">
|
|
2
3
|
<application>
|
|
3
4
|
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
|
|
5
|
+
<meta-data android:name="firebase_messaging_notification_delegation_enabled" android:value="false" />
|
|
6
|
+
<provider
|
|
7
|
+
android:name="com.onekey.jpush.fcm.FcmInitProvider"
|
|
8
|
+
android:authorities="${applicationId}.onekey.fcm.init"
|
|
9
|
+
android:exported="false" />
|
|
10
|
+
<service
|
|
11
|
+
android:name="com.google.firebase.messaging.FirebaseMessagingService"
|
|
12
|
+
tools:node="remove" />
|
|
4
13
|
<service android:name="com.onekey.jpush.fcm.FcmMessagingService" android:exported="false">
|
|
5
14
|
<intent-filter>
|
|
6
15
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.onekey.jpush.fcm;
|
|
2
|
+
|
|
3
|
+
import android.app.NotificationManager;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.os.Build;
|
|
6
|
+
import cn.jiguang.plugins.push.common.JLogger;
|
|
7
|
+
import cn.jpush.android.service.InitProvider;
|
|
8
|
+
|
|
9
|
+
public final class FcmInitProvider extends InitProvider {
|
|
10
|
+
@Override
|
|
11
|
+
public boolean onCreate() {
|
|
12
|
+
super.onCreate();
|
|
13
|
+
Context context = getContext();
|
|
14
|
+
if (context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
15
|
+
NotificationManager notifications = context.getSystemService(NotificationManager.class);
|
|
16
|
+
// Firebase caches delegation initialization across upgrades, so manifest opt-out alone
|
|
17
|
+
// does not revoke an existing Play services delegate.
|
|
18
|
+
if (notifications != null && "com.google.android.gms".equals(notifications.getNotificationDelegate())) {
|
|
19
|
+
notifications.setNotificationDelegate(null);
|
|
20
|
+
JLogger.i("FCM notification delegation disabled");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
package com.onekey.jpush.fcm;
|
|
2
2
|
|
|
3
|
+
import android.content.Intent;
|
|
3
4
|
import android.os.Bundle;
|
|
5
|
+
import cn.jiguang.api.JCoreManager;
|
|
4
6
|
import cn.jiguang.core.JCoreStorage;
|
|
5
7
|
import cn.jiguang.plugins.push.common.JLogger;
|
|
6
|
-
import cn.jpush.android.api.JPushInterface;
|
|
7
8
|
import cn.jpush.android.api.JThirdPlatFormInterface;
|
|
8
9
|
import cn.jpush.android.local.ActionHelper;
|
|
9
10
|
import com.google.firebase.messaging.FirebaseMessagingService;
|
|
@@ -12,12 +13,23 @@ import java.util.Map;
|
|
|
12
13
|
import org.json.JSONObject;
|
|
13
14
|
|
|
14
15
|
public final class FcmMessagingService extends FirebaseMessagingService {
|
|
16
|
+
@Override
|
|
17
|
+
public void handleIntent(Intent intent) {
|
|
18
|
+
// Firebase may display background notifications before onMessageReceived is called.
|
|
19
|
+
if (JCoreStorage.isPushStopped(this) || !JCoreStorage.isCollectionAuth(this)) {
|
|
20
|
+
JLogger.i("FCM receive blocked by push or authorization state");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
super.handleIntent(intent);
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
@Override
|
|
16
27
|
public void onNewToken(String token) {
|
|
17
28
|
if (JCoreStorage.isPushStopped(this) || !JCoreStorage.isCollectionAuth(this)) {
|
|
18
29
|
return;
|
|
19
30
|
}
|
|
20
|
-
|
|
31
|
+
// Initialize the transport without fetching and submitting this token a second time.
|
|
32
|
+
JCoreManager.init(getApplicationContext());
|
|
21
33
|
FcmPushManager.acceptToken(getApplicationContext(), token);
|
|
22
34
|
}
|
|
23
35
|
|
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
package com.onekey.jpush.fcm;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.*;
|
|
4
|
+
import static org.mockito.ArgumentMatchers.*;
|
|
5
|
+
import static org.mockito.Mockito.*;
|
|
6
|
+
|
|
7
|
+
import android.app.ActivityManager;
|
|
8
|
+
import android.app.KeyguardManager;
|
|
9
|
+
import android.app.Notification;
|
|
10
|
+
import android.app.NotificationManager;
|
|
11
|
+
import android.content.Context;
|
|
12
|
+
import android.content.Intent;
|
|
13
|
+
import android.content.pm.ActivityInfo;
|
|
14
|
+
import android.content.pm.ApplicationInfo;
|
|
15
|
+
import android.content.pm.ResolveInfo;
|
|
16
|
+
import android.os.Bundle;
|
|
17
|
+
import android.os.Looper;
|
|
18
|
+
import android.os.Process;
|
|
19
|
+
import cn.jiguang.api.JCoreManager;
|
|
20
|
+
import cn.jiguang.api.utils.JCollectionAuth;
|
|
21
|
+
import cn.jiguang.core.JCoreCrypto;
|
|
22
|
+
import cn.jiguang.core.JCoreStorage;
|
|
23
|
+
import cn.jiguang.core.JCoreTcpEngine;
|
|
24
|
+
import cn.jiguang.core.JCoreWireCodec;
|
|
25
|
+
import cn.jiguang.plugins.push.common.JLogger;
|
|
26
|
+
import cn.jpush.android.api.CustomMessage;
|
|
27
|
+
import cn.jpush.android.api.JPushInterface;
|
|
28
|
+
import cn.jpush.android.api.JThirdPlatFormInterface;
|
|
29
|
+
import cn.jpush.android.api.NotificationMessage;
|
|
30
|
+
import cn.jpush.android.local.ActionHelper;
|
|
31
|
+
import cn.jpush.android.service.JNotifyActivity;
|
|
32
|
+
import cn.jpush.android.service.JPushMessageReceiver;
|
|
33
|
+
import com.google.android.gms.common.GoogleApiAvailabilityLight;
|
|
34
|
+
import com.google.android.gms.tasks.TaskCompletionSource;
|
|
35
|
+
import com.google.android.gms.tasks.Tasks;
|
|
36
|
+
import com.google.firebase.FirebaseApp;
|
|
37
|
+
import com.google.firebase.FirebaseOptions;
|
|
38
|
+
import com.google.firebase.messaging.FirebaseMessaging;
|
|
39
|
+
import com.google.firebase.messaging.FirebaseMessagingService;
|
|
40
|
+
import com.google.firebase.messaging.RemoteMessage;
|
|
41
|
+
import java.io.IOException;
|
|
42
|
+
import java.nio.charset.StandardCharsets;
|
|
43
|
+
import java.util.ArrayList;
|
|
44
|
+
import java.util.Collections;
|
|
45
|
+
import java.util.List;
|
|
46
|
+
import java.util.Map;
|
|
47
|
+
import org.junit.After;
|
|
48
|
+
import org.junit.Before;
|
|
49
|
+
import org.junit.Test;
|
|
50
|
+
import org.junit.runner.RunWith;
|
|
51
|
+
import org.mockito.ArgumentCaptor;
|
|
52
|
+
import org.mockito.MockedStatic;
|
|
53
|
+
import org.robolectric.Robolectric;
|
|
54
|
+
import org.robolectric.RobolectricTestRunner;
|
|
55
|
+
import org.robolectric.RuntimeEnvironment;
|
|
56
|
+
import org.robolectric.Shadows;
|
|
57
|
+
import org.robolectric.annotation.Config;
|
|
58
|
+
import org.robolectric.annotation.LooperMode;
|
|
59
|
+
import org.robolectric.util.ReflectionHelpers;
|
|
60
|
+
|
|
61
|
+
@RunWith(RobolectricTestRunner.class)
|
|
62
|
+
@Config(sdk = 28, manifest = Config.NONE)
|
|
63
|
+
@LooperMode(LooperMode.Mode.PAUSED)
|
|
64
|
+
public class FcmAdapterTest {
|
|
65
|
+
private Context context;
|
|
66
|
+
private FcmMessagingService service;
|
|
67
|
+
private NotificationManager notifications;
|
|
68
|
+
private Object firebaseRpc;
|
|
69
|
+
private final List<NotificationMessage> arrived = new ArrayList<>();
|
|
70
|
+
private final List<NotificationMessage> opened = new ArrayList<>();
|
|
71
|
+
private final List<CustomMessage> custom = new ArrayList<>();
|
|
72
|
+
|
|
73
|
+
@Before
|
|
74
|
+
public void setUp() throws Exception {
|
|
75
|
+
context = RuntimeEnvironment.getApplication();
|
|
76
|
+
ReflectionHelpers.setStaticField(JCoreStorage.class, "sAppContext", null);
|
|
77
|
+
ReflectionHelpers.<Map<String, Object>>getStaticField(JCoreStorage.class, "MEMORY_FALLBACK").clear();
|
|
78
|
+
JCoreStorage.setAppContext(context);
|
|
79
|
+
JCoreStorage.setCollectionAuth(context, true);
|
|
80
|
+
JCoreStorage.setPushStopped(context, false);
|
|
81
|
+
ReflectionHelpers.setStaticField(JThirdPlatFormInterface.class, "fcmPlugin", null);
|
|
82
|
+
ReflectionHelpers.setStaticField(JThirdPlatFormInterface.class, "fcmRegistered", false);
|
|
83
|
+
ReflectionHelpers.<java.util.Queue<String>>getStaticField(FirebaseMessagingService.class, "recentlyReceivedMessageIds").clear();
|
|
84
|
+
JLogger.setLoggerEnable(true);
|
|
85
|
+
JLogger.clearRecentLogs();
|
|
86
|
+
notifications = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
87
|
+
notifications.cancelAll();
|
|
88
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
|
|
89
|
+
ActionHelper.getInstance().clearRegisteredReceivers();
|
|
90
|
+
ActionHelper.getInstance().registerReceiver(new JPushMessageReceiver() {
|
|
91
|
+
@Override public void onNotifyMessageArrived(Context c, NotificationMessage message) { arrived.add(message); }
|
|
92
|
+
@Override public void onNotifyMessageOpened(Context c, NotificationMessage message) { opened.add(message); }
|
|
93
|
+
@Override public void onMessage(Context c, CustomMessage message) { custom.add(message); }
|
|
94
|
+
});
|
|
95
|
+
service = Robolectric.buildService(FcmMessagingService.class).create().get();
|
|
96
|
+
// Isolate only the SDK's transport acknowledgement; run its real receive/display code.
|
|
97
|
+
firebaseRpc = mock(Class.forName("com.google.android.gms.cloudmessaging.Rpc"));
|
|
98
|
+
ReflectionHelpers.setField(service, "rpc", firebaseRpc);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@After
|
|
102
|
+
public void tearDown() {
|
|
103
|
+
ActionHelper.getInstance().clearRegisteredReceivers();
|
|
104
|
+
notifications.cancelAll();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Test
|
|
108
|
+
public void coreDiscoversSourceAdapterOnceAndRebindsTheCurrentToken() {
|
|
109
|
+
FirebaseMessaging provider = mock(FirebaseMessaging.class);
|
|
110
|
+
when(provider.getToken()).thenReturn(Tasks.forResult("current_secret_token"));
|
|
111
|
+
GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
|
|
112
|
+
when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0);
|
|
113
|
+
ActionHelper dispatcher = mock(ActionHelper.class);
|
|
114
|
+
JCoreTcpEngine engine = synchronousEngine();
|
|
115
|
+
try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
|
|
116
|
+
MockedStatic<FirebaseApp> app = mockStatic(FirebaseApp.class);
|
|
117
|
+
MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
|
|
118
|
+
MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class);
|
|
119
|
+
MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class)) {
|
|
120
|
+
firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
|
|
121
|
+
google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
|
|
122
|
+
helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
|
|
123
|
+
tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
|
|
124
|
+
JThirdPlatFormInterface.initializeFcm(context);
|
|
125
|
+
JThirdPlatFormInterface.initializeFcm(context);
|
|
126
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
127
|
+
app.verify(() -> FirebaseApp.initializeApp(context), times(2));
|
|
128
|
+
verify(provider, times(2)).getToken();
|
|
129
|
+
ArgumentCaptor<Bundle> actions = ArgumentCaptor.forClass(Bundle.class);
|
|
130
|
+
verify(dispatcher, times(2)).doAction(eq(context), eq("third_action"), actions.capture());
|
|
131
|
+
for (Bundle action : actions.getAllValues()) {
|
|
132
|
+
assertEquals("current_secret_token", action.getString("token"));
|
|
133
|
+
assertEquals((byte) 8, action.getByte("platform"));
|
|
134
|
+
}
|
|
135
|
+
assertLogsExclude("current_secret_token");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@Test
|
|
140
|
+
public void missingFirebaseConfigurationDoesNotEscapeCoreInitialization() {
|
|
141
|
+
GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
|
|
142
|
+
when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0);
|
|
143
|
+
JCoreTcpEngine engine = synchronousEngine();
|
|
144
|
+
try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
|
|
145
|
+
MockedStatic<FirebaseApp> app = mockStatic(FirebaseApp.class);
|
|
146
|
+
MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
|
|
147
|
+
MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class)) {
|
|
148
|
+
firebase.when(FirebaseMessaging::getInstance).thenThrow(new IllegalStateException("private_configuration_details"));
|
|
149
|
+
google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
|
|
150
|
+
tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
|
|
151
|
+
JThirdPlatFormInterface.initializeFcm(context);
|
|
152
|
+
assertTrue(JLogger.getRecentLogs().toString().contains("FCM registration unavailable"));
|
|
153
|
+
assertLogsExclude("private_configuration_details");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@Test
|
|
158
|
+
public void authorizationGrantedAfterSkippedInitRequestsTokenWithoutReconnect() {
|
|
159
|
+
JCoreStorage.setCollectionAuth(context, false);
|
|
160
|
+
JCoreStorage.setConnected(context, true);
|
|
161
|
+
FirebaseMessaging provider = mock(FirebaseMessaging.class);
|
|
162
|
+
when(provider.getToken()).thenReturn(Tasks.forResult("consented_secret_token"));
|
|
163
|
+
GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
|
|
164
|
+
when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0);
|
|
165
|
+
ActionHelper dispatcher = mock(ActionHelper.class);
|
|
166
|
+
JCoreTcpEngine engine = synchronousEngine();
|
|
167
|
+
try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
|
|
168
|
+
MockedStatic<FirebaseApp> app = mockStatic(FirebaseApp.class);
|
|
169
|
+
MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
|
|
170
|
+
MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class);
|
|
171
|
+
MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class);
|
|
172
|
+
MockedStatic<JCoreManager> core = mockStatic(JCoreManager.class)) {
|
|
173
|
+
firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
|
|
174
|
+
google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
|
|
175
|
+
helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
|
|
176
|
+
tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
|
|
177
|
+
JPushInterface.init(context);
|
|
178
|
+
verifyNoInteractions(provider);
|
|
179
|
+
JCollectionAuth.setAuth(context, true);
|
|
180
|
+
JCollectionAuth.setAuth(context, true);
|
|
181
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
182
|
+
verify(provider).getToken();
|
|
183
|
+
assertEquals("consented_secret_token", tokenAction(dispatcher).getString("token"));
|
|
184
|
+
assertTrue(JCoreStorage.isConnected(context));
|
|
185
|
+
core.verify(() -> JCoreManager.init(context));
|
|
186
|
+
assertLogsExclude("consented_secret_token");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@Test
|
|
191
|
+
public void grantingAuthorizationWhileStoppedDoesNotRequestRegistration() {
|
|
192
|
+
JCoreStorage.setCollectionAuth(context, false);
|
|
193
|
+
JCoreStorage.setPushStopped(context, true);
|
|
194
|
+
try (MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class)) {
|
|
195
|
+
JCollectionAuth.setAuth(context, true);
|
|
196
|
+
assertTrue(JCollectionAuth.isAuth(context));
|
|
197
|
+
tcp.verifyNoInteractions();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@Test
|
|
202
|
+
public void revokedAuthorizationCancelsQueuedFcmRegistration() {
|
|
203
|
+
JCoreStorage.setCollectionAuth(context, false);
|
|
204
|
+
JCoreTcpEngine engine = mock(JCoreTcpEngine.class);
|
|
205
|
+
try (MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class);
|
|
206
|
+
MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class)) {
|
|
207
|
+
tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
|
|
208
|
+
JCollectionAuth.setAuth(context, true);
|
|
209
|
+
ArgumentCaptor<Runnable> work = ArgumentCaptor.forClass(Runnable.class);
|
|
210
|
+
verify(engine).executeAsync(work.capture());
|
|
211
|
+
JCollectionAuth.setAuth(context, false);
|
|
212
|
+
work.getValue().run();
|
|
213
|
+
firebase.verifyNoInteractions();
|
|
214
|
+
assertFalse(JCollectionAuth.isAuth(context));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@Test
|
|
219
|
+
public void tokenSuccessDispatchesPlatformEightWithoutPersistingBeforeAck() {
|
|
220
|
+
JCoreStorage.saveThirdToken(context, (byte) 8, "previous_acknowledged_token");
|
|
221
|
+
FirebaseMessaging provider = mock(FirebaseMessaging.class);
|
|
222
|
+
TaskCompletionSource<String> request = new TaskCompletionSource<>();
|
|
223
|
+
when(provider.getToken()).thenReturn(request.getTask());
|
|
224
|
+
ActionHelper dispatcher = mock(ActionHelper.class);
|
|
225
|
+
try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
|
|
226
|
+
MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class)) {
|
|
227
|
+
firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
|
|
228
|
+
helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
|
|
229
|
+
assertEquals("previous_acknowledged_token", new FcmPushManager().getToken(context));
|
|
230
|
+
verifyNoInteractions(dispatcher);
|
|
231
|
+
request.setResult("rotated_secret_token");
|
|
232
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
233
|
+
Bundle token = tokenAction(dispatcher);
|
|
234
|
+
assertEquals("rotated_secret_token", token.getString("token"));
|
|
235
|
+
assertEquals((byte) 8, token.getByte("platform"));
|
|
236
|
+
assertEquals("previous_acknowledged_token", JCoreStorage.getThirdToken(context, (byte) 8));
|
|
237
|
+
assertLogsExclude("rotated_secret_token");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@Test
|
|
242
|
+
public void failedTokenRequestDoesNotBindOrLogRawError() {
|
|
243
|
+
FirebaseMessaging provider = mock(FirebaseMessaging.class);
|
|
244
|
+
when(provider.getToken()).thenReturn(Tasks.forException(new IOException("secret_provider_error")));
|
|
245
|
+
ActionHelper dispatcher = mock(ActionHelper.class);
|
|
246
|
+
try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
|
|
247
|
+
MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class)) {
|
|
248
|
+
firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
|
|
249
|
+
helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
|
|
250
|
+
new FcmPushManager().getToken(context);
|
|
251
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
252
|
+
verifyNoInteractions(dispatcher);
|
|
253
|
+
assertTrue(JLogger.getRecentLogs().toString().contains("IOException"));
|
|
254
|
+
assertLogsExclude("secret_provider_error");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@Test
|
|
259
|
+
public void tokenCallbackRechecksStopAndAuthorizationAfterRequest() {
|
|
260
|
+
for (boolean stopped : new boolean[]{true, false}) {
|
|
261
|
+
JCoreStorage.setPushStopped(context, false);
|
|
262
|
+
JCoreStorage.setCollectionAuth(context, true);
|
|
263
|
+
FirebaseMessaging provider = mock(FirebaseMessaging.class);
|
|
264
|
+
TaskCompletionSource<String> request = new TaskCompletionSource<>();
|
|
265
|
+
when(provider.getToken()).thenReturn(request.getTask());
|
|
266
|
+
ActionHelper dispatcher = mock(ActionHelper.class);
|
|
267
|
+
try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
|
|
268
|
+
MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class)) {
|
|
269
|
+
firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
|
|
270
|
+
helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
|
|
271
|
+
new FcmPushManager().getToken(context);
|
|
272
|
+
JCoreStorage.setPushStopped(context, stopped);
|
|
273
|
+
JCoreStorage.setCollectionAuth(context, stopped);
|
|
274
|
+
request.setResult("late_secret_token");
|
|
275
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
276
|
+
verifyNoInteractions(dispatcher);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@Test
|
|
282
|
+
public void newTokenInitializesCoreAndForwardsRotation() {
|
|
283
|
+
JCoreStorage.setConnected(context, true);
|
|
284
|
+
FirebaseMessaging provider = mock(FirebaseMessaging.class);
|
|
285
|
+
when(provider.getToken()).thenReturn(Tasks.forResult("new_secret_token"));
|
|
286
|
+
GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
|
|
287
|
+
when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0);
|
|
288
|
+
JCoreTcpEngine engine = synchronousEngine();
|
|
289
|
+
try (MockedStatic<JCoreManager> core = mockStatic(JCoreManager.class);
|
|
290
|
+
MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class);
|
|
291
|
+
MockedStatic<FirebaseApp> app = mockStatic(FirebaseApp.class);
|
|
292
|
+
MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
|
|
293
|
+
MockedStatic<JCoreTcpEngine> tcp = mockStatic(JCoreTcpEngine.class)) {
|
|
294
|
+
firebase.when(FirebaseMessaging::getInstance).thenReturn(provider);
|
|
295
|
+
google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
|
|
296
|
+
tcp.when(JCoreTcpEngine::getInstance).thenReturn(engine);
|
|
297
|
+
service.handleIntent(new Intent("com.google.firebase.messaging.NEW_TOKEN").putExtra("token", "new_secret_token"));
|
|
298
|
+
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
299
|
+
core.verify(() -> JCoreManager.init(context));
|
|
300
|
+
ArgumentCaptor<byte[]> body = ArgumentCaptor.forClass(byte[].class);
|
|
301
|
+
verify(engine).enqueueOrSendRequest(eq(context), eq(JCoreWireCodec.CMD_THIRD_TOKEN),
|
|
302
|
+
eq(JCoreWireCodec.VER_THIRD_TOKEN), anyLong(), body.capture(), anyLong(), eq(3),
|
|
303
|
+
any(JCoreTcpEngine.RequestCallback.class));
|
|
304
|
+
assertArrayEquals(JCoreWireCodec.buildThirdTokenPayload("new_secret_token", (byte) 8, false), body.getValue());
|
|
305
|
+
verifyNoInteractions(provider);
|
|
306
|
+
assertLogsExclude("new_secret_token");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@Test
|
|
311
|
+
public void stoppedOrUnauthorizedNewTokenDoesNotInitializeCore() {
|
|
312
|
+
try (MockedStatic<JCoreManager> core = mockStatic(JCoreManager.class)) {
|
|
313
|
+
JCoreStorage.setPushStopped(context, true);
|
|
314
|
+
service.onNewToken("ignored");
|
|
315
|
+
JCoreStorage.setPushStopped(context, false);
|
|
316
|
+
JCoreStorage.setCollectionAuth(context, false);
|
|
317
|
+
service.onNewToken("ignored");
|
|
318
|
+
core.verifyNoInteractions();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@Test
|
|
323
|
+
public void emptyTokensAreIgnored() {
|
|
324
|
+
ActionHelper dispatcher = mock(ActionHelper.class);
|
|
325
|
+
try (MockedStatic<ActionHelper> helper = mockStatic(ActionHelper.class)) {
|
|
326
|
+
helper.when(ActionHelper::getInstance).thenReturn(dispatcher);
|
|
327
|
+
FcmPushManager.acceptToken(context, null);
|
|
328
|
+
FcmPushManager.acceptToken(context, "");
|
|
329
|
+
verifyNoInteractions(dispatcher);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@Test
|
|
334
|
+
public void googlePlayAvailabilityAndNotificationDelegation() {
|
|
335
|
+
GoogleApiAvailabilityLight availability = mock(GoogleApiAvailabilityLight.class);
|
|
336
|
+
when(availability.isGooglePlayServicesAvailable(context)).thenReturn(0, 9);
|
|
337
|
+
try (MockedStatic<GoogleApiAvailabilityLight> google = mockStatic(GoogleApiAvailabilityLight.class);
|
|
338
|
+
MockedStatic<JPushInterface> core = mockStatic(JPushInterface.class)) {
|
|
339
|
+
google.when(GoogleApiAvailabilityLight::getInstance).thenReturn(availability);
|
|
340
|
+
FcmPushManager manager = new FcmPushManager();
|
|
341
|
+
assertTrue(manager.isSupport(context));
|
|
342
|
+
assertFalse(manager.isSupport(context));
|
|
343
|
+
assertFalse(manager.isSupport(null));
|
|
344
|
+
manager.clearNotification(context, 123);
|
|
345
|
+
manager.clearAllNotification(context);
|
|
346
|
+
core.verify(() -> JPushInterface.clearNotificationById(context, 123));
|
|
347
|
+
core.verify(() -> JPushInterface.clearAllNotifications(context));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@Test
|
|
352
|
+
public void firebaseInitializationAndApplicationMetadata() {
|
|
353
|
+
FirebaseApp app = mock(FirebaseApp.class);
|
|
354
|
+
FirebaseOptions options = new FirebaseOptions.Builder()
|
|
355
|
+
.setApplicationId("1:123456:android:synthetic").setApiKey("synthetic_api_key").build();
|
|
356
|
+
when(app.getOptions()).thenReturn(options);
|
|
357
|
+
try (MockedStatic<FirebaseApp> firebase = mockStatic(FirebaseApp.class)) {
|
|
358
|
+
firebase.when(FirebaseApp::getInstance).thenReturn(app);
|
|
359
|
+
FcmPushManager manager = new FcmPushManager();
|
|
360
|
+
manager.init(context);
|
|
361
|
+
manager.register(context, new Bundle());
|
|
362
|
+
firebase.verify(() -> FirebaseApp.initializeApp(context), times(2));
|
|
363
|
+
assertEquals(options.getApplicationId(), manager.getAppId(context));
|
|
364
|
+
assertEquals(options.getApiKey(), manager.getAppkey(context));
|
|
365
|
+
assertEquals((byte) 8, manager.getRomType(context));
|
|
366
|
+
assertFalse(manager.isNeedClearToken(context));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
@Test
|
|
371
|
+
public void firebaseNotificationFieldsFillMetadataAndTapOpensOnce() {
|
|
372
|
+
Bundle payload = metadata("{\"_jmsgid_\":\"fcm_notification\",\"n_extras\":{\"route\":\"synthetic_route\"}}");
|
|
373
|
+
payload.putString("gcm.n.e", "1");
|
|
374
|
+
payload.putString("gcm.n.title", "private_title");
|
|
375
|
+
payload.putString("gcm.n.body", "private_body");
|
|
376
|
+
service.onMessageReceived(new RemoteMessage(payload));
|
|
377
|
+
assertEquals(1, arrived.size());
|
|
378
|
+
assertEquals("private_title", arrived.get(0).notificationTitle);
|
|
379
|
+
assertEquals("private_body", arrived.get(0).notificationContent);
|
|
380
|
+
assertEquals(8, arrived.get(0).platform);
|
|
381
|
+
List<Notification> posted = Shadows.shadowOf(notifications).getAllNotifications();
|
|
382
|
+
assertEquals(1, posted.size());
|
|
383
|
+
assertEquals("private_title", posted.get(0).extras.getString(Notification.EXTRA_TITLE));
|
|
384
|
+
Intent tap = Shadows.shadowOf(posted.get(0).contentIntent).getSavedIntent();
|
|
385
|
+
assertEquals(JNotifyActivity.class.getName(), tap.getComponent().getClassName());
|
|
386
|
+
Robolectric.buildActivity(JNotifyActivity.class, tap).create();
|
|
387
|
+
assertEquals(1, opened.size());
|
|
388
|
+
assertEquals("fcm_notification", opened.get(0).msgId);
|
|
389
|
+
assertTrue(opened.get(0).notificationExtras.contains("synthetic_route"));
|
|
390
|
+
assertLogsExclude("private_title", "private_body", "synthetic_route");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@Test
|
|
394
|
+
public void postedDataOnlyTapIsIgnoredAfterStopOrAuthorizationWithdrawal() {
|
|
395
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
|
|
396
|
+
ActivityInfo host = new ActivityInfo();
|
|
397
|
+
host.packageName = context.getPackageName();
|
|
398
|
+
host.name = context.getPackageName() + ".MainActivity";
|
|
399
|
+
host.applicationInfo = new ApplicationInfo(context.getApplicationInfo());
|
|
400
|
+
ResolveInfo resolvedHost = new ResolveInfo();
|
|
401
|
+
resolvedHost.activityInfo = host;
|
|
402
|
+
Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER).setPackage(context.getPackageName());
|
|
403
|
+
Shadows.shadowOf(context.getPackageManager()).addResolveInfoForIntent(launcher, resolvedHost);
|
|
404
|
+
for (boolean stopped : new boolean[]{true, false}) {
|
|
405
|
+
JCoreStorage.setPushStopped(context, false);
|
|
406
|
+
JCoreStorage.setCollectionAuth(context, true);
|
|
407
|
+
service.handleIntent(firebaseReceiveIntent("deferred_tap_" + stopped, false));
|
|
408
|
+
List<Notification> posted = Shadows.shadowOf(notifications).getAllNotifications();
|
|
409
|
+
assertEquals(1, posted.size());
|
|
410
|
+
Intent tap = Shadows.shadowOf(posted.get(0).contentIntent).getSavedIntent();
|
|
411
|
+
JCoreStorage.setPushStopped(context, stopped);
|
|
412
|
+
JCoreStorage.setCollectionAuth(context, stopped);
|
|
413
|
+
Robolectric.buildActivity(JNotifyActivity.class, tap).create();
|
|
414
|
+
assertTrue(opened.isEmpty());
|
|
415
|
+
assertNull(Shadows.shadowOf(RuntimeEnvironment.getApplication()).getNextStartedActivity());
|
|
416
|
+
notifications.cancelAll();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
@Test
|
|
421
|
+
public void compressedMetadataRetainsItsOwnTitleAndExtras() {
|
|
422
|
+
Bundle payload = metadata(compress("{\"msg_id\":\"zipped\",\"override_msg_id\":\"\",\"n_title\":\"metadata_title\",\"n_content\":\"metadata_body\",\"n_extras\":{\"route\":\"private_route\"}}"));
|
|
423
|
+
payload.putString("gcm.n.e", "1");
|
|
424
|
+
payload.putString("gcm.n.title", "fallback_title");
|
|
425
|
+
payload.putString("gcm.n.body", "fallback_body");
|
|
426
|
+
service.onMessageReceived(new RemoteMessage(payload));
|
|
427
|
+
assertEquals(1, arrived.size());
|
|
428
|
+
assertEquals("zipped", arrived.get(0).msgId);
|
|
429
|
+
assertEquals("metadata_title", arrived.get(0).notificationTitle);
|
|
430
|
+
assertTrue(arrived.get(0).notificationExtras.contains("private_route"));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
@Test
|
|
434
|
+
public void standardFirebaseDataUsesTransportIdAndPreservesExtras() {
|
|
435
|
+
Bundle payload = new Bundle();
|
|
436
|
+
payload.putString("google.message_id", "transport_id");
|
|
437
|
+
payload.putString("n_title", "Synthetic notification");
|
|
438
|
+
payload.putString("n_content", "Synthetic body");
|
|
439
|
+
payload.putString("route", "synthetic_route");
|
|
440
|
+
service.onMessageReceived(new RemoteMessage(payload));
|
|
441
|
+
assertEquals(1, arrived.size());
|
|
442
|
+
assertEquals("transport_id", arrived.get(0).msgId);
|
|
443
|
+
assertTrue(arrived.get(0).notificationExtras.contains("synthetic_route"));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
@Test
|
|
447
|
+
public void customDataDoesNotDisplayANotification() {
|
|
448
|
+
service.onMessageReceived(new RemoteMessage(metadata("{\"msg_id\":\"custom_id\",\"message\":\"private_custom_body\",\"content_type\":\"text\",\"extras\":{\"route\":\"private_custom_route\"}}")));
|
|
449
|
+
assertEquals(1, custom.size());
|
|
450
|
+
assertEquals("custom_id", custom.get(0).messageId);
|
|
451
|
+
assertEquals("private_custom_body", custom.get(0).message);
|
|
452
|
+
assertEquals("text", custom.get(0).contentType);
|
|
453
|
+
assertTrue(arrived.isEmpty());
|
|
454
|
+
assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
455
|
+
assertLogsExclude("private_custom_body", "private_custom_route");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
@Test
|
|
459
|
+
public void malformedAndOversizedPayloadsDoNotReachCallbacks() {
|
|
460
|
+
service.onMessageReceived(new RemoteMessage(metadata("{broken")));
|
|
461
|
+
service.onMessageReceived(new RemoteMessage(metadata("{\"jg_c\":\"not-gzip\"}")));
|
|
462
|
+
StringBuilder oversized = new StringBuilder();
|
|
463
|
+
for (int i = 0; i < 70000; i++) oversized.append('x');
|
|
464
|
+
service.onMessageReceived(new RemoteMessage(metadata(compress(oversized.toString()))));
|
|
465
|
+
assertTrue(arrived.isEmpty());
|
|
466
|
+
assertTrue(custom.isEmpty());
|
|
467
|
+
assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
@Test
|
|
471
|
+
public void stoppedAndUnauthorizedNotificationsAreIgnored() {
|
|
472
|
+
String body = "{\"msg_id\":\"blocked\",\"n_content\":\"synthetic\"}";
|
|
473
|
+
JCoreStorage.setPushStopped(context, true);
|
|
474
|
+
service.onMessageReceived(new RemoteMessage(metadata(body)));
|
|
475
|
+
JCoreStorage.setPushStopped(context, false);
|
|
476
|
+
JCoreStorage.setCollectionAuth(context, false);
|
|
477
|
+
service.onMessageReceived(new RemoteMessage(metadata(body)));
|
|
478
|
+
JCoreStorage.setCollectionAuth(context, true);
|
|
479
|
+
assertTrue(arrived.isEmpty());
|
|
480
|
+
assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
@Test
|
|
484
|
+
public void foregroundDisplayDisabledSuppressesNotificationAndArrival() {
|
|
485
|
+
service.onMessageReceived(foregroundSuppressedMessage());
|
|
486
|
+
assertTrue(arrived.isEmpty());
|
|
487
|
+
assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
@Test
|
|
491
|
+
public void backgroundDataOnlyMessageStillDisplaysAndDispatchesArrival() {
|
|
492
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
|
|
493
|
+
RemoteMessage message = foregroundSuppressedMessage();
|
|
494
|
+
assertNull(message.getNotification());
|
|
495
|
+
service.onMessageReceived(message);
|
|
496
|
+
assertEquals(1, arrived.size());
|
|
497
|
+
assertEquals("foreground_suppressed", arrived.get(0).msgId);
|
|
498
|
+
assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
@Test
|
|
502
|
+
public void foregroundDisplayDecisionUsesCurrentProcessStateForEachMessage() {
|
|
503
|
+
service.onMessageReceived(foregroundSuppressedMessage());
|
|
504
|
+
assertTrue(arrived.isEmpty());
|
|
505
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
|
|
506
|
+
service.onMessageReceived(foregroundSuppressedMessage());
|
|
507
|
+
assertEquals(1, arrived.size());
|
|
508
|
+
notifications.cancelAll();
|
|
509
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
|
|
510
|
+
service.onMessageReceived(foregroundSuppressedMessage());
|
|
511
|
+
assertEquals(1, arrived.size());
|
|
512
|
+
assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
@Test
|
|
516
|
+
public void lockedScreenDoesNotSuppressDataOnlyNotification() {
|
|
517
|
+
KeyguardManager keyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
|
|
518
|
+
Shadows.shadowOf(keyguard).setKeyguardLocked(true);
|
|
519
|
+
service.onMessageReceived(foregroundSuppressedMessage());
|
|
520
|
+
assertEquals(1, arrived.size());
|
|
521
|
+
assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
@Test
|
|
525
|
+
public void stoppedBackgroundCombinedIntentCannotUseFirebaseAutomaticDisplay() {
|
|
526
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
|
|
527
|
+
JCoreStorage.setPushStopped(context, true);
|
|
528
|
+
service.handleIntent(firebaseReceiveIntent("stopped_combined", true));
|
|
529
|
+
assertTrue(arrived.isEmpty());
|
|
530
|
+
assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
531
|
+
verifyNoInteractions(firebaseRpc);
|
|
532
|
+
assertLogsExclude("private_title", "private_body");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
@Test
|
|
536
|
+
public void unauthorizedBackgroundNotificationIntentCannotUseFirebaseAutomaticDisplay() {
|
|
537
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
|
|
538
|
+
JCoreStorage.setCollectionAuth(context, false);
|
|
539
|
+
Intent notificationOnly = firebaseReceiveIntent("unauthorized_notification", true);
|
|
540
|
+
notificationOnly.removeExtra("JMessageExtra");
|
|
541
|
+
service.handleIntent(notificationOnly);
|
|
542
|
+
assertTrue(arrived.isEmpty());
|
|
543
|
+
assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
544
|
+
verifyNoInteractions(firebaseRpc);
|
|
545
|
+
assertLogsExclude("private_title", "private_body");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
@Test
|
|
549
|
+
public void authorizedBackgroundCombinedIntentKeepsFirebaseDisplayAndDeduplication() {
|
|
550
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
|
|
551
|
+
Intent receive = firebaseReceiveIntent("allowed_combined", true);
|
|
552
|
+
service.handleIntent(receive);
|
|
553
|
+
service.handleIntent(new Intent(receive));
|
|
554
|
+
assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
555
|
+
assertTrue(arrived.isEmpty());
|
|
556
|
+
assertEquals(2, mockingDetails(firebaseRpc).getInvocations().size());
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
@Test
|
|
560
|
+
public void authorizedBackgroundDataOnlyIntentUsesNativeDisplayAndFirebaseDeduplication() {
|
|
561
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
|
|
562
|
+
Intent receive = firebaseReceiveIntent("allowed_data", false);
|
|
563
|
+
service.handleIntent(receive);
|
|
564
|
+
service.handleIntent(new Intent(receive));
|
|
565
|
+
assertEquals(1, arrived.size());
|
|
566
|
+
assertEquals("allowed_data", arrived.get(0).msgId);
|
|
567
|
+
assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
@Test
|
|
571
|
+
public void receiveGateRechecksStateAfterStopWithdrawalAndRegrant() {
|
|
572
|
+
setProcessImportance(ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE);
|
|
573
|
+
JCoreStorage.setPushStopped(context, true);
|
|
574
|
+
service.handleIntent(firebaseReceiveIntent("stopped", true));
|
|
575
|
+
JCoreStorage.setPushStopped(context, false);
|
|
576
|
+
JCoreStorage.setCollectionAuth(context, false);
|
|
577
|
+
service.handleIntent(firebaseReceiveIntent("withdrawn", true));
|
|
578
|
+
assertEquals(0, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
579
|
+
JCoreStorage.setCollectionAuth(context, true);
|
|
580
|
+
service.handleIntent(firebaseReceiveIntent("regranted", true));
|
|
581
|
+
assertEquals(1, Shadows.shadowOf(notifications).getAllNotifications().size());
|
|
582
|
+
assertEquals(1, mockingDetails(firebaseRpc).getInvocations().size());
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
@Test
|
|
586
|
+
@Config(sdk = 34)
|
|
587
|
+
public void startupRevokesOldPlayServicesDelegateWithoutTokenOrConsent() {
|
|
588
|
+
JCoreStorage.setPushStopped(context, true);
|
|
589
|
+
JCoreStorage.setCollectionAuth(context, false);
|
|
590
|
+
notifications.setNotificationDelegate("com.google.android.gms");
|
|
591
|
+
try (MockedStatic<FirebaseMessaging> firebase = mockStatic(FirebaseMessaging.class)) {
|
|
592
|
+
Robolectric.buildContentProvider(FcmInitProvider.class).create();
|
|
593
|
+
assertNull(notifications.getNotificationDelegate());
|
|
594
|
+
assertFalse(JCollectionAuth.isAuth(context));
|
|
595
|
+
assertTrue(JCoreStorage.isPushStopped(context));
|
|
596
|
+
firebase.verifyNoInteractions();
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
@Test
|
|
601
|
+
@Config(sdk = 34)
|
|
602
|
+
public void startupPreservesOtherNotificationDelegates() {
|
|
603
|
+
notifications.setNotificationDelegate("other.notification.delegate");
|
|
604
|
+
Robolectric.buildContentProvider(FcmInitProvider.class).create();
|
|
605
|
+
assertEquals("other.notification.delegate", notifications.getNotificationDelegate());
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
@Test
|
|
609
|
+
public void automaticFirebaseTapLaunchesHostAndKeepsCompressedMetadata() {
|
|
610
|
+
ActivityInfo activity = new ActivityInfo();
|
|
611
|
+
activity.packageName = context.getPackageName();
|
|
612
|
+
activity.name = context.getPackageName() + ".MainActivity";
|
|
613
|
+
activity.applicationInfo = new ApplicationInfo(context.getApplicationInfo());
|
|
614
|
+
ResolveInfo resolve = new ResolveInfo();
|
|
615
|
+
resolve.activityInfo = activity;
|
|
616
|
+
Intent launcher = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER).setPackage(context.getPackageName());
|
|
617
|
+
Shadows.shadowOf(context.getPackageManager()).addResolveInfoForIntent(launcher, resolve);
|
|
618
|
+
Intent tap = new Intent(JPushInterface.ACTION_NOTIFICATION_OPENED)
|
|
619
|
+
.putExtra("JMessageExtra", compress("{\"msg_id\":\"cold_tap\",\"n_extras\":{\"route\":\"synthetic_route\"}}"));
|
|
620
|
+
Robolectric.buildActivity(JNotifyActivity.class, tap).create();
|
|
621
|
+
assertEquals(1, opened.size());
|
|
622
|
+
assertEquals("cold_tap", opened.get(0).msgId);
|
|
623
|
+
Intent launched = Shadows.shadowOf(RuntimeEnvironment.getApplication()).getNextStartedActivity();
|
|
624
|
+
assertNotNull(launched);
|
|
625
|
+
assertEquals(activity.name, launched.getComponent().getClassName());
|
|
626
|
+
assertTrue(opened.get(0).notificationExtras.contains("synthetic_route"));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private static Bundle metadata(String json) {
|
|
630
|
+
Bundle payload = new Bundle();
|
|
631
|
+
payload.putString("JMessageExtra", json);
|
|
632
|
+
return payload;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private static RemoteMessage foregroundSuppressedMessage() {
|
|
636
|
+
return new RemoteMessage(metadata("{\"msg_id\":\"foreground_suppressed\",\"n_content\":\"synthetic\",\"n_foreground\":\"0\"}"));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
private static Intent firebaseReceiveIntent(String id, boolean notification) {
|
|
640
|
+
Intent receive = new Intent("com.google.android.c2dm.intent.RECEIVE")
|
|
641
|
+
.putExtra("google.message_id", id)
|
|
642
|
+
.putExtras(metadata("{\"msg_id\":\"" + id + "\",\"n_content\":\"private_body\",\"n_foreground\":\"0\"}"));
|
|
643
|
+
if (notification) {
|
|
644
|
+
receive.putExtra("gcm.n.e", "1");
|
|
645
|
+
receive.putExtra("gcm.n.title", "private_title");
|
|
646
|
+
receive.putExtra("gcm.n.body", "private_body");
|
|
647
|
+
receive.putExtra("gcm.n.icon", "android");
|
|
648
|
+
}
|
|
649
|
+
return receive;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private void setProcessImportance(int importance) {
|
|
653
|
+
ActivityManager.RunningAppProcessInfo process = new ActivityManager.RunningAppProcessInfo();
|
|
654
|
+
process.pid = Process.myPid();
|
|
655
|
+
process.importance = importance;
|
|
656
|
+
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
657
|
+
Shadows.shadowOf(manager).setProcesses(Collections.singletonList(process));
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
private static String compress(String json) {
|
|
661
|
+
return "{\"jg_c\":\"" + JCoreCrypto.base64Encode(JCoreCrypto.gzipCompress(json.getBytes(StandardCharsets.UTF_8))) + "\"}";
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private Bundle tokenAction(ActionHelper dispatcher) {
|
|
665
|
+
ArgumentCaptor<Bundle> action = ArgumentCaptor.forClass(Bundle.class);
|
|
666
|
+
verify(dispatcher).doAction(eq(context), eq("third_action"), action.capture());
|
|
667
|
+
assertEquals(JThirdPlatFormInterface.ACTION_REGISTER_TOKEN, action.getValue().getString("third_key_action"));
|
|
668
|
+
return action.getValue();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
private static JCoreTcpEngine synchronousEngine() {
|
|
672
|
+
JCoreTcpEngine engine = mock(JCoreTcpEngine.class);
|
|
673
|
+
doAnswer(call -> {
|
|
674
|
+
call.<Runnable>getArgument(0).run();
|
|
675
|
+
return null;
|
|
676
|
+
}).when(engine).executeAsync(any(Runnable.class));
|
|
677
|
+
return engine;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private static void assertLogsExclude(String... values) {
|
|
681
|
+
String logs = JLogger.getRecentLogs().toString();
|
|
682
|
+
for (String value : values) assertFalse("Sensitive payload appeared in native logger", logs.contains(value));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package com.onekey.jpush.fcm;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.*;
|
|
4
|
+
|
|
5
|
+
import android.content.Context;
|
|
6
|
+
import android.content.pm.PackageInfo;
|
|
7
|
+
import android.content.pm.PackageManager;
|
|
8
|
+
import android.content.pm.ProviderInfo;
|
|
9
|
+
import android.content.pm.ServiceInfo;
|
|
10
|
+
import android.content.Intent;
|
|
11
|
+
import org.junit.Test;
|
|
12
|
+
import org.junit.runner.RunWith;
|
|
13
|
+
import org.robolectric.RobolectricTestRunner;
|
|
14
|
+
import org.robolectric.RuntimeEnvironment;
|
|
15
|
+
import org.robolectric.annotation.Config;
|
|
16
|
+
|
|
17
|
+
@RunWith(RobolectricTestRunner.class)
|
|
18
|
+
@Config(sdk = 34)
|
|
19
|
+
public class FcmManifestTest {
|
|
20
|
+
@Test
|
|
21
|
+
public void mergedManifestRoutesOnlyToGuardedServiceAndDisablesDelegation() throws Exception {
|
|
22
|
+
Context context = RuntimeEnvironment.getApplication();
|
|
23
|
+
PackageManager packages = context.getPackageManager();
|
|
24
|
+
PackageInfo info = packages.getPackageInfo(context.getPackageName(),
|
|
25
|
+
PackageManager.GET_META_DATA | PackageManager.GET_PROVIDERS | PackageManager.GET_SERVICES);
|
|
26
|
+
assertTrue(info.applicationInfo.metaData.containsKey("firebase_messaging_notification_delegation_enabled"));
|
|
27
|
+
assertFalse(info.applicationInfo.metaData.getBoolean("firebase_messaging_notification_delegation_enabled"));
|
|
28
|
+
assertTrue(info.applicationInfo.metaData.containsKey("firebase_messaging_auto_init_enabled"));
|
|
29
|
+
assertFalse(info.applicationInfo.metaData.getBoolean("firebase_messaging_auto_init_enabled"));
|
|
30
|
+
boolean initializerFound = false;
|
|
31
|
+
for (ProviderInfo provider : info.providers) {
|
|
32
|
+
if (FcmInitProvider.class.getName().equals(provider.name)) {
|
|
33
|
+
initializerFound = true;
|
|
34
|
+
assertFalse(provider.exported);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
assertTrue("The startup delegate cleanup must be installed", initializerFound);
|
|
38
|
+
boolean guardedServiceFound = false;
|
|
39
|
+
for (ServiceInfo service : info.services) {
|
|
40
|
+
assertNotEquals("Unguarded Firebase fallback must not be installed",
|
|
41
|
+
"com.google.firebase.messaging.FirebaseMessagingService", service.name);
|
|
42
|
+
if (FcmMessagingService.class.getName().equals(service.name)) {
|
|
43
|
+
guardedServiceFound = true;
|
|
44
|
+
assertFalse(service.exported);
|
|
45
|
+
assertFalse(service.directBootAware);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
assertTrue("The guarded receive service must be installed", guardedServiceFound);
|
|
49
|
+
android.content.pm.ResolveInfo handler = packages.resolveService(
|
|
50
|
+
new Intent("com.google.firebase.MESSAGING_EVENT").setPackage(context.getPackageName()), 0);
|
|
51
|
+
assertNotNull(handler);
|
|
52
|
+
assertEquals(FcmMessagingService.class.getName(), handler.serviceInfo.name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -2,10 +2,15 @@ package cn.jiguang.api.utils;
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
4
|
import cn.jiguang.core.JCoreStorage;
|
|
5
|
+
import cn.jpush.android.api.JThirdPlatFormInterface;
|
|
5
6
|
|
|
6
7
|
public class JCollectionAuth {
|
|
7
|
-
public static void setAuth(Context context, boolean auth) {
|
|
8
|
+
public static synchronized void setAuth(Context context, boolean auth) {
|
|
9
|
+
boolean wasAuthorized = JCoreStorage.isCollectionAuth(context);
|
|
8
10
|
JCoreStorage.setCollectionAuth(context, auth);
|
|
11
|
+
if (auth && !wasAuthorized) {
|
|
12
|
+
JThirdPlatFormInterface.initializeFcm(context);
|
|
13
|
+
}
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
public static boolean isAuth(Context context) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package cn.jpush.android.local;
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
|
+
import android.app.ActivityManager;
|
|
5
|
+
import android.app.KeyguardManager;
|
|
4
6
|
import android.app.Notification;
|
|
5
7
|
import android.app.NotificationChannel;
|
|
6
8
|
import android.app.NotificationManager;
|
|
@@ -154,7 +156,7 @@ public class ActionHelper {
|
|
|
154
156
|
custom.platform = 8;
|
|
155
157
|
dispatchCustomMessageEvent(context, custom);
|
|
156
158
|
} else {
|
|
157
|
-
if ("0".equals(message.displayForeground)) {
|
|
159
|
+
if ("0".equals(message.displayForeground) && isApplicationForeground(context)) {
|
|
158
160
|
return;
|
|
159
161
|
}
|
|
160
162
|
postSystemNotification(context, message);
|
|
@@ -172,6 +174,16 @@ public class ActionHelper {
|
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
|
|
177
|
+
private boolean isApplicationForeground(Context context) {
|
|
178
|
+
KeyguardManager keyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
|
|
179
|
+
if (keyguard != null && keyguard.isKeyguardLocked()) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
ActivityManager.RunningAppProcessInfo process = new ActivityManager.RunningAppProcessInfo();
|
|
183
|
+
ActivityManager.getMyMemoryState(process);
|
|
184
|
+
return process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
|
185
|
+
}
|
|
186
|
+
|
|
175
187
|
public void doSingleAction(Context context, String action, Object data) {
|
|
176
188
|
}
|
|
177
189
|
|
|
@@ -220,6 +232,10 @@ public class ActionHelper {
|
|
|
220
232
|
}
|
|
221
233
|
if (msg instanceof NotificationMessage) {
|
|
222
234
|
NotificationMessage notificationMessage = (NotificationMessage) msg;
|
|
235
|
+
if (notificationMessage.platform == 8
|
|
236
|
+
&& (JCoreStorage.isPushStopped(context) || !JCoreStorage.isCollectionAuth(context))) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
223
239
|
dispatchNotificationEvent(context, "notify_opened", notificationMessage);
|
|
224
240
|
broadcastNotificationOpened(context, notificationMessage);
|
|
225
241
|
if (fcmClick || notificationMessage.platform == 8) {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
package com.margelo.nitro.reactnativejpush;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.*;
|
|
4
|
+
import cn.jiguang.core.JCoreCrypto;
|
|
5
|
+
import cn.jiguang.core.JCoreWireCodec;
|
|
6
|
+
import java.io.InputStream;
|
|
7
|
+
import java.io.OutputStream;
|
|
8
|
+
import java.net.DatagramPacket;
|
|
9
|
+
import java.net.DatagramSocket;
|
|
10
|
+
import java.net.InetAddress;
|
|
11
|
+
import java.net.InetSocketAddress;
|
|
12
|
+
import java.net.Socket;
|
|
13
|
+
import java.util.Arrays;
|
|
14
|
+
import java.util.Collections;
|
|
15
|
+
import java.util.Set;
|
|
16
|
+
import org.junit.Assume;
|
|
17
|
+
import org.junit.Test;
|
|
18
|
+
|
|
19
|
+
public class JPushCloudHandshakeTest {
|
|
20
|
+
@Test
|
|
21
|
+
public void liveCloudHandshake() throws Exception {
|
|
22
|
+
boolean enabled = "1".equals(System.getenv("JPUSH_LIVE_TESTS"));
|
|
23
|
+
if (!enabled) {
|
|
24
|
+
System.out.println("SKIPPED live Jiguang handshake: set JPUSH_LIVE_TESTS=1 to enable");
|
|
25
|
+
}
|
|
26
|
+
Assume.assumeTrue("External cloud handshake requires JPUSH_LIVE_TESTS=1", enabled);
|
|
27
|
+
String sisJson;
|
|
28
|
+
try (DatagramSocket udp = new DatagramSocket()) {
|
|
29
|
+
String request = JCoreWireCodec.buildSisRequestJson(
|
|
30
|
+
1, ReactNativeJpushProtocolTest.DEMO_TEST_APP_KEY, 0L, "",
|
|
31
|
+
Collections.<String>emptyList(), 1
|
|
32
|
+
);
|
|
33
|
+
byte[] packet = JCoreWireCodec.encodeUdpSisPacket(request, JCoreCrypto.nextPositiveSeed24());
|
|
34
|
+
udp.setSoTimeout(2500);
|
|
35
|
+
udp.send(new DatagramPacket(packet, packet.length, InetAddress.getByName("s.jpush.cn"), 19000));
|
|
36
|
+
DatagramPacket response = new DatagramPacket(new byte[4096], 4096);
|
|
37
|
+
udp.receive(response);
|
|
38
|
+
sisJson = JCoreWireCodec.decodeUdpSisPacket(Arrays.copyOf(response.getData(), response.getLength()));
|
|
39
|
+
}
|
|
40
|
+
assertTrue("SIS response must contain endpoints", sisJson.contains("\"ips\""));
|
|
41
|
+
Set<String> endpoints = JCoreWireCodec.extractJsonStringArray(sisJson, "ips");
|
|
42
|
+
assertFalse("SIS endpoints must not be empty", endpoints.isEmpty());
|
|
43
|
+
String endpoint = endpoints.iterator().next();
|
|
44
|
+
int colon = endpoint.lastIndexOf(':');
|
|
45
|
+
try (Socket tcp = new Socket()) {
|
|
46
|
+
tcp.connect(new InetSocketAddress(endpoint.substring(0, colon), Integer.parseInt(endpoint.substring(colon + 1))), 3000);
|
|
47
|
+
tcp.setSoTimeout(5000);
|
|
48
|
+
performHandshake(tcp.getInputStream(), tcp.getOutputStream());
|
|
49
|
+
}
|
|
50
|
+
System.out.println("Live Jiguang register/login handshake passed");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Shared by the opt-in cloud test and deterministic failure-propagation regressions.
|
|
54
|
+
static void performHandshake(InputStream in, OutputStream out) throws Exception {
|
|
55
|
+
int seed = JCoreCrypto.nextRegisterSeed();
|
|
56
|
+
JCoreWireCodec.setLastRegisterSeed(seed);
|
|
57
|
+
byte[] regBody = JCoreWireCodec.buildRegisterPayload(
|
|
58
|
+
ReactNativeJpushProtocolTest.DEMO_TEST_APP_KEY,
|
|
59
|
+
"developer-default",
|
|
60
|
+
ReactNativeJpushProtocolTest.DEMO_TEST_PACKAGE_NAME,
|
|
61
|
+
"msgbench_auto_001"
|
|
62
|
+
);
|
|
63
|
+
byte[] regFrame = JCoreWireCodec.encodeFrame(
|
|
64
|
+
JCoreWireCodec.CMD_REGISTER,
|
|
65
|
+
JCoreWireCodec.VER_REGISTER,
|
|
66
|
+
101L,
|
|
67
|
+
seed,
|
|
68
|
+
0L,
|
|
69
|
+
regBody
|
|
70
|
+
);
|
|
71
|
+
out.write(regFrame);
|
|
72
|
+
out.flush();
|
|
73
|
+
|
|
74
|
+
JCoreWireCodec.DecodedFrame regRespFrame = JCoreWireCodec.readFrameFromStream(in, 0L);
|
|
75
|
+
assertNotNull("Missing register response", regRespFrame);
|
|
76
|
+
assertEquals("Register command", JCoreWireCodec.CMD_REGISTER, regRespFrame.command);
|
|
77
|
+
assertEquals("Register status", 0, regRespFrame.statusCode);
|
|
78
|
+
JCoreWireCodec.RegisterResponse regResp = JCoreWireCodec.parseRegisterResponse(regRespFrame);
|
|
79
|
+
assertNotNull(regResp);
|
|
80
|
+
assertEquals(0L, (long) regResp.statusCode);
|
|
81
|
+
assertTrue(regResp.uid > 0L);
|
|
82
|
+
assertTrue(regResp.registrationId != null && !regResp.registrationId.isEmpty());
|
|
83
|
+
|
|
84
|
+
byte[] loginBody = JCoreWireCodec.buildLoginPayload(
|
|
85
|
+
regResp.password,
|
|
86
|
+
ReactNativeJpushProtocolTest.DEMO_TEST_APP_KEY,
|
|
87
|
+
regResp.deviceId,
|
|
88
|
+
"CN"
|
|
89
|
+
);
|
|
90
|
+
byte[] loginFrame = JCoreWireCodec.encodeFrame(
|
|
91
|
+
JCoreWireCodec.CMD_LOGIN,
|
|
92
|
+
JCoreWireCodec.VER_LOGIN,
|
|
93
|
+
102L,
|
|
94
|
+
0,
|
|
95
|
+
regResp.uid,
|
|
96
|
+
loginBody
|
|
97
|
+
);
|
|
98
|
+
out.write(loginFrame);
|
|
99
|
+
out.flush();
|
|
100
|
+
|
|
101
|
+
JCoreWireCodec.DecodedFrame loginRespFrame = JCoreWireCodec.readFrameFromStream(in, regResp.uid);
|
|
102
|
+
assertNotNull("Missing login response", loginRespFrame);
|
|
103
|
+
assertEquals("Login command", JCoreWireCodec.CMD_LOGIN, loginRespFrame.command);
|
|
104
|
+
assertEquals("Login status", 0, loginRespFrame.statusCode);
|
|
105
|
+
JCoreWireCodec.LoginResponse loginResp = JCoreWireCodec.parseLoginResponse(loginRespFrame);
|
|
106
|
+
assertNotNull(loginResp);
|
|
107
|
+
assertEquals(0L, (long) loginResp.statusCode);
|
|
108
|
+
assertTrue(loginResp.sessionId != 0);
|
|
109
|
+
}
|
|
110
|
+
}
|
package/android/src/test/java/com/margelo/nitro/reactnativejpush/JPushHandshakeFailureTest.java
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package com.margelo.nitro.reactnativejpush;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.*;
|
|
4
|
+
import cn.jiguang.core.JCoreWireCodec;
|
|
5
|
+
import java.io.ByteArrayInputStream;
|
|
6
|
+
import java.io.ByteArrayOutputStream;
|
|
7
|
+
import java.io.IOException;
|
|
8
|
+
import java.io.InputStream;
|
|
9
|
+
import java.util.function.Supplier;
|
|
10
|
+
import org.junit.Test;
|
|
11
|
+
|
|
12
|
+
public class JPushHandshakeFailureTest {
|
|
13
|
+
@Test
|
|
14
|
+
public void networkFailureIsNotConvertedToSuccess() {
|
|
15
|
+
InputStream broken = new InputStream() {
|
|
16
|
+
@Override public int read() throws IOException {
|
|
17
|
+
throw new IOException("Synthetic connection loss");
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
assertThrows(IOException.class, () -> JPushCloudHandshakeTest.performHandshake(broken, new ByteArrayOutputStream()));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Test
|
|
24
|
+
public void missingResponseAssertionIsNotSwallowed() {
|
|
25
|
+
assertThrows(AssertionError.class, () -> JPushCloudHandshakeTest.performHandshake(
|
|
26
|
+
new ByteArrayInputStream(new byte[0]), new ByteArrayOutputStream()
|
|
27
|
+
));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Test
|
|
31
|
+
public void rejectedRegistrationFailsTheHandshake() {
|
|
32
|
+
AssertionError failure = assertThrows(AssertionError.class, () -> JPushCloudHandshakeTest.performHandshake(
|
|
33
|
+
lazyReply(() -> JCoreWireCodec.encodeResponseFrame(
|
|
34
|
+
JCoreWireCodec.CMD_REGISTER, JCoreWireCodec.VER_REGISTER, 101L,
|
|
35
|
+
(int) JCoreWireCodec.getLastRegisterSeed(), 42L, 1009, new byte[0]
|
|
36
|
+
)), new ByteArrayOutputStream()
|
|
37
|
+
));
|
|
38
|
+
assertTrue(failure.getMessage().contains("Register status"));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Test
|
|
42
|
+
public void rejectedLoginFailsTheHandshake() {
|
|
43
|
+
AssertionError failure = assertThrows(AssertionError.class, () -> JPushCloudHandshakeTest.performHandshake(
|
|
44
|
+
lazyReply(() -> {
|
|
45
|
+
byte[] registration = JCoreWireCodec.encodeResponseFrame(
|
|
46
|
+
JCoreWireCodec.CMD_REGISTER, JCoreWireCodec.VER_REGISTER, 101L,
|
|
47
|
+
(int) JCoreWireCodec.getLastRegisterSeed(), 42L, 0,
|
|
48
|
+
JCoreWireCodec.buildRegisterResponsePayload(42L, "synthetic_password", "synthetic_registration", "synthetic_device")
|
|
49
|
+
);
|
|
50
|
+
byte[] login = JCoreWireCodec.encodeResponseFrame(
|
|
51
|
+
JCoreWireCodec.CMD_LOGIN, JCoreWireCodec.VER_LOGIN, 102L, 0, 42L, 1005, new byte[0]
|
|
52
|
+
);
|
|
53
|
+
byte[] replies = new byte[registration.length + login.length];
|
|
54
|
+
System.arraycopy(registration, 0, replies, 0, registration.length);
|
|
55
|
+
System.arraycopy(login, 0, replies, registration.length, login.length);
|
|
56
|
+
return replies;
|
|
57
|
+
}), new ByteArrayOutputStream()
|
|
58
|
+
));
|
|
59
|
+
assertTrue(failure.getMessage().contains("Login status"));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private static InputStream lazyReply(Supplier<byte[]> response) {
|
|
63
|
+
return new InputStream() {
|
|
64
|
+
private ByteArrayInputStream input;
|
|
65
|
+
private ByteArrayInputStream input() {
|
|
66
|
+
if (input == null) input = new ByteArrayInputStream(response.get());
|
|
67
|
+
return input;
|
|
68
|
+
}
|
|
69
|
+
@Override public int read() { return input().read(); }
|
|
70
|
+
@Override public int read(byte[] bytes, int offset, int length) { return input().read(bytes, offset, length); }
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
package/android/src/test/java/com/margelo/nitro/reactnativejpush/ReactNativeJpushProtocolTest.java
CHANGED
|
@@ -200,114 +200,6 @@ public class ReactNativeJpushProtocolTest {
|
|
|
200
200
|
assertTrue(propPayload.length > 2);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
@Test
|
|
204
|
-
public void testLiveJiguangCloudHandshakeWhenNetworkReachable() throws Exception {
|
|
205
|
-
String sisJson = "";
|
|
206
|
-
DatagramSocket udp = null;
|
|
207
|
-
try {
|
|
208
|
-
String reqJson = JCoreWireCodec.buildSisRequestJson(
|
|
209
|
-
1,
|
|
210
|
-
DEMO_TEST_APP_KEY,
|
|
211
|
-
0L,
|
|
212
|
-
"",
|
|
213
|
-
Collections.<String>emptyList(),
|
|
214
|
-
1
|
|
215
|
-
);
|
|
216
|
-
byte[] reqPkt = JCoreWireCodec.encodeUdpSisPacket(reqJson, JCoreCrypto.nextPositiveSeed24());
|
|
217
|
-
udp = new DatagramSocket();
|
|
218
|
-
udp.setSoTimeout(2500);
|
|
219
|
-
InetAddress addr = InetAddress.getByName("s.jpush.cn");
|
|
220
|
-
udp.send(new DatagramPacket(reqPkt, reqPkt.length, addr, 19000));
|
|
221
|
-
byte[] buf = new byte[4096];
|
|
222
|
-
DatagramPacket respPkt = new DatagramPacket(buf, buf.length);
|
|
223
|
-
udp.receive(respPkt);
|
|
224
|
-
byte[] received = Arrays.copyOf(respPkt.getData(), respPkt.getLength());
|
|
225
|
-
sisJson = JCoreWireCodec.decodeUdpSisPacket(received);
|
|
226
|
-
} catch (Throwable networkUnavailable) {
|
|
227
|
-
// Skip live cloud assertions when running in an offline or firewall-restricted CI container.
|
|
228
|
-
return;
|
|
229
|
-
} finally {
|
|
230
|
-
if (udp != null) {
|
|
231
|
-
udp.close();
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
assertTrue(sisJson.contains("\"ips\""));
|
|
236
|
-
Set<String> ips = JCoreWireCodec.extractJsonStringArray(sisJson, "ips");
|
|
237
|
-
assertTrue(!ips.isEmpty());
|
|
238
|
-
|
|
239
|
-
String firstIpPort = ips.iterator().next();
|
|
240
|
-
int colon = firstIpPort.lastIndexOf(':');
|
|
241
|
-
String host = firstIpPort.substring(0, colon);
|
|
242
|
-
int port = Integer.parseInt(firstIpPort.substring(colon + 1));
|
|
243
|
-
|
|
244
|
-
Socket tcp = new Socket();
|
|
245
|
-
try {
|
|
246
|
-
tcp.connect(new InetSocketAddress(host, port), 3000);
|
|
247
|
-
tcp.setSoTimeout(5000);
|
|
248
|
-
InputStream in = tcp.getInputStream();
|
|
249
|
-
OutputStream out = tcp.getOutputStream();
|
|
250
|
-
|
|
251
|
-
int seed = JCoreCrypto.nextRegisterSeed();
|
|
252
|
-
JCoreWireCodec.setLastRegisterSeed(seed);
|
|
253
|
-
byte[] regBody = JCoreWireCodec.buildRegisterPayload(
|
|
254
|
-
DEMO_TEST_APP_KEY,
|
|
255
|
-
"developer-default",
|
|
256
|
-
DEMO_TEST_PACKAGE_NAME,
|
|
257
|
-
"msgbench_auto_001"
|
|
258
|
-
);
|
|
259
|
-
byte[] regFrame = JCoreWireCodec.encodeFrame(
|
|
260
|
-
JCoreWireCodec.CMD_REGISTER,
|
|
261
|
-
JCoreWireCodec.VER_REGISTER,
|
|
262
|
-
101L,
|
|
263
|
-
seed,
|
|
264
|
-
0L,
|
|
265
|
-
regBody
|
|
266
|
-
);
|
|
267
|
-
out.write(regFrame);
|
|
268
|
-
out.flush();
|
|
269
|
-
|
|
270
|
-
JCoreWireCodec.DecodedFrame regRespFrame = JCoreWireCodec.readFrameFromStream(in, 0L);
|
|
271
|
-
assertNotNull(regRespFrame);
|
|
272
|
-
JCoreWireCodec.RegisterResponse regResp = JCoreWireCodec.parseRegisterResponse(regRespFrame);
|
|
273
|
-
assertNotNull(regResp);
|
|
274
|
-
assertEquals(0L, (long) regResp.statusCode);
|
|
275
|
-
assertTrue(regResp.uid > 0L);
|
|
276
|
-
assertTrue(regResp.registrationId != null && !regResp.registrationId.isEmpty());
|
|
277
|
-
|
|
278
|
-
byte[] loginBody = JCoreWireCodec.buildLoginPayload(
|
|
279
|
-
regResp.password,
|
|
280
|
-
DEMO_TEST_APP_KEY,
|
|
281
|
-
regResp.deviceId,
|
|
282
|
-
"CN"
|
|
283
|
-
);
|
|
284
|
-
byte[] loginFrame = JCoreWireCodec.encodeFrame(
|
|
285
|
-
JCoreWireCodec.CMD_LOGIN,
|
|
286
|
-
JCoreWireCodec.VER_LOGIN,
|
|
287
|
-
102L,
|
|
288
|
-
0,
|
|
289
|
-
regResp.uid,
|
|
290
|
-
loginBody
|
|
291
|
-
);
|
|
292
|
-
out.write(loginFrame);
|
|
293
|
-
out.flush();
|
|
294
|
-
|
|
295
|
-
JCoreWireCodec.DecodedFrame loginRespFrame = JCoreWireCodec.readFrameFromStream(in, regResp.uid);
|
|
296
|
-
assertNotNull(loginRespFrame);
|
|
297
|
-
JCoreWireCodec.LoginResponse loginResp = JCoreWireCodec.parseLoginResponse(loginRespFrame);
|
|
298
|
-
assertNotNull(loginResp);
|
|
299
|
-
assertEquals(0L, (long) loginResp.statusCode);
|
|
300
|
-
assertTrue(loginResp.sessionId != 0);
|
|
301
|
-
} catch (Throwable tcpUnavailable) {
|
|
302
|
-
// Ignore transient external network drop after UDP SIS succeeds.
|
|
303
|
-
} finally {
|
|
304
|
-
try {
|
|
305
|
-
tcp.close();
|
|
306
|
-
} catch (Throwable ignored) {
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
203
|
@Test
|
|
312
204
|
public void testEmptyRegistrationBeforeHandshakeMultiRuntimeEventsAndManifestExportedFalse() throws Exception {
|
|
313
205
|
// 1. Fresh state before TCP Register (cmd=0) must return "" for getRegistrationID (no fake 1a0018970a...)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-jpush",
|
|
3
|
-
"version": "6.7.0-alpha.
|
|
3
|
+
"version": "6.7.0-alpha.444",
|
|
4
4
|
"description": "Clean-room JPush and JCore React Native Nitro module for OneKey",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"typescript": "^5.9.2"
|
|
95
95
|
},
|
|
96
96
|
"peerDependencies": {
|
|
97
|
-
"@onekeyfe/react-native-native-logger": "6.7.0-alpha.
|
|
97
|
+
"@onekeyfe/react-native-native-logger": "6.7.0-alpha.444",
|
|
98
98
|
"react": "*",
|
|
99
99
|
"react-native": "*",
|
|
100
100
|
"react-native-nitro-modules": "0.37.0"
|