My Boundary As Much As I Experienced

react-native-play-install-referrer를 통해 디퍼드 딥링킹 구현하기 본문

FrontEnd/React Native

react-native-play-install-referrer를 통해 디퍼드 딥링킹 구현하기

Bumang 2024. 11. 18. 18:07

문제 상황

현재 ANTTIME의 초대 링크를 통해 앱을 설치 했을 때 초대한 사람이 추천인으로 안 뜨고 있었다...

전임자 분들이 구현한 로직으로는,

앱이 설치 안 되어 있음 ->  ios/android 각각 마켓에 보내기
앱이 설치 되어 있음 -> 앱 실행

까지는 가능했는데...

앱이 설치 안 되어 있음 ->  ios/android 각각 마켓에 보내기 -> 앱 실행 - 회원가입 시 추천인 코드가 입력되어 있기!

이게 안 되는 것이다.

 

이런 기능을 구현하려면 디퍼드 딥링킹이라는 기술이 필요하다.

 

디퍼드 딥링킹을 직접 구현하려면 어떻게 구현하나?

1) 딥링크 생성

  • 마케팅 링크 또는 딥링크를 아래와 같은 형태로 생성
https://example.com/app?utm_source=campaign&utm_medium=email&utm_campaign=test_campaign&custom_param=value

 

요소 설명:

  • utm_source, utm_medium, utm_campaign: 마케팅 UTM 파라미터.
  • custom_param: 추가적으로 전달하고 싶은 데이터.

2) 서버에서 딥링크 클릭 데이터를 저장

  • 사용자가 딥링크를 클릭할 때, 클릭 데이터를 서버에 저장한다.
  • 서버는 각 사용자의 세션을 추적하고, 디바이스id도 수집하고, 앱 설치 후 이를 복구할 수 있도록 해야 한다.

3) 앱 실행 후 서버 데이터를 호출하여 딥링크 처리

  • 앱 실행 후 서버에 한 번 찔러본다. "이거 딥링크로 깔린거니?"
  • 그 후 맞다면 서버와 통신하여 진입 데이터를 복구..
fetch("https://api.example.com/get-deferred-data", {
  method: "POST",
  body: JSON.stringify({
    device_id: "UNIQUE_DEVICE_ID",
  }),
})
  .then((response) => response.json())
  .then((data) => {
    console.log("Recovered Deep Link Data:", data);
    if (data.utm_campaign === "test_campaign") {
      navigation.navigate("PromoScreen", { campaign: data.utm_campaign });
    }
  })
  .catch((error) => console.error("Error fetching deferred deep link:", error));

 

즉 디퍼드 딥링크가 url만 어떻게 만지작한다고 해결되는 문제가 아니라,

프론트엔드와 백엔드가 모두 맨먼스 소요해야되는 하나의 기능인 것이다.

 

그런데 현재 우리 백엔드 개발자 분이 다른 프로젝트에 리소스가 다 뺐기고 있어서

그냥 대안을 찾아보기로 했다.

 

대안

디퍼드 딥링킹 기능을 제공하면서 어떤 경로로 많이 인입하는지 체크하는 서비스들이 더러 존재했다.

 

- firebase dynamic link (가장 선호되는 디퍼드 딥링킹 솔루션. 그러나 2025년 5월에 서비스 종료 선언을 했다..)

- branch.io (한 달 5k 호출까지는 무료)

- appsflyer (무료플랜 존재)

 

- Google Play Store Referrer API (구글 플레이스토어 링크 세션 정보를 저장하는 방식)

 

 

선택

일단 파베 다이나믹 링크는 내년에 서비스 종료하니 후보군에서 제외했고,

branch.io는 한 달 5k 호출이 넘어갈까 대표님이 걱정을 많이 해서 제외했다... (안 넘을거 같은데..)

appsflyer는 나는 많이 들어본 마케팅 폼이긴 한데 대표님께서 너무 못 들어본 곳이라 믿기지 않는다고 하심..

 

그래서 일단 안드로이드에서 기본 지원하는 Google Play Store Referrer API를 사용하기로 했다.

안드로이드 API이니 iOS가 지원 안 된다는게 흠이긴 하다.

그러나 현재 우리 유저의 98%가 안드 유저기 때문에 솔직히 이것 하나만 해도 괜찮을거란 얘기를 하심..

 

하여튼, 안드로이드 플랫폼에서 지원하는 간단한 API로 해결한 다음에 Appsflyer 연동은 다음에 해보기로 했다.

https://developer.android.com/google/play/installreferrer/library?hl=ko

 

 

구현

1) Play-install-referrer-react-native 설치

