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

chore: migrate UUID from v4 to v7 #145

Merged
merged 5 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
## Next

- chore: migrate UUID from v4 to v7 ([#145](https://github.com/PostHog/posthog-ios/pull/145))

## 3.5.1 - 2024-06-12

- recording: fix `screenshotMode` typo ([#143](https://github.com/PostHog/posthog-android/pull/143))
- recording: fix `screenshotMode` typo ([#143](https://github.com/PostHog/posthog-ios/pull/143))

## 3.5.0 - 2024-06-11

- chore: change host to new address ([#139](https://github.com/PostHog/posthog-ios/pull/139))
- fix: rename groupProperties to groups for capture methods ([#140](https://github.com/PostHog/posthog-ios/pull/140))
- recording: add `screenshotMode` option for session replay instead of wireframe ([#142](https://github.com/PostHog/posthog-android/pull/142))
- recording: add `screenshotMode` option for session replay instead of wireframe ([#142](https://github.com/PostHog/posthog-ios/pull/142))

## 3.4.0 - 2024-05-23

Expand Down
8 changes: 8 additions & 0 deletions PostHog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
699991942AFE1B56000DCB78 /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; };
699991952AFE1B56000DCB78 /* PostHog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
6999919A2AFE1BAB000DCB78 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699991992AFE1BAB000DCB78 /* AppDelegate.swift */; };
699C5FE62C20178E007DB818 /* UUIDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699C5FE52C20178E007DB818 /* UUIDUtils.swift */; };
699C5FEF2C20242A007DB818 /* UUIDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699C5FEE2C20242A007DB818 /* UUIDTest.swift */; };
69BA38D72B888E8500AA69D6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 69BA38D62B888E8500AA69D6 /* PrivacyInfo.xcprivacy */; };
69EE82BA2BA9C50400EB9542 /* PostHogReplayIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */; };
69EE82BC2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */; };
Expand Down Expand Up @@ -326,6 +328,8 @@
6999918E2AFE1B39000DCB78 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
699991902AFE1B39000DCB78 /* PostHogExampleMacOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PostHogExampleMacOS.entitlements; sourceTree = "<group>"; };
699991992AFE1BAB000DCB78 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
699C5FE52C20178E007DB818 /* UUIDUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUIDUtils.swift; sourceTree = "<group>"; };
699C5FEE2C20242A007DB818 /* UUIDTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUIDTest.swift; sourceTree = "<group>"; };
69BA38D62B888E8500AA69D6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogReplayIntegration.swift; sourceTree = "<group>"; };
69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionReplayConfig.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -470,6 +474,7 @@
690FF0BE2AEFA97F00A0B06B /* FileUtils.swift */,
690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */,
690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */,
699C5FE52C20178E007DB818 /* UUIDUtils.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -548,6 +553,7 @@
690FF0E22AEFD12900A0B06B /* PostHogConfigTest.swift */,
690FF0E82AEFD3BD00A0B06B /* PostHogQueueTest.swift */,
690FF0F42AF0F06100A0B06B /* PostHogSDKTest.swift */,
699C5FEE2C20242A007DB818 /* UUIDTest.swift */,
);
path = PostHogTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1081,6 +1087,7 @@
69EE82BA2BA9C50400EB9542 /* PostHogReplayIntegration.swift in Sources */,
3AE3FB472992AB0000AFFC18 /* Hedgelog.swift in Sources */,
69261D132AD5685B00232EC7 /* PostHogFeatureFlags.swift in Sources */,
699C5FE62C20178E007DB818 /* UUIDUtils.swift in Sources */,
69261D1D2AD967CD00232EC7 /* PostHogFileBackedQueue.swift in Sources */,
3AE3FB432992985A00AFFC18 /* Reachability.swift in Sources */,
69F518122BAC783300F52C14 /* CGColor+Util.swift in Sources */,
Expand All @@ -1094,6 +1101,7 @@
690FF0F52AF0F06100A0B06B /* PostHogSDKTest.swift in Sources */,
690FF0E12AEFC59100A0B06B /* PostHogFileBackedQueueTest.swift in Sources */,
3A62647529CB0168007E8C07 /* TestPostHog.swift in Sources */,
699C5FEF2C20242A007DB818 /* UUIDTest.swift in Sources */,
3A62646A29C9E385007E8C07 /* MockPostHogServer.swift in Sources */,
690FF0BB2AEF8B8200A0B06B /* PostHogContextTest.swift in Sources */,
690FF0E32AEFD12900A0B06B /* PostHogConfigTest.swift in Sources */,
Expand Down
6 changes: 3 additions & 3 deletions PostHog/Models/PostHogEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class PostHogEvent {
case apiKey
}

init(event: String, distinctId: String, properties: [String: Any]? = nil, timestamp: Date = Date(), uuid: UUID = .init(), apiKey: String? = nil) {
init(event: String, distinctId: String, properties: [String: Any]? = nil, timestamp: Date = Date(), uuid: UUID = UUID.v7(), apiKey: String? = nil) {
self.event = event
self.distinctId = distinctId
self.properties = properties ?? [:]
Expand Down Expand Up @@ -61,8 +61,8 @@ public class PostHogEvent {

guard let distinctId = (json["distinct_id"] as? String) ?? (properties["distinct_id"] as? String) else { return nil }

let uuid = ((json["uuid"] as? String) ?? (json["message_id"] as? String)) ?? UUID().uuidString
let uuidObj = UUID(uuidString: uuid) ?? UUID()
let uuid = ((json["uuid"] as? String) ?? (json["message_id"] as? String)) ?? UUID.v7().uuidString
let uuidObj = UUID(uuidString: uuid) ?? UUID.v7()

let apiKey = json["api_key"] as? String

Expand Down
2 changes: 1 addition & 1 deletion PostHog/PostHogSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30
}

private func rotateSession() {
let newSessionId = UUID().uuidString
let newSessionId = UUID.v7().uuidString
let newSessionLastTimestamp = now().timeIntervalSince1970

sessionLock.withLock {
Expand Down
3 changes: 2 additions & 1 deletion PostHog/PostHogSessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class PostHogSessionManager {
anonymousId = storage.getString(forKey: .anonymousId)

if anonymousId == nil {
anonymousId = idGen(UUID()).uuidString
let uuid = UUID.v7()
anonymousId = idGen(uuid).uuidString
setAnonId(anonymousId ?? "")
}
}
Expand Down
59 changes: 59 additions & 0 deletions PostHog/Utils/UUIDUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// UUIDUtils.swift
// PostHog
//
// Created by Manoel Aranda Neto on 17.06.24.
//

// Inspired and adapted from https://github.com/nthState/UUIDV7/blob/main/Sources/UUIDV7/UUIDV7.swift
// but using SecRandomCopyBytes

import Foundation

public extension UUID {
static func v7() -> Self {
let timestamp = Date().timeIntervalSince1970
let unixTimeMilliseconds = UInt64(timestamp * 1000)
let timeBytes = unixTimeMilliseconds.bigEndianData.suffix(6) // First 6 bytes for the timestamp

// Prepare the random part (10 bytes to complete the UUID)
var randomBytes = [UInt8](repeating: 0, count: 10)
let status = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)
// If we can't generate secure random bytes, use a fallback
if status != errSecSuccess {
let randomData = (0 ..< 10).map { _ in UInt8.random(in: 0 ... 255) }
randomBytes = randomData
}

// Combine parts
var uuidBytes = [UInt8]()
uuidBytes.append(contentsOf: timeBytes)
uuidBytes.append(contentsOf: randomBytes)

// Set version (7) in the version byte
uuidBytes[6] = (uuidBytes[6] & 0x0F) | 0x70

// Set the UUID variant (10xx for standard UUIDs)
uuidBytes[8] = (uuidBytes[8] & 0x3F) | 0x80

// Ensure we have a total of 16 bytes
if uuidBytes.count == 16 {
let uuid = UUID(uuid: (uuidBytes[0], uuidBytes[1], uuidBytes[2], uuidBytes[3],
uuidBytes[4], uuidBytes[5], uuidBytes[6], uuidBytes[7],
uuidBytes[8], uuidBytes[9], uuidBytes[10], uuidBytes[11],
uuidBytes[12], uuidBytes[13], uuidBytes[14], uuidBytes[15]))
return uuid
} else {
// or fallback to UUID v4
return UUID()
}
}
}

extension UInt64 {
// Correctly generate Data representation in big endian format
var bigEndianData: Data {
var bigEndianValue = bigEndian
return Data(bytes: &bigEndianValue, count: MemoryLayout<UInt64>.size)
}
}
4 changes: 2 additions & 2 deletions PostHogTests/PostHogSessionManagerTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class PostHogSessionManagerTest: QuickSpec {
let distinctId = sut.getDistinctId()
expect(distinctId) == anonymousId

let idToSet = UUID().uuidString
let idToSet = UUID.v7().uuidString
sut.setDistinctId(idToSet)
let newAnonymousId = sut.getAnonymousId()
let newDistinctId = sut.getDistinctId()
Expand All @@ -48,7 +48,7 @@ class PostHogSessionManagerTest: QuickSpec {

it("Can can accept id customization via config") {
let config = PostHogConfig(apiKey: "123")
let fixedUuid = UUID()
let fixedUuid = UUID.v7()
config.getAnonymousId = { _ in fixedUuid }
let sut = PostHogSessionManager(config)
let anonymousId = sut.getAnonymousId()
Expand Down
34 changes: 34 additions & 0 deletions PostHogTests/UUIDTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// UUIDTest.swift
// PostHogTests
//
// Created by Manoel Aranda Neto on 17.06.24.
//

import Foundation

import Foundation
import Nimble
@testable import PostHog
import Quick

class UUIDTest: QuickSpec {
override func spec() {
it("test duplicated") {
let count = 10000

var created: [UUID] = []
for _ in 0 ..< count {
created.append(UUID.v7())
}

var unique: Set<UUID> = Set(minimumCapacity: count)

for i in 0 ..< created.count {
if !unique.insert(created[i]).inserted {
fatalError("Duplicate at index \(i)")
}
}
}
}
}
Loading