@jaypie/constructs 1.2.18 → 1.2.20
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.cjs
CHANGED
|
@@ -9,7 +9,7 @@ var route53Targets = require('aws-cdk-lib/aws-route53-targets');
|
|
|
9
9
|
var secretsmanager = require('aws-cdk-lib/aws-secretsmanager');
|
|
10
10
|
var datadogCdkConstructsV2 = require('datadog-cdk-constructs-v2');
|
|
11
11
|
var errors = require('@jaypie/errors');
|
|
12
|
-
var
|
|
12
|
+
var iam = require('aws-cdk-lib/aws-iam');
|
|
13
13
|
var acm = require('aws-cdk-lib/aws-certificatemanager');
|
|
14
14
|
var lambda = require('aws-cdk-lib/aws-lambda');
|
|
15
15
|
var logDestinations = require('aws-cdk-lib/aws-logs-destinations');
|
|
@@ -28,6 +28,8 @@ var path = require('path');
|
|
|
28
28
|
var awsCloudtrail = require('aws-cdk-lib/aws-cloudtrail');
|
|
29
29
|
var awsSso = require('aws-cdk-lib/aws-sso');
|
|
30
30
|
var awsSam = require('aws-cdk-lib/aws-sam');
|
|
31
|
+
var apigatewayv2 = require('aws-cdk-lib/aws-apigatewayv2');
|
|
32
|
+
var apigatewayv2Integrations = require('aws-cdk-lib/aws-apigatewayv2-integrations');
|
|
31
33
|
|
|
32
34
|
function _interopNamespaceDefault(e) {
|
|
33
35
|
var n = Object.create(null);
|
|
@@ -52,6 +54,7 @@ var apiGateway__namespace = /*#__PURE__*/_interopNamespaceDefault(apiGateway);
|
|
|
52
54
|
var route53__namespace = /*#__PURE__*/_interopNamespaceDefault(route53);
|
|
53
55
|
var route53Targets__namespace = /*#__PURE__*/_interopNamespaceDefault(route53Targets);
|
|
54
56
|
var secretsmanager__namespace = /*#__PURE__*/_interopNamespaceDefault(secretsmanager);
|
|
57
|
+
var iam__namespace = /*#__PURE__*/_interopNamespaceDefault(iam);
|
|
55
58
|
var acm__namespace = /*#__PURE__*/_interopNamespaceDefault(acm);
|
|
56
59
|
var lambda__namespace = /*#__PURE__*/_interopNamespaceDefault(lambda);
|
|
57
60
|
var logDestinations__namespace = /*#__PURE__*/_interopNamespaceDefault(logDestinations);
|
|
@@ -63,6 +66,8 @@ var cloudfront__namespace = /*#__PURE__*/_interopNamespaceDefault(cloudfront);
|
|
|
63
66
|
var origins__namespace = /*#__PURE__*/_interopNamespaceDefault(origins);
|
|
64
67
|
var dynamodb__namespace = /*#__PURE__*/_interopNamespaceDefault(dynamodb);
|
|
65
68
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
69
|
+
var apigatewayv2__namespace = /*#__PURE__*/_interopNamespaceDefault(apigatewayv2);
|
|
70
|
+
var apigatewayv2Integrations__namespace = /*#__PURE__*/_interopNamespaceDefault(apigatewayv2Integrations);
|
|
66
71
|
|
|
67
72
|
const CDK$2 = {
|
|
68
73
|
ACCOUNT: {
|
|
@@ -434,22 +439,22 @@ function extendDatadogRole(scope, options) {
|
|
|
434
439
|
}
|
|
435
440
|
const { id = "DatadogCustomPolicy", project, service = CDK$2.SERVICE.DATADOG, } = options || {};
|
|
436
441
|
// Lookup the Datadog role
|
|
437
|
-
const datadogRole =
|
|
442
|
+
const datadogRole = iam.Role.fromRoleArn(scope, "DatadogRole", datadogRoleArn);
|
|
438
443
|
// Build policy statements
|
|
439
444
|
const statements = [
|
|
440
445
|
// Allow view budget
|
|
441
|
-
new
|
|
446
|
+
new iam.PolicyStatement({
|
|
442
447
|
actions: ["budgets:ViewBudget"],
|
|
443
448
|
resources: ["*"],
|
|
444
449
|
}),
|
|
445
450
|
// Allow describe log groups
|
|
446
|
-
new
|
|
451
|
+
new iam.PolicyStatement({
|
|
447
452
|
actions: ["logs:DescribeLogGroups"],
|
|
448
453
|
resources: ["*"],
|
|
449
454
|
}),
|
|
450
455
|
];
|
|
451
456
|
// Create the custom policy
|
|
452
|
-
const datadogCustomPolicy = new
|
|
457
|
+
const datadogCustomPolicy = new iam.Policy(scope, id, {
|
|
453
458
|
roles: [datadogRole],
|
|
454
459
|
statements,
|
|
455
460
|
});
|
|
@@ -2225,22 +2230,22 @@ class JaypieDatadogBucket extends constructs.Construct {
|
|
|
2225
2230
|
}
|
|
2226
2231
|
const { project, service = CDK$2.SERVICE.DATADOG } = options || {};
|
|
2227
2232
|
// Lookup the Datadog role
|
|
2228
|
-
const datadogRole =
|
|
2233
|
+
const datadogRole = iam.Role.fromRoleArn(this, "DatadogRole", datadogRoleArn);
|
|
2229
2234
|
// Build policy statements for bucket access
|
|
2230
2235
|
const statements = [
|
|
2231
2236
|
// Allow list bucket
|
|
2232
|
-
new
|
|
2237
|
+
new iam.PolicyStatement({
|
|
2233
2238
|
actions: ["s3:ListBucket"],
|
|
2234
2239
|
resources: [this.bucket.bucketArn],
|
|
2235
2240
|
}),
|
|
2236
2241
|
// Allow read and write to the bucket
|
|
2237
|
-
new
|
|
2242
|
+
new iam.PolicyStatement({
|
|
2238
2243
|
actions: ["s3:GetObject", "s3:PutObject"],
|
|
2239
2244
|
resources: [`${this.bucket.bucketArn}/*`],
|
|
2240
2245
|
}),
|
|
2241
2246
|
];
|
|
2242
2247
|
// Create the custom policy
|
|
2243
|
-
const datadogBucketPolicy = new
|
|
2248
|
+
const datadogBucketPolicy = new iam.Policy(this, "DatadogBucketPolicy", {
|
|
2244
2249
|
roles: [datadogRole],
|
|
2245
2250
|
statements,
|
|
2246
2251
|
});
|
|
@@ -3009,8 +3014,8 @@ class JaypieGitHubDeployRole extends constructs.Construct {
|
|
|
3009
3014
|
repoRestriction = `repo:${organization}/*:*`;
|
|
3010
3015
|
}
|
|
3011
3016
|
// Create the IAM role
|
|
3012
|
-
this._role = new
|
|
3013
|
-
assumedBy: new
|
|
3017
|
+
this._role = new iam.Role(this, "GitHubActionsRole", {
|
|
3018
|
+
assumedBy: new iam.FederatedPrincipal(oidcProviderArn, {
|
|
3014
3019
|
StringLike: {
|
|
3015
3020
|
"token.actions.githubusercontent.com:sub": repoRestriction,
|
|
3016
3021
|
},
|
|
@@ -3020,12 +3025,12 @@ class JaypieGitHubDeployRole extends constructs.Construct {
|
|
|
3020
3025
|
});
|
|
3021
3026
|
cdk.Tags.of(this._role).add(CDK$2.TAG.ROLE, CDK$2.ROLE.DEPLOY);
|
|
3022
3027
|
// Allow the role to access the GitHub OIDC provider
|
|
3023
|
-
this._role.addToPolicy(new
|
|
3028
|
+
this._role.addToPolicy(new iam.PolicyStatement({
|
|
3024
3029
|
actions: ["sts:AssumeRoleWithWebIdentity"],
|
|
3025
3030
|
resources: [`arn:aws:iam::${accountId}:oidc-provider/*`],
|
|
3026
3031
|
}));
|
|
3027
3032
|
// Allow the role to deploy CDK apps
|
|
3028
|
-
this._role.addToPolicy(new
|
|
3033
|
+
this._role.addToPolicy(new iam.PolicyStatement({
|
|
3029
3034
|
actions: [
|
|
3030
3035
|
"cloudformation:CreateStack",
|
|
3031
3036
|
"cloudformation:DeleteStack",
|
|
@@ -3042,12 +3047,12 @@ class JaypieGitHubDeployRole extends constructs.Construct {
|
|
|
3042
3047
|
"s3:GetObject",
|
|
3043
3048
|
"s3:ListBucket",
|
|
3044
3049
|
],
|
|
3045
|
-
effect:
|
|
3050
|
+
effect: iam.Effect.ALLOW,
|
|
3046
3051
|
resources: ["*"],
|
|
3047
3052
|
}));
|
|
3048
|
-
this._role.addToPolicy(new
|
|
3053
|
+
this._role.addToPolicy(new iam.PolicyStatement({
|
|
3049
3054
|
actions: ["iam:PassRole", "sts:AssumeRole"],
|
|
3050
|
-
effect:
|
|
3055
|
+
effect: iam.Effect.ALLOW,
|
|
3051
3056
|
resources: [
|
|
3052
3057
|
"arn:aws:iam::*:role/cdk-hnb659fds-deploy-role-*",
|
|
3053
3058
|
"arn:aws:iam::*:role/cdk-hnb659fds-file-publishing-*",
|
|
@@ -3142,7 +3147,7 @@ class JaypieHostedZone extends constructs.Construct {
|
|
|
3142
3147
|
cdk__namespace.Tags.of(this.logGroup).add(CDK$2.TAG.PROJECT, project);
|
|
3143
3148
|
}
|
|
3144
3149
|
// Grant Route 53 permissions to write to the log group
|
|
3145
|
-
this.logGroup.grantWrite(new
|
|
3150
|
+
this.logGroup.grantWrite(new iam.ServicePrincipal(SERVICE.ROUTE53));
|
|
3146
3151
|
// Add destination based on configuration
|
|
3147
3152
|
if (destination !== false) {
|
|
3148
3153
|
const lambdaDestination = destination === true
|
|
@@ -3440,21 +3445,21 @@ class JaypieOrganizationTrail extends constructs.Construct {
|
|
|
3440
3445
|
],
|
|
3441
3446
|
});
|
|
3442
3447
|
// Add CloudTrail bucket policies
|
|
3443
|
-
this.bucket.addToResourcePolicy(new
|
|
3448
|
+
this.bucket.addToResourcePolicy(new iam.PolicyStatement({
|
|
3444
3449
|
actions: ["s3:GetBucketAcl"],
|
|
3445
|
-
effect:
|
|
3446
|
-
principals: [new
|
|
3450
|
+
effect: iam.Effect.ALLOW,
|
|
3451
|
+
principals: [new iam.ServicePrincipal("cloudtrail.amazonaws.com")],
|
|
3447
3452
|
resources: [this.bucket.bucketArn],
|
|
3448
3453
|
}));
|
|
3449
|
-
this.bucket.addToResourcePolicy(new
|
|
3454
|
+
this.bucket.addToResourcePolicy(new iam.PolicyStatement({
|
|
3450
3455
|
actions: ["s3:PutObject"],
|
|
3451
3456
|
conditions: {
|
|
3452
3457
|
StringEquals: {
|
|
3453
3458
|
"s3:x-amz-acl": "bucket-owner-full-control",
|
|
3454
3459
|
},
|
|
3455
3460
|
},
|
|
3456
|
-
effect:
|
|
3457
|
-
principals: [new
|
|
3461
|
+
effect: iam.Effect.ALLOW,
|
|
3462
|
+
principals: [new iam.ServicePrincipal("cloudtrail.amazonaws.com")],
|
|
3458
3463
|
resources: [`${this.bucket.bucketArn}/*`],
|
|
3459
3464
|
}));
|
|
3460
3465
|
// Add tags to bucket
|
|
@@ -3547,9 +3552,9 @@ class JaypieSsoPermissions extends constructs.Construct {
|
|
|
3547
3552
|
],
|
|
3548
3553
|
},
|
|
3549
3554
|
managedPolicies: [
|
|
3550
|
-
|
|
3555
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess")
|
|
3551
3556
|
.managedPolicyArn,
|
|
3552
|
-
|
|
3557
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
|
|
3553
3558
|
],
|
|
3554
3559
|
sessionDuration: cdk.Duration.hours(1).toIsoString(),
|
|
3555
3560
|
tags: [
|
|
@@ -3628,10 +3633,10 @@ class JaypieSsoPermissions extends constructs.Construct {
|
|
|
3628
3633
|
],
|
|
3629
3634
|
},
|
|
3630
3635
|
managedPolicies: [
|
|
3631
|
-
|
|
3636
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
|
|
3632
3637
|
.managedPolicyArn,
|
|
3633
|
-
|
|
3634
|
-
|
|
3638
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
|
|
3639
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
|
|
3635
3640
|
.managedPolicyArn,
|
|
3636
3641
|
],
|
|
3637
3642
|
sessionDuration: cdk.Duration.hours(12).toIsoString(),
|
|
@@ -3686,12 +3691,12 @@ class JaypieSsoPermissions extends constructs.Construct {
|
|
|
3686
3691
|
],
|
|
3687
3692
|
},
|
|
3688
3693
|
managedPolicies: [
|
|
3689
|
-
|
|
3694
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
|
|
3690
3695
|
.managedPolicyArn,
|
|
3691
|
-
|
|
3692
|
-
|
|
3696
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
|
|
3697
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
|
|
3693
3698
|
.managedPolicyArn,
|
|
3694
|
-
|
|
3699
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("job-function/SystemAdministrator").managedPolicyArn,
|
|
3695
3700
|
],
|
|
3696
3701
|
sessionDuration: cdk.Duration.hours(4).toIsoString(),
|
|
3697
3702
|
tags: [
|
|
@@ -3904,8 +3909,8 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
|
|
|
3904
3909
|
repo = `repo:${process.env.CDK_ENV_REPO}:*`;
|
|
3905
3910
|
}
|
|
3906
3911
|
if (repo) {
|
|
3907
|
-
const bucketDeployRole = new
|
|
3908
|
-
assumedBy: new
|
|
3912
|
+
const bucketDeployRole = new iam.Role(this, "DestinationBucketDeployRole", {
|
|
3913
|
+
assumedBy: new iam.FederatedPrincipal(cdk.Fn.importValue(CDK$2.IMPORT.OIDC_PROVIDER), {
|
|
3909
3914
|
StringLike: {
|
|
3910
3915
|
"token.actions.githubusercontent.com:sub": repo,
|
|
3911
3916
|
},
|
|
@@ -3914,8 +3919,8 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
|
|
|
3914
3919
|
});
|
|
3915
3920
|
cdk.Tags.of(bucketDeployRole).add(CDK$2.TAG.ROLE, CDK$2.ROLE.DEPLOY);
|
|
3916
3921
|
// Allow the role to write to the bucket
|
|
3917
|
-
bucketDeployRole.addToPolicy(new
|
|
3918
|
-
effect:
|
|
3922
|
+
bucketDeployRole.addToPolicy(new iam.PolicyStatement({
|
|
3923
|
+
effect: iam.Effect.ALLOW,
|
|
3919
3924
|
actions: [
|
|
3920
3925
|
"s3:DeleteObject",
|
|
3921
3926
|
"s3:GetObject",
|
|
@@ -3924,16 +3929,16 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
|
|
|
3924
3929
|
],
|
|
3925
3930
|
resources: [`${this.bucket.bucketArn}/*`],
|
|
3926
3931
|
}));
|
|
3927
|
-
bucketDeployRole.addToPolicy(new
|
|
3928
|
-
effect:
|
|
3932
|
+
bucketDeployRole.addToPolicy(new iam.PolicyStatement({
|
|
3933
|
+
effect: iam.Effect.ALLOW,
|
|
3929
3934
|
actions: ["s3:ListBucket"],
|
|
3930
3935
|
resources: [this.bucket.bucketArn],
|
|
3931
3936
|
}));
|
|
3932
3937
|
// Allow the role to describe the current stack
|
|
3933
3938
|
const stack = cdk.Stack.of(this);
|
|
3934
|
-
bucketDeployRole.addToPolicy(new
|
|
3939
|
+
bucketDeployRole.addToPolicy(new iam.PolicyStatement({
|
|
3935
3940
|
actions: ["cloudformation:DescribeStacks"],
|
|
3936
|
-
effect:
|
|
3941
|
+
effect: iam.Effect.ALLOW,
|
|
3937
3942
|
resources: [
|
|
3938
3943
|
`arn:aws:cloudformation:${stack.region}:${stack.account}:stack/${stack.stackName}/*`,
|
|
3939
3944
|
],
|
|
@@ -4150,6 +4155,377 @@ class JaypieTraceSigningKeySecret extends JaypieEnvSecret {
|
|
|
4150
4155
|
}
|
|
4151
4156
|
}
|
|
4152
4157
|
|
|
4158
|
+
//
|
|
4159
|
+
//
|
|
4160
|
+
// Main
|
|
4161
|
+
//
|
|
4162
|
+
class JaypieWebSocket extends constructs.Construct {
|
|
4163
|
+
constructor(scope, id, props = {}) {
|
|
4164
|
+
super(scope, id);
|
|
4165
|
+
const { certificate = true, connect, default: defaultHandler, disconnect, handler, host: propsHost, logRetention = logs__namespace.RetentionDays.THREE_MONTHS, name, roleTag = CDK$2.ROLE.API, routes = {}, stageName = "production", zone: propsZone, } = props;
|
|
4166
|
+
// Validate: either handler OR individual handlers, not both
|
|
4167
|
+
const hasIndividualHandlers = connect || disconnect || defaultHandler;
|
|
4168
|
+
if (handler && hasIndividualHandlers) {
|
|
4169
|
+
throw new Error("Cannot specify both 'handler' and individual route handlers (connect/disconnect/default)");
|
|
4170
|
+
}
|
|
4171
|
+
// Determine zone from props or environment
|
|
4172
|
+
let zone = propsZone;
|
|
4173
|
+
if (!zone && process.env.CDK_ENV_HOSTED_ZONE) {
|
|
4174
|
+
zone = process.env.CDK_ENV_HOSTED_ZONE;
|
|
4175
|
+
}
|
|
4176
|
+
// Determine host from props or environment
|
|
4177
|
+
let host;
|
|
4178
|
+
if (typeof propsHost === "string") {
|
|
4179
|
+
host = propsHost;
|
|
4180
|
+
}
|
|
4181
|
+
else if (typeof propsHost === "object") {
|
|
4182
|
+
// Resolve host from HostConfig using envHostname()
|
|
4183
|
+
host = envHostname(propsHost);
|
|
4184
|
+
}
|
|
4185
|
+
else if (process.env.CDK_ENV_WS_HOST_NAME) {
|
|
4186
|
+
host = process.env.CDK_ENV_WS_HOST_NAME;
|
|
4187
|
+
}
|
|
4188
|
+
else if (process.env.CDK_ENV_WS_SUBDOMAIN &&
|
|
4189
|
+
process.env.CDK_ENV_HOSTED_ZONE) {
|
|
4190
|
+
host = mergeDomain(process.env.CDK_ENV_WS_SUBDOMAIN, process.env.CDK_ENV_HOSTED_ZONE);
|
|
4191
|
+
}
|
|
4192
|
+
const apiName = name || constructEnvName("WebSocket");
|
|
4193
|
+
// Create WebSocket API
|
|
4194
|
+
this._api = new apigatewayv2__namespace.WebSocketApi(this, "Api", {
|
|
4195
|
+
apiName,
|
|
4196
|
+
});
|
|
4197
|
+
cdk.Tags.of(this._api).add(CDK$2.TAG.ROLE, roleTag);
|
|
4198
|
+
// Add routes with Lambda integrations
|
|
4199
|
+
const connectHandler = handler || connect;
|
|
4200
|
+
const disconnectHandler = handler || disconnect;
|
|
4201
|
+
const defaultRouteHandler = handler || defaultHandler;
|
|
4202
|
+
if (connectHandler) {
|
|
4203
|
+
this._api.addRoute("$connect", {
|
|
4204
|
+
integration: new apigatewayv2Integrations__namespace.WebSocketLambdaIntegration("ConnectIntegration", connectHandler),
|
|
4205
|
+
});
|
|
4206
|
+
}
|
|
4207
|
+
if (disconnectHandler) {
|
|
4208
|
+
this._api.addRoute("$disconnect", {
|
|
4209
|
+
integration: new apigatewayv2Integrations__namespace.WebSocketLambdaIntegration("DisconnectIntegration", disconnectHandler),
|
|
4210
|
+
});
|
|
4211
|
+
}
|
|
4212
|
+
if (defaultRouteHandler) {
|
|
4213
|
+
this._api.addRoute("$default", {
|
|
4214
|
+
integration: new apigatewayv2Integrations__namespace.WebSocketLambdaIntegration("DefaultIntegration", defaultRouteHandler),
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
// Add custom routes
|
|
4218
|
+
for (const [routeKey, routeHandler] of Object.entries(routes)) {
|
|
4219
|
+
this._api.addRoute(routeKey, {
|
|
4220
|
+
integration: new apigatewayv2Integrations__namespace.WebSocketLambdaIntegration(`${routeKey}Integration`, routeHandler),
|
|
4221
|
+
});
|
|
4222
|
+
}
|
|
4223
|
+
// Create log group for access logs
|
|
4224
|
+
// Note: logGroup is created for future use when API Gateway v2 WebSocket
|
|
4225
|
+
// access logging is fully supported in CDK
|
|
4226
|
+
new logs__namespace.LogGroup(this, "AccessLogs", {
|
|
4227
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
4228
|
+
retention: logRetention,
|
|
4229
|
+
});
|
|
4230
|
+
// Create stage
|
|
4231
|
+
this._stage = new apigatewayv2__namespace.WebSocketStage(this, "Stage", {
|
|
4232
|
+
autoDeploy: true,
|
|
4233
|
+
stageName,
|
|
4234
|
+
webSocketApi: this._api,
|
|
4235
|
+
});
|
|
4236
|
+
cdk.Tags.of(this._stage).add(CDK$2.TAG.ROLE, roleTag);
|
|
4237
|
+
// Set up custom domain if host and zone are provided
|
|
4238
|
+
let hostedZone;
|
|
4239
|
+
let certificateToUse;
|
|
4240
|
+
if (host && zone) {
|
|
4241
|
+
hostedZone = resolveHostedZone(this, { zone });
|
|
4242
|
+
// Use resolveCertificate to create certificate at stack level (enables reuse)
|
|
4243
|
+
certificateToUse = resolveCertificate(this, {
|
|
4244
|
+
certificate,
|
|
4245
|
+
domainName: host,
|
|
4246
|
+
roleTag: CDK$2.ROLE.HOSTING,
|
|
4247
|
+
zone: hostedZone,
|
|
4248
|
+
});
|
|
4249
|
+
this._certificate = certificateToUse;
|
|
4250
|
+
this._host = host;
|
|
4251
|
+
if (certificateToUse) {
|
|
4252
|
+
// Create custom domain
|
|
4253
|
+
this._domainName = new apigatewayv2__namespace.DomainName(this, "DomainName", {
|
|
4254
|
+
certificate: certificateToUse,
|
|
4255
|
+
domainName: host,
|
|
4256
|
+
});
|
|
4257
|
+
cdk.Tags.of(this._domainName).add(CDK$2.TAG.ROLE, roleTag);
|
|
4258
|
+
// Map domain to stage
|
|
4259
|
+
new apigatewayv2__namespace.ApiMapping(this, "ApiMapping", {
|
|
4260
|
+
api: this._api,
|
|
4261
|
+
domainName: this._domainName,
|
|
4262
|
+
stage: this._stage,
|
|
4263
|
+
});
|
|
4264
|
+
// Create DNS record
|
|
4265
|
+
new route53__namespace.ARecord(this, "AliasRecord", {
|
|
4266
|
+
recordName: host,
|
|
4267
|
+
target: route53__namespace.RecordTarget.fromAlias(new route53Targets__namespace.ApiGatewayv2DomainProperties(this._domainName.regionalDomainName, this._domainName.regionalHostedZoneId)),
|
|
4268
|
+
zone: hostedZone,
|
|
4269
|
+
});
|
|
4270
|
+
// Also create AAAA record for IPv6
|
|
4271
|
+
new route53__namespace.AaaaRecord(this, "AaaaAliasRecord", {
|
|
4272
|
+
recordName: host,
|
|
4273
|
+
target: route53__namespace.RecordTarget.fromAlias(new route53Targets__namespace.ApiGatewayv2DomainProperties(this._domainName.regionalDomainName, this._domainName.regionalHostedZoneId)),
|
|
4274
|
+
zone: hostedZone,
|
|
4275
|
+
});
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
// Grant all handlers permission to manage connections
|
|
4279
|
+
const allHandlers = new Set();
|
|
4280
|
+
if (connectHandler)
|
|
4281
|
+
allHandlers.add(connectHandler);
|
|
4282
|
+
if (disconnectHandler)
|
|
4283
|
+
allHandlers.add(disconnectHandler);
|
|
4284
|
+
if (defaultRouteHandler)
|
|
4285
|
+
allHandlers.add(defaultRouteHandler);
|
|
4286
|
+
Object.values(routes).forEach((h) => allHandlers.add(h));
|
|
4287
|
+
for (const lambdaHandler of allHandlers) {
|
|
4288
|
+
this.grantManageConnections(lambdaHandler);
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
//
|
|
4292
|
+
//
|
|
4293
|
+
// Public accessors
|
|
4294
|
+
//
|
|
4295
|
+
get api() {
|
|
4296
|
+
return this._api;
|
|
4297
|
+
}
|
|
4298
|
+
get apiId() {
|
|
4299
|
+
return this._api.apiId;
|
|
4300
|
+
}
|
|
4301
|
+
get certificate() {
|
|
4302
|
+
return this._certificate;
|
|
4303
|
+
}
|
|
4304
|
+
get domainName() {
|
|
4305
|
+
return this._domainName?.name;
|
|
4306
|
+
}
|
|
4307
|
+
/**
|
|
4308
|
+
* The WebSocket endpoint URL.
|
|
4309
|
+
* Uses custom domain if configured, otherwise returns the default stage URL.
|
|
4310
|
+
*/
|
|
4311
|
+
get endpoint() {
|
|
4312
|
+
if (this._host) {
|
|
4313
|
+
return `wss://${this._host}`;
|
|
4314
|
+
}
|
|
4315
|
+
return this._stage.url;
|
|
4316
|
+
}
|
|
4317
|
+
get host() {
|
|
4318
|
+
return this._host;
|
|
4319
|
+
}
|
|
4320
|
+
get stage() {
|
|
4321
|
+
return this._stage;
|
|
4322
|
+
}
|
|
4323
|
+
/**
|
|
4324
|
+
* The callback URL for API Gateway Management API.
|
|
4325
|
+
* Use this URL to send messages to connected clients.
|
|
4326
|
+
*/
|
|
4327
|
+
get callbackUrl() {
|
|
4328
|
+
if (this._host) {
|
|
4329
|
+
return `https://${this._host}`;
|
|
4330
|
+
}
|
|
4331
|
+
// Extract callback URL from stage URL
|
|
4332
|
+
// Stage URL: wss://abc123.execute-api.us-east-1.amazonaws.com/production
|
|
4333
|
+
// Callback URL: https://abc123.execute-api.us-east-1.amazonaws.com/production
|
|
4334
|
+
return this._stage.url.replace("wss://", "https://");
|
|
4335
|
+
}
|
|
4336
|
+
//
|
|
4337
|
+
//
|
|
4338
|
+
// Public methods
|
|
4339
|
+
//
|
|
4340
|
+
/**
|
|
4341
|
+
* Grant a Lambda function permission to manage WebSocket connections
|
|
4342
|
+
* (post to connections, delete connections).
|
|
4343
|
+
*/
|
|
4344
|
+
grantManageConnections(grantee) {
|
|
4345
|
+
return iam__namespace.Grant.addToPrincipal({
|
|
4346
|
+
actions: ["execute-api:ManageConnections"],
|
|
4347
|
+
grantee: grantee.grantPrincipal,
|
|
4348
|
+
resourceArns: [
|
|
4349
|
+
cdk.Stack.of(this).formatArn({
|
|
4350
|
+
arnFormat: cdk.ArnFormat.SLASH_RESOURCE_SLASH_RESOURCE_NAME,
|
|
4351
|
+
resource: this._api.apiId,
|
|
4352
|
+
resourceName: `${this._stage.stageName}/POST/@connections/*`,
|
|
4353
|
+
service: "execute-api",
|
|
4354
|
+
}),
|
|
4355
|
+
],
|
|
4356
|
+
});
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
|
|
4360
|
+
/**
|
|
4361
|
+
* JaypieWebSocketLambda - A Lambda function optimized for WebSocket handlers.
|
|
4362
|
+
*
|
|
4363
|
+
* Provides sensible defaults for WebSocket event handling:
|
|
4364
|
+
* - 30 second timeout (same as API handlers)
|
|
4365
|
+
* - API role tag
|
|
4366
|
+
*
|
|
4367
|
+
* @example
|
|
4368
|
+
* ```typescript
|
|
4369
|
+
* const handler = new JaypieWebSocketLambda(this, "ChatHandler", {
|
|
4370
|
+
* code: "dist/handlers",
|
|
4371
|
+
* handler: "chat.handler",
|
|
4372
|
+
* secrets: ["MONGODB_URI"],
|
|
4373
|
+
* });
|
|
4374
|
+
*
|
|
4375
|
+
* new JaypieWebSocket(this, "Chat", {
|
|
4376
|
+
* host: "ws.example.com",
|
|
4377
|
+
* handler,
|
|
4378
|
+
* });
|
|
4379
|
+
* ```
|
|
4380
|
+
*/
|
|
4381
|
+
class JaypieWebSocketLambda extends JaypieLambda {
|
|
4382
|
+
constructor(scope, id, props) {
|
|
4383
|
+
super(scope, id, {
|
|
4384
|
+
roleTag: CDK$2.ROLE.API,
|
|
4385
|
+
timeout: cdk.Duration.seconds(CDK$2.DURATION.EXPRESS_API),
|
|
4386
|
+
...props,
|
|
4387
|
+
});
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
//
|
|
4392
|
+
//
|
|
4393
|
+
// Main
|
|
4394
|
+
//
|
|
4395
|
+
/**
|
|
4396
|
+
* JaypieWebSocketTable - DynamoDB table for storing WebSocket connection IDs.
|
|
4397
|
+
*
|
|
4398
|
+
* Provides a simple table structure for tracking active WebSocket connections:
|
|
4399
|
+
* - Partition key: connectionId (String)
|
|
4400
|
+
* - TTL attribute: expiresAt (for automatic cleanup)
|
|
4401
|
+
* - Optional GSI: userId-index (for looking up connections by user)
|
|
4402
|
+
*
|
|
4403
|
+
* @example
|
|
4404
|
+
* ```typescript
|
|
4405
|
+
* const connectionTable = new JaypieWebSocketTable(this, "Connections");
|
|
4406
|
+
*
|
|
4407
|
+
* const ws = new JaypieWebSocket(this, "Chat", {
|
|
4408
|
+
* host: "ws.example.com",
|
|
4409
|
+
* handler: chatHandler,
|
|
4410
|
+
* });
|
|
4411
|
+
*
|
|
4412
|
+
* // Grant Lambda access to the table
|
|
4413
|
+
* connectionTable.grantReadWriteData(chatHandler);
|
|
4414
|
+
*
|
|
4415
|
+
* // Pass table name to Lambda
|
|
4416
|
+
* chatHandler.addEnvironment("CONNECTION_TABLE", connectionTable.tableName);
|
|
4417
|
+
* ```
|
|
4418
|
+
*
|
|
4419
|
+
* @example
|
|
4420
|
+
* // With user index for looking up all connections for a user
|
|
4421
|
+
* const connectionTable = new JaypieWebSocketTable(this, "Connections", {
|
|
4422
|
+
* userIndex: true,
|
|
4423
|
+
* ttl: Duration.hours(12),
|
|
4424
|
+
* });
|
|
4425
|
+
*/
|
|
4426
|
+
class JaypieWebSocketTable extends constructs.Construct {
|
|
4427
|
+
constructor(scope, id, props = {}) {
|
|
4428
|
+
super(scope, id);
|
|
4429
|
+
const { roleTag = CDK$2.ROLE.STORAGE, tableName, ttl = cdk.Duration.hours(24), userIndex = false, } = props;
|
|
4430
|
+
this._ttlDuration = ttl;
|
|
4431
|
+
// Build global secondary indexes
|
|
4432
|
+
const globalSecondaryIndexes = [];
|
|
4433
|
+
if (userIndex) {
|
|
4434
|
+
globalSecondaryIndexes.push({
|
|
4435
|
+
indexName: "userId-index",
|
|
4436
|
+
partitionKey: { name: "userId", type: dynamodb__namespace.AttributeType.STRING },
|
|
4437
|
+
sortKey: { name: "connectedAt", type: dynamodb__namespace.AttributeType.STRING },
|
|
4438
|
+
});
|
|
4439
|
+
}
|
|
4440
|
+
// Create the table
|
|
4441
|
+
this._table = new dynamodb__namespace.TableV2(this, "Table", {
|
|
4442
|
+
billing: dynamodb__namespace.Billing.onDemand(),
|
|
4443
|
+
globalSecondaryIndexes: globalSecondaryIndexes.length > 0 ? globalSecondaryIndexes : undefined,
|
|
4444
|
+
partitionKey: {
|
|
4445
|
+
name: "connectionId",
|
|
4446
|
+
type: dynamodb__namespace.AttributeType.STRING,
|
|
4447
|
+
},
|
|
4448
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
4449
|
+
tableName: tableName || constructEnvName("WebSocketConnections"),
|
|
4450
|
+
timeToLiveAttribute: "expiresAt",
|
|
4451
|
+
});
|
|
4452
|
+
cdk.Tags.of(this._table).add(CDK$2.TAG.ROLE, roleTag);
|
|
4453
|
+
}
|
|
4454
|
+
//
|
|
4455
|
+
//
|
|
4456
|
+
// Public accessors
|
|
4457
|
+
//
|
|
4458
|
+
/**
|
|
4459
|
+
* The underlying DynamoDB TableV2 construct.
|
|
4460
|
+
*/
|
|
4461
|
+
get table() {
|
|
4462
|
+
return this._table;
|
|
4463
|
+
}
|
|
4464
|
+
/**
|
|
4465
|
+
* The name of the DynamoDB table.
|
|
4466
|
+
*/
|
|
4467
|
+
get tableName() {
|
|
4468
|
+
return this._table.tableName;
|
|
4469
|
+
}
|
|
4470
|
+
/**
|
|
4471
|
+
* The ARN of the DynamoDB table.
|
|
4472
|
+
*/
|
|
4473
|
+
get tableArn() {
|
|
4474
|
+
return this._table.tableArn;
|
|
4475
|
+
}
|
|
4476
|
+
/**
|
|
4477
|
+
* TTL duration for connections in seconds.
|
|
4478
|
+
* Use this to calculate expiresAt when storing connections.
|
|
4479
|
+
*/
|
|
4480
|
+
get ttlSeconds() {
|
|
4481
|
+
return this._ttlDuration.toSeconds();
|
|
4482
|
+
}
|
|
4483
|
+
//
|
|
4484
|
+
//
|
|
4485
|
+
// Grant methods
|
|
4486
|
+
//
|
|
4487
|
+
/**
|
|
4488
|
+
* Grant read permissions to the table.
|
|
4489
|
+
*/
|
|
4490
|
+
grantReadData(grantee) {
|
|
4491
|
+
return this._table.grantReadData(grantee);
|
|
4492
|
+
}
|
|
4493
|
+
/**
|
|
4494
|
+
* Grant write permissions to the table.
|
|
4495
|
+
*/
|
|
4496
|
+
grantWriteData(grantee) {
|
|
4497
|
+
return this._table.grantWriteData(grantee);
|
|
4498
|
+
}
|
|
4499
|
+
/**
|
|
4500
|
+
* Grant read and write permissions to the table.
|
|
4501
|
+
*/
|
|
4502
|
+
grantReadWriteData(grantee) {
|
|
4503
|
+
return this._table.grantReadWriteData(grantee);
|
|
4504
|
+
}
|
|
4505
|
+
//
|
|
4506
|
+
//
|
|
4507
|
+
// Convenience methods
|
|
4508
|
+
//
|
|
4509
|
+
/**
|
|
4510
|
+
* Add the table name to a Lambda function's environment variables.
|
|
4511
|
+
* Also grants read/write access to the table.
|
|
4512
|
+
*/
|
|
4513
|
+
connectLambda(lambdaFunction, options = {}) {
|
|
4514
|
+
const { envKey = "CONNECTION_TABLE", readOnly = false } = options;
|
|
4515
|
+
// Add environment variable
|
|
4516
|
+
if ("addEnvironment" in lambdaFunction) {
|
|
4517
|
+
lambdaFunction.addEnvironment(envKey, this.tableName);
|
|
4518
|
+
}
|
|
4519
|
+
// Grant permissions
|
|
4520
|
+
if (readOnly) {
|
|
4521
|
+
this.grantReadData(lambdaFunction.grantPrincipal);
|
|
4522
|
+
}
|
|
4523
|
+
else {
|
|
4524
|
+
this.grantReadWriteData(lambdaFunction.grantPrincipal);
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4153
4529
|
exports.CDK = CDK$2;
|
|
4154
4530
|
exports.JaypieAccountLoggingBucket = JaypieAccountLoggingBucket;
|
|
4155
4531
|
exports.JaypieApiGateway = JaypieApiGateway;
|
|
@@ -4180,6 +4556,9 @@ exports.JaypieStack = JaypieStack;
|
|
|
4180
4556
|
exports.JaypieStaticWebBucket = JaypieStaticWebBucket;
|
|
4181
4557
|
exports.JaypieTraceSigningKeySecret = JaypieTraceSigningKeySecret;
|
|
4182
4558
|
exports.JaypieWebDeploymentBucket = JaypieWebDeploymentBucket;
|
|
4559
|
+
exports.JaypieWebSocket = JaypieWebSocket;
|
|
4560
|
+
exports.JaypieWebSocketLambda = JaypieWebSocketLambda;
|
|
4561
|
+
exports.JaypieWebSocketTable = JaypieWebSocketTable;
|
|
4183
4562
|
exports.addDatadogLayers = addDatadogLayers;
|
|
4184
4563
|
exports.clearAllCertificateCaches = clearAllCertificateCaches;
|
|
4185
4564
|
exports.clearAllSecretsCaches = clearAllSecretsCaches;
|