Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
VITE_SOCKET_SERVER_URL=http://localhost:4000
VITE_SERVER_URL=http://localhost:4000
VITE_SOCKET_SERVER_URL=http://localhost:3050
VITE_SOCKET_SERVER_PORT=3050
VITE_AUTH_KEY=_PLUG_AUTH_
15 changes: 15 additions & 0 deletions codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
overwrite: true,
schema: 'http://localhost:4000/graphql',
documents: ['src/**/*.tsx'],
ignoreNoDocuments: true,
generates: {
'./src/__api__/types.ts': {
plugins: ['typescript', 'typescript-operations'],
},
},
}

export default config
18 changes: 14 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,34 @@
"name": "react-typescript-vite",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"start" : "vite serve ./dist",
"start": "vite serve ./dist",
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"prepare": "husky install"
"prepare": "husky install",
"codegen": "graphql-codegen --config codegen.ts"
},
"dependencies": {
"@apollo/client": "^3.7.7",
"@types/js-cookie": "^3.0.2",
"graphql": "^16.6.0",
"js-cookie": "^3.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.0",
"react-router-dom": "^6.8.0",
"socket.io-client": "^4.5.4"
"socket.io-client": "^4.5.4",
"zustand": "^4.3.2"
},
"devDependencies": {
"@commitlint/cli": "^17.4.2",
"@commitlint/config-conventional": "^17.4.2",
"@graphql-codegen/cli": "3.0.0",
"@graphql-codegen/client-preset": "2.0.0",
"@graphql-codegen/introspection": "3.0.0",
"@graphql-codegen/typescript": "^3.0.0",
"@graphql-codegen/typescript-operations": "^3.0.0",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^3.0.0",
Expand Down
74 changes: 74 additions & 0 deletions src/__api__/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
DateTime: any;
};

export type LoginInput = {
nickname: Scalars['String'];
};

export type LoginOutput = {
__typename?: 'LoginOutput';
error?: Maybe<Scalars['String']>;
ok: Scalars['Boolean'];
token?: Maybe<Scalars['String']>;
user?: Maybe<User>;
};

export type Mutation = {
__typename?: 'Mutation';
login: LoginOutput;
};


export type MutationLoginArgs = {
input: LoginInput;
};

export type Query = {
__typename?: 'Query';
getMe: UserProfileOutput;
};

export type User = {
__typename?: 'User';
createdAt: Scalars['DateTime'];
id: Scalars['Float'];
nickname: Scalars['String'];
role?: Maybe<UserRole>;
updatedAt: Scalars['DateTime'];
};

export type UserProfileOutput = {
__typename?: 'UserProfileOutput';
error?: Maybe<Scalars['String']>;
ok: Scalars['Boolean'];
user?: Maybe<User>;
};

export enum UserRole {
Admin = 'Admin',
Client = 'Client'
}

export type GetMeQueryVariables = Exact<{ [key: string]: never; }>;


export type GetMeQuery = { __typename?: 'Query', getMe: { __typename?: 'UserProfileOutput', ok: boolean, error?: string | null, user?: { __typename?: 'User', id: number, nickname: string, createdAt: any, updatedAt: any, role?: UserRole | null } | null } };

export type LoginMutationVariables = Exact<{
LoginInput: LoginInput;
}>;


export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'LoginOutput', ok: boolean, token?: string | null, error?: string | null, user?: { __typename?: 'User', id: number, nickname: string, updatedAt: any, createdAt: any, role?: UserRole | null } | null } };
7 changes: 7 additions & 0 deletions src/apollo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ApolloClient } from '@apollo/client'
import { InMemoryCache } from '@apollo/client/cache'

export const client = new ApolloClient({
uri: `${import.meta.env.VITE_SERVER_URL}/graphql`,
cache: new InMemoryCache(),
})
24 changes: 14 additions & 10 deletions src/context/socketManager.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { Manager } from 'libs/Manager'
import { createContext, FC, ReactNode } from 'react'
import { io, Manager, Socket } from 'socket.io-client'
import { Socket } from 'socket.io-client'

// export const manager = new Manager(import.meta.env.VITE_SOCKET_SERVER_URL, {
// path: '/',
// transports: ['websocket'],
// })

