Files
Obsidian/ZELLYY/zellyy note/02_기술_문서/SNS_통합_가이드.md
2025-03-26 18:16:46 +09:00

20 KiB

SNS 통합 가이드

이 문서는 Zellyy 앱에서 다양한 소셜 미디어 플랫폼과의 통합 방법을 안내합니다.

개요

Zellyy 앱의 핵심 기능 중 하나는 사용자가 작성한 카드를 다양한 소셜 미디어 플랫폼에 공유하는 기능입니다. 이 문서는 다음 플랫폼과의 통합 방법을 설명합니다:

  • Facebook
  • Instagram
  • Twitter (X)
  • LinkedIn
  • Pinterest

사전 요구사항

  • 각 소셜 미디어 플랫폼의 개발자 계정
  • 각 플랫폼에 등록된 앱
  • React Native 개발 환경
  • Supabase 설정 완료

1. 소셜 미디어 앱 등록

1.1 Facebook 앱 등록

  1. Facebook 개발자 포털에 접속
  2. "내 앱" > "앱 만들기" 클릭
  3. "소비자" 유형 선택
  4. 앱 이름 입력 (예: "Zellyy")
  5. 앱 생성 후 다음 제품 추가:
    • Facebook 로그인
    • Instagram API
    • 공유 API

설정 완료 후 다음 정보를 기록:

  • 앱 ID
  • 앱 시크릿

1.2 Twitter 앱 등록

  1. Twitter 개발자 포털에 접속
  2. "Projects & Apps" > "Overview" > "Create App" 클릭
  3. 앱 이름, 설명 입력
  4. 앱 권한 설정: "Read and Write"
  5. 리디렉션 URL 설정: zellyy://auth/twitter

설정 완료 후 다음 정보를 기록:

  • API 키
  • API 시크릿 키
  • 액세스 토큰
  • 액세스 토큰 시크릿

1.3 LinkedIn 앱 등록

  1. LinkedIn 개발자 포털에 접속
  2. "Create App" 클릭
  3. 앱 이름, 설명, 로고 등 입력
  4. 제품 추가: "Share on LinkedIn"
  5. OAuth 2.0 설정:
    • 리디렉션 URL: zellyy://auth/linkedin
    • 권한: r_liteprofile, w_member_social

설정 완료 후 다음 정보를 기록:

  • 클라이언트 ID
  • 클라이언트 시크릿

1.4 Pinterest 앱 등록

  1. Pinterest 개발자 포털에 접속
  2. "Apps" > "Create app" 클릭
  3. 앱 이름, 설명 입력
  4. 리디렉션 URL 설정: zellyy://auth/pinterest
  5. 권한 설정: read_public, write_public

설정 완료 후 다음 정보를 기록:

  • 앱 ID
  • 앱 시크릿

2. 환경 변수 설정

앱의 .env 파일에 소셜 미디어 API 키를 추가합니다:

# Facebook
FACEBOOK_APP_ID=your_facebook_app_id
FACEBOOK_APP_SECRET=your_facebook_app_secret

# Twitter
TWITTER_CONSUMER_KEY=your_twitter_api_key
TWITTER_CONSUMER_SECRET=your_twitter_api_secret

# LinkedIn
LINKEDIN_CLIENT_ID=your_linkedin_client_id
LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret

# Pinterest
PINTEREST_APP_ID=your_pinterest_app_id
PINTEREST_APP_SECRET=your_pinterest_app_secret

3. 필요한 패키지 설치

# 소셜 로그인 및 공유 라이브러리
npm install react-native-fbsdk-next
npm install @react-native-twitter-signin/twitter-signin
npm install react-native-linkedin
npm install react-native-pinterest

# 일반 공유 기능
npm install react-native-share

# 딥링크 처리
npm install react-native-app-auth

4. iOS 설정

4.1 Info.plist 설정

ios/YourApp/Info.plist 파일에 다음 내용을 추가합니다:

