core

package
v0.0.0-...-525fc53 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 18, 2025 License: MIT Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const (
	WSOpened WSState = iota
	WSOpening
	WSClosed
	WSClosing

	WSServer WSPeerType = iota
	WSClient
)
View Source
const (
	AuthCookieName = "auth_token"
)

Variables

View Source
var (
	ErrBadCredentials  = errors.New("invalid credentials")
	ErrUnauthenticated = errors.New("unauthenticated")
	ErrUnauthorized    = errors.New("unauthorized")
)
View Source
var (
	// ErrInvalidUser is returned when a user is not found or is invalid.
	ErrInvalidUser = errors.New("invalid user")
	// ErrConflictedRoom is returned when a private chat room already exists between two users.
	ErrConflictedRoom = errors.New("chat already exists")
	// ErrInvalidRoom is returned when a chat room is not found for
	ErrInvalidRoom = errors.New("invalid room")
	// ErrInvalidMessage is returned when a message is invalid.
	ErrInvalidMessage = errors.New("invalid message")
	// ErrInvalidMessageType is returned when the type of the message is not supported.
	ErrInvalidMessageType = errors.New("invalid message type")
	// ErrInsufficientUsers is returned when insufficient users are provided to create a room.
	ErrInsufficientUsers   = errors.New("insufficient users")
	ErrDisAllowedOperation = errors.New("disallowed operation")
	ErrInvalidMember       = errors.New("invalid member")
)
View Source
var (
	ErrTokenExpired      = errors.New("token expired")
	ErrTokenInvalid      = errors.New("token invalid")
	ErrUnrecognizedToken = errors.New("unrecognized token")
)
View Source
var (
	ErrConflictedUser = errors.New("user already exists")
)

Functions

func CookieFromRequest

func CookieFromRequest(session Session, httpOnly bool, path string) *http.Cookie

func DecodeEvent

func DecodeEvent(r io.Reader, e *Event) error

func EncodeEvent

func EncodeEvent(w io.Writer, e *Event) error

func JWTMiddleware

func JWTMiddleware(a AuthStore) router.Middleware

JWTMiddleware extracts the JWT token from the request and validates it and attaches the session to the request context. The session is gaurenteed to be attached to the request context if the JWT token is valid for subsequent handlers.

func NewToken

func NewToken(user UserWithoutSecrets, expiration time.Duration, secret []byte) (string, time.Time, error)

Types

type AuthClaims

type AuthClaims struct {
	Username string
	jwt.RegisteredClaims
}

func NewClaim

func NewClaim(user UserWithoutSecrets, exp time.Time) *AuthClaims

func VerifyToken

func VerifyToken(token string, secret []byte) (*AuthClaims, error)

type AuthOptions

type AuthOptions func(*SQLiteAuthStore)

func WithTokenExp

func WithTokenExp(exp time.Duration) AuthOptions

type AuthStore

type AuthStore interface {
	NewSession(ctx context.Context, username, password string) (sesion *Session, err error)

	DestroySession(ctx context.Context, session Session) error

	Session(ctx context.Context, token string) (payload *Session, err error)
}

type AutoIncrementConnIDGenerator

type AutoIncrementConnIDGenerator struct {
	// contains filtered or unexported fields
}

func (*AutoIncrementConnIDGenerator) Generate

type ChatStore

