├── .gitallowed ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── usage-question.md ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── workflows │ ├── issue_closed.yml │ ├── issue_comment.yml │ ├── issue_opened.yml │ └── notify_release.yml ├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── .travis.yml ├── AWSAppSync.podspec ├── AWSAppSyncClient.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── AWSDeepDishClient.xcscmblueprint │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── rohandub.xcuserdatad │ │ ├── IDEFindNavigatorScopes.plist │ │ └── UserInterfaceState.xcuserstate └── xcshareddata │ └── xcschemes │ ├── AWSAppSync-IntegTests.xcscheme │ ├── AWSAppSync.xcscheme │ └── AWSAppSyncTestApp.xcscheme ├── AWSAppSyncClient.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDETemplateMacros.plist ├── AWSAppSyncClient ├── AWSAppSync.h ├── AWSAppSyncAuthProvider.swift ├── AWSAppSyncAuthType.swift ├── AWSAppSyncCache.swift ├── AWSAppSyncCacheConfigurationMigration.swift ├── AWSAppSyncClient.swift ├── AWSAppSyncClientConfiguration.swift ├── AWSAppSyncClientConfigurationError.swift ├── AWSAppSyncClientConflictResolutionExtensions.swift ├── AWSAppSyncClientError.swift ├── AWSAppSyncClientInfo.swift ├── AWSAppSyncClientInfoError.swift ├── AWSAppSyncClientLogFormatter.swift ├── AWSAppSyncClientS3ObjectsExtensions.swift ├── AWSAppSyncConnection.swift ├── AWSAppSyncHTTPNetworkTransport.swift ├── AWSAppSyncMutations.swift ├── AWSAppSyncReachability.swift ├── AWSAppSyncRetryStrategy.swift ├── AWSAppSyncServiceConfig.swift ├── AWSAppSyncServiceConfigError.swift ├── AWSAppSyncSubscriptionError.swift ├── AWSAppSyncSubscriptionWatcher.swift ├── AWSAppSyncSubscriptionWatcherStatus.swift ├── AWSNetworkTransport.swift ├── AWSS3ObjectProtocol.swift ├── Apollo │ └── Sources │ │ └── Apollo │ │ ├── AWSGraphQLSubscriptionResponse.swift │ │ ├── Apollo.h │ │ ├── ApolloClient.swift │ │ ├── ApolloStore.swift │ │ ├── AsynchronousOperation.swift │ │ ├── Collections.swift │ │ ├── DataLoader.swift │ │ ├── GraphQLDependencyTracker.swift │ │ ├── GraphQLError.swift │ │ ├── GraphQLExecutor.swift │ │ ├── GraphQLInputValue.swift │ │ ├── GraphQLOperation.swift │ │ ├── GraphQLQueryWatcher.swift │ │ ├── GraphQLResponse.swift │ │ ├── GraphQLResponseGenerator.swift │ │ ├── GraphQLResult.swift │ │ ├── GraphQLResultAccumulator.swift │ │ ├── GraphQLResultNormalizer.swift │ │ ├── GraphQLSelectionSet.swift │ │ ├── GraphQLSelectionSetMapper.swift │ │ ├── HTTPNetworkTransport.swift │ │ ├── InMemoryNormalizedCache.swift │ │ ├── Info.plist │ │ ├── JSON.swift │ │ ├── JSONSerializationFormat.swift │ │ ├── JSONStandardTypeConversions.swift │ │ ├── Locking.swift │ │ ├── NetworkTransport.swift │ │ ├── NormalizedCache.swift │ │ ├── Promise.swift │ │ ├── Record.swift │ │ ├── RecordSet.swift │ │ ├── Result.swift │ │ ├── ResultOrPromise.swift │ │ └── Utilities.swift ├── Info.plist ├── Internal │ ├── AWSAppSyncRetryHandler.swift │ ├── AWSAppSyncSubscriptionMetadataCache.swift │ ├── AWSConfigurationFile.swift │ ├── AWSMutationCache.swift │ ├── AWSMutationRetryAdviceHelper.swift │ ├── AWSMutationRetryNotifier.swift │ ├── AWSMutationState.swift │ ├── AWSOfflineMutation.swift │ ├── AWSPerformMutationQueue.swift │ ├── AWSRequestBuilder.swift │ ├── AWSSQLiteNormalizedCache.swift │ ├── AppSyncLogHelper.swift │ ├── AppSyncLogWrapper.swift │ ├── AppSyncSubscriptionWithSync.swift │ ├── AuthInterceptor │ │ ├── IAMAuthInterceptor.swift │ │ └── LambdaAuthInterceptor.swift │ ├── BasicAWSAPIKeyAuthProvider.swift │ ├── CachedMutationOperation.swift │ ├── Foundation+Utils.swift │ ├── InternalS3ObjectDetails.swift │ ├── NetworkReachabilityNotifier.swift │ ├── SQLiteSerialization.swift │ ├── SessionMutationOperation.swift │ ├── SubscriptionFactory │ │ ├── APIKeyBasedConnectionPool.swift │ │ ├── AppSyncRealTimeClientOIDCAuthProvider.swift │ │ ├── ConnectionPool │ │ │ └── BasicSubscriptionConnectionFactory.swift │ │ ├── IAMBasedConnectionPool.swift │ │ ├── LambdaBasedConnectionPool.swift │ │ ├── OIDCBasedConnectionPool.swift │ │ ├── SubscriptionConnectionFactory.swift │ │ └── UserPoolsBasedConnectionPool.swift │ ├── SubscriptionMessageQueue.swift │ ├── SyncConfiguration.swift │ └── SyncStrategy.swift └── NetworkReachability.swift ├── AWSAppSyncIntegrationTests ├── AWSAppSyncAPIKeyAuthTests.swift ├── AWSAppSyncCognitoAuthTests.swift ├── AWSAppSyncLambdaAuthTests.swift ├── AWSAppSyncMultiAuthClientsTests.swift ├── AuthProviderTests.swift ├── ConsoleResources │ ├── appsync-console-operations.graphql │ ├── appsync-console-query-variables.json │ ├── appsync-integrationtests-cloudformation.yaml │ └── appsync-lambda-authorizer.js ├── Helpers │ └── SubscriptionStressTestHelper.swift ├── Info.plist ├── MutationQueuePerformanceTests.swift ├── SubscriptionMessagesQueueStressTests.swift ├── SubscriptionTests.swift ├── appsync_test_credentials.json.enc └── testS3Object.jpg ├── AWSAppSyncTestApp ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Image.imageset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.swift └── awsconfiguration.json ├── AWSAppSyncTestAppUITests ├── AWSAppSyncTestAppUITests.swift └── Info.plist ├── AWSAppSyncTestCommon ├── AWSAppSyncTestCommon.h ├── AppSyncClientTestConfiguration.swift ├── AppSyncClientTestConfigurationDefaults.swift ├── AppSyncClientTestHelper.swift ├── AppSyncTestAPI+S3Object.swift ├── AppSyncTestAPI.swift ├── DefaultTestPostData.swift ├── Foundation+TestUtils.swift ├── Info.plist ├── MockAWSAppSyncServiceConfigProvider.swift ├── MockAWSNetworkTransport.swift ├── MockAuthProvider │ ├── MockAPIKeyAuthProvider.swift │ ├── MockIAMAuthProvider.swift │ └── MockUserPoolsAuthProvider.swift ├── MockAuthProviders.swift ├── MockCancellable.swift ├── MockConnectionProvider │ └── MockConnectionProvider.swift ├── MockReachability.swift ├── MockS3ObjectManager.swift ├── MockSubscriptionFactory │ ├── MockSubscriptionConnection.swift │ └── MockSubscriptionFactory.swift ├── Object+Swizzling.swift ├── S3ObjectWrapper.swift └── UnitTestHelpers.swift ├── AWSAppSyncTestHostApp ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.swift ├── awsconfiguration.json ├── cacheConfigUnitTests-allTables.db ├── cacheConfigUnitTests-noMutationRecords.db ├── cacheConfigUnitTests-noRecords.db ├── cacheConfigUnitTests-noSubscriptionMetadata.db └── cacheConfigUnitTests-noTables.db ├── AWSAppSyncUnitTests ├── AWSAppSyncAuthTypeTests.swift ├── AWSAppSyncCacheConfigurationMigrationTests.swift ├── AWSAppSyncCacheConfigurationTests.swift ├── AWSAppSyncClientConfigurationTests.swift ├── AWSAppSyncClientInfoTests.swift ├── AWSAppSyncHTTPNetworkTransportTests.swift ├── AWSAppSyncRetryHandlerTests.swift ├── AWSAppSyncServiceConfigTests.swift ├── AWSSQLiteNormalizedCacheTests.swift ├── AppSyncApolloCustomizationTests.swift ├── AppSyncClientComplexObjectMutationUnitTests.swift ├── AppSyncSubscriptionWithSyncTests.swift ├── CacheKeyTests.swift ├── Foundation+UtilsTests.swift ├── Info.plist ├── JSONValueSerializationTests.swift ├── Logging │ └── AppSyncLogHelperTests.swift ├── MockAWSAppSyncServiceConfig.swift ├── MutationOptimisticUpdateTests.swift ├── MutationQueueTests.swift ├── ReachabilityChangeNotifierTests.swift ├── Subscription │ ├── AuthInterceptor │ │ └── IAMAuthInterceptorTests.swift │ └── Connection │ │ └── ConnectionPool │ │ ├── APIKeyBasedConnectionPoolTests.swift │ │ ├── IAMBasedConnectionPoolTests.swift │ │ ├── LambdaBasedConnectionPoolTests.swift │ │ ├── OIDCBasedConnectionPoolTests.swift │ │ ├── SubscriptionConnectionFactoryTests.swift │ │ └── UserPoolsBasedConnectionPoolTests.swift ├── SubscriptionMessagesQueueTests.swift ├── SubscriptionMiscTests.swift └── SyncStrategyTests.swift ├── ApolloTests ├── BatchedLoadTests.swift ├── CacheKeyForFieldTests.swift ├── CachePersistenceTests.swift ├── DataLoaderTests.swift ├── FetchQueryTests.swift ├── FetchQueryTestsComplex.swift ├── FetchQueryTestsSimple.swift ├── FragmentConstructionAndConversionTests.swift ├── Info.plist ├── InputValueEncodingTests.swift ├── LoadQueryFromStoreTests.swift ├── MockNetworkTransport.swift ├── MutatingResultsTests.swift ├── NormalizeQueryResults.swift ├── ParseQueryResponseTests.swift ├── PromiseTests.swift ├── ReadFieldValueTests.swift ├── ReadWriteFromStoreTests.swift ├── ResultOrPromiseTests.swift ├── TestCacheProvider.swift ├── WatchQueryTests.swift ├── XCTAssertHelpers.swift └── XCTestCase+Promise.swift ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cartfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── NOTICE ├── Package.resolved ├── Package.swift ├── Podfile ├── Podfile.lock ├── README.md ├── StarWarsAPI ├── API.swift ├── CharacterAndSubTypesFragments.graphql ├── CreateReviewForEpisode.graphql ├── HeroAndFriendsNames.graphql ├── HeroAppearsIn.graphql ├── HeroConditional.graphql ├── HeroDetails.graphql ├── HeroFriendsOfFriends.graphql ├── HeroName.graphql ├── HeroNameAndAppearsIn.graphql ├── HeroParentTypeDependentField.graphql ├── HeroTypeDependentAliasedField.graphql ├── Human.graphql ├── HumanFriendsFilteredById.graphql ├── Info.plist ├── SameHeroTwice.graphql ├── StarWarsAPI.h ├── Starship.graphql ├── SubscribeReview.graphql ├── TwoHeroes.graphql └── schema.json └── build-support ├── build-xcframeworks.sh ├── carthage-build.sh ├── cocoapods_release.sh ├── stage_sdk_release.sh └── update_sdk_deps.sh /.gitallowed: -------------------------------------------------------------------------------- 1 | Apollo 2 | apollo 3 | CocoaLumberjack/blob/master 4 | customer master key 5 | CustomerMasterKey 6 | EXAMPLEKEY 7 | master encryption key 8 | Pods 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @awslabs/amplify-ios 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Environment(please complete the following information):** 24 | - AppSync SDK Version: [e.g. 2.6.21] 25 | - Dependency Manager: [e.g. Cocoapods, Carthage] 26 | - Swift Version : [e.g. 4.0] 27 | 28 | **Device Information (please complete the following information):** 29 | - Device: [e.g. iPhone6, Simulator] 30 | - iOS Version: [e.g. iOS 11.4] 31 | - Specific to simulators: 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/usage-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Usage Question 3 | about: Ask a question about AWS AppSync usage 4 | 5 | --- 6 | 7 | **State your question** 8 | 9 | **Provide code snippets (if applicable)** 10 | 11 | **Environment(please complete the following information):** 12 | - AppSync SDK Version: [e.g. 2.6.21] 13 | - Dependency Manager: [e.g. Cocoapods, Carthage] 14 | - Swift Version : [e.g. 4.0] 15 | 16 | **Device Information (please complete the following information):** 17 | - Device: [e.g. iPhone6, Simulator] 18 | - iOS Version: [e.g. iOS 11.4] 19 | - Specific to simulators: 20 | 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | # Setting this to 100 years, because we do not want to automatically mark issues as stale 3 | daysUntilStale: 36500 4 | # Number of days of inactivity before a stale issue is closed 5 | daysUntilClose: 7 6 | # Label to use when marking an issue as stale 7 | staleLabel: closing-soon-if-no-response 8 | # Comment to post when marking an issue as stale. Set to `false` to disable 9 | markComment: > 10 | This issue has been automatically marked as stale because it has not had 11 | recent activity. It will be closed if no further activity occurs. Thank you 12 | for your contributions. 13 | # Comment to post when closing a stale issue. Set to `false` to disable 14 | closeComment: > 15 | This issue has been automatically closed because of inactivity. 16 | Please open a new issue if are still encountering problems. 17 | # Limit to only `issues` or `pulls` 18 | only: issues 19 | -------------------------------------------------------------------------------- /.github/workflows/issue_closed.yml: -------------------------------------------------------------------------------- 1 | name: Issue Closed 2 | 3 | on: 4 | issues: 5 | types: [closed] 6 | 7 | permissions: 8 | issues: write 9 | 10 | jobs: 11 | cleanup-labels: 12 | runs-on: ubuntu-latest 13 | if: ${{ (contains(github.event.issue.labels.*.name, 'pending-response') || contains(github.event.issue.labels.*.name, 'closing soon') || contains(github.event.issue.labels.*.name, 'pending-release')) }} 14 | steps: 15 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 16 | - name: remove unnecessary labels after closing 17 | shell: bash 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | ISSUE_NUMBER: ${{ github.event.issue.number }} 21 | run: | 22 | gh issue edit $ISSUE_NUMBER --remove-label "closing soon" --remove-label "pending-response" --remove-label "pending-release" 23 | 24 | comment-visibility-warning: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: aws-actions/closed-issue-message@v1 28 | with: 29 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 30 | message: | 31 | This issue is now closed. Comments on closed issues are hard for our team to see. 32 | If you need more assistance, please open a new issue that references this one. 33 | If you wish to keep having a conversation with other community members under this issue feel free to do so. 34 | -------------------------------------------------------------------------------- /.github/workflows/issue_comment.yml: -------------------------------------------------------------------------------- 1 | name: Issue Comment 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | jobs: 8 | notify: 9 | runs-on: ubuntu-latest 10 | permissions: {} 11 | if: ${{ !github.event.issue.pull_request && !contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association) }} 12 | steps: 13 | - name: Run webhook curl command 14 | env: 15 | WEBHOOK_URL: ${{ secrets.SLACK_COMMENT_WEBHOOK_URL }} 16 | COMMENT: ${{toJson(github.event.comment.body)}} 17 | USER: ${{github.event.comment.user.login}} 18 | COMMENT_URL: ${{github.event.comment.html_url}} 19 | shell: bash 20 | run: echo $COMMENT | sed "s/\\\n/. /g; s/\\\r//g; s/[^a-zA-Z0-9 &().,:]//g" | xargs -I {} curl -s POST "$WEBHOOK_URL" -H "Content-Type:application/json" --data '{"comment":"{}", "commentUrl":"'$COMMENT_URL'", "user":"'$USER'"}' 21 | 22 | remove-pending-response-label: 23 | runs-on: ubuntu-latest 24 | permissions: 25 | issues: write 26 | if: ${{ !github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'pending-response') }} 27 | steps: 28 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 29 | - name: remove unnecessary labels after closing 30 | shell: bash 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | ISSUE_NUMBER: ${{ github.event.issue.number }} 34 | run: | 35 | gh issue edit $ISSUE_NUMBER --remove-label "pending-response" -------------------------------------------------------------------------------- /.github/workflows/issue_opened.yml: -------------------------------------------------------------------------------- 1 | name: Issue Opened 2 | on: 3 | issues: 4 | types: [opened] 5 | 6 | jobs: 7 | notify: 8 | runs-on: ubuntu-latest 9 | permissions: {} 10 | if: ${{ !contains(fromJSON('["MEMBER", "OWNER"]'), github.event.issue.author_association) }} 11 | steps: 12 | - name: Run webhook curl command 13 | env: 14 | WEBHOOK_URL: ${{ secrets.SLACK_ISSUE_WEBHOOK_URL }} 15 | ISSUE: ${{toJson(github.event.issue.title)}} 16 | ISSUE_URL: ${{github.event.issue.html_url}} 17 | USER: ${{github.event.issue.user.login}} 18 | shell: bash 19 | run: echo $ISSUE | sed 's/[^a-zA-Z0-9 &().,:]//g' | xargs -I {} curl -s POST "$WEBHOOK_URL" -H "Content-Type:application/json" --data '{"issue":"{}", "issueUrl":"'$ISSUE_URL'", "user":"'$USER'"}' 20 | 21 | maintainer-opened: 22 | runs-on: ubuntu-latest 23 | permissions: 24 | issues: write 25 | if: ${{ contains(fromJSON('["MEMBER", "OWNER"]'), github.event.issue.author_association) }} 26 | steps: 27 | - name: Post comment if maintainer opened. 28 | shell: bash 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | ISSUE_NUMBER: ${{ github.event.issue.number }} 32 | run: | 33 | gh issue comment $ISSUE_NUMBER --repo awslabs/aws-mobile-appsync-sdk-ios -b "This issue was opened by a maintainer of this repository; updates will be posted here. If you are also experiencing this issue, please comment here with any relevant information so that we're aware and can prioritize accordingly." 34 | -------------------------------------------------------------------------------- /.github/workflows/notify_release.yml: -------------------------------------------------------------------------------- 1 | name: Notify Release 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | permissions: {} 8 | 9 | jobs: 10 | notify: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Run webhook curl command 14 | env: 15 | WEBHOOK_URL: ${{ secrets.SLACK_RELEASE_WEBHOOK_URL }} 16 | VERSION: ${{github.event.release.html_url}} 17 | REPO_NAME: ${{github.event.repository.name}} 18 | ACTION_NAME: ${{github.event.action}} 19 | shell: bash 20 | run: echo $VERSION | xargs -I {} curl -s POST "$WEBHOOK_URL" -H "Content-Type:application/json" --data '{"action":"'$ACTION_NAME'", "repo":"'$REPO_NAME'", "version":"{}"}' 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | AWSAppSyncClient.xcworkspace/ 3 | docs/_site/ 4 | xcuserdata 5 | */appsync_test_credentials.json 6 | Carthage 7 | Pods 8 | .build/ 9 | build/ 10 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # Do not specify an `included` section at this top-level file. Specify the 2 | # `--config` option pointing to this file, and the `--path` option to the files 3 | # you wish to lint 4 | 5 | excluded: 6 | - AWSAppSyncClient/Apollo 7 | 8 | 9 | # Note: This feature is experimental. As of this writing (4-Dec-2018) warnings 10 | # triggered by these rules should not be used to fail builds. Known issues: 11 | # - unused_private_declaration incorrectly triggers on 12 | # AWSAppSyncClientConfiguration.oidcAuthProvider 13 | analyzer_rules: 14 | - unused_import 15 | 16 | disabled_rules: # rule identifiers to exclude from running 17 | - file_length 18 | - force_cast 19 | - force_try 20 | - function_parameter_count 21 | - large_tuple 22 | - line_length 23 | - nesting 24 | - redundant_optional_initialization 25 | - trailing_whitespace 26 | - type_body_length 27 | - type_name 28 | - identifier_name 29 | - unused_closure_parameter 30 | - weak_delegate 31 | 32 | # configurable rules can be customized from this configuration file 33 | closing_brace: error 34 | comma: error 35 | colon: 36 | severity: error 37 | empty_enum_arguments: error 38 | function_body_length: 39 | warning: 100 40 | error: 150 41 | opening_brace: error 42 | return_arrow_whitespace: error 43 | statement_position: 44 | severity: error 45 | todo: warning 46 | trailing_semicolon: error 47 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode13.2 3 | env: 4 | global: 5 | - FRAMEWORK_NAME=AWSAppSync 6 | - BUILD_FOLDER=./build 7 | xcode_workspace: AWSAppSyncClient.xcworkspace 8 | xcode_scheme: AWSAppSync 9 | before_install: 10 | - gem update bundler 11 | - openssl aes-256-cbc -K $encrypted_6919a533707f_key -iv $encrypted_6919a533707f_iv 12 | -in AWSAppSyncIntegrationTests/appsync_test_credentials.json.enc 13 | -out AWSAppSyncIntegrationTests/appsync_test_credentials.json 14 | -d 15 | - brew update 16 | - brew outdated carthage || brew upgrade carthage 17 | 18 | before_deploy: 19 | - bash ./build-support/carthage-build.sh build --no-skip-current 20 | - bash ./build-support/carthage-build.sh archive $FRAMEWORK_NAME 21 | - bash ./build-support/build-xcframeworks.sh $FRAMEWORK_NAME 22 | script: 23 | - xcodebuild -quiet -workspace AWSAppSyncClient.xcworkspace -scheme AWSAppSync build test -destination 'platform=iOS Simulator,name=iPhone 11,OS=latest' 24 | deploy: 25 | - provider: releases 26 | api_key: 27 | secure: "grCqWY+k3n5skvqToIQ6oC+5KPZGvrW0/YV5BghmtXA5QC+N2wadEBr/8rzdHnFq4WDDrlpHSMKVe/X0zgvuAjbGdSRWAynKpuN7frHVewzAA0q06v/zW8UMpCT3T1Oaz+Yn4jNH2GHAZy05onbZfXKQbQlgJaHl9LLBUYCqWekc/LNQ1+YxreyGxROTOWoQgPBEqrkcHss2Ol1N2D3i4/eTI2CvAhLpJWOm2TlttZHfW8x1vVcCGMx/URcnqP08lJoHhMG25rTLQRABRycf473HMnR9fzhYGTGvlY0cykuvdFRrOAS1FcFxiNkDrs5m3+VvvDzZwL7+iExd9Uz+TZJehhMUb5uXcxNXkf4dIReFaDkN/Jj6uQQYhZJPi5hJjciTWqaGsdZ2dJtJpx1oVGI7pm2OwhVqYeh+AH8tmshmZK4swMnSvo+cZXvc9Pn59GdrUkt1XhfQmRLeCWxZgpG7jEShpBg7J7ky3iQj8xVfcnsA9xks+WQ4qt9DXdhS1Xou6UItDFBW8VB7zNU4SobKCMJDF4ZHWLPsR5jmU8jolSE4gO9+vNmhcAEbaW8gCsFOBsoQUcZwuxuSBwHDp+TDmKRtlTXhHVeOwR1H+Cij29zUiK1+Pr8NFv6448Kn0dIlgGkhPFq7c+5lZQtT9C2aelg0hwyPinCYVw5XpHY=" 28 | file: 29 | - "$FRAMEWORK_NAME.framework.zip" 30 | - "$BUILD_FOLDER/$FRAMEWORK_NAME.xcframework.zip" 31 | skip_cleanup: true 32 | on: 33 | repo: awslabs/aws-mobile-appsync-sdk-ios 34 | tags: true 35 | - provider: script 36 | script: bash ./build-support/cocoapods_release.sh 37 | on: 38 | repo: awslabs/aws-mobile-appsync-sdk-ios 39 | tags: true 40 | -------------------------------------------------------------------------------- /AWSAppSync.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'AWSAppSync' 3 | s.version = '3.7.1' 4 | s.author = 'AWS' 5 | s.homepage = 'http://aws.amazon.com/mobile/sdk' 6 | s.license = { :type => 'Amazon Software License', :file => 'LICENSE' } 7 | s.summary = "iOS client to access AWSAppSync backend." 8 | s.source = { :git => 'https://github.com/awslabs/aws-mobile-appsync-sdk-ios.git', 9 | :tag => s.version } 10 | s.requires_arc = true 11 | s.ios.deployment_target = '12.0' 12 | s.swift_version = '5.5.2' 13 | 14 | s.dependency 'AWSCore', '~> 2.36.0' 15 | s.dependency 'SQLite.swift', '~> 0.12.2' 16 | s.dependency 'AppSyncRealTimeClient', '~> 3.2.0' 17 | 18 | s.source_files = 'AWSAppSyncClient/AWSAppSync.h', 'AWSAppSyncClient/*.swift', 'AWSAppSyncClient/Internal/**/*.{h,m,swift}', 'AWSAppSyncClient/Apollo/Sources/Apollo/*.swift' 19 | s.public_header_files = ['AWSAppSyncClient/AWSAppSync.h', 'AWSAppSyncClient/AWSAppSync-Swift.h', 'AWSAppSyncClient/Internal/AppSyncLogHelper.h'] 20 | end 21 | -------------------------------------------------------------------------------- /AWSAppSyncClient.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AWSAppSyncClient.xcodeproj/project.xcworkspace/xcshareddata/AWSDeepDishClient.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "6EF152C53E8F39FB6C48AF9285DD3D14B4A81CFE", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "C4040456399D4D4B43992C78F2AD702EE726E16A" : 9223372036854775807, 8 | "6EF152C53E8F39FB6C48AF9285DD3D14B4A81CFE" : 9223372036854775807 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "749CC790-83D4-46F6-96D7-7BC2CD204228", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "C4040456399D4D4B43992C78F2AD702EE726E16A" : "AWSiOSSDKv2\/", 13 | "6EF152C53E8F39FB6C48AF9285DD3D14B4A81CFE" : "AWSDeepDishiOSClient\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "AWSDeepDishClient", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "AWSDeepDishClient.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "ssh:\/\/git.amazon.com:2222\/pkg\/AWSDeepDishiOSClient", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "6EF152C53E8F39FB6C48AF9285DD3D14B4A81CFE" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "ssh:\/\/git.amazon.com:2222\/pkg\/AWSiOSSDKv2", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C4040456399D4D4B43992C78F2AD702EE726E16A" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /AWSAppSyncClient.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AWSAppSyncClient.xcodeproj/project.xcworkspace/xcuserdata/rohandub.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AWSAppSyncClient.xcodeproj/project.xcworkspace/xcuserdata/rohandub.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-mobile-appsync-sdk-ios/589d58ba0e2ae7eac0058235f27bf2b41f66fb85/AWSAppSyncClient.xcodeproj/project.xcworkspace/xcuserdata/rohandub.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AWSAppSyncClient.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AWSAppSyncClient.xcworkspace/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | 7 | // Copyright ___YEAR___ Amazon.com, Inc. or its affiliates. All Rights Reserved. 8 | // Licensed under the Amazon Software License 9 | // http://aws.amazon.com/asl/ 10 | // 11 | 12 | 13 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSync.h: -------------------------------------------------------------------------------- 1 | // 2 | // AWSAppSync.h 3 | // AWSAppSync 4 | // 5 | 6 | #import 7 | 8 | //! Project version number for AWSAppSyncClient. 9 | FOUNDATION_EXPORT double AWSAppSyncClientVersionNumber; 10 | 11 | //! Project version string for AWSAppSyncClient. 12 | FOUNDATION_EXPORT const unsigned char AWSAppSyncClientVersionString[]; 13 | 14 | // In this header, you should import all the public headers of your framework using statements like #import 15 | 16 | #if __has_include("AWSAppSync/AWSAppSync-umbrella.h") 17 | #import "AWSAppSync/AWSAppSync-umbrella.h" 18 | #endif 19 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncAuthProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AWSAppSyncAuthProvider.swift 3 | // AWSAppSync 4 | // 5 | 6 | // MARK: AWSOIDCAuthProvider 7 | // For using OIDC based authorization, this protocol needs to be implemented and passed to configuration object. 8 | // Use this for cases where the OIDC token needs to be fetched asynchronously and requires a callback 9 | public protocol AWSOIDCAuthProviderAsync: AWSOIDCAuthProvider { 10 | func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) 11 | } 12 | 13 | // For AuthProviders that use a callback, the getLatestAuthToken is defaulted to return an empty string 14 | extension AWSOIDCAuthProviderAsync { 15 | public func getLatestAuthToken() -> String { fatalError("Callback method required") } 16 | } 17 | 18 | // For using OIDC based authorization, this protocol needs to be implemented and passed to configuration object. 19 | public protocol AWSOIDCAuthProvider { 20 | /// The method should fetch the token and return it to the client for using in header request. 21 | func getLatestAuthToken() -> String 22 | } 23 | 24 | // MARK: - AWSCognitoUserPoolsProvider 25 | // For using User Pool based authorization, this protocol needs to be implemented and passed to configuration object. 26 | // Use this for cases where the UserPool auth token needs to be fetched asynchronously and requires a callback 27 | public protocol AWSCognitoUserPoolsAuthProviderAsync: AWSCognitoUserPoolsAuthProvider { 28 | func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) 29 | } 30 | 31 | // For CognitoUserPoolAuthProviders that use a callback, the getLatestAuthToken is defaulted to return an empty string 32 | extension AWSCognitoUserPoolsAuthProviderAsync { 33 | public func getLatestAuthToken() -> String { fatalError("Callback method required") } 34 | } 35 | 36 | // For using Cognito User Pools based authorization, this protocol needs to be implemented and passed to configuration object. 37 | public protocol AWSCognitoUserPoolsAuthProvider: AWSOIDCAuthProvider { 38 | 39 | } 40 | 41 | // MARK: - AWSLambdaAuthProvider 42 | // For using Lambda based authorization, this protocol needs to be implemented and passed to configuration object. 43 | // Use this for cases where the authorization token needs to be fetched asynchronously and requires a callback 44 | public protocol AWSLambdaAuthProviderAsync: AWSLambdaAuthProvider { 45 | func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) 46 | } 47 | 48 | // For AWSLambdaAuthProvider that use a callback, the getLatestAuthToken is defaulted to return an empty string 49 | extension AWSLambdaAuthProviderAsync { 50 | public func getLatestAuthToken() -> String { fatalError("Callback method required") } 51 | } 52 | 53 | // For using AWS Lambda based authorization, this protocol needs to be implemented and passed to configuration object. 54 | public protocol AWSLambdaAuthProvider { 55 | /// The method should fetch the token and return it to the client for using in header request. 56 | func getLatestAuthToken() -> String 57 | } 58 | 59 | // MARK: - AWSAPIKeyAuthProvider 60 | // For using API Key based authorization, this protocol needs to be implemented and passed to configuration object. 61 | public protocol AWSAPIKeyAuthProvider { 62 | func getAPIKey() -> String 63 | } 64 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncAuthType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | /// Supported authentication types for the AppSyncClient 8 | public enum AWSAppSyncAuthType: String, Codable, Hashable { 9 | /// AWS Identity and Access Management (IAM), for role-based authentication 10 | case awsIAM = "AWS_IAM" 11 | 12 | /// A single API key for all app users 13 | case apiKey = "API_KEY" 14 | 15 | /// OpenID Connect 16 | case oidcToken = "OPENID_CONNECT" 17 | 18 | /// User directory based authentication 19 | case amazonCognitoUserPools = "AMAZON_COGNITO_USER_POOLS" 20 | 21 | case awsLambda = "AWS_LAMBDA" 22 | 23 | /// Convenience method to use instead of `AuthType(rawValue:)` 24 | public static func getAuthType(rawValue: String) throws -> AWSAppSyncAuthType { 25 | guard let authType = AWSAppSyncAuthType(rawValue: rawValue) else { 26 | throw AWSAppSyncClientConfigurationError.invalidAuthConfiguration("AuthType not recognized. Pass in a valid AuthType.") 27 | } 28 | return authType 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncClientConfigurationError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | public enum AWSAppSyncClientConfigurationError { 10 | case invalidAuthConfiguration(String) 11 | case cacheConfigurationAlreadyInUse(String) 12 | } 13 | 14 | extension AWSAppSyncClientConfigurationError: Error { 15 | var localizedDescription: String { 16 | return errorDescription ?? String(describing: self) 17 | } 18 | } 19 | 20 | extension AWSAppSyncClientConfigurationError: LocalizedError { 21 | public var errorDescription: String? { 22 | switch self { 23 | case .invalidAuthConfiguration(let message): 24 | return "Invalid Auth Configuration: \(message)" 25 | case .cacheConfigurationAlreadyInUse(let message): 26 | return "Cache Configuration Already In Use: \(message)" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncClientError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | public enum AWSAppSyncClientError: Error { 10 | case requestFailed(Data?, HTTPURLResponse?, Error?) 11 | case noData(HTTPURLResponse) 12 | case parseError(Data, HTTPURLResponse, Error?) 13 | case authenticationError(Error) 14 | } 15 | 16 | // MARK: - LocalizedError 17 | 18 | extension AWSAppSyncClientError: LocalizedError { 19 | 20 | public var errorDescription: String? { 21 | let underlyingError: Error? 22 | var message: String 23 | let errorResponse: HTTPURLResponse? 24 | 25 | switch self { 26 | case .requestFailed(_, let response, let error): 27 | errorResponse = response 28 | underlyingError = error 29 | message = "Did not receive a successful HTTP code." 30 | case .noData(let response): 31 | errorResponse = response 32 | underlyingError = nil 33 | message = "No Data received in response." 34 | case .parseError(_, let response, let error): 35 | underlyingError = error 36 | errorResponse = response 37 | message = "Could not parse response data." 38 | case .authenticationError(let error): 39 | underlyingError = error 40 | errorResponse = nil 41 | message = "Failed to authenticate request." 42 | } 43 | 44 | if let error = underlyingError { 45 | message += " Error: \(error)" 46 | } 47 | 48 | if let unwrappedResponse = errorResponse { 49 | return "(\(unwrappedResponse.statusCode) \(unwrappedResponse.statusCodeDescription)) \(message)" 50 | } else { 51 | return "\(message)" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncClientInfoError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | public struct AWSAppSyncClientInfoError { 10 | public let errorMessage: String? 11 | } 12 | 13 | extension AWSAppSyncClientInfoError: Error { 14 | public var localizedDescription: String { 15 | return errorDescription ?? errorMessage ?? String(describing: self) 16 | } 17 | } 18 | 19 | extension AWSAppSyncClientInfoError: LocalizedError { 20 | public var errorDescription: String? { 21 | return errorMessage 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncClientLogFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AWSCore 9 | 10 | public final class AWSAppSyncClientLogFormatter: NSObject, AWSDDLogFormatter { 11 | 12 | static let dateFormatter: DateFormatter = { 13 | let dateFormatter = DateFormatter() 14 | // 2019-02-27 15:09:34.624-0800 15 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSZ" 16 | return dateFormatter 17 | }() 18 | 19 | public func format(message logMessage: AWSDDLogMessage) -> String? { 20 | let logLevelPrefix: String 21 | switch logMessage.flag { 22 | case .error: 23 | logLevelPrefix = "E" 24 | case .warning: 25 | logLevelPrefix = "W" 26 | case .info: 27 | logLevelPrefix = "I" 28 | case .debug: 29 | logLevelPrefix = "D" 30 | default: 31 | logLevelPrefix = "V" 32 | } 33 | 34 | let date = AWSAppSyncClientLogFormatter.dateFormatter.string(from: logMessage.timestamp) 35 | let file = AWSDDExtractFileNameWithoutExtension(logMessage.file, false) ?? "(no file)" 36 | let line = String(describing: logMessage.line) 37 | 38 | var sourceSection = file 39 | if let function = logMessage.function { 40 | sourceSection += ".\(function)" 41 | } 42 | sourceSection += ", L\(line)" 43 | 44 | let message = "\(date) [\(logLevelPrefix) \(sourceSection)] \(logMessage.message)" 45 | return message 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncClientS3ObjectsExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AWSAppSyncS3ObjectsExtensions.swift 3 | // AWSAppSync 4 | // 5 | 6 | import Foundation 7 | 8 | extension AWSAppSyncClient { 9 | 10 | func performS3ObjectUploadForMutation( 11 | operation: Operation, 12 | s3Object: InternalS3ObjectDetails, 13 | resultHandler: @escaping (Error?) -> Void) { 14 | 15 | s3ObjectManager!.upload(s3Object: s3Object) { _, error in 16 | resultHandler(error) 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | public struct AppSyncConnectionInfo { 10 | public let isConnectionAvailable: Bool 11 | public let isInitialConnection: Bool 12 | } 13 | 14 | public enum ClientNetworkAccessState { 15 | case Online 16 | case Offline 17 | } 18 | 19 | public protocol ConnectionStateChangeHandler { 20 | func stateChanged(networkState: ClientNetworkAccessState) 21 | } 22 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncMutations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | public enum MutationRecordState: String { 10 | case inProgress 11 | case inQueue 12 | case isDone 13 | } 14 | 15 | public enum MutationType: String { 16 | case graphQLMutation 17 | case graphQLMutationWithS3Object 18 | } 19 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncRetryStrategy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | /// The retry strategy to be used by the `AWSAppSyncClient`. 10 | /// You can specify this in the `AWSAppSyncClientConfiguration`. 11 | /// 12 | /// - exponential: Backs off exponentially before retrying a HTTP request. Starts from 400ms and grows exponentially w/ jitter; stops the retries after the back off reaches 5 minutes. 13 | /// - aggressive: Aggressively retries every 1s w/ jitter for up to 12 attempts. 14 | public enum AWSAppSyncRetryStrategy { 15 | case exponential, aggressive 16 | } 17 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncServiceConfigError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | public enum AWSAppSyncServiceConfigError: String { 10 | /// An error occurred reading the configuration file, or the file did not contain a properly configured "AppSync" section 11 | case invalidConfigFile 12 | 13 | /// An error occurred loading the API endpoing URL 14 | case invalidAPIURL 15 | 16 | /// An error occurred loading the API region 17 | case invalidRegion 18 | 19 | /// An error occurred loading the auth mode 20 | case invalidAuthMode 21 | 22 | /// AuthMode was set to "API_KEY" but a valid value for API_KEY was not found 23 | case invalidAPIKey 24 | } 25 | 26 | extension AWSAppSyncServiceConfigError: Error { 27 | public var localizedDescription: String { 28 | return errorDescription ?? self.rawValue 29 | } 30 | } 31 | 32 | extension AWSAppSyncServiceConfigError: LocalizedError { 33 | public var errorDescription: String? { 34 | return self.rawValue 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncSubscriptionError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | public enum AWSAppSyncSubscriptionError: Error, LocalizedError { 10 | /// The underlying connection reported a status of "connectionError" 11 | case connectionError 12 | 13 | /// The underlying connection reported a status of "connectionRefused" 14 | case connectionRefused 15 | 16 | /// The underlying connection reported a status of "disconnected" 17 | case disconnected 18 | 19 | /// An error occurred parsing the subscription message received from the service 20 | case messageCallbackError(String) 21 | 22 | /// Some other error occurred. See associated value for details 23 | case other(Error) 24 | 25 | /// An error occurred parsing the published subscription message 26 | case parseError(Error) 27 | 28 | /// The underlying connection reported a status of "protocolError" 29 | case protocolError 30 | 31 | /// An error occurred while making the initial subscription request to AppSync, parsing its response, or 32 | /// evaluating the response's subscription info payload 33 | case setupError(String) 34 | 35 | /// The underlying MQTT client reported a status of "unknown" 36 | @available(*, deprecated, message: "Subscription is not tied with mqtt connection anymore") 37 | case unknownMQTTConnectionStatus 38 | 39 | public var errorDescription: String? { 40 | switch self { 41 | case .messageCallbackError(let message): 42 | return message 43 | case .other(let error): 44 | return error.localizedDescription 45 | case .parseError(let error): 46 | return error.localizedDescription 47 | case .setupError(let message): 48 | return message 49 | case .unknownMQTTConnectionStatus: 50 | return "MQTT status unknown" 51 | default: 52 | return "Subscription Terminated." 53 | } 54 | } 55 | 56 | public var recoverySuggestion: String? { 57 | switch self { 58 | case .other(let error as NSError): 59 | return error.localizedRecoverySuggestion 60 | case .parseError, .unknownMQTTConnectionStatus: 61 | return nil 62 | default: 63 | return "Restart subscription request." 64 | } 65 | } 66 | 67 | public var failureReason: String? { 68 | switch self { 69 | case .other(let error as NSError): 70 | return error.localizedFailureReason 71 | case .parseError, .unknownMQTTConnectionStatus: 72 | return nil 73 | case .setupError(let message): 74 | return message 75 | default: 76 | return "Disconnected from service." 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSAppSyncSubscriptionWatcherStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | /// The status of a SubscriptionWatcher 10 | public enum AWSAppSyncSubscriptionWatcherStatus { 11 | /// The subscription is in process of connecting 12 | case connecting 13 | 14 | /// The subscription has connected and is receiving events from the service 15 | case connected 16 | 17 | /// The subscription has been disconnected because of a lifecycle event or manual disconnect request 18 | case disconnected 19 | 20 | /// The subscription is in an error state. The enum's associated value will provide more details, including recovery options if available. 21 | case error(AWSAppSyncSubscriptionError) 22 | } 23 | -------------------------------------------------------------------------------- /AWSAppSyncClient/AWSNetworkTransport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AWSNetworkTransport.swift 3 | // AWSAppSyncClient 4 | // 5 | 6 | import Foundation 7 | 8 | public protocol AWSNetworkTransport: AnyObject, NetworkTransport { 9 | func send(data: Data, completionHandler: ((JSONObject?, Error?) -> Void)?) 10 | func sendSubscriptionRequest(operation: Operation, completionHandler: @escaping (JSONObject?, Error?) -> Void) throws -> Cancellable 11 | } 12 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/Apollo.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for Apollo. 4 | FOUNDATION_EXPORT double ApolloVersionNumber; 5 | 6 | //! Project version string for Apollo. 7 | FOUNDATION_EXPORT const unsigned char ApolloVersionString[]; 8 | 9 | // In this header, you should import all the public headers of your framework using statements like #import 10 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/AsynchronousOperation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class AsynchronousOperation: Operation { 4 | @objc class func keyPathsForValuesAffectingIsExecuting() -> Set { 5 | return ["state"] 6 | } 7 | 8 | @objc class func keyPathsForValuesAffectingIsFinished() -> Set { 9 | return ["state"] 10 | } 11 | 12 | enum State { 13 | case initialized 14 | case ready 15 | case executing 16 | case finished 17 | } 18 | 19 | var state: State = .initialized { 20 | willSet { 21 | willChangeValue(forKey: "state") 22 | } 23 | didSet { 24 | didChangeValue(forKey: "state") 25 | } 26 | } 27 | 28 | override var isAsynchronous: Bool { 29 | return true 30 | } 31 | 32 | override var isReady: Bool { 33 | let ready = super.isReady 34 | if ready { 35 | state = .ready 36 | } 37 | return ready 38 | } 39 | 40 | override var isExecuting: Bool { 41 | return state == .executing 42 | } 43 | 44 | override var isFinished: Bool { 45 | return state == .finished 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/Collections.swift: -------------------------------------------------------------------------------- 1 | public extension Dictionary { 2 | static func += (lhs: inout Dictionary, rhs: Dictionary) { 3 | #if swift(>=3.2) 4 | lhs.merge(rhs) { (_, new) in new } 5 | #else 6 | for (key, value) in rhs { 7 | lhs[key] = value 8 | } 9 | #endif 10 | } 11 | } 12 | 13 | extension Dictionary { 14 | init(_ entries: S) where S.Iterator.Element == Element { 15 | self = Dictionary(minimumCapacity: entries.underestimatedCount) 16 | for (key, value) in entries { 17 | self[key] = value 18 | } 19 | } 20 | } 21 | 22 | struct GroupedSequence { 23 | private(set) var keys: [Key] = [] 24 | fileprivate var groupsForKeys: [[Value]] = [] 25 | 26 | mutating func append(value: Value, forKey key: Key) -> (Int, Int) { 27 | if let index = keys.firstIndex(where: { $0 == key }) { 28 | groupsForKeys[index].append(value) 29 | return (index, groupsForKeys[index].endIndex - 1) 30 | } else { 31 | keys.append(key) 32 | groupsForKeys.append([value]) 33 | return (keys.endIndex - 1, 0) 34 | } 35 | } 36 | } 37 | 38 | extension GroupedSequence: Sequence { 39 | func makeIterator() -> GroupedSequenceIterator { 40 | return GroupedSequenceIterator(base: self) 41 | } 42 | } 43 | 44 | struct GroupedSequenceIterator: IteratorProtocol { 45 | private var base: GroupedSequence 46 | 47 | private var keyIterator: EnumeratedSequence>.Iterator 48 | 49 | init(base: GroupedSequence) { 50 | self.base = base 51 | keyIterator = base.keys.enumerated().makeIterator() 52 | } 53 | 54 | mutating func next() -> (Key, [Value])? { 55 | if let (index, key) = keyIterator.next() { 56 | let values = base.groupsForKeys[index] 57 | return (key, values) 58 | } else { 59 | return nil 60 | } 61 | } 62 | } 63 | 64 | public func unzip(_ array: [(Element1, Element2)]) -> ([Element1], [Element2]) { 65 | var array1: [Element1] = [] 66 | var array2: [Element2] = [] 67 | 68 | for element in array { 69 | array1.append(element.0) 70 | array2.append(element.1) 71 | } 72 | 73 | return (array1, array2) 74 | } 75 | 76 | public func unzip(_ array: [(Element1, Element2, Element3)]) -> ([Element1], [Element2], [Element3]) { 77 | var array1: [Element1] = [] 78 | var array2: [Element2] = [] 79 | var array3: [Element3] = [] 80 | 81 | for element in array { 82 | array1.append(element.0) 83 | array2.append(element.1) 84 | array3.append(element.2) 85 | } 86 | 87 | return (array1, array2, array3) 88 | } 89 | 90 | public func unzip(_ array: [[Element]], count: Int) -> [[Element]] { 91 | var unzippedArray: [[Element]] = Array(repeating: [], count: count) 92 | 93 | for valuesForElement in array { 94 | for (index, value) in valuesForElement.enumerated() { 95 | unzippedArray[index].append(value) 96 | } 97 | } 98 | 99 | return unzippedArray 100 | } 101 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/DataLoader.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | public final class DataLoader { 4 | public typealias BatchLoad = ([Key]) -> Promise<[Value]> 5 | typealias Load = (key: Key, fulfill: (Value) -> Void, reject: (Error) -> Void) 6 | 7 | private let queue: DispatchQueue 8 | 9 | private var batchLoad: BatchLoad 10 | 11 | private var cache: [Key: Promise] = [:] 12 | private var loads: [Load] = [] 13 | 14 | public init(_ batchLoad: @escaping BatchLoad) { 15 | queue = DispatchQueue(label: "com.apollographql.DataLoader") 16 | 17 | self.batchLoad = batchLoad 18 | } 19 | 20 | subscript(key: Key) -> Promise { 21 | if let promise = cache[key] { 22 | return promise 23 | } 24 | 25 | let promise = Promise { fulfill, reject in 26 | enqueue(load: (key, fulfill, reject)) 27 | } 28 | 29 | cache[key] = promise 30 | 31 | return promise 32 | } 33 | 34 | private func enqueue(load: Load) { 35 | queue.async { 36 | self.loads.append(load) 37 | } 38 | } 39 | 40 | func dispatch() { 41 | queue.async { 42 | let loads = self.loads 43 | 44 | if loads.isEmpty { return } 45 | 46 | self.loads = [] 47 | 48 | let keys = loads.map { $0.key } 49 | 50 | self.batchLoad(keys).andThen { values in 51 | for (load, value) in zip(loads, values) { 52 | load.fulfill(value) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLDependencyTracker.swift: -------------------------------------------------------------------------------- 1 | final class GraphQLDependencyTracker: GraphQLResultAccumulator { 2 | private var dependentKeys: Set = Set() 3 | 4 | func accept(scalar: JSONValue, info: GraphQLResolveInfo) { 5 | dependentKeys.insert(joined(path: info.cachePath)) 6 | } 7 | 8 | func acceptNullValue(info: GraphQLResolveInfo) { 9 | dependentKeys.insert(joined(path: info.cachePath)) 10 | } 11 | 12 | func accept(list: [Void], info: GraphQLResolveInfo) { 13 | dependentKeys.insert(joined(path: info.cachePath)) 14 | } 15 | 16 | func accept(fieldEntry: Void, info: GraphQLResolveInfo) { 17 | } 18 | 19 | func accept(fieldEntries: [Void], info: GraphQLResolveInfo) { 20 | } 21 | 22 | func finish(rootValue: Void, info: GraphQLResolveInfo) -> Set { 23 | return dependentKeys 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Represents an error encountered during the execution of a GraphQL operation. 4 | /// 5 | /// - SeeAlso: [The Response Format section in the GraphQL specification](https://facebook.github.io/graphql/#sec-Response-Format) 6 | public struct GraphQLError: Error { 7 | private let object: JSONObject 8 | 9 | init(_ object: JSONObject) { 10 | self.object = object 11 | } 12 | 13 | init(_ message: String) { 14 | self.init(["message": message]) 15 | } 16 | 17 | /// GraphQL servers may provide additional entries as they choose to produce more helpful or machine‐readable errors. 18 | public subscript(key: String) -> Any? { 19 | return object[key] 20 | } 21 | 22 | /// A description of the error. 23 | public var message: String { 24 | return self["message"] as! String 25 | } 26 | 27 | /// A list of locations in the requested GraphQL document associated with the error. 28 | public var locations: [Location]? { 29 | return (self["locations"] as? [JSONObject])?.map(Location.init) 30 | } 31 | 32 | /// Represents a location in a GraphQL document. 33 | public struct Location { 34 | /// The line number of a syntax element. 35 | public let line: Int 36 | /// The column number of a syntax element. 37 | public let column: Int 38 | 39 | init(_ object: JSONObject) { 40 | line = object["line"] as! Int 41 | column = object["column"] as! Int 42 | } 43 | } 44 | } 45 | 46 | extension GraphQLError: CustomStringConvertible { 47 | public var description: String { 48 | return self.message 49 | } 50 | } 51 | 52 | extension GraphQLError: LocalizedError { 53 | public var errorDescription: String? { 54 | return description 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLOperation.swift: -------------------------------------------------------------------------------- 1 | public protocol GraphQLOperation: AnyObject { 2 | static var rootCacheKey: String { get } 3 | 4 | static var operationString: String { get } 5 | static var requestString: String { get } 6 | static var operationIdentifier: String? { get } 7 | 8 | var variables: GraphQLMap? { get } 9 | 10 | associatedtype Data: GraphQLSelectionSet 11 | } 12 | 13 | public extension GraphQLOperation { 14 | static var requestString: String { 15 | return operationString 16 | } 17 | 18 | static var operationIdentifier: String? { 19 | return nil 20 | } 21 | 22 | var variables: GraphQLMap? { 23 | return nil 24 | } 25 | } 26 | 27 | public protocol GraphQLQuery: GraphQLOperation {} 28 | public extension GraphQLQuery { 29 | static var rootCacheKey: String { return "QUERY_ROOT" } 30 | } 31 | 32 | public protocol GraphQLMutation: GraphQLOperation {} 33 | public extension GraphQLMutation { 34 | static var rootCacheKey: String { return "MUTATION_ROOT" } 35 | } 36 | 37 | public protocol GraphQLSubscription: GraphQLOperation {} 38 | 39 | public extension GraphQLSubscription { 40 | static var rootCacheKey: String { return "SUBSCRIPTION_ROOT" } 41 | } 42 | 43 | public protocol GraphQLFragment: GraphQLSelectionSet { 44 | static var possibleTypes: [String] { get } 45 | } 46 | 47 | public extension GraphQLOperation { 48 | static func getResponseGraphQLSelections() -> [GraphQLSelection] { 49 | return Data.selections 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLQueryWatcher.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | /// A `GraphQLQueryWatcher` is responsible for watching the store, and calling the result handler with a new result whenever any of the data the previous result depends on changes. 4 | public final class GraphQLQueryWatcher: Cancellable, ApolloStoreSubscriber { 5 | weak var client: ApolloClient? 6 | let query: Query 7 | let handlerQueue: DispatchQueue 8 | let resultHandler: OperationResultHandler 9 | 10 | private var context = 0 11 | 12 | private weak var fetching: Cancellable? 13 | 14 | private var dependentKeys: Set? 15 | 16 | init(client: ApolloClient, query: Query, handlerQueue: DispatchQueue, resultHandler: @escaping OperationResultHandler) { 17 | self.client = client 18 | self.query = query 19 | self.handlerQueue = handlerQueue 20 | self.resultHandler = resultHandler 21 | 22 | client.store.subscribe(self) 23 | } 24 | 25 | /// Refetch a query from the server. 26 | public func refetch() { 27 | fetch(cachePolicy: .fetchIgnoringCacheData) 28 | } 29 | 30 | func fetch(cachePolicy: CachePolicy) { 31 | fetching = client?._fetch(query: query, cachePolicy: cachePolicy, context: &context, queue: handlerQueue) { (result, error) in 32 | self.dependentKeys = result?.dependentKeys 33 | self.resultHandler(result, error) 34 | } 35 | } 36 | 37 | /// Cancel any in progress fetching operations and unsubscribe from the store. 38 | public func cancel() { 39 | fetching?.cancel() 40 | client?.store.unsubscribe(self) 41 | } 42 | 43 | func store(_ store: ApolloStore, didChangeKeys changedKeys: Set, context: UnsafeMutableRawPointer?) { 44 | if context == &self.context { return } 45 | 46 | guard let dependentKeys = dependentKeys else { return } 47 | 48 | if !dependentKeys.isDisjoint(with: changedKeys) { 49 | fetch(cachePolicy: .returnCacheDataElseFetch) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLResponse.swift: -------------------------------------------------------------------------------- 1 | /// Represents a GraphQL response received from a server. 2 | public final class GraphQLResponse { 3 | public let operation: Operation 4 | public let body: JSONObject 5 | 6 | public init(operation: Operation, body: JSONObject) { 7 | self.operation = operation 8 | self.body = body 9 | } 10 | 11 | public func parseResult(cacheKeyForObject: CacheKeyForObject? = nil) throws -> Promise<(GraphQLResult, RecordSet?)> { 12 | let errors: [GraphQLError]? 13 | 14 | if let errorsEntry = body["errors"] as? [JSONObject] { 15 | errors = errorsEntry.map(GraphQLError.init) 16 | } else { 17 | errors = nil 18 | } 19 | 20 | if let dataEntry = body["data"] as? JSONObject { 21 | let executor = GraphQLExecutor { object, info in 22 | return .result(.success(object[info.responseKeyForField])) 23 | } 24 | 25 | executor.cacheKeyForObject = cacheKeyForObject 26 | 27 | let mapper = GraphQLSelectionSetMapper() 28 | let normalizer = GraphQLResultNormalizer() 29 | let dependencyTracker = GraphQLDependencyTracker() 30 | 31 | return firstly { 32 | try executor.execute(selections: Operation.Data.selections, on: dataEntry, withKey: Operation.rootCacheKey, variables: operation.variables, accumulator: zip(mapper, normalizer, dependencyTracker)) 33 | }.map { (data, records, dependentKeys) in 34 | (GraphQLResult(data: data, errors: errors, source: .server, dependentKeys: dependentKeys), records) 35 | } 36 | } else { 37 | return Promise(fulfilled: (GraphQLResult(data: nil, errors: errors, source: .server, dependentKeys: nil), nil)) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLResponseGenerator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class GraphQLResponseGenerator: GraphQLResultAccumulator { 4 | func accept(scalar: JSONValue, info: GraphQLResolveInfo) -> JSONValue { 5 | return scalar 6 | } 7 | 8 | func acceptNullValue(info: GraphQLResolveInfo) -> JSONValue { 9 | return NSNull() 10 | } 11 | 12 | func accept(list: [JSONValue], info: GraphQLResolveInfo) -> JSONValue { 13 | return list 14 | } 15 | 16 | func accept(fieldEntry: JSONValue, info: GraphQLResolveInfo) -> (key: String, value: JSONValue) { 17 | return (info.responseKeyForField, fieldEntry) 18 | } 19 | 20 | func accept(fieldEntries: [(key: String, value: JSONValue)], info: GraphQLResolveInfo) -> JSONValue { 21 | return JSONObject(fieldEntries) 22 | } 23 | 24 | func finish(rootValue: JSONValue, info: GraphQLResolveInfo) throws -> JSONObject { 25 | return rootValue as! JSONObject 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLResult.swift: -------------------------------------------------------------------------------- 1 | /// Represents the result of a GraphQL operation. 2 | public struct GraphQLResult { 3 | /// Represents source of data 4 | public enum Source { 5 | case cache 6 | case server 7 | } 8 | 9 | /// The typed result data, or `nil` if an error was encountered that prevented a valid response. 10 | public let data: Data? 11 | /// A list of errors, or `nil` if the operation completed without encountering any errors. 12 | public let errors: [GraphQLError]? 13 | /// Source of data 14 | public let source: Source 15 | 16 | let dependentKeys: Set? 17 | 18 | init(data: Data?, errors: [GraphQLError]?, source:Source, dependentKeys: Set?) { 19 | self.data = data 20 | self.errors = errors 21 | self.dependentKeys = dependentKeys 22 | self.source = source 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLResultNormalizer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class GraphQLResultNormalizer: GraphQLResultAccumulator { 4 | private var records: RecordSet = [:] 5 | 6 | func accept(scalar: JSONValue, info: GraphQLResolveInfo) -> JSONValue { 7 | return scalar 8 | } 9 | 10 | func acceptNullValue(info: GraphQLResolveInfo) -> JSONValue { 11 | return NSNull() 12 | } 13 | 14 | func accept(list: [JSONValue], info: GraphQLResolveInfo) -> JSONValue { 15 | return list 16 | } 17 | 18 | func accept(fieldEntry: JSONValue, info: GraphQLResolveInfo) -> (key: String, value: JSONValue) { 19 | return (info.cacheKeyForField, fieldEntry) 20 | } 21 | 22 | func accept(fieldEntries: [(key: String, value: JSONValue)], info: GraphQLResolveInfo) throws -> JSONValue { 23 | let cachePath = joined(path: info.cachePath) 24 | 25 | let object = JSONObject(fieldEntries) 26 | records.merge(record: Record(key: cachePath, object)) 27 | 28 | return Reference(key: cachePath) 29 | } 30 | 31 | func finish(rootValue: JSONValue, info: GraphQLResolveInfo) throws -> RecordSet { 32 | return records 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/GraphQLSelectionSetMapper.swift: -------------------------------------------------------------------------------- 1 | final class GraphQLSelectionSetMapper: GraphQLResultAccumulator { 2 | func accept(scalar: JSONValue, info: GraphQLResolveInfo) throws -> Any? { 3 | guard case .scalar(let decodable) = info.fields[0].type.namedType else { preconditionFailure() } 4 | // This will convert a JSON value to the expected value type, which could be a custom scalar or an enum. 5 | return try decodable.init(jsonValue: scalar) 6 | } 7 | 8 | func acceptNullValue(info: GraphQLResolveInfo) -> Any? { 9 | return nil 10 | } 11 | 12 | func accept(list: [Any?], info: GraphQLResolveInfo) -> Any? { 13 | return list 14 | } 15 | 16 | func accept(fieldEntry: Any?, info: GraphQLResolveInfo) -> (key: String, value: Any?) { 17 | return (info.responseKeyForField, fieldEntry) 18 | } 19 | 20 | func accept(fieldEntries: [(key: String, value: Any?)], info: GraphQLResolveInfo) throws -> Snapshot { 21 | return Snapshot(fieldEntries) 22 | } 23 | 24 | func finish(rootValue: Snapshot, info: GraphQLResolveInfo) -> SelectionSet { 25 | return SelectionSet.init(snapshot: rootValue) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/InMemoryNormalizedCache.swift: -------------------------------------------------------------------------------- 1 | public final class InMemoryNormalizedCache: NormalizedCache { 2 | private var records: RecordSet 3 | 4 | public init(records: RecordSet = RecordSet()) { 5 | if records.isEmpty { 6 | self.records = InMemoryNormalizedCache.emptyQueryRootRecords() 7 | } else { 8 | self.records = records 9 | } 10 | } 11 | 12 | public func loadRecords(forKeys keys: [CacheKey]) -> Promise<[Record?]> { 13 | let records = keys.map { self.records[$0] } 14 | return Promise(fulfilled: records) 15 | } 16 | 17 | public func merge(records: RecordSet) -> Promise> { 18 | return Promise(fulfilled: self.records.merge(records: records)) 19 | } 20 | 21 | public func clear() -> Promise { 22 | records.clear() 23 | self.records = InMemoryNormalizedCache.emptyQueryRootRecords() 24 | return Promise(fulfilled: ()) 25 | } 26 | 27 | private static func emptyQueryRootRecords() -> RecordSet { 28 | // Prepopulate the InMemoryNormalizedCache record set with an empty QUERY_ROOT, to allow optimistic 29 | // updates against empty caches to succeed. Otherwise, such an operation will fail with a "missingValue" 30 | // error (#92) 31 | let emptyQueryRootRecord = Record(key: AWSAppSyncClient.EmptyQuery.rootCacheKey, [:]) 32 | return RecordSet(records: [emptyQueryRootRecord]) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(CURRENT_PROJECT_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/JSON.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public typealias JSONValue = Any 4 | 5 | public typealias JSONObject = [String: JSONValue] 6 | 7 | public protocol JSONDecodable { 8 | init(jsonValue value: JSONValue) throws 9 | } 10 | 11 | public protocol JSONEncodable: GraphQLInputValue { 12 | var jsonValue: JSONValue { get } 13 | } 14 | 15 | public enum JSONDecodingError: Error, LocalizedError { 16 | case missingValue 17 | case nullValue 18 | case wrongType 19 | case couldNotConvert(value: Any, to: Any.Type) 20 | 21 | public var errorDescription: String? { 22 | switch self { 23 | case .missingValue: 24 | return "Missing value" 25 | case .nullValue: 26 | return "Unexpected null value" 27 | case .wrongType: 28 | return "Wrong type" 29 | case .couldNotConvert(let value, let expectedType): 30 | return "Could not convert \"\(value)\" to \(expectedType)" 31 | } 32 | } 33 | } 34 | 35 | extension JSONDecodingError: Matchable { 36 | public typealias Base = Error 37 | public static func ~=(pattern: JSONDecodingError, value: Error) -> Bool { 38 | guard let value = value as? JSONDecodingError else { 39 | return false 40 | } 41 | 42 | switch (value, pattern) { 43 | case (.missingValue, .missingValue), (.nullValue, .nullValue), (.couldNotConvert, .couldNotConvert): 44 | return true 45 | default: 46 | return false 47 | } 48 | } 49 | } 50 | 51 | // MARK: Helpers 52 | 53 | func equals(_ lhs: Any, _ rhs: Any) -> Bool { 54 | if let lhs = lhs as? Reference, let rhs = rhs as? Reference { 55 | return lhs == rhs 56 | } 57 | 58 | let lhs = lhs as AnyObject, rhs = rhs as AnyObject 59 | return lhs.isEqual(rhs) 60 | } 61 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/JSONSerializationFormat.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public final class JSONSerializationFormat { 4 | public class func serialize(value: JSONEncodable) throws -> Data { 5 | return try JSONSerialization.data(withJSONObject: value.jsonValue, options: []) 6 | } 7 | 8 | public class func deserialize(data: Data) throws -> JSONValue { 9 | return try JSONSerialization.jsonObject(with: data, options: []) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/Locking.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class Mutex { 4 | private var _lock = pthread_mutex_t() 5 | 6 | init() { 7 | let result = pthread_mutex_init(&_lock, nil) 8 | assert(result == 0) 9 | } 10 | 11 | deinit { 12 | let result = pthread_mutex_destroy(&_lock) 13 | assert(result == 0) 14 | } 15 | 16 | func lock() { 17 | let result = pthread_mutex_lock(&_lock) 18 | assert(result == 0) 19 | } 20 | 21 | func unlock() { 22 | let result = pthread_mutex_unlock(&_lock) 23 | assert(result == 0) 24 | } 25 | 26 | func withLock(_ body: () throws -> T) rethrows -> T { 27 | lock() 28 | defer { unlock() } 29 | return try body() 30 | } 31 | } 32 | 33 | final class ReadWriteLock { 34 | private var _lock = pthread_rwlock_t() 35 | 36 | init() { 37 | let status = pthread_rwlock_init(&_lock, nil) 38 | assert(status == 0) 39 | } 40 | 41 | deinit { 42 | let status = pthread_rwlock_destroy(&_lock) 43 | assert(status == 0) 44 | } 45 | 46 | func lockForReading() { 47 | pthread_rwlock_rdlock(&_lock) 48 | } 49 | 50 | func lockForWriting() { 51 | pthread_rwlock_wrlock(&_lock) 52 | } 53 | 54 | func unlock() { 55 | pthread_rwlock_unlock(&_lock) 56 | } 57 | 58 | func withReadLock(_ body: () throws -> T) rethrows -> T { 59 | lockForReading() 60 | defer { unlock() } 61 | return try body() 62 | } 63 | 64 | func withWriteLock(_ body: () throws -> T) rethrows -> T { 65 | lockForWriting() 66 | defer { unlock() } 67 | return try body() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/NetworkTransport.swift: -------------------------------------------------------------------------------- 1 | /// A network transport is responsible for sending GraphQL operations to a server. 2 | public protocol NetworkTransport { 3 | /// Send a GraphQL operation to a server and return a response. 4 | /// 5 | /// - Parameters: 6 | /// - operation: The operation to send. 7 | /// - completionHandler: A closure to call when a request completes. 8 | /// - response: The response received from the server, or `nil` if an error occurred. 9 | /// - error: An error that indicates why a request failed, or `nil` if the request was succesful. 10 | /// - Returns: An object that can be used to cancel an in progress request. 11 | func send(operation: Operation, completionHandler: @escaping (_ response: GraphQLResponse?, _ error: Error?) -> Void) -> Cancellable 12 | } 13 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/NormalizedCache.swift: -------------------------------------------------------------------------------- 1 | public protocol NormalizedCache { 2 | 3 | /// Loads records corresponding to the given keys. 4 | /// - returns: A promise that fulfills with an array, with each index containing either the 5 | /// record corresponding to the key at that index or nil if not found. 6 | func loadRecords(forKeys keys: [CacheKey]) -> Promise<[Record?]> 7 | 8 | /// Merges a set of records into the cache. 9 | /// - returns: A promise that fulfills with a set of keys corresponding to *fields* that have 10 | /// changed (i.e. QUERY_ROOT.Foo.myField). These are the same type of keys as are 11 | /// returned by RecordSet.merge(records:). 12 | func merge(records: RecordSet) -> Promise> 13 | 14 | // Clears all records 15 | func clear() -> Promise 16 | } 17 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/Record.swift: -------------------------------------------------------------------------------- 1 | /// A cache key for a record. 2 | public typealias CacheKey = String 3 | 4 | /// A cache record. 5 | public struct Record { 6 | public let key: CacheKey 7 | 8 | public typealias Value = Any 9 | public typealias Fields = [CacheKey: Value] 10 | public private(set) var fields: Fields 11 | 12 | public init(key: CacheKey, _ fields: Fields = [:]) { 13 | self.key = key 14 | self.fields = fields 15 | } 16 | 17 | public subscript(key: CacheKey) -> Value? { 18 | get { 19 | return fields[key] 20 | } 21 | set { 22 | fields[key] = newValue 23 | } 24 | } 25 | } 26 | 27 | extension Record: CustomStringConvertible { 28 | public var description: String { 29 | return "#\(key) -> \(fields)" 30 | } 31 | } 32 | 33 | /// A reference to a cache record. 34 | public struct Reference { 35 | public let key: CacheKey 36 | 37 | public init(key: CacheKey) { 38 | self.key = key 39 | } 40 | } 41 | 42 | extension Reference: Equatable { 43 | public static func ==(lhs: Reference, rhs: Reference) -> Bool { 44 | return lhs.key == rhs.key 45 | } 46 | } 47 | 48 | extension Reference: CustomStringConvertible { 49 | public var description: String { 50 | return "-> #\(key)" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/RecordSet.swift: -------------------------------------------------------------------------------- 1 | /// A set of cache records. 2 | public struct RecordSet { 3 | public private(set) var storage: [CacheKey: Record] = [:] 4 | 5 | public init(records: S) where S.Iterator.Element == Record { 6 | insert(contentsOf: records) 7 | } 8 | 9 | public mutating func insert(_ record: Record) { 10 | storage[record.key] = record 11 | } 12 | 13 | public mutating func clear() { 14 | storage.removeAll() 15 | } 16 | 17 | public mutating func insert(contentsOf records: S) where S.Iterator.Element == Record { 18 | for record in records { 19 | insert(record) 20 | } 21 | } 22 | 23 | public subscript(key: CacheKey) -> Record? { 24 | return storage[key] 25 | } 26 | 27 | public var isEmpty: Bool { 28 | return storage.isEmpty 29 | } 30 | 31 | public var keys: [CacheKey] { 32 | return Array(storage.keys) 33 | } 34 | 35 | @discardableResult public mutating func merge(records: RecordSet) -> Set { 36 | var changedKeys: Set = Set() 37 | 38 | for (_, record) in records.storage { 39 | changedKeys.formUnion(merge(record: record)) 40 | } 41 | 42 | return changedKeys 43 | } 44 | 45 | @discardableResult public mutating func merge(record: Record) -> Set { 46 | if var oldRecord = storage.removeValue(forKey: record.key) { 47 | var changedKeys: Set = Set() 48 | 49 | for (key, value) in record.fields { 50 | if let oldValue = oldRecord.fields[key], equals(oldValue, value) { 51 | continue 52 | } 53 | oldRecord[key] = value 54 | changedKeys.insert([record.key, key].joined(separator: ".")) 55 | } 56 | storage[record.key] = oldRecord 57 | return changedKeys 58 | } else { 59 | storage[record.key] = record 60 | return Set(record.fields.keys.map { [record.key, $0].joined(separator: ".") }) 61 | } 62 | } 63 | } 64 | 65 | extension RecordSet: ExpressibleByDictionaryLiteral { 66 | public init(dictionaryLiteral elements: (CacheKey, Record.Fields)...) { 67 | self.init(records: elements.map { Record(key: $0.0, $0.1) }) 68 | } 69 | } 70 | 71 | extension RecordSet: CustomStringConvertible { 72 | public var description: String { 73 | return String(describing: Array(storage.values)) 74 | } 75 | } 76 | 77 | extension RecordSet: CustomPlaygroundDisplayConvertible { 78 | public var playgroundDescription: Any { 79 | return description 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/Result.swift: -------------------------------------------------------------------------------- 1 | public enum Result { 2 | case success(Value) 3 | case failure(Error) 4 | } 5 | 6 | extension Result: CustomStringConvertible { 7 | public var description: String { 8 | switch self { 9 | case .success(let value): 10 | return "Success(\(value))" 11 | case .failure(let error): 12 | return "Error(\(error))" 13 | } 14 | } 15 | } 16 | 17 | extension Result { 18 | var value: Value? { 19 | switch self { 20 | case .success(let value): 21 | return value 22 | case .failure(_): 23 | return nil 24 | } 25 | } 26 | 27 | var error: Error? { 28 | switch self { 29 | case .success(_): 30 | return nil 31 | case .failure(let error): 32 | return error 33 | } 34 | } 35 | 36 | func valueOrError() throws -> Value { 37 | switch self { 38 | case .success(let value): 39 | return value 40 | case .failure(let error): 41 | throw error 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Apollo/Sources/Apollo/Utilities.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension HTTPURLResponse { 4 | @objc var isSuccessful: Bool { 5 | return (200..<300).contains(statusCode) 6 | } 7 | 8 | @objc var statusCodeDescription: String { 9 | return HTTPURLResponse.localizedString(forStatusCode: statusCode) 10 | } 11 | 12 | var textEncoding: String.Encoding? { 13 | guard let encodingName = textEncodingName else { return nil } 14 | 15 | return String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))) 16 | } 17 | } 18 | 19 | public protocol Matchable { 20 | associatedtype Base 21 | static func ~=(pattern: Self, value: Base) -> Bool 22 | } 23 | 24 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.7.1 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/AWSAppSyncSubscriptionMetadataCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import SQLite 9 | import struct SQLite.Expression 10 | 11 | final class AWSSubscriptionMetaDataCache { 12 | 13 | private let db: Connection 14 | private let subscriptionMetadataRecords = Table("subscription_metadata") 15 | private let operationHash = Expression("operationHash") 16 | private let lastSyncDate = Expression("lastSyncDate") 17 | 18 | init(fileURL: URL) throws { 19 | AppSyncLog.verbose("Initializing subscription metadata cache at \(fileURL.absoluteString)") 20 | db = try Connection(.uri(fileURL.absoluteString), readonly: false) 21 | db.busyTimeout = sqlBusyTimeoutConstant 22 | try createTableIfNeeded() 23 | } 24 | 25 | private func createTableIfNeeded() throws { 26 | try db.run(subscriptionMetadataRecords.create(ifNotExists: true) { table in 27 | table.column(operationHash, primaryKey: true) 28 | table.column(lastSyncDate) 29 | }) 30 | } 31 | 32 | internal func updateLastSyncTime(for operationHash: String, with lastSyncTime: Date) throws { 33 | let sqlRecord = subscriptionMetadataRecords.filter(self.operationHash == operationHash) 34 | let recordCount = try db.scalar(sqlRecord.count) 35 | 36 | guard recordCount == 0 else { 37 | try db.run(sqlRecord.update(self.operationHash <- operationHash, self.lastSyncDate <- lastSyncTime)) 38 | return 39 | } 40 | 41 | let insert = subscriptionMetadataRecords.insert(self.lastSyncDate <- lastSyncTime, 42 | self.operationHash <- operationHash) 43 | try db.run(insert) 44 | } 45 | 46 | internal func getLastSyncTime(operationHash: String) throws -> Date? { 47 | let sqlRecord = subscriptionMetadataRecords.filter(self.operationHash == operationHash) 48 | let recordCount = try db.scalar(sqlRecord.count) 49 | 50 | guard recordCount != 0 else { 51 | return nil 52 | } 53 | 54 | var syncDate: Date? 55 | for record in try db.prepare(sqlRecord) { 56 | syncDate = try record.get(lastSyncDate) 57 | } 58 | return syncDate 59 | } 60 | 61 | internal func clear() throws { 62 | try db.run("DELETE FROM subscription_metadata") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/AWSConfigurationFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | /// Describes the shape of the 'AppSync' section of the `awsconfiguration.json` file 10 | struct AWSConfigurationFile { 11 | static let fileName = "awsconfiguration.json" 12 | 13 | struct Keys { 14 | /// The root of the AppSync configuration section 15 | static let root = "AppSync" 16 | 17 | /// The endpoint URL for the AppSync API described by this configuration 18 | static let apiURL = "ApiUrl" 19 | 20 | /// The AWS region of the AppSync API. The value must be a string that can be resolved to an `AWSRegionType` 21 | static let region = "Region" 22 | 23 | /// The Auth mode of the API. The value must be a string that is a raw value of the `AWSAppSyncAuthType` enum 24 | static let authMode = "AuthMode" 25 | 26 | /// If the `authMode` value is "API_KEY", this value should be filled in with a valid value. If not, then the AppSync 27 | /// constructors must be provided with an already-configured apiKeyProvider. 28 | static let apiKey = "ApiKey" 29 | 30 | /// This prefix is used to partition the caches and on-disk stores used by the client. Changing this value will 31 | /// orphan resources created by previous instances of the client. 32 | static let clientDatabasePrefix = "ClientDatabasePrefix" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/AWSMutationRetryAdviceHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | final class AWSMutationRetryAdviceHelper { 10 | 11 | /// This method is a special retry evaluator currently only used for mutations.It is responsible to identify if the error is caused due to internet 12 | /// not being available or appsync hosts not reachable from the client. It evaluates it by checking for error codes in NSURLErrorDomain. 13 | /// We have an additional HTTP layer retry handleer which is responsible to parse and retry errors which occur at HTTP layer(5XX, 429 status codes.) 14 | /// We would ideally want to have a single retry layer which can account for these multiple use-cases and suggest retry advice. 15 | /// See [PR #223](https://github.com/awslabs/aws-mobile-appsync-sdk-ios/pull/233) for more details on the discussion. 16 | /// 17 | /// - Parameter error: error being evaluated 18 | /// - Returns: true if the error is retriable. 19 | static func isRetriableNetworkError(error: Error) -> Bool { 20 | if let appsyncError = error as? AWSAppSyncClientError { 21 | switch appsyncError { 22 | case .authenticationError(let authError): 23 | // We are currently checking for this error due to IAM auth. 24 | // If Cognito Identity SDK does not have an identity id available, 25 | // It tries to get one before giving the callback to appsync SDK. 26 | // If Cognito Identity SDK cannot reach the service to fetch identityd id, 27 | // it will propogate the error it encoutered to AppSync. We specifically 28 | // check if the error is of type internet not available and then retry. 29 | return isErrorURLDomainError(error: authError) 30 | case .requestFailed(_, _, let urlError): 31 | if let urlError = urlError { 32 | return isErrorURLDomainError(error: urlError) 33 | } 34 | default: 35 | break 36 | } 37 | } else { 38 | return isErrorURLDomainError(error: error) 39 | } 40 | return false 41 | } 42 | 43 | /// We evaluate the error against known error codes which could result due to unavailable internet or spotty network connection. 44 | private static func isErrorURLDomainError(error: Error) -> Bool { 45 | let nsError = error as NSError 46 | guard nsError.domain == NSURLErrorDomain else { 47 | return false 48 | } 49 | switch nsError.code { 50 | case NSURLErrorNotConnectedToInternet, 51 | NSURLErrorDNSLookupFailed, 52 | NSURLErrorCannotConnectToHost, 53 | NSURLErrorCannotFindHost, 54 | NSURLErrorTimedOut: 55 | return true 56 | default: 57 | break 58 | } 59 | return false 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/AWSMutationState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | /// Determines the next step in a mutation operation. 10 | /// 11 | /// - unknown: when the next step of mutation is not determined yet 12 | /// - s3Upload: the mutation is required to do a s3 upload before the graphql call 13 | /// - graphqlOperation: the mutation to complete needs to make a graphql call 14 | enum MutationState { 15 | case unknown, s3Upload, graphqlOperation 16 | } 17 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/AWSOfflineMutation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | final class AWSAppSyncMutationRecord { 10 | var jsonRecord: JSONObject? 11 | var data: Data? 12 | var contentMap: GraphQLMap? 13 | var recordIdentifier: String 14 | var recordState: MutationRecordState = .inQueue 15 | var timestamp: Date 16 | var type: MutationType 17 | var s3ObjectInput: InternalS3ObjectDetails? 18 | var operationString: String? 19 | 20 | init( 21 | recordIdentifier: String = UUID().uuidString, 22 | timestamp: Date = Date(), 23 | type: MutationType = .graphQLMutation) { 24 | self.recordIdentifier = recordIdentifier 25 | self.timestamp = timestamp 26 | self.type = type 27 | } 28 | } 29 | 30 | // MARK: - CustomStringConvertible 31 | 32 | extension AWSAppSyncMutationRecord: CustomStringConvertible { 33 | 34 | var description: String { 35 | var desc: String = "<\(self):\(recordIdentifier)" 36 | desc.append("\tID: \(recordIdentifier)") 37 | desc.append("\ttimestamp: \(timestamp)") 38 | desc.append("\thasS3Object: \(s3ObjectInput != nil ? true : false)") 39 | desc.append(">") 40 | 41 | return desc 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/AWSRequestBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | final class AWSRequestBuilder { 10 | 11 | /// Given a `GraphQLMap` (e.g., parameters to a mutation, or an input type for a mutation), inspects the graph 12 | /// to find a set of variables that can be cast to an S3InputObject. This currently only supports one S3Object per 13 | /// GraphQLMap. The behavior of maps containing multiple S3 objects is undefined. 14 | static func s3Object(from variables: GraphQLMap?) -> InternalS3ObjectDetails? { 15 | guard let variables = variables else { 16 | return nil 17 | } 18 | 19 | var builder = InternalS3ObjectDetailsBuilder() 20 | 21 | for (key, value) in variables { 22 | guard let value = value else { 23 | continue 24 | } 25 | 26 | if let nestedMap = value as? GraphQLMapConvertible { 27 | if let s3Object = s3Object(from: nestedMap.graphQLMap) { 28 | return s3Object 29 | } 30 | } 31 | 32 | builder.offer(key: key, value: value) 33 | } 34 | 35 | return builder.build() 36 | } 37 | 38 | static func requestBody( 39 | from operation: Operation) -> GraphQLMap { 40 | return [ 41 | "query": type(of: operation).requestString, 42 | "variables": operation.variables] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/AppSyncLogHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AWSCore 9 | import AppSyncRealTimeClient 10 | 11 | struct AppSyncLogHelper { 12 | 13 | public static func shouldLog(flag: AWSDDLogFlag) -> Bool { 14 | let shouldLog = flag.rawValue & AWSDDLog.sharedInstance.logLevel.rawValue 15 | return shouldLog != 0 16 | } 17 | 18 | public static func log(_ message: String, 19 | flag: AWSDDLogFlag, 20 | file: String, 21 | function: String, 22 | line: UInt) { 23 | AWSDDLog.log(asynchronous: true, 24 | level: AWSDDLog.sharedInstance.logLevel, 25 | flag: flag, 26 | context: 0, 27 | file: file.cString(using: .utf8)!, 28 | function: function.cString(using: .utf8)!, 29 | line: line, 30 | tag: nil, 31 | format: message, 32 | arguments: getVaList([])) 33 | } 34 | 35 | static var subscriptionLogLevel: AppSyncRealTimeClient.LogLevel { 36 | switch AWSDDLog.sharedInstance.logLevel { 37 | case .off, .error: 38 | return .error 39 | case .warning: 40 | return .warn 41 | case .info: 42 | return .info 43 | case .debug: 44 | return .debug 45 | case .verbose, .all: 46 | return .verbose 47 | @unknown default: 48 | return .error 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/AppSyncLogWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | import AWSCore 7 | 8 | final class AppSyncLog { 9 | class func verbose(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) { 10 | log(message(), flag: .verbose, file: file, function: function, line: line) 11 | } 12 | 13 | class func debug(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) { 14 | log(message(), flag: .debug, file: file, function: function, line: line) 15 | } 16 | 17 | class func info(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) { 18 | log(message(), flag: .info, file: file, function: function, line: line) 19 | } 20 | 21 | class func warn(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) { 22 | log(message(), flag: .warning, file: file, function: function, line: line) 23 | } 24 | 25 | class func error(_ message: @autoclosure () -> String, file: String = #file, function: String = #function, line: Int = #line) { 26 | log(message(), flag: .error, file: file, function: function, line: line) 27 | } 28 | 29 | class func error(_ error: Error, file: String = #file, function: String = #function, line: Int = #line) { 30 | log(error.localizedDescription, flag: .error, file: file, function: function, line: line) 31 | } 32 | 33 | private class func log(_ message: @autoclosure () -> String, flag: AWSDDLogFlag, file: String, function: String, line: Int) { 34 | if AppSyncLogHelper.shouldLog(flag: flag) { 35 | AppSyncLogHelper.log(message(), 36 | flag: flag, 37 | file: file, 38 | function: function, 39 | line: UInt(line)) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/BasicAWSAPIKeyAuthProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | class BasicAWSAPIKeyAuthProvider: AWSAPIKeyAuthProvider { 8 | var apiKey: String 9 | 10 | init(key: String) { 11 | apiKey = key 12 | } 13 | 14 | func getAPIKey() -> String { 15 | return apiKey 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/Foundation+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | /// Allows use of `isEmpty` on optional `Collection`s: 10 | /// let optionalString: String? = getSomeOptionalString() 11 | /// guard optionalString.isEmpty else { return } 12 | /// 13 | /// `Collection` provides the `isEmpty` property to declare whether an instance has any members. But it’s also pretty common to 14 | /// expand the definition of “empty” to include nil. Unfortunately, the standard library doesn't include an extension mapping 15 | /// the Collection.isEmpty property, so testing Optional collections means you have to unwrap: 16 | /// 17 | /// var optionalString: String? 18 | /// // Do some work 19 | /// if let s = optionalString where s != "" { 20 | /// // s is not empty or nil 21 | /// } 22 | /// 23 | /// Or slightly more succinctly, use the nil coalescing operator “??”: 24 | /// 25 | /// if !(optionalString ?? "").isEmpty { 26 | /// // optionalString is not empty or nil 27 | /// } 28 | /// 29 | /// This extension simply unwraps the `Optional` and returns the value of `isEmpty` for non-nil collections, and returns `true` 30 | /// if the collection is nil. 31 | extension Optional where Wrapped: Collection { 32 | /// Returns `true` for nil values, or `value.isEmpty` for non-nil values. 33 | var isEmpty: Bool { 34 | switch self { 35 | case .some(let val): 36 | return val.isEmpty 37 | case .none: 38 | return true 39 | } 40 | } 41 | } 42 | 43 | extension DispatchSource { 44 | /// Convenience function to encapsulate creation of a one-off DispatchSourceTimer for different versions of Swift 45 | /// 46 | /// - Parameters: 47 | /// - interval: The future DispatchInterval at which to fire the timer 48 | /// - queue: The queue on which the timer should perform its block 49 | /// - block: The block to invoke when the timer is fired 50 | /// - Returns: The unstarted timer 51 | static func makeOneOffDispatchSourceTimer(interval: DispatchTimeInterval, queue: DispatchQueue, block: @escaping () -> Void ) -> DispatchSourceTimer { 52 | let deadline = DispatchTime.now() + interval 53 | return makeOneOffDispatchSourceTimer(deadline: deadline, queue: queue, block: block) 54 | } 55 | 56 | /// Convenience function to encapsulate creation of a one-off DispatchSourceTimer for different versions of Swift 57 | /// - Parameters: 58 | /// - deadline: The time to fire the timer 59 | /// - queue: The queue on which the timer should perform its block 60 | /// - block: The block to invoke when the timer is fired 61 | static func makeOneOffDispatchSourceTimer(deadline: DispatchTime, queue: DispatchQueue, block: @escaping () -> Void ) -> DispatchSourceTimer { 62 | let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: 0), queue: queue) 63 | #if swift(>=4) 64 | timer.schedule(deadline: deadline) 65 | #else 66 | timer.scheduleOneshot(deadline: deadline) 67 | #endif 68 | timer.setEventHandler(handler: block) 69 | return timer 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SQLiteSerialization.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import SQLite 9 | 10 | final class SQLiteSerialization { 11 | private static let serializedReferenceKey = "$reference" 12 | 13 | static func serialize(fields: Record.Fields) throws -> Data { 14 | var objectToSerialize = JSONObject() 15 | for (key, value) in fields { 16 | objectToSerialize[key] = try serialize(fieldValue: value) 17 | } 18 | return try JSONSerialization.data(withJSONObject: objectToSerialize, options: []) 19 | } 20 | 21 | private static func serialize(fieldValue: Record.Value) throws -> JSONValue { 22 | switch fieldValue { 23 | case let reference as Reference: 24 | return [serializedReferenceKey: reference.key] 25 | case let array as [Record.Value]: 26 | return try array.map { try serialize(fieldValue: $0) } 27 | default: 28 | return fieldValue 29 | } 30 | } 31 | 32 | static func deserialize(data: Data) throws -> Record.Fields { 33 | let object = try JSONSerialization.jsonObject(with: data, options: []) 34 | guard let jsonObject = object as? JSONObject else { 35 | throw AWSAppSyncQueriesCacheError.invalidRecordShape(object: object) 36 | } 37 | var fields = Record.Fields() 38 | for (key, value) in jsonObject { 39 | fields[key] = try deserialize(fieldJSONValue: value) 40 | } 41 | return fields 42 | } 43 | 44 | private static func deserialize(fieldJSONValue: JSONValue) throws -> Record.Value { 45 | switch fieldJSONValue { 46 | case let dictionary as JSONObject: 47 | guard let reference = dictionary[serializedReferenceKey] as? String else { 48 | return fieldJSONValue 49 | } 50 | return Reference(key: reference) 51 | case let array as [JSONValue]: 52 | return try array.map { try deserialize(fieldJSONValue: $0) } 53 | default: 54 | return fieldJSONValue 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SubscriptionFactory/APIKeyBasedConnectionPool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AppSyncRealTimeClient 9 | 10 | class APIKeyBasedConnectionPool: SubscriptionConnectionPool { 11 | 12 | private let apiKeyProvider: AWSAPIKeyAuthProvider 13 | var endPointToProvider: [String: ConnectionProvider] 14 | 15 | private let queue = DispatchQueue( 16 | label: "com.amazonaws.connectionPool.APIKeyBased.concurrentQueue", 17 | attributes: .concurrent, 18 | target: .global( 19 | qos: .userInitiated 20 | ) 21 | ) 22 | 23 | init(_ apiKeyProvider: AWSAPIKeyAuthProvider) { 24 | self.apiKeyProvider = apiKeyProvider 25 | self.endPointToProvider = [:] 26 | } 27 | 28 | func connection(for url: URL, connectionType: SubscriptionConnectionType) -> SubscriptionConnection { 29 | queue.sync(flags: .barrier) { 30 | let connectionProvider = endPointToProvider[url.absoluteString] ?? 31 | ConnectionProviderFactory.createConnectionProvider(for: URLRequest(url: url), 32 | authInterceptor: APIKeyAuthInterceptor(apiKeyProvider.getAPIKey()), 33 | connectionType: connectionType) 34 | endPointToProvider[url.absoluteString] = connectionProvider 35 | let connection = AppSyncSubscriptionConnection(provider: connectionProvider) 36 | return connection 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SubscriptionFactory/AppSyncRealTimeClientOIDCAuthProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AppSyncRealTimeClient 9 | 10 | class AppSyncRealTimeClientOIDCAuthProvider: OIDCAuthProvider { 11 | 12 | let authProvider: AWSOIDCAuthProvider 13 | init(authProvider: AWSOIDCAuthProvider) { 14 | self.authProvider = authProvider 15 | } 16 | 17 | func getLatestAuthToken() -> Swift.Result { 18 | var jwtToken: String? 19 | var authError: Error? 20 | 21 | if let asyncAuthProvider = authProvider as? AWSCognitoUserPoolsAuthProviderAsync { 22 | let semaphore = DispatchSemaphore(value: 0) 23 | asyncAuthProvider.getLatestAuthToken { (token, error) in 24 | jwtToken = token 25 | authError = error 26 | semaphore.signal() 27 | } 28 | semaphore.wait() 29 | 30 | if let error = authError { 31 | return .failure(error) 32 | } 33 | 34 | if let token = jwtToken { 35 | return .success(token) 36 | } 37 | } 38 | 39 | if let asyncAuthProvider = authProvider as? AWSOIDCAuthProviderAsync { 40 | let semaphore = DispatchSemaphore(value: 0) 41 | asyncAuthProvider.getLatestAuthToken { (token, error) in 42 | jwtToken = token 43 | authError = error 44 | semaphore.signal() 45 | } 46 | semaphore.wait() 47 | if let error = authError { 48 | return .failure(error) 49 | } 50 | 51 | if let token = jwtToken { 52 | return .success(token) 53 | } 54 | } 55 | 56 | return .success(authProvider.getLatestAuthToken()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SubscriptionFactory/IAMBasedConnectionPool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AppSyncRealTimeClient 9 | import AWSCore 10 | 11 | class IAMBasedConnectionPool: SubscriptionConnectionPool { 12 | 13 | private let credentialProvider: AWSCredentialsProvider 14 | private let regionType: AWSRegionType 15 | var endPointToProvider: [String: ConnectionProvider] 16 | 17 | private let queue = DispatchQueue(label: "com.amazonaws.connectionPool.IAMBased.concurrentQueue", 18 | attributes: .concurrent, 19 | target: .global(qos: .userInitiated)) 20 | 21 | init(_ credentialProvider: AWSCredentialsProvider, region: AWSRegionType) { 22 | self.credentialProvider = credentialProvider 23 | self.regionType = region 24 | self.endPointToProvider = [:] 25 | } 26 | 27 | func connection(for url: URL, connectionType: SubscriptionConnectionType) -> SubscriptionConnection { 28 | queue.sync(flags: .barrier) { 29 | let connectionProvider = endPointToProvider[url.absoluteString] ?? 30 | ConnectionProviderFactory.createConnectionProvider(for: URLRequest(url: url), 31 | authInterceptor: IAMAuthInterceptor(credentialProvider, 32 | region: regionType), 33 | connectionType: connectionType) 34 | 35 | endPointToProvider[url.absoluteString] = connectionProvider 36 | let connection = AppSyncSubscriptionConnection(provider: connectionProvider) 37 | return connection 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SubscriptionFactory/LambdaBasedConnectionPool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AppSyncRealTimeClient 9 | 10 | class LambdaBasedConnectionPool: SubscriptionConnectionPool { 11 | 12 | private let tokenProvider: AWSLambdaAuthProvider 13 | var endPointToProvider: [String: ConnectionProvider] 14 | 15 | init(_ tokenProvider: AWSLambdaAuthProvider) { 16 | self.tokenProvider = tokenProvider 17 | self.endPointToProvider = [:] 18 | } 19 | 20 | func connection(for url: URL, connectionType: SubscriptionConnectionType) -> SubscriptionConnection { 21 | if let connectionProvider = endPointToProvider[url.absoluteString] { 22 | return AppSyncSubscriptionConnection(provider: connectionProvider) 23 | } 24 | 25 | let authInterceptor = LambdaAuthInterceptor(authTokenProvider: tokenProvider) 26 | let connectionProvider = ConnectionProviderFactory.createConnectionProvider(for: URLRequest(url: url), 27 | authInterceptor: authInterceptor, 28 | connectionType: connectionType) 29 | endPointToProvider[url.absoluteString] = connectionProvider 30 | 31 | return AppSyncSubscriptionConnection(provider: connectionProvider) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SubscriptionFactory/OIDCBasedConnectionPool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AppSyncRealTimeClient 9 | 10 | class OIDCBasedConnectionPool: SubscriptionConnectionPool { 11 | 12 | private let tokenProvider: AWSOIDCAuthProvider 13 | var endPointToProvider: [String: ConnectionProvider] 14 | 15 | init(_ tokenProvider: AWSOIDCAuthProvider) { 16 | self.tokenProvider = tokenProvider 17 | self.endPointToProvider = [:] 18 | } 19 | 20 | func connection(for url: URL, connectionType: SubscriptionConnectionType) -> SubscriptionConnection { 21 | if let connectionProvider = endPointToProvider[url.absoluteString] { 22 | return AppSyncSubscriptionConnection(provider: connectionProvider) 23 | } 24 | 25 | let authProvider = AppSyncRealTimeClientOIDCAuthProvider(authProvider: tokenProvider) 26 | let authInterceptor = OIDCAuthInterceptor(authProvider) 27 | let connectionProvider = ConnectionProviderFactory.createConnectionProvider(for: URLRequest(url: url), 28 | authInterceptor: authInterceptor, 29 | connectionType: connectionType) 30 | endPointToProvider[url.absoluteString] = connectionProvider 31 | 32 | return AppSyncSubscriptionConnection(provider: connectionProvider) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SubscriptionFactory/SubscriptionConnectionFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AppSyncRealTimeClient 9 | 10 | /// Protocol for the subscription factory 11 | protocol SubscriptionConnectionFactory { 12 | 13 | /// Get connection based on the connection type 14 | /// - Parameter connectionType: 15 | func connection(connectionType: SubscriptionConnectionType) -> SubscriptionConnection? 16 | } 17 | 18 | /// Protocol for the different connection pool 19 | protocol SubscriptionConnectionPool { 20 | 21 | /// Get Connection based on the url and connection type 22 | /// - Parameter url: url to connect to 23 | /// - Parameter connectionType: 24 | func connection(for url: URL, connectionType: SubscriptionConnectionType) -> SubscriptionConnection 25 | } 26 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SubscriptionFactory/UserPoolsBasedConnectionPool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AppSyncRealTimeClient 9 | 10 | class UserPoolsBasedConnectionPool: SubscriptionConnectionPool { 11 | 12 | private let tokenProvider: AWSCognitoUserPoolsAuthProvider 13 | var endPointToProvider: [String: ConnectionProvider] 14 | 15 | init(_ tokenProvider: AWSCognitoUserPoolsAuthProvider) { 16 | self.tokenProvider = tokenProvider 17 | self.endPointToProvider = [:] 18 | } 19 | 20 | func connection(for url: URL, connectionType: SubscriptionConnectionType) -> SubscriptionConnection { 21 | if let connectionProvider = endPointToProvider[url.absoluteString] { 22 | return AppSyncSubscriptionConnection(provider: connectionProvider) 23 | } 24 | 25 | let authProvider = AppSyncRealTimeClientOIDCAuthProvider(authProvider: tokenProvider) 26 | let authInterceptor = OIDCAuthInterceptor(authProvider) 27 | let connectionProvider = ConnectionProviderFactory.createConnectionProvider(for: URLRequest(url: url), 28 | authInterceptor: authInterceptor, 29 | connectionType: connectionType) 30 | endPointToProvider[url.absoluteString] = connectionProvider 31 | 32 | return AppSyncSubscriptionConnection(provider: connectionProvider) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SyncConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | /// Configuration values for an AppSyncSubscriptionWithSync instance. 8 | public struct SyncConfiguration { 9 | /// The interval, in whole seconds, at which the subscription will be refreshed using the `deltaQuery`. If more time has 10 | /// elapsed since the last sync, then local data will be refreshed using `baseQuery` instead. 11 | let baseRefreshIntervalInSeconds: Int 12 | 13 | /// Creates a new SyncConfiguration with the specified sync interval. 14 | /// 15 | /// - Parameters: 16 | /// - baseRefreshIntervalInSeconds: The sync interval. Defaults to one day (86,400 seconds) 17 | public init(baseRefreshIntervalInSeconds: Int = 86_400) { 18 | self.baseRefreshIntervalInSeconds = baseRefreshIntervalInSeconds 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AWSAppSyncClient/Internal/SyncStrategy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | /// The method for syncing local data with the service 10 | enum SyncMethod { 11 | /// Sync data by performing a full (base) query to retrieve all data from the service 12 | case full 13 | 14 | /// Sync data by performing a partial (delta) query to retrieve only data since the last successful sync 15 | case partial 16 | } 17 | 18 | /// Logic that determines which SyncMethod to use to refresh local data for a given subscription. Tracks the last 19 | /// sync time to compare it against the specified refresh interval. 20 | struct SyncStrategy { 21 | var lastSyncTime: Date? 22 | let baseRefreshIntervalInSeconds: TimeInterval 23 | private let hasDeltaQuery: Bool 24 | 25 | var methodToUseForSync: SyncMethod { 26 | guard let lastSyncTime = lastSyncTime else { 27 | return .full 28 | } 29 | 30 | let timeIntervalSinceLastSync = Date().timeIntervalSince(lastSyncTime) 31 | 32 | if timeIntervalSinceLastSync <= baseRefreshIntervalInSeconds { 33 | return .partial 34 | } else { 35 | return .full 36 | } 37 | } 38 | 39 | init(hasDeltaQuery: Bool, baseRefreshIntervalInSeconds: Int) { 40 | self.hasDeltaQuery = hasDeltaQuery 41 | self.baseRefreshIntervalInSeconds = TimeInterval(exactly: baseRefreshIntervalInSeconds)! 42 | } 43 | 44 | } 45 | 46 | internal extension TimeInterval { 47 | var asDispatchTimeInterval: DispatchTimeInterval { 48 | return DispatchTimeInterval.seconds(Int(exactly: self)!) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AWSAppSyncClient/NetworkReachability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | /// Defines a factory to return a NetworkReachabilityProviding instance 10 | public protocol NetworkReachabilityProvidingFactory { 11 | /// Abstracting the only of Reachability's initializers that we care about into a factory method. Since Reachability isn't 12 | /// final, we'd have to add a lot of code to conform its initializers otherwise. 13 | static func make(for hostname: String) -> NetworkReachabilityProviding? 14 | } 15 | 16 | /// Wraps methods and properties of Reachability 17 | public protocol NetworkReachabilityProviding: AnyObject { 18 | /// If `true`, device can attempt to reach the host using a cellular connection (WAN). If `false`, host is only considered 19 | /// reachable if it can be accessed via WiFi 20 | var allowsCellularConnection: Bool { get set } 21 | 22 | var connection: AWSAppSyncReachability.Connection { get } 23 | 24 | /// The notification center on which "reachability changed" events are being posted 25 | var notificationCenter: NotificationCenter { get set } 26 | 27 | /// Starts notifications for reachability changes 28 | func startNotifier() throws 29 | 30 | /// Pauses notifications for reachability changes 31 | func stopNotifier() 32 | } 33 | -------------------------------------------------------------------------------- /AWSAppSyncIntegrationTests/ConsoleResources/appsync-console-query-variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "getPost": "a-post-id", 3 | 4 | "createPostWithFileUsingInputType": { 5 | "author": "Console, createPostWithFileUsingInputType", 6 | "title": "createPostWithFileUsingInputType", 7 | "content": "New content", 8 | "url": "http://www.amazon.com", 9 | "file": { 10 | "bucket": "my-bucket", 11 | "key": "public/new-image.jpg", 12 | "region": "us-east-1", 13 | "localUri": "/path/to/new-image.jpg", 14 | "mimeType": "image/jpeg" 15 | } 16 | }, 17 | 18 | "createPostWithFileUsingParameters_author": "Console, createPostWithFileUsingParameters", 19 | "createPostWithFileUsingParameters_title": "createPostWithFileUsingParameters", 20 | "createPostWithFileUsingParameters_content": "New content", 21 | "createPostWithFileUsingParameters_url": "http://www.amazon.com/", 22 | "createPostWithFileUsingParameters_file_bucket": "my-bucket", 23 | "createPostWithFileUsingParameters_file_key": "public/new-image.jpg", 24 | "createPostWithFileUsingParameters_file_region": "us-east-1", 25 | "createPostWithFileUsingParameters_file_localUri": "/path/to/new-image.jpg", 26 | "createPostWithFileUsingParameters_file_mimeType": "image/jpeg", 27 | 28 | "createPostWithoutFileUsingInputType": { 29 | "author": "Console, createPostWithoutFileUsingInputType", 30 | "title": "createPostWithoutFileUsingInputType", 31 | "content": "New content", 32 | "url": "http://www.amazon.com" 33 | }, 34 | 35 | "createPostWithoutFileUsingParameters_author": "Console, createPostWithoutFileUsingParameters", 36 | "createPostWithoutFileUsingParameters_title": "createPostWithoutFileUsingParameters", 37 | "createPostWithoutFileUsingParameters_content": "New content", 38 | "createPostWithoutFileUsingParameters_url": "http://www.amazon.com/", 39 | 40 | "updatePostWithFileUsingInputType": { 41 | "id": "a-post-id", 42 | "content": "updatePostWithFileUsingInputType", 43 | "file": { 44 | "bucket": "my-updated-bucket", 45 | "key": "public/updated-image.png", 46 | "region": "us-east-1", 47 | "localUri": "/path/to/updated-image.png", 48 | "mimeType": "image/png" 49 | } 50 | }, 51 | 52 | "updatePostWithFileUsingParameters_id": "a-post-id", 53 | "updatePostWithFileUsingParameters_content": "updatePostWithFileUsingParameters", 54 | "updatePostWithFileUsingParameters_file_bucket": "my-updated-bucket", 55 | "updatePostWithFileUsingParameters_file_key": "public/updated-image.png", 56 | "updatePostWithFileUsingParameters_file_region": "us-east-1", 57 | "updatePostWithFileUsingParameters_file_localUri": "/path/to/updated-image.png", 58 | "updatePostWithFileUsingParameters_file_mimeType": "image/png", 59 | 60 | "updatePostWithoutFileUsingInputType": { 61 | "id": "a-post-id", 62 | "content": "updatePostWithoutFileUsingInputType" 63 | }, 64 | 65 | "updatePostWithoutFileUsingParameters_id": "a-post-id", 66 | "updatePostWithoutFileUsingParameters_content": "updatePostWithoutFileUsingParameters", 67 | 68 | "upvotePost": "a-post-id", 69 | 70 | "downvotePost": "a-post-id", 71 | 72 | "onUpvotePost": "a-post-id", 73 | 74 | "onDownvotePost": "a-post-id", 75 | 76 | "deletePostUsingInputType": { 77 | "id": "a-post-id" 78 | }, 79 | 80 | "deletePostUsingParameters": "a-post-id" 81 | } -------------------------------------------------------------------------------- /AWSAppSyncIntegrationTests/ConsoleResources/appsync-lambda-authorizer.js: -------------------------------------------------------------------------------- 1 | exports.handler = async (event) => { 2 | console.log(`auth event >`, JSON.stringify(event, null, 2)) 3 | const { 4 | authorizationToken, 5 | requestContext: { apiId, accountId }, 6 | } = event 7 | const response = { 8 | isAuthorized: authorizationToken === 'custom-lambda-token', 9 | ttlOverride: 10, 10 | } 11 | console.log(`response >`, JSON.stringify(response, null, 2)) 12 | return response 13 | }; 14 | -------------------------------------------------------------------------------- /AWSAppSyncIntegrationTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /AWSAppSyncIntegrationTests/appsync_test_credentials.json.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-mobile-appsync-sdk-ios/589d58ba0e2ae7eac0058235f27bf2b41f66fb85/AWSAppSyncIntegrationTests/appsync_test_credentials.json.enc -------------------------------------------------------------------------------- /AWSAppSyncIntegrationTests/testS3Object.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-mobile-appsync-sdk-ios/589d58ba0e2ae7eac0058235f27bf2b41f66fb85/AWSAppSyncIntegrationTests/testS3Object.jpg -------------------------------------------------------------------------------- /AWSAppSyncTestApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AWSAppSyncTestApp 4 | // 5 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | // Licensed under the Amazon Software License 7 | // http://aws.amazon.com/asl/ 8 | // 9 | 10 | import UIKit 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 19 | // Override point for customization after application launch. 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /AWSAppSyncTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /AWSAppSyncTestApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AWSAppSyncTestApp/Assets.xcassets/Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "version" : 1, 18 | "author" : "xcode" 19 | } 20 | } -------------------------------------------------------------------------------- /AWSAppSyncTestApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AWSAppSyncTestApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /AWSAppSyncTestApp/awsconfiguration.json: -------------------------------------------------------------------------------- 1 | { 2 | "UserAgent": "aws-amplify/cli", 3 | "Version": "0.1.0", 4 | "IdentityManager": { 5 | "Default": {} 6 | }, 7 | "CognitoUserPool": { 8 | "Default": { 9 | "PoolId": "us-west-2_replace_me", 10 | "AppClientId": "replace_me", 11 | "AppClientSecret": "replace_me", 12 | "Region": "us-west-2" 13 | } 14 | }, 15 | "AppSync": { 16 | "Default": { 17 | "ApiUrl": "https://replace_me.appsync-api.us-west-2.amazonaws.com/graphql", 18 | "Region": "us-west-2", 19 | "AuthMode": "AMAZON_COGNITO_USER_POOLS" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AWSAppSyncTestAppUITests/AWSAppSyncTestAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import XCTest 8 | 9 | class AWSAppSyncTestAppUITests: XCTestCase { 10 | 11 | override func setUp() { 12 | // Put setup code here. This method is called before the invocation of each test method in the class. 13 | 14 | // In UI tests it is usually best to stop immediately when a failure occurs. 15 | continueAfterFailure = false 16 | 17 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 18 | XCUIApplication().launch() 19 | 20 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 21 | } 22 | 23 | override func tearDown() { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | } 26 | 27 | func testExample() { 28 | // Use recording to get started writing UI tests. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /AWSAppSyncTestAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/AWSAppSyncTestCommon.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | #import 8 | 9 | //! Project version number for AWSAppSyncTestCommon. 10 | FOUNDATION_EXPORT double AWSAppSyncTestCommonVersionNumber; 11 | 12 | //! Project version string for AWSAppSyncTestCommon. 13 | FOUNDATION_EXPORT const unsigned char AWSAppSyncTestCommonVersionString[]; 14 | 15 | // In this header, you should import all the public headers of your framework using statements like #import 16 | 17 | 18 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/AppSyncClientTestConfigurationDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppSyncClientTestConfigurationDefaults.swift 3 | // AWSAppSyncTests 4 | // 5 | // Created by Schmelter, Tim on 12/11/18. 6 | // Copyright © 2018 Amazon Web Services. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AWSCore 11 | 12 | // Override these defaults if you are not using the `AppSyncIntegrationTests/appsync_test_credentials.json` file to manage your 13 | // test client configuration. 14 | // 15 | // Note: You must either provide all values in the `AppSyncIntegrationTests/appsync_test_credentials.json` or in this 16 | // structure. There is no mechanism to handle partial overrides of one source with the other. All values must be 17 | // specified before running the functional tests. 18 | struct AppSyncClientTestConfigurationDefaults { 19 | 20 | // MARK: - Values used for API Key-based tests 21 | 22 | // Equivalent to the JSON key "AppSyncAPIKey" 23 | static let apiKey = "YOUR_API_KEY" 24 | 25 | // Equivalent to the JSON key "AppSyncEndpointAPIKey" 26 | static let apiKeyEndpointURL = URL(string: "https://localhost")! 27 | 28 | // Equivalent to the JSON key "AppSyncEndpointAPIKeyRegion" 29 | static let apiKeyEndpointRegion = AWSRegionType.USEast1 30 | 31 | static let apiKeyForCognitoPoolEndpoint = "YOUR_API_KEY" 32 | 33 | // MARK: - Values used for IAM-based tests 34 | 35 | // Equivalent to the JSON key "CognitoIdentityPoolId" 36 | static let cognitoPoolId = "YOUR_POOL_ID" 37 | 38 | // Equivalent to the JSON key "CognitoIdentityPoolRegion" 39 | static let cognitoPoolRegion = AWSRegionType.USEast1 40 | 41 | // Equivalent to the JSON key "AppSyncEndpoint" 42 | static let cognitoPoolEndpointURL = URL(string: "https://localhost")! 43 | 44 | // Equivalent to the JSON key "AppSyncRegion" 45 | static let cognitoPoolEndpointRegion = AWSRegionType.USEast1 46 | 47 | // Equivalent to the JSON key "BucketName" 48 | static let bucketName = "YOUR_BUCKET_NAME" 49 | 50 | // Equivalent to the JSON key "BucketRegion" 51 | static let bucketRegion = AWSRegionType.USEast1 52 | 53 | // Equivalent to the JSON key "AppSyncEndpoint" 54 | static let lambdaEndpointURL = URL(string: "https://localhost")! 55 | 56 | // Equivalent to the JSON key "AppSyncRegion" 57 | static let lambdaEndpointRegion = AWSRegionType.USEast1 58 | } 59 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/AppSyncTestAPI+S3Object.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import AWSAppSync 9 | 10 | // Extensions to allow Various `File` fields of the GraphQL API to work with S3ObjectManager 11 | 12 | extension GetPostQuery.Data.GetPost.File: AWSS3ObjectProtocol { 13 | public func getBucketName() -> String { 14 | return bucket 15 | } 16 | 17 | public func getKeyName() -> String { 18 | return key 19 | } 20 | 21 | public func getRegion() -> String { 22 | return region 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/DefaultTestPostData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | @testable import AWSAppSync 8 | 9 | struct DefaultTestPostData { 10 | static let author = "Test author" 11 | static let title = "Test title" 12 | static let content = "Test content" 13 | static let url: String? = "https://aws.amazon.com/" 14 | static let ups = 0 15 | static let downs = 0 16 | 17 | static var defaultCreatePostWithoutFileUsingParametersMutation: CreatePostWithoutFileUsingParametersMutation { 18 | let mutation = CreatePostWithoutFileUsingParametersMutation( 19 | author: author, 20 | title: title, 21 | content: content, 22 | url: url, 23 | ups: ups, 24 | downs: downs 25 | ) 26 | return mutation 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/Foundation+TestUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | // Conform String to error module so we can easily use bare strings in Result failures 10 | extension String: Error, LocalizedError { 11 | public var errorDescription: String? { 12 | return self 13 | } 14 | 15 | public var localizedDescription: String { 16 | return self 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockAWSAppSyncServiceConfigProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | 9 | @testable import AWSAppSync 10 | import AWSCore 11 | 12 | struct MockAWSAppSyncServiceConfigProvider: AWSAppSyncServiceConfigProvider { 13 | public let endpoint: URL 14 | public let region: AWSRegionType 15 | public let authType: AWSAppSyncAuthType 16 | public let apiKey: String? 17 | public let clientDatabasePrefix: String? 18 | 19 | init(endpoint: URL, 20 | region: AWSRegionType, 21 | authType: AWSAppSyncAuthType, 22 | apiKey: String?, 23 | clientDatabasePrefix: String?) { 24 | self.endpoint = endpoint 25 | self.region = region 26 | self.authType = authType 27 | self.apiKey = apiKey 28 | self.clientDatabasePrefix = clientDatabasePrefix 29 | } 30 | 31 | /// Convenience initializer to return a service config for APIKey auth 32 | init(with testConfiguration: AppSyncClientTestConfiguration) { 33 | self.endpoint = testConfiguration.apiKeyEndpointURL 34 | self.region = testConfiguration.apiKeyEndpointRegion 35 | self.authType = .apiKey 36 | self.apiKey = testConfiguration.apiKey 37 | self.clientDatabasePrefix = testConfiguration.clientDatabasePrefix 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockAuthProvider/MockAPIKeyAuthProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | @testable import AWSAppSync 9 | 10 | class MockAPIKeyAuthProvider: AWSAPIKeyAuthProvider { 11 | 12 | func getAPIKey() -> String { 13 | return "mock_api_key" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockAuthProvider/MockIAMAuthProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | @testable import AWSAppSync 9 | import AWSCore 10 | 11 | class MockIAMAuthProvider: NSObject, AWSCredentialsProvider { 12 | 13 | func credentials() -> AWSTask { 14 | let credentials = AWSCredentials(accessKey: "accessKey", 15 | secretKey: "secretKey", 16 | sessionKey: "sessionKey", 17 | expiration: Date()) 18 | return AWSTask(result: credentials) 19 | } 20 | 21 | func invalidateCachedTemporaryCredentials() { 22 | 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockAuthProvider/MockUserPoolsAuthProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | @testable import AWSAppSync 9 | 10 | class MockUserPoolsAuthProvider: AWSCognitoUserPoolsAuthProviderAsync { 11 | 12 | func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) { 13 | callback("jwtToken", nil) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockCancellable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | @testable import AWSAppSync 8 | 9 | typealias BasicClosure = () -> Void 10 | 11 | /// A mock that invokes a handler when `cancel` is invoked on it 12 | class MockCancellable: Cancellable { 13 | private var handler: BasicClosure? 14 | 15 | /// Makes a MockCancellable with the supplied optional handler 16 | /// 17 | /// - Parameter handler: handler to call when `cancel` is invoked 18 | init(handler: BasicClosure? = nil) { 19 | self.handler = handler 20 | } 21 | 22 | /// Immediately invokes handler 23 | func cancel() { 24 | handler?() 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockConnectionProvider/MockConnectionProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | @testable import AWSAppSync 9 | @testable import AppSyncRealTimeClient 10 | 11 | class MockConnectionProvider: ConnectionProvider { 12 | 13 | /// If this boolean is set, all the request to this provider will return a connection error 14 | let validConnection: Bool 15 | 16 | /// If this boolean is set, all the request to this provider will return `notConnected` event 17 | var isConnected: Bool = true 18 | 19 | init (validConnection: Bool = true) { 20 | self.validConnection = validConnection 21 | } 22 | 23 | var listener: ConnectionProviderCallback? 24 | 25 | func connect() { 26 | guard validConnection else { 27 | listener?(.error(ConnectionProviderError.connection)) 28 | return 29 | } 30 | 31 | guard isConnected else { 32 | listener?(.connection(.notConnected)) 33 | return 34 | } 35 | 36 | listener?(.connection(.connected)) 37 | } 38 | 39 | func write(_ message: AppSyncMessage) { 40 | 41 | guard validConnection else { 42 | listener?(.error(ConnectionProviderError.connection)) 43 | return 44 | } 45 | 46 | guard isConnected else { 47 | listener?(.connection(.notConnected)) 48 | return 49 | } 50 | 51 | switch message.messageType { 52 | case .connectionInit: 53 | print("") 54 | case .subscribe: 55 | let response = AppSyncResponse(id: message.id, 56 | payload: [:], 57 | type: .subscriptionAck) 58 | listener?(.data(response)) 59 | case .unsubscribe: 60 | let response = AppSyncResponse(id: message.id, 61 | payload: [:], 62 | type: .unsubscriptionAck) 63 | listener?(.data(response)) 64 | } 65 | } 66 | 67 | func disconnect() { 68 | guard validConnection else { 69 | listener?(.error(ConnectionProviderError.connection)) 70 | return 71 | } 72 | 73 | guard isConnected else { 74 | listener?(.connection(.notConnected)) 75 | return 76 | } 77 | 78 | listener?(.connection(.notConnected)) 79 | } 80 | 81 | func addListener(identifier: String, callback: @escaping ConnectionProviderCallback) { 82 | listener = callback 83 | } 84 | 85 | func removeListener(identifier: String) { 86 | listener = nil 87 | } 88 | func sendDataResponse(_ response: AppSyncResponse) { 89 | listener?(.data(response)) 90 | } 91 | } 92 | 93 | class MockConnectionProviderAlwaysConnect: MockConnectionProvider { 94 | 95 | override func connect() { 96 | listener?(.connection(.connected)) 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockReachability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | @testable import AWSAppSync 9 | 10 | /// The factory will always return the same instance. Make sure to clear the instance between tests by calling `clearShared` 11 | /// in the test's `tearDown` method. 12 | struct MockReachabilityProvidingFactory: NetworkReachabilityProvidingFactory { 13 | static var instance = MockReachabilityProviding() 14 | 15 | static func clearShared() { 16 | instance = MockReachabilityProviding() 17 | } 18 | 19 | static func make(for hostname: String) -> NetworkReachabilityProviding? { 20 | return instance 21 | } 22 | } 23 | 24 | /// The instance class vended by ReachabilityProvidingTestFactory 25 | class MockReachabilityProviding: NetworkReachabilityProviding { 26 | 27 | var allowsCellularConnection = false 28 | var notificationCenter = NotificationCenter.default 29 | 30 | var connection = AWSAppSyncReachability.Connection.wifi { 31 | didSet { 32 | guard isNotifierStarted else { 33 | return 34 | } 35 | 36 | DispatchQueue.main.async { [weak self] in 37 | guard let self = self else { 38 | return 39 | } 40 | self.notificationCenter.post(name: .reachabilityChanged, object: self) 41 | } 42 | } 43 | } 44 | 45 | private var isNotifierStarted = false 46 | 47 | func startNotifier() throws { 48 | isNotifierStarted = true 49 | } 50 | 51 | func stopNotifier() { 52 | isNotifierStarted = false 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockS3ObjectManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | @testable import AWSAppSync 8 | 9 | typealias S3ResultHandler = (Bool, Error?) -> Void 10 | 11 | /// A mock to allow interception of calls to upload and download S3 objects 12 | class MockS3ObjectManager: AWSS3ObjectManager { 13 | /// A handler to call when `upload` is invoked. Handler is invoked with same arguments 14 | /// to `upload` 15 | var uploadHandler: (AWSS3ObjectProtocol & AWSS3InputObjectProtocol, @escaping S3ResultHandler) -> Void = { _, _ in } 16 | func upload(s3Object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, completion: @escaping S3ResultHandler) { 17 | uploadHandler(s3Object, completion) 18 | } 19 | 20 | /// A handler to call when `download` is invoked. Handler is invoked with the same arguments 21 | /// to `download` 22 | var downloadHandler: (AWSS3ObjectProtocol, URL, @escaping S3ResultHandler) -> Void = { _, _, _ in } 23 | func download(s3Object: AWSS3ObjectProtocol, toURL: URL, completion: @escaping S3ResultHandler) { 24 | downloadHandler(s3Object, toURL, completion) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockSubscriptionFactory/MockSubscriptionConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | @testable import AWSAppSync 9 | @testable import AppSyncRealTimeClient 10 | 11 | class MockSubscriptionConnection: SubscriptionConnection { 12 | 13 | /// Current item that is subscriped 14 | var subscriptionItem: SubscriptionItem! 15 | 16 | func subscribe(requestString: String, 17 | variables: [String : Any?]?, 18 | eventHandler: @escaping SubscriptionEventHandler) -> SubscriptionItem { 19 | subscriptionItem = SubscriptionItem(requestString: requestString, 20 | variables: variables, 21 | eventHandler: eventHandler) 22 | 23 | return subscriptionItem 24 | } 25 | 26 | func unsubscribe(item: SubscriptionItem) { 27 | 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/MockSubscriptionFactory/MockSubscriptionFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | @testable import AWSAppSync 9 | import AppSyncRealTimeClient 10 | 11 | class MockSubscriptionFactory: SubscriptionConnectionFactory { 12 | 13 | func connection(connectionType: SubscriptionConnectionType) -> SubscriptionConnection? { 14 | let connection = MockSubscriptionConnection() 15 | return connection 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /AWSAppSyncTestCommon/Object+Swizzling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Object+Swizzling.swift 3 | // AWSAppSync 4 | // 5 | // Created by Mario Araujo on 11/07/2018. 6 | // Copyright © 2018 Amazon Web Services. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | func setAssociatedObject(object: AnyObject, value: T, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) { 13 | objc_setAssociatedObject(object, associativeKey, value, policy) 14 | } 15 | 16 | func getAssociatedObject(object: AnyObject, associativeKey: UnsafeRawPointer) -> T? { 17 | guard let value = objc_getAssociatedObject(object, associativeKey) as? T else { 18 | return nil 19 | } 20 | return value 21 | } 22 | 23 | extension NSObject { 24 | private struct AssociatedKey { 25 | static var swizzledMethodsRestorations = "swizzledMethodsRestorations" 26 | } 27 | 28 | @objc class var swizzledMethodsRestorations: NSMutableArray? { 29 | get { 30 | return getAssociatedObject(object: self, associativeKey: &AssociatedKey.swizzledMethodsRestorations) 31 | } 32 | 33 | set { 34 | if let value = newValue { 35 | setAssociatedObject(object: self, value: value, associativeKey: &AssociatedKey.swizzledMethodsRestorations, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 36 | } 37 | } 38 | } 39 | 40 | @objc static func swizzle(selector: Selector, withBlock block: Any) { 41 | guard let originalMethod = class_getInstanceMethod(self, selector) else { 42 | return 43 | } 44 | 45 | let swizzledBlock = imp_implementationWithBlock(block) 46 | 47 | let newMethod = method_setImplementation(originalMethod, swizzledBlock) 48 | 49 | let block: () -> Void = { 50 | method_setImplementation(originalMethod, newMethod) 51 | } 52 | 53 | if let array = self.swizzledMethodsRestorations { 54 | array.add(block) 55 | } else { 56 | self.swizzledMethodsRestorations = NSMutableArray(array: [block]) 57 | } 58 | } 59 | 60 | @objc static func restoreSwizzledMethods() { 61 | if let array = self.swizzledMethodsRestorations { 62 | array.forEach { (object) in 63 | if let block = object as? () -> Void { 64 | block() 65 | } 66 | } 67 | array.removeAllObjects() 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import UIKit 8 | 9 | @UIApplicationMain 10 | class AppDelegate: UIResponder, UIApplicationDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(_ application: UIApplication) { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 23 | } 24 | 25 | func applicationDidEnterBackground(_ application: UIApplication) { 26 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(_ application: UIApplication) { 31 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 32 | } 33 | 34 | func applicationDidBecomeActive(_ application: UIApplication) { 35 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 36 | } 37 | 38 | func applicationWillTerminate(_ application: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | } 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import UIKit 8 | 9 | class ViewController: UIViewController { 10 | 11 | override func viewDidLoad() { 12 | super.viewDidLoad() 13 | // Do any additional setup after loading the view, typically from a nib. 14 | } 15 | 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/awsconfiguration.json: -------------------------------------------------------------------------------- 1 | { 2 | "UserAgent": "aws-amplify/cli", 3 | "Version": "0.1.0", 4 | "IdentityManager": { 5 | "Default": {} 6 | }, 7 | "AppSync": { 8 | "Default": { 9 | "ApiUrl": "https://default.appsync-api.us-east-1.amazonaws.com/graphql", 10 | "Region": "us-east-1", 11 | "AuthMode": "AWS_IAM" 12 | }, 13 | "UnitTests_EmptyAPIKeyConfiguration": { 14 | "ApiUrl": "https://empty-api-key.appsync-api.us-east-1.amazonaws.com/graphql", 15 | "Region": "us-east-1", 16 | "AuthMode": "API_KEY", 17 | "ApiKey": "" 18 | }, 19 | "UnitTests_MissingAPIKeyConfiguration": { 20 | "ApiUrl": "https://missing-api-key.appsync-api.us-east-1.amazonaws.com/graphql", 21 | "Region": "us-east-1", 22 | "AuthMode": "API_KEY" 23 | }, 24 | "UnitTests_GoodAPIKeyConfiguration": { 25 | "ApiUrl": "https://good-api-key.appsync-api.us-east-1.amazonaws.com/graphql", 26 | "Region": "us-east-1", 27 | "AuthMode": "API_KEY", 28 | "ApiKey": "THE_API_KEY" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/cacheConfigUnitTests-allTables.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-mobile-appsync-sdk-ios/589d58ba0e2ae7eac0058235f27bf2b41f66fb85/AWSAppSyncTestHostApp/cacheConfigUnitTests-allTables.db -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/cacheConfigUnitTests-noMutationRecords.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-mobile-appsync-sdk-ios/589d58ba0e2ae7eac0058235f27bf2b41f66fb85/AWSAppSyncTestHostApp/cacheConfigUnitTests-noMutationRecords.db -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/cacheConfigUnitTests-noRecords.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-mobile-appsync-sdk-ios/589d58ba0e2ae7eac0058235f27bf2b41f66fb85/AWSAppSyncTestHostApp/cacheConfigUnitTests-noRecords.db -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/cacheConfigUnitTests-noSubscriptionMetadata.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-mobile-appsync-sdk-ios/589d58ba0e2ae7eac0058235f27bf2b41f66fb85/AWSAppSyncTestHostApp/cacheConfigUnitTests-noSubscriptionMetadata.db -------------------------------------------------------------------------------- /AWSAppSyncTestHostApp/cacheConfigUnitTests-noTables.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-mobile-appsync-sdk-ios/589d58ba0e2ae7eac0058235f27bf2b41f66fb85/AWSAppSyncTestHostApp/cacheConfigUnitTests-noTables.db -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/AWSAppSyncClientInfoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import XCTest 8 | @testable import AWSAppSync 9 | 10 | @available(*, deprecated, message: "To be removed when we remove AWSAppSyncClientInfo") 11 | class AWSAppSyncClientInfoTests: XCTestCase { 12 | 13 | func testCanLoadFromDefaultConfigSection() { 14 | do { 15 | let config = try AWSAppSyncClientInfo() 16 | XCTAssert(config.apiUrl.starts(with: "https://default.appsync-api")) 17 | } catch { 18 | XCTFail("Can't load from default config section: \(error.localizedDescription)") 19 | } 20 | } 21 | 22 | func testCanLoadFromSpecifiedConfigSection() { 23 | do { 24 | let config = try AWSAppSyncClientInfo(forKey: "UnitTests_GoodAPIKeyConfiguration") 25 | XCTAssert(config.apiUrl.starts(with: "https://good-api-key.appsync-api")) 26 | } catch { 27 | XCTFail("Can't load from good-api-key config section: \(error.localizedDescription)") 28 | } 29 | } 30 | 31 | func testCanLoadValidAPIConfiguration() { 32 | do { 33 | let config = try AWSAppSyncClientInfo(forKey: "UnitTests_GoodAPIKeyConfiguration") 34 | XCTAssertEqual(config.apiKey, "THE_API_KEY") 35 | } catch { 36 | XCTFail("Can't load from good-api-key config section: \(error.localizedDescription)") 37 | } 38 | } 39 | 40 | func testThrowsOnEmptyAPIKey() { 41 | do { 42 | let _ = try AWSAppSyncClientInfo(forKey: "UnitTests_EmptyAPIKeyConfiguration") 43 | XCTFail("Expected validation to fail with empty API Key") 44 | } catch { 45 | guard let clientInfoError = error as? AWSAppSyncClientInfoError else { 46 | XCTFail("Expected validation to throw AWSAppSyncClientInfoError for empty API key, but got \(type(of: error))") 47 | return 48 | } 49 | XCTAssert(clientInfoError.localizedDescription.starts(with: "API_KEY")) 50 | XCTAssert(true, "Threw validation error as expected: \(error.localizedDescription)") 51 | } 52 | } 53 | 54 | func testThrowsOnMissingAPIKey() { 55 | do { 56 | let _ = try AWSAppSyncClientInfo(forKey: "UnitTests_MissingAPIKeyConfiguration") 57 | XCTFail("Expected validation to fail with missing API Key") 58 | } catch { 59 | guard let clientInfoError = error as? AWSAppSyncClientInfoError else { 60 | XCTFail("Expected validation to throw AWSAppSyncClientInfoError for missing API key, but got \(type(of: error))") 61 | return 62 | } 63 | XCTAssert(clientInfoError.localizedDescription.starts(with: "API_KEY")) 64 | XCTAssert(true, "Threw validation error as expected: \(error.localizedDescription)") 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/AWSAppSyncServiceConfigTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import XCTest 8 | @testable import AWSAppSync 9 | 10 | class AWSAppSyncServiceConfigTests: XCTestCase { 11 | 12 | func testCanLoadFromDefaultConfigSection() { 13 | do { 14 | let config = try AWSAppSyncServiceConfig() 15 | XCTAssert(config.endpoint.absoluteString.starts(with: "https://default.appsync-api")) 16 | } catch { 17 | XCTFail("Can't load from default config section: \(error.localizedDescription)") 18 | } 19 | } 20 | 21 | func testCanLoadFromSpecifiedConfigSection() { 22 | do { 23 | let config = try AWSAppSyncServiceConfig(forKey: "UnitTests_GoodAPIKeyConfiguration") 24 | XCTAssert(config.endpoint.absoluteString.starts(with: "https://good-api-key.appsync-api")) 25 | } catch { 26 | XCTFail("Can't load from good-api-key config section: \(error.localizedDescription)") 27 | } 28 | } 29 | 30 | func testCanLoadValidAPIConfiguration() { 31 | do { 32 | let config = try AWSAppSyncServiceConfig(forKey: "UnitTests_GoodAPIKeyConfiguration") 33 | XCTAssertEqual(config.apiKey, "THE_API_KEY") 34 | } catch { 35 | XCTFail("Can't load from good-api-key config section: \(error.localizedDescription)") 36 | } 37 | } 38 | 39 | func testThrowsOnEmptyAPIKey() { 40 | do { 41 | let _ = try AWSAppSyncServiceConfig(forKey: "UnitTests_EmptyAPIKeyConfiguration") 42 | XCTFail("Expected validation to fail with empty API Key") 43 | } catch { 44 | guard case AWSAppSyncServiceConfigError.invalidAPIKey = error else { 45 | XCTFail("Expected validation to throw AWSAppSyncServiceConfigError.invalidAPIKey for empty API key, but got \(type(of: error))") 46 | return 47 | } 48 | } 49 | } 50 | 51 | func testThrowsOnMissingAPIKey() { 52 | do { 53 | let _ = try AWSAppSyncServiceConfig(forKey: "UnitTests_MissingAPIKeyConfiguration") 54 | XCTFail("Expected validation to fail with missing API Key") 55 | } catch { 56 | guard case AWSAppSyncServiceConfigError.invalidAPIKey = error else { 57 | XCTFail("Expected validation to throw AWSAppSyncServiceConfigError.invalidAPIKey for missing API key, but got \(type(of: error))") 58 | return 59 | } 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/AWSSQLiteNormalizedCacheTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import XCTest 8 | @testable import AWSAppSync 9 | @testable import AWSAppSyncTestCommon 10 | import SQLite 11 | 12 | // This class only tests a few high-level operations like database setup. For more 13 | // detailed behavior tests, see appropriate tests in the ApolloTests suites. 14 | class AWSSQLiteNormalizedCacheTests: XCTestCase { 15 | static let fetchQueue = DispatchQueue(label: "AWSSQLiteNormalizedCacheTests.fetch") 16 | static let mutationQueue = DispatchQueue(label: "AWSSQLiteNormalizedCacheTests.mutations") 17 | 18 | var cacheConfiguration: AWSAppSyncCacheConfiguration! 19 | let mockHTTPTransport = MockAWSNetworkTransport() 20 | 21 | // Set up a new DB for each test 22 | override func setUp() { 23 | let tempDir = FileManager.default.temporaryDirectory 24 | let rootDirectory = tempDir.appendingPathComponent("AWSSQLiteNormalizedCacheTests-\(UUID().uuidString)") 25 | cacheConfiguration = try! AWSAppSyncCacheConfiguration(withRootDirectory: rootDirectory) 26 | } 27 | 28 | /// Xcode 10.2 introduced a behavior change in the #function expression that broke SQLite.swift. That 29 | /// manifested as a break in the caching behavior. This test simply asserts that our creation routine 30 | /// properly populates an initial QUERY_ROOT record as expected. This isn't technically testing the 31 | /// public API, but it's the simplest test that asserts the correct SQLite.swift behavior. 32 | /// See [Issue #211](https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues/211) 33 | func testInitializedDatabaseHasQueryRoot() throws { 34 | _ = try UnitTestHelpers.makeAppSyncClient(using: mockHTTPTransport, 35 | cacheConfiguration: cacheConfiguration) 36 | 37 | let queriesDB = try Connection(.uri(cacheConfiguration.queries!.absoluteString), 38 | readonly: false) 39 | 40 | let queryRootCount = try queriesDB.scalar("SELECT count(*) FROM records WHERE key='QUERY_ROOT'") as! Int64 41 | XCTAssertEqual(queryRootCount, 1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/AppSyncApolloCustomizationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | import XCTest 9 | @testable import AWSAppSync 10 | 11 | class AppSyncApolloCustomizationTests: XCTestCase { 12 | 13 | func testRecordSetConformsToCustomPlaygroundDisplayConvertible() { 14 | // Note: this line will generate a compiler warning, but we're using it as documentation to ensure no regressions in our 15 | // forked Apollo code 16 | let conformedRecordSet = RecordSet(records: []) 17 | XCTAssertNotNil(conformedRecordSet) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/Foundation+UtilsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import XCTest 8 | @testable import AWSAppSync 9 | 10 | class Foundation_UtilsTests: XCTestCase { 11 | 12 | func test_isEmpty_extensionPlaysNicelyWithStandardLib_Array() { 13 | let notEmpty: [String] = ["Foo"] 14 | XCTAssertFalse(notEmpty.isEmpty) 15 | 16 | let empty: [String] = [] 17 | XCTAssert(empty.isEmpty) 18 | } 19 | 20 | func test_isEmpty_extensionPlaysNicelyWithStandardLib_Dict() { 21 | let notEmpty: [String: Int] = ["Foo": 1] 22 | XCTAssertFalse(notEmpty.isEmpty) 23 | 24 | let empty: [String: Int] = [:] 25 | XCTAssert(empty.isEmpty) 26 | } 27 | 28 | func test_isEmpty_String() { 29 | let notEmpty: String = "Foo" 30 | XCTAssertFalse(notEmpty.isEmpty) 31 | 32 | let empty: String = "" 33 | XCTAssert(empty.isEmpty) 34 | 35 | let notEmptyOptional: String? = "Foo" 36 | XCTAssertFalse(notEmptyOptional.isEmpty) 37 | 38 | let emptyOptional: String? = "" 39 | XCTAssert(emptyOptional.isEmpty) 40 | 41 | let nilOptional: String? = nil 42 | XCTAssert(nilOptional.isEmpty) 43 | } 44 | 45 | func test_isEmpty_Array() { 46 | let notEmpty: [String] = ["Foo"] 47 | XCTAssertFalse(notEmpty.isEmpty) 48 | 49 | let empty: [String] = [] 50 | XCTAssert(empty.isEmpty) 51 | 52 | let notEmptyOptional: [String]? = ["Foo"] 53 | XCTAssertFalse(notEmptyOptional.isEmpty) 54 | 55 | let emptyOptional: [String]? = [] 56 | XCTAssert(emptyOptional.isEmpty) 57 | 58 | let nilOptional: [String]? = nil 59 | XCTAssert(nilOptional.isEmpty) 60 | } 61 | 62 | func test_isEmpty_Dict() { 63 | let notEmpty: [String: Int] = ["Foo": 1] 64 | XCTAssertFalse(notEmpty.isEmpty) 65 | 66 | let empty: [String: Int] = [:] 67 | XCTAssert(empty.isEmpty) 68 | 69 | let notEmptyOptional: [String: Int]? = ["Foo": 1] 70 | XCTAssertFalse(notEmptyOptional.isEmpty) 71 | 72 | let emptyOptional: [String: Int]? = [:] 73 | XCTAssert(emptyOptional.isEmpty) 74 | 75 | let nilOptional: [String: Int]? = nil 76 | XCTAssert(nilOptional.isEmpty) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/Logging/AppSyncLogHelperTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import XCTest 8 | @testable import AWSAppSync 9 | import AWSCore 10 | 11 | class AppSyncLogHelperTests: XCTestCase { 12 | 13 | func testShouldLogTrueResult() { 14 | AWSDDLog.sharedInstance.logLevel = .warning 15 | let result = AppSyncLogHelper.shouldLog(flag: .warning) 16 | XCTAssertTrue(result) 17 | } 18 | 19 | func testShouldLogFalseResult() { 20 | AWSDDLog.sharedInstance.logLevel = .info 21 | let result = AppSyncLogHelper.shouldLog(flag: .warning) 22 | XCTAssertTrue(result) 23 | } 24 | 25 | func testLoggingInfo() { 26 | let mockedLogger = MockLogger() 27 | AWSDDLog.sharedInstance.logLevel = .info 28 | AWSDDLog.sharedInstance.add(mockedLogger) 29 | AppSyncLog.info("Hi there") 30 | // Logging happens in an async queue, so wait a second for 31 | // logging to happen 32 | sleep(1) 33 | XCTAssertEqual(mockedLogger.loggedMessage, "Hi there") 34 | } 35 | 36 | func testLoggingFail() { 37 | let mockedLogger = MockLogger() 38 | AWSDDLog.sharedInstance.logLevel = .info 39 | AWSDDLog.sharedInstance.add(mockedLogger) 40 | AppSyncLog.debug("Hi there") 41 | // Logging happens in an async queue, so wait a second for 42 | // logging to happen 43 | sleep(1) 44 | XCTAssertEqual(mockedLogger.loggedMessage, "") 45 | } 46 | } 47 | 48 | class MockLogger: NSObject, AWSDDLogger { 49 | var loggedMessage = "" 50 | 51 | var logFormatter: AWSDDLogFormatter? = AWSAppSyncClientLogFormatter() 52 | 53 | func log(message logMessage: AWSDDLogMessage) { 54 | loggedMessage = logMessage.message 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/MockAWSAppSyncServiceConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import Foundation 8 | @testable import AWSAppSync 9 | import AWSCore 10 | 11 | public struct MockAWSAppSyncServiceConfig: AWSAppSyncServiceConfigProvider { 12 | public let endpoint: URL 13 | public let region: AWSRegionType 14 | public let authType: AWSAppSyncAuthType 15 | public let apiKey: String? 16 | public let clientDatabasePrefix: String? 17 | 18 | init(endpoint: URL, 19 | region: AWSRegionType, 20 | authType: AWSAppSyncAuthType, 21 | apiKey: String? = nil, 22 | clientDatabasePrefix: String? = nil) { 23 | self.endpoint = endpoint 24 | self.region = region 25 | self.authType = authType 26 | self.apiKey = apiKey 27 | self.clientDatabasePrefix = clientDatabasePrefix 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/Subscription/AuthInterceptor/IAMAuthInterceptorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // Licensed under the Amazon Software License 4 | // http://aws.amazon.com/asl/ 5 | // 6 | 7 | import XCTest 8 | @testable import AWSAppSync 9 | @testable import AWSAppSyncTestCommon 10 | import AppSyncRealTimeClient 11 | 12 | class IAMAuthInterceptorTests: XCTestCase { 13 | 14 | var authInterceptor: IAMAuthInterceptor! 15 | 16 | override func setUp() { 17 | authInterceptor = IAMAuthInterceptor(MockIAMAuthProvider(), region: .USWest2) 18 | } 19 | 20 | func testInterceptRequest() { 21 | let url = URL(string: "http://xxxc.appsync-api.ap-southeast-2.amazonaws.com/sd")! 22 | let request = AppSyncConnectionRequest(url: url) 23 | let signedRequest = authInterceptor.interceptConnection(request, for: url) 24 | 25 | guard let queries = URLComponents(url: signedRequest.url, resolvingAgainstBaseURL: true)?.queryItems else { 26 | assertionFailure("Query parameters should not be nil") 27 | return 28 | } 29 | XCTAssertTrue(queries.contains{ $0.name == "header"}, "Should contain the header query") 30 | XCTAssertTrue(queries.contains{ $0.name == "payload"}, "Should contain the payload query") 31 | } 32 | 33 | func testInterceptMessage() { 34 | let message = AppSyncMessage(type: .subscribe("start")) 35 | let url = URL(string: "http://xxxc.appsync-api.ap-southeast-2.amazonaws.com/sd")! 36 | let signedMessage = authInterceptor.interceptMessage(message, for: url) 37 | XCTAssertNotNil(signedMessage.payload?.authHeader) 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AWSAppSyncUnitTests/SyncStrategyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncStrategyTests.swift 3 | // AWSAppSyncTests 4 | // 5 | // Created by Schmelter, Tim on 12/12/18. 6 | // Copyright © 2018 Amazon Web Services. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import AWSAppSync 11 | 12 | class SyncStrategyTests: XCTestCase { 13 | 14 | func test_ReturnsFullIfNoDeltaQuery() { 15 | let baseRefreshIntervalInSeconds = 1 16 | let syncStrategy = SyncStrategy(hasDeltaQuery: false, baseRefreshIntervalInSeconds: baseRefreshIntervalInSeconds) 17 | let syncMethodToUse = syncStrategy.methodToUseForSync 18 | XCTAssertEqual(syncMethodToUse, SyncMethod.full) 19 | } 20 | 21 | func test_ReturnsFullIfNotPreviouslySynced() { 22 | let baseRefreshIntervalInSeconds = 1 23 | let syncStrategy = SyncStrategy(hasDeltaQuery: false, baseRefreshIntervalInSeconds: baseRefreshIntervalInSeconds) 24 | let syncMethodToUse = syncStrategy.methodToUseForSync 25 | XCTAssertEqual(syncMethodToUse, SyncMethod.full) 26 | } 27 | 28 | func test_ReturnsFullIfLastSyncTimeIsOutsideInterval() { 29 | let now = Date() 30 | let baseRefreshIntervalInSeconds = 1 31 | let lastSyncTime = now.addingTimeInterval(-10) 32 | 33 | var syncStrategy = SyncStrategy(hasDeltaQuery: false, baseRefreshIntervalInSeconds: baseRefreshIntervalInSeconds) 34 | syncStrategy.lastSyncTime = lastSyncTime 35 | 36 | let syncMethodToUse = syncStrategy.methodToUseForSync 37 | XCTAssertEqual(syncMethodToUse, SyncMethod.full) 38 | } 39 | 40 | func test_ReturnsPartialIfLastSyncTimeIsInsideInterval() { 41 | let now = Date() 42 | let baseRefreshIntervalInSeconds = 10 43 | let lastSyncTime = now.addingTimeInterval(-5) 44 | 45 | var syncStrategy = SyncStrategy(hasDeltaQuery: false, baseRefreshIntervalInSeconds: baseRefreshIntervalInSeconds) 46 | syncStrategy.lastSyncTime = lastSyncTime 47 | 48 | let syncMethodToUse = syncStrategy.methodToUseForSync 49 | XCTAssertEqual(syncMethodToUse, SyncMethod.partial) 50 | } 51 | 52 | func test_ReturnsPartialIfLastSyncTimeIsInFuture() { 53 | let now = Date() 54 | let baseRefreshIntervalInSeconds = 1 55 | let lastSyncTime = now.addingTimeInterval(10) 56 | 57 | var syncStrategy = SyncStrategy(hasDeltaQuery: false, baseRefreshIntervalInSeconds: baseRefreshIntervalInSeconds) 58 | syncStrategy.lastSyncTime = lastSyncTime 59 | 60 | let syncMethodToUse = syncStrategy.methodToUseForSync 61 | XCTAssertEqual(syncMethodToUse, SyncMethod.partial) 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /ApolloTests/CacheKeyForFieldTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AWSAppSync 3 | import StarWarsAPI 4 | 5 | extension GraphQLField { 6 | var cacheKey: String { 7 | return try! cacheKey(with: nil) 8 | } 9 | } 10 | 11 | class CacheKeyForFieldTests: XCTestCase { 12 | func testFieldWithResponseNameOnly() { 13 | let field = GraphQLField("hero", type: .scalar(String.self)) 14 | XCTAssertEqual(field.cacheKey, "hero") 15 | } 16 | 17 | func testFieldWithAlias() { 18 | let field = GraphQLField("hero", alias: "r2", type: .scalar(String.self)) 19 | XCTAssertEqual(field.cacheKey, "hero") 20 | } 21 | 22 | func testFieldWithArgument() { 23 | let field = GraphQLField("hero", arguments: ["episode": Episode.jedi], type: .scalar(String.self)) 24 | XCTAssertEqual(field.cacheKey, "hero(episode:JEDI)") 25 | } 26 | 27 | func testFieldWithAliasAndArgument() { 28 | let field = GraphQLField("hero", alias: "r2", arguments: ["episode": Episode.jedi], type: .scalar(String.self)) 29 | XCTAssertEqual(field.cacheKey, "hero(episode:JEDI)") 30 | } 31 | 32 | func testFieldWithInputObjectArgument() throws { 33 | let field = GraphQLField("hero", arguments: ["nested": ["foo": 1, "bar": 2]], type: .scalar(String.self)) 34 | XCTAssertEqual(field.cacheKey, "hero([nested:bar:2,foo:1])") 35 | } 36 | 37 | func testFieldWithInputObjectArgumentWithVariables() throws { 38 | let field = GraphQLField("hero", arguments: ["nested": ["foo": GraphQLVariable("a"), "bar": GraphQLVariable("b")]], type: .scalar(String.self)) 39 | let variables: GraphQLMap = ["a": 1, "b": 2] 40 | XCTAssertEqual(try field.cacheKey(with: variables), "hero([nested:bar:2,foo:1])") 41 | } 42 | 43 | func testFieldWithMultipleArgumentsIsOrderIndependent() { 44 | let field1 = GraphQLField("hero", arguments: ["foo": "a", "bar": "b"], type: .scalar(String.self)) 45 | let field2 = GraphQLField("hero", arguments: ["bar": "b", "foo": "a"], type: .scalar(String.self)) 46 | XCTAssertEqual(field1.cacheKey, field2.cacheKey) 47 | } 48 | 49 | func testFieldWithInputObjectArgumentIsOrderIndependent() { 50 | let field1 = GraphQLField("hero", arguments: ["episode": Episode.jedi, "nested": ["foo": "a", "bar": "b"]], type: .scalar(String.self)) 51 | let field2 = GraphQLField("hero", arguments: ["episode": Episode.jedi, "nested": ["bar": "b", "foo": "a"]], type: .scalar(String.self)) 52 | XCTAssertEqual(field1.cacheKey, field2.cacheKey) 53 | } 54 | 55 | func testFieldWithVariableArgument() throws { 56 | let field = GraphQLField("hero", arguments: ["episode": GraphQLVariable("episode")], type: .scalar(String.self)) 57 | let variables = ["episode": Episode.jedi] 58 | XCTAssertEqual(try field.cacheKey(with: variables), "hero(episode:JEDI)") 59 | } 60 | 61 | func testFieldWithVariableArgumentWithNil() throws { 62 | let field = GraphQLField("hero", arguments: ["episode": GraphQLVariable("episode")], type: .scalar(String.self)) 63 | let variables: GraphQLMap = ["episode": nil as Optional] 64 | XCTAssertEqual(try field.cacheKey(with: variables), "hero") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ApolloTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ApolloTests/MockNetworkTransport.swift: -------------------------------------------------------------------------------- 1 | import AWSAppSync 2 | import Dispatch 3 | 4 | public final class MockNetworkTransport: NetworkTransport { 5 | let body: JSONObject 6 | 7 | public init(body: JSONObject) { 8 | self.body = body 9 | } 10 | 11 | public func send(operation: Operation, completionHandler: @escaping (_ response: GraphQLResponse?, _ error: Error?) -> Void) -> Cancellable { 12 | DispatchQueue.global(qos: .default).async { 13 | completionHandler(GraphQLResponse(operation: operation, body: self.body), nil) 14 | } 15 | return MockTask() 16 | } 17 | } 18 | 19 | private final class MockTask: Cancellable { 20 | func cancel() { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ApolloTests/MutatingResultsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AWSAppSync 3 | import StarWarsAPI 4 | 5 | class MutatingResultsTests: XCTestCase { 6 | func testSettingNewFragment() throws { 7 | var hero = HeroNameWithFragmentAndIdQuery.Data.Hero.makeDroid(id: "2001", name: "R2-D2") 8 | 9 | let r2d2 = CharacterName.makeDroid(name: "Artoo") 10 | 11 | hero.fragments.characterName = r2d2 12 | 13 | XCTAssertEqual(hero.__typename, "Droid") 14 | XCTAssertEqual(hero.id, "2001") 15 | XCTAssertEqual(hero.name, "Artoo") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ApolloTests/TestCacheProvider.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AWSAppSync 3 | 4 | public protocol TestCacheProvider: AnyObject { 5 | static func withCache(initialRecords: RecordSet?, execute test: (NormalizedCache) throws -> ()) rethrows 6 | } 7 | 8 | public class InMemoryTestCacheProvider: TestCacheProvider { 9 | /// Execute a test block rather than return a cache synchronously, since cache setup may be 10 | /// asynchronous at some point. 11 | public static func withCache(initialRecords: RecordSet? = nil, execute test: (NormalizedCache) throws -> ()) rethrows { 12 | let cache = InMemoryNormalizedCache(records: initialRecords ?? [:]) 13 | try test(cache) 14 | } 15 | } 16 | 17 | extension XCTestCase { 18 | public static var bundleDirectoryURL: URL { 19 | return Bundle(for: self).bundleURL.deletingLastPathComponent() 20 | } 21 | 22 | public static var cacheProviderClass: TestCacheProvider.Type { 23 | guard let cacheProviderClassName = ProcessInfo.processInfo.environment["APOLLO_TEST_CACHE_PROVIDER"] else { 24 | fatalError("Please define the APOLLO_TEST_CACHE_PROVIDER environment variable") 25 | } 26 | 27 | guard let cacheProviderClass = _typeByName(cacheProviderClassName) as? TestCacheProvider.Type else { 28 | fatalError("Could not load APOLLO_TEST_CACHE_PROVIDER \(cacheProviderClassName)") 29 | } 30 | 31 | return cacheProviderClass 32 | } 33 | 34 | public func withCache(initialRecords: RecordSet? = nil, execute test: (NormalizedCache) throws -> ()) rethrows { 35 | return try type(of: self).cacheProviderClass.withCache(initialRecords: initialRecords, execute: test) 36 | } 37 | } 38 | 39 | public class SQLiteTestCacheProvider: TestCacheProvider { 40 | public static func withCache(initialRecords: RecordSet? = nil, execute test: (NormalizedCache) throws -> ()) rethrows { 41 | return try withCache(initialRecords: initialRecords, fileURL: nil, execute: test) 42 | } 43 | 44 | /// Execute a test block rather than return a cache synchronously, since cache setup may be 45 | /// asynchronous at some point. 46 | public static func withCache(initialRecords: RecordSet? = nil, fileURL: URL? = nil, execute test: (NormalizedCache) throws -> ()) rethrows { 47 | let fileURL = fileURL ?? temporarySQLiteFileURL() 48 | let cache = try! AWSSQLiteNormalizedCache(fileURL: fileURL) 49 | if let initialRecords = initialRecords { 50 | _ = cache.merge(records: initialRecords) // This is synchronous 51 | } 52 | try test(cache) 53 | } 54 | 55 | public static func temporarySQLiteFileURL() -> URL { 56 | let applicationSupportPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first! 57 | let applicationSupportURL = URL(fileURLWithPath: applicationSupportPath) 58 | let temporaryDirectoryURL = try! FileManager.default.url( 59 | for: .itemReplacementDirectory, 60 | in: .userDomainMask, 61 | appropriateFor: applicationSupportURL, 62 | create: true) 63 | return temporaryDirectoryURL.appendingPathComponent("db.sqlite3") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ApolloTests/XCTAssertHelpers.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AWSAppSync 3 | 4 | public func XCTAssertEqual(_ expression1: @autoclosure () throws -> [T?]?, _ expression2: @autoclosure () throws -> [T?]?, file: StaticString = #file, line: UInt = #line) rethrows { 5 | let optionalValue1 = try expression1() 6 | let optionalValue2 = try expression2() 7 | 8 | let message = { 9 | "(\"\(String(describing: optionalValue1))\") is not equal to (\"\(String(describing: optionalValue2))\")" 10 | } 11 | 12 | switch (optionalValue1, optionalValue2) { 13 | case (.none, .none): 14 | break 15 | case let (value1?, value2?): 16 | // FIXME: This ignores nil values in both lists, which is probably not what you want for true equality checking 17 | XCTAssertEqual(value1.compactMap { $0 }, value2.compactMap { $0 }, message(), file: file, line: line) 18 | default: 19 | XCTFail(message(), file: file, line: line) 20 | } 21 | } 22 | 23 | public func XCTAssertEqual(_ expression1: @autoclosure () throws -> [T : U]?, _ expression2: @autoclosure () throws -> [T : U]?, file: StaticString = #file, line: UInt = #line) rethrows { 24 | let optionalValue1 = try expression1() 25 | let optionalValue2 = try expression2() 26 | 27 | let message = { 28 | "(\"\(String(describing: optionalValue1))\") is not equal to (\"\(String(describing: optionalValue2))\")" 29 | } 30 | 31 | switch (optionalValue1, optionalValue2) { 32 | case (.none, .none): 33 | break 34 | case let (value1 as NSDictionary, value2 as NSDictionary): 35 | XCTAssertEqual(value1, value2, message(), file: file, line: line) 36 | default: 37 | XCTFail(message(), file: file, line: line) 38 | } 39 | } 40 | 41 | public func XCTAssertMatch(_ valueExpression: @autoclosure () throws -> Pattern.Base, _ patternExpression: @autoclosure () throws -> Pattern, file: StaticString = #file, line: UInt = #line) rethrows { 42 | let value = try valueExpression() 43 | let pattern = try patternExpression() 44 | 45 | let message = { 46 | "(\"\(value)\") does not match (\"\(pattern)\")" 47 | } 48 | 49 | if case pattern = value { return } 50 | 51 | XCTFail(message(), file: file, line: line) 52 | } 53 | -------------------------------------------------------------------------------- /ApolloTests/XCTestCase+Promise.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import AWSAppSync 3 | 4 | extension XCTestCase { 5 | public func awaitWith(_ promise: Promise) throws -> T { 6 | let expectation = self.expectation(description: "Expected promise to be resolved") 7 | 8 | promise.finally { 9 | expectation.fulfill() 10 | } 11 | 12 | waitForExpectations(timeout: 5) 13 | 14 | return try promise.result!.valueOrError() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "aws/aws-sdk-ios" ~> 2.36.0 2 | github "stephencelis/SQLite.swift" ~> 0.12.2 3 | github "aws-amplify/aws-appsync-realtime-client-ios" ~> 3.0.0 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | gem 'cocoapods', '1.11.3' 5 | gem 'cocoapods-downloader', '1.6.3' 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.7) 5 | base64 6 | nkf 7 | rexml 8 | activesupport (6.1.7.6) 9 | concurrent-ruby (~> 1.0, >= 1.0.2) 10 | i18n (>= 1.6, < 2) 11 | minitest (>= 5.1) 12 | tzinfo (~> 2.0) 13 | zeitwerk (~> 2.3) 14 | addressable (2.8.0) 15 | public_suffix (>= 2.0.2, < 5.0) 16 | algoliasearch (1.27.5) 17 | httpclient (~> 2.8, >= 2.8.3) 18 | json (>= 1.5.1) 19 | atomos (0.1.3) 20 | base64 (0.2.0) 21 | claide (1.1.0) 22 | cocoapods (1.11.3) 23 | addressable (~> 2.8) 24 | claide (>= 1.0.2, < 2.0) 25 | cocoapods-core (= 1.11.3) 26 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 27 | cocoapods-downloader (>= 1.4.0, < 2.0) 28 | cocoapods-plugins (>= 1.0.0, < 2.0) 29 | cocoapods-search (>= 1.0.0, < 2.0) 30 | cocoapods-trunk (>= 1.4.0, < 2.0) 31 | cocoapods-try (>= 1.1.0, < 2.0) 32 | colored2 (~> 3.1) 33 | escape (~> 0.0.4) 34 | fourflusher (>= 2.3.0, < 3.0) 35 | gh_inspector (~> 1.0) 36 | molinillo (~> 0.8.0) 37 | nap (~> 1.0) 38 | ruby-macho (>= 1.0, < 3.0) 39 | xcodeproj (>= 1.21.0, < 2.0) 40 | cocoapods-core (1.11.3) 41 | activesupport (>= 5.0, < 7) 42 | addressable (~> 2.8) 43 | algoliasearch (~> 1.0) 44 | concurrent-ruby (~> 1.1) 45 | fuzzy_match (~> 2.0.4) 46 | nap (~> 1.0) 47 | netrc (~> 0.11) 48 | public_suffix (~> 4.0) 49 | typhoeus (~> 1.0) 50 | cocoapods-deintegrate (1.0.5) 51 | cocoapods-downloader (1.6.3) 52 | cocoapods-plugins (1.0.0) 53 | nap 54 | cocoapods-search (1.0.1) 55 | cocoapods-trunk (1.6.0) 56 | nap (>= 0.8, < 2.0) 57 | netrc (~> 0.11) 58 | cocoapods-try (1.2.0) 59 | colored2 (3.1.2) 60 | concurrent-ruby (1.2.2) 61 | escape (0.0.4) 62 | ethon (0.15.0) 63 | ffi (>= 1.15.0) 64 | ffi (1.15.5) 65 | fourflusher (2.3.1) 66 | fuzzy_match (2.0.4) 67 | gh_inspector (1.1.3) 68 | httpclient (2.8.3) 69 | i18n (1.14.1) 70 | concurrent-ruby (~> 1.0) 71 | json (2.6.1) 72 | minitest (5.19.0) 73 | molinillo (0.8.0) 74 | nanaimo (0.3.0) 75 | nap (1.1.0) 76 | netrc (0.11.0) 77 | nkf (0.2.0) 78 | public_suffix (4.0.6) 79 | rexml (3.3.9) 80 | ruby-macho (2.5.1) 81 | typhoeus (1.4.0) 82 | ethon (>= 0.9.0) 83 | tzinfo (2.0.6) 84 | concurrent-ruby (~> 1.0) 85 | xcodeproj (1.25.0) 86 | CFPropertyList (>= 2.3.3, < 4.0) 87 | atomos (~> 0.1.3) 88 | claide (>= 1.0.2, < 2.0) 89 | colored2 (~> 3.1) 90 | nanaimo (~> 0.3.0) 91 | rexml (>= 3.3.2, < 4.0) 92 | zeitwerk (2.6.11) 93 | 94 | PLATFORMS 95 | ruby 96 | 97 | DEPENDENCIES 98 | cocoapods (= 1.11.3) 99 | cocoapods-downloader (= 1.6.3) 100 | 101 | BUNDLED WITH 102 | 2.5.14 103 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS Mobile Events SDK for iOS 2 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | This product includes the Apollo iOS GraphQL client library Copyright (c) 2016-2017 Meteor Development Group, Inc., licensed under the MIT license. 5 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AppSyncRealTimeClient", 6 | "repositoryURL": "https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "9fd77b043e06193848a68740dfde0836648b2b4e", 10 | "version": "3.2.0" 11 | } 12 | }, 13 | { 14 | "package": "AWSiOSSDKV2", 15 | "repositoryURL": "https://github.com/aws-amplify/aws-sdk-ios-spm.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "3588f2d300e4a5cbeee09d37d795db94e6158112", 19 | "version": "2.36.2" 20 | } 21 | }, 22 | { 23 | "package": "SQLite.swift", 24 | "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "0a9893ec030501a3956bee572d6b4fdd3ae158a1", 28 | "version": "0.12.2" 29 | } 30 | }, 31 | { 32 | "package": "Starscream", 33 | "repositoryURL": "https://github.com/daltoniam/Starscream", 34 | "state": { 35 | "branch": null, 36 | "revision": "c6bfd1af48efcc9a9ad203665db12375ba6b145a", 37 | "version": "4.0.8" 38 | } 39 | } 40 | ] 41 | }, 42 | "version": 1 43 | } 44 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "AWSAppSync", 8 | products: [ 9 | .library( 10 | name: "AWSAppSync", 11 | targets: ["AWSAppSync"]), 12 | ], 13 | dependencies: [ 14 | .package( 15 | name: "AWSiOSSDKV2", 16 | url: "https://github.com/aws-amplify/aws-sdk-ios-spm.git", 17 | .upToNextMinor(from: "2.36.0") 18 | ), 19 | .package( 20 | name: "AppSyncRealTimeClient", 21 | url: "https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git", 22 | .upToNextMinor(from: "3.2.0") 23 | ), 24 | .package( 25 | url: "https://github.com/stephencelis/SQLite.swift.git", 26 | .upToNextMinor(from: "0.12.0") 27 | ) 28 | ], 29 | targets: [ 30 | .target( 31 | name: "AWSAppSync", 32 | dependencies: [ 33 | 34 | .product(name: "SQLite", package: "SQLite.swift"), 35 | .product(name: "AppSyncRealTimeClient", package: "AppSyncRealTimeClient"), 36 | .product(name: "AWSCore", package: "AWSiOSSDKV2") 37 | ], 38 | path: "AWSAppSyncClient", 39 | exclude: [ 40 | "Info.plist", 41 | "Apollo/Sources/Apollo/Info.plist" 42 | ] 43 | ) 44 | ] 45 | ) 46 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, "12.0" 2 | 3 | use_frameworks! 4 | inhibit_all_warnings! 5 | 6 | AWS_SDK_VERSION = "2.36.0" 7 | 8 | target "AWSAppSync" do 9 | pod "AWSCore", "~> #{AWS_SDK_VERSION}" 10 | pod "SQLite.swift", "~> 0.12.2" 11 | pod "AppSyncRealTimeClient", "~> 3.2.0" 12 | 13 | pod "SwiftLint" 14 | end 15 | 16 | target "AWSAppSyncTestCommon" do 17 | pod "AWSS3", "~> #{AWS_SDK_VERSION}" 18 | # We directly access a database connection to verify certain initialization 19 | # setups 20 | pod "SQLite.swift", "~> 0.12.2" 21 | pod "AppSyncRealTimeClient", "~> 3.2.0" 22 | end 23 | 24 | target "AWSAppSyncTestApp" do 25 | pod "AWSS3", "~> #{AWS_SDK_VERSION}" 26 | pod "AWSMobileClient", "~> #{AWS_SDK_VERSION}" 27 | end 28 | 29 | target "AWSAppSyncTestHostApp" do 30 | end 31 | 32 | target "AWSAppSyncUnitTests" do 33 | end 34 | 35 | target "AWSAppSyncIntegrationTests" do 36 | end 37 | 38 | target "ApolloTests" do 39 | pod "AWSCore", "~> #{AWS_SDK_VERSION}" 40 | end 41 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AppSyncRealTimeClient (3.2.0): 3 | - Starscream (= 4.0.8) 4 | - AWSAuthCore (2.36.2): 5 | - AWSCore (= 2.36.2) 6 | - AWSCognitoIdentityProvider (2.36.2): 7 | - AWSCognitoIdentityProviderASF (= 2.36.2) 8 | - AWSCore (= 2.36.2) 9 | - AWSCognitoIdentityProviderASF (2.36.2): 10 | - AWSCore (= 2.36.2) 11 | - AWSCore (2.36.2) 12 | - AWSMobileClient (2.36.2): 13 | - AWSAuthCore (= 2.36.2) 14 | - AWSCognitoIdentityProvider (= 2.36.2) 15 | - AWSCognitoIdentityProviderASF (= 2.36.2) 16 | - AWSCore (= 2.36.2) 17 | - AWSS3 (2.36.2): 18 | - AWSCore (= 2.36.2) 19 | - SQLite.swift (0.12.2): 20 | - SQLite.swift/standard (= 0.12.2) 21 | - SQLite.swift/standard (0.12.2) 22 | - Starscream (4.0.8) 23 | - SwiftLint (0.55.0) 24 | 25 | DEPENDENCIES: 26 | - AppSyncRealTimeClient (~> 3.2.0) 27 | - AWSCore (~> 2.36.0) 28 | - AWSMobileClient (~> 2.36.0) 29 | - AWSS3 (~> 2.36.0) 30 | - SQLite.swift (~> 0.12.2) 31 | - SwiftLint 32 | 33 | SPEC REPOS: 34 | trunk: 35 | - AppSyncRealTimeClient 36 | - AWSAuthCore 37 | - AWSCognitoIdentityProvider 38 | - AWSCognitoIdentityProviderASF 39 | - AWSCore 40 | - AWSMobileClient 41 | - AWSS3 42 | - SQLite.swift 43 | - Starscream 44 | - SwiftLint 45 | 46 | SPEC CHECKSUMS: 47 | AppSyncRealTimeClient: 35c0d2ae28234a9f5daba5dc31402acf45ad802f 48 | AWSAuthCore: 0cb5d14bba49eaac2c4f470c3363a0dc93cbd5f0 49 | AWSCognitoIdentityProvider: 8c04f76b85b8c6e3463a36f01a2d3980404efc46 50 | AWSCognitoIdentityProviderASF: 39f47443203aefdc90d7758bad98fbecf9afbe8d 51 | AWSCore: 389e60725f6cd23873ffa6a6292c8f38a940fe11 52 | AWSMobileClient: aad2c8dfeea9806e72000d2be187f2680073a400 53 | AWSS3: eb7863c11386653894f722f27f540efb310db04c 54 | SQLite.swift: d2b4642190917051ce6bd1d49aab565fe794eea3 55 | Starscream: 19b5533ddb925208db698f0ac508a100b884a1b9 56 | SwiftLint: c5b49076d727b772434318c236548f0a1110c299 57 | 58 | PODFILE CHECKSUM: 5e218425b76767a14793a18c26000168492e6ce0 59 | 60 | COCOAPODS: 1.12.1 61 | -------------------------------------------------------------------------------- /StarWarsAPI/CharacterAndSubTypesFragments.graphql: -------------------------------------------------------------------------------- 1 | fragment DroidNameAndPrimaryFunction on Droid { 2 | ...CharacterName 3 | ...DroidPrimaryFunction 4 | } 5 | 6 | fragment CharacterNameAndDroidPrimaryFunction on Character { 7 | ...CharacterName 8 | ...DroidPrimaryFunction 9 | } 10 | 11 | fragment CharacterNameAndDroidAppearsIn on Character { 12 | name 13 | ... on Droid { 14 | appearsIn 15 | } 16 | } 17 | 18 | fragment DroidName on Droid { 19 | name 20 | } 21 | 22 | fragment DroidPrimaryFunction on Droid { 23 | primaryFunction 24 | } 25 | 26 | fragment HumanHeightWithVariable on Human { 27 | height(unit: $heightUnit) 28 | } 29 | 30 | fragment CharacterNameAndAppearsInWithNestedFragments on Character { 31 | ...CharacterNameWithNestedAppearsInFragment 32 | } 33 | 34 | fragment CharacterNameWithNestedAppearsInFragment on Character { 35 | name 36 | ...CharacterAppearsIn 37 | } 38 | 39 | fragment CharacterNameWithInlineFragment on Character { 40 | ... on Human { 41 | friends { 42 | appearsIn 43 | } 44 | } 45 | 46 | ... on Droid { 47 | ...CharacterName 48 | ...FriendsNames 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /StarWarsAPI/CreateReviewForEpisode.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateReviewForEpisode($episode: Episode!, $review: ReviewInput!) { 2 | createReview(episode: $episode, review: $review) { 3 | stars 4 | commentary 5 | } 6 | } 7 | 8 | mutation CreateAwesomeReview { 9 | createReview(episode: JEDI, review: { stars: 10, commentary: "This is awesome!" }) { 10 | stars 11 | commentary 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroAndFriendsNames.graphql: -------------------------------------------------------------------------------- 1 | query HeroAndFriendsNames($episode: Episode) { 2 | hero(episode: $episode) { 3 | name 4 | friends { 5 | name 6 | } 7 | } 8 | } 9 | 10 | query HeroAndFriendsNamesWithIDs($episode: Episode) { 11 | hero(episode: $episode) { 12 | id 13 | name 14 | friends { 15 | id 16 | name 17 | } 18 | } 19 | } 20 | 21 | query HeroAndFriendsNamesWithIDForParentOnly($episode: Episode) { 22 | hero(episode: $episode) { 23 | id 24 | name 25 | friends { 26 | name 27 | } 28 | } 29 | } 30 | 31 | query HeroAndFriendsNamesWithFragment($episode: Episode) { 32 | hero(episode: $episode) { 33 | name 34 | ...FriendsNames 35 | } 36 | } 37 | 38 | query HeroAndFriendsNamesWithFragmentTwice($episode: Episode) { 39 | hero(episode: $episode) { 40 | friends { 41 | ...CharacterName 42 | } 43 | ... on Droid { 44 | friends { 45 | ...CharacterName 46 | } 47 | } 48 | } 49 | } 50 | 51 | fragment FriendsNames on Character { 52 | friends { 53 | name 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroAppearsIn.graphql: -------------------------------------------------------------------------------- 1 | query HeroAppearsIn { 2 | hero { 3 | appearsIn 4 | } 5 | } 6 | 7 | query HeroAppearsInWithFragment($episode: Episode) { 8 | hero(episode: $episode) { 9 | ...CharacterAppearsIn 10 | } 11 | } 12 | 13 | fragment CharacterAppearsIn on Character { 14 | appearsIn 15 | } 16 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroConditional.graphql: -------------------------------------------------------------------------------- 1 | query HeroNameConditionalExclusion($skipName: Boolean!) { 2 | hero { 3 | name @skip(if: $skipName) 4 | } 5 | } 6 | 7 | query HeroNameConditionalInclusion($includeName: Boolean!) { 8 | hero { 9 | name @include(if: $includeName) 10 | } 11 | } 12 | 13 | query HeroNameConditionalBoth($skipName: Boolean!, $includeName: Boolean!) { 14 | hero { 15 | name @skip(if: $skipName) @include(if: $includeName) 16 | } 17 | } 18 | 19 | query HeroNameConditionalBothSeparate($skipName: Boolean!, $includeName: Boolean!) { 20 | hero { 21 | name @skip(if: $skipName) 22 | name @include(if: $includeName) 23 | } 24 | } 25 | 26 | query HeroDetailsInlineConditionalInclusion($includeDetails: Boolean!) { 27 | hero { 28 | ... @include(if: $includeDetails) { 29 | name 30 | appearsIn 31 | } 32 | } 33 | } 34 | 35 | query HeroDetailsFragmentConditionalInclusion($includeDetails: Boolean!) { 36 | hero { 37 | ...HeroDetails @include(if: $includeDetails) 38 | } 39 | } 40 | 41 | query HeroNameTypeSpecificConditionalInclusion($episode: Episode, $includeName: Boolean!) { 42 | hero(episode: $episode) { 43 | name @include(if: $includeName) 44 | ... on Droid { 45 | name 46 | } 47 | } 48 | } 49 | 50 | query HeroFriendsDetailsConditionalInclusion($includeFriendsDetails: Boolean!) { 51 | hero { 52 | friends @include(if: $includeFriendsDetails) { 53 | name 54 | ... on Droid { 55 | primaryFunction 56 | } 57 | } 58 | } 59 | } 60 | 61 | query HeroFriendsDetailsUnconditionalAndConditionalInclusion($includeFriendsDetails: Boolean!) { 62 | hero { 63 | friends { 64 | name 65 | } 66 | friends @include(if: $includeFriendsDetails) { 67 | name 68 | ... on Droid { 69 | primaryFunction 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroDetails.graphql: -------------------------------------------------------------------------------- 1 | query HeroDetails($episode: Episode) { 2 | hero(episode: $episode) { 3 | name 4 | ... on Human { 5 | height 6 | } 7 | ... on Droid { 8 | primaryFunction 9 | } 10 | } 11 | } 12 | 13 | query HeroDetailsWithFragment($episode: Episode) { 14 | hero(episode: $episode) { 15 | ...HeroDetails 16 | } 17 | } 18 | 19 | query DroidDetailsWithFragment($episode: Episode) { 20 | hero(episode: $episode) { 21 | ...DroidDetails 22 | } 23 | } 24 | 25 | fragment HeroDetails on Character { 26 | name 27 | ... on Human { 28 | height 29 | } 30 | ... on Droid { 31 | primaryFunction 32 | } 33 | } 34 | 35 | fragment DroidDetails on Droid { 36 | name 37 | primaryFunction 38 | } 39 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroFriendsOfFriends.graphql: -------------------------------------------------------------------------------- 1 | query HeroFriendsOfFriendsNames($episode: Episode) { 2 | hero(episode: $episode) { 3 | friends { 4 | id 5 | friends { 6 | name 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroName.graphql: -------------------------------------------------------------------------------- 1 | query HeroName($episode: Episode) { 2 | hero(episode: $episode) { 3 | name 4 | } 5 | } 6 | 7 | query HeroNameWithID($episode: Episode) { 8 | hero(episode: $episode) { 9 | id 10 | name 11 | } 12 | } 13 | 14 | query HeroNameWithFragment($episode: Episode) { 15 | hero(episode: $episode) { 16 | ...CharacterName 17 | } 18 | } 19 | 20 | query HeroNameWithFragmentAndID($episode: Episode) { 21 | hero(episode: $episode) { 22 | id 23 | ...CharacterName 24 | } 25 | } 26 | 27 | fragment CharacterName on Character { 28 | name 29 | } 30 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroNameAndAppearsIn.graphql: -------------------------------------------------------------------------------- 1 | query HeroNameAndAppearsInWithFragment($episode: Episode) { 2 | hero(episode: $episode) { 3 | ...CharacterNameAndAppearsIn 4 | } 5 | } 6 | 7 | fragment CharacterNameAndAppearsIn on Character { 8 | name 9 | appearsIn 10 | } 11 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroParentTypeDependentField.graphql: -------------------------------------------------------------------------------- 1 | query HeroParentTypeDependentField($episode: Episode) { 2 | hero(episode: $episode) { 3 | name 4 | ... on Human { 5 | friends { 6 | name 7 | ... on Human { 8 | height(unit: FOOT) 9 | } 10 | } 11 | } 12 | ... on Droid { 13 | friends { 14 | name 15 | ... on Human { 16 | height(unit: METER) 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StarWarsAPI/HeroTypeDependentAliasedField.graphql: -------------------------------------------------------------------------------- 1 | query HeroTypeDependentAliasedField($episode: Episode) { 2 | hero(episode: $episode) { 3 | ... on Human { 4 | property: homePlanet 5 | } 6 | ... on Droid { 7 | property: primaryFunction 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /StarWarsAPI/Human.graphql: -------------------------------------------------------------------------------- 1 | query Human($id: ID!) { 2 | human(id: $id) { 3 | name 4 | mass 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /StarWarsAPI/HumanFriendsFilteredById.graphql: -------------------------------------------------------------------------------- 1 | query HumanFriendsFilteredById($id: ID!, $friendId: ID = null) { 2 | human(id: $id) { 3 | name 4 | mass 5 | friendsFilteredById(id: $friendId) { 6 | id 7 | name 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /StarWarsAPI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /StarWarsAPI/SameHeroTwice.graphql: -------------------------------------------------------------------------------- 1 | query SameHeroTwice { 2 | hero { 3 | name 4 | } 5 | r2: hero { 6 | appearsIn 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /StarWarsAPI/StarWarsAPI.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for StarWarsAPI. 4 | FOUNDATION_EXPORT double StarWarsAPIVersionNumber; 5 | 6 | //! Project version string for StarWarsAPI. 7 | FOUNDATION_EXPORT const unsigned char StarWarsAPIVersionString[]; 8 | 9 | // In this header, you should import all the public headers of your framework using statements like #import 10 | -------------------------------------------------------------------------------- /StarWarsAPI/Starship.graphql: -------------------------------------------------------------------------------- 1 | query Starship { 2 | starship(id: 3000) { 3 | name 4 | coordinates 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /StarWarsAPI/SubscribeReview.graphql: -------------------------------------------------------------------------------- 1 | subscription ReviewAdded($episode: Episode) { 2 | reviewAdded(episode: $episode) { 3 | episode 4 | stars 5 | commentary 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /StarWarsAPI/TwoHeroes.graphql: -------------------------------------------------------------------------------- 1 | query TwoHeroes { 2 | r2: hero { 3 | name 4 | } 5 | luke: hero(episode: EMPIRE) { 6 | name 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /build-support/build-xcframeworks.sh: -------------------------------------------------------------------------------- 1 | set -euo pipefail 2 | 3 | framework=$@ 4 | pwd=$(pwd) 5 | ios_device_archive_path="$pwd/build/iOS/AWSAppSync" 6 | ios_simulator_archive_path="$pwd/build/Simulator/AWSAppSync" 7 | xcframework_path="$pwd/build/$framework.xcframework" 8 | 9 | if [ -d "$xcframework_path" ] 10 | then 11 | echo "XCFramework exists already, skipping." 12 | exit 0 13 | fi 14 | 15 | # archive for device 16 | xcodebuild archive -workspace AWSAppSyncClient.xcworkspace \ 17 | -scheme $framework \ 18 | -destination "generic/platform=iOS" \ 19 | -archivePath $ios_device_archive_path \ 20 | -quiet \ 21 | SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 22 | 23 | 24 | # archive for simulator 25 | xcodebuild archive -workspace AWSAppSyncClient.xcworkspace \ 26 | -scheme $framework \ 27 | -destination "generic/platform=iOS Simulator" \ 28 | -archivePath $ios_simulator_archive_path \ 29 | -quiet \ 30 | SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 31 | 32 | # create xcframework 33 | xcodebuild -create-xcframework \ 34 | -framework "$ios_device_archive_path.xcarchive/Products/Library/Frameworks/$framework.framework" \ 35 | -debug-symbols "$ios_device_archive_path.xcarchive/dSYMs/$framework.framework.dSYM" \ 36 | -framework "$ios_simulator_archive_path.xcarchive/Products/Library/Frameworks/$framework.framework" \ 37 | -debug-symbols "$ios_simulator_archive_path.xcarchive/dSYMs/$framework.framework.dSYM" \ 38 | -output "$xcframework_path" 39 | 40 | cd build && zip -q -r AWSAppSync.xcframework.zip AWSAppSync.xcframework 41 | 42 | -------------------------------------------------------------------------------- /build-support/cocoapods_release.sh: -------------------------------------------------------------------------------- 1 | bundle exec pod trunk push AWSAppSync.podspec --allow-warnings 2 | -------------------------------------------------------------------------------- /build-support/stage_sdk_release.sh: -------------------------------------------------------------------------------- 1 | if [ -z $1 ] || [ -z $2 ]; then 2 | echo "Usage: ./$0 [current_sdk_version] [new_sdk_version]" 3 | exit 1 4 | fi 5 | 6 | export LC_CTYPE=C LANG=C 7 | echo "Bumping version from $1 to $2" 8 | find . -name 'AWSAppSync.podspec' -print0 | xargs -0 sed -i '' -e "s/$1/$2/g" 9 | find . -path '*AWSAppSyncClient/*.plist' -print0 | xargs -0 sed -i '' -e "s/$1/$2/g" 10 | find . -path '*AWSAppSyncClient/AWSAppSyncHTTPNetworkTransport.swift' -print0 | xargs -0 sed -i '' -e "s/$1/$2/g" 11 | sed -i '' -e "s/$1/$2/g" README.md 12 | 13 | echo "SDK version replaced in podspec, info.plist, network transport.\n\n" 14 | 15 | echo "Adding git commit and tag..." 16 | git add -u && git commit -m "Release SDK version $2" 17 | git tag -a "$2" -m "$2" 18 | -------------------------------------------------------------------------------- /build-support/update_sdk_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -z "$1" ]] || [[ -z "$2" ]]; then 4 | echo "Usage: $0 " >&2 5 | exit 1 6 | fi 7 | 8 | echo "Bumping version from $1 to $2" 9 | find . -name 'AWSAppSync.podspec' -print0 | xargs -0 sed -i '' -e "s/$1/$2/g" 10 | find . -name 'Podfile' -print0 | xargs -0 sed -i '' -e "s/$1/$2/g" 11 | find . -name 'Cartfile' -print0 | xargs -0 sed -i '' -e "s/$1/$2/g" 12 | sed -i '' -e "s/$1/$2/g" Package.swift 13 | sed -i '' -e "s/$1/$2/g" README.md 14 | 15 | echo "SDK dependency updated in podspec, Package.swift, Podfile, and Cartfile." 16 | echo "Review the diff. If it looks correct, run 'swift package resolve && pod update'" 17 | echo "Build and test, then commit, and create a PR." 18 | 19 | --------------------------------------------------------------------------------