<key>CFBundleURLTypes</key>
<array>
  <!-- Facebook -->
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>fb{FACEBOOK_APP_ID}</string>
    </array>
  </dict>
  <!-- Twitter -->
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>twitterkit-{TWITTER_CONSUMER_KEY}</string>
    </array>
  </dict>
  <!-- LinkedIn -->
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>zellyy</string>
    </array>
  </dict>
  <!-- Pinterest -->
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>pdk{PINTEREST_APP_ID}</string>
    </array>
  </dict>
</array>

<!-- Facebook 설정 -->
<key>FacebookAppID</key>
<string>{FACEBOOK_APP_ID}</string>
<key>FacebookDisplayName</key>
<string>Zellyy</string>

<key>LSApplicationQueriesSchemes</key>
<array>
  <string>fbapi</string>
  <string>fb-messenger-share-api</string>
  <string>fbauth2</string>
  <string>fbshareextension</string>
  <string>twitter</string>
  <string>twitterauth</string>
  <string>linkedin</string>
  <string>linkedin-sdk2</string>
  <string>pinterestsdk</string>
</array>

4.2 AppDelegate.m 수정

ios/YourApp/AppDelegate.m 파일에 다음 내용을 추가합니다:

#import <FBSDKCoreKit/FBSDKCoreKit-swift.h>
#import <TwitterKit/TwitterKit.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // ...
  
  // Facebook SDK 초기화
  [[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
  
  // Twitter SDK 초기화
  [[Twitter sharedInstance] startWithConsumerKey:@"TWITTER_CONSUMER_KEY" consumerSecret:@"TWITTER_CONSUMER_SECRET"];
  
  return YES;
}

// URL 스킴 처리
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  // Facebook URL 처리
  BOOL handled = [[FBSDKApplicationDelegate sharedInstance] application:app openURL:url options:options];
  
  // Twitter URL 처리
  if (!handled) {
    handled = [[Twitter sharedInstance] application:app openURL:url options:options];
  }
  
  return handled;
}

@end

5. Android 설정

5.1 AndroidManifest.xml 수정

android/app/src/main/AndroidManifest.xml 파일에 다음 내용을 추가합니다:

<manifest ...>
  <uses-permission android:name="android.permission.INTERNET" />
  
  <application ...>
    <!-- Facebook 설정 -->
    <meta-data
      android:name="com.facebook.sdk.ApplicationId"
      android:value="@string/facebook_app_id" />
    <meta-data
      android:name="com.facebook.sdk.ClientToken"
      android:value="@string/facebook_client_token" />
      
    <activity
      android:name="com.facebook.FacebookActivity"
      android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
      android:label="@string/app_name" />
    <activity
      android:name="com.facebook.CustomTabActivity"
      android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="@string/fb_login_protocol_scheme" />
      </intent-filter>
    </activity>
    
    <!-- Twitter 설정 -->
    <activity
      android:name="com.twitter.sdk.android.core.identity.OAuthActivity"
      android:exported="true">
    </activity>
    
    <!-- 딥링크 처리 -->
    <activity
      android:name=".MainActivity"
      android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="zellyy" />
      </intent-filter>
    </activity>
  </application>
</manifest>

5.2 strings.xml 설정

android/app/src/main/res/values/strings.xml 파일에 다음 내용을 추가합니다:

<resources>
  <string name="app_name">Zellyy</string>
  <string name="facebook_app_id">FACEBOOK_APP_ID</string>
  <string name="fb_login_protocol_scheme">fbFACEBOOK_APP_ID</string>
  <string name="facebook_client_token">FACEBOOK_CLIENT_TOKEN</string>
</resources>

5.3 build.gradle 설정

android/app/build.gradle 파일에 다음 내용을 추가합니다:

dependencies {
  // ...
  implementation 'com.facebook.android:facebook-android-sdk:latest.release'
  implementation 'com.twitter.sdk.android:twitter-core:3.3.0'
  implementation 'com.twitter.sdk.android:tweet-composer:3.3.0'
  // ...
}

6. 소셜 로그인 구현

