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

Adapter - AWS API Gateway Websocket #211

Open
1 of 4 tasks
leonardodimarchi opened this issue Mar 2, 2024 · 2 comments
Open
1 of 4 tasks

Adapter - AWS API Gateway Websocket #211

leonardodimarchi opened this issue Mar 2, 2024 · 2 comments
Labels
enhancement New feature or request

Comments

@leonardodimarchi
Copy link
Contributor

Feature Request

Is your feature request related to a problem? Please describe.

A few days ago i implemented a chat functionality at my api using api gateway websocket from aws. To do that, i've created an adapter (to forward the request context to my application) by extending aws simple adapter (just like SQS adapter) and creating the required interfaces.

It would be nice to have this adapter available in the library 🚀

Describe the solution you'd like

Create an adapter to handle api gateway websocket requests, maybe a more robust one than mine (that only forwards the request context).

Describe alternatives you've considered

  • Custom adapter extending aws simple adapter to forward the request context to my application

Are you willing to resolve this issue by submitting a Pull Request?

Right now i don't have the time for it, but it would be awesome to contribute!

  • Yes, I have the time, and I know how to start.
  • Yes, I have the time, but I don't know how to start. I would need guidance.
  • No, I don't have the time, although I believe I could do it if I had the time...
  • No, I don't have the time and I wouldn't even know how to start.
@leonardodimarchi leonardodimarchi added the enhancement New feature or request label Mar 2, 2024
@H4ad
Copy link
Owner

H4ad commented Mar 3, 2024

Hm... I already thought we could add support for Websocket but I never found any reasonable API Design to work with Websockets.

Websockets has the following events:

  • $connect: when the user is connected to the websocket.
  • $disconnect: when the user is disconnected from the websocket.
  • $default or personalized route: when the user sends any information to the websocket.

The support to receive data is very easy, and return data in the same connection is also easy since we can get the connectionId from the context and forward the response to the user using @connections

The main issue is when the API wants to send data to one or more users that are not related to the current connectionId, like when someone wins a game and they need to notify everyone connected, they have two choices:

  • get all the connections and then send the response synchronously via @connections.
  • gell all the connections, queue the send message and then send via @connections in the queue handler (that could be the same API).

The first option is preferable for faster responses, the second one is slower but also works.

But in any case, to support via my library, I will need to create some "Websocket -> HTTP", I don't think there is a way to map 1:1 with websocket protocol that we usually use (socket.io, ...etc).

Storing the users is something my library will not deal but maybe I could create an abstraction the developers could use, something like:

// my lib
export type WebsocketConnection<TExtraValues> = { connectionId: string } & TExtraValues;

export interface WebsocketStore<TExtraValues = {}> {
  getConnections(): Promise<WebsocketConnection<TExtraValues>[]>;
  storeConnection(connection: WebsocketConnection<TExtraValues>): Promise<void>;
  deleteConnection(connectionId: string): Promise<void>;
}

export function getCurrentStore<TExtraValues>(): WebsocketStore<TExtraValues>;

// on index.ts
type WebsocketCustomData = { name: string };
const customStore = new RedisWebsocketStore<WebsocketCustomData>(); // implements WebsocketStore
const websocketHandler = new WebsocketHandler(customStore);

// developers
const store = getCurrentStore<WebsocketCustomData>();
const connections = store.getConnections();

What do you think, this is something that could work for you?

@leonardodimarchi
Copy link
Contributor Author

leonardodimarchi commented Mar 3, 2024

It would be great to have the possibility to just create a store, specify the headers that i want to use as my extra values and delegate the connect and disconnect part for the library!

I personally don't know how exactly it would work if my store were a nestjs service with typeorm dependency.

For me, the best thing that the adapter could do would be to forward the request context to my controller with the correct interface, i mean, it would be great to use something like this:

index.ts

const express = new ExpressFramework();
const framework = new LazyFramework(express, bootstrap);

export const handler = ServerlessAdapter.new(null)
  .setFramework(framework)
  .setHandler(new DefaultHandler())
  .setResolver(new PromiseResolver())
  .setLogger(createDefaultLogger({ level: 'error' }))
  .addAdapter(new ApiGatewayV2Adapter())
  .addAdapter(new ApiGatewayWebsocketAdapter({
       connectOptions: { // If not using the custom store for some reason
          path: '/websocket/connect',
          method: 'POST'
       },
       disconnectOptions: { // If not using the custom store for some reason
          path: '/websocket/disconnect',
          method: 'POST'
       },
       defaultOptions: { 
          path: '/websocket/default',
          method: 'POST'
       },
       messageOptions: { 
          basePath: '/websocket',  // Maybe append the custom route here as well, e.g: /websocket/new-chat-message
          method: 'POST'
       },
   }))
  .build();

websocket.controller.ts

@Controller('websocket')
export class WebsocketController {
  constructor(
      connectionRepo: TypeormConnectionRepo,
      messageRepo: TypeormMessageRepo
  ) {}
  
  // If not using the custom store for some reason
  @Post('connect')
  connect(connection: {  connectionId: string, headers:   {  [key:string]: string }}) {
      this.connectionRepo.insert(...)
  }
  
  [...]
  
  @Post('new-chat-message')
  newMessage(connectionId: string) {
      this.messageRepo.insert(...)
      
      // Iterate through all connections to send the message using @connections via aws-sdk for example
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants