@jaypie/constructs 1.2.18 → 1.2.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/JaypieDynamoDb.d.ts +1 -1
- package/dist/cjs/JaypieWebSocket.d.ts +115 -0
- package/dist/cjs/JaypieWebSocketLambda.d.ts +26 -0
- package/dist/cjs/JaypieWebSocketTable.d.ts +100 -0
- package/dist/cjs/__tests__/JaypieWebSocket.spec.d.ts +1 -0
- package/dist/cjs/index.cjs +419 -40
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +3 -0
- package/dist/esm/JaypieDynamoDb.d.ts +1 -1
- package/dist/esm/JaypieWebSocket.d.ts +115 -0
- package/dist/esm/JaypieWebSocketLambda.d.ts +26 -0
- package/dist/esm/JaypieWebSocketTable.d.ts +100 -0
- package/dist/esm/__tests__/JaypieWebSocket.spec.d.ts +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +376 -2
- package/dist/esm/index.js.map +1 -1
- package/package.json +3 -3
package/dist/cjs/index.d.ts
CHANGED
|
@@ -29,4 +29,7 @@ export { JaypieStack, JaypieStackProps } from "./JaypieStack";
|
|
|
29
29
|
export { JaypieStaticWebBucket, JaypieStaticWebBucketProps, } from "./JaypieStaticWebBucket";
|
|
30
30
|
export { JaypieTraceSigningKeySecret } from "./JaypieTraceSigningKeySecret";
|
|
31
31
|
export { JaypieWebDeploymentBucket } from "./JaypieWebDeploymentBucket";
|
|
32
|
+
export { JaypieWebSocket, JaypieWebSocketProps } from "./JaypieWebSocket";
|
|
33
|
+
export { JaypieWebSocketLambda } from "./JaypieWebSocketLambda";
|
|
34
|
+
export { JaypieWebSocketTable, JaypieWebSocketTableProps, } from "./JaypieWebSocketTable";
|
|
32
35
|
export * from "./helpers";
|
|
@@ -93,7 +93,7 @@ export declare class JaypieDynamoDb extends Construct implements dynamodb.ITable
|
|
|
93
93
|
* Default Jaypie GSI definitions from @jaypie/fabric.
|
|
94
94
|
* Pass to `indexes` prop to create all standard GSIs.
|
|
95
95
|
*/
|
|
96
|
-
static readonly DEFAULT_INDEXES:
|
|
96
|
+
static readonly DEFAULT_INDEXES: IndexDefinition[];
|
|
97
97
|
private readonly _table;
|
|
98
98
|
constructor(scope: Construct, id: string, props?: JaypieDynamoDbProps);
|
|
99
99
|
/**
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Construct } from "constructs";
|
|
2
|
+
import * as acm from "aws-cdk-lib/aws-certificatemanager";
|
|
3
|
+
import * as apigatewayv2 from "aws-cdk-lib/aws-apigatewayv2";
|
|
4
|
+
import * as iam from "aws-cdk-lib/aws-iam";
|
|
5
|
+
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
6
|
+
import * as logs from "aws-cdk-lib/aws-logs";
|
|
7
|
+
import * as route53 from "aws-cdk-lib/aws-route53";
|
|
8
|
+
import { HostConfig } from "./helpers";
|
|
9
|
+
export interface JaypieWebSocketProps {
|
|
10
|
+
/**
|
|
11
|
+
* Certificate configuration.
|
|
12
|
+
* - true: Create certificate at stack level (default, reusable)
|
|
13
|
+
* - false: No certificate (use regional endpoint)
|
|
14
|
+
* - ICertificate: Use provided certificate
|
|
15
|
+
* - string: Import certificate from ARN
|
|
16
|
+
*/
|
|
17
|
+
certificate?: boolean | acm.ICertificate | string;
|
|
18
|
+
/**
|
|
19
|
+
* Lambda handler for $connect route (connection established).
|
|
20
|
+
* Use this to validate connections (e.g., auth tokens) and store connection IDs.
|
|
21
|
+
*/
|
|
22
|
+
connect?: lambda.IFunction;
|
|
23
|
+
/**
|
|
24
|
+
* Lambda handler for $default route (catches unmatched messages).
|
|
25
|
+
* Use this as the main message handler.
|
|
26
|
+
*/
|
|
27
|
+
default?: lambda.IFunction;
|
|
28
|
+
/**
|
|
29
|
+
* Lambda handler for $disconnect route (connection closed).
|
|
30
|
+
* Use this to clean up connection IDs from storage.
|
|
31
|
+
*/
|
|
32
|
+
disconnect?: lambda.IFunction;
|
|
33
|
+
/**
|
|
34
|
+
* Single Lambda handler for all routes.
|
|
35
|
+
* Alternative to providing separate connect/disconnect/default handlers.
|
|
36
|
+
* The handler receives routeKey in the context to determine which route was invoked.
|
|
37
|
+
*/
|
|
38
|
+
handler?: lambda.IFunction;
|
|
39
|
+
/**
|
|
40
|
+
* The domain name for the WebSocket API.
|
|
41
|
+
*
|
|
42
|
+
* Supports both string and config object:
|
|
43
|
+
* - String: used directly as the domain name (e.g., "ws.example.com")
|
|
44
|
+
* - Object: passed to envHostname() to construct the domain name
|
|
45
|
+
* - { subdomain, domain, env, component }
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // Direct string
|
|
49
|
+
* host: "ws.example.com"
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Config object - resolves using envHostname()
|
|
53
|
+
* host: { component: "ws" }
|
|
54
|
+
*/
|
|
55
|
+
host?: string | HostConfig;
|
|
56
|
+
/**
|
|
57
|
+
* Log retention for WebSocket API access logs.
|
|
58
|
+
* @default logs.RetentionDays.THREE_MONTHS
|
|
59
|
+
*/
|
|
60
|
+
logRetention?: logs.RetentionDays;
|
|
61
|
+
/**
|
|
62
|
+
* Construct name (used for resource naming).
|
|
63
|
+
*/
|
|
64
|
+
name?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Role tag for tagging resources.
|
|
67
|
+
* @default CDK.ROLE.API
|
|
68
|
+
*/
|
|
69
|
+
roleTag?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Additional named routes beyond $connect, $disconnect, and $default.
|
|
72
|
+
* Keys are route keys (e.g., "sendMessage", "subscribe").
|
|
73
|
+
*/
|
|
74
|
+
routes?: Record<string, lambda.IFunction>;
|
|
75
|
+
/**
|
|
76
|
+
* Stage name for the WebSocket API.
|
|
77
|
+
* @default "production"
|
|
78
|
+
*/
|
|
79
|
+
stageName?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Route53 hosted zone for DNS records.
|
|
82
|
+
* - string: Zone domain name (looked up or imported)
|
|
83
|
+
* - IHostedZone: Use provided hosted zone
|
|
84
|
+
*/
|
|
85
|
+
zone?: string | route53.IHostedZone;
|
|
86
|
+
}
|
|
87
|
+
export declare class JaypieWebSocket extends Construct {
|
|
88
|
+
private readonly _api;
|
|
89
|
+
private readonly _certificate?;
|
|
90
|
+
private readonly _domainName?;
|
|
91
|
+
private readonly _host?;
|
|
92
|
+
private readonly _stage;
|
|
93
|
+
constructor(scope: Construct, id: string, props?: JaypieWebSocketProps);
|
|
94
|
+
get api(): apigatewayv2.WebSocketApi;
|
|
95
|
+
get apiId(): string;
|
|
96
|
+
get certificate(): acm.ICertificate | undefined;
|
|
97
|
+
get domainName(): string | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* The WebSocket endpoint URL.
|
|
100
|
+
* Uses custom domain if configured, otherwise returns the default stage URL.
|
|
101
|
+
*/
|
|
102
|
+
get endpoint(): string;
|
|
103
|
+
get host(): string | undefined;
|
|
104
|
+
get stage(): apigatewayv2.WebSocketStage;
|
|
105
|
+
/**
|
|
106
|
+
* The callback URL for API Gateway Management API.
|
|
107
|
+
* Use this URL to send messages to connected clients.
|
|
108
|
+
*/
|
|
109
|
+
get callbackUrl(): string;
|
|
110
|
+
/**
|
|
111
|
+
* Grant a Lambda function permission to manage WebSocket connections
|
|
112
|
+
* (post to connections, delete connections).
|
|
113
|
+
*/
|
|
114
|
+
grantManageConnections(grantee: lambda.IFunction): iam.Grant;
|
|
115
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Construct } from "constructs";
|
|
2
|
+
import { JaypieLambda, JaypieLambdaProps } from "./JaypieLambda.js";
|
|
3
|
+
/**
|
|
4
|
+
* JaypieWebSocketLambda - A Lambda function optimized for WebSocket handlers.
|
|
5
|
+
*
|
|
6
|
+
* Provides sensible defaults for WebSocket event handling:
|
|
7
|
+
* - 30 second timeout (same as API handlers)
|
|
8
|
+
* - API role tag
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const handler = new JaypieWebSocketLambda(this, "ChatHandler", {
|
|
13
|
+
* code: "dist/handlers",
|
|
14
|
+
* handler: "chat.handler",
|
|
15
|
+
* secrets: ["MONGODB_URI"],
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* new JaypieWebSocket(this, "Chat", {
|
|
19
|
+
* host: "ws.example.com",
|
|
20
|
+
* handler,
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare class JaypieWebSocketLambda extends JaypieLambda {
|
|
25
|
+
constructor(scope: Construct, id: string, props: JaypieLambdaProps);
|
|
26
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Construct } from "constructs";
|
|
2
|
+
import { Duration } from "aws-cdk-lib";
|
|
3
|
+
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
|
|
4
|
+
import * as iam from "aws-cdk-lib/aws-iam";
|
|
5
|
+
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
6
|
+
export interface JaypieWebSocketTableProps {
|
|
7
|
+
/**
|
|
8
|
+
* Explicit table name. If not provided, uses CDK-generated name.
|
|
9
|
+
*/
|
|
10
|
+
tableName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Time-to-live duration for connections.
|
|
13
|
+
* Connections will be automatically deleted after this duration.
|
|
14
|
+
* @default Duration.hours(24)
|
|
15
|
+
*/
|
|
16
|
+
ttl?: Duration;
|
|
17
|
+
/**
|
|
18
|
+
* Whether to create a GSI for looking up connections by user ID.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
userIndex?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Role tag for tagging resources.
|
|
24
|
+
* @default CDK.ROLE.STORAGE
|
|
25
|
+
*/
|
|
26
|
+
roleTag?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* JaypieWebSocketTable - DynamoDB table for storing WebSocket connection IDs.
|
|
30
|
+
*
|
|
31
|
+
* Provides a simple table structure for tracking active WebSocket connections:
|
|
32
|
+
* - Partition key: connectionId (String)
|
|
33
|
+
* - TTL attribute: expiresAt (for automatic cleanup)
|
|
34
|
+
* - Optional GSI: userId-index (for looking up connections by user)
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const connectionTable = new JaypieWebSocketTable(this, "Connections");
|
|
39
|
+
*
|
|
40
|
+
* const ws = new JaypieWebSocket(this, "Chat", {
|
|
41
|
+
* host: "ws.example.com",
|
|
42
|
+
* handler: chatHandler,
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // Grant Lambda access to the table
|
|
46
|
+
* connectionTable.grantReadWriteData(chatHandler);
|
|
47
|
+
*
|
|
48
|
+
* // Pass table name to Lambda
|
|
49
|
+
* chatHandler.addEnvironment("CONNECTION_TABLE", connectionTable.tableName);
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // With user index for looking up all connections for a user
|
|
54
|
+
* const connectionTable = new JaypieWebSocketTable(this, "Connections", {
|
|
55
|
+
* userIndex: true,
|
|
56
|
+
* ttl: Duration.hours(12),
|
|
57
|
+
* });
|
|
58
|
+
*/
|
|
59
|
+
export declare class JaypieWebSocketTable extends Construct {
|
|
60
|
+
private readonly _table;
|
|
61
|
+
private readonly _ttlDuration;
|
|
62
|
+
constructor(scope: Construct, id: string, props?: JaypieWebSocketTableProps);
|
|
63
|
+
/**
|
|
64
|
+
* The underlying DynamoDB TableV2 construct.
|
|
65
|
+
*/
|
|
66
|
+
get table(): dynamodb.TableV2;
|
|
67
|
+
/**
|
|
68
|
+
* The name of the DynamoDB table.
|
|
69
|
+
*/
|
|
70
|
+
get tableName(): string;
|
|
71
|
+
/**
|
|
72
|
+
* The ARN of the DynamoDB table.
|
|
73
|
+
*/
|
|
74
|
+
get tableArn(): string;
|
|
75
|
+
/**
|
|
76
|
+
* TTL duration for connections in seconds.
|
|
77
|
+
* Use this to calculate expiresAt when storing connections.
|
|
78
|
+
*/
|
|
79
|
+
get ttlSeconds(): number;
|
|
80
|
+
/**
|
|
81
|
+
* Grant read permissions to the table.
|
|
82
|
+
*/
|
|
83
|
+
grantReadData(grantee: iam.IGrantable): iam.Grant;
|
|
84
|
+
/**
|
|
85
|
+
* Grant write permissions to the table.
|
|
86
|
+
*/
|
|
87
|
+
grantWriteData(grantee: iam.IGrantable): iam.Grant;
|
|
88
|
+
/**
|
|
89
|
+
* Grant read and write permissions to the table.
|
|
90
|
+
*/
|
|
91
|
+
grantReadWriteData(grantee: iam.IGrantable): iam.Grant;
|
|
92
|
+
/**
|
|
93
|
+
* Add the table name to a Lambda function's environment variables.
|
|
94
|
+
* Also grants read/write access to the table.
|
|
95
|
+
*/
|
|
96
|
+
connectLambda(lambdaFunction: lambda.IFunction, options?: {
|
|
97
|
+
envKey?: string;
|
|
98
|
+
readOnly?: boolean;
|
|
99
|
+
}): void;
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -29,4 +29,7 @@ export { JaypieStack, JaypieStackProps } from "./JaypieStack";
|
|
|
29
29
|
export { JaypieStaticWebBucket, JaypieStaticWebBucketProps, } from "./JaypieStaticWebBucket";
|
|
30
30
|
export { JaypieTraceSigningKeySecret } from "./JaypieTraceSigningKeySecret";
|
|
31
31
|
export { JaypieWebDeploymentBucket } from "./JaypieWebDeploymentBucket";
|
|
32
|
+
export { JaypieWebSocket, JaypieWebSocketProps } from "./JaypieWebSocket";
|
|
33
|
+
export { JaypieWebSocketLambda } from "./JaypieWebSocketLambda";
|
|
34
|
+
export { JaypieWebSocketTable, JaypieWebSocketTableProps, } from "./JaypieWebSocketTable";
|
|
32
35
|
export * from "./helpers";
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as cdk from 'aws-cdk-lib';
|
|
2
|
-
import { Tags, Stack, Fn, CfnOutput, SecretValue, Duration, RemovalPolicy, CfnStack } from 'aws-cdk-lib';
|
|
2
|
+
import { Tags, Stack, Fn, CfnOutput, SecretValue, Duration, RemovalPolicy, CfnStack, ArnFormat } from 'aws-cdk-lib';
|
|
3
3
|
import * as s3 from 'aws-cdk-lib/aws-s3';
|
|
4
4
|
import { Bucket, StorageClass, BucketAccessControl, EventType } from 'aws-cdk-lib/aws-s3';
|
|
5
5
|
import { Construct } from 'constructs';
|
|
@@ -10,6 +10,7 @@ import * as route53Targets from 'aws-cdk-lib/aws-route53-targets';
|
|
|
10
10
|
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
11
11
|
import { DatadogLambda } from 'datadog-cdk-constructs-v2';
|
|
12
12
|
import { ConfigurationError } from '@jaypie/errors';
|
|
13
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
13
14
|
import { Role, PolicyStatement, Policy, FederatedPrincipal, Effect, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
|
|
14
15
|
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
|
|
15
16
|
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
|
@@ -31,6 +32,8 @@ import * as path from 'path';
|
|
|
31
32
|
import { Trail, ReadWriteType } from 'aws-cdk-lib/aws-cloudtrail';
|
|
32
33
|
import { CfnPermissionSet, CfnAssignment } from 'aws-cdk-lib/aws-sso';
|
|
33
34
|
import { CfnApplication } from 'aws-cdk-lib/aws-sam';
|
|
35
|
+
import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
|
|
36
|
+
import * as apigatewayv2Integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
|
|
34
37
|
|
|
35
38
|
const CDK$2 = {
|
|
36
39
|
ACCOUNT: {
|
|
@@ -4118,5 +4121,376 @@ class JaypieTraceSigningKeySecret extends JaypieEnvSecret {
|
|
|
4118
4121
|
}
|
|
4119
4122
|
}
|
|
4120
4123
|
|
|
4121
|
-
|
|
4124
|
+
//
|
|
4125
|
+
//
|
|
4126
|
+
// Main
|
|
4127
|
+
//
|
|
4128
|
+
class JaypieWebSocket extends Construct {
|
|
4129
|
+
constructor(scope, id, props = {}) {
|
|
4130
|
+
super(scope, id);
|
|
4131
|
+
const { certificate = true, connect, default: defaultHandler, disconnect, handler, host: propsHost, logRetention = logs.RetentionDays.THREE_MONTHS, name, roleTag = CDK$2.ROLE.API, routes = {}, stageName = "production", zone: propsZone, } = props;
|
|
4132
|
+
// Validate: either handler OR individual handlers, not both
|
|
4133
|
+
const hasIndividualHandlers = connect || disconnect || defaultHandler;
|
|
4134
|
+
if (handler && hasIndividualHandlers) {
|
|
4135
|
+
throw new Error("Cannot specify both 'handler' and individual route handlers (connect/disconnect/default)");
|
|
4136
|
+
}
|
|
4137
|
+
// Determine zone from props or environment
|
|
4138
|
+
let zone = propsZone;
|
|
4139
|
+
if (!zone && process.env.CDK_ENV_HOSTED_ZONE) {
|
|
4140
|
+
zone = process.env.CDK_ENV_HOSTED_ZONE;
|
|
4141
|
+
}
|
|
4142
|
+
// Determine host from props or environment
|
|
4143
|
+
let host;
|
|
4144
|
+
if (typeof propsHost === "string") {
|
|
4145
|
+
host = propsHost;
|
|
4146
|
+
}
|
|
4147
|
+
else if (typeof propsHost === "object") {
|
|
4148
|
+
// Resolve host from HostConfig using envHostname()
|
|
4149
|
+
host = envHostname(propsHost);
|
|
4150
|
+
}
|
|
4151
|
+
else if (process.env.CDK_ENV_WS_HOST_NAME) {
|
|
4152
|
+
host = process.env.CDK_ENV_WS_HOST_NAME;
|
|
4153
|
+
}
|
|
4154
|
+
else if (process.env.CDK_ENV_WS_SUBDOMAIN &&
|
|
4155
|
+
process.env.CDK_ENV_HOSTED_ZONE) {
|
|
4156
|
+
host = mergeDomain(process.env.CDK_ENV_WS_SUBDOMAIN, process.env.CDK_ENV_HOSTED_ZONE);
|
|
4157
|
+
}
|
|
4158
|
+
const apiName = name || constructEnvName("WebSocket");
|
|
4159
|
+
// Create WebSocket API
|
|
4160
|
+
this._api = new apigatewayv2.WebSocketApi(this, "Api", {
|
|
4161
|
+
apiName,
|
|
4162
|
+
});
|
|
4163
|
+
Tags.of(this._api).add(CDK$2.TAG.ROLE, roleTag);
|
|
4164
|
+
// Add routes with Lambda integrations
|
|
4165
|
+
const connectHandler = handler || connect;
|
|
4166
|
+
const disconnectHandler = handler || disconnect;
|
|
4167
|
+
const defaultRouteHandler = handler || defaultHandler;
|
|
4168
|
+
if (connectHandler) {
|
|
4169
|
+
this._api.addRoute("$connect", {
|
|
4170
|
+
integration: new apigatewayv2Integrations.WebSocketLambdaIntegration("ConnectIntegration", connectHandler),
|
|
4171
|
+
});
|
|
4172
|
+
}
|
|
4173
|
+
if (disconnectHandler) {
|
|
4174
|
+
this._api.addRoute("$disconnect", {
|
|
4175
|
+
integration: new apigatewayv2Integrations.WebSocketLambdaIntegration("DisconnectIntegration", disconnectHandler),
|
|
4176
|
+
});
|
|
4177
|
+
}
|
|
4178
|
+
if (defaultRouteHandler) {
|
|
4179
|
+
this._api.addRoute("$default", {
|
|
4180
|
+
integration: new apigatewayv2Integrations.WebSocketLambdaIntegration("DefaultIntegration", defaultRouteHandler),
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
// Add custom routes
|
|
4184
|
+
for (const [routeKey, routeHandler] of Object.entries(routes)) {
|
|
4185
|
+
this._api.addRoute(routeKey, {
|
|
4186
|
+
integration: new apigatewayv2Integrations.WebSocketLambdaIntegration(`${routeKey}Integration`, routeHandler),
|
|
4187
|
+
});
|
|
4188
|
+
}
|
|
4189
|
+
// Create log group for access logs
|
|
4190
|
+
// Note: logGroup is created for future use when API Gateway v2 WebSocket
|
|
4191
|
+
// access logging is fully supported in CDK
|
|
4192
|
+
new logs.LogGroup(this, "AccessLogs", {
|
|
4193
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
4194
|
+
retention: logRetention,
|
|
4195
|
+
});
|
|
4196
|
+
// Create stage
|
|
4197
|
+
this._stage = new apigatewayv2.WebSocketStage(this, "Stage", {
|
|
4198
|
+
autoDeploy: true,
|
|
4199
|
+
stageName,
|
|
4200
|
+
webSocketApi: this._api,
|
|
4201
|
+
});
|
|
4202
|
+
Tags.of(this._stage).add(CDK$2.TAG.ROLE, roleTag);
|
|
4203
|
+
// Set up custom domain if host and zone are provided
|
|
4204
|
+
let hostedZone;
|
|
4205
|
+
let certificateToUse;
|
|
4206
|
+
if (host && zone) {
|
|
4207
|
+
hostedZone = resolveHostedZone(this, { zone });
|
|
4208
|
+
// Use resolveCertificate to create certificate at stack level (enables reuse)
|
|
4209
|
+
certificateToUse = resolveCertificate(this, {
|
|
4210
|
+
certificate,
|
|
4211
|
+
domainName: host,
|
|
4212
|
+
roleTag: CDK$2.ROLE.HOSTING,
|
|
4213
|
+
zone: hostedZone,
|
|
4214
|
+
});
|
|
4215
|
+
this._certificate = certificateToUse;
|
|
4216
|
+
this._host = host;
|
|
4217
|
+
if (certificateToUse) {
|
|
4218
|
+
// Create custom domain
|
|
4219
|
+
this._domainName = new apigatewayv2.DomainName(this, "DomainName", {
|
|
4220
|
+
certificate: certificateToUse,
|
|
4221
|
+
domainName: host,
|
|
4222
|
+
});
|
|
4223
|
+
Tags.of(this._domainName).add(CDK$2.TAG.ROLE, roleTag);
|
|
4224
|
+
// Map domain to stage
|
|
4225
|
+
new apigatewayv2.ApiMapping(this, "ApiMapping", {
|
|
4226
|
+
api: this._api,
|
|
4227
|
+
domainName: this._domainName,
|
|
4228
|
+
stage: this._stage,
|
|
4229
|
+
});
|
|
4230
|
+
// Create DNS record
|
|
4231
|
+
new route53.ARecord(this, "AliasRecord", {
|
|
4232
|
+
recordName: host,
|
|
4233
|
+
target: route53.RecordTarget.fromAlias(new route53Targets.ApiGatewayv2DomainProperties(this._domainName.regionalDomainName, this._domainName.regionalHostedZoneId)),
|
|
4234
|
+
zone: hostedZone,
|
|
4235
|
+
});
|
|
4236
|
+
// Also create AAAA record for IPv6
|
|
4237
|
+
new route53.AaaaRecord(this, "AaaaAliasRecord", {
|
|
4238
|
+
recordName: host,
|
|
4239
|
+
target: route53.RecordTarget.fromAlias(new route53Targets.ApiGatewayv2DomainProperties(this._domainName.regionalDomainName, this._domainName.regionalHostedZoneId)),
|
|
4240
|
+
zone: hostedZone,
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
// Grant all handlers permission to manage connections
|
|
4245
|
+
const allHandlers = new Set();
|
|
4246
|
+
if (connectHandler)
|
|
4247
|
+
allHandlers.add(connectHandler);
|
|
4248
|
+
if (disconnectHandler)
|
|
4249
|
+
allHandlers.add(disconnectHandler);
|
|
4250
|
+
if (defaultRouteHandler)
|
|
4251
|
+
allHandlers.add(defaultRouteHandler);
|
|
4252
|
+
Object.values(routes).forEach((h) => allHandlers.add(h));
|
|
4253
|
+
for (const lambdaHandler of allHandlers) {
|
|
4254
|
+
this.grantManageConnections(lambdaHandler);
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
//
|
|
4258
|
+
//
|
|
4259
|
+
// Public accessors
|
|
4260
|
+
//
|
|
4261
|
+
get api() {
|
|
4262
|
+
return this._api;
|
|
4263
|
+
}
|
|
4264
|
+
get apiId() {
|
|
4265
|
+
return this._api.apiId;
|
|
4266
|
+
}
|
|
4267
|
+
get certificate() {
|
|
4268
|
+
return this._certificate;
|
|
4269
|
+
}
|
|
4270
|
+
get domainName() {
|
|
4271
|
+
return this._domainName?.name;
|
|
4272
|
+
}
|
|
4273
|
+
/**
|
|
4274
|
+
* The WebSocket endpoint URL.
|
|
4275
|
+
* Uses custom domain if configured, otherwise returns the default stage URL.
|
|
4276
|
+
*/
|
|
4277
|
+
get endpoint() {
|
|
4278
|
+
if (this._host) {
|
|
4279
|
+
return `wss://${this._host}`;
|
|
4280
|
+
}
|
|
4281
|
+
return this._stage.url;
|
|
4282
|
+
}
|
|
4283
|
+
get host() {
|
|
4284
|
+
return this._host;
|
|
4285
|
+
}
|
|
4286
|
+
get stage() {
|
|
4287
|
+
return this._stage;
|
|
4288
|
+
}
|
|
4289
|
+
/**
|
|
4290
|
+
* The callback URL for API Gateway Management API.
|
|
4291
|
+
* Use this URL to send messages to connected clients.
|
|
4292
|
+
*/
|
|
4293
|
+
get callbackUrl() {
|
|
4294
|
+
if (this._host) {
|
|
4295
|
+
return `https://${this._host}`;
|
|
4296
|
+
}
|
|
4297
|
+
// Extract callback URL from stage URL
|
|
4298
|
+
// Stage URL: wss://abc123.execute-api.us-east-1.amazonaws.com/production
|
|
4299
|
+
// Callback URL: https://abc123.execute-api.us-east-1.amazonaws.com/production
|
|
4300
|
+
return this._stage.url.replace("wss://", "https://");
|
|
4301
|
+
}
|
|
4302
|
+
//
|
|
4303
|
+
//
|
|
4304
|
+
// Public methods
|
|
4305
|
+
//
|
|
4306
|
+
/**
|
|
4307
|
+
* Grant a Lambda function permission to manage WebSocket connections
|
|
4308
|
+
* (post to connections, delete connections).
|
|
4309
|
+
*/
|
|
4310
|
+
grantManageConnections(grantee) {
|
|
4311
|
+
return iam.Grant.addToPrincipal({
|
|
4312
|
+
actions: ["execute-api:ManageConnections"],
|
|
4313
|
+
grantee: grantee.grantPrincipal,
|
|
4314
|
+
resourceArns: [
|
|
4315
|
+
Stack.of(this).formatArn({
|
|
4316
|
+
arnFormat: ArnFormat.SLASH_RESOURCE_SLASH_RESOURCE_NAME,
|
|
4317
|
+
resource: this._api.apiId,
|
|
4318
|
+
resourceName: `${this._stage.stageName}/*`,
|
|
4319
|
+
service: "execute-api",
|
|
4320
|
+
}),
|
|
4321
|
+
],
|
|
4322
|
+
});
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
/**
|
|
4327
|
+
* JaypieWebSocketLambda - A Lambda function optimized for WebSocket handlers.
|
|
4328
|
+
*
|
|
4329
|
+
* Provides sensible defaults for WebSocket event handling:
|
|
4330
|
+
* - 30 second timeout (same as API handlers)
|
|
4331
|
+
* - API role tag
|
|
4332
|
+
*
|
|
4333
|
+
* @example
|
|
4334
|
+
* ```typescript
|
|
4335
|
+
* const handler = new JaypieWebSocketLambda(this, "ChatHandler", {
|
|
4336
|
+
* code: "dist/handlers",
|
|
4337
|
+
* handler: "chat.handler",
|
|
4338
|
+
* secrets: ["MONGODB_URI"],
|
|
4339
|
+
* });
|
|
4340
|
+
*
|
|
4341
|
+
* new JaypieWebSocket(this, "Chat", {
|
|
4342
|
+
* host: "ws.example.com",
|
|
4343
|
+
* handler,
|
|
4344
|
+
* });
|
|
4345
|
+
* ```
|
|
4346
|
+
*/
|
|
4347
|
+
class JaypieWebSocketLambda extends JaypieLambda {
|
|
4348
|
+
constructor(scope, id, props) {
|
|
4349
|
+
super(scope, id, {
|
|
4350
|
+
roleTag: CDK$2.ROLE.API,
|
|
4351
|
+
timeout: Duration.seconds(CDK$2.DURATION.EXPRESS_API),
|
|
4352
|
+
...props,
|
|
4353
|
+
});
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
//
|
|
4358
|
+
//
|
|
4359
|
+
// Main
|
|
4360
|
+
//
|
|
4361
|
+
/**
|
|
4362
|
+
* JaypieWebSocketTable - DynamoDB table for storing WebSocket connection IDs.
|
|
4363
|
+
*
|
|
4364
|
+
* Provides a simple table structure for tracking active WebSocket connections:
|
|
4365
|
+
* - Partition key: connectionId (String)
|
|
4366
|
+
* - TTL attribute: expiresAt (for automatic cleanup)
|
|
4367
|
+
* - Optional GSI: userId-index (for looking up connections by user)
|
|
4368
|
+
*
|
|
4369
|
+
* @example
|
|
4370
|
+
* ```typescript
|
|
4371
|
+
* const connectionTable = new JaypieWebSocketTable(this, "Connections");
|
|
4372
|
+
*
|
|
4373
|
+
* const ws = new JaypieWebSocket(this, "Chat", {
|
|
4374
|
+
* host: "ws.example.com",
|
|
4375
|
+
* handler: chatHandler,
|
|
4376
|
+
* });
|
|
4377
|
+
*
|
|
4378
|
+
* // Grant Lambda access to the table
|
|
4379
|
+
* connectionTable.grantReadWriteData(chatHandler);
|
|
4380
|
+
*
|
|
4381
|
+
* // Pass table name to Lambda
|
|
4382
|
+
* chatHandler.addEnvironment("CONNECTION_TABLE", connectionTable.tableName);
|
|
4383
|
+
* ```
|
|
4384
|
+
*
|
|
4385
|
+
* @example
|
|
4386
|
+
* // With user index for looking up all connections for a user
|
|
4387
|
+
* const connectionTable = new JaypieWebSocketTable(this, "Connections", {
|
|
4388
|
+
* userIndex: true,
|
|
4389
|
+
* ttl: Duration.hours(12),
|
|
4390
|
+
* });
|
|
4391
|
+
*/
|
|
4392
|
+
class JaypieWebSocketTable extends Construct {
|
|
4393
|
+
constructor(scope, id, props = {}) {
|
|
4394
|
+
super(scope, id);
|
|
4395
|
+
const { roleTag = CDK$2.ROLE.STORAGE, tableName, ttl = Duration.hours(24), userIndex = false, } = props;
|
|
4396
|
+
this._ttlDuration = ttl;
|
|
4397
|
+
// Build global secondary indexes
|
|
4398
|
+
const globalSecondaryIndexes = [];
|
|
4399
|
+
if (userIndex) {
|
|
4400
|
+
globalSecondaryIndexes.push({
|
|
4401
|
+
indexName: "userId-index",
|
|
4402
|
+
partitionKey: { name: "userId", type: dynamodb.AttributeType.STRING },
|
|
4403
|
+
sortKey: { name: "connectedAt", type: dynamodb.AttributeType.STRING },
|
|
4404
|
+
});
|
|
4405
|
+
}
|
|
4406
|
+
// Create the table
|
|
4407
|
+
this._table = new dynamodb.TableV2(this, "Table", {
|
|
4408
|
+
billing: dynamodb.Billing.onDemand(),
|
|
4409
|
+
globalSecondaryIndexes: globalSecondaryIndexes.length > 0 ? globalSecondaryIndexes : undefined,
|
|
4410
|
+
partitionKey: {
|
|
4411
|
+
name: "connectionId",
|
|
4412
|
+
type: dynamodb.AttributeType.STRING,
|
|
4413
|
+
},
|
|
4414
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
4415
|
+
tableName: tableName || constructEnvName("WebSocketConnections"),
|
|
4416
|
+
timeToLiveAttribute: "expiresAt",
|
|
4417
|
+
});
|
|
4418
|
+
Tags.of(this._table).add(CDK$2.TAG.ROLE, roleTag);
|
|
4419
|
+
}
|
|
4420
|
+
//
|
|
4421
|
+
//
|
|
4422
|
+
// Public accessors
|
|
4423
|
+
//
|
|
4424
|
+
/**
|
|
4425
|
+
* The underlying DynamoDB TableV2 construct.
|
|
4426
|
+
*/
|
|
4427
|
+
get table() {
|
|
4428
|
+
return this._table;
|
|
4429
|
+
}
|
|
4430
|
+
/**
|
|
4431
|
+
* The name of the DynamoDB table.
|
|
4432
|
+
*/
|
|
4433
|
+
get tableName() {
|
|
4434
|
+
return this._table.tableName;
|
|
4435
|
+
}
|
|
4436
|
+
/**
|
|
4437
|
+
* The ARN of the DynamoDB table.
|
|
4438
|
+
*/
|
|
4439
|
+
get tableArn() {
|
|
4440
|
+
return this._table.tableArn;
|
|
4441
|
+
}
|
|
4442
|
+
/**
|
|
4443
|
+
* TTL duration for connections in seconds.
|
|
4444
|
+
* Use this to calculate expiresAt when storing connections.
|
|
4445
|
+
*/
|
|
4446
|
+
get ttlSeconds() {
|
|
4447
|
+
return this._ttlDuration.toSeconds();
|
|
4448
|
+
}
|
|
4449
|
+
//
|
|
4450
|
+
//
|
|
4451
|
+
// Grant methods
|
|
4452
|
+
//
|
|
4453
|
+
/**
|
|
4454
|
+
* Grant read permissions to the table.
|
|
4455
|
+
*/
|
|
4456
|
+
grantReadData(grantee) {
|
|
4457
|
+
return this._table.grantReadData(grantee);
|
|
4458
|
+
}
|
|
4459
|
+
/**
|
|
4460
|
+
* Grant write permissions to the table.
|
|
4461
|
+
*/
|
|
4462
|
+
grantWriteData(grantee) {
|
|
4463
|
+
return this._table.grantWriteData(grantee);
|
|
4464
|
+
}
|
|
4465
|
+
/**
|
|
4466
|
+
* Grant read and write permissions to the table.
|
|
4467
|
+
*/
|
|
4468
|
+
grantReadWriteData(grantee) {
|
|
4469
|
+
return this._table.grantReadWriteData(grantee);
|
|
4470
|
+
}
|
|
4471
|
+
//
|
|
4472
|
+
//
|
|
4473
|
+
// Convenience methods
|
|
4474
|
+
//
|
|
4475
|
+
/**
|
|
4476
|
+
* Add the table name to a Lambda function's environment variables.
|
|
4477
|
+
* Also grants read/write access to the table.
|
|
4478
|
+
*/
|
|
4479
|
+
connectLambda(lambdaFunction, options = {}) {
|
|
4480
|
+
const { envKey = "CONNECTION_TABLE", readOnly = false } = options;
|
|
4481
|
+
// Add environment variable
|
|
4482
|
+
if ("addEnvironment" in lambdaFunction) {
|
|
4483
|
+
lambdaFunction.addEnvironment(envKey, this.tableName);
|
|
4484
|
+
}
|
|
4485
|
+
// Grant permissions
|
|
4486
|
+
if (readOnly) {
|
|
4487
|
+
this.grantReadData(lambdaFunction.grantPrincipal);
|
|
4488
|
+
}
|
|
4489
|
+
else {
|
|
4490
|
+
this.grantReadWriteData(lambdaFunction.grantPrincipal);
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4495
|
+
export { CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieCertificate, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDistribution, JaypieDnsRecord, JaypieDynamoDb, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMongoDbSecret, JaypieNextJs, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieStaticWebBucket, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, JaypieWebSocket, JaypieWebSocketLambda, JaypieWebSocketTable, addDatadogLayers, clearAllCertificateCaches, clearAllSecretsCaches, clearCertificateCache, clearSecretsCache, constructEnvName, constructStackName, constructTagger, envHostname, extendDatadogRole, isEnv, isProductionEnv, isSandboxEnv, isValidHostname$1 as isValidHostname, isValidSubdomain, jaypieLambdaEnv, mergeDomain, resolveCertificate, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveEnvironment, resolveHostedZone, resolveParamsAndSecrets, resolveSecrets };
|
|
4122
4496
|
//# sourceMappingURL=index.js.map
|