소스 정보
- 저장소
- ffsshhttiikk/opencode-agents-skills
- 최근 소스 활동
- 2026년 2월 28일 22:51
- 감지된 SKILL.md 언어
- 영어
- 스타
- 2
- 포크
- 2
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/ffsshhttiikk/opencode-agents-skills --skill http-https명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
| name | http-https |
| description | HTTP/HTTPS protocol implementation and best practices |
| category | networking |
| difficulty | intermediate |
| tags | ["http","https","protocol","web","rest"] |
| author | OpenCode Community |
| version | 1 |
| last_updated | 2024-01-15T00:00:00.000Z |
I am HTTP/HTTPS, the application layer protocol enabling communication between clients and servers on the World Wide Web. I define the rules for requesting and transferring web resources through methods (GET, POST, PUT, DELETE, etc.), status codes, headers, and message bodies. HTTPS adds Transport Layer Security (TLS) encryption to protect data in transit. I support persistent connections, chunked transfer encoding, content negotiation, and caching mechanisms. HTTP/2 and HTTP/3 introduce multiplexing, header compression, and server push for improved performance. I form the foundation for RESTful APIs, web services, and modern web applications.
HTTP Methods: GET (retrieve), POST (create), PUT (update), PATCH (partial update), DELETE (remove).
Status Codes: 1xx (informational), 2xx (success), 3xx (redirection), 4xx (client error), 5xx (server error).
Headers: Metadata controlling caching, authentication, content type, and request behavior.
Body/Payload: Request or response content in various formats (JSON, XML, multipart, streaming).
Cookies and Sessions: State management across HTTP requests.
Caching: Headers (Cache-Control, ETag, Last-Modified) controlling response caching.
Content Negotiation: Headers (Accept, Accept-Language) for format and language preferences.
HTTPS/TLS: Encryption, certificate validation, and secure communication.
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"math"
"net/http"
"sync"
"time"
)
type HTTPClient struct {
client *http.Client
baseURL string
defaultTimeout time.Duration
retryConfig RetryConfig
circuitBreaker *CircuitBreaker
}
type RetryConfig struct {
MaxRetries int
InitialDelay time.Duration
MaxDelay time.Duration
Multiplier float64
}
type CircuitBreaker struct {
name string
failureCount int
failureThreshold int
resetTimeout time.Duration
lastFailure time.Time
state string // closed, open, half-open
mutex sync.RWMutex
}
func NewHTTPClient(baseURL string, timeout time.Duration) *HTTPClient {
return &HTTPClient{
client: &http.Client{
Timeout: timeout,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
MaxConnsPerHost: 100,
},
},
baseURL: baseURL,
defaultTimeout: timeout,
retryConfig: RetryConfig{
MaxRetries: 3,
InitialDelay: 100 * time.Millisecond,
MaxDelay: 10 * time.Second,
Multiplier: 2.0,
},
circuitBreaker: &CircuitBreaker{
name: "http-circuit-breaker",
failureThreshold: 5,
resetTimeout: 30 * time.Second,
state: "closed",
},
}
}
func (c *HTTPClient) Get(ctx context.Context, endpoint string, headers map[string]string) (*HTTPResponse, error) {
return c.doRequest(ctx, http.MethodGet, endpoint, nil, headers)
}
func (c *HTTPClient) Post(ctx context.Context, endpoint string, body interface{}, headers map[string]string) (*HTTPResponse, error) {
return c.doRequest(ctx, http.MethodPost, endpoint, body, headers)
}
func (c *HTTPClient) Put(ctx context.Context, endpoint string, body interface{}, headers map[string]string) (*HTTPResponse, error) {
return c.doRequest(ctx, http.MethodPut, endpoint, body, headers)
}
func (c *HTTPClient) Delete(ctx context.Context, endpoint string, headers map[string]string) (*HTTPResponse, error) {
return c.doRequest(ctx, http.MethodDelete, endpoint, nil, headers)
}
func (c *HTTPClient) doRequest(ctx context.Context, method, endpoint string, body interface{}, headers map[string]string) (*HTTPResponse, error) {
if !c.circuitBreaker.canExecute() {
return nil, fmt.Errorf("circuit breaker is open")
}
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
bodyReader = bytes.NewReader(jsonBody)
if headers == nil {
headers = make(map[string]string)
}
headers["Content-Type"] = "application/json"
}
url := c.baseURL + endpoint
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
for key, value := range headers {
req.Header.Set(key, value)
}
var lastErr error
var response *http.Response
delay := c.retryConfig.InitialDelay
for attempt := 0; attempt <= c.retryConfig.MaxRetries; attempt++ {
response, lastErr = c.client.Do(req)
if lastErr == nil {
break
}
if attempt == c.retryConfig.MaxRetries {
c.circuitBreaker.recordFailure()
return nil, fmt.Errorf("request failed after %d attempts: %w", c.retryConfig.MaxRetries, lastErr)
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(delay):
}
delay = time.Duration(float64(delay) * c.retryConfig.Multiplier)
delay = time.Duration(math.Min(float64(delay), float64(c.retryConfig.MaxDelay)))
}
defer response.Body.Close()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
httpResponse := &HTTPResponse{
StatusCode: response.StatusCode,
Headers: response.Header,
Body: responseBody,
}
if response.StatusCode >= 400 {
c.circuitBreaker.recordFailure()
return httpResponse, fmt.Errorf("HTTP error %d: %s", response.StatusCode, string(responseBody))
}
c.circuitBreaker.recordSuccess()
return httpResponse, nil
}
func (cb *CircuitBreaker) canExecute() bool {
cb.mutex.RLock()
defer cb.mutex.RUnlock()
if cb.state == "open" {
if time.Since(cb.lastFailure) > cb.resetTimeout {
cb.mutex.RUnlock()
cb.mutex.Lock()
cb.state = "half-open"
cb.mutex.Unlock()
cb.mutex.RLock()
}
}
return cb.state != "open"
}
func (cb *CircuitBreaker) recordFailure() {
cb.mutex.Lock()
defer cb.mutex.Unlock()
cb.failureCount++
cb.lastFailure = time.Now()
if cb.failureCount >= cb.failureThreshold {
cb.state = "open"
}
}
func (cb *CircuitBreaker) recordSuccess() {
cb.mutex.Lock()
defer cb.mutex.Unlock()
cb.failureCount = 0
cb.state = "closed"
}
type HTTPResponse struct {
StatusCode int
Headers http.Header
Body []byte
}
func (r *HTTPResponse) UnmarshalJSON(v interface{}) error {
return json.Unmarshal(r.Body, v)
}
const http = require('http');
class HTTPMiddleware {
constructor() {
this.middleware = [];
}
use(fn) {
this.middleware.push(fn);
return this;
}
async handle(request, response) {
const context = {
request,
response,
state: {},
status: 200
};
for (const fn of this.middleware) {
await fn(context);
if (context.response.writableEnded) {
return;
}
if (context.status >= 400) {
break;
}
}
if (!context.response.writableEnded) {
response.statusCode = context.status;
response.end(JSON.stringify(context.body || {}));
}
}
}
class HTTPServer {
constructor(port = 3000) {
this.port = port;
this.middleware = new HTTPMiddleware();
this.routes = new Map();
this.server = null;
}
use(fn) {
this.middleware.use(fn);
return this;
}
get(path, handler) {
this.addRoute('GET', path, handler);
return this;
}
post(path, handler) {
this.addRoute('POST', path, handler);
return this;
}
put(path, handler) {
this.addRoute('PUT', path, handler);
return this;
}
delete(path, handler) {
this.addRoute('DELETE', path, handler);
return this;
}
addRoute(method, path, handler) {
const pattern = this.pathToRegex(path);
this.routes.set(`${method}:${pattern}`, { pattern, handler });
}
pathToRegex(path) {
const pattern = path
.replace(/\//g, '\\/')
.replace(/:(\w+)/g, '(?<$1>[^/]+)');
return new RegExp(`^${pattern}$`);
}
findRoute(method, path) {
for (const [key, route] of this.routes) {
const [routeMethod, pattern] = key.split(':');
if (routeMethod !== method) continue;
const match = path.match(pattern);
if (match) {
return { handler: route.handler, params: match.groups };
}
}
return null;
}
async handleRequest(req, res) {
const url = new URL(req.url, `http://${req.headers.host}`);
const route = this.findRoute(req.method, url.pathname);
const context = {
request: req,
response: res,
method: req.method,
path: url.pathname,
query: Object.fromEntries(url.searchParams),
params: route?.params || {},
body: null,
status: 200
};
// Parse body for POST/PUT
if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
await this.parseBody(req);
context.body = req.body;
}
// Run middleware
for (const fn of this.middleware.middleware) {
await fn(context);
if (res.writableEnded) return;
}
// Handle route
if (route) {
await route.handler(context);
} else {
context.status = 404;
context.body = { error: 'Not Found' };
res.statusCode = 404;
res.end(JSON.stringify(context.body));
}
}
async parseBody(req) {
return new Promise((resolve) => {
let body = '';
req.on('data', chunk => body += chunk.toString());
req.on('end', () => {
try {
req.body = JSON.parse(body || '{}');
} catch {
req.body = {};
}
resolve();
});
});
}
listen() {
this.server = http.createServer(this.handleRequest.bind(this));
this.server.listen(this.port, () => {
console.log(`Server running on port ${this.port}`);
});
return this.server;
}
}
// Usage
const app = new HTTPServer(3000);
// Logging middleware
app.use(async (ctx) => {
console.log(`${ctx.method} ${ctx.path} - ${ctx.status}`);
});
// CORS middleware
app.use(async (ctx) => {
ctx.response.setHeader('Access-Control-Allow-Origin', '*');
ctx.response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
ctx.response.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
});
// Auth middleware
app.use(async (ctx) => {
const auth = ctx.request.headers.authorization;
if (!auth && !ctx.request.url.includes('/public')) {
ctx.status = 401;
ctx.body = { error: 'Unauthorized' };
}
});
// Routes
app.get('/api/users', async (ctx) => {
ctx.body = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
});
app.get('/api/users/:id', async (ctx) => {
ctx.body = { id: ctx.params.id, name: `User ${ctx.params.id}` };
});
app.post('/api/users', async (ctx) => {
ctx.body = { id: 3, ...ctx.body };
ctx.status = 201;
});
app.listen();
import ssl
import http.server
import socketserver
import json
import os
from urllib.parse import urlparse, parse_qs
from typing import Dict, Any, Optional
from datetime import datetime
class HTTPSServer:
def __init__(
self,
host: str = '0.0.0.0',
port: int = 8443,
cert_path: str = 'cert.pem',
key_path: str = 'key.pem'
):
self.host = host
self.port = port
self.cert_path = cert_path
self.key_path = key_path
self.routes: Dict[str, Dict[str, Any]] = {}
self.middleware: list = []
def route(self, path: str, methods: list = ['GET']):
def decorator(func):
self.routes[f"{methods[0]}:{path}"] = {
'handler': func,
'methods': methods
}
return func
return decorator
def use(self, middleware_func):
self.middleware.append(middleware_func)
return middleware_func
def create_ssl_context(self) -> ssl.SSLContext:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(self.cert_path, self.key_path)
context.minimum_version = ssl.TLSVersion.TLSv1_3
context.set_alpn_protocols(['h2', 'http/1.1'])
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
return context
def run(self):
context = self.create_ssl_context()
class RequestHandler(http.server.BaseHTTPRequestHandler):
def log_message(self, format, *args):
print(f"[{datetime.now().isoformat()}] {args[0]}")
def do_method(self, method: str):
parsed = urlparse(self.path)
path = parsed.path
query = parse_qs(parsed.query)
handler_key = f"{method}:{path}"
if handler_key not in self.server.routes:
self.send_error(404, 'Not Found')
return
route = self.server.routes[handler_key]
if method not in route['methods']:
self.send_error(405, 'Method Not Allowed')
return
try:
# Read request body
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length) if content_length > 0 else None
request_context = {
'method': method,
'path': path,
'query': query,
'body': json.loads(body.decode('utf-8')) if body else None,
'headers': dict(self.headers),
'params': {}
}
# Run middleware
for middleware in self.server.middleware:
result = middleware(request_context)
if result and 'response' in result:
self.send_json_response(result['response'])
return
# Handle route
response = route['handler'](request_context)
self.send_json_response(response)
except Exception as e:
self.send_json_response({'error': str(e)}, status=500)
def do_GET(self):
self.do_method('GET')
def do_POST(self):
self.do_method('POST')
def do_PUT(self):
self.do_method('PUT')
def do_DELETE(self):
self.do_method('DELETE')
def send_json_response(self, data: dict, status: int = 200):
response_body = json.dumps(data, default=str).encode('utf-8')
self.send_response(status)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', len(response_body))
self.send_header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
self.end_headers()
self.wfile.write(response_body)
socketserver.TCPServer.allow_reuse_address = True
with socketserver.TCPServer((self.host, self.port), RequestHandler) as httpd:
httpd.routes = self.routes
httpd.middleware = self.middleware
print(f"HTTPS Server running on https://{self.host}:{self.port}")
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()
# Usage
app = HTTPSServer(host='0.0.0.0', port=8443)
# Middleware
@app.use
def cors_middleware(request):
if 'Origin' in request['headers']:
return {
'response': {
'headers': {
'Access-Control-Allow-Origin': request['headers']['Origin'],
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
}
}
@app.use
def auth_middleware(request):
protected_paths = ['/api/admin', '/api/users']
if any(request['path'].startswith(p) for p in protected_paths):
auth = request['headers'].get('Authorization', '')
if not auth.startswith('Bearer '):
return {'response': {'error': 'Unauthorized'}, 'status': 401}
# Routes
@app.route('/api/health', methods=['GET'])
def health_check(request):
return {'status': 'healthy', 'timestamp': datetime.now().isoformat()}
@app.route('/api/users', methods=['GET'])
def get_users(request):
return [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
@app.route('/api/users/:id', methods=['GET'])
def get_user(request):
return {'id': request['params']['id'], 'name': f"User {request['params']['id']}"}
@app.route('/api/users', methods=['POST'])
def create_user(request):
return {'id': 3, **request['body']}, 201
# Generate self-signed certificate for testing
# openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
if __name__ == '__main__':
if os.path.exists('cert.pem') and os.path.exists('key.pem'):
app.run()
else:
print("Please generate TLS certificate and key first:")
print("openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes")
package main
import (
"crypto/tls"
"fmt"
"io"
"log"
"net/http"
"time"
)
type HTTP2Server struct {
server *http.Server
router *Router
}
type Router struct {
routes map[string]http.Handler
}
func NewRouter() *Router {
return &Router{
routes: make(map[string]http.Handler),
}
}
func (r *Router) GET(path string, handler http.HandlerFunc) {
r.routes["GET:"+path] = handler
}
func (r *Router) POST(path string, handler http.HandlerFunc) {
r.routes["POST:"+path] = handler
}
func (r *Router) Handle(path string, handler http.Handler) {
r.routes["HANDLER:"+path] = handler
}
func NewHTTP2Server(addr string) *HTTP2Server {
router := NewRouter()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if handler, ok := router.routes["GET:"+r.URL.Path]; ok {
handler.ServeHTTP(w, r)
return
}
http.NotFound(w, r)
})
// HTTP/2 server configuration
server := &http.Server{
Addr: addr,
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
return &HTTP2Server{
server: server,
router: router,
}
}
func (s *HTTP2Server) ConfigureTLS(minVersion uint16) {
s.server.TLSConfig = &tls.Config{
MinVersion: minVersion,
CurvePreferences: []tls.CurveID{tls.CurveP256, tls.X25519},
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
NextProtos: []string{"h2", "http/1.1"},
}
}
func (s *HTTP2Server) Start() error {
s.router.GET("/index.html", func(w http.ResponseWriter, r *http.Request) {
// Server Push for critical resources
pusher, ok := w.(http.Pusher)
if ok {
// Push CSS
if err := pusher.Push("/styles.css", nil); err != nil {
log.Printf("Failed to push styles.css: %v", err)
}
// Push JavaScript
if err := pusher.Push("/app.js", nil); err != nil {
log.Printf("Failed to push app.js: %v", err)
}
// Push images
if err := pusher.Push("/logo.png", nil); err != nil {
log.Printf("Failed to push logo.png: %v", err)
}
}
w.Header().Set("Content-Type", "text/html")
io.WriteString(w, `<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<h1>HTTP/2 Server Push Demo</h1>
<script src="/app.js"></script>
</body>
</html>`)
})
s.router.GET("/styles.css", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/css")
io.WriteString(w, `body { font-family: Arial, sans-serif; margin: 40px; }`)
})
s.router.GET("/app.js", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/javascript")
io.WriteString(w, `console.log('Hello from HTTP/2!');`)
})
s.router.GET("/api/users", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "public, max-age=3600")
io.WriteString(w, `[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]`)
})
s.router.GET("/api/users/:id", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, `{"id":1,"name":"Alice"}`)
})
s.router.POST("/api/users", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
io.WriteString(w, `{"id":3,"name":"New User"}`)
})
log.Printf("HTTP/2 Server starting on %s", s.server.Addr)
return s.server.ListenAndServeTLS("cert.pem", "key.pem")
}
import { IncomingMessage, Server as HttpServer } from 'http';
import { Server as SocketIOServer, Socket, ServerOptions } from 'socket.io';
import { URL } from 'url';
import { verify } from 'jsonwebtoken';
interface WebSocketMessage {
type: string;
payload: any;
timestamp: number;
}
interface AuthenticatedSocket extends Socket {
userId?: string;
user?: any;
}
class WebSocketServer {
private io: SocketIOServer;
private rooms: Map<string, Set<string>> = new Map();
private userSockets: Map<string, Set<string>> = new Map();
constructor(server: HttpServer, options: Partial<ServerOptions> = {}) {
this.io = new SocketIOServer(server, {
cors: {
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
methods: ['GET', 'POST'],
credentials: true
},
pingTimeout: 60000,
pingInterval: 25000,
...options
});
this.setupMiddleware();
this.setupEventHandlers();
}
private setupMiddleware(): void {
this.io.use(async (socket: AuthenticatedSocket, next) => {
try {
const token = socket.handshake.auth?.token ||
socket.handshake.headers?.authorization?.replace('Bearer ', '');
if (!token) {
return next(new Error('Authentication required'));
}
const decoded = verify(token, process.env.JWT_SECRET!) as any;
socket.userId = decoded.userId;
socket.user = decoded;
next();
} catch (error) {
next(new Error('Invalid token'));
}
});
this.io.engine.on("connection_error", (err) => {
console.log("Connection error:", err.message);
});
}
private setupEventHandlers(): void {
this.io.on('connection', (socket: AuthenticatedSocket) => {
console.log(`User connected: ${socket.userId}`);
// Join user's personal room
const userRoom = `user:${socket.userId}`;
socket.join(userRoom);
this.trackUserSocket(socket.userId!, socket.id);
// Handle joining topic rooms
socket.on('join:room', (roomId: string) => {
socket.join(`room:${roomId}`);
if (!this.rooms.has(roomId)) {
this.rooms.set(roomId, new Set());
}
this.rooms.get(roomId)!.add(socket.userId!);
socket.to(`room:${roomId}`).emit('user:joined', {
userId: socket.userId,
roomId,
timestamp: Date.now()
});
});
// Handle leaving rooms
socket.on('leave:room', (roomId: string) => {
socket.leave(`room:${roomId}`);
this.rooms.get(roomId)?.delete(socket.userId!);
socket.to(`room:${roomId}`).emit('user:left', {
userId: socket.userId,
roomId,
timestamp: Date.now()
});
});
// Handle private messages
socket.on('message:private', (data: { to: string; content: string }) => {
const message: WebSocketMessage = {
type: 'private',
payload: {
from: socket.userId,
to: data.to,
content: data.content
},
timestamp: Date.now()
};
// Send to recipient's personal room
this.io.to(`user:${data.to}`).emit('message:received', message);
// Confirm delivery to sender
socket.emit('message:sent', message);
});
// Handle room messages
socket.on('message:room', (data: { roomId: string; content: string }) => {
const message: WebSocketMessage = {
type: 'room',
payload: {
from: socket.userId,
fromName: socket.user?.name,
roomId: data.roomId,
content: data.content
},
timestamp: Date.now()
};
socket.to(`room:${data.roomId}`).emit('message:room', message);
socket.emit('message:sent', message);
});
// Handle typing indicators
socket.on('typing:start', (roomId: string) => {
socket.to(`room:${roomId}`).emit('user:typing', {
userId: socket.userId,
roomId
});
});
socket.on('typing:stop', (roomId: string) => {
socket.to(`room:${roomId}`).emit('user:stopped-typing', {
userId: socket.userId,
roomId
});
});
// Handle presence updates
socket.on('presence:update', (status: string) => {
socket.to(`user:${socket.userId}`).broadcast.emit('presence:changed', {
userId: socket.userId,
status,
timestamp: Date.now()
});
});
// Handle disconnection
socket.on('disconnect', (reason) => {
console.log(`User disconnected: ${socket.userId}, reason: ${reason}`);
this.removeUserSocket(socket.userId!, socket.id);
// Notify all rooms user was in
this.rooms.forEach((users, roomId) => {
if (users.has(socket.userId!)) {
users.delete(socket.userId!);
socket.to(`room:${roomId}`).emit('user:left', {
userId: socket.userId,
roomId,
timestamp: Date.now()
});
}
});
});
});
}
private trackUserSocket(userId: string, socketId: string): void {
if (!this.userSockets.has(userId)) {
this.userSockets.set(userId, new Set());
}
this.userSockets.get(userId)!.add(socketId);
}
private removeUserSocket(userId: string, socketId: string): void {
const sockets = this.userSockets.get(userId);
if (sockets) {
sockets.delete(socketId);
if (sockets.size === 0) {
this.userSockets.delete(userId);
}
}
}
// Broadcast to all connected clients
broadcast(event: string, data: any): void {
this.io.emit(event, {
...data,
timestamp: Date.now()
});
}
// Send to specific user
sendToUser(userId: string, event: string, data: any): void {
this.io.to(`user:${userId}`).emit(event, {
...data,
timestamp: Date.now()
});
}
// Send to room
sendToRoom(roomId: string, event: string, data: any, exclude?: string): void {
if (exclude) {
this.io.to(`room:${roomId}`).except(`user:${exclude}`).emit(event, {
...data,
timestamp: Date.now()
});
} else {
this.io.to(`room:${roomId}`).emit(event, {
...data,
timestamp: Date.now()
});
}
}
// Get online users in room
getRoomUsers(roomId: string): string[] {
return Array.from(this.rooms.get(roomId) || []);
}
// Check if user is online
isUserOnline(userId: string): boolean {
return this.userSockets.has(userId);
}
}
SOC 직업 분류 기준