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

Transfer socketio middleware to contrib #956

Merged
merged 20 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ updates:
- "🤖 Dependencies"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/socketio" # Location of package manifests
labels:
- "🤖 Dependencies"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/fibersentry" # Location of package manifests
labels:
Expand Down
50 changes: 50 additions & 0 deletions .github/release-drafter-socketio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name-template: "socketio - v$RESOLVED_VERSION"
tag-template: "socketio/v$RESOLVED_VERSION"
tag-prefix: socketio/v
include-paths:
- socketio
categories:
- title: "❗ Breaking Changes"
labels:
- "❗ BreakingChange"
- title: "🚀 New"
labels:
- "✏️ Feature"
- title: "🧹 Updates"
labels:
- "🧹 Updates"
- "🤖 Dependencies"
- title: "🐛 Fixes"
labels:
- "☢️ Bug"
- title: "📚 Documentation"
labels:
- "📒 Documentation"
change-template: "- $TITLE (#$NUMBER)"
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
exclude-contributors:
- dependabot
- dependabot[bot]
version-resolver:
major:
labels:
- "major"
- "❗ BreakingChange"
minor:
labels:
- "minor"
- "✏️ Feature"
patch:
labels:
- "patch"
- "📒 Documentation"
- "☢️ Bug"
- "🤖 Dependencies"
- "🧹 Updates"
default: patch
template: |
$CHANGES

**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...socketio/v$RESOLVED_VERSION

Thank you $CONTRIBUTORS for making this update possible.
19 changes: 19 additions & 0 deletions .github/workflows/release-drafter-socketio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Release Drafter socketio
on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- master
- main
paths:
- 'socketio/**'
jobs:
draft_release_socketio:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: release-drafter/release-drafter@v5
with:
config-name: release-drafter-socketio.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 changes: 32 additions & 0 deletions .github/workflows/test-socketio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: "Test Socket.io"

on:
push:
branches:
- master
- main
paths:
- "socketio/**"
pull_request:
paths:
- "socketio/**"

