SessionStartEvent.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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
  42. .bundle_short_version = makeProtoString(appInfo.appDisplayVersion)
  43. proto.application_info.apple_app_info.network_connection_info
  44. .network_type = convertNetworkType(networkType: appInfo.networkInfo.networkType)
  45. proto.application_info.apple_app_info.network_connection_info
  46. .mobile_subtype = convertMobileSubtype(mobileSubtype: appInfo.networkInfo.mobileSubtype)
  47. proto.application_info.apple_app_info.os_name = convertOSName(osName: appInfo.osName)
  48. proto.application_info.apple_app_info.mcc_mnc = makeProtoString(appInfo.mccMNC)
  49. proto.session_data.data_collection_status
  50. .crashlytics = firebase_appquality_sessions_DataCollectionState_COLLECTION_UNKNOWN
  51. proto.session_data.data_collection_status
  52. .performance = firebase_appquality_sessions_DataCollectionState_COLLECTION_UNKNOWN
  53. }
  54. func setInstallationID(installationId: String) {
  55. proto.session_data.firebase_installation_id = makeProtoString(installationId)
  56. }
  57. func setSamplingRate(samplingRate: Double) {
  58. proto.session_data.data_collection_status.session_sampling_rate = samplingRate
  59. }
  60. // MARK: - GDTCOREventDataObject
  61. func transportBytes() -> Data {
  62. var fields = firebase_appquality_sessions_SessionEvent_fields
  63. var error: NSError?
  64. let data = FIRSESEncodeProto(&fields.0, &proto, &error)
  65. if error != nil {
  66. Logger.logError(error.debugDescription)
  67. }
  68. guard let data = data else {
  69. Logger.logError("Session event generated nil transportBytes. Returning empty data.")
  70. return Data()
  71. }
  72. return data
  73. }
  74. // MARK: - Data Conversion
  75. private func makeProtoStringOrNil(_ string: String?) -> UnsafeMutablePointer<pb_bytes_array_t>? {
  76. guard let string = string else {
  77. return nil
  78. }
  79. return FIRSESEncodeString(string)
  80. }
  81. private func makeProtoString(_ string: String) -> UnsafeMutablePointer<pb_bytes_array_t>? {
  82. return FIRSESEncodeString(string)
  83. }
  84. private func convertOSName(osName: String) -> firebase_appquality_sessions_OsName {
  85. switch osName.lowercased() {
  86. case "macos":
  87. return firebase_appquality_sessions_OsName_MACOS
  88. case "maccatalyst":
  89. return firebase_appquality_sessions_OsName_MACCATALYST
  90. case "ios_on_mac":
  91. return firebase_appquality_sessions_OsName_IOS_ON_MAC
  92. case "ios":
  93. return firebase_appquality_sessions_OsName_IOS
  94. case "tvos":
  95. return firebase_appquality_sessions_OsName_TVOS
  96. case "watchos":
  97. return firebase_appquality_sessions_OsName_WATCHOS
  98. case "ipados":
  99. return firebase_appquality_sessions_OsName_IPADOS
  100. default:
  101. Logger.logWarning("Found unknown OSName: \"\(osName)\" while converting.")
  102. return firebase_appquality_sessions_OsName_UNKNOWN_OSNAME
  103. }
  104. }
  105. /// Encodes the proto in this SessionStartEvent to Data, and then decodes the Data back into
  106. /// the proto object and returns the decoded proto. This is used for validating encoding works
  107. /// and should not be used in production code.
  108. func encodeDecodeEvent() -> firebase_appquality_sessions_SessionEvent {
  109. let transportBytes = self.transportBytes()
  110. var proto = firebase_appquality_sessions_SessionEvent()
  111. var fields = firebase_appquality_sessions_SessionEvent_fields
  112. let bytes = (transportBytes as NSData).bytes
  113. var istream: pb_istream_t = pb_istream_from_buffer(bytes, transportBytes.count)
  114. if !pb_decode(&istream, &fields.0, &proto) {
  115. let errorMessage = FIRSESPBGetError(istream)
  116. if errorMessage.count > 0 {
  117. Logger.logInfo("Failed to decode transportBytes: \(errorMessage)")
  118. }
  119. }
  120. return proto
  121. }
  122. /// Converts the provided log environment to its Proto format.
  123. private func convertLogEnvironment(environment: DevEnvironment)
  124. -> firebase_appquality_sessions_LogEnvironment {
  125. switch environment {
  126. case .prod:
  127. return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_PROD
  128. case .staging:
  129. return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_STAGING
  130. case .autopush:
  131. return firebase_appquality_sessions_LogEnvironment_LOG_ENVIRONMENT_AUTOPUSH
  132. }
  133. }
  134. private func convertNetworkType(networkType: GULNetworkType)
  135. -> firebase_appquality_sessions_NetworkConnectionInfo_NetworkType {
  136. switch networkType {
  137. case .WIFI:
  138. return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_WIFI
  139. case .mobile:
  140. return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_MOBILE
  141. case .none:
  142. return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_DUMMY
  143. @unknown default:
  144. return firebase_appquality_sessions_NetworkConnectionInfo_NetworkType_DUMMY
  145. }
  146. }
  147. private func convertMobileSubtype(mobileSubtype: String)
  148. -> firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype {
  149. var subtype: firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype
  150. #if os(iOS) && !targetEnvironment(macCatalyst)
  151. switch mobileSubtype {
  152. case CTRadioAccessTechnologyGPRS:
  153. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_GPRS
  154. case CTRadioAccessTechnologyEdge:
  155. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EDGE
  156. case CTRadioAccessTechnologyWCDMA:
  157. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_CDMA
  158. case CTRadioAccessTechnologyCDMA1x:
  159. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_CDMA
  160. case CTRadioAccessTechnologyHSDPA:
  161. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_HSDPA
  162. case CTRadioAccessTechnologyHSUPA:
  163. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_HSUPA
  164. case CTRadioAccessTechnologyCDMAEVDORev0:
  165. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_0
  166. case CTRadioAccessTechnologyCDMAEVDORevA:
  167. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_A
  168. case CTRadioAccessTechnologyCDMAEVDORevB:
  169. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EVDO_B
  170. case CTRadioAccessTechnologyeHRPD:
  171. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_EHRPD
  172. case CTRadioAccessTechnologyLTE:
  173. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_LTE
  174. default:
  175. subtype =
  176. firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE
  177. }
  178. if #available(iOS 14.1, *) {
  179. if mobileSubtype == CTRadioAccessTechnologyNRNSA || mobileSubtype ==
  180. CTRadioAccessTechnologyNR {
  181. subtype = firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_NR
  182. }
  183. }
  184. #else
  185. subtype =
  186. firebase_appquality_sessions_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE
  187. #endif
  188. return subtype
  189. }
  190. }