diff --git a/frontend/src/interface/response.ts b/frontend/src/interface/response.ts new file mode 100644 index 0000000..ce1ca43 --- /dev/null +++ b/frontend/src/interface/response.ts @@ -0,0 +1,6 @@ +export interface Resp{ + status: number; + msg: string; + err: string; + data: T; +} \ No newline at end of file diff --git a/frontend/src/page/local.tsx b/frontend/src/page/local.tsx index 288c441..9c15fbf 100644 --- a/frontend/src/page/local.tsx +++ b/frontend/src/page/local.tsx @@ -1,7 +1,8 @@ import {CloudBackground} from "../component/fluid/cloud.tsx"; -import {useEffect, useState} from "react"; +import {useEffect} from "react"; import {createUseStyles} from "react-jss"; -import {useRoom} from "../store/ws.ts"; +import {useRTC} from "../store/rtc.ts"; +import {Client, useRoom} from "../store/local.ts"; const useClass = createUseStyles({ '@global': { @@ -27,6 +28,12 @@ const useClass = createUseStyles({ overflow: "hidden", position: "relative", }, + title: { + width: '100%', + display: "flex", + justifyContent: "center", + color: '#1661ab', + }, bubble: { position: "absolute", width: "100px", @@ -50,13 +57,10 @@ const useClass = createUseStyles({ } }) -interface Client { + +interface Bubble { id: string; name: string; -} - -interface BubblePosition { - id: string; x: number; y: number; color: string; @@ -66,10 +70,8 @@ interface BubblePosition { export const LocalSharing: React.FC = () => { const classes = useClass(); - const [clients, setClients] = useState([]); - const [bubbles, setBubbles] = useState([]); - const {register, cleanup} = useRoom(); - const BUBBLE_SIZE = 100; + const {register, enter, list, cleanup, client, clients} = useRoom(); + const {connect, create} = useRTC(); // 生成随机颜色 const generateColor = () => { @@ -80,131 +82,92 @@ export const LocalSharing: React.FC = () => { }; // 防碰撞位置生成 - const generatePosition = (existing: BubblePosition[]) => { + const generateBubbles = (cs: Client[]) => { + if (!cs) return [] + + const BUBBLE_SIZE = 100; const centerX = window.innerWidth / 2; const centerY = window.innerHeight / 2; - const maxRadius = Math.min(centerX, centerY) - BUBBLE_SIZE; - // 初始化参数 - let radius = 0; - let angle = Math.random() * Math.PI * 2; - let attempts = 0; + const bubbles: Bubble[] = []; + let currentRadius = 0; + let angleStep = (2 * Math.PI) / 6; // 初始6个位置 - do { - // 极坐标转笛卡尔坐标 - const x = centerX + radius * Math.cos(angle); - const y = centerY + radius * Math.sin(angle); + for (let index = 0; index < cs.length; index++) { + let attempt = 0; + let validPosition = false; - // 边界检测 - if (x < 0 || x > window.innerWidth - BUBBLE_SIZE || - y < 0 || y > window.innerHeight - BUBBLE_SIZE) { - radius = 0; - angle += Math.PI / 6; - continue; + if (cs[index].id == client?.id) { + continue } - // 碰撞检测 - const collision = existing.some(bubble => { - const distance = Math.sqrt( - Math.pow(bubble.x - x, 2) + - Math.pow(bubble.y - y, 2) - ); - return distance < BUBBLE_SIZE * 1.5; - }); + while (!validPosition && attempt < 100) { + // 螺旋布局算法 + const angle = angleStep * (index + attempt); + const radius = currentRadius + (attempt * BUBBLE_SIZE * 0.8); - if (!collision) { - return { - x, - y, - radius, - angle - }; - } + // 极坐标转笛卡尔坐标 + const x = centerX + radius * Math.cos(angle); + const y = centerY + radius * Math.sin(angle); - // 逐步扩大搜索半径和角度 - radius += BUBBLE_SIZE * 0.7; - if (radius > maxRadius) { - radius = 0; - angle += Math.PI / 6; // 每30度尝试一次 - } + // 边界检测 + const inBounds = x >= 0 && x <= window.innerWidth - BUBBLE_SIZE && + y >= 0 && y <= window.innerHeight - BUBBLE_SIZE; - attempts++; - } while (attempts < 200); - - return null; - }; - - // 修改updateBubbles中的生成逻辑 - const updateBubbles = (newClients: Client[]) => { - const newBubbles: BubblePosition[] = []; - - newClients.forEach(client => { - const existing = bubbles.find(b => b.id === client.id); - if (existing) { - newBubbles.push(existing); - return; - } - - const position = generatePosition([...bubbles, ...newBubbles]); - if (position) { - newBubbles.push({ - id: client.id, - ...position, - color: generateColor() + // 碰撞检测 + const collision = bubbles.some(pos => { + const distance = Math.sqrt( + Math.pow(pos.x - x, 2) + + Math.pow(pos.y - y, 2) + ); + return distance < BUBBLE_SIZE * 1.5; }); - } - }); - setBubbles(newBubbles); + if (inBounds && !collision) { + bubbles.push({ + id: cs[index].id, + name: cs[index].name, + x: x, + y: y, + color: generateColor(), + } as Bubble); + + // 动态调整布局参数 + currentRadius = Math.max(currentRadius, radius); + angleStep = (2 * Math.PI) / Math.max(6, bubbles.length * 0.7); + validPosition = true; + } + + attempt++; + } + } + + return bubbles; }; useEffect(() => { - // 模拟API获取数据 - const fetchData = async () => { - // const response = await fetch('/api/clients'); - // const data = await response.json(); - - await register(); - - const mockData: Client[] = [ - { id: '1', name: '宁静的梦境' }, - { id: '2', name: '温暖的时光' }, - { id: '3', name: '甜蜜的旋律' }, - { id: '4', name: '柔和的花园' } - ]; - setClients(mockData); - updateBubbles(mockData); - - return () => cleanup(); - }; - fetchData(); + register().then(() => { + enter().then(() => { + list().then() + }) + }); + connect().then(() => { + console.log("[D] rtc create!!!") + }) + return () => cleanup(); }, []); - // 窗口尺寸变化处理 - useEffect(() => { - const handleResize = () => { - const validBubbles = bubbles.filter(bubble => - bubble.x <= window.innerWidth - BUBBLE_SIZE && - bubble.y <= window.innerHeight - BUBBLE_SIZE - ); - setBubbles(validBubbles); - }; - - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, [bubbles]); - // 气泡点击处理 - const handleBubbleClick = (id: string) => { - // 实际开发中这里调用API删除 - setClients(prev => prev.filter(c => c.id !== id)); - setBubbles(prev => prev.filter(b => b.id !== id)); + const handleBubbleClick = async (id: string) => { + console.log('[D] click bubble!!!', id) + await create() }; return
- - {bubbles.map(bubble => { - const client = clients.find(c => c.id === bubble.id); + +

{client?.name}

+ {clients && generateBubbles(clients).map(bubble => { + // const client = clients.find(c => c.id === bubble.id); return client ? (
{ }} onClick={() => handleBubbleClick(bubble.id)} > - {client.name} + {bubble.name}
) : null; })} diff --git a/frontend/src/store/local.ts b/frontend/src/store/local.ts new file mode 100644 index 0000000..219c20f --- /dev/null +++ b/frontend/src/store/local.ts @@ -0,0 +1,140 @@ +import {create} from 'zustand' +import {Resp} from "../interface/response.ts"; + +export interface Client { + client_type: 'desktop' | 'mobile' | 'tablet'; + app_type: 'web'; + room: string; + ip: number; + name: string; + id: string; + register_at: string; +} + +type RoomState = { + conn: WebSocket | null + client: Client | null + clients: Client[] + retryCount: number + reconnectTimer: number | null +} + +type RoomActions = { + register: () => Promise + enter: () => Promise + list: () => Promise + cleanup: () => void +} + +interface Message { + type: 'ping' | 'self' | 'enter' | 'leave'; + time: number; + body: any; +} + +const MAX_RETRY_DELAY = 30000 // 最大重试间隔30秒 +const NORMAL_CLOSE_CODE = 1000 // 正常关闭的状态码 + +export const useRoom = create()((set, get) => ({ + conn: null, + client: null, + clients: [], + retryCount: 0, + reconnectTimer: null, + register: async () => { + const api = `/api/ulocal/register` + const res = await fetch(api, {method: 'POST'}) + const jes = await res.json() as Resp + return set(state => { + return {...state, client: jes.data} + }) + }, + enter: async () => { + const {conn, reconnectTimer} = get() + + // 清理旧连接和定时器 + if (reconnectTimer) clearTimeout(reconnectTimer) + if (conn) conn.close() + + const api = `${window.location.protocol === 'https' ? 'wss' : 'ws'}://${window.location.host}/api/ulocal/ws?id=${get().client?.id}` + console.log('[D] websocket api =',api) + const newConn = new WebSocket(api) + + newConn.onopen = () => { + set({conn: newConn, retryCount: 0}) // 重置重试计数器 + } + + newConn.onerror = (error) => { + console.error('WebSocket error:', error) + } + + newConn.onmessage = (event) => { + const msg = JSON.parse(event.data) as Message; + console.log('[D] ws msg =', msg) + let nc: Client + switch (msg.type) { + case "enter": + nc = msg.body as Client + if(nc.id && nc.name && nc.id !== get().client?.id) { + console.log('[D] enter new client =', nc) + set(state => { + return {...state, clients: [...get().clients, nc]} + }) + } + break + case "leave": + nc = msg.body as Client + if(nc.id) { + let idx = 0; + let items = get().clients; + for (const item of items) { + if (item.id === nc.id) { + items.splice(idx, 1) + set(state => { + return {...state, clients: items} + }) + break; + } + idx++; + } + } + break + } + } + + newConn.onclose = (event) => { + // 非正常关闭时触发重连 + if (event.code !== NORMAL_CLOSE_CODE) { + const {retryCount} = get() + const nextRetry = retryCount + 1 + const delay = Math.min(1000 * Math.pow(2, nextRetry), MAX_RETRY_DELAY) + + const timer = setTimeout(() => { + get().register() + }, delay) + + set({ + retryCount: nextRetry, + reconnectTimer: timer, + conn: null + }) + } + } + + set({conn: newConn, reconnectTimer: null}) + }, + list: async () => { + const api = "/api/ulocal/clients?room=" + const res = await fetch(api + get().client?.room) + const jes = await res.json() as Resp + set(state => { + return {...state, clients: jes.data} + }) + }, + cleanup: () => { + const {conn, reconnectTimer} = get() + if (reconnectTimer) clearTimeout(reconnectTimer) + if (conn) conn.close() + set({conn: null, retryCount: 0, reconnectTimer: null}) + } +})) diff --git a/frontend/src/store/rtc.ts b/frontend/src/store/rtc.ts new file mode 100644 index 0000000..746c905 --- /dev/null +++ b/frontend/src/store/rtc.ts @@ -0,0 +1,50 @@ +import {create} from 'zustand' + +type RTCState = { + conn: RTCPeerConnection | null +} + +type RTCAction = { + connect: () => Promise + create: () => Promise + cleanup: () => void +} + +export const useRTC = create()((set, get) => ({ + conn: null, + connect: async () => { + const conn = new RTCPeerConnection() + + const ch = conn.createDataChannel("fileTransfer", {ordered: true}) + + console.log('[D] channel =', ch) + + ch.onopen = (event) => { + console.log('🚀🚀🚀 / rtc open event', event) + } + + ch.onclose = (event) => { + } + + ch.onerror = (event) => { + } + + ch.onmessage = (event) => { + console.log('🚀🚀🚀 / rtc message event', event) + } + + set((state) => { + return {...state, conn: conn} + }) + }, + create: async () => { + const conn = get().conn + if (conn) conn.onicecandidate = async (event) => { + console.log('[D] rtc local desc =', conn.localDescription) + const offer = await conn.createOffer() + await conn.setLocalDescription(offer) + } + }, + cleanup: () => { + }, +})) \ No newline at end of file diff --git a/frontend/src/store/ws.ts b/frontend/src/store/ws.ts deleted file mode 100644 index 5ea36a2..0000000 --- a/frontend/src/store/ws.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { create } from 'zustand' - -type RoomState = { - conn: WebSocket | null - retryCount: number - reconnectTimer: number | null -} - -type RoomActions = { - register: () => Promise - cleanup: () => void -} - -const MAX_RETRY_DELAY = 30000 // 最大重试间隔30秒 -const NORMAL_CLOSE_CODE = 1000 // 正常关闭的状态码 - -export const useRoom = create()((set, get) => ({ - conn: null, - retryCount: 0, - reconnectTimer: null, - - register: async () => { - const { conn, reconnectTimer } = get() - - // 清理旧连接和定时器 - if (reconnectTimer) clearTimeout(reconnectTimer) - if (conn) conn.close() - - const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://' - const wsUrl = protocol + window.location.host + '/api/ulocal/registry' - const newConn = new WebSocket(wsUrl) - - newConn.onopen = () => { - console.log('WebSocket connected') - set({ conn: newConn, retryCount: 0 }) // 重置重试计数器 - } - - newConn.onerror = (error) => { - console.error('WebSocket error:', error) - } - - newConn.onmessage = (event) => { - console.log("[D] websocket message =", event) - } - - newConn.onclose = (event) => { - // 非正常关闭时触发重连 - if (event.code !== NORMAL_CLOSE_CODE) { - const { retryCount } = get() - const nextRetry = retryCount + 1 - const delay = Math.min(1000 * Math.pow(2, nextRetry), MAX_RETRY_DELAY) - - const timer = setTimeout(() => { - get().register() - }, delay) - - set({ - retryCount: nextRetry, - reconnectTimer: timer, - conn: null - }) - } - } - - set({ conn: newConn, reconnectTimer: null }) - }, - - cleanup: () => { - const { conn, reconnectTimer } = get() - if (reconnectTimer) clearTimeout(reconnectTimer) - if (conn) conn.close() - set({ conn: null, retryCount: 0, reconnectTimer: null }) - } -})) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 6d09045..3f2308a 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ target: 'http://127.0.0.1:9119', changeOrigin: true }, - '/api/ulocal/registry': { + '/api/ulocal/ws': { target: 'ws://127.0.0.1:9119', rewriteWsOrigin: true, ws: true, diff --git a/internal/api/api.go b/internal/api/api.go index 46855be..be94128 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -25,7 +25,9 @@ func Start(ctx context.Context) <-chan struct{} { { api := app.Group("/api/ulocal") - api.Get("/registry", handler.LocalRegistry()) + api.Post("/register", handler.LocalRegister()) + api.Get("/clients", handler.LocalClients()) + api.Get("/ws", handler.LocalWS()) } ready := make(chan struct{}) diff --git a/internal/controller/room.go b/internal/controller/room.go index 3994119..66835c5 100644 --- a/internal/controller/room.go +++ b/internal/controller/room.go @@ -1,6 +1,6 @@ // room controller: // local share websocket room controller -// same remote ip as a +// same remote IP as a package controller import ( @@ -29,53 +29,71 @@ const ( RoomAppTypeWeb = "web" ) +type RoomMessageType string + +const ( + RoomMessageTypePing RoomMessageType = "ping" + RoomMessageTypeSelf RoomMessageType = "self" + RoomMessageTypeEnter RoomMessageType = "enter" + RoomMessageTypeLeave RoomMessageType = "leave" +) + type roomClient struct { controller *roomController conn *websocket.Conn - clientType RoomClientType - appType RoomAppType - ip string - name string - id string + ClientType RoomClientType `json:"client_type"` + AppType RoomAppType `json:"app_type"` + IP string `json:"ip"` + Room string `json:"room"` + Name string `json:"name"` + Id string `json:"id"` + RegisterAt time.Time `json:"register_at"` msgChan chan any } func (rc *roomClient) start(ctx context.Context) { + // start write go func() { for { select { case <-ctx.Done(): + _ = rc.conn.Close() return case msg, _ := <-rc.msgChan: err := rc.conn.WriteJSON(msg) - log.Debug("RoomClient: write json message, ip = %s, id = %s, name = %s, err = %v", rc.ip, rc.id, rc.name, err) + log.Debug("RoomClient: write json message, IP = %s, Id = %s, Name = %s, err = %v", rc.IP, rc.Id, rc.Name, err) if err != nil { - log.Error("RoomClient: write json message failed, ip = %s, id = %s, name = %s, err = %s", rc.ip, rc.id, rc.name, err.Error()) - } - default: - mt, bs, err := rc.conn.ReadMessage() - if err != nil { - log.Error("RoomClient: read message failed, ip = %s, id = %s, name = %s, err = %s", rc.ip, rc.id, rc.name, err.Error()) - rc.controller.Unregister(rc) - return + log.Error("RoomClient: write json message failed, IP = %s, Id = %s, Name = %s, err = %s", rc.IP, rc.Id, rc.Name, err.Error()) } + } + } + }() - switch mt { - case websocket.PingMessage: - rs, _ := json.Marshal(map[string]any{"type": "pong", "time": time.Now().UnixMilli(), "id": rc.id, "name": rc.name}) - if err := rc.conn.WriteMessage(websocket.PongMessage, rs); err != nil { - log.Error("RoomClient: response ping message failed, ip = %s, id = %s, name = %s, err = %s", rc.ip, rc.id, rc.name, err.Error()) - } - case websocket.CloseMessage: - log.Debug("RoomClient: received close message, unregister ip = %s id = %s, name = %s", rc.ip, rc.id, rc.name) - rc.controller.Unregister(rc) - return - case websocket.TextMessage: - log.Info("RoomClient: received text message, ip = %s, id = %s, name = %s, text = %s", rc.ip, rc.id, rc.name, string(bs)) - case websocket.BinaryMessage: - // todo - log.Info("RoomClient: received bytes message, ip = %s, id = %s, name = %s, text = %s", rc.ip, rc.id, rc.name, string(bs)) + // start read + go func() { + for { + mt, bs, err := rc.conn.ReadMessage() + if err != nil { + log.Error("RoomClient: read message failed, IP = %s, Id = %s, Name = %s, err = %s", rc.IP, rc.Id, rc.Name, err.Error()) + rc.controller.Unregister(rc) + return + } + + switch mt { + case websocket.PingMessage: + rs, _ := json.Marshal(map[string]any{"type": "pong", "time": time.Now().UnixMilli(), "Id": rc.Id, "Name": rc.Name}) + if err := rc.conn.WriteMessage(websocket.PongMessage, rs); err != nil { + log.Error("RoomClient: response ping message failed, IP = %s, Id = %s, Name = %s, err = %s", rc.IP, rc.Id, rc.Name, err.Error()) } + case websocket.CloseMessage: + log.Debug("RoomClient: received close message, unregister IP = %s Id = %s, Name = %s", rc.IP, rc.Id, rc.Name) + rc.controller.Unregister(rc) + return + case websocket.TextMessage: + log.Info("RoomClient: received text message, IP = %s, Id = %s, Name = %s, text = %s", rc.IP, rc.Id, rc.Name, string(bs)) + case websocket.BinaryMessage: + // todo + log.Info("RoomClient: received bytes message, IP = %s, Id = %s, Name = %s, text = %s", rc.IP, rc.Id, rc.Name, string(bs)) } } }() @@ -83,52 +101,57 @@ func (rc *roomClient) start(ctx context.Context) { type roomController struct { sync.Mutex - ctx context.Context - rooms map[string]map[string]*roomClient // map[room_id(remote-ip)][id] + ctx context.Context + rooms map[string]map[string]*roomClient // map[room_id(remote-IP)][Id] + notReadies map[string]*roomClient } var ( RoomController = &roomController{ - rooms: make(map[string]map[string]*roomClient), + rooms: make(map[string]map[string]*roomClient), + notReadies: make(map[string]*roomClient), } ) func (rc *roomController) Start(ctx context.Context) { rc.ctx = ctx - go func() { - ticker := time.NewTicker(10 * time.Second) + ticker := time.NewTicker(1 * time.Minute) for { select { - case <-ctx.Done(): + case <-rc.ctx.Done(): return case now := <-ticker.C: - for room := range rc.rooms { - rc.Broadcast(room, now.String()) + for _, nrc := range rc.notReadies { + if now.Sub(nrc.RegisterAt).Minutes() > 1 { + rc.Lock() + delete(rc.notReadies, nrc.Id) + rc.Unlock() + } } } } }() } -func (rc *roomController) Register(c *websocket.Conn, ip, userAgent string) { +func (rc *roomController) Register(ip, userAgent string) *roomClient { nrc := &roomClient{ controller: rc, - conn: c, - clientType: ClientTypeDesktop, - appType: RoomAppTypeWeb, - ip: ip, - id: uuid.Must(uuid.NewV7()).String(), - name: tool.RandomName(), + ClientType: ClientTypeDesktop, + AppType: RoomAppTypeWeb, + IP: ip, + Id: uuid.Must(uuid.NewV7()).String(), + Name: tool.RandomName(), msgChan: make(chan any, 1), + RegisterAt: time.Now(), } ua := useragent.Parse(userAgent) switch { case ua.Mobile: - nrc.clientType = ClientTypeMobile + nrc.ClientType = ClientTypeMobile case ua.Tablet: - nrc.clientType = ClientTypeTablet + nrc.ClientType = ClientTypeTablet } key := "local" @@ -136,37 +159,84 @@ func (rc *roomController) Register(c *websocket.Conn, ip, userAgent string) { key = ip } + nrc.Room = key + rc.Lock() - if _, ok := rc.rooms[key]; !ok { - rc.rooms[key] = make(map[string]*roomClient) + log.Debug("controller.room: registry client, IP = %s(%s), Id = %s, Name = %s", key, nrc.IP, nrc.Id, nrc.Name) + rc.notReadies[nrc.Id] = nrc + if _, ok := rc.rooms[nrc.Room]; !ok { + rc.rooms[nrc.Room] = make(map[string]*roomClient) } - nrc.start(rc.ctx) - log.Debug("controller.room: registry client, ip = %s(%s), id = %s, name = %s", key, nrc.ip, nrc.id, nrc.name) - rc.rooms[key][nrc.id] = nrc - rc.Unlock() - rc.Broadcast(key, "new member") + return nrc +} + +func (rc *roomController) Enter(conn *websocket.Conn, id string) { + client, ok := rc.notReadies[id] + if !ok { + log.Warn("controller.room: entry room id not exist, id = %s", id) + return + } + + rc.Lock() + + if _, ok = rc.rooms[client.Room]; !ok { + log.Warn("controller.room: entry room not exist, room = %s, id = %s, name = %s", client.Room, id, client.Name) + return + } + + rc.rooms[client.Room][id] = client + client.conn = conn + + rc.Unlock() + + client.start(rc.ctx) + + rc.Broadcast(client.Room, map[string]any{"type": RoomMessageTypeEnter, "time": time.Now().UnixMilli(), "body": client}) +} + +func (rc *roomController) List(room string) []*roomClient { + clientList := make([]*roomClient, 0) + + rc.Lock() + defer rc.Unlock() + + clients, ok := rc.rooms[room] + if !ok { + return clientList + } + + for _, client := range clients { + clientList = append(clientList, client) + } + + return clientList } func (rc *roomController) Broadcast(room string, msg any) { for _, client := range rc.rooms[room] { - client.msgChan <- msg + select { + case client.msgChan <- msg: + case <-time.After(2 * time.Second): + log.Warn("RoomController: broadcast timeout, room = %s, client Id = %s, IP = %s", room, client.Id, client.IP) + } } } func (rc *roomController) Unregister(client *roomClient) { key := "local" - if !tool.IsPrivateIP(client.ip) { - key = client.ip + if !tool.IsPrivateIP(client.IP) { + key = client.IP } + log.Debug("controller.room: unregister client, IP = %s(%s), Id = %s, Name = %s", client.IP, key, client.Id, client.Name) + rc.Lock() - defer rc.Unlock() + delete(rc.rooms[key], client.Id) + rc.Unlock() - log.Debug("controller.room: unregister client, ip = %s(%s), id = %s, name = %s", client.ip, key, client.id, client.name) - - delete(rc.rooms[key], client.id) + rc.Broadcast(key, map[string]any{"type": RoomMessageTypeLeave, "time": time.Now().UnixMilli(), "body": client}) } diff --git a/internal/handler/local.go b/internal/handler/local.go index d7bc3c8..f452880 100644 --- a/internal/handler/local.go +++ b/internal/handler/local.go @@ -4,11 +4,36 @@ import ( "github.com/gorilla/websocket" "github.com/loveuer/nf" "github.com/loveuer/nf/nft/log" + "github.com/loveuer/nf/nft/resp" "github.com/loveuer/ushare/internal/controller" "net/http" ) -func LocalRegistry() nf.HandlerFunc { +func LocalRegister() nf.HandlerFunc { + return func(c *nf.Ctx) error { + ip := c.IP(true) + ua := c.Get("User-Agent") + + client := controller.RoomController.Register(ip, ua) + + return resp.Resp200(c, client) + } +} + +func LocalClients() nf.HandlerFunc { + return func(c *nf.Ctx) error { + room := c.Query("room") + if room == "" { + return c.Status(http.StatusBadRequest).JSON(map[string]string{"err": "room can't be empty"}) + } + + list := controller.RoomController.List(room) + + return resp.Resp200(c, list) + } +} + +func LocalWS() nf.HandlerFunc { upgrader := websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, @@ -19,16 +44,19 @@ func LocalRegistry() nf.HandlerFunc { return func(c *nf.Ctx) error { - ip := c.IP(true) - ua := c.Get("User-Agent") + id := c.Query("id") + + if id == "" { + return c.Status(http.StatusBadRequest).JSON(map[string]string{"error": "id is empty"}) + } conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { - log.Error("LocalRegistry: failed to upgrade websocket connection, err = %s", err.Error()) + log.Error("LocalWS: failed to upgrade websocket connection, err = %s", err.Error()) return err } - controller.RoomController.Register(conn, ip, ua) + controller.RoomController.Enter(conn, id) return nil }