0%
January 5, 2025

AWS Websocket-API 1: An overview from API Gateway

api-gateway

aws

web-socket

API-Gateway

The Endpoints

After we have built websocket-apis from API-Gateway, we get the endpoints from:

Here the one with wss is for WebSocket api, and the one with https is for POST request or aws-sdk.

Routes (routeKeys) available for the https-endpoint

By default we have $connect, $disconnect and $default:

Although we have configured custom routeKey, but it is no more convenient than setting a data.action. Therefore we can omit it and relies on $default.

Quick link for the cloudwatch of the corresponding lambda function.

Edit execution role in order to send messages to frontend

With the default role we will get the following error when we try to send a message to a connnectionId (we explain how to do it later):

User: arn:aws:sts::562976154517:assumed-role/websocket-testing-role-vkzcsju3
/websocket-testing is not authorized to perform: execute-api:ManageConnections
on resource: arn:aws:execute-api:ap-northeast-1:********4517:3hfbt7ivk0/production
/POST/@connections/{connectionId}",

Here we can identify:

  • 562976154517 is our AWS Account ID;
  • 3hfbt7ivk0 is our API Gateway API ID;

repsectively, therefore we need to adjust the execution role of the lambda:

We create an inline-policy in json format:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["execute-api:ManageConnections"],
      "Resource": [
        "arn:aws:execute-api:ap-southeast-2:798404461798:smcflfkjb6/dev/POST/@connections/*"
      ]
    }
  ]
}

Basically when we provide the error log to LLM, we will get the related configuration.

Implementation

Procedures to get connectionId in frontend

Here we get our connectionId in 3 steps:

  1. We first connect by calling the api:

    new WebSocket("wss://<domain-name>/production/");
  2. We listen to open (i.e., connected) event, we then send an event to request for connectionId by sending { action: GET_CONNECTION_ID }

  3. We listen to message and get the connectionId once

    JSON.parse(event?.data || "{}")?.connectionId;

    exists.

Now we can request to join any chatroom using our own connectionId.

Frontend: Get connectionId from frontend
1  useEffect(()=>{
2    const socket = new WebSocket('wss://<domain-name>/production/');
3      socket.addEventListener('open', (_event) => {
4        console.log("getting connection id ...")
5        socket.send(JSON.stringify({ action: "GET_CONNECTION_ID" }));
6    });

We have dispatched a GET_CONNECTION_ID event, next let's wait for the return:

7      socket.addEventListener('message', (event) => {
8        console.log("message recevied")
9        const messageData = JSON.parse(event?.data || "{}") as {connectionId?: string}
10          if (messageData.connectionId) {
11              setConnectionId(messageData.connectionId)
12          }
13    });
14  }, [])
Code of lambda connected to websocket-api
// index.mjs
import {
  ApiGatewayManagementApiClient,
  PostToConnectionCommand,
} from "@aws-sdk/client-apigatewaymanagementapi";

export const handler = async (event) => {
  const connectionId = event.requestContext.connectionId;
  const routeKey = event.requestContext.routeKey;
  const eventType = event.requestContext.eventType;
  console.log("is it a normal event?", event);
  const action = JSON.parse(event?.body || "{}")?.action;

  if (action === "GET_CONNECTION_ID") {
    const callbackUrl = `https://${event.requestContext.domainName}/${event.requestContext.stage}`;
    const clientApi = new ApiGatewayManagementApiClient({
      endpoint: callbackUrl,
    });
    const requestParams = {
      ConnectionId: connectionId,
      Data: JSON.stringify({ connectionId }),
    };
    const command = new PostToConnectionCommand(requestParams);
    await clientApi.send(command);
  }

  const response = {
    statusCode: 200,
  };

  return response;
};

Reference