aws-appsync-realtime-client.../AppSyncRTCSample/AppSyncRTCProvider.swift

225 lines
6.9 KiB
Swift

//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import os.log
import Foundation
import AppSyncRealTimeClient
class AppSyncRTCProvider: ObservableObject {
private static var instance: AppSyncRTCProvider?
public static var `default`: AppSyncRTCProvider {
guard let existingInstance = instance else {
// If we can't initialize the provider, we can't do anything meaningful in the
// app. A crash is appropriate here.
// swiftlint:disable:next force_try
let newInstance = try! AppSyncRTCProvider()
instance = newInstance
return newInstance
}
return existingInstance
}
@Published var connectionState: ConnectionState
@Published var events: [SubscriptionItemEvent]
@Published var lastData: String?
@Published var lastError: Error?
private let url: URL
private let apiKey: String
private let connectionProvider: ConnectionProvider
private let requestString = """
subscription onCreate {
onCreateTodo{
id
description
name
}
}
"""
private var subscriptionItem: SubscriptionItem?
private var subscriptionConnection: SubscriptionConnection?
init() throws {
os_log(#function, log: .subscription, type: .debug)
let json = try AppSyncRTCProvider.retrieveConfigurationJSON()
guard let data = json as? [String: Any],
let apiCategoryConfig = data["api"] as? [String: Any],
let plugins = apiCategoryConfig["plugins"] as? [String: Any],
let awsAPIPlugin = plugins["awsAPIPlugin"] as? [String: Any],
let pluginConfig = awsAPIPlugin.first?.value as? [String: Any],
let endpoint = pluginConfig["endpoint"] as? String,
let apiKey = pluginConfig["apiKey"] as? String,
let url = URL(string: endpoint)
else {
os_log(#function, log: .subscription, type: .fault)
throw "Could not retrieve endpoint configuration from amplifyconfiguration.json"
}
self.url = url
self.apiKey = apiKey
let authInterceptor = AppSyncRTCProvider.makeAuthInterceptor(for: apiKey)
self.connectionProvider = AppSyncRTCProvider.makeConnectionProvider(
for: url,
authInterceptor: authInterceptor
)
self.connectionState = .notConnected
self.events = []
self.lastData = nil
self.lastError = nil
}
static func retrieveConfigurationJSON() throws -> Any {
guard let path = Bundle.main.path(forResource: "amplifyconfiguration", ofType: "json") else {
throw "Could not retrieve configuration file `amplifyconfiguration.json`"
}
let url = URL(fileURLWithPath: path)
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data)
return json
}
func handleEvent(event: SubscriptionItemEvent) {
events.append(event)
switch event {
case .connection(let connectionEvent):
switch connectionEvent {
case .connected:
connectionState = .connected
case .connecting:
connectionState = .inProgress
case .disconnected:
connectionState = .notConnected
}
case .failed(let error):
OSLog.subscription.log(
"event is error:\(error)",
log: .subscription,
type: .error
)
AppSyncSubscriptionConnection.logExtendedErrorInfo(for: error)
lastError = error
case .data(let data):
OSLog.subscription.log(
"event is data:\(data)",
log: .subscription,
type: .debug
)
lastData = String(data: data, encoding: .utf8)
}
}
func subscribe() {
os_log(#function, log: .subscription, type: .debug)
if subscriptionConnection != nil, subscriptionItem != nil {
unsubscribe()
}
let subscriptionConnection = AppSyncRTCProvider
.makeSubscriptionConnection(using: connectionProvider)
self.subscriptionConnection = subscriptionConnection
DispatchQueue.global().async {
self.subscriptionItem = subscriptionConnection.subscribe(
requestString: self.requestString,
variables: nil
) { event, item in
OSLog.subscription.log(
"received event:\(event), item:\(item)",
log: .subscription,
type: .debug
)
DispatchQueue.main.async {
self.handleEvent(event: event)
}
}
}
}
func unsubscribe() {
os_log(#function)
guard
let subscriptionConnection = subscriptionConnection,
let subscriptionItem = subscriptionItem
else {
return
}
OSLog.subscription.log(
"unsubscribe: connection=\(subscriptionConnection); subscriptionItem=\(subscriptionItem)",
log: .subscription,
type: .debug
)
subscriptionConnection.unsubscribe(item: subscriptionItem)
self.subscriptionConnection = nil
self.subscriptionItem = nil
}
func disconnect() {
connectionProvider.disconnect()
}
private static func makeSubscriptionConnection(
using connectionProvider: ConnectionProvider
) -> SubscriptionConnection {
os_log(#function)
return AppSyncSubscriptionConnection(provider: connectionProvider)
}
private static func makeConnectionProvider(
for url: URL,
authInterceptor: AuthInterceptor
) -> ConnectionProvider {
os_log(#function)
return ConnectionProviderFactory.createConnectionProvider(
for: url,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
}
private static func makeAuthInterceptor(for apiKey: String) -> AuthInterceptor {
os_log(#function)
return APIKeyAuthInterceptor(apiKey)
}
}
extension String: Error { }
extension ConnectionState: CustomStringConvertible {
public var description: String {
switch self {
case .connected: return "connected"
case .inProgress: return "inProgress"
case .notConnected: return "notConnected"
}
}
}
extension OSLog {
private static var subsystem = Bundle.main.bundleIdentifier!
/// Logs the view cycles like viewDidLoad.
static let subscription = OSLog(subsystem: subsystem, category: "subscription")
func log(_ message: String, log: OSLog = .default, type: OSLogType = .default) {
os_log("%@", log: log, type: type, message)
}
}