| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- //
- // Copyright 2022 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- import Foundation
- @_implementationOnly import GoogleDataTransport
- #if SWIFT_PACKAGE
- import FirebaseSessionsObjC
- #endif // SWIFT_PACKAGE
- #if SWIFT_PACKAGE
- @_implementationOnly import GoogleUtilities_Environment
- #else
- @_implementationOnly import GoogleUtilities
- #endif // SWIFT_PACKAGE
- ///
- /// SessionStartEvent is responsible for:
- /// 1) Writing fields to the Session proto
- /// 2) Synthesizing itself for persisting to disk and logging to GoogleDataTransport
- ///
- class SessionStartEvent: NSObject, GDTCOREventDataObject {
- var proto: firebase_appquality_sessions_SessionEvent
- init(sessionInfo: SessionInfo, appInfo: ApplicationInfoProtocol,
- time: TimeProvider = Time()) {
- proto = firebase_appquality_sessions_SessionEvent()
- super.init()
- proto.event_type = firebase_appquality_sessions_EventType_SESSION_START
- proto.session_data.session_id = makeProtoString(sessionInfo.sessionId)
- proto.session_data.first_session_id = makeProtoString(sessionInfo.firstSessionId)
- proto.session_data.session_index = sessionInfo.sessionIndex
- proto.session_data.event_timestamp_us = time.timestampUS
- proto.application_info.app_id = makeProtoString(appInfo.appID)
- proto.application_info.session_sdk_version = makeProtoString(appInfo.sdkVersion)
- proto.application_info.os_version = makeProtoString(appInfo.osDisplayVersion)
- proto.application_info.log_environment = convertLogEnvironment(environment: appInfo.environment)
- proto.application_info.device_model = makeProtoString(appInfo.deviceModel)
- // proto.application_info.development_platform_name;
- // proto.application_info.development_platform_version;
- // `which_platform_info` tells nanopb which oneof we're choosing to fill in for our proto
- proto.application_info.which_platform_info = FIRSESGetAppleApplicationInfoTag()
- proto.application_info.apple_app_info
- .bundle_short_version = makeProtoString(appInfo.appDisplayVersion)
- proto.application_info.apple_app_info
- .app_build_version = makeProtoString(appInfo.appBuildVersion)
- proto.application_info.apple_app_info.os_name = convertOSName(osName: appInfo.osName)
- // Set network info to base values but don't fill them in with the real
- // value because these are only tracked when Performance is installed
- proto.application_info.apple_app_info.mcc_mnc = makeProtoString("")
- proto.application_info.apple_app_info.network_connection_info
- .network_type = convertNetworkType(networkType: .none)
- proto.application_info.apple_app_info.network_connection_info
- .mobile_subtype = convertMobileSubtype(mobileSubtype: "")
- proto.session_data.data_collection_status
- .crashlytics = firebase_appquality_sessions_DataCollectionState_COLLECTION_SDK_NOT_INSTALLED
- proto.session_data.data_collection_status
- .performance = firebase_appquality_sessions_DataCollectionState_COLLECTION_SDK_NOT_INSTALLED
- }
- deinit {
- let garbage: [UnsafeMutablePointer<pb_bytes_array_t>?] = [
- proto.session_data.session_id,
- proto.session_data.first_session_id,
- proto.session_data.firebase_installation_id,
- proto.application_info.app_id,
- proto.application_info.session_sdk_version,
- proto.application_info.device_model,
- proto.application_info.development_platform_name,
- proto.application_info.development_platform_version,
- proto.application_info.apple_app_info.bundle_short_version,
- proto.application_info.apple_app_info.mcc_mnc,
- ]
- for pointer in garbage {
- nanopb_free(pointer)
- }
- }
- func setInstallationID(installationId: String) {
- let oldID = proto.session_data.firebase_installation_id
- proto.session_data.firebase_installation_id = makeProtoString(installationId)
- nanopb_free(oldID)
- }
- func setSamplingRate(samplingRate: Double) {
- proto.session_data.data_collection_status.session_sampling_rate = samplingRate
- }
- func set(subscriber: SessionsSubscriberName, isDataCollectionEnabled: Bool,
- appInfo: ApplicationInfoProtocol) {
- let dataCollectionState = makeDataCollectionProto(isDataCollectionEnabled)
- switch subscriber {
- case .Crashlytics:
- proto.session_data.data_collection_status.crashlytics = dataCollectionState
- case .Performance:
- proto.session_data.data_collection_status.performance = dataCollectionState
- default:
- Logger
- .logWarning("Attempted to set Data Collection status for unknown Subscriber: \(subscriber)")
- }
- // Only set restricted fields if Data Collection is enabled. If it's disabled,
- // we're treating that as if the product isn't installed.
- if isDataCollectionEnabled {
- setRestrictedFields(subscriber: subscriber,
- appInfo: appInfo)
- }
- }
- /// This method should be called for every subscribed Subscriber. This is for cases where
- /// fields should only be collected if a specific SDK is installed.
- private func setRestrictedFields(subscriber: SessionsSubscriberName,
- appInfo: ApplicationInfoProtocol) {
- switch subscriber {
- case .Performance:
- let oldString = proto.application_info.apple_app_info.mcc_mnc
- proto.application_info.apple_app_info.mcc_mnc = makeProtoString("")
- nanopb_free(oldString)
- proto.application_info.apple_app_info.network_connection_info
- .network_type = convertNetworkType(networkType: appInfo.networkInfo.networkType)
- proto.application_info.apple_app_info.network_connection_info
- .mobile_subtype = convertMobileSubtype(mobileSubtype: appInfo.networkInfo.mobileSubtype)
- default:
- break
- }
- }
- // MARK: - GDTCOREventDataObject
- func transportBytes() -> Data {
- var fields = firebase_appquality_sessions_SessionEvent_fields
- var error: NSError?
- let data = FIRSESEncodeProto(&fields.0, &proto, &error)
- if error != nil {
- Logger
- .logError("Session Event failed to encode as proto with error: \(error.debugDescription)")
- }
- guard let data = data else {
- Logger.logError("Session Event generated nil transportBytes. Returning empty data.")
- return Data()
- }
- return data
- }
- // MARK: - Data Conversion
- func makeDataCollectionProto(_ isDataCollectionEnabled: Bool)
- -> firebase_appquality_sessions_DataCollectionState {
- if isDataCollectionEnabled {
- return firebase_appquality_sessions_DataCollectionState_COLLECTION_ENABLED
- } else {
- return firebase_appquality_sessions_DataCollectionState_COLLECTION_DISABLED
- }
- }
- private func makeProtoStringOrNil(_ string: String?) -> UnsafeMutablePointer<pb_bytes_array_t>? {
- guard let string = string else {
- return nil
- }
- return FIRSESEncodeString(string)
- }
- private func makeProtoString(_ string: String) -> UnsafeMutablePointer<pb_bytes_array_t>? {
- return FIRSESEncodeString(string)
- }
- private func convertOSName(osName: String) -> firebase_appquality_sessions_OsName {
- switch osName.lowercased() {
- case "macos":
- return firebase_appquality_sessions_OsName_MACOS
- case "maccatalyst":
- return firebase_appquality_sessions_OsName_MACCATALYST
- case "ios_on_mac":
- return firebase_appquality_sessions_OsName_IOS_ON_MAC
- case "ios":
- return firebase_appquality_sessions_OsName_IOS
- case "tvos":
- return firebase_appquality_sessions_OsName_TVOS
- case "watchos":
- return firebase_appquality_sessions_OsName_WATCHOS
- case "ipados":
- return firebase_appquality_sessions_OsName_IPADOS
- default:
- Logger.logWarning("Found unknown OSName: \"\(osName)\" while converting.")
- return firebase_appquality_sessions_OsName_UNKNOWN_OSNAME
- }
- }
- /// Encodes the proto in this SessionStartEvent to Data, and then decodes the Data back into
- /// the proto object and returns the decoded proto. This is used for validating encoding works
- /// and should not be used in production code.
- func encodeDecodeEvent() -> firebase_appquality_sessions_SessionEvent {
- let transportBytes = self.transportBytes()
- var proto = firebase_appquality_sessions_SessionEvent()
- var fields = firebase_appquality_sessions_SessionEvent_fields
- let bytes = (transportBytes as NSData).bytes
- var istream: pb_istream_t = pb_istream_from_buffer(bytes, transportBytes.count)
- if !pb_decode(&istream, &fields.0, &proto) {
- let errorMessage = FIRSESPBGetError(istream)
- if errorMessage.count > 0 {
- Logger.logError("Session Event failed to decode transportBytes: \(errorMessage)")
- }
- }
- return proto
- }
- /// Converts the provided log environment to its Proto format.
- private func convertLogEnvironment(environment: DevEnvironment)
- -> firebase_appquality_sessions_LogEnvironment {
- switch environment {
- case .prod:
- return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_PROD
- case .staging:
- return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_STAGING
- case .autopush:
- return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_AUTOPUSH
- }
- }
- private func convertNetworkType(networkType: GULNetworkType)
- -> firebase_appquality_sessions_NetworkConnectionInfo_NetworkType {
- switch networkType {
- case .WIFI:
- return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_WIFI
- case .mobile:
- return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_MOBILE
- case .none:
- return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_DUMMY
- @unknown default:
- return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_DUMMY
- }
- }
- private func convertMobileSubtype(mobileSubtype: String)
- -> firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype {
- var subtype: firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype
- #if os(iOS) && !targetEnvironment(macCatalyst)
- switch mobileSubtype {
- case CTRadioAccessTechnologyGPRS:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_GPRS
- case CTRadioAccessTechnologyEdge:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EDGE
- case CTRadioAccessTechnologyWCDMA:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_CDMA
- case CTRadioAccessTechnologyCDMA1x:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_CDMA
- case CTRadioAccessTechnologyHSDPA:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_HSDPA
- case CTRadioAccessTechnologyHSUPA:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_HSUPA
- case CTRadioAccessTechnologyCDMAEVDORev0:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_0
- case CTRadioAccessTechnologyCDMAEVDORevA:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_A
- case CTRadioAccessTechnologyCDMAEVDORevB:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_B
- case CTRadioAccessTechnologyeHRPD:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EHRPD
- case CTRadioAccessTechnologyLTE:
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_LTE
- default:
- subtype =
- firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE
- }
- if #available(iOS 14.1, *) {
- if mobileSubtype == CTRadioAccessTechnologyNRNSA || mobileSubtype ==
- CTRadioAccessTechnologyNR {
- subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_NR
- }
- }
- #else // os(iOS) && !targetEnvironment(macCatalyst)
- subtype =
- firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE
- #endif // os(iOS) && !targetEnvironment(macCatalyst)
- return subtype
- }
- }
|