Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(appsync): add L2 constructs for AWS AppSync Events #32505

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
66df5d6
(feat) AppSync Event API constructs
kwwendt Nov 21, 2024
8d8e1d4
(cleanup) Clean up linting issues, initial auth test updates.
kwwendt Nov 25, 2024
bf7d917
(cleanup) Switch back name property
kwwendt Nov 25, 2024
d2829e2
(cleanup) AppSync Event API changes
kwwendt Nov 26, 2024
af393fe
wip
onlybakam Dec 7, 2024
39d4a6e
wip
onlybakam Dec 8, 2024
6a22555
wip: update test, allow multiple API keys, use cognitoConfig
onlybakam Dec 9, 2024
c2fcac3
wip
onlybakam Dec 9, 2024
cf03ca0
wip
onlybakam Dec 9, 2024
8669b85
Merge branch 'appsync-events-bricep' into HEAD
onlybakam Dec 9, 2024
51effd9
Merge pull request #3 from kwwendt/appsync-events-bricep
kwwendt Dec 9, 2024
65cf6bb
Resolve build errors
kwwendt Dec 10, 2024
22f2528
Merge branch 'aws:main' into appsync-events
kwwendt Dec 11, 2024
f17f025
Updates to appsync events
kwwendt Dec 11, 2024
5037e15
Update fromEventApiAttributes method
kwwendt Dec 11, 2024
c61d5c5
Fix event api constructor
kwwendt Dec 11, 2024
c6213ef
AppSync Event grant updates
kwwendt Dec 11, 2024
859b553
Add custom domain functionality and update comment for Event Api.
kwwendt Dec 12, 2024
dd464e7
Clean up for Event API, sync integration tests.
kwwendt Dec 13, 2024
f16b6e8
Merge branch 'aws:main' into appsync-events
kwwendt Dec 13, 2024
29d8151
Update integration test snapshot files and slight mods to constructs,…
kwwendt Dec 14, 2024
b83432e
Merge branch 'main' into appsync-events
kwwendt Dec 14, 2024
64bcc1e
Merge branch 'main' into appsync-events
kwwendt Dec 14, 2024
dca38c9
Fix integration tests
kwwendt Dec 14, 2024
ab64ed0
Merge branch 'main' into appsync-events
kwwendt Dec 16, 2024
0328809
Update packages/aws-cdk-lib/aws-appsync/lib/eventapi.ts
kwwendt Dec 17, 2024
bcff247
Update packages/aws-cdk-lib/aws-appsync/lib/eventapi.ts
kwwendt Dec 17, 2024
b5bec82
Updates based on review feedback
kwwendt Dec 17, 2024
86b11bb
Re-run integ tests
kwwendt Dec 17, 2024
b34577b
Merge branch 'main' into appsync-events
kwwendt Dec 17, 2024
68d4959
Build update
kwwendt Dec 17, 2024
da935fa
Updates based on review.
kwwendt Dec 20, 2024
cba1a49
Merge branch 'main' into appsync-events
kwwendt Dec 20, 2024
6b331c8
wip
onlybakam Dec 9, 2024
7397f21
Updates and fixes after merge.
kwwendt Dec 20, 2024
b2b059e
Updates and fixes after merge.
kwwendt Dec 20, 2024
f187be4
Re-run integ tests
kwwendt Dec 20, 2024
51bf49c
Update README
kwwendt Dec 20, 2024
1ae1554
Updates based on review.
kwwendt Dec 28, 2024
7bdfbb7
Update based on review.
kwwendt Dec 28, 2024
5d26e86
Updates based on latest review.
kwwendt Jan 2, 2025
4fb6405
Merge branch 'main' into appsync-events
kwwendt Jan 2, 2025
3f5c841
Merge branch 'main' of github.com:kwwendt/aws-cdk into appsync-events
kwwendt Jan 2, 2025
4688bb3
Merge branch 'main' into appsync-events
kwwendt Jan 2, 2025
4f2a73b
Merge branch 'main' into appsync-events
kwwendt Jan 6, 2025
b01eb6c
Merge branch 'appsync-events' of github.com:kwwendt/aws-cdk into apps…
kwwendt Jan 8, 2025
a253f41
Update integration tests and unit tests based on security review.
kwwendt Jan 8, 2025
b12202c
Merge branch 'main' of github.com:kwwendt/aws-cdk into appsync-events
kwwendt Jan 8, 2025
34be276
Updates based on security review
kwwendt Jan 9, 2025
addcd38
remove oidc tests due to static values
kwwendt Jan 9, 2025
91172b4
Merge branch 'main' into appsync-events
kwwendt Jan 11, 2025
4a2f64d
Add additional integration test for validating grants
kwwendt Jan 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function onPublish(ctx) {
return ctx.events.filter((event) => event.payload.odds > 0)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Reference: https://github.com/onlybakam/appsync-events-client-tutorial/blob/main/app/signer-smithy.mjs

import { HttpRequest } from '@smithy/protocol-http'
import { SignatureV4 } from '@smithy/signature-v4'
import { fromNodeProviderChain } from '@aws-sdk/credential-providers'
import { Sha256 } from '@aws-crypto/sha256-js'

// The default headers to to sign the request
const DEFAULT_HEADERS = {
accept: 'application/json, text/javascript',
'content-encoding': 'amz-1.0',
'content-type': 'application/json; charset=UTF-8',
}

const AWS_APPSYNC_EVENTS_SUBPROTOCOL = 'aws-appsync-event-ws';
const realtimeUrl = process.env.EVENT_API_REALTIME_URL;
const httpUrl = process.env.EVENT_API_HTTP_URL;
const region = process.env.AWS_REGION;

/**
* Returns a signed authorization object
*
* @param {string} httpDomain the AppSync Event API HTTP domain
* @param {string} region the AWS region of your API
* @param {string} [body] the body of the request
* @returns {Object}
*/
async function signWithAWSV4(httpDomain, region, body) {
const signer = new SignatureV4({
credentials: fromNodeProviderChain(),
service: 'appsync',
region,
sha256: Sha256,
})

const url = new URL(`https://${httpDomain}/event`)
const request = new HttpRequest({
method: 'POST',
headers: {
...DEFAULT_HEADERS,
host: url.hostname,
},
body: body ?? '{}',
hostname: url.hostname,
path: url.pathname,
})

const signedHttpRequest = await signer.sign(request)

return {
host: signedHttpRequest.hostname,
...signedHttpRequest.headers,
}
}

/**
* Returns a header value for the SubProtocol header
* @param {string} httpDomain the AppSync Event API HTTP domain
* @param {string} region the AWS region of your API
* @returns string a header string
*/
async function getAuthProtocolForIAM(httpDomain, region) {
const signed = await signWithAWSV4(httpDomain, region)
const based64UrlHeader = btoa(JSON.stringify(signed))
.replace(/\+/g, '-') // Convert '+' to '-'
.replace(/\//g, '_') // Convert '/' to '_'
.replace(/=+$/, '') // Remove padding `=`
return `header-${based64UrlHeader}`
}

/**
* Returns a Promise after a delay
*
* @param {int} ms milliseconds to delay
* @returns {Promise}
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

/**
* Initiates a subscription to a channel and returns the response
*
* @param {string} channel the channel to subscribe to
* @param {boolean} triggerPub whether to also publish in the method
* @returns {Object}
*/
async function subscribe(channel, triggerPub=false) {
const response = {};
const auth = await getAuthProtocolForIAM(httpUrl, region)
const socket = await new Promise((resolve, reject) => {
const socket = new WebSocket(
`wss://${realtimeUrl}/event/realtime`,
[AWS_APPSYNC_EVENTS_SUBPROTOCOL, auth],
{ headers: { ...DEFAULT_HEADERS } },
)

socket.onopen = () => {
socket.send(JSON.stringify({ type: 'connection_init' }))
console.log("Initialize connection");
resolve(socket)
}

socket.onclose = (evt) => reject(new Error(evt.reason))
socket.onmessage = (event) => {
const payload = JSON.parse(event.data);
console.log('=>', payload);
if (payload.type === 'subscribe_success') {
console.log('Connection established')
response.statusCode = 200;
response.msg = 'subscribe_success';
} else if (payload.type === 'data') {
console.log('Data received');
response.pubStatusCode = 200;
response.pubMsg = JSON.parse(payload.event).message;
} else if (payload.type === "subscribe_error") {
console.log(payload);
if (payload.errors.some((error) => error.errorType === "UnauthorizedException")) {
console.log("Error received");
response.statusCode = 401;
response.msg = "UnauthorizedException";
} else if (payload.errors.some(error => error.errorType === 'AccessDeniedException')) {
console.log('Error received');
response.statusCode = 403;
response.msg = 'Forbidden';
} else {
console.log("Error received");
response.statusCode = 400;
response.msg = payload.errors[0].errorType;
}
}
}
socket.onerror = (event) => console.log(event)
});

const subChannel = `/${channel}/*`;
socket.send(JSON.stringify({
type: 'subscribe',
id: crypto.randomUUID(),
channel: subChannel,
authorization: await signWithAWSV4(httpUrl, region, JSON.stringify({ channel: subChannel })),
}));

if (triggerPub) {
await sleep(1000);
await publish(channel);
}
await sleep(3000);
return response;
}

/**
* Publishes to a channel and returns the response
*
* @param {string} channel the channel to publish to
* @returns {Object}
*/
async function publish(channel) {
const event = {
"channel": `/${channel}/test`,
"events": [
JSON.stringify({message:'Hello World!'})
]
}

const response = await fetch(`https://${httpUrl}/event`, {
method: 'POST',
headers: await signWithAWSV4(httpUrl, region, JSON.stringify(event)),
body: JSON.stringify(event)
});

if (!response.ok) {
return {
statusCode: response.status,
msg: response.statusText
}
}
const output = await response.json();
return {
statusCode: 200,
msg: output.successful.length == 1 ? 'publish_success' : 'publish_fail',
}
}

/**
*
* @param {Object} event json object that contains the action and channel
* @returns {Object}
*/
exports.handler = async function(event) {
const pubSubAction = event.action;
const channel = event.channel;

if (pubSubAction === 'publish') {
const res = await publish(channel, false);
console.log(res);
return res;
} else if (pubSubAction === 'subscribe') {
const res = await subscribe(channel, false);
console.log(res);
return res;
} else if (pubSubAction === 'pubSub') {
const res = await subscribe(channel, true);
console.log(res);
return res;
}
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading