diff --git a/packages/storage/__tests__/internals/apis/getUrl.test.ts b/packages/storage/__tests__/internals/apis/getUrl.test.ts new file mode 100644 index 00000000000..5ec4909fa16 --- /dev/null +++ b/packages/storage/__tests__/internals/apis/getUrl.test.ts @@ -0,0 +1,75 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { AmplifyClassV6 } from '@aws-amplify/core'; + +import { getUrl as advancedGetUrl } from '../../../src/internals'; +import { getUrl as getUrlInternal } from '../../../src/providers/s3/apis/internal/getUrl'; + +jest.mock('../../../src/providers/s3/apis/internal/getUrl'); +const mockedGetUrlInternal = jest.mocked(getUrlInternal); + +const MOCK_URL = new URL('https://s3.aws/mock-presigned-url'); +const MOCK_DATE = new Date(); +MOCK_DATE.setMonth(MOCK_DATE.getMonth() + 1); + +describe('getUrl (internal)', () => { + beforeEach(() => { + mockedGetUrlInternal.mockResolvedValue({ + url: MOCK_URL, + expiresAt: MOCK_DATE, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should pass through advanced options to the internal getUrl', async () => { + const useAccelerateEndpoint = true; + const validateObjectExistence = false; + const expiresIn = 300; // seconds + const contentDisposition = 'inline; filename="example.jpg"'; + const contentType = 'image/jpeg'; + const bucket = { bucketName: 'bucket', region: 'us-east-1' }; + const locationCredentialsProvider = async () => ({ + credentials: { + accessKeyId: 'akid', + secretAccessKey: 'secret', + sessionToken: 'token', + expiration: new Date(), + }, + }); + const result = await advancedGetUrl({ + path: 'input/path/to/mock/object', + options: { + useAccelerateEndpoint, + bucket, + validateObjectExistence, + expiresIn, + contentDisposition, + contentType, + locationCredentialsProvider, + }, + }); + expect(mockedGetUrlInternal).toHaveBeenCalledTimes(1); + expect(mockedGetUrlInternal).toHaveBeenCalledWith( + expect.any(AmplifyClassV6), + { + path: 'input/path/to/mock/object', + options: { + useAccelerateEndpoint, + bucket, + validateObjectExistence, + expiresIn, + contentDisposition, + contentType, + locationCredentialsProvider, + }, + }, + ); + expect(result).toEqual({ + url: MOCK_URL, + expiresAt: MOCK_DATE, + }); + }); +}); diff --git a/packages/storage/src/internals/apis/getUrl.ts b/packages/storage/src/internals/apis/getUrl.ts new file mode 100644 index 00000000000..40a8ee2d0d1 --- /dev/null +++ b/packages/storage/src/internals/apis/getUrl.ts @@ -0,0 +1,30 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Amplify } from '@aws-amplify/core'; + +import { getUrl as getUrlInternal } from '../../providers/s3/apis/internal/getUrl'; +import { GetUrlInput } from '../types/inputs'; +import { GetUrlOutput } from '../types/outputs'; + +/** + * @internal + */ +export function getUrl(input: GetUrlInput) { + return getUrlInternal(Amplify, { + path: input.path, + options: { + useAccelerateEndpoint: input?.options?.useAccelerateEndpoint, + bucket: input?.options?.bucket, + validateObjectExistence: input?.options?.validateObjectExistence, + expiresIn: input?.options?.expiresIn, + contentDisposition: input?.options?.contentDisposition, + contentType: input?.options?.contentType, + + // Advanced options + locationCredentialsProvider: input?.options?.locationCredentialsProvider, + }, + // Type casting is necessary because `getPropertiesInternal` supports both Gen1 and Gen2 signatures, but here + // given in input can only be Gen2 signature, the return can only ben Gen2 signature. + }) as Promise; +} diff --git a/packages/storage/src/internals/index.ts b/packages/storage/src/internals/index.ts index a514ad88547..9988969ef0b 100644 --- a/packages/storage/src/internals/index.ts +++ b/packages/storage/src/internals/index.ts @@ -12,6 +12,7 @@ export { GetDataAccessInput, ListCallerAccessGrantsInput, GetPropertiesInput, + GetUrlInput, CopyInput, RemoveInput, } from './types/inputs'; @@ -19,12 +20,14 @@ export { GetDataAccessOutput, ListCallerAccessGrantsOutput, GetPropertiesOutput, + GetUrlOutput, RemoveOutput, } from './types/outputs'; export { getDataAccess } from './apis/getDataAccess'; export { listCallerAccessGrants } from './apis/listCallerAccessGrants'; export { getProperties } from './apis/getProperties'; +export { getUrl } from './apis/getUrl'; export { remove } from './apis/remove'; /* diff --git a/packages/storage/src/internals/types/inputs.ts b/packages/storage/src/internals/types/inputs.ts index f8a36943066..3f224fc46fb 100644 --- a/packages/storage/src/internals/types/inputs.ts +++ b/packages/storage/src/internals/types/inputs.ts @@ -9,6 +9,7 @@ import { import { CopyWithPathInput, GetPropertiesWithPathInput, + GetUrlWithPathInput, RemoveWithPathInput, } from '../../providers/s3'; @@ -58,6 +59,16 @@ export type GetPropertiesInput = ExtendInputWithAdvancedOptions< } >; +/** + * @internal + */ +export type GetUrlInput = ExtendInputWithAdvancedOptions< + GetUrlWithPathInput, + { + locationCredentialsProvider?: CredentialsProvider; + } +>; + /** * @internal */ diff --git a/packages/storage/src/internals/types/outputs.ts b/packages/storage/src/internals/types/outputs.ts index cf1e96acfe2..370d3a18bcf 100644 --- a/packages/storage/src/internals/types/outputs.ts +++ b/packages/storage/src/internals/types/outputs.ts @@ -3,6 +3,7 @@ import { GetPropertiesWithPathOutput, + GetUrlWithPathOutput, RemoveWithPathOutput, } from '../../providers/s3/types'; @@ -23,6 +24,11 @@ export type GetDataAccessOutput = LocationCredentials; */ export type GetPropertiesOutput = GetPropertiesWithPathOutput; +/** + * @internal + */ +export type GetUrlOutput = GetUrlWithPathOutput; + /** * @internal */ diff --git a/packages/storage/src/providers/s3/apis/internal/getUrl.ts b/packages/storage/src/providers/s3/apis/internal/getUrl.ts index 98e7a198cc3..6897a9a5d64 100644 --- a/packages/storage/src/providers/s3/apis/internal/getUrl.ts +++ b/packages/storage/src/providers/s3/apis/internal/getUrl.ts @@ -4,12 +4,7 @@ import { AmplifyClassV6 } from '@aws-amplify/core'; import { StorageAction } from '@aws-amplify/core/internals/utils'; -import { - GetUrlInput, - GetUrlOutput, - GetUrlWithPathInput, - GetUrlWithPathOutput, -} from '../../types'; +import { GetUrlInput, GetUrlOutput, GetUrlWithPathOutput } from '../../types'; import { StorageValidationErrorCode } from '../../../../errors/types/validation'; import { getPresignedGetObjectUrl } from '../../utils/client/s3data'; import { @@ -23,12 +18,14 @@ import { STORAGE_INPUT_KEY, } from '../../utils/constants'; import { constructContentDisposition } from '../../utils/constructContentDisposition'; +// TODO: Remove this interface when we move to public advanced APIs. +import { GetUrlInput as GetUrlWithPathInputWithAdvancedOptions } from '../../../../internals'; import { getProperties } from './getProperties'; export const getUrl = async ( amplify: AmplifyClassV6, - input: GetUrlInput | GetUrlWithPathInput, + input: GetUrlInput | GetUrlWithPathInputWithAdvancedOptions, ): Promise => { const { options: getUrlOptions } = input; const { s3Config, keyPrefix, bucket, identityId } =