@the_pixelport/aws-lambda-graphql
Version:
Apollo server for AWS Lambda with WebSocket subscriptions support over API Gateway v1 + v2
184 lines • 8.51 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DynamoDBConnectionManager = void 0;
const assert_1 = __importDefault(require("assert"));
const client_apigatewaymanagementapi_1 = require("@aws-sdk/client-apigatewaymanagementapi");
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
const errors_1 = require("./errors");
const helpers_1 = require("./helpers");
const isTTLExpired_1 = require("./helpers/isTTLExpired");
const DEFAULT_TTL = 7200;
/**
* DynamoDBConnectionManager
*
* Stores connections in DynamoDB table (default table name is Connections, you can override that)
*/
class DynamoDBConnectionManager {
constructor({ apiGatewayManager, connectionsTable = 'Connections', dynamoDbClient, subscriptions, ttl = DEFAULT_TTL, debug = false, }) {
this.hydrateConnection = async (connectionId, options) => {
const { retryCount = 0, timeout = 50 } = options || {};
// if connection is not found, throw so we can terminate connection
let connection;
for (let i = 0; i <= retryCount; i++) {
const result = await this.db.send(new lib_dynamodb_1.GetCommand({
TableName: this.connectionsTable,
Key: {
id: connectionId,
},
}));
if (result.Item) {
// Jump out of loop
connection = result.Item;
break;
}
// wait for another round
await new Promise((r) => setTimeout(r, timeout));
}
if (!connection || (0, isTTLExpired_1.isTTLExpired)(connection.ttl)) {
throw new errors_1.ConnectionNotFoundError(`Connection ${connectionId} not found`);
}
return connection;
};
this.setConnectionData = async (data, { id }) => {
await this.db.send(new lib_dynamodb_1.UpdateCommand({
TableName: this.connectionsTable,
Key: {
id,
},
UpdateExpression: 'set #data = :data',
ExpressionAttributeValues: {
':data': data,
},
ExpressionAttributeNames: {
'#data': 'data',
},
}));
};
this.registerConnection = async ({ connectionId, endpoint, }) => {
const connection = {
id: connectionId,
data: { endpoint, context: {}, isInitialized: false },
};
console.log('[DEBUG] DynamoDBConnectionManager.registerConnection:', {
connectionId,
endpoint,
connectionData: connection.data,
});
if (this.debug)
console.log(`Connected ${connection.id}`, connection.data);
await this.db.send(new lib_dynamodb_1.PutCommand({
TableName: this.connectionsTable,
Item: {
createdAt: new Date().toString(),
id: connection.id,
data: connection.data,
...(this.ttl === false || this.ttl == null
? {}
: {
ttl: (0, helpers_1.computeTTL)(this.ttl),
}),
},
}));
return connection;
};
this.sendToConnection = async (connection, payload) => {
var _a;
console.log('[DEBUG] DynamoDBConnectionManager.sendToConnection:', {
connectionId: connection.id,
endpoint: connection.data.endpoint,
payloadLength: payload.length,
});
try {
await this.createApiGatewayManager(connection.data.endpoint).send(new client_apigatewaymanagementapi_1.PostToConnectionCommand({
ConnectionId: connection.id,
Data: payload,
}));
}
catch (e) {
// this is stale connection
// remove it from DB
if (e && ((_a = e.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode) === 410) {
await this.unregisterConnection(connection);
}
else {
throw e;
}
}
};
this.unregisterConnection = async ({ id }) => {
await Promise.all([
this.db.send(new lib_dynamodb_1.DeleteCommand({
Key: {
id,
},
TableName: this.connectionsTable,
})),
this.subscriptions.unsubscribeAllByConnectionId(id),
]);
};
this.closeConnection = async ({ id, data }) => {
if (this.debug)
console.log('Disconnected ', id);
await this.createApiGatewayManager(data.endpoint).send(new client_apigatewaymanagementapi_1.DeleteConnectionCommand({ ConnectionId: id }));
};
assert_1.default.ok(typeof connectionsTable === 'string', 'Please provide connectionsTable as a string');
assert_1.default.ok(typeof subscriptions === 'object', 'Please provide subscriptions to manage subscriptions.');
assert_1.default.ok(ttl === false || (typeof ttl === 'number' && ttl > 0), 'Please provide ttl as a number greater than 0 or false to turn it off');
assert_1.default.ok(dynamoDbClient == null || typeof dynamoDbClient === 'object', 'Please provide dynamoDbClient as an instance of DynamoDBDocumentClient or DynamoDBClient');
assert_1.default.ok(apiGatewayManager == null || typeof apiGatewayManager === 'object', 'Please provide apiGatewayManager as an instance of ApiGatewayManagementApiClient');
assert_1.default.ok(typeof debug === 'boolean', 'Please provide debug as a boolean');
this.apiGatewayManager = apiGatewayManager;
this.connectionsTable = connectionsTable;
// Handle both DynamoDBDocumentClient and DynamoDBClient
// Always ensure proper marshalling options are applied
if (dynamoDbClient) {
if (dynamoDbClient instanceof lib_dynamodb_1.DynamoDBDocumentClient) {
// If a DynamoDBDocumentClient is passed, warn the user about potential marshalling issues
console.warn('[aws-lambda-graphql] Warning: DynamoDBDocumentClient passed directly. Please ensure it was created with marshallOptions: { convertClassInstanceToMap: true, removeUndefinedValues: true } to avoid marshalling errors.');
this.db = dynamoDbClient;
}
else {
this.db = lib_dynamodb_1.DynamoDBDocumentClient.from(dynamoDbClient, {
marshallOptions: {
convertClassInstanceToMap: true,
removeUndefinedValues: true,
},
});
}
}
else {
this.db = lib_dynamodb_1.DynamoDBDocumentClient.from(new client_dynamodb_1.DynamoDBClient({}), {
marshallOptions: {
convertClassInstanceToMap: true,
removeUndefinedValues: true,
},
});
}
this.subscriptions = subscriptions;
this.ttl = ttl;
this.debug = debug;
}
/**
* Creates api gateway manager
*
* If custom api gateway manager is provided, uses it instead
*/
createApiGatewayManager(endpoint) {
console.log('[DEBUG] DynamoDBConnectionManager.createApiGatewayManager:', {
endpoint,
isValidUrl: endpoint.startsWith('https://'),
customManagerExists: !!this.apiGatewayManager,
});
if (this.apiGatewayManager) {
return this.apiGatewayManager;
}
this.apiGatewayManager = new client_apigatewaymanagementapi_1.ApiGatewayManagementApiClient({ endpoint });
return this.apiGatewayManager;
}
}
exports.DynamoDBConnectionManager = DynamoDBConnectionManager;
//# sourceMappingURL=DynamoDBConnectionManager.js.map