Giter Site home page Giter Site logo

telemetrydeck / swiftclient Goto Github PK

View Code? Open in Web Editor NEW
146.0 6.0 31.0 176 KB

Swift SDK for TelemetryDeck, a privacy-conscious analytics service for apps and websites.

Home Page: https://telemetrydeck.com/

License: Other

Swift 95.89% Objective-C 0.62% Shell 1.14% Ruby 2.35%
analytics privacy-protection telemetry ios swift swiftui hacktoberfest

swiftclient's Introduction

TelemetryClient

This package allows you to send signals to TelemetryDeck from your Swift code. Sign up for a free account at telemetrydeck.com

Installation

The easiest way to install TelemetryDeck is using Swift Package Manager, Apple's solution which is built into Xcode. In Xcode, press File > Add Packages..., then in the resulting window enter https://github.com/TelemetryDeck/SwiftClient into the search field. Set the Dependency Rule field to Up to Next Major Version, then press the Add Package button. Xcode will download it, then you can choose which target of your app to add it to.

See our detailed setup guide for more information.

Initialization

Init the Telemetry Manager at app startup, so it knows your App ID (you can retrieve the App ID from your TelemetryDeck Dashboard under Set Up App)

let configuration = TelemetryManagerConfiguration(appID: "<YOUR-APP-ID>")
// optional: modify the configuration here
TelemetryManager.initialize(with: configuration)

For example, if you're building a scene based app, in the init() function for your App:

import SwiftUI
import TelemetryClient

@main
struct TelemetryTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }

    init() {
        // Note: Do not add this code to `WindowGroup.onAppear`, which will be called
        //       *after* your window has been initialized, and might lead to our initialization
        //       occurring too late.
        let configuration = TelemetryManagerConfiguration(appID: "<YOUR-APP-ID>")
        TelemetryManager.initialize(with: configuration)
    }
}

Then send signals like so:

TelemetryManager.send("appLaunchedRegularly")

Debug -> Test Mode

If your app's build configuration is set to "Debug", all signals sent will be marked as testing signals. In the Telemetry Viewer app, activate Test Mode to see those.

If you want to manually control whether test mode is active, you can set the configuration.testMode property.

User Identifiers

Telemetry Manager will create a user identifier for you user that is specific to app installation and device. If you have a better user identifier available, such as an email address or a username, you can use that instead, by passing it on to the TelemetryManagerConfiguration (the identifier will be hashed before sending it).

configuration.defaultUser = "[email protected]"

You can update the configuration after TelemetryManager is already initialized.

Payload

You can also send additional payload data with each signal:

TelemetryManager.send("databaseUpdated", with: ["numberOfDatabaseEntries": "3831"])

Telemetry Manager will automatically send a base payload with these keys:

  • platform
  • systemVersion
  • appVersion
  • buildNumber
  • isSimulator
  • isTestFlight
  • isAppStore
  • modelName
  • architecture
  • operatingSystem
  • targetEnvironment

Sessions

With each Signal, the client sends a hash of your user ID as well as a session ID. This gets automatically generated when the client is initialized, so if you do nothing, you'll get a new session each time your app is started from cold storage.

On iOS, tvOS, and watchOS, the session identifier will automatically update whenever your app returns from background, or if it is launched from cold storage. On other platforms, a new identifier will be generated each time your app launches. If you'd like more fine-grained session support, write a new random session identifier into the TelemetryManagerConfiguration's sessionID property each time a new session begins.

Custom Salt

By default, user identifiers are hashed by the TelemetryDeck SDK, and then sent to the Ingestion API, where we'll add a salt to the received identifier and hash it again.

This is enough for most use cases, but if you want to extra privacy conscious, you can add in you own salt on the client side. The TelemetryDeck SDK will append the salt to all user identifers before hashing them and sending them to us.

If you'd like to use a custom salt, you can do so by passing it on to the TelemetryManagerConfiguration

let configuration = TelemetryManagerConfiguration(appID: "<YOUR-APP-ID>", salt: "<A RANDOM STRING>")

Custom Server

A very small subset of our customers will want to use a custom signal ingestion server or a custom proxy server. To do so, you can pass the URL of the custom server to the TelemetryManagerConfiguration:

