Notification receipt delivery for Android and iOS (#2755)

* Notification receipt delivery for Android and iOS

* Update Fastlane with iOS notification service extension

* User JSONObject to build response
This commit is contained in:
Elias Nahum 2019-05-15 12:45:26 -04:00 committed by GitHub
parent b746a170a7
commit 5bf96272ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 435 additions and 22 deletions

View file

@ -94,7 +94,13 @@ public class CustomPushNotification extends PushNotification {
Bundle data = mNotificationProps.asBundle();
final String channelId = data.getString("channel_id");
final String type = data.getString("type");
final String ackId = data.getString("ack_id");
int notificationId = MESSAGE_NOTIFICATION_ID;
if (ackId != null) {
notificationReceiptDelivery(ackId, type);
}
if (channelId != null) {
notificationId = channelId.hashCode();
Object objCount = channelIdToNotificationCount.get(channelId);
@ -379,4 +385,8 @@ public class CustomPushNotification extends PushNotification {
String sender = String.format("%s: ", getSenderName(senderName, channelName, message));
return message.replaceFirst(sender, "");
}
private void notificationReceiptDelivery(String ackId, String type) {
ReceiptDelivery.send(context, ackId, type);
}
}

View file

@ -0,0 +1,89 @@
package com.mattermost.rnbeta;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import java.lang.System;
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.mattermost.react_native_interface.ResolvePromise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.oblador.keychain.KeychainModule;
public class ReceiptDelivery {
public static void send (Context context, final String ackId, final String type) {
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
final KeychainModule keychainModule = new KeychainModule(reactApplicationContext);
keychainModule.getGenericPasswordForOptions(null, new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
if (value instanceof Boolean && !(Boolean)value) {
return;
}
WritableMap map = (WritableMap) value;
if (map != null) {
String[] credentials = map.getString("password").split(",[ ]*");
String token = null;
String serverUrl = null;
if (credentials.length == 2) {
token = credentials[0];
serverUrl = credentials[1];
}
Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with TOKEN=%s", ackId, type, serverUrl, token));
execute(serverUrl, token, ackId, type);
}
}
});
}
protected static void execute(String serverUrl, String token, String ackId, String type) {
if (token == null || serverUrl == null) {
return;
}
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);
} catch (JSONException e) {
Log.e("ReactNative", "Receipt delivery failed to build json payload");
return;
}
final OkHttpClient client = new OkHttpClient();
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, json.toString());
Request request = new Request.Builder()
.header("Authorization", String.format("Bearer %s", token))
.header("Content-Type", "application/json")
.url(String.format("%s/api/v4/notifications/ack", serverUrl.replaceAll("/$", "")))
.post(body)
.build();
try {
client.newCall(request).execute();
} catch (Exception e) {
Log.e("ReactNative", "Receipt delivery failed to send");
}
}
}

View file

@ -237,6 +237,20 @@ platform :ios do
display_name: app_name
)
# Set the notification service extension bundle identifier
notification_bundle_id = ENV['NOTIFICATION_SERVICE_IDENTIFIER'] || 'com.mattermost.rnbeta.NotificationService'
update_app_identifier(
xcodeproj: './ios/Mattermost.xcodeproj',
plist_path: 'NotificationService/Info.plist',
app_identifier: notification_bundle_id
)
find_replace_string(
path_to_file: './ios/Mattermost.xcodeproj/project.pbxproj',
old_string: 'com.mattermost.rnbeta.NotificationService',
new_string: notification_bundle_id
)
# Set the share extension bundle identifier
extension_bundle_id = ENV['EXTENSION_APP_IDENTIFIER'] || 'com.mattermost.rnbeta.MattermostShare'
update_app_identifier(
@ -299,6 +313,11 @@ platform :ios do
app_group_identifiers: [app_group_id]
)
update_app_group_identifiers(
entitlements_file: './ios/NotificationService/NotificationService.entitlements',
app_group_identifiers: [app_group_id]
)
# Sync the provisioning profiles using match
if ENV['SYNC_PROVISIONING_PROFILES'] == 'true'
match(type: ENV['MATCH_TYPE'] || 'adhoc')
@ -381,11 +400,11 @@ platform :android do
lane :configure_telemetry_android do
if ENV['TELEMETRY_ENABLED'] == 'true' && ENV['TELEMETRY_URL'] != '' && ENV['TELEMETRY_API_KEY'] != ''
# Update telemetry config
json = load_config_json
json = load_config_json('../dist/assets/config.json')
json['TelemetryEnabled'] = true
json['TelemetryUrl'] = ENV['TELEMETRY_URL']
json['TelemetryApiKey'] = ENV['TELEMETRY_API_KEY']
save_config_json(json)
save_config_json('../dist/assets/config.json', json)
beta_dir = './android/app/src/main/java/com/mattermost/rnbeta/'

