@jaypie/constructs 1.2.17 → 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/JaypieNextJs.d.ts +9 -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 +449 -55
- 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/JaypieNextJs.d.ts +9 -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 +406 -17
- 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
|
|
@@ -3217,13 +3222,19 @@ class JaypieMongoDbSecret extends JaypieEnvSecret {
|
|
|
3217
3222
|
class JaypieNextJs extends constructs.Construct {
|
|
3218
3223
|
constructor(scope, id, props) {
|
|
3219
3224
|
super(scope, id);
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3225
|
+
// Determine if we should use a custom domain
|
|
3226
|
+
const useDomain = props?.domainProps !== false;
|
|
3227
|
+
// Resolve domain name only if using a custom domain
|
|
3228
|
+
const domainName = useDomain
|
|
3229
|
+
? typeof props?.domainName === "string"
|
|
3230
|
+
? props.domainName
|
|
3231
|
+
: envHostname(props?.domainName)
|
|
3232
|
+
: undefined;
|
|
3223
3233
|
this.domainName = domainName;
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
.replace(/[^a-zA-Z0-9]/g, "_")
|
|
3234
|
+
// Use domain name or construct ID for cache policy naming
|
|
3235
|
+
const cachePolicyIdentifier = domainName
|
|
3236
|
+
? domainName.replace(/\./g, "-").replace(/[^a-zA-Z0-9]/g, "_")
|
|
3237
|
+
: id.replace(/[^a-zA-Z0-9]/g, "_");
|
|
3227
3238
|
// Resolve environment from array or object syntax
|
|
3228
3239
|
const environment = resolveEnvironment(props?.environment);
|
|
3229
3240
|
const envSecrets = props?.envSecrets || {};
|
|
@@ -3260,27 +3271,32 @@ class JaypieNextJs extends constructs.Construct {
|
|
|
3260
3271
|
}, {});
|
|
3261
3272
|
const nextjs = new cdkNextjsStandalone.Nextjs(this, "NextJsApp", {
|
|
3262
3273
|
nextjsPath,
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3274
|
+
// Only configure custom domain if useDomain is true
|
|
3275
|
+
...(useDomain &&
|
|
3276
|
+
domainName && {
|
|
3277
|
+
domainProps: {
|
|
3278
|
+
domainName,
|
|
3279
|
+
hostedZone: resolveHostedZone(this, {
|
|
3280
|
+
zone: props?.hostedZone,
|
|
3281
|
+
}),
|
|
3282
|
+
},
|
|
3283
|
+
}),
|
|
3269
3284
|
environment: {
|
|
3270
3285
|
...jaypieLambdaEnv(),
|
|
3271
3286
|
...environment,
|
|
3272
3287
|
...secretsEnvironment,
|
|
3273
3288
|
...jaypieSecretsEnvironment,
|
|
3274
3289
|
...nextPublicEnv,
|
|
3275
|
-
NEXT_PUBLIC_SITE_URL
|
|
3290
|
+
// NEXT_PUBLIC_SITE_URL will be set after construct creation for CloudFront URL
|
|
3291
|
+
...(domainName && { NEXT_PUBLIC_SITE_URL: `https://${domainName}` }),
|
|
3276
3292
|
},
|
|
3277
3293
|
overrides: {
|
|
3278
3294
|
nextjsDistribution: {
|
|
3279
3295
|
imageCachePolicyProps: {
|
|
3280
|
-
cachePolicyName: `NextJsImageCachePolicy-${
|
|
3296
|
+
cachePolicyName: `NextJsImageCachePolicy-${cachePolicyIdentifier}`,
|
|
3281
3297
|
},
|
|
3282
3298
|
serverCachePolicyProps: {
|
|
3283
|
-
cachePolicyName: `NextJsServerCachePolicy-${
|
|
3299
|
+
cachePolicyName: `NextJsServerCachePolicy-${cachePolicyIdentifier}`,
|
|
3284
3300
|
},
|
|
3285
3301
|
},
|
|
3286
3302
|
nextjsImage: {
|
|
@@ -3295,6 +3311,10 @@ class JaypieNextJs extends constructs.Construct {
|
|
|
3295
3311
|
},
|
|
3296
3312
|
},
|
|
3297
3313
|
});
|
|
3314
|
+
// Set NEXT_PUBLIC_SITE_URL to CloudFront URL when no custom domain
|
|
3315
|
+
if (!domainName) {
|
|
3316
|
+
nextjs.serverFunction.lambdaFunction.addEnvironment("NEXT_PUBLIC_SITE_URL", `https://${nextjs.distribution.distributionDomain}`);
|
|
3317
|
+
}
|
|
3298
3318
|
addDatadogLayers(nextjs.imageOptimizationFunction);
|
|
3299
3319
|
addDatadogLayers(nextjs.serverFunction.lambdaFunction);
|
|
3300
3320
|
// Grant secret read permissions
|
|
@@ -3425,21 +3445,21 @@ class JaypieOrganizationTrail extends constructs.Construct {
|
|
|
3425
3445
|
],
|
|
3426
3446
|
});
|
|
3427
3447
|
// Add CloudTrail bucket policies
|
|
3428
|
-
this.bucket.addToResourcePolicy(new
|
|
3448
|
+
this.bucket.addToResourcePolicy(new iam.PolicyStatement({
|
|
3429
3449
|
actions: ["s3:GetBucketAcl"],
|
|
3430
|
-
effect:
|
|
3431
|
-
principals: [new
|
|
3450
|
+
effect: iam.Effect.ALLOW,
|
|
3451
|
+
principals: [new iam.ServicePrincipal("cloudtrail.amazonaws.com")],
|
|
3432
3452
|
resources: [this.bucket.bucketArn],
|
|
3433
3453
|
}));
|
|
3434
|
-
this.bucket.addToResourcePolicy(new
|
|
3454
|
+
this.bucket.addToResourcePolicy(new iam.PolicyStatement({
|
|
3435
3455
|
actions: ["s3:PutObject"],
|
|
3436
3456
|
conditions: {
|
|
3437
3457
|
StringEquals: {
|
|
3438
3458
|
"s3:x-amz-acl": "bucket-owner-full-control",
|
|
3439
3459
|
},
|
|
3440
3460
|
},
|
|
3441
|
-
effect:
|
|
3442
|
-
principals: [new
|
|
3461
|
+
effect: iam.Effect.ALLOW,
|
|
3462
|
+
principals: [new iam.ServicePrincipal("cloudtrail.amazonaws.com")],
|
|
3443
3463
|
resources: [`${this.bucket.bucketArn}/*`],
|
|
3444
3464
|
}));
|
|
3445
3465
|
// Add tags to bucket
|
|
@@ -3532,9 +3552,9 @@ class JaypieSsoPermissions extends constructs.Construct {
|
|
|
3532
3552
|
],
|
|
3533
3553
|
},
|
|
3534
3554
|
managedPolicies: [
|
|
3535
|
-
|
|
3555
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess")
|
|
3536
3556
|
.managedPolicyArn,
|
|
3537
|
-
|
|
3557
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
|
|
3538
3558
|
],
|
|
3539
3559
|
sessionDuration: cdk.Duration.hours(1).toIsoString(),
|
|
3540
3560
|
tags: [
|
|
@@ -3613,10 +3633,10 @@ class JaypieSsoPermissions extends constructs.Construct {
|
|
|
3613
3633
|
],
|
|
3614
3634
|
},
|
|
3615
3635
|
managedPolicies: [
|
|
3616
|
-
|
|
3636
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
|
|
3617
3637
|
.managedPolicyArn,
|
|
3618
|
-
|
|
3619
|
-
|
|
3638
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
|
|
3639
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
|
|
3620
3640
|
.managedPolicyArn,
|
|
3621
3641
|
],
|
|
3622
3642
|
sessionDuration: cdk.Duration.hours(12).toIsoString(),
|
|
@@ -3671,12 +3691,12 @@ class JaypieSsoPermissions extends constructs.Construct {
|
|
|
3671
3691
|
],
|
|
3672
3692
|
},
|
|
3673
3693
|
managedPolicies: [
|
|
3674
|
-
|
|
3694
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonQDeveloperAccess")
|
|
3675
3695
|
.managedPolicyArn,
|
|
3676
|
-
|
|
3677
|
-
|
|
3696
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("AWSManagementConsoleBasicUserAccess").managedPolicyArn,
|
|
3697
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")
|
|
3678
3698
|
.managedPolicyArn,
|
|
3679
|
-
|
|
3699
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName("job-function/SystemAdministrator").managedPolicyArn,
|
|
3680
3700
|
],
|
|
3681
3701
|
sessionDuration: cdk.Duration.hours(4).toIsoString(),
|
|
3682
3702
|
tags: [
|
|
@@ -3889,8 +3909,8 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
|
|
|
3889
3909
|
repo = `repo:${process.env.CDK_ENV_REPO}:*`;
|
|
3890
3910
|
}
|
|
3891
3911
|
if (repo) {
|
|
3892
|
-
const bucketDeployRole = new
|
|
3893
|
-
assumedBy: new
|
|
3912
|
+
const bucketDeployRole = new iam.Role(this, "DestinationBucketDeployRole", {
|
|
3913
|
+
assumedBy: new iam.FederatedPrincipal(cdk.Fn.importValue(CDK$2.IMPORT.OIDC_PROVIDER), {
|
|
3894
3914
|
StringLike: {
|
|
3895
3915
|
"token.actions.githubusercontent.com:sub": repo,
|
|
3896
3916
|
},
|
|
@@ -3899,8 +3919,8 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
|
|
|
3899
3919
|
});
|
|
3900
3920
|
cdk.Tags.of(bucketDeployRole).add(CDK$2.TAG.ROLE, CDK$2.ROLE.DEPLOY);
|
|
3901
3921
|
// Allow the role to write to the bucket
|
|
3902
|
-
bucketDeployRole.addToPolicy(new
|
|
3903
|
-
effect:
|
|
3922
|
+
bucketDeployRole.addToPolicy(new iam.PolicyStatement({
|
|
3923
|
+
effect: iam.Effect.ALLOW,
|
|
3904
3924
|
actions: [
|
|
3905
3925
|
"s3:DeleteObject",
|
|
3906
3926
|
"s3:GetObject",
|
|
@@ -3909,16 +3929,16 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
|
|
|
3909
3929
|
],
|
|
3910
3930
|
resources: [`${this.bucket.bucketArn}/*`],
|
|
3911
3931
|
}));
|
|
3912
|
-
bucketDeployRole.addToPolicy(new
|
|
3913
|
-
effect:
|
|
3932
|
+
bucketDeployRole.addToPolicy(new iam.PolicyStatement({
|
|
3933
|
+
effect: iam.Effect.ALLOW,
|
|
3914
3934
|
actions: ["s3:ListBucket"],
|
|
3915
3935
|
resources: [this.bucket.bucketArn],
|
|
3916
3936
|
}));
|
|
3917
3937
|
// Allow the role to describe the current stack
|
|
3918
3938
|
const stack = cdk.Stack.of(this);
|
|
3919
|
-
bucketDeployRole.addToPolicy(new
|
|
3939
|
+
bucketDeployRole.addToPolicy(new iam.PolicyStatement({
|
|
3920
3940
|
actions: ["cloudformation:DescribeStacks"],
|
|
3921
|
-
effect:
|
|
3941
|
+
effect: iam.Effect.ALLOW,
|
|
3922
3942
|
resources: [
|
|
3923
3943
|
`arn:aws:cloudformation:${stack.region}:${stack.account}:stack/${stack.stackName}/*`,
|
|
3924
3944
|
],
|
|
@@ -4135,6 +4155,377 @@ class JaypieTraceSigningKeySecret extends JaypieEnvSecret {
|
|
|
4135
4155
|
}
|
|
4136
4156
|
}
|
|
4137
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}/*`,
|
|
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
|
+
|
|
4138
4529
|
exports.CDK = CDK$2;
|
|
4139
4530
|
exports.JaypieAccountLoggingBucket = JaypieAccountLoggingBucket;
|
|
4140
4531
|
exports.JaypieApiGateway = JaypieApiGateway;
|
|
@@ -4165,6 +4556,9 @@ exports.JaypieStack = JaypieStack;
|
|
|
4165
4556
|
exports.JaypieStaticWebBucket = JaypieStaticWebBucket;
|
|
4166
4557
|
exports.JaypieTraceSigningKeySecret = JaypieTraceSigningKeySecret;
|
|
4167
4558
|
exports.JaypieWebDeploymentBucket = JaypieWebDeploymentBucket;
|
|
4559
|
+
exports.JaypieWebSocket = JaypieWebSocket;
|
|
4560
|
+
exports.JaypieWebSocketLambda = JaypieWebSocketLambda;
|
|
4561
|
+
exports.JaypieWebSocketTable = JaypieWebSocketTable;
|
|
4168
4562
|
exports.addDatadogLayers = addDatadogLayers;
|
|
4169
4563
|
exports.clearAllCertificateCaches = clearAllCertificateCaches;
|
|
4170
4564
|
exports.clearAllSecretsCaches = clearAllSecretsCaches;
|