jobs:
Tests:
runs-on: ubuntu-latest
strategy:
matrix:
go-version:
- 1.20.x
- 1.21.x
- 1.22.x
steps:
- name: Fetch Repository
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "${{ matrix.go-version }}"
- name: Run Test
working-directory: ./socketio
run: go test -v -race ./...
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ Repository for third party middlewares with dependencies.
* [Open Policy Agent](./opafiber/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+opafiber%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-opafiber.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
* [Otelfiber (OpenTelemetry)](./otelfiber/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+otelfiber%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-otelfiber.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
* [Paseto](./paseto/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+paseto%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-paseto.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
* [Socket.io](./socketio/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+socketio%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-socketio.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
* [Swagger](./swagger/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+swagger%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swagger.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
* [Websocket](./websocket/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+websocket%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-websocket.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
235 changes: 235 additions & 0 deletions socketio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
id: socketio
---

# Socket.io

![Release](https://img.shields.io/github/v/tag/gofiber/contrib?filter=socketio*)
![Test](https://github.com/gofiber/contrib/workflows/Tests/badge.svg)
![Security](https://github.com/gofiber/contrib/workflows/Security/badge.svg)
![Linter](https://github.com/gofiber/contrib/workflows/Linter/badge.svg)

WebSocket wrapper for [Fiber](https://github.com/gofiber/fiber) with events support and inspired by [Socket.io](https://github.com/socketio/socket.io)

**Note: Requires Go 1.20 and above**

## Install

```
go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/contrib/socketio
```

## Signatures

```go
// Initialize new socketio in the callback this will
// execute a callback that expects kws *Websocket Object
func New(callback func(kws *Websocket)) func(*fiber.Ctx) error
```

```go
// Add listener callback for an event into the listeners list
func On(event string, callback func(payload *EventPayload))
```

```go
// Emit the message to a specific socket uuids list
// Ignores all errors
func EmitToList(uuids []string, message []byte)
```

```go
// Emit to a specific socket connection
func EmitTo(uuid string, message []byte) error
```

```go
// Broadcast to all the active connections
// except avoid broadcasting the message to itself
func Broadcast(message []byte)
```

```go
// Fire custom event on all connections
func Fire(event string, data []byte)
```

## Example

```go
package main

import (
"encoding/json"
"fmt"
"log"

"github.com/gofiber/contrib/socketio"
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2"
)

// MessageObject Basic chat message object
type MessageObject struct {
Data string `json:"data"`
From string `json:"from"`
Event string `json:"event"`
To string `json:"to"`
}

func main() {

// The key for the map is message.to
clients := make(map[string]string)

// Start a new Fiber application
app := fiber.New()

// Setup the middleware to retrieve the data sent in first GET request
app.Use(func(c *fiber.Ctx) error {
// IsWebSocketUpgrade returns true if the client
// requested upgrade to the WebSocket protocol.
if websocket.IsWebSocketUpgrade(c) {
c.Locals("allowed", true)
return c.Next()
}
return fiber.ErrUpgradeRequired
})

// Multiple event handling supported
socketio.On(socketio.EventConnect, func(ep *socketio.EventPayload) {
fmt.Println(fmt.Sprintf("Connection event 1 - User: %s", ep.Kws.GetStringAttribute("user_id")))
})

// Custom event handling supported
socketio.On("CUSTOM_EVENT", func(ep *socketio.EventPayload) {
fmt.Println(fmt.Sprintf("Custom event - User: %s", ep.Kws.GetStringAttribute("user_id")))
// --->

// DO YOUR BUSINESS HERE

// --->
})

// On message event
socketio.On(socketio.EventMessage, func(ep *socketio.EventPayload) {

fmt.Println(fmt.Sprintf("Message event - User: %s - Message: %s", ep.Kws.GetStringAttribute("user_id"), string(ep.Data)))

message := MessageObject{}

// Unmarshal the json message
// {
// "from": "<user-id>",
// "to": "<recipient-user-id>",
// "event": "CUSTOM_EVENT",
// "data": "hello"
//}
err := json.Unmarshal(ep.Data, &message)
if err != nil {
fmt.Println(err)
return
}

// Fire custom event based on some
// business logic
if message.Event != "" {
ep.Kws.Fire(message.Event, []byte(message.Data))
}

// Emit the message directly to specified user
err = ep.Kws.EmitTo(clients[message.To], ep.Data, socketio.TextMessage)
if err != nil {
fmt.Println(err)
}
})

// On disconnect event
socketio.On(socketio.EventDisconnect, func(ep *socketio.EventPayload) {
// Remove the user from the local clients
delete(clients, ep.Kws.GetStringAttribute("user_id"))
fmt.Println(fmt.Sprintf("Disconnection event - User: %s", ep.Kws.GetStringAttribute("user_id")))
})

// On close event
// This event is called when the server disconnects the user actively with .Close() method
socketio.On(socketio.EventClose, func(ep *socketio.EventPayload) {
// Remove the user from the local clients
delete(clients, ep.Kws.GetStringAttribute("user_id"))
fmt.Println(fmt.Sprintf("Close event - User: %s", ep.Kws.GetStringAttribute("user_id")))
})

// On error event
socketio.On(socketio.EventError, func(ep *socketio.EventPayload) {
fmt.Println(fmt.Sprintf("Error event - User: %s", ep.Kws.GetStringAttribute("user_id")))
})

app.Get("/ws/:id", socketio.New(func(kws *socketio.Websocket) {

// Retrieve the user id from endpoint
userId := kws.Params("id")

// Add the connection to the list of the connected clients
// The UUID is generated randomly and is the key that allow
// socketio to manage Emit/EmitTo/Broadcast
clients[userId] = kws.UUID

// Every websocket connection has an optional session key => value storage
kws.SetAttribute("user_id", userId)

//Broadcast to all the connected users the newcomer
kws.Broadcast([]byte(fmt.Sprintf("New user connected: %s and UUID: %s", userId, kws.UUID)), true, socketio.TextMessage)
//Write welcome message
kws.Emit([]byte(fmt.Sprintf("Hello user: %s with UUID: %s", userId, kws.UUID)), socketio.TextMessage)
}))

log.Fatal(app.Listen(":3000"))
}

```

---

## Supported events

| Const | Event | Description |
|:----------------|:-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| EventMessage | `message` | Fired when a Text/Binary message is received |
| EventPing | `ping` | More details here: @url <https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Pings_and_Pongs_The_Heartbeat_of_WebSockets> |
| EventPong | `pong` | Refer to ping description |
| EventDisconnect | `disconnect` | Fired on disconnection. The error provided in disconnection event as defined in RFC 6455, section 11.7. |
| EventConnect | `connect` | Fired on first connection |
| EventClose | `close` | Fired when the connection is actively closed from the server. Different from client disconnection |
| EventError | `error` | Fired when some error appears useful also for debugging websockets |

## Event Payload object

| Variable | Type | Description |
|:-----------------|:--------------------|:--------------------------------------------------------------------------------|
| Kws | `*Websocket` | The connection object |
| Name | `string` | The name of the event |
| SocketUUID | `string` | Unique connection UUID |
| SocketAttributes | `map[string]string` | Optional websocket attributes |
| Error | `error` | (optional) Fired from disconnection or error events |
| Data | `[]byte` | Data used on Message and on Error event, contains the payload for custom events |

## Socket instance functions

| Name | Type | Description |
|:-------------|:---------|:----------------------------------------------------------------------------------|
| SetAttribute | `void` | Set a specific attribute for the specific socket connection |
| GetUUID | `string` | Get socket connection UUID |
| SetUUID | `error` | Set socket connection UUID |
| GetAttribute | `string` | Get a specific attribute from the socket attributes |
| EmitToList | `void` | Emit the message to a specific socket uuids list |
| EmitTo | `error` | Emit to a specific socket connection |
| Broadcast | `void` | Broadcast to all the active connections except broadcasting the message to itself |
| Fire | `void` | Fire custom event |
| Emit | `void` | Emit/Write the message into the given connection |
| Close | `void` | Actively close the connection from the server |

**Note: the FastHTTP connection can be accessed directly from the instance**

```go
kws.Conn
```
29 changes: 29 additions & 0 deletions socketio/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module github.com/gofiber/contrib/socketio

go 1.20

require (
github.com/fasthttp/websocket v1.5.4
github.com/gofiber/contrib/websocket v1.2.0
github.com/gofiber/fiber/v2 v2.52.0
github.com/google/uuid v1.5.0
github.com/stretchr/testify v1.8.4
github.com/valyala/fasthttp v1.51.0
)

require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading