Skip to content

Commit 39a50b6

Browse files
authored
chore: kickoff release
2 parents 88e871f + 459aaed commit 39a50b6

File tree

63 files changed

+585
-125
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+585
-125
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/AWSCloudWatchLoggingPlugin.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
33
LastUpgradeVersion = "1430"
4-
version = "1.7">
4+
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"
77
buildImplicitDependencies = "YES">

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/AWSDataStorePlugin.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
5555
}
5656
}
5757

58-
/// No-argument init that uses defaults for all providers
58+
#if os(watchOS)
59+
/// Initializer
60+
/// - Parameters:
61+
/// - modelRegistration: Register DataStore models.
62+
/// - dataStoreConfiguration: Configuration object for DataStore
5963
public init(modelRegistration: AmplifyModelRegistration,
60-
configuration dataStoreConfiguration: DataStoreConfiguration = .default) {
64+
configuration dataStoreConfiguration: DataStoreConfiguration) {
6165
self.modelRegistration = modelRegistration
6266
self.configuration = InternalDatastoreConfiguration(
6367
isSyncEnabled: false,
@@ -77,10 +81,37 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
7781
self.dataStorePublisher = DataStorePublisher()
7882
self.dispatchedModelSyncedEvents = [:]
7983
}
84+
#else
85+
/// Initializer
86+
/// - Parameters:
87+
/// - modelRegistration: Register DataStore models.
88+
/// - dataStoreConfiguration: Configuration object for DataStore
89+
public init(modelRegistration: AmplifyModelRegistration,
90+
configuration dataStoreConfiguration: DataStoreConfiguration = .default) {
91+
self.modelRegistration = modelRegistration
92+
self.configuration = InternalDatastoreConfiguration(
93+
isSyncEnabled: false,
94+
validAPIPluginKey: "awsAPIPlugin",
95+
validAuthPluginKey: "awsCognitoAuthPlugin",
96+
pluginConfiguration: dataStoreConfiguration)
8097

98+
self.storageEngineBehaviorFactory =
99+
StorageEngine.init(
100+
isSyncEnabled:
101+
dataStoreConfiguration:
102+
validAPIPluginKey:
103+
validAuthPluginKey:
104+
modelRegistryVersion:
105+
userDefault:
106+
)
107+
self.dataStorePublisher = DataStorePublisher()
108+
self.dispatchedModelSyncedEvents = [:]
109+
}
110+
#endif
111+
81112
/// Internal initializer for testing
82113
init(modelRegistration: AmplifyModelRegistration,
83-
configuration dataStoreConfiguration: DataStoreConfiguration = .default,
114+
configuration dataStoreConfiguration: DataStoreConfiguration = .testDefault(),
84115
storageEngineBehaviorFactory: StorageEngineBehaviorFactory? = nil,
85116
dataStorePublisher: ModelSubcriptionBehavior,
86117
operationQueue: OperationQueue = OperationQueue(),

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Configuration/DataStoreConfiguration+Helper.swift

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,42 @@ extension DataStoreConfiguration {
1515
public static let defaultSyncMaxRecords: UInt = 10_000
1616
public static let defaultSyncPageSize: UInt = 1_000
1717

18+
#if os(watchOS)
19+
/// Creates a custom configuration. The only required property is `conflictHandler`.
20+
///
21+
/// - Parameters:
22+
/// - errorHandler: a callback function called on unhandled errors
23+
/// - conflictHandler: a callback called when a conflict could not be resolved by the service
24+
/// - syncInterval: how often the sync engine will run (in seconds)
25+
/// - syncMaxRecords: the number of records to sync per execution
26+
/// - syncPageSize: the page size of each sync execution
27+
/// - authModeStrategy: authorization strategy (.default | multiauth)
28+
/// - disableSubscriptions: called before establishing subscriptions. Return true to disable subscriptions.
29+
/// - Returns: an instance of `DataStoreConfiguration` with the passed parameters.
30+
public static func custom(
31+
errorHandler: @escaping DataStoreErrorHandler = { error in
32+
Amplify.Logging.error(error: error)
33+
},
34+
conflictHandler: @escaping DataStoreConflictHandler = { _, resolve in
35+
resolve(.applyRemote)
36+
},
37+
syncInterval: TimeInterval = DataStoreConfiguration.defaultSyncInterval,
38+
syncMaxRecords: UInt = DataStoreConfiguration.defaultSyncMaxRecords,
39+
syncPageSize: UInt = DataStoreConfiguration.defaultSyncPageSize,
40+
syncExpressions: [DataStoreSyncExpression] = [],
41+
authModeStrategy: AuthModeStrategyType = .default,
42+
disableSubscriptions: @escaping () -> Bool
43+
) -> DataStoreConfiguration {
44+
return DataStoreConfiguration(errorHandler: errorHandler,
45+
conflictHandler: conflictHandler,
46+
syncInterval: syncInterval,
47+
syncMaxRecords: syncMaxRecords,
48+
syncPageSize: syncPageSize,
49+
syncExpressions: syncExpressions,
50+
authModeStrategy: authModeStrategy,
51+
disableSubscriptions: disableSubscriptions)
52+
}
53+
#else
1854
/// Creates a custom configuration. The only required property is `conflictHandler`.
1955
///
2056
/// - Parameters:
@@ -46,10 +82,32 @@ extension DataStoreConfiguration {
4682
syncExpressions: syncExpressions,
4783
authModeStrategy: authModeStrategy)
4884
}
49-
85+
#endif
86+
87+
#if os(watchOS)
88+
/// Default configuration with subscriptions disabled for watchOS. DataStore uses subscriptions via websockets,
89+
/// which work on the watchOS simulator but not on the device. Running DataStore on watchOS with subscriptions
90+
/// enabled is only possible during special circumstances such as actively streaming audio.
91+
/// See https://github.com/aws-amplify/amplify-swift/pull/3368 for more details.
92+
public static var subscriptionsDisabled: DataStoreConfiguration {
93+
.custom(disableSubscriptions: { false })
94+
}
95+
#else
5096
/// The default configuration.
5197
public static var `default`: DataStoreConfiguration {
5298
.custom()
5399
}
54-
100+
#endif
101+
102+
#if os(watchOS)
103+
/// Internal method for testing
104+
static func testDefault(disableSubscriptions: @escaping () -> Bool = { false }) -> DataStoreConfiguration {
105+
.custom(disableSubscriptions: disableSubscriptions)
106+
}
107+
#else
108+
/// Internal method for testing
109+
static func testDefault() -> DataStoreConfiguration {
110+
.custom()
111+
}
112+
#endif
55113
}

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Configuration/DataStoreConfiguration.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,27 @@ public struct DataStoreConfiguration {
7070
/// Authorization mode strategy
7171
public var authModeStrategyType: AuthModeStrategyType
7272

73+
public let disableSubscriptions: () -> Bool
74+
75+
#if os(watchOS)
76+
init(errorHandler: @escaping DataStoreErrorHandler,
77+
conflictHandler: @escaping DataStoreConflictHandler,
78+
syncInterval: TimeInterval,
79+
syncMaxRecords: UInt,
80+
syncPageSize: UInt,
81+
syncExpressions: [DataStoreSyncExpression],
82+
authModeStrategy: AuthModeStrategyType = .default,
83+
disableSubscriptions: @escaping () -> Bool) {
84+
self.errorHandler = errorHandler
85+
self.conflictHandler = conflictHandler
86+
self.syncInterval = syncInterval
87+
self.syncMaxRecords = syncMaxRecords
88+
self.syncPageSize = syncPageSize
89+
self.syncExpressions = syncExpressions
90+
self.authModeStrategyType = authModeStrategy
91+
self.disableSubscriptions = disableSubscriptions
92+
}
93+
#else
7394
init(errorHandler: @escaping DataStoreErrorHandler,
7495
conflictHandler: @escaping DataStoreConflictHandler,
7596
syncInterval: TimeInterval,
@@ -84,6 +105,7 @@ public struct DataStoreConfiguration {
84105
self.syncPageSize = syncPageSize
85106
self.syncExpressions = syncExpressions
86107
self.authModeStrategyType = authModeStrategy
108+
self.disableSubscriptions = { false }
87109
}
88-
110+
#endif
89111
}

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior {
8686
authModeStrategy: resolvedAuthStrategy)
8787

8888
let reconciliationQueueFactory = reconciliationQueueFactory ??
89-
AWSIncomingEventReconciliationQueue.init(modelSchemas:api:storageAdapter:syncExpressions:auth:authModeStrategy:modelReconciliationQueueFactory:)
89+
AWSIncomingEventReconciliationQueue.init(modelSchemas:api:storageAdapter:syncExpressions:auth:authModeStrategy:modelReconciliationQueueFactory:disableSubscriptions:)
9090

9191
let initialSyncOrchestratorFactory = initialSyncOrchestratorFactory ??
9292
AWSInitialSyncOrchestrator.init(dataStoreConfiguration:authModeStrategy:api:reconciliationQueue:storageAdapter:)
@@ -289,7 +289,8 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior {
289289
dataStoreConfiguration.syncExpressions,
290290
auth,
291291
authModeStrategy,
292-
nil)
292+
nil,
293+
dataStoreConfiguration.disableSubscriptions)
293294
reconciliationQueueSink = reconciliationQueue?
294295
.publisher
295296
.sink(

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import AWSPluginsCore
1010
import Combine
1111
import Foundation
1212

13+
typealias DisableSubscriptions = () -> Bool
14+
1315
// Used for testing:
1416
typealias IncomingEventReconciliationQueueFactory =
1517
([ModelSchema],
@@ -18,7 +20,8 @@ typealias IncomingEventReconciliationQueueFactory =
1820
[DataStoreSyncExpression],
1921
AuthCategoryBehavior?,
2022
AuthModeStrategy,
21-
ModelReconciliationQueueFactory?
23+
ModelReconciliationQueueFactory?,
24+
@escaping DisableSubscriptions
2225
) async -> IncomingEventReconciliationQueue
2326

2427
final class AWSIncomingEventReconciliationQueue: IncomingEventReconciliationQueue {
@@ -48,7 +51,8 @@ final class AWSIncomingEventReconciliationQueue: IncomingEventReconciliationQueu
4851
syncExpressions: [DataStoreSyncExpression],
4952
auth: AuthCategoryBehavior? = nil,
5053
authModeStrategy: AuthModeStrategy,
51-
modelReconciliationQueueFactory: ModelReconciliationQueueFactory? = nil) async {
54+
modelReconciliationQueueFactory: ModelReconciliationQueueFactory? = nil,
55+
disableSubscriptions: @escaping () -> Bool = { false } ) async {
5256
self.modelSchemasCount = modelSchemas.count
5357
self.modelReconciliationQueueSinks.set([:])
5458
self.eventReconciliationQueueTopic = CurrentValueSubject<IncomingEventReconciliationQueueEvent, DataStoreError>(.idle)
@@ -67,6 +71,19 @@ final class AWSIncomingEventReconciliationQueue: IncomingEventReconciliationQueu
6771
self.connectionStatusSerialQueue
6872
= DispatchQueue(label: "com.amazonaws.DataStore.AWSIncomingEventReconciliationQueue")
6973

74+
let subscriptionsDisabled = disableSubscriptions()
75+
76+
#if targetEnvironment(simulator) && os(watchOS)
77+
if !subscriptionsDisabled {
78+
let message = """
79+
DataStore uses subscriptions via websockets, which work on the watchOS simulator but not on the device.
80+
Running DataStore on watchOS with subscriptions enabled is only possible during special circumstances
81+
such as actively streaming audio. See https://github.com/aws-amplify/amplify-swift/pull/3368 for more details.
82+
"""
83+
self.log.verbose(message)
84+
}
85+
#endif
86+
7087
for modelSchema in modelSchemas {
7188
let modelName = modelSchema.name
7289
let syncExpression = syncExpressions.first(where: {
@@ -78,13 +95,13 @@ final class AWSIncomingEventReconciliationQueue: IncomingEventReconciliationQueu
7895
continue
7996
}
8097
let queue = await self.modelReconciliationQueueFactory(modelSchema,
81-
storageAdapter,
82-
api,
83-
reconcileAndSaveQueue,
84-
modelPredicate,
85-
auth,
86-
authModeStrategy,
87-
nil)
98+
storageAdapter,
99+
api,
100+
reconcileAndSaveQueue,
101+
modelPredicate,
102+
auth,
103+
authModeStrategy,
104+
subscriptionsDisabled ? OperationDisabledIncomingSubscriptionEventPublisher() : nil)
88105

89106
reconciliationQueues.with { reconciliationQueues in
90107
reconciliationQueues[modelName] = queue
@@ -190,14 +207,15 @@ extension AWSIncomingEventReconciliationQueue: DefaultLogger {
190207

191208
// MARK: - Static factory
192209
extension AWSIncomingEventReconciliationQueue {
193-
static let factory: IncomingEventReconciliationQueueFactory = { modelSchemas, api, storageAdapter, syncExpressions, auth, authModeStrategy, _ in
210+
static let factory: IncomingEventReconciliationQueueFactory = { modelSchemas, api, storageAdapter, syncExpressions, auth, authModeStrategy, _, disableSubscriptions in
194211
await AWSIncomingEventReconciliationQueue(modelSchemas: modelSchemas,
195-
api: api,
196-
storageAdapter: storageAdapter,
197-
syncExpressions: syncExpressions,
198-
auth: auth,
199-
authModeStrategy: authModeStrategy,
200-
modelReconciliationQueueFactory: nil)
212+
api: api,
213+
storageAdapter: storageAdapter,
214+
syncExpressions: syncExpressions,
215+
auth: auth,
216+
authModeStrategy: authModeStrategy,
217+
modelReconciliationQueueFactory: nil,
218+
disableSubscriptions: disableSubscriptions)
201219
}
202220
}
203221

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Amplify
9+
import AWSPluginsCore
10+
import Combine
11+
12+
final class OperationDisabledIncomingSubscriptionEventPublisher: IncomingSubscriptionEventPublisher {
13+
14+
private let subscriptionEventSubject: PassthroughSubject<IncomingSubscriptionEventPublisherEvent, DataStoreError>
15+
16+
var publisher: AnyPublisher<IncomingSubscriptionEventPublisherEvent, DataStoreError> {
17+
return subscriptionEventSubject.eraseToAnyPublisher()
18+
}
19+
20+
init() {
21+
self.subscriptionEventSubject = PassthroughSubject<IncomingSubscriptionEventPublisherEvent, DataStoreError>()
22+
23+
let apiError = APIError.operationError(AppSyncErrorType.operationDisabled.rawValue, "", nil)
24+
let dataStoreError = DataStoreError.api(apiError, nil)
25+
subscriptionEventSubject.send(completion: .failure(dataStoreError))
26+
27+
}
28+
29+
func cancel() {
30+
}
31+
}

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ final class AWSModelReconciliationQueue: ModelReconciliationQueue {
209209
return
210210
}
211211
if case let .api(error, _) = dataStoreError,
212-
case let APIError.operationError(_, _, underlyingError) = error,
213-
isOperationDisabledError(underlyingError) {
212+
case let APIError.operationError(errorMessage, _, underlyingError) = error,
213+
isOperationDisabledError(errorMessage, underlyingError) {
214214
log.verbose("[InitializeSubscription.3] AWSModelReconciliationQueue determined isOperationDisabledError \(modelSchema.name)")
215215
modelReconciliationQueueSubject.send(.disconnected(modelName: modelSchema.name, reason: .operationDisabled))
216216
return
@@ -284,7 +284,12 @@ extension AWSModelReconciliationQueue {
284284
return false
285285
}
286286

287-
private func isOperationDisabledError(_ error: Error?) -> Bool {
287+
private func isOperationDisabledError(_ errorMessage: String?, _ error: Error?) -> Bool {
288+
if let errorMessage = errorMessage,
289+
case .operationDisabled = AppSyncErrorType(errorMessage) {
290+
return true
291+
}
292+
288293
if let responseError = error as? GraphQLResponseError<ResponseType>,
289294
let graphQLError = graphqlErrors(from: responseError)?.first,
290295
let errorTypeValue = errorTypeValueFrom(graphQLError: graphQLError),

AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginAmplifyVersionableTests.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import AWSDataStorePlugin
1313
class AWSDataStorePluginAmplifyVersionableTests: XCTestCase {
1414

1515
func testVersionExists() {
16+
#if os(watchOS)
17+
let plugin = AWSDataStorePlugin(modelRegistration: AmplifyModels(),
18+
configuration: .subscriptionsDisabled)
19+
#else
1620
let plugin = AWSDataStorePlugin(modelRegistration: AmplifyModels())
21+
#endif
1722
XCTAssertNotNil(plugin.version)
1823
}
1924

AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/DataStoreCategoryConfigurationTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ class AWSDataStorePluginConfigurationTests: XCTestCase {
1616
}
1717

1818
func testDoesNotThrowOnMissingConfig() throws {
19+
#if os(watchOS)
20+
let plugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(),
21+
configuration: .subscriptionsDisabled)
22+
#else
1923
let plugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration())
24+
25+
#endif
2026
try Amplify.add(plugin: plugin)
2127

2228
let categoryConfig = DataStoreCategoryConfiguration(plugins: ["NonExistentPlugin": true])

0 commit comments

Comments
 (0)