# React Native 앱의 딥링크

`@mocco/react-native/links`는 앱에서 세 가지를 합니다.

- Mocco 링크로 앱이 열리면 어떤 화면을 보여 줄지 알려 줍니다.
- Android에서 링크를 통해 앱을 설치한 경우, 첫 실행에서 그 링크를 찾아 줍니다.
- 앱 안의 콘텐츠로 공유 링크를 만듭니다.

자체 네이티브 코드는 없습니다. 앱에 이미 있는 몇 가지를 넘겨주기만 하면 됩니다.

## 시작하기 전에

- 링크 호스트와 앱 정보를 설정하고, 링크가 앱을 열도록 해 둡니다([링크로 앱 열기](https://www.mocco.club/docs/ko/articles/ikc6rm-open-the-app) 참고). 이 설정이 없으면 휴대폰이 링크를 앱 대신 브라우저에서 엽니다.
- 프로젝트의 **API 키**에서 `links:app` 권한의 **퍼블리셔블** 키를 만듭니다([서버나 앱에서 링크 만들기](https://www.mocco.club/docs/ko/articles/wmyyuw-api) 참고). 앱에 넣어도 안전합니다.

## 설치

```sh
npx expo install @mocco/react-native @react-native-async-storage/async-storage expo-application
```

bare React Native 앱이라면 `@mocco/react-native`, `@react-native-async-storage/async-storage`, `react-native-play-install-referrer`를 설치합니다.

## 설정

앱이 시작될 때 한 번 만듭니다.

```ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Application from 'expo-application';
import { Linking, Platform } from 'react-native';
import { createMoccoLinks } from '@mocco/react-native/links';

export const links = createMoccoLinks({
  publishableKey: 'mk_pub_…',
  // 링크 호스트: 내 링크 도메인과 프로젝트의 Mocco 호스트
  domains: ['go.example.com', 'acme.<link domain>'],
  linking: Linking,
  storage: AsyncStorage,
  appVersion: Application.nativeApplicationVersion ?? '0.0.0',
  platform: Platform.OS,
  installReferrer: Platform.OS === 'android' ? () => Application.getInstallReferrerAsync() : undefined,
  onError: error => console.warn('Mocco links', error),
});
```

bare 앱에서는 `react-native-play-install-referrer`로 리퍼러를 읽고, 그 `installReferrer` 문자열을 돌려주는 함수를 넘깁니다.

호스트는 **딥링크** 설정 페이지에 나온 값을 씁니다. 다른 호스트의 링크와 앱 자체 URL 스킴은 앱이 원래 하던 대로 처리합니다.

## 맞는 화면 열기

`onLink`는 앱을 시작한 링크와 그 뒤에 앱을 여는 모든 링크를 받습니다. 이벤트마다 앱 안의 경로(`path`), 링크의 `params`와 `campaign`, 그리고 `source`가 들어 있습니다. `source`는 휴대폰이 링크로 앱을 열었으면 `link`, 링크를 통해 설치했으면 `deferred`입니다.

React Navigation을 쓴다면 `prefixes`에 링크 호스트를 넣고, Mocco가 알려 준 경로를 `getStateFromPath`에 넘깁니다.

```tsx
import { NavigationContainer, getStateFromPath } from '@react-navigation/native';

const linking = {
  prefixes: ['https://go.example.com', 'https://acme.<link domain>'],
  config: { screens: { Product: 'products/:id' } },
  // 휴대폰은 앱에 짧은 링크를 넘깁니다. 경로는 Mocco가 알려 줍니다.
  async getInitialURL() {
    return null;
  },
  subscribe(listener: (url: string) => void) {
    const stop = links.onLink(event => {
      const query = new URLSearchParams(event.params).toString();
      listener(`https://go.example.com${event.path}${query === '' ? '' : `?${query}`}`);
    });
    return stop;
  },
  getStateFromPath,
};

