Skip to content

Commit

Permalink
[MOBILE-10980] 0.6.0 Release (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
bnickel authored May 22, 2024
1 parent d1ae581 commit c2aaec8
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 49 deletions.
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

## [0.6.0]

### Added

- Added new `startRecording` option, `resumePreviousSession`, which resumes the previous session on
start if the session has not yet expired.
- Added new signature `stopRecording(deleteUser: Bool)` which deletes the current user state.
Subsequent calls to `startRecording` will have a new user and session as a result.
- Added several internal interfaces to support an upcoming integration.

## [0.5.3]

### Changed

- Improved trace logging for failed Sqlite queries.

## [0.5.2]

### Added
Expand Down Expand Up @@ -164,7 +180,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for manual capture within WKWebView.
- Support for platforms targeting Swift: macOS, watchOS, iOS, iPadOS, tvOS.

[Unreleased]: https://github.com/heap/heap-swift-core-sdk/compare/0.5.2...main
[Unreleased]: https://github.com/heap/heap-swift-core-sdk/compare/0.6.0...main
[0.6.0]: https://github.com/heap/heap-swift-core-sdk/compare/0.5.3...0.6.0
[0.5.3]: https://github.com/heap/heap-swift-core-sdk/compare/0.5.2...0.5.3
[0.5.2]: https://github.com/heap/heap-swift-core-sdk/compare/0.5.1...0.5.2
[0.5.1]: https://github.com/heap/heap-swift-core-sdk/compare/0.5.0...0.5.1
[0.5.0]: https://github.com/heap/heap-swift-core-sdk/compare/0.4.0...0.5.0
Expand Down
4 changes: 2 additions & 2 deletions Development/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ let package = Package(
]),
.binaryTarget(
name: "HeapSwiftCoreInterfaces",
url: "https://cdn.heapanalytics.com/ios/heap-swift-core-interfaces-0.6.0-alpha.3.zip", // END HeapSwiftCoreInterfaces URL
checksum: "8a6477840ec8871f5dca44c707d087e9e9a1b0c121f80dcb4cb04f2a5eaa625d" // END HeapSwiftCoreInterfaces checksum
url: "https://cdn.heapanalytics.com/ios/heap-swift-core-interfaces-0.6.0.zip", // END HeapSwiftCoreInterfaces URL
checksum: "e92bf74d2525cbc9503ed9f4ef3369e09e3b216c6f6b23a4de8a4614ed1bc840" // END HeapSwiftCoreInterfaces checksum
),
.target(
name: "HeapSwiftCoreTestSupport",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import SQLite3
import Foundation

struct SqliteError: Error, Equatable, Hashable {
struct SqliteError: Error, Equatable, Hashable, CustomStringConvertible {
let code: Int32
let message: String
let file: String
let line: UInt

var description: String {
"Sqlite command at \(file):\(line) failed with error \(code) and the following message: \(message)"
}
}

fileprivate extension Int32 {

/// Throws an error if a value is not a successful Sqlite return code.
func assertSuccess() throws {
func assertSuccess(message: @autoclosure () -> String, file: String, line: UInt) throws {
switch self {
case SQLITE_OK, SQLITE_ROW, SQLITE_DONE:
return
default:
throw SqliteError(code: self)
throw SqliteError(code: self, message: message(), file: file, line: line)
}
}

func assertSuccess(message: @autoclosure () -> String, in statement: SqliteConnection.Statement) throws {
try assertSuccess(message: "\(message()) in \(statement.query)", file: statement.file, line: statement.line)
}
}

/// A lightweight wrapper around Sqlite that bridges common types.
Expand All @@ -30,9 +41,9 @@ final class SqliteConnection {
databaseUrl = url
}

func connect() throws {
func connect(file: String = #fileID, line: UInt = #line) throws {
guard ppDb == nil else { return }
try sqlite3_open(databaseUrl.path, &ppDb).assertSuccess()
try sqlite3_open(databaseUrl.path, &ppDb).assertSuccess(message: "Failed to open database", file: file, line: line)
}

/// Creates
Expand All @@ -41,12 +52,12 @@ final class SqliteConnection {
/// - parameters: A list of indexed parameters to apply, of known convertable types.
/// - rowCallback: A callback to execute after each row is read (if any). This is used for extracting row data.
/// - Throws: A `SqliteError` if an error is encountered on any step of the process.
func perform(query: String, parameters: [Sqlite3Parameter] = [], rowCallback: (_ row: Row) throws -> Void = { _ in }) throws {
func perform(query: String, parameters: [Sqlite3Parameter] = [], file: String = #fileID, line: UInt = #line, rowCallback: (_ row: Row) throws -> Void = { _ in }) throws {
guard let ppDb = ppDb else {
throw SqliteError(code: SQLITE_ERROR)
throw SqliteError(code: SQLITE_ERROR, message: "Database pointer is nil", file: file, line: line)
}

let statement = try Statement(query: query, db: ppDb)
let statement = try Statement(query: query, db: ppDb, file: file, line: line)
try statement.bindIndexedParameters(parameters)
defer { statement.finalize() }
try statement.stepUntilDone(rowCallback)
Expand All @@ -64,34 +75,40 @@ final class SqliteConnection {

/// A wrapper around a prepared query.
final class Statement {
private var pointer: OpaquePointer?
private(set) var pointer: OpaquePointer?
let query: String
let file: String
let line: UInt

fileprivate init(pointer: OpaquePointer?) {
fileprivate init(pointer: OpaquePointer?, query: String, file: String = #fileID, line: UInt = #line) {
self.pointer = pointer
self.query = query
self.file = file
self.line = line
}

fileprivate convenience init(query: String, db: OpaquePointer) throws {
fileprivate convenience init(query: String, db: OpaquePointer, file: String = #fileID, line: UInt = #line) throws {
var pointer: OpaquePointer?
try sqlite3_prepare_v2(db, query, -1, &pointer, nil).assertSuccess()
self.init(pointer: pointer)
try sqlite3_prepare_v2(db, query, -1, &pointer, nil).assertSuccess(message: "Failed to prepare query: \(query)", file: file, line: line)
self.init(pointer: pointer, query: query)
}

/// Binds parameters to the query.
func bindIndexedParameters(_ parameters: [Sqlite3Parameter]) throws {
for (parameter, index) in zip(parameters, 1...) {
try parameter.bind(at: index, statementPointer: pointer)
try parameter.bind(at: index, statement: self)
}
}

/// Performs a step of the query.
/// - Returns: True if the execution returned a row.
private func step() throws -> Bool {
guard let pointer = pointer else {
throw SqliteError(code: SQLITE_ERROR)
throw SqliteError(code: SQLITE_ERROR, message: "Statement pointer is nil for query: \(query)", file: file, line: line)
}

let result = sqlite3_step(pointer)
try result.assertSuccess()
try result.assertSuccess(message: "Step failed for query: \(query)", file: file, line: line)
if result == SQLITE_DONE {
finalize()
}
Expand Down Expand Up @@ -168,48 +185,48 @@ private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.sel

/// A protocol for binding Swift data types to Sqlite parameters.
protocol Sqlite3Parameter {
func bind(at index: Int, statementPointer: OpaquePointer?) throws
func bind(at index: Int, statement: SqliteConnection.Statement) throws
}

extension Int: Sqlite3Parameter {
func bind(at index: Int, statementPointer: OpaquePointer?) throws {
try sqlite3_bind_int64(statementPointer, Int32(index), Int64(self)).assertSuccess()
func bind(at index: Int, statement: SqliteConnection.Statement) throws {
try sqlite3_bind_int64(statement.pointer, Int32(index), Int64(self)).assertSuccess(message: "Failed to bind integer \"\(self)\" at index \(index)", in: statement)
}
}

extension Bool: Sqlite3Parameter {
func bind(at index: Int, statementPointer: OpaquePointer?) throws {
try (self ? 1 : 0).bind(at: index, statementPointer: statementPointer)
func bind(at index: Int, statement: SqliteConnection.Statement) throws {
try (self ? 1 : 0).bind(at: index, statement: statement)
}
}

extension String: Sqlite3Parameter {
func bind(at index: Int, statementPointer: OpaquePointer?) throws {
try sqlite3_bind_text(statementPointer, Int32(index), self, -1, SQLITE_TRANSIENT).assertSuccess()
func bind(at index: Int, statement: SqliteConnection.Statement) throws {
try sqlite3_bind_text(statement.pointer, Int32(index), self, -1, SQLITE_TRANSIENT).assertSuccess(message: "Failed to bind text \"\(self)\" at index \(index)", in: statement)
}
}

extension Data: Sqlite3Parameter {
func bind(at index: Int, statementPointer: OpaquePointer?) throws {
func bind(at index: Int, statement: SqliteConnection.Statement) throws {
try self.withUnsafeBytes { (pointer: UnsafeRawBufferPointer) in
try sqlite3_bind_blob(statementPointer, Int32(index), pointer.baseAddress, Int32(self.count), SQLITE_TRANSIENT).assertSuccess()
try sqlite3_bind_blob(statement.pointer, Int32(index), pointer.baseAddress, Int32(self.count), SQLITE_TRANSIENT).assertSuccess(message: "Failed to bind data of length \(count) at index \(index)", in: statement)
}
}
}

/// This rounds to the nearest second, which is close enough for us.
extension Date: Sqlite3Parameter {
func bind(at index: Int, statementPointer: OpaquePointer?) throws {
try Int(timeIntervalSinceReferenceDate).bind(at: index, statementPointer: statementPointer)
func bind(at index: Int, statement: SqliteConnection.Statement) throws {
try Int(timeIntervalSinceReferenceDate).bind(at: index, statement: statement)
}
}

extension Optional: Sqlite3Parameter where Wrapped: Sqlite3Parameter {
func bind(at index: Int, statementPointer: OpaquePointer?) throws {
func bind(at index: Int, statement: SqliteConnection.Statement) throws {
if let value = self {
try value.bind(at: index, statementPointer: statementPointer)
try value.bind(at: index, statement: statement)
} else {
try sqlite3_bind_null(statementPointer, Int32(index)).assertSuccess()
try sqlite3_bind_null(statement.pointer, Int32(index)).assertSuccess(message: "Failed to bind null at index \(index)", in: statement)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import Foundation
import HeapSwiftCoreInterfaces

extension Operation {
static func forSqlite(connection: SqliteConnection, block: @escaping (_ connection: SqliteConnection) throws -> Void) -> Operation {
static func forSqlite(connection: SqliteConnection, file: String = #fileID, line: UInt = #line, block: @escaping (_ connection: SqliteConnection) throws -> Void) -> Operation {
BlockOperation {
do {
try block(connection)
} catch {
HeapLogger.shared.trace("Error occurred executing query: \(error)")
HeapLogger.shared.trace("Error occurred executing query: \(error)", file: file, line: line)
}
}
}
Expand All @@ -18,9 +18,9 @@ class SqliteDataStore: DataStoreProtocol {
private let connection: SqliteConnection
internal let dataStoreSettings: DataStoreSettings

func performOnSqliteQueue(waitUntilFinished: Bool = false, block: @escaping (_ connection: SqliteConnection) throws -> Void) {
func performOnSqliteQueue(waitUntilFinished: Bool = false, file: String = #fileID, line: UInt = #line, block: @escaping (_ connection: SqliteConnection) throws -> Void) {
OperationQueue.dataStore.addOperations([
.forSqlite(connection: connection, block: block),
.forSqlite(connection: connection, file: file, line: line, block: block),
], waitUntilFinished: waitUntilFinished)
}

Expand Down
4 changes: 2 additions & 2 deletions Development/Sources/HeapSwiftCore/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ struct Version {
static let major = 0

/// Minor version.
static let minor = 5
static let minor = 6

/// Revision number.
static let revision = 2
static let revision = 0

/// Optional pre-release version
static let prerelease: String? = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import HeapSwiftCoreInterfaces

class CountingContentsquareIntegration: _ContentsquareIntegration {

var sessionTimeoutDuration: TimeInterval
var sessionTimeoutDuration: TimeInterval?
var contentsquareMethods: _ContentsquareMethods? = nil

var pageviews: [Pageview] = []

init(sessionTimeoutDuration: TimeInterval) {
init(sessionTimeoutDuration: TimeInterval?) {
self.sessionTimeoutDuration = sessionTimeoutDuration
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ final class SessionExtensionSpec: HeapSpec {
expect(result.current?.contentsquareSessionProperties).to(equal(.createdByContentsquareScreenView))
}

context("with _ContentsquareIntegration") {
context("with _ContentsquareIntegration providing a value") {

var integration: CountingContentsquareIntegration!

Expand Down Expand Up @@ -124,6 +124,27 @@ final class SessionExtensionSpec: HeapSpec {
expect(result.current?.sessionExpirationDate).to(beCloseTo(expectedExpirationDate, within: 1))
}
}

context("with _ContentsquareIntegration not providing a value") {

var integration: CountingContentsquareIntegration!

beforeEach {
integration = CountingContentsquareIntegration(sessionTimeoutDuration: nil)
manager.contentsquareIntegration = integration
}

it("extends the session using the Heap timeout") {
integration.sessionTimeoutDuration = 5
let timestamp = sessionTimestamp.addingTimeInterval(60)
let expectedExpirationDate = timestamp.addingTimeInterval(300)

let result = manager.createSessionIfExpired(extendIfNotExpired: true, properties: .init(), at: timestamp)

expect(result.current?.sessionInfo.id).to(equal(originalSessionId))
expect(result.current?.sessionExpirationDate).to(beCloseTo(expectedExpirationDate, within: 1))
}
}
}

describe("extendSessionAndSetLastPageview") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ final class TransformPipelineSpec: HeapSpec {
}

it("transforms the data") {
transformerA.applyToAll(sessionReplay: "SR", contentsquareProperties: .init(cspid: "PID", cspvid: "PVID", cssn: "SN", csts: "TS", csuu: "UU"))
transformerA.applyToAll(sessionReplay: "SR", contentsquareProperties: .init(cspid: "PID", csuu: "UU", cssn: "SN", cspvid: "PVID", csts: "TS"))

pipeline.add(transformerA)
dataStore.createNewUserIfNeeded(environmentId: "11", userId: "123", identity: nil, creationDate: Date())
Expand Down Expand Up @@ -111,7 +111,7 @@ final class TransformPipelineSpec: HeapSpec {
}

it("transforms the data") {
transformerA.applyToAll(sessionReplay: "SR", contentsquareProperties: .init(cspid: "PID", cspvid: "PVID", cssn: "SN", csts: "TS", csuu: "UU"))
transformerA.applyToAll(sessionReplay: "SR", contentsquareProperties: .init(cspid: "PID", csuu: "UU", cssn: "SN", cspvid: "PVID", csts: "TS"))
pipeline.add(transformerA)

pipeline.insertPendingMessage(message)
Expand All @@ -129,7 +129,7 @@ final class TransformPipelineSpec: HeapSpec {
}

it("works with an already completed processor") {
transformerA.applyToAll(sessionReplay: "SR", contentsquareProperties: .init(cspid: "PID", cspvid: "PVID", cssn: "SN", csts: "TS", csuu: "UU"))
transformerA.applyToAll(sessionReplay: "SR", contentsquareProperties: .init(cspid: "PID", csuu: "UU", cssn: "SN", cspvid: "PVID", csts: "TS"))
pipeline.add(transformerA)

let processor = pipeline.processor(for: message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ Create Table TestTable (

}

it("throws with meaningful data") {
let query = "Insert Into TestTable (FakeColumn) Values (1);"
do {
try connection.perform(query: query)
XCTFail("The above code should have failed")
} catch let error as SqliteError {
expect(error.code).to(equal(1))
expect(error.message).to(contain(query))
expect("\(error)").to(contain("HeapSwiftCoreTests/SqliteConnectionSpec.swift:71"))
expect("\(error)").to(contain("error 1"))
expect("\(error)").to(contain(query))
}
}

it("can edit and query tables") {

// Fun fact! Sqlite only executes the first statement in a query.
Expand Down
4 changes: 2 additions & 2 deletions HeapSwiftCore.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'HeapSwiftCore'
s.version = '0.5.2'
s.version = '0.6.0'
s.license = { :type => 'MIT' }
s.summary = 'The core Heap library used for apps on Apple platforms.'
s.homepage = 'https://heap.io'
Expand All @@ -18,7 +18,7 @@ Pod::Spec.new do |s|
s.source_files = 'Development/Sources/HeapSwiftCore/**/*.swift'

s.dependency 'SwiftProtobuf', '~> 1.6'
s.dependency 'HeapSwiftCoreInterfaces', '0.6.0-alpha.3'
s.dependency 'HeapSwiftCoreInterfaces', '0.6.0'

s.swift_versions = ['5.0']
end
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ let package = Package(
path: "Development/Sources/HeapSwiftCore"),
.binaryTarget(
name: "HeapSwiftCoreInterfaces",
url: "https://cdn.heapanalytics.com/ios/heap-swift-core-interfaces-0.6.0-alpha.3.zip", // END HeapSwiftCoreInterfaces URL
checksum: "8a6477840ec8871f5dca44c707d087e9e9a1b0c121f80dcb4cb04f2a5eaa625d" // END HeapSwiftCoreInterfaces checksum
url: "https://cdn.heapanalytics.com/ios/heap-swift-core-interfaces-0.6.0.zip", // END HeapSwiftCoreInterfaces URL
checksum: "e92bf74d2525cbc9503ed9f4ef3369e09e3b216c6f6b23a4de8a4614ed1bc840" // END HeapSwiftCoreInterfaces checksum
)
]
)

0 comments on commit c2aaec8

Please sign in to comment.