개인이 만든 라이브러리이고 3년 전 라이브러리긴 한데 한 번 시도해보기로 했다. 

그래도 리드미를 읽어보니 Google install referrer API의 최신 버전을 대응하고 있다.

(Google install referrer API의 마지막 릴리즈가 3년 전이기 때문이다.

만약 새로운 install referrer API 버전이 릴리즈 되면 대응해줄지는 모르겠다.)

 

https://github.com/uerceg/play-install-referrer-react-native

 

GitHub - uerceg/play-install-referrer-react-native: Play Install Referrer Library ported to React Native

Play Install Referrer Library ported to React Native - uerceg/play-install-referrer-react-native

github.com

npm or yarn 설치

npm install --save react-native-play-install-referrer

 

 AndroidManifest.xml에 아래 permission 태그 추가

<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE"/>

 

*공식문서에는 안 적혀있는데, Play Install Referrer API를 활용하려면

android/app/build.gradle의 dependency에 아래 코드를 추가해줘야한다.

dependencies {
	...
    implementation 'com.android.installreferrer:installreferrer:2.1'
}

 

어떻게 알았냐고? 빌드 후 제대로 추천인 값이 안 들어와서 구글 검색해보니 추가해야된다 카더라..

 

 

2) 활용

기본적인 활용 형태는 이렇다고 한다.

import { PlayInstallReferrer } from 'react-native-play-install-referrer';

PlayInstallReferrer.getInstallReferrerInfo((installReferrerInfo, error) => {
  if (!error) {
    console.log("Install referrer = " + installReferrerInfo.installReferrer);
    console.log("Referrer click timestamp seconds = " + installReferrerInfo.referrerClickTimestampSeconds);
    console.log("Install begin timestamp seconds = " + installReferrerInfo.installBeginTimestampSeconds);
    console.log("Referrer click timestamp server seconds = " + installReferrerInfo.referrerClickTimestampServerSeconds);
    console.log("Install begin timestamp server seconds = " + installReferrerInfo.installBeginTimestampServerSeconds);
    console.log("Install version = " + installReferrerInfo.installVersion);
    console.log("Google Play instant = " + installReferrerInfo.googlePlayInstant);
  } else {
    console.log("Failed to get install referrer info!");
    console.log("Response code: " + error.responseCode);
    console.log("Message: " + error.message);
  }
});

 

예를 들어 아래처럼 url을 생성해놓으면

위의 PlayInstallReferrer.getInstallReferrerInfo(...)

utm_campaign=BUMANG-TEST&utm_source=BUMANG&utm_medium=BUMANG

부분을 제대로 탐지할 수 있을거라 예측했는데

 

에러가 나는 링크 예시:

https://play.google.com/store/apps/details?id=YOUR_SERVICE_NAME&utm_campaign=BUMANG-TEST&utm_source=BUMANG&utm_medium=BUMANG

 

막상 실행시켜보니 아래처럼 나왔다.. 구글 플레이에 직접 들어가서 찾은 오가닉 유저라네..?

"utm_source=google-play&utm_medium=organic"

 

알고보니 제대로 값을 검출하지 못했을 경우 위의 설정이 나온다고 한다.

...그 후 조금의 삽질을 통해 알아낸 결과 'referrer=' 이후에 utm 쿼리 스트링들을 입력해야된다고 한다.

 

수정 ('referrer=' 이후 쿼리 스트링 입력) :

https://play.google.com/store/apps/details?id=YOUR_SERVICE_NAME&referrer=&code=BUMANG-TEST&utm_campaign=BUMANG-TEST&utm_source=BUMANG&utm_medium=BUMANG

*이슈를 뒤져보니 제작자가 손수 referrer= 다음에 입력하라고 알려준게 있어서 적용해봤다.

https://github.com/uerceg/play-install-referrer-react-native/issues/39 

 

그래서 이를 통해 Bumang이라는 값을 utm_source로 불러올 수 있었고 아래 로직을 통해 전역 변수로 활용할 수 있었다.

    useEffect(() => {
        PlayInstallReferrer.getInstallReferrerInfo((installReferrerInfo, error) => {
            if (!error) {
                if (installReferrerInfo?.installReferrer.includes('utm_source')) {
                    let referrer = installReferrerInfo?.installReferrer.split('utm_source=')[1];
                    if (referrer.includes('&')) {
                        referrer = referrer.split('&')[0];
                    }

                    setReferralCode(referrer); // zustand 전역 상태 관리로 저장 후 회원가입 페이지에서 활용
                }
            } else {
                console.log('Failed to get install referrer info!');
                console.log('Response code: ' + error.responseCode);
                console.log('Message: ' + error.message);
            }
        });
    }, []);