type ChatStore interface {

	// CreateRoom creates a chat room with the given name and users.
	// If the one of the users does not exist, it returns ErrInvalidUser.
	// If the number of users is less than 2, it returns ErrInvalidUser.
	// If there are duplicate users, it is deduplicated.
	// If the error is nil, it returns the ID of the created room.
	CreateRoom(ctx context.Context, name, owner string) (string, error)

	AddRoomMember(ctx context.Context, roomID string, user string, role MemberRole) error

	RemoveRoomMember(ctx context.Context, roomID string, user string) error

	GetUserRooms(ctx context.Context, user string, offset, litmit int) ([]Room, error)

	// GetRoomByID returns the room with the given ID.
	// If the room is not found, it returns nil.
	GetRoomByID(ctx context.Context, roomID string) (*Room, error)

	// GetRoomSummaries returns a list of room summaries for the given user.
	// The rooms are ordered by the last message sent time to the room then room name.
	// Reading offset and limit can be specified to paginate the results.
	// If the limit is a zero value, the limit is set to 100.
	// A nil slice is returned if there are no rooms.
	GetRoomSummaries(ctx context.Context, user string, offset, limit int) ([]RoomSummary, error)

	// SendMessageToRoom sends a message to the room.
	// If the user is not a member of the room, it returns ErrInvalidRoom.
	// If the message type is not supported, it returns ErrInvaidMessageType.
	// If the message is invalid, it returns ErrInvalidMessage.
	// The validity of the message is determined by the MessageCreateInput.Validate method.
	// Sender's last read message will be set to the message ID. This assumes that the sender
	// has read all previous messages in the room.
	SendMessageToRoom(ctx context.Context, message MessageCreateInput) (*Message, error)

	// GetRoomMessages returns a list of messages in the room ordered in descending order of sent_at.
	// Reading offset and limit can be specified to paginate the results.
	// If the limit is a zero value, the limit is set to 100.
	GetRoomMessages(ctx context.Context, roomID string, offset, limit int) ([]Message, error)

	// IsRoomMember returns true and the role of that membert if the user is a member of the room.
	IsRoomMember(ctx context.Context, roomID, user string) (bool, MemberRole, error)

	// ReadRoomMessages marks the messages in the room as read up to a message.
	// It returns the message ID of the last message read and the time the messages were read.
	ReadRoomMessages(ctx context.Context, roomID, user string) (int, time.Time, error)

	// GetRoomMembers returns a list of members in the room.
	// If the room is not found, it returns nil.
	GetRoomMembers(ctx context.Context, roomID string) ([]RoomMember, error)

	// GetFriends returns a list of friends for the given user.
	// A friend is a user that are member of at least one common chat room.
	GetFriends(ctx context.Context, username string) ([]string, error)

	// AreFriends returns true if the two users are friends.
	AreFriends(ctx context.Context, user1, user2 string) (bool, error)
}

type ChatType

type ChatType = int

ChatType represents the type of a chat room.

const (
	// PrivateChat is a chat room where only two users can participate.
	// Only one private chat room can exist between two users.
	PrivateChat ChatType = iota
	// GroupChat is a chat room where multiple users can participate.
	// A group chat must have two or more users in it.
	GroupChat
)

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

type ConnIDGenerator

type ConnIDGenerator interface {
	Generate(r *http.Request, conn *websocket.Conn) (int, error)
}

type ConnManager

type ConnManager struct {
	ReadStreamSize  int
	WriteStreamSize int
	// contains filtered or unexported fields
}

func NewConnManager

func NewConnManager(ctx context.Context, wg *sync.WaitGroup, logger *slog.Logger, opts ...ManagerOption) *ConnManager

func (*ConnManager) Connect

func (m *ConnManager) Connect(username string, w http.ResponseWriter, r *http.Request) error

func (*ConnManager) IsUserConnected

func (m *ConnManager) IsUserConnected(username string) bool

func (*ConnManager) OnConnectionClosed

func (m *ConnManager) OnConnectionClosed(f func(context.Context, string, int))

func (*ConnManager) OnConnectionOpened

func (m *ConnManager) OnConnectionOpened(f func(context.Context, string, int))

func (*ConnManager) OnUserConnected

func (m *ConnManager) OnUserConnected(f func(context.Context, string))

func (*ConnManager) OnUserDisconnected

func (m *ConnManager) OnUserDisconnected(f func(context.Context, string))

func (*ConnManager) Receive

func (m *ConnManager) Receive() <-chan *Event

func (*ConnManager) Send

func (m *ConnManager) Send(e *Event)

func (*ConnManager) SendToConn

func (m *ConnManager) SendToConn(e *Event, username string, id int)

func (*ConnManager) SendToUsers

func (m *ConnManager) SendToUsers(e *Event, username ...string)

type Error

