Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions PostHog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
EADEB8F31DECD335005322DA /* PostHogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E71DECD335005322DA /* PostHogTests.swift */; };
EADEB8F41DECD335005322DA /* ContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E81DECD335005322DA /* ContextTest.swift */; };
EADEB8F51DECD335005322DA /* CryptoTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E91DECD335005322DA /* CryptoTest.swift */; };
F250ADE0288B401B0045FA7E /* PHGSession.m in Sources */ = {isa = PBXBuildFile; fileRef = F250ADDF288B401B0045FA7E /* PHGSession.m */; };
F250ADE2288ED2E20045FA7E /* SessionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F250ADE1288ED2E20045FA7E /* SessionTest.swift */; };
F250ADE3288ED4910045FA7E /* PHGSession.h in Headers */ = {isa = PBXBuildFile; fileRef = F250ADDE288B3ED80045FA7E /* PHGSession.h */; settings = {ATTRIBUTES = (Public, ); }; };
F2A54FB028772F97006C53FF /* FeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A54FAF28772F97006C53FF /* FeatureFlagTests.swift */; };
F2A54FB128772F97006C53FF /* FeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A54FAF28772F97006C53FF /* FeatureFlagTests.swift */; };
F2A54FB32877582E006C53FF /* PHGGroupPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = F2A54FB22877582E006C53FF /* PHGGroupPayload.m */; };
Expand Down Expand Up @@ -197,6 +200,9 @@
EADEB8FF1DED3711005322DA /* circle.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = circle.yml; sourceTree = "<group>"; };
EADEB9001DED3711005322DA /* PostHog.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = PostHog.podspec; sourceTree = "<group>"; };
EADEB9011DED3711005322DA /* Podfile.lock */ = {isa = PBXFileReference; lastKnownFileType = text; path = Podfile.lock; sourceTree = "<group>"; };
F250ADDE288B3ED80045FA7E /* PHGSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PHGSession.h; sourceTree = "<group>"; };
F250ADDF288B401B0045FA7E /* PHGSession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PHGSession.m; sourceTree = "<group>"; };
F250ADE1288ED2E20045FA7E /* SessionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTest.swift; sourceTree = "<group>"; };
F2A54FAF28772F97006C53FF /* FeatureFlagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagTests.swift; sourceTree = "<group>"; };
F2A54FB22877582E006C53FF /* PHGGroupPayload.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PHGGroupPayload.m; sourceTree = "<group>"; };
F2A54FB42877583C006C53FF /* PHGGroupPayload.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PHGGroupPayload.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -341,6 +347,7 @@
6EEC1C702017EA370089C478 /* EndToEndTests.swift */,
A31958EE2385AC3A00A47EFA /* SerializationTests.m */,
F2A54FAF28772F97006C53FF /* FeatureFlagTests.swift */,
F250ADE1288ED2E20045FA7E /* SessionTest.swift */,
);
indentWidth = 2;
path = PostHogTests;
Expand Down Expand Up @@ -376,6 +383,8 @@
BF8C89BCDF12D1B64E9475F1 /* PHGCrypto.h */,
F2A54FB22877582E006C53FF /* PHGGroupPayload.m */,
F2A54FB42877583C006C53FF /* PHGGroupPayload.h */,
F250ADDE288B3ED80045FA7E /* PHGSession.h */,
F250ADDF288B401B0045FA7E /* PHGSession.m */,
);
path = Classes;
sourceTree = "<group>";
Expand Down Expand Up @@ -423,6 +432,7 @@
BF8C8AD1FBFBB6941DE29D3B /* PHGFileStorage.h in Headers */,
BF8C845A65AB5AD06E1EE26B /* PHGStorage.h in Headers */,
BF8C890FADEC88022E7A3588 /* NSData+PHGGZIP.h in Headers */,
F250ADE3288ED4910045FA7E /* PHGSession.h in Headers */,
BF8C8181F96E1C8CB86D783D /* PHGPostHogUtils.h in Headers */,
BF8C8FCEC80B47BF2236236E /* PHGUtils.h in Headers */,
BF8C8DCC18E3AA39CCB330BC /* PHGStoreKitCapturer.h in Headers */,
Expand Down Expand Up @@ -713,6 +723,7 @@
BF8C8A634BCECED539A18D57 /* PHGPostHogIntegration.m in Sources */,
BF8C8735F5CF80C193F5AEDF /* PHGAliasPayload.m in Sources */,
BF8C8374E0D8F97469715282 /* PHGPayload.m in Sources */,
F250ADE0288B401B0045FA7E /* PHGSession.m in Sources */,
BF8C86ED787477703D8BCD6C /* PHGScreenPayload.m in Sources */,
BF8C8334DB2564A7CFA7A87A /* PHGPayloadManager.m in Sources */,
BF8C87E4B70CA478721EB11D /* PHGCapturePayload.m in Sources */,
Expand All @@ -726,6 +737,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F250ADE2288ED2E20045FA7E /* SessionTest.swift in Sources */,
EADEB8F51DECD335005322DA /* CryptoTest.swift in Sources */,
EADEB8F21DECD335005322DA /* UserDefaultsStorageTest.swift in Sources */,
EAA542771EB4035400945DA7 /* CapturingTests.swift in Sources */,
Expand Down
16 changes: 16 additions & 0 deletions PostHog/Classes/PHGSession.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#import <Foundation/Foundation.h>
#import "PHGStorage.h"