export function App() {
  return <NavigationContainer linking={linking}>{/* … */}</NavigationContainer>;
}
```

`getInitialURL`이 `null`을 돌려주는 이유는 `onLink`가 앱을 시작한 링크를 이미 전달하기 때문입니다. 첫 화면이 구독하기 전에 도착한 링크도 전달합니다.

첫 화면이 준비되면 `links.resolveDeferred()`를 한 번 호출합니다. Android에서 링크를 통해 설치한 뒤 처음 실행하면, 같은 `onLink` 리스너로 링크 화면이 열립니다. 설치마다 Mocco에 한 번만 묻고 예외를 던지지 않습니다. 문제가 생기면 앱은 평소처럼 열립니다.

iOS에서는 링크의 랜딩 페이지에서 **앱 받기**를 누르면 일회용 코드가 복사됩니다([iOS의 지연 링크](https://www.mocco.club/docs/ko/articles/hyaqqz-deferred#ios의-지연-링크) 참고). 첫 실행에서 이 코드를 받으려면 `expo-clipboard`를 추가하고 기능을 켭니다.

```ts
import * as Clipboard from 'expo-clipboard';

export const links = createMoccoLinks({
  // …위와 같음
  ios: { readClipboardOnFirstLaunch: true, clipboard: Clipboard },
});
```

그러면 `resolveDeferred()`가 첫 실행에서만 클립보드를 한 번 읽고, Mocco 코드가 정확히 들어 있을 때만 씁니다. 클립보드에 쓰거나 지우지 않습니다. 읽을 때 iOS의 붙여넣기 확인창이 뜰 수 있습니다. 이를 피하려면 기능을 끄고, 첫 화면에 expo-clipboard의 `ClipboardPasteButton`을 둡니다.

```tsx
<Clipboard.ClipboardPasteButton
  acceptedContentTypes={['plain-text']}
  onPress={async data => {
    if (data.type === 'text') {
      await links.resolveFromPaste(data.text);
    }
  }}
/>
```

둘 다 쓰지 않으면 iOS에서 `resolveDeferred()`는 첫 실행만 기록하고 `none`을 돌려줍니다.

## 공유 링크 만들기

```ts
const { url } = await links.createLink({
  deepLinkPath: '/products/42',
  params: { ref: 'share' },
  og: { title: 'Ceramic mug', description: 'Hand-made, 350 ml' },
});
```

같은 콘텐츠는 같은 링크를 돌려받으므로, 사용자가 **공유**를 누를 때마다 만들어도 됩니다. 공유 링크는 항상 앱의 스토어 페이지로 이어집니다. 링크를 만들 수 없을 때는 `createLink`가 예외를 던집니다. 예를 들어 프로젝트가 오늘 공유 링크를 너무 많이 만든 경우입니다.

## 휴대폰에 남는 정보

SDK는 넘겨받은 저장소에 두 가지를 남깁니다. 한 번 만드는 임의의 설치 ID(기기 식별자가 아닙니다)와 첫 실행 확인 결과입니다. Mocco는 휴대폰의 IP 주소나 브라우저 정보를 저장하지 않습니다.

## 테스트

- **앱을 여는 링크:** 앱 링크 정보가 들어간 빌드를 설치한 뒤, 메모나 메시지 앱에서 링크를 누릅니다. 브라우저 주소창에 붙여 넣은 링크는 웹 페이지로 열립니다.
- **링크로 iOS 설치:** App Store나 TestFlight가 필요합니다. 앱을 지우고, iPhone의 Safari에서 링크를 열고, **앱 받기**를 누르고, 설치한 뒤 앱을 엽니다. 링크 화면이 열려야 합니다. 그다음 앱이 설치된 상태에서 같은 페이지의 **앱에서 열기**를 누르면 앱이 열려야 합니다.
- **링크로 Android 설치:** Google Play가 필요하므로 내부 테스트 트랙을 씁니다. 앱을 지우고, 휴대폰에서 링크를 누르고, Play에서 설치한 뒤 앱을 엽니다. 링크 화면이 열려야 합니다.
- **로그:** 테스트하는 동안 `onError`를 넘겨 둡니다. Mocco가 모르는 링크나 네트워크 오류가 여기에 나타납니다.

## 관련 문서

- [링크로 앱 열기](https://www.mocco.club/docs/ko/articles/ikc6rm-open-the-app)
- [지연 딥링크](https://www.mocco.club/docs/ko/articles/hyaqqz-deferred): SDK 없이 쓰는 같은 호출
- [서버나 앱에서 링크 만들기](https://www.mocco.club/docs/ko/articles/wmyyuw-api)