type Error struct {

	// sensitive is a flag to indicate if the error is sensitive or not.
	// If it is not, it can be returned to the client.
	Sensitive bool
	// contains filtered or unexported fields
}

func NewError

func NewError(msg string, sensitive bool) *Error

func NewErrorf

func NewErrorf(format string, args ...interface{}) *Error

func NewInsensitiveError

func NewInsensitiveError(msg string) *Error

func NewSensitiveError

func NewSensitiveError(msg string) *Error

func (*Error) Error

func (e *Error) Error() string

type Event

type Event struct {
	ID         int             `json:"-"`
	Dispatcher string          `json:"-"`
	Type       string          `json:"type"`
	Payload    json.RawMessage `json:"payload"`
}

func (Event) String

func (e Event) String() string

type EventHandler

type EventHandler func(context.Context, *Event) error

type EventRouter

type EventRouter struct {
	// contains filtered or unexported fields
}

func NewEventRouter

func NewEventRouter(ctx context.Context, logger *slog.Logger, transport EventTransport) *EventRouter

func (*EventRouter) Close

func (em *EventRouter) Close(ctx context.Context)

func (*EventRouter) Emit

func (em *EventRouter) Emit(t string, payload interface{}) error

Emit sends an event to the specified targets.

func (*EventRouter) EmitTo

func (em *EventRouter) EmitTo(t string, payload interface{}, usernames ...string) error

func (*EventRouter) Listen

func (em *EventRouter) Listen()

func (*EventRouter) On

func (em *EventRouter) On(eventName string, handler EventHandler)

type EventTransport

type EventTransport interface {
	Send(event *Event)
	SendToUsers(event *Event, usernames ...string)
	Receive() <-chan *Event
}

type GetUsersOptions

type GetUsersOptions struct {
	// contains filtered or unexported fields
}

type HttpAuth

type HttpAuth struct {
}

type ManagerOption

type ManagerOption func(*ConnManager)

func WithCheckOrigin

func WithCheckOrigin(f func(r *http.Request) bool) ManagerOption

func WithLogger

func WithLogger(l *slog.Logger) ManagerOption

type MemberRole

type MemberRole string
const (
	Owner  MemberRole = "owner"
	Admin  MemberRole = "admin"
	Member MemberRole = "member"
)

type Message

type Message struct {
	ID int `json:"id"`
	// Type is used to determined how the message data should be interpreted.
	Type   MessageType `json:"type"`
	Data   string      `json:"data"`
	RoomID string      `json:"room_id"`
	Sender string      `json:"sender"`
	SentAt time.Time   `json:"sent_at"`
}

Message represents a chat message sent by a user to a room.

type MessageCreateInput

type MessageCreateInput struct {
	Type   MessageType `json:"type" validate:"required"`
	Data   string      `json:"data" validate:"required"`
	Sender string      `json:"sender" validate:"required"`
	RoomID string      `json:"room_id" validate:"required"`
}

MessageCreateInput represents the input for creating a message.

func (*MessageCreateInput) Validate

func (m *MessageCreateInput) Validate() error

Validate validates the message input.

type MessageType

type MessageType = int

MessageType represents the type of a chat message. It is used to determine how the message data should be interpreted.

const (

	// TextMessage indicates that the message is a text message.
	// The Data field of the message should be interpreted as a UTF-8 encoded string.
	TextMessage MessageType
)

type NamespaceTree

type NamespaceTree struct {
}

type NamnespaceNode

type NamnespaceNode struct {
}

type Room

type Room struct {
	ID                  string       `json:"id"`
	Members             []RoomMember `json:"members"`
	Name                string       `json:"name"`
	LastMessageSentAt   time.Time    `json:"last_message_sent_at"`
	LastMessageSent     int          `json:"last_message_sent"`
	LastMessageSentData string       `json:"last_message_sent_data"`
}

Room represents a chat room.

type RoomMember

type RoomMember struct {
	Role            MemberRole `json:"role"`
	Username        string     `json:"username"`
	RoomID          string     `json:"room_id"`
	LastMessageRead int        `json:"last_message_read"`
}

RoomUser represents a user in a chat room. It is used to store additional information about the room that is specific to the user.

type RoomSummary

type RoomSummary struct {
	ID              string   `json:"id"`
	Name            string   `json:"name"`
	Members         []string `json:"members"`
	LastMessageRead int      `json:"last_message_read"`
}

RoomSummary represents a summary of a chat room from the perspective of a member.

type SQLiteAuthStore

type SQLiteAuthStore struct {
	// contains filtered or unexported fields
}

func NewSQLiteAuthStore

func NewSQLiteAuthStore(db *sql.DB, userStore UserStore, secret []byte, opts ...AuthOptions) *SQLiteAuthStore

func (*SQLiteAuthStore) DestroySession

func (a *SQLiteAuthStore) DestroySession(ctx context.Context, session Session) error

func (*SQLiteAuthStore) NewSession

func (a *SQLiteAuthStore) NewSession(ctx context.Context, username, password string) (*Session, error)

func (*SQLiteAuthStore) Session

func (a *SQLiteAuthStore) Session(ctx context.Context, t string) (session *Session, err error)

type SQLiteChatStore

type SQLiteChatStore struct {
	// contains filtered or unexported fields
}

func NewSQLiteChatStore

func NewSQLiteChatStore(db *sql.DB, userStore UserStore) *SQLiteChatStore

func (*SQLiteChatStore) AddRoomMember

func (s *SQLiteChatStore) AddRoomMember(ctx context.Context, roomID, username string, role MemberRole) error

func (*SQLiteChatStore) AreFriends

func (s *SQLiteChatStore) AreFriends(ctx context.Context, username1, username2 string) (bool, error)

func (*SQLiteChatStore) CreateRoom

func (s *SQLiteChatStore) CreateRoom(ctx context.Context, name string, ownerUsername string) (string, error)

func (*SQLiteChatStore) GetFriends

func (s *SQLiteChatStore) GetFriends(ctx context.Context, username string) ([]string, error)

GetConnectedUsers returns a list of friends of the user. A friend is a user that has a private chat or are part of a group chat with the user.

func (*SQLiteChatStore) GetRoomByID

func (s *SQLiteChatStore) GetRoomByID(ctx context.Context, roomID string) (*Room, error)

func (*SQLiteChatStore) GetRoomMembers

func (s *SQLiteChatStore) GetRoomMembers(ctx context.Context, roomID string) ([]RoomMember, error)

func (*SQLiteChatStore) GetRoomMessages

func (s *SQLiteChatStore) GetRoomMessages(ctx context.Context, roomID string, offset, limit int) ([]Message, error)

func (*SQLiteChatStore) GetRoomSummaries

func (s *SQLiteChatStore) GetRoomSummaries(ctx context.Context, user string, offset, limit int) ([]RoomSummary, error)

func (*SQLiteChatStore) GetUserRooms

func (s *SQLiteChatStore) GetUserRooms(ctx context.Context, user string, offset, litmit int) ([]Room, error)

func (*SQLiteChatStore) IsRoomMember

func (s *SQLiteChatStore) IsRoomMember(ctx context.Context, roomID, user string) (bool, MemberRole, error)

func (*SQLiteChatStore) ReadRoomMessages

func (s *SQLiteChatStore) ReadRoomMessages(ctx context.Context, roomID, user string) (int, time.Time, error)

func (*SQLiteChatStore) RemoveRoomMember

func (s *SQLiteChatStore) RemoveRoomMember(ctx context.Context, roomID, username string) error

func (*SQLiteChatStore) SendMessageToRoom

func (s *SQLiteChatStore) SendMessageToRoom(ctx context.Context, message MessageCreateInput) (*Message, error)

type SQLiteDB

type SQLiteDB struct {
	*sql.DB
	// contains filtered or unexported fields
}

func NewSQLiteDB

func NewSQLiteDB(file, migrationDir string, config *SQLiteDBOption) (*SQLiteDB, error)

func (*SQLiteDB) Migrate

func (db *SQLiteDB) Migrate() error

type SQLiteDBOption

type SQLiteDBOption struct {
	// mode can be ro | rw | rwc | memory
	Mode string
	// cache can be shared | private
	Cache string
	// JournalMode be DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
	JournalMode string
}

