diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx
index 79f8b5d..2ba77ac 100644
--- a/frontend/app/layout.tsx
+++ b/frontend/app/layout.tsx
@@ -7,7 +7,8 @@ import { ClerkProvider } from "@clerk/nextjs"
import { Toaster } from "@/components/ui/sonner"
import { Analytics } from "@vercel/analytics/react"
import { TerminalProvider } from '@/context/TerminalContext';
-import { PreviewProvider } from "@/context/PreviewContext"
+import { PreviewProvider } from "@/context/PreviewContext";
+import { SocketProvider } from '@/context/SocketContext'
export const metadata: Metadata = {
title: "Sandbox",
@@ -15,7 +16,7 @@ export const metadata: Metadata = {
}
export default function RootLayout({
- children,
+ children
}: Readonly<{
children: React.ReactNode
}>) {
@@ -29,11 +30,13 @@ export default function RootLayout({
forcedTheme="dark"
disableTransitionOnChange
>
+
{children}
+
diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx
index 595c914..5d4340d 100644
--- a/frontend/components/editor/index.tsx
+++ b/frontend/components/editor/index.tsx
@@ -3,7 +3,6 @@
import { SetStateAction, useCallback, useEffect, useRef, useState } from "react"
import monaco from "monaco-editor"
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
-import { Socket, io } from "socket.io-client"
import { toast } from "sonner"
import { useClerk } from "@clerk/nextjs"
@@ -32,7 +31,7 @@ import PreviewWindow from "./preview"
import Terminals from "./terminals"
import { ImperativePanelHandle } from "react-resizable-panels"
import { PreviewProvider, usePreview } from '@/context/PreviewContext';
-import { useTerminal } from '@/context/TerminalContext';
+import { useSocket } from "@/context/SocketContext"
export default function CodeEditor({
userData,
@@ -41,24 +40,16 @@ export default function CodeEditor({
userData: User
sandboxData: Sandbox
}) {
- const socketRef = useRef(null);
- // Initialize socket connection if it doesn't exist
- if (!socketRef.current) {
- socketRef.current = io(
- `${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userData.id}&sandboxId=${sandboxData.id}`,
- {
- timeout: 2000,
- }
- );
- }
-
- //Terminalcontext functionsand effects
- const { setUserAndSandboxId } = useTerminal();
+ //SocketContext functions and effects
+ const { socket, setUserAndSandboxId } = useSocket();
useEffect(() => {
- setUserAndSandboxId(userData.id, sandboxData.id);
- }, [userData.id, sandboxData.id, setUserAndSandboxId]);
+ // Check if socket is null, and initialize it by setting userId and sandboxId
+ if (!socket && userData.id && sandboxData.id) {
+ setUserAndSandboxId(userData.id, sandboxData.id);
+ }
+ }, [socket, userData.id, sandboxData.id, setUserAndSandboxId]);
//Preview Button state
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
@@ -120,7 +111,7 @@ export default function CodeEditor({
const room = useRoom()
const [provider, setProvider] = useState()
const userInfo = useSelf((me) => me.info)
-
+
// Liveblocks providers map to prevent reinitializing providers
type ProviderData = {
provider: LiveblocksProvider;
@@ -333,9 +324,9 @@ export default function CodeEditor({
);
console.log(`Saving file...${activeFileId}`);
console.log(`Saving file...${value}`);
- socketRef.current?.emit("saveFile", activeFileId, value);
+ socket?.emit("saveFile", activeFileId, value);
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
- [socketRef]
+ [socket]
);
useEffect(() => {
@@ -432,9 +423,9 @@ export default function CodeEditor({
// Connection/disconnection effect
useEffect(() => {
- socketRef.current?.connect()
+ socket?.connect()
return () => {
- socketRef.current?.disconnect()
+ socket?.disconnect()
}
}, [])
@@ -469,22 +460,22 @@ export default function CodeEditor({
})
}
- socketRef.current?.on("connect", onConnect)
- socketRef.current?.on("disconnect", onDisconnect)
- socketRef.current?.on("loaded", onLoadedEvent)
- socketRef.current?.on("error", onError)
- socketRef.current?.on("terminalResponse", onTerminalResponse)
- socketRef.current?.on("disableAccess", onDisableAccess)
- socketRef.current?.on("previewURL", loadPreviewURL)
+ socket?.on("connect", onConnect)
+ socket?.on("disconnect", onDisconnect)
+ socket?.on("loaded", onLoadedEvent)
+ socket?.on("error", onError)
+ socket?.on("terminalResponse", onTerminalResponse)
+ socket?.on("disableAccess", onDisableAccess)
+ socket?.on("previewURL", loadPreviewURL)
return () => {
- socketRef.current?.off("connect", onConnect)
- socketRef.current?.off("disconnect", onDisconnect)
- socketRef.current?.off("loaded", onLoadedEvent)
- socketRef.current?.off("error", onError)
- socketRef.current?.off("terminalResponse", onTerminalResponse)
- socketRef.current?.off("disableAccess", onDisableAccess)
- socketRef.current?.off("previewURL", loadPreviewURL)
+ socket?.off("connect", onConnect)
+ socket?.off("disconnect", onDisconnect)
+ socket?.off("loaded", onLoadedEvent)
+ socket?.off("error", onError)
+ socket?.off("terminalResponse", onTerminalResponse)
+ socket?.off("disableAccess", onDisableAccess)
+ socket?.off("previewURL", loadPreviewURL)
}
// }, []);
}, [terminals])
@@ -499,7 +490,7 @@ export default function CodeEditor({
// Debounced function to get file content
const debouncedGetFile = useCallback(
debounce((tabId, callback) => {
- socketRef.current?.emit('getFile', tabId, callback);
+ socket?.emit('getFile', tabId, callback);
}, 300), // 300ms debounce delay, adjust as needed
[]
);
@@ -603,7 +594,7 @@ export default function CodeEditor({
return false
}
- socketRef.current?.emit("renameFile", id, newName)
+ socket?.emit("renameFile", id, newName)
setTabs((prev) =>
prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab))
)
@@ -612,7 +603,7 @@ export default function CodeEditor({
}
const handleDeleteFile = (file: TFile) => {
- socketRef.current?.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => {
+ socket?.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => {
setFiles(response)
})
closeTab(file.id)
@@ -622,11 +613,11 @@ export default function CodeEditor({
setDeletingFolderId(folder.id)
console.log("deleting folder", folder.id)
- socketRef.current?.emit("getFolder", folder.id, (response: string[]) =>
+ socket?.emit("getFolder", folder.id, (response: string[]) =>
closeTabs(response)
)
- socketRef.current?.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => {
+ socket?.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => {
setFiles(response)
setDeletingFolderId("")
})
@@ -654,7 +645,7 @@ export default function CodeEditor({
{generate.show && ai ? (
t.id === activeFileId)?.name ?? "",
@@ -714,7 +705,7 @@ export default function CodeEditor({
handleRename={handleRename}
handleDeleteFile={handleDeleteFile}
handleDeleteFolder={handleDeleteFolder}
- socket={socketRef.current}
+ socket={socket!}
setFiles={setFiles}
addNew={(name, type) => addNew(name, type, setFiles, sandboxData)}
deletingFolderId={deletingFolderId}
@@ -832,7 +823,7 @@ export default function CodeEditor({
open={() => {
usePreview().previewPanelRef.current?.expand()
setIsPreviewCollapsed(false)
- } } collapsed={isPreviewCollapsed} src={previewURL} ref={previewWindowRef} />
+ }} collapsed={isPreviewCollapsed} src={previewURL} ref={previewWindowRef} />
void;
+}
+
+const SocketContext = createContext(undefined);
+
+export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [socket, setSocket] = useState(null);
+ const [userId, setUserId] = useState(null);
+ const [sandboxId, setSandboxId] = useState(null);
+
+ useEffect(() => {
+ if (userId && sandboxId) {
+ console.log("Initializing socket connection...");
+ const newSocket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userId}&sandboxId=${sandboxId}`);
+ console.log("Socket instance:", newSocket);
+ setSocket(newSocket);
+
+ newSocket.on('connect', () => {
+ console.log("Socket connected:", newSocket.id);
+ });
+
+ newSocket.on('disconnect', () => {
+ console.log("Socket disconnected");
+ });
+
+ return () => {
+ console.log("Disconnecting socket...");
+ newSocket.disconnect();
+ };
+ }
+ }, [userId, sandboxId]);
+
+ const setUserAndSandboxId = (newUserId: string, newSandboxId: string) => {
+ setUserId(newUserId);
+ setSandboxId(newSandboxId);
+ };
+
+ const value = {
+ socket,
+ setUserAndSandboxId,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useSocket = (): SocketContextType => {
+ const context = useContext(SocketContext);
+ if (!context) {
+ throw new Error('useSocket must be used within a SocketProvider');
+ }
+ return context;
+};
diff --git a/frontend/context/TerminalContext.tsx b/frontend/context/TerminalContext.tsx
index a329bec..cb1cc47 100644
--- a/frontend/context/TerminalContext.tsx
+++ b/frontend/context/TerminalContext.tsx
@@ -1,12 +1,11 @@
"use client";
-import React, { createContext, useContext, useState, useEffect } from 'react';
-import { io, Socket } from 'socket.io-client';
+import React, { createContext, useContext, useState } from 'react';
import { Terminal } from '@xterm/xterm';
import { createTerminal as createTerminalHelper, closeTerminal as closeTerminalHelper } from '@/lib/terminal';
+import { useSocket } from '@/context/SocketContext';
interface TerminalContextType {
- socket: Socket | null;
terminals: { id: string; terminal: Terminal | null }[];
setTerminals: React.Dispatch>;
activeTerminalId: string;
@@ -15,41 +14,16 @@ interface TerminalContextType {
setCreatingTerminal: React.Dispatch>;
createNewTerminal: (command?: string) => Promise;
closeTerminal: (id: string) => void;
- setUserAndSandboxId: (userId: string, sandboxId: string) => void;
deploy: (callback: () => void) => void;
}
const TerminalContext = createContext(undefined);
export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
- const [socket, setSocket] = useState(null);
+ const { socket } = useSocket();
const [terminals, setTerminals] = useState<{ id: string; terminal: Terminal | null }[]>([]);
const [activeTerminalId, setActiveTerminalId] = useState('');
const [creatingTerminal, setCreatingTerminal] = useState(false);
- const [userId, setUserId] = useState(null);
- const [sandboxId, setSandboxId] = useState(null);
-
- useEffect(() => {
- if (userId && sandboxId) {
- console.log("Initializing socket connection...");
- const newSocket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}?userId=${userId}&sandboxId=${sandboxId}`);
- console.log("Socket instance:", newSocket);
- setSocket(newSocket);
-
- newSocket.on('connect', () => {
- console.log("Socket connected:", newSocket.id);
- });
-
- newSocket.on('disconnect', () => {
- console.log("Socket disconnected");
- });
-
- return () => {
- console.log("Disconnecting socket...");
- newSocket.disconnect();
- };
- }
- }, [userId, sandboxId]);
const createNewTerminal = async (command?: string): Promise => {
if (!socket) return;
@@ -85,11 +59,6 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
}
};
- const setUserAndSandboxId = (newUserId: string, newSandboxId: string) => {
- setUserId(newUserId);
- setSandboxId(newSandboxId);
- };
-
const deploy = (callback: () => void) => {
if (!socket) console.error("Couldn't deploy: No socket");
console.log("Deploying...")
@@ -99,7 +68,6 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
}
const value = {
- socket,
terminals,
setTerminals,
activeTerminalId,
@@ -108,7 +76,6 @@ export const TerminalProvider: React.FC<{ children: React.ReactNode }> = ({ chil
setCreatingTerminal,
createNewTerminal,
closeTerminal,
- setUserAndSandboxId,
deploy
};