Skip to content

Commit

Permalink
Merge pull request #5093 from ustaxcourt/devex-10415-convert-apigateway
Browse files Browse the repository at this point in the history
Devex 10415: AWS v2 -> v3 conversion: ApiGatewayManagementApiClient
  • Loading branch information
jimlerza authored Aug 1, 2024
2 parents f6c563c + 9d98ef1 commit 0592708
Show file tree
Hide file tree
Showing 13 changed files with 5,700 additions and 1,776 deletions.
7,320 changes: 5,636 additions & 1,684 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@18f/us-federal-holidays": "4.0.0",
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-sdk/client-apigatewaymanagementapi": "3.616.0",
"@aws-sdk/client-api-gateway": "3.616.0",
"@aws-sdk/client-apigatewayv2": "3.616.0",
"@aws-sdk/client-cloudfront": "3.616.0",
Expand Down
14 changes: 2 additions & 12 deletions web-api/src/applicationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { getDocumentGenerators } from './getDocumentGenerators';
import { getDynamoClient } from '@web-api/persistence/dynamo/getDynamoClient';
import { getEmailClient } from './persistence/messages/getEmailClient';
import { getEnvironment, getUniqueId } from '../../shared/src/sharedAppContext';
import { getNotificationClient } from '@web-api/notifications/getNotificationClient';
import { getNotificationService } from '@web-api/notifications/getNotificationService';
import { getPersistenceGateway } from './getPersistenceGateway';
import { getStorageClient } from '@web-api/persistence/s3/getStorageClient';
Expand All @@ -72,7 +73,6 @@ import { sendSetTrialSessionCalendarEvent } from './persistence/messages/sendSet
import { sendSlackNotification } from './dispatchers/slack/sendSlackNotification';
import { worker } from '@web-api/gateways/worker/worker';
import { workerLocal } from '@web-api/gateways/worker/workerLocal';
import AWS from 'aws-sdk';

import axios from 'axios';
import pug from 'pug';
Expand Down Expand Up @@ -231,17 +231,7 @@ export const createApplicationContext = (
getNodeSass: () => {
return sass;
},
getNotificationClient: ({ endpoint }) => {
if (endpoint.includes('localhost')) {
endpoint = 'http://localhost:3011';
}
return new AWS.ApiGatewayManagementApi({
endpoint,
httpOptions: {
timeout: 900000, // 15 minutes
},
});
},
getNotificationClient,
getNotificationGateway: () => ({
retrySendNotificationToConnections,
saveRequestResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { ServerApplicationContext } from '@web-api/applicationContext';

/**
* sendMaintenanceNotificationsInteractor
*
* @param {object} applicationContext the application context
* @param {object} providers the providers object
* @param {string} providers.maintenanceMode true or false depending on whether we are turning maintenance mode on or off
*/
export const sendMaintenanceNotificationsInteractor = async (
applicationContext: ServerApplicationContext,
{ maintenanceMode }: { maintenanceMode: boolean },
Expand Down
14 changes: 14 additions & 0 deletions web-api/src/notifications/getNotificationClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApiGatewayManagementApiClient } from '@aws-sdk/client-apigatewaymanagementapi';
import { NodeHttpHandler } from '@smithy/node-http-handler';

export const getNotificationClient = ({ endpoint }: { endpoint: string }) => {
if (endpoint && endpoint.includes('localhost')) {
endpoint = 'http://localhost:3011';
}
return new ApiGatewayManagementApiClient({
endpoint,
requestHandler: new NodeHttpHandler({
requestTimeout: 900000, // 15 minutes
}),
});
};
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
import { applicationContext } from '../../../shared/src/business/test/createTestApplicationContext';
import { GoneException } from '@aws-sdk/client-apigatewaymanagementapi';
import { applicationContext } from '@shared/business/test/createTestApplicationContext';
import { retrySendNotificationToConnections } from './retrySendNotificationToConnections';

const mockConnections = [
{
clientConnectionId: 'aaaa',
connectionId: '1111',
endpoint: 'endpoint-01',
pk: 'connections-01',
sk: 'sk-01',
userId: 'f911636b-2d69-4dd2-a01a-2622878d070c',
},
{
clientConnectionId: 'bbbb',
connectionId: '2222',
endpoint: 'endpoint-02',
pk: 'connections-02',
sk: 'sk-02',
userId: '324cd0c7-1d82-4916-a0fa-965255f7e087',
},
{
clientConnectionId: 'cccc',
connectionId: '3333',
endpoint: 'endpoint-03',
pk: 'connections-03',
sk: 'sk-03',
userId: '029c5833-9a79-4a3e-91ae-7eb9c7dd0f84',
},
{
clientConnectionId: 'dddd',
connectionId: '4444',
endpoint: 'endpoint-04',
pk: 'connections-04',
sk: 'sk-04',
userId: '1dca6ea7-66e8-473b-9790-c2cb1b822464',
},
];

const mockMessageStringified = JSON.stringify('hello, computer');

const notificationError: NotificationError = new Error(
'could not get notification client',
);
notificationError.statusCode = 410;
const mockErrorMessage = 'ws connection is gone';
const notificationError = new GoneException({
$metadata: { httpStatusCode: 410 },
message: mockErrorMessage,
});

describe('retrySendNotificationToConnections', () => {
it('should send notification to connections', async () => {
Expand Down Expand Up @@ -68,7 +78,7 @@ describe('retrySendNotificationToConnections', () => {
it('does not call client.delete if deleteGoneConnections is false', async () => {
await applicationContext
.getNotificationGateway()
.sendNotificationToConnection.mockRejectedValueOnce(notificationError);
.sendNotificationToConnection.mockRejectedValue(notificationError);

await retrySendNotificationToConnections({
applicationContext,
Expand All @@ -83,7 +93,7 @@ describe('retrySendNotificationToConnections', () => {
});

it('rethrows and logs exception for statusCode not 410', async () => {
notificationError.statusCode = 400;
notificationError['$metadata'].httpStatusCode = 400;
await applicationContext
.getNotificationGateway()
.sendNotificationToConnection.mockRejectedValue(notificationError);
Expand All @@ -94,7 +104,7 @@ describe('retrySendNotificationToConnections', () => {
connections: mockConnections,
messageStringified: mockMessageStringified,
}),
).rejects.toThrow('could not get notification client');
).rejects.toThrow(mockErrorMessage);

expect(applicationContext.logger.error).toHaveBeenCalled();
});
Expand Down
14 changes: 3 additions & 11 deletions web-api/src/notifications/retrySendNotificationToConnections.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
// eslint-disable-next-line spellcheck/spell-checker
import { Connection } from '@web-api/notifications/sendNotificationToConnection';
import { ServerApplicationContext } from '@web-api/applicationContext';

/**
* retrySendNotificationToConnections
*
* @param {object} providers the providers object
* @param {object} providers.applicationContext the application context
* @param {object} providers.connections the connections
* @param {string} providers.messageStringified the messageStringified
*/
export const retrySendNotificationToConnections = async ({
applicationContext,
connections,
Expand All @@ -34,10 +25,11 @@ export const retrySendNotificationToConnections = async ({
messageStringified,
});
break;
} catch (err) {
} catch (err: any) {
if (retryCount >= maxRetries && deleteGoneConnections) {
const AWSWebSocketConnectionGone = 410;
if (err.statusCode === AWSWebSocketConnectionGone) {

if (err['$metadata'].httpStatusCode === AWSWebSocketConnectionGone) {
try {
await applicationContext
.getPersistenceGateway()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ const mockConnection = {

const mockMessage = 'hello, computer';

const postToConnection = jest
.fn()
.mockReturnValue({ promise: () => Promise.resolve('ok') });
const send = jest.fn().mockResolvedValue('ok');

beforeEach(() => {
applicationContext.getNotificationClient.mockImplementation(() => {
return { postToConnection };
return { send };
});

applicationContext
Expand All @@ -31,7 +29,7 @@ it('should send notification to connection', async () => {
messageStringified: mockMessage,
});

expect(postToConnection.mock.calls[0][0]).toMatchObject({
expect(send.mock.calls[0][0].input).toMatchObject({
ConnectionId: mockConnection.connectionId,
Data: mockMessage,
});
Expand Down
30 changes: 14 additions & 16 deletions web-api/src/notifications/sendNotificationToConnection.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import {
type ApiGatewayManagementApiClient,
PostToConnectionCommand,
} from '@aws-sdk/client-apigatewaymanagementapi';
import { ServerApplicationContext } from '@web-api/applicationContext';

export type Connection = {
clientConnectionId: string;
connectionId: string;
endpoint: string;
pk: string;
sk: string;
userId: string;
};

/**
* sendNotificationToConnection
*
* @param {object} providers the providers object
* @param {object} providers.applicationContext the application context
* @param {object} providers.connection the connection to send the message to
* @param {string} providers.messageStringified the message
*/
export const sendNotificationToConnection = async ({
applicationContext,
connection,
Expand All @@ -26,14 +24,14 @@ export const sendNotificationToConnection = async ({
}) => {
const { connectionId, endpoint } = connection;

const notificationClient = applicationContext.getNotificationClient({
endpoint,
const notificationClient: ApiGatewayManagementApiClient =
applicationContext.getNotificationClient({
endpoint,
});
const postToConnectionCommand = new PostToConnectionCommand({
ConnectionId: connectionId,
Data: messageStringified,
});

await notificationClient
.postToConnection({
ConnectionId: connectionId,
Data: messageStringified,
})
.promise();
await notificationClient.send(postToConnectionCommand);
};
6 changes: 2 additions & 4 deletions web-api/src/notifications/sendNotificationToUser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,11 @@ describe('sendNotificationToUser', () => {
];
const mockMessage = 'hello, computer';

const postToConnection = jest
.fn()
.mockReturnValue({ promise: () => Promise.resolve('ok') });
const send = jest.fn().mockResolvedValue('ok');

beforeEach(() => {
applicationContext.getNotificationClient.mockImplementation(() => {
return { postToConnection };
return { send };
});

applicationContext
Expand Down
9 changes: 0 additions & 9 deletions web-api/src/notifications/sendNotificationToUser.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import { ServerApplicationContext } from '@web-api/applicationContext';

/**
* sendNotificationToUser
*
* @param {object} providers the providers object
* @param {object} providers.applicationContext the application context
* @param {object} providers.message the message
* @param {string} providers.userId the id of the user
* @returns {Promise} upon completion of notification delivery
*/
export const sendNotificationToUser = async ({
applicationContext,
clientConnectionId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { queryFull } from '../../dynamodbClientService';
import { Connection } from '@web-api/notifications/sendNotificationToConnection';
import { queryFull } from '@web-api/persistence/dynamodbClientService';

/**
* getAllWebSocketConnections
*
* @param {object} providers the providers object
* @param {object} providers.applicationContext the application context
* @returns {Promise} the promise of the call to persistence
*/
export const getAllWebSocketConnections = ({
applicationContext,
}: {
applicationContext: IApplicationContext;
}) =>
}): Promise<Connection[]> =>
queryFull({
ExpressionAttributeNames: {
'#gsi1pk': 'gsi1pk',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { query } from '../../dynamodbClientService';
import { Connection } from '@web-api/notifications/sendNotificationToConnection';
import { queryFull } from '@web-api/persistence/dynamodbClientService';

/**
* getWebSocketConnectionsByUserId
*
* @param {object} providers the providers object
* @param {object} providers.applicationContext the application context
* @param {object} providers.userId the user id
* @returns {Promise} the promise of the call to persistence
*/
export const getWebSocketConnectionsByUserId = ({
applicationContext,
userId,
}: {
applicationContext: IApplicationContext;
userId: string;
}) =>
query({
}): Promise<Connection[]> =>
queryFull({
ExpressionAttributeNames: {
'#pk': 'pk',
'#sk': 'sk',
Expand Down

0 comments on commit 0592708

Please sign in to comment.