Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

iOS: Support GDPR (APSAnalytics 2.1.0), add missing native header #10051

Merged
merged 14 commits into from
May 24, 2018
Merged
38 changes: 33 additions & 5 deletions apidoc/Titanium/Analytics/Analytics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ name: Titanium.Analytics
summary: |
Used for transmitting developer-defined Analytics events to the Appcelerator Analytics product.
description: |
The analytics module can be used to supply additional context or application-specific
The Analytics module can be used to supply additional context or application-specific
information which can then be accessed during analysis using Analytics.

Use the [featureEvent](Titanium.Analytics.featureEvent) method to generate custom
events that you can view through the Analytics product. You can specify a name for
the feature event which is visible through Analytics.

**NOTE** The analytics module lets you transmit some data that is stored, but
**NOTE** The Analytics module lets you transmit some data that is stored, but
not made accessible through the Analytics UI. To access this data, you must
sign up for the optional raw data export service.

Expand All @@ -24,6 +24,16 @@ description: |
of the Analytics product. There is no plan to support the other event types, and they
should not be used.

**GDPR Compliance**

The Ti.Analytics namespace in Titanium is GDPR compliant since SDK 7.2.0. End users
should be able to opt out of Analytics using the <Titanium.Analytics.optedOut> property.
If a user opted out from Analytics and you still call Analytics events, they will be ignored.

Note: This does not affect your app until you make use of the `optedOut` property. Once
enabled for your users (e.g. by having a <Titanium.UI.Switch> in your settings), you
should handle it within your app logic.

extends: Titanium.Module
# technically these methods exist, but they serve no purpose and confuse the doc.
excludes: { methods: [ addEventListener, applyProperties, fireEvent, removeEventListener ] }
Expand Down Expand Up @@ -80,7 +90,7 @@ methods:
default: null

- name: filterEvents
summary: Sets a list of events that will not be sent to the analytics server.
summary: Sets a list of events that will not be sent to the Analytics server.
parameters:
- name: events
summary: List of events to be filtered.
Expand Down Expand Up @@ -188,7 +198,7 @@ methods:

properties:
- name: lastEvent
summary: JSON representation of the last analytics event generated.
summary: JSON representation of the last Analytics event generated.
description: |
LastEvent is the JSON version of the last event prepared to be sent to Appcelerator
during the application's session. This value may be null or undefined if no such
Expand All @@ -198,13 +208,31 @@ properties:
permission: read-only
platforms: [android, iphone, ipad]

- name: optedOut
summary: Allows the user to opt out from Analytics during runtime to comply to GPDR.
description: |
Once opted out, no Analytics events will be sent anymore. If implemented to the end user,
ensure to check if the user is opted out before trying to send an Analytics event.
In case you do not guard the Analytics call, it will be ignored silently.

Here is an example of a guarded Analytics call:

if (!Ti.Analytics.optedOut) {
Ti.Analytics.featureEvent('gdpr_rocks');
}

Read more about GDPR on the [official website](https://www.eugdpr.org/).
type: Boolean
since: "7.2.0"
platforms: [android, iphone, ipad]

examples:
- title: Custom Feature Event
example: |
This example shows how to send a feature event during an application session to indicate
some feature that you would like to track was used.

Titanium.Analytics.featureEvent('app.feature');
Ti.Analytics.featureEvent('app.feature');

In this case, the Analytics product would show statistics about how many times the
'app.feature' event was generated.
Expand Down
12 changes: 9 additions & 3 deletions iphone/Classes/APSAnalytics/APSAnalytics.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extern NSString * const APSDeployTypeProduction;
/**
* The session timeout in seconds. If the application has been in the background
* for longer than the timeout, the analytics service logs an end time to the current user session.
* Default value is 30 s.
* Default: 30s
*/
@property (atomic, readwrite) NSTimeInterval sessionTimeout;

Expand All @@ -41,12 +41,18 @@ extern NSString * const APSDeployTypeProduction;
*/
@property (atomic, strong, readonly) NSString *deployType;

//Analytic Event Generators
/**
* Allows the user to opt out from Analytics during runtime to comply to GPDR.
* Default: NO
*
* @since 2.1.0
*/
@property (nonatomic, assign, getter=isOptedOut) BOOL optedOut;

/**
* Sends a geolocation event.
* @param location A CLLocation object containing the location data.
*/

- (void)sendAppGeoEvent:(CLLocation *) location;

/**
Expand Down
67 changes: 67 additions & 0 deletions iphone/Classes/APSAnalytics/APSUtility.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Appcelerator Platform SDK
* Copyright (c) 2014 by Appcelerator, Inc. All Rights Reserved.
* Proprietary and Confidential - This source code is not for redistribution
*/

@import Foundation;
@import CoreLocation;
@import UIKit;


#import <sys/sysctl.h>
#import <mach/mach.h>
#import <sys/utsname.h>

#import <sys/types.h>
#import <sys/socket.h>
#import <ifaddrs.h>
#import <arpa/inet.h>

#ifdef DebugLog
#undef DebugLog
#endif // DebugLog

#ifdef TraceLog
#undef TraceLog
#endif // TraceLog

#ifdef ErrorLog
#undef ErrorLog
#endif

#ifdef DEBUG
#define DebugLog(fmt, ...) NSLog((@"[DEBUG] " fmt), ##__VA_ARGS__)
#define TraceLog(fmt, ...) NSLog((@"[TRACE] " fmt), ##__VA_ARGS__)
#define ErrorLog(fmt, ...) NSLog((@"[ERROR] " fmt), ##__VA_ARGS__)
#else
#define DebugLog(...)
#define TraceLog(...)
#define ErrorLog(...)
#endif // DEBUG

@interface APSUtility : NSObject

+(BOOL)isiPad;
+(NSString*)deviceModel;
+(NSString *)stringFromHexString:(NSString *)hexString;
+(NSString*)jsonStringify:(id)value error:(NSError**)error;
+(id)jsonParse:(NSString*)value error:(NSError**)error;
+(NSString *) UTCDate;
+(NSString *)UTCDateForDate:(NSDate*)date;
+(NSString*)createUUID;
+(NSString*)appIdentifier;
+(NSNumber *) numCores;
+(NSString *)ostype;
+(NSString *)getArchitecture;
+(BOOL)isOnline;
+(NSString *)systemVersion;
+(NSString *)deviceName;
+(NSString *)networkType;
+(NSString*)stringValue:(id)value;
+(NSDate *)dateForString:(NSString *)dateString;
+(BOOL) isEmptyString:(NSString*)value;
+(NSTimeInterval)timeBetweenStartDate:(NSDate *)startDate andEndDate:(NSDate*)endDate;
+(NSDictionary *)locationDictionary:(CLLocation *)newLocation;

@end
Binary file modified iphone/Classes/APSAnalytics/libAPSAnalytics.a
Binary file not shown.
16 changes: 16 additions & 0 deletions iphone/Classes/AnalyticsModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@

@interface AnalyticsModule : TiModule

#pragma mark Public API's

- (NSString *)lastEvent;

- (void)navEvent:(id)args;

- (NSInteger)featureEvent:(id)args;

- (void)filterEvents:(id)args;

- (void)setOptedOut:(id)optedOut;

- (NSNumber *)optedOut;

#pragma mark Internal API's

+ (BOOL)isEventFiltered:(NSString *)eventName;

@end
11 changes: 11 additions & 0 deletions iphone/Classes/AnalyticsModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ - (void)filterEvents:(id)args
}
}

- (void)setOptedOut:(id)optedOut
{
ENSURE_TYPE(optedOut, NSNumber);
[[APSAnalytics sharedInstance] setOptedOut:[TiUtils boolValue:optedOut]];
}

- (NSNumber *)optedOut
{
return @([[APSAnalytics sharedInstance] isOptedOut]);
}

+ (BOOL)isEventFiltered:(NSString *)eventName
{
if (_filteredEvents == nil)
Expand Down
2 changes: 2 additions & 0 deletions iphone/iphone/Titanium.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,7 @@
DB90749E1D84299500BB0FD5 /* TiUIiOSFeedbackGeneratorProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIiOSFeedbackGeneratorProxy.m; sourceTree = "<group>"; };
DBCFCB7A1F8261BF00A4CE61 /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SBJSON.h; path = ../Classes/SBJSON.h; sourceTree = "<group>"; };
DBCFCB7B1F8261BF00A4CE61 /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SBJSON.m; path = ../Classes/SBJSON.m; sourceTree = "<group>"; };
DBE07F2D20AC4B42000307EA /* APSUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APSUtility.h; sourceTree = "<group>"; };
EF0ADCF9143F9ABF00977386 /* NSData+Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+Additions.m"; path = "../Classes/NSData+Additions.m"; sourceTree = "<group>"; };
EF0ADD19143FA0CD00977386 /* GeolocationModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeolocationModule.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -2102,6 +2103,7 @@
isa = PBXGroup;
children = (
312E36BF190B06D9008EAB8D /* APSAnalytics.h */,
DBE07F2D20AC4B42000307EA /* APSUtility.h */,
312E36C0190B06D9008EAB8D /* libAPSAnalytics.a */,
);
name = APSAnalytics;
Expand Down
32 changes: 32 additions & 0 deletions tests/ti.analytics.addontest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Appcelerator Titanium Mobile
* Copyright (c) 2011-Present by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
/* eslint-env mocha */
/* global Ti */
/* eslint no-unused-expressions: "off" */
'use strict';
var should = require('./utilities/assertions');

describe('Titanium.Analytics', function () {
it.androidMissing('.optedOut', function () {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@garymathews @ypbnv Once Android is merged, we can update this one to work or all platforms.

@infosia I think this works on Windows already, since your tests have been pretty extensive before merging. Any objections? :-)

should(Ti.Analytics.optedOut).be.a.Boolean;
should(Ti.Analytics.setOptedOut).be.a.Function;
should(Ti.Analytics.getOptedOut).be.a.Function;

should(Ti.Analytics.optedOut).eql(false);
should(Ti.Analytics.getOptedOut()).eql(false);

Ti.Analytics.optedOut = true;

should(Ti.Analytics.optedOut).eql(true);
should(Ti.Analytics.getOptedOut()).eql(true);

Ti.Analytics.setOptedOut(false);

should(Ti.Analytics.optedOut).eql(false);
should(Ti.Analytics.getOptedOut()).eql(false);
});
});