728x90

https://console.firebase.google.com/u/0/

 

로그인 - Google 계정

이메일 또는 휴대전화

accounts.google.com

 

Firebase는 데이터가 변경될 때마다 클라이언트에 즉시 업데이트를 제공하여 실시간 애플리케이션에 유용하다.

다양한 인증 방법과 빠르고 안전한 웹 호스팅을 제공하며, SSL 인증서가 자동으로 제공되어 보안에도 좋다.

 

위 사이트에서 로그인을 한 뒤, 프로젝트를 생성하고 DB를 관리할 수 있는 Firestore에 접근해 api키를 볼 수 있다. 그리고 Firestore 규칙란에

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}

이런식으로 내가 연결한 프로젝트의 전체 접근을 허용할 수 있고, 특정 유저만 접근 가능하게 권한설정도 가능하다.

 

firebase를 로컬환경으로 개발할땐, .env.local 파일에 firebase 관련 api키를 넣어놓는게 좋다.

 

아래는 리덕스툴킷을 사용한 firebase에서의 db삭제 예제이다.

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { collection, getDocs, addDoc, updateDoc, deleteDoc, doc, query, orderBy, Timestamp } from 'firebase/firestore';
import { AppThunk } from './index';
import { db } from '../firebase/firebaseConfig';

...

deletePost(state, action: PayloadAction<string>) {
  state.posts = state.posts.filter(post => post.id !== action.payload);
}

...

export const removePost = (id: string): AppThunk => async (dispatch) => {
  try {
    const docRef = doc(db, 'posts', id);
    await deleteDoc(docRef);
    dispatch(deletePost(id));
  } catch (error) {
    console.error('Error deleting document: ', error);
  }
};

 

리덕스 툴킷을 사용하면 Date 객체를 상태나 액션에 저장하는 것이 매우 안좋다. 그래서 Date 객체를 직렬화 가능한 형태로 변환하여 저장하고 사용할 때는 다시 Date 객체로 변환해야한다.

interface BoardState {
  posts: { id: string; title: string; content: string, createdAt: string }[];
  loading: boolean;
  error: string | null;
}

...

addPost(state, action: PayloadAction<{ id: string; title: string; content: string; createdAt: string }>) {
  state.posts.unshift(action.payload);
},


...


  try {
    const q = query(collection(db, 'posts'), orderBy('createdAt', 'desc'));
    const querySnapshot = await getDocs(q);
    const posts = querySnapshot.docs.map((doc) => {
      const data = doc.data();
      return {
        id: doc.id,
        title: data.title,
        content: data.content,
        createdAt: (data.createdAt as Timestamp).toDate().toISOString() // Timestamp를 Date로 변환
      };
    });
    dispatch(fetchPostsSuccess(posts));
  }

 

app/duplicate-app오류는 firebase 앱을 초기화할 때 발생하는 오류이다. 동일한 앱이 이미 초기화되어 있는지 확인하고, 초기화되지 않은 경우에만 초기화하도록 해야한다.

 

@/firebase/firebaseConfig.ts

import { initializeApp, getApps, FirebaseApp } from 'firebase/app';
import { getFirestore, Firestore } from 'firebase/firestore';

interface FirebaseConfig {
  apiKey: string;
  authDomain: string;
  projectId: string;
  storageBucket: string;
  messagingSenderId: string;
  appId: string;
  measurementId?: string;
}

const firebaseConfig: FirebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY!,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN!,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET!,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID!,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID!,
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

const apps: FirebaseApp[] = getApps();
let app: FirebaseApp;
if (apps.length === 0) {
  app = initializeApp(firebaseConfig);
} else {
  app = apps[0];
}

const db: Firestore = getFirestore(app);


export { db };

이미 초기화된 앱이 없다면 initializeApp을 호출하고, 있다면 그 앱을 재사용하게 해야 오류가 나지 않는다.

 

firebase 관리자는 db를 직접 추가, 수정, 삭제 등이 가능하다.

 

728x90
728x90

https://developer.riotgames.com/apis

 

Riot Developer Portal

 

developer.riotgames.com

 

위의 공식 게임회사의 제공된 API 사이트를 참고하여 특정 데이터를 가져올때, 프론트엔드에서 직접 Riot API를 호출하면 Riot에서 400대의 에러를 반환할 것이다. 보안상의 이유로 직접 프론트엔드에서 호출하는 것을 권장하지 않기 때문이다. API는 백엔드 서버를 통해 처리해야 한다.

 

