Compare commits

..

2 Commits

Author SHA1 Message Date
Michael fc84acdb84 Add configuration value 2021-11-01 20:53:22 -04:00
Michael 9685d376f0 pod install 2021-11-01 20:43:49 -04:00
143 changed files with 2784 additions and 7532 deletions

View File

@ -1,12 +1,10 @@
# .circleci/config.yml
version: 2.1
orbs:
aws-cli: circleci/aws-cli@3.1.4
defaults: &defaults
macos:
xcode: '14.2.0'
xcode: "11.2.1"
working_directory: ~/aws-appsync-realtime-client-ios
environment:
BUNDLE_PATH: vendor/bundle
@ -49,8 +47,8 @@ commands:
steps:
- restore_cache:
keys:
- app-{{ .Environment.CACHE_VERSION }}-gems-{{ checksum "~/aws-appsync-realtime-client-ios/Gemfile.lock" }}
- app-{{ .Environment.CACHE_VERSION }}-gems-
- v1-gems-{{ checksum "~/aws-appsync-realtime-client-ios/Gemfile.lock" }}
- v1-gems-
check_bundle:
steps:
@ -96,12 +94,12 @@ jobs:
- restore_gems
- run:
name: Bundle install
command: bundle config set --local path 'vendor/bundle' && bundle install
command: bundle check --path $BUNDLE_PATH || bundle install --path $BUNDLE_PATH
environment:
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
- save_cache:
key: app-{{ .Environment.CACHE_VERSION }}-gems-{{ checksum "~/aws-appsync-realtime-client-ios/Gemfile.lock" }}
key: v1-gems-{{ checksum "~/aws-appsync-realtime-client-ios/Gemfile.lock" }}
paths:
- vendor/bundle
build_test_appsync_realtime_client:
@ -109,15 +107,15 @@ jobs:
steps:
- *restore_repo
- pre_start_simulator
- run: pod install
- restore_gems
- run: bundle exec pod install
- check_bundle
- run:
name: Build AppSyncRealTimeClient
command: xcodebuild build-for-testing -workspace AppSyncRealTimeClient.xcworkspace -scheme AppSyncRealTimeClient -sdk iphonesimulator -destination "${destination}" | xcpretty
- run:
name: Test AppSyncRealTimeClient
command: xcodebuild test -workspace AppSyncRealTimeClient.xcworkspace -scheme AppSyncRealTimeClient -sdk iphonesimulator -destination "${destination}" | xcpretty --simple --color --report junit
command: xcodebuild test -enableThreadSanitizer NO -workspace AppSyncRealTimeClient.xcworkspace -scheme AppSyncRealTimeClient -sdk iphonesimulator -destination "${destination}" | xcpretty --simple --color --report junit
- store_test_results:
path: build/reports
deploy:
@ -126,10 +124,6 @@ jobs:
steps:
- *restore_repo
- restore_gems
- aws-cli/setup:
role-arn: $AWS_OIDC_ROLE_ARN
role-session-name: "${CIRCLE_WORKFLOW_JOB_ID}.deploy"
session-duration: '900'
- check_bundle
- run:
name: Release pods
@ -145,10 +139,9 @@ workflows:
requires:
- install_gems
- deploy:
context: amplify-swift-aws-oidc
filters:
branches:
only:
- release
requires:
- build_test_appsync_realtime_client
- build_test_appsync_realtime_client

1
.github/CODEOWNERS vendored
View File

@ -1 +0,0 @@
* @aws-amplify/amplify-ios

2
.gitignore vendored
View File

@ -43,8 +43,6 @@ credentials-mc.json
amplify
build/
.build/
.vscode/
dist/
node_modules/
aws-exports.js

View File

@ -10,11 +10,13 @@
--elseposition same-line
--enable fileHeader
--header "//\n// Copyright Amazon.com Inc. or its affiliates.\n// All Rights Reserved.\n//\n// SPDX-License-Identifier: Apache-2.0\n//"
--header "//\n// Copyright 2018-{created.year} Amazon.com,\n// Inc. or its affiliates. All Rights Reserved.\n//\n// SPDX-License-Identifier: Apache-2.0\n//"
--disable hoistPatternLet
--patternlet inline
--disable indent
--ifdef outdent
--indent 4
--indentcase false
--xcodeindentation disabled
@ -43,6 +45,7 @@
--semicolons never
--disable sortedImports
--importgrouping testable-bottom
--enable spaceAroundOperators
--operatorfunc spaced
@ -51,11 +54,13 @@
--trailingclosures
--disable trailingCommas
--commas inline
--enable trailingSpace
--trimwhitespace always
--disable unusedArguments
--stripunusedargs closure-only
--enable void
--empty void

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = 'AppSyncRealTimeClient'
s.version = '3.1.0'
s.version = '1.5.0'
s.summary = 'Amazon Web Services AppSync RealTime Client for iOS.'
s.description = 'AppSync RealTime Client provides subscription connections to AppSync websocket endpoints'
@ -16,5 +16,5 @@ Pod::Spec.new do |s|
s.requires_arc = true
s.source_files = 'AppSyncRealTimeClient/**/*.swift'
s.dependency 'Starscream', '~> 4.0.4'
end
s.dependency 'Starscream', '~> 3.1.1'
end

View File

@ -3,18 +3,13 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
0B59161BB37D32073E4FD61B /* Pods_AppSyncRTCSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CF486070B34EFD15B4DB8FC /* Pods_AppSyncRTCSample.framework */; };
2143D46727B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2143D46627B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift */; };
2143D4B027BC49BE0066B2F7 /* AWSAppSyncRealTimeClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2143D4AF27BC49BE0066B2F7 /* AWSAppSyncRealTimeClient.swift */; };
2143D4B227BE40B30066B2F7 /* AppSyncRealTimeClientFailureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2143D4B127BE40B30066B2F7 /* AppSyncRealTimeClientFailureTests.swift */; };
2151D5CA27C68C3C00F3C866 /* ConnectionProviderHandleErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2151D5C927C68C3C00F3C866 /* ConnectionProviderHandleErrorTests.swift */; };
2164E65D2639AD5600385027 /* StarscreamAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2164E65C2639AD5600385027 /* StarscreamAdapterTests.swift */; };
2164E674263C58CE00385027 /* AppSyncRealTimeClientTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2164E673263C58CD00385027 /* AppSyncRealTimeClientTestBase.swift */; };
217C74D227C45B6600AE054F /* AppSyncSubscriptionConnectionErrorHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217C74D127C45B6600AE054F /* AppSyncSubscriptionConnectionErrorHandlerTests.swift */; };
217F39992405D9D500F1A0B3 /* AppSyncRealTimeClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217F398F2405D9D500F1A0B3 /* AppSyncRealTimeClient.framework */; };
217F39CC2406E98400F1A0B3 /* AppSyncResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217F39AA2406E98300F1A0B3 /* AppSyncResponse.swift */; };
217F39CD2406E98400F1A0B3 /* InterceptableConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217F39AB2406E98300F1A0B3 /* InterceptableConnection.swift */; };
@ -47,11 +42,6 @@
217F39F12406EA4000F1A0B3 /* MockConnectionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217F39EB2406EA3F00F1A0B3 /* MockConnectionProvider.swift */; };
217F39F22406EA4000F1A0B3 /* ConnectionProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217F39ED2406EA4000F1A0B3 /* ConnectionProviderTests.swift */; };
217F39F32406EA4000F1A0B3 /* RealtimeGatewayURLInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217F39EF2406EA4000F1A0B3 /* RealtimeGatewayURLInterceptorTests.swift */; };
219BFF3B27A38902000FC148 /* schema.graphql in Resources */ = {isa = PBXBuildFile; fileRef = 219BFF3A27A38902000FC148 /* schema.graphql */; };
219BFF4927A3B238000FC148 /* ConnectivityMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219BFF4827A3B237000FC148 /* ConnectivityMonitor.swift */; };
219BFF4B27A3B24F000FC148 /* ConnectivityPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219BFF4A27A3B24F000FC148 /* ConnectivityPath.swift */; };
219BFF6A27A9AC6E000FC148 /* ConnectivityMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219BFF6927A9AC6E000FC148 /* ConnectivityMonitorTests.swift */; };
219BFF6C27A9AF85000FC148 /* MockConnectivityMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219BFF6B27A9AF85000FC148 /* MockConnectivityMonitor.swift */; };
21D38B412409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B402409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.swift */; };
21D38B432409AFBD00EC2A8D /* AppSyncRealTimeClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217F398F2405D9D500F1A0B3 /* AppSyncRealTimeClient.framework */; };
21D38B4C2409B6C000EC2A8D /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21D38B4B2409B6C000EC2A8D /* amplifyconfiguration.json */; };
@ -76,22 +66,6 @@
259F9EB83B9F67D0413A6D4C /* Pods_AppSyncRealTimeClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81AFCBED5A97E398A4BD9D06 /* Pods_AppSyncRealTimeClient.framework */; };
450019BB151D701382536BD0 /* Pods_HostApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29CDD85F44666C7241232D29 /* Pods_HostApp.framework */; };
4A3EAC9B20D96D81CC3A7EF4 /* Pods_HostApp_AppSyncRealTimeClientIntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D112B03D6D38F84995D6FA /* Pods_HostApp_AppSyncRealTimeClientIntegrationTests.framework */; };
5C4E7C5E289082D3001800C1 /* OIDCAuthProviderAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4E7C5D289082D3001800C1 /* OIDCAuthProviderAsync.swift */; };
5C5609EE2821E6FC0002ACF5 /* InterceptableConnectionAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5609ED2821E6FC0002ACF5 /* InterceptableConnectionAsync.swift */; };
5C95D878289081F0008D66E9 /* OIDCAuthInterceptorAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C95D877289081F0008D66E9 /* OIDCAuthInterceptorAsync.swift */; };
5CF3E96B283C33D40036EAD2 /* AppSyncRealTimeClientAsyncFailureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF3E968283C33D40036EAD2 /* AppSyncRealTimeClientAsyncFailureTests.swift */; };
5CF3E96D283C33D40036EAD2 /* AppSyncRealTimeClientAsyncIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF3E96A283C33D40036EAD2 /* AppSyncRealTimeClientAsyncIntegrationTests.swift */; };
5CFF7233283C1971001D5471 /* RealtimeConnectionProviderAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF722E283C1971001D5471 /* RealtimeConnectionProviderAsync.swift */; };
5CFF7234283C1971001D5471 /* RealtimeConnectionProviderAsync+StaleConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF722F283C1971001D5471 /* RealtimeConnectionProviderAsync+StaleConnection.swift */; };
5CFF7235283C1971001D5471 /* RealtimeConnectionProviderAsync+ConnectionInterceptableAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF7230283C1971001D5471 /* RealtimeConnectionProviderAsync+ConnectionInterceptableAsync.swift */; };
5CFF7236283C1971001D5471 /* RealtimeConnectionProviderAsync+Websocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF7231283C1971001D5471 /* RealtimeConnectionProviderAsync+Websocket.swift */; };
5CFF7237283C1971001D5471 /* RealtimeConnectionProviderAsync+MessageInterceptableAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF7232283C1971001D5471 /* RealtimeConnectionProviderAsync+MessageInterceptableAsync.swift */; };
5CFF7239283C19CF001D5471 /* TaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF7238283C19CF001D5471 /* TaskQueue.swift */; };
5CFF723D283C1AF5001D5471 /* ConnectionProviderAsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF723B283C1AF5001D5471 /* ConnectionProviderAsyncTests.swift */; };
5CFF723E283C1AF5001D5471 /* RealtimeConnectionProviderAsyncTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF723C283C1AF5001D5471 /* RealtimeConnectionProviderAsyncTestBase.swift */; };
978409B82739C7BE002362A7 /* AppSyncURLHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978409B72739C7BE002362A7 /* AppSyncURLHelper.swift */; };
978409BA2739C7E1002362A7 /* AppSyncURLHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978409B92739C7E1002362A7 /* AppSyncURLHelperTests.swift */; };
B4AEC68C29E8A14F00D693CD /* InterceptableConnection+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AEC68B29E8A14F00D693CD /* InterceptableConnection+Default.swift */; };
DCFE701B5D1380E566694A48 /* Pods_AppSyncRealTimeClient_AppSyncRealTimeClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E662A46AB2C93EE316F784C /* Pods_AppSyncRealTimeClient_AppSyncRealTimeClientTests.framework */; };
FA67507824D3244A005A1345 /* MockWebsocketProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67507724D3244A005A1345 /* MockWebsocketProvider.swift */; };
FA67507B24D338CB005A1345 /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA67507A24D338C6005A1345 /* Error+Extension.swift */; };
@ -145,13 +119,8 @@
/* Begin PBXFileReference section */
18D6E56CE03BAC33493CC19B /* Pods-HostApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp.debug.xcconfig"; path = "Target Support Files/Pods-HostApp/Pods-HostApp.debug.xcconfig"; sourceTree = "<group>"; };
2143D46627B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealTimeConnectionProviderResponseTests.swift; sourceTree = "<group>"; };
2143D4AF27BC49BE0066B2F7 /* AWSAppSyncRealTimeClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSAppSyncRealTimeClient.swift; sourceTree = "<group>"; };
2143D4B127BE40B30066B2F7 /* AppSyncRealTimeClientFailureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncRealTimeClientFailureTests.swift; sourceTree = "<group>"; };
2151D5C927C68C3C00F3C866 /* ConnectionProviderHandleErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionProviderHandleErrorTests.swift; sourceTree = "<group>"; };
2164E65C2639AD5600385027 /* StarscreamAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarscreamAdapterTests.swift; sourceTree = "<group>"; };
2164E673263C58CD00385027 /* AppSyncRealTimeClientTestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncRealTimeClientTestBase.swift; sourceTree = "<group>"; };
217C74D127C45B6600AE054F /* AppSyncSubscriptionConnectionErrorHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncSubscriptionConnectionErrorHandlerTests.swift; sourceTree = "<group>"; };
217F398F2405D9D500F1A0B3 /* AppSyncRealTimeClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppSyncRealTimeClient.framework; sourceTree = BUILT_PRODUCTS_DIR; };
217F39932405D9D500F1A0B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
217F39982405D9D500F1A0B3 /* AppSyncRealTimeClientTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppSyncRealTimeClientTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@ -188,11 +157,6 @@
217F39EB2406EA3F00F1A0B3 /* MockConnectionProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockConnectionProvider.swift; sourceTree = "<group>"; };
217F39ED2406EA4000F1A0B3 /* ConnectionProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionProviderTests.swift; sourceTree = "<group>"; };
217F39EF2406EA4000F1A0B3 /* RealtimeGatewayURLInterceptorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeGatewayURLInterceptorTests.swift; sourceTree = "<group>"; };
219BFF3A27A38902000FC148 /* schema.graphql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = schema.graphql; sourceTree = "<group>"; };
219BFF4827A3B237000FC148 /* ConnectivityMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityMonitor.swift; sourceTree = "<group>"; };
219BFF4A27A3B24F000FC148 /* ConnectivityPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityPath.swift; sourceTree = "<group>"; };
219BFF6927A9AC6E000FC148 /* ConnectivityMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityMonitorTests.swift; sourceTree = "<group>"; };
219BFF6B27A9AF85000FC148 /* MockConnectivityMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockConnectivityMonitor.swift; sourceTree = "<group>"; };
21D38B3E2409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppSyncRealTimeClientIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
21D38B402409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncRealTimeClientIntegrationTests.swift; sourceTree = "<group>"; };
21D38B422409AFBD00EC2A8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -221,29 +185,13 @@
356411189EAD2C776D250FB7 /* Pods-HostApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp.release.xcconfig"; path = "Target Support Files/Pods-HostApp/Pods-HostApp.release.xcconfig"; sourceTree = "<group>"; };
3E662A46AB2C93EE316F784C /* Pods_AppSyncRealTimeClient_AppSyncRealTimeClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppSyncRealTimeClient_AppSyncRealTimeClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
43D112B03D6D38F84995D6FA /* Pods_HostApp_AppSyncRealTimeClientIntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HostApp_AppSyncRealTimeClientIntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5C4E7C5D289082D3001800C1 /* OIDCAuthProviderAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OIDCAuthProviderAsync.swift; sourceTree = "<group>"; };
5C5609ED2821E6FC0002ACF5 /* InterceptableConnectionAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterceptableConnectionAsync.swift; sourceTree = "<group>"; };
5C95D877289081F0008D66E9 /* OIDCAuthInterceptorAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OIDCAuthInterceptorAsync.swift; sourceTree = "<group>"; };
5CF3E968283C33D40036EAD2 /* AppSyncRealTimeClientAsyncFailureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSyncRealTimeClientAsyncFailureTests.swift; sourceTree = "<group>"; };
5CF3E96A283C33D40036EAD2 /* AppSyncRealTimeClientAsyncIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSyncRealTimeClientAsyncIntegrationTests.swift; sourceTree = "<group>"; };
5CFF722E283C1971001D5471 /* RealtimeConnectionProviderAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeConnectionProviderAsync.swift; sourceTree = "<group>"; };
5CFF722F283C1971001D5471 /* RealtimeConnectionProviderAsync+StaleConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RealtimeConnectionProviderAsync+StaleConnection.swift"; sourceTree = "<group>"; };
5CFF7230283C1971001D5471 /* RealtimeConnectionProviderAsync+ConnectionInterceptableAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RealtimeConnectionProviderAsync+ConnectionInterceptableAsync.swift"; sourceTree = "<group>"; };
5CFF7231283C1971001D5471 /* RealtimeConnectionProviderAsync+Websocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RealtimeConnectionProviderAsync+Websocket.swift"; sourceTree = "<group>"; };
5CFF7232283C1971001D5471 /* RealtimeConnectionProviderAsync+MessageInterceptableAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RealtimeConnectionProviderAsync+MessageInterceptableAsync.swift"; sourceTree = "<group>"; };
5CFF7238283C19CF001D5471 /* TaskQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskQueue.swift; sourceTree = "<group>"; };
5CFF723B283C1AF5001D5471 /* ConnectionProviderAsyncTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionProviderAsyncTests.swift; sourceTree = "<group>"; };
5CFF723C283C1AF5001D5471 /* RealtimeConnectionProviderAsyncTestBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeConnectionProviderAsyncTestBase.swift; sourceTree = "<group>"; };
7CF486070B34EFD15B4DB8FC /* Pods_AppSyncRTCSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppSyncRTCSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7D4F451B830A5837CE5274A3 /* Pods-AppSyncRealTimeClient-AppSyncRealTimeClientTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppSyncRealTimeClient-AppSyncRealTimeClientTests.release.xcconfig"; path = "Target Support Files/Pods-AppSyncRealTimeClient-AppSyncRealTimeClientTests/Pods-AppSyncRealTimeClient-AppSyncRealTimeClientTests.release.xcconfig"; sourceTree = "<group>"; };
81AFCBED5A97E398A4BD9D06 /* Pods_AppSyncRealTimeClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppSyncRealTimeClient.framework; sourceTree = BUILT_PRODUCTS_DIR; };
93AA1C96F1B8D428CCC24CF0 /* Pods-AppSyncRealTimeClient-AppSyncRealTimeClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppSyncRealTimeClient-AppSyncRealTimeClientTests.debug.xcconfig"; path = "Target Support Files/Pods-AppSyncRealTimeClient-AppSyncRealTimeClientTests/Pods-AppSyncRealTimeClient-AppSyncRealTimeClientTests.debug.xcconfig"; sourceTree = "<group>"; };
978409B72739C7BE002362A7 /* AppSyncURLHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSyncURLHelper.swift; sourceTree = "<group>"; };
978409B92739C7E1002362A7 /* AppSyncURLHelperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSyncURLHelperTests.swift; sourceTree = "<group>"; };
A843B4589131E0D2DB13ADC7 /* Pods-HostApp-AppSyncRealTimeClientIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AppSyncRealTimeClientIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-HostApp-AppSyncRealTimeClientIntegrationTests/Pods-HostApp-AppSyncRealTimeClientIntegrationTests.debug.xcconfig"; sourceTree = "<group>"; };
B07A87133C24AA78AF59093C /* Pods-AppSyncRealTimeClient.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppSyncRealTimeClient.debug.xcconfig"; path = "Target Support Files/Pods-AppSyncRealTimeClient/Pods-AppSyncRealTimeClient.debug.xcconfig"; sourceTree = "<group>"; };
B4A642C2C000E0D0B568A4A9 /* Pods-AppSyncRTCSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppSyncRTCSample.release.xcconfig"; path = "Target Support Files/Pods-AppSyncRTCSample/Pods-AppSyncRTCSample.release.xcconfig"; sourceTree = "<group>"; };
B4AEC68B29E8A14F00D693CD /* InterceptableConnection+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "InterceptableConnection+Default.swift"; sourceTree = "<group>"; };
BBF14AA5E9A4D036CA87B952 /* Pods-AppSyncRTCSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppSyncRTCSample.debug.xcconfig"; path = "Target Support Files/Pods-AppSyncRTCSample/Pods-AppSyncRTCSample.debug.xcconfig"; sourceTree = "<group>"; };
C73EA796F50FB0196FDF4865 /* Pods-AppSyncRealTimeClient.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppSyncRealTimeClient.release.xcconfig"; path = "Target Support Files/Pods-AppSyncRealTimeClient/Pods-AppSyncRealTimeClient.release.xcconfig"; sourceTree = "<group>"; };
E65A19C826699DA299A49284 /* Pods-HostApp-AppSyncRealTimeClientIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp-AppSyncRealTimeClientIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-HostApp-AppSyncRealTimeClientIntegrationTests/Pods-HostApp-AppSyncRealTimeClientIntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
@ -311,14 +259,6 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2143D46527B5D9730066B2F7 /* AppSyncRealTimeConnection */ = {
isa = PBXGroup;
children = (
2143D46627B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift */,
);
path = AppSyncRealTimeConnection;
sourceTree = "<group>";
};
217F39852405D9D500F1A0B3 = {
isa = PBXGroup;
children = (
@ -348,11 +288,9 @@
217F39912405D9D500F1A0B3 /* AppSyncRealTimeClient */ = {
isa = PBXGroup;
children = (
2143D4AF27BC49BE0066B2F7 /* AWSAppSyncRealTimeClient.swift */,
217F39932405D9D500F1A0B3 /* Info.plist */,
217F39B82406E98300F1A0B3 /* Connection */,
217F39A92406E98300F1A0B3 /* ConnectionProvider */,
219BFF4727A3B223000FC148 /* ConnectivityMonitor */,
217F39932405D9D500F1A0B3 /* Info.plist */,
21D38B6E240A272F00EC2A8D /* Interceptor */,
217F39C62406E98400F1A0B3 /* Support */,
217F39C12406E98400F1A0B3 /* Websocket */,
@ -365,7 +303,6 @@
children = (
217F39E82406EA3F00F1A0B3 /* Connection */,
217F39EC2406EA4000F1A0B3 /* ConnectionProvider */,
219BFF6827A9AC4F000FC148 /* ConnectivityMonitor */,
217F399F2405D9D500F1A0B3 /* Info.plist */,
21D38B84240A39E400EC2A8D /* Interceptor */,
217F39EA2406EA3F00F1A0B3 /* Mocks */,
@ -381,15 +318,12 @@
217F39AF2406E98300F1A0B3 /* AppSyncConnectionRequest.swift */,
217F39AE2406E98300F1A0B3 /* AppSyncMessage.swift */,
217F39AD2406E98300F1A0B3 /* AppSyncMessage+Encodable.swift */,
217F39B02406E98300F1A0B3 /* AppsyncRealtimeConnection */,
5CFF722D283C1971001D5471 /* AppsyncRealtimeConnectionAsync */,
217F39AA2406E98300F1A0B3 /* AppSyncResponse.swift */,
217F39B72406E98300F1A0B3 /* ConnectionProvider.swift */,
217F39AC2406E98300F1A0B3 /* ConnectionProviderError.swift */,
21D38B8D240A3C2300EC2A8D /* ConnectionProviderFactory.swift */,
217F39AB2406E98300F1A0B3 /* InterceptableConnection.swift */,
B4AEC68B29E8A14F00D693CD /* InterceptableConnection+Default.swift */,
5C5609ED2821E6FC0002ACF5 /* InterceptableConnectionAsync.swift */,
217F39B02406E98300F1A0B3 /* AppsyncRealtimeConnection */,
);
path = ConnectionProvider;
sourceTree = "<group>";
@ -453,14 +387,11 @@
21D38B6C240A262800EC2A8D /* AppSyncJSONHelper.swift */,
217F39C92406E98400F1A0B3 /* AppSyncJSONValue.swift */,
217F39C72406E98400F1A0B3 /* AppSyncLogger.swift */,
978409B72739C7BE002362A7 /* AppSyncURLHelper.swift */,
FA2EFABB2550CAC6007698C7 /* AtomicValue.swift */,
FA67508124D33A7A005A1345 /* CountdownTimer.swift */,
21D38B93240C4A2A00EC2A8D /* OIDCAuthProvider.swift */,
5C4E7C5D289082D3001800C1 /* OIDCAuthProviderAsync.swift */,
217F39CB2406E98400F1A0B3 /* SubscriptionConnectionType.swift */,
217F39CA2406E98400F1A0B3 /* SubscriptionConstants.swift */,
5CFF7238283C19CF001D5471 /* TaskQueue.swift */,
);
path = Support;
sourceTree = "<group>";
@ -468,7 +399,6 @@
217F39E82406EA3F00F1A0B3 /* Connection */ = {
isa = PBXGroup;
children = (
217C74D127C45B6600AE054F /* AppSyncSubscriptionConnectionErrorHandlerTests.swift */,
217F39E92406EA3F00F1A0B3 /* AppSyncSubscriptionConnectionTests.swift */,
);
path = Connection;
@ -479,7 +409,6 @@
children = (
217F39EB2406EA3F00F1A0B3 /* MockConnectionProvider.swift */,
FA67507724D3244A005A1345 /* MockWebsocketProvider.swift */,
219BFF6B27A9AF85000FC148 /* MockConnectivityMonitor.swift */,
);
path = Mocks;
sourceTree = "<group>";
@ -487,9 +416,6 @@
217F39EC2406EA4000F1A0B3 /* ConnectionProvider */ = {
isa = PBXGroup;
children = (
5CFF723A283C1AA0001D5471 /* ConnectionProviderAsync */,
2143D46527B5D9730066B2F7 /* AppSyncRealTimeConnection */,
2151D5C927C68C3C00F3C866 /* ConnectionProviderHandleErrorTests.swift */,
FA67507F24D339B0005A1345 /* ConnectionProviderStaleConnectionTests.swift */,
217F39ED2406EA4000F1A0B3 /* ConnectionProviderTests.swift */,
FA67507C24D338FA005A1345 /* RealtimeConnectionProviderTestBase.swift */,
@ -506,29 +432,10 @@
path = Support;
sourceTree = "<group>";
};
219BFF4727A3B223000FC148 /* ConnectivityMonitor */ = {
isa = PBXGroup;
children = (
219BFF4827A3B237000FC148 /* ConnectivityMonitor.swift */,
219BFF4A27A3B24F000FC148 /* ConnectivityPath.swift */,
);
path = ConnectivityMonitor;
sourceTree = "<group>";
};
219BFF6827A9AC4F000FC148 /* ConnectivityMonitor */ = {
isa = PBXGroup;
children = (
219BFF6927A9AC6E000FC148 /* ConnectivityMonitorTests.swift */,
);
path = ConnectivityMonitor;
sourceTree = "<group>";
};
21D38B3F2409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests */ = {
isa = PBXGroup;
children = (
5CF3E967283C33D40036EAD2 /* AppSyncRealTimeClientAsync */,
21D38B4B2409B6C000EC2A8D /* amplifyconfiguration.json */,
2143D4B127BE40B30066B2F7 /* AppSyncRealTimeClientFailureTests.swift */,
21D38B402409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.swift */,
2164E673263C58CD00385027 /* AppSyncRealTimeClientTestBase.swift */,
21D38B422409AFBD00EC2A8D /* Info.plist */,
@ -566,7 +473,6 @@
children = (
21D38B82240A392B00EC2A8D /* APIKeyAuthInterceptor.swift */,
21D38B78240A2A1300EC2A8D /* OIDCAuthInterceptor.swift */,
5C95D877289081F0008D66E9 /* OIDCAuthInterceptorAsync.swift */,
217F39C82406E98400F1A0B3 /* RealtimeGatewayURLInterceptor.swift */,
);
path = Interceptor;
@ -577,7 +483,6 @@
children = (
21D38B88240A39E400EC2A8D /* APIKeyAuthInterceptorTests.swift */,
21D38B87240A39E400EC2A8D /* AppSyncJSONHelperTests.swift */,
978409B92739C7E1002362A7 /* AppSyncURLHelperTests.swift */,
21D38B85240A39E400EC2A8D /* OIDCAuthInterceptorTests.swift */,
);
path = Interceptor;
@ -586,7 +491,6 @@
21D38B95240C4DC200EC2A8D /* Support */ = {
isa = PBXGroup;
children = (
219BFF3A27A38902000FC148 /* schema.graphql */,
21D38B98240C4E1C00EC2A8D /* ConfigurationHelper.swift */,
21D38B96240C4DCF00EC2A8D /* Error+Extension.swift */,
21D38B9C240C540D00EC2A8D /* TestCommonConstants.swift */,
@ -594,36 +498,6 @@
path = Support;
sourceTree = "<group>";
};
5CF3E967283C33D40036EAD2 /* AppSyncRealTimeClientAsync */ = {
isa = PBXGroup;
children = (
5CF3E968283C33D40036EAD2 /* AppSyncRealTimeClientAsyncFailureTests.swift */,
5CF3E96A283C33D40036EAD2 /* AppSyncRealTimeClientAsyncIntegrationTests.swift */,
);
path = AppSyncRealTimeClientAsync;
sourceTree = "<group>";
};
5CFF722D283C1971001D5471 /* AppsyncRealtimeConnectionAsync */ = {
isa = PBXGroup;
children = (
5CFF722E283C1971001D5471 /* RealtimeConnectionProviderAsync.swift */,
5CFF7230283C1971001D5471 /* RealtimeConnectionProviderAsync+ConnectionInterceptableAsync.swift */,
5CFF7232283C1971001D5471 /* RealtimeConnectionProviderAsync+MessageInterceptableAsync.swift */,
5CFF722F283C1971001D5471 /* RealtimeConnectionProviderAsync+StaleConnection.swift */,
5CFF7231283C1971001D5471 /* RealtimeConnectionProviderAsync+Websocket.swift */,
);
path = AppsyncRealtimeConnectionAsync;
sourceTree = "<group>";
};
5CFF723A283C1AA0001D5471 /* ConnectionProviderAsync */ = {
isa = PBXGroup;
children = (
5CFF723B283C1AF5001D5471 /* ConnectionProviderAsyncTests.swift */,
5CFF723C283C1AF5001D5471 /* RealtimeConnectionProviderAsyncTestBase.swift */,
);
path = ConnectionProviderAsync;
sourceTree = "<group>";
};
CC47BA7DC6033B1626BAD959 /* Frameworks */ = {
isa = PBXGroup;
children = (
@ -873,7 +747,6 @@
buildActionMask = 2147483647;
files = (
21D38B4E2409B8B200EC2A8D /* README.md in Resources */,
219BFF3B27A38902000FC148 /* schema.graphql in Resources */,
21D38B4C2409B6C000EC2A8D /* amplifyconfiguration.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1066,7 +939,6 @@
};
FA91B21124D308330017404D /* SwiftFormat */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -1085,7 +957,6 @@
};
FA91B21224D308550017404D /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -1104,7 +975,6 @@
};
FA91B21324D308800017404D /* SwiftFormat */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -1123,7 +993,6 @@
};
FA91B21424D308B00017404D /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -1142,7 +1011,6 @@
};
FA91B21524D308C70017404D /* SwiftFormat */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -1161,7 +1029,6 @@
};
FA91B21624D308D90017404D /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -1222,7 +1089,6 @@
buildActionMask = 2147483647;
files = (
21D38B7C240A2A1300EC2A8D /* OIDCAuthInterceptor.swift in Sources */,
5CFF7235283C1971001D5471 /* RealtimeConnectionProviderAsync+ConnectionInterceptableAsync.swift in Sources */,
217F39DB2406E98400F1A0B3 /* AppSyncSubscriptionConnection+DataHandler.swift in Sources */,
21D38B94240C4A2A00EC2A8D /* OIDCAuthProvider.swift in Sources */,
217F39DF2406E98400F1A0B3 /* SubscriptionItem.swift in Sources */,
@ -1231,45 +1097,32 @@
217F39CD2406E98400F1A0B3 /* InterceptableConnection.swift in Sources */,
21D38B8E240A3C2300EC2A8D /* ConnectionProviderFactory.swift in Sources */,
217F39E02406E98400F1A0B3 /* AppSyncWebsocketProvider.swift in Sources */,
219BFF4927A3B238000FC148 /* ConnectivityMonitor.swift in Sources */,
FAB7E91224D2644E00DF1EA1 /* RealtimeConnectionProvider+StaleConnection.swift in Sources */,
217F39D32406E98400F1A0B3 /* RealtimeConnectionProvider.swift in Sources */,
B4AEC68C29E8A14F00D693CD /* InterceptableConnection+Default.swift in Sources */,
217F39D12406E98400F1A0B3 /* AppSyncConnectionRequest.swift in Sources */,
5C5609EE2821E6FC0002ACF5 /* InterceptableConnectionAsync.swift in Sources */,
217F39DD2406E98400F1A0B3 /* AppSyncSubscriptionConnection+Connection.swift in Sources */,
217F39E52406E98400F1A0B3 /* AppSyncJSONValue.swift in Sources */,
217F39E42406E98400F1A0B3 /* RealtimeGatewayURLInterceptor.swift in Sources */,
2143D4B027BC49BE0066B2F7 /* AWSAppSyncRealTimeClient.swift in Sources */,
217F39E22406E98400F1A0B3 /* StarscreamAdapter+Delegate.swift in Sources */,
217F39D52406E98400F1A0B3 /* RealtimeConnectionProviderResponse.swift in Sources */,
217F39D62406E98400F1A0B3 /* RealtimeConnectionProvider+MessageInterceptable.swift in Sources */,
5CFF7233283C1971001D5471 /* RealtimeConnectionProviderAsync.swift in Sources */,
5C95D878289081F0008D66E9 /* OIDCAuthInterceptorAsync.swift in Sources */,
FA67508224D33A7A005A1345 /* CountdownTimer.swift in Sources */,
217F39E62406E98400F1A0B3 /* SubscriptionConstants.swift in Sources */,
5CFF7234283C1971001D5471 /* RealtimeConnectionProviderAsync+StaleConnection.swift in Sources */,
217F39D02406E98400F1A0B3 /* AppSyncMessage.swift in Sources */,
217F39E32406E98400F1A0B3 /* AppSyncLogger.swift in Sources */,
217F39D82406E98400F1A0B3 /* ConnectionProvider.swift in Sources */,
21D38B83240A392B00EC2A8D /* APIKeyAuthInterceptor.swift in Sources */,
217F39D92406E98400F1A0B3 /* SubscriptionConnection.swift in Sources */,
5C4E7C5E289082D3001800C1 /* OIDCAuthProviderAsync.swift in Sources */,
217F39DA2406E98400F1A0B3 /* RetryableConnection.swift in Sources */,
978409B82739C7BE002362A7 /* AppSyncURLHelper.swift in Sources */,
217F39CC2406E98400F1A0B3 /* AppSyncResponse.swift in Sources */,
5CFF7239283C19CF001D5471 /* TaskQueue.swift in Sources */,
217F39E72406E98400F1A0B3 /* SubscriptionConnectionType.swift in Sources */,
217F39D42406E98400F1A0B3 /* RealtimeConnectionProvider+Websocket.swift in Sources */,
217F39DC2406E98400F1A0B3 /* AppSyncSubscriptionConnection.swift in Sources */,
5CFF7236283C1971001D5471 /* RealtimeConnectionProviderAsync+Websocket.swift in Sources */,
21D38B6D240A262800EC2A8D /* AppSyncJSONHelper.swift in Sources */,
219BFF4B27A3B24F000FC148 /* ConnectivityPath.swift in Sources */,
217F39CE2406E98400F1A0B3 /* ConnectionProviderError.swift in Sources */,
217F39E12406E98400F1A0B3 /* StarscreamAdapter.swift in Sources */,
217F39D72406E98400F1A0B3 /* RealtimeConnectionProvider+ConnectionInterceptable.swift in Sources */,
217F39DE2406E98400F1A0B3 /* AppSyncSubscriptionConnection+ErrorHandler.swift in Sources */,
5CFF7237283C1971001D5471 /* RealtimeConnectionProviderAsync+MessageInterceptableAsync.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1278,22 +1131,14 @@
buildActionMask = 2147483647;
files = (
21D38B89240A39E400EC2A8D /* OIDCAuthInterceptorTests.swift in Sources */,
219BFF6C27A9AF85000FC148 /* MockConnectivityMonitor.swift in Sources */,
217F39F22406EA4000F1A0B3 /* ConnectionProviderTests.swift in Sources */,
2143D46727B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift in Sources */,
FA67507824D3244A005A1345 /* MockWebsocketProvider.swift in Sources */,
217F39F02406EA4000F1A0B3 /* AppSyncSubscriptionConnectionTests.swift in Sources */,
FA67507B24D338CB005A1345 /* Error+Extension.swift in Sources */,
21D38B8C240A39E400EC2A8D /* APIKeyAuthInterceptorTests.swift in Sources */,
5CFF723E283C1AF5001D5471 /* RealtimeConnectionProviderAsyncTestBase.swift in Sources */,
5CFF723D283C1AF5001D5471 /* ConnectionProviderAsyncTests.swift in Sources */,
21D38B8B240A39E400EC2A8D /* AppSyncJSONHelperTests.swift in Sources */,
217F39F32406EA4000F1A0B3 /* RealtimeGatewayURLInterceptorTests.swift in Sources */,
217C74D227C45B6600AE054F /* AppSyncSubscriptionConnectionErrorHandlerTests.swift in Sources */,
FA67507E24D33976005A1345 /* RealtimeConnectionProviderTestBase.swift in Sources */,
219BFF6A27A9AC6E000FC148 /* ConnectivityMonitorTests.swift in Sources */,
2151D5CA27C68C3C00F3C866 /* ConnectionProviderHandleErrorTests.swift in Sources */,
978409BA2739C7E1002362A7 /* AppSyncURLHelperTests.swift in Sources */,
FA67508424D33ACC005A1345 /* CountdownTimerTests.swift in Sources */,
FA67508024D339B0005A1345 /* ConnectionProviderStaleConnectionTests.swift in Sources */,
217F39F12406EA4000F1A0B3 /* MockConnectionProvider.swift in Sources */,
@ -1305,12 +1150,9 @@
buildActionMask = 2147483647;
files = (
21D38B99240C4E1C00EC2A8D /* ConfigurationHelper.swift in Sources */,
5CF3E96D283C33D40036EAD2 /* AppSyncRealTimeClientAsyncIntegrationTests.swift in Sources */,
2164E674263C58CE00385027 /* AppSyncRealTimeClientTestBase.swift in Sources */,
21D38B97240C4DCF00EC2A8D /* Error+Extension.swift in Sources */,
2143D4B227BE40B30066B2F7 /* AppSyncRealTimeClientFailureTests.swift in Sources */,
21D38B9D240C540D00EC2A8D /* TestCommonConstants.swift in Sources */,
5CF3E96B283C33D40036EAD2 /* AppSyncRealTimeClientAsyncFailureTests.swift in Sources */,
21D38B412409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.swift in Sources */,
2164E65D2639AD5600385027 /* StarscreamAdapterTests.swift in Sources */,
);
@ -1433,7 +1275,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@ -1490,7 +1332,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;

View File

@ -45,7 +45,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableThreadSanitizer = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -1,44 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
public enum AppSyncRealTimeClient {
static let lock: NSLocking = NSLock()
static var _logLevel = LogLevel.error // swiftlint:disable:this identifier_name
public static var logLevel: LogLevel {
get {
lock.lock()
defer {
lock.unlock()
}
return _logLevel
}
set {
lock.lock()
defer {
lock.unlock()
}
_logLevel = newValue
}
}
}
public extension AppSyncRealTimeClient {
enum LogLevel: Int {
case error
case warn
case info
case debug
case verbose
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -18,6 +18,7 @@ extension AppSyncSubscriptionConnection {
return
}
if connectionState == .connected {
AppSyncLogger.debug("[AppSyncSubscriptionConnection] \(#function): connection is connected, start subscription.")
startSubscription()
}
}
@ -31,9 +32,7 @@ extension AppSyncSubscriptionConnection {
else {
return
}
AppSyncLogger.debug(
"[AppSyncSubscriptionConnection]: Connection connected, start subscription \(subscriptionItem.identifier)."
)
subscriptionState = .inProgress
guard let payload = convertToPayload(

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -16,6 +16,9 @@ extension AppSyncSubscriptionConnection {
}
guard response.id == subscriptionItem.identifier else {
AppSyncLogger.verbose("""
[AppSyncSubscriptionConnection] \(#function): \(subscriptionItem.identifier). Ignoring data event for \(response.id ?? "(null)")
""")
return
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -15,35 +15,13 @@ extension AppSyncSubscriptionConnection {
return
}
// If the error identifier is not for the this subscription
// If the error identifier is not for the this connection
// we return immediately without handling the error.
if case let ConnectionProviderError.subscription(identifier, _) = error,
identifier != subscriptionItem.identifier {
return
}
if case let ConnectionProviderError.limitExceeded(identifier) = error {
// If the error identifier is not for the this subscription
// we return immediately without handling the error.
if let identifier = identifier, identifier != subscriptionItem.identifier {
return
}
// Limit exceeded without an subscription identifier is an error for the entire connection
// that can be caused by multiple subscriptions trying to subscribe at the same time.
// Return the error on those subscriptions in-progress, and return immediately.
if identifier == nil {
if subscriptionState == .inProgress {
subscriptionState = .notSubscribed
AppSyncSubscriptionConnection.logExtendedErrorInfo(for: error)
subscriptionItem.subscriptionEventHandler(.failed(error), subscriptionItem)
connectionProvider?.removeListener(identifier: subscriptionItem.identifier)
}
return
}
}
AppSyncSubscriptionConnection.logExtendedErrorInfo(for: error)
subscriptionState = .notSubscribed
@ -51,20 +29,17 @@ extension AppSyncSubscriptionConnection {
let connectionError = error as? ConnectionProviderError
else {
subscriptionItem.subscriptionEventHandler(.failed(error), subscriptionItem)
connectionProvider?.removeListener(identifier: subscriptionItem.identifier)
return
}
let retryAdvice = retryHandler.shouldRetryRequest(for: connectionError)
if retryAdvice.shouldRetry, let retryInterval = retryAdvice.retryInterval {
// swiftlint:disable:next line_length
AppSyncLogger.debug("[AppSyncSubscriptionConnection] Retrying subscription \(subscriptionItem.identifier) after \(retryInterval)")
DispatchQueue.global().asyncAfter(deadline: .now() + retryInterval) {
self.connectionProvider?.connect()
}
} else {
subscriptionItem.subscriptionEventHandler(.failed(error), subscriptionItem)
connectionProvider?.removeListener(identifier: subscriptionItem.identifier)
}
}
@ -108,10 +83,8 @@ extension AppSyncSubscriptionConnection {
additionalInfo=\(String(describing: errorPayload))
"""
)
case .unauthorized:
AppSyncLogger.error("ConnectionProviderError.unauthorized")
case .unknown:
AppSyncLogger.error("ConnectionProviderError.unknown")
case .other:
AppSyncLogger.error("ConnectionProviderError.other")
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -81,9 +81,7 @@ public class AppSyncSubscriptionConnection: SubscriptionConnection, RetryableCon
connectionProvider.addListener(identifier: subscriptionItem.identifier) { [weak self] event in
guard let self = self else {
AppSyncLogger.debug(
"[AppSyncSubscriptionConnection]: Subscription (Self) is nil, connection event is not handled."
)
AppSyncLogger.debug("[AppSyncSubscriptionConnection] \(#function): Self is nil, listener is not called.")
return
}
switch event {

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -13,48 +13,10 @@ extension RealtimeConnectionProvider: ConnectionInterceptable {
connectionInterceptors.append(interceptor)
}
public func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL,
completion: @escaping (AppSyncConnectionRequest) -> Void
) {
chainInterceptors(
iterator: connectionInterceptors.makeIterator(),
request: request,
endpoint: endpoint,
completion: completion
)
}
private func chainInterceptors<I: IteratorProtocol>(
iterator: I,
request: AppSyncConnectionRequest,
endpoint: URL,
completion: @escaping (AppSyncConnectionRequest) -> Void
) where I.Element == ConnectionInterceptor {
var mutableIterator = iterator
guard let interceptor = mutableIterator.next() else {
completion(request)
return
}
interceptor.interceptConnection(request, for: endpoint) { interceptedRequest in
self.chainInterceptors(
iterator: mutableIterator,
request: interceptedRequest,
endpoint: endpoint,
completion: completion
)
}
}
// MARK: Deprecated method
public func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL
) -> AppSyncConnectionRequest {
// This is added here for backward compatibility
let finalRequest = connectionInterceptors.reduce(request) { $1.interceptConnection($0, for: endpoint) }
return finalRequest
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -13,46 +13,7 @@ extension RealtimeConnectionProvider: MessageInterceptable {
messageInterceptors.append(interceptor)
}
public func interceptMessage(
_ message: AppSyncMessage,
for endpoint: URL,
completion: @escaping (AppSyncMessage) -> Void
) {
chainInterceptors(
iterator: messageInterceptors.makeIterator(),
message: message,
endpoint: endpoint,
completion: completion
)
}
private func chainInterceptors<I: IteratorProtocol>(
iterator: I,
message: AppSyncMessage,
endpoint: URL,
completion: @escaping (AppSyncMessage) -> Void
) where I.Element == MessageInterceptor {
var mutableIterator = iterator
guard let interceptor = mutableIterator.next() else {
completion(message)
return
}
interceptor.interceptMessage(message, for: endpoint) { interceptedMessage in
self.chainInterceptors(
iterator: mutableIterator,
message: interceptedMessage,
endpoint: endpoint,
completion: completion
)
}
}
// MARK: Deprecated method
public func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) -> AppSyncMessage {
// This is added here for backward compatibility
let finalMessage = messageInterceptors.reduce(message) { $1.interceptMessage($0, for: endpoint) }
return finalMessage
}

View File

@ -1,60 +1,36 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
/// Consolidates usage and parameters passed to the `staleConnectionTimer` methods.
extension RealtimeConnectionProvider {
/// Start a stale connection timer, first invalidating and destroying any existing timer
func startStaleConnectionTimer() {
AppSyncLogger.debug(
"[RealtimeConnectionProvider] Starting stale connection timer for \(staleConnectionTimer.interval)s"
)
staleConnectionTimer.start(interval: RealtimeConnectionProvider.staleConnectionTimeout) {
AppSyncLogger.debug("[RealtimeConnectionProvider] Starting stale connection timer for \(staleConnectionTimeout.get())s")
if staleConnectionTimer != nil {
stopStaleConnectionTimer()
}
staleConnectionTimer = CountdownTimer(interval: staleConnectionTimeout.get()) {
self.disconnectStaleConnection()
}
}
/// Reset the stale connection timer in response to receiving a message from the websocket
func resetStaleConnectionTimer(interval: TimeInterval? = nil) {
AppSyncLogger.verbose("[RealtimeConnectionProvider] Resetting stale connection timer")
staleConnectionTimer.reset(interval: interval)
/// Stop and destroy any existing stale connection timer
func stopStaleConnectionTimer() {
AppSyncLogger.debug("[RealtimeConnectionProvider] Stopping and destroying stale connection timer")
staleConnectionTimer?.invalidate()
staleConnectionTimer = nil
}
/// Stops the timer when disconnecting the websocket.
func invalidateStaleConnectionTimer() {
staleConnectionTimer.invalidate()
}
/// Handle updates from the ConnectivityMonitor
func handleConnectivityUpdates(connectivity: ConnectivityPath) {
connectionQueue.async {[weak self] in
guard let self = self else {
return
}
AppSyncLogger.debug(
"[RealtimeConnectionProvider] Status: \(self.status). Connectivity status: \(connectivity.status)"
)
if self.status == .connected && connectivity.status == .unsatisfied && !self.isStaleConnection {
AppSyncLogger.debug(
"[RealtimeConnectionProvider] Connetion is stale. Pending reconnect on connectivity."
)
self.isStaleConnection = true
} else if self.status == .connected && self.isStaleConnection && connectivity.status == .satisfied {
AppSyncLogger.debug(
"[RealtimeConnectionProvider] Connetion is stale. Disconnecting to begin reconnect."
)
self.staleConnectionTimer.invalidate()
self.disconnectStaleConnection()
}
}
/// Reset the stale connection timer in response to receiving a message
func resetStaleConnectionTimer() {
AppSyncLogger.debug("[RealtimeConnectionProvider] Resetting stale connection timer")
staleConnectionTimer?.resetCountdown()
}
/// Fired when the stale connection timer expires
@ -65,9 +41,9 @@ extension RealtimeConnectionProvider {
}
AppSyncLogger.error("[RealtimeConnectionProvider] Realtime connection is stale, disconnecting.")
self.status = .notConnected
self.isStaleConnection = false
self.websocket.disconnect()
self.updateCallback(event: .error(ConnectionProviderError.connection))
}
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -53,12 +53,7 @@ extension RealtimeConnectionProvider: AppSyncWebsocketDelegate {
self?.handleConnectionAck(response: response)
}
case .error:
AppSyncLogger.verbose("[RealtimeConnectionProvider] received error")
connectionQueue.async { [weak self] in
self?.handleError(response: response)
}
case .connectionError:
AppSyncLogger.verbose("[RealtimeConnectionProvider] received error")
AppSyncLogger.debug("[RealtimeConnectionProvider] received error")
connectionQueue.async { [weak self] in
self?.handleError(response: response)
}
@ -67,7 +62,7 @@ extension RealtimeConnectionProvider: AppSyncWebsocketDelegate {
updateCallback(event: .data(appSyncResponse))
}
case .keepAlive:
AppSyncLogger.verbose("[RealtimeConnectionProvider] received keepAlive")
AppSyncLogger.debug("[RealtimeConnectionProvider] received keepAlive")
}
}
@ -85,6 +80,16 @@ extension RealtimeConnectionProvider: AppSyncWebsocketDelegate {
status = .connected
updateCallback(event: .connection(status))
if let overrideConnectionTimeoutInSeconds = overrideConnectionTimeoutInSeconds {
AppSyncLogger.debug(
"""
`overrideConnectionTimeoutInSeconds` exists: \(overrideConnectionTimeoutInSeconds) seconds.
Ignoring service `connectionTimeoutMs`.
"""
)
return
}
// If the service returns a connection timeout, use that instead of the default
guard case let .number(value) = response.payload?["connectionTimeoutMs"] else {
return
@ -92,7 +97,8 @@ extension RealtimeConnectionProvider: AppSyncWebsocketDelegate {
let interval = value / 1_000
guard interval != staleConnectionTimer.interval else {
// Only use the service value if it is not equal to the one set already
guard interval != staleConnectionTimer?.interval else {
return
}
@ -102,28 +108,40 @@ extension RealtimeConnectionProvider: AppSyncWebsocketDelegate {
instructions: \(interval)s
"""
)
resetStaleConnectionTimer(interval: interval)
staleConnectionTimeout.set(interval)
startStaleConnectionTimer()
}
/// Resolves & dispatches errors from `response`.
///
/// - Warning: This method must be invoked on the `connectionQueue`
func handleError(response: RealtimeConnectionProviderResponse) {
// If we get an error while the connection was inProgress state,
let error = response.toConnectionProviderError(connectionState: status)
if status == .inProgress {
private func handleError(response: RealtimeConnectionProviderResponse) {
// If we get an error in connection inprogress state, return back as connection error.
guard status != .inProgress else {
status = .notConnected
updateCallback(event: .error(ConnectionProviderError.connection))
return
}
// If limit exceeded is for a particular subscription identifier, throttle using `limitExceededSubject`
if case .limitExceeded(let id) = error,
id == nil,
#available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) {
self.limitExceededSubject.send(error)
} else {
updateCallback(event: .error(error))
// Return back as generic error if there is no identifier.
guard let identifier = response.id else {
let genericError = ConnectionProviderError.other
updateCallback(event: .error(genericError))
return
}
// Map to limit exceed error if we get MaxSubscriptionsReachedException
if let errorType = response.payload?["errorType"],
errorType == "MaxSubscriptionsReachedException" {
let limitExceedError = ConnectionProviderError.limitExceeded(identifier)
updateCallback(event: .error(limitExceedError))
return
}
let subscriptionError = ConnectionProviderError.subscription(identifier, response.payload)
updateCallback(event: .error(subscriptionError))
}
}
extension RealtimeConnectionProviderResponse {

View File

@ -1,21 +1,16 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
import Combine
/// Appsync Real time connection that connects to subscriptions
/// through websocket.
public class RealtimeConnectionProvider: ConnectionProvider {
/// Maximum number of seconds a connection may go without receiving a keep alive
/// message before we consider it stale and force a disconnect
static let staleConnectionTimeout: TimeInterval = 5 * 60
private let urlRequest: URLRequest
private let url: URL
var listeners: [String: ConnectionProviderCallback]
let websocket: AppSyncWebsocketProvider
@ -24,13 +19,17 @@ public class RealtimeConnectionProvider: ConnectionProvider {
var messageInterceptors: [MessageInterceptor]
var connectionInterceptors: [ConnectionInterceptor]
/// Maximum number of seconds a connection may go without receiving a keep alive
/// message before we consider it stale and force a disconnect
let staleConnectionTimeout: AtomicValue<TimeInterval>
/// Optional overide for taking client side precendent over service's `connectionTimeoutMs`
var overrideConnectionTimeoutInSeconds: Int?
/// A timer that automatically disconnects the current connection if it goes longer
/// than `staleConnectionTimeout` without activity. Receiving any data or "keep
/// alive" message will cause the timer to be reset to the full interval.
var staleConnectionTimer: CountdownTimer
/// Intermediate state when the connection is connected and connectivity updates to unsatisfied (offline)
var isStaleConnection: Bool
var staleConnectionTimer: CountdownTimer?
/// Manages concurrency for socket connections, disconnections, writes, and status reports.
///
@ -38,58 +37,29 @@ public class RealtimeConnectionProvider: ConnectionProvider {
/// handled one at a time.
let connectionQueue: DispatchQueue
/// Monitor for connectivity updates
let connectivityMonitor: ConnectivityMonitor
/// The serial queue on which status & message callbacks from the web socket are invoked.
private let serialCallbackQueue: DispatchQueue
private let serialCallbackQueue = DispatchQueue(
label: "com.amazonaws.AppSyncRealTimeConnectionProvider.callbackQueue"
)
/// Throttle when AppSync sends LimitExceeded error. High rate of subscriptions requests will cause AppSync to send
/// connection level LimitExceeded errors for each subscribe made. A connection level error means that there is no
/// subscription id associated with the error. When handling these errors, all subscriptions will receive a message
/// for the error. Use this subject to send and throttle the errors on the client side.
var limitExceededThrottleSink: Any?
var iLimitExceededSubject: Any?
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
var limitExceededSubject: PassthroughSubject<ConnectionProviderError, Never> {
if iLimitExceededSubject == nil {
iLimitExceededSubject = PassthroughSubject<ConnectionProviderError, Never>()
}
return iLimitExceededSubject as! PassthroughSubject<ConnectionProviderError, Never> // swiftlint:disable:this force_cast line_length
}
public convenience init(for urlRequest: URLRequest, websocket: AppSyncWebsocketProvider) {
self.init(urlRequest: urlRequest, websocket: websocket)
}
init(
urlRequest: URLRequest,
websocket: AppSyncWebsocketProvider,
connectionQueue: DispatchQueue = DispatchQueue(
label: "com.amazonaws.AppSyncRealTimeConnectionProvider.serialQueue"
),
serialCallbackQueue: DispatchQueue = DispatchQueue(
label: "com.amazonaws.AppSyncRealTimeConnectionProvider.callbackQueue"
),
connectivityMonitor: ConnectivityMonitor = ConnectivityMonitor()
) {
self.urlRequest = urlRequest
public init(for url: URL, websocket: AppSyncWebsocketProvider, overrideConnectionTimeoutInSeconds: Int? = nil) {
self.url = url
self.websocket = websocket
self.listeners = [:]
self.status = .notConnected
self.messageInterceptors = []
self.connectionInterceptors = []
self.staleConnectionTimer = CountdownTimer()
self.isStaleConnection = false
self.connectionQueue = connectionQueue
self.serialCallbackQueue = serialCallbackQueue
self.connectivityMonitor = connectivityMonitor
connectivityMonitor.start(onUpdates: handleConnectivityUpdates(connectivity:))
if #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) {
subscribeToLimitExceededThrottle()
self.overrideConnectionTimeoutInSeconds = overrideConnectionTimeoutInSeconds
if let overrideConnectionTimeoutInSeconds = overrideConnectionTimeoutInSeconds {
self.staleConnectionTimeout = AtomicValue(initialValue: Double(overrideConnectionTimeoutInSeconds))
} else {
self.staleConnectionTimeout = AtomicValue(initialValue: 5 * 60)
}
self.connectionQueue = DispatchQueue(
label: "com.amazonaws.AppSyncRealTimeConnectionProvider.serialQueue"
)
}
// MARK: - ConnectionProvider methods
@ -103,29 +73,16 @@ public class RealtimeConnectionProvider: ConnectionProvider {
self.updateCallback(event: .connection(self.status))
return
}
guard let url = self.urlRequest.url else {
self.updateCallback(event: .error(ConnectionProviderError.unknown(
message: "Missing URL",
payload: nil
)))
return
}
self.status = .inProgress
self.updateCallback(event: .connection(self.status))
let request = AppSyncConnectionRequest(url: url)
self.interceptConnection(request, for: url) { signedRequest in
var urlRequest = self.urlRequest
urlRequest.url = signedRequest.url
DispatchQueue.global().async {
self.websocket.connect(
urlRequest: urlRequest,
protocols: ["graphql-ws"],
delegate: self
)
}
let request = AppSyncConnectionRequest(url: self.url)
let signedRequest = self.interceptConnection(request, for: self.url)
DispatchQueue.global().async {
self.websocket.connect(
url: signedRequest.url,
protocols: ["graphql-ws"],
delegate: self
)
}
}
}
@ -136,34 +93,26 @@ public class RealtimeConnectionProvider: ConnectionProvider {
guard let self = self else {
return
}
guard let url = self.urlRequest.url else {
self.updateCallback(event: .error(ConnectionProviderError.unknown(
message: "Missing URL",
payload: nil
)))
return
}
self.interceptMessage(message, for: url) { signedMessage in
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(signedMessage)
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
let jsonError = ConnectionProviderError.jsonParse(message.id, nil)
self.updateCallback(event: .error(jsonError))
return
}
self.websocket.write(message: jsonString)
} catch {
AppSyncLogger.error(error)
switch message.messageType {
case .connectionInit:
self.receivedConnectionInit()
default:
self.updateCallback(event: .error(ConnectionProviderError.jsonParse(message.id, error)))
}
let signedMessage = self.interceptMessage(message, for: self.url)
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(signedMessage)
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
let jsonError = ConnectionProviderError.jsonParse(message.id, nil)
self.updateCallback(event: .error(jsonError))
return
}
self.websocket.write(message: jsonString)
} catch {
AppSyncLogger.error(error)
switch message.messageType {
case .connectionInit:
self.receivedConnectionInit()
default:
self.updateCallback(event: .error(ConnectionProviderError.jsonParse(message.id, error)))
}
}
}
}
@ -171,7 +120,8 @@ public class RealtimeConnectionProvider: ConnectionProvider {
public func disconnect() {
connectionQueue.async {
self.websocket.disconnect()
self.invalidateStaleConnectionTimer()
self.staleConnectionTimer?.invalidate()
self.staleConnectionTimer = nil
}
}
@ -190,12 +140,11 @@ public class RealtimeConnectionProvider: ConnectionProvider {
self.listeners.removeValue(forKey: identifier)
if self.listeners.isEmpty {
AppSyncLogger.debug(
"[RealtimeConnectionProvider] all subscriptions removed, disconnecting websocket connection."
)
AppSyncLogger.debug("[RealtimeConnectionProvider] all subscriptions removed, disconnecting websocket connection.")
self.status = .notConnected
self.websocket.disconnect()
self.invalidateStaleConnectionTimer()
self.staleConnectionTimer?.invalidate()
self.staleConnectionTimer = nil
}
}
}
@ -222,30 +171,6 @@ public class RealtimeConnectionProvider: ConnectionProvider {
}
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
func subscribeToLimitExceededThrottle() {
limitExceededThrottleSink = limitExceededSubject
.filter {
// Make sure the limitExceeded error is a connection level error (no subscription id present).
// When id is present, it is passed back directly subscription via `updateCallback`.
if case .limitExceeded(let id) = $0, id == nil {
return true
}
return false
}
.throttle(for: .milliseconds(150), scheduler: connectionQueue, latest: true)
.sink { completion in
switch completion {
case .failure(let error):
AppSyncLogger.verbose("limitExceededThrottleSink failed \(error)")
case .finished:
AppSyncLogger.verbose("limitExceededThrottleSink finished")
}
} receiveValue: { result in
self.updateCallback(event: .error(result))
}
}
/// - Warning: This must be invoked from the `connectionQueue`
private func receivedConnectionInit() {
status = .notConnected

View File

@ -1,14 +1,12 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
/// More information about the response can be found here
/// https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#connection-init-message
struct RealtimeConnectionProviderResponse {
/// Subscription Identifier
@ -44,8 +42,6 @@ enum RealtimeConnectionProviderResponseType: String, Decodable {
case data
case error
case connectionError = "connection_error"
}
extension RealtimeConnectionProviderResponse: Decodable {
@ -56,122 +52,3 @@ extension RealtimeConnectionProviderResponse: Decodable {
case responseType = "type"
}
}
/// Helper methods to check which type of errors, such as `MaxSubscriptionsReachedError`, `LimitExceededError`.
/// Errors have the following shape
///
/// "type": "error": A constant <string> parameter.
/// "id": <string>: The ID of the corresponding registered subscription, if relevant.
/// "payload" <Object>: An object that contains the corresponding error information.
///
/// More information can be found here
/// https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#error-message
extension RealtimeConnectionProviderResponse {
func toConnectionProviderError(connectionState: ConnectionState) -> ConnectionProviderError {
// If it is Unauthorized, return `.unauthorized` error.
guard !isUnauthorizationError() else {
return .unauthorized
}
// If it is in-progress, return `.connection`.
guard connectionState != .inProgress else {
return .connection
}
if isLimitExceededError() || isMaxSubscriptionReachedError() {
// Is it observed that LimitExceeded error does not have `id` while MaxSubscriptionReached does have a
// corresponding identifier. Both are mapped to `limitExceeded` with optional identifier.
return .limitExceeded(id)
}
// If the type of error is not handled (by checking `isLimitExceededError`, `isMaxSubscriptionReachedError`,
// etc), and is not for a specific subscription, then return unknown error
guard let identifier = id else {
return .unknown(message: nil, causedBy: nil, payload: payload)
}
// Default scenario - return the error with subscription id and error payload.
return .subscription(identifier, payload)
}
func isMaxSubscriptionReachedError() -> Bool {
// It is expected to contain payload with corresponding error information
guard let payload = payload else {
return false
}
// Keep this here for backwards compatibility for previously provisioned AppSync services
if let errorType = payload["errorType"],
errorType == "MaxSubscriptionsReachedException" {
return true
}
// The observed response from the service
// { "id":"DB23EC80-C51A-4FEE-82F7-AA4949B4F299",
// "type":"error",
// "payload": {
// "errors": {
// "errorType":"MaxSubscriptionsReachedError",
// "message":"Max number of 100 subscriptions reached" }}}
if let errors = payload["errors"],
case let .object(errorsObject) = errors,
let errorType = errorsObject["errorType"],
errorType == "MaxSubscriptionsReachedError" {
return true
}
return false
}
func isLimitExceededError() -> Bool {
// It is expected to contain payload with corresponding error information
guard let payload = payload else {
return false
}
// The observed response from the service
// { "type":"error",
// "payload": {
// "errors": {
// "errorType":"LimitExceededError",
// "message":"Rate limit exceeded" }}}
if let errors = payload["errors"],
case let .object(errorsObject) = errors,
let errorType = errorsObject["errorType"],
errorType == "LimitExceededError" {
return true
}
return false
}
func isUnauthorizationError() -> Bool {
// It is expected to contain payload with corresponding error information
guard let payload = payload,
let errors = payload["errors"],
case let .array(errorsArray) = errors else {
return false
}
// The observed response from the service
// { "payload": {
// "errors": [{
// "errorType":"com.amazonaws.deepdish.graphql.auth#UnauthorizedException",
// "message":"You are not authorized to make this call.",
// "errorCode":400 }]},
// "type":"connection_error" }
return errorsArray.contains { error in
guard case let .object(errorObject) = error,
case let .string(errorObjectString) = errorObject["errorType"] else {
return false
}
if errorObjectString.contains("UnauthorizedException") {
return true
}
return false
}
}
}

View File

@ -1,32 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
#if swift(>=5.5.2)
import Foundation
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension RealtimeConnectionProviderAsync: ConnectionInterceptableAsync {
public func addInterceptor(_ interceptor: ConnectionInterceptorAsync) {
connectionInterceptors.append(interceptor)
}
public func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL
) async -> AppSyncConnectionRequest {
var finalRequest = request
for interceptor in connectionInterceptors {
finalRequest = await interceptor.interceptConnection(finalRequest, for: endpoint)
}
return finalRequest
}
}
#endif

View File

@ -1,28 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
#if swift(>=5.5.2)
import Foundation
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension RealtimeConnectionProviderAsync: MessageInterceptableAsync {
public func addInterceptor(_ interceptor: MessageInterceptorAsync) {
messageInterceptors.append(interceptor)
}
public func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) async -> AppSyncMessage {
var finalMessage = message
for interceptor in messageInterceptors {
finalMessage = await interceptor.interceptMessage(finalMessage, for: endpoint)
}
return finalMessage
}
}
#endif

View File

@ -1,77 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
#if swift(>=5.5.2)
import Foundation
/// Consolidates usage and parameters passed to the `staleConnectionTimer` methods.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension RealtimeConnectionProviderAsync {
/// Start a stale connection timer, first invalidating and destroying any existing timer
func startStaleConnectionTimer() {
AppSyncLogger.debug(
"[RealtimeConnectionProvider] Starting stale connection timer for \(staleConnectionTimer.interval)s"
)
staleConnectionTimer.start(interval: RealtimeConnectionProviderAsync.staleConnectionTimeout) {
self.disconnectStaleConnection()
}
}
/// Reset the stale connection timer in response to receiving a message from the websocket
func resetStaleConnectionTimer(interval: TimeInterval? = nil) {
AppSyncLogger.verbose("[RealtimeConnectionProvider] Resetting stale connection timer")
staleConnectionTimer.reset(interval: interval)
}
/// Stops the timer when disconnecting the websocket.
func invalidateStaleConnectionTimer() {
staleConnectionTimer.invalidate()
}
/// Handle updates from the ConnectivityMonitor
func handleConnectivityUpdates(connectivity: ConnectivityPath) {
taskQueue.async { [weak self] in
guard let self = self else {
return
}
AppSyncLogger.debug(
"[RealtimeConnectionProvider] Status: \(self.status). Connectivity status: \(connectivity.status)"
)
if self.status == .connected && connectivity.status == .unsatisfied && !self.isStaleConnection {
AppSyncLogger.debug(
"[RealtimeConnectionProvider] Connetion is stale. Pending reconnect on connectivity."
)
self.isStaleConnection = true
} else if self.status == .connected && self.isStaleConnection && connectivity.status == .satisfied {
AppSyncLogger.debug(
"[RealtimeConnectionProvider] Connetion is stale. Disconnecting to begin reconnect."
)
self.staleConnectionTimer.invalidate()
self.disconnectStaleConnection()
}
}
}
/// Fired when the stale connection timer expires
private func disconnectStaleConnection() {
taskQueue.async { [weak self] in
guard let self = self else {
return
}
AppSyncLogger.error("[RealtimeConnectionProvider] Realtime connection is stale, disconnecting.")
self.status = .notConnected
self.isStaleConnection = false
self.websocket.disconnect()
self.updateCallback(event: .error(ConnectionProviderError.connection))
}
}
}
#endif

View File

@ -1,130 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
#if swift(>=5.5.2)
import Foundation
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension RealtimeConnectionProviderAsync: AppSyncWebsocketDelegate {
public func websocketDidConnect(provider: AppSyncWebsocketProvider) {
// Call the ack to finish the connection handshake
// Inform the callback when ack gives back a response.
AppSyncLogger.debug("[RealtimeConnectionProvider] WebsocketDidConnect, sending init message")
sendConnectionInitMessage()
startStaleConnectionTimer()
}
public func websocketDidDisconnect(provider: AppSyncWebsocketProvider, error: Error?) {
taskQueue.async { [weak self] in
guard let self = self else {
return
}
self.status = .notConnected
guard error != nil else {
self.updateCallback(event: .connection(self.status))
return
}
self.updateCallback(event: .error(ConnectionProviderError.connection))
}
}
public func websocketDidReceiveData(provider: AppSyncWebsocketProvider, data: Data) {
do {
let response = try JSONDecoder().decode(RealtimeConnectionProviderResponse.self, from: data)
handleResponse(response)
} catch {
AppSyncLogger.error(error)
updateCallback(event: .error(ConnectionProviderError.jsonParse(nil, error)))
}
}
// MARK: - Handle websocket response
private func handleResponse(_ response: RealtimeConnectionProviderResponse) {
resetStaleConnectionTimer()
switch response.responseType {
case .connectionAck:
AppSyncLogger.debug("[RealtimeConnectionProvider] received connectionAck")
taskQueue.async { [weak self] in
self?.handleConnectionAck(response: response)
}
case .error:
AppSyncLogger.verbose("[RealtimeConnectionProvider] received error")
taskQueue.async { [weak self] in
self?.handleError(response: response)
}
case .connectionError:
AppSyncLogger.verbose("[RealtimeConnectionProvider] received error")
taskQueue.async { [weak self] in
self?.handleError(response: response)
}
case .subscriptionAck, .unsubscriptionAck, .data:
if let appSyncResponse = response.toAppSyncResponse() {
updateCallback(event: .data(appSyncResponse))
}
case .keepAlive:
AppSyncLogger.verbose("[RealtimeConnectionProvider] received keepAlive")
}
}
/// Updates connection status callbacks and sets stale connection timeout
///
/// - Warning: This method must be invoked on the `connectionQueue`
private func handleConnectionAck(response: RealtimeConnectionProviderResponse) {
// Only from in progress state, the connection can transition to connected state.
// The below guard statement make sure that. If we get connectionAck in other
// state means that we have initiated a disconnect parallely.
guard status == .inProgress else {
return
}
status = .connected
updateCallback(event: .connection(status))
// If the service returns a connection timeout, use that instead of the default
guard case let .number(value) = response.payload?["connectionTimeoutMs"] else {
return
}
let interval = value / 1_000
guard interval != staleConnectionTimer.interval else {
return
}
AppSyncLogger.debug(
"""
Resetting keep alive timer in response to service timeout \
instructions: \(interval)s
"""
)
resetStaleConnectionTimer(interval: interval)
}
/// Resolves & dispatches errors from `response`.
///
/// - Warning: This method must be invoked on the `connectionQueue`
func handleError(response: RealtimeConnectionProviderResponse) {
// If we get an error while the connection was inProgress state,
let error = response.toConnectionProviderError(connectionState: status)
if status == .inProgress {
status = .notConnected
}
// If limit exceeded is for a particular subscription identifier, throttle using `limitExceededSubject`
if case .limitExceeded(let id) = error, id == nil {
self.limitExceededSubject.send(error)
} else {
updateCallback(event: .error(error))
}
}
}
#endif

View File

@ -1,242 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
#if swift(>=5.5.2)
import Foundation
import Combine
/// Appsync Real time connection that connects to subscriptions
/// through websocket.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public class RealtimeConnectionProviderAsync: ConnectionProvider {
/// Maximum number of seconds a connection may go without receiving a keep alive
/// message before we consider it stale and force a disconnect
static let staleConnectionTimeout: TimeInterval = 5 * 60
let urlRequest: URLRequest
var listeners: [String: ConnectionProviderCallback]
let websocket: AppSyncWebsocketProvider
var status: ConnectionState
var messageInterceptors = [MessageInterceptorAsync]()
var connectionInterceptors = [ConnectionInterceptorAsync]()
/// A timer that automatically disconnects the current connection if it goes longer
/// than `staleConnectionTimeout` without activity. Receiving any data or "keep
/// alive" message will cause the timer to be reset to the full interval.
var staleConnectionTimer: CountdownTimer
/// Intermediate state when the connection is connected and connectivity updates to unsatisfied (offline)
var isStaleConnection: Bool
/// Manages concurrency for socket connections, disconnections, writes, and status reports.
///
/// Each connection request will be sent to this queue. Connection request are
/// handled one at a time.
let taskQueue = TaskQueue<Void>()
/// Monitor for connectivity updates
let connectivityMonitor: ConnectivityMonitor
/// The serial queue on which status & message callbacks from the web socket are invoked.
private let serialCallbackQueue: DispatchQueue
/// Throttle when AppSync sends LimitExceeded error. High rate of subscriptions requests will cause AppSync to send
/// connection level LimitExceeded errors for each subscribe made. A connection level error means that there is no
/// subscription id associated with the error. When handling these errors, all subscriptions will receive a message
/// for the error. Use this subject to send and throttle the errors on the client side.
var limitExceededThrottleSink: Any?
var iLimitExceededSubject: Any?
var limitExceededSubject: PassthroughSubject<ConnectionProviderError, Never> {
if iLimitExceededSubject == nil {
iLimitExceededSubject = PassthroughSubject<ConnectionProviderError, Never>()
}
// swiftlint:disable:next force_cast
return iLimitExceededSubject as! PassthroughSubject<ConnectionProviderError, Never>
}
init(
urlRequest: URLRequest,
websocket: AppSyncWebsocketProvider,
serialCallbackQueue: DispatchQueue = DispatchQueue(
label: "com.amazonaws.AppSyncRealTimeConnectionProvider.callbackQueue"
),
connectivityMonitor: ConnectivityMonitor = ConnectivityMonitor()
) {
self.urlRequest = urlRequest
self.websocket = websocket
self.listeners = [:]
self.status = .notConnected
self.staleConnectionTimer = CountdownTimer()
self.isStaleConnection = false
self.serialCallbackQueue = serialCallbackQueue
self.connectivityMonitor = connectivityMonitor
connectivityMonitor.start(onUpdates: handleConnectivityUpdates(connectivity:))
subscribeToLimitExceededThrottle()
}
public convenience init(for urlRequest: URLRequest, websocket: AppSyncWebsocketProvider) {
self.init(urlRequest: urlRequest, websocket: websocket)
}
// MARK: - ConnectionProvider methods
public func connect() {
taskQueue.async { [weak self] in
guard let self = self else {
return
}
guard self.status == .notConnected else {
self.updateCallback(event: .connection(self.status))
return
}
guard let url = self.urlRequest.url else {
self.updateCallback(event: .error(ConnectionProviderError.unknown(
message: "Missing URL",
payload: nil
)))
return
}
self.status = .inProgress
self.updateCallback(event: .connection(self.status))
let request = AppSyncConnectionRequest(url: url)
let signedRequest = await self.interceptConnection(request, for: url)
var urlRequest = self.urlRequest
urlRequest.url = signedRequest.url
self.websocket.connect(
urlRequest: urlRequest,
protocols: ["graphql-ws"],
delegate: self
)
}
}
public func write(_ message: AppSyncMessage) {
taskQueue.async { [weak self] in
guard let self = self else {
return
}
guard let url = self.urlRequest.url else {
self.updateCallback(event: .error(ConnectionProviderError.unknown(
message: "Missing URL",
payload: nil
)))
return
}
let signedMessage = await self.interceptMessage(message, for: url)
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(signedMessage)
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
let jsonError = ConnectionProviderError.jsonParse(signedMessage.id, nil)
self.updateCallback(event: .error(jsonError))
return
}
self.websocket.write(message: jsonString)
} catch {
AppSyncLogger.error(error)
switch signedMessage.messageType {
case .connectionInit:
self.receivedConnectionInit()
default:
self.updateCallback(event: .error(ConnectionProviderError.jsonParse(signedMessage.id, error)))
}
}
}
}
public func disconnect() {
taskQueue.async {
self.websocket.disconnect()
self.invalidateStaleConnectionTimer()
}
}
public func addListener(identifier: String, callback: @escaping ConnectionProviderCallback) {
taskQueue.async { [weak self] in
self?.listeners[identifier] = callback
}
}
public func removeListener(identifier: String) {
taskQueue.async { [weak self] in
guard let self = self else {
return
}
self.listeners.removeValue(forKey: identifier)
if self.listeners.isEmpty {
AppSyncLogger.debug(
"[RealtimeConnectionProvider] all subscriptions removed, disconnecting websocket connection."
)
self.status = .notConnected
self.websocket.disconnect()
self.invalidateStaleConnectionTimer()
}
}
}
// MARK: -
func sendConnectionInitMessage() {
let message = AppSyncMessage(type: .connectionInit("connection_init"))
write(message)
}
/// Invokes all registered listeners with `event`. The event is dispatched on `serialCallbackQueue`,
/// but internally this method uses the connectionQueue to get the currently registered listeners.
///
/// - Parameter event: The connection event to dispatch
func updateCallback(event: ConnectionProviderEvent) {
taskQueue.async { [weak self] in
guard let self = self else {
return
}
let allListeners = Array(self.listeners.values)
self.serialCallbackQueue.async {
allListeners.forEach { $0(event) }
}
}
}
func subscribeToLimitExceededThrottle() {
limitExceededThrottleSink = limitExceededSubject
.filter {
// Make sure the limitExceeded error is a connection level error (no subscription id present).
// When id is present, it is passed back directly subscription via `updateCallback`.
if case .limitExceeded(_?) = $0 {
return false
}
return true
}
.throttle(for: .milliseconds(150), scheduler: serialCallbackQueue, latest: true)
.sink { completion in
switch completion {
case .failure(let error):
AppSyncLogger.verbose("limitExceededThrottleSink failed \(error)")
case .finished:
AppSyncLogger.verbose("limitExceededThrottleSink finished")
}
} receiveValue: { result in
self.updateCallback(event: .error(result))
}
}
/// - Warning: This must be invoked from the `taskQueue`
private func receivedConnectionInit() {
status = .notConnected
updateCallback(event: .error(ConnectionProviderError.connection))
}
}
#endif

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -23,9 +23,6 @@ public enum ConnectionProviderError: Error {
/// payload in dictionary format.
case subscription(String, [String: Any]?)
/// Caused when not authorized to establish the connection.
case unauthorized
/// Unknown error
case unknown(message: String? = nil, causedBy: Error? = nil, payload: [String: Any]?)
/// Any other error is identified by this type
case other
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -8,20 +8,19 @@
import Foundation
/// Create connection providers to connect to the websocket endpoint of the AppSync endpoint.
public enum ConnectionProviderFactory {
public struct ConnectionProviderFactory {
public static func createConnectionProvider(
for urlRequest: URLRequest,
for url: URL,
authInterceptor: AuthInterceptor,
connectionType: SubscriptionConnectionType
connectionType: SubscriptionConnectionType,
overrideConnectionTimeoutInSeconds: Int?
) -> ConnectionProvider {
let provider: ConnectionProvider
switch connectionType {
case .appSyncRealtime:
let websocketProvider = StarscreamAdapter()
provider = RealtimeConnectionProvider(for: urlRequest, websocket: websocketProvider)
}
let provider = ConnectionProviderFactory.createConnectionProvider(
for: url,
connectionType: connectionType,
overrideConnectionTimeoutInSeconds: overrideConnectionTimeoutInSeconds
)
if let messageInterceptable = provider as? MessageInterceptable {
messageInterceptable.addInterceptor(authInterceptor)
@ -34,30 +33,20 @@ public enum ConnectionProviderFactory {
return provider
}
#if swift(>=5.5.2)
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public static func createConnectionProviderAsync(
for urlRequest: URLRequest,
authInterceptor: AuthInterceptorAsync,
connectionType: SubscriptionConnectionType
static func createConnectionProvider(
for url: URL,
connectionType: SubscriptionConnectionType,
overrideConnectionTimeoutInSeconds: Int?
) -> ConnectionProvider {
let provider: ConnectionProvider
switch connectionType {
case .appSyncRealtime:
let websocketProvider = StarscreamAdapter()
provider = RealtimeConnectionProviderAsync(for: urlRequest, websocket: websocketProvider)
let connectionProvider = RealtimeConnectionProvider(
for: url,
websocket: websocketProvider,
overrideConnectionTimeoutInSeconds: overrideConnectionTimeoutInSeconds
)
return connectionProvider
}
if let messageInterceptable = provider as? MessageInterceptableAsync {
messageInterceptable.addInterceptor(authInterceptor)
}
if let connectionInterceptable = provider as? ConnectionInterceptableAsync {
connectionInterceptable.addInterceptor(RealtimeGatewayURLInterceptor())
connectionInterceptable.addInterceptor(authInterceptor)
}
return provider
}
#endif
}

View File

@ -1,56 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
public extension ConnectionInterceptable {
func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL,
completion: @escaping (AppSyncConnectionRequest) -> Void
) {
let result = interceptConnection(request, for: endpoint)
completion(result)
}
}
public extension MessageInterceptable {
func interceptMessage(
_ message: AppSyncMessage,
for endpoint: URL,
completion: @escaping (AppSyncMessage) -> Void
) {
let result = interceptMessage(message, for: endpoint)
completion(result)
}
}
public extension ConnectionInterceptor {
func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL,
completion: @escaping (AppSyncConnectionRequest) -> Void
) {
let result = interceptConnection(request, for: endpoint)
completion(result)
}
}
public extension MessageInterceptor {
func interceptMessage(
_ message: AppSyncMessage,
for endpoint: URL,
completion: @escaping (AppSyncMessage) -> Void
) {
let result = interceptMessage(message, for: endpoint)
completion(result)
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -15,86 +15,24 @@ public protocol ConnectionInterceptable {
/// - Parameter interceptor: interceptor to be added
func addInterceptor(_ interceptor: ConnectionInterceptor)
@available(
*,
deprecated,
message:
"""
Use the async version under ConnectionInterceptableAsync or completion handler flavor
"""
)
func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL
) -> AppSyncConnectionRequest
func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL,
completion: @escaping (AppSyncConnectionRequest) -> Void
)
func interceptConnection(_ request: AppSyncConnectionRequest, for endpoint: URL) -> AppSyncConnectionRequest
}
public protocol MessageInterceptable {
func addInterceptor(_ interceptor: MessageInterceptor)
@available(
*,
deprecated,
message:
"""
Use the async version under MessageInterceptableAsync or completion handler flavor
"""
)
func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) -> AppSyncMessage
func interceptMessage(
_ message: AppSyncMessage,
for endpoint: URL,
completion: @escaping (AppSyncMessage) -> Void
)
}
public protocol ConnectionInterceptor {
@available(
*,
deprecated,
message:
"""
Use the async version under ConnectionInterceptorAsync or completion handler flavor
"""
)
func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL
) -> AppSyncConnectionRequest
func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL,
completion: @escaping (AppSyncConnectionRequest) -> Void
)
func interceptConnection(_ request: AppSyncConnectionRequest, for endpoint: URL) -> AppSyncConnectionRequest
}
public protocol MessageInterceptor {
@available(
*,
deprecated,
message:
"""
Use the async version under MessageInterceptorAsync or completion handler flavor
"""
)
func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) -> AppSyncMessage
func interceptMessage(
_ message: AppSyncMessage,
for endpoint: URL,
completion: @escaping (AppSyncMessage) -> Void
)
}
public protocol AuthInterceptor: MessageInterceptor, ConnectionInterceptor {}

View File

@ -1,46 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ConnectionInterceptableAsync {
#if swift(>=5.5.2)
/// Add a new interceptor to the object.
///
/// - Parameter interceptor: interceptor to be added
func addInterceptor(_ interceptor: ConnectionInterceptorAsync)
func interceptConnection(_ request: AppSyncConnectionRequest, for endpoint: URL) async -> AppSyncConnectionRequest
#endif
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol MessageInterceptableAsync {
#if swift(>=5.5.2)
func addInterceptor(_ interceptor: MessageInterceptorAsync)
func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) async -> AppSyncMessage
#endif
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ConnectionInterceptorAsync {
#if swift(>=5.5.2)
func interceptConnection(_ request: AppSyncConnectionRequest, for endpoint: URL) async -> AppSyncConnectionRequest
#endif
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol MessageInterceptorAsync {
#if swift(>=5.5.2)
func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) async -> AppSyncMessage
#endif
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol AuthInterceptorAsync: MessageInterceptorAsync, ConnectionInterceptorAsync {}

View File

@ -1,93 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
import Network
typealias ConnectivityUpdates = (ConnectivityPath) -> Void
protocol AnyConnectivityMonitor {
func start(connectivityUpdatesQueue: DispatchQueue, onConnectivityUpdates: @escaping ConnectivityUpdates)
func cancel()
}
class ConnectivityMonitor {
private let connectivityUpdatesQueue = DispatchQueue(
label: "com.amazonaws.ConnectivityMonitor.connectivityUpdatesQueue",
qos: .background
)
private var monitor: AnyConnectivityMonitor?
init(monitor: AnyConnectivityMonitor? = nil) {
self.monitor = monitor
}
func start(onUpdates: @escaping ConnectivityUpdates) {
if let monitor = monitor {
monitor.start(
connectivityUpdatesQueue: connectivityUpdatesQueue,
onConnectivityUpdates: onUpdates
)
} else if #available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) {
let monitor = NetworkMonitor()
self.monitor = monitor
monitor.start(
connectivityUpdatesQueue: connectivityUpdatesQueue,
onConnectivityUpdates: onUpdates
)
}
}
func cancel() {
guard let monitor = monitor else {
return
}
monitor.cancel()
}
deinit {
cancel()
}
}
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *)
class NetworkMonitor: AnyConnectivityMonitor {
private var monitor: NWPathMonitor?
private var onConnectivityUpdates: ConnectivityUpdates?
private var connectivityUpdatesQueue: DispatchQueue?
private let queue = DispatchQueue(label: "com.amazonaws.NetworkMonitor.queue", qos: .background)
func start(connectivityUpdatesQueue: DispatchQueue, onConnectivityUpdates: @escaping ConnectivityUpdates) {
self.connectivityUpdatesQueue = connectivityUpdatesQueue
self.onConnectivityUpdates = onConnectivityUpdates
// A new instance is required each time a monitor is started
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = didUpdate(path:)
monitor.start(queue: queue)
self.monitor = monitor
}
func cancel() {
guard let monitor = monitor else { return }
defer {
self.monitor = nil
}
monitor.cancel()
}
func didUpdate(path: NWPath) {
guard let onConnectivityUpdates = onConnectivityUpdates,
let connectivityUpdatesQueue = connectivityUpdatesQueue else {
return
}
let connectivityPath = ConnectivityPath(path: path)
connectivityUpdatesQueue.async {
onConnectivityUpdates(connectivityPath)
}
}
}

View File

@ -1,125 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
import Network
struct ConnectivityPath {
let status: ConnectivityStatus
let availableInterfaces: [ConnectivityInterface]
let isExpensive: Bool
let supportsDNS: Bool
let supportsIPv4: Bool
let supportsIPv6: Bool
init(
status: ConnectivityStatus = .unsatisfied,
availableInterfaces: [ConnectivityInterface] = [],
isExpensive: Bool = false,
supportsDNS: Bool = false,
supportsIPv4: Bool = false,
supportsIPv6: Bool = false
) {
self.status = status
self.availableInterfaces = availableInterfaces
self.isExpensive = isExpensive
self.supportsDNS = supportsDNS
self.supportsIPv4 = supportsIPv4
self.supportsIPv6 = supportsIPv6
}
}
extension ConnectivityPath: CustomStringConvertible {
var description: String {
[
"\(status): \(availableInterfaces.description)",
"Expensive = \(isExpensive ? "YES" : "NO")",
"DNS = \(supportsDNS ? "YES" : "NO")",
"IPv4 = \(supportsIPv4 ? "YES" : "NO")",
"IPv6 = \(supportsIPv6 ? "YES" : "NO")"
].joined(separator: "; ")
}
}
extension ConnectivityPath {
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *)
init(path: NWPath) {
self.status = ConnectivityStatus(status: path.status)
self.availableInterfaces = path.availableInterfaces.map { ConnectivityInterface(interface: $0) }
self.isExpensive = path.isExpensive
self.supportsDNS = path.supportsDNS
self.supportsIPv4 = path.supportsIPv4
self.supportsIPv6 = path.supportsIPv6
}
}
enum ConnectivityInterfaceType: String {
case other
case wifi
case cellular
case wiredEthernet
case loopback
}
extension ConnectivityInterfaceType {
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *)
init(interfaceType: NWInterface.InterfaceType) {
switch interfaceType {
case .other:
self = .other
case .wifi:
self = .wifi
case .cellular:
self = .cellular
case .wiredEthernet:
self = .wiredEthernet
case .loopback:
self = .loopback
@unknown default:
self = .other
}
}
}
struct ConnectivityInterface {
public let name: String
public let type: ConnectivityInterfaceType
public init(name: String, type: ConnectivityInterfaceType) {
self.name = name
self.type = type
}
}
extension ConnectivityInterface {
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *)
init(interface: NWInterface) {
self.name = interface.name
self.type = ConnectivityInterfaceType(interfaceType: interface.type)
}
}
enum ConnectivityStatus: String {
case satisfied
case unsatisfied
case requiresConnection
}
extension ConnectivityStatus {
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *)
init(status: NWPath.Status) {
switch status {
case .satisfied:
self = .satisfied
case .unsatisfied:
self = .unsatisfied
case .requiresConnection:
self = .requiresConnection
@unknown default:
self = .unsatisfied
}
}
}

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>3.1.0</string>
<string>1.5.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -8,7 +8,7 @@
import Foundation
/// Auth interceptor for API Key based authentication
public class APIKeyAuthInterceptor: AuthInterceptor, AuthInterceptorAsync {
public class APIKeyAuthInterceptor: AuthInterceptor {
let apiKey: String

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -15,142 +15,62 @@ public class OIDCAuthInterceptor: AuthInterceptor {
self.authProvider = authProvider
}
public func interceptMessage(
_ message: AppSyncMessage,
for endpoint: URL,
completion: @escaping (AppSyncMessage) -> Void
) {
guard let host = endpoint.host else {
completion(message)
return
public func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) -> AppSyncMessage {
let host = endpoint.host!
let jwtToken: String
switch authProvider.getLatestAuthToken() {
case .success(let token):
jwtToken = token
case .failure:
return message
}
guard case .subscribe = message.messageType else {
completion(message)
return
}
authProvider.getLatestAuthToken { result in
let signedMessage = self.signMessage(message, with: result, host: host)
completion(signedMessage)
return
}
}
public func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL,
completion: @escaping (AppSyncConnectionRequest) -> Void
) {
guard let host = endpoint.host else {
completion(request)
return
}
authProvider.getLatestAuthToken { result in
let signedRequest = self.signRequest(request, with: result, host: host)
completion(signedRequest)
return
}
}
private func signMessage(
_ message: AppSyncMessage,
with tokenResult: Result<String, Error>,
host: String
) -> AppSyncMessage {
let jwtToken: String
switch tokenResult {
case .success(let token):
jwtToken = token
case .failure:
return message
}
switch message.messageType {
case .subscribe:
let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host)
var payload = message.payload ?? AppSyncMessage.Payload()
payload.authHeader = authHeader
return AppSyncMessage(
let signedMessage = AppSyncMessage(
id: message.id,
payload: payload,
type: message.messageType
)
return signedMessage
default:
break
}
private func signRequest(
_ request: AppSyncConnectionRequest,
with tokenResult: Result<String, Error>,
host: String
) -> AppSyncConnectionRequest {
let jwtToken: String
switch tokenResult {
case .success(let token):
jwtToken = token
case .failure:
// A user that is not signed in should receive an unauthorized error from
// the connection attempt. This code achieves this by always creating a valid
// request to AppSync even when the token cannot be retrieved. The request sent
// to AppSync will receive a response indicating the request is unauthorized.
// If we do not use empty token string and perform the remaining logic of the
// request construction then it will fail request validation at AppSync before
// the authorization check, which ends up being propagated back to the caller
// as a "bad request". Example of bad requests are when the header and payload
// query strings are missing or when the data is not base64 encoded.
jwtToken = ""
}
let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host)
let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader)
let payloadData = SubscriptionConstants.emptyPayload.data(using: .utf8)
let payloadBase64 = payloadData?.base64EncodedString()
guard var urlComponents = URLComponents(url: request.url, resolvingAgainstBaseURL: false) else {
return request
}
let headerQuery = URLQueryItem(name: RealtimeProviderConstants.header, value: base64Auth)
let payloadQuery = URLQueryItem(name: RealtimeProviderConstants.payload, value: payloadBase64)
urlComponents.queryItems = [headerQuery, payloadQuery]
guard let url = urlComponents.url else {
return request
}
let signedRequest = AppSyncConnectionRequest(url: url)
return signedRequest
}
// MARK: Deprecated methods
// Keeping these methods for backward compatibility
public func interceptMessage(
_ message: AppSyncMessage,
for endpoint: URL
) -> AppSyncMessage {
guard let host = endpoint.host else {
return message
}
guard case .subscribe = message.messageType else {
return message
}
let result = authProvider.getLatestAuthToken()
let signedMessage = signMessage(message, with: result, host: host)
return signedMessage
return message
}
public func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL
) -> AppSyncConnectionRequest {
guard let host = endpoint.host else {
let host = endpoint.host!
let jwtToken: String
switch authProvider.getLatestAuthToken() {
case .success(let token):
jwtToken = token
case .failure:
return request
}
let result = authProvider.getLatestAuthToken()
let signedRequest = signRequest(request, with: result, host: host)
let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host)
let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader)
let payloadData = SubscriptionConstants.emptyPayload.data(using: .utf8)
let payloadBase64 = payloadData?.base64EncodedString()
guard var urlComponents = URLComponents(url: request.url, resolvingAgainstBaseURL: false) else {
return request
}
let headerQuery = URLQueryItem(name: RealtimeProviderConstants.header, value: base64Auth)
let payloadQuery = URLQueryItem(name: RealtimeProviderConstants.payload, value: payloadBase64)
urlComponents.queryItems = [headerQuery, payloadQuery]
guard let url = urlComponents.url else {
return request
}
let signedRequest = AppSyncConnectionRequest(url: url)
return signedRequest
}
}

View File

@ -1,105 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
#if swift(>=5.5.2)
import Foundation
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public class OIDCAuthInterceptorAsync: AuthInterceptorAsync {
let authProvider: OIDCAuthProviderAsync
public init(_ authProvider: OIDCAuthProviderAsync) {
self.authProvider = authProvider
}
public func interceptMessage(_ message: AppSyncMessage, for endpoint: URL) async -> AppSyncMessage {
let host = endpoint.host!
let jwtToken: String
do {
jwtToken = try await authProvider.getLatestAuthToken()
} catch {
return message
}
switch message.messageType {
case .subscribe:
let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host)
var payload = message.payload ?? AppSyncMessage.Payload()
payload.authHeader = authHeader
let signedMessage = AppSyncMessage(
id: message.id,
payload: payload,
type: message.messageType
)
return signedMessage
default:
break
}
return message
}
public func interceptConnection(
_ request: AppSyncConnectionRequest,
for endpoint: URL
) async -> AppSyncConnectionRequest {
let host = endpoint.host!
let jwtToken: String
do {
jwtToken = try await authProvider.getLatestAuthToken()
} catch {
// A user that is not signed in should receive an unauthorized error from the connection attempt. This code
// achieves this by always creating a valid request to AppSync even when the token cannot be retrieved. The
// request sent to AppSync will receive a response indicating the request is unauthorized. If we do not use
// empty token string and perform the remaining logic of the request construction then it will fail request
// validation at AppSync before the authorization check, which ends up being propagated back to the caller
// as a "bad request". Example of bad requests are when the header and payload query strings are missing
// or when the data is not base64 encoded.
jwtToken = ""
}
let authHeader = UserPoolsAuthenticationHeader(token: jwtToken, host: host)
let base64Auth = AppSyncJSONHelper.base64AuthenticationBlob(authHeader)
let payloadData = SubscriptionConstants.emptyPayload.data(using: .utf8)
let payloadBase64 = payloadData?.base64EncodedString()
guard var urlComponents = URLComponents(url: request.url, resolvingAgainstBaseURL: false) else {
return request
}
let headerQuery = URLQueryItem(name: RealtimeProviderConstants.header, value: base64Auth)
let payloadQuery = URLQueryItem(name: RealtimeProviderConstants.payload, value: payloadBase64)
urlComponents.queryItems = [headerQuery, payloadQuery]
guard let url = urlComponents.url else {
return request
}
let signedRequest = AppSyncConnectionRequest(url: url)
return signedRequest
}
}
/// Authentication header for user pool based auth
private class UserPoolsAuthenticationHeader: AuthenticationHeader {
let authorization: String
init(token: String, host: String) {
self.authorization = token
super.init(host: host)
}
private enum CodingKeys: String, CodingKey {
case authorization = "Authorization"
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(authorization, forKey: .authorization)
try super.encode(to: encoder)
}
}
#endif

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -8,7 +8,7 @@
import Foundation
/// Connection interceptor for real time connection provider
public class RealtimeGatewayURLInterceptor: ConnectionInterceptor, ConnectionInterceptorAsync {
public class RealtimeGatewayURLInterceptor: ConnectionInterceptor {
public init() {
// Do nothing
}
@ -23,18 +23,11 @@ public class RealtimeGatewayURLInterceptor: ConnectionInterceptor, ConnectionInt
guard var urlComponents = URLComponents(url: request.url, resolvingAgainstBaseURL: false) else {
return request
}
urlComponents.scheme = SubscriptionConstants.realtimeWebsocketScheme
if AppSyncURLHelper.isStandardAppSyncGraphQLEndpoint(url: endpoint) {
urlComponents.host = host.replacingOccurrences(
of: SubscriptionConstants.appsyncHostPart,
with: SubscriptionConstants.appsyncRealtimeHostPart
)
} else {
// else this is a custom domain such that the host remains untouched and "/realtime" path is added
urlComponents.path.append(contentsOf: "/" + SubscriptionConstants.appsyncCustomDomainRealtimePath)
}
urlComponents.host = host.replacingOccurrences(
of: SubscriptionConstants.appsyncHostPart,
with: SubscriptionConstants.appsyncRealtimeHostPart
)
guard let url = urlComponents.url else {
return request
}

View File

@ -1,21 +1,19 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
public enum AppSyncJSONHelper {
public struct AppSyncJSONHelper {
public static func base64AuthenticationBlob(_ header: AuthenticationHeader ) -> String {
let jsonEncoder = JSONEncoder()
do {
let jsonHeader = try jsonEncoder.encode(header)
AppSyncLogger.verbose(
"Generated Header for request - \(String(describing: String(data: jsonHeader, encoding: .utf8)))"
)
AppSyncLogger.verbose("Generated Header for request - \(String(describing: String(data: jsonHeader, encoding: .utf8)))")
return jsonHeader.base64EncodedString()
} catch {
AppSyncLogger.error(error.localizedDescription)

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -10,58 +10,16 @@ import os
struct AppSyncLogger {
static var logLevel: AppSyncRealTimeClient.LogLevel {
AppSyncRealTimeClient.logLevel
}
// iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *
static func error(_ log: String) {
// Always logged, no conditional check needed
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
if #available(iOS 10.0, *) {
os_log("%@", type: .error, log)
} else {
NSLog("%@", log)
}
}
static func error(_ error: Error) {
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
os_log("%@", type: .error, error.localizedDescription)
} else {
NSLog("%@", error.localizedDescription)
}
}
static func warn(_ log: String) {
guard logLevel.rawValue >= AppSyncRealTimeClient.LogLevel.warn.rawValue else {
return
}
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
os_log("%@", type: .info, log)
} else {
NSLog("%@", log)
}
}
static func info(_ log: String) {
guard logLevel.rawValue >= AppSyncRealTimeClient.LogLevel.info.rawValue else {
return
}
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
os_log("%@", type: .info, log)
} else {
NSLog("%@", log)
}
}
static func debug(_ log: String) {
guard logLevel.rawValue >= AppSyncRealTimeClient.LogLevel.debug.rawValue else {
return
}
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
if #available(iOS 10.0, *) {
os_log("%@", type: .debug, log)
} else {
NSLog("%@", log)
@ -69,14 +27,34 @@ struct AppSyncLogger {
}
static func verbose(_ log: String) {
guard logLevel.rawValue >= AppSyncRealTimeClient.LogLevel.verbose.rawValue else {
return
}
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
if #available(iOS 10.0, *) {
os_log("%@", type: .debug, log)
} else {
NSLog("%@", log)
}
}
static func info(_ log: String) {
if #available(iOS 10.0, *) {
os_log("%@", type: .info, log)
} else {
NSLog("%@", log)
}
}
static func warn(_ log: String) {
if #available(iOS 10.0, *) {
os_log("%@", type: .info, log)
} else {
NSLog("%@", log)
}
}
static func error(_ error: Error) {
if #available(iOS 10.0, *) {
os_log("%@", type: .error, error.localizedDescription)
} else {
NSLog("%@", error.localizedDescription)
}
}
}

View File

@ -1,34 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
public enum AppSyncURLHelper {
// A standard AppSync URL has the format of
// https://[DOMAIN].appsync-api.[REGION].amazonaws.com/graphql
// The regex `\w{26}` is used to check that the DOMAIN is 26 alphanumeric characters long.
// The regex `\w{2}(?:(?:-\w{2,})+)-\d` is used to check that the REGION matches the pattern
// {2letterword}{atleast one instance of pattern {{-}{word with atleast 2 letters}}}{-}{single digit}.
// for example, "us-west-1'
// AppSync endpoints reference : https://docs.aws.amazon.com/general/latest/gr/appsync.html
public static let standardDomainPattern =
"^https://\\w{26}.appsync-api.\\w{2}(?:(?:-\\w{2,})+)-\\d.amazonaws.com/graphql$"
// Check whether the provided GraphQL endpoint has standard appsync domain
public static func isStandardAppSyncGraphQLEndpoint(url: URL) -> Bool {
return url.absoluteString.range(
of: standardDomainPattern,
options: [
.regularExpression,
.caseInsensitive
],
range: nil,
locale: nil
) != nil
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -14,54 +14,31 @@ import Foundation
/// includes work that must be performed on a specific queue, make sure to dispatch
/// it inside the closure.
class CountdownTimer {
private static let defaultInterval: TimeInterval = 5 * 60
/// The interval after which the timer will fire
let interval: TimeInterval
/// The interval in seconds of the timer
private var _interval: TimeInterval?
private let lock: NSLock
private var workItem: DispatchWorkItem?
private var onCountdownComplete: (() -> Void)?
private let onCountdownComplete: () -> Void
init() {
init(interval: TimeInterval, onCountdownComplete: @escaping () -> Void) {
self.lock = NSLock()
}
var interval: TimeInterval {
_interval ?? CountdownTimer.defaultInterval
}
/// Starts the countdown of the timer with `interval` and perform
///
/// - Parameters:
/// - interval: The interval after which the timer will fire, and be reset on.
/// - onCountdownComplete: The closure to perform when the timer fires.
func start(interval: TimeInterval, onCountdownComplete: @escaping () -> Void) {
lock.lock()
defer {
lock.unlock()
}
_interval = interval
self.interval = interval
self.onCountdownComplete = onCountdownComplete
cancelAndClearWorkItem()
createAndScheduleTimer(interval: interval)
createAndScheduleTimer()
}
/// Resets the timer to begin counting down from the `interval` again.
///
/// - Parameter interval: Optionally pass in a new interval for the timer.
func reset(interval: TimeInterval? = nil) {
/// Resets the countdown of the timer to `interval`
func resetCountdown() {
lock.lock()
defer {
lock.unlock()
}
if let interval = interval {
_interval = interval
}
cancelAndClearWorkItem()
createAndScheduleTimer(interval: self.interval)
createAndScheduleTimer()
}
/// Invalidates/stops the timer
/// Invalidates the timer
func invalidate() {
lock.lock()
defer {
@ -70,16 +47,9 @@ class CountdownTimer {
cancelAndClearWorkItem()
}
// MARK: - Private helpers
/// Invoked by all puclic methods (`start`, `reset`, `invalidate`) to clear the previous timer
private func cancelAndClearWorkItem() {
guard let workItem = workItem else {
return
}
workItem.cancel()
self.workItem = nil
workItem?.cancel()
workItem = nil
}
/// Invoked by the timer. Do not execute this method directly.
@ -94,13 +64,13 @@ class CountdownTimer {
return
}
onCountdownComplete?()
onCountdownComplete()
}
/// Invoked by `start` and `reset` when creating a new timer.
private func createAndScheduleTimer(interval: TimeInterval) {
private func createAndScheduleTimer() {
let workItem = DispatchWorkItem { self.timerFired() }
self.workItem = workItem
DispatchQueue.global().asyncAfter(deadline: .now() + interval, execute: workItem)
}
}

View File

@ -1,20 +1,10 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
public protocol OIDCAuthProvider {
func getLatestAuthToken() -> Result<String, Error>
func getLatestAuthToken(completion: @escaping (Result<String, Error>) -> Void )
}
public extension OIDCAuthProvider {
func getLatestAuthToken(completion: @escaping (Result<String, Error>) -> Void) {
let result = getLatestAuthToken()
completion(result)
}
}

View File

@ -1,14 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
#if swift(>=5.5.2)
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol OIDCAuthProviderAsync {
func getLatestAuthToken() async throws -> String
}
#endif

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,21 +1,19 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
public enum SubscriptionConstants {
public struct SubscriptionConstants {
public static let appsyncHostPart = "appsync-api"
public static let appsyncRealtimeHostPart = "appsync-realtime-api"
public static let realtimeWebsocketScheme = "wss"
public static let appsyncCustomDomainRealtimePath = "realtime"
public static let emptyPayload = "{}"
public static let appsyncServiceName = "appsync"
@ -23,7 +21,7 @@ public enum SubscriptionConstants {
public static let authorizationkey = "Authorization"
}
public enum RealtimeProviderConstants {
public struct RealtimeProviderConstants {
public static let header = "header"
public static let payload = "payload"

View File

@ -1,31 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
#if swift(>=5.5.2)
import Foundation
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
actor TaskQueue<Success> {
private var previousTask: Task<Success, Error>?
func sync(block: @Sendable @escaping () async throws -> Success) async throws {
previousTask = Task { [previousTask] in
_ = await previousTask?.result
return try await block()
}
_ = try await previousTask?.value
}
nonisolated func async(block: @Sendable @escaping () async throws -> Success) rethrows {
Task {
try await sync(block: block)
}
}
}
#endif

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -14,7 +14,7 @@ public protocol AppSyncWebsocketProvider {
///
/// This is an async call. After the connection is succesfully established, the delegate
/// will receive the callback on `websocketDidConnect(:)`
func connect(urlRequest: URLRequest, protocols: [String], delegate: AppSyncWebsocketDelegate?)
func connect(url: URL, protocols: [String], delegate: AppSyncWebsocketDelegate?)
/// Disconnects the websocket.
func disconnect()

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -10,57 +10,24 @@ import Starscream
/// Extension to handle delegate callback from Starscream
extension StarscreamAdapter: Starscream.WebSocketDelegate {
public func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected:
websocketDidConnect(socket: client)
case .disconnected(let reason, let code):
AppSyncLogger.verbose("[StarscreamAdapter] disconnected: reason=\(reason); code=\(code)")
websocketDidDisconnect(socket: client, error: nil)
case .text(let string):
websocketDidReceiveMessage(socket: client, text: string)
case .binary(let data):
websocketDidReceiveData(socket: client, data: data)
case .ping:
AppSyncLogger.verbose("[StarscreamAdapter] ping")
case .pong:
AppSyncLogger.verbose("[StarscreamAdapter] pong")
case .viabilityChanged(let viability):
AppSyncLogger.verbose("[StarscreamAdapter] viabilityChanged: \(viability)")
case .reconnectSuggested(let suggestion):
AppSyncLogger.verbose("[StarscreamAdapter] reconnectSuggested: \(suggestion)")
case .cancelled:
websocketDidDisconnect(socket: client, error: nil)
case .error(let error):
websocketDidDisconnect(socket: client, error: error)
}
}
private func websocketDidConnect(socket: WebSocketClient) {
public func websocketDidConnect(socket: WebSocketClient) {
AppSyncLogger.verbose("[StarscreamAdapter] websocketDidConnect: websocket has been connected.")
serialQueue.async {
self._isConnected = true
self.delegate?.websocketDidConnect(provider: self)
}
delegate?.websocketDidConnect(provider: self)
}
private func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
AppSyncLogger.verbose(
"[StarscreamAdapter] websocketDidDisconnect: \(error?.localizedDescription ?? "No error")"
)
serialQueue.async {
self._isConnected = false
self.delegate?.websocketDidDisconnect(provider: self, error: error)
}
public func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
AppSyncLogger.verbose("[StarscreamAdapter] websocketDidDisconnect: \(error?.localizedDescription ?? "No error")")
delegate?.websocketDidDisconnect(provider: self, error: error)
}
private func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
public func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
AppSyncLogger.verbose("[StarscreamAdapter] websocketDidReceiveMessage: - \(text)")
let data = text.data(using: .utf8) ?? Data()
delegate?.websocketDidReceiveData(provider: self, data: data)
}
private func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
public func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
AppSyncLogger.verbose("[StarscreamAdapter] WebsocketDidReceiveData - \(data)")
delegate?.websocketDidReceiveData(provider: self, data: data)
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -9,45 +9,22 @@ import Foundation
import Starscream
public class StarscreamAdapter: AppSyncWebsocketProvider {
let serialQueue: DispatchQueue
private let callbackQueue: DispatchQueue
public init() {
// Do nothing
}
private let serialQueue = DispatchQueue(label: "com.amazonaws.StarscreamAdapter.serialQueue")
var socket: WebSocket?
weak var delegate: AppSyncWebsocketDelegate?
// swiftlint:disable:next identifier_name
var _isConnected: Bool
public var isConnected: Bool {
serialQueue.sync {
_isConnected
}
}
public init() {
let serialQueue = DispatchQueue(label: "com.amazonaws.StarscreamAdapter.serialQueue")
let callbackQueue = DispatchQueue(
label: "com.amazonaws.StarscreamAdapter.callBack",
target: serialQueue
)
self._isConnected = false
self.serialQueue = serialQueue
self.callbackQueue = callbackQueue
}
public func connect(urlRequest: URLRequest, protocols: [String], delegate: AppSyncWebsocketDelegate?) {
public func connect(url: URL, protocols: [String], delegate: AppSyncWebsocketDelegate?) {
serialQueue.async {
AppSyncLogger.verbose("[StarscreamAdapter] connect. Connecting to url")
var urlRequest = urlRequest
urlRequest.setValue("no-store", forHTTPHeaderField: "Cache-Control")
let protocolHeaderValue = protocols.joined(separator: ", ")
urlRequest.setValue(protocolHeaderValue, forHTTPHeaderField: "Sec-WebSocket-Protocol")
self.socket = WebSocket(request: urlRequest)
self.socket = WebSocket(url: url, protocols: protocols)
self.delegate = delegate
self.socket?.delegate = self
self.socket?.callbackQueue = self.callbackQueue
self.socket?.callbackQueue = DispatchQueue(label: "com.amazonaws.StarscreamAdapter.callBack")
self.socket?.connect()
}
}
@ -66,4 +43,10 @@ public class StarscreamAdapter: AppSyncWebsocketProvider {
self.socket?.write(string: message)
}
}
public var isConnected: Bool {
serialQueue.sync {
return socket?.isConnected ?? false
}
}
}

View File

@ -1,190 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
class AppSyncRealTimeClientAsyncFailureTests: AppSyncRealTimeClientTestBase {
/// Test the current AppSync limit of 100 subscriptions per connection
func testMaxSubscriptionReached() { // swiftlint:disable:this cyclomatic_complexity
let subscribeSuccess = expectation(description: "subscribe successfully")
subscribeSuccess.expectedFulfillmentCount = 100
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProviderAsync(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
var subscriptions = [AppSyncSubscriptionConnection]()
for _ in 1 ... 100 {
let subscription = AppSyncSubscriptionConnection(provider: connectionProvider)
_ = subscription.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
subscribeSuccess.fulfill()
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
XCTFail("Got error \(error)")
}
}
subscriptions.append(subscription)
}
wait(for: [subscribeSuccess], timeout: TestCommonConstants.networkTimeout)
XCTAssertEqual(subscriptions.count, 100)
let limitExceeded = expectation(description: "Received Limit Exceeded error")
let subscription = AppSyncSubscriptionConnection(provider: connectionProvider)
_ = subscription.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
XCTFail("Got connected successfully - Should have been limit exceeded")
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
guard let connectionError = error as? ConnectionProviderError,
case .limitExceeded = connectionError else {
XCTFail("Should Be Limited Exceeded error")
return
}
limitExceeded.fulfill()
}
}
wait(for: [limitExceeded], timeout: TestCommonConstants.networkTimeout)
for subscription in subscriptions {
if let item = subscription.subscriptionItem {
subscription.unsubscribe(item: item)
}
}
}
/// Subscriptions receiving a failed event should only receive it once.
func testMaxSubscriptionReachedWithRetry() { // swiftlint:disable:this cyclomatic_complexity
let subscribeSuccess = expectation(description: "subscribe successfully")
subscribeSuccess.expectedFulfillmentCount = 100
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProviderAsync(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
var subscriptions = [AppSyncSubscriptionConnection]()
for _ in 1 ... 100 {
let subscription = AppSyncSubscriptionConnection(provider: connectionProvider)
_ = subscription.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
subscribeSuccess.fulfill()
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
XCTFail("Got error \(error)")
}
}
subscriptions.append(subscription)
}
wait(for: [subscribeSuccess], timeout: TestCommonConstants.networkTimeout)
XCTAssertEqual(subscriptions.count, 100)
let limitExceeded = expectation(description: "Received Limit Exceeded error")
limitExceeded.expectedFulfillmentCount = 2
for _ in 1 ... 2 {
let subscription = AppSyncSubscriptionConnection(provider: connectionProvider)
subscription.addRetryHandler(handler: TestConnectionRetryHandler())
_ = subscription.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
XCTFail("Got connected successfully - Should have been limit exceeded")
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
guard let connectionError = error as? ConnectionProviderError,
case .limitExceeded = connectionError else {
XCTFail("Should Be Limited Exceeded error")
return
}
limitExceeded.fulfill()
}
}
subscriptions.append(subscription)
}
wait(for: [limitExceeded], timeout: TestCommonConstants.networkTimeout)
for subscription in subscriptions {
if let item = subscription.subscriptionItem {
subscription.unsubscribe(item: item)
}
}
}
class TestConnectionRetryHandler: ConnectionRetryHandler {
var count: Int = 0
func shouldRetryRequest(for error: ConnectionProviderError) -> RetryAdvice {
if count > 10 {
return TestRetryAdvice(shouldRetry: false)
}
if case .limitExceeded = error {
self.count += 1
return TestRetryAdvice(shouldRetry: true, retryInterval: .seconds(1))
}
return TestRetryAdvice(shouldRetry: false)
}
}
struct TestRetryAdvice: RetryAdvice {
var shouldRetry: Bool
var retryInterval: DispatchTimeInterval?
}
}

View File

@ -1,262 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
class AppSyncRealTimeClientAsyncIntegrationTests: AppSyncRealTimeClientTestBase { // swiftlint:disable:this type_name
/// Simple integration test against an AppSync service provisioned with a simple
/// Todo model generated by the GraphQL Transform on the `model` directive.
///
/// - Given: A subscription connection on an AppSync endpoint with Todo model provisioned
/// - When:
/// - Subscribe to the `onCreateTodo`
/// - Then:
/// - Webosocket connection and subscription connection is established.
///
func testSubscribeWithSubscriptionConnection() {
let subscribeSuccess = expectation(description: "subscribe successfully")
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProviderAsync(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
let subscriptionConnection = AppSyncSubscriptionConnection(provider: connectionProvider)
_ = subscriptionConnection.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
subscribeSuccess.fulfill()
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
XCTFail("Got error \(error)")
}
}
wait(for: [subscribeSuccess], timeout: TestCommonConstants.networkTimeout)
}
/// The purpose of this test is to ensure that all websockets can be successfully
/// created, exercised and terminated while keeping a single connection provider in
/// memory.
///
/// Specifically, the following test exercises the following:
/// 1. Create a new connection provider
/// 2. Create multiple subscriptions
/// 3. Unsubscribe the subscriptions
/// 4. Ensure the socket is disconnected
/// 5. Repeat Steps 2-4 with the existing connection provider.
///
/// - Given: Connected subscriptions
/// - When:
/// - All subscription items are unsubscribed
/// - Then:
/// - Underlying websocket is disconnected
func testAllSubscriptionsCancelledShouldDisconnectTheWebsocket2() {
let connectedInvoked = expectation(description: "Connection established")
connectedInvoked.expectedFulfillmentCount = 3
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProviderAsync(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
let subscriptionConnection1 = AppSyncSubscriptionConnection(provider: connectionProvider)
let item1 = subscriptionConnection1.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
if case let .connection(state) = event {
if case .connected = state {
connectedInvoked.fulfill()
}
}
}
let subscriptionConnection2 = AppSyncSubscriptionConnection(provider: connectionProvider)
let item2 = subscriptionConnection2.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
if case let .connection(state) = event {
if case .connected = state {
connectedInvoked.fulfill()
}
}
}
let subscriptionConnection3 = AppSyncSubscriptionConnection(provider: connectionProvider)
let item3 = subscriptionConnection3.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
if case let .connection(state) = event {
if case .connected = state {
connectedInvoked.fulfill()
}
}
}
XCTAssertNotNil(item1)
XCTAssertNotNil(item2)
XCTAssertNotNil(item3)
wait(for: [connectedInvoked], timeout: TestCommonConstants.networkTimeout)
guard let realTimeConnectionProvider = connectionProvider as? RealtimeConnectionProviderAsync else {
XCTFail("Could not retrieve concrete connection provider")
return
}
assertStatus(of: realTimeConnectionProvider, equals: .connected)
subscriptionConnection1.unsubscribe(item: item1)
assertStatus(of: realTimeConnectionProvider, equals: .connected)
subscriptionConnection2.unsubscribe(item: item2)
assertStatus(of: realTimeConnectionProvider, equals: .connected)
subscriptionConnection3.unsubscribe(item: item3)
assertStatus(of: realTimeConnectionProvider, equals: .notConnected)
let newConnectedInvoked = expectation(description: "Connection established")
let subscriptionConnection4 = AppSyncSubscriptionConnection(provider: connectionProvider)
let newItem = subscriptionConnection4.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
if case let .connection(state) = event {
if case .connected = state {
newConnectedInvoked.fulfill()
}
}
}
wait(for: [newConnectedInvoked], timeout: TestCommonConstants.networkTimeout)
assertStatus(of: realTimeConnectionProvider, equals: .connected)
subscriptionConnection4.unsubscribe(item: newItem)
sleep(5)
assertStatus(of: realTimeConnectionProvider, equals: .notConnected)
}
/// The purpose of this test is to ensure that a signifcant number of subscriptions
/// can be created on a websocket, then unsubscribed, and repeated.
///
/// Specifically, the following test exercises the following:
/// 1. Create a new connection provider
/// 2. Create multiple subscriptions
/// 3. Unsubscribe the subscriptions
/// 4. Ensure the socket is disconnected
/// 5. Repeat Steps 2-4 with the existing connection provider.
///
/// - Given: Connected subscriptions
/// - When:
/// - All subscription items are unsubscribed
/// - Then:
/// - Underlying websocket is disconnected
func testSubscribeUnsubscribeRepeat() {
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProviderAsync(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
guard let realTimeConnectionProvider = connectionProvider as? RealtimeConnectionProviderAsync else {
XCTFail("Could not retrieve concrete connection provider")
return
}
let count = 30
let subscriptions = subscribe(connectionProvider, count: count)
assertStatus(of: realTimeConnectionProvider, equals: .connected)
for index in 0 ..< count {
subscriptions[index].1.unsubscribe(item: subscriptions[index].0)
}
assertStatus(of: realTimeConnectionProvider, equals: .notConnected)
let subscriptions2 = subscribe(connectionProvider, count: count)
assertStatus(of: realTimeConnectionProvider, equals: .connected)
for index in 0 ..< count {
subscriptions2[index].1.unsubscribe(item: subscriptions2[index].0)
}
assertStatus(of: realTimeConnectionProvider, equals: .notConnected)
}
func testMultipleThreadsSubscribeUnsubscribe() {
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProviderAsync(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
guard let realTimeConnectionProvider = connectionProvider as? RealtimeConnectionProviderAsync else {
XCTFail("Could not retrieve concrete connection provider")
return
}
let expectedPerforms = expectation(description: "total performs")
expectedPerforms.expectedFulfillmentCount = 1_000
DispatchQueue.concurrentPerform(iterations: 1_000) { _ in
let subscriptionConnection = AppSyncSubscriptionConnection(provider: connectionProvider)
let item = subscriptionConnection.subscribe(
requestString: requestString,
variables: nil
) { _, _ in }
subscriptionConnection.unsubscribe(item: item)
expectedPerforms.fulfill()
}
wait(for: [expectedPerforms], timeout: 1)
assertStatus(of: realTimeConnectionProvider, equals: .notConnected)
}
// MARK: - Helpers
private func subscribe(
_ connectionProvider: ConnectionProvider,
count: Int
) -> [(SubscriptionItem, AppSyncSubscriptionConnection)] {
let connectedInvoked = expectation(description: "Connection established")
connectedInvoked.expectedFulfillmentCount = count
var subscriptions = [(SubscriptionItem, AppSyncSubscriptionConnection)]()
for _ in 1 ... count {
let subscriptionConnection = AppSyncSubscriptionConnection(provider: connectionProvider)
let item = subscriptionConnection.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
if case let .connection(state) = event {
if case .connected = state {
connectedInvoked.fulfill()
}
}
}
subscriptions.append((item, subscriptionConnection))
}
wait(for: [connectedInvoked], timeout: TestCommonConstants.networkTimeout)
return subscriptions
}
/// Checks the status of the provider in a thread-safe way. This is only needed for tests; real-world
/// call sites wouldn't be able to access the `status` as it has `internal` access.
private func assertStatus(
of provider: RealtimeConnectionProviderAsync,
equals status: ConnectionState
) {
provider.taskQueue.async {
XCTAssertEqual(provider.status, status)
}
}
}