func (*SQLiteDBOption) DSN

func (config *SQLiteDBOption) DSN(sb *strings.Builder)

type SQLiteUserStore

type SQLiteUserStore struct {
	// contains filtered or unexported fields
}

func NewSqlieUserStore

func NewSqlieUserStore(db *sql.DB) *SQLiteUserStore

func (*SQLiteUserStore) ComparePassword

func (s *SQLiteUserStore) ComparePassword(ctx context.Context, username, password string) (bool, error)

func (*SQLiteUserStore) CreateUser

func (s *SQLiteUserStore) CreateUser(ctx context.Context, user User) error

func (*SQLiteUserStore) GetUserByUsername

func (s *SQLiteUserStore) GetUserByUsername(ctx context.Context, username string) (*UserWithoutSecrets, error)

func (*SQLiteUserStore) GetUsers

func (s *SQLiteUserStore) GetUsers(ctx context.Context, options *GetUsersOptions) ([]UserWithoutSecrets, error)

func (*SQLiteUserStore) GetUsersByUsernames

func (s *SQLiteUserStore) GetUsersByUsernames(ctx context.Context, usernames ...string) ([]UserWithoutSecrets, error)

type Session

type Session struct {
	Username  string    `json:"username"`
	Token     string    `json:"token"`
	ExpiresAt time.Time `json:"expires_at"`
}

func SessionFromRequest

func SessionFromRequest(r *http.Request) Session

SessionFromRequest extracts the session from the request context. It must be called in handlers that are protected by the JWTMiddleware. It panics if the session is not found in the request context.

type SyncMap

type SyncMap[K comparable, V any] struct {
	// contains filtered or unexported fields
}

SyncMap is an implementation of a map that is safe for concurrent usage.

func NewSyncMap

func NewSyncMap[K comparable, V any]() *SyncMap[K, V]

func (*SyncMap[K, V]) Delete

func (s *SyncMap[K, V]) Delete(key K)

func (*SyncMap[K, V]) Load

func (s *SyncMap[K, V]) Load(key K) (value V, ok bool)

func (*SyncMap[K, V]) LoadAndStore

func (s *SyncMap[K, V]) LoadAndStore(key K, f func(value V, ok bool) V)

LoadAndStore retrieves the value for a key, applies the function f to it, and stores the result. It guarantees that the whole operation is atomic.

func (*SyncMap[K, V]) RRange

func (s *SyncMap[K, V]) RRange(f func(key K, value V) bool)

func (*SyncMap[K, V]) Store

func (s *SyncMap[K, V]) Store(key K, value V)

func (*SyncMap[K, V]) WRange

func (s *SyncMap[K, V]) WRange(f func(key K, value V) bool)

type User

type User struct {
	Name     string `json:"name" validate:"required,min=3"`
	Username string `json:"username" validate:"required,min=3"`
	Password string `json:"password" validate:"required,min=8"`
}

func (User) Validate

func (u User) Validate() error

type UserStore

type UserStore interface {
	CreateUser(ctx context.Context, user User) error

	GetUserByUsername(ctx context.Context, username string) (*UserWithoutSecrets, error)

	GetUsersByUsernames(ctx context.Context, usernames ...string) ([]UserWithoutSecrets, error)

	ComparePassword(ctx context.Context, username, password string) (bool, error)

	GetUsers(ctx context.Context, opts *GetUsersOptions) ([]UserWithoutSecrets, error)
}

type UserWithoutSecrets

type UserWithoutSecrets struct {
	Name     string `json:"name"`
	Username string `json:"username"`
}

type WSMessage

type WSMessage struct {
	Format int
	*bytes.Buffer
	// contains filtered or unexported fields
}

func NewWSMessage

func NewWSMessage(format int) *WSMessage

type WSMessageWithSender

type WSMessageWithSender struct {
	Sender string
	*WSMessage
}

func NewWSMessageWithSender

func NewWSMessageWithSender(sender string, m *WSMessage) *WSMessageWithSender

type WSPeerType

type WSPeerType int

type WSState

type WSState int

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL