From 618ee2f0fa4297f8a2dfb8207f5405b339faf99a Mon Sep 17 00:00:00 2001 From: ijunaid Date: Thu, 20 Jan 2022 17:10:00 +0500 Subject: [PATCH] [SDK-636] : Disable push notifications for iOS (#91) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * — COUNTLY_EXCLUDE_PUSHNOTIFICATIONS added to disable push notifications — SDK version updated to 20.11.3 — underlying iOS sdk version updated to 20.11.3 — Changlelog updated. * Changelog updated for SDK-637 * Typo fixes * Code Indentation fixes * Updated underlying android SDK to 20.11.11 --- .versions | 2 +- CHANGELOG.md | 6 ++ Countly.js | 2 +- package.js | 2 +- package.json | 2 +- plugin.xml | 4 +- src/android/CountlyNative.java | 2 +- src/ios/CountlyNative.m | 28 +++++- src/ios/CountlyiOS/CountlyCommon.h | 1 + src/ios/CountlyiOS/CountlyCommon.m | 19 +++- src/ios/CountlyiOS/CountlyConnectionManager.h | 3 + src/ios/CountlyiOS/CountlyConnectionManager.m | 1 + src/ios/CountlyiOS/CountlyFeedbackWidget.h | 27 +++++- src/ios/CountlyiOS/CountlyFeedbackWidget.m | 97 ++++++++++++++++++- src/ios/CountlyiOS/CountlyFeedbacks.m | 8 +- src/ios/CountlyiOS/CountlyRemoteConfig.m | 2 +- 16 files changed, 182 insertions(+), 24 deletions(-) diff --git a/.versions b/.versions index 2492e49..d55efaf 100644 --- a/.versions +++ b/.versions @@ -1,2 +1,2 @@ -countly:countly-sdk-js@20.11.2 +countly:countly-sdk-js@20.11.3 meteor@1.9.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f5a31..c15ea54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 20.11.3 +* Added COUNTLY_EXCLUDE_PUSHNOTIFICATIONS flag to disable push notifications altogether in order to avoid App Store Connect warnings. +* Fixed issues related to Push notification crash when notification recieved from other SDK's/Plugins (not from Countly). +* Updated underlying android SDK to 20.11.11 +* Updated underlying iOS SDK version to 20.11.3 + ## 20.11.2 * Moving a push related broadcast receiver declaration to the manifest to comply with 'PendingIntent' checks * Updated underlying android SDK to 20.11.9 diff --git a/Countly.js b/Countly.js index 8d3e296..f45c2dc 100644 --- a/Countly.js +++ b/Countly.js @@ -2,7 +2,7 @@ Countly = {}; Countly.serverUrl = ""; Countly.appKey = ""; Countly.ready = false; -Countly.version = "20.11.2"; +Countly.version = "20.11.3"; Countly.isDebug = false; Countly.isInitCalled = false; if (window.cordova.platformId == "android") { diff --git a/package.js b/package.js index 1c7591f..ee80376 100644 --- a/package.js +++ b/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'countly:countly-sdk-js', - version: '20.11.2', + version: '20.11.3', summary: 'Countly is an innovative, real-time, open source mobile analytics and push notifications platform. It collects data from mobile devices, and visualizes this information to analyze mobile application usage and end-user behavior. There are two parts of Countly: the server that collects and analyzes data, and mobile SDK that sends this data. Both parts are open source with different licensing terms.', git: 'https://github.com/Countly/countly-sdk-cordova.git', documentation: 'README.md' diff --git a/package.json b/package.json index 718906b..54335ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "countly-sdk-js", - "version": "20.11.2", + "version": "20.11.3", "description": "Countly is an innovative, real-time, open source mobile analytics and push notifications platform. It collects data from mobile devices, and visualizes this information to analyze mobile application usage and end-user behavior. There are two parts of Countly: the server that collects and analyzes data, and mobile SDK that sends this data. Both parts are open source with different licensing terms.", "cordova": { "id": "countly-sdk-js", diff --git a/plugin.xml b/plugin.xml index 06e228f..8cbf4a8 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + Countly Cordova SDK @@ -141,7 +141,7 @@ - + diff --git a/src/android/CountlyNative.java b/src/android/CountlyNative.java index 84136fd..cf20085 100644 --- a/src/android/CountlyNative.java +++ b/src/android/CountlyNative.java @@ -36,7 +36,7 @@ public class CountlyNative { public static final String TAG = "CountlyCordovaPlugin"; - private String COUNTLY_CORDOVA_SDK_VERSION_STRING = "20.11.2"; + private String COUNTLY_CORDOVA_SDK_VERSION_STRING = "20.11.3"; private String COUNTLY_CORDOVA_SDK_NAME = "js-cordovab-android"; private Countly.CountlyMessagingMode pushTokenTypeVariable = Countly.CountlyMessagingMode.PRODUCTION; diff --git a/src/ios/CountlyNative.m b/src/ios/CountlyNative.m index da781d7..0f3680f 100644 --- a/src/ios/CountlyNative.m +++ b/src/ios/CountlyNative.m @@ -19,7 +19,7 @@ Boolean isInitialized = false; NSString *const pushPluginApplicationDidBecomeActiveNotification = @"pushPluginApplicationDidBecomeActiveNotification"; -NSString* const kCountlyCordovaSDKVersion = @"20.11.2"; +NSString* const kCountlyCordovaSDKVersion = @"20.11.3"; NSString* const kCountlyCordovaSDKName = @"js-cordovab-ios"; @interface CountlyFeedbackWidget () @@ -70,8 +70,9 @@ + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS Class class = [self class]; - SEL originalSelector = @selector(init); SEL swizzledSelector = @selector(pushPluginSwizzledInit); @@ -92,9 +93,11 @@ + (void)load } else { method_exchangeImplementations(original, swizzled); } +#endif }); } +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS - (AppDelegate *)pushPluginSwizzledInit { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; @@ -209,6 +212,7 @@ - (void)pushPluginOnApplicationDidBecomeActive:(NSNotification *)notification { [CountlyNative onNotification: self.launchNotification]; [[NSNotificationCenter defaultCenter] postNotificationName:pushPluginApplicationDidBecomeActiveNotification object:nil]; } +#endif @end @@ -223,6 +227,7 @@ @implementation CountlyNative CountlyConfig* config = nil; Boolean isDebug = false; +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS + (void)onNotification: (NSDictionary *) notificationMessage{ if(!notificationMessage) { COUNTLY_CORDOVA_LOG(@"onNotification, Notification received. No valid message"); @@ -269,6 +274,7 @@ - (void)recordPushAction } [notificationIDs removeAllObjects]; } +#endif - (void) onCall:(NSString *)method commandString:(NSArray *)command callback:(Result) result{ if(isDebug == true){ @@ -293,7 +299,9 @@ - (void) onCall:(NSString *)method commandString:(NSArray *)command callback:(Re CountlyCommon.sharedInstance.SDKVersion = kCountlyCordovaSDKVersion; Countly.sharedInstance.isAutoViewTrackingActive = NO; +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS [self addCountlyFeature:CLYPushNotifications]; +#endif if(command.count == 3){ deviceID = [command objectAtIndex:2]; @@ -304,7 +312,10 @@ - (void) onCall:(NSString *)method commandString:(NSArray *)command callback:(Re dispatch_async(dispatch_get_main_queue(), ^ { isInitialized = true; [[Countly sharedInstance] startWithConfig:config]; + +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS [self recordPushAction]; +#endif }); result(@"initialized."); } else { @@ -614,6 +625,7 @@ - (void) onCall:(NSString *)method commandString:(NSArray *)command callback:(Re }); }else if ([@"sendPushToken" isEqualToString:method]) { +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS if(config != nil){ NSString* token = [command objectAtIndex:0]; int messagingMode = 1; @@ -625,22 +637,29 @@ - (void) onCall:(NSString *)method commandString:(NSArray *)command callback:(Re [request setHTTPMethod:@"GET"]; [request setURL:[NSURL URLWithString:urlString]]; } +#endif result(@"sendPushToken!"); }else if ([@"askForNotificationPermission" isEqualToString:method]) { dispatch_async(dispatch_get_main_queue(), ^ { +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS [Countly.sharedInstance askForNotificationPermission]; +#endif result(@"askForNotificationPermission!"); }); }else if ([@"registerForNotification" isEqualToString:method]) { +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS COUNTLY_CORDOVA_LOG(@"registerForNotification"); notificationListener = result; if(lastStoredNotification != nil){ result([lastStoredNotification description]); lastStoredNotification = nil; } +#endif }else if ([@"pushTokenType" isEqualToString:method]) { + dispatch_async(dispatch_get_main_queue(), ^ { +#ifndef COUNTLY_EXCLUDE_PUSHNOTIFICATIONS NSString* tokenType = [command objectAtIndex:0]; if([tokenType isEqualToString: @"1"]){ config.pushTestMode = @"CLYPushTestModeDevelopment"; @@ -649,6 +668,7 @@ - (void) onCall:(NSString *)method commandString:(NSArray *)command callback:(Re config.pushTestMode = @"CLYPushTestModeTestFlightOrAdHoc"; }else{ } +#endif result(@"pushTokenType!"); }); }else if ([@"userData_setProperty" isEqualToString:method]) { @@ -1106,6 +1126,4 @@ void CountlyCordovaInternalLog(NSString *format, ...) va_end(args); } -@end - - +@end \ No newline at end of file diff --git a/src/ios/CountlyiOS/CountlyCommon.h b/src/ios/CountlyiOS/CountlyCommon.h index 37e7e70..68b9fae 100644 --- a/src/ios/CountlyiOS/CountlyCommon.h +++ b/src/ios/CountlyiOS/CountlyCommon.h @@ -84,6 +84,7 @@ void CountlyPrint(NSString *stringToPrint); #if (TARGET_OS_IOS || TARGET_OS_TV) - (UIViewController *)topViewController; - (void)tryPresentingViewController:(UIViewController *)viewController; +- (void)tryPresentingViewController:(UIViewController *)viewController withCompletion:(void (^ __nullable) (void))completion; #endif - (void)startAppleWatchMatching; diff --git a/src/ios/CountlyiOS/CountlyCommon.m b/src/ios/CountlyiOS/CountlyCommon.m index 5abcf49..bfa129d 100644 --- a/src/ios/CountlyiOS/CountlyCommon.m +++ b/src/ios/CountlyiOS/CountlyCommon.m @@ -33,7 +33,7 @@ @interface CountlyCommon () #endif @end -NSString* const kCountlySDKVersion = @"20.11.2"; +NSString* const kCountlySDKVersion = @"20.11.3"; NSString* const kCountlySDKName = @"objc-native-ios"; NSString* const kCountlyParentDeviceIDTransferKey = @"kCountlyParentDeviceIDTransferKey"; @@ -301,16 +301,29 @@ - (UIViewController *)topViewController } - (void)tryPresentingViewController:(UIViewController *)viewController +{ + [self tryPresentingViewController:viewController withCompletion:nil]; +} + +- (void)tryPresentingViewController:(UIViewController *)viewController withCompletion:(void (^ __nullable) (void))completion { UIViewController* topVC = self.topViewController; if (topVC) { - [topVC presentViewController:viewController animated:YES completion:nil]; + [topVC presentViewController:viewController animated:YES completion:^ + { + if (completion) + completion(); + }]; + return; } - [self performSelector:@selector(tryPresentingViewController:) withObject:viewController afterDelay:1.0]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ + { + [self tryPresentingViewController:viewController]; + }); } #endif diff --git a/src/ios/CountlyiOS/CountlyConnectionManager.h b/src/ios/CountlyiOS/CountlyConnectionManager.h index 6bbab5a..7daafdd 100644 --- a/src/ios/CountlyiOS/CountlyConnectionManager.h +++ b/src/ios/CountlyiOS/CountlyConnectionManager.h @@ -18,6 +18,9 @@ extern NSString* const kCountlyEndpointO; extern NSString* const kCountlyEndpointSDK; extern NSString* const kCountlyEndpointFeedback; extern NSString* const kCountlyEndpointWidget; +extern NSString* const kCountlyEndpointSurveys; + +extern const NSInteger kCountlyGETRequestMaxLength; @interface CountlyConnectionManager : NSObject diff --git a/src/ios/CountlyiOS/CountlyConnectionManager.m b/src/ios/CountlyiOS/CountlyConnectionManager.m index b94da56..87a1c23 100644 --- a/src/ios/CountlyiOS/CountlyConnectionManager.m +++ b/src/ios/CountlyiOS/CountlyConnectionManager.m @@ -61,6 +61,7 @@ @interface CountlyConnectionManager () NSString* const kCountlyEndpointSDK = @"/sdk"; NSString* const kCountlyEndpointFeedback = @"/feedback"; NSString* const kCountlyEndpointWidget = @"/widget"; +NSString* const kCountlyEndpointSurveys = @"/surveys"; const NSInteger kCountlyGETRequestMaxLength = 2048; diff --git a/src/ios/CountlyiOS/CountlyFeedbackWidget.h b/src/ios/CountlyiOS/CountlyFeedbackWidget.h index 5e5b1eb..3cb4e2e 100644 --- a/src/ios/CountlyiOS/CountlyFeedbackWidget.h +++ b/src/ios/CountlyiOS/CountlyFeedbackWidget.h @@ -19,7 +19,7 @@ extern CLYFeedbackWidgetType const CLYFeedbackWidgetTypeNPS; @property (nonatomic, readonly) CLYFeedbackWidgetType type; @property (nonatomic, readonly) NSString* ID; @property (nonatomic, readonly) NSString* name; - +@property (nonatomic, readonly) NSDictionary* data; /** * Modally presents the feedback widget above the top visible view controller. @@ -28,6 +28,31 @@ extern CLYFeedbackWidgetType const CLYFeedbackWidgetTypeNPS; */ - (void)present; +/** + * Modally presents the feedback widget above the top visible view controller and executes given blocks. + * @discussion Calls to this method will be ignored if consent for @c CLYConsentFeedback is not given while @c requiresConsent flag is set on initial configuration. + * @param appearBlock Block to be executed when widget is displayed + * @param dismissBlock Block to be executed when widget is dismissed + */ +- (void)presentWithAppearBlock:(void(^ __nullable)(void))appearBlock andDismissBlock:(void(^ __nullable)(void))dismissBlock; + +/** + * Fetches feedback widget's data to be used for manually presenting it. + * @discussion When feedback widget's data is fetched successfully, @c completionHandler will be executed with an @c NSDictionary + * @discussion This @c NSDictionary represents the feedback widget's data and can be used for creating custom feedback widget UI. + * @discussion Otherwise, @c completionHandler will be executed with an @c NSError. + * @param completionHandler A completion handler block to be executed when data is fetched successfully or there is an error + */ +- (void)getWidgetData:(void (^)(NSDictionary * __nullable widgetData, NSError * __nullable error))completionHandler; + +/** + * Records manually presented feedback widget's result. + * @discussion Calls to this method will be ignored if consent for @c CLYConsentFeedback is not given while @c requiresConsent flag is set on initial configuration. + * @discussion If there is no result available due to user dismissing the feedback widget without completing it, @c result can be passed as @c nil. + * @param result A dictionary representing result of manually presented feedback widget + */ +- (void)recordResult:(NSDictionary * __nullable)result; + #endif @end diff --git a/src/ios/CountlyiOS/CountlyFeedbackWidget.m b/src/ios/CountlyiOS/CountlyFeedbackWidget.m index e88b668..9ec1bc6 100644 --- a/src/ios/CountlyiOS/CountlyFeedbackWidget.m +++ b/src/ios/CountlyiOS/CountlyFeedbackWidget.m @@ -14,11 +14,13 @@ NSString* const kCountlyReservedEventPrefix = @"[CLY]_"; //NOTE: This will be used with feedback type. NSString* const kCountlyFBKeyClosed = @"closed"; +NSString* const kCountlyFBKeyShown = @"shown"; @interface CountlyFeedbackWidget () @property (nonatomic) CLYFeedbackWidgetType type; @property (nonatomic) NSString* ID; @property (nonatomic) NSString* name; +@property (nonatomic) NSDictionary* data; @end @@ -35,6 +37,11 @@ + (CountlyFeedbackWidget *)createWithDictionary:(NSDictionary *)dictionary } - (void)present +{ + [self presentWithAppearBlock:nil andDismissBlock:nil]; +} + +- (void)presentWithAppearBlock:(void(^ __nullable)(void))appearBlock andDismissBlock:(void(^ __nullable)(void))dismissBlock; { if (!CountlyConsentManager.sharedInstance.consentForFeedback) return; @@ -55,6 +62,9 @@ - (void)present { [webVC dismissViewControllerAnimated:YES completion:^ { + if (dismissBlock) + dismissBlock(); + webVC = nil; }]; @@ -63,7 +73,82 @@ - (void)present [webVC.view addSubview:dismissButton]; [dismissButton positionToTopRightConsideringStatusBar]; - [CountlyCommon.sharedInstance tryPresentingViewController:webVC]; + [CountlyCommon.sharedInstance tryPresentingViewController:webVC withCompletion:appearBlock]; +} + +- (void)getWidgetData:(void (^)(NSDictionary * __nullable widgetData, NSError * __nullable error))completionHandler +{ + NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:[self dataRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + { + NSDictionary *widgetData = nil; + + if (!error) + { + widgetData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + } + + if (!error) + { + if (((NSHTTPURLResponse*)response).statusCode != 200) + { + NSMutableDictionary* userInfo = widgetData.mutableCopy; + userInfo[NSLocalizedDescriptionKey] = @"Feedbacks general API error"; + error = [NSError errorWithDomain:kCountlyErrorDomain code:CLYErrorFeedbacksGeneralAPIError userInfo:userInfo]; + } + } + + self.data = widgetData; + + dispatch_async(dispatch_get_main_queue(), ^ + { + if (completionHandler) + completionHandler(widgetData, error); + }); + }]; + + [task resume]; +} + +- (void)recordResult:(NSDictionary * __nullable)result +{ + if (!result) + [self recordReservedEventForDismissing]; + else + [self recordReservedEventWithSegmentation:result]; +} + +- (NSURLRequest *)dataRequest +{ + NSString* queryString = [NSString stringWithFormat:@"%@=%@&%@=%@&%@=%@&%@=%@&%@=%@&%@=%@", + kCountlyQSKeySDKName, CountlyCommon.sharedInstance.SDKName, + kCountlyQSKeySDKVersion, CountlyCommon.sharedInstance.SDKVersion, + kCountlyFBKeyAppVersion, CountlyDeviceInfo.appVersion, + kCountlyFBKeyPlatform, CountlyDeviceInfo.osName, + kCountlyFBKeyShown, @"1", + kCountlyFBKeyWidgetID, self.ID]; + + queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; + + NSMutableString* URL = CountlyConnectionManager.sharedInstance.host.mutableCopy; + [URL appendString:kCountlyEndpointO]; + [URL appendString:kCountlyEndpointSurveys]; + NSString* feedbackTypeEndpoint = [@"/" stringByAppendingString:self.type]; + [URL appendString:feedbackTypeEndpoint]; + [URL appendString:kCountlyEndpointWidget]; + + if (queryString.length > kCountlyGETRequestMaxLength || CountlyConnectionManager.sharedInstance.alwaysUsePOST) + { + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [queryString cly_dataUTF8]; + return request.copy; + } + else + { + [URL appendFormat:@"?%@", queryString]; + NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:URL]]; + return request; + } } - (NSURLRequest *)displayRequest @@ -90,17 +175,23 @@ - (NSURLRequest *)displayRequest } - (void)recordReservedEventForDismissing +{ + [self recordReservedEventWithSegmentation:@{kCountlyFBKeyClosed: @1}]; +} + +- (void)recordReservedEventWithSegmentation:(NSDictionary *)segm { if (!CountlyConsentManager.sharedInstance.consentForFeedback) return; NSString* eventName = [kCountlyReservedEventPrefix stringByAppendingString:self.type]; - NSMutableDictionary* segmentation = NSMutableDictionary.new; + NSMutableDictionary* segmentation = segm.mutableCopy; segmentation[kCountlyFBKeyPlatform] = CountlyDeviceInfo.osName; segmentation[kCountlyFBKeyAppVersion] = CountlyDeviceInfo.appVersion; - segmentation[kCountlyFBKeyClosed] = @1; segmentation[kCountlyFBKeyWidgetID] = self.ID; [Countly.sharedInstance recordReservedEvent:eventName segmentation:segmentation]; + + [CountlyConnectionManager.sharedInstance sendEvents]; } - (NSString *)description diff --git a/src/ios/CountlyiOS/CountlyFeedbacks.m b/src/ios/CountlyiOS/CountlyFeedbacks.m index 6b28de9..fec7b7a 100644 --- a/src/ios/CountlyiOS/CountlyFeedbacks.m +++ b/src/ios/CountlyiOS/CountlyFeedbacks.m @@ -330,12 +330,12 @@ - (NSURLRequest *)widgetCheckURLRequest:(NSString *)widgetID kCountlyEndpointFeedback, kCountlyEndpointWidget]; - if (CountlyConnectionManager.sharedInstance.alwaysUsePOST) + if (queryString.length > kCountlyGETRequestMaxLength || CountlyConnectionManager.sharedInstance.alwaysUsePOST) { NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serverOutputFeedbackWidgetEndpoint]]; request.HTTPMethod = @"POST"; request.HTTPBody = [queryString cly_dataUTF8]; - return request.copy; + return request.copy; } else { @@ -447,12 +447,12 @@ - (NSURLRequest *)feedbacksRequest [URL appendString:kCountlyEndpointO]; [URL appendString:kCountlyEndpointSDK]; - if (CountlyConnectionManager.sharedInstance.alwaysUsePOST) + if (queryString.length > kCountlyGETRequestMaxLength || CountlyConnectionManager.sharedInstance.alwaysUsePOST) { NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL]]; request.HTTPMethod = @"POST"; request.HTTPBody = [queryString cly_dataUTF8]; - return request.copy; + return request.copy; } else { diff --git a/src/ios/CountlyiOS/CountlyRemoteConfig.m b/src/ios/CountlyiOS/CountlyRemoteConfig.m index 0de5539..6949a94 100644 --- a/src/ios/CountlyiOS/CountlyRemoteConfig.m +++ b/src/ios/CountlyiOS/CountlyRemoteConfig.m @@ -204,7 +204,7 @@ - (NSURLRequest *)remoteConfigRequestForKeys:(NSArray *)keys omitKeys:(NSArray * NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serverOutputSDKEndpoint]]; request.HTTPMethod = @"POST"; request.HTTPBody = [queryString cly_dataUTF8]; - return request.copy; + return request.copy; } else {