forked from Ivasoft/mattermost-mobile
Request permissions for Android push notifications and refactor code to use network client (#7059)
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package com.mattermost.helpers
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.LruCache
|
||||
|
||||
class BitmapCache {
|
||||
private var memoryCache: LruCache<String, Bitmap>
|
||||
|
||||
init {
|
||||
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
|
||||
val cacheSize = maxMemory / 8
|
||||
memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
|
||||
override fun sizeOf(key: String, bitmap: Bitmap): Int {
|
||||
return bitmap.byteCount / 1024
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBitmapFromMemCache(key: String): Bitmap? {
|
||||
return memoryCache.get(key)
|
||||
}
|
||||
|
||||
fun addBitmapToMemoryCache(key: String, bitmap: Bitmap) {
|
||||
if (getBitmapFromMemCache(key) == null) {
|
||||
memoryCache.put(key, bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
@@ -28,7 +27,6 @@ import androidx.core.app.Person;
|
||||
import androidx.core.app.RemoteInput;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.mattermost.rnbeta.*;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -53,7 +51,11 @@ public class CustomPushNotificationHelper {
|
||||
private static NotificationChannel mHighImportanceChannel;
|
||||
private static NotificationChannel mMinImportanceChannel;
|
||||
|
||||
private static void addMessagingStyleMessages(Context context, NotificationCompat.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
|
||||
private static final OkHttpClient client = new OkHttpClient();
|
||||
|
||||
private static final BitmapCache bitmapCache = new BitmapCache();
|
||||
|
||||
private static void addMessagingStyleMessages(NotificationCompat.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
|
||||
String message = bundle.getString("message", bundle.getString("body"));
|
||||
String senderId = bundle.getString("sender_id");
|
||||
String serverUrl = bundle.getString("server_url");
|
||||
@@ -75,7 +77,7 @@ public class CustomPushNotificationHelper {
|
||||
|
||||
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
try {
|
||||
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
|
||||
Bitmap avatar = userAvatar(serverUrl, senderId, urlOverride);
|
||||
if (avatar != null) {
|
||||
sender.setIcon(IconCompat.createWithBitmap(avatar));
|
||||
}
|
||||
@@ -177,12 +179,12 @@ public class CustomPushNotificationHelper {
|
||||
String groupId = is_crt_enabled && !android.text.TextUtils.isEmpty(rootId) ? rootId : channelId;
|
||||
|
||||
addNotificationExtras(notification, bundle);
|
||||
setNotificationIcons(context, notification, bundle);
|
||||
setNotificationMessagingStyle(context, notification, bundle);
|
||||
setNotificationIcons(notification, bundle);
|
||||
setNotificationMessagingStyle(notification, bundle);
|
||||
setNotificationGroup(notification, groupId, createSummary);
|
||||
setNotificationBadgeType(notification);
|
||||
|
||||
setNotificationChannel(context, notification, bundle);
|
||||
setNotificationChannel(context, notification);
|
||||
setNotificationDeleteIntent(context, notification, bundle, notificationId);
|
||||
addNotificationReplyAction(context, notification, bundle, notificationId);
|
||||
|
||||
@@ -254,15 +256,7 @@ public class CustomPushNotificationHelper {
|
||||
return title;
|
||||
}
|
||||
|
||||
private static int getIconResourceId(Context context, String iconName) {
|
||||
final Resources res = context.getResources();
|
||||
String packageName = context.getPackageName();
|
||||
String defType = "mipmap";
|
||||
|
||||
return res.getIdentifier(iconName, defType, packageName);
|
||||
}
|
||||
|
||||
private static NotificationCompat.MessagingStyle getMessagingStyle(Context context, Bundle bundle) {
|
||||
private static NotificationCompat.MessagingStyle getMessagingStyle(Bundle bundle) {
|
||||
NotificationCompat.MessagingStyle messagingStyle;
|
||||
final String senderId = "me";
|
||||
final String serverUrl = bundle.getString("server_url");
|
||||
@@ -275,7 +269,7 @@ public class CustomPushNotificationHelper {
|
||||
|
||||
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
try {
|
||||
Bitmap avatar = userAvatar(context, serverUrl, "me", urlOverride);
|
||||
Bitmap avatar = userAvatar(serverUrl, "me", urlOverride);
|
||||
if (avatar != null) {
|
||||
sender.setIcon(IconCompat.createWithBitmap(avatar));
|
||||
}
|
||||
@@ -288,7 +282,7 @@ public class CustomPushNotificationHelper {
|
||||
|
||||
String conversationTitle = getConversationTitle(bundle);
|
||||
setMessagingStyleConversationTitle(messagingStyle, conversationTitle, bundle);
|
||||
addMessagingStyleMessages(context, messagingStyle, conversationTitle, bundle);
|
||||
addMessagingStyleMessages(messagingStyle, conversationTitle, bundle);
|
||||
|
||||
return messagingStyle;
|
||||
}
|
||||
@@ -315,25 +309,6 @@ public class CustomPushNotificationHelper {
|
||||
return getConversationTitle(bundle);
|
||||
}
|
||||
|
||||
public static int getSmallIconResourceId(Context context, String iconName) {
|
||||
if (iconName == null) {
|
||||
iconName = "ic_notification";
|
||||
}
|
||||
|
||||
int resourceId = getIconResourceId(context, iconName);
|
||||
|
||||
if (resourceId == 0) {
|
||||
iconName = "ic_launcher";
|
||||
resourceId = getIconResourceId(context, iconName);
|
||||
|
||||
if (resourceId == 0) {
|
||||
resourceId = android.R.drawable.ic_dialog_info;
|
||||
}
|
||||
}
|
||||
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
private static String removeSenderNameFromMessage(String message, String senderName) {
|
||||
int index = message.indexOf(senderName);
|
||||
if (index == 0) {
|
||||
@@ -365,7 +340,7 @@ public class CustomPushNotificationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNotificationChannel(Context context, NotificationCompat.Builder notification, Bundle bundle) {
|
||||
private static void setNotificationChannel(Context context, NotificationCompat.Builder notification) {
|
||||
// If Android Oreo or above we need to register a channel
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
@@ -389,8 +364,8 @@ public class CustomPushNotificationHelper {
|
||||
notification.setDeleteIntent(deleteIntent);
|
||||
}
|
||||
|
||||
private static void setNotificationMessagingStyle(Context context, NotificationCompat.Builder notification, Bundle bundle) {
|
||||
NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(context, bundle);
|
||||
private static void setNotificationMessagingStyle(NotificationCompat.Builder notification, Bundle bundle) {
|
||||
NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(bundle);
|
||||
notification.setStyle(messagingStyle);
|
||||
}
|
||||
|
||||
@@ -403,20 +378,18 @@ public class CustomPushNotificationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNotificationIcons(Context context, NotificationCompat.Builder notification, Bundle bundle) {
|
||||
String smallIcon = bundle.getString("smallIcon");
|
||||
private static void setNotificationIcons(NotificationCompat.Builder notification, Bundle bundle) {
|
||||
String channelName = getConversationTitle(bundle);
|
||||
String senderName = bundle.getString("sender_name");
|
||||
String serverUrl = bundle.getString("server_url");
|
||||
String urlOverride = bundle.getString("override_icon_url");
|
||||
|
||||
int smallIconResId = getSmallIconResourceId(context, smallIcon);
|
||||
notification.setSmallIcon(smallIconResId);
|
||||
notification.setSmallIcon(R.mipmap.ic_notification);
|
||||
|
||||
if (serverUrl != null && channelName.equals(senderName)) {
|
||||
try {
|
||||
String senderId = bundle.getString("sender_id");
|
||||
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
|
||||
Bitmap avatar = userAvatar(serverUrl, senderId, urlOverride);
|
||||
if (avatar != null) {
|
||||
notification.setLargeIcon(avatar);
|
||||
}
|
||||
@@ -426,29 +399,31 @@ public class CustomPushNotificationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap userAvatar(Context context, final String serverUrl, final String userId, final String urlOverride) throws IOException {
|
||||
private static Bitmap userAvatar(final String serverUrl, final String userId, final String urlOverride) throws IOException {
|
||||
try {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
Request request;
|
||||
String url;
|
||||
Response response;
|
||||
if (!TextUtils.isEmpty(urlOverride)) {
|
||||
request = new Request.Builder().url(urlOverride).build();
|
||||
Request request = new Request.Builder().url(urlOverride).build();
|
||||
Log.i("ReactNative", String.format("Fetch override profile image %s", urlOverride));
|
||||
response = client.newCall(request).execute();
|
||||
} else {
|
||||
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
|
||||
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
|
||||
url = String.format("%s/api/v4/users/%s/image", serverUrl, userId);
|
||||
Bitmap cached = bitmapCache.getBitmapFromMemCache(userId);
|
||||
if (cached != null) {
|
||||
Bitmap bitmap = cached.copy(cached.getConfig(), false);
|
||||
return getCircleBitmap(bitmap);
|
||||
}
|
||||
String url = String.format("api/v4/users/%s/image", userId);
|
||||
Log.i("ReactNative", String.format("Fetch profile image %s", url));
|
||||
request = new Request.Builder()
|
||||
.header("Authorization", String.format("Bearer %s", token))
|
||||
.url(url)
|
||||
.build();
|
||||
response = Network.getSync(serverUrl, url, null);
|
||||
}
|
||||
Response response = client.newCall(request).execute();
|
||||
|
||||
if (response.code() == 200) {
|
||||
assert response.body() != null;
|
||||
byte[] bytes = Objects.requireNonNull(response.body()).bytes();
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
if (TextUtils.isEmpty(urlOverride) && !TextUtils.isEmpty(userId)) {
|
||||
bitmapCache.addBitmapToMemoryCache(userId, bitmap.copy(bitmap.getConfig(), false));
|
||||
}
|
||||
return getCircleBitmap(bitmap);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.mattermost.networkclient.APIClientModule;
|
||||
import com.mattermost.networkclient.enums.RetryTypes;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Response;
|
||||
|
||||
|
||||
public class Network {
|
||||
@@ -35,6 +36,16 @@ public class Network {
|
||||
clientModule.post(baseUrl, endpoint, options, promise);
|
||||
}
|
||||
|
||||
public static Response getSync(String baseUrl, String endpoint, ReadableMap options) {
|
||||
createClientIfNeeded(baseUrl);
|
||||
return clientModule.getSync(baseUrl, endpoint, options);
|
||||
}
|
||||
|
||||
public static Response postSync(String baseUrl, String endpoint, ReadableMap options) {
|
||||
createClientIfNeeded(baseUrl);
|
||||
return clientModule.postSync(baseUrl, endpoint, options);
|
||||
}
|
||||
|
||||
private static void createClientOptions() {
|
||||
WritableMap headers = Arguments.createMap();
|
||||
headers.putString("X-Requested-With", "XMLHttpRequest");
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -17,7 +16,6 @@ import com.mattermost.helpers.DatabaseHelper;
|
||||
import com.mattermost.helpers.Network;
|
||||
import com.mattermost.helpers.NotificationHelper;
|
||||
import com.mattermost.helpers.PushNotificationDataHelper;
|
||||
import com.mattermost.helpers.ResolvePromise;
|
||||
import com.mattermost.share.ShareModule;
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
||||
@@ -56,27 +54,31 @@ public class CustomPushNotification extends PushNotification {
|
||||
boolean isReactInit = mAppLifecycleFacade.isReactInitialized();
|
||||
|
||||
if (ackId != null && serverUrl != null) {
|
||||
notificationReceiptDelivery(ackId, serverUrl, postId, type, isIdLoaded, new ResolvePromise() {
|
||||
@Override
|
||||
public void resolve(@Nullable Object value) {
|
||||
if (isIdLoaded) {
|
||||
Bundle response = (Bundle) value;
|
||||
if (value != null) {
|
||||
response.putString("server_url", serverUrl);
|
||||
Bundle current = mNotificationProps.asBundle();
|
||||
current.putAll(response);
|
||||
mNotificationProps = createProps(current);
|
||||
}
|
||||
}
|
||||
Bundle response = ReceiptDelivery.send(ackId, serverUrl, postId, type, isIdLoaded);
|
||||
if (isIdLoaded && response != null) {
|
||||
Bundle current = mNotificationProps.asBundle();
|
||||
if (!current.containsKey("server_url")) {
|
||||
response.putString("server_url", serverUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, String message) {
|
||||
Log.e("ReactNative", code + ": " + message);
|
||||
}
|
||||
});
|
||||
current.putAll(response);
|
||||
mNotificationProps = createProps(current);
|
||||
}
|
||||
}
|
||||
|
||||
finishProcessingNotification(serverUrl, type, channelId, notificationId, isReactInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened() {
|
||||
if (mNotificationProps != null) {
|
||||
digestNotification();
|
||||
|
||||
Bundle data = mNotificationProps.asBundle();
|
||||
NotificationHelper.clearChannelOrThreadNotifications(mContext, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void finishProcessingNotification(String serverUrl, String type, String channelId, int notificationId, Boolean isReactInit) {
|
||||
switch (type) {
|
||||
case CustomPushNotificationHelper.PUSH_TYPE_MESSAGE:
|
||||
case CustomPushNotificationHelper.PUSH_TYPE_SESSION:
|
||||
@@ -117,16 +119,6 @@ public class CustomPushNotification extends PushNotification {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened() {
|
||||
if (mNotificationProps != null) {
|
||||
digestNotification();
|
||||
|
||||
Bundle data = mNotificationProps.asBundle();
|
||||
NotificationHelper.clearChannelOrThreadNotifications(mContext, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildNotification(Integer notificationId, boolean createSummary) {
|
||||
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, mNotificationProps);
|
||||
final Notification notification = buildNotification(pendingIntent);
|
||||
@@ -148,10 +140,6 @@ public class CustomPushNotification extends PushNotification {
|
||||
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, true);
|
||||
}
|
||||
|
||||
private void notificationReceiptDelivery(String ackId, String serverUrl, String postId, String type, boolean isIdLoaded, ResolvePromise promise) {
|
||||
ReceiptDelivery.send(mContext, ackId, serverUrl, postId, type, isIdLoaded, promise);
|
||||
}
|
||||
|
||||
private void notifyReceivedToJS() {
|
||||
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
|
||||
}
|
||||
|
||||
@@ -8,28 +8,16 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.Person;
|
||||
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import com.mattermost.helpers.*;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
|
||||
|
||||
@@ -53,12 +41,7 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
final int notificationId = intent.getIntExtra(CustomPushNotificationHelper.NOTIFICATION_ID, -1);
|
||||
final String serverUrl = bundle.getString("server_url");
|
||||
if (serverUrl != null) {
|
||||
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
|
||||
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
|
||||
|
||||
if (token != null) {
|
||||
replyToMessage(serverUrl, token, notificationId, message);
|
||||
}
|
||||
replyToMessage(serverUrl, notificationId, message);
|
||||
} else {
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
@@ -67,7 +50,7 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
protected void replyToMessage(final String serverUrl, final String token, final int notificationId, final CharSequence message) {
|
||||
protected void replyToMessage(final String serverUrl, final int notificationId, final CharSequence message) {
|
||||
final String channelId = bundle.getString("channel_id");
|
||||
final String postId = bundle.getString("post_id");
|
||||
String rootId = bundle.getString("root_id");
|
||||
@@ -75,63 +58,53 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
rootId = postId;
|
||||
}
|
||||
|
||||
if (token == null || serverUrl == null) {
|
||||
if (serverUrl == null) {
|
||||
onReplyFailed(notificationId);
|
||||
return;
|
||||
}
|
||||
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
String json = buildReplyPost(channelId, rootId, message.toString());
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
WritableMap headers = Arguments.createMap();
|
||||
headers.putString("Content-Type", "application/json");
|
||||
|
||||
|
||||
WritableMap body = Arguments.createMap();
|
||||
body.putString("channel_id", channelId);
|
||||
body.putString("message", message.toString());
|
||||
body.putString("root_id", rootId);
|
||||
|
||||
WritableMap options = Arguments.createMap();
|
||||
options.putMap("headers", headers);
|
||||
options.putMap("body", body);
|
||||
|
||||
String postsEndpoint = "/api/v4/posts?set_online=false";
|
||||
String url = String.format("%s%s", serverUrl.replaceAll("/$", ""), postsEndpoint);
|
||||
Log.i("ReactNative", String.format("Reply URL=%s", url));
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", String.format("Bearer %s", token))
|
||||
.header("Content-Type", "application/json")
|
||||
.url(url)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new okhttp3.Callback() {
|
||||
Network.post(serverUrl, postsEndpoint, options, new ResolvePromise() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
Log.i("ReactNative", String.format("Reply FAILED exception %s", e.getMessage()));
|
||||
public void resolve(@Nullable Object value) {
|
||||
if (value != null) {
|
||||
onReplySuccess(notificationId, message);
|
||||
Log.i("ReactNative", "Reply SUCCESS");
|
||||
} else {
|
||||
Log.i("ReactNative", "Reply FAILED resolved without value");
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(Throwable reason) {
|
||||
Log.i("ReactNative", String.format("Reply FAILED exception %s", reason.getMessage()));
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull final Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
onReplySuccess(notificationId, message);
|
||||
Log.i("ReactNative", "Reply SUCCESS");
|
||||
} else {
|
||||
Log.i("ReactNative",
|
||||
String.format("Reply FAILED status %s BODY %s",
|
||||
response.code(),
|
||||
Objects.requireNonNull(response.body()).string()
|
||||
)
|
||||
);
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
public void reject(String code, String message) {
|
||||
Log.i("ReactNative",
|
||||
String.format("Reply FAILED status %s BODY %s", code, message)
|
||||
);
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected String buildReplyPost(String channelId, String rootId, String message) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("channel_id", channelId);
|
||||
json.put("message", message);
|
||||
json.put("root_id", rootId);
|
||||
return json.toString();
|
||||
} catch(JSONException e) {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
protected void onReplyFailed(int notificationId) {
|
||||
recreateNotification(notificationId, "Message failed to send.");
|
||||
}
|
||||
|
||||
@@ -1,135 +1,60 @@
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.System;
|
||||
import java.util.Objects;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.mattermost.helpers.*;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class ReceiptDelivery {
|
||||
private static final int[] FIBONACCI_BACKOFF = new int[] { 0, 1, 2, 3, 5, 8 };
|
||||
private static final String[] ackKeys = new String[]{"post_id", "root_id", "category", "message", "team_id", "channel_id", "channel_name", "type", "sender_id", "sender_name", "version"};
|
||||
|
||||
public static void send(Context context, final String ackId, final String serverUrl, final String postId, final String type, final boolean isIdLoaded, ResolvePromise promise) {
|
||||
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
|
||||
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
|
||||
public static Bundle send(final String ackId, final String serverUrl, final String postId, final String type, final boolean isIdLoaded) {
|
||||
Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with ID-LOADED=%s", ackId, type, serverUrl, isIdLoaded));
|
||||
execute(serverUrl, postId, token, ackId, type, isIdLoaded, promise);
|
||||
}
|
||||
WritableMap options = Arguments.createMap();
|
||||
WritableMap headers = Arguments.createMap();
|
||||
WritableMap body = Arguments.createMap();
|
||||
headers.putString("Content-Type", "application/json");
|
||||
options.putMap("headers", headers);
|
||||
body.putString("id", ackId);
|
||||
body.putDouble("received_at", System.currentTimeMillis());
|
||||
body.putString("platform", "android");
|
||||
body.putString("type", type);
|
||||
body.putString("post_id", postId);
|
||||
body.putBoolean("is_id_loaded", isIdLoaded);
|
||||
options.putMap("body", body);
|
||||
|
||||
protected static void execute(String serverUrl, String postId, String token, String ackId, String type, boolean isIdLoaded, ResolvePromise promise) {
|
||||
if (token == null) {
|
||||
promise.reject("Receipt delivery failure", "Invalid token");
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverUrl == null) {
|
||||
promise.reject("Receipt delivery failure", "Invalid server URL");
|
||||
}
|
||||
|
||||
JSONObject json;
|
||||
long receivedAt = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
json = new JSONObject();
|
||||
json.put("id", ackId);
|
||||
json.put("received_at", receivedAt);
|
||||
json.put("platform", "android");
|
||||
json.put("type", type);
|
||||
json.put("post_id", postId);
|
||||
json.put("is_id_loaded", isIdLoaded);
|
||||
} catch (JSONException e) {
|
||||
Log.e("ReactNative", "Receipt delivery failed to build json payload");
|
||||
promise.reject("Receipt delivery failure", e.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
final HttpUrl url;
|
||||
if (serverUrl != null) {
|
||||
url = HttpUrl.parse(
|
||||
String.format("%s/api/v4/notifications/ack", serverUrl.replaceAll("/$", "")));
|
||||
if (url != null) {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
RequestBody body = RequestBody.create(json.toString(), JSON);
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", String.format("Bearer %s", token))
|
||||
.header("Content-Type", "application/json")
|
||||
.url(url)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
makeServerRequest(client, request, isIdLoaded, 0, promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServerRequest(OkHttpClient client, Request request, Boolean isIdLoaded, int reRequestCount, ResolvePromise promise) {
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
try (Response response = Network.postSync(serverUrl, "api/v4/notifications/ack", options)) {
|
||||
String responseBody = Objects.requireNonNull(response.body()).string();
|
||||
if (response.code() != 200) {
|
||||
switch (response.code()) {
|
||||
case 302:
|
||||
promise.reject("Receipt delivery failure", "StatusFound");
|
||||
return;
|
||||
case 400:
|
||||
promise.reject("Receipt delivery failure", "StatusBadRequest");
|
||||
return;
|
||||
case 401:
|
||||
promise.reject("Receipt delivery failure", "Unauthorized");
|
||||
case 403:
|
||||
promise.reject("Receipt delivery failure", "Forbidden");
|
||||
return;
|
||||
case 500:
|
||||
promise.reject("Receipt delivery failure", "StatusInternalServerError");
|
||||
return;
|
||||
case 501:
|
||||
promise.reject("Receipt delivery failure", "StatusNotImplemented");
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception(responseBody);
|
||||
}
|
||||
|
||||
JSONObject jsonResponse = new JSONObject(responseBody);
|
||||
return parseAckResponse(jsonResponse);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Bundle parseAckResponse(JSONObject jsonResponse) {
|
||||
try {
|
||||
Bundle bundle = new Bundle();
|
||||
String[] keys = new String[]{"post_id", "root_id", "category", "message", "team_id", "channel_id", "channel_name", "type", "sender_id", "sender_name", "version"};
|
||||
for (String key : keys) {
|
||||
for (String key : ackKeys) {
|
||||
if (jsonResponse.has(key)) {
|
||||
bundle.putString(key, jsonResponse.getString(key));
|
||||
}
|
||||
}
|
||||
promise.resolve(bundle);
|
||||
return bundle;
|
||||
} catch (Exception e) {
|
||||
Log.e("ReactNative", "Receipt delivery failed to send");
|
||||
if (isIdLoaded) {
|
||||
try {
|
||||
reRequestCount++;
|
||||
if (reRequestCount < FIBONACCI_BACKOFF.length) {
|
||||
Log.i("ReactNative", "Retry attempt " + reRequestCount + " with backoff delay: " + FIBONACCI_BACKOFF[reRequestCount] + " seconds");
|
||||
Thread.sleep(FIBONACCI_BACKOFF[reRequestCount] * 1000);
|
||||
makeServerRequest(client, request, true, reRequestCount, promise);
|
||||
}
|
||||
} catch(InterruptedException ie) {
|
||||
ie.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
promise.reject("Receipt delivery failure", e.toString());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class PushNotifications {
|
||||
|
||||
init(register: boolean) {
|
||||
if (register) {
|
||||
Notifications.registerRemoteNotifications();
|
||||
this.registerIfNeeded();
|
||||
}
|
||||
|
||||
Notifications.events().registerNotificationOpened(this.onNotificationOpened);
|
||||
@@ -50,11 +50,8 @@ class PushNotifications {
|
||||
async registerIfNeeded() {
|
||||
const isRegistered = await Notifications.isRegisteredForRemoteNotifications();
|
||||
if (!isRegistered) {
|
||||
if (Platform.OS === 'android') {
|
||||
Notifications.registerRemoteNotifications();
|
||||
} else {
|
||||
await requestNotifications(['alert', 'sound', 'badge']);
|
||||
}
|
||||
await requestNotifications(['alert', 'sound', 'badge']);
|
||||
Notifications.registerRemoteNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-netinfo (9.3.7):
|
||||
- React-Core
|
||||
- react-native-network-client (1.2.0):
|
||||
- react-native-network-client (1.3.0):
|
||||
- Alamofire (~> 5.6.4)
|
||||
- React-Core
|
||||
- Starscream (~> 4.0.4)
|
||||
@@ -942,7 +942,7 @@ SPEC CHECKSUMS:
|
||||
react-native-image-picker: 8cb4280e2c1efc3daeb2d9d597f9429a60472e40
|
||||
react-native-in-app-review: a073f67c5f3392af6ea7fb383217cdb1aa2aa726
|
||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||
react-native-network-client: 04b0a2a3d2beab43698e227389c4750b41d003ec
|
||||
react-native-network-client: 019b790a27f21c116ddfc43c0fc68f77f0a1b20f
|
||||
react-native-notifications: 83b4fd4a127a6c918fc846cae90da60f84819e44
|
||||
react-native-paste-input: fb7156dc75960c9895ddd9b9d68eeb874c9f323a
|
||||
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -19,7 +19,7 @@
|
||||
"@gorhom/bottom-sheet": "4.4.5",
|
||||
"@mattermost/compass-icons": "0.1.35",
|
||||
"@mattermost/react-native-emm": "1.3.4",
|
||||
"@mattermost/react-native-network-client": "1.2.0",
|
||||
"@mattermost/react-native-network-client": "1.3.0",
|
||||
"@mattermost/react-native-paste-input": "0.6.1",
|
||||
"@mattermost/react-native-turbo-log": "0.2.2",
|
||||
"@mattermost/react-native-turbo-mailer": "0.2.3",
|
||||
@@ -3241,9 +3241,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mattermost/react-native-network-client": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.2.0.tgz",
|
||||
"integrity": "sha512-agK5epaTvTi1drr4Pn9HDtz5TbDjgUl19n0FiZQpNHioIvI3HP5SfOAK6qO7brCJCMj/srn6o8OKsRAVR3AWGA==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.0.tgz",
|
||||
"integrity": "sha512-eDU/bpXBkU+GIbM7QvabzNF8i2te7oklX/WEfF5au8gMuk8MOOhU0GLDlpxaO3V/LZvr7bPoT4dY8nch43bcMQ==",
|
||||
"dependencies": {
|
||||
"validator": "13.7.0",
|
||||
"zod": "3.20.2"
|
||||
@@ -24293,9 +24293,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@mattermost/react-native-network-client": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.2.0.tgz",
|
||||
"integrity": "sha512-agK5epaTvTi1drr4Pn9HDtz5TbDjgUl19n0FiZQpNHioIvI3HP5SfOAK6qO7brCJCMj/srn6o8OKsRAVR3AWGA==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.0.tgz",
|
||||
"integrity": "sha512-eDU/bpXBkU+GIbM7QvabzNF8i2te7oklX/WEfF5au8gMuk8MOOhU0GLDlpxaO3V/LZvr7bPoT4dY8nch43bcMQ==",
|
||||
"requires": {
|
||||
"validator": "13.7.0",
|
||||
"zod": "3.20.2"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@gorhom/bottom-sheet": "4.4.5",
|
||||
"@mattermost/compass-icons": "0.1.35",
|
||||
"@mattermost/react-native-emm": "1.3.4",
|
||||
"@mattermost/react-native-network-client": "1.2.0",
|
||||
"@mattermost/react-native-network-client": "1.3.0",
|
||||
"@mattermost/react-native-paste-input": "0.6.1",
|
||||
"@mattermost/react-native-turbo-log": "0.2.2",
|
||||
"@mattermost/react-native-turbo-mailer": "0.2.3",
|
||||
|
||||
Reference in New Issue
Block a user