Skip to content

Commit

Permalink
Merge pull request #956 from antoniodipinto/main
Browse files Browse the repository at this point in the history
Transfer socketio middleware to contrib
  • Loading branch information
ReneWerner87 committed Feb 18, 2024
2 parents 77801ce + e124f56 commit b6746cc
Show file tree
Hide file tree
Showing 10 changed files with 1,475 additions and 0 deletions.
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
)

0 comments on commit b6746cc

Please sign in to comment.