diff --git a/.github/canary-config/canary-all.yml b/.github/canary-config/canary-all.yml index b3de09873f1..067692a9cc9 100644 --- a/.github/canary-config/canary-all.yml +++ b/.github/canary-config/canary-all.yml @@ -66,24 +66,24 @@ tests: spec: background-process-manager browser: *extended_browser_list # TODO: remove when updated CPK + related models tests are added - - test_name: integ_react_datastore_related_models - desc: 'DataStore - Related Models' - framework: react - category: datastore - sample_name: [related-models] - spec: related-models - browser: *minimal_browser_list - timeout_minutes: 45 - retry_count: 10 - - test_name: integ_react_datastore_cpk_related_models - desc: 'DataStore - Custom Primary Key + Related Models' - framework: react - category: datastore - sample_name: [v2/related-models] - spec: cpk-related-models - browser: *minimal_browser_list - timeout_minutes: 45 - retry_count: 10 + # - test_name: integ_react_datastore_related_models + # desc: 'DataStore - Related Models' + # framework: react + # category: datastore + # sample_name: [related-models] + # spec: related-models + # browser: *minimal_browser_list + # timeout_minutes: 45 + # retry_count: 10 + # - test_name: integ_react_datastore_cpk_related_models + # desc: 'DataStore - Custom Primary Key + Related Models' + # framework: react + # category: datastore + # sample_name: [v2/related-models] + # spec: cpk-related-models + # browser: *minimal_browser_list + # timeout_minutes: 45 + # retry_count: 10 - test_name: integ_react_datastore_selective_sync desc: 'DataStore - Selective Sync' framework: react diff --git a/.github/workflows/callable-canary-e2e-tests.yml b/.github/workflows/callable-canary-e2e-tests.yml index 2827adea201..e7e17524487 100644 --- a/.github/workflows/callable-canary-e2e-tests.yml +++ b/.github/workflows/callable-canary-e2e-tests.yml @@ -57,7 +57,7 @@ jobs: with: # Minimal depth 2 so we can checkout the commit before possible merge commit. fetch-depth: 2 - path: amplify-js + path: amplify-js - name: Setup Node.js 16 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 https://github.com/actions/setup-node/commit/64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c with: @@ -81,8 +81,8 @@ jobs: uses: ./amplify-js/.github/actions/setup-samples-staging with: GH_TOKEN_STAGING_READ: ${{ secrets.GH_TOKEN_STAGING_READ }} - - name: Modify package.json to run against aws-amplify latest - env: + - name: Modify package.json to run against aws-amplify latest + env: E2E_FRAMEWORK: ${{ inputs.framework }} E2E_CATEGORY: ${{ inputs.category }} E2E_SAMPLE_NAME: ${{ matrix.sample_name }} @@ -102,7 +102,7 @@ jobs: E2E_RETRY_COUNT: ${{ inputs.retry_count }} E2E_TEST_NAME: ${{ inputs.test_name }} run: | - ../amplify-js/.circleci/retry-yarn-script.sh -s \ + ../amplify-js/scripts/retry-yarn-script.sh -s \ "ci:test \ $E2E_FRAMEWORK \ $E2E_CATEGORY \ @@ -125,7 +125,7 @@ jobs: E2E_RETRY_COUNT: ${{ inputs.retry_count }} E2E_TEST_NAME: ${{ inputs.test_name }} run: | - ../amplify-js/.circleci/retry-yarn-script.sh -s \ + ../amplify-js/scripts/retry-yarn-script.sh -s \ "ci:test \ $E2E_FRAMEWORK \ $E2E_CATEGORY \ @@ -146,4 +146,4 @@ jobs: path: | amplify-js-samples-staging/cypress/videos amplify-js-samples-staging/cypress/screenshots - retention-days: 14 \ No newline at end of file + retention-days: 14 diff --git a/.github/workflows/callable-canary-e2e.yml b/.github/workflows/callable-canary-e2e.yml index e5f07fbd452..7c820c601a5 100644 --- a/.github/workflows/callable-canary-e2e.yml +++ b/.github/workflows/callable-canary-e2e.yml @@ -38,9 +38,9 @@ jobs: steps: - name: Send slack message env: - WORKFLOW_URL: ${{ env.WORKFLOW_URL }} + WORKFLOW_URL: '{ "URL": "${{ env.WORKFLOW_URL }}" }' WEBHOOK_URL: ${{ env.WEBHOOK_URL }} run: | curl -X POST -H "Content-Type: application/json" \ - --data {URL:$WORKFLOW_URL} \ + --data "$WORKFLOW_URL" \ $WEBHOOK_URL diff --git a/.github/workflows/callable-canary-sampleapp-tests.yml b/.github/workflows/callable-canary-sampleapp-tests.yml index 97478f0df89..51ab2e75060 100644 --- a/.github/workflows/callable-canary-sampleapp-tests.yml +++ b/.github/workflows/callable-canary-sampleapp-tests.yml @@ -8,7 +8,7 @@ on: type: boolean default: true -env: +env: AMPLIFY_DIR: /home/runner/work/amplify-js/amplify-js/amplify-js CYPRESS_GOOGLE_CLIENTID: ${{ secrets.CYPRESS_GOOGLE_CLIENTID }} CYPRESS_GOOGLE_CLIENT_SECRET: ${{ secrets.CYPRESS_GOOGLE_CLIENT_SECRET }} @@ -18,13 +18,13 @@ jobs: build_apps: name: Build Sample App Tests runs-on: ubuntu-latest - steps: + steps: - name: Checkout repository uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 https://github.com/actions/checkout/commit/24cb9080177205b6e8c946b17badbe402adc938f with: # Minimal depth 2 so we can checkout the commit before possible merge commit. fetch-depth: 2 - path: amplify-js + path: amplify-js - name: Setup samples staging repository uses: ./amplify-js/.github/actions/setup-samples-staging with: @@ -53,18 +53,18 @@ jobs: working-directory: amplify-js-samples-staging/samples/react/auth/new-react-app - name: Start new application and run test run: | - ../amplify-js/.circleci/retry-yarn-script.sh -s \ - "ci:test \ - react \ - auth \ - new-react-app \ - new-react-app \ - chrome \ - dev" \ - -n 3 + ../amplify-js/scripts/retry-yarn-script.sh -s \ + "ci:test \ + react \ + auth \ + new-react-app \ + new-react-app \ + chrome \ + dev" \ + -n 3 working-directory: amplify-js-samples-staging shell: bash - + # Angular - name: Install angular CLI run: npm install -g @angular/cli @@ -77,7 +77,7 @@ jobs: - name: Copy files from samples staging repo run: | rm -r ./samples/angular/interactions/new-angular-app - cp -r ./samples/angular/interactions/chatbot-component ./samples/angular/interactions/new-angular-app + cp -r ./samples/angular/interactions/chatbot-component ./samples/angular/interactions/new-angular-app working-directory: amplify-js-samples-staging - name: Copy test file from samples staging repo run: | @@ -93,15 +93,15 @@ jobs: working-directory: amplify-js-samples-staging/samples/angular/interactions/new-angular-app - name: Start application and run test run: | - ../amplify-js/.circleci/retry-yarn-script.sh -s \ - "ci:test \ - angular \ - interactions \ - new-angular-app \ - new-angular-app \ - chrome \ - dev" \ - -n 3 + ../amplify-js/scripts/retry-yarn-script.sh -s \ + "ci:test \ + angular \ + interactions \ + new-angular-app \ + new-angular-app \ + chrome \ + dev" \ + -n 3 working-directory: amplify-js-samples-staging shell: bash @@ -115,7 +115,7 @@ jobs: - name: Copy files from samples staging repo run: | rm -r ./samples/next/auth/new-next-app - cp -r ./samples/next/auth/auth-rsc ./samples/next/auth/new-next-app + cp -r ./samples/next/auth/auth-rsc ./samples/next/auth/new-next-app working-directory: amplify-js-samples-staging - name: Copy test file from samples staging repo run: | @@ -131,21 +131,21 @@ jobs: working-directory: amplify-js-samples-staging/samples/next/auth/new-next-app - name: Start application and run test run: | - ../amplify-js/.circleci/retry-yarn-script.sh -s \ - "ci:test \ - next \ - auth \ - new-next-app \ - new-next-app \ - chrome \ - dev" \ - -n 3 + ../amplify-js/scripts/retry-yarn-script.sh -s \ + "ci:test \ + next \ + auth \ + new-next-app \ + new-next-app \ + chrome \ + dev" \ + -n 3 working-directory: amplify-js-samples-staging shell: bash - + # Vue - name: Create Vue application - run: | + run: | npm init vue@3 working-directory: amplify-js-samples-staging/samples/vue/auth - name: Copy files from samples staging repo @@ -166,15 +166,15 @@ jobs: working-directory: amplify-js-samples-staging/samples/vue/auth/new-vue-app - name: Start application and run test run: | - ../amplify-js/.circleci/retry-yarn-script.sh -s \ - "ci:test \ - vue \ - auth \ - new-vue-app \ - new-vue-app \ - chrome \ - dev" \ - -n 3 + ../amplify-js/scripts/retry-yarn-script.sh -s \ + "ci:test \ + vue \ + auth \ + new-vue-app \ + new-vue-app \ + chrome \ + dev" \ + -n 3 working-directory: amplify-js-samples-staging shell: bash @@ -195,7 +195,7 @@ jobs: - name: Copy files from samples staging repo run: | rm -r ./samples/javascript/auth/new-javascript-app - cp -r ./samples/javascript/auth/auth-cdn ./samples/javascript/auth/new-javascript-app + cp -r ./samples/javascript/auth/auth-cdn ./samples/javascript/auth/new-javascript-app working-directory: amplify-js-samples-staging - name: Copy test file from samples staging repo run: | @@ -208,15 +208,15 @@ jobs: env: AMPLIFY_DIR: ${{ env.AMPLIFY_DIR }} run: | - ../amplify-js/.circleci/retry-yarn-script.sh -s \ - "ci:test \ - javascript \ - auth \ - new-javascript-app \ - new-javascript-app \ - chrome \ - dev \ - $AMPLIFY_DIR" \ - -n 3 + ../amplify-js/scripts/retry-yarn-script.sh -s \ + "ci:test \ + javascript \ + auth \ + new-javascript-app \ + new-javascript-app \ + chrome \ + dev \ + $AMPLIFY_DIR" \ + -n 3 working-directory: amplify-js-samples-staging - shell: bash \ No newline at end of file + shell: bash diff --git a/lerna.json b/lerna.json index 125fde7d69c..02cd14a57ce 100644 --- a/lerna.json +++ b/lerna.json @@ -7,7 +7,10 @@ "packages/analytics", "packages/storage", "packages/aws-amplify", - "packages/adapter-nextjs" + "packages/adapter-nextjs", + "packages/api", + "packages/api-rest", + "packages/api-graphql" ], "exact": true, "version": "independent", diff --git a/package.json b/package.json index 3404a7d56da..f4fc22313a3 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,10 @@ "packages/analytics", "packages/storage", "packages/aws-amplify", - "packages/adapter-nextjs" + "packages/adapter-nextjs", + "packages/api", + "packages/api-graphql", + "packages/api-rest" ], "nohoist": [ "**/@types/react-native", @@ -113,7 +116,8 @@ "@types/babel__traverse": "7.20.0", "path-scurry": "1.10.0", "**/glob/minipass": "6.0.2", - "nx": "16.7.0" + "nx": "16.7.0", + "@smithy/types": "2.1.0" }, "jest": { "resetMocks": true, diff --git a/packages/api-graphql/__tests__/GraphQLAPI.test.ts b/packages/api-graphql/__tests__/GraphQLAPI.test.ts index 20750e9918e..a62de6d0e8c 100644 --- a/packages/api-graphql/__tests__/GraphQLAPI.test.ts +++ b/packages/api-graphql/__tests__/GraphQLAPI.test.ts @@ -1,1532 +1,1693 @@ -import { Auth } from '@aws-amplify/auth'; -import { GraphQLAPIClass as API } from '../src'; -import { InternalGraphQLAPIClass as InternalAPI } from '../src/internals'; -import { graphqlOperation } from '../src/GraphQLAPI'; -import { GRAPHQL_AUTH_MODE, GraphQLAuthError } from '../src/types'; -import { RestClient } from '@aws-amplify/api-rest'; -import { print } from 'graphql/language/printer'; -import { parse } from 'graphql/language/parser'; -import { - Credentials, - Constants, - INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER, - Category, - Framework, - ApiAction, - CustomUserAgentDetails, - Cache -} from '@aws-amplify/core'; -import { InternalPubSub } from '@aws-amplify/pubsub/internals'; -import * as Observable from 'zen-observable'; -import axios, { CancelTokenStatic } from 'axios'; - -axios.CancelToken = { - source: () => ({ token: null, cancel: null }), -}; -axios.isCancel = (value: any): boolean => { - return false; -}; - -let isCancelSpy; -let cancelTokenSpy; -let cancelMock; -let tokenMock; -let mockCancellableToken; -jest.mock('axios'); - -const config = { - API: { - region: 'region', - header: {}, - }, -}; - -const GetEvent = `query GetEvent($id: ID! $nextToken: String) { - getEvent(id: $id) { - id - name - where - when - description - comments(nextToken: $nextToken) { - items { - commentId - content - createdAt - } - } - } -}`; -const getEventDoc = parse(GetEvent); -const getEventQuery = print(getEventDoc); - -/* TODO: Test with actual actions */ -const expectedUserAgentFrameworkOnly = `${Constants.userAgent} framework/${Framework.WebUnknown}`; -const customUserAgentDetailsAPI: CustomUserAgentDetails = { - category: Category.API, - action: ApiAction.GraphQl, -}; -const expectedUserAgentAPI = `${Constants.userAgent} ${Category.API}/${ApiAction.GraphQl} framework/${Framework.WebUnknown}`; - -afterEach(() => { - jest.restoreAllMocks(); -}); - -describe('API test', () => { - beforeEach(() => { - cancelMock = jest.fn(); - tokenMock = jest.fn(); - mockCancellableToken = { token: tokenMock, cancel: cancelMock }; - isCancelSpy = jest.spyOn(axios, 'isCancel').mockReturnValue(true); - cancelTokenSpy = jest - .spyOn(axios.CancelToken, 'source') - .mockImplementation(() => { - return mockCancellableToken; - }); - }); - describe('graphql test', () => { - test('happy-case-query', async () => { - const spyonAuth = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - Authorization: null, - 'X-Api-Key': apiKey, - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql(graphqlOperation(GetEvent, variables)); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('cancel-graphql-query', async () => { - const spyonAuth = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - rej('error cancelled'); - }); - }); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - Authorization: null, - 'X-Api-Key': apiKey, - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - const promiseResponse = api.graphql( - graphqlOperation(GetEvent, variables) - ); - api.cancel(promiseResponse as Promise, 'testmessage'); - - expect.assertions(5); - - expect(cancelTokenSpy).toBeCalledTimes(1); - expect(cancelMock).toBeCalledWith('testmessage'); - try { - await promiseResponse; - } catch (err) { - expect(err).toEqual('error cancelled'); - expect(api.isCancel(err)).toBeTruthy(); - } - expect(spyon).toBeCalledWith(url, init); - }); - - test('happy-case-query-ast', async () => { - const spyonAuth = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - Authorization: null, - 'X-Api-Key': apiKey, - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql(graphqlOperation(getEventDoc, variables)); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('happy-case-query-oidc with Cache token', async () => { - const spyonAuth = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const cache_config = { - capacityInBytes: 3000, - itemMaxSize: 800, - defaultTTL: 3000000, - defaultPriority: 5, - warningThreshold: 0.8, - storage: window.localStorage, - }; - - Cache.configure(cache_config); - - const spyonCache = jest - .spyOn(Cache, 'getItem') - .mockImplementationOnce(() => { - return { - token: 'id_token', - }; - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'OPENID_CONNECT', - }); - - const headers = { - Authorization: 'id_token', - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql(graphqlOperation(GetEvent, variables)); - - expect(spyon).toBeCalledWith(url, init); - - spyonCache.mockClear(); - }); - - test('happy-case-query-oidc with auth storage federated token', async () => { - const spyonCredentials = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const cache_config = { - capacityInBytes: 3000, - itemMaxSize: 800, - defaultTTL: 3000000, - defaultPriority: 5, - warningThreshold: 0.8, - storage: window.localStorage, - }; - - Cache.configure(cache_config); - - const spyonCache = jest - .spyOn(Cache, 'getItem') - .mockImplementationOnce(() => { - return null; - }); - - const spyonAuth = jest - .spyOn(Auth, 'currentAuthenticatedUser') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res({ - name: 'federated user', - token: 'federated_token_from_storage', - }); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'OPENID_CONNECT', - }); - - const headers = { - Authorization: 'federated_token_from_storage', - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql(graphqlOperation(GetEvent, variables)); - - expect(spyon).toBeCalledWith(url, init); - - spyonCredentials.mockClear(); - spyonCache.mockClear(); - spyonAuth.mockClear(); - }); - - test('happy case query with AWS_LAMBDA', async () => { - expect.assertions(1); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockReturnValue(Promise.resolve({})); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com'; - const region = 'us-east-2'; - const variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'AWS_LAMBDA', - }); - - const headers = { - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - Authorization: 'myAuthToken', - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql({ - query: GetEvent, - variables, - authToken: 'myAuthToken', - }); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('additional headers with AWS_LAMBDA', async () => { - expect.assertions(1); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockReturnValue(Promise.resolve({})); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com'; - const region = 'us-east-2'; - const variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'AWS_LAMBDA', - }); - - const headers = { - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - Authorization: 'myAuthToken', - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql( - { - query: GetEvent, - variables, - authToken: 'myAuthToken', - }, - { Authorization: 'anotherAuthToken' } - ); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('multi-auth default case AWS_IAM, using API_KEY as auth mode', async () => { - expect.assertions(1); - - const cache_config = { - capacityInBytes: 3000, - itemMaxSize: 800, - defaultTTL: 3000000, - defaultPriority: 5, - warningThreshold: 0.8, - storage: window.localStorage, - }; - - Cache.configure(cache_config); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockReturnValue(Promise.resolve({})); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'AWS_IAM', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - Authorization: null, - 'X-Api-Key': 'secret-api-key', - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.API_KEY, - }); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('multi-auth default case api-key, using AWS_IAM as auth mode', async () => { - expect.assertions(1); - jest.spyOn(Credentials, 'get').mockReturnValue(Promise.resolve('cred')); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockReturnValue(Promise.resolve({})); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.AWS_IAM, - }); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('multi-auth default case api-key, using AWS_LAMBDA as auth mode', async () => { - expect.assertions(1); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockReturnValue(Promise.resolve({})); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - Authorization: 'myAuthToken', - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.AWS_LAMBDA, - authToken: 'myAuthToken', - }); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('multi-auth default case api-key, using OIDC as auth mode', async () => { - expect.assertions(1); - const cache_config = { - capacityInBytes: 3000, - itemMaxSize: 800, - defaultTTL: 3000000, - defaultPriority: 5, - warningThreshold: 0.8, - storage: window.localStorage, - }; - - Cache.configure(cache_config); - - jest.spyOn(Cache, 'getItem').mockReturnValue({ token: 'oidc_token' }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockReturnValue(Promise.resolve({})); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - Authorization: 'oidc_token', - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.OPENID_CONNECT, - }); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('multi-auth using OIDC as auth mode, but no federatedSign', async () => { - expect.assertions(1); - - const cache_config = { - capacityInBytes: 3000, - itemMaxSize: 800, - defaultTTL: 3000000, - defaultPriority: 5, - warningThreshold: 0.8, - storage: window.localStorage, - }; - - Cache.configure(cache_config); - - jest.spyOn(Cache, 'getItem').mockReturnValue(null); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - await expect( - api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.OPENID_CONNECT, - }) - ).rejects.toThrowError('No current user'); - }); - - test('multi-auth using CUP as auth mode, but no userpool', async () => { - expect.assertions(1); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - await expect( - api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, - }) - ).rejects.toThrow(); - }); - - test('multi-auth using AWS_LAMBDA as auth mode, but no auth token specified', async () => { - expect.assertions(1); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'AWS_IAM', - }); - - await expect( - api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.AWS_LAMBDA, - }) - ).rejects.toThrowError(GraphQLAuthError.NO_AUTH_TOKEN); - }); - - test('multi-auth using API_KEY as auth mode, but no api-key configured', async () => { - expect.assertions(1); - - const cache_config = { - capacityInBytes: 3000, - itemMaxSize: 800, - defaultTTL: 3000000, - defaultPriority: 5, - warningThreshold: 0.8, - storage: window.localStorage, - }; - - Cache.configure(cache_config); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'AWS_IAM', - }); - - await expect( - api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.API_KEY, - }) - ).rejects.toThrowError('No api-key configured'); - }); - - test('multi-auth using AWS_IAM as auth mode, but no credentials', async () => { - expect.assertions(1); - - jest.spyOn(Credentials, 'get').mockReturnValue(Promise.reject()); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - await expect( - api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.AWS_IAM, - }) - ).rejects.toThrowError('No credentials'); - }); - - test('multi-auth default case api-key, using CUP as auth mode', async () => { - expect.assertions(1); - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockReturnValue(Promise.resolve({})); - - jest.spyOn(Auth, 'currentSession').mockReturnValue({ - getAccessToken: () => ({ - getJwtToken: () => 'Secret-Token', - }), - } as any); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, - apiKey = 'secret-api-key'; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const headers = { - Authorization: 'Secret-Token', - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql({ - query: GetEvent, - variables, - authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, - }); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('authMode on subscription', async () => { - expect.assertions(1); - - jest - .spyOn(RestClient.prototype, 'post') - .mockImplementation(async (url, init) => ({ - extensions: { - subscription: { - newSubscriptions: {}, - }, - }, - })); - - const cache_config = { - capacityInBytes: 3000, - itemMaxSize: 800, - defaultTTL: 3000000, - defaultPriority: 5, - warningThreshold: 0.8, - storage: window.localStorage, - }; - - Cache.configure(cache_config); - - jest.spyOn(Cache, 'getItem').mockReturnValue({ token: 'id_token' }); - - const spyon_pubsub = jest - .spyOn(InternalPubSub, 'subscribe') - .mockImplementation(jest.fn(() => Observable.of({}))); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - const SubscribeToEventComments = `subscription SubscribeToEventComments($eventId: String!) { - subscribeToEventComments(eventId: $eventId) { - eventId - commentId - content - } - }`; - - const doc = parse(SubscribeToEventComments); - const query = print(doc); - - ( - api.graphql({ - query, - variables, - authMode: GRAPHQL_AUTH_MODE.OPENID_CONNECT, - }) as any - ).subscribe(); - - expect(spyon_pubsub).toBeCalledWith( - '', - expect.objectContaining({ - authenticationType: 'OPENID_CONNECT', - }), - undefined - ); - }); - - test('happy-case-subscription', async done => { - jest - .spyOn(RestClient.prototype, 'post') - .mockImplementation(async (url, init) => ({ - extensions: { - subscription: { - newSubscriptions: {}, - }, - }, - })); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - InternalPubSub.subscribe = jest.fn(() => Observable.of({})); - - const SubscribeToEventComments = `subscription SubscribeToEventComments($eventId: String!) { - subscribeToEventComments(eventId: $eventId) { - eventId - commentId - content - } - }`; - - const doc = parse(SubscribeToEventComments); - const query = print(doc); - - const observable = ( - api.graphql(graphqlOperation(query, variables)) as Observable - ).subscribe({ - next: () => { - expect(InternalPubSub.subscribe).toHaveBeenCalledTimes(1); - const subscribeOptions = (InternalPubSub.subscribe as any).mock - .calls[0][1]; - expect(subscribeOptions.provider).toBe( - INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER - ); - done(); - }, - }); - - expect(observable).not.toBe(undefined); - }); - - test('happy case subscription with additionalHeaders', async done => { - jest - .spyOn(RestClient.prototype, 'post') - .mockImplementation(async (url, init) => ({ - extensions: { - subscription: { - newSubscriptions: {}, - }, - }, - })); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - InternalPubSub.subscribe = jest.fn(() => Observable.of({})); - - const SubscribeToEventComments = `subscription SubscribeToEventComments($eventId: String!) { - subscribeToEventComments(eventId: $eventId) { - eventId - commentId - content - } - }`; - - const doc = parse(SubscribeToEventComments); - const query = print(doc); - - const additionalHeaders = { - 'x-custom-header': 'value', - }; - - const observable = ( - api.graphql( - graphqlOperation(query, variables), - additionalHeaders - ) as Observable - ).subscribe({ - next: () => { - expect(InternalPubSub.subscribe).toHaveBeenCalledTimes(1); - const subscribeOptions = (InternalPubSub.subscribe as any).mock - .calls[0][1]; - expect(subscribeOptions.additionalHeaders).toBe(additionalHeaders); - done(); - }, - }); - - expect(observable).not.toBe(undefined); - }); - - test('happy case mutation', async () => { - const spyonAuth = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { - id: '809392da-ec91-4ef0-b219-5238a8f942b2', - content: 'lalala', - createdAt: new Date().toISOString(), - }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - const AddComment = `mutation AddComment($eventId: ID!, $content: String!, $createdAt: String!) { - commentOnEvent(eventId: $eventId, content: $content, createdAt: $createdAt) { - eventId - content - createdAt - } - }`; - - const doc = parse(AddComment); - const query = print(doc); - - const headers = { - Authorization: null, - 'X-Api-Key': apiKey, - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await api.graphql(graphqlOperation(AddComment, variables)); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('happy case query with additionalHeaders', async () => { - const spyonAuth = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - graphql_headers: async () => - Promise.resolve({ - someHeaderSetAtConfigThatWillBeOverridden: 'initialValue', - someOtherHeaderSetAtConfig: 'expectedValue', - }), - }); - - const headers = { - Authorization: null, - 'X-Api-Key': apiKey, - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - const additionalHeaders = { - someAddtionalHeader: 'foo', - someHeaderSetAtConfigThatWillBeOverridden: 'expectedValue', - }; - - await api.graphql( - graphqlOperation(GetEvent, variables), - additionalHeaders - ); - - expect(spyon).toBeCalledWith(url, { - ...init, - headers: { - someAddtionalHeader: 'foo', - someHeaderSetAtConfigThatWillBeOverridden: 'expectedValue', - ...init.headers, - someOtherHeaderSetAtConfig: 'expectedValue', - }, - }); - }); - - test('call isInstanceCreated', () => { - const createInstanceMock = spyOn(API.prototype, 'createInstance'); - const api = new API(config); - api.createInstanceIfNotCreated(); - expect(createInstanceMock).toHaveBeenCalled(); - }); - - test('should not call createInstance when there is already an instance', () => { - const api = new API(config); - api.createInstance(); - const createInstanceMock = spyOn(API.prototype, 'createInstance'); - api.createInstanceIfNotCreated(); - expect(createInstanceMock).not.toHaveBeenCalled(); - }); - - test('sends cookies with request', async () => { - const spyonAuth = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - - const api = new API(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - api.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - withCredentials: true, - }); - - const headers = { - Authorization: null, - 'X-Api-Key': apiKey, - 'x-amz-user-agent': expectedUserAgentFrameworkOnly, - }; - - const body = { - query: getEventQuery, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - withCredentials: true, - }; - let authToken: undefined; - - await api.graphql(graphqlOperation(GetEvent, variables, authToken)); - - expect(spyon).toBeCalledWith(url, init); - }); - }); - - describe('configure test', () => { - test('without aws_project_region', () => { - const api = new API({}); - - const options = { - myoption: 'myoption', - }; - - expect(api.configure(options)).toEqual({ - myoption: 'myoption', - }); - }); - - test('with aws_project_region', () => { - const api = new API({}); - - const options = { - aws_project_region: 'region', - }; - - expect(api.configure(options)).toEqual({ - aws_project_region: 'region', - header: {}, - region: 'region', - }); - }); - - test('with API options', () => { - const api = new API({}); - - const options = { - API: { - aws_project_region: 'api-region', - }, - aws_project_region: 'region', - aws_appsync_region: 'appsync-region', - }; - - expect(api.configure(options)).toEqual({ - aws_project_region: 'api-region', - aws_appsync_region: 'appsync-region', - header: {}, - region: 'api-region', - }); - }); - }); -}); - -describe('Internal API customUserAgent test', () => { - beforeEach(() => { - cancelMock = jest.fn(); - tokenMock = jest.fn(); - mockCancellableToken = { token: tokenMock, cancel: cancelMock }; - isCancelSpy = jest.spyOn(axios, 'isCancel').mockReturnValue(true); - cancelTokenSpy = jest - .spyOn(axios.CancelToken, 'source') - .mockImplementation(() => { - return mockCancellableToken; - }); - }); - describe('graphql test', () => { - test('happy case mutation', async () => { - const spyonAuth = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - - const spyon = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce((url, init) => { - return new Promise((res, rej) => { - res({}); - }); - }); - const internalApi = new InternalAPI(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { - id: '809392da-ec91-4ef0-b219-5238a8f942b2', - content: 'lalala', - createdAt: new Date().toISOString(), - }; - internalApi.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - const AddComment = `mutation AddComment($eventId: ID!, $content: String!, $createdAt: String!) { - commentOnEvent(eventId: $eventId, content: $content, createdAt: $createdAt) { - eventId - content - createdAt - } - }`; - - const doc = parse(AddComment); - const query = print(doc); - - const headers = { - Authorization: null, - 'X-Api-Key': apiKey, - 'x-amz-user-agent': expectedUserAgentAPI, - }; - - const body = { - query, - variables, - }; - - const init = { - headers, - body, - signerServiceInfo: { - service: 'appsync', - region, - }, - cancellableToken: mockCancellableToken, - }; - - await internalApi.graphql( - graphqlOperation(AddComment, variables), - undefined, - customUserAgentDetailsAPI - ); - - expect(spyon).toBeCalledWith(url, init); - }); - - test('happy case subscription', async done => { - jest - .spyOn(RestClient.prototype, 'post') - .mockImplementation(async (url, init) => ({ - extensions: { - subscription: { - newSubscriptions: {}, - }, - }, - })); - - const internalApi = new InternalAPI(config); - const url = 'https://appsync.amazonaws.com', - region = 'us-east-2', - apiKey = 'secret_api_key', - variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; - - internalApi.configure({ - aws_appsync_graphqlEndpoint: url, - aws_appsync_region: region, - aws_appsync_authenticationType: 'API_KEY', - aws_appsync_apiKey: apiKey, - }); - - InternalPubSub.subscribe = jest.fn(() => Observable.of({})); - - const SubscribeToEventComments = `subscription SubscribeToEventComments($eventId: String!) { - subscribeToEventComments(eventId: $eventId) { - eventId - commentId - content - } - }`; - - const doc = parse(SubscribeToEventComments); - const query = print(doc); - - const observable = ( - internalApi.graphql( - graphqlOperation(query, variables), - undefined, - customUserAgentDetailsAPI - ) as Observable - ).subscribe({ - next: () => { - expect(InternalPubSub.subscribe).toHaveBeenCalledTimes(1); - expect(InternalPubSub.subscribe).toHaveBeenCalledWith( - expect.anything(), - expect.anything(), - customUserAgentDetailsAPI - ); - const subscribeOptions = (InternalPubSub.subscribe as any).mock - .calls[0][1]; - expect(subscribeOptions.provider).toBe( - INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER - ); - done(); - }, - }); - - expect(observable).not.toBe(undefined); - }); - }); +// import { InternalAuth } from '@aws-amplify/auth/internals'; +// import { GraphQLAPIClass as API } from '../src'; +// import { InternalGraphQLAPIClass as InternalAPI } from '../src/internals'; +// import { graphqlOperation } from '../src/GraphQLAPI'; +// import { GRAPHQL_AUTH_MODE, GraphQLAuthError } from '../src/types'; +// import { RestClient } from '@aws-amplify/api-rest'; +// import { print } from 'graphql/language/printer'; +// import { parse } from 'graphql/language/parser'; +// import { +// Credentials, +// // Constants, +// // INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER, +// // Category, +// // Framework, +// // ApiAction, +// // CustomUserAgentDetails, +// } from '@aws-amplify/core'; +// import { +// Constants, +// INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER, +// Category, +// Framework, +// ApiAction, +// CustomUserAgentDetails, +// } from '@aws-amplify/core/internals/utils'; +// import { InternalPubSub } from '@aws-amplify/pubsub/internals'; +// import { Cache } from '@aws-amplify/cache'; +// import * as Observable from 'zen-observable'; +// import axios, { CancelTokenStatic } from 'axios'; + +// axios.CancelToken = { +// source: () => ({ token: null, cancel: null } as any), +// }; +// axios.isCancel = (value: any): boolean => { +// return false; +// }; + +// let isCancelSpy; +// let cancelTokenSpy; +// let cancelMock; +// let tokenMock; +// let mockCancellableToken; +// jest.mock('axios'); + +// const config = { +// API: { +// region: 'region', +// header: {}, +// }, +// }; + +// const GetEvent = `query GetEvent($id: ID! $nextToken: String) { +// getEvent(id: $id) { +// id +// name +// where +// when +// description +// comments(nextToken: $nextToken) { +// items { +// commentId +// content +// createdAt +// } +// } +// } +// }`; +// const getEventDoc = parse(GetEvent); +// const getEventQuery = print(getEventDoc); + +// /* TODO: Test with actual actions */ +// const expectedUserAgentFrameworkOnly = `${Constants.userAgent} framework/${Framework.WebUnknown}`; +// const customUserAgentDetailsAPI: CustomUserAgentDetails = { +// category: Category.API, +// action: ApiAction.GraphQl, +// }; +// const expectedUserAgentAPI = `${Constants.userAgent} ${Category.API}/${ApiAction.GraphQl} framework/${Framework.WebUnknown}`; + +// afterEach(() => { +// jest.restoreAllMocks(); +// }); + +// describe('API test', () => { +// beforeEach(() => { +// cancelMock = jest.fn(); +// tokenMock = jest.fn(); +// mockCancellableToken = { token: tokenMock, cancel: cancelMock }; +// isCancelSpy = jest.spyOn(axios, 'isCancel').mockReturnValue(true); +// cancelTokenSpy = jest +// .spyOn(axios.CancelToken, 'source') +// .mockImplementation(() => { +// return mockCancellableToken; +// }); +// }); +// describe('graphql test', () => { +// test('happy-case-query', async () => { +// const spyonAuth = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// Authorization: null, +// 'X-Api-Key': apiKey, +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql(graphqlOperation(GetEvent, variables)); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('cancel-graphql-query', async () => { +// const spyonAuth = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// rej('error cancelled'); +// }); +// }); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// Authorization: null, +// 'X-Api-Key': apiKey, +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// const promiseResponse = api.graphql( +// graphqlOperation(GetEvent, variables) +// ); +// api.cancel(promiseResponse as Promise, 'testmessage'); + +// expect.assertions(5); + +// expect(cancelTokenSpy).toBeCalledTimes(1); +// expect(cancelMock).toBeCalledWith('testmessage'); +// try { +// await promiseResponse; +// } catch (err) { +// expect(err).toEqual('error cancelled'); +// expect(api.isCancel(err)).toBeTruthy(); +// } +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('happy-case-query-ast', async () => { +// const spyonAuth = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// Authorization: null, +// 'X-Api-Key': apiKey, +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql(graphqlOperation(getEventDoc, variables)); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('happy-case-query-oidc with Cache token', async () => { +// const spyonAuth = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const cache_config = { +// capacityInBytes: 3000, +// itemMaxSize: 800, +// defaultTTL: 3000000, +// defaultPriority: 5, +// warningThreshold: 0.8, +// storage: window.localStorage, +// }; + +// Cache.configure(cache_config); + +// const spyonCache = jest +// .spyOn(Cache, 'getItem') +// .mockImplementationOnce(() => { +// return { +// token: 'id_token', +// }; +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'OPENID_CONNECT', +// }); + +// const headers = { +// Authorization: 'id_token', +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql(graphqlOperation(GetEvent, variables)); + +// expect(spyon).toBeCalledWith(url, init); + +// spyonCache.mockClear(); +// }); + +// test('happy-case-query-oidc with auth storage federated token', async () => { +// const spyonCredentials = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const cache_config = { +// capacityInBytes: 3000, +// itemMaxSize: 800, +// defaultTTL: 3000000, +// defaultPriority: 5, +// warningThreshold: 0.8, +// storage: window.localStorage, +// }; + +// Cache.configure(cache_config); + +// const spyonCache = jest +// .spyOn(Cache, 'getItem') +// .mockImplementationOnce(() => { +// return null; +// }); + +// const spyonAuth = jest +// .spyOn(InternalAuth, 'currentAuthenticatedUser') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res({ +// name: 'federated user', +// token: 'federated_token_from_storage', +// }); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'OPENID_CONNECT', +// }); + +// const headers = { +// Authorization: 'federated_token_from_storage', +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql(graphqlOperation(GetEvent, variables)); + +// expect(spyon).toBeCalledWith(url, init); + +// spyonCredentials.mockClear(); +// spyonCache.mockClear(); +// spyonAuth.mockClear(); +// }); + +// test('happy case query with AWS_LAMBDA', async () => { +// expect.assertions(1); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockReturnValue(Promise.resolve({})); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com'; +// const region = 'us-east-2'; +// const variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; + +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'AWS_LAMBDA', +// }); + +// const headers = { +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// Authorization: 'myAuthToken', +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql({ +// query: GetEvent, +// variables, +// authToken: 'myAuthToken', +// }); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('additional headers with AWS_LAMBDA', async () => { +// expect.assertions(1); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockReturnValue(Promise.resolve({})); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com'; +// const region = 'us-east-2'; +// const variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; + +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'AWS_LAMBDA', +// }); + +// const headers = { +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// Authorization: 'myAuthToken', +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql( +// { +// query: GetEvent, +// variables, +// authToken: 'myAuthToken', +// }, +// { Authorization: 'anotherAuthToken' } +// ); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('multi-auth default case AWS_IAM, using API_KEY as auth mode', async () => { +// expect.assertions(1); + +// const cache_config = { +// capacityInBytes: 3000, +// itemMaxSize: 800, +// defaultTTL: 3000000, +// defaultPriority: 5, +// warningThreshold: 0.8, +// storage: window.localStorage, +// }; + +// Cache.configure(cache_config); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockReturnValue(Promise.resolve({})); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'AWS_IAM', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// Authorization: null, +// 'X-Api-Key': 'secret-api-key', +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.API_KEY, +// }); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('multi-auth default case api-key, using AWS_IAM as auth mode', async () => { +// expect.assertions(1); +// jest.spyOn(Credentials, 'get').mockReturnValue(Promise.resolve('cred')); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockReturnValue(Promise.resolve({})); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.AWS_IAM, +// }); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('multi-auth default case api-key, using AWS_LAMBDA as auth mode', async () => { +// expect.assertions(1); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockReturnValue(Promise.resolve({})); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; + +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// Authorization: 'myAuthToken', +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.AWS_LAMBDA, +// authToken: 'myAuthToken', +// }); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('multi-auth default case api-key, using OIDC as auth mode', async () => { +// expect.assertions(1); +// const cache_config = { +// capacityInBytes: 3000, +// itemMaxSize: 800, +// defaultTTL: 3000000, +// defaultPriority: 5, +// warningThreshold: 0.8, +// storage: window.localStorage, +// }; + +// Cache.configure(cache_config); + +// jest.spyOn(Cache, 'getItem').mockReturnValue({ token: 'oidc_token' }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockReturnValue(Promise.resolve({})); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// Authorization: 'oidc_token', +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.OPENID_CONNECT, +// }); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('multi-auth using OIDC as auth mode, but no federatedSign', async () => { +// expect.assertions(1); + +// const cache_config = { +// capacityInBytes: 3000, +// itemMaxSize: 800, +// defaultTTL: 3000000, +// defaultPriority: 5, +// warningThreshold: 0.8, +// storage: window.localStorage, +// }; + +// Cache.configure(cache_config); + +// jest.spyOn(Cache, 'getItem').mockReturnValue(null); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// await expect( +// api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.OPENID_CONNECT, +// }) +// ).rejects.toThrowError('No current user'); +// }); + +// test('multi-auth using CUP as auth mode, but no userpool', async () => { +// expect.assertions(1); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// await expect( +// api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, +// }) +// ).rejects.toThrow(); +// }); + +// test('multi-auth using AWS_LAMBDA as auth mode, but no auth token specified', async () => { +// expect.assertions(1); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; + +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'AWS_IAM', +// }); + +// await expect( +// api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.AWS_LAMBDA, +// }) +// ).rejects.toThrowError(GraphQLAuthError.NO_AUTH_TOKEN); +// }); + +// test('multi-auth using API_KEY as auth mode, but no api-key configured', async () => { +// expect.assertions(1); + +// const cache_config = { +// capacityInBytes: 3000, +// itemMaxSize: 800, +// defaultTTL: 3000000, +// defaultPriority: 5, +// warningThreshold: 0.8, +// storage: window.localStorage, +// }; + +// Cache.configure(cache_config); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'AWS_IAM', +// }); + +// await expect( +// api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.API_KEY, +// }) +// ).rejects.toThrowError('No api-key configured'); +// }); + +// test('multi-auth using AWS_IAM as auth mode, but no credentials', async () => { +// expect.assertions(1); + +// jest.spyOn(Credentials, 'get').mockReturnValue(Promise.reject()); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// await expect( +// api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.AWS_IAM, +// }) +// ).rejects.toThrowError('No credentials'); +// }); + +// test('multi-auth default case api-key, using CUP as auth mode', async () => { +// expect.assertions(1); +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockReturnValue(Promise.resolve({})); + +// jest.spyOn(InternalAuth, 'currentSession').mockReturnValue({ +// getAccessToken: () => ({ +// getJwtToken: () => 'Secret-Token', +// }), +// } as any); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// Authorization: 'Secret-Token', +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql({ +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, +// }); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('authMode on subscription', async () => { +// expect.assertions(1); + +// jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementation(async (url, init) => ({ +// extensions: { +// subscription: { +// newSubscriptions: {}, +// }, +// }, +// })); + +// const cache_config = { +// capacityInBytes: 3000, +// itemMaxSize: 800, +// defaultTTL: 3000000, +// defaultPriority: 5, +// warningThreshold: 0.8, +// storage: window.localStorage, +// }; + +// Cache.configure(cache_config); + +// jest.spyOn(Cache, 'getItem').mockReturnValue({ token: 'id_token' }); + +// const spyon_pubsub = jest +// .spyOn(InternalPubSub, 'subscribe') +// .mockImplementation(jest.fn(() => Observable.of({}) as any)); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; + +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const SubscribeToEventComments = `subscription SubscribeToEventComments($eventId: String!) { +// subscribeToEventComments(eventId: $eventId) { +// eventId +// commentId +// content +// } +// }`; + +// const doc = parse(SubscribeToEventComments); +// const query = print(doc); + +// ( +// api.graphql({ +// query, +// variables, +// authMode: GRAPHQL_AUTH_MODE.OPENID_CONNECT, +// }) as any +// ).subscribe(); + +// expect(spyon_pubsub).toBeCalledWith( +// '', +// expect.objectContaining({ +// authenticationType: 'OPENID_CONNECT', +// }), +// undefined +// ); +// }); + +// test('happy-case-subscription', async done => { +// jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementation(async (url, init) => ({ +// extensions: { +// subscription: { +// newSubscriptions: {}, +// }, +// }, +// })); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; + +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// InternalPubSub.subscribe = jest.fn(() => Observable.of({}) as any); + +// const SubscribeToEventComments = `subscription SubscribeToEventComments($eventId: String!) { +// subscribeToEventComments(eventId: $eventId) { +// eventId +// commentId +// content +// } +// }`; + +// const doc = parse(SubscribeToEventComments); +// const query = print(doc); + +// const observable = ( +// api.graphql( +// graphqlOperation(query, variables) +// ) as unknown as Observable +// ).subscribe({ +// next: () => { +// expect(InternalPubSub.subscribe).toHaveBeenCalledTimes(1); +// const subscribeOptions = (InternalPubSub.subscribe as any).mock +// .calls[0][1]; +// expect(subscribeOptions.provider).toBe( +// INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER +// ); +// done(); +// }, +// }); + +// expect(observable).not.toBe(undefined); +// }); + +// test('happy case subscription with additionalHeaders', async done => { +// jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementation(async (url, init) => ({ +// extensions: { +// subscription: { +// newSubscriptions: {}, +// }, +// }, +// })); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; + +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// InternalPubSub.subscribe = jest.fn(() => Observable.of({}) as any); + +// const SubscribeToEventComments = `subscription SubscribeToEventComments($eventId: String!) { +// subscribeToEventComments(eventId: $eventId) { +// eventId +// commentId +// content +// } +// }`; + +// const doc = parse(SubscribeToEventComments); +// const query = print(doc); + +// const additionalHeaders = { +// 'x-custom-header': 'value', +// }; + +// const observable = ( +// api.graphql( +// graphqlOperation(query, variables), +// additionalHeaders +// ) as unknown as Observable +// ).subscribe({ +// next: () => { +// expect(InternalPubSub.subscribe).toHaveBeenCalledTimes(1); +// const subscribeOptions = (InternalPubSub.subscribe as any).mock +// .calls[0][1]; +// expect(subscribeOptions.additionalHeaders).toBe(additionalHeaders); +// done(); +// }, +// }); + +// expect(observable).not.toBe(undefined); +// }); + +// test('happy case mutation', async () => { +// const spyonAuth = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { +// id: '809392da-ec91-4ef0-b219-5238a8f942b2', +// content: 'lalala', +// createdAt: new Date().toISOString(), +// }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); +// const AddComment = `mutation AddComment($eventId: ID!, $content: String!, $createdAt: String!) { +// commentOnEvent(eventId: $eventId, content: $content, createdAt: $createdAt) { +// eventId +// content +// createdAt +// } +// }`; + +// const doc = parse(AddComment); +// const query = print(doc); + +// const headers = { +// Authorization: null, +// 'X-Api-Key': apiKey, +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await api.graphql(graphqlOperation(AddComment, variables)); + +// expect(spyon).toBeCalledWith(url, init); +// }); + +// test('happy case query with additionalHeaders', async () => { +// const spyonAuth = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// graphql_headers: async () => +// Promise.resolve({ +// someHeaderSetAtConfigThatWillBeOverridden: 'initialValue', +// someOtherHeaderSetAtConfig: 'expectedValue', +// }), +// }); + +// const headers = { +// Authorization: null, +// 'X-Api-Key': apiKey, +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// const additionalHeaders = { +// someAddtionalHeader: 'foo', +// someHeaderSetAtConfigThatWillBeOverridden: 'expectedValue', +// }; + +// await api.graphql( +// graphqlOperation(GetEvent, variables), +// additionalHeaders +// ); + +// expect(spyon).toBeCalledWith(url, { +// ...init, +// headers: { +// someAddtionalHeader: 'foo', +// someHeaderSetAtConfigThatWillBeOverridden: 'expectedValue', +// ...init.headers, +// someOtherHeaderSetAtConfig: 'expectedValue', +// }, +// }); +// }); + +// test('call isInstanceCreated', () => { +// const createInstanceMock = spyOn(API.prototype, 'createInstance'); +// const api = new API(config); +// api.createInstanceIfNotCreated(); +// expect(createInstanceMock).toHaveBeenCalled(); +// }); + +// test('should not call createInstance when there is already an instance', () => { +// const api = new API(config); +// api.createInstance(); +// const createInstanceMock = spyOn(API.prototype, 'createInstance'); +// api.createInstanceIfNotCreated(); +// expect(createInstanceMock).not.toHaveBeenCalled(); +// }); + +// test('sends cookies with request', async () => { +// const spyonAuth = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); + +// const api = new API(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// api.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// withCredentials: true, +// }); + +// const headers = { +// Authorization: null, +// 'X-Api-Key': apiKey, +// 'x-amz-user-agent': expectedUserAgentFrameworkOnly, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// withCredentials: true, +// }; +// let authToken: undefined; + +// await api.graphql(graphqlOperation(GetEvent, variables, authToken)); + +// expect(spyon).toBeCalledWith(url, init); +// }); +// }); + +// describe('configure test', () => { +// test('without aws_project_region', () => { +// const api = new API({}); + +// const options = { +// myoption: 'myoption', +// }; + +// expect(api.configure(options)).toEqual({ +// myoption: 'myoption', +// }); +// }); + +// test('with aws_project_region', () => { +// const api = new API({}); + +// const options = { +// aws_project_region: 'region', +// }; + +// expect(api.configure(options)).toEqual({ +// aws_project_region: 'region', +// header: {}, +// region: 'region', +// }); +// }); + +// test('with API options', () => { +// const api = new API({}); + +// const options = { +// API: { +// aws_project_region: 'api-region', +// }, +// aws_project_region: 'region', +// aws_appsync_region: 'appsync-region', +// }; + +// expect(api.configure(options)).toEqual({ +// aws_project_region: 'api-region', +// aws_appsync_region: 'appsync-region', +// header: {}, +// region: 'api-region', +// }); +// }); +// }); +// }); + +// describe('Internal API customUserAgent test', () => { +// beforeEach(() => { +// cancelMock = jest.fn(); +// tokenMock = jest.fn(); +// mockCancellableToken = { token: tokenMock, cancel: cancelMock }; +// isCancelSpy = jest.spyOn(axios, 'isCancel').mockReturnValue(true); +// cancelTokenSpy = jest +// .spyOn(axios.CancelToken, 'source') +// .mockImplementation(() => { +// return mockCancellableToken; +// }); +// }); +// describe('graphql test', () => { +// test('happy case mutation - API_KEY', async () => { +// const spyonAuth = jest +// .spyOn(Credentials, 'get') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res('cred'); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); + +// const internalApi = new InternalAPI(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { +// id: '809392da-ec91-4ef0-b219-5238a8f942b2', +// content: 'lalala', +// createdAt: new Date().toISOString(), +// }; +// internalApi.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); +// const AddComment = `mutation AddComment($eventId: ID!, $content: String!, $createdAt: String!) { +// commentOnEvent(eventId: $eventId, content: $content, createdAt: $createdAt) { +// eventId +// content +// createdAt +// } +// }`; + +// const doc = parse(AddComment); +// const query = print(doc); + +// const headers = { +// Authorization: null, +// 'X-Api-Key': apiKey, +// 'x-amz-user-agent': expectedUserAgentAPI, +// }; + +// const body = { +// query, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await internalApi.graphql( +// graphqlOperation(AddComment, variables), +// undefined, +// customUserAgentDetailsAPI +// ); + +// expect(spyon).toBeCalledWith(url, init); + +// spyonAuth.mockClear(); +// spyon.mockClear(); +// }); + +// test('happy case mutation - OPENID_CONNECT', async () => { +// const cache_config = { +// capacityInBytes: 3000, +// itemMaxSize: 800, +// defaultTTL: 3000000, +// defaultPriority: 5, +// warningThreshold: 0.8, +// storage: window.localStorage, +// }; + +// Cache.configure(cache_config); + +// const spyonCache = jest +// .spyOn(Cache, 'getItem') +// .mockImplementationOnce(() => { +// return null; +// }); + +// const spyonAuth = jest +// .spyOn(InternalAuth, 'currentAuthenticatedUser') +// .mockImplementationOnce(() => { +// return new Promise((res, rej) => { +// res({ +// name: 'federated user', +// token: 'federated_token_from_storage', +// }); +// }); +// }); + +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementationOnce((url, init) => { +// return new Promise((res, rej) => { +// res({}); +// }); +// }); + +// const internalApi = new InternalAPI(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; +// internalApi.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'OPENID_CONNECT', +// }); + +// const headers = { +// Authorization: 'federated_token_from_storage', +// 'x-amz-user-agent': expectedUserAgentAPI, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await internalApi.graphql( +// graphqlOperation(GetEvent, variables), +// undefined, +// customUserAgentDetailsAPI +// ); + +// expect(spyon).toBeCalledWith(url, init); +// expect(spyonAuth).toBeCalledWith(undefined, customUserAgentDetailsAPI); + +// spyonCache.mockClear(); +// spyonAuth.mockClear(); +// spyon.mockClear(); +// }); + +// test('happy case mutation - AMAZON_COGNITO_USER_POOLS', async () => { +// const spyon = jest +// .spyOn(RestClient.prototype, 'post') +// .mockReturnValue(Promise.resolve({})); + +// const spyonAuth = jest +// .spyOn(InternalAuth, 'currentSession') +// .mockReturnValue({ +// getAccessToken: () => ({ +// getJwtToken: () => 'Secret-Token', +// }), +// } as any); + +// const internalApi = new InternalAPI(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }, +// apiKey = 'secret-api-key'; +// internalApi.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// const headers = { +// Authorization: 'Secret-Token', +// 'x-amz-user-agent': expectedUserAgentAPI, +// }; + +// const body = { +// query: getEventQuery, +// variables, +// }; + +// const init = { +// headers, +// body, +// signerServiceInfo: { +// service: 'appsync', +// region, +// }, +// cancellableToken: mockCancellableToken, +// }; + +// await internalApi.graphql( +// { +// query: GetEvent, +// variables, +// authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS, +// }, +// undefined, +// customUserAgentDetailsAPI +// ); + +// expect(spyon).toBeCalledWith(url, init); +// expect(spyonAuth).toBeCalledWith(customUserAgentDetailsAPI); + +// spyon.mockClear(); +// spyonAuth.mockClear(); +// }); + +// test('happy case subscription', async done => { +// jest +// .spyOn(RestClient.prototype, 'post') +// .mockImplementation(async (url, init) => ({ +// extensions: { +// subscription: { +// newSubscriptions: {}, +// }, +// }, +// })); + +// const internalApi = new InternalAPI(config); +// const url = 'https://appsync.amazonaws.com', +// region = 'us-east-2', +// apiKey = 'secret_api_key', +// variables = { id: '809392da-ec91-4ef0-b219-5238a8f942b2' }; + +// internalApi.configure({ +// aws_appsync_graphqlEndpoint: url, +// aws_appsync_region: region, +// aws_appsync_authenticationType: 'API_KEY', +// aws_appsync_apiKey: apiKey, +// }); + +// InternalPubSub.subscribe = jest.fn(() => Observable.of({}) as any); + +// const SubscribeToEventComments = `subscription SubscribeToEventComments($eventId: String!) { +// subscribeToEventComments(eventId: $eventId) { +// eventId +// commentId +// content +// } +// }`; + +// const doc = parse(SubscribeToEventComments); +// const query = print(doc); + +// const observable = ( +// internalApi.graphql( +// graphqlOperation(query, variables), +// undefined, +// customUserAgentDetailsAPI +// ) as unknown as Observable +// ).subscribe({ +// next: () => { +// expect(InternalPubSub.subscribe).toHaveBeenCalledTimes(1); +// expect(InternalPubSub.subscribe).toHaveBeenCalledWith( +// expect.anything(), +// expect.anything(), +// customUserAgentDetailsAPI +// ); +// const subscribeOptions = (InternalPubSub.subscribe as any).mock +// .calls[0][1]; +// expect(subscribeOptions.provider).toBe( +// INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER +// ); +// done(); +// }, +// }); + +// expect(observable).not.toBe(undefined); +// }); +// }); +// }); +// TODO(v6): add tests +describe.skip('API tests', () => { + test('add tests', async () => {}); }); diff --git a/packages/api-graphql/package.json b/packages/api-graphql/package.json index 93fe5835aa9..35ea3323632 100644 --- a/packages/api-graphql/package.json +++ b/packages/api-graphql/package.json @@ -1,76 +1,72 @@ { - "name": "@aws-amplify/api-graphql", - "private": true, - "version": "4.0.0", - "description": "Api-graphql category of aws-amplify", - "main": "./lib/index.js", - "module": "./lib-esm/index.js", - "typings": "./lib-esm/index.d.ts", - "react-native": { - "./lib/index": "./lib-esm/index.js" - }, - "sideEffects": [ - "./lib/GraphQLAPI.js", - "./lib-esm/GraphQLAPI.js" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "test": "npm run lint && jest --coverage", - "test:size": "size-limit", - "build-with-test": "npm test && npm run build", - "build:cjs": "node ./build es5 && webpack && webpack --config ./webpack.config.dev.js", - "build:esm": "node ./build es6", - "build:cjs:watch": "node ./build es5 --watch", - "build:esm:watch": "node ./build es6 --watch", - "build": "npm run clean && npm run build:esm && npm run build:cjs", - "clean": "npm run clean:size && rimraf lib-esm lib dist", - "clean:size": "rimraf dual-publish-tmp tmp*", - "format": "echo \"Not implemented\"", - "lint": "tslint 'src/**/*.ts' && npm run ts-coverage", - "ts-coverage": "typescript-coverage-report -p ./tsconfig.build.json -t 75.62" - }, - "repository": { - "type": "git", - "url": "https://github.com/aws-amplify/amplify-js.git" - }, - "author": "Amazon Web Services", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/aws/aws-amplify/issues" - }, - "homepage": "https://aws-amplify.github.io/", - "files": [ - "lib", - "lib-esm", - "src", - "internals" - ], - "dependencies": { - "@aws-amplify/api-rest": "4.0.0", - "@aws-amplify/auth": "6.0.0", - "@aws-amplify/pubsub": "6.0.0", - "graphql": "15.8.0", - "tslib": "^2.5.0", - "uuid": "^9.0.0", - "zen-observable-ts": "0.8.19" - }, - "peerDependencies": { - "@aws-amplify/core": "^6.0.0" - }, - "devDependencies": { - "@aws-amplify/core": "6.0.0", - "@types/zen-observable": "^0.8.0" - }, - "size-limit": [ - { - "name": "API (GraphQL client)", - "path": "./lib-esm/index.js", - "import": "{ Amplify, GraphQLAPI }", - "limit": "90.35 kB" - } - ], + "name": "@aws-amplify/api-graphql", + "version": "4.0.0", + "description": "Api-graphql category of aws-amplify", + "main": "./lib/index.js", + "module": "./lib-esm/index.js", + "typings": "./lib-esm/index.d.ts", + "react-native": { + "./lib/index": "./lib-esm/index.js" + }, + "sideEffects": [ + "./lib/GraphQLAPI.js", + "./lib-esm/GraphQLAPI.js" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "npm run lint && jest -w 1 --coverage", + "test:watch": "tslint 'src/**/*.ts' && jest -w 1 --watch", + "build-with-test": "npm run clean && npm test && tsc && webpack", + "build:cjs": "rimraf lib && tsc -m commonjs --outDir lib && webpack && webpack --config ./webpack.config.dev.js", + "build:esm": "rimraf lib-esm && tsc -m esnext --outDir lib-esm", + "build:cjs:watch": "rimraf lib && tsc -m commonjs --outDir lib --watch", + "build:esm:watch": "rimraf lib-esm && tsc -m esnext --outDir lib-esm --watch", + "build": "npm run clean && npm run build:esm && npm run build:cjs", + "clean": "npm run clean:size && rimraf lib-esm lib dist", + "clean:size": "rimraf dual-publish-tmp tmp*", + "format": "echo \"Not implemented\"", + "lint": "tslint 'src/**/*.ts' && npm run ts-coverage", + "ts-coverage": "typescript-coverage-report -p ./tsconfig.json -t 70.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/aws-amplify/amplify-js.git" + }, + "author": "Amazon Web Services", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/aws/aws-amplify/issues" + }, + "homepage": "https://aws-amplify.github.io/", + "devDependencies": { + "@types/zen-observable": "^0.8.0" + }, + "files": [ + "lib", + "lib-esm", + "src", + "internals" + ], + "dependencies": { + "@aws-amplify/api-rest": "4.0.0", + "@aws-amplify/auth": "6.0.0", + "@aws-amplify/core": "6.0.0", + "@aws-sdk/types": "3.387.0", + "graphql": "15.8.0", + "tslib": "^1.8.0", + "uuid": "^3.2.1", + "zen-observable-ts": "0.8.19" + }, + "size-limit": [ + { + "name": "API (GraphQL client)", + "path": "./lib-esm/index.js", + "import": "{ Amplify, GraphQLAPI }", + "limit": "91.7 kB" + } + ], "jest": { "globals": { "ts-jest": { @@ -83,7 +79,8 @@ "esnext.asynciterable", "es2017.object" ], - "allowJs": true + "allowJs": true, + "noEmitOnError": false } } }, diff --git a/packages/api-graphql/src/GraphQLAPI.ts b/packages/api-graphql/src/GraphQLAPI.ts index b7796b389b1..905c50dafc8 100644 --- a/packages/api-graphql/src/GraphQLAPI.ts +++ b/packages/api-graphql/src/GraphQLAPI.ts @@ -1,6 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Amplify } from '@aws-amplify/core'; import { GraphQLOptions, GraphQLResult } from './types'; import { InternalGraphQLAPIClass } from './internals'; import Observable from 'zen-observable-ts'; @@ -39,4 +38,3 @@ export class GraphQLAPIClass extends InternalGraphQLAPIClass { } export const GraphQLAPI = new GraphQLAPIClass(null); -Amplify.register(GraphQLAPI); diff --git a/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts b/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts new file mode 100644 index 00000000000..1e7f805e1c6 --- /dev/null +++ b/packages/api-graphql/src/Providers/AWSAppSyncRealTimeProvider/index.ts @@ -0,0 +1,1003 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import Observable, { ZenObservable } from 'zen-observable-ts'; +import { GraphQLError } from 'graphql'; +import * as url from 'url'; +import { v4 as uuid } from 'uuid'; +import { Buffer } from 'buffer'; +import { Hub, fetchAuthSession } from '@aws-amplify/core'; + +import { + CONTROL_MSG, + ConnectionState, + PubSubContent, + PubSubContentObserver, +} from '../../types/PubSub'; + +import { signRequest } from '@aws-amplify/core/internals/aws-client-utils'; + +import { + AMPLIFY_SYMBOL, + AWS_APPSYNC_REALTIME_HEADERS, + CONNECTION_INIT_TIMEOUT, + DEFAULT_KEEP_ALIVE_TIMEOUT, + DEFAULT_KEEP_ALIVE_ALERT_TIMEOUT, + MAX_DELAY_MS, + MESSAGE_TYPES, + NON_RETRYABLE_CODES, + SOCKET_STATUS, + START_ACK_TIMEOUT, + SUBSCRIPTION_STATUS, + CONNECTION_STATE_CHANGE, +} from '../constants'; +import { + ConnectionStateMonitor, + CONNECTION_CHANGE, +} from '../../utils/ConnectionStateMonitor'; +import { + ReconnectEvent, + ReconnectionMonitor, +} from '../../utils/ReconnectionMonitor'; +import { GraphQLAuthMode } from '@aws-amplify/core/lib-esm/singleton/API/types'; + +import { + CustomUserAgentDetails, + Logger, + NonRetryableError, + USER_AGENT_HEADER, + getAmplifyUserAgent, + isNonRetryableError, + jitteredExponentialRetry, +} from '@aws-amplify/core/internals/utils'; +import { DocumentType } from '@aws-amplify/api-rest'; + +const logger = new Logger('AWSAppSyncRealTimeProvider'); + +const dispatchApiEvent = payload => { + Hub.dispatch('api', payload, 'PubSub', AMPLIFY_SYMBOL); +}; + +export type ObserverQuery = { + observer: PubSubContentObserver; + query: string; + variables: Record; + subscriptionState: SUBSCRIPTION_STATUS; + subscriptionReadyCallback?: Function; + subscriptionFailedCallback?: Function; + startAckTimeoutId?: ReturnType; +}; + +const standardDomainPattern = + /^https:\/\/\w{26}\.appsync\-api\.\w{2}(?:(?:\-\w{2,})+)\-\d\.amazonaws.com(?:\.cn)?\/graphql$/i; + +const customDomainPath = '/realtime'; + +type DataObject = { + data: Record; +}; + +type DataPayload = { + id: string; + payload: DataObject; + type: string; +}; + +type ParsedMessagePayload = { + type: string; + payload: { + connectionTimeoutMs: number; + errors?: [{ errorType: string; errorCode: number }]; + }; +}; + +export interface AWSAppSyncRealTimeProviderOptions { + appSyncGraphqlEndpoint?: string; + authenticationType?: GraphQLAuthMode; + query?: string; + variables?: Record; + apiKey?: string; + region?: string; + graphql_headers?: () => {} | (() => Promise<{}>); + additionalHeaders?: { [key: string]: string }; +} + +type AWSAppSyncRealTimeAuthInput = + Partial & { + canonicalUri: string; + payload: string; + host?: string | undefined; + }; + +export class AWSAppSyncRealTimeProvider { + private awsRealTimeSocket?: WebSocket; + private socketStatus: SOCKET_STATUS = SOCKET_STATUS.CLOSED; + private keepAliveTimeoutId?: ReturnType; + private keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT; + private keepAliveAlertTimeoutId?: ReturnType; + private subscriptionObserverMap: Map = new Map(); + private promiseArray: Array<{ res: Function; rej: Function }> = []; + private connectionState: ConnectionState; + private readonly connectionStateMonitor = new ConnectionStateMonitor(); + private readonly reconnectionMonitor = new ReconnectionMonitor(); + private connectionStateMonitorSubscription: ZenObservable.Subscription; + + constructor(options: AWSAppSyncRealTimeProviderOptions = {}) { + // Monitor the connection state and pass changes along to Hub + this.connectionStateMonitorSubscription = + this.connectionStateMonitor.connectionStateObservable.subscribe( + connectionState => { + dispatchApiEvent({ + event: CONNECTION_STATE_CHANGE, + data: { + provider: this, + connectionState, + }, + message: `Connection state is ${connectionState}`, + }); + this.connectionState = connectionState; + + // Trigger START_RECONNECT when the connection is disrupted + if (connectionState === ConnectionState.ConnectionDisrupted) { + this.reconnectionMonitor.record(ReconnectEvent.START_RECONNECT); + } + + // Trigger HALT_RECONNECT to halt reconnection attempts when the state is anything other than + // ConnectionDisrupted or Connecting + if ( + [ + ConnectionState.Connected, + ConnectionState.ConnectedPendingDisconnect, + ConnectionState.ConnectedPendingKeepAlive, + ConnectionState.ConnectedPendingNetwork, + ConnectionState.ConnectionDisruptedPendingNetwork, + ConnectionState.Disconnected, + ].includes(connectionState) + ) { + this.reconnectionMonitor.record(ReconnectEvent.HALT_RECONNECT); + } + } + ); + } + + /** + * Mark the socket closed and release all active listeners + */ + close() { + // Mark the socket closed both in status and the connection monitor + this.socketStatus = SOCKET_STATUS.CLOSED; + this.connectionStateMonitor.record(CONNECTION_CHANGE.CONNECTION_FAILED); + + // Turn off the subscription monitor Hub publishing + this.connectionStateMonitorSubscription.unsubscribe(); + // Complete all reconnect observers + this.reconnectionMonitor.close(); + } + + getNewWebSocket(url: string, protocol: string) { + return new WebSocket(url, protocol); + } + + getProviderName() { + return 'AWSAppSyncRealTimeProvider'; + } + + // Check if url matches standard domain pattern + private isCustomDomain(url: string): boolean { + return url.match(standardDomainPattern) === null; + } + + subscribe( + options?: AWSAppSyncRealTimeProviderOptions, + customUserAgentDetails?: CustomUserAgentDetails + ): Observable> { + const { + appSyncGraphqlEndpoint, + region, + query, + variables, + authenticationType, + } = options; + + return new Observable(observer => { + if (!options || !appSyncGraphqlEndpoint) { + observer.error({ + errors: [ + { + ...new GraphQLError( + `Subscribe only available for AWS AppSync endpoint` + ), + }, + ], + }); + observer.complete(); + } else { + let subscriptionStartActive = false; + const subscriptionId = uuid(); + const startSubscription = () => { + if (!subscriptionStartActive) { + subscriptionStartActive = true; + + const startSubscriptionPromise = + this._startSubscriptionWithAWSAppSyncRealTime({ + options: { + query, + variables, + region, + authenticationType, + appSyncGraphqlEndpoint, + }, + observer, + subscriptionId, + customUserAgentDetails, + }).catch(err => { + logger.debug( + `${CONTROL_MSG.REALTIME_SUBSCRIPTION_INIT_ERROR}: ${err}` + ); + + this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED); + }); + startSubscriptionPromise.finally(() => { + subscriptionStartActive = false; + }); + } + }; + + let reconnectSubscription: ZenObservable.Subscription; + + // Add an observable to the reconnection list to manage reconnection for this subscription + reconnectSubscription = new Observable(observer => { + this.reconnectionMonitor.addObserver(observer); + }).subscribe(() => { + startSubscription(); + }); + + startSubscription(); + + return async () => { + // Cleanup reconnection subscription + reconnectSubscription?.unsubscribe(); + + // Cleanup after unsubscribing or observer.complete was called after _startSubscriptionWithAWSAppSyncRealTime + try { + // Waiting that subscription has been connected before trying to unsubscribe + await this._waitForSubscriptionToBeConnected(subscriptionId); + + const { subscriptionState } = + this.subscriptionObserverMap.get(subscriptionId) || {}; + + if (!subscriptionState) { + // subscription already unsubscribed + return; + } + + if (subscriptionState === SUBSCRIPTION_STATUS.CONNECTED) { + this._sendUnsubscriptionMessage(subscriptionId); + } else { + throw new Error('Subscription never connected'); + } + } catch (err) { + logger.debug(`Error while unsubscribing ${err}`); + } finally { + this._removeSubscriptionObserver(subscriptionId); + } + }; + } + }); + } + + private async _startSubscriptionWithAWSAppSyncRealTime({ + options, + observer, + subscriptionId, + customUserAgentDetails, + }: { + options: AWSAppSyncRealTimeProviderOptions; + observer: PubSubContentObserver; + subscriptionId: string; + customUserAgentDetails: CustomUserAgentDetails; + }) { + const { + appSyncGraphqlEndpoint, + authenticationType, + query, + variables, + apiKey, + region, + graphql_headers = () => ({}), + additionalHeaders = {}, + } = options; + + const subscriptionState: SUBSCRIPTION_STATUS = SUBSCRIPTION_STATUS.PENDING; + const data = { + query, + variables, + }; + // Having a subscription id map will make it simple to forward messages received + this.subscriptionObserverMap.set(subscriptionId, { + observer, + query: query ?? '', + variables: variables ?? {}, + subscriptionState, + startAckTimeoutId: undefined, + }); + + // Preparing payload for subscription message + + const dataString = JSON.stringify(data); + const headerObj = { + ...(await this._awsRealTimeHeaderBasedAuth({ + apiKey, + appSyncGraphqlEndpoint, + authenticationType, + payload: dataString, + canonicalUri: '', + region, + additionalHeaders, + })), + ...(await graphql_headers()), + ...additionalHeaders, + [USER_AGENT_HEADER]: getAmplifyUserAgent(customUserAgentDetails), + }; + + const subscriptionMessage = { + id: subscriptionId, + payload: { + data: dataString, + extensions: { + authorization: { + ...headerObj, + }, + }, + }, + type: MESSAGE_TYPES.GQL_START, + }; + + const stringToAWSRealTime = JSON.stringify(subscriptionMessage); + + try { + this.connectionStateMonitor.record(CONNECTION_CHANGE.OPENING_CONNECTION); + await this._initializeWebSocketConnection({ + apiKey, + appSyncGraphqlEndpoint, + authenticationType, + region, + additionalHeaders, + }); + } catch (err) { + this._logStartSubscriptionError(subscriptionId, observer, err); + return; + } + + // Potential race condition can occur when unsubscribe is called during _initializeWebSocketConnection. + // E.g.unsubscribe gets invoked prior to finishing WebSocket handshake or START_ACK. + // Both subscriptionFailedCallback and subscriptionReadyCallback are used to synchronized this. + + const { subscriptionFailedCallback, subscriptionReadyCallback } = + this.subscriptionObserverMap.get(subscriptionId) ?? {}; + + // This must be done before sending the message in order to be listening immediately + this.subscriptionObserverMap.set(subscriptionId, { + observer, + subscriptionState, + query: query ?? '', + variables: variables ?? {}, + subscriptionReadyCallback, + subscriptionFailedCallback, + startAckTimeoutId: setTimeout(() => { + this._timeoutStartSubscriptionAck.call(this, subscriptionId); + }, START_ACK_TIMEOUT), + }); + if (this.awsRealTimeSocket) { + this.awsRealTimeSocket.send(stringToAWSRealTime); + } + } + + // Log logic for start subscription failures + private _logStartSubscriptionError( + subscriptionId: string, + observer: PubSubContentObserver, + err: { message?: string } + ) { + logger.debug({ err }); + const message = String(err.message ?? ''); + // Resolving to give the state observer time to propogate the update + Promise.resolve( + this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED) + ); + + // Capture the error only when the network didn't cause disruption + if ( + this.connectionState !== ConnectionState.ConnectionDisruptedPendingNetwork + ) { + // When the error is non-retriable, error out the observable + if (isNonRetryableError(err)) { + observer.error({ + errors: [ + { + ...new GraphQLError( + `${CONTROL_MSG.CONNECTION_FAILED}: ${message}` + ), + }, + ], + }); + } else { + logger.debug(`${CONTROL_MSG.CONNECTION_FAILED}: ${message}`); + } + + const { subscriptionFailedCallback } = + this.subscriptionObserverMap.get(subscriptionId) || {}; + + // Notify concurrent unsubscription + if (typeof subscriptionFailedCallback === 'function') { + subscriptionFailedCallback(); + } + } + } + + // Waiting that subscription has been connected before trying to unsubscribe + private async _waitForSubscriptionToBeConnected(subscriptionId: string) { + const subscriptionObserver = + this.subscriptionObserverMap.get(subscriptionId); + if (subscriptionObserver) { + const { subscriptionState } = subscriptionObserver; + // This in case unsubscribe is invoked before sending start subscription message + if (subscriptionState === SUBSCRIPTION_STATUS.PENDING) { + return new Promise((res, rej) => { + const { observer, subscriptionState, variables, query } = + subscriptionObserver; + this.subscriptionObserverMap.set(subscriptionId, { + observer, + subscriptionState, + variables, + query, + subscriptionReadyCallback: res, + subscriptionFailedCallback: rej, + }); + }); + } + } + } + + private _sendUnsubscriptionMessage(subscriptionId: string) { + try { + if ( + this.awsRealTimeSocket && + this.awsRealTimeSocket.readyState === WebSocket.OPEN && + this.socketStatus === SOCKET_STATUS.READY + ) { + // Preparing unsubscribe message to stop receiving messages for that subscription + const unsubscribeMessage = { + id: subscriptionId, + type: MESSAGE_TYPES.GQL_STOP, + }; + const stringToAWSRealTime = JSON.stringify(unsubscribeMessage); + this.awsRealTimeSocket.send(stringToAWSRealTime); + } + } catch (err) { + // If GQL_STOP is not sent because of disconnection issue, then there is nothing the client can do + logger.debug({ err }); + } + } + + private _removeSubscriptionObserver(subscriptionId: string) { + this.subscriptionObserverMap.delete(subscriptionId); + + // Verifying 1000ms after removing subscription in case there are new subscription unmount/mount + setTimeout(this._closeSocketIfRequired.bind(this), 1000); + } + + private _closeSocketIfRequired() { + if (this.subscriptionObserverMap.size > 0) { + // Active subscriptions on the WebSocket + return; + } + + if (!this.awsRealTimeSocket) { + this.socketStatus = SOCKET_STATUS.CLOSED; + return; + } + + this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSING_CONNECTION); + + if (this.awsRealTimeSocket.bufferedAmount > 0) { + // Still data on the WebSocket + setTimeout(this._closeSocketIfRequired.bind(this), 1000); + } else { + logger.debug('closing WebSocket...'); + if (this.keepAliveTimeoutId) { + clearTimeout(this.keepAliveTimeoutId); + } + if (this.keepAliveAlertTimeoutId) { + clearTimeout(this.keepAliveAlertTimeoutId); + } + const tempSocket = this.awsRealTimeSocket; + // Cleaning callbacks to avoid race condition, socket still exists + tempSocket.onclose = null; + tempSocket.onerror = null; + tempSocket.close(1000); + this.awsRealTimeSocket = undefined; + this.socketStatus = SOCKET_STATUS.CLOSED; + this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED); + } + } + + private _handleIncomingSubscriptionMessage(message: MessageEvent) { + if (typeof message.data !== 'string') { + return; + } + logger.debug( + `subscription message from AWS AppSync RealTime: ${message.data}` + ); + const { + id = '', + payload, + type, + }: DataPayload = JSON.parse(String(message.data)); + const { + observer = null, + query = '', + variables = {}, + startAckTimeoutId, + subscriptionReadyCallback, + subscriptionFailedCallback, + } = this.subscriptionObserverMap.get(id) || {}; + + logger.debug({ id, observer, query, variables }); + + if (type === MESSAGE_TYPES.GQL_DATA && payload && payload.data) { + if (observer) { + observer.next(payload); + } else { + logger.debug(`observer not found for id: ${id}`); + } + return; + } + + if (type === MESSAGE_TYPES.GQL_START_ACK) { + logger.debug( + `subscription ready for ${JSON.stringify({ query, variables })}` + ); + if (typeof subscriptionReadyCallback === 'function') { + subscriptionReadyCallback(); + } + if (startAckTimeoutId) clearTimeout(startAckTimeoutId); + // dispatchApiEvent( + // CONTROL_MSG.SUBSCRIPTION_ACK, + // { query, variables }, + // 'Connection established for subscription' + // ); + const subscriptionState = SUBSCRIPTION_STATUS.CONNECTED; + if (observer) { + this.subscriptionObserverMap.set(id, { + observer, + query, + variables, + startAckTimeoutId: undefined, + subscriptionState, + subscriptionReadyCallback, + subscriptionFailedCallback, + }); + } + this.connectionStateMonitor.record( + CONNECTION_CHANGE.CONNECTION_ESTABLISHED + ); + + return; + } + + if (type === MESSAGE_TYPES.GQL_CONNECTION_KEEP_ALIVE) { + if (this.keepAliveTimeoutId) clearTimeout(this.keepAliveTimeoutId); + if (this.keepAliveAlertTimeoutId) + clearTimeout(this.keepAliveAlertTimeoutId); + this.keepAliveTimeoutId = setTimeout( + () => this._errorDisconnect(CONTROL_MSG.TIMEOUT_DISCONNECT), + this.keepAliveTimeout + ); + this.keepAliveAlertTimeoutId = setTimeout(() => { + this.connectionStateMonitor.record(CONNECTION_CHANGE.KEEP_ALIVE_MISSED); + }, DEFAULT_KEEP_ALIVE_ALERT_TIMEOUT); + this.connectionStateMonitor.record(CONNECTION_CHANGE.KEEP_ALIVE); + return; + } + + if (type === MESSAGE_TYPES.GQL_ERROR) { + const subscriptionState = SUBSCRIPTION_STATUS.FAILED; + if (observer) { + this.subscriptionObserverMap.set(id, { + observer, + query, + variables, + startAckTimeoutId, + subscriptionReadyCallback, + subscriptionFailedCallback, + subscriptionState, + }); + + logger.debug( + `${CONTROL_MSG.CONNECTION_FAILED}: ${JSON.stringify(payload)}` + ); + + observer.error({ + errors: [ + { + ...new GraphQLError( + `${CONTROL_MSG.CONNECTION_FAILED}: ${JSON.stringify(payload)}` + ), + }, + ], + }); + + if (startAckTimeoutId) clearTimeout(startAckTimeoutId); + + if (typeof subscriptionFailedCallback === 'function') { + subscriptionFailedCallback(); + } + } + } + } + + private _errorDisconnect(msg: string) { + logger.debug(`Disconnect error: ${msg}`); + + if (this.awsRealTimeSocket) { + this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED); + this.awsRealTimeSocket.close(); + } + + this.socketStatus = SOCKET_STATUS.CLOSED; + } + + private _timeoutStartSubscriptionAck(subscriptionId: string) { + const subscriptionObserver = + this.subscriptionObserverMap.get(subscriptionId); + if (subscriptionObserver) { + const { observer, query, variables } = subscriptionObserver; + if (!observer) { + return; + } + this.subscriptionObserverMap.set(subscriptionId, { + observer, + query, + variables, + subscriptionState: SUBSCRIPTION_STATUS.FAILED, + }); + + this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED); + logger.debug( + 'timeoutStartSubscription', + JSON.stringify({ query, variables }) + ); + } + } + + private _initializeWebSocketConnection({ + appSyncGraphqlEndpoint, + authenticationType, + apiKey, + region, + additionalHeaders, + }: AWSAppSyncRealTimeProviderOptions) { + if (this.socketStatus === SOCKET_STATUS.READY) { + return; + } + return new Promise(async (res, rej) => { + this.promiseArray.push({ res, rej }); + + if (this.socketStatus === SOCKET_STATUS.CLOSED) { + try { + this.socketStatus = SOCKET_STATUS.CONNECTING; + + const payloadString = '{}'; + + const authHeader = await this._awsRealTimeHeaderBasedAuth({ + authenticationType, + payload: payloadString, + canonicalUri: '/connect', + apiKey, + appSyncGraphqlEndpoint, + region, + additionalHeaders, + }); + + const headerString = authHeader ? JSON.stringify(authHeader) : ''; + const headerQs = Buffer.from(headerString).toString('base64'); + + const payloadQs = Buffer.from(payloadString).toString('base64'); + + let discoverableEndpoint = appSyncGraphqlEndpoint ?? ''; + + if (this.isCustomDomain(discoverableEndpoint)) { + discoverableEndpoint = + discoverableEndpoint.concat(customDomainPath); + } else { + discoverableEndpoint = discoverableEndpoint + .replace('appsync-api', 'appsync-realtime-api') + .replace('gogi-beta', 'grt-beta'); + } + + // Creating websocket url with required query strings + const protocol = 'wss://'; + discoverableEndpoint = discoverableEndpoint + .replace('https://', protocol) + .replace('http://', protocol); + + const awsRealTimeUrl = `${discoverableEndpoint}?header=${headerQs}&payload=${payloadQs}`; + + await this._initializeRetryableHandshake(awsRealTimeUrl); + + this.promiseArray.forEach(({ res }) => { + logger.debug('Notifying connection successful'); + res(); + }); + this.socketStatus = SOCKET_STATUS.READY; + this.promiseArray = []; + } catch (err) { + logger.debug('Connection exited with', err); + this.promiseArray.forEach(({ rej }) => rej(err)); + this.promiseArray = []; + if ( + this.awsRealTimeSocket && + this.awsRealTimeSocket.readyState === WebSocket.OPEN + ) { + this.awsRealTimeSocket.close(3001); + } + this.awsRealTimeSocket = undefined; + this.socketStatus = SOCKET_STATUS.CLOSED; + } + } + }); + } + + private async _initializeRetryableHandshake(awsRealTimeUrl: string) { + logger.debug(`Initializaling retryable Handshake`); + await jitteredExponentialRetry( + this._initializeHandshake.bind(this), + [awsRealTimeUrl], + MAX_DELAY_MS + ); + } + + private async _initializeHandshake(awsRealTimeUrl: string) { + logger.debug(`Initializing handshake ${awsRealTimeUrl}`); + // Because connecting the socket is async, is waiting until connection is open + // Step 1: connect websocket + try { + await (() => { + return new Promise((res, rej) => { + const newSocket = this.getNewWebSocket(awsRealTimeUrl, 'graphql-ws'); + newSocket.onerror = () => { + logger.debug(`WebSocket connection error`); + }; + newSocket.onclose = () => { + rej(new Error('Connection handshake error')); + }; + newSocket.onopen = () => { + this.awsRealTimeSocket = newSocket; + return res(); + }; + }); + })(); + // Step 2: wait for ack from AWS AppSyncReaTime after sending init + await (() => { + return new Promise((res, rej) => { + if (this.awsRealTimeSocket) { + let ackOk = false; + this.awsRealTimeSocket.onerror = error => { + logger.debug(`WebSocket error ${JSON.stringify(error)}`); + }; + this.awsRealTimeSocket.onclose = event => { + logger.debug(`WebSocket closed ${event.reason}`); + rej(new Error(JSON.stringify(event))); + }; + + this.awsRealTimeSocket.onmessage = (message: MessageEvent) => { + if (typeof message.data !== 'string') { + return; + } + logger.debug( + `subscription message from AWS AppSyncRealTime: ${message.data} ` + ); + const data = JSON.parse(message.data) as ParsedMessagePayload; + const { + type, + payload: { + connectionTimeoutMs = DEFAULT_KEEP_ALIVE_TIMEOUT, + } = {}, + } = data; + if (type === MESSAGE_TYPES.GQL_CONNECTION_ACK) { + ackOk = true; + if (this.awsRealTimeSocket) { + this.keepAliveTimeout = connectionTimeoutMs; + this.awsRealTimeSocket.onmessage = + this._handleIncomingSubscriptionMessage.bind(this); + this.awsRealTimeSocket.onerror = err => { + logger.debug(err); + this._errorDisconnect(CONTROL_MSG.CONNECTION_CLOSED); + }; + this.awsRealTimeSocket.onclose = event => { + logger.debug(`WebSocket closed ${event.reason}`); + this._errorDisconnect(CONTROL_MSG.CONNECTION_CLOSED); + }; + } + res('Cool, connected to AWS AppSyncRealTime'); + return; + } + + if (type === MESSAGE_TYPES.GQL_CONNECTION_ERROR) { + const { + payload: { + errors: [{ errorType = '', errorCode = 0 } = {}] = [], + } = {}, + } = data; + + rej({ errorType, errorCode }); + } + }; + + const gqlInit = { + type: MESSAGE_TYPES.GQL_CONNECTION_INIT, + }; + this.awsRealTimeSocket.send(JSON.stringify(gqlInit)); + + const checkAckOk = (ackOk: boolean) => { + if (!ackOk) { + this.connectionStateMonitor.record( + CONNECTION_CHANGE.CONNECTION_FAILED + ); + rej( + new Error( + `Connection timeout: ack from AWSAppSyncRealTime was not received after ${CONNECTION_INIT_TIMEOUT} ms` + ) + ); + } + }; + + setTimeout(() => checkAckOk(ackOk), CONNECTION_INIT_TIMEOUT); + } + }); + })(); + } catch (err) { + const { errorType, errorCode } = err as { + errorType: string; + errorCode: number; + }; + + if (NON_RETRYABLE_CODES.includes(errorCode)) { + throw new NonRetryableError(errorType); + } else if (errorType) { + throw new Error(errorType); + } else { + throw err; + } + } + } + + private async _awsRealTimeHeaderBasedAuth({ + authenticationType, + payload, + canonicalUri, + appSyncGraphqlEndpoint, + region, + additionalHeaders, + }: AWSAppSyncRealTimeAuthInput): Promise< + Record | undefined + > { + const headerHandler: { + [key: string]: (arg0: AWSAppSyncRealTimeAuthInput) => {}; + } = { + apiKey: this._awsRealTimeApiKeyHeader.bind(this), + iam: this._awsRealTimeIAMHeader.bind(this), + jwt: this._awsRealTimeOPENIDHeader.bind(this), + custom: this._customAuthHeader, + }; + + if (!authenticationType || !headerHandler[authenticationType.type]) { + logger.debug(`Authentication type ${authenticationType} not supported`); + return undefined; + } else { + const handler = headerHandler[authenticationType.type]; + + const { host } = url.parse(appSyncGraphqlEndpoint ?? ''); + + logger.debug(`Authenticating with ${authenticationType}`); + let apiKey; + if (authenticationType.type === 'apiKey') { + apiKey = authenticationType.apiKey; + } + const result = await handler({ + payload, + canonicalUri, + appSyncGraphqlEndpoint, + apiKey, + region, + host, + additionalHeaders, + }); + + return result; + } + } + + private async _awsRealTimeCUPHeader({ host }: AWSAppSyncRealTimeAuthInput) { + const session = await fetchAuthSession(); + return { + Authorization: session.tokens.accessToken.toString(), + host, + }; + } + + private async _awsRealTimeOPENIDHeader({ + host, + }: AWSAppSyncRealTimeAuthInput) { + const session = await fetchAuthSession(); + + return { + Authorization: session.tokens.accessToken.toString(), + host, + }; + } + + private async _awsRealTimeApiKeyHeader({ + apiKey, + host, + }: AWSAppSyncRealTimeAuthInput) { + const dt = new Date(); + const dtStr = dt.toISOString().replace(/[:\-]|\.\d{3}/g, ''); + + return { + host, + 'x-amz-date': dtStr, + 'x-api-key': apiKey, + }; + } + + private async _awsRealTimeIAMHeader({ + payload, + canonicalUri, + appSyncGraphqlEndpoint, + region, + }: AWSAppSyncRealTimeAuthInput) { + const endpointInfo = { + region, + service: 'appsync', + }; + + const creds = (await fetchAuthSession()).credentials; + + const request = { + url: `${appSyncGraphqlEndpoint}${canonicalUri}`, + data: payload, + method: 'POST', + headers: { ...AWS_APPSYNC_REALTIME_HEADERS }, + }; + + const signed_params = signRequest( + { + headers: request.headers, + method: request.method, + url: new URL(request.url), + body: request.data, + }, + { + credentials: creds, + signingRegion: endpointInfo.region, + signingService: endpointInfo.service, + } + ); + return signed_params.headers; + } + + private _customAuthHeader({ + host, + additionalHeaders, + }: AWSAppSyncRealTimeAuthInput) { + if (!additionalHeaders || !additionalHeaders['Authorization']) { + throw new Error('No auth token specified'); + } + + return { + Authorization: additionalHeaders.Authorization, + host, + }; + } +} diff --git a/packages/api-graphql/src/Providers/constants.ts b/packages/api-graphql/src/Providers/constants.ts new file mode 100644 index 00000000000..cda958b7f6a --- /dev/null +++ b/packages/api-graphql/src/Providers/constants.ts @@ -0,0 +1,110 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +export { AMPLIFY_SYMBOL } from '@aws-amplify/core/internals/utils'; + +export const MAX_DELAY_MS = 5000; + +export const NON_RETRYABLE_CODES = [400, 401, 403]; + +export const CONNECTION_STATE_CHANGE = 'ConnectionStateChange'; + +export enum MESSAGE_TYPES { + /** + * Client -> Server message. + * This message type is the first message after handshake and this will initialize AWS AppSync RealTime communication + */ + GQL_CONNECTION_INIT = 'connection_init', + /** + * Server -> Client message + * This message type is in case there is an issue with AWS AppSync RealTime when establishing connection + */ + GQL_CONNECTION_ERROR = 'connection_error', + /** + * Server -> Client message. + * This message type is for the ack response from AWS AppSync RealTime for GQL_CONNECTION_INIT message + */ + GQL_CONNECTION_ACK = 'connection_ack', + /** + * Client -> Server message. + * This message type is for register subscriptions with AWS AppSync RealTime + */ + GQL_START = 'start', + /** + * Server -> Client message. + * This message type is for the ack response from AWS AppSync RealTime for GQL_START message + */ + GQL_START_ACK = 'start_ack', + /** + * Server -> Client message. + * This message type is for subscription message from AWS AppSync RealTime + */ + GQL_DATA = 'data', + /** + * Server -> Client message. + * This message type helps the client to know is still receiving messages from AWS AppSync RealTime + */ + GQL_CONNECTION_KEEP_ALIVE = 'ka', + /** + * Client -> Server message. + * This message type is for unregister subscriptions with AWS AppSync RealTime + */ + GQL_STOP = 'stop', + /** + * Server -> Client message. + * This message type is for the ack response from AWS AppSync RealTime for GQL_STOP message + */ + GQL_COMPLETE = 'complete', + /** + * Server -> Client message. + * This message type is for sending error messages from AWS AppSync RealTime to the client + */ + GQL_ERROR = 'error', // Server -> Client +} + +export enum SUBSCRIPTION_STATUS { + PENDING, + CONNECTED, + FAILED, +} + +export enum SOCKET_STATUS { + CLOSED, + READY, + CONNECTING, +} + +export const AWS_APPSYNC_REALTIME_HEADERS = { + accept: 'application/json, text/javascript', + 'content-encoding': 'amz-1.0', + 'content-type': 'application/json; charset=UTF-8', +}; + +/** + * Time in milleseconds to wait for GQL_CONNECTION_INIT message + */ +export const CONNECTION_INIT_TIMEOUT = 15000; + +/** + * Time in milleseconds to wait for GQL_START_ACK message + */ +export const START_ACK_TIMEOUT = 15000; + +/** + * Default Time in milleseconds to wait for GQL_CONNECTION_KEEP_ALIVE message + */ +export const DEFAULT_KEEP_ALIVE_TIMEOUT = 5 * 60 * 1000; + +/** + * Default Time in milleseconds to alert for missed GQL_CONNECTION_KEEP_ALIVE message + */ +export const DEFAULT_KEEP_ALIVE_ALERT_TIMEOUT = 65 * 1000; + +/** + * Default delay time in milleseconds between when reconnect is triggered vs when it is attempted + */ +export const RECONNECT_DELAY = 5 * 1000; + +/** + * Default interval time in milleseconds between when reconnect is re-attempted + */ +export const RECONNECT_INTERVAL = 60 * 1000; diff --git a/packages/api-graphql/src/index.ts b/packages/api-graphql/src/index.ts index e69d50f6dd7..c8627526a45 100644 --- a/packages/api-graphql/src/index.ts +++ b/packages/api-graphql/src/index.ts @@ -1,8 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { GraphQLAPI } from './GraphQLAPI'; -export { GraphQLResult, GraphQLAuthError, GRAPHQL_AUTH_MODE } from './types'; export { GraphQLAPI, GraphQLAPIClass, graphqlOperation } from './GraphQLAPI'; export * from './types'; -export default GraphQLAPI; diff --git a/packages/api-graphql/src/internals/InternalGraphQLAPI.ts b/packages/api-graphql/src/internals/InternalGraphQLAPI.ts index 67ead720405..28514b56d7e 100644 --- a/packages/api-graphql/src/internals/InternalGraphQLAPI.ts +++ b/packages/api-graphql/src/internals/InternalGraphQLAPI.ts @@ -9,24 +9,21 @@ import { OperationTypeNode, } from 'graphql'; import Observable from 'zen-observable-ts'; +import { Amplify, Cache, fetchAuthSession } from '@aws-amplify/core'; import { - Amplify, - ConsoleLogger as Logger, - Credentials, CustomUserAgentDetails, + ConsoleLogger as Logger, getAmplifyUserAgent, - INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER, - Cache, -} from '@aws-amplify/core'; -import { InternalPubSub } from '@aws-amplify/pubsub/internals'; -import { Auth } from '@aws-amplify/auth'; +} from '@aws-amplify/core/internals/utils'; import { GraphQLAuthError, - GraphQLOptions, GraphQLResult, GraphQLOperation, + GraphQLOptions, } from '../types'; -import { RestClient } from '@aws-amplify/api-rest'; +import { post } from '@aws-amplify/api-rest'; +import { AWSAppSyncRealTimeProvider } from '../Providers/AWSAppSyncRealTimeProvider'; + const USER_AGENT_HEADER = 'x-amz-user-agent'; const logger = new Logger('GraphQLAPI'); @@ -49,11 +46,9 @@ export class InternalGraphQLAPIClass { * @private */ private _options; - private _api = null; + private appSyncRealTime: AWSAppSyncRealTimeProvider | null; - Auth = Auth; Cache = Cache; - Credentials = Credentials; /** * Initialize GraphQL API with AWS configuration @@ -68,94 +63,41 @@ export class InternalGraphQLAPIClass { return 'InternalGraphQLAPI'; } - /** - * Configure API - * @param {Object} config - Configuration of the API - * @return {Object} - The current configuration - */ - configure(options) { - const { API = {}, ...otherOptions } = options || {}; - let opt = { ...otherOptions, ...API }; - logger.debug('configure GraphQL API', { opt }); - - if (opt['aws_project_region']) { - opt = Object.assign({}, opt, { - region: opt['aws_project_region'], - header: {}, - }); - } - - if ( - typeof opt.graphql_headers !== 'undefined' && - typeof opt.graphql_headers !== 'function' - ) { - logger.warn('graphql_headers should be a function'); - opt.graphql_headers = undefined; - } - - this._options = Object.assign({}, this._options, opt); - - this.createInstance(); - - return this._options; - } - - /** - * Create an instance of API for the library - * @return - A promise of true if Success - */ - createInstance() { - logger.debug('create Rest instance'); - if (this._options) { - this._api = new RestClient(this._options); - // Share instance Credentials with client for SSR - this._api.Credentials = this.Credentials; - - return true; - } else { - return Promise.reject('API not configured'); - } - } - private async _headerBasedAuth( defaultAuthenticationType?, - additionalHeaders: { [key: string]: string } = {} + additionalHeaders: { [key: string]: string } = {}, + customUserAgentDetails?: CustomUserAgentDetails ) { - const { aws_appsync_authenticationType, aws_appsync_apiKey: apiKey } = - this._options; - const authenticationType = - defaultAuthenticationType || aws_appsync_authenticationType || 'AWS_IAM'; + const config = Amplify.getConfig(); + const { + region: region, + endpoint: appSyncGraphqlEndpoint, + defaultAuthMode: authenticationType, + } = config.API.AppSync; + let headers = {}; - switch (authenticationType) { - case 'API_KEY': - if (!apiKey) { + switch (authenticationType.type) { + case 'apiKey': + if (!authenticationType.apiKey) { throw new Error(GraphQLAuthError.NO_API_KEY); } headers = { - Authorization: null, - 'X-Api-Key': apiKey, + 'X-Api-Key': authenticationType.apiKey, }; break; - case 'AWS_IAM': - const credentialsOK = await this._ensureCredentials(); - if (!credentialsOK) { + case 'iam': + const session = await fetchAuthSession(); + if (session.credentials === undefined) { throw new Error(GraphQLAuthError.NO_CREDENTIALS); } break; - case 'OPENID_CONNECT': + case 'jwt': try { let token; - // backwards compatibility - const federatedInfo = await Cache.getItem('federatedInfo'); - if (federatedInfo) { - token = federatedInfo.token; - } else { - const currentUser = await Auth.currentAuthenticatedUser(); - if (currentUser) { - token = currentUser.token; - } - } + + token = (await fetchAuthSession()).tokens?.accessToken.toString(); + if (!token) { throw new Error(GraphQLAuthError.NO_FEDERATED_JWT); } @@ -166,17 +108,7 @@ export class InternalGraphQLAPIClass { throw new Error(GraphQLAuthError.NO_CURRENT_USER); } break; - case 'AMAZON_COGNITO_USER_POOLS': - try { - const session = await this.Auth.currentSession(); - headers = { - Authorization: session.getAccessToken().getJwtToken(), - }; - } catch (e) { - throw new Error(GraphQLAuthError.NO_CURRENT_USER); - } - break; - case 'AWS_LAMBDA': + case 'custom': if (!additionalHeaders.Authorization) { throw new Error(GraphQLAuthError.NO_AUTH_TOKEN); } @@ -240,22 +172,11 @@ export class InternalGraphQLAPIClass { switch (operationType) { case 'query': case 'mutation': - this.createInstanceIfNotCreated(); - const cancellableToken = this._api.getCancellableToken(); - const initParams = { - cancellableToken, - withCredentials: this._options.withCredentials, - }; const responsePromise = this._graphql( { query, variables, authMode }, headers, - initParams, customUserAgentDetails ); - this._api.updateRequestToBeCancellable( - responsePromise, - cancellableToken - ); return responsePromise; case 'subscription': return this._graphqlSubscribe( @@ -271,26 +192,31 @@ export class InternalGraphQLAPIClass { private async _graphql( { query, variables, authMode }: GraphQLOptions, additionalHeaders = {}, - initParams = {}, customUserAgentDetails?: CustomUserAgentDetails ): Promise> { - this.createInstanceIfNotCreated(); - const { - aws_appsync_region: region, - aws_appsync_graphqlEndpoint: appSyncGraphqlEndpoint, - graphql_headers = () => ({}), - graphql_endpoint: customGraphqlEndpoint, - graphql_endpoint_iam_region: customEndpointRegion, - } = this._options; + const config = Amplify.getConfig(); + + const { region: region, endpoint: appSyncGraphqlEndpoint } = + config.API.AppSync; + + const customGraphqlEndpoint = null; + const customEndpointRegion = null; const headers = { ...(!customGraphqlEndpoint && - (await this._headerBasedAuth(authMode, additionalHeaders))), + (await this._headerBasedAuth( + authMode, + additionalHeaders, + customUserAgentDetails + ))), ...(customGraphqlEndpoint && (customEndpointRegion - ? await this._headerBasedAuth(authMode, additionalHeaders) + ? await this._headerBasedAuth( + authMode, + additionalHeaders, + customUserAgentDetails + ) : { Authorization: null })), - ...(await graphql_headers({ query, variables })), ...additionalHeaders, ...(!customGraphqlEndpoint && { [USER_AGENT_HEADER]: getAmplifyUserAgent(customUserAgentDetails), @@ -302,18 +228,6 @@ export class InternalGraphQLAPIClass { variables, }; - const init = Object.assign( - { - headers, - body, - signerServiceInfo: { - service: !customGraphqlEndpoint ? 'appsync' : 'execute-api', - region: !customGraphqlEndpoint ? region : customEndpointRegion, - }, - }, - initParams - ); - const endpoint = customGraphqlEndpoint || appSyncGraphqlEndpoint; if (!endpoint) { @@ -327,14 +241,13 @@ export class InternalGraphQLAPIClass { let response; try { - response = await this._api.post(endpoint, init); + response = await post(endpoint, { + headers, + body, + region, + serviceName: 'appsync', + }); } catch (err) { - // If the exception is because user intentionally - // cancelled the request, do not modify the exception - // so that clients can identify the exception correctly. - if (this._api.isCancel(err)) { - throw err; - } response = { data: {}, errors: [new GraphQLError(err.message, null, null, null, null, err)], @@ -350,39 +263,6 @@ export class InternalGraphQLAPIClass { return response; } - async createInstanceIfNotCreated() { - if (!this._api) { - await this.createInstance(); - } - } - - /** - * Checks to see if an error thrown is from an api request cancellation - * @param {any} error - Any error - * @return {boolean} - A boolean indicating if the error was from an api request cancellation - */ - isCancel(error) { - return this._api.isCancel(error); - } - - /** - * Cancels an inflight request. Only applicable for graphql queries and mutations - * @param {any} request - request to cancel - * @return {boolean} - A boolean indicating if the request was cancelled - */ - cancel(request: Promise, message?: string) { - return this._api.cancel(request, message); - } - - /** - * Check if the request has a corresponding cancel token in the WeakMap. - * @params request - The request promise - * @return if the request has a corresponding cancel token. - */ - hasCancelToken(request: Promise) { - return this._api.hasCancelToken(request); - } - private _graphqlSubscribe( { query, @@ -393,57 +273,18 @@ export class InternalGraphQLAPIClass { additionalHeaders = {}, customUserAgentDetails?: CustomUserAgentDetails ): Observable { - const { - aws_appsync_region: region, - aws_appsync_graphqlEndpoint: appSyncGraphqlEndpoint, - aws_appsync_authenticationType, - aws_appsync_apiKey: apiKey, - graphql_headers = () => ({}), - } = this._options; - const authenticationType = - defaultAuthenticationType || aws_appsync_authenticationType || 'AWS_IAM'; - - if (InternalPubSub && typeof InternalPubSub.subscribe === 'function') { - return InternalPubSub.subscribe( - '', - { - provider: INTERNAL_AWS_APPSYNC_REALTIME_PUBSUB_PROVIDER, - appSyncGraphqlEndpoint, - authenticationType, - apiKey, - query: print(query as DocumentNode), - region, - variables, - graphql_headers, - additionalHeaders, - authToken, - }, - customUserAgentDetails - ); - } else { - logger.debug('No pubsub module applied for subscription'); - throw new Error('No pubsub module applied for subscription'); + const { AppSync } = Amplify.getConfig().API ?? {}; + if (!this.appSyncRealTime) { + this.appSyncRealTime = new AWSAppSyncRealTimeProvider(); } - } - - /** - * @private - */ - _ensureCredentials() { - return this.Credentials.get() - .then(credentials => { - if (!credentials) return false; - const cred = this.Credentials.shear(credentials); - logger.debug('set credentials for api', cred); - - return true; - }) - .catch(err => { - logger.warn('ensure credentials error', err); - return false; - }); + return this.appSyncRealTime.subscribe({ + query: print(query as DocumentNode), + variables, + appSyncGraphqlEndpoint: AppSync.endpoint, + region: AppSync.region, + authenticationType: AppSync.defaultAuthMode, + }); } } export const InternalGraphQLAPI = new InternalGraphQLAPIClass(null); -Amplify.register(InternalGraphQLAPI); diff --git a/packages/api-graphql/src/internals/index.ts b/packages/api-graphql/src/internals/index.ts index 357172d36c0..eb382eac470 100644 --- a/packages/api-graphql/src/internals/index.ts +++ b/packages/api-graphql/src/internals/index.ts @@ -4,3 +4,5 @@ export { InternalGraphQLAPI, InternalGraphQLAPIClass, } from './InternalGraphQLAPI'; + +export { graphql } from './v6'; diff --git a/packages/api-graphql/src/internals/v6.ts b/packages/api-graphql/src/internals/v6.ts new file mode 100644 index 00000000000..6267d817195 --- /dev/null +++ b/packages/api-graphql/src/internals/v6.ts @@ -0,0 +1,106 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { GraphQLAPI } from '../GraphQLAPI'; +import { GraphQLOptionsV6, GraphQLResponseV6 } from '../types'; + +/** + * Invokes graphql operations against a graphql service, providing correct input and + * output types if Amplify-generated graphql from a recent version of the CLI/codegen + * are used *or* correct typing is provided via the type argument. + * + * Amplify-generated "branded" graphql queries will look similar to this: + * + * ```ts + * // + * // |-- branding + * // v + * export const getModel = `...` as GeneratedQuery< + * GetModelQueryVariables, + * GetModelQuery + * >; + * ``` + * + * If this branding is not in your generated graphql, update to a newer version of + * CLI/codegen and regenerate your graphql using `amplify codegen`. + * + * ## Using Amplify-generated graphql + * + * ```ts + * import * as queries from './graphql/queries'; + * + * // + * // |-- correctly typed graphql response containing a Widget + * // v + * const queryResult = await graphql({ + * query: queries.getWidget, + * variables: { + * id: "abc", // <-- type hinted/enforced + * }, + * }); + * + * // + * // |-- a correctly typed Widget + * // v + * const fetchedWidget = queryResult.data?.getWidget!; + * ``` + * + * ## Custom input + result types + * + * To provide input types (`variables`) and result types: + * + * ```ts + * type GetById_NameOnly = { + * variables: { + * id: string + * }, + * result: Promise<{ + * data: { getWidget: { name: string } } + * }> + * } + * + * // + * // |-- type is GetById_NameOnly["result"] + * // v + * const result = graphql({ + * query: "...", + * variables: { id: "abc" } // <-- type of GetById_NameOnly["variables"] + * }); + * ``` + * + * ## Custom result type only + * + * To specify result types only, use a type that is *not* in the `{variables, result}` shape: + * + * ```ts + * type MyResultType = Promise<{ + * data: { + * getWidget: { name: string } + * } + * }> + * + * // + * // |-- type is MyResultType + * // v + * const result = graphql({query: "..."}); + * ``` + * + * @param options + * @param additionalHeaders + */ +export function graphql< + FALLBACK_TYPES = unknown, + TYPED_GQL_STRING extends string = string +>( + options: GraphQLOptionsV6, + additionalHeaders?: { [key: string]: string } +): GraphQLResponseV6 { + /** + * The correctness of these typings depends on correct string branding or overrides. + * Neither of these can actually be validated at runtime. Hence, we don't perform + * any validation or type-guarding here. + */ + const result = GraphQLAPI.graphql(options, additionalHeaders); + return result as any; +} + +export { GraphQLOptionsV6, GraphQLResponseV6 }; diff --git a/packages/pubsub/src/types/PubSub.ts b/packages/api-graphql/src/types/PubSub.ts similarity index 100% rename from packages/pubsub/src/types/PubSub.ts rename to packages/api-graphql/src/types/PubSub.ts diff --git a/packages/api-graphql/src/types/index.ts b/packages/api-graphql/src/types/index.ts index 16e30c2b820..e9edfca39e6 100644 --- a/packages/api-graphql/src/types/index.ts +++ b/packages/api-graphql/src/types/index.ts @@ -1,20 +1,25 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Source, DocumentNode, GraphQLError, OperationTypeNode } from 'graphql'; +import { Source, DocumentNode, GraphQLError } from 'graphql'; export { OperationTypeNode } from 'graphql'; -import { GRAPHQL_AUTH_MODE } from '@aws-amplify/auth'; -export { GRAPHQL_AUTH_MODE }; -import { CustomUserAgentDetails } from '@aws-amplify/core'; +import { Observable } from 'zen-observable-ts'; +import { DocumentType } from '@aws-amplify/api-rest'; +export type GraphQLAuthMode = 'AWS_IAM' | 'COGNITO_USERPOOLS' | 'API_KEY'; + +/** + * Loose/Unknown options for raw GraphQLAPICategory `graphql()`. + */ export interface GraphQLOptions { query: string | DocumentNode; - variables?: object; - authMode?: keyof typeof GRAPHQL_AUTH_MODE; + variables?: Record; + // authMode?: GraphQLAuthMode; + authMode?: string; authToken?: string; /** * @deprecated This property should not be used */ - userAgentSuffix?: string; // TODO: remove in v6 + userAgentSuffix?: string; } export interface GraphQLResult { @@ -25,6 +30,75 @@ export interface GraphQLResult { }; } +// Opaque type used for determining the graphql query type +declare const queryType: unique symbol; + +export type GraphQLQuery = T & { readonly [queryType]: 'query' }; +export type GraphQLSubscription = T & { + readonly [queryType]: 'subscription'; +}; + +/** + * The return value from a `graphql({query})` call when `query` is a subscription. + * + * ```ts + * // |-- You are here + * // v + * const subResult: GraphqlSubscriptionResult = client.graphql({ + * query: onCreateWidget + * }); + * + * const sub = subResult.subscribe({ + * // + * // |-- You are here + * // v + * next(message: GraphqlSubscriptionMessage) { + * handle(message.value); // <-- type OnCreateWidgetSubscription + * } + * }) + * ``` + */ +export type GraphqlSubscriptionResult = Observable< + GraphqlSubscriptionMessage +>; + +/** + * The shape of messages passed to `next()` from a graphql subscription. E.g., + * + * ```ts + * const sub = client.graphql({ + * query: onCreateWidget, + * }).subscribe({ + * // + * // |-- You are here + * // v + * next(message: GraphqlSubscriptionMessage) { + * handle(message.value); // <-- type OnCreateWidgetSubscription + * } + * }) + * ``` + */ +export type GraphqlSubscriptionMessage = { + data?: T; +}; + +export interface AWSAppSyncRealTimeProviderOptions { + appSyncGraphqlEndpoint?: string; + authenticationType?: GraphQLAuthMode; + query?: string; + variables?: Record; + apiKey?: string; + region?: string; + graphql_headers?: () => {} | (() => Promise<{}>); + additionalHeaders?: { [key: string]: string }; +} + +export type AWSAppSyncRealTimeProvider = { + subscribe( + options?: AWSAppSyncRealTimeProviderOptions + ): Observable>; +}; + export enum GraphQLAuthError { NO_API_KEY = 'No api-key configured', NO_CURRENT_USER = 'No current user', @@ -38,3 +112,156 @@ export enum GraphQLAuthError { * @see: https://graphql.org/graphql-js/language/#parse */ export type GraphQLOperation = Source | string; + +/** + * API V6 `graphql({options})` type that can leverage branded graphql `query` + * objects and fallback types. + */ +export interface GraphQLOptionsV6< + FALLBACK_TYPES = unknown, + TYPED_GQL_STRING extends string = string +> { + query: TYPED_GQL_STRING | DocumentNode; + variables?: GraphQLVariablesV6; + authMode?: GraphQLAuthMode; + authToken?: string; + /** + * @deprecated This property should not be used + */ + userAgentSuffix?: string; +} + +/** + * Result type for `graphql()` operations that don't include any specific + * type information. The result could be either a `Promise` or `Subscription`. + * + * Invoking code should either cast the result or use `?` and `!` operators to + * navigate the result objects. + */ +export type UnknownGraphQLResponse = + | Promise> + | GraphqlSubscriptionResult; + +/** + * The expected type for `variables` in a V6 `graphql()` operation with + * respect to the given `FALLBACK_TYPES` and `TYPED_GQL_STRING`. + */ +export type GraphQLVariablesV6< + FALLBACK_TYPES = unknown, + TYPED_GQL_STRING extends string = string +> = TYPED_GQL_STRING extends GeneratedQuery + ? IN + : TYPED_GQL_STRING extends GeneratedMutation + ? IN + : TYPED_GQL_STRING extends GeneratedSubscription + ? IN + : FALLBACK_TYPES extends GraphQLOperationType + ? IN + : any; + +/** + * The expected return type with respect to the given `FALLBACK_TYPE` + * and `TYPED_GQL_STRING`. + */ +export type GraphQLResponseV6< + FALLBACK_TYPE = unknown, + TYPED_GQL_STRING extends string = string +> = TYPED_GQL_STRING extends GeneratedQuery + ? Promise> + : TYPED_GQL_STRING extends GeneratedMutation + ? Promise> + : TYPED_GQL_STRING extends GeneratedSubscription + ? GraphqlSubscriptionResult + : FALLBACK_TYPE extends GraphQLQuery + ? Promise> + : FALLBACK_TYPE extends GraphQLSubscription + ? GraphqlSubscriptionResult + : FALLBACK_TYPE extends GraphQLOperationType + ? CUSTOM_OUT + : UnknownGraphQLResponse; + +/** + * The shape customers can use to provide `T` to `graphql()` to specify both + * `IN` and `OUT` types (the type of `variables` and the return type, respectively). + * + * I.E., + * + * ```ts + * type MyVariablesType = { ... }; + * type MyResultType = { ... }; + * type MyOperationType = { variables: MyVariablesType, result: MyResultType }; + * + * const result: MyResultType = graphql("graphql string", { + * variables: { + * // MyVariablesType + * } + * }) + * ``` + */ +export type GraphQLOperationType = { + variables: IN; + result: OUT; +}; + +/** + * Nominal type for branding generated graphql query operation strings with + * input and output types. + * + * E.g., + * + * ```ts + * export const getWidget = `...` as GeneratedQuery< + * GetWidgetQueryVariables, + * GetWidgetQuery + * >; + * ``` + * + * This allows `graphql()` to extract `InputType` and `OutputType` to correctly + * assign types to the `variables` and result objects. + */ +export type GeneratedQuery = string & { + __generatedQueryInput: InputType; + __generatedQueryOutput: OutputType; +}; + +/** + * Nominal type for branding generated graphql mutation operation strings with + * input and output types. + * + * E.g., + * + * ```ts + * export const createWidget = `...` as GeneratedQuery< + * CreateWidgetMutationVariables, + * CreateWidgetMutation + * >; + * ``` + * + * This allows `graphql()` to extract `InputType` and `OutputType` to correctly + * assign types to the `variables` and result objects. + */ +export type GeneratedMutation = string & { + __generatedMutationInput: InputType; + __generatedMutationOutput: OutputType; +}; + +/** + * Nominal type for branding generated graphql mutation operation strings with + * input and output types. + * + * E.g., + * + * ```ts + * export const createWidget = `...` as GeneratedMutation< + * CreateWidgetMutationVariables, + * CreateWidgetMutation + * >; + * ``` + * + * This allows `graphql()` to extract `InputType` and `OutputType` to correctly + * assign types to the `variables` and result objects. + */ +export type GeneratedSubscription = string & { + __generatedSubscriptionInput: InputType; + __generatedSubscriptionOutput: OutputType; +}; diff --git a/packages/api-graphql/src/utils/ConnectionStateMonitor.ts b/packages/api-graphql/src/utils/ConnectionStateMonitor.ts new file mode 100644 index 00000000000..b506006121d --- /dev/null +++ b/packages/api-graphql/src/utils/ConnectionStateMonitor.ts @@ -0,0 +1,195 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import Observable, { ZenObservable } from 'zen-observable-ts'; +import { ConnectionState } from '../types/PubSub'; +import { ReachabilityMonitor } from './ReachabilityMonitor'; + +// Internal types for tracking different connection states +type LinkedConnectionState = 'connected' | 'disconnected'; +type LinkedHealthState = 'healthy' | 'unhealthy'; +type LinkedConnectionStates = { + networkState: LinkedConnectionState; + connectionState: LinkedConnectionState | 'connecting'; + intendedConnectionState: LinkedConnectionState; + keepAliveState: LinkedHealthState; +}; + +export const CONNECTION_CHANGE: { + [key in + | 'KEEP_ALIVE_MISSED' + | 'KEEP_ALIVE' + | 'CONNECTION_ESTABLISHED' + | 'CONNECTION_FAILED' + | 'CLOSING_CONNECTION' + | 'OPENING_CONNECTION' + | 'CLOSED' + | 'ONLINE' + | 'OFFLINE']: Partial; +} = { + KEEP_ALIVE_MISSED: { keepAliveState: 'unhealthy' }, + KEEP_ALIVE: { keepAliveState: 'healthy' }, + CONNECTION_ESTABLISHED: { connectionState: 'connected' }, + CONNECTION_FAILED: { + intendedConnectionState: 'disconnected', + connectionState: 'disconnected', + }, + CLOSING_CONNECTION: { intendedConnectionState: 'disconnected' }, + OPENING_CONNECTION: { + intendedConnectionState: 'connected', + connectionState: 'connecting', + }, + CLOSED: { connectionState: 'disconnected' }, + ONLINE: { networkState: 'connected' }, + OFFLINE: { networkState: 'disconnected' }, +}; + +export class ConnectionStateMonitor { + /** + * @private + */ + private _linkedConnectionState: LinkedConnectionStates; + private _linkedConnectionStateObservable: Observable; + private _linkedConnectionStateObserver: ZenObservable.SubscriptionObserver; + private _networkMonitoringSubscription?: ZenObservable.Subscription; + private _initialNetworkStateSubscription?: ZenObservable.Subscription; + + constructor() { + this._networkMonitoringSubscription = undefined; + this._linkedConnectionState = { + networkState: 'connected', + connectionState: 'disconnected', + intendedConnectionState: 'disconnected', + keepAliveState: 'healthy', + }; + + // Attempt to update the state with the current actual network state + this._initialNetworkStateSubscription = ReachabilityMonitor().subscribe( + ({ online }) => { + this.record( + online ? CONNECTION_CHANGE.ONLINE : CONNECTION_CHANGE.OFFLINE + ); + this._initialNetworkStateSubscription?.unsubscribe(); + } + ); + + this._linkedConnectionStateObservable = + new Observable(connectionStateObserver => { + connectionStateObserver.next(this._linkedConnectionState); + this._linkedConnectionStateObserver = connectionStateObserver; + }); + } + + /** + * Turn network state monitoring on if it isn't on already + */ + private enableNetworkMonitoring() { + // If no initial network state was discovered, stop trying + this._initialNetworkStateSubscription?.unsubscribe(); + + // Maintain the network state based on the reachability monitor + if (this._networkMonitoringSubscription === undefined) { + this._networkMonitoringSubscription = ReachabilityMonitor().subscribe( + ({ online }) => { + this.record( + online ? CONNECTION_CHANGE.ONLINE : CONNECTION_CHANGE.OFFLINE + ); + } + ); + } + } + + /** + * Turn network state monitoring off if it isn't off already + */ + private disableNetworkMonitoring() { + this._networkMonitoringSubscription?.unsubscribe(); + this._networkMonitoringSubscription = undefined; + } + + /** + * Get the observable that allows us to monitor the connection state + * + * @returns {Observable} - The observable that emits ConnectionState updates + */ + public get connectionStateObservable(): Observable { + let previous: ConnectionState; + + // The linked state aggregates state changes to any of the network, connection, + // intendedConnection and keepAliveHealth. Some states will change these independent + // states without changing the overall connection state. + + // After translating from linked states to ConnectionState, then remove any duplicates + return this._linkedConnectionStateObservable + .map(value => { + return this.connectionStatesTranslator(value); + }) + .filter(current => { + const toInclude = current !== previous; + previous = current; + return toInclude; + }); + } + + /* + * Updates local connection state and emits the full state to the observer. + */ + record(statusUpdates: Partial) { + // Maintain the network monitor + if (statusUpdates.intendedConnectionState === 'connected') { + this.enableNetworkMonitoring(); + } else if (statusUpdates.intendedConnectionState === 'disconnected') { + this.disableNetworkMonitoring(); + } + + // Maintain the socket state + const newSocketStatus = { + ...this._linkedConnectionState, + ...statusUpdates, + }; + + this._linkedConnectionState = { ...newSocketStatus }; + + this._linkedConnectionStateObserver.next(this._linkedConnectionState); + } + + /* + * Translate the ConnectionState structure into a specific ConnectionState string literal union + */ + private connectionStatesTranslator({ + connectionState, + networkState, + intendedConnectionState, + keepAliveState, + }: LinkedConnectionStates): ConnectionState { + if (connectionState === 'connected' && networkState === 'disconnected') + return ConnectionState.ConnectedPendingNetwork; + + if ( + connectionState === 'connected' && + intendedConnectionState === 'disconnected' + ) + return ConnectionState.ConnectedPendingDisconnect; + + if ( + connectionState === 'disconnected' && + intendedConnectionState === 'connected' && + networkState === 'disconnected' + ) + return ConnectionState.ConnectionDisruptedPendingNetwork; + + if ( + connectionState === 'disconnected' && + intendedConnectionState === 'connected' + ) + return ConnectionState.ConnectionDisrupted; + + if (connectionState === 'connected' && keepAliveState === 'unhealthy') + return ConnectionState.ConnectedPendingKeepAlive; + + // All remaining states directly correspond to the connection state + if (connectionState === 'connecting') return ConnectionState.Connecting; + if (connectionState === 'disconnected') return ConnectionState.Disconnected; + return ConnectionState.Connected; + } +} diff --git a/packages/api-graphql/src/utils/ReachabilityMonitor/index.native.ts b/packages/api-graphql/src/utils/ReachabilityMonitor/index.native.ts new file mode 100644 index 00000000000..540d7da67ec --- /dev/null +++ b/packages/api-graphql/src/utils/ReachabilityMonitor/index.native.ts @@ -0,0 +1,7 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Reachability } from '@aws-amplify/core/internals/utils'; +import { default as NetInfo } from '@react-native-community/netinfo'; + +export const ReachabilityMonitor = () => + new Reachability().networkMonitor(NetInfo); diff --git a/packages/api-graphql/src/utils/ReachabilityMonitor/index.ts b/packages/api-graphql/src/utils/ReachabilityMonitor/index.ts new file mode 100644 index 00000000000..1f17e311524 --- /dev/null +++ b/packages/api-graphql/src/utils/ReachabilityMonitor/index.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Reachability } from '@aws-amplify/core/internals/utils'; +export const ReachabilityMonitor = () => new Reachability().networkMonitor(); diff --git a/packages/api-graphql/src/utils/ReconnectionMonitor.ts b/packages/api-graphql/src/utils/ReconnectionMonitor.ts new file mode 100644 index 00000000000..fd89f51f8c1 --- /dev/null +++ b/packages/api-graphql/src/utils/ReconnectionMonitor.ts @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { Observer } from 'zen-observable-ts'; +import { RECONNECT_DELAY, RECONNECT_INTERVAL } from '../Providers/constants'; + +export enum ReconnectEvent { + START_RECONNECT = 'START_RECONNECT', + HALT_RECONNECT = 'HALT_RECONNECT', +} + +/** + * Captures the reconnect event logic used to determine when to reconnect to PubSub providers. + * Reconnnect attempts are delayed by 5 seconds to let the interface settle. + * Attempting to reconnect only once creates unrecoverable states when the network state isn't + * supported by the browser, so this keeps retrying every minute until halted. + */ +export class ReconnectionMonitor { + private reconnectObservers: Observer[] = []; + private reconnectIntervalId?: ReturnType; + private reconnectSetTimeoutId?: ReturnType; + + /** + * Add reconnect observer to the list of observers to alert on reconnect + */ + addObserver(reconnectObserver: Observer) { + this.reconnectObservers.push(reconnectObserver); + } + + /** + * Given a reconnect event, start the appropriate behavior + */ + record(event: ReconnectEvent) { + if (event === ReconnectEvent.START_RECONNECT) { + // If the reconnection hasn't been started + if ( + this.reconnectSetTimeoutId === undefined && + this.reconnectIntervalId === undefined + ) { + this.reconnectSetTimeoutId = setTimeout(() => { + // Reconnect now + this._triggerReconnect(); + // Retry reconnect every periodically until it works + this.reconnectIntervalId = setInterval(() => { + this._triggerReconnect(); + }, RECONNECT_INTERVAL); + }, RECONNECT_DELAY); + } + } + + if (event === ReconnectEvent.HALT_RECONNECT) { + if (this.reconnectIntervalId) { + clearInterval(this.reconnectIntervalId); + this.reconnectIntervalId = undefined; + } + if (this.reconnectSetTimeoutId) { + clearTimeout(this.reconnectSetTimeoutId); + this.reconnectSetTimeoutId = undefined; + } + } + } + + /** + * Complete all reconnect observers + */ + close() { + this.reconnectObservers.forEach(reconnectObserver => { + reconnectObserver.complete?.(); + }); + } + + private _triggerReconnect() { + this.reconnectObservers.forEach(reconnectObserver => { + reconnectObserver.next?.(); + }); + } +} diff --git a/packages/api-graphql/src/utils/errors/APIError.ts b/packages/api-graphql/src/utils/errors/APIError.ts new file mode 100644 index 00000000000..01945a2bc65 --- /dev/null +++ b/packages/api-graphql/src/utils/errors/APIError.ts @@ -0,0 +1,18 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AmplifyError, ErrorParams } from '@aws-amplify/core/internals/utils'; + +/** + * @internal + */ +export class APIError extends AmplifyError { + constructor(params: ErrorParams) { + super(params); + + // Hack for making the custom error class work when transpiled to es5 + // TODO: Delete the following 2 lines after we change the build target to >= es2015 + this.constructor = APIError; + Object.setPrototypeOf(this, APIError.prototype); + } +} diff --git a/packages/api-graphql/src/utils/errors/assertValidationError.ts b/packages/api-graphql/src/utils/errors/assertValidationError.ts new file mode 100644 index 00000000000..2a93d6e8bb2 --- /dev/null +++ b/packages/api-graphql/src/utils/errors/assertValidationError.ts @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { APIError } from './APIError'; +import { APIValidationErrorCode, validationErrorMap } from './validation'; + +/** + * @internal + */ +export function assertValidationError( + assertion: boolean, + name: APIValidationErrorCode +): asserts assertion { + const { message, recoverySuggestion } = validationErrorMap[name]; + + if (!assertion) { + throw new APIError({ name, message, recoverySuggestion }); + } +} diff --git a/packages/api-graphql/src/utils/errors/index.ts b/packages/api-graphql/src/utils/errors/index.ts new file mode 100644 index 00000000000..73dc1d1e31f --- /dev/null +++ b/packages/api-graphql/src/utils/errors/index.ts @@ -0,0 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export { APIError } from './APIError'; +export { assertValidationError } from './assertValidationError'; +export { APIValidationErrorCode, validationErrorMap } from './validation'; diff --git a/packages/api-graphql/src/utils/errors/validation.ts b/packages/api-graphql/src/utils/errors/validation.ts new file mode 100644 index 00000000000..44ed721174d --- /dev/null +++ b/packages/api-graphql/src/utils/errors/validation.ts @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { AmplifyErrorMap } from '@aws-amplify/core/internals/utils'; + +export enum APIValidationErrorCode { + NoAppId = 'NoAppId', + NoCredentials = 'NoCredentials', + NoRegion = 'NoRegion', + NoDefaultAuthMode = 'NoDefaultAuthMode', +} + +export const validationErrorMap: AmplifyErrorMap = { + [APIValidationErrorCode.NoAppId]: { + message: 'Missing application id.', + }, + [APIValidationErrorCode.NoCredentials]: { + message: 'Credentials should not be empty.', + }, + [APIValidationErrorCode.NoRegion]: { + message: 'Missing region.', + }, + [APIValidationErrorCode.NoDefaultAuthMode]: { + message: 'Missing default auth mode', + }, +}; diff --git a/packages/api-graphql/src/utils/index.ts b/packages/api-graphql/src/utils/index.ts new file mode 100644 index 00000000000..0afb6284246 --- /dev/null +++ b/packages/api-graphql/src/utils/index.ts @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export { resolveConfig } from './resolveConfig'; +export { resolveCredentials } from './resolveCredentials'; diff --git a/packages/api-graphql/src/utils/resolveConfig.ts b/packages/api-graphql/src/utils/resolveConfig.ts new file mode 100644 index 00000000000..d4f76cd929b --- /dev/null +++ b/packages/api-graphql/src/utils/resolveConfig.ts @@ -0,0 +1,20 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Amplify } from '@aws-amplify/core'; +import { APIValidationErrorCode, assertValidationError } from './errors'; + +/** + * @internal + */ +export const resolveConfig = () => { + const { region, defaultAuthMode, endpoint } = + Amplify.getConfig().API?.AppSync ?? {}; + assertValidationError(!!endpoint, APIValidationErrorCode.NoAppId); + assertValidationError(!!region, APIValidationErrorCode.NoRegion); + assertValidationError( + !!defaultAuthMode, + APIValidationErrorCode.NoDefaultAuthMode + ); + return { endpoint, region, defaultAuthMode }; +}; diff --git a/packages/api-graphql/src/utils/resolveCredentials.ts b/packages/api-graphql/src/utils/resolveCredentials.ts new file mode 100644 index 00000000000..3036c34d788 --- /dev/null +++ b/packages/api-graphql/src/utils/resolveCredentials.ts @@ -0,0 +1,14 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { fetchAuthSession } from '@aws-amplify/core'; +import { APIValidationErrorCode, assertValidationError } from './errors'; + +/** + * @internal + */ +export const resolveCredentials = async () => { + const { credentials, identityId } = await fetchAuthSession(); + assertValidationError(!!credentials, APIValidationErrorCode.NoCredentials); + return { credentials, identityId }; +}; diff --git a/packages/api-graphql/tsconfig.json b/packages/api-graphql/tsconfig.json new file mode 100644 index 00000000000..f3d5ed63841 --- /dev/null +++ b/packages/api-graphql/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "importHelpers": true, + "strict": false, + "noImplicitAny": false, + "skipLibCheck": true + }, + "include": ["./src"] +} diff --git a/packages/api-rest/__tests__/RestAPI.test.ts b/packages/api-rest/__tests__/RestAPI.test.ts deleted file mode 100644 index 6ad1963dccd..00000000000 --- a/packages/api-rest/__tests__/RestAPI.test.ts +++ /dev/null @@ -1,1048 +0,0 @@ -import axios, { CancelTokenStatic } from 'axios'; -import { RestAPIClass as API } from '../src/'; -import { RestClient } from '../src/RestClient'; -import { Signer, Credentials, DateUtils } from '@aws-amplify/core'; - -jest.mock('axios'); - -const mockAxios = jest.spyOn(axios as any, 'default'); - -axios.CancelToken = { - source: () => ({ token: null, cancel: null }), -}; - -let cancelTokenSpy = null; -let cancelMock = null; -let tokenMock = null; - -const config = { - API: { - endpoints: [ - { - name: 'apiName', - endpoint: 'endpoint', - region: 'region', - service: 'execute-api', - }, - ], - }, -}; - -afterEach(() => { - jest.restoreAllMocks(); - mockAxios.mockClear(); -}); - -describe('Rest API test', () => { - beforeEach(() => { - cancelMock = jest.fn(); - tokenMock = jest.fn(); - cancelTokenSpy = jest - .spyOn(axios.CancelToken, 'source') - .mockImplementation(() => { - return { token: tokenMock, cancel: cancelMock }; - }); - }); - - const aws_cloud_logic_custom = [ - { - id: 'lh3s27sl16', - name: 'todosCRUD', - description: '', - endpoint: - 'https://lh3s27sl16.execute-api.us-east-1.amazonaws.com/Development', - region: 'us-east-1', - paths: ['/todos', '/todos/123'], - }, - { - name: 'apiName', - description: '', - endpoint: 'endpoint', - region: 'us-east-1', - paths: ['/todos', '/todos/123'], - }, - ]; - - describe('configure test', () => { - test('without aws_project_region', () => { - const api = new API({}); - - const options = { - myoption: 'myoption', - }; - - expect(api.configure(options)).toEqual({ - endpoints: [], - myoption: 'myoption', - }); - }); - - test('with aws_project_region', () => { - const api = new API({}); - - const options = { - aws_project_region: 'region', - aws_cloud_logic_custom, - }; - - expect(api.configure(options)).toEqual({ - aws_cloud_logic_custom, - aws_project_region: 'region', - endpoints: aws_cloud_logic_custom, - header: {}, - region: 'region', - }); - }); - - test('with API options', () => { - const api = new API({}); - - const options = { - API: { - aws_project_region: 'api-region', - }, - aws_project_region: 'region', - aws_appsync_region: 'appsync-region', - aws_cloud_logic_custom, - }; - - expect(api.configure(options)).toEqual({ - aws_cloud_logic_custom, - aws_project_region: 'api-region', - aws_appsync_region: 'appsync-region', - endpoints: aws_cloud_logic_custom, - header: {}, - region: 'api-region', - }); - }); - }); - - describe('get test', () => { - test('happy case', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'get') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - await api.get('apiName', 'path', { init: 'init' }); - - expect(spyon2).toBeCalledWith( - { - custom_header: undefined, - endpoint: 'endpointpath', - region: 'region', - service: 'execute-api', - }, - { - init: 'init', - cancellableToken: { cancel: cancelMock, token: tokenMock }, - } - ); - }); - - test('custom_header', async () => { - const custom_config = { - API: { - endpoints: [ - { - name: 'apiName', - endpoint: 'https://www.amazonaws.com', - custom_header: () => { - return { Authorization: 'mytoken' }; - }, - }, - ], - }, - }; - const api = new API({}); - api.configure(custom_config); - const spyon = jest - .spyOn(Credentials, 'get') - .mockResolvedValueOnce('cred'); - - const spyonRequest = jest - .spyOn(RestClient.prototype as any, '_request') - .mockResolvedValueOnce({}); - await api.get('apiName', 'path', {}); - - expect(spyonRequest).toBeCalledWith( - { - data: null, - headers: { Authorization: 'mytoken' }, - host: 'www.amazonaws.compath', - method: 'GET', - path: '/', - responseType: 'json', - signerServiceInfo: undefined, - url: 'https://www.amazonaws.compath/', - timeout: 0, - cancelToken: tokenMock, - }, - undefined - ); - }); - - test('non-default timeout', async () => { - const resp = { data: [{ name: 'Bob' }] }; - - const options = { - aws_project_region: 'region', - aws_cloud_logic_custom, - }; - - const api = new API({}); - api.configure(options); - - const creds = { - secretAccessKey: 'secret', - accessKeyId: 'access', - sessionToken: 'token', - }; - - const creds2 = { - secret_key: 'secret', - access_key: 'access', - session_token: 'token', - }; - - const spyon = jest.spyOn(Credentials, 'get').mockResolvedValue(creds); - - const spyonSigner = jest - .spyOn(Signer, 'sign') - .mockImplementationOnce(() => { - return { headers: {} }; - }); - - mockAxios.mockResolvedValue(resp); - - const init = { - timeout: 2500, - }; - await api.get('apiName', '/items', init); - const expectedParams = { - data: null, - headers: {}, - host: undefined, - method: 'GET', - path: '/', - responseType: 'json', - url: 'endpoint/items', - timeout: 2500, - cancelToken: tokenMock, - }; - expect(spyonSigner).toBeCalledWith(expectedParams, creds2, { - region: 'us-east-1', - service: 'execute-api', - }); - }); - - test('query-string on init', async () => { - const resp = { data: [{ name: 'Bob' }] }; - - const options = { - aws_project_region: 'region', - aws_cloud_logic_custom, - }; - - const api = new API({}); - api.configure(options); - - const creds = { - secretAccessKey: 'secret', - accessKeyId: 'access', - sessionToken: 'token', - }; - - const creds2 = { - secret_key: 'secret', - access_key: 'access', - session_token: 'token', - }; - - const spyon = jest.spyOn(Credentials, 'get').mockImplementation(() => { - return new Promise((res, rej) => { - res(creds); - }); - }); - - const spyonSigner = jest - .spyOn(Signer, 'sign') - .mockImplementationOnce(() => { - return { headers: {} }; - }); - - mockAxios.mockResolvedValue(resp); - - const init = { - queryStringParameters: { - 'ke:y3': 'val:ue 3', - }, - }; - await api.get('apiName', '/items', init); - const expectedParams = { - data: null, - headers: {}, - host: undefined, - method: 'GET', - path: '/', - responseType: 'json', - url: 'endpoint/items?ke%3Ay3=val%3Aue%203', - timeout: 0, - cancelToken: tokenMock, - }; - expect(spyonSigner).toBeCalledWith(expectedParams, creds2, { - region: 'us-east-1', - service: 'execute-api', - }); - }); - - test('query-string on init-custom-auth', async () => { - const resp = { data: [{ name: 'Bob' }] }; - - const options = { - aws_project_region: 'region', - aws_cloud_logic_custom, - }; - - const api = new API({}); - api.configure(options); - - const creds = { - secretAccessKey: 'secret', - accessKeyId: 'access', - sessionToken: 'token', - }; - - const creds2 = { - secret_key: 'secret', - access_key: 'access', - session_token: 'token', - }; - - const spyon = jest.spyOn(Credentials, 'get').mockImplementation(() => { - return new Promise((res, rej) => { - res(creds); - }); - }); - - const spyonRequest = jest - .spyOn(RestClient.prototype as any, '_request') - .mockImplementationOnce(() => { - return { headers: {} }; - }); - - mockAxios.mockResolvedValue(resp); - - const init = { - queryStringParameters: { - 'ke:y3': 'val:ue 3', - }, - headers: { - Authorization: 'apikey', - }, - }; - await api.get('apiName', '/items', init); - const expectedParams = { - data: null, - headers: { Authorization: 'apikey' }, - host: undefined, - method: 'GET', - path: '/', - responseType: 'json', - signerServiceInfo: undefined, - url: 'endpoint/items?ke%3Ay3=val%3Aue%203', - timeout: 0, - cancelToken: tokenMock, - }; - expect(spyonRequest).toBeCalledWith(expectedParams, undefined); - }); - test('query-string on init and url', async () => { - const resp = { data: [{ name: 'Bob' }] }; - - const options = { - aws_project_region: 'region', - aws_cloud_logic_custom, - }; - - const api = new API({}); - api.configure(options); - - const creds = { - secretAccessKey: 'secret', - accessKeyId: 'access', - sessionToken: 'token', - }; - - const creds2 = { - secret_key: 'secret', - access_key: 'access', - session_token: 'token', - }; - - const spyon = jest.spyOn(Credentials, 'get').mockImplementation(() => { - return new Promise((res, rej) => { - res(creds); - }); - }); - - const spyonSigner = jest - .spyOn(Signer, 'sign') - .mockImplementationOnce(() => { - return { headers: {} }; - }); - - mockAxios.mockResolvedValue(resp); - - const init = { - queryStringParameters: { - key2: 'value2_real', - }, - }; - await api.get('apiName', '/items?key1=value1&key2=value', init); - const expectedParams = { - data: null, - headers: {}, - host: undefined, - method: 'GET', - path: '/', - responseType: 'json', - url: 'endpoint/items?key1=value1&key2=value2_real', - timeout: 0, - cancelToken: tokenMock, - }; - expect(spyonSigner).toBeCalledWith(expectedParams, creds2, { - region: 'us-east-1', - service: 'execute-api', - }); - }); - - test('endpoint length 0', async () => { - const api = new API({}); - api.configure({}); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'get') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - expect.assertions(1); - try { - await api.get('apiNameDoesntExist', 'path', { init: 'init' }); - } catch (e) { - expect(e).toBe('API apiNameDoesntExist does not exist'); - } - }); - - test('cred not ready', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - rej('err no current credentials'); - }); - }); - - const spyon4 = jest - .spyOn(RestClient.prototype as any, '_request') - .mockImplementationOnce(() => { - return 'endpoint'; - }); - - expect.assertions(1); - await api.get('apiName', 'path', { init: 'init' }); - expect(spyon4).toBeCalled(); - }); - - test('clock skew', async () => { - const api = new API({}); - api.configure({ - endpoints: [ - { - name: 'url', - endpoint: 'https://domain.fakeurl/', - }, - ], - }); - - const normalError = new Error('Response Error'); - - // Server is always "correct" - const serverDate = new Date(); - const requestDate = new Date(); - - // Local machine is behind by 1 hour - // It's important to change the _server_ time in this test, - // because the local time "looks correct" to the user & DateUtils - // compares the server response to local time. - serverDate.setHours(serverDate.getHours() + 1); - - const clockSkewError: any = new Error('BadRequestException'); - const init = { - headers: { - 'x-amz-date': DateUtils.getHeaderStringFromDate(requestDate), - }, - }; - - clockSkewError.response = { - headers: { - 'x-amzn-errortype': 'BadRequestException', - date: serverDate.toString(), - }, - }; - - // Clock should not be skewed yet - expect(DateUtils.getClockOffset()).toBe(0); - // Ensure the errors are the correct type for gating - expect(DateUtils.isClockSkewError(normalError)).toBe(false); - expect(DateUtils.isClockSkewError(clockSkewError)).toBe(true); - expect(DateUtils.isClockSkewed(serverDate)).toBe(true); - - jest - .spyOn(RestClient.prototype as any, 'endpoint') - .mockImplementation(() => 'endpoint'); - - jest.spyOn(Credentials, 'get').mockResolvedValue('creds'); - jest.spyOn(RestClient.prototype as any, '_sign').mockReturnValue({ - ...init, - headers: { ...init.headers, Authorization: 'signed' }, - }); - mockAxios.mockImplementationOnce(() => { - return new Promise((_, rej) => { - rej(normalError); - }); - }); - - await expect(api.post('url', 'path', init)).rejects.toThrow(normalError); - - // Clock should not be skewed from normal errors - expect(DateUtils.getClockOffset()).toBe(0); - - // mock clock skew error response and successful response after retry - mockAxios - .mockImplementationOnce(() => { - return new Promise((_, rej) => { - rej(clockSkewError); - }); - }) - .mockResolvedValue({ - data: [{ name: 'Bob' }], - }); - - await expect(api.post('url', 'path', init)).resolves.toEqual([ - { name: 'Bob' }, - ]); - - // With a clock skew error, the clock will get offset with the difference - expect(DateUtils.getClockOffset()).toBe( - serverDate.getTime() - requestDate.getTime() - ); - }); - - test('cancel request', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'get') - .mockImplementationOnce(() => { - return Promise.reject('error cancelled'); - }); - const spyon4 = jest - .spyOn(RestClient.prototype, 'isCancel') - .mockImplementationOnce(() => { - return true; - }); - - const promiseResponse = api.get('apiName', 'path', { init: 'init' }); - api.cancel(promiseResponse, 'testmessage'); - - expect.assertions(5); - - expect(spyon2).toBeCalledWith( - { - custom_header: undefined, - endpoint: 'endpointpath', - region: 'region', - service: 'execute-api', - }, - { - init: 'init', - cancellableToken: { cancel: cancelMock, token: tokenMock }, - } - ); - expect(cancelTokenSpy).toBeCalledTimes(1); - expect(cancelMock).toBeCalledWith('testmessage'); - try { - await promiseResponse; - } catch (err) { - expect(err).toEqual('error cancelled'); - expect(api.isCancel(err)).toBeTruthy(); - } - }); - }); - - describe('post test', () => { - test('happy case', async () => { - const api = new API({ - region: 'region-2', - }); - const options = { - aws_project_region: 'region', - aws_cloud_logic_custom, - }; - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - api.configure(options); - await api.post('apiName', 'path', { init: 'init' }); - - expect(spyon2).toBeCalledWith( - { - custom_header: undefined, - endpoint: 'endpointpath', - region: 'us-east-1', - service: 'execute-api', - }, - { - init: 'init', - cancellableToken: { cancel: cancelMock, token: tokenMock }, - } - ); - }); - - test('endpoint length 0', async () => { - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'post') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - const api = new API({}); - api.configure(config); - - expect.assertions(1); - try { - await api.post('apiNameDoesNotExists', 'path', { init: 'init' }); - } catch (e) { - expect(e).toBe('API apiNameDoesNotExists does not exist'); - } - }); - - test('cred not ready', async () => { - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - rej('err'); - }); - }); - - const api = new API({}); - api.configure(config); - - const spyon4 = jest - .spyOn(RestClient.prototype as any, '_request') - .mockImplementationOnce(() => { - return 'endpoint'; - }); - - expect.assertions(1); - await api.post('apiName', 'path', { init: 'init' }); - expect(spyon4).toBeCalled(); - }); - }); - - describe('put test', () => { - test('happy case', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'put') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - await api.put('apiName', 'path', { init: 'init' }); - - expect(spyon2).toBeCalledWith( - { - custom_header: undefined, - endpoint: 'endpointpath', - region: 'region', - service: 'execute-api', - }, - { - init: 'init', - cancellableToken: { cancel: cancelMock, token: tokenMock }, - } - ); - }); - - test('endpoint length 0', async () => { - const api = new API({}); - api.configure({ - endpoints: [], - }); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'put') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - expect.assertions(1); - try { - await api.put('apiName', 'path', { init: 'init' }); - } catch (e) { - expect(e).toBe('API apiName does not exist'); - } - }); - - test('cred not ready', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - rej('err'); - }); - }); - - const spyon4 = jest - .spyOn(RestClient.prototype as any, '_request') - .mockImplementationOnce(() => { - return 'endpoint'; - }); - - expect.assertions(1); - await api.put('apiName', 'path', { init: 'init' }); - expect(spyon4).toBeCalled(); - }); - }); - - describe('patch test', () => { - test('happy case', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'patch') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - await api.patch('apiName', 'path', { init: 'init' }); - - expect(spyon2).toBeCalledWith( - { - custom_header: undefined, - endpoint: 'endpointpath', - region: 'region', - service: 'execute-api', - }, - { - init: 'init', - cancellableToken: { cancel: cancelMock, token: tokenMock }, - } - ); - }); - - test('endpoint length 0', async () => { - const api = new API({}); - api.configure({ - endpoints: [], - }); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'patch') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - expect.assertions(1); - try { - await api.patch('apiName', 'path', { init: 'init' }); - } catch (e) { - expect(e).toBe('API apiName does not exist'); - } - }); - - test('cred not ready', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - rej('err'); - }); - }); - - const spyon4 = jest - .spyOn(RestClient.prototype as any, '_request') - .mockImplementationOnce(() => { - return 'endpoint'; - }); - - expect.assertions(1); - await api.patch('apiName', 'path', { init: 'init' }); - expect(spyon4).toBeCalled(); - }); - }); - - describe('del test', () => { - test('happy case', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'del') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - await api.del('apiName', 'path', { init: 'init' }); - - expect(spyon2).toBeCalledWith( - { - custom_header: undefined, - endpoint: 'endpointpath', - region: 'region', - service: 'execute-api', - }, - { - init: 'init', - cancellableToken: { cancel: cancelMock, token: tokenMock }, - } - ); - }); - - test('endpoint length 0', async () => { - const api = new API({}); - api.configure({}); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'del') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - expect.assertions(1); - try { - await api.del('apiName', 'path', { init: 'init' }); - } catch (e) { - expect(e).toBe('API apiName does not exist'); - } - }); - - test('cred not ready', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - rej('err'); - }); - }); - - const spyon4 = jest - .spyOn(RestClient.prototype as any, '_request') - .mockImplementationOnce(() => { - return 'endpoint'; - }); - - expect.assertions(1); - await api.del('apiName', 'path', { init: 'init' }); - expect(spyon4).toBeCalled(); - }); - }); - - describe('head test', () => { - test('happy case', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'head') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - await api.head('apiName', 'path', { init: 'init' }); - - expect(spyon2).toBeCalledWith( - { - custom_header: undefined, - endpoint: 'endpointpath', - region: 'region', - service: 'execute-api', - }, - { - init: 'init', - cancellableToken: { cancel: cancelMock, token: tokenMock }, - } - ); - }); - - test('endpoint length 0', async () => { - const api = new API({}); - api.configure({}); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - res('cred'); - }); - }); - const spyon2 = jest - .spyOn(RestClient.prototype, 'head') - .mockImplementationOnce(() => { - return Promise.resolve(); - }); - - expect.assertions(1); - try { - await api.head('apiName', 'path', { init: 'init' }); - } catch (e) { - expect(e).toBe('API apiName does not exist'); - } - }); - - test('cred not ready', async () => { - const api = new API({}); - api.configure(config); - - const spyon = jest - .spyOn(Credentials, 'get') - .mockImplementationOnce(() => { - return new Promise((res, rej) => { - rej('err'); - }); - }); - - const spyon4 = jest - .spyOn(RestClient.prototype as any, '_request') - .mockImplementationOnce(() => { - return 'endpoint'; - }); - - expect.assertions(1); - await api.head('apiName', 'path', { init: 'init' }); - expect(spyon4).toBeCalled(); - }); - }); - - describe('endpoint test', () => { - test('happy case', async () => { - const api = new API({}); - api.configure(config); - - const endpoint = await api.endpoint('apiName'); - - expect(endpoint).toBe('endpoint'); - }); - }); -}); diff --git a/packages/api-rest/__tests__/RestClient.ssr.test.ts b/packages/api-rest/__tests__/RestClient.ssr.test.ts deleted file mode 100644 index 668b5cc7f46..00000000000 --- a/packages/api-rest/__tests__/RestClient.ssr.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @jest-environment node - */ - -import { Signer } from '@aws-amplify/core'; - -jest - .spyOn(Signer, 'sign') - .mockImplementation( - (request: any, access_info: any, service_info?: any) => request - ); - -jest.mock('axios', () => { - return { - default: signed_params => { - return new Promise((res, rej) => { - const withCredentialsSuffix = - signed_params && signed_params.withCredentials - ? '-withCredentials' - : ''; - if ( - signed_params && - signed_params.headers && - signed_params.headers.reject - ) { - rej({ - data: 'error' + withCredentialsSuffix, - }); - } else if (signed_params && signed_params.responseType === 'blob') { - res({ - data: 'blob' + withCredentialsSuffix, - }); - } else { - res({ - data: 'data' + withCredentialsSuffix, - }); - } - }); - }, - }; -}); - -import { RestClient } from '../src/RestClient'; - -describe('RestClient test', () => { - test('should not perform FormData check in Node', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect( - await restClient.ajax('url', 'method', { - body: 'data', - }) - ).toEqual('data'); - }); -}); diff --git a/packages/api-rest/__tests__/RestClient.test.ts b/packages/api-rest/__tests__/RestClient.test.ts deleted file mode 100644 index aa4941ea9ce..00000000000 --- a/packages/api-rest/__tests__/RestClient.test.ts +++ /dev/null @@ -1,519 +0,0 @@ -import { Signer } from '@aws-amplify/core'; - -jest - .spyOn(Signer, 'sign') - .mockImplementation( - (request: any, access_info: any, service_info?: any) => request - ); - -jest.mock('axios', () => { - return { - default: signed_params => { - return new Promise((res, rej) => { - const withCredentialsSuffix = - signed_params && signed_params.withCredentials - ? '-withCredentials' - : ''; - if ( - signed_params && - signed_params.headers && - signed_params.headers.reject - ) { - rej({ - data: 'error' + withCredentialsSuffix, - }); - } else if (signed_params && signed_params.responseType === 'blob') { - res({ - data: 'blob' + withCredentialsSuffix, - }); - } else if (signed_params && signed_params.data instanceof FormData) { - res({ - data: signed_params.data.get('key') + withCredentialsSuffix, - }); - } else { - res({ - data: 'data' + withCredentialsSuffix, - }); - } - }); - }, - }; -}); - -import { RestClient } from '../src/RestClient'; -import axios, { CancelTokenStatic } from 'axios'; - -axios.CancelToken = { - source: () => ({ token: null, cancel: null }), -}; -axios.isCancel = (value: any): boolean => { - return false; -}; - -let isCancelSpy = null; -let cancelTokenSpy = null; -let cancelMock = null; -let tokenMock = null; - -describe('RestClient test', () => { - beforeEach(() => { - cancelMock = jest.fn(); - tokenMock = jest.fn(); - isCancelSpy = jest.spyOn(axios, 'isCancel').mockReturnValue(true); - cancelTokenSpy = jest - .spyOn(axios.CancelToken, 'source') - .mockImplementation(() => { - return { token: tokenMock, cancel: cancelMock }; - }); - }); - - describe('ajax', () => { - test('fetch with signed request', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect(await restClient.ajax('url', 'method', {})).toEqual('data'); - }); - - test('fetch with signed failing request', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect.assertions(1); - - try { - await restClient.ajax('url', 'method', { headers: { reject: 'true' } }); - } catch (error) { - expect(error).toEqual({ data: 'error' }); - } - }); - - test('fetch with signed request', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect(await restClient.ajax('url', 'method', {})).toEqual('data'); - }); - - test('ajax with no credentials', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - }; - - const restClient = new RestClient(apiOptions); - - try { - await restClient.ajax('url', 'method', {}); - } catch (e) { - expect(e).toBe('credentials not set for API rest client '); - } - }); - - test('ajax with extraParams', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect(await restClient.ajax('url', 'method', { body: 'body' })).toEqual( - 'data' - ); - }); - - test('ajax with formData', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - const formData = new FormData(); - formData.append('key', 'contents'); - - expect( - await restClient.ajax('url', 'method', { body: formData }) - ).toEqual('contents'); - }); - - test('ajax with custom responseType', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect( - await restClient.ajax('url', 'method', { - body: 'body', - responseType: 'blob', - }) - ).toEqual('blob'); - }); - - test('ajax with Authorization header', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect( - await restClient.ajax('url', 'method', { - headers: { Authorization: 'authorization' }, - }) - ).toEqual('data'); - }); - - test('ajax with withCredentials set to true', async () => { - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect( - await restClient.ajax('url', 'method', { withCredentials: true }) - ).toEqual('data-withCredentials'); - }); - }); - - describe('get test', () => { - test('happy case', async () => { - const spyon = jest.spyOn(RestClient.prototype, 'ajax'); - - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect.assertions(5); - await restClient.get('url', {}); - - expect(spyon.mock.calls[0][0]).toBe('url'); - expect(spyon.mock.calls[0][1]).toBe('GET'); - - await restClient.get('url', { withCredentials: true }); - - expect(spyon.mock.calls[1][0]).toBe('url'); - expect(spyon.mock.calls[1][1]).toBe('GET'); - expect(spyon.mock.calls[1][2]).toEqual({ withCredentials: true }); - - spyon.mockClear(); - }); - }); - - describe('put test', () => { - test('happy case', async () => { - const spyon = jest.spyOn(RestClient.prototype, 'ajax'); - - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect.assertions(3); - await restClient.put('url', 'data'); - - expect(spyon.mock.calls[0][0]).toBe('url'); - expect(spyon.mock.calls[0][1]).toBe('PUT'); - expect(spyon.mock.calls[0][2]).toBe('data'); - spyon.mockClear(); - }); - }); - - describe('patch test', () => { - test('happy case', async () => { - const spyon = jest.spyOn(RestClient.prototype, 'ajax'); - - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect.assertions(3); - await restClient.patch('url', 'data'); - - expect(spyon.mock.calls[0][0]).toBe('url'); - expect(spyon.mock.calls[0][1]).toBe('PATCH'); - expect(spyon.mock.calls[0][2]).toBe('data'); - spyon.mockClear(); - }); - }); - - describe('post test', () => { - test('happy case', async () => { - const spyon = jest.spyOn(RestClient.prototype, 'ajax'); - - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect.assertions(3); - await restClient.post('url', 'data'); - - expect(spyon.mock.calls[0][0]).toBe('url'); - expect(spyon.mock.calls[0][1]).toBe('POST'); - expect(spyon.mock.calls[0][2]).toBe('data'); - spyon.mockClear(); - }); - }); - - describe('del test', () => { - test('happy case', async () => { - const spyon = jest.spyOn(RestClient.prototype, 'ajax'); - - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect.assertions(2); - await restClient.del('url', {}); - - expect(spyon.mock.calls[0][0]).toBe('url'); - expect(spyon.mock.calls[0][1]).toBe('DELETE'); - spyon.mockClear(); - }); - }); - - describe('head test', () => { - test('happy case', async () => { - const spyon = jest.spyOn(RestClient.prototype, 'ajax'); - - const apiOptions = { - headers: {}, - endpoints: {}, - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect.assertions(2); - await restClient.head('url', {}); - - expect(spyon.mock.calls[0][0]).toBe('url'); - expect(spyon.mock.calls[0][1]).toBe('HEAD'); - spyon.mockClear(); - }); - }); - - describe('endpoint test', () => { - test('happy case', () => { - const apiOptions = { - headers: {}, - endpoints: [ - { - name: 'myApi', - endpoint: 'endpoint of myApi', - }, - { - name: 'otherApi', - endpoint: 'endpoint of otherApi', - }, - ], - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - region: 'myregion', - }; - - const restClient = new RestClient(apiOptions); - - expect(restClient.endpoint('myApi')).toBe('endpoint of myApi'); - }); - - test('custom endpoint', () => { - const apiOptions = { - headers: {}, - endpoints: [ - { - name: 'myApi', - endpoint: 'endpoint of myApi', - }, - { - name: 'otherApi', - endpoint: 'endpoint of otherApi', - region: 'myregion', - service: 'myservice', - }, - ], - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - }; - - const restClient = new RestClient(apiOptions); - - expect(restClient.endpoint('otherApi')).toBe('endpoint of otherApi'); - }); - }); - - describe('Cancel Token', () => { - afterEach(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - const apiOptions = { - headers: {}, - endpoints: [ - { - name: 'myApi', - endpoint: 'endpoint of myApi', - }, - { - name: 'otherApi', - endpoint: 'endpoint of otherApi', - }, - ], - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - sessionToken: 'sessionToken', - }, - region: 'myregion', - }; - - test('request non existent', () => { - const restClient = new RestClient(apiOptions); - expect(restClient.cancel(new Promise((req, res) => {}))).toBe(false); - }); - - test('request exist', () => { - const restClient = new RestClient(apiOptions); - const request = Promise.resolve(); - restClient.updateRequestToBeCancellable( - request, - restClient.getCancellableToken() - ); - expect(restClient.cancel(request)).toBe(true); - }); - - test('happy case', () => { - const restClient = new RestClient(apiOptions); - jest - .spyOn(RestClient.prototype, 'ajax') - .mockImplementationOnce(() => Promise.resolve()); - - const cancellableToken = restClient.getCancellableToken(); - const request = restClient.ajax('url', 'method', { cancellableToken }); - restClient.updateRequestToBeCancellable(request, cancellableToken); - - // cancel the request - const cancelSuccess = restClient.cancel(request, 'message'); - - expect(cancelSuccess).toBeTruthy(); - expect(cancelTokenSpy).toBeCalledTimes(1); - expect(cancelMock).toBeCalledWith('message'); - }); - - test('iscancel called', () => { - const restClient = new RestClient(apiOptions); - restClient.isCancel({}); - expect(isCancelSpy).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/packages/api-rest/__tests__/httpPost.test.ts b/packages/api-rest/__tests__/httpPost.test.ts new file mode 100644 index 00000000000..14a66eea96b --- /dev/null +++ b/packages/api-rest/__tests__/httpPost.test.ts @@ -0,0 +1,3 @@ +describe.skip('API tests', () => { + test('add tests', async () => {}); +}); diff --git a/packages/api-rest/package.json b/packages/api-rest/package.json index 837c29d556c..df89df6c1a9 100644 --- a/packages/api-rest/package.json +++ b/packages/api-rest/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/api-rest", - "private": true, + "private": false, "version": "4.0.0", "description": "Api-rest category of aws-amplify", "main": "./lib/index.js", @@ -17,19 +17,19 @@ "access": "public" }, "scripts": { - "test": "npm run lint && jest --coverage", - "test:size": "size-limit", - "build-with-test": "npm test && npm run build", - "build:cjs": "node ./build es5 && webpack && webpack --config ./webpack.config.dev.js", - "build:esm": "node ./build es6", - "build:cjs:watch": "node ./build es5 --watch", - "build:esm:watch": "node ./build es6 --watch", + "test": "npm run lint && jest -w 1 --coverage", + "test:watch": "tslint 'src/**/*.ts' && jest -w 1 --watch", + "build-with-test": "npm run clean && npm test && tsc && webpack", + "build:cjs": "rimraf lib && tsc -m commonjs --outDir lib && webpack && webpack --config ./webpack.config.dev.js", + "build:esm": "rimraf lib-esm && tsc -m esnext --outDir lib-esm", + "build:cjs:watch": "rimraf lib && tsc -m commonjs --outDir lib --watch", + "build:esm:watch": "rimraf lib-esm && tsc -m esnext --outDir lib-esm --watch", "build": "npm run clean && npm run build:esm && npm run build:cjs", "clean": "npm run clean:size && rimraf lib-esm lib dist", "clean:size": "rimraf dual-publish-tmp tmp*", "format": "echo \"Not implemented\"", "lint": "tslint 'src/**/*.ts' && npm run ts-coverage", - "ts-coverage": "typescript-coverage-report -p ./tsconfig.build.json -t 65.41" + "ts-coverage": "typescript-coverage-report -p ./tsconfig.json -t 70.0" }, "repository": { "type": "git", @@ -47,7 +47,7 @@ "src" ], "dependencies": { - "axios": "0.26.0", + "axios": "1.5.0", "tslib": "^2.5.0", "url": "0.11.0" }, @@ -55,7 +55,8 @@ "@aws-amplify/core": "^6.0.0" }, "devDependencies": { - "@aws-amplify/core": "6.0.0" + "@aws-amplify/core": "6.0.0", + "typescript": "5.0.2" }, "size-limit": [ { @@ -70,20 +71,17 @@ "ts-jest": { "diagnostics": false, "tsConfig": { - "lib": [ - "es5", - "es2015", - "dom", - "esnext.asynciterable", - "es2017.object" - ], - "allowJs": true + "allowJs": true, + "noEmitOnError": false } } }, "transform": { "^.+\\.(js|jsx|ts|tsx)$": "ts-jest" }, + "testPathIgnorePatterns": [ + "/testUtils/" + ], "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$", "moduleFileExtensions": [ "ts", @@ -103,10 +101,13 @@ } }, "coveragePathIgnorePatterns": [ - "/node_modules/", + "node_modules", "dist", "lib", "lib-esm" + ], + "setupFiles": [ + "/setupTests.ts" ] } } diff --git a/packages/api-rest/setupTests.ts b/packages/api-rest/setupTests.ts new file mode 100644 index 00000000000..980bec1759b --- /dev/null +++ b/packages/api-rest/setupTests.ts @@ -0,0 +1,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +const anyGlobal = global as any; + +anyGlobal.navigator = anyGlobal.navigator || {}; + +// @ts-ignore +anyGlobal.navigator.sendBeacon = anyGlobal.navigator.sendBeacon || jest.fn(); diff --git a/packages/api-rest/src/API.ts b/packages/api-rest/src/API.ts new file mode 100644 index 00000000000..c3d014959d9 --- /dev/null +++ b/packages/api-rest/src/API.ts @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { RestClient } from './RestClient'; +import { PostOptions } from './types'; + +const restClient = new RestClient({ headers: {}, endpoints: [] }); +export function post(url: string, options: PostOptions) { + return restClient.post(url, options); +} + +export function cancel(request: Promise, message?: string) { + return restClient.cancel(request, message); +} + +export function isCancel(error: Error) { + return restClient.isCancel(error); +} diff --git a/packages/api-rest/src/RestAPI.ts b/packages/api-rest/src/RestAPI.ts deleted file mode 100644 index b424bfd9743..00000000000 --- a/packages/api-rest/src/RestAPI.ts +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { RestClient } from './RestClient'; -import { - Amplify, - ConsoleLogger as Logger, - Credentials, -} from '@aws-amplify/core'; -import { ApiInfo } from './types'; - -const logger = new Logger('RestAPI'); - -/** - * Export Cloud Logic APIs - */ -export class RestAPIClass { - /** - * @private - */ - private _options; - private _api: RestClient = null; - - Credentials = Credentials; - - /** - * Initialize Rest API with AWS configuration - * @param {Object} options - Configuration object for API - */ - constructor(options) { - this._options = options; - logger.debug('API Options', this._options); - } - - public getModuleName() { - return 'RestAPI'; - } - - /** - * Configure API part with aws configurations - * @param {Object} config - Configuration of the API - * @return {Object} - The current configuration - */ - configure(options) { - const { API = {}, ...otherOptions } = options || {}; - let opt = { ...otherOptions, ...API }; - logger.debug('configure Rest API', { opt }); - - if (opt['aws_project_region']) { - if (opt['aws_cloud_logic_custom']) { - const custom = opt['aws_cloud_logic_custom']; - opt.endpoints = - typeof custom === 'string' ? JSON.parse(custom) : custom; - } - - opt = Object.assign({}, opt, { - region: opt['aws_project_region'], - header: {}, - }); - } - - if (Array.isArray(opt.endpoints)) { - // Check if endpoints has custom_headers and validate if is a function - opt.endpoints.forEach(endpoint => { - if ( - typeof endpoint.custom_header !== 'undefined' && - typeof endpoint.custom_header !== 'function' - ) { - logger.warn( - 'Rest API ' + endpoint.name + ', custom_header should be a function' - ); - endpoint.custom_header = undefined; - } - }); - } else if (this._options && Array.isArray(this._options.endpoints)) { - opt.endpoints = this._options.endpoints; - } else { - opt.endpoints = []; - } - - this._options = Object.assign({}, this._options, opt); - - this.createInstance(); - - return this._options; - } - - /** - * Create an instance of API for the library - * @return - A promise of true if Success - */ - createInstance() { - logger.debug('create Rest API instance'); - this._api = new RestClient(this._options); - - // Share Amplify instance with client for SSR - this._api.Credentials = this.Credentials; - return true; - } - - /** - * Make a GET request - * @param {string} apiName - The api name of the request - * @param {string} path - The path of the request - * @param {json} [init] - Request extra params - * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. - */ - get(apiName, path, init): Promise { - try { - const apiInfo = this.getEndpointInfo(apiName, path); - - const cancellableToken = this._api.getCancellableToken(); - - const initParams = Object.assign({}, init); - initParams.cancellableToken = cancellableToken; - - const responsePromise = this._api.get(apiInfo, initParams); - - this._api.updateRequestToBeCancellable(responsePromise, cancellableToken); - - return responsePromise; - } catch (err) { - return Promise.reject(err.message); - } - } - - /** - * Make a POST request - * @param {string} apiName - The api name of the request - * @param {string} path - The path of the request - * @param {json} [init] - Request extra params - * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. - */ - post(apiName, path, init): Promise { - try { - const apiInfo = this.getEndpointInfo(apiName, path); - - const cancellableToken = this._api.getCancellableToken(); - - const initParams = Object.assign({}, init); - initParams.cancellableToken = cancellableToken; - - const responsePromise = this._api.post(apiInfo, initParams); - - this._api.updateRequestToBeCancellable(responsePromise, cancellableToken); - - return responsePromise; - } catch (err) { - return Promise.reject(err.message); - } - } - - /** - * Make a PUT request - * @param {string} apiName - The api name of the request - * @param {string} path - The path of the request - * @param {json} [init] - Request extra params - * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. - */ - put(apiName, path, init): Promise { - try { - const apiInfo = this.getEndpointInfo(apiName, path); - - const cancellableToken = this._api.getCancellableToken(); - - const initParams = Object.assign({}, init); - initParams.cancellableToken = cancellableToken; - - const responsePromise = this._api.put(apiInfo, initParams); - - this._api.updateRequestToBeCancellable(responsePromise, cancellableToken); - - return responsePromise; - } catch (err) { - return Promise.reject(err.message); - } - } - - /** - * Make a PATCH request - * @param {string} apiName - The api name of the request - * @param {string} path - The path of the request - * @param {json} [init] - Request extra params - * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. - */ - patch(apiName, path, init): Promise { - try { - const apiInfo = this.getEndpointInfo(apiName, path); - - const cancellableToken = this._api.getCancellableToken(); - - const initParams = Object.assign({}, init); - initParams.cancellableToken = cancellableToken; - - const responsePromise = this._api.patch(apiInfo, initParams); - - this._api.updateRequestToBeCancellable(responsePromise, cancellableToken); - - return responsePromise; - } catch (err) { - return Promise.reject(err.message); - } - } - - /** - * Make a DEL request - * @param {string} apiName - The api name of the request - * @param {string} path - The path of the request - * @param {json} [init] - Request extra params - * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. - */ - del(apiName, path, init): Promise { - try { - const apiInfo = this.getEndpointInfo(apiName, path); - - const cancellableToken = this._api.getCancellableToken(); - - const initParams = Object.assign({}, init); - initParams.cancellableToken = cancellableToken; - - const responsePromise = this._api.del(apiInfo, initParams); - - this._api.updateRequestToBeCancellable(responsePromise, cancellableToken); - - return responsePromise; - } catch (err) { - return Promise.reject(err.message); - } - } - - /** - * Make a HEAD request - * @param {string} apiName - The api name of the request - * @param {string} path - The path of the request - * @param {json} [init] - Request extra params - * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. - */ - head(apiName, path, init): Promise { - try { - const apiInfo = this.getEndpointInfo(apiName, path); - - const cancellableToken = this._api.getCancellableToken(); - - const initParams = Object.assign({}, init); - initParams.cancellableToken = cancellableToken; - - const responsePromise = this._api.head(apiInfo, initParams); - - this._api.updateRequestToBeCancellable(responsePromise, cancellableToken); - - return responsePromise; - } catch (err) { - return Promise.reject(err.message); - } - } - - /** - * Checks to see if an error thrown is from an api request cancellation - * @param {any} error - Any error - * @return {boolean} - A boolean indicating if the error was from an api request cancellation - */ - isCancel(error) { - return this._api.isCancel(error); - } - - /** - * Cancels an inflight request - * @param {any} request - request to cancel - * @return {boolean} - A boolean indicating if the request was cancelled - */ - cancel(request: Promise, message?: string) { - return this._api.cancel(request, message); - } - - /** - * Check if the request has a corresponding cancel token in the WeakMap. - * @params request - The request promise - * @return if the request has a corresponding cancel token. - */ - hasCancelToken(request: Promise) { - return this._api.hasCancelToken(request); - } - - /** - * Getting endpoint for API - * @param {string} apiName - The name of the api - * @return {string} - The endpoint of the api - */ - async endpoint(apiName) { - return this._api.endpoint(apiName); - } - - /** - * Getting endpoint info for API - * @param {string} apiName - The name of the api - * @param {string} path - The path of the api that is going to accessed - * @return {ApiInfo} - The endpoint information for that api-name - */ - private getEndpointInfo(apiName: string, path: string): ApiInfo { - const cloud_logic_array = this._options.endpoints; - - if (!Array.isArray(cloud_logic_array)) { - throw new Error(`API category not configured`); - } - - const apiConfig = cloud_logic_array.find(api => api.name === apiName); - - if (!apiConfig) { - throw new Error(`API ${apiName} does not exist`); - } - - const response: ApiInfo = { - endpoint: apiConfig.endpoint + path, - }; - - if (typeof apiConfig.region === 'string') { - response.region = apiConfig.region; - } else if (typeof this._options.region === 'string') { - response.region = this._options.region; - } - - if (typeof apiConfig.service === 'string') { - response.service = apiConfig.service || 'execute-api'; - } else { - response.service = 'execute-api'; - } - - if (typeof apiConfig.custom_header === 'function') { - response.custom_header = apiConfig.custom_header; - } else { - response.custom_header = undefined; - } - - return response; - } -} - -export const RestAPI = new RestAPIClass(null); -Amplify.register(RestAPI); diff --git a/packages/api-rest/src/RestClient.ts b/packages/api-rest/src/RestClient.ts index 073eb0b4089..6c336eca8ab 100644 --- a/packages/api-rest/src/RestClient.ts +++ b/packages/api-rest/src/RestClient.ts @@ -1,32 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - import { - ConsoleLogger as Logger, - Credentials, - DateUtils, - Signer, + AWSCredentialsAndIdentityId, + fetchAuthSession, } from '@aws-amplify/core'; - -import { apiOptions, ApiInfo } from './types'; +import { apiOptions } from './types'; import axios, { CancelTokenSource } from 'axios'; import { parse, format } from 'url'; - -const logger = new Logger('RestClient'); - -/** -* HTTP Client for REST requests. Send and receive JSON data. -* Sign request with AWS credentials if available -* Usage: -
-const restClient = new RestClient();
-restClient.get('...')
-    .then(function(data) {
-        console.log(data);
-    })
-    .catch(err => console.log(err));
-
-*/ +import { signRequest } from '@aws-amplify/core/internals/aws-client-utils'; export class RestClient { private _options; private _region: string = 'us-east-1'; // this will be updated by endpoint function @@ -47,166 +28,145 @@ export class RestClient { * * For more details, see https://github.com/aws-amplify/amplify-js/pull/3769#issuecomment-552660025 */ - private _cancelTokenMap: WeakMap = null; - - Credentials = Credentials; - + private _cancelTokenMap: WeakMap, CancelTokenSource> | null = + null; /** * @param {RestClientOptions} [options] - Instance options */ constructor(options: apiOptions) { this._options = options; - logger.debug('API Options', this._options); if (this._cancelTokenMap == null) { this._cancelTokenMap = new WeakMap(); } } /** - * Update AWS credentials - * @param {AWSCredentials} credentials - AWS credentials - * - updateCredentials(credentials: AWSCredentials) { - this.options.credentials = credentials; - } -*/ - /** * Basic HTTP request. Customizable * @param {string | ApiInfo } urlOrApiInfo - Full request URL or Api information * @param {string} method - Request HTTP method * @param {json} [init] - Request extra params * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. */ - async ajax(urlOrApiInfo: string | ApiInfo, method: string, init) { - logger.debug(method, urlOrApiInfo); - - let parsed_url; - let url: string; - let region: string = 'us-east-1'; - let service: string = 'execute-api'; - let custom_header: () => { - [key: string]: string; - } = undefined; - - if (typeof urlOrApiInfo === 'string') { - parsed_url = this._parseUrl(urlOrApiInfo); - url = urlOrApiInfo; - } else { - ({ endpoint: url, custom_header, region, service } = urlOrApiInfo); - parsed_url = this._parseUrl(urlOrApiInfo.endpoint); - } + ajax(url: string, method: string, init) { + const source = axios.CancelToken.source(); + const promise = new Promise(async (res, rej) => { + const parsed_url = new URL(url); + + const region: string = init.region || 'us-east-1'; + const service: string = init.serviceName || 'execute-api'; + + const params = { + method, + url, + host: parsed_url.host, + path: parsed_url.pathname, + headers: {}, + data: JSON.stringify(''), + responseType: 'json', + timeout: 0, + }; + + const libraryHeaders = {}; + const initParams = Object.assign({}, init); + const isAllResponse = initParams.response; + if (initParams.body) { + if ( + typeof FormData === 'function' && + initParams.body instanceof FormData + ) { + libraryHeaders['Content-Type'] = 'multipart/form-data'; + params.data = initParams.body; + } else { + libraryHeaders['Content-Type'] = 'application/json; charset=UTF-8'; + params.data = JSON.stringify(initParams.body); + } + } + if (initParams.responseType) { + params.responseType = initParams.responseType; + } + if (initParams.withCredentials) { + params['withCredentials'] = initParams.withCredentials; + } + if (initParams.timeout) { + params.timeout = initParams.timeout; + } - const params = { - method, - url, - host: parsed_url.host, - path: parsed_url.path, - headers: {}, - data: null, - responseType: 'json', - timeout: 0, - cancelToken: null, - }; - - const libraryHeaders = {}; - const initParams = Object.assign({}, init); - const isAllResponse = initParams.response; - if (initParams.body) { + params['signerServiceInfo'] = initParams.signerServiceInfo; + + params.headers = { + ...libraryHeaders, + ...initParams.headers, + }; + + // Intentionally discarding search + const { search, ...parsedUrl } = parse(url, true, true); + params.url = format({ + ...parsedUrl, + query: { + ...parsedUrl.query, + ...(initParams.queryStringParameters || {}), + }, + }); + + // Do not sign the request if client has added 'Authorization' or x-api-key header, + // which means custom authorizer. if ( - typeof FormData === 'function' && - initParams.body instanceof FormData + (params.headers['Authorization'] && + typeof params.headers['Authorization'] !== 'undefined') || + (params.headers['X-Api-Key'] && + typeof params.headers['X-Api-Key'] !== 'undefined') ) { - libraryHeaders['Content-Type'] = 'multipart/form-data'; - params.data = initParams.body; - } else { - libraryHeaders['Content-Type'] = 'application/json; charset=UTF-8'; - params.data = JSON.stringify(initParams.body); + params.headers = Object.keys(params.headers).reduce((acc, k) => { + if (params.headers[k]) { + acc[k] = params.headers[k]; + } + return acc; + // tslint:disable-next-line:align + }, {}); + + return res(await this._request(params, isAllResponse)); } - } - if (initParams.responseType) { - params.responseType = initParams.responseType; - } - if (initParams.withCredentials) { - params['withCredentials'] = initParams.withCredentials; - } - if (initParams.timeout) { - params.timeout = initParams.timeout; - } - if (initParams.cancellableToken) { - params.cancelToken = initParams.cancellableToken.token; - } - params['signerServiceInfo'] = initParams.signerServiceInfo; - - // custom_header callback - const custom_header_obj = - typeof custom_header === 'function' ? await custom_header() : undefined; - - params.headers = { - ...libraryHeaders, - ...custom_header_obj, - ...initParams.headers, - }; - - // Intentionally discarding search - const { search, ...parsedUrl } = parse(url, true, true); - params.url = format({ - ...parsedUrl, - query: { - ...parsedUrl.query, - ...(initParams.queryStringParameters || {}), - }, - }); + let credentials: AWSCredentialsAndIdentityId; - // Do not sign the request if client has added 'Authorization' header, - // which means custom authorizer. - if (typeof params.headers['Authorization'] !== 'undefined') { - params.headers = Object.keys(params.headers).reduce((acc, k) => { - if (params.headers[k]) { - acc[k] = params.headers[k]; + try { + const session = await fetchAuthSession(); + if ( + session.credentials === undefined && + session.identityId === undefined + ) { + throw new Error('No credentials available'); } - return acc; - // tslint:disable-next-line:align - }, {}); - return this._request(params, isAllResponse); - } + credentials = { + credentials: session.credentials, + identityId: session.identityId, + }; + } catch (error) { + res(await this._request(params, isAllResponse)); + } - let credentials; - try { - credentials = await this.Credentials.get(); - } catch (error) { - logger.debug('No credentials available, the request will be unsigned'); - return this._request(params, isAllResponse); - } - let signedParams; - try { + let signedParams; + // before signed PARAMS signedParams = this._sign({ ...params }, credentials, { region, service, }); - const response = await axios(signedParams); - return isAllResponse ? response : response.data; - } catch (error) { - logger.debug(error); - if (DateUtils.isClockSkewError(error)) { - const { headers } = error.response; - const dateHeader = headers && (headers.date || headers.Date); - const responseDate = new Date(dateHeader); - const requestDate = DateUtils.getDateFromHeaderString( - signedParams.headers['x-amz-date'] - ); - - // Compare local clock to the server clock - if (DateUtils.isClockSkewed(responseDate)) { - DateUtils.setClockOffset( - responseDate.getTime() - requestDate.getTime() - ); - return this.ajax(urlOrApiInfo, method, init); - } + try { + res( + await this._request({ + ...signedParams, + data: signedParams.body, + cancelToken: source.token, + }) + ); + } catch (error) { + rej(error); } - throw error; - } + }); + this._cancelTokenMap.set(promise, source); + + return promise; } /** @@ -215,7 +175,7 @@ export class RestClient { * @param {JSON} init - Request extra params * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. */ - get(urlOrApiInfo: string | ApiInfo, init) { + get(urlOrApiInfo: string, init) { return this.ajax(urlOrApiInfo, 'GET', init); } @@ -225,7 +185,7 @@ export class RestClient { * @param {json} init - Request extra params * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. */ - put(urlOrApiInfo: string | ApiInfo, init) { + put(urlOrApiInfo: string, init) { return this.ajax(urlOrApiInfo, 'PUT', init); } @@ -235,7 +195,7 @@ export class RestClient { * @param {json} init - Request extra params * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. */ - patch(urlOrApiInfo: string | ApiInfo, init) { + patch(urlOrApiInfo: string, init) { return this.ajax(urlOrApiInfo, 'PATCH', init); } @@ -245,7 +205,7 @@ export class RestClient { * @param {json} init - Request extra params * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. */ - post(urlOrApiInfo: string | ApiInfo, init) { + post(urlOrApiInfo: string, init) { return this.ajax(urlOrApiInfo, 'POST', init); } @@ -255,7 +215,7 @@ export class RestClient { * @param {json} init - Request extra params * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. */ - del(urlOrApiInfo: string | ApiInfo, init) { + del(urlOrApiInfo: string, init) { return this.ajax(urlOrApiInfo, 'DELETE', init); } @@ -265,7 +225,7 @@ export class RestClient { * @param {json} init - Request extra params * @return {Promise} - A promise that resolves to an object with response status and JSON data, if successful. */ - head(urlOrApiInfo: string | ApiInfo, init) { + head(urlOrApiInfo: string, init) { return this.ajax(urlOrApiInfo, 'HEAD', init); } @@ -275,7 +235,7 @@ export class RestClient { * @param {string} [message] - A message to include in the cancelation exception */ cancel(request: Promise, message?: string) { - const source = this._cancelTokenMap.get(request); + const source = this._cancelTokenMap?.get(request); if (source) { source.cancel(message); return true; @@ -289,7 +249,7 @@ export class RestClient { * @return if the request has a corresponding cancel token. */ hasCancelToken(request: Promise) { - return this._cancelTokenMap.has(request); + return this._cancelTokenMap?.has(request); } /** @@ -318,7 +278,7 @@ export class RestClient { promise: Promise, cancelTokenSource: CancelTokenSource ) { - this._cancelTokenMap.set(promise, cancelTokenSource); + this._cancelTokenMap?.set(promise, cancelTokenSource); } /** @@ -359,39 +319,34 @@ export class RestClient { /** private methods **/ - private _sign(params, credentials, { service, region }) { - const { signerServiceInfo: signerServiceInfoParams, ...otherParams } = - params; - - const endpoint_region: string = - region || this._region || this._options.region; - const endpoint_service: string = - service || this._service || this._options.service; - - const creds = { - secret_key: credentials.secretAccessKey, - access_key: credentials.accessKeyId, - session_token: credentials.sessionToken, - }; - - const endpointInfo = { - region: endpoint_region, - service: endpoint_service, - }; - - const signerServiceInfo = Object.assign( - endpointInfo, - signerServiceInfoParams + private _sign( + params: { + method: string; + url: string; + host: string; + path: string; + headers: {}; + data: BodyInit; + responseType: string; + timeout: number; + }, + credentialsAndIdentityId: AWSCredentialsAndIdentityId, + { service, region } + ) { + const signed_params = signRequest( + { + method: params.method, + headers: params.headers, + url: new URL(params.url), + body: params.data, + }, + { + credentials: credentialsAndIdentityId.credentials, + signingRegion: region, + signingService: service, + } ); - const signed_params = Signer.sign(otherParams, creds, signerServiceInfo); - - if (signed_params.data) { - signed_params.body = signed_params.data; - } - - logger.debug('Signed Request: ', signed_params); - delete signed_params.headers['host']; return signed_params; @@ -401,17 +356,7 @@ export class RestClient { return axios(params) .then(response => (isAllResponse ? response : response.data)) .catch(error => { - logger.debug(error); throw error; }); } - - private _parseUrl(url) { - const parts = url.split('/'); - - return { - host: parts[2], - path: '/' + parts.slice(3).join('/'), - }; - } } diff --git a/packages/api-rest/src/index.ts b/packages/api-rest/src/index.ts index 1677bbc880b..130e39bb4f3 100644 --- a/packages/api-rest/src/index.ts +++ b/packages/api-rest/src/index.ts @@ -1,5 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -export { RestAPI, RestAPIClass } from './RestAPI'; -export { RestClient } from './RestClient'; +export { post, cancel, isCancel } from './API'; +export { DocumentType } from './types'; diff --git a/packages/api-rest/src/types/index.ts b/packages/api-rest/src/types/index.ts index 7386370121e..55b71d37cd2 100644 --- a/packages/api-rest/src/types/index.ts +++ b/packages/api-rest/src/types/index.ts @@ -24,6 +24,20 @@ export class RestClientOptions { } } +export type DocumentType = + | null + | boolean + | number + | string + | DocumentType[] + | { [prop: string]: DocumentType }; + +export type PostOptions = { + headers?: Record; + body: DocumentType; + region?: string; + serviceName?: string; +}; /** * AWS credentials needed for RestClient */ diff --git a/packages/api-rest/tsconfig.json b/packages/api-rest/tsconfig.json new file mode 100644 index 00000000000..f3d5ed63841 --- /dev/null +++ b/packages/api-rest/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "importHelpers": true, + "strict": false, + "noImplicitAny": false, + "skipLibCheck": true + }, + "include": ["./src"] +} diff --git a/packages/api/__tests__/API.test.ts b/packages/api/__tests__/API.test.ts index 8685e56d8b2..952a971e09c 100644 --- a/packages/api/__tests__/API.test.ts +++ b/packages/api/__tests__/API.test.ts @@ -1,166 +1,170 @@ -import { RestAPIClass } from '@aws-amplify/api-rest'; -import { InternalGraphQLAPIClass } from '@aws-amplify/api-graphql/internals'; -import { APIClass as API } from '../src/API'; -import { ApiAction, Category } from '@aws-amplify/core'; - -describe('API test', () => { - test('configure', () => { - jest - .spyOn(RestAPIClass.prototype, 'configure') - .mockReturnValue({ restapi: 'configured' }); - jest - .spyOn(InternalGraphQLAPIClass.prototype, 'configure') - .mockReturnValue({ graphqlapi: 'configured' }); - const api = new API(null); - expect(api.configure(null)).toStrictEqual({ - graphqlapi: 'configured', - restapi: 'configured', - }); - }); - - test('get', async () => { - const spy = jest - .spyOn(RestAPIClass.prototype, 'get') - .mockResolvedValue('getResponse'); - const api = new API(null); - expect(await api.get(null, null, null)).toBe('getResponse'); - - expect(spy).toBeCalledWith(null, null, { - customUserAgentDetails: { - category: Category.API, - action: ApiAction.Get, - }, - }); - }); - - test('post', async () => { - const spy = jest - .spyOn(RestAPIClass.prototype, 'post') - .mockResolvedValue('postResponse'); - const api = new API(null); - expect(await api.post(null, null, null)).toBe('postResponse'); - - expect(spy).toBeCalledWith(null, null, { - customUserAgentDetails: { - category: Category.API, - action: ApiAction.Post, - }, - }); - }); - - test('put', async () => { - const spy = jest - .spyOn(RestAPIClass.prototype, 'put') - .mockResolvedValue('putResponse'); - const api = new API(null); - expect(await api.put(null, null, null)).toBe('putResponse'); - - expect(spy).toBeCalledWith(null, null, { - customUserAgentDetails: { - category: Category.API, - action: ApiAction.Put, - }, - }); - }); - - test('patch', async () => { - const spy = jest - .spyOn(RestAPIClass.prototype, 'patch') - .mockResolvedValue('patchResponse'); - const api = new API(null); - expect(await api.patch(null, null, null)).toBe('patchResponse'); - - expect(spy).toBeCalledWith(null, null, { - customUserAgentDetails: { - category: Category.API, - action: ApiAction.Patch, - }, - }); - }); - - test('del', async () => { - jest.spyOn(RestAPIClass.prototype, 'del').mockResolvedValue('delResponse'); - const api = new API(null); - expect(await api.del(null, null, null)).toBe('delResponse'); - }); - - test('head', async () => { - const spy = jest - .spyOn(RestAPIClass.prototype, 'head') - .mockResolvedValue('headResponse'); - const api = new API(null); - expect(await api.head(null, null, null)).toBe('headResponse'); - - expect(spy).toBeCalledWith(null, null, { - customUserAgentDetails: { - category: Category.API, - action: ApiAction.Head, - }, - }); - }); - - test('endpoint', async () => { - jest - .spyOn(RestAPIClass.prototype, 'endpoint') - .mockResolvedValue('endpointResponse'); - const api = new API(null); - expect(await api.endpoint(null)).toBe('endpointResponse'); - }); - - test('getGraphqlOperationType', () => { - jest - .spyOn(InternalGraphQLAPIClass.prototype, 'getGraphqlOperationType') - .mockReturnValueOnce('getGraphqlOperationTypeResponse' as any); - const api = new API(null); - expect(api.getGraphqlOperationType(null)).toBe( - 'getGraphqlOperationTypeResponse' - ); - }); - - test('graphql', async () => { - const spy = jest - .spyOn(InternalGraphQLAPIClass.prototype, 'graphql') - .mockResolvedValue('grapqhqlResponse' as any); - const api = new API(null); - expect(await api.graphql({ query: 'query' })).toBe('grapqhqlResponse'); - - expect(spy).toBeCalledWith(expect.anything(), undefined, { - category: Category.API, - action: ApiAction.GraphQl, - }); - }); - - describe('cancel', () => { - test('cancel RestAPI request', async () => { - jest - .spyOn(InternalGraphQLAPIClass.prototype, 'hasCancelToken') - .mockImplementation(() => false); - const restAPICancelSpy = jest - .spyOn(RestAPIClass.prototype, 'cancel') - .mockImplementation(() => true); - jest - .spyOn(RestAPIClass.prototype, 'hasCancelToken') - .mockImplementation(() => true); - const api = new API(null); - const request = Promise.resolve(); - expect(api.cancel(request)).toBe(true); - expect(restAPICancelSpy).toHaveBeenCalled(); - }); - - test('cancel GraphQLAPI request', async () => { - jest - .spyOn(InternalGraphQLAPIClass.prototype, 'hasCancelToken') - .mockImplementation(() => true); - const graphQLAPICancelSpy = jest - .spyOn(InternalGraphQLAPIClass.prototype, 'cancel') - .mockImplementation(() => true); - jest - .spyOn(RestAPIClass.prototype, 'hasCancelToken') - .mockImplementation(() => false); - const api = new API(null); - const request = Promise.resolve(); - expect(api.cancel(request)).toBe(true); - expect(graphQLAPICancelSpy).toHaveBeenCalled(); - }); - }); +// import { RestAPIClass } from '@aws-amplify/api-rest'; +// import { InternalGraphQLAPIClass } from '@aws-amplify/api-graphql/internals'; +// import { APIClass as API } from '../src/API'; +// import { ApiAction, Category } from '@aws-amplify/core/internals/utils'; + +// describe('API test', () => { +// test('configure', () => { +// jest +// .spyOn(RestAPIClass.prototype, 'configure') +// .mockReturnValue({ restapi: 'configured' }); +// jest +// .spyOn(InternalGraphQLAPIClass.prototype, 'configure') +// .mockReturnValue({ graphqlapi: 'configured' }); +// const api = new API(null); +// expect(api.configure(null)).toStrictEqual({ +// graphqlapi: 'configured', +// restapi: 'configured', +// }); +// }); + +// test('get', async () => { +// const spy = jest +// .spyOn(RestAPIClass.prototype, 'get') +// .mockResolvedValue('getResponse'); +// const api = new API(null); +// expect(await api.get(null, null, null)).toBe('getResponse'); + +// expect(spy).toBeCalledWith(null, null, { +// customUserAgentDetails: { +// category: Category.API, +// action: ApiAction.Get, +// }, +// }); +// }); + +// test('post', async () => { +// const spy = jest +// .spyOn(RestAPIClass.prototype, 'post') +// .mockResolvedValue('postResponse'); +// const api = new API(null); +// expect(await api.post(null, null, null)).toBe('postResponse'); + +// expect(spy).toBeCalledWith(null, null, { +// customUserAgentDetails: { +// category: Category.API, +// action: ApiAction.Post, +// }, +// }); +// }); + +// test('put', async () => { +// const spy = jest +// .spyOn(RestAPIClass.prototype, 'put') +// .mockResolvedValue('putResponse'); +// const api = new API(null); +// expect(await api.put(null, null, null)).toBe('putResponse'); + +// expect(spy).toBeCalledWith(null, null, { +// customUserAgentDetails: { +// category: Category.API, +// action: ApiAction.Put, +// }, +// }); +// }); + +// test('patch', async () => { +// const spy = jest +// .spyOn(RestAPIClass.prototype, 'patch') +// .mockResolvedValue('patchResponse'); +// const api = new API(null); +// expect(await api.patch(null, null, null)).toBe('patchResponse'); + +// expect(spy).toBeCalledWith(null, null, { +// customUserAgentDetails: { +// category: Category.API, +// action: ApiAction.Patch, +// }, +// }); +// }); + +// test('del', async () => { +// jest.spyOn(RestAPIClass.prototype, 'del').mockResolvedValue('delResponse'); +// const api = new API(null); +// expect(await api.del(null, null, null)).toBe('delResponse'); +// }); + +// test('head', async () => { +// const spy = jest +// .spyOn(RestAPIClass.prototype, 'head') +// .mockResolvedValue('headResponse'); +// const api = new API(null); +// expect(await api.head(null, null, null)).toBe('headResponse'); + +// expect(spy).toBeCalledWith(null, null, { +// customUserAgentDetails: { +// category: Category.API, +// action: ApiAction.Head, +// }, +// }); +// }); + +// test('endpoint', async () => { +// jest +// .spyOn(RestAPIClass.prototype, 'endpoint') +// .mockResolvedValue('endpointResponse'); +// const api = new API(null); +// expect(await api.endpoint(null)).toBe('endpointResponse'); +// }); + +// test('getGraphqlOperationType', () => { +// jest +// .spyOn(InternalGraphQLAPIClass.prototype, 'getGraphqlOperationType') +// .mockReturnValueOnce('getGraphqlOperationTypeResponse' as any); +// const api = new API(null); +// expect(api.getGraphqlOperationType(null)).toBe( +// 'getGraphqlOperationTypeResponse' +// ); +// }); + +// test('graphql', async () => { +// const spy = jest +// .spyOn(InternalGraphQLAPIClass.prototype, 'graphql') +// .mockResolvedValue('grapqhqlResponse' as any); +// const api = new API(null); +// expect(await api.graphql({ query: 'query' })).toBe('grapqhqlResponse'); + +// expect(spy).toBeCalledWith(expect.anything(), undefined, { +// category: Category.API, +// action: ApiAction.GraphQl, +// }); +// }); + +// describe('cancel', () => { +// test('cancel RestAPI request', async () => { +// jest +// .spyOn(InternalGraphQLAPIClass.prototype, 'hasCancelToken') +// .mockImplementation(() => false); +// const restAPICancelSpy = jest +// .spyOn(RestAPIClass.prototype, 'cancel') +// .mockImplementation(() => true); +// jest +// .spyOn(RestAPIClass.prototype, 'hasCancelToken') +// .mockImplementation(() => true); +// const api = new API(null); +// const request = Promise.resolve(); +// expect(api.cancel(request)).toBe(true); +// expect(restAPICancelSpy).toHaveBeenCalled(); +// }); + +// test('cancel GraphQLAPI request', async () => { +// jest +// .spyOn(InternalGraphQLAPIClass.prototype, 'hasCancelToken') +// .mockImplementation(() => true); +// const graphQLAPICancelSpy = jest +// .spyOn(InternalGraphQLAPIClass.prototype, 'cancel') +// .mockImplementation(() => true); +// jest +// .spyOn(RestAPIClass.prototype, 'hasCancelToken') +// .mockImplementation(() => false); +// const api = new API(null); +// const request = Promise.resolve(); +// expect(api.cancel(request)).toBe(true); +// expect(graphQLAPICancelSpy).toHaveBeenCalled(); +// }); +// }); +// }); +// TODO(v6): add tests +describe.skip('API tests', () => { + test('add tests', async () => {}); }); diff --git a/packages/api/package.json b/packages/api/package.json index 3cfcc29c94c..3fe1a411d32 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,5 @@ { "name": "@aws-amplify/api", - "private": true, "version": "6.0.0", "description": "Api category of aws-amplify", "main": "./lib/index.js", @@ -36,7 +35,7 @@ "clean:size": "rimraf dual-publish-tmp tmp*", "format": "echo \"Not implemented\"", "lint": "tslint 'src/**/*.ts' && npm run ts-coverage", - "ts-coverage": "typescript-coverage-report -p ./tsconfig.build.json -t 91.93" + "ts-coverage": "typescript-coverage-report -p ./tsconfig.build.json -t 88" }, "repository": { "type": "git", @@ -48,6 +47,10 @@ "url": "https://github.com/aws/aws-amplify/issues" }, "homepage": "https://aws-amplify.github.io/", + "devDependencies": { + "@types/zen-observable": "^0.8.0", + "typescript": "5.1.6" + }, "files": [ "lib", "lib-esm", @@ -58,21 +61,14 @@ "dependencies": { "@aws-amplify/api-graphql": "4.0.0", "@aws-amplify/api-rest": "4.0.0", - "tslib": "^2.5.0" - }, - "peerDependencies": { - "@aws-amplify/core": "^6.0.0" - }, - "devDependencies": { - "@aws-amplify/core": "6.0.0", - "@types/zen-observable": "^0.8.0" + "tslib": "^2.6.1" }, "size-limit": [ { "name": "API (top-level class)", "path": "./lib-esm/index.js", "import": "{ Amplify, API }", - "limit": "90.28 kB" + "limit": "93.82 kB" } ], "jest": { diff --git a/packages/api/src/API.ts b/packages/api/src/API.ts index e2200841dfe..ff903e5bbc2 100644 --- a/packages/api/src/API.ts +++ b/packages/api/src/API.ts @@ -1,13 +1,16 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { AWSAppSyncRealTimeProvider } from '@aws-amplify/pubsub'; -import { GraphQLOptions, GraphQLResult } from '@aws-amplify/api-graphql'; -import { Amplify, ConsoleLogger as Logger } from '@aws-amplify/core'; +import { + AWSAppSyncRealTimeProvider, + GraphQLOptions, + GraphQLResult, + GraphQLQuery, + GraphQLSubscription, +} from '@aws-amplify/api-graphql'; +import { graphql as v6graphql } from '@aws-amplify/api-graphql/internals'; import Observable from 'zen-observable-ts'; -import { GraphQLQuery, GraphQLSubscription } from './types'; import { InternalAPIClass } from './internals/InternalAPI'; -const logger = new Logger('API'); /** * @deprecated * Use RestApi or GraphQLAPI to reduce your application bundle size @@ -42,7 +45,29 @@ export class APIClass extends InternalAPIClass { ): Promise> | Observable { return super.graphql(options, additionalHeaders); } + + /** + * Generates an API client that can work with models or raw GraphQL + */ + generateClient = never>(): V6Client { + const client: V6Client = { + graphql: v6graphql, + }; + + return client as V6Client; + } } +type FilteredKeys = { + [P in keyof T]: T[P] extends never ? never : P; +}[keyof T]; +type ExcludeNeverFields = { + [K in FilteredKeys]: O[K]; +}; + +// If no T is passed, ExcludeNeverFields removes "models" from the client +declare type V6Client = never> = ExcludeNeverFields<{ + graphql: typeof v6graphql; +}>; + export const API = new APIClass(null); -Amplify.register(API); diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 9d0bb40f389..521d251dd63 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,12 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// TODO(v6): revisit exports + export { GraphQLQuery, GraphQLSubscription } from './types'; -export { API, APIClass } from './API'; +import { API, APIClass } from './API'; export { graphqlOperation, GraphQLAuthError, - GRAPHQL_AUTH_MODE, + GraphQLAuthMode, } from '@aws-amplify/api-graphql'; export type { GraphQLResult } from '@aws-amplify/api-graphql'; + +const generateClient = API.generateClient; + +export { API, APIClass, generateClient }; diff --git a/packages/api/src/internals/InternalAPI.ts b/packages/api/src/internals/InternalAPI.ts index 085c6042429..ab8d9b507ab 100644 --- a/packages/api/src/internals/InternalAPI.ts +++ b/packages/api/src/internals/InternalAPI.ts @@ -1,26 +1,24 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { + AWSAppSyncRealTimeProvider, GraphQLOperation, GraphQLOptions, GraphQLResult, OperationTypeNode, + GraphQLQuery, + GraphQLSubscription, } from '@aws-amplify/api-graphql'; import { InternalGraphQLAPIClass } from '@aws-amplify/api-graphql/internals'; -import { RestAPIClass } from '@aws-amplify/api-rest'; -import { Auth } from '@aws-amplify/auth'; +import { cancel, isCancel } from '@aws-amplify/api-rest'; +import { Cache } from '@aws-amplify/core'; import { - Amplify, ApiAction, - Cache, Category, - Credentials, - CustomUserAgentDetails, ConsoleLogger as Logger, -} from '@aws-amplify/core'; -import { AWSAppSyncRealTimeProvider } from '@aws-amplify/pubsub'; + CustomUserAgentDetails, +} from '@aws-amplify/core/internals/utils'; import Observable from 'zen-observable-ts'; -import { GraphQLQuery, GraphQLSubscription } from '../types'; const logger = new Logger('API'); /** @@ -34,12 +32,9 @@ export class InternalAPIClass { * @param {Object} options - Configuration object for API */ private _options; - private _restApi: RestAPIClass; private _graphqlApi: InternalGraphQLAPIClass; - Auth = Auth; Cache = Cache; - Credentials = Credentials; /** * Initialize API with AWS configuration @@ -47,7 +42,6 @@ export class InternalAPIClass { */ constructor(options) { this._options = options; - this._restApi = new RestAPIClass(options); this._graphqlApi = new InternalGraphQLAPIClass(options); logger.debug('API Options', this._options); } @@ -56,148 +50,13 @@ export class InternalAPIClass { return 'InternalAPI'; } - /** - * Configure API part with aws configurations - * @param {Object} config - Configuration of the API - * @return {Object} - The current configuration - */ - configure(options) { - this._options = Object.assign({}, this._options, options); - - // Share Amplify instance with client for SSR - this._restApi.Credentials = this.Credentials; - - this._graphqlApi.Auth = this.Auth; - this._graphqlApi.Cache = this.Cache; - this._graphqlApi.Credentials = this.Credentials; - - const restAPIConfig = this._restApi.configure(this._options); - const graphQLAPIConfig = this._graphqlApi.configure(this._options); - - return { ...restAPIConfig, ...graphQLAPIConfig }; - } - - /** - * Make a GET request - * @param apiName - The api name of the request - * @param path - The path of the request - * @param [init] - Request extra params - * @return A promise that resolves to an object with response status and JSON data, if successful. - */ - get( - apiName: string, - path: string, - init: { [key: string]: any } - ): Promise { - return this._restApi.get( - apiName, - path, - this.getInitWithCustomUserAgentDetails(init, ApiAction.Get) - ); - } - - /** - * Make a POST request - * @param apiName - The api name of the request - * @param path - The path of the request - * @param [init] - Request extra params - * @return A promise that resolves to an object with response status and JSON data, if successful. - */ - post( - apiName: string, - path: string, - init: { [key: string]: any } - ): Promise { - return this._restApi.post( - apiName, - path, - this.getInitWithCustomUserAgentDetails(init, ApiAction.Post) - ); - } - - /** - * Make a PUT request - * @param apiName - The api name of the request - * @param path - The path of the request - * @param [init] - Request extra params - * @return A promise that resolves to an object with response status and JSON data, if successful. - */ - put( - apiName: string, - path: string, - init: { [key: string]: any } - ): Promise { - return this._restApi.put( - apiName, - path, - this.getInitWithCustomUserAgentDetails(init, ApiAction.Put) - ); - } - - /** - * Make a PATCH request - * @param apiName - The api name of the request - * @param path - The path of the request - * @param [init] - Request extra params - * @return A promise that resolves to an object with response status and JSON data, if successful. - */ - patch( - apiName: string, - path: string, - init: { [key: string]: any } - ): Promise { - return this._restApi.patch( - apiName, - path, - this.getInitWithCustomUserAgentDetails(init, ApiAction.Patch) - ); - } - - /** - * Make a DEL request - * @param apiName - The api name of the request - * @param path - The path of the request - * @param [init] - Request extra params - * @return A promise that resolves to an object with response status and JSON data, if successful. - */ - del( - apiName: string, - path: string, - init: { [key: string]: any } - ): Promise { - return this._restApi.del( - apiName, - path, - this.getInitWithCustomUserAgentDetails(init, ApiAction.Del) - ); - } - - /** - * Make a HEAD request - * @param apiName - The api name of the request - * @param path - The path of the request - * @param [init] - Request extra params - * @return A promise that resolves to an object with response status and JSON data, if successful. - */ - head( - apiName: string, - path: string, - init: { [key: string]: any } - ): Promise { - return this._restApi.head( - apiName, - path, - this.getInitWithCustomUserAgentDetails(init, ApiAction.Head) - ); - } - /** * Checks to see if an error thrown is from an api request cancellation * @param error - Any error * @return If the error was from an api request cancellation */ isCancel(error: any): boolean { - return this._restApi.isCancel(error); + return isCancel(error); } /** * Cancels an inflight request for either a GraphQL request or a Rest API request. @@ -206,33 +65,7 @@ export class InternalAPIClass { * @return If the request was cancelled */ cancel(request: Promise, message?: string): boolean { - if (this._restApi.hasCancelToken(request)) { - return this._restApi.cancel(request, message); - } else if (this._graphqlApi.hasCancelToken(request)) { - return this._graphqlApi.cancel(request, message); - } - return false; - } - - private getInitWithCustomUserAgentDetails( - init: { [key: string]: any }, - action: ApiAction - ) { - const customUserAgentDetails: CustomUserAgentDetails = { - category: Category.API, - action, - }; - const initParams = { ...init, customUserAgentDetails }; - return initParams; - } - - /** - * Getting endpoint for API - * @param apiName - The name of the api - * @return The endpoint of the api - */ - async endpoint(apiName: string): Promise { - return this._restApi.endpoint(apiName); + return cancel(request, message); } /** @@ -282,4 +115,3 @@ export class InternalAPIClass { } export const InternalAPI = new InternalAPIClass(null); -Amplify.register(InternalAPI); diff --git a/packages/api/src/types/index.ts b/packages/api/src/types/index.ts index 1a11786619a..344825f580b 100644 --- a/packages/api/src/types/index.ts +++ b/packages/api/src/types/index.ts @@ -10,13 +10,7 @@ export { graphqlOperation, GraphQLAuthError, GraphQLResult, - GRAPHQL_AUTH_MODE, + GraphQLAuthMode, + GraphQLQuery, + GraphQLSubscription, } from '@aws-amplify/api-graphql'; - -// Opaque type used for determining the graphql query type -declare const queryType: unique symbol; - -export type GraphQLQuery = T & { readonly [queryType]: 'query' }; -export type GraphQLSubscription = T & { - readonly [queryType]: 'subscription'; -}; diff --git a/packages/aws-amplify/api/package.json b/packages/aws-amplify/api/package.json new file mode 100644 index 00000000000..0c463014b64 --- /dev/null +++ b/packages/aws-amplify/api/package.json @@ -0,0 +1,7 @@ +{ + "name": "aws-amplify/api", + "main": "../lib/api/index.js", + "browser": "../lib-esm/api/index.js", + "module": "../lib-esm/api/index.js", + "typings": "../lib-esm/api/index.d.ts" +} diff --git a/packages/aws-amplify/package.json b/packages/aws-amplify/package.json index 33c8a73aad5..47d04c3b7a0 100644 --- a/packages/aws-amplify/package.json +++ b/packages/aws-amplify/package.json @@ -22,6 +22,11 @@ "import": "./lib-esm/auth/index.js", "require": "./lib/auth/index.js" }, + "./api": { + "types": "./lib-esm/api/index.d.ts", + "import": "./lib-esm/api/index.js", + "require": "./lib/api/index.js" + }, "./auth/cognito": { "types": "./lib-esm/auth/cognito/index.d.ts", "import": "./lib-esm/auth/cognito/index.js", @@ -79,6 +84,9 @@ "*": [ "./lib-esm/index.d.ts" ], + "api": [ + "./lib-esm/api/index.d.ts" + ], "utils": [ "./lib-esm/utils/index.d.ts" ], @@ -149,11 +157,13 @@ "lib-esm", "src", "analytics", + "api", "auth", "internals", "storage" ], "dependencies": { + "@aws-amplify/api": "6.0.0", "@aws-amplify/analytics": "7.0.0", "@aws-amplify/auth": "6.0.0", "@aws-amplify/core": "6.0.0", @@ -170,6 +180,18 @@ "import": "{ record }", "limit": "20.49 kB" }, + { + "name": "[API] class (AppSync)", + "path": "./lib-esm/api/index.js", + "import": "{ API }", + "limit": "70.00 kB" + }, + { + "name": "[API] generateClient (AppSync)", + "path": "./lib-esm/api/index.js", + "import": "{ generateClient }", + "limit": "70.00 kB" + }, { "name": "[Analytics] identifyUser (Pinpoint)", "path": "./lib-esm/analytics/index.js", diff --git a/packages/aws-amplify/src/api/index.ts b/packages/aws-amplify/src/api/index.ts new file mode 100644 index 00000000000..7aa85ee68e1 --- /dev/null +++ b/packages/aws-amplify/src/api/index.ts @@ -0,0 +1,7 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/* +This file maps exports from `aws-amplify/api`. It provides access to the default API provider and category utils. +*/ +export * from '@aws-amplify/api'; diff --git a/packages/core/__tests__/parseAWSExports.test.ts b/packages/core/__tests__/parseAWSExports.test.ts index bae0c6ea074..00f6b64f76c 100644 --- a/packages/core/__tests__/parseAWSExports.test.ts +++ b/packages/core/__tests__/parseAWSExports.test.ts @@ -1,5 +1,6 @@ import { parseAWSExports } from '../src/parseAWSExports'; +// TODO: Add API category tests describe('Parser', () => { test('aws_mobile_analytics_app_id', () => { const appId = 'app-id'; diff --git a/packages/core/src/parseAWSExports.ts b/packages/core/src/parseAWSExports.ts index acac048c2c4..16736be7412 100644 --- a/packages/core/src/parseAWSExports.ts +++ b/packages/core/src/parseAWSExports.ts @@ -3,6 +3,12 @@ import { OAuthConfig } from './singleton/Auth/types'; import { ResourcesConfig } from './singleton/types'; +const authTypeMapping: Record = { + API_KEY: 'apiKey', + AWS_IAM: 'iam', + AMAZON_COGNITO_USER_POOLS: 'jwt', +}; + /** * This utility converts the `aws-exports.js` file generated by the Amplify CLI into a {@link ResourcesConfig} object * consumable by Amplify. @@ -11,10 +17,15 @@ import { ResourcesConfig } from './singleton/types'; * * @returns A {@link ResourcesConfig} object. */ + export const parseAWSExports = ( config: Record = {} ): ResourcesConfig => { const { + aws_appsync_apiKey, + aws_appsync_authenticationType, + aws_appsync_graphqlEndpoint, + aws_appsync_region, aws_cognito_identity_pool_id, aws_cognito_sign_up_verification_method, aws_mandatory_sign_in, @@ -40,6 +51,21 @@ export const parseAWSExports = ( }; } + // TODO: Need to support all API configurations + // API + if (aws_appsync_graphqlEndpoint) { + amplifyConfig.API = { + AppSync: { + defaultAuthMode: { + type: authTypeMapping[aws_appsync_authenticationType], + apiKey: aws_appsync_apiKey, + }, + endpoint: aws_appsync_graphqlEndpoint, + region: aws_appsync_region, + }, + }; + } + // Auth if (aws_cognito_identity_pool_id || aws_user_pools_id) { amplifyConfig.Auth = { diff --git a/packages/core/src/singleton/API/types.ts b/packages/core/src/singleton/API/types.ts new file mode 100644 index 00000000000..937348477cd --- /dev/null +++ b/packages/core/src/singleton/API/types.ts @@ -0,0 +1,31 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +export type LibraryAPIOptions = { + AppSync: { + query: string; + variables?: object; + authMode?: any; + authToken?: string; + /** + * @deprecated This property should not be used + */ + userAgentSuffix?: string; + }; + customHeaders: Function; +}; + +export type APIConfig = { + AppSync?: { + defaultAuthMode?: GraphQLAuthMode; + region?: string; + endpoint?: string; + modelIntrospectionSchema?: any; + }; +}; + +export type GraphQLAuthMode = + | { type: 'apiKey'; apiKey: string } + | { type: 'jwt'; token: 'id' | 'access' } + | { type: 'iam' } + | { type: 'lambda' } + | { type: 'custom' }; diff --git a/packages/core/src/singleton/types.ts b/packages/core/src/singleton/types.ts index ce78a4016f9..e7087e74736 100644 --- a/packages/core/src/singleton/types.ts +++ b/packages/core/src/singleton/types.ts @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { APIConfig, LibraryAPIOptions } from './API/types'; import { AnalyticsConfig } from './Analytics/types'; import { AuthConfig, @@ -18,7 +19,7 @@ import { } from './Storage/types'; export type ResourcesConfig = { - // API?: {}; + API?: APIConfig; Analytics?: AnalyticsConfig; Auth?: AuthConfig; // Cache?: CacheConfig; @@ -31,12 +32,14 @@ export type ResourcesConfig = { }; export type LibraryOptions = { + API?: LibraryAPIOptions; Auth?: LibraryAuthOptions; Storage?: LibraryStorageOptions; ssr?: boolean; }; export { + APIConfig, AuthConfig, AuthUserPoolConfig, AuthIdentityPoolConfig, diff --git a/yarn.lock b/yarn.lock index 7ec7ca7281b..d49797e75e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,7 +12,7 @@ "@aws-crypto/sha256-js@5.0.0": version "5.0.0" - resolved "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.0.0.tgz#fec6d5a9a097e812207eacaaa707bfa9191b3ad8" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-5.0.0.tgz#fec6d5a9a097e812207eacaaa707bfa9191b3ad8" integrity sha512-g+u9iKkaQVp9Mjoxq1IJSHj9NHGZF441+R/GIH0dn7u4mix5QQ4VqgpppHrNm1LzjUzb0BpcFGsBXP6cOVf+ZQ== dependencies: "@aws-crypto/util" "^5.0.0" @@ -21,14 +21,22 @@ "@aws-crypto/util@^5.0.0": version "5.0.0" - resolved "https://registry.npmjs.org/@aws-crypto/util/-/util-5.0.0.tgz#afa286af897ea2bd9fab194b4a6be9cc562db23a" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-5.0.0.tgz#afa286af897ea2bd9fab194b4a6be9cc562db23a" integrity sha512-1GYqLdYRe96idcCltlqxdJ68OWE6ADT8qGLmVi7PVHKl8AxD2EWSbJSSevPq2eTx6vaPZpkr1RoZ3lcw/uGoEA== dependencies: "@aws-sdk/types" "^3.222.0" "@aws-sdk/util-utf8-browser" "^3.0.0" tslib "^1.11.1" -"@aws-sdk/types@3.398.0", "@aws-sdk/types@^3.222.0": +"@aws-sdk/types@3.387.0": + version "3.387.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.387.0.tgz#15a968344956b2587dbab1224718d72329e050f4" + integrity sha512-YTjFabNwjTF+6yl88f0/tWff018qmmgMmjlw45s6sdVKueWxdxV68U7gepNLF2nhaQPZa6FDOBoA51NaviVs0Q== + dependencies: + "@smithy/types" "^2.1.0" + tslib "^2.5.0" + +"@aws-sdk/types@3.398.0": version "3.398.0" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.398.0.tgz#8ce02559536670f9188cddfce32e9dd12b4fe965" integrity sha512-r44fkS+vsEgKCuEuTV+TIk0t0m5ZlXHNjSDYEUvzLStbbfUFiNus/YG4UCa0wOk9R7VuQI67badsvvPeVPCGDQ== @@ -36,6 +44,14 @@ "@smithy/types" "^2.2.2" tslib "^2.5.0" +"@aws-sdk/types@^3.222.0": + version "3.410.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.410.0.tgz#8c293e3763acb64c68f5752359523c3a40e5eb88" + integrity sha512-D7iaUCszv/v04NDaZUmCmekamy6VD/lKozm/3gS9+dkfU6cC2CsNoUfPV8BlV6dPdw0oWgF91am3I1stdvfVrQ== + dependencies: + "@smithy/types" "^2.3.0" + tslib "^2.5.0" + "@aws-sdk/util-utf8-browser@^3.0.0": version "3.259.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz#3275a6f5eb334f96ca76635b961d3c50259fd9ff" @@ -59,7 +75,7 @@ "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" chokidar "^3.4.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.5": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.13": version "7.22.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== @@ -94,32 +110,32 @@ semver "^6.3.0" "@babel/core@^7.1.0", "@babel/core@^7.13.16", "@babel/core@^7.14.0": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.11.tgz#8033acaa2aa24c3f814edaaa057f3ce0ba559c24" - integrity sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ== + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.19.tgz#b38162460a6f3baf2a424bda720b24a8aafea241" + integrity sha512-Q8Yj5X4LHVYTbLCKVz0//2D2aDmHF4xzCdEttYvKOnWvErGsa6geHXD6w46x64n5tP69VfeH+IfSrdyH3MLhwA== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.10" - "@babel/generator" "^7.22.10" - "@babel/helper-compilation-targets" "^7.22.10" - "@babel/helper-module-transforms" "^7.22.9" - "@babel/helpers" "^7.22.11" - "@babel/parser" "^7.22.11" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.11" - "@babel/types" "^7.22.11" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.22.19" + "@babel/helpers" "^7.22.15" + "@babel/parser" "^7.22.16" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.19" + "@babel/types" "^7.22.19" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.14.0", "@babel/generator@^7.17.0", "@babel/generator@^7.22.10", "@babel/generator@^7.4.0": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.10.tgz#c92254361f398e160645ac58831069707382b722" - integrity sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A== +"@babel/generator@^7.14.0", "@babel/generator@^7.17.0", "@babel/generator@^7.22.15", "@babel/generator@^7.4.0": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" + integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== dependencies: - "@babel/types" "^7.22.10" + "@babel/types" "^7.22.15" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -132,32 +148,32 @@ "@babel/types" "^7.22.5" "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz#573e735937e99ea75ea30788b57eb52fab7468c9" - integrity sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== dependencies: - "@babel/types" "^7.22.10" + "@babel/types" "^7.22.15" -"@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.10", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz#01d648bbc25dd88f513d862ee0df27b7d4e67024" - integrity sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q== +"@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== dependencies: "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" browserslist "^4.21.9" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.5": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz#4078686740459eeb4af3494a273ac09148dfb213" - integrity sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" + integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-replace-supers" "^7.22.9" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -165,9 +181,9 @@ semver "^6.3.1" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" - integrity sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" regexpu-core "^5.3.1" @@ -204,30 +220,30 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-member-expression-to-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" - integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== +"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz#b95a144896f6d491ca7863576f820f3628818621" + integrity sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.15" -"@babel/helper-module-imports@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" - integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== +"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" - integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== +"@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.22.15", "@babel/helper-module-transforms@^7.22.19", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.19.tgz#94b1f281caa6518f02ec0f5ea2b5348e298ce266" + integrity sha512-m6h1cJvn+OJ+R3jOHp30faq5xKJ7VbjwDj5RGgHuRlU9hrMeKsGC+JpihkR5w1g7IfseCPPtZ0r7/hB4UKaYlA== dependencies: "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" "@babel/helper-simple-access" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.19" "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" @@ -242,13 +258,13 @@ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== "@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82" - integrity sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ== + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.17.tgz#dabaa50622b3b4670bd6546fc8db23eb12d89da0" + integrity sha512-bxH77R5gjH3Nkde6/LuncQoLaP16THYPscurp1S8z7S9ZgezCyV3G8Hc+TZiCmY8pz4fp8CvKSgtJMW0FkLAxA== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-wrap-function" "^7.22.9" + "@babel/helper-wrap-function" "^7.22.17" "@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": version "7.22.9" @@ -285,33 +301,33 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" - integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== +"@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.5": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.19.tgz#2f34ab1e445f5b95e2e6edfe50ea2449e610583a" + integrity sha512-Tinq7ybnEPFFXhlYOYFiSjespWQk0dq2dRNAiMdRTOYQzEGqnnNyrTxPYHP5r6wGjlF1rFgABdDV0g8EwD6Qbg== -"@babel/helper-validator-option@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" - integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== +"@babel/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== -"@babel/helper-wrap-function@^7.22.9": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz#d845e043880ed0b8c18bd194a12005cb16d2f614" - integrity sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ== +"@babel/helper-wrap-function@^7.22.17": + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.17.tgz#222ac3ff9cc8f9b617cc1e5db75c0b538e722801" + integrity sha512-nAhoheCMlrqU41tAojw9GpVEKDlTS8r3lzFmF0lP52LwblCPbuFSO7nGIZoIcoU5NIm1ABrna0cJExE4Ay6l2Q== dependencies: "@babel/helper-function-name" "^7.22.5" - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.10" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.17" -"@babel/helpers@^7.17.2", "@babel/helpers@^7.22.11": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.11.tgz#b02f5d5f2d7abc21ab59eeed80de410ba70b056a" - integrity sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg== +"@babel/helpers@^7.17.2", "@babel/helpers@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" + integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== dependencies: - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.11" - "@babel/types" "^7.22.11" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.15" + "@babel/types" "^7.22.15" "@babel/highlight@^7.22.13": version "7.22.13" @@ -322,26 +338,26 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.17.0", "@babel/parser@^7.20.7", "@babel/parser@^7.22.11", "@babel/parser@^7.22.5", "@babel/parser@^7.4.3": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.13.tgz#23fb17892b2be7afef94f573031c2f4b42839a2b" - integrity sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw== +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.17.0", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.4.3": + version "7.22.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" + integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" - integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962" + integrity sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" - integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz#2aeb91d337d4e1a1e7ce85b76a37f5301781200f" + integrity sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.15" "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.13.0": version "7.18.6" @@ -352,9 +368,9 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-proposal-export-default-from@^7.0.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.22.5.tgz#825924eda1fad382c3de4db6fe1711b6fa03362f" - integrity sha512-UCe1X/hplyv6A5g2WnQ90tnHRvYL29dabCWww92lO7VdfMVTVReBTRrhiMrKQejHD9oVkdnRdwYuzUZkBVQisg== + version "7.22.17" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.22.17.tgz#91b60cd338f501cccdf549af2308768911ec5fbb" + integrity sha512-cop/3quQBVvdz6X5SJC6AhUv3C9DrVTM06LUEXimEdWAhCSyOJIr9NiZDU9leHZ0/aiG0Sh7Zmvaku5TWYNgbA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-default-from" "^7.22.5" @@ -562,10 +578,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-async-generator-functions@^7.22.10": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.11.tgz#dbe3b1ff5a52e2e5edc4b19a60d325a675ed2649" - integrity sha512-0pAlmeRJn6wU84zzZsEOx1JV1Jf8fqO9ok7wofIJwUnplYo247dcd24P+cMJht7ts9xkzdtB0EPHmOb7F+KzXw== +"@babel/plugin-transform-async-generator-functions@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz#3b153af4a6b779f340d5b80d3f634f55820aefa3" + integrity sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" @@ -588,10 +604,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-block-scoping@^7.0.0", "@babel/plugin-transform-block-scoping@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz#88a1dccc3383899eb5e660534a76a22ecee64faa" - integrity sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg== +"@babel/plugin-transform-block-scoping@^7.0.0", "@babel/plugin-transform-block-scoping@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz#494eb82b87b5f8b1d8f6f28ea74078ec0a10a841" + integrity sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -603,7 +619,7 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-class-static-block@^7.22.5": +"@babel/plugin-transform-class-static-block@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974" integrity sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g== @@ -612,18 +628,18 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.0.0", "@babel/plugin-transform-classes@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363" - integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ== +"@babel/plugin-transform-classes@^7.0.0", "@babel/plugin-transform-classes@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz#aaf4753aee262a232bbc95451b4bdf9599c65a0b" + integrity sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" @@ -635,10 +651,10 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/template" "^7.22.5" -"@babel/plugin-transform-destructuring@^7.0.0", "@babel/plugin-transform-destructuring@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz#38e2273814a58c810b6c34ea293be4973c4eb5e2" - integrity sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw== +"@babel/plugin-transform-destructuring@^7.0.0", "@babel/plugin-transform-destructuring@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz#e7404ea5bb3387073b9754be654eecb578324694" + integrity sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -657,7 +673,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dynamic-import@^7.22.5": +"@babel/plugin-transform-dynamic-import@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa" integrity sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA== @@ -673,7 +689,7 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-export-namespace-from@^7.22.5": +"@babel/plugin-transform-export-namespace-from@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c" integrity sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw== @@ -689,10 +705,10 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-flow" "^7.22.5" -"@babel/plugin-transform-for-of@^7.0.0", "@babel/plugin-transform-for-of@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" - integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== +"@babel/plugin-transform-for-of@^7.0.0", "@babel/plugin-transform-for-of@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz#f64b4ccc3a4f131a996388fae7680b472b306b29" + integrity sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -705,7 +721,7 @@ "@babel/helper-function-name" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-json-strings@^7.22.5": +"@babel/plugin-transform-json-strings@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835" integrity sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw== @@ -720,7 +736,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-logical-assignment-operators@^7.22.5": +"@babel/plugin-transform-logical-assignment-operators@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c" integrity sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ== @@ -743,16 +759,16 @@ "@babel/helper-module-transforms" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.22.11", "@babel/plugin-transform-modules-commonjs@^7.22.5": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.11.tgz#d7991d3abad199c03b68ee66a64f216c47ffdfae" - integrity sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g== +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz#b11810117ed4ee7691b29bd29fd9f3f98276034f" + integrity sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg== dependencies: - "@babel/helper-module-transforms" "^7.22.9" + "@babel/helper-module-transforms" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-modules-systemjs@^7.22.5": +"@babel/plugin-transform-modules-systemjs@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz#3386be5875d316493b517207e8f1931d93154bb1" integrity sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA== @@ -785,7 +801,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc" integrity sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg== @@ -793,7 +809,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.22.5": +"@babel/plugin-transform-numeric-separator@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd" integrity sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg== @@ -808,16 +824,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-object-rest-spread@^7.22.5": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.11.tgz#dbbb06ce783cd994a8f430d8cefa553e9b42ca62" - integrity sha512-nX8cPFa6+UmbepISvlf5jhQyaC7ASs/7UxHmMkuJ/k5xSHvDPPaibMo+v3TXwU/Pjqhep/nFNpd3zn4YR59pnw== +"@babel/plugin-transform-object-rest-spread@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz#21a95db166be59b91cde48775310c0df6e1da56f" + integrity sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q== dependencies: "@babel/compat-data" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.10" + "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.22.5" + "@babel/plugin-transform-parameters" "^7.22.15" "@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.22.5": version "7.22.5" @@ -827,7 +843,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.5" -"@babel/plugin-transform-optional-catch-binding@^7.22.5": +"@babel/plugin-transform-optional-catch-binding@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0" integrity sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ== @@ -835,19 +851,19 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.22.10", "@babel/plugin-transform-optional-chaining@^7.22.5": - version "7.22.12" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.12.tgz#d7ebf6a88cd2f4d307b0e000ab630acd8124b333" - integrity sha512-7XXCVqZtyFWqjDsYDY4T45w4mlx1rf7aOgkc/Ww76xkgBiOlmjPkx36PBLHa1k1rwWvVgYMPsbuVnIamx2ZQJw== +"@babel/plugin-transform-optional-chaining@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz#d7a5996c2f7ca4ad2ad16dbb74444e5c4385b1ba" + integrity sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" - integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz#719ca82a01d177af358df64a514d64c2e3edb114" + integrity sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -859,7 +875,7 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-property-in-object@^7.22.5": +"@babel/plugin-transform-private-property-in-object@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1" integrity sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ== @@ -904,16 +920,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz#932c291eb6dd1153359e2a90cb5e557dcf068416" - integrity sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA== +"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz#7e6266d88705d7c49f11c98db8b9464531289cd6" + integrity sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-jsx" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.15" "@babel/plugin-transform-react-pure-annotations@^7.22.5": version "7.22.5" @@ -939,11 +955,11 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-runtime@^7.0.0": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.10.tgz#89eda6daf1d3af6f36fb368766553054c8d7cd46" - integrity sha512-RchI7HePu1eu0CYNKHHHQdfenZcM4nz8rew5B1VWqeRKdcwW5aQ5HeG9eTUbWiAS1UrmHVLmoxTWHt3iLD/NhA== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.15.tgz#3a625c4c05a39e932d7d34f5d4895cdd0172fdc9" + integrity sha512-tEVLhk8NRZSmwQ0DJtxxhTrCht1HVo8VaMzYT4w6lwyKBuHsgoioAUA7/6eT2fRfc5/23fuGdlwIxXhRVgWr4g== dependencies: - "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" babel-plugin-polyfill-corejs2 "^0.4.5" babel-plugin-polyfill-corejs3 "^0.8.3" @@ -986,13 +1002,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-typescript@^7.22.11", "@babel/plugin-transform-typescript@^7.5.0": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.11.tgz#9f27fb5e51585729374bb767ab6a6d9005a23329" - integrity sha512-0E4/L+7gfvHub7wsbTv03oRtD69X31LByy44fGmFzbZScpupFByMcgCJ0VbBTkzyjSJKuRoGN8tcijOWKTmqOA== +"@babel/plugin-transform-typescript@^7.22.15", "@babel/plugin-transform-typescript@^7.5.0": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz#15adef906451d86349eb4b8764865c960eb54127" + integrity sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.11" + "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.22.5" @@ -1028,16 +1044,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.0.0": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.10.tgz#3263b9fe2c8823d191d28e61eac60a79f9ce8a0f" - integrity sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.15.tgz#142716f8e00bc030dae5b2ac6a46fbd8b3e18ff8" + integrity sha512-tZFHr54GBkHk6hQuVA8w4Fmq+MSPsfvMG0vPnOYyTnJpyfMqybL8/MbNCPRT9zc2KBO2pe4tq15g6Uno4Jpoag== dependencies: "@babel/compat-data" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.10" + "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.15" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.15" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" @@ -1058,41 +1074,41 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.22.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.10" + "@babel/plugin-transform-async-generator-functions" "^7.22.15" "@babel/plugin-transform-async-to-generator" "^7.22.5" "@babel/plugin-transform-block-scoped-functions" "^7.22.5" - "@babel/plugin-transform-block-scoping" "^7.22.10" + "@babel/plugin-transform-block-scoping" "^7.22.15" "@babel/plugin-transform-class-properties" "^7.22.5" - "@babel/plugin-transform-class-static-block" "^7.22.5" - "@babel/plugin-transform-classes" "^7.22.6" + "@babel/plugin-transform-class-static-block" "^7.22.11" + "@babel/plugin-transform-classes" "^7.22.15" "@babel/plugin-transform-computed-properties" "^7.22.5" - "@babel/plugin-transform-destructuring" "^7.22.10" + "@babel/plugin-transform-destructuring" "^7.22.15" "@babel/plugin-transform-dotall-regex" "^7.22.5" "@babel/plugin-transform-duplicate-keys" "^7.22.5" - "@babel/plugin-transform-dynamic-import" "^7.22.5" + "@babel/plugin-transform-dynamic-import" "^7.22.11" "@babel/plugin-transform-exponentiation-operator" "^7.22.5" - "@babel/plugin-transform-export-namespace-from" "^7.22.5" - "@babel/plugin-transform-for-of" "^7.22.5" + "@babel/plugin-transform-export-namespace-from" "^7.22.11" + "@babel/plugin-transform-for-of" "^7.22.15" "@babel/plugin-transform-function-name" "^7.22.5" - "@babel/plugin-transform-json-strings" "^7.22.5" + "@babel/plugin-transform-json-strings" "^7.22.11" "@babel/plugin-transform-literals" "^7.22.5" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.11" "@babel/plugin-transform-member-expression-literals" "^7.22.5" "@babel/plugin-transform-modules-amd" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.5" - "@babel/plugin-transform-modules-systemjs" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.15" + "@babel/plugin-transform-modules-systemjs" "^7.22.11" "@babel/plugin-transform-modules-umd" "^7.22.5" "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" "@babel/plugin-transform-new-target" "^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5" - "@babel/plugin-transform-numeric-separator" "^7.22.5" - "@babel/plugin-transform-object-rest-spread" "^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" + "@babel/plugin-transform-numeric-separator" "^7.22.11" + "@babel/plugin-transform-object-rest-spread" "^7.22.15" "@babel/plugin-transform-object-super" "^7.22.5" - "@babel/plugin-transform-optional-catch-binding" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.10" - "@babel/plugin-transform-parameters" "^7.22.5" + "@babel/plugin-transform-optional-catch-binding" "^7.22.11" + "@babel/plugin-transform-optional-chaining" "^7.22.15" + "@babel/plugin-transform-parameters" "^7.22.15" "@babel/plugin-transform-private-methods" "^7.22.5" - "@babel/plugin-transform-private-property-in-object" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" "@babel/plugin-transform-property-literals" "^7.22.5" "@babel/plugin-transform-regenerator" "^7.22.10" "@babel/plugin-transform-reserved-words" "^7.22.5" @@ -1106,7 +1122,7 @@ "@babel/plugin-transform-unicode-regex" "^7.22.5" "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "0.1.6-no-external-plugins" - "@babel/types" "^7.22.10" + "@babel/types" "^7.22.15" babel-plugin-polyfill-corejs2 "^0.4.5" babel-plugin-polyfill-corejs3 "^0.8.3" babel-plugin-polyfill-regenerator "^0.5.2" @@ -1114,12 +1130,12 @@ semver "^6.3.1" "@babel/preset-flow@^7.13.13": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.22.5.tgz#876f24ab6b38bd79703a93f32020ca2162312784" - integrity sha512-ta2qZ+LSiGCrP5pgcGt8xMnnkXQrq8Sa4Ulhy06BOlF5QbLw9q5hIx7bn5MrsvyTGAfh6kTOo07Q+Pfld/8Y5Q== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.22.15.tgz#30318deb9b3ebd9f5738e96da03a531e0cd3165d" + integrity sha512-dB5aIMqpkgbTfN5vDdTRPzjqtWiZcRESNR88QYnoPR+bmdYoluOzMX9tQerTv0XzSgZYctPfO1oc0N5zdog1ew== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" "@babel/plugin-transform-flow-strip-types" "^7.22.5" "@babel/preset-modules@0.1.6-no-external-plugins": @@ -1132,32 +1148,32 @@ esutils "^2.0.2" "@babel/preset-react@^7.0.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.5.tgz#c4d6058fbf80bccad02dd8c313a9aaa67e3c3dd6" - integrity sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.15.tgz#9a776892b648e13cc8ca2edf5ed1264eea6b6afc" + integrity sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" "@babel/plugin-transform-react-display-name" "^7.22.5" - "@babel/plugin-transform-react-jsx" "^7.22.5" + "@babel/plugin-transform-react-jsx" "^7.22.15" "@babel/plugin-transform-react-jsx-development" "^7.22.5" "@babel/plugin-transform-react-pure-annotations" "^7.22.5" "@babel/preset-typescript@^7.13.0": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.11.tgz#f218cd0345524ac888aa3dc32f029de5b064b575" - integrity sha512-tWY5wyCZYBGY7IlalfKI1rLiGlIfnwsRHZqlky0HVv8qviwQ1Uo/05M6+s+TcTCVa6Bmoo2uJW5TMFX6Wa4qVg== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz#43db30516fae1d417d748105a0bc95f637239d48" + integrity sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" "@babel/plugin-syntax-jsx" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.11" - "@babel/plugin-transform-typescript" "^7.22.11" + "@babel/plugin-transform-modules-commonjs" "^7.22.15" + "@babel/plugin-transform-typescript" "^7.22.15" "@babel/register@^7.13.16": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.5.tgz#e4d8d0f615ea3233a27b5c6ada6750ee59559939" - integrity sha512-vV6pm/4CijSQ8Y47RH5SopXzursN35RQINfGJkmOlcpAtGuf94miFvIPhCKGQN7WGIcsgG1BHEX2KVdTYwTwUQ== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.15.tgz#c2c294a361d59f5fa7bcc8b97ef7319c32ecaec7" + integrity sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" @@ -1171,44 +1187,44 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.1.2", "@babel/runtime@^7.8.4": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.11.tgz#7a9ba3bbe406ad6f9e8dd4da2ece453eb23a77a4" - integrity sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.0.0", "@babel/template@^7.16.7", "@babel/template@^7.22.5", "@babel/template@^7.4.0": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" - integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== +"@babel/template@^7.0.0", "@babel/template@^7.16.7", "@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.4.0": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/parser" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.17.0", "@babel/traverse@^7.22.11", "@babel/traverse@^7.4.3": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.11.tgz#71ebb3af7a05ff97280b83f05f8865ac94b2027c" - integrity sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.17.0", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.19", "@babel/traverse@^7.4.3": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.19.tgz#bb2b12b7de9d7fec9e812ed89eea097b941954f8" + integrity sha512-ZCcpVPK64krfdScRbpxF6xA5fz7IOsfMwx1tcACvCzt6JY+0aHkBk7eIU8FRDSZRU5Zei6Z4JfgAxN1bqXGECg== dependencies: - "@babel/code-frame" "^7.22.10" - "@babel/generator" "^7.22.10" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.11" - "@babel/types" "^7.22.11" + "@babel/parser" "^7.22.16" + "@babel/types" "^7.22.19" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.22.5", "@babel/types@^7.4.0", "@babel/types@^7.4.4": - version "7.22.11" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.11.tgz#0e65a6a1d4d9cbaa892b2213f6159485fe632ea2" - integrity sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg== +"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.17", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.4.0", "@babel/types@^7.4.4": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" + integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== dependencies: "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.19" to-fast-properties "^2.0.0" "@cnakazawa/watch@^1.0.3": @@ -1851,9 +1867,9 @@ which "^3.0.0" "@npmcli/query@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@npmcli/query/-/query-3.0.0.tgz#51a0dfb85811e04f244171f164b6bc83b36113a7" - integrity sha512-MFNDSJNgsLZIEBVZ0Q9w9K7o07j5N4o4yjtdz2uEpuCZlXGMuPENiRaFYk0vRqAA64qVuUQwC05g27fRtfUgnA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/query/-/query-3.0.1.tgz#77d63ceb7d27ed748da3cc8b50d45fc341448ed6" + integrity sha512-0jE8iHBogf/+bFDj+ju6/UMLbJ39c8h6nSe6qile+dB7PJ0iV3gNqcb2vtt6WWCBrxv9uAjzUT/8vroluulidA== dependencies: postcss-selector-parser "^6.0.10" @@ -1880,13 +1896,13 @@ which "^3.0.0" "@nrwl/devkit@>=15.5.2 < 16": - version "15.9.6" - resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-15.9.6.tgz#3eee51bb3b2a357b8cbb747be4cb505dc5fa5548" - integrity sha512-+gPyrvcUmZMzyVadFSkgfQJItJV8xhydsPMNL1g+KBYu9EzsLG6bqlioJvsOFT8v3zcFrzvoF84imEDs/Cym9Q== + version "15.9.7" + resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-15.9.7.tgz#14d19ec82ff4209c12147a97f1cdea05d8f6c087" + integrity sha512-Sb7Am2TMT8AVq8e+vxOlk3AtOA2M0qCmhBzoM1OJbdHaPKc0g0UgSnWRml1kPGg5qfPk72tWclLoZJ5/ut0vTg== dependencies: ejs "^3.1.7" ignore "^5.0.4" - semver "7.3.4" + semver "7.5.4" tmp "~0.2.1" tslib "^2.3.0" @@ -2090,9 +2106,9 @@ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@polka/url@^1.0.0-next.20": - version "1.0.0-next.21" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" - integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== + version "1.0.0-next.23" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.23.tgz#498e41218ab3b6a1419c735e5c6ae2c5ed609b6c" + integrity sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg== "@react-native-async-storage/async-storage@^1.17.12": version "1.19.3" @@ -2385,30 +2401,30 @@ "@smithy/is-array-buffer@^2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz#8fa9b8040651e7ba0b2f6106e636a91354ff7d34" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz#8fa9b8040651e7ba0b2f6106e636a91354ff7d34" integrity sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug== dependencies: tslib "^2.5.0" "@smithy/md5-js@2.0.5": version "2.0.5" - resolved "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.5.tgz#02173e4e21105819efa8ebaa17eab23d5663f896" + resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-2.0.5.tgz#02173e4e21105819efa8ebaa17eab23d5663f896" integrity sha512-k5EOte/Ye2r7XBVaXv2rhiehk6l3T4uRiPF+pnxKEc+G9Fwd1xAXBDZrtOq1syFPBKBmVfNszG4nevngST7NKg== dependencies: "@smithy/types" "^2.2.2" "@smithy/util-utf8" "^2.0.0" tslib "^2.5.0" -"@smithy/types@^2.2.2": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.2.2.tgz#bd8691eb92dd07ac33b83e0e1c45f283502b1bf7" - integrity sha512-4PS0y1VxDnELGHGgBWlDksB2LJK8TG8lcvlWxIsgR+8vROI7Ms8h1P4FQUx+ftAX2QZv5g1CJCdhdRmQKyonyw== +"@smithy/types@2.1.0", "@smithy/types@^2.1.0", "@smithy/types@^2.2.2", "@smithy/types@^2.3.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.1.0.tgz#67fd47c25bbb0fd818951891bf7bcf19a8ee2fe6" + integrity sha512-KLsCsqxX0j2l99iP8s0f7LBlcsp7a7ceXGn0LPYPyVOsqmIKvSaPQajq0YevlL4T9Bm+DtcyXfBTbtBcLX1I7A== dependencies: tslib "^2.5.0" "@smithy/util-base64@2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.0.tgz#1beeabfb155471d1d41c8d0603be1351f883c444" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-2.0.0.tgz#1beeabfb155471d1d41c8d0603be1351f883c444" integrity sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA== dependencies: "@smithy/util-buffer-from" "^2.0.0" @@ -2416,7 +2432,7 @@ "@smithy/util-buffer-from@^2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz#7eb75d72288b6b3001bc5f75b48b711513091deb" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz#7eb75d72288b6b3001bc5f75b48b711513091deb" integrity sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw== dependencies: "@smithy/is-array-buffer" "^2.0.0" @@ -2424,14 +2440,14 @@ "@smithy/util-hex-encoding@2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz#0aa3515acd2b005c6d55675e377080a7c513b59e" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz#0aa3515acd2b005c6d55675e377080a7c513b59e" integrity sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA== dependencies: tslib "^2.5.0" "@smithy/util-utf8@^2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.0.tgz#b4da87566ea7757435e153799df9da717262ad42" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.0.0.tgz#b4da87566ea7757435e153799df9da717262ad42" integrity sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ== dependencies: "@smithy/util-buffer-from" "^2.0.0" @@ -2738,7 +2754,7 @@ "@types/js-cookie@^2.2.7": version "2.2.7" - resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA== "@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": @@ -2767,9 +2783,9 @@ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*", "@types/node@^20.3.1": - version "20.5.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.7.tgz#4b8ecac87fbefbc92f431d09c30e176fc0a7c377" - integrity sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA== + version "20.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16" + integrity sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg== "@types/node@^8.9.5": version "8.10.66" @@ -2833,9 +2849,9 @@ integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== "@types/semver@^7.3.10": - version "7.5.1" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.1.tgz#0480eeb7221eb9bc398ad7432c9d7e14b1a5a367" - integrity sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg== + version "7.5.2" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" + integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== "@types/sinon@^7.5.1": version "7.5.2" @@ -2848,14 +2864,14 @@ integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== "@types/triple-beam@^1.3.2": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8" - integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g== + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.3.tgz#726ae98a5f6418c8f24f9b0f2a9f81a8664876ae" + integrity sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g== "@types/uuid@^9.0.0": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b" - integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ== + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.4.tgz#e884a59338da907bda8d2ed03e01c5c49d036f1c" + integrity sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA== "@types/yargs-parser@*": version "21.0.0" @@ -2883,6 +2899,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/zen-observable@^0.8.0": + version "0.8.4" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.4.tgz#e06f78a43387899cfa60c02f166620907fc534c8" + integrity sha512-XWquk4B9Y9bP++I9FsKBVDR+cM1duIqTksuD4l+XUDcqKdngHrtLBe6A5DQX5sdJPWDhLFM9xHZBCiWcecZ0Jg== + "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": version "1.11.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" @@ -3396,25 +3417,26 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== -array.prototype.reduce@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" - integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q== +array.prototype.reduce@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.6.tgz#63149931808c5fc1e1354814923d92d45f7d96d5" + integrity sha512-UW+Mz8LG/sPSU8jRDCjVr6J/ZKAGpHfwrZ6kWTG5qCxIEiXdVshqGnu5vEZA8S1y6X4aCSbQZ0/EEsfvEvBiSg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" -arraybuffer.prototype.slice@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz#9b5ea3868a6eebc30273da577eb888381c0044bb" - integrity sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw== +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== dependencies: array-buffer-byte-length "^1.0.0" call-bind "^1.0.2" define-properties "^1.2.0" + es-abstract "^1.22.1" get-intrinsic "^1.2.1" is-array-buffer "^3.0.2" is-shared-array-buffer "^1.0.2" @@ -3510,7 +3532,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== -axios@^1.0.0: +axios@1.5.0, axios@^1.0.0: version "1.5.0" resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.0.tgz#f02e4af823e2e46a9768cfc74691fdd0517ea267" integrity sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ== @@ -3998,9 +4020,9 @@ camelcase@^6.0.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001517: - version "1.0.30001524" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz#1e14bce4f43c41a7deaeb5ebfe86664fe8dadb80" - integrity sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA== + version "1.0.30001534" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz#f24a9b2a6d39630bac5c132b5dff89b39a12e7dd" + integrity sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q== capture-exit@^2.0.0: version "2.0.0" @@ -4152,9 +4174,9 @@ cli-spinners@2.6.1: integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== cli-spinners@^2.0.0, cli-spinners@^2.5.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.0.tgz#5881d0ad96381e117bbe07ad91f2008fe6ffd8db" - integrity sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g== + version "2.9.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35" + integrity sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ== cli-table3@^0.6.1: version "0.6.3" @@ -4587,9 +4609,9 @@ copy-descriptor@^0.1.0: integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== core-js-compat@^3.31.0: - version "3.32.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.1.tgz#55f9a7d297c0761a8eb1d31b593e0f5b6ffae964" - integrity sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA== + version "3.32.2" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.2.tgz#8047d1a8b3ac4e639f0d4f66d4431aa3b16e004c" + integrity sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ== dependencies: browserslist "^4.21.10" @@ -4789,16 +4811,26 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" - integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: + define-data-property "^1.0.1" has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -4979,9 +5011,9 @@ ejs@^3.1.7: jake "^10.8.5" electron-to-chromium@^1.4.477: - version "1.4.505" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.505.tgz#00571ade5975b58413f0f56a665b065bfc29cdfc" - integrity sha512-0A50eL5BCCKdxig2SsCXhpuztnB9PfUgRMojj5tMvt8O54lbwz3t6wNgnpiTRosw5QjlJB7ixhVyeg8daLQwSQ== + version "1.4.520" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.520.tgz#c19c25a10d87bd88a9aae2b76cae9235a50c2994" + integrity sha512-Frfus2VpYADsrh1lB3v/ft/WVFlVzOIm+Q0p7U7VqHI6qr7NWHYKe+Wif3W50n7JAFoBsWVsoU0+qDks6WQ60g== emoji-regex@^7.0.1: version "7.0.3" @@ -5079,18 +5111,18 @@ errorhandler@^1.5.0: accepts "~1.3.7" escape-html "~1.0.3" -es-abstract@^1.20.4, es-abstract@^1.21.2, es-abstract@^1.22.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" - integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== +es-abstract@^1.22.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a" + integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA== dependencies: array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.1" + arraybuffer.prototype.slice "^1.0.2" available-typed-arrays "^1.0.5" call-bind "^1.0.2" es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function.prototype.name "^1.1.5" + function.prototype.name "^1.1.6" get-intrinsic "^1.2.1" get-symbol-description "^1.0.0" globalthis "^1.0.3" @@ -5106,23 +5138,23 @@ es-abstract@^1.20.4, es-abstract@^1.21.2, es-abstract@^1.22.1: is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" - is-typed-array "^1.1.10" + is-typed-array "^1.1.12" is-weakref "^1.0.2" object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.5.0" - safe-array-concat "^1.0.0" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.7" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" typed-array-buffer "^1.0.0" typed-array-byte-length "^1.0.0" typed-array-byte-offset "^1.0.0" typed-array-length "^1.0.4" unbox-primitive "^1.0.2" - which-typed-array "^1.1.10" + which-typed-array "^1.1.11" es-array-method-boxes-properly@^1.0.0: version "1.0.0" @@ -5130,9 +5162,9 @@ es-array-method-boxes-properly@^1.0.0: integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== es-module-lexer@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f" - integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA== + version "1.3.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" + integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== es-set-tostringtag@^2.0.1: version "2.0.1" @@ -5172,6 +5204,11 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + escodegen@^1.9.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" @@ -5637,9 +5674,9 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flow-parser@0.*: - version "0.215.1" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.215.1.tgz#a14007f404db46ac829bb6db3a22a7956d9e298f" - integrity sha512-qq3rdRToqwesrddyXf+Ml8Tuf7TdoJS+EMbJgC6fHAVoBCXjb4mHelNd3J+jD8ts0bSHX81FG3LN7Qn/dcl6pA== + version "0.216.1" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.216.1.tgz#eeba9b0b689deeccc34a6b7d2b1f97b8f943afc0" + integrity sha512-wstw46/C/8bRv/8RySCl15lK376j8DHxm41xFjD9eVL+jSS1UmVpbdLdA0LzGuS2v5uGgQiBLEj6mgSJQwW+MA== flow-parser@^0.121.0: version "0.121.0" @@ -5806,7 +5843,7 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.5: +function.prototype.name@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== @@ -6024,9 +6061,9 @@ glob@7.1.4: path-is-absolute "^1.0.0" glob@^10.2.2: - version "10.3.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.3.tgz#8360a4ffdd6ed90df84aa8d52f21f452e86a123b" - integrity sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw== + version "10.3.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.4.tgz#c85c9c7ab98669102b6defda76d35c5b1ef9766f" + integrity sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ== dependencies: foreground-child "^3.1.0" jackspeak "^2.0.3" @@ -6122,6 +6159,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphql@15.8.0: + version "15.8.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" + integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -6974,7 +7016,7 @@ is-there@^4.3.3: resolved "https://registry.yarnpkg.com/is-there/-/is-there-4.5.1.tgz#ea292e7fad3fc4d70763fe0af40a286c9f5e1e2e" integrity sha512-vIZ7HTXAoRoIwYSsTnxb0sg9L6rth+JOulNcavsbskQkCIWoSM2cjFOWZs4wGziGZER+Xgs/HXiCQZgiL8ppxQ== -is-typed-array@^1.1.10, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: version "1.1.12" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== @@ -7103,9 +7145,9 @@ istanbul-reports@^2.2.6: html-escaper "^2.0.0" jackspeak@^2.0.3: - version "2.3.1" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.1.tgz#ce2effa4c458e053640e61938865a5b5fae98456" - integrity sha512-4iSY3Bh1Htv+kLhiiZunUhQ+OYXIn0ze3ulq8JeWrFKmhPAJSySV2+kdtRh2pGcCeF0s6oR8Oc+pYZynJj4t8A== + version "2.3.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.3.tgz#95e4cbcc03b3eb357bf6bcce14a903fb3d1151e1" + integrity sha512-R2bUw+kVZFS/h1AZqBKrSgDmdmjApzgY0AlCPumopFiAlbUxE2gf+SCuBzQ0cP5hHmUmFYF5yw55T97Th5Kstg== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -7592,9 +7634,9 @@ jetifier@^1.6.2: integrity sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw== joi@^17.2.1: - version "17.10.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.10.0.tgz#04e249daa24d48fada2d34046a8262e474b1326f" - integrity sha512-hrazgRSlhzacZ69LdcKfhi3Vu13z2yFfoAzmEov3yFIJlatTdVGUW6vle1zjH8qkzdCn/qGw8rapjqsObbYXAg== + version "17.10.1" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.10.1.tgz#f908ee1617137cca5d83b91587cde80e472b5753" + integrity sha512-vIiDxQKmRidUVp8KngT8MZSOcmRVm2zV7jbMjNYWuHcJWI0bUck3nRTGQjhpPlQenIQIBC5Vp9AhcnHbWQqafw== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -8105,6 +8147,21 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + +lodash.invokemap@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz#1748cda5d8b0ef8369c4eb3ec54c21feba1f2d62" + integrity sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" @@ -8115,6 +8172,11 @@ lodash.memoize@4.x: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash.pullall@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba" + integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -8125,7 +8187,12 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.3.0: +lodash.uniqby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== + +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.3.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8912,9 +8979,9 @@ mute-stream@0.0.8, mute-stream@~0.0.4: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.12.1: - version "2.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== nanoid@^3.3.4, nanoid@^3.3.6: version "3.3.6" @@ -9436,14 +9503,14 @@ object.assign@^4.1.4: object-keys "^1.1.1" object.getownpropertydescriptors@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz#5e5c384dd209fa4efffead39e3a0512770ccc312" - integrity sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ== + version "2.1.7" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.7.tgz#7a466a356cd7da4ba8b9e94ff6d35c3eeab5d56a" + integrity sha512-PrJz0C2xJ58FNn11XV2lr4Jt5Gzl94qpy9Lu0JlfEj14z88sqbSBJCBEzdlNUCzY2gburhbrwOZ5BHCmuNUy0g== dependencies: - array.prototype.reduce "^1.0.5" + array.prototype.reduce "^1.0.6" call-bind "^1.0.2" define-properties "^1.2.0" - es-abstract "^1.21.2" + es-abstract "^1.22.1" safe-array-concat "^1.0.0" object.pick@^1.3.0: @@ -10190,6 +10257,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -10210,6 +10282,11 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -10603,14 +10680,14 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" - integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== dependencies: call-bind "^1.0.2" define-properties "^1.2.0" - functions-have-names "^1.2.3" + set-function-name "^2.0.0" regexpu-core@^5.3.1: version "5.3.2" @@ -10915,13 +10992,13 @@ rxjs@^7.5.5: dependencies: tslib "^2.1.0" -safe-array-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" - integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ== +safe-array-concat@^1.0.0, safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.2.0" + get-intrinsic "^1.2.1" has-symbols "^1.0.3" isarray "^2.0.5" @@ -11042,13 +11119,6 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== - dependencies: - lru-cache "^6.0.0" - semver@7.3.8: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" @@ -11063,18 +11133,18 @@ semver@7.5.3: dependencies: lru-cache "^6.0.0" -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.0.0, semver@^7.1.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: +semver@7.5.4, semver@^7.0.0, semver@^7.1.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -11126,6 +11196,15 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -11242,14 +11321,14 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -sirv@^1.0.7: - version "1.0.19" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" - integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== +sirv@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" + integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== dependencies: "@polka/url" "^1.0.0-next.20" mrmime "^1.0.0" - totalist "^1.0.0" + totalist "^3.0.0" sisteransi@^1.0.5: version "1.0.5" @@ -11592,32 +11671,32 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string.prototype.trim@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" - integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" @@ -11805,9 +11884,9 @@ tar@6.1.11: yallist "^4.0.0" tar@^6.1.11, tar@^6.1.2: - version "6.1.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" - integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -11875,9 +11954,9 @@ terser-webpack-plugin@^5.3.6, terser-webpack-plugin@^5.3.7: terser "^5.16.8" terser@^5.16.8: - version "5.19.3" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.3.tgz#359baeba615aef13db4b8c4d77a2aa0d8814aa9e" - integrity sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg== + version "5.19.4" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.4.tgz#941426fa482bf9b40a0308ab2b3cd0cf7c775ebd" + integrity sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -12002,10 +12081,10 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -totalist@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" - integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: version "2.5.0" @@ -12082,7 +12161,7 @@ tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" -"tslib@1 || 2", tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0: +"tslib@1 || 2", tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -12200,9 +12279,9 @@ type-check@~0.3.2: prelude-ls "~1.1.2" type-coverage-core@^2.17.2: - version "2.26.1" - resolved "https://registry.yarnpkg.com/type-coverage-core/-/type-coverage-core-2.26.1.tgz#a5a1adf78c628a5cb76e9a79ac8f48636a354864" - integrity sha512-KoGejLimF+LPr/JKdgo6fmaHIn5FJ74xCzUt3lSbcoPVDLDbqBGQQ3rizkQJmSV0R6/35iIbE16ln0atkwNE+g== + version "2.26.3" + resolved "https://registry.yarnpkg.com/type-coverage-core/-/type-coverage-core-2.26.3.tgz#47e2c8225f582d1ca9551c2bace20836b295c944" + integrity sha512-rzNdW/tClHJvsUiy787b/UX53bNh1Dn7A5KqZDQjkL3j7iKFv/KnTolxDBBgTPcK4Zn9Ab7WLrik7cXw2oZZqw== dependencies: fast-glob "3" minimatch "6 || 7 || 8 || 9" @@ -12501,6 +12580,14 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== +url@0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + urlgrey@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-1.0.0.tgz#72d2f904482d0b602e3c7fa599343d699bbe1017" @@ -12558,7 +12645,7 @@ uuid@8.3.2, uuid@^8.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^3.3.2: +uuid@^3.2.1, uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -12569,9 +12656,9 @@ uuid@^7.0.3: integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== uuid@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" - integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== v8-compile-cache@2.3.0: version "2.3.0" @@ -12678,19 +12765,26 @@ webidl-conversions@^4.0.2: integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== webpack-bundle-analyzer@^4.7.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz#fc093c4ab174fd3dcbd1c30b763f56d10141209d" - integrity sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw== + version "4.9.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d" + integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w== dependencies: "@discoveryjs/json-ext" "0.5.7" acorn "^8.0.4" acorn-walk "^8.0.0" - chalk "^4.1.0" commander "^7.2.0" + escape-string-regexp "^4.0.0" gzip-size "^6.0.0" - lodash "^4.17.20" + is-plain-object "^5.0.0" + lodash.debounce "^4.0.8" + lodash.escape "^4.0.1" + lodash.flatten "^4.4.0" + lodash.invokemap "^4.6.0" + lodash.pullall "^4.2.0" + lodash.uniqby "^4.7.0" opener "^1.5.2" - sirv "^1.0.7" + picocolors "^1.0.0" + sirv "^2.0.3" ws "^7.3.1" webpack-cli@^5.0.0: @@ -12763,9 +12857,9 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: iconv-lite "0.4.24" whatwg-fetch@^3.0.0: - version "3.6.18" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.18.tgz#2f640cdee315abced7daeaed2309abd1e44e62d4" - integrity sha512-ltN7j66EneWn5TFDO4L9inYC1D+Czsxlrw2SalgjMmEMkLfA5SIZxEFdE6QtHFiiM6Q7WL32c7AkI3w6yxM84Q== + version "3.6.19" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz#caefd92ae630b91c07345537e67f8354db470973" + integrity sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw== whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: version "2.3.0" @@ -12819,7 +12913,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.10, which-typed-array@^1.1.11: +which-typed-array@^1.1.11: version "1.1.11" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==