@class PHGSession;


@interface PHGSession : NSObject

- (instancetype _Nonnull)initWithStorage:(id<PHGStorage> _Nonnull)fileStorage userDefaultsStorage:(id<PHGStorage> _Nonnull)userDefaultsStorage;
- (NSString *_Nullable)getId;

- (void)checkAndSetSessionId:(NSTimeInterval)timestamp;
- (void)checkAndSetSessionId;
- (void)resetSession;

@end
111 changes: 111 additions & 0 deletions PostHog/Classes/PHGSession.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// PHGSession.m
// PostHog
//
// Created by Eric Duong on 7/22/22.
// Copyright © 2022 PostHog. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "PHGSession.h"
#import "PHGPostHogUtils.h"
#import "PHGStorage.h"

static int const SESSION_CHANGE_THRESHOLD = 1800;

static NSString *const PHGSessionId = @"PHGSessionId";
static NSString *const kPHGSessionId = @"posthog.sessionId";

static NSString *const PHGSessionLastTimestamp = @"PHGSessionLastTimestamp";
static NSString *const kPHGSessionLastTimestamp = @"posthog.sessionlastTimestamp";

@interface PHGSession ()

@property (nonatomic, strong) id<PHGStorage> fileStorage;
@property (nonatomic, strong) id<PHGStorage> userDefaultsStorage;

@end

@implementation PHGSession

- (instancetype _Nonnull)initWithStorage:(id<PHGStorage>)fileStorage userDefaultsStorage:(id<PHGStorage>)userDefaultsStorage;
{
if (self = [super init]) {
self.fileStorage = fileStorage;
self.userDefaultsStorage = userDefaultsStorage;

}
return self;
}

- (void)checkAndSetSessionId
{
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
[self checkAndSetSessionId:now];
}

- (void)checkAndSetSessionId:(NSTimeInterval)timestamp
{
NSString *sessionId = [self getSessionId];
NSTimeInterval sessionLastTimestamp = [self getSessionLastTimestamp];
if (sessionId == nil || sessionLastTimestamp == 0 || fabs(timestamp - sessionLastTimestamp) > SESSION_CHANGE_THRESHOLD) {
NSString *newSessionId = createUUIDString();
[self saveSessionId:newSessionId sessionLastTimestamp:timestamp];
}
}

- (void)saveSessionId:(NSString *)sessionId sessionLastTimestamp:(NSTimeInterval)sessionLastTimestamp
{

#if TARGET_OS_TV
[self.userDefaultsStorage setString:sessionId forKey:PHGSessionId];
#else
[self.fileStorage setString:sessionId forKey:kPHGSessionId];
#endif

NSNumber* nsSessionLastTimestamp = [NSNumber numberWithDouble:sessionLastTimestamp];
#if TARGET_OS_TV
[self.userDefaultsStorage setString:distinctId forKey:PHGSessionLastTimestamp];
#else
[self.fileStorage setNumber:nsSessionLastTimestamp forKey:kPHGSessionLastTimestamp];
#endif
}

- (NSString *)getSessionId
{
#if TARGET_OS_TV
return [[NSUserDefaults standardUserDefaults] valueForKey:PHGSessionId];
#else
return [self.fileStorage stringForKey:kPHGSessionId];
#endif
}

- (NSTimeInterval)getSessionLastTimestamp
{
NSNumber *timestamp;
#if TARGET_OS_TV
timestamp = [[NSUserDefaults standardUserDefaults] numberForKey:PHGSessionLastTimestamp];
#else
timestamp = [self.fileStorage numberForKey:kPHGSessionLastTimestamp];
#endif
return [timestamp doubleValue];
}

- (NSString *)getId
{
[self checkAndSetSessionId];
return [self getSessionId];
}

- (void)resetSession
{
#if TARGET_OS_TV
[self.userDefaultsStorage removeKey:PHGSessionId];
[self.userDefaultsStorage removeKey:PHGSessionLastTimestamp];
#else
[self.fileStorage removeKey:kPHGSessionId];
[self.fileStorage removeKey:kPHGSessionLastTimestamp];
#endif
}

@end
15 changes: 15 additions & 0 deletions PostHog/Internal/PHGFileStorage.m
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,21 @@ - (void)setString:(nullable NSString *)string forKey:(NSString *)key
[self setJSON:string forKey:key];
}

- (nullable NSNumber *)numberForKey:(NSString *)key
{
NSDictionary *data = [self jsonForKey:key];
if (data) {
return data[key];
}
return nil;
}


- (void)setNumber:(nullable NSNumber *)number forKey:(NSString *)key
{
[self setJSON:number forKey:key];
}

+ (NSURL *)applicationSupportDirectoryURL
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
Expand Down
5 changes: 5 additions & 0 deletions PostHog/Internal/PHGPostHogIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#import "PHGScreenPayload.h"
#import "PHGAliasPayload.h"
#import "PHGGroupPayload.h"
#import "PHGSession.h"

#if TARGET_OS_IOS
#import <CoreTelephony/CTCarrier.h>
Expand Down Expand Up @@ -54,6 +55,7 @@ @interface PHGPostHogIntegration ()
@property (nonatomic, strong) NSURLSessionUploadTask *batchRequest;
@property (nonatomic, assign) UIBackgroundTaskIdentifier flushTaskID;
@property (nonatomic, strong) PHGReachability *reachability;
@property (nonatomic, strong) PHGSession *sessionManager;
@property (nonatomic, strong) NSTimer *flushTimer;
@property (nonatomic, strong) dispatch_queue_t serialQueue;
@property (nonatomic, strong) dispatch_queue_t backgroundTaskQueue;
Expand All @@ -79,6 +81,7 @@ - (id)initWithPostHog:(PHGPostHog *)posthog httpClient:(PHGHTTPClient *)httpClie
self.fileStorage = fileStorage;
self.userDefaultsStorage = userDefaultsStorage;
self.distinctId = [self getDistinctId];
self.sessionManager = [[PHGSession alloc] initWithStorage:fileStorage userDefaultsStorage:userDefaultsStorage];
self.reachability = [PHGReachability reachabilityWithHostname:@"google.com"];
[self.reachability startNotifier];
self.cachedStaticContext = [self staticContext];
Expand Down Expand Up @@ -226,6 +229,7 @@ - (NSDictionary *)liveContext

context[@"$groups"] = [self getGroups];
context[@"$active_feature_flags"] = [self getFeatureFlags];
context[@"$session_id"] = [self.sessionManager getId];

NSDictionary *flagsAndValues = [self getFeatureFlagsAndValues];

Expand Down Expand Up @@ -506,6 +510,7 @@ - (void)reset
#else
[self.fileStorage removeKey:kPHGDistinctIdFilename];
#endif
[self.sessionManager resetSession];
self.distinctId = nil;
}];
}
Expand Down
5 changes: 4 additions & 1 deletion PostHog/Internal/PHGStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
- (void)setString:(NSString *_Nullable)string forKey:(NSString *_Nonnull)key;
- (NSString *_Nullable)stringForKey:(NSString *_Nonnull)key;

// Number and Booleans are intentionally omitted at the moment because they are not needed
- (void)setNumber:(NSNumber *_Nullable)number forKey:(NSString *_Nonnull)key;
- (nullable NSNumber *)numberForKey:(NSString *_Nonnull)key;

// Booleans are intentionally omitted at the moment because they are not needed

@end
21 changes: 21 additions & 0 deletions PostHog/Internal/PHGUserDefaultsStorage.m
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,27 @@ - (void)setString:(NSString *)string forKey:(NSString *)key
[self setPlist:string forKey:key];
}

- (NSNumber *)numberForKey:(NSString *)key
{
if (!self.crypto) {
key = [self namespacedKey:key];

double value = [self.defaults doubleForKey:key];
return [NSNumber numberWithDouble:value];
}
return [self plistForKey:key];
}

- (void)setNumber:(NSNumber *)number forKey:(NSString *)key
{
if (!self.crypto) {
key = [self namespacedKey:key];
[self setObject:number forKey:key];
return;
}
[self setPlist:number forKey:key];
}

#pragma mark - Helpers

- (id _Nullable)plistForKey:(NSString *)key
Expand Down
1 change: 1 addition & 0 deletions PostHog/PostHog.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ FOUNDATION_EXPORT const unsigned char PostHogVersionString[];
#import "PHGPostHogIntegration.h"
#import "PHGContext.h"
#import "PHGMiddleware.h"
#import "PHGSession.h"
45 changes: 45 additions & 0 deletions PostHogTests/SessionTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Quick
import Nimble
import PostHog

class SessionTest: QuickSpec {
override func spec() {

var sessionHandler: PHGSession!
var storage : PHGFileStorage!
var userDefaultsStorage : PHGUserDefaultsStorage!

beforeEach {
let url = PHGFileStorage.applicationSupportDirectoryURL()
expect(url).toNot(beNil())
expect(url?.pathComponents[url!.pathComponents.count - 2]) == "Application Support"
expect(url?.lastPathComponent) == Bundle.main.bundleIdentifier
storage = PHGFileStorage(folder: url!, crypto: nil)
userDefaultsStorage = PHGUserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: nil, crypto: nil)
sessionHandler = PHGSession(storage: storage, userDefaultsStorage: userDefaultsStorage)
}

it("Generates a session id") {
let sessionId = sessionHandler.getId()
expect(sessionId).toNot(beNil())

let anotherSessionId = sessionHandler.getId()
expect(anotherSessionId).to(equal(sessionId))

}

it("Generates a new session id after refresh") {
let sessionId = sessionHandler.getId()
expect(sessionId).toNot(beNil())

let timeInterval = Date().addingTimeInterval(TimeInterval(40.0 * 60.0)).timeIntervalSince1970
sessionHandler.checkAndSetSessionId(timeInterval)

let anotherSessionId = sessionHandler.getId()
expect(anotherSessionId).toNot(equal(sessionId))

}

}

}