DataConnect.swift 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. // Copyright 2024 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. import FirebaseAppCheck
  16. import FirebaseAuth
  17. import FirebaseCore
  18. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  19. public class DataConnect {
  20. private var connectorConfig: ConnectorConfig
  21. private var app: FirebaseApp
  22. private var settings: DataConnectSettings
  23. private var grpcClient: GrpcClient
  24. private var operationsManager: OperationsManager
  25. private static var instanceStore = InstanceStore()
  26. public enum EmulatorDefaults {
  27. public static let host = "127.0.0.1"
  28. public static let port = 9399
  29. }
  30. // MARK: Static Creation
  31. public class func dataConnect(app: FirebaseApp? = FirebaseApp.app(),
  32. connectorConfig: ConnectorConfig,
  33. settings: DataConnectSettings = DataConnectSettings())
  34. -> DataConnect {
  35. guard let app = app else {
  36. fatalError("No Firebase App present")
  37. }
  38. return instanceStore.instance(for: app, config: connectorConfig, settings: settings)
  39. }
  40. // MARK: Emulator
  41. public func useEmulator(host: String = EmulatorDefaults.host,
  42. port: Int = EmulatorDefaults.port) {
  43. settings = DataConnectSettings(host: host, port: port, sslEnabled: false)
  44. // TODO: - shutdown grpc client
  45. // self.grpcClient.close
  46. // self.operations.close
  47. guard let projectID = app.options.projectID else {
  48. fatalError("Firebase DataConnect requires the projectID to be set in the app options")
  49. }
  50. grpcClient = GrpcClient(
  51. projectId: projectID,
  52. settings: settings,
  53. connectorConfig: connectorConfig,
  54. auth: Auth.auth(app: app),
  55. appCheck: AppCheck.appCheck(app: app)
  56. )
  57. operationsManager = OperationsManager(grpcClient: grpcClient)
  58. }
  59. // MARK: Init
  60. init(app: FirebaseApp, connectorConfig: ConnectorConfig, settings: DataConnectSettings) {
  61. self.app = app
  62. self.settings = settings
  63. self.connectorConfig = connectorConfig
  64. guard let projectID = app.options.projectID else {
  65. fatalError("Firebase DataConnect requires the projectID to be set in the app options")
  66. }
  67. grpcClient = GrpcClient(
  68. projectId: projectID,
  69. settings: settings,
  70. connectorConfig: connectorConfig,
  71. auth: Auth.auth(app: app),
  72. appCheck: AppCheck.appCheck(app: app)
  73. )
  74. operationsManager = OperationsManager(grpcClient: grpcClient)
  75. }
  76. // MARK: Operations
  77. public func query<ResultData: Decodable,
  78. Variable: OperationVariable>(request: QueryRequest<Variable>,
  79. resultsDataType: ResultData
  80. .Type,
  81. publisher: ResultsPublisherType = .observableObject)
  82. -> any ObservableQueryRef {
  83. return operationsManager.queryRef(for: request, with: resultsDataType, publisher: publisher)
  84. }
  85. public func mutation<ResultData: Decodable,
  86. Variable: OperationVariable>(request: MutationRequest<Variable>,
  87. resultsDataType: ResultData
  88. .Type)
  89. -> MutationRef<ResultData,
  90. Variable> {
  91. return operationsManager.mutationRef(for: request, with: resultsDataType)
  92. }
  93. }
  94. // MARK: Instance Creation
  95. // Support for creating or reusing DataConnect instances.
  96. // Instances are keyed by ConnectorConfig and FirebaseApp (projectID)
  97. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  98. private struct InstanceKey: Hashable, Equatable {
  99. let config: ConnectorConfig
  100. let app: FirebaseApp
  101. init(app: FirebaseApp, config: ConnectorConfig) {
  102. self.app = app
  103. self.config = config
  104. }
  105. func hash(into hasher: inout Hasher) {
  106. hasher.combine(app.name)
  107. hasher.combine(app.options.projectID)
  108. hasher.combine(config)
  109. }
  110. }
  111. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  112. private class InstanceStore {
  113. let accessQ = DispatchQueue(
  114. label: "firebase.dataconnect.instanceQ",
  115. autoreleaseFrequency: .workItem
  116. )
  117. var instances = [InstanceKey: DataConnect]()
  118. func instance(for app: FirebaseApp, config: ConnectorConfig,
  119. settings: DataConnectSettings) -> DataConnect {
  120. accessQ.sync {
  121. let key = InstanceKey(app: app, config: config)
  122. if let inst = instances[key] {
  123. return inst
  124. } else {
  125. let dc = DataConnect(app: app, connectorConfig: config, settings: settings)
  126. instances[key] = dc
  127. return dc
  128. }
  129. }
  130. }
  131. }