6.1 Facebook 로그인

import { LoginManager, AccessToken, GraphRequest, GraphRequestManager } from 'react-native-fbsdk-next';
import supabase from '../services/supabase';

const loginWithFacebook = async () => {
  try {
    // 로그인 권한 요청
    const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);
    
    if (result.isCancelled) {
      throw new Error('User cancelled login');
    }
    
    // 액세스 토큰 가져오기
    const data = await AccessToken.getCurrentAccessToken();
    
    if (!data) {
      throw new Error('Failed to get access token');
    }
    
    // 사용자 정보 가져오기
    const profileRequest = new GraphRequest(
      '/me',
      {
        accessToken: data.accessToken,
        parameters: {
          fields: {
            string: 'id,name,email,picture.type(large)',
          },
        },
      },
      async (error, result) => {
        if (error) {
          console.error('Error fetching profile:', error);
          return;
        }
        
        // Supabase에 소셜 계정 연결
        const { data: socialAccount, error: socialError } = await supabase
          .from('zellyy.social_accounts')
          .upsert({
            user_id: supabase.auth.user().id,
            platform: 'facebook',
            platform_user_id: result.id,
            access_token: data.accessToken,
            token_expires_at: new Date(data.expirationTime),
          })
          .select()
          .single();
          
        if (socialError) {
          console.error('Error connecting Facebook account:', socialError);
        }
      }
    );
    
    new GraphRequestManager().addRequest(profileRequest).start();
  } catch (error) {
    console.error('Facebook login error:', error);
    throw error;
  }
};

6.2 Twitter 로그인

import { TwitterSignIn } from '@react-native-twitter-signin/twitter-signin';
import supabase from '../services/supabase';

const loginWithTwitter = async () => {
  try {
    // Twitter SDK 초기화
    TwitterSignIn.init(
      'TWITTER_CONSUMER_KEY',
      'TWITTER_CONSUMER_SECRET'
    );
    
    // 로그인 요청
    const { authToken, authTokenSecret, userName, userID } = await TwitterSignIn.logIn();
    
    // Supabase에 소셜 계정 연결
    const { data: socialAccount, error: socialError } = await supabase
      .from('zellyy.social_accounts')
      .upsert({
        user_id: supabase.auth.user().id,
        platform: 'twitter',
        platform_user_id: userID,
        access_token: authToken,
        refresh_token: authTokenSecret,
      })
      .select()
      .single();
      
    if (socialError) {
      console.error('Error connecting Twitter account:', socialError);
    }
  } catch (error) {
    console.error('Twitter login error:', error);
    throw error;
  }
};

7. 소셜 공유 구현

7.1 일반 공유 기능

import Share from 'react-native-share';

const shareToDefault = async (card) => {
  try {
    // 카드 이미지 생성 (별도 함수로 구현)
    const imageUrl = await generateCardImage(card);
    
    const options = {
      title: 'Share via',
      message: card.content,
      url: imageUrl,
    };
    
    const result = await Share.open(options);
    console.log('Share result:', result);
    
    return result;
  } catch (error) {
    console.error('Error sharing:', error);
    throw error;
  }
};

7.2 Facebook 공유

import { ShareDialog } from 'react-native-fbsdk-next';
import supabase from '../services/supabase';

const shareToFacebook = async (card) => {
  try {
    // 카드 이미지 생성 (별도 함수로 구현)
    const imageUrl = await generateCardImage(card);
    
    const shareContent = {
      contentType: 'link',
      contentUrl: imageUrl,
      contentDescription: card.content,
    };
    
    // 공유 다이얼로그 표시
    const result = await ShareDialog.show(shareContent);
    
    if (result.isCancelled) {
      throw new Error('User cancelled sharing');
    }
    
    // 공유 기록 저장
    const { data: share, error: shareError } = await supabase
      .from('zellyy.social_shares')
      .insert({
        card_id: card.id,
        user_id: supabase.auth.user().id,
        platform: 'facebook',
        status: 'success',
        response_data: result,
      })
      .select()
      .single();
      
    if (shareError) {
      console.error('Error recording share:', shareError);
    }
    
    return result;
  } catch (error) {
    console.error('Error sharing to Facebook:', error);
    throw error;
  }
};

