https://github.com/duart38/serverless-sockets
https://github.com/duart38/serverless-sockets
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/duart38/serverless-sockets
- Owner: duart38
- License: gpl-3.0
- Created: 2021-03-30T12:55:36.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2023-01-01T13:20:40.000Z (over 2 years ago)
- Last Synced: 2024-05-01T23:18:47.733Z (about 1 year ago)
- Language: TypeScript
- Size: 1020 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: COPYING
Awesome Lists containing this project
README
# The idea
Bringing serverless functionality and extendability to a websocket server.
## But why??
- Dynamically interchange "modules" at runtime without any downtime
- no server config modifications required.. just add a function and you're done..
- Hide the messy webscoket implementation and work with easy functions
- wrap existing APIs into a websocket interface for legacy codebases (familiar interface)
- Reduce websocket abuse (e.g. sending very large objects) by allowing for clever synchronization methods.# Installing
Requirements: [Deno](https://deno.land)To install run the command:
```bash
deno install -A -f -r --import-map=https://raw.githubusercontent.com/duart38/serverless-sockets/main/import_map.json -n ssocket https://raw.githubusercontent.com/duart38/serverless-sockets/main/src/mod.ts
```After this you can use the server in any folder with the command:
```bash
ssocket
```
> The server expects a folder called "plugs" to be present in the current directory.it's also possible to pre-configure the server before installing it to make sure the configurations persist every time the command is run.
To do this use the command to install and prepend the configuration flags to the command:
```bash
deno install -A -f -r --import-map=https://raw.githubusercontent.com/duart38/serverless-sockets/main/import_map.json -n ssocket https://raw.githubusercontent.com/duart38/serverless-sockets/main/src/mod.ts
```> See configuration documentation (CLI) on how to configure the server.
For more information on installing scripts [check this page out](https://deno.land/[email protected]/tools/script_installer)
# Running locally
Requirements: [Deno](https://deno.land)1. clone the repo and go into the folder
2. go into the src folder```
deno run -A mod.ts
```🎉
# How it works
Depending on your configuration your only concern (as a developer) will be the 'plugs' folder (folder name depends on the config).
The default folder obtained with the cloning of this repository contains some example code in the 'plugs' folder.
Here's an example of how the modular functions look like.
## Simple return message(s)
```TypeScript
export async function* test(message: SocketMessage, _from: WebSocket): ModuleGenerator { // _from is the id of the client
for(let i = 0; i This returns the yielded object to the client.
---
```TypeScript
export async function* test(message: SocketMessage<{count: number}>, _from: WebSocket): ModuleGenerator<{name: string}> {
for(let i = 0; i Types are also supported.Esentially you 'yield' back values to the client side every time you want to send them an update.
If you're unfamiliar with generator functions take a look at [this page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*)## Broadcasting message to everyone
To broadcast a message to everyone you can either add ```type: EventType.BROADCAST,``` to your **yield**s or you can use the staticly exposed **broadcast()** method from the Socket class.
> NOTE: it is recommended to use the type property as it hides the implementation details and also prevents memory leaks.
#### yielding a broadcast (Recommended)
```TypeScript
export async function* broadcast(message: SocketMessage, _from: WebSocket): ModuleGenerator {
yield {
type: EventType.BROADCAST, // instruction to broadcast this message
event: "broadcast-1",
payload: message.payload
}
}
```#### method-based broadcast (avoid if possible)
```TypeScript
export async function* broadcast(message: SocketMessage, _from: WebSocket): ModuleGenerator {
Socket.broadcast({
type: EventType.BROADCAST,
event: "broadcast-1",
payload: message.payload
}, _from
);
yield {event: '', payload: {}};
}
```
> The reason this method is still available is because yielding does not work in certain scopes (I.E. inner lambdas and or functions).## Sending to specific client
```TypeScript
Socket.sendMessage(/*client ID*/5, {event: "ev", payload: {}});
```## Synchronizing big objects with clients
> NOTE: this method is only recommended if you have big objects that need to be synchronized between the server and the client!!. it also requires some extra logic on the client-side to make sure everything is in sync.Oftentimes we tend to abuse the websocket protocol by sending really big objects with small changed to the client to keep things in sync.. this is .. well... bad.
To mitigate this the framework offers a method to synchronize big objects with the client by sending only the required instructions to the client which will then update it's local object.
To use this method you simply need to add ```type: EventType.SYNC,``` to your **yield**s.
example:
```TypeScript
export async function* sync(message: SocketMessage, _from: WebSocket): ModuleGenerator {
yield {
type: EventType.SYNC,
event: "sync-1",
payload: {} // some big message here
}
}
```## The eventType
```TypeScript
/**
* Global events that the socket server can send back to clients. depending on the value the client is to respond differently
*/
export enum EventType {
/**
* a regular message. oftentimes a reply to whoever sent the last message.
*/
MESSAGE,
/**
* A broadcast event is sent to all clients.3
*/
BROADCAST,
/**
* Reserved for authentication, cleans up code a bit.
*/
AUTH,
/**
* Reserved for unknown errors
*/
ERROR,
/**
* Sent back when the event was not found
*/
NOT_FOUND,/**
* Custom message, developers are free to do what they please here
*/
CUSTOM_1,
/**
* Custom message, developers are free to do what they please here
*/
CUSTOM_2,
/**
* Custom message, developers are free to do what they please here
*/
CUSTOM_3,
/**
* Custom message, developers are free to do what they please here
*/
CUSTOM_4,
/**
* Custom message, developers are free to do what they please here
*/
CUSTOM_5,
}
```# CLI
> Note: Configurations adapted on the command line are only active for the duration of the program. I.E. they will not persist.This project ships with a command line interface that aims to help you configure the server entirely from the command line.
## Inline configuration (will not persist)
To change configuration items:
```sh
deno run -A mod.ts --payloadLimit 10 --INSECURE.port 6969
```The CLI is also capable of printing out all the configuration options by running:
```sh
deno run -A mod.ts -h
```or if you want to read up the documentation of a specific item:
```sh
deno run -A mod.ts -h --payloadLimit
```## Generating modules
The CLI is able to generate modules:
```sh
deno run -A mod.ts --generate
```
> name_of_event here is the file name but also the name of the event that is to be called for on the client side to trigger the module.# Configuration
To configure the server see the config.js file within the 'src' folder of this project.
The configuration class in this file should contain all the information and documentation needed.Additionally, it is also possible to make (if installed as a script) a INIT.ts file with a function named INIT which, upon server start, will be called (see example below).
```TypeScript
export async function INIT(_socket: Socket) {
// Will be called when the server starts up (before the loading of the plugs)
}
```
> This can be used for example when one needs to connect to some other server/database to retrieve data
---
If you want to shorten the import URLs you can use an import map. For example:
```TypeScript
import { SocketMessage, ModuleGenerator } from "ssocket";
```
To do this (in VSCode) change the settings.json within .vscode to include:
```
"deno.importMap": "./import_map.json",
```
Also add this file inside your repository. A template of this file can be [found here](https://raw.githubusercontent.com/duart38/serverless-sockets/main/import_map.json)# Payload shape
> This section is only needed if you are modifying the framework.This is how a message is encoded when the client communicates with the server and vice versa.
# SYNC payload shape
> This section is only needed if you are modifying the framework.The payload shape for the SYNC event will return the difference between the last sent or received object in the form of a multi-dimensional array.
```TypeScript
[
[number], // the difference between the 2 known arrays, will be negative if the size decreases and positive otherwise
[indexToStart, number, number, ...], // subsequent arrays start with the index of where to start placing each item and this is then followed by all the items to be placed.
...
...
]
```The
```TypeScript
SocketMessage.syncIncoming(msg: Uint8Array)
```
message will take in a SYNC style payload and synchronize the already existing object with the new one.Here's how this all looks:


# Testing
```sh
deno test -A --unstable --coverage=cov_profile && deno coverage cov_profile
```# Considerations and limitations
When developing modules keep in mind that the idea of the module is for it to be spawned, to yield it's messages and then to be cleaned up aftwerwards.
This means that you need to be careful when creating any floating references in the method that would prevent it from being cleaned up.
These are things like refering to an object somewhere else that is never cleaned up (I.E. always in use by something else) or an interval timer.> Modules that are not cleaned up will continue to exist and for each client that calls the module (via it's event string) a new instance of that module is spawned and is also kept in memory indefinitely thus causing a memory leak.