@pikku/lambda 0.6.3

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +3 -0
  3. package/dist/http/anywhere-http.d.ts +3 -0
  4. package/dist/http/anywhere-http.js +13 -0
  5. package/dist/http/anywhere-http.js.map +1 -0
  6. package/dist/http/cors-http.d.ts +3 -0
  7. package/dist/http/cors-http.js +35 -0
  8. package/dist/http/cors-http.js.map +1 -0
  9. package/dist/http/corsless-http.d.ts +3 -0
  10. package/dist/http/corsless-http.js +9 -0
  11. package/dist/http/corsless-http.js.map +1 -0
  12. package/dist/http/general-http-handler.d.ts +5 -0
  13. package/dist/http/general-http-handler.js +28 -0
  14. package/dist/http/general-http-handler.js.map +1 -0
  15. package/dist/http/index.d.ts +3 -0
  16. package/dist/http/index.js +4 -0
  17. package/dist/http/index.js.map +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/pikku-api-gateway-lambda-request.d.ts +12 -0
  22. package/dist/pikku-api-gateway-lambda-request.js +24 -0
  23. package/dist/pikku-api-gateway-lambda-request.js.map +1 -0
  24. package/dist/pikku-api-gateway-lambda-response.d.ts +15 -0
  25. package/dist/pikku-api-gateway-lambda-response.js +32 -0
  26. package/dist/pikku-api-gateway-lambda-response.js.map +1 -0
  27. package/dist/websocket/index.d.ts +4 -0
  28. package/dist/websocket/index.js +5 -0
  29. package/dist/websocket/index.js.map +1 -0
  30. package/dist/websocket/lambda-channel-handler.d.ts +4 -0
  31. package/dist/websocket/lambda-channel-handler.js +29 -0
  32. package/dist/websocket/lambda-channel-handler.js.map +1 -0
  33. package/dist/websocket/lambda-eventhub-service.d.ts +14 -0
  34. package/dist/websocket/lambda-eventhub-service.js +27 -0
  35. package/dist/websocket/lambda-eventhub-service.js.map +1 -0
  36. package/dist/websocket/utils.d.ts +13 -0
  37. package/dist/websocket/utils.js +59 -0
  38. package/dist/websocket/utils.js.map +1 -0
  39. package/dist/websocket/websocket-connect.d.ts +4 -0
  40. package/dist/websocket/websocket-connect.js +19 -0
  41. package/dist/websocket/websocket-connect.js.map +1 -0
  42. package/dist/websocket/websocket-disconnect.d.ts +4 -0
  43. package/dist/websocket/websocket-disconnect.js +12 -0
  44. package/dist/websocket/websocket-disconnect.js.map +1 -0
  45. package/dist/websocket/websocket-message.d.ts +4 -0
  46. package/dist/websocket/websocket-message.js +22 -0
  47. package/dist/websocket/websocket-message.js.map +1 -0
  48. package/dist/websocket/websocket-types.d.ts +7 -0
  49. package/dist/websocket/websocket-types.js +2 -0
  50. package/dist/websocket/websocket-types.js.map +1 -0
  51. package/package.json +37 -0
  52. package/run-tests.sh +53 -0
  53. package/src/http/anywhere-http.ts +39 -0
  54. package/src/http/cors-http.ts +70 -0
  55. package/src/http/corsless-http.ts +33 -0
  56. package/src/http/general-http-handler.ts +59 -0
  57. package/src/http/index.ts +3 -0
  58. package/src/index.ts +1 -0
  59. package/src/pikku-api-gateway-lambda-request.ts +31 -0
  60. package/src/pikku-api-gateway-lambda-response.ts +41 -0
  61. package/src/websocket/index.ts +4 -0
  62. package/src/websocket/lambda-channel-handler.ts +63 -0
  63. package/src/websocket/lambda-eventhub-service.ts +59 -0
  64. package/src/websocket/utils.ts +95 -0
  65. package/src/websocket/websocket-connect.ts +41 -0
  66. package/src/websocket/websocket-disconnect.ts +35 -0
  67. package/src/websocket/websocket-message.ts +48 -0
  68. package/src/websocket/websocket-types.ts +22 -0
  69. package/tsconfig.json +17 -0
  70. package/tsconfig.tsbuildinfo +1 -0
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@pikku/lambda",
3
+ "version": "0.6.3",
4
+ "license": "MIT",
5
+ "author": "yasser.fadl@gmail.com",
6
+ "module": "dist/index.js",
7
+ "main": "dist/index.js",
8
+ "type": "module",
9
+ "scripts": {
10
+ "tsc": "tsc",
11
+ "build:esm": "tsc -b",
12
+ "build": "yarn build:esm",
13
+ "release": "npm run build && npm test",
14
+ "ncu": "ncu",
15
+ "test": "bash run-tests.sh",
16
+ "test:watch": "bash run-tests.sh --watch",
17
+ "test:coverage": "bash run-tests.sh --coverage"
18
+ },
19
+ "exports": {
20
+ ".": "./dist/index.js",
21
+ "./http": "./dist/http/index.js",
22
+ "./websocket": "./dist/websocket/index.js"
23
+ },
24
+ "peerDependencies": {
25
+ "@aws-sdk/client-apigatewaymanagementapi": "^3.713.0",
26
+ "@pikku/core": "^0.6.7",
27
+ "@types/aws-lambda": "^8.10.146"
28
+ },
29
+ "devDependencies": {
30
+ "@aws-sdk/client-apigatewaymanagementapi": "^3.713.0",
31
+ "@types/aws-lambda": "^8.10.146",
32
+ "typescript": "^5.7.2"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ }
37
+ }
package/run-tests.sh ADDED
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+
3
+ # Enable nullglob to handle cases where no files match the pattern
4
+ shopt -s nullglob
5
+
6
+ # Initialize variables for options
7
+ watch_mode=false
8
+ coverage_mode=false
9
+
10
+ # Parse command-line options
11
+ while [[ $# -gt 0 ]]; do
12
+ case $1 in
13
+ --watch)
14
+ watch_mode=true
15
+ shift
16
+ ;;
17
+ --coverage)
18
+ coverage_mode=true
19
+ shift
20
+ ;;
21
+ *)
22
+ echo "Unknown option: $1"
23
+ exit 1
24
+ ;;
25
+ esac
26
+ done
27
+
28
+ # Define the pattern to match your test files
29
+ pattern="src/*.test.ts"
30
+
31
+ # Expand the pattern into an array of files
32
+ files=($(find src -type f -name "*.test.ts"))
33
+
34
+ # Check if any files matched the pattern
35
+ if [ ${#files[@]} -eq 0 ]; then
36
+ echo "No test files found matching pattern: $pattern"
37
+ exit 0
38
+ fi
39
+
40
+ # Construct the node command
41
+ node_cmd="node --import tsx --test"
42
+
43
+ # Append options based on flags
44
+ if [ "$watch_mode" = true ]; then
45
+ node_cmd="$node_cmd --watch"
46
+ fi
47
+
48
+ if [ "$coverage_mode" = true ]; then
49
+ node_cmd="$node_cmd --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info"
50
+ fi
51
+
52
+ # Execute the node command with the expanded list of files
53
+ $node_cmd "${files[@]}"
@@ -0,0 +1,39 @@
1
+ import {
2
+ CoreSingletonServices,
3
+ CoreServices,
4
+ CreateSessionServices,
5
+ CoreUserSession,
6
+ } from '@pikku/core'
7
+ import { APIGatewayProxyEvent } from 'aws-lambda'
8
+ import { generalHTTPHandler } from './general-http-handler.js'
9
+ import { PikkuAPIGatewayLambdaRequest } from '../pikku-api-gateway-lambda-request.js'
10
+ import { PikkuAPIGatewayLambdaResponse } from '../pikku-api-gateway-lambda-response.js'
11
+
12
+ export const anywhereHTTP = async <
13
+ SingletonServices extends CoreSingletonServices,
14
+ Services extends CoreServices<SingletonServices>,
15
+ >(
16
+ event: APIGatewayProxyEvent,
17
+ singletonServices: SingletonServices,
18
+ createSessionServices: CreateSessionServices<
19
+ SingletonServices,
20
+ Services,
21
+ CoreUserSession
22
+ >
23
+ ) => {
24
+ const request = new PikkuAPIGatewayLambdaRequest(event)
25
+ const response = new PikkuAPIGatewayLambdaResponse()
26
+ if (event.headers.origin) {
27
+ response.setHeader(
28
+ 'Access-Control-Allow-Origin',
29
+ event.headers.origin.toString()
30
+ )
31
+ }
32
+ response.setHeader('Access-Control-Allow-Credentials', true)
33
+ return await generalHTTPHandler(
34
+ singletonServices,
35
+ createSessionServices,
36
+ request,
37
+ response
38
+ )
39
+ }
@@ -0,0 +1,70 @@
1
+ import type {
2
+ CoreSingletonServices,
3
+ CoreServices,
4
+ CreateSessionServices,
5
+ CoreUserSession,
6
+ } from '@pikku/core'
7
+ import type { APIGatewayProxyEvent } from 'aws-lambda'
8
+ import type { Logger } from '@pikku/core/services'
9
+
10
+ import { generalHTTPHandler } from './general-http-handler.js'
11
+ import { InvalidOriginError } from '@pikku/core/errors'
12
+ import { PikkuAPIGatewayLambdaResponse } from '../pikku-api-gateway-lambda-response.js'
13
+ import { PikkuAPIGatewayLambdaRequest } from '../pikku-api-gateway-lambda-request.js'
14
+
15
+ const validateOrigin = (
16
+ allowsOrigins: string[],
17
+ logger: Logger,
18
+ event: APIGatewayProxyEvent
19
+ ): string => {
20
+ const origin = event.headers.origin
21
+ if (!origin || allowsOrigins.every((domain) => !origin.includes(domain))) {
22
+ logger.error(`
23
+ CORS Error
24
+ - Recieved from origin: ${origin}
25
+ - Expected domain(s): ${allowsOrigins.join(', ')}
26
+ - Host: ${event.headers.host}
27
+ - Path: ${event.path}
28
+ - Headers: ${JSON.stringify(event.headers, null, '\t')}
29
+ `)
30
+ throw new InvalidOriginError()
31
+ }
32
+
33
+ return origin
34
+ }
35
+
36
+ export const corsHTTP = async <
37
+ SingletonServices extends CoreSingletonServices,
38
+ Services extends CoreServices<SingletonServices>,
39
+ UserSession extends CoreUserSession,
40
+ >(
41
+ event: APIGatewayProxyEvent,
42
+ allowedOrigins: string[],
43
+ singletonServices: SingletonServices,
44
+ createSessionServices: CreateSessionServices<
45
+ SingletonServices,
46
+ Services,
47
+ UserSession
48
+ >
49
+ ) => {
50
+ const request = new PikkuAPIGatewayLambdaRequest(event)
51
+ const response = new PikkuAPIGatewayLambdaResponse()
52
+
53
+ let origin: string | false = false
54
+ try {
55
+ origin = validateOrigin(allowedOrigins, singletonServices.logger, event)
56
+ } catch {
57
+ response.setStatus(400)
58
+ response.setJson({ error: 'error.invalid_origin' })
59
+ }
60
+
61
+ response.setHeader('Access-Control-Allow-Origin', origin)
62
+ response.setHeader('Access-Control-Allow-Credentials', true)
63
+
64
+ return await generalHTTPHandler(
65
+ singletonServices,
66
+ createSessionServices,
67
+ request,
68
+ response
69
+ )
70
+ }
@@ -0,0 +1,33 @@
1
+ import { APIGatewayProxyEvent } from 'aws-lambda'
2
+ import {
3
+ CoreServices,
4
+ CoreSingletonServices,
5
+ CoreUserSession,
6
+ CreateSessionServices,
7
+ } from '@pikku/core'
8
+ import { generalHTTPHandler } from './general-http-handler.js'
9
+ import { PikkuAPIGatewayLambdaRequest } from '../pikku-api-gateway-lambda-request.js'
10
+ import { PikkuAPIGatewayLambdaResponse } from '../pikku-api-gateway-lambda-response.js'
11
+
12
+ export const corslessHTTP = async <
13
+ SingletonServices extends CoreSingletonServices,
14
+ Services extends CoreServices<SingletonServices>,
15
+ UserSession extends CoreUserSession,
16
+ >(
17
+ event: APIGatewayProxyEvent,
18
+ singletonServices: SingletonServices,
19
+ createSessionServices: CreateSessionServices<
20
+ SingletonServices,
21
+ Services,
22
+ UserSession
23
+ >
24
+ ) => {
25
+ const request = new PikkuAPIGatewayLambdaRequest(event)
26
+ const response = new PikkuAPIGatewayLambdaResponse()
27
+ return await generalHTTPHandler(
28
+ singletonServices,
29
+ createSessionServices,
30
+ request,
31
+ response
32
+ )
33
+ }
@@ -0,0 +1,59 @@
1
+ import {
2
+ CoreSingletonServices,
3
+ CoreServices,
4
+ CreateSessionServices,
5
+ CoreUserSession,
6
+ } from '@pikku/core'
7
+ import { runHTTPRoute } from '@pikku/core/http'
8
+ import { APIGatewayProxyResult } from 'aws-lambda'
9
+ import { PikkuAPIGatewayLambdaRequest } from '../pikku-api-gateway-lambda-request.js'
10
+ import { PikkuAPIGatewayLambdaResponse } from '../pikku-api-gateway-lambda-response.js'
11
+
12
+ export const generalHTTPHandler = async <
13
+ SingletonServices extends CoreSingletonServices,
14
+ Services extends CoreServices<SingletonServices>,
15
+ UserSession extends CoreUserSession,
16
+ >(
17
+ singletonServices: SingletonServices,
18
+ createSessionServices: CreateSessionServices<
19
+ SingletonServices,
20
+ Services,
21
+ UserSession
22
+ >,
23
+ request: PikkuAPIGatewayLambdaRequest,
24
+ response: PikkuAPIGatewayLambdaResponse
25
+ ): Promise<APIGatewayProxyResult> => {
26
+ if (request.getMethod() === 'options') {
27
+ response.setHeader(
28
+ 'Access-Control-Allow-Headers',
29
+ 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'
30
+ )
31
+ response.setHeader(
32
+ 'Access-Control-Allow-Methods',
33
+ 'OPTIONS,DELETE,GET,HEAD,PATCH,POST,PUT'
34
+ )
35
+ response.setStatus(200)
36
+ response.setJson({})
37
+ return response.getLambdaResponse()
38
+ }
39
+
40
+ if (request.getPath().includes('health-check')) {
41
+ response.setStatus(200)
42
+ return response.getLambdaResponse()
43
+ }
44
+
45
+ try {
46
+ await runHTTPRoute({
47
+ request,
48
+ response,
49
+ singletonServices,
50
+ createSessionServices: createSessionServices as any,
51
+ route: request.getPath(),
52
+ method: request.getMethod() as any,
53
+ })
54
+ } catch {
55
+ // Error should have already been handled by runHTTPRoute
56
+ }
57
+
58
+ return response.getLambdaResponse()
59
+ }
@@ -0,0 +1,3 @@
1
+ export * from './anywhere-http.js'
2
+ export * from './corsless-http.js'
3
+ export * from './cors-http.js'
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export default null
@@ -0,0 +1,31 @@
1
+ import type { APIGatewayProxyEvent } from 'aws-lambda'
2
+ import type { HTTPMethod, PikkuQuery } from '@pikku/core/http'
3
+ import { PikkuHTTPAbstractRequest } from '@pikku/core/http/pikku-http-abstract-request'
4
+
5
+ export class PikkuAPIGatewayLambdaRequest extends PikkuHTTPAbstractRequest {
6
+ constructor(protected event: APIGatewayProxyEvent) {
7
+ super()
8
+ }
9
+
10
+ public getPath() {
11
+ // TODO: The path can be undefined, types are invalid
12
+ return this.event.path || '/'
13
+ }
14
+
15
+ public getMethod() {
16
+ return this.event.httpMethod.toLowerCase() as HTTPMethod
17
+ }
18
+
19
+ public async getBody() {
20
+ return this.event.body
21
+ }
22
+
23
+ public getHeader(headerName: string): string | undefined {
24
+ return this.event.headers[headerName]
25
+ }
26
+
27
+ public getQuery() {
28
+ // TODO: If query isn't defined, it should be an empty object
29
+ return (this.event.queryStringParameters as PikkuQuery) || {}
30
+ }
31
+ }
@@ -0,0 +1,41 @@
1
+ import { JSONValue } from '@pikku/core'
2
+ import { PikkuHTTPAbstractResponse } from '@pikku/core/http/pikku-http-abstract-response'
3
+ import { APIGatewayProxyResult } from 'aws-lambda'
4
+
5
+ export class PikkuAPIGatewayLambdaResponse extends PikkuHTTPAbstractResponse {
6
+ public headers: Record<string, string> = {}
7
+ private status: number = 200
8
+ private body: any | undefined
9
+
10
+ constructor() {
11
+ super()
12
+ }
13
+
14
+ public getLambdaResponse(): APIGatewayProxyResult {
15
+ return {
16
+ headers: this.headers,
17
+ statusCode: this.status,
18
+ body: this.body,
19
+ }
20
+ }
21
+
22
+ public setStatus(status: number): void {
23
+ this.status = status
24
+ }
25
+
26
+ public setHeader(name: string, value: string | boolean | string[]): void {
27
+ this.headers[name] = value.toString()
28
+ }
29
+
30
+ public setJson(value: JSONValue): void {
31
+ this.body = JSON.stringify(value)
32
+ }
33
+
34
+ public setResponse(response: string | Buffer): void {
35
+ this.body = response
36
+ }
37
+
38
+ public setRedirect(path: string, status: number) {
39
+ throw new Error('Method not implemented.')
40
+ }
41
+ }
@@ -0,0 +1,4 @@
1
+ export * from './websocket-message.js'
2
+ export * from './websocket-connect.js'
3
+ export * from './websocket-disconnect.js'
4
+ export * from './lambda-eventhub-service.js'
@@ -0,0 +1,63 @@
1
+ import { CoreUserSession } from '@pikku/core'
2
+ import {
3
+ ChannelStore,
4
+ PikkuAbstractChannelHandler,
5
+ PikkuChannelHandlerFactory,
6
+ } from '@pikku/core/channel'
7
+ import { sendMessage } from './utils.js'
8
+ import { ApiGatewayManagementApiClient } from '@aws-sdk/client-apigatewaymanagementapi'
9
+ import { Logger } from '@pikku/core/services'
10
+
11
+ class LambdaChannelHandler<
12
+ UserSession extends CoreUserSession = CoreUserSession,
13
+ OpeningData = unknown,
14
+ Out = unknown,
15
+ > extends PikkuAbstractChannelHandler<UserSession, OpeningData, Out> {
16
+ constructor(
17
+ private logger: Logger,
18
+ userSession: UserSession | undefined,
19
+ private channelStore: ChannelStore,
20
+ private callbackAPI: ApiGatewayManagementApiClient,
21
+ channelId: string,
22
+ channelName: string,
23
+ openingData: OpeningData
24
+ ) {
25
+ super(channelId, channelName, userSession, openingData)
26
+ }
27
+
28
+ public async setUserSession(userSession: UserSession): Promise<void> {
29
+ this.userSession = userSession
30
+ await this.channelStore.setUserSession(this.channelId, userSession)
31
+ }
32
+
33
+ public async send(message: Out, isBinary?: boolean) {
34
+ if (isBinary) {
35
+ throw new Error('Binary data is not supported on serverless lambdas')
36
+ }
37
+ const data = JSON.stringify(message)
38
+ await sendMessage(this.logger, this.callbackAPI, this.channelId, data)
39
+ }
40
+ }
41
+
42
+ export const createLambdaChannelHandlerFactory = (
43
+ logger: Logger,
44
+ channelStore: ChannelStore,
45
+ callbackAPI: ApiGatewayManagementApiClient
46
+ ) => {
47
+ const factory: PikkuChannelHandlerFactory = (
48
+ channelId,
49
+ channelName,
50
+ openingData,
51
+ userSession
52
+ ) =>
53
+ new LambdaChannelHandler(
54
+ logger,
55
+ userSession,
56
+ channelStore,
57
+ callbackAPI,
58
+ channelId,
59
+ channelName,
60
+ openingData
61
+ )
62
+ return factory
63
+ }
@@ -0,0 +1,59 @@
1
+ import { ApiGatewayManagementApiClient } from '@aws-sdk/client-apigatewaymanagementapi'
2
+ import {
3
+ ChannelStore,
4
+ EventHubService,
5
+ EventHubStore,
6
+ } from '@pikku/core/channel'
7
+ import { getApiGatewayManagementApiClient, sendMessages } from './utils.js'
8
+ import { Logger } from '@pikku/core/services'
9
+ import { APIGatewayEvent } from 'aws-lambda'
10
+
11
+ export class LambdaEventHubService<Out = unknown>
12
+ implements EventHubService<Out>
13
+ {
14
+ private callbackAPI: ApiGatewayManagementApiClient
15
+
16
+ constructor(
17
+ private logger: Logger,
18
+ event: APIGatewayEvent,
19
+ private channelStore: ChannelStore,
20
+ private eventHubStore: EventHubStore
21
+ ) {
22
+ this.callbackAPI = getApiGatewayManagementApiClient(logger, event)
23
+ }
24
+
25
+ async subscribe(topic: string, channelId: string): Promise<void> {
26
+ await this.eventHubStore.subscribe(topic, channelId)
27
+ }
28
+
29
+ async unsubscribe(topic: string, channelId: string): Promise<void> {
30
+ await this.eventHubStore.unsubscribe(topic, channelId)
31
+ }
32
+
33
+ async publish(
34
+ topic: string,
35
+ fromChannelId: string,
36
+ data: Out,
37
+ isBinary?: boolean
38
+ ): Promise<void> {
39
+ const channelIds = await this.eventHubStore.getChannelIdsForTopic(topic)
40
+ await this.sendMessages(channelIds, fromChannelId, data)
41
+ }
42
+
43
+ private async sendMessages(
44
+ channelIds: string[],
45
+ fromChannelId: string,
46
+ data: Out,
47
+ isBinary?: boolean
48
+ ): Promise<void> {
49
+ await sendMessages(
50
+ this.logger,
51
+ this.channelStore,
52
+ this.callbackAPI,
53
+ fromChannelId,
54
+ channelIds,
55
+ data,
56
+ isBinary
57
+ )
58
+ }
59
+ }
@@ -0,0 +1,95 @@
1
+ import {
2
+ ApiGatewayManagementApiClient,
3
+ PostToConnectionCommand,
4
+ } from '@aws-sdk/client-apigatewaymanagementapi'
5
+ import { Logger } from '@pikku/core/services'
6
+ import { APIGatewayEvent } from 'aws-lambda'
7
+ import { createLambdaChannelHandlerFactory } from './lambda-channel-handler.js'
8
+ import { ChannelStore } from '@pikku/core/channel'
9
+
10
+ export const sendMessage = async (
11
+ logger: Logger,
12
+ callbackAPI: ApiGatewayManagementApiClient,
13
+ ConnectionId: string,
14
+ Data: string
15
+ ): Promise<boolean> => {
16
+ try {
17
+ await callbackAPI.send(new PostToConnectionCommand({ ConnectionId, Data }))
18
+ return true
19
+ } catch (e: any) {
20
+ // TODO: We need to check if it's a 410 and remove the connection if it is
21
+ // Otherwise we need to log the error
22
+ logger.error(e.message)
23
+ return false
24
+ }
25
+ }
26
+
27
+ export const sendMessages = async (
28
+ logger: Logger,
29
+ channelStore: ChannelStore,
30
+ callbackAPI: ApiGatewayManagementApiClient,
31
+ fromChannelId: string,
32
+ channelIds: string[],
33
+ data: unknown,
34
+ isBinary?: boolean
35
+ ): Promise<void> => {
36
+ if (isBinary) {
37
+ throw new Error('Binary data is not supported on serverless lambdas')
38
+ }
39
+ const staleWebsockets: string[] = []
40
+ const Data = JSON.stringify(data)
41
+ await Promise.all(
42
+ channelIds.map(async (channelId) => {
43
+ if (channelId !== fromChannelId) {
44
+ const success = await sendMessage(logger, callbackAPI, channelId, Data)
45
+ if (!success) {
46
+ staleWebsockets.push(channelId)
47
+ }
48
+ }
49
+ })
50
+ )
51
+ if (staleWebsockets.length > 0) {
52
+ await channelStore.removeChannels(staleWebsockets)
53
+ }
54
+ }
55
+
56
+ export const getApiGatewayManagementApiClient = (
57
+ logger: Logger,
58
+ event: APIGatewayEvent
59
+ ) => {
60
+ let endpoint: string
61
+ if (process.env.IS_OFFLINE) {
62
+ logger.info('Assuming serverless offline mode due to IS_OFFLINE')
63
+ endpoint = `http://localhost:3001`
64
+ } else if (event.requestContext.domainName?.includes('amazonaws.com')) {
65
+ logger.info(
66
+ 'Assuming we are using the default endpoint mode due to amazonaws.com domain'
67
+ )
68
+ endpoint = `https://${event.requestContext.domainName}/${event.requestContext.stage}`
69
+ } else {
70
+ logger.info('Assuming we are using a custom domain mode')
71
+ endpoint = `https://${event.requestContext.domainName}`
72
+ }
73
+ return new ApiGatewayManagementApiClient({
74
+ apiVersion: '2018-11-29',
75
+ endpoint,
76
+ })
77
+ }
78
+
79
+ export const getServerlessDependencies = (
80
+ logger: Logger,
81
+ channelStore: ChannelStore,
82
+ event: APIGatewayEvent
83
+ ) => {
84
+ const channelId = event.requestContext.connectionId
85
+ if (!channelId) {
86
+ throw new Error('No connectionId found in requestContext')
87
+ }
88
+ const callbackAPI = getApiGatewayManagementApiClient(logger, event)
89
+ const channelHandlerFactory = createLambdaChannelHandlerFactory(
90
+ logger,
91
+ channelStore,
92
+ callbackAPI
93
+ )
94
+ return { channelId, callbackAPI, channelHandlerFactory, channelStore }
95
+ }
@@ -0,0 +1,41 @@
1
+ import {
2
+ CoreSingletonServices,
3
+ CoreServices,
4
+ CoreUserSession,
5
+ } from '@pikku/core'
6
+ import { APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda'
7
+ import { PikkuAPIGatewayLambdaResponse } from '../pikku-api-gateway-lambda-response.js'
8
+ import { runChannelConnect } from '@pikku/core/channel/serverless'
9
+ import { getServerlessDependencies } from './utils.js'
10
+ import { WebsocketParams } from './websocket-types.js'
11
+ import { PikkuAPIGatewayLambdaRequest } from '../pikku-api-gateway-lambda-request.js'
12
+
13
+ export const connectWebsocket = async <
14
+ SingletonServices extends CoreSingletonServices,
15
+ Services extends CoreServices<SingletonServices>,
16
+ UserSession extends CoreUserSession,
17
+ >(
18
+ event: APIGatewayEvent,
19
+ {
20
+ singletonServices,
21
+ createSessionServices,
22
+ channelStore,
23
+ }: WebsocketParams<SingletonServices, Services, UserSession>
24
+ ): Promise<APIGatewayProxyResult> => {
25
+ const runnerParams = getServerlessDependencies(
26
+ singletonServices.logger,
27
+ channelStore,
28
+ event
29
+ )
30
+ const request = new PikkuAPIGatewayLambdaRequest(event)
31
+ const response = new PikkuAPIGatewayLambdaResponse()
32
+ await runChannelConnect({
33
+ ...runnerParams,
34
+ request,
35
+ response,
36
+ singletonServices: singletonServices as any,
37
+ createSessionServices: createSessionServices as any,
38
+ route: event.path || '/',
39
+ })
40
+ return response.getLambdaResponse()
41
+ }
@@ -0,0 +1,35 @@
1
+ import {
2
+ CoreSingletonServices,
3
+ CoreServices,
4
+ CoreUserSession,
5
+ } from '@pikku/core'
6
+ import { runChannelDisconnect } from '@pikku/core/channel/serverless'
7
+ import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'
8
+
9
+ import { getServerlessDependencies } from './utils.js'
10
+ import { WebsocketParams } from './websocket-types.js'
11
+
12
+ export const disconnectWebsocket = async <
13
+ SingletonServices extends CoreSingletonServices,
14
+ Services extends CoreServices<SingletonServices>,
15
+ UserSession extends CoreUserSession,
16
+ >(
17
+ event: APIGatewayProxyEvent,
18
+ {
19
+ singletonServices,
20
+ createSessionServices,
21
+ channelStore,
22
+ }: WebsocketParams<SingletonServices, Services, UserSession>
23
+ ): Promise<APIGatewayProxyResult> => {
24
+ const runnerParams = getServerlessDependencies(
25
+ singletonServices.logger,
26
+ channelStore,
27
+ event
28
+ )
29
+ await runChannelDisconnect({
30
+ ...runnerParams,
31
+ singletonServices: singletonServices as any,
32
+ createSessionServices: createSessionServices as any,
33
+ })
34
+ return { statusCode: 200, body: '' }
35
+ }