7.3 Twitter 공유

import { TwitterSignIn } from '@react-native-twitter-signin/twitter-signin';
import supabase from '../services/supabase';

const shareToTwitter = async (card) => {
  try {
    // 소셜 계정 정보 가져오기
    const { data: socialAccount, error: socialError } = await supabase
      .from('zellyy.social_accounts')
      .select('*')
      .eq('user_id', supabase.auth.user().id)
      .eq('platform', 'twitter')
      .single();
      
    if (socialError || !socialAccount) {
      throw new Error('Twitter account not connected');
    }
    
    // 카드 이미지 생성 (별도 함수로 구현)
    const imageUrl = await generateCardImage(card);
    
    // Edge Function 호출하여 트윗 게시
    const response = await fetch('https://a11.ism.kr/functions/v1/social-share', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${supabase.auth.session().access_token}`,
      },
      body: JSON.stringify({
        cardId: card.id,
        platform: 'twitter',
        message: card.content,
      }),
    });
    
    const result = await response.json();
    
    if (!result.success) {
      throw new Error(result.error || 'Failed to share to Twitter');
    }
    
    return result;
  } catch (error) {
    console.error('Error sharing to Twitter:', error);
    throw error;
  }
};

8. 소셜 계정 관리

8.1 연결된 계정 목록 조회

import { useEffect, useState } from 'react';
import supabase from '../services/supabase';

const SocialAccountsScreen = () => {
  const [accounts, setAccounts] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchAccounts();
  }, []);
  
  const fetchAccounts = async () => {
    try {
      setLoading(true);
      
      const { data, error } = await supabase
        .from('zellyy.social_accounts')
        .select('*')
        .eq('user_id', supabase.auth.user().id);
        
      if (error) {
        throw error;
      }
      
      setAccounts(data || []);
    } catch (error) {
      console.error('Error fetching social accounts:', error);
      alert('Failed to load social accounts');
    } finally {
      setLoading(false);
    }
  };
  
  // 컴포넌트 렌더링 코드
};

8.2 계정 연결 해제

const disconnectAccount = async (platform) => {
  try {
    // 소셜 계정 삭제
    const { error } = await supabase
      .from('zellyy.social_accounts')
      .delete()
      .match({
        user_id: supabase.auth.user().id,
        platform,
      });
      
    if (error) {
      throw error;
    }
    
    // 플랫폼별 로그아웃 처리
    if (platform === 'facebook') {
      LoginManager.logOut();
    } else if (platform === 'twitter') {
      TwitterSignIn.logOut();
    }
    
    // 계정 목록 새로고침
    fetchAccounts();
    
    alert(`${platform} account disconnected`);
  } catch (error) {
    console.error(`Error disconnecting ${platform} account:`, error);
    alert(`Failed to disconnect ${platform} account`);
  }
};

9. 공유 기록 관리

9.1 공유 기록 조회

import { useEffect, useState } from 'react';
import supabase from '../services/supabase';

const ShareHistoryScreen = () => {
  const [shares, setShares] = useState([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchShareHistory();
  }, []);
  
  const fetchShareHistory = async () => {
    try {
      setLoading(true);
      
      const { data, error } = await supabase
        .from('zellyy.social_shares')
        .select(`
          id,
          platform,
          share_url,
          shared_at,
          status,
          cards:card_id (
            id,
            content,
            background_color,
            text_color
          )
        `)
        .eq('user_id', supabase.auth.user().id)
        .order('shared_at', { ascending: false });
        
      if (error) {
        throw error;
      }
      
      setShares(data || []);
    } catch (error) {
      console.error('Error fetching share history:', error);
      alert('Failed to load share history');
    } finally {
      setLoading(false);
    }
  };
  
  // 컴포넌트 렌더링 코드
};

10. 카드 이미지 생성

카드를 이미지로 변환하여 소셜 미디어에 공유하기 위한 함수입니다.

import ViewShot from 'react-native-view-shot';
import { useRef } from 'react';
import supabase from '../services/supabase';

// 카드 컴포넌트
const Card = ({ card, viewShotRef }) => {
  return (
    <ViewShot ref={viewShotRef} options={{ format: 'jpg', quality: 0.9 }}>
      <View
        style={{
          width: 300,
          height: 300,
          backgroundColor: card.background_color,
          justifyContent: 'center',
          alignItems: 'center',
          padding: 20,
        }}
      >
        <Text
          style={{
            color: card.text_color,
            fontFamily: card.font_family,
            fontSize: card.font_size,
            textAlign: card.text_align,
          }}
        >
          {card.content}
        </Text>
      </View>
    </ViewShot>
  );
};

// 이미지 생성 및 업로드 함수
const generateCardImage = async (card) => {
  try {
    // ViewShot 참조
    const viewShotRef = useRef();
    
    // 카드 이미지 캡처
    const uri = await viewShotRef.current.capture();
    
    // 파일 이름 생성
    const fileName = `${supabase.auth.user().id}/${card.id}_${Date.now()}.jpg`;
    
    // 이미지 파일 생성
    const formData = new FormData();
    formData.append('file', {
      uri,
      name: fileName,
      type: 'image/jpeg',
    });
    
    // Supabase Storage에 업로드
    const { data, error } = await supabase
      .storage
      .from('card-images')
      .upload(fileName, formData);
      
    if (error) {
      throw error;
    }
    
    // 공개 URL 생성
    const { publicURL, error: urlError } = supabase
      .storage
      .from('card-images')
      .getPublicUrl(fileName);
      
    if (urlError) {
      throw urlError;
    }
    
    return publicURL;
  } catch (error) {
    console.error('Error generating card image:', error);
    throw error;
  }
};

11. 문제 해결

11.1 일반적인 문제 및 해결 방법

  1. 소셜 로그인 실패:

    • 앱 ID와 시크릿이 올바른지 확인
    • 리디렉션 URL이 올바르게 설정되었는지 확인
    • 앱 권한이 올바르게 설정되었는지 확인
  2. 공유 기능 실패:

    • 소셜 미디어 앱이 기기에 설치되어 있는지 확인
    • 액세스 토큰이 유효한지 확인
    • 필요한 권한이 부여되었는지 확인
  3. 이미지 업로드 실패:

    • 스토리지 버킷 권한 설정 확인
    • 파일 크기 및 형식 확인

11.2 플랫폼별 문제 해결

Facebook

  • 앱 검토 상태 확인
  • 개발 모드에서는 테스트 사용자만 앱에 접근 가능

Twitter

  • API 키와 시크릿이 올바른지 확인
  • 개발자 계정 상태 확인

Instagram

  • Facebook 개발자 계정과 연결되어 있는지 확인
  • Instagram Graph API 권한 확인

12. 보안 고려사항

  1. 토큰 보안:

    • 액세스 토큰과 리프레시 토큰은 안전하게 저장
    • 서버 측에서만 API 시크릿 사용
  2. 사용자 데이터 보호:

    • 필요한 최소한의 권한만 요청
    • 사용자 동의 없이 데이터를 공유하지 않음
  3. API 키 관리:

    • 환경 변수로 관리하고 소스 코드에 하드코딩하지 않음
    • 프로덕션과 개발 환경에 별도의 API 키 사용

결론

이 가이드는 Zellyy 앱에서 다양한 소셜 미디어 플랫폼과의 통합 방법을 제공합니다. 각 플랫폼의 API는 지속적으로 변경될 수 있으므로, 최신 문서를 참조하여 구현을 업데이트하는 것이 중요합니다. 소셜 미디어 통합은 사용자 경험을 향상시키고 앱의 확산을 촉진하는 중요한 기능입니다.