SessionStartEvent.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. //
  2. // Copyright 2022 Google LLC
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. import Foundation
  16. @_implementationOnly import GoogleDataTransport
  17. @_implementationOnly import GoogleUtilities
  18. ///
  19. /// SessionStartEvent is responsible for:
  20. /// 1) Writing fields to the Session proto
  21. /// 2) Synthesizing itself for persisting to disk and logging to GoogleDataTransport
  22. ///
  23. class SessionStartEvent: NSObject, GDTCOREventDataObject {
  24. var proto: firebase_appquality_sessions_SessionEvent
  25. init(identifiers: IdentifierProvider, appInfo: ApplicationInfoProtocol,
  26. time: TimeProvider = Time()) {
  27. proto = firebase_appquality_sessions_SessionEvent()
  28. super.init()
  29. proto.event_type = firebase_appquality_sessions_EventType_SESSION_START
  30. proto.session_data.session_id = makeProtoString(identifiers.sessionID)
  31. proto.session_data.previous_session_id = makeProtoStringOrNil(identifiers.previousSessionID)
  32. proto.session_data.event_timestamp_us = time.timestampUS
  33. proto.application_info.app_id = makeProtoString(appInfo.appID)
  34. proto.application_info.session_sdk_version = makeProtoString(appInfo.sdkVersion)
  35. proto.application_info.log_environment = convertLogEnvironment(environment: appInfo.environment)
  36. proto.application_info.device_model = makeProtoString(appInfo.deviceModel)
  37. // proto.application_info.development_platform_name;
  38. // proto.application_info.development_platform_version;
  39. // `which_platform_info` tells nanopb which oneof we're choosing to fill in for our proto
  40. proto.application_info.which_platform_info = FIRSESGetAppleApplicationInfoTag()
  41. proto.application_info.apple_app_info.bundle_short_version = makeProtoString(appInfo.bundleID)
  42. proto.application_info.apple_app_info.network_connection_info
  43. .network_type = convertNetworkType(networkType: appInfo.networkInfo.networkType)
  44. proto.application_info.apple_app_info.network_connection_info
  45. .mobile_subtype = convertMobileSubtype(mobileSubtype: appInfo.networkInfo.mobileSubtype)
  46. proto.application_info.apple_app_info.os_name = convertOSName(osName: appInfo.osName)
  47. proto.application_info.apple_app_info.mcc_mnc = makeProtoString(appInfo.mccMNC)
  48. proto.session_data.data_collection_status
  49. .crashlytics = firebase_appquality_sessions_DataCollectionState_COLLECTION_UNKNOWN
  50. proto.session_data.data_collection_status
  51. .performance = firebase_appquality_sessions_DataCollectionState_COLLECTION_UNKNOWN
  52. }
  53. func setInstallationID(installationId: String) {
  54. proto.session_data.firebase_installation_id = makeProtoString(installationId)
  55. }
  56. func setSamplingRate(samplingRate: Double) {
  57. proto.session_data.data_collection_status.session_sampling_rate = samplingRate
  58. }
  59. // MARK: - GDTCOREventDataObject
  60. func transportBytes() -> Data {
  61. var fields = firebase_appquality_sessions_SessionEvent_fields
  62. var error: NSError?
  63. let data = FIRSESEncodeProto(&fields.0, &proto, &error)
  64. if error != nil {
  65. Logger.logError(error.debugDescription)
  66. }
  67. guard let data = data else {
  68. Logger.logError("Session event generated nil transportBytes. Returning empty data.")
  69. return Data()
  70. }
  71. return data
  72. }
  73. // MARK: - Data Conversion
  74. private func makeProtoStringOrNil(_ string: String?) -> UnsafeMutablePointer<pb_bytes_array_t>? {
  75. guard let string = string else {
  76. return nil
  77. }
  78. return FIRSESEncodeString(string)
  79. }
  80. private func makeProtoString(_ string: String) -> UnsafeMutablePointer<pb_bytes_array_t>? {
  81. return FIRSESEncodeString(string)
  82. }
  83. private func convertOSName(osName: String) -> firebase_appquality_sessions_OsName {
  84. switch osName.lowercased() {
  85. case "macos":
  86. return firebase_appquality_sessions_OsName_MACOS
  87. case "maccatalyst":
  88. return firebase_appquality_sessions_OsName_MACCATALYST
  89. case "ios_on_mac":
  90. return firebase_appquality_sessions_OsName_IOS_ON_MAC
  91. case "ios":
  92. return firebase_appquality_sessions_OsName_IOS
  93. case "tvos":
  94. return firebase_appquality_sessions_OsName_TVOS
  95. case "watchos":
  96. return firebase_appquality_sessions_OsName_WATCHOS
  97. case "ipados":
  98. return firebase_appquality_sessions_OsName_IPADOS
  99. default:
  100. Logger.logWarning("Found unknown OSName: \"\(osName)\" while converting.")
  101. return firebase_appquality_sessions_OsName_UNKNOWN_OSNAME
  102. }
  103. }
  104. /// Encodes the proto in this SessionStartEvent to Data, and then decodes the Data back into
  105. /// the proto object and returns the decoded proto. This is used for validating encoding works
  106. /// and should not be used in production code.
  107. func encodeDecodeEvent() -> firebase_appquality_sessions_SessionEvent {
  108. let transportBytes = self.transportBytes()
  109. var proto = firebase_appquality_sessions_SessionEvent()
  110. var fields = firebase_appquality_sessions_SessionEvent_fields
  111. let bytes = (transportBytes as NSData).bytes
  112. var istream: pb_istream_t = pb_istream_from_buffer(bytes, transportBytes.count)
  113. if !pb_decode(&istream, &fields.0, &proto) {
  114. let errorMessage = FIRSESPBGetError(istream)
  115. if errorMessage.count > 0 {
  116. Logger.logInfo("Failed to decode transportBytes: \(errorMessage)")
  117. }
  118. }
  119. return proto
  120. }
  121. /// Converts the provided log environment to its Proto format.
  122. private func convertLogEnvironment(environment: DevEnvironment)
  123. -> firebase_appquality_sessions_LogEnvironment {
  124. switch environment {
  125. case .prod:
  126. return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_PROD
  127. case .staging:
  128. return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_STAGING
  129. case .autopush:
  130. return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_AUTOPUSH
  131. }
  132. }
  133. private func convertNetworkType(networkType: GULNetworkType)
  134. -> firebase_appquality_sessions_NetworkConnectionInfo_NetworkType {
  135. switch networkType {
  136. case .WIFI:
  137. return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_WIFI
  138. case .mobile:
  139. return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_MOBILE
  140. case .none:
  141. return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_DUMMY
  142. @unknown default:
  143. return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_DUMMY
  144. }
  145. }
  146. private func convertMobileSubtype(mobileSubtype: String)
  147. -> firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype {
  148. var subtype: firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype
  149. #if os(iOS) && !targetEnvironment(macCatalyst)
  150. switch mobileSubtype {
  151. case CTRadioAccessTechnologyGPRS:
  152. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_GPRS
  153. case CTRadioAccessTechnologyEdge:
  154. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EDGE
  155. case CTRadioAccessTechnologyWCDMA:
  156. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_CDMA
  157. case CTRadioAccessTechnologyCDMA1x:
  158. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_CDMA
  159. case CTRadioAccessTechnologyHSDPA:
  160. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_HSDPA
  161. case CTRadioAccessTechnologyHSUPA:
  162. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_HSUPA
  163. case CTRadioAccessTechnologyCDMAEVDORev0:
  164. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_0
  165. case CTRadioAccessTechnologyCDMAEVDORevA:
  166. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_A
  167. case CTRadioAccessTechnologyCDMAEVDORevB:
  168. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_B
  169. case CTRadioAccessTechnologyeHRPD:
  170. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EHRPD
  171. case CTRadioAccessTechnologyLTE:
  172. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_LTE
  173. default:
  174. subtype =
  175. firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE
  176. }
  177. if #available(iOS 14.1, *) {
  178. if mobileSubtype == CTRadioAccessTechnologyNRNSA || mobileSubtype ==
  179. CTRadioAccessTechnologyNR {
  180. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_NR
  181. }
  182. }
  183. #else
  184. subtype =
  185. firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE
  186. #endif
  187. return subtype
  188. }
  189. }