const socket = io(import.meta.env.VITE_SOCKET_SERVER_URL, {
path: '/',
export const manager = new Manager(import.meta.env.VITE_SOCKET_SERVER_URL, {
transports: ['websocket'],
multiplex: true,
})

export const SocketContext = createContext<{ socket: Socket | null }>({ socket: null })
export const SocketContext = createContext<{
socket: Socket | null
manager: Manager
}>({
socket: null,
manager,
})
const SocketProvider: FC<{ children: ReactNode }> = ({ children }) => {
return <SocketContext.Provider value={{ socket }}>{children}</SocketContext.Provider>
return (
<SocketContext.Provider value={{ socket: null, manager }}>{children}</SocketContext.Provider>
)
}
export default SocketProvider

Expand Down
1 change: 1 addition & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ declare namespace NodeJS {
readonly VITE_SERVER_URL: string
readonly VITE_SOCKET_SERVER_URL: string
readonly VITE_SOCKET_SERVER_PORT: string
readonly VITE_AUTH_KEY: string
readonly [key: string]: string
}
}
44 changes: 43 additions & 1 deletion src/hooks/useSocket.ts
Original file line number Diff line number Diff line change
@@ -1 +1,43 @@
export {}
import { useEffect, useRef, useState } from 'react'
import { useContext } from 'react'
import { SocketContext } from 'context/socketManager'

interface useSocketProps {
nsp: string
}

const useSocket = ({ nsp }: useSocketProps) => {
const { manager } = useContext(SocketContext)
const [isError, setIsError] = useState<string | null>(null)

const socket = useRef(
manager.create_socket(nsp, {
auth: {
token: localStorage.getItem('_PLUG_AUTH_') || '',
},
}),
)
useEffect(() => {
if (socket.current) {
socket.current.listen('connect_error', ({ message }: Error) => {
if (!isError) {
setIsError(message)
}
})
socket.current.listen('connect', () => {
if (isError) {
setIsError(null)
}
})
}
}, [socket.current, isError])
return {
manager,
socket: socket.current,
isError,
isConnected: socket.current.connected,
isLoading: !socket.current,
}
}

export default useSocket
66 changes: 48 additions & 18 deletions src/layout/SocketLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,52 @@
import { SocketContext } from 'context/socketManager'
import { useContext, useLayoutEffect, useState } from 'react'
import { Outlet } from 'react-router-dom'
const SocketLayout = () => {
const { socket } = useContext(SocketContext)
const [loading, setLoading] = useState(true)
useLayoutEffect(() => {
const nickname = localStorage.getItem('plug_nickname')
if (socket && nickname) {
socket?.emit('set_nickname', nickname, (nickname: string) => {
if (nickname) {
localStorage.setItem('plug_nickname', nickname)
} else {
// todo
}
})
import { gql, useQuery } from '@apollo/client'
import { Outlet, useNavigate } from 'react-router-dom'
import { useClearUser } from 'store/action'
import store from 'store/index'
import { GetMeQuery } from '__api__/types'

const GET_ME = gql`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphQl ๋ฅผ ๋„์ž… ํ•˜์…จ๋„ค์š”.

query getMe {
getMe {
ok
user {
id
nickname
createdAt
updatedAt
role
}
error
}
setLoading(false)
}, [])
}
`

const SocketLayout = () => {
const key = import.meta.env.VITE_AUTH_KEY
const token = localStorage.getItem(key) || ''

const { setUser, clear } = store((state) => ({
setUser: state.setUser,
clear: state.clear,
}))
const { data, loading, client } = useQuery<GetMeQuery>(GET_ME, {
context: {
headers: {
[key]: token,
},
},
onCompleted({ getMe: { ok, error, user } }) {
if (ok && user) {
setUser({ user, token })
} else {
clear()
navigate('/login')
}
},
fetchPolicy: 'no-cache',
})

const navigate = useNavigate()

return <div>{loading ? 'loading...' : <Outlet />}</div>
}

Expand Down
34 changes: 34 additions & 0 deletions src/libs/Manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Manager as M, Socket } from 'socket.io-client'
import { SocketOptions } from 'socket.io-client/build/esm/socket'

export class Manager extends M {
private readonly sockets: { [key: string]: Socket } = {}
private readonly latestSocketNsp = null
create_socket(nsp: string, opts?: Partial<SocketOptions> | undefined): Socket {
//TODO: ์ตœ๊ทผ์‚ฌ์šฉํ•œ ์†Œ์ผ“ ์ปค๋„ฅ์…˜ ์ข…๋ฃŒ

if (this.sockets.hasOwnProperty(nsp)) {
return this.sockets[nsp]
}

// if (this.latestSocketNsp) {

// }
const socket = this.socket(nsp, opts)

socket.publicId = nsp
socket.listen = function bindEventListnerOnSocket(ev, lisnter) {
if (this.hasListeners(ev)) {
socket.off(ev)
}
socket.on(ev, lisnter)
return socket
}
this.sockets[nsp] = socket
return socket
}

isExistNsp(url: string) {
return !!this.sockets[url]
}
}
6 changes: 5 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ApolloProvider } from '@apollo/client/react'
import { client } from './apollo'
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import Router from './router/Routes'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Router />
<ApolloProvider client={client}>
<Router />
</ApolloProvider>
</React.StrictMode>,
)
11 changes: 8 additions & 3 deletions src/router/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import {
Route,
createBrowserRouter,
RouterProvider,
Outlet,
} from 'react-router-dom'
import Home from 'screen/Home'
import Login from 'screen/Login'
import Room from 'screen/Room'

const router = createBrowserRouter(
createRoutesFromElements(
<Route path='' element={<SocketLayout />}>
<Route index element={<Home />} />
<Route path='/:roomName' element={<Room />} />
<Route path='' element={<Outlet />}>
<Route path='' element={<SocketLayout />}>
<Route index element={<Home />} />
<Route path='/:roomName' element={<Room />} />
</Route>
<Route path='login' element={<Login />} />
</Route>,
),
)
Expand Down
Loading