Skip to content

Commit

Permalink
Merge branch 'next/main' into next-main-api-v6
Browse files Browse the repository at this point in the history
  • Loading branch information
david-mcafee authored Sep 14, 2023
2 parents fe9aa7d + 448963f commit 1f745b0
Show file tree
Hide file tree
Showing 102 changed files with 1,403 additions and 1,417 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('runWithAmplifyServerContext', () => {

describe('when amplifyConfig.Auth is defined', () => {
describe('when nextServerContext is null (opt-in unauthenticated role)', () => {
it('should create auth providers with MemoryKeyValueStorage', () => {
it('should create auth providers with sharedInMemoryStorage', () => {
const operation = jest.fn();
runWithAmplifyServerContext({ operation, nextServerContext: null });
expect(
Expand Down
44 changes: 32 additions & 12 deletions packages/adapter-nextjs/__tests__/utils/getAmplifyConfig.test.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,52 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { NextConfig } from 'next';
import getConfig from 'next/config';
import { getAmplifyConfig } from '../../src/utils/getAmplifyConfig';
import { AmplifyError } from '@aws-amplify/core/internals/utils';

jest.mock('next/config');

const mockGetConfig = getConfig as jest.Mock;

describe('getAmplifyConfig', () => {
const mockAmplifyConfig = {
Auth: {
identityPoolId: '123',
userPoolId: 'abc',
userPoolWebClientId: 'def',
},
Storage: {
bucket: 'bucket',
region: 'us-east-1',
},
};

beforeEach(() => {
mockGetConfig.mockReturnValue({});
delete process.env.amplifyConfig;
});

it('should return amplifyConfig from env vars', () => {
const mockAmplifyConfig = {
Auth: {
identityPoolId: '123',
userPoolId: 'abc',
userPoolWebClientId: 'def',
},
Storage: {
bucket: 'bucket',
region: 'us-east-1',
},
};
process.env.amplifyConfig = JSON.stringify(mockAmplifyConfig);

const result = getAmplifyConfig();
expect(result).toEqual(mockAmplifyConfig);
});

it('should attempt to get amplifyConfig via getConfig provided by Next.js as a fallback', () => {
mockGetConfig.mockReturnValueOnce({
serverRuntimeConfig: {
amplifyConfig: JSON.stringify(mockAmplifyConfig),
},
});

const result = getAmplifyConfig();
expect(result).toEqual(mockAmplifyConfig);
});

it('should throw error when amplifyConfig is not found from env vars', () => {
expect(() => getAmplifyConfig()).toThrowError();
expect(() => getAmplifyConfig()).toThrow(AmplifyError);
});
});
10 changes: 10 additions & 0 deletions packages/adapter-nextjs/__tests__/withAmplify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ describe('withAmplify', () => {
env: {
amplifyConfig: JSON.stringify(mockAmplifyConfig),
},
serverRuntimeConfig: {
amplifyConfig: JSON.stringify(mockAmplifyConfig),
},
});
});

Expand All @@ -37,6 +40,9 @@ describe('withAmplify', () => {
env: {
existingKey: '123',
},
serverRuntimeConfig: {
myKey: 'myValue',
},
};
const result = withAmplify(nextConfig, mockAmplifyConfig);

Expand All @@ -45,6 +51,10 @@ describe('withAmplify', () => {
existingKey: '123',
amplifyConfig: JSON.stringify(mockAmplifyConfig),
},
serverRuntimeConfig: {
myKey: 'myValue',
amplifyConfig: JSON.stringify(mockAmplifyConfig),
},
});
});
});
43 changes: 43 additions & 0 deletions packages/adapter-nextjs/src/runWithAmplifyServerContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,49 @@ import {
runWithAmplifyServerContext as runWithAmplifyServerContextCore,
} from 'aws-amplify/internals/adapter-core';

/**
* Runs the {@link operation} with the the context created from the {@link nextServerContext}.
*
* @param input The input to call the {@link runWithAmplifyServerContext}.
* @param input.nextServerContext The Next.js server context. It varies depends
* where the {@link runWithAmplifyServerContext} is being called.
* - In the [middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware):
* the context consists of an instance of the `NextRequest` and an instance
* of the `NextResponse`.
* - In a [Page server component](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#pages):
* the context is the [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies)
* function provided by Next.js.
* - In a [Route Handler](https://nextjs.org/docs/app/building-your-application/routing/route-handlers):
* the context can be the [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies)
* function or a combination of an instance of the `NextRequest` and an instance
* of the `NextResponse`.
* - In a [Server Action](https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations#how-server-actions-work):
* the context is the [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies)
* function provided by Next.js.
* @param input.operation The function that contains the business logic calling
* Amplify APIs. It expects a `contextSpec` parameter.
* @returns The result returned by the {@link operation}.
* @example
* // Use the `fetchAuthSession` API in the Next.js `middleware`.
* import { NextRequest, NextResponse } from "next/server";
* import { fetchAuthSession } from "aws-amplify/auth/server";
* import { runWithAmplifyServerContext } from "@aws-amplify/adapter-nextjs";
* export async function middleware(request: NextRequest) {
* const response = NextResponse.next();
* const authenticated = await runWithAmplifyServerContext({
* nextServerContext: { request, response },
* operation: async (contextSpec) => {
* const session = await fetchAuthSession(contextSpec);
* return session.tokens !== undefined;
* }
* });
* if (authenticated) {
* return response;
* }
* return NextResponse.redirect(new URL('/sign-in', request.url));
* }
*/
export const runWithAmplifyServerContext: NextServer.RunOperationWithContext =
async ({ nextServerContext, operation }) => {
// 1. get amplify config from env vars
Expand Down
6 changes: 6 additions & 0 deletions packages/adapter-nextjs/src/types/NextServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,18 @@ export namespace NextServer {
response: NextGetServerSidePropsContext['res'];
};

/**
* The union of possible Next.js app server context types.
*/
export type Context =
| NextRequestAndNextResponseContext
| NextRequestAndResponseContext
| ServerComponentContext
| GetServerSidePropsContext;

/**
* The interface of the input of {@link RunOperationWithContext}.
*/
export interface RunWithContextInput<OperationResult> {
nextServerContext: Context | null;
operation: (
Expand Down
12 changes: 11 additions & 1 deletion packages/adapter-nextjs/src/utils/getAmplifyConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@

import { ResourcesConfig } from 'aws-amplify';
import { AmplifyServerContextError } from '@aws-amplify/core/internals/adapter-core';
import getConfig from 'next/config';

export const getAmplifyConfig = (): ResourcesConfig => {
const configStr = process.env.amplifyConfig;
let configStr = process.env.amplifyConfig;

// With a Next.js app that uses the Pages Router, the key-value pairs
// listed under the `env` field in the `next.config.js` is not accessible
// via process.env.<key> at some occasion. Using the following as a fallback
// See: https://github.com/vercel/next.js/issues/39299
if (!configStr) {
const { serverRuntimeConfig } = getConfig();
configStr = serverRuntimeConfig?.amplifyConfig;
}

if (!configStr) {
throw new AmplifyServerContextError({
Expand Down
8 changes: 7 additions & 1 deletion packages/adapter-nextjs/src/withAmplify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,15 @@ export const withAmplify = (
nextConfig: NextConfig,
amplifyConfig: ResourcesConfig
) => {
const configStr = JSON.stringify(amplifyConfig);
nextConfig.env = {
...nextConfig.env,
amplifyConfig: JSON.stringify(amplifyConfig),
amplifyConfig: configStr,
};

nextConfig.serverRuntimeConfig = {
...nextConfig.serverRuntimeConfig,
amplifyConfig: configStr,
};

return nextConfig;
Expand Down
8 changes: 7 additions & 1 deletion packages/adapter-nextjs/tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
"jsRules": {},
"rules": {
"prefer-const": true,
"max-line-length": [true, 120],
"max-line-length": [
true,
{
"limit": 120,
"ignore-pattern": "^//|^ *"
}
],
"no-empty-interface": true,
"no-var-keyword": true,
"object-literal-shorthand": true,
Expand Down
7 changes: 6 additions & 1 deletion packages/analytics/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export * from './providers/pinpoint';
export {
record,
identifyUser,
RecordInput,
IdentifyUserInput,
} from './providers/pinpoint';
export { AnalyticsError } from './errors';
47 changes: 41 additions & 6 deletions packages/analytics/src/providers/pinpoint/apis/identifyUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,56 @@ import { AnalyticsAction } from '@aws-amplify/core/internals/utils';
import { updateEndpoint } from '@aws-amplify/core/internals/providers/pinpoint';
import { AnalyticsValidationErrorCode } from '../../../errors';
import { getAnalyticsUserAgentString } from '../../../utils/userAgent';
import { IdentifyUserParameters, UpdateEndpointException } from '../types';
import { IdentifyUserInput, UpdateEndpointException } from '../types';
import { resolveConfig, resolveCredentials } from '../utils';

/**
* Identifies the current user with Pinpoint.
* Sends information about a user to Pinpoint. Sending user information allows you to associate a user to their user
* profile and activities or actions in your application. Activity can be tracked across devices & platforms by using
* the same `userId`.
*
* @param {IdentifyUserParameters} params parameters used to construct requests sent to Pinpoint's UpdateEndpoint API.
* @param {IdentifyUserParameters} params The input object used to construct requests sent to Pinpoint's UpdateEndpoint
* API.
*
* @throws An {@link UpdateEndpointException} when the underlying Pinpoint service returns an error.
* @throws An {@link AnalyticsValidationErrorCode} when API call parameters are invalid.
* @throws service: {@link UpdateEndpointException} - Thrown when the underlying Pinpoint service returns an error.
* @throws validation: {@link AnalyticsValidationErrorCode} - Thrown when the provided parameters or library
* configuration is incorrect.
*
* @returns A promise that will resolve when the operation is complete.
*
* @example
* ```ts
* // Identify a user with Pinpoint
* await identifyUser({
* userId,
* userProfile: {
* attributes: {
* email: [userEmail],
* },
* }
* });
* ```
*
* @example
* ```ts
* // Identify a user with Pinpoint with some additional demographics
* await identifyUser({
* userId,
* userProfile: {
* attributes: {
* email: [userEmail],
* },
* demographic: {
* platform: 'ios',
* timezone: 'America/Los_Angeles'
* }
* }
* });
*/
export const identifyUser = async ({
userId,
userProfile,
}: IdentifyUserParameters): Promise<void> => {
}: IdentifyUserInput): Promise<void> => {
const { credentials, identityId } = await resolveCredentials();
const { appId, region } = resolveConfig();
updateEndpoint({
Expand Down
39 changes: 32 additions & 7 deletions packages/analytics/src/providers/pinpoint/apis/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,46 @@ import {
assertValidationError,
} from '../../../errors';
import { getAnalyticsUserAgentString } from '../../../utils/userAgent';
import { RecordParameters } from '../types/parameters';
import { RecordInput } from '../types';
import { resolveConfig, resolveCredentials } from '../utils';

const logger = new Logger('Analytics');

/**
* Sends an event to Pinpoint.
* Records an Analytic event to Pinpoint. Events will be buffered and periodically sent to Pinpoint.
*
* @param {RecordParameters} params Parameters used to construct the request.
* @param {RecordInput} params The input object used to construct the request.
*
* @throws An {@link AnalyticsValidationErrorCode} when there is an error in the parameters or configuration.
*
* @returns A promise that will resolve when the request is complete.
* @throws validation: {@link AnalyticsValidationErrorCode} - Thrown when the provided parameters or library
* configuration is incorrect.
*
* @example
* ```ts
* // Send an event to Pinpoint
* record({
* event: {
* name: eventName,
* }
* })
* ```
*
* @example
* ```ts
* // Send an event to Pinpoint with metrics & custom attributes
* record({
* event: {
* name: eventName,
* attributes: {
* 'my-attribute': attributeValue
* },
* metrics: {
* 'my-metric': metricValue
* }
* }
* })
* ```
*/
export const record = ({ event }: RecordParameters): void => {
export const record = ({ event }: RecordInput): void => {
const { appId, region } = resolveConfig();

assertValidationError(!!event, AnalyticsValidationErrorCode.NoEvent);
Expand Down
9 changes: 8 additions & 1 deletion packages/analytics/src/providers/pinpoint/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export * from './apis';
export {
record,
identifyUser
} from './apis';
export {
RecordInput,
IdentifyUserInput
} from './types/inputs';
5 changes: 4 additions & 1 deletion packages/analytics/src/providers/pinpoint/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

export { UpdateEndpointException } from './errors';
export { IdentifyUserParameters } from './parameters';
export {
RecordInput,
IdentifyUserInput
} from './inputs';
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import { UserProfile } from '@aws-amplify/core';
import { PinpointAnalyticsEvent } from '@aws-amplify/core/internals/providers/pinpoint';

export type RecordParameters = {
export type RecordInput = {
/**
* An event to send to the default Analytics provider.
*/
event: PinpointAnalyticsEvent;
};

export type IdentifyUserParameters = {
export type IdentifyUserInput = {
/**
* A User ID associated to the current device.
*/
Expand Down
Loading

0 comments on commit 1f745b0

Please sign in to comment.