let configuration = TelemetryManagerConfiguration(appID: "<YOUR-APP-ID>", baseURL: "https://nom.telemetrydeck.com")

Custom Logging Strategy

By default, some logs helpful for monitoring TelemetryDeck are printed out to the console. This behaviour can be customised by overriding configuration.logHandler. This struct accepts a minimum allows log level (any log with the same or higher log level will be accepted) and a closure.

This allows for compatibility with other logging solutions, such as swift-log, by providing your own closure.

Developing this SDK

Your PRs on TelemetryDeck's Swift Client are very much welcome. Check out the SwiftClientTester project, which provides a harness you can use to work on the library and try out new things.

When making a new release, run ./tag-release.sh MAJOR.MINOR.PATCH to bump the version string in the SDK, create a new commit and tag that commit accordingly all in one step.

swiftclient's People

Contributors

andreyz avatar ca13ra1 avatar chrisvasselli avatar clafou avatar conath avatar ddaddy avatar finnvoor avatar giannicarlo avatar jazzychad avatar jeehut avatar kimar avatar maxbaeumle avatar mortengregersen avatar mrackwitz avatar nevillco avatar nickkohrn avatar rubenfer avatar shaundon avatar sherlouk avatar starlard avatar winsmith avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

swiftclient's Issues

Add support for usage through Server Side Swift

