ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [3~4일차] firebase Authentication를 이용한 OAuth 방식 로그인/로그아웃 구현 진행
    프로젝트 개발/뮤직플레이어 웹앱 2021. 2. 9. 22:59
    반응형

    지난시간에는 대략 적인 재생화면 뼈대만 만들어봤었다. 이번에는 firebase에서 제공하는 인증(Authentication) 기능을 사용해 로그인/로그아웃 구현을 진행했다. firebase에는 OAuth 를 제공하는 업체의 로그인 기능을 손쉽게 사용할 수 있도록 구현해 두었다.

    (물론 네이버, 다음 같은 서비스는 제공되지 않는다)

     

     

    인증을 구현한 이유는 확인되지 않은 사람이 URL을 알아내 접근하여 음악을 받을 수 있는 문제를 사전에 차단하기 위함이다. 로그인/로그아웃 구현에 참조한 소스는 아래와 같다.

     

    https://fireship.io/lessons/react-firebase-chat-app-tutorial/

     

    React Firebase Chat App

    Learn the basics of React & Firebase by building a simple group chat app from scratch.

    fireship.io

    해당 기능을 사용하려면 아래 모듈을 추가적으로 설치해야 한다.

    npm i react-firebase-hooks

     

    전체소스를 아래와 같이 구성했다. 컴포넌트를 App.ts에 몰아넣어 보기 불편하다. 대략적인 화면과 기능 구현이 완료되면 분할시킬 계획이다.

    import React, { useEffect, useRef, useState } from 'react';
    import firebase from "firebase/app";
    import "firebase/storage";
    import "firebase/auth";
    
    import { useAuthState } from "react-firebase-hooks/auth";
    
    import localforage from "localforage";
    import logo from './logo.svg';
    import './App.css';
    
    import { createStyles, makeStyles, Theme, useTheme } from '@material-ui/core/styles';
    import { AppBar, Fab, Card, CardContent, CardMedia, colors, IconButton, Toolbar, Typography, CardHeader, Button } from '@material-ui/core';
    
    // https://material-ui.com/components/material-icons/#material-icons
    
    // 이 방식은 빌드 및 테스트 초기 로딩이 느린 단점이 있음.
    // import { PlaylistPlay, PlayArrow, Pause, SkipNext, SkipPrevious } from "@material-ui/icons";
    
    import PlaylistPlay from '@material-ui/icons/PlaylistPlay';
    import PlayArrow from "@material-ui/icons/PlayArrowRounded";
    import Pause from "@material-ui/icons/Pause";
    import SkipNext from "@material-ui/icons/SkipNext";
    import SkipPrevious from "@material-ui/icons/SkipPrevious";
    import Repeat from "@material-ui/icons/Repeat";
    import Shuffle from "@material-ui/icons/Shuffle";
    
    import { firebaseConfig } from "./firebaseConfig";
    
    firebase.initializeApp(firebaseConfig);
    
    const auth = firebase.auth();
    const storage = firebase.storage();
    
    localforage.config({
      storeName: "media"
    });
    
    
    const useStyles = makeStyles((theme: Theme) =>
      createStyles({
        root: {
          display: 'flex',
          bottom: 0,
        },
        appBar: {
          top: 'auto',
          bottom: 0,
          paddingTop: theme.spacing(2)
        },
        controls: {
          alignItems: 'center',
          flexGrow: 1 // 해당 영역과 함께 나란히 놓인 다른 태그들을 양끝으로 밀어낸다(?)
        },
        card : {
          margin: theme.spacing(1),
          minHeight: 400
        },
        Icon: {
          height: 32,
          width: 32
        }
      }),
    );
    
    function App() {
      const [user] = useAuthState(auth); // firebase auth 기능 사용을 위한 Hook
    
      return (
        <div className="App">
          { user ? <MusicPlayer /> : <SignIn /> }
        </div>
        );
    }
    
    export default App;
    
    /**
     * 로그인 버튼(화면)
     */
    function SignIn() {
      const signInWithGoogle = () => {
        const provider = new firebase.auth.GoogleAuthProvider();
        auth.signInWithPopup(provider);
      }
      return (<Button color={'primary'} onClick={signInWithGoogle}>Sign in with Google</Button>)
    }
    /**
     * 로그아웃 버튼
     */
    function SignOut() {
      return auth.currentUser && (
        <Button color={'inherit'} onClick={() => auth.signOut()}>Sign out</Button>
      )
    }
    
    /**
     * 음악 재생 화면 
     */
    function MusicPlayer() {
      const classes = useStyles();
      const theme = useTheme();
    
      const [isPlay, setIsPlay] = useState(false);
      const [isRepeat, setIsRepeat] = useState(false);
      const [isSuffle, setIsSuffle] = useState(false);
    
      const [currentPlayIdx, setCurrentPlayIdx] = useState(0);
    
      useEffect(() => {
        console.log('test'); // 첫 시작 또는 이벤트 발생시마다 호출됨
      });
    
      /**
       * 음악을 재생/일시정지 시킵니다.
       */
      function togglePlay() {
        setIsPlay(!isPlay);
        if(!isPlay) {
          
        }
        else {
          // play
          const storage = firebase.storage().ref();
          storage.listAll().then(result => {
            console.log(result.items); // 인증된 사용자에 대한 접근확인을 위해 작성한 임시코드
          });
        }
      }
    
      /**
       * 반복재생을 끄거나 켭니다.
       */
      function toggleRepeat() {
        setIsRepeat(!isRepeat);
        if (!isRepeat) {
          
        }
        else {
    
        }
      }
    
      /**
       * 재생리스트를 랜덤하게 정렬하거나, 인덱스 순서에 맞춰 정렬합니다.
       */
      function toggleShuffle() {
        setIsSuffle(!isSuffle);
        if (!isSuffle) {
          
        }
        else {
    
        }
      }
    
      return (
        <>
        <Card className={classes.card}>
            <CardHeader>
              <Typography>Test</Typography>
            </CardHeader>
            <CardContent>
              <Typography>Test</Typography>
            </CardContent>
          </Card>
          <AppBar position={'fixed'} className={classes.appBar}>
            <Toolbar>
            <IconButton color={ isRepeat? 'inherit' : 'default' } aria-label="loop" onClick={toggleRepeat}>
              <Repeat />
            </IconButton>
            <div className={classes.controls}>
            <IconButton aria-label="previous">
              {theme.direction === 'rtl' ? 
              <SkipNext className={classes.Icon} /> : 
              <SkipPrevious className={classes.Icon} />
              }
            </IconButton>
            <Fab color={'secondary'} aria-label="play/pause" onClick={togglePlay}>
              {
                isPlay? 
                <Pause className={classes.Icon} /> : 
                <PlayArrow className={classes.Icon} />
              }
            </Fab>
            <IconButton aria-label="next">
              {theme.direction === 'rtl' ? 
              <SkipPrevious className={classes.Icon} /> : 
              <SkipNext className={classes.Icon} />}
            </IconButton>
          </div>
          <IconButton color={isSuffle? 'inherit' : 'default'} aria-label="shuffle" onClick={toggleShuffle}>
            <Shuffle />
          </IconButton>
          </Toolbar>
          <Toolbar>
            <IconButton edge='start' color="inherit" aria-label="menu">
              <PlaylistPlay />
            </IconButton>
            <SignOut />
          </Toolbar>
          </AppBar>
        </>
      );
    }

    로그인을 하지 않은 상태에선 아래와 같은 화면이 표시된다.

     

    조악하지만 로그인 화면이다.

    의도하지 않았는데 버튼이 흰색바탕으로 나와 버튼 처럼 보이지 않는다. 해당 버튼을 누르면 Google 로그인화면이 표시되거나, 이미 로그인정보가 있는 브라우저일 경우, 해당 앱에 대한 접근을 허용할지 여부를 묻는 구글계정화면이 표시된다.

     

    로그아웃 버튼이 추가된 플레이어화면(향후 아이콘 버튼으로 변경예정)

     

    [내용 추가]

    togglePlay 버튼을 눌러 리스트를 가져오는건 확인했지만 뭔가 내가 원한것과 반대로 동작하는게 느껴졌다.

    Pause 버튼이 표시되는, 즉 isPlay = true인 재생상태에서 음악파일 리스트를 표시하고 싶었는데 반대로 동작한다. 정확한 원인은 모르겠지만 togglePlay함수의 if 절 위치를 아래 소스를 useEffect로 옮기면 해결은 된다.

      useEffect(() => {  // 첫 시작 또는 이벤트 발생시마다 호출됨
        console.log(`isPlay : ${isPlay}`);
        console.log(`isRepeat : ${isRepeat}`);
        console.log(`isSuffle : ${isSuffle}`);
        if(isPlay) {
          const storage = firebase.storage().ref();
          storage.listAll().then(result => {
            console.log(result.items);
          });
        }
        else {
          
        }
      });
    

     

    반응형

    댓글

Designed by Tistory.