server creation
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
'use server';
|
||||
|
||||
import { redirect, RedirectType } from 'next/navigation';
|
||||
import { loginUser } from '@/lib/api/server/auth';
|
||||
import { login, logout } from '@/lib/auth/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { loginUser, getOpenToken } from '@/lib/api/server/auth';
|
||||
import { login } from '@/lib/auth/server';
|
||||
|
||||
export type LoginResult = {
|
||||
success: boolean;
|
||||
@@ -24,7 +24,8 @@ export async function loginAction(prevState: LoginResult, formData: FormData) {
|
||||
const result = await loginUser(username, password);
|
||||
|
||||
if (result.token && result.user) {
|
||||
await login(result.token, result.user);
|
||||
const openToken = await getOpenToken(result.token);
|
||||
await login(result.token, result.user, openToken);
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
|
||||
60
src/lib/actions/server-management.ts
Normal file
60
src/lib/actions/server-management.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { requireAuth } from '@/lib/auth/server';
|
||||
import { createServer, deleteServer } from '@/lib/api/server/servers';
|
||||
|
||||
export type ServerActionResult = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data?: any;
|
||||
};
|
||||
|
||||
export async function createServerAction(
|
||||
prevState: ServerActionResult,
|
||||
formData: FormData
|
||||
): Promise<ServerActionResult> {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const name = formData.get('name') as string;
|
||||
|
||||
if (!name?.trim()) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Server name is required'
|
||||
};
|
||||
}
|
||||
|
||||
const server = await createServer(session.token!, name.trim());
|
||||
revalidatePath('/dashboard');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Server creation started',
|
||||
data: server
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to create server'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteServerAction(serverId: string): Promise<ServerActionResult> {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
await deleteServer(session.token!, serverId);
|
||||
revalidatePath('/dashboard');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Server deleted successfully'
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to delete server'
|
||||
};
|
||||
}
|
||||
}
|
||||
55
src/lib/api/client/base.ts
Normal file
55
src/lib/api/client/base.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
|
||||
|
||||
export type ClientApiResponse<T> = {
|
||||
data?: T;
|
||||
error?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export async function fetchClientAPI<T>(
|
||||
endpoint: string,
|
||||
method: string = 'GET',
|
||||
body?: object,
|
||||
customToken?: string
|
||||
): Promise<ClientApiResponse<T>> {
|
||||
let token = customToken;
|
||||
|
||||
if (!token) {
|
||||
const response = await fetch('/api/session');
|
||||
if (response.ok) {
|
||||
const session = await response.json();
|
||||
token = session.openToken;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Error('No authentication token available');
|
||||
}
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
};
|
||||
|
||||
const response = await fetch(`${BASE_URL}${endpoint}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/logout';
|
||||
return { error: 'unauthorized' };
|
||||
}
|
||||
throw new Error(`API Error: ${response.statusText} - ${method} - ${BASE_URL}${endpoint}`);
|
||||
}
|
||||
|
||||
if (response.headers.get('Content-Type')?.includes('application/json')) {
|
||||
return { data: await response.json() };
|
||||
}
|
||||
|
||||
return { message: await response.text() };
|
||||
}
|
||||
@@ -33,3 +33,12 @@ export async function getCurrentUser(token: string): Promise<User> {
|
||||
const response = await fetchServerAPI<User>(`${authRoute}/me`, token);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
export async function getOpenToken(token: string): Promise<string> {
|
||||
const response = await fetchServerAPI<{ token: string }>(
|
||||
`${authRoute}/open-token`,
|
||||
token,
|
||||
'POST'
|
||||
);
|
||||
return response.data!.token;
|
||||
}
|
||||
|
||||
@@ -29,3 +29,12 @@ export async function getServiceStatus(token: string, serverId: string): Promise
|
||||
const response = await fetchServerAPI<ServiceStatus>(`${serverRoute}/${serverId}/service`, token);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
export async function createServer(token: string, name: string): Promise<Server> {
|
||||
const response = await fetchServerAPI<Server>(serverRoute, token, 'POST', { name });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
export async function deleteServer(token: string, serverId: string): Promise<void> {
|
||||
await fetchServerAPI(`${serverRoute}/${serverId}`, token, 'DELETE');
|
||||
}
|
||||
|
||||
@@ -18,10 +18,11 @@ export async function requireAuth() {
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function login(token: string, user: SessionData['user']) {
|
||||
export async function login(token: string, user: SessionData['user'], openToken?: string) {
|
||||
const session = await getSession();
|
||||
session.token = token;
|
||||
session.user = user;
|
||||
session.openToken = openToken;
|
||||
await session.save();
|
||||
}
|
||||
|
||||
|
||||
61
src/lib/context/ServerCreationPopupContext.tsx
Normal file
61
src/lib/context/ServerCreationPopupContext.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useState, ReactNode } from 'react';
|
||||
|
||||
interface ServerCreationPopupState {
|
||||
serverId: string;
|
||||
serverName: string;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
interface ServerCreationPopupContextType {
|
||||
popup: ServerCreationPopupState | null;
|
||||
showPopup: (serverId: string, serverName: string) => void;
|
||||
hidePopup: () => void;
|
||||
isPopupOpen: boolean;
|
||||
}
|
||||
|
||||
const ServerCreationPopupContext = createContext<ServerCreationPopupContextType | null>(null);
|
||||
|
||||
export function useServerCreationPopup() {
|
||||
const context = useContext(ServerCreationPopupContext);
|
||||
if (!context) {
|
||||
throw new Error('useServerCreationPopup must be used within a ServerCreationPopupProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
interface ServerCreationPopupProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function ServerCreationPopupProvider({ children }: ServerCreationPopupProviderProps) {
|
||||
const [popup, setPopup] = useState<ServerCreationPopupState | null>(null);
|
||||
|
||||
const showPopup = (serverId: string, serverName: string) => {
|
||||
setPopup({
|
||||
serverId,
|
||||
serverName,
|
||||
isOpen: true
|
||||
});
|
||||
};
|
||||
|
||||
const hidePopup = () => {
|
||||
setPopup(null);
|
||||
};
|
||||
|
||||
const isPopupOpen = popup?.isOpen || false;
|
||||
|
||||
return (
|
||||
<ServerCreationPopupContext.Provider
|
||||
value={{
|
||||
popup,
|
||||
showPopup,
|
||||
hidePopup,
|
||||
isPopupOpen
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ServerCreationPopupContext.Provider>
|
||||
);
|
||||
}
|
||||
69
src/lib/context/SteamCMDContext.tsx
Normal file
69
src/lib/context/SteamCMDContext.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useState, ReactNode, useEffect } from 'react';
|
||||
import { useWebSocket } from '@/lib/websocket/context';
|
||||
import { WebSocketMessage, StepData } from '@/lib/websocket/client';
|
||||
|
||||
interface SteamCMDContextType {
|
||||
isSteamCMDRunning: boolean;
|
||||
runningSteamServers: Set<string>;
|
||||
}
|
||||
|
||||
const SteamCMDContext = createContext<SteamCMDContextType | null>(null);
|
||||
|
||||
export function useSteamCMD() {
|
||||
const context = useContext(SteamCMDContext);
|
||||
if (!context) {
|
||||
throw new Error('useSteamCMD must be used within a SteamCMDProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
interface SteamCMDProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function SteamCMDProvider({ children }: SteamCMDProviderProps) {
|
||||
const [runningSteamServers, setRunningSteamServers] = useState<Set<string>>(new Set());
|
||||
const { addMessageHandler, removeMessageHandler } = useWebSocket();
|
||||
|
||||
const isSteamCMDRunning = runningSteamServers.size > 0;
|
||||
|
||||
useEffect(() => {
|
||||
const handleWebSocketMessage = (message: WebSocketMessage) => {
|
||||
if (message.type === 'step') {
|
||||
const data = message.data as StepData;
|
||||
|
||||
if (data.step === 'steam_download') {
|
||||
setRunningSteamServers(prev => {
|
||||
const newSet = new Set(prev);
|
||||
|
||||
if (data.status === 'in_progress') {
|
||||
newSet.add(message.server_id);
|
||||
} else if (data.status === 'completed' || data.status === 'failed') {
|
||||
newSet.delete(message.server_id);
|
||||
}
|
||||
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addMessageHandler(handleWebSocketMessage);
|
||||
return () => {
|
||||
removeMessageHandler(handleWebSocketMessage);
|
||||
};
|
||||
}, [addMessageHandler, removeMessageHandler]);
|
||||
|
||||
return (
|
||||
<SteamCMDContext.Provider
|
||||
value={{
|
||||
isSteamCMDRunning,
|
||||
runningSteamServers
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SteamCMDContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { User } from '@/lib/types';
|
||||
|
||||
export interface SessionData {
|
||||
token?: string;
|
||||
openToken?: string;
|
||||
user?: User;
|
||||
}
|
||||
|
||||
|
||||
207
src/lib/websocket/client.ts
Normal file
207
src/lib/websocket/client.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_WEBSOCKET_BASE_URL || 'ws://localhost:8080';
|
||||
|
||||
export interface WebSocketMessage {
|
||||
type: 'step' | 'steam_output' | 'error' | 'complete';
|
||||
server_id: string;
|
||||
timestamp: number;
|
||||
data: StepData | SteamOutputData | ErrorData | CompleteData;
|
||||
}
|
||||
|
||||
export interface StepData {
|
||||
step:
|
||||
| 'validation'
|
||||
| 'directory_creation'
|
||||
| 'steam_download'
|
||||
| 'config_generation'
|
||||
| 'service_creation'
|
||||
| 'firewall_rules'
|
||||
| 'database_save'
|
||||
| 'completed';
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
||||
message: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface SteamOutputData {
|
||||
output: string;
|
||||
is_error: boolean;
|
||||
}
|
||||
|
||||
export interface ErrorData {
|
||||
error: string;
|
||||
details: string;
|
||||
}
|
||||
|
||||
export interface CompleteData {
|
||||
server_id: string;
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type MessageHandler = (message: WebSocketMessage) => void;
|
||||
export type ConnectionStatusHandler = (
|
||||
status: 'connecting' | 'connected' | 'disconnected' | 'error',
|
||||
error?: string
|
||||
) => void;
|
||||
|
||||
export class WebSocketClient {
|
||||
private ws: WebSocket | null = null;
|
||||
private token: string;
|
||||
private messageHandlers: MessageHandler[] = [];
|
||||
private connectionStatusHandlers: ConnectionStatusHandler[] = [];
|
||||
private connectionPromise: Promise<void> | null = null;
|
||||
private reconnectAttempts = 0;
|
||||
private maxReconnectAttempts = 10;
|
||||
private reconnectDelay = 1000; // Start with 1 second
|
||||
private maxReconnectDelay = 30000; // Max 30 seconds
|
||||
private reconnectTimer: NodeJS.Timeout | null = null;
|
||||
private shouldReconnect = true;
|
||||
private associatedServerId: string | null = null;
|
||||
|
||||
constructor(token: string) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
connect(): Promise<void> {
|
||||
if (this.connectionPromise) {
|
||||
return this.connectionPromise;
|
||||
}
|
||||
|
||||
this.shouldReconnect = true;
|
||||
this.notifyStatus('connecting');
|
||||
|
||||
this.connectionPromise = new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.ws = new WebSocket(`${BASE_URL}?token=${this.token}`);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('WebSocket connected');
|
||||
this.reconnectAttempts = 0;
|
||||
this.reconnectDelay = 5000;
|
||||
this.notifyStatus('connected');
|
||||
|
||||
// Re-associate with server if we had one
|
||||
if (this.associatedServerId) {
|
||||
this.associateWithServer(this.associatedServerId);
|
||||
}
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(event.data);
|
||||
this.messageHandlers.forEach((handler) => handler(message));
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
console.log('WebSocket disconnected:', event.code, event.reason);
|
||||
this.connectionPromise = null;
|
||||
this.notifyStatus('disconnected');
|
||||
|
||||
if (this.shouldReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
this.notifyStatus('error', 'Connection failed');
|
||||
reject(error);
|
||||
};
|
||||
} catch (error) {
|
||||
this.notifyStatus('error', error instanceof Error ? error.message : 'Unknown error');
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
return this.connectionPromise;
|
||||
}
|
||||
|
||||
associateWithServer(serverId: string): void {
|
||||
this.associatedServerId = serverId;
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(`server_id:${serverId}`);
|
||||
}
|
||||
}
|
||||
|
||||
addMessageHandler(handler: MessageHandler): void {
|
||||
this.messageHandlers.push(handler);
|
||||
}
|
||||
|
||||
removeMessageHandler(handler: MessageHandler): void {
|
||||
const index = this.messageHandlers.indexOf(handler);
|
||||
if (index > -1) {
|
||||
this.messageHandlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.shouldReconnect = false;
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer);
|
||||
this.reconnectTimer = null;
|
||||
}
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
this.connectionPromise = null;
|
||||
this.messageHandlers = [];
|
||||
this.connectionStatusHandlers = [];
|
||||
this.associatedServerId = null;
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.ws?.readyState === WebSocket.OPEN;
|
||||
}
|
||||
|
||||
addConnectionStatusHandler(handler: ConnectionStatusHandler): void {
|
||||
this.connectionStatusHandlers.push(handler);
|
||||
}
|
||||
|
||||
removeConnectionStatusHandler(handler: ConnectionStatusHandler): void {
|
||||
const index = this.connectionStatusHandlers.indexOf(handler);
|
||||
if (index > -1) {
|
||||
this.connectionStatusHandlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
reconnect(): Promise<void> {
|
||||
this.disconnect();
|
||||
return this.connect();
|
||||
}
|
||||
|
||||
private notifyStatus(
|
||||
status: 'connecting' | 'connected' | 'disconnected' | 'error',
|
||||
error?: string
|
||||
): void {
|
||||
this.connectionStatusHandlers.forEach((handler) => handler(status, error));
|
||||
}
|
||||
|
||||
private scheduleReconnect(): void {
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer);
|
||||
}
|
||||
|
||||
this.reconnectAttempts++;
|
||||
const delay = Math.min(
|
||||
this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
|
||||
this.maxReconnectDelay
|
||||
);
|
||||
|
||||
console.log(
|
||||
`WebSocket reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`
|
||||
);
|
||||
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
this.reconnectTimer = null;
|
||||
this.connect().catch((error) => {
|
||||
console.error('Reconnection failed:', error);
|
||||
});
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
148
src/lib/websocket/context.tsx
Normal file
148
src/lib/websocket/context.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState, ReactNode, useCallback } from 'react';
|
||||
import { WebSocketClient, MessageHandler, ConnectionStatusHandler } from './client';
|
||||
|
||||
interface WebSocketContextType {
|
||||
client: WebSocketClient | null;
|
||||
isConnected: boolean;
|
||||
connectionStatus: 'connecting' | 'connected' | 'disconnected' | 'error';
|
||||
connectionError: string | null;
|
||||
connect: (token: string) => Promise<void>;
|
||||
disconnect: () => void;
|
||||
reconnect: () => Promise<void>;
|
||||
associateWithServer: (serverId: string) => void;
|
||||
addMessageHandler: (handler: MessageHandler) => void;
|
||||
removeMessageHandler: (handler: MessageHandler) => void;
|
||||
addConnectionStatusHandler: (handler: ConnectionStatusHandler) => void;
|
||||
removeConnectionStatusHandler: (handler: ConnectionStatusHandler) => void;
|
||||
}
|
||||
|
||||
const WebSocketContext = createContext<WebSocketContextType | null>(null);
|
||||
|
||||
export function useWebSocket() {
|
||||
const context = useContext(WebSocketContext);
|
||||
if (!context) {
|
||||
throw new Error('useWebSocket must be used within a WebSocketProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
interface WebSocketProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function WebSocketProvider({ children }: WebSocketProviderProps) {
|
||||
const [client, setClient] = useState<WebSocketClient | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [connectionStatus, setConnectionStatus] = useState<
|
||||
'connecting' | 'connected' | 'disconnected' | 'error'
|
||||
>('disconnected');
|
||||
const [connectionError, setConnectionError] = useState<string | null>(null);
|
||||
|
||||
const connect = useCallback(
|
||||
async (token: string) => {
|
||||
if (client) {
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
const newClient = new WebSocketClient(token);
|
||||
|
||||
// Add connection status handler
|
||||
const statusHandler: ConnectionStatusHandler = (status, error) => {
|
||||
setConnectionStatus(status);
|
||||
setIsConnected(status === 'connected');
|
||||
setConnectionError(error || null);
|
||||
};
|
||||
|
||||
newClient.addConnectionStatusHandler(statusHandler);
|
||||
|
||||
try {
|
||||
await newClient.connect();
|
||||
setClient(newClient);
|
||||
} catch (error) {
|
||||
console.error('Failed to connect WebSocket:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
if (client) {
|
||||
client.disconnect();
|
||||
setClient(null);
|
||||
setIsConnected(false);
|
||||
setConnectionStatus('disconnected');
|
||||
setConnectionError(null);
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
const reconnect = async () => {
|
||||
if (client) {
|
||||
try {
|
||||
await client.reconnect();
|
||||
} catch (error) {
|
||||
console.error('Failed to reconnect WebSocket:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const associateWithServer = (serverId: string) => {
|
||||
if (client && isConnected) {
|
||||
client.associateWithServer(serverId);
|
||||
}
|
||||
};
|
||||
|
||||
const addMessageHandler = (handler: MessageHandler) => {
|
||||
if (client) {
|
||||
client.addMessageHandler(handler);
|
||||
}
|
||||
};
|
||||
|
||||
const removeMessageHandler = (handler: MessageHandler) => {
|
||||
if (client) {
|
||||
client.removeMessageHandler(handler);
|
||||
}
|
||||
};
|
||||
|
||||
const addConnectionStatusHandler = (handler: ConnectionStatusHandler) => {
|
||||
if (client) {
|
||||
client.addConnectionStatusHandler(handler);
|
||||
}
|
||||
};
|
||||
|
||||
const removeConnectionStatusHandler = (handler: ConnectionStatusHandler) => {
|
||||
if (client) {
|
||||
client.removeConnectionStatusHandler(handler);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
disconnect();
|
||||
};
|
||||
}, [disconnect]);
|
||||
|
||||
return (
|
||||
<WebSocketContext.Provider
|
||||
value={{
|
||||
client,
|
||||
isConnected,
|
||||
connectionStatus,
|
||||
connectionError,
|
||||
connect,
|
||||
disconnect,
|
||||
reconnect,
|
||||
associateWithServer,
|
||||
addMessageHandler,
|
||||
removeMessageHandler,
|
||||
addConnectionStatusHandler,
|
||||
removeConnectionStatusHandler
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</WebSocketContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user