View File

@ -1,223 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
class AppSyncRealTimeClientFailureTests: AppSyncRealTimeClientTestBase {
/// Test the current AppSync limit of 100 subscriptions per connection
func testMaxSubscriptionReached() { // swiftlint:disable:this cyclomatic_complexity
let subscribeSuccess = expectation(description: "subscribe successfully")
subscribeSuccess.expectedFulfillmentCount = 100
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProvider(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
var subscriptions = [AppSyncSubscriptionConnection]()
for _ in 1 ... 100 {
let subscription = AppSyncSubscriptionConnection(provider: connectionProvider)
_ = subscription.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
subscribeSuccess.fulfill()
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
XCTFail("Got error \(error)")
}
}
subscriptions.append(subscription)
}
wait(for: [subscribeSuccess], timeout: TestCommonConstants.networkTimeout)
XCTAssertEqual(subscriptions.count, 100)
let limitExceeded = expectation(description: "Received Limit Exceeded error")
let subscription = AppSyncSubscriptionConnection(provider: connectionProvider)
_ = subscription.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
XCTFail("Got connected successfully - Should have been limit exceeded")
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
guard let connectionError = error as? ConnectionProviderError,
case .limitExceeded = connectionError else {
XCTFail("Should Be Limited Exceeded error")
return
}
limitExceeded.fulfill()
}
}
wait(for: [limitExceeded], timeout: TestCommonConstants.networkTimeout)
for subscription in subscriptions {
if let item = subscription.subscriptionItem {
subscription.unsubscribe(item: item)
}
}
}
/// Subscriptions receiving a failed event should only receive it once.
func testMaxSubscriptionReachedWithRetry() { // swiftlint:disable:this cyclomatic_complexity
let subscribeSuccess = expectation(description: "subscribe successfully")
subscribeSuccess.expectedFulfillmentCount = 100
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProvider(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
var subscriptions = [AppSyncSubscriptionConnection]()
for _ in 1 ... 100 {
let subscription = AppSyncSubscriptionConnection(provider: connectionProvider)
_ = subscription.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
subscribeSuccess.fulfill()
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
XCTFail("Got error \(error)")
}
}
subscriptions.append(subscription)
}
wait(for: [subscribeSuccess], timeout: TestCommonConstants.networkTimeout)
XCTAssertEqual(subscriptions.count, 100)
let limitExceeded = expectation(description: "Received Limit Exceeded error")
limitExceeded.expectedFulfillmentCount = 2
for _ in 1 ... 2 {
let subscription = AppSyncSubscriptionConnection(provider: connectionProvider)
subscription.addRetryHandler(handler: TestConnectionRetryHandler())
_ = subscription.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection(let subscriptionConnectionEvent):
switch subscriptionConnectionEvent {
case .connecting:
break
case .connected:
XCTFail("Got connected successfully - Should have been limit exceeded")
case .disconnected:
break
}
case .data(let data):
print("Got data back \(data)")
case .failed(let error):
guard let connectionError = error as? ConnectionProviderError,
case .limitExceeded = connectionError else {
XCTFail("Should Be Limited Exceeded error")
return
}
limitExceeded.fulfill()
}
}
subscriptions.append(subscription)
}
wait(for: [limitExceeded], timeout: TestCommonConstants.networkTimeout)
for subscription in subscriptions {
if let item = subscription.subscriptionItem {
subscription.unsubscribe(item: item)
}
}
}
func testAPIKeyInvalid() {
apiKey = "invalid"
let subscribeFailed = expectation(description: "subscribe failed")
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProvider(
for: urlRequest,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
let subscriptionConnection = AppSyncSubscriptionConnection(provider: connectionProvider)
_ = subscriptionConnection.subscribe(
requestString: requestString,
variables: nil
) { event, _ in
switch event {
case .connection:
break
case .data:
break
case .failed(let error):
guard let connectionError = error as? ConnectionProviderError,
case .unauthorized = connectionError else {
XCTFail("Should be `.unauthorized` error")
return
}
subscribeFailed.fulfill()
}
}
wait(for: [subscribeFailed], timeout: TestCommonConstants.networkTimeout)
}
class TestConnectionRetryHandler: ConnectionRetryHandler {
var count: Int = 0
func shouldRetryRequest(for error: ConnectionProviderError) -> RetryAdvice {
if count > 10 {
return TestRetryAdvice(shouldRetry: false)
}
if case .limitExceeded = error {
self.count += 1
return TestRetryAdvice(shouldRetry: true, retryInterval: .seconds(1))
}
return TestRetryAdvice(shouldRetry: false)
}
}
struct TestRetryAdvice: RetryAdvice {
var shouldRetry: Bool
var retryInterval: DispatchTimeInterval?
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -23,7 +23,7 @@ class AppSyncRealTimeClientIntegrationTests: AppSyncRealTimeClientTestBase {
let subscribeSuccess = expectation(description: "subscribe successfully")
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProvider(
for: urlRequest,
for: url,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
@ -75,7 +75,7 @@ class AppSyncRealTimeClientIntegrationTests: AppSyncRealTimeClientTestBase {
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProvider(
for: urlRequest,
for: url,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
@ -152,8 +152,7 @@ class AppSyncRealTimeClientIntegrationTests: AppSyncRealTimeClientTestBase {
assertStatus(of: realTimeConnectionProvider, equals: .notConnected)
}
/// The purpose of this test is to ensure that a signifcant number of subscriptions
/// can be created on a websocket, then unsubscribed, and repeated.
/// The purpose of this test is to ensure that a signifcant number of subscriptions can be created on a websocket, then unsubscribed, and repeated.
///
/// Specifically, the following test exercises the following:
/// 1. Create a new connection provider
@ -170,7 +169,7 @@ class AppSyncRealTimeClientIntegrationTests: AppSyncRealTimeClientTestBase {
func testSubscribeUnsubscribeRepeat() {
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProvider(
for: urlRequest,
for: url,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
@ -197,7 +196,7 @@ class AppSyncRealTimeClientIntegrationTests: AppSyncRealTimeClientTestBase {
func testMultipleThreadsSubscribeUnsubscribe() {
let authInterceptor = APIKeyAuthInterceptor(apiKey)
let connectionProvider = ConnectionProviderFactory.createConnectionProvider(
for: urlRequest,
for: url,
authInterceptor: authInterceptor,
connectionType: .appSyncRealtime
)
@ -212,7 +211,7 @@ class AppSyncRealTimeClientIntegrationTests: AppSyncRealTimeClientTestBase {
let item = subscriptionConnection.subscribe(
requestString: requestString,
variables: nil
) { _, _ in }
) { event, _ in }
subscriptionConnection.unsubscribe(item: item)
expectedPerforms.fulfill()
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -10,7 +10,7 @@ import XCTest
class AppSyncRealTimeClientTestBase: XCTestCase {
var urlRequest: URLRequest!
var url: URL!
var apiKey: String!
let requestString = """
subscription onCreate {
@ -23,7 +23,6 @@ class AppSyncRealTimeClientTestBase: XCTestCase {
"""
override func setUp() {
AppSyncRealTimeClient.logLevel = .debug
do {
let json = try ConfigurationHelper.retrieve(forResource: "amplifyconfiguration")
if let data = json as? [String: Any],
@ -35,8 +34,7 @@ class AppSyncRealTimeClientTestBase: XCTestCase {
let endpoint = apiName["endpoint"] as? String,
let apiKey = apiName["apiKey"] as? String {
urlRequest = URLRequest(url: URL(string: endpoint)!)
url = URL(string: endpoint)
self.apiKey = apiKey
} else {
throw "Could not retrieve endpoint"

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -14,14 +14,13 @@ class StarscreamAdapterTests: AppSyncRealTimeClientTestBase {
func testConnectDisconnect() throws {
let starscreamAdapter = StarscreamAdapter()
let apiKeyAuthInterceptor = APIKeyAuthInterceptor(apiKey)
let request = AppSyncConnectionRequest(url: urlRequest.url!)
let signedRequest = apiKeyAuthInterceptor.interceptConnection(request, for: urlRequest.url!)
urlRequest.url = signedRequest.url
let request = AppSyncConnectionRequest(url: url)
let signedRequest = apiKeyAuthInterceptor.interceptConnection(request, for: url)
let expectedPerforms = expectation(description: "total performs")
expectedPerforms.expectedFulfillmentCount = 1_000
DispatchQueue.concurrentPerform(iterations: 1_000) { _ in
starscreamAdapter.connect(
urlRequest: urlRequest,
url: signedRequest.url,
protocols: ["graphql-ws"],
delegate: nil
)

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,348 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
// swiftlint:disable:next type_name type_body_length
class AppSyncSubscriptionConnectionErrorHandlerTests: XCTestCase {
let connectionProvider = MockConnectionProvider()
let mockRequestString = """
subscription OnCreateMessage {
onCreateMessage {
__typename
id
message
createdAt
}
}
"""
let variables = [String: Any]()
func testOtherSubscription() throws {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
let connectedMessageExpectation = expectation(description: "Connected event should be fired")
let failedEvent = expectation(description: "Failed event should not be fired")
failedEvent.isInverted = true
let item = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
connectedMessageExpectation.fulfill()
}
case .data:
XCTFail("Data event should not be published")
case .failed:
failedEvent.fulfill()
XCTFail("Error should not be thrown")
}
}
XCTAssertNotNil(item, "Subscription item should not be nil")
wait(for: [connectedMessageExpectation], timeout: 5)
XCTAssertNotNil(connectionProvider.listener)
let otherSubscriptionError = ConnectionProviderError.subscription("otherId", nil)
connection.handleError(error: otherSubscriptionError)
wait(for: [failedEvent], timeout: 5)
}
func testThisSubscription() throws {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
let connectedMessageExpectation = expectation(description: "Connected event should be fired")
let failedEvent = expectation(description: "Failed event should be fired")
let item = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
connectedMessageExpectation.fulfill()
}
case .data:
XCTFail("Data event should not be published")
case .failed(let error):
guard let connection = error as? ConnectionProviderError,
case .subscription(let id, _) = connection,
!id.isEmpty else {
XCTFail("Should be .subscription(item.identifier)")
return
}
failedEvent.fulfill()
}
}
wait(for: [connectedMessageExpectation], timeout: 5)
XCTAssertNotNil(connectionProvider.listener)
let thisSubscriptionError = ConnectionProviderError.subscription(item.identifier, nil)
connection.handleError(error: thisSubscriptionError)
wait(for: [failedEvent], timeout: 5)
XCTAssertNil(connectionProvider.listener)
}
func testLimitExceededOtherSubscription() throws {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
let connectedMessageExpectation = expectation(description: "Connected event should be fired")
let failedEvent = expectation(description: "Failed event should not be fired")
failedEvent.isInverted = true
let item = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
connectedMessageExpectation.fulfill()
}
case .data:
XCTFail("Data event should not be published")
case .failed:
failedEvent.fulfill()
XCTFail("Error should not be thrown")
}
}
XCTAssertNotNil(item, "Subscription item should not be nil")
wait(for: [connectedMessageExpectation], timeout: 5)
XCTAssertNotNil(connectionProvider.listener)
let limitExceeded = ConnectionProviderError.limitExceeded("otherId")
connection.handleError(error: limitExceeded)
wait(for: [failedEvent], timeout: 5)
}
func testLimitExceededThisSubscription() throws {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
let connectedMessageExpectation = expectation(description: "Connected event should be fired")
let failedEvent = expectation(description: "Failed event should be fired")
let item = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
connectedMessageExpectation.fulfill()
}
case .data:
XCTFail("Data event should not be published")
case .failed(let error):
guard let connection = error as? ConnectionProviderError,
case .limitExceeded(let id) = connection, id != nil else {
XCTFail("Should be .limitExceeded(item.identifier)")
return
}
failedEvent.fulfill()
}
}
wait(for: [connectedMessageExpectation], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .subscribed)
XCTAssertNotNil(connectionProvider.listener)
let limitExceeded = ConnectionProviderError.limitExceeded(item.identifier)
connection.handleError(error: limitExceeded)
wait(for: [failedEvent], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .notSubscribed)
XCTAssertNil(connectionProvider.listener)
}
func testLimitExceededConnectionLevel_SubscribedShouldNoOp() throws {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
let connectedMessageExpectation = expectation(description: "Connected event should be fired")
let failedEvent = expectation(description: "Failed event should not be fired")
failedEvent.isInverted = true
_ = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
connectedMessageExpectation.fulfill()
}
case .data:
XCTFail("Data event should not be published")
case .failed:
failedEvent.fulfill()
XCTFail("Error should not be thrown")
}
}
wait(for: [connectedMessageExpectation], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .subscribed)
XCTAssertNotNil(connectionProvider.listener)
let connectionLevelLimitedExceeded = ConnectionProviderError.limitExceeded(nil)
connection.handleError(error: connectionLevelLimitedExceeded)
wait(for: [failedEvent], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .subscribed)
XCTAssertNotNil(connectionProvider.listener)
}
func testLimitExceededConnectionLevel_InProgressToNotSubscribed() throws {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
let connectedMessageExpectation = expectation(description: "Connected event should be fired")
let failedEvent = expectation(description: "Failed event should be fired")
_ = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
connectedMessageExpectation.fulfill()
}
case .data:
XCTFail("Data event should not be published")
case .failed(let error):
guard let connection = error as? ConnectionProviderError,
case .limitExceeded(let id) = connection,
id == nil else {
XCTFail("Should be .limitExceeded(nil)")
return
}
failedEvent.fulfill()
}
}
wait(for: [connectedMessageExpectation], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .subscribed)
XCTAssertNotNil(connectionProvider.listener)
connection.subscriptionState = .inProgress
let connectionLevelLimitedExceeded = ConnectionProviderError.limitExceeded(nil)
connection.handleError(error: connectionLevelLimitedExceeded)
wait(for: [failedEvent], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .notSubscribed)
XCTAssertNil(connectionProvider.listener)
}
func testConnection() throws {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
let connectedMessageExpectation = expectation(description: "Connected event should be fired")
let failedEvent = expectation(description: "Failed event should be fired")
_ = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
connectedMessageExpectation.fulfill()
}
case .data:
XCTFail("Data event should not be published")
case .failed(let error):
guard let connection = error as? ConnectionProviderError,
case .connection = connection else {
XCTFail("Should be .connection")
return
}
failedEvent.fulfill()
}
}
wait(for: [connectedMessageExpectation], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .subscribed)
XCTAssertNotNil(connectionProvider.listener)
let connectionError = ConnectionProviderError.connection
connection.handleError(error: connectionError)
wait(for: [failedEvent], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .notSubscribed)
XCTAssertNil(connectionProvider.listener)
}
func testUnknown() throws {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
let connectedMessageExpectation = expectation(description: "Connected event should be fired")
let failedEvent = expectation(description: "Failed event should be fired")
_ = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
connectedMessageExpectation.fulfill()
}
case .data:
XCTFail("Data event should not be published")
case .failed(let error):
guard let connection = error as? ConnectionProviderError,
case .unknown = connection else {
XCTFail("Should be .unknown")
return
}
failedEvent.fulfill()
}
}
wait(for: [connectedMessageExpectation], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .subscribed)
XCTAssertNotNil(connectionProvider.listener)
let unknownError = ConnectionProviderError.unknown(message: nil, causedBy: nil, payload: nil)
connection.handleError(error: unknownError)
wait(for: [failedEvent], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .notSubscribed)
XCTAssertNil(connectionProvider.listener)
}
func testRetry() {
let connection = AppSyncSubscriptionConnection(provider: connectionProvider)
connection.addRetryHandler(handler: TestConnectionRetryHandler(maxCount: 0))
let connectedExpectation = expectation(description: "Connected event should be fired")
let connectedOnRetryExpectation = expectation(description: "Connected event should be fired")
var connectedOnce = false
let failedEvent = expectation(description: "Failed event should be fired")
_ = connection.subscribe(requestString: mockRequestString, variables: variables) { event, _ in
switch event {
case .connection(let status):
if status == .connected {
if !connectedOnce {
connectedOnce = true
connectedExpectation.fulfill()
} else {
connectedOnRetryExpectation.fulfill()
}
}
case .data:
XCTFail("Data event should not be published")
case .failed(let error):
guard let connection = error as? ConnectionProviderError,
case .connection = connection else {
XCTFail("Should be .connection")
return
}
failedEvent.fulfill()
}
}
wait(for: [connectedExpectation], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .subscribed)
XCTAssertNotNil(connectionProvider.listener)
let connectionError = ConnectionProviderError.connection
connection.handleError(error: connectionError)
wait(for: [connectedOnRetryExpectation], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .subscribed)
XCTAssertNotNil(connectionProvider.listener)
connection.handleError(error: connectionError)
wait(for: [failedEvent], timeout: 5)
XCTAssertEqual(connection.subscriptionState, .notSubscribed)
XCTAssertNil(connectionProvider.listener)
}
// MARK: - Helpers
class TestConnectionRetryHandler: ConnectionRetryHandler {
var count: Int = 0
let maxCount: Int
init(maxCount: Int = 10) {
self.maxCount = maxCount
}
func shouldRetryRequest(for error: ConnectionProviderError) -> RetryAdvice {
if count > maxCount {
return TestRetryAdvice(shouldRetry: false)
}
if case .connection = error {
self.count += 1
return TestRetryAdvice(shouldRetry: true, retryInterval: .seconds(1))
}
return TestRetryAdvice(shouldRetry: false)
}
}
struct TestRetryAdvice: RetryAdvice {
var shouldRetry: Bool
var retryInterval: DispatchTimeInterval?
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -59,7 +59,6 @@ class AppSyncSubscriptionConnectionTests: XCTestCase {
}
XCTAssertNotNil(item, "Subscription item should not be nil")
wait(for: [connectingMessageExpectation, connectedMessageExpectation], timeout: 5, enforceOrder: true)
XCTAssertNotNil(connectionProvider.listener)
}
/// Test unsubscribe subscription gives us back the right events
@ -102,10 +101,8 @@ class AppSyncSubscriptionConnectionTests: XCTestCase {
XCTAssertNotNil(item, "Subscription item should not be nil")
wait(for: [connectingMessageExpectation, connectedMessageExpectation], timeout: 5, enforceOrder: true)
XCTAssertNotNil(connectionProvider.listener)
connection.unsubscribe(item: item)
wait(for: [unsubscribeAckExpectation], timeout: 2)
XCTAssertNil(connectionProvider.listener)
}
/// Test subscription with invalid connection
@ -144,7 +141,6 @@ class AppSyncSubscriptionConnectionTests: XCTestCase {
}
XCTAssertNotNil(item, "Subscription item should not be nil")
wait(for: [connectingMessageExpectation, errorEventExpectation], timeout: 5, enforceOrder: true)
XCTAssertNil(connectionProvider.listener)
}
/// Test if trying to subscribe with a 'not connected' connection gives error
@ -185,7 +181,6 @@ class AppSyncSubscriptionConnectionTests: XCTestCase {
}
XCTAssertNotNil(item, "Subscription item should not be nil")
wait(for: [connectingMessageExpectation, errorEventExpectation], timeout: 5, enforceOrder: true)
XCTAssertNil(connectionProvider.listener)
}
/// Test if valid data is returned
@ -235,7 +230,6 @@ class AppSyncSubscriptionConnectionTests: XCTestCase {
)
connectionProvider.sendDataResponse(mockResponse)
wait(for: [dataEventExpectation], timeout: 2)
XCTAssertNotNil(connectionProvider.listener)
}
func testNilDataInVariables() {
@ -267,7 +261,6 @@ class AppSyncSubscriptionConnectionTests: XCTestCase {
}
XCTAssertNotNil(item, "Subscription item should not be nil")
wait(for: [connectingMessageExpectation, connectedMessageExpectation], timeout: 5, enforceOrder: true)
XCTAssertNotNil(connectionProvider.listener)
}
}

View File

@ -1,121 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
class RealTimeConnectionProviderResponseTests: XCTestCase {
func testIsMaxSubscriptionReached_EmptyPayload() throws {
let response = RealtimeConnectionProviderResponse(
id: "id",
payload: nil,
type: .error
)
XCTAssertFalse(response.isMaxSubscriptionReachedError())
XCTAssertEqual(response.toConnectionProviderError(connectionState: .connected), .subscription("id", nil))
}
func testIsMaxSubscriptionReached_MaxSubscriptionsReachedException() throws {
let payload = ["errorType": AppSyncJSONValue.string("MaxSubscriptionsReachedException")]
let response = RealtimeConnectionProviderResponse(
id: "id",
payload: payload,
type: .error
)
XCTAssertTrue(response.isMaxSubscriptionReachedError())
XCTAssertEqual(response.toConnectionProviderError(connectionState: .connected), .limitExceeded("id"))
}
func testIsMaxSubscriptionReached_MaxSubscriptionsReachedError() throws {
let payload = ["errors": AppSyncJSONValue.object(["errorType": "MaxSubscriptionsReachedError"])]
let response = RealtimeConnectionProviderResponse(
id: "id",
payload: payload,
type: .error
)
XCTAssertTrue(response.isMaxSubscriptionReachedError())
XCTAssertEqual(response.toConnectionProviderError(connectionState: .connected), .limitExceeded("id"))
}
func testIsLimitExceeded_EmptyPayload() throws {
let response = RealtimeConnectionProviderResponse(
payload: nil,
type: .error
)
XCTAssertFalse(response.isLimitExceededError())
XCTAssertEqual(
response.toConnectionProviderError(connectionState: .connected),
.unknown(message: nil, causedBy: nil, payload: nil)
)
}
func testIsLimitExceeded_LimitExceededError() throws {
let payload = ["errors": AppSyncJSONValue.object(["errorType": "LimitExceededError"])]
let response = RealtimeConnectionProviderResponse(
payload: payload,
type: .error
)
XCTAssertTrue(response.isLimitExceededError())
XCTAssertEqual(response.toConnectionProviderError(connectionState: .connected), .limitExceeded(nil))
}
func testIsUnauthorized_EmptyPayload() throws {
let response = RealtimeConnectionProviderResponse(
payload: nil,
type: .error
)
XCTAssertFalse(response.isUnauthorizationError())
XCTAssertEqual(
response.toConnectionProviderError(connectionState: .connected),
.unknown(message: nil, causedBy: nil, payload: nil)
)
}
func testIsUnauthorized_UnauthorizedException() throws {
let payload = ["errors": AppSyncJSONValue.array([
["errorType": "com.amazonaws.deepdish.graphql.auth#UnauthorizedException"]
])]
let response = RealtimeConnectionProviderResponse(
payload: payload,
type: .error
)
XCTAssertTrue(response.isUnauthorizationError())
XCTAssertEqual(
response.toConnectionProviderError(connectionState: .connected),
.unauthorized
)
}
}
extension ConnectionProviderError: Equatable {
public static func == (lhs: ConnectionProviderError, rhs: ConnectionProviderError) -> Bool {
switch (lhs, rhs) {
case (.connection, .connection):
return true
case (.jsonParse, .jsonParse):
return true
case (.limitExceeded(let id1), .limitExceeded(let id2)):
return id1 == id2
case (.subscription(let id1, _), .subscription(let id2, _)):
return id1 == id2
case (.unauthorized, .unauthorized):
return true
case (.unknown(let message1, _, _), .unknown(let message2, _, _)):
return message1 == message2
default:
return false
}
}
}

View File

@ -1,228 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
class ConnectionProviderAsyncTests: RealtimeConnectionProviderTestBase {
/// Provider test
///
/// Given:
/// - A configured subscriber -> provider -> websocket chain
/// When:
/// - I invoke `provider.connect()`
/// - And the websocket properly connects
/// Then:
/// - The subscriber is notified of the successful connection
func testSuccessfulConnection() {
receivedNotConnected.isInverted = true
receivedError.isInverted = true
let onConnect: MockWebsocketProvider.OnConnect = { _, _, delegate in
self.websocketDelegate = delegate
DispatchQueue.global().async {
delegate?.websocketDidConnect(provider: self.websocket)
}
}
let onDisconnect: MockWebsocketProvider.OnDisconnect = { }
let onWrite: MockWebsocketProvider.OnWrite = { message in
guard RealtimeConnectionProviderTestBase.messageType(of: message, equals: "connection_init") else {
XCTFail("Incoming message did not have 'connection_init' type")
return
}
self.websocketDelegate.websocketDidReceiveData(
provider: self.websocket,
data: RealtimeConnectionProviderTestBase.makeConnectionAckMessage()
)
}
websocket = MockWebsocketProvider(
onConnect: onConnect,
onDisconnect: onDisconnect,
onWrite: onWrite
)
// Retain the provider so it doesn't release prior to executing callbacks
let provider = createProviderAndConnect()
// Get rid of "written to, but never read" compiler warnings
print(provider)
waitForExpectations(timeout: 0.05)
}
/// Provider add and remove listeners tests
///
/// Given:
/// - A connected websocket with a listener
/// When:
/// - remove all listeners
/// Then:
/// - The listeners are removed and the connection is disconnected
func testAddRemoveListeners() {
receivedNotConnected.isInverted = true
receivedError.isInverted = true
let onConnect: MockWebsocketProvider.OnConnect = { _, _, delegate in
self.websocketDelegate = delegate
DispatchQueue.global().async {
delegate?.websocketDidConnect(provider: self.websocket)
}
}
let receivedDisconnect = expectation(description: "receivedDisconnect")
let onDisconnect: MockWebsocketProvider.OnDisconnect = {
receivedDisconnect.fulfill()
}
let onWrite: MockWebsocketProvider.OnWrite = { message in
guard RealtimeConnectionProviderTestBase.messageType(of: message, equals: "connection_init") else {
XCTFail("Incoming message did not have 'connection_init' type")
return
}
self.websocketDelegate.websocketDidReceiveData(
provider: self.websocket,
data: RealtimeConnectionProviderTestBase.makeConnectionAckMessage()
)
}
websocket = MockWebsocketProvider(
onConnect: onConnect,
onDisconnect: onDisconnect,
onWrite: onWrite
)
// Retain the provider so it doesn't release prior to executing callbacks
let provider = createProviderAndConnect(listeners: ["1", "2", "3", "4"])
wait(
for: [receivedInProgress, receivedConnected, receivedNotConnected, receivedError],
timeout: 1
)
XCTAssertFalse(provider.listeners.isEmpty)
let listenersToRemove = provider.listeners.map { $0.key }
// Removing all the listeners will disconnect the websocket connection
for identifier in listenersToRemove {
provider.removeListener(identifier: identifier)
}
// Since removing listeners is asynchronous, we have to wait for the disconnect
wait(for: [receivedDisconnect], timeout: 1)
XCTAssertTrue(provider.listeners.isEmpty)
XCTAssertEqual(provider.status, .notConnected)
}
/// Provider test
///
/// Given:
/// - A configured subscriber -> provider -> websocket chain
/// When:
/// - I invoke `provider.connect()`
/// - And the websocket reports a connection error
/// Then:
/// - The subscriber is notified of the unsuccessful connection
func testConnectionError() {
receivedConnected.isInverted = true
receivedNotConnected.isInverted = true
let onConnect: MockWebsocketProvider.OnConnect = { _, _, delegate in
self.websocketDelegate = delegate
DispatchQueue.global().async {
delegate?.websocketDidConnect(provider: self.websocket)
}
}
let onDisconnect: MockWebsocketProvider.OnDisconnect = { }
let onWrite: MockWebsocketProvider.OnWrite = { message in
guard RealtimeConnectionProviderTestBase.messageType(of: message, equals: "connection_init") else {
XCTFail("Incoming message did not have 'connection_init' type")
return
}
self.websocketDelegate.websocketDidDisconnect(
provider: self.websocket,
error: "test error"
)
}
websocket = MockWebsocketProvider(
onConnect: onConnect,
onDisconnect: onDisconnect,
onWrite: onWrite
)
// Retain the provider so it doesn't release prior to executing callbacks
let provider = createProviderAndConnect()
// Get rid of "written to, but never read" compiler warnings
print(provider)
waitForExpectations(timeout: 0.05)
}
/// Stale connection test
///
/// Given:
/// - A provider configured with a default stale connection timeout
/// When:
/// - The service sends a message containing an override timeout value
/// Then:
/// - The provider updates its stale connection timeout to the service-provided value
func testServiceOverridesStaleConnectionTimeout() {
receivedNotConnected.isInverted = true
receivedError.isInverted = true
let expectedTimeoutInSeconds = 60.0
let timeoutInMilliseconds = Int(expectedTimeoutInSeconds) * 1_000
let onConnect: MockWebsocketProvider.OnConnect = { _, _, delegate in
self.websocketDelegate = delegate
DispatchQueue.global().async {
delegate?.websocketDidConnect(provider: self.websocket)
}
}
let onDisconnect: MockWebsocketProvider.OnDisconnect = { }
let connectionAckMessage = RealtimeConnectionProviderTestBase
.makeConnectionAckMessage(withTimeout: timeoutInMilliseconds)
let onWrite: MockWebsocketProvider.OnWrite = { message in
guard RealtimeConnectionProviderTestBase.messageType(of: message, equals: "connection_init") else {
XCTFail("Incoming message did not have 'connection_init' type")
return
}
self.websocketDelegate.websocketDidReceiveData(
provider: self.websocket,
data: connectionAckMessage
)
}
websocket = MockWebsocketProvider(
onConnect: onConnect,
onDisconnect: onDisconnect,
onWrite: onWrite
)
let provider = createProviderAndConnect()
wait(for: [receivedConnected], timeout: 0.05)
XCTAssertEqual(provider.staleConnectionTimer.interval, expectedTimeoutInSeconds)
waitForExpectations(timeout: 0.05)
}
}

View File

@ -1,107 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
class RealtimeConnectionProviderAsyncTestBase: XCTestCase {
let urlRequest = URLRequest(url: URL(string: "https://www.appsyncrealtimeclient.test/")!)
var websocket: MockWebsocketProvider!
// swiftlint:disable:next weak_delegate
var websocketDelegate: AppSyncWebsocketDelegate!
// Shared test expectations. Set expected fulfillment counts and inversions as
// needed in the body of the test.
var receivedInProgress: XCTestExpectation!
var receivedConnected: XCTestExpectation!
var receivedNotConnected: XCTestExpectation!
var receivedError: XCTestExpectation!
override func setUp() {
receivedInProgress = expectation(description: "receivedInProgress")
receivedConnected = expectation(description: "receivedConnected")
receivedNotConnected = expectation(description: "receivedNotConnected")
receivedError = expectation(description: "receivedError")
}
// MARK: - Utilities
/// Creates a RealtimeConnectionProvider, adds a listener that fulfills the shared
/// expectations as appropriate, and invokes `connect()`. Returns the provider for
/// subsequent testing.
///
/// Preconditions:
/// - `self.websocket` must be initialized in the mock provider's `onConnect`
func createProviderAndConnect(
listeners: [String]? = nil,
serialCallbackQueue: DispatchQueue = DispatchQueue(
label: "com.amazonaws.RealtimeConnectionProviderTestBase.serialCallbackQueue"
),
connectivityMonitor: ConnectivityMonitor = ConnectivityMonitor()
) -> RealtimeConnectionProvider {
let provider = RealtimeConnectionProvider(
urlRequest: urlRequest,
websocket: websocket,
serialCallbackQueue: serialCallbackQueue,
connectivityMonitor: connectivityMonitor
)
provider.addListener(identifier: "testListener") { event in
switch event {
case .connection(let connectionState):
switch connectionState {
case .inProgress:
self.receivedInProgress.fulfill()
case .connected:
self.receivedConnected.fulfill()
case .notConnected:
self.receivedNotConnected.fulfill()
}
case .error:
self.receivedError.fulfill()
default:
break
}
}
if let listeners = listeners {
listeners.forEach { identifier in
provider.addListener(identifier: identifier) { _ in }
}
}
provider.connect()
return provider
}
/// Given a Stringified AppSyncMessage, validates the `type` is equal to `expectedType`
/// - Parameter message: a string representation of a websocket message
/// - Parameter expectedType: the expected value of the type
/// - Returns: type `type` field of the message, if present
static func messageType(of message: String, equals expectedType: String) -> Bool {
guard
let messageData = message.data(using: .utf8),
let dict = try? JSONSerialization.jsonObject(with: messageData) as? [String: String]
else {
return false
}
guard let type = dict["type"] else {
return false
}
return type == expectedType
}
/// Creates a connection acknowledgement message with the specified timeout
/// - Parameter timeout: stale connection timeout, in milliseconds (defaults to 300,000)
static func makeConnectionAckMessage(withTimeout timeout: Int = 300_000) -> Data {
#"{"type":"connection_ack","payload":{"connectionTimeoutMs":\#(timeout)}}"#
.data(using: .utf8)!
}
}

View File