하지만 백엔드 서버를 따로 작성하고 싶지 않을때, Next.js에서 지원하는 서버 사이드 렌더링을 통하여 프론트엔드와 백엔드 코드를 함께 작성할 수 있다.

import type { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';

const RIOT_API_KEY = process.env.NEXT_PUBLIC_RIOT_API_KEY;
const ACCOUNT_REGION = 'kr';
const MATCH_REGION = 'asia';

const getSummonerByPUUID = async (puuid: string) => {
  const url = `https://${ACCOUNT_REGION}.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/${puuid}`;
  return axios.get(url, {
    headers: {
      'X-Riot-Token': RIOT_API_KEY,
    },
  });
};

 

NextApiRequest와 NextApiResponse는 Next.js에서 API 라우트를 작성할 때 사용하는 타입이다. 이 타입들은 각각 HTTP 요청과 응답을 나타내며, Express.js와 같은 기존 Node.js 서버에서 사용하는 Request와 Response 객체와 유사한 역할을 한다. Next.js API 라우트에서 이 객체들을 사용하여 클라이언트의 요청을 처리하고 응답을 보낼 수 있다.

 

NextApiRequest는 클라이언트로부터 오는 HTTP 요청을 나타낸다. 이 객체에는 요청과 관련된 다양한 정보가 포함되어 있다.

  • req.query: URL 쿼리 문자열을 객체 형태로 포함한다. 예를 들어, URL이 /api/hello?name=John이라면 req.query는 { name: 'John' }이 된다.
  • req.body: 요청 본문을 포함한다. 주로 POST, PUT 요청에서 사용된다.
  • req.cookies: 요청에 포함된 쿠키를 객체 형태로 포함한다.
  • req.method: HTTP 요청 메서드를 나타낸다 (예: GET, POST, PUT, DELETE 등).
  • req.headers: 요청 헤더를 포함한다.

 

NextApiResponse는 서버에서 클라이언트로 보내는 HTTP 응답을 나타낸다. 이 객체를 사용하여 클라이언트에 데이터를 보내거나 상태 코드를 설정할 수 있다.

 

  • res.status(code: number): 응답 상태 코드를 설정한다. 예를 들어, res.status(200)은 성공 상태 코드를 설정한다.
  • res.json(data: any): JSON 형식의 데이터를 응답으로 보낸다.
  • res.send(data: any): 문자열이나 버퍼 데이터를 응답으로 보낸다.
  • res.setHeader(name: string, value: string | string[]): 응답 헤더를 설정한다.

 

적용 사례 url: (예시 입력 데이터: hide on bush # kr1)

https://www.theze.de/team-balance

 

https://www.theze.de/team-balance

 

www.theze.de

 

728x90
728x90

크롬 익스텐션은 manifest를 루트로 로드해야한다.

{
    "manifest_version": 3,
    "name": "Learn on Air",
    "version": "1.0",
    "description": "Learn on Air for Chrome && inflearn",
    "permissions": [
      "activeTab",
      "tabs",
      "storage",
      "https://*/*"
    ],
    "action": {
      "default_popup": "./src/popup/popup.html",
      "default_icon": {
        "16": "public/16.png",
        "48": "public/48.png",
        "128": "public/128.png"
      }
    },
    "content_scripts": [
      {
        "matches": ["http://*/*", "https://*/*"],
        "js": ["./content/contentScript.js"]
      }
    ],
    "icons": {
      "16": "public/16.png",
      "48": "public/48.png",
      "128": "public/128.png"
    }
  }

 

contentScript는 ES6모델을 지원하지 않기 때문에, import와 export가 지원이 안되는데, 이때메 컴포넌트별로 나누어 작업하려면 웹팩을 이용해서 import된 파일을 빌드시켜서 해당 dist를 로드시켜야한다.

    const infoWindow = document.createElement('div');
    infoWindow.id = 'infoWindow';
    infoWindow.style.width = '300px';
    infoWindow.style.height = '500px';
    infoWindow.style.position = 'absolute';
    infoWindow.style.top = '140px';
    infoWindow.style.left = '50px';
    infoWindow.style.backgroundColor = 'lightgray';
    infoWindow.style.border = '1px solid gray';
    infoWindow.style.zIndex = '1001';
    infoWindow.style.padding = '10px';
    infoWindow.innerHTML = `
    <div style="position: absolute; top: 1px; right: 3px; cursor: pointer;">
        &#10005;
    </div>
    <p style="margin-top: 13px; margin-bottom: 17px;">연결상태: 양호</p>
    <p style="margin-top: 13px; margin-bottom: 17px;">졸음상태: 0회, (00:00:00)</p>
    <p style="margin-top: 13px; margin-bottom: 17px;">자리이탈: 0회, (00:00:00)</p>
    <p style="margin-top: 13px; margin-bottom: 17px;">녹화여부: 허용</p>
    <p style="margin-top: 13px; margin-bottom: 17px;">캠화면:</p>
    `;

 

contentScript로 내부기능, 비기능을 전부 작성해야하기 때문에, 자바스크립트에서 HTML을 작성해야했다. React와 Typescript로 작성된 보일러플레이트를 사용하면 웹팩 등 설정들이 다 되어있었는데, 그 코드는 생각보다 나와의 개발스타일도 다르고 빌드과정을 이해하기에는 시간이 없을 것 같아서 바닐라로 처음부터 짜기로 하였다.

if (!isActive) {
        const scriptButton = Array.from(document.querySelectorAll('.css-zl1inp')).find(li => {
            const button = li.querySelector('button');
            return (button && button.getAttribute('title') === '스크립트') || (button && button.getAttribute('title') === '성장 로그');
        });

        if (scriptButton) {
            const li = document.createElement('li');
            li.className = 'css-zl1inp';
            li.id = 'learningAssistantIcon';
            li.innerHTML = `
                <button class="mantine-UnstyledButton-root mantine-Button-root mantine-syxma7" type="button">
                    <div class="mantine-1yjkc96 mantine-Button-inner">
                        <span class="mantine-1vgkxjh mantine-Button-label">
                            <img src="https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw6E6e%2FbtsGRJCy10B%2FDorSShP9f3AJjKRGQnCAZk%2Fimg.png">
                            <p class="mantine-Text-root mantine-qjo01i">학습 보조</p>
                        </span>
                    </div>
                </button>
            `;

 

인프런의 무료강의와 유료강의의 사이드바의 아이콘 수가 다르기 때문에 OR로 지정해주고 해당 아이콘 밑에 학습 보조라는 새로운 아이콘을 삽입시켜주었다.

이제 사용자의 졸음, 자리이탈, 영상 중간에 퀴즈를 정중앙에 나오게 하기 등의 기능을 익스텐션에서 개발하고 팀원들이 따로 개발한 웹페이지에선 사용자들이 퀴즈를 만들 수 있는 기능을 제공할 예정이다. 영상처리모델은 Mediapipe가 정확도나 속도면에서 매우 뛰어났기 때문에 Mediapipe의 landmarker을 채택할 것 같다.

 

728x90
728x90

React와 Express는 Node.js 환경에서 실행되므로, Node.js가 설치되어 있지 않다면 먼저 설치한다. Node.js 공식 웹사이트(https://nodejs.org)에서 안내에 따라 설치할 수 있다.

 

Node.js — Run JavaScript Everywhere

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

 

터미널을 열고, 원하는 위치에 프로젝트를 위한 새 디렉토리를 생성한다.

mkdir my-web-app
cd my-web-app

 

Create React App을 사용해 React 프론트엔드 애플리케이션을 생성한다.

npx create-react-app client

 

이 명령은 client라는 이름의 디렉토리에 React 애플리케이션을 생성한다.

 

React 애플리케이션 외부에서 Express 애플리케이션을 설정한다.

 

루트 디렉토리(my-web-app)로 돌아가고 Express 애플리케이션을 위한 server 디렉토리를 생성하고 초기화한다.

mkdir server
cd server
npm init -y

 

Express를 설치한다.

npm install express

 

server 디렉토리 내에 index.js 파일을 생성하고, 기본 서버 설정을 추가한다.

const express = require('express');
const app = express();
const PORT = process.env.PORT || 5000;

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

 

개발 중에 React 애플리케이션과 Express 서버를 동시에 실행하기 위해, 루트 디렉토리(my-web-app)에 concurrentlynodemon을 사용하는 설정을 추가한다.

 

my-web-app로 돌아가 package.json 파일을 생성한다.

npm init -y

 

concurrentlynodemon을 설치한다.

npm install concurrently nodemon --save-dev

 

my-web-app/package.json 파일에서 scripts 섹션을 다음과 같이 수정한다.

"scripts": {
  "client": "cd client && npm start",
  "server": "cd server && nodemon index.js",
  "dev": "concurrently \"npm run server\" \"npm run client\""
}

 

이 설정을 통해, npm run dev 명령으로 React 애플리케이션과 Express 서버를 동시에 실행할 수 있다.

 

이러면 DeprecationWarning 이 뜨는데,

 

DeprecationWarning은 React 스크립트나 사용 중인 라이브러리에서 더 이상 사용하지 않는 기능을 사용했을 때 발생한다. 이 경고들은 주로 개발 환경 설정에 관련되어 있으며, 애플리케이션의 실행에는 직접적인 영향을 주지 않는다. 만약 webpack 관련 버전 문제로 DeprecationWarning이 났고 이를 해결하기 위해서는 webpack 설정을 확인하고 업데이트하는 것이 좋다. React와 관련된 설정은 node_modules 안의 관련 라이브러리 설정에서 찾을 수 있으며, 일반적으로 사용자가 직접 수정할 필요는 없다.

 

webpack 설정 확인하고 업데이트 하는 법:

 

프로젝트 폴더에서 터미널을 열고, 현재 설치된 Webpack 버전을 확인한다.

npm list webpack

 

npm 레지스트리에서 Webpack의 최신 버전을 확인한다.

npm view webpack version

 

원하는 버전(대개 최신 버전)으로 Webpack을 업데이트한다.

npm install webpack@latest --save-dev

 

@latest 대신 특정 버전을 지정하고 싶다면, @ 뒤에 해당 버전 번호를 명시한다 (예: webpack@4.44.1). --save-dev는 이 패키지가 개발 의존성임을 나타낸다.

 

Webpack과 함께 사용되는 로더(loader) 및 플러그인(plugin)도 최신 버전으로 업데이트하는 것이 좋다. 이는 호환성 문제를 방지하기 위함이다.

npm install webpack-cli@latest webpack-dev-server@latest --save-dev

 

여기서 webpack-cliwebpack-dev-server는 Webpack을 사용하는 데 흔히 필요한 패키지이며, 프로젝트에 따라 추가적인 로더나 플러그인이 필요할 수 있다.

 

업데이트 후 프로젝트가 여전히 정상적으로 작동하는지 확인해야한다. 때때로 메이저 버전 업데이트는 기존 설정이나 코드와 호환되지 않을 수 있으므로, 특히 중요한 변경 사항을 적용한 후에는 프로젝트를 꼼꼼히 테스트해야 한다.

 

업데이트 후 의존성 문제나 버전 충돌이 발생할 수 있다. 이런 경우, 해당 패키지의 문서를 참고하여 새 버전에 맞게 설정을 조정하거나 필요한 변경을 적용한다.

 

최신 버전으로 업데이트 한 후에도 여전히 동일한 경고 메시지가 나타나면, react - scripts 패키지 내부에서 사용되는 webpack 설정 때문에 발생할 수 있다. react - scripts 는 Create React App을 통해 생성된 프로젝트에서 Webpack과 같은 도구들을 추상화해 제공한다. 때문에 react - scripts 내부의 webpack 설정은 직접적으로 수정할 수 없다.

 

Create React App 팀이 react-scripts의 새 버전을 출시하여 이러한 경고를 해결할 수 있다. 따라서 react-scripts의 최신 버전을 확인하고 업데이트하는 것이 좋다.

npm install react-scripts@latest --save

 

ESLint 경고는 개발자가 직접 해결할 수 있다. App.tsx 파일에서 사용하지 않는 logo 변수를 제거하면 된다.

 

npm audit보안 취약점에 대한 경고다. npm audit 또는 npm audit fix를 실행하여 취약한 의존성을 해결할 수 있다. 일부 의존성은 메이저 업데이트를 필요로 할 수 있으며, npm audit fix --force를 사용하여 강제로 수정할 수 있다. 하지만, 이는 기존 코드에 영향을 줄 수 있으므로 주의해서 사용해야 한다.

 

npm audit를 정기적으로 실행하여 보안 취약점을 확인하고 해결하는 것이 좋다.

728x90
728x90

이 웹 게임은 플래피 버드처럼 space bar를 누를 때마다 위로 오르고 가만히 있으면 새가 점점 아래로 내려가는 게임이다.

좌측 상단에 있는 퀴즈를 보고 정답 표지판을 들고 있는 원숭이에게 새를 닿게하면 점수가 오르고 최대 점수인 8점을 득점하는 것이 게임의 목표다.

 

 

JWT는 JSON Web Tokens의 약자로, HEADER:ALGORITHM & TOKEN TYPE, PAYLOAD:DATA, VERIFY SIGNATURE로 구분된다. 나는 이 게임을 만들면서 HS256이라는 대칭키 해싱 알고리즘을 사용하여 보안을 강화시켰다.

 

 

세션과 토큰은 사용자 인증 방식에서 자주 사용되는 두 가지 개념이다. 둘 다 사용자의 로그인 상태를 관리하는 데 사용되지만, 관리 방식에 차이가 있다. 세션과 토큰의 차이점은 무엇이 있을까?

 

세션은 서버 기반 인증 시스템에서 사용된다. 사용자가 로그인하면 서버는 사용자에 대한 세션을 생성하고 세션 ID를 발급한다. 이 세션 ID는 사용자의 브라우저에 쿠키로 저장되며, 사용자가 서버에 요청할 때마다 이 쿠키를 통해 서버는 사용자를 식별한다. 세션 정보는 서버의 메모리나 데이터베이스에 저장된다.

 

토큰은 사용자가 로그인하면 서버는 특정 정보를 포함한 토큰을 생성하고 이를 사용자에게 발급한다. 사용자는 이 토큰을 저장해두었다가, 서버에 요청할 때마다 헤더에 토큰을 포함시켜 서버에 전송한다. 서버는 토큰의 유효성을 검증하여, 사용자를 식별한다. 토큰은 클라이언트 측에서 관리되며, 서버는 상태를 유지할 필요가 없어서 무상태 통신이 가능하다.

 

 

jinja2는 flask의 render_template 라는 기능을 사용하여 데이터를 넘겨 html로 구현하는 것이다. 웹 페이지를 동적으로 생성하는 데 유용하다.

 

function showRanking(ranking_list) {
    $("#modalBackground").addClass("opacity-100", "z-1000", "transition-all", "duration-500");
    $("#modalBackground").removeClass("-z-10");
    $("#ranking-list").empty();

    for (var i = 0; i < ranking_list.length; i++) {
        var rank = i + 1;
        var id = ranking_list[i]['id'];
        var score = ranking_list[i]['score'];
        $("#ranking-list").append("<li class='mx-20 px-10 pl-40 text-customListFontSize1 text-nowrap font-jgF1'>" + rank + "위 " + id + "      " + score + "점" + "</li>");
    }
}

function closeRanking() {
    $("#modalBackground").removeClass("opacity-100", "z-1000", "transition-all", "duration-500");
    $("#modalBackground").addClass("-z-10");
}

window.onload = function () {
    $("#ranking").click(function () {
        showRanking(ranking_list);
    });
    $("#close").click(closeRanking);
}

 

Jinja2의 서버 사이드 렌더링 방식

1. 사용자가 웹 페이지를 요청하면 서버는 해당 요청을 받아 처리한다.

2. 필요한 데이터는 데이터베이스나 다른 서비스로부터 로드된다.

3. 로드된 데이터를 Jinja2 템플릿에 삽입하여 HTML을 동적을 생성한다.

4. 생성된 HTML 페이지는 사용자의 브라우저로 전송되어 표시된다

 

 <script type=text/javascript>
    var ranking_list = JSON.parse({{ ranker | tojson | safe}});
  </script>

 

jinja2는 javascript 코드를 직접 실행하거나 조작하는 기능은 지원이 안 되기 때문에 위 코드처럼 구현하여야한다.

 

해당 게임 github URL

 

GitHub - JeongJongMun/JungleGap: 크래프톤 4기 미니프로젝트 - 정글차이

크래프톤 4기 미니프로젝트 - 정글차이. Contribute to JeongJongMun/JungleGap development by creating an account on GitHub.

github.com

728x90

+ Recent posts