I just tried to setup TelemetryDeck on the Server (using Vapor) to track some anonymized events of my users API endpoint actions. One reason for this is that I take privacy seriously and therefore don't want to include any library that automatically accesses environment data if I can avoid it. This library for sure does that, so my preferred way to use TelemetryDeck is on the server side, where the environment doesn't provide any user-specific information. I already found out that I can set a user identifier manually, and I saw in the code that any additional payload fields can also override the ones automatically set. This way I will be able to control what data is being tracked of my users and to also implement Differential Privacy on my server if needed (as Telemetry doesn't support that yet).

But even with all these manual actions, this library seems still not to be prepared for Server-side usage as it's not only failing to recognize Linux as a platform automatically, but is also using DispatchQueue for doing asynchronous network calls, which I think is not the right way to go for a Vapor app. I'd expect some setting to run the network requests in an EventLoop instead (using Swift NIO).

@winsmith Do you have plans to add proper support for usage from the Server side? Or is this only intended for app clients?

Signals sent in Debug Build appear as TestFlight in Viewer

Signals sent from a debug version of the app have their isTestflight value set to true. I think this value should be false. Ideally an isDebug value would also be present.

Setup configuration allowing debug signals:

let configuration = TelemetryManagerConfiguration(appID: "MY_APP_ID")
configuration.telemetryAllowDebugBuilds = true
TelemetryManager.initialize(with: configuration)
TelemetryManager.send("applicationDidFinishLaunching"

Signal Values:
Screen Shot 2021-05-28 at 2 38 38 PM

Within my personal iOS project I use this code to identify between Debug/TestFlight/AppStore for various other checks. Maybe it will be helpful for this?

public extension Bundle {
    enum DistributionSource {
        case debug, testFlight, appStore

        public var displayableTitle: String {
            switch self {
            case .debug: return "Debug"
            case .testFlight: return "TestFlight"
            case .appStore: return "App Store"
            }
        }
    }

    static var distributionSource: DistributionSource {
        #if DEBUG
        return .debug
        #else
        let isTestFlight = main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
        return isTestFlight ? .testFlight : .appStore
        #endif
    }
}

Can `TelemetryManager` detect when running in iOS Widget?

Description

I have code which is shared between iOS app, iOS Widget and WatchOS app - but I can't see how to differentiate between the code running as an iOS process, or an iOS Widget process.

(The same probably applies to WatchOS or WatchOS Complication)

Can something be added to the standard payload dictionary, to help?

Possible solution 1

Change the platform property to (somehow!) detect when running in an iOS Widget or WatchOS Complication.

Possible solution 2

Include the bundle identifier (Bundle.main.bundleIdentifier) in the payload dictionary.

Update README

Hey! In the set up README for the Swift package it says you'll find your App ID under 'App Settings' but it's actually under 'Set Up App'. Maybe just a nitpick if a little confusing, but I thought you might want to know 😊

Make `defaultUserIdentifier` public, or supply `clientUser` once somehow

Description

I guess that sessions, when implemented, can be filtered by their user identifier. So if a customer sends a support email via my app, I'd like to include that user ID in the body of the email.

Problem

The TelemetryManager.defaultUserIdentifier property is internal - so I can't include it in the email body.

Alternatively...

Alternatively, I could make my own user UUID, store it somewhere, and then supply it with every call to TelemetryManager.send(...). But that's a very repetitive process.

Would it be possible to set a custom user value once - either in TelemetryManagerConfiguration, or TelemetryManager itself - and have that value used for every signal?

(I'd be happy to make a pull request - but would like some guidance first about your preferred approach!)

Compilation Failure on watchOS

When included in a watchOS target, this Telemetry library fails to compile with the following errors:

.../SourcePackages/checkouts/SwiftClient/Sources/TelemetryClient/TelemetryClient.swift:138:31: error: cannot find 'UIDevice' in scope
        return "\(platform) \(UIDevice.current.systemVersion)"
                              ^~~~~~~~
.../SourcePackages/checkouts/SwiftClient/Sources/TelemetryClient/TelemetryClient.swift:165:16: error: cannot find 'UIDevice' in scope
        return UIDevice.current.identifierForVendor?.uuidString ?? "unknown user \(systemVersion) \(buildNumber)"

Use CI to update Version String

While skimming through the code, I noticed this line stating the current version of the client to be 1.1.5, but 1.1.6 seems to be already released. Could it be that this version String is manually updated?

Maybe we should think about a solution to ensure it can never be outdated either by making it dynamic or adding some checks (in the build process or CI) to prevent such a thing from happening again. I imagine it could be hard to debug issues if you're searching in the wrong commit. :)

isAppStore true on local test builds

It appears that isAppStore is true when the build is not on a simulator, and not on TestFlight.

However, if I build from Xcode onto a physical device then this makes it show as "isAppStore: true" which is incorrect and a lil misleading.

[bug]

Allow signals in debug builds

I would like to enable sending signals while in debug build. This would make for easier setup and integration.

In my current analytics tool I choose different backend ids depending on debug / release builds to separate my testing data from production data. It's not a big blocker as there are ways around, but would be great to have support for this as well.

Provide Delegate Support for default user and payload

Provide a TelemetryManagerDelegate protocol with methods that return a default user identifier string and a default payload.

Every time TelemetryManager.send is called, the method queries wether a delegate is present and what it has to say about default user and payload, mixing in first the built-in payload, then the delegates payload, then the provided payload at method call.

This way, developers do not have to provide these data over and over again, leading to repetition and errors.

Architecture reported incorrectly

Hello,

I'm using the SDK in a Mac app, and lately I've noticed:

  1. The architecture graph has automatically switched to modelName attribute.
  2. The architecture data is mixed with model name (see attached image)

Screenshot 2023-01-25 at 2 56 34 PM

This was an important data metric for me to track how many of my users were using Apple Silicon vs Intel. Is there something wrong with my query / graph configuration, or is this a bug in the SDK?

I'm using the library version 1.4.0

Store Signals Locally if no Internet Connection

This has been floated by a few people asking. Can the client store signals if the internet connection is not good enough. This has some implications:

Needs local storage

With this, we'd need to save signals to local storage (Defaults, a temp file, etc). This might be easy, but it might also be a problem if an app sends a lot of signals and fills up storage. Writes and encodes also might use up more CPU than just firing and forgetting the signal. Finally, the client should be able to run on all platforms that support Swift (iOS, macOS, watchOS, tvOS, Linux), and not all storage options might be available on all platforms.

Signals no longer transmitted live

This might need changes on the server: The time the signal arrives at the server is no longer necessarily the time the signal was generated. On the plus side, it might be possible to bundle up signals. Apps that send a lot of signals might use less energy that way, because they don't connect to the net for every signal.

Privacy Implications

I can't see no serious privacy implications in storing the locally generated signals on the user's device.

Default `newSessionBegan` signal doesn't fire when app starts

Tested on iOS 15.4, with TelemetryDeck 1.1.6 : it seems the default signal is sent only when the notification UIApplication.willEnterForegroundNotification is received, but that one doesn't seem to be posted on app startup. Is this intended behavior ?

Cannot compile version 1.1.3 on macOS

I just upgraded from version 1.0.13 -> 1.1.3, and the SDK fails to compile. I'm using this for a macOS only app, and my deployment target is macOS 10.13+.

The error is in the file Signal.swift for an availability check for the property isiOSAppOnMac

isiOSAppOnMac' is only available in macOS 11.0 or newer

Versions upto 1.1.1 work fine. The issue starts with version >= 1.1.2.

Show log when Signals not sent because DEBUG

It took a few tries to realize that launching from Xcode would not send Signals — some logging, or including that detail in the documentation would be helpful (likely that I missed it in the docs, if it’s there)

Return correct model name on macOS

Right now we only return the model name when running the Swift SDK in macOS. Instead, it should return the hardware identifier.

Using IOKit:

public func getMacModel() -> String? {
    let service = IOServiceGetMatchingService(kIOMasterPortDefault,
                                              IOServiceMatching("IOPlatformExpertDevice"))
    var modelIdentifier: String?

    if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
        if let modelIdentifierCString = String(data: modelData, encoding: .utf8)?.cString(using: .utf8) {
            modelIdentifier = String(cString: modelIdentifierCString)
        }
    }

    IOObjectRelease(service)
    return modelIdentifier
}

This will have to be gated with "if OS macOS" calls, so that the SDK still compiles for iOS and Linux.

Runtime crash in `SignalManager.checkForSignalsAndSend()`

AppStore released app is getting 6-12 crashes per day originating from TelemetryClient codebase.
The app is using TelemetryClient v1.3.0.

#0	(null) in __pthread_kill ()
#1	(null) in pthread_kill ()
#2	(null) in abort ()
#3	(null) in swift::fatalErrorv(unsigned int, char const*, char*) ()
#4	(null) in swift::fatalError(unsigned int, char const*, ...) ()
#5	(null) in swift::swift_abortRetainUnowned(void const*) ()
#6	(null) in swift_unownedRetainStrong ()
#7	0x0000000104c12620 in closure #1 in SignalManager.checkForSignalsAndSend() at [...]SwiftClient/Sources/TelemetryClient/SignalManager.swift:129
#8	(null) in thunk for @escaping @callee_guaranteed @Sendable (@guaranteed Data?, @guaranteed NSURLResponse?, @guaranteed Error?) -> () ()
#9	(null) in __40-[__NSURLSessionLocal taskForClassInfo:]_block_invoke ()
#10	(null) in __49-[__NSCFLocalSessionTask _task_onqueue_didFinish]_block_invoke_2 ()
#11	(null) in _dispatch_call_block_and_release ()
#12	(null) in _dispatch_client_callout ()
#13	(null) in _dispatch_lane_serial_drain ()
#14	(null) in _dispatch_lane_invoke ()
#15	(null) in _dispatch_workloop_worker_thread ()
#16	(null) in _pthread_wqthread ()
#17	(null) in start_wqthread ()

Can't see custom payload in viewer apps

I've got TelemetryDeck working inside my app, and I can see the signals appearing in the viewer app with the generic payload key-val pairs (locale, app version, etc).

However, I can't for the life of me find my custom payload in both the iOS and macOS viewer apps. I am calling TelemetryDeck.send("mySignalName", with: ["key": "val"]), but don't see any my payload info. I am building and running my app locally, so everything is coming in under 'Test Mode'.

Is there something I'm missing? I followed the docs to get myself to this point.

I am using the latest SDK and viewer app versions (installed about 2 hours ago).

Add limit, throttle and debounce algorithms for sending signals

In my app, I have places where users can adjust some strings such as file paths to search or ignore. I would like to track how often these fields are being changed (or if they are changed at all), but I don't want to send a signal for every key change a user enters as this would result to too many signals.

Therefore I need to implement an algorithm that limits how often I send this event. While I will probably add this to my app, I thought maybe this might be useful to add right into the client itself for others convenience. Here are 3 additions I would suggest:

  1. Limit: Add a send(String, with: [String: String], limit: Int, context: Equatable) overload where limit is the max count a signal with the same name can be sent for a given context, where the context could be a selected item, for example.
  2. Throttle: Add a send(String, with: [String: String], limit: Int, for: TimeInterval) overload where limit is the max count a signal with the same name can be sent within a given period of time, so any signals with the same name that exceed this limit are simply dropped and ignored.
  3. Debounce: Add a send(String, with: [String: String], first/last: Int, after: TimeInterval) overload where first/last is the max count of first/last signals to be sent after waiting at least the amount of time to pass since the last sending of the signal.

With these 3 I could reach my goal of only sending my event once in 3 different ways depending on what I need in a given context. Of course, instead of overloading the send method, we could also introduce a wrapper type and go for a more function chained API with func limit(count: 1, context: Equatable) -> Self, func throttle(count: Int = 1, for: TimeInterval) -> Self, and func debounce(count: Int = 1, after: TimeInterval).

I would be up to implement this based on which API design you prefer and which of these 3 you would accept.

Mac Catalyst apps running under TestFlight are reporting as production

Mac Catalyst apps that are running via TestFlight aren’t reporting as being TestFlight.

Payload looks like:
isAppStore = true
isDebug = false
isSimulator = false
isTestFlight = false
operatingSystem = iOS (even though it’s on my Mac - probably just a Catalyst thing)
platform = macCatalyst
targetEnvironment = macCatalyst
systemVersion = macCatalyst 15.0

Wait For Connectivity Before Attempting Send

I was just looking over the code and noticed it doesn't check whether the network is up before sending events.

It might be a good idea to configure the session with this property so, instead of blindly sending the request and it failing and then sticking it back in the queue to try again next time, it automatically sends them when the network becomes available.

https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/2908812-waitsforconnectivity

Readme references SwiftClientTester project that does not exist

At the bottom of the Readme ( under Developing this SDK )

## Developing this SDK

Your PRs on TelemetryDeck's Swift Client are very much welcome. Check out the [SwiftClientTester](https://github.com/TelemetryDeck/SwiftClientTester) project, which provides a harness you can use to work on the library and try out new things.

The link SwiftClientTester points to a non existing project https://github.com/TelemetryDeck/SwiftClientTester

Expectation:
The link SwiftClientTester should redirect appropriately

Question: How to disable if users opts out of analytics in SwiftUI

I'm releasing an application soon, I just noticed there's not the ability to disable since we're putting the initialization inside my App's init() in SwiftUI. I'd like to have an option for the user to disable if they so chose in my apps settings.

init() {
    // MARK: ERROR Accessing StateObject's object without being installed on a View. This will create a new instance each time.
    /*
    if viewmodel.enabled {
        
    } else {
        
    }
     */
    // MARK: - Analytics
    let configuration = TelemetryManagerConfiguration(appID: "ID_HERE")
    TelemetryManager.initialize(with: configuration)
    TelemetryManager.send("appLaunchedRegularly")
}

[iPhone] Can't create insight

When opening an insight group on an iPhone, there's no button to add a new insight

IMG_A201D2284669-1

device: iOS 14.2, iPhone 11 Pro latest version from TestFlight

XCPreviewAgent crashed due to fatalError in TelemetryClient.swift at line 160

When working on my macOS application, which has some TelemetryDeck analytics events being sent on the TCA reducer layer, I received this error while trying to edit a SwiftUI view and tried to see the previews. Unfortunately, previews seem not to work due to TelemetryManage.initialize(...) not being called.

Given how common SwiftUI is and will become in the future, I think there should be a built-in solution that prevents this from happening. My SwiftUI previews are already all in an if DEBUG, so if this error didn't happen on debug builds for example, this would help. Or even better, maybe there's a way to detect when run in a preview, this could be auto-detected and some mocked version could be initialized.

Here's what I see:
Screen Shot 2022-08-29 at 19 49 18

Note that my app is modularized using this approach, therefore I don't need to build the top-level App to see the preview of a component (this is by design for improved build performance). This might be why it's not happening in all projects.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.