@ -1,166 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
class ConnectionProviderHandleErrorTests: XCTestCase {
let urlRequest = URLRequest(url: URL(string: "https://www.appsyncrealtimeclient.test/")!)
var websocket = MockWebsocketProvider()
/// Error response is limit exceeded with id
/// Should receive ConnectionProviderError.limitExceeded with id
func testLimitExceededWithId() {
let provider = RealtimeConnectionProvider(urlRequest: urlRequest, websocket: websocket)
let subscriptionEvent = expectation(description: "Receieved subscription event")
provider.addListener(identifier: "id") { event in
guard case .error(let error) = event,
let connectionError = error as? ConnectionProviderError else {
XCTFail("Should have received error event")
return
}
guard case .limitExceeded(let id) = connectionError else {
XCTFail("Should have received .limitExceeded error")
return
}
XCTAssertEqual(id, "id")
subscriptionEvent.fulfill()
}
let payload = ["errors": AppSyncJSONValue.object(["errorType": "LimitExceededError"])]
let response = RealtimeConnectionProviderResponse.init(id: "id", payload: payload, type: .error)
provider.handleError(response: response)
wait(for: [subscriptionEvent], timeout: 1)
}
/// Error response is limit exceeded (connection level error without subscription id)
/// Should throttle and receive a fraction of ConnectionProviderError.limitExceeded event without id
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
func testLimitExceededMissingIdThrottled() {
let provider = RealtimeConnectionProvider(urlRequest: urlRequest, websocket: websocket)
let limitExceededThrottle = expectation(description: "received limit exceeded")
limitExceededThrottle.expectedFulfillmentCount = 100
let sink = provider.limitExceededSubject.sink { error in
guard case .limitExceeded(let id) = error else {
XCTFail("Should have received .limitExceeded error")
return
}
XCTAssertNil(id)
limitExceededThrottle.fulfill()
}
let subscriptionEvent = expectation(description: "Receieved subscription event")
subscriptionEvent.assertForOverFulfill = false
var subscriptionEventCount = 0
provider.addListener(identifier: "id") { event in
guard case .error(let error) = event,
let connectionError = error as? ConnectionProviderError else {
XCTFail("Should have received error event")
return
}
guard case .limitExceeded(let id) = connectionError else {
XCTFail("Should have received .limitExceeded error")
return
}
XCTAssertNil(id)
subscriptionEventCount += 1
subscriptionEvent.fulfill()
}
let payload = ["errors": AppSyncJSONValue.object(["errorType": "LimitExceededError"])]
let response = RealtimeConnectionProviderResponse.init(id: nil, payload: payload, type: .error)
DispatchQueue.concurrentPerform(iterations: 100) { _ in
provider.handleError(response: response)
}
wait(for: [subscriptionEvent, limitExceededThrottle], timeout: 1)
// The number of subscription events received should be a fraction of the number of throttled events (100)
XCTAssertTrue(subscriptionEventCount < 10)
sink.cancel()
}
/// Error response is max subscription with subscription id
/// Should receive ConnectionProviderError.limitExceeded with id
func testMaxSubscriptionReached() {
let provider = RealtimeConnectionProvider(urlRequest: urlRequest, websocket: websocket)
let subscriptionEvent = expectation(description: "Receieved subscription event")
provider.addListener(identifier: "id") { event in
guard case .error(let error) = event,
let connectionError = error as? ConnectionProviderError else {
XCTFail("Should have received error event")
return
}
guard case .limitExceeded(let id) = connectionError else {
XCTFail("Should have received .limitExceeded error")
return
}
XCTAssertEqual(id, "id")
subscriptionEvent.fulfill()
}
let payload = ["errors": AppSyncJSONValue.object(["errorType": "MaxSubscriptionsReachedError"])]
let response = RealtimeConnectionProviderResponse.init(id: "id", payload: payload, type: .error)
provider.handleError(response: response)
wait(for: [subscriptionEvent], timeout: 1)
}
/// Error response with no indication for which subscription and missing payload
/// Should receive ConnectionProviderError.other
func testMissingId() throws {
let provider = RealtimeConnectionProvider(urlRequest: urlRequest, websocket: websocket)
let subscriptionEvent = expectation(description: "Receieved subscription event")
provider.addListener(identifier: "id") { event in
guard case .error(let error) = event,
let connectionError = error as? ConnectionProviderError else {
XCTFail("Should have received error event")
return
}
guard case .unknown = connectionError else {
XCTFail("Should have received .unknown error")
return
}
subscriptionEvent.fulfill()
}
let response = RealtimeConnectionProviderResponse.init(id: nil, payload: nil, type: .error)
provider.handleError(response: response)
wait(for: [subscriptionEvent], timeout: 1)
}
/// Error response subscription id and payload
/// Should receive ConnectionProviderError.subscription(id, payload)
func testWithSubscriptionId() throws {
let provider = RealtimeConnectionProvider(urlRequest: urlRequest, websocket: websocket)
let subscriptionEvent = expectation(description: "Receieved subscription event")
provider.addListener(identifier: "id") { event in
guard case .error(let error) = event,
let connectionError = error as? ConnectionProviderError else {
XCTFail("Should have received error event")
return
}
guard case .subscription(let id, let payload) = connectionError else {
XCTFail("Should have received .subscription error")
return
}
XCTAssertEqual(id, "id")
XCTAssertNotNil(payload)
subscriptionEvent.fulfill()
}
let response = RealtimeConnectionProviderResponse.init(
id: "id",
payload: ["errorType": "SomeError"],
type: .error
)
provider.handleError(response: response)
wait(for: [subscriptionEvent], timeout: 1)
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -57,92 +57,9 @@ class ConnectionProviderStaleConnectionTests: RealtimeConnectionProviderTestBase
let provider = createProviderAndConnect()
wait(for: [receivedConnected], timeout: 0.05)
XCTAssertEqual(provider.staleConnectionTimer.interval, expectedTimeoutInSeconds)
XCTAssertEqual(provider.staleConnectionTimeout.get(), expectedTimeoutInSeconds)
waitForExpectations(timeout: 0.05)
}
/// Given a connected websocket, when the network status toggles to disconnected and back to connected,
/// the connecion should be disconnected with `error`.
/// Note: This error is handled by the subscriptions to attempt to reconnect the websocket.
///
/// - Given: Connected websocket
/// - When:
/// - Connectivity updates to unsatisfied (network is down)
/// - Connectivity updates to satisfied (network is back up)
/// - Then:
/// - Websocket is disconnected
func testConnectionDisconnectsAfterConnectivityUpdates() {
receivedNotConnected.isInverted = true
receivedError.isInverted = true
let onConnect: MockWebsocketProvider.OnConnect = { _, _, delegate in
self.websocketDelegate = delegate
DispatchQueue.global().async {
delegate?.websocketDidConnect(provider: self.websocket)
}
}
let onDisconnect: MockWebsocketProvider.OnDisconnect = { }
let onWrite: MockWebsocketProvider.OnWrite = { message in
guard RealtimeConnectionProviderTestBase.messageType(of: message, equals: "connection_init") else {
XCTFail("Incoming message did not have 'connection_init' type")
return
}
self.websocketDelegate.websocketDidReceiveData(
provider: self.websocket,
data: RealtimeConnectionProviderTestBase.makeConnectionAckMessage()
)
}
websocket = MockWebsocketProvider(
onConnect: onConnect,
onDisconnect: onDisconnect,
onWrite: onWrite
)
let connectionQueue = DispatchQueue(
label: "com.amazonaws.ConnectionProviderStaleConnectionTests.connectionQueue")
let monitor = MockConnectivityMonitor()
let connectivityMonitor = ConnectivityMonitor(monitor: monitor)
// Retain the provider so it doesn't release prior to executing callbacks
let provider = createProviderAndConnect(
listeners: nil,
connectionQueue: connectionQueue,
connectivityMonitor: connectivityMonitor
)
// Wait for websocket to be connected
waitForExpectations(timeout: 0.05)
// Send connectivity update - network down
monitor.sendConnectivityUpdate(.init(status: .unsatisfied))
let connectionIsStale = expectation(description: "connection is stale")
connectionQueue.asyncAfter(deadline: .now() + 0.5) {
XCTAssertTrue(provider.isStaleConnection)
connectionIsStale.fulfill()
}
wait(for: [connectionIsStale], timeout: 1.0)
// Send connectivity update - network up
let receivedError = expectation(description: "listeners receive error event")
provider.addListener(identifier: "id") { event in
guard case .error = event else {
XCTFail("Event should be error")
return
}
receivedError.fulfill()
}
monitor.sendConnectivityUpdate(.init(status: .satisfied))
let connectionIsDisconnected = expectation(description: "connection is disconnected")
connectionQueue.asyncAfter(deadline: .now() + 0.5) {
XCTAssertFalse(provider.isStaleConnection)
XCTAssertTrue(provider.status == .notConnected)
connectionIsDisconnected.fulfill()
}
wait(for: [connectionIsDisconnected, receivedError], timeout: 1.0)
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -220,7 +220,7 @@ class ConnectionProviderTests: RealtimeConnectionProviderTestBase {
let provider = createProviderAndConnect()
wait(for: [receivedConnected], timeout: 0.05)
XCTAssertEqual(provider.staleConnectionTimer.interval, expectedTimeoutInSeconds)
XCTAssertEqual(provider.staleConnectionTimeout.get(), expectedTimeoutInSeconds)
waitForExpectations(timeout: 0.05)
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2021 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -10,11 +10,11 @@ import XCTest
class RealtimeConnectionProviderTestBase: XCTestCase {
let urlRequest = URLRequest(url: URL(string: "https://www.appsyncrealtimeclient.test/")!)
let url = URL(string: "https://www.appsyncrealtimeclient.test/")!
var websocket: MockWebsocketProvider!
// swiftlint:disable:next weak_delegate
//swiftlint:disable:next weak_delegate
var websocketDelegate: AppSyncWebsocketDelegate!
// Shared test expectations. Set expected fulfillment counts and inversions as
@ -39,23 +39,8 @@ class RealtimeConnectionProviderTestBase: XCTestCase {
///
/// Preconditions:
/// - `self.websocket` must be initialized in the mock provider's `onConnect`
func createProviderAndConnect(
listeners: [String]? = nil,
connectionQueue: DispatchQueue = DispatchQueue(
label: "com.amazonaws.RealtimeConnectionProviderTestBase.connectionQueue"
),
serialCallbackQueue: DispatchQueue = DispatchQueue(
label: "com.amazonaws.RealtimeConnectionProviderTestBase.serialCallbackQueue"
),
connectivityMonitor: ConnectivityMonitor = ConnectivityMonitor()
) -> RealtimeConnectionProvider {
let provider = RealtimeConnectionProvider(
urlRequest: urlRequest,
websocket: websocket,
connectionQueue: connectionQueue,
serialCallbackQueue: serialCallbackQueue,
connectivityMonitor: connectivityMonitor
)
func createProviderAndConnect(listeners: [String]? = nil) -> RealtimeConnectionProvider {
let provider = RealtimeConnectionProvider(for: url, websocket: websocket)
provider.addListener(identifier: "testListener") { event in
switch event {
case .connection(let connectionState):

View File

@ -1,43 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
import Network
class ConnectivityMonitorTests: XCTestCase {
/// Test `NetworkMonitor` that uses `NWPathMonitor`
/// Only assert that the connectivity update isn't `nil` since the status is based on the conditions
/// of when test is run.
func testNetworkMonitor() {
let connectivityUpdateReceived = expectation(description: "connectivity update received")
let monitor = ConnectivityMonitor(monitor: nil)
monitor.start { connectivity in
connectivityUpdateReceived.fulfill()
XCTAssertNotNil(connectivity)
}
wait(for: [connectivityUpdateReceived], timeout: 1)
monitor.cancel()
}
func testMockConnectivityMonitor() {
let mockMonitor = MockConnectivityMonitor()
let monitor = ConnectivityMonitor(monitor: mockMonitor)
let connectivityUpdateReceived = expectation(description: "connectivity update received")
connectivityUpdateReceived.expectedFulfillmentCount = 3
monitor.start { updates in
XCTAssertNotNil(updates)
connectivityUpdateReceived.fulfill()
}
mockMonitor.sendConnectivityUpdate(.init())
mockMonitor.sendConnectivityUpdate(.init())
mockMonitor.sendConnectivityUpdate(.init())
wait(for: [connectivityUpdateReceived], timeout: 1)
monitor.cancel()
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,45 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
import AppSyncRealTimeClient
class AppSyncURLHelperTests: XCTestCase {
/// Test if given graphql endpoint has standard appsync domain
///
/// - Given: A graphql endpoint with standard appsync domain
/// - When: I invoke AppSyncURLHelper.hasStandardAppsyncGraphQLDomain()
/// - Then: It should return true
func testURLStandardAppSyncDomain() {
let standardDomainURL =
URL(string: "https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-west-2.amazonaws.com/graphql")!
XCTAssertTrue(AppSyncURLHelper.isStandardAppSyncGraphQLEndpoint(url: standardDomainURL))
}
/// Test if given graphql endpoint in capital letters has standard appsync domain
///
/// - Given: A graphql endpoint in capital letters with standard appsync domain
/// - When: I invoke AppSyncURLHelper.hasStandardAppsyncGraphQLDomain()
/// - Then: It should return true
func testURLStandardAppSyncDomainCaseInsensitive() {
let standardDomainURL =
URL(string: "HTTPS://ABCDEFGHIJKLMNOPQRSTUVWXYZ.APPSYNC-API.US-WEST-2.AMAZONAWS.COM/GRAPHQL")!
XCTAssertTrue(AppSyncURLHelper.isStandardAppSyncGraphQLEndpoint(url: standardDomainURL))
}
/// Test if given graphql endpoint has a custom domain
///
/// - Given: A graphql endpoint with a custom domain
/// - When: I invoke AppSyncURLHelper.hasStandardAppsyncGraphQLDomain()
/// - Then: It should return false
func testURLCustomAppSyncDomain() {
let customDomainURL =
URL(string: "https://api.example.com/graphql")!
XCTAssertFalse(AppSyncURLHelper.isStandardAppSyncGraphQLEndpoint(url: customDomainURL))
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -10,29 +10,13 @@ import AppSyncRealTimeClient
class OIDCAuthInterceptorTests: XCTestCase {
var userPoolAuthProvider: MockUserPoolsAuthProvider!
var authInterceptor: OIDCAuthInterceptor!
override func setUp() {
userPoolAuthProvider = MockUserPoolsAuthProvider()
authInterceptor = OIDCAuthInterceptor(userPoolAuthProvider)
authInterceptor = OIDCAuthInterceptor(MockUserPoolsAuthProvider())
}
func testInterceptConnection() {
let url = URL(string: "http://xxxc.appsync-api.ap-southeast-2.amazonaws.com/sd")!
let request = AppSyncConnectionRequest(url: url)
let signedRequest = authInterceptor.interceptConnection(request, for: url)
guard let queries = URLComponents(url: signedRequest.url, resolvingAgainstBaseURL: true)?.queryItems else {
assertionFailure("Query parameters should not be nil")
return
}
XCTAssertTrue(queries.contains { $0.name == "header"}, "Should contain the header query")
XCTAssertTrue(queries.contains { $0.name == "payload"}, "Should contain the payload query")
}
func testInterceptConnectionWithInvalidToken() {
userPoolAuthProvider.hasError = true
func testInterceptRequest() {
let url = URL(string: "http://xxxc.appsync-api.ap-southeast-2.amazonaws.com/sd")!
let request = AppSyncConnectionRequest(url: url)
let signedRequest = authInterceptor.interceptConnection(request, for: url)
@ -55,15 +39,7 @@ class OIDCAuthInterceptorTests: XCTestCase {
}
class MockUserPoolsAuthProvider: OIDCAuthProvider {
struct AuthError: Error { }
var hasError: Bool = false
func getLatestAuthToken() -> Result<String, Error> {
if hasError {
return .failure(AuthError())
}
return .success("jwtToken")
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,36 +0,0 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
@testable import AppSyncRealTimeClient
class MockConnectivityMonitor: AnyConnectivityMonitor {
private var connectivityUpdatesQueue: DispatchQueue?
private var onConnectivityUpdates: ConnectivityUpdates?
func start(
connectivityUpdatesQueue: DispatchQueue,
onConnectivityUpdates: @escaping ConnectivityUpdates
) {
self.connectivityUpdatesQueue = connectivityUpdatesQueue
self.onConnectivityUpdates = onConnectivityUpdates
}
func sendConnectivityUpdate(_ connectivityPath: ConnectivityPath) {
guard let onConnectivityUpdates = onConnectivityUpdates,
let connectivityUpdatesQueue = connectivityUpdatesQueue else {
return
}
connectivityUpdatesQueue.async {
onConnectivityUpdates(connectivityPath)
}
}
func cancel() {
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -9,20 +9,20 @@ import Foundation
import AppSyncRealTimeClient
class MockWebsocketProvider: AppSyncWebsocketProvider {
typealias OnConnect = (URLRequest, [String], AppSyncWebsocketDelegate?) -> Void
typealias OnConnect = (URL, [String], AppSyncWebsocketDelegate?) -> Void
typealias OnDisconnect = () -> Void
typealias OnWrite = (String) -> Void
var isConnected: Bool
let onConnect: OnConnect?
let onDisconnect: OnDisconnect?
let onWrite: OnWrite?
let onConnect: OnConnect
let onDisconnect: OnDisconnect
let onWrite: OnWrite
init(
onConnect: OnConnect? = nil,
onDisconnect: OnDisconnect? = nil,
onWrite: OnWrite? = nil
onConnect: @escaping OnConnect,
onDisconnect: @escaping OnDisconnect,
onWrite: @escaping OnWrite
) {
self.isConnected = false
self.onConnect = onConnect
@ -30,16 +30,16 @@ class MockWebsocketProvider: AppSyncWebsocketProvider {
self.onWrite = onWrite
}
func connect(urlRequest: URLRequest, protocols: [String], delegate: AppSyncWebsocketDelegate?) {
onConnect?(urlRequest, protocols, delegate)
func connect(url: URL, protocols: [String], delegate: AppSyncWebsocketDelegate?) {
onConnect(url, protocols, delegate)
}
func disconnect() {
onDisconnect?()
onDisconnect()
}
func write(message: String) {
onWrite?(message)
onWrite(message)
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -19,8 +19,7 @@ class CountdownTimerTests: XCTestCase {
func testTimerFires() {
let timerFired = expectation(description: "timerFired")
let timer = CountdownTimer()
timer.start(interval: 0.1) { timerFired.fulfill() }
let timer = CountdownTimer(interval: 0.1) { timerFired.fulfill() }
waitForExpectations(timeout: 1.0)
timer.invalidate()
}
@ -28,8 +27,7 @@ class CountdownTimerTests: XCTestCase {
func testTimerDoesNotFireEarly() {
let timerFired = expectation(description: "timerFired")
timerFired.isInverted = true
let timer = CountdownTimer()
timer.start(interval: 0.5) { timerFired.fulfill() }
let timer = CountdownTimer(interval: 0.5) { timerFired.fulfill() }
waitForExpectations(timeout: 0.1)
timer.invalidate()
}
@ -38,8 +36,7 @@ class CountdownTimerTests: XCTestCase {
let timerFired = expectation(description: "timerFired")
timerFired.isInverted = true
XCTAssert(Thread.isMainThread)
let timer = CountdownTimer()
timer.start(interval: 1.0) {
let timer = CountdownTimer(interval: 1.0) {
timerFired.fulfill()
XCTAssertFalse(Thread.isMainThread)
}
@ -50,10 +47,7 @@ class CountdownTimerTests: XCTestCase {
func testTimerDoesNotFireAfterInvalidate() {
let timerFired = expectation(description: "timerFired")
timerFired.isInverted = true
let timer = CountdownTimer()
timer.start(interval: 0.1) {
timerFired.fulfill()
}
let timer = CountdownTimer(interval: 0.1) { timerFired.fulfill() }
timer.invalidate()
waitForExpectations(timeout: 0.2)
}
@ -72,13 +66,13 @@ class CountdownTimerTests: XCTestCase {
/// - 100: Ensure timer has not yet fired
/// - 300: Ensure timer has fired
func testTimerFiresOnSchedule() {
let timer = CountdownTimer()
var timer: CountdownTimer!
let timerHasFired = AtomicValue(initialValue: false)
let timerShouldHaveFired = expectation(description: "the timer should have fired by now")
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(0)) {
timer.start(interval: 0.200) {
timer = CountdownTimer(interval: 0.200) {
timerHasFired.set(true)
}
}
@ -109,20 +103,17 @@ class CountdownTimerTests: XCTestCase {
/// Test timing in ms:
/// - 000: Set up a timer with a .3 sec interval
/// - 100: Ensure timer has not yet fired
/// - 200: Issue a `reset` before timer would fire
/// - 200: Issue a `resetCountdown`
/// - 400: Ensure timer has not yet fired
/// - 600: Timer should fire around this time
/// - 700: Ensure timer has fired
/// - 600: Ensure timer has yet fired
func testTimerResets() {
let timer = CountdownTimer()
var timer: CountdownTimer!
let timerHasFired = AtomicValue(initialValue: false)
let timerShouldHaveFired = expectation(description: "the timer should have fired by now")
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(0)) {
timer.start(interval: 0.300) {
timerHasFired.set(true)
}
timer = CountdownTimer(interval: 0.300) { timerHasFired.set(true) }
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(100)) {
@ -130,14 +121,14 @@ class CountdownTimerTests: XCTestCase {
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(200)) {
timer.reset()
timer.resetCountdown()
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(400)) {
XCTAssertFalse(timerHasFired.get(), "The timer should not have fired yet")
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(700)) {
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(600)) {
XCTAssert(timerHasFired.get(), "The timer should have fired by now")
timerShouldHaveFired.fulfill()
}
@ -149,21 +140,12 @@ class CountdownTimerTests: XCTestCase {
/// Test that concurrent operations on the timer do not result in data races
func testConcurrency() {
let concurrentCount = expectation(description: "timer fired at least once")
concurrentCount.expectedFulfillmentCount = 10_000
let timer = CountdownTimer()
let timerShouldHaveFired = expectation(description: "the timer should have fired by now")
let timer = CountdownTimer(interval: 0.1) { timerShouldHaveFired.fulfill() }
DispatchQueue.concurrentPerform(iterations: 10_000) { _ in
let randomInt = Int.random(in: 1 ... 3)
if randomInt == 1 {
timer.start(interval: 0.01) { }
} else if randomInt == 2 {
timer.invalidate()
} else {
timer.reset()
}
concurrentCount.fulfill()
timer.resetCountdown()
}
waitForExpectations(timeout: 10)
waitForExpectations(timeout: 1.0)
}
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@ -16,27 +16,14 @@ class RealtimeGatewayURLInterceptorTests: XCTestCase {
realtimeGatewayURLInterceptor = RealtimeGatewayURLInterceptor()
}
func testStandardDomainInterceptRequest() {
let url = URL(string: "https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-west-2.amazonaws.com/graphql")!
func testInterceptRequest() {
let url = URL(string: "http://xxxc.appsync-api.ap-southeast-2.amazonaws.com/sd")!
let request = AppSyncConnectionRequest(url: url)
let changedRequest = realtimeGatewayURLInterceptor.interceptConnection(request, for: url)
XCTAssertEqual(changedRequest.url.scheme, "wss", "Scheme should be wss")
XCTAssertEqual(
changedRequest.url.absoluteString,
"wss://abcdefghijklmnopqrstuvwxyz.appsync-realtime-api.us-west-2.amazonaws.com/graphql",
"URL string should be wss://abcdefghijklmnopqrstuvwxyz.appsync-realtime-api.us-west-2.amazonaws.com/graphql"
)
}
func testCustomDomainInterceptRequest() {
let url = URL(string: "https://api.example.com/graphql")!
let request = AppSyncConnectionRequest(url: url)
let changedRequest = realtimeGatewayURLInterceptor.interceptConnection(request, for: url)
XCTAssertEqual(changedRequest.url.scheme, "wss", "Scheme should be wss")
XCTAssertEqual(
changedRequest.url.absoluteString,
"wss://api.example.com/graphql/realtime",
"URL string should be wss://api.example.com/graphql/realtime"
XCTAssertTrue(
changedRequest.url.absoluteString.contains("appsync-realtime-api"),
"URL should contain the realtime part"
)
}

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -3,92 +3,6 @@
## Unreleased
*Changes on `main` branch that have not yet been released*
## 3.1.0
### Features
- feat: Add async implementation and OIDCAuthProvider protocol ([#119](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/119))
- feat: Add asynchronous version of interceptor protocol ([#118](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/118))
### Misc
- chore: Update the pods ([#120](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/120))
- chore: fix build warnings ([#116](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/116))
## 3.0.0
**Breaking changes**: This is a major version release due to the changes made in [PR #110](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/110). The public interface for `ConnectionProviderFactory`, `RealtimeConnectionProviderAsync`, and `RealtimeConnectionProvider` has been modified to take in a `URLRequest` parameter instead of a `URL`.
### Features
- feat: pass URLRequest instead of URL to interfaces (See [PR #110](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/110))
### Fixes
- fix: fixed a bug that prevented TaskQueue sync method from waiting for the task to complete. (See [PR #107](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/107))
- chore: Add no store for the cache in urlsession (See [PR #109](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/109))
## 2.1.1
### Fixes
- fix: add runtime and compiler gates to OIDCAuthInterceptorAsync OIDCAuthProviderAsync and add files to workspace (See [PR #104](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/104))
## 2.1.0
### Features
- feat: added async oidc interceptor (See [PR #100](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/100))
### Fixes
- fix: sets OS versions for os_log (See [PR #99](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/99))
- fix: supports cross platform builds (See [PR #98](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/98))
## 2.0.0
- feat: Handle Unauthorized errors (See [PR #69](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/69))
- fix: rebase RTConnectionProvider+Websocket to Async version (See [PR #91](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/91))
- fix: create valid unauthorized request for odic/userpool connections (See [PR #93](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/93))
Breaking changes: `ConnectionProviderError.other` has been removed and `.unauthorized` and `.unknown` cases has been added.
## 1.10.0
### Features
- feat: Add Swift concurrency (async/await) support for async interceptors
## 1.9.1
### Bug fixes
- fix: Throttle AppSync LimitExceeded errors (See [PR #67](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/67))
## 1.9.0
### Features
- feat: Attempt to reconnect on connectivity (See [PR #58](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/58))
## 1.8.1
### Bug fixes
- fix: Subscription failed event should be terminal event (See [PR #74](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/74))
## 1.8.0
- feat: Allow setting log level (See [PR #71](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/71))
## 1.7.1
### Bug fixes
- fix: Retry on MaxSubscriptionReached (See [PR #66](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/66))
- fix: data race in CountdownTimer (See [PR #65](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/65))
## 1.7.0
- feat: Upgrade Starscream to 4.0.4 (See [PR #62](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/62))
## 1.6.0
- feat: Realtime interceptor changes for GraphQL subscriptions (See [PR #53](https://github.com/aws-amplify/aws-appsync-realtime-client-ios/pull/53))
## 1.5.0

View File

@ -1 +1 @@
github "daltoniam/starscream" ~> 4.0.4
github "daltoniam/starscream" ~> 3.1.1

View File

@ -9,7 +9,7 @@ if [[ -z $test_device_id ]] ; then
echo "Runtime: '${runtime}'"
# Get the last alphabetical device (probably something in the iPhone X family, as of 2019-05-31)
devicetype=$( xcrun simctl list devicetypes "iPhone " | sort | tail -1 | sed 's/.*(//' | sed 's/).*//' | tr -d '[:space:]' )
devicetype=$( xcrun simctl list devicetypes iPhone | sort | tail -1 | sed 's/.*(//' | sed 's/).*//' | tr -d '[:space:]' )
echo "Device type: '${devicetype}'"
test_device_id=$( xcrun simctl create "circleci-test-device" "${devicetype}" "${runtime}" | tr -d '[:space:]' )

View File

@ -1,7 +1,6 @@
# Gemfile
source 'https://rubygems.org'
gem 'xcpretty', '0.3.0'
gem 'cocoapods', '1.11.3'
gem 'cocoapods-downloader', '1.6.3'
gem 'fastlane', '2.205.1'
gem 'xcpretty'
gem 'cocoapods'
gem 'fastlane'

View File

@ -1,286 +1,218 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
rexml
activesupport (6.1.7.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
CFPropertyList (3.0.1)
activesupport (4.2.11.1)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
algoliasearch (1.27.5)
algoliasearch (1.27.1)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.573.0)
aws-sdk-core (3.130.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.55.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.113.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
cocoapods (1.11.3)
addressable (~> 2.8)
babosa (1.0.3)
claide (1.0.3)
cocoapods (1.8.4)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.11.3)
cocoapods-core (= 1.8.4)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.4.0, < 2.0)
cocoapods-downloader (>= 1.2.2, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.4.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
molinillo (~> 0.6.6)
nap (~> 1.0)
ruby-macho (>= 1.0, < 3.0)
xcodeproj (>= 1.21.0, < 2.0)
cocoapods-core (1.11.3)
activesupport (>= 5.0, < 7)
addressable (~> 2.8)
ruby-macho (~> 1.4)
xcodeproj (>= 1.11.1, < 2.0)
cocoapods-core (1.8.4)
activesupport (>= 4.0.2, < 6)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
public_suffix (~> 4.0)
typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.5)
cocoapods-downloader (1.6.3)
cocoapods-deintegrate (1.0.4)
cocoapods-downloader (1.3.0)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
cocoapods-trunk (1.6.0)
cocoapods-search (1.0.0)
cocoapods-stats (1.1.0)
cocoapods-trunk (1.4.1)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.2.0)
cocoapods-try (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
concurrent-ruby (1.2.2)
declarative (0.0.20)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
concurrent-ruby (1.1.5)
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.3)
dotenv (2.7.5)
emoji_regex (1.0.1)
escape (0.0.4)
ethon (0.15.0)
ffi (>= 1.15.0)
excon (0.92.2)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
excon (0.71.0)
faraday (0.17.1)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.205.1)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.137.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander (~> 4.6)
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
faraday_middleware (~> 0.13.1)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
google-api-client (>= 0.21.2, < 0.24.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
jwt (~> 2.1.0)
mini_magick (>= 4.9.4, < 5.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
public_suffix (~> 2.0.0)
rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
ffi (1.15.5)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.17.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.4.2)
google-api-client (0.23.9)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.10.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.7.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.11.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
google-cloud-core (1.4.1)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.36.1)
addressable (~> 2.8)
google-cloud-env (1.3.0)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.1.2)
faraday (>= 0.17.3, < 3.a)
google-api-client (~> 0.23)
google-cloud-core (~> 1.2)
googleauth (>= 0.6.2, < 0.10.0)
googleauth (0.6.7)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.4)
signet (~> 0.7)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
i18n (1.12.0)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
jmespath (1.6.1)
json (2.6.1)
jwt (2.3.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
minitest (5.18.0)
molinillo (0.8.0)
multi_json (1.15.0)
json (2.3.1)
jwt (2.1.0)
memoist (0.16.1)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
mini_magick (4.9.5)
minitest (5.13.0)
molinillo (0.6.6)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
nanaimo (0.2.6)
nap (1.1.0)
naturally (2.2.1)
naturally (2.2.0)
netrc (0.11.0)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
public_suffix (4.0.6)
rake (13.0.6)
representable (3.1.1)
os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
rouge (2.0.7)
ruby-macho (2.5.1)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
ruby-macho (1.4.0)
rubyzip (1.3.0)
security (0.1.3)
signet (0.16.1)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.0)
signet (0.12.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
simctl (1.6.6)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
thread_safe (0.3.6)
tty-cursor (0.7.0)
tty-screen (0.7.0)
tty-spinner (0.9.1)
tty-cursor (~> 0.7)
typhoeus (1.4.0)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.1)
unicode-display_width (1.8.0)
webrick (1.7.0)
unf_ext (0.0.7.6)
unicode-display_width (1.6.0)
word_wrap (1.0.0)
xcodeproj (1.21.0)
xcodeproj (1.13.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
nanaimo (~> 0.2.6)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
zeitwerk (2.6.7)
PLATFORMS
ruby
DEPENDENCIES
cocoapods (= 1.11.3)
cocoapods-downloader (= 1.6.3)
fastlane (= 2.205.1)
xcpretty (= 0.3.0)
cocoapods
fastlane
xcpretty
BUNDLED WITH
2.3.7
1.17.2

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -1,6 +1,6 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
// Copyright 2018-2020 Amazon.com,
// Inc. or its affiliates. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

View File

@ -6,8 +6,17 @@
"repositoryURL": "https://github.com/daltoniam/Starscream",
"state": {
"branch": null,
"revision": "df8d82047f6654d8e4b655d1b1525c64e1059d21",
"version": "4.0.4"
"revision": "e6b65c6d9077ea48b4a7bdda8994a1d3c6969c8d",
"version": "3.1.1"
}
},
{
"package": "swift-nio-zlib-support",
"repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
"state": {
"branch": null,
"revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
"version": "1.0.0"
}
}
]

View File

@ -1,4 +1,4 @@
// swift-tools-version:5.5
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
@ -11,7 +11,7 @@ let package = Package(
targets: ["AppSyncRealTimeClient"]),
],
dependencies: [
.package(url: "https://github.com/daltoniam/Starscream", .upToNextMinor(from: "4.0.4"))
.package(url: "https://github.com/daltoniam/Starscream", .upToNextMinor(from: "3.1.1"))
],
targets: [
.target(

Some files were not shown because too many files have changed in this diff Show More