View file

@ -74,6 +74,9 @@
7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */; };
7F43D63F1F6BFA19001FC614 /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D8FEC21E80B5230091F3BD /* libBVLinearGradient.a */; };
7F43D6401F6BFA82001FC614 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */; };
7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F581D34221ED5C60099E66B /* NotificationService.swift */; };
7F581D39221ED5C60099E66B /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7F581D32221ED5C60099E66B /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
7F581F78221EEA7C0099E66B /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
7F4C2598227E3B11009144EF /* libRNCNetInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F4C2595227E3AE9009144EF /* libRNCNetInfo.a */; };
7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; };
7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; };
@ -475,6 +478,13 @@
remoteGlobalIDString = ED296FEE214C9CF800B7C4FE;
remoteInfo = "jsiexecutor-tvOS";
};
7F581D37221ED5C60099E66B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 7F581D31221ED5C60099E66B;
remoteInfo = NotificationService;
};
7F5CA990208FE38F004F91CE /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */;
@ -636,6 +646,13 @@
remoteGlobalIDString = 5DBEB1501B18CEA900B34395;
remoteInfo = RNVectorIcons;
};
7FE5F961227739E600FEFFE1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7FABE04022137F2900D0F595 /* UploadAttachments.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 7FABE03522137F2900D0F595;
remoteInfo = UploadAttachments;
};
7FE5FB3521A1EB710075D1AA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 2B25899FDAC149EB96ED3305 /* RNVectorIcons.xcodeproj */;
@ -691,6 +708,7 @@
dstSubfolderSpec = 13;
files = (
7F240A23220D3A2300637665 /* MattermostShare.appex in Embed App Extensions */,
7F581D39221ED5C60099E66B /* NotificationService.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
@ -786,6 +804,10 @@
7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-Mattermost.a"; path = "../../../../../../../Library/Developer/Xcode/DerivedData/Mattermost-czlinsdviifujheezzjvmisotjrm/Build/Products/Debug-iphonesimulator/libPods-Mattermost.a"; sourceTree = "<group>"; };
7F4C258F227E3AE9009144EF /* RNCNetInfo.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNCNetInfo.xcodeproj; path = "../node_modules/@react-native-community/netinfo/ios/RNCNetInfo.xcodeproj"; sourceTree = "<group>"; };
7F54ABFAE6CE4A6DB11D1ED7 /* Roboto-BlackItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-BlackItalic.ttf"; path = "../assets/fonts/Roboto-BlackItalic.ttf"; sourceTree = "<group>"; };
7F581D32221ED5C60099E66B /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
7F581D34221ED5C60099E66B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
7F581D36221ED5C60099E66B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7F581F77221EEA5A0099E66B /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; };
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = "<group>"; };
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = "<group>"; };
@ -919,6 +941,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
7F581D2F221ED5C60099E66B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7F581F78221EEA7C0099E66B /* libUploadAttachments.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -1049,7 +1079,6 @@
13B07FAE1A68108700A75B9A /* Mattermost */ = {
isa = PBXGroup;
children = (
7F151D43221B082A00FAD8F3 /* Mattermost-Bridging-Header.h */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
7FEB10961F6101710039A015 /* BlurAppScreen.h */,
@ -1061,6 +1090,7 @@
7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */,
7F151D43221B082A00FAD8F3 /* Mattermost-Bridging-Header.h */,
7F240ACB220D460300637665 /* MattermostBucketModule.h */,
7F240ACC220D460300637665 /* MattermostBucketModule.m */,
7FEB10991F61019C0039A015 /* MattermostManaged.h */,
@ -1282,6 +1312,16 @@
name = Products;
sourceTree = "<group>";
};
7F581D33221ED5C60099E66B /* NotificationService */ = {
isa = PBXGroup;
children = (
7F581F77221EEA5A0099E66B /* NotificationService.entitlements */,
7F581D34221ED5C60099E66B /* NotificationService.swift */,
7F581D36221ED5C60099E66B /* Info.plist */,
);
path = NotificationService;
sourceTree = "<group>";
};
7F5CA957208FE38F004F91CE /* Products */ = {
isa = PBXGroup;
children = (
@ -1501,6 +1541,7 @@
children = (
13B07FAE1A68108700A75B9A /* Mattermost */,
7F240A1A220D3A2300637665 /* MattermostShare */,
7F581D33221ED5C60099E66B /* NotificationService */,
7F292A701E8AB73400A450A3 /* SplashScreenResource */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* MattermostTests */,
@ -1520,6 +1561,7 @@
13B07F961A680F5B00A75B9A /* Mattermost.app */,
00E356EE1AD99517003FC87E /* MattermostTests.xctest */,
7F240A19220D3A2300637665 /* MattermostShare.appex */,
7F581D32221ED5C60099E66B /* NotificationService.appex */,
);
name = Products;
sourceTree = "<group>";
@ -1564,6 +1606,7 @@
dependencies = (
7FAB45BA222DD0E300EBFFC8 /* PBXTargetDependency */,
7F240A22220D3A2300637665 /* PBXTargetDependency */,
7F581D38221ED5C60099E66B /* PBXTargetDependency */,
);
name = Mattermost;
productName = "Hello World";
@ -1588,6 +1631,24 @@
productReference = 7F240A19220D3A2300637665 /* MattermostShare.appex */;
productType = "com.apple.product-type.app-extension";
};
7F581D31221ED5C60099E66B /* NotificationService */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7F581D82221ED5C60099E66B /* Build configuration list for PBXNativeTarget "NotificationService" */;
buildPhases = (
7F581D2E221ED5C60099E66B /* Sources */,
7F581D2F221ED5C60099E66B /* Frameworks */,
7F581D30221ED5C60099E66B /* Resources */,
);
buildRules = (
);
dependencies = (
7FE5F962227739E600FEFFE1 /* PBXTargetDependency */,
);
name = NotificationService;
productName = NotificationService;
productReference = 7F581D32221ED5C60099E66B /* NotificationService.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -1634,6 +1695,16 @@
};
};
};
7F581D31221ED5C60099E66B = {
CreatedOnToolsVersion = 10.1;
DevelopmentTeam = UQ8HT4Q2XM;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.ApplicationGroups.iOS = {
enabled = 1;
};
};
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Mattermost" */;
@ -1815,6 +1886,7 @@
13B07F861A680F5B00A75B9A /* Mattermost */,
00E356ED1AD99517003FC87E /* MattermostTests */,
7F240A18220D3A2300637665 /* MattermostShare */,
7F581D31221ED5C60099E66B /* NotificationService */,
);
};
/* End PBXProject section */
@ -2416,6 +2488,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
7F581D30221ED5C60099E66B /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@ -2522,6 +2601,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
7F581D2E221ED5C60099E66B /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -2535,6 +2622,11 @@
target = 7F240A18220D3A2300637665 /* MattermostShare */;
targetProxy = 7F240A21220D3A2300637665 /* PBXContainerItemProxy */;
};
7F581D38221ED5C60099E66B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 7F581D31221ED5C60099E66B /* NotificationService */;
targetProxy = 7F581D37221ED5C60099E66B /* PBXContainerItemProxy */;
};
7FAB4571222DD0DA00EBFFC8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = UploadAttachments;
@ -2545,6 +2637,11 @@
name = UploadAttachments;
targetProxy = 7FAB45B9222DD0E300EBFFC8 /* PBXContainerItemProxy */;
};
7FE5F962227739E600FEFFE1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = UploadAttachments;
targetProxy = 7FE5F961227739E600FEFFE1 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@ -2820,6 +2917,83 @@
};
name = Release;
};
7F581D3A221ED5C60099E66B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7F581D3B221ED5C60099E66B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -2934,6 +3108,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7F581D82221ED5C60099E66B /* Build configuration list for PBXNativeTarget "NotificationService" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7F581D3A221ED5C60099E66B /* Debug */,
7F581D3B221ED5C60099E66B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Mattermost" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View file

@ -120,11 +120,13 @@ NSString* const NotificationClearAction = @"clear";
UIApplicationState state = [UIApplication sharedApplication].applicationState;
NSString* action = [userInfo objectForKey:@"type"];
NSString* channelId = [userInfo objectForKey:@"channel_id"];
NSString* ackId = [userInfo objectForKey:@"ack_id"];
if (action && [action isEqualToString: NotificationClearAction]) {
// If received a notification that a channel was read, remove all notifications from that channel (only with app in foreground/background)
[self cleanNotificationsFromChannel:channelId andUpdateBadge:NO];
RuntimeUtils *utils = [[RuntimeUtils alloc] init];
[[UploadSession shared] notificationReceiptWithNotificationId:ackId receivedAt:round([[NSDate date] timeIntervalSince1970] * 1000.0) type:action];
[utils delayWithSeconds:0.2 closure:^(void) {
// This is to notify the NotificationCenter that something has changed.
completionHandler(UIBackgroundFetchResultNewData);

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>NotificationService</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.19.0</string>
<key>CFBundleVersion</key>
<string>188</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.mattermost.rnbeta</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,42 @@
import UserNotifications
import UploadAttachments
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
UploadSession.shared.notificationReceipt(
notificationId: bestAttemptContent.userInfo["ack_id"],
receivedAt: Date().millisencondsSince1970,
type: bestAttemptContent.userInfo["type"]
)
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
extension Date {
var millisencondsSince1970: Int {
return Int((self.timeIntervalSince1970 * 1000.0).rounded())
}
init(milliseconds: Int) {
self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
}
}

View file

@ -38,9 +38,9 @@ import os.log
if #available(iOS 12.0, *) {
os_log(OSLogType.default, "Mattermost will post identifier=%{public}@", identifier)
}
URLSession(configuration: .ephemeral).dataTask(with: request).resume()
UploadSessionManager.shared.removeUploadSessionData(identifier: identifier)
UploadSessionManager.shared.clearTempDirectory()
}
@ -56,7 +56,7 @@ import os.log
if #available(iOS 11.0, *) {
sessionConfig.waitsForConnectivity = true
}
session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main)
}
@ -66,32 +66,32 @@ import os.log
if #available(iOS 11.0, *) {
sessionConfig.waitsForConnectivity = true
}
self.session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
if #available(iOS 12.0, *) {
os_log(OSLogType.default, "Mattermost Session created identifier=%{public}@", identifier)
}
return self.session!
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
// here we should get the file Id and update it in the session
guard let identifier = session.configuration.identifier else {return}
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as! NSDictionary
let fileInfos = jsonObject.object(forKey: "file_infos") as! NSArray
if fileInfos.count > 0 {
let fileInfoData = fileInfos[0] as! NSDictionary
let fileId = fileInfoData.object(forKey: "id") as! String
UploadSessionManager.shared.appendCompletedUploadToSession(identifier: identifier, fileId: fileId)
}
} catch {
if #available(iOS 12.0, *) {
os_log(OSLogType.default, "Mattermost Failed to receive data identifier=%{public}@ error=%{public}", identifier, error.localizedDescription)
}
print("MMLOG: Failed to get the file upload response %@", error.localizedDescription)
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as! NSDictionary
let fileInfos = jsonObject.object(forKey: "file_infos") as! NSArray
if fileInfos.count > 0 {
let fileInfoData = fileInfos[0] as! NSDictionary
let fileId = fileInfoData.object(forKey: "id") as! String
UploadSessionManager.shared.appendCompletedUploadToSession(identifier: identifier, fileId: fileId)
}
} catch {
if #available(iOS 12.0, *) {
os_log(OSLogType.default, "Mattermost Failed to receive data identifier=%{public}@ error=%{public}", identifier, error.localizedDescription)
}
print("MMLOG: Failed to get the file upload response %@", error.localizedDescription)
}
}
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
@ -123,4 +123,31 @@ import os.log
}
})
}
public func notificationReceipt(notificationId: Any?, receivedAt: Int, type: Any?) {
let store = StoreManager.shared() as StoreManager
let _ = store.getEntities(true)
let serverURL = store.getServerUrl()
let sessionToken = store.getToken()
let urlString = "\(serverURL!)/api/v4/notifications/ack"
if (notificationId != nil) {
let jsonObject: [String: Any] = [
"id": notificationId as Any,
"received_at": receivedAt,
"platform": "ios",
"type": type as Any
]
if !JSONSerialization.isValidJSONObject(jsonObject) {return}
guard let url = URL(string: urlString) else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(sessionToken!)", forHTTPHeaderField: "Authorization")
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted)
URLSession(configuration: .ephemeral).dataTask(with: request).resume()
}
}
}