Home react-native-webview에서 외부 앱 실행 (pg)
Post
Cancel

react-native-webview에서 외부 앱 실행 (pg)

react-native-webview를 이용하여 모바일 web으로 이동한 후 앱 카드 결제 시 앱 카드 앱 호출이 되지 않는 이슈가 발생하였다. 구글링을 통해 마침내 외부 앱을 실행했다.. 정확한 원리와 이해를 완벽히 한 건 아니지만 내가 시도했던 것들을 아래에 정리해 보았다.

android


안드로이드의 경우 intent 호출을 할 수 없기 때문에 라이브러리를 설치해 intent 호출을 할 수 있었다.

install

1
2
3
npm install react-native-send-intent --save

npx react-native link react-native-send-intent
  • android/setting.gradle
1
2
3
    ...
    include ':RNSendIntentModule', ':app'
    project(':RNSendIntentModule').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-send-intent/android')
  • android/app/build.gradle
1
2
3
4
5
    ...
    dependencies {
        ...
        compile project(':RNSendIntentModule')
    }

구글링을 통해 here1 를 발견했다. 댓글에 나와있던 here2 와 같이 아래 파일을 수정해서 openAppWithUri함수를 만들었다.

  • node_modules/react-native-send-intent/android/src/main/java/com/burnweb/rnsendintent/RNSendIntentModule.java 에 아래 소수를 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    ...
    @ReactMethod
        public void openAppWithUri(String intentUri, ReadableMap extras, final Promise promise) {
            try {
                Intent intent = Intent.parseUri(intentUri, Intent.URI_INTENT_SCHEME);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                Intent existPackage = this.reactContext.getPackageManager().getLaunchIntentForPackage(intent.getPackage());
                if (existPackage != null) {
                    this.reactContext.startActivity(intent);
                } else {
                    Intent marketIntent = new Intent(Intent.ACTION_VIEW);
                    marketIntent.setData(Uri.parse("market://details?id="+intent.getPackage()));
                    marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    this.reactContext.startActivity(marketIntent);
                }
                promise.resolve(true);
            } catch (Exception e) {
                promise.resolve(false);
            }
        }
    ...
  • node_modules/react-native-send-intent/index.js 에 아래 소스를 추가한다.
1
2
3
    openAppWithUri(intentUri, extras) {
            return RNSendIntentAndroid.openAppWithUri(intentUri, extras || {});
    }

## ios —

  • AppDelegate.m 파일에 아래의 소스를 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    - (BOOL)application:(UIApplication *)application
       openURL:(NSURL *)url
       options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
    {
      return [RCTLinkingManager application:application openURL:url options:options];
    }

    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
      sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
    {
      return [RCTLinkingManager application:application openURL:url
                          sourceApplication:sourceApplication annotation:annotation];
    }

    - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
     restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
    {
     return [RCTLinkingManager application:application
                      continueUserActivity:userActivity
                        restorationHandler:restorationHandler];
    }

iOS 9 버전부터 LSApplicationQueriesSchemes라는 Info 항목에 URL scheme을 사용해서 외부 앱을 열 경우, 특별한 제한이 없던 기존 방식에서 화이트리스트에 등록된 scheme만 열 수 있도록 보안 정책이 강화되어 화이트리스트에 등록되지 않은 경우, 웹뷰에서는 무조건 차단하는 정책으로 변경되었기 때문에 info.plist 파일에 LSApplicationQueriesSchemes에 스키마를 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    <key>LSApplicationQueriesSchemes</key>
    <array>
        <string>lguthepay://</string>
    	<string>lguthepay-xpay://</string>
    	<string>smartxpay-transfer://</string>
    	<string>nhappcash-acp:// </string>
    	<string>SmartBank2WB://</string>
    	<string>hdcardappcardansimclick://</string>
    	<string>smhyundaiansimclick://</string>
    	<string>shinhan-sr-ansimclick://</string>
    	<string>smshinhanansimclick://</string>
    	<string>kb-acp://</string>
    	<string>mpocket.online.ansimclick://</string>
    	<string>ansimclickscard://</string>
    	<string>tswansimclick://</string>
    	<string>ansimclickipcollect://</string>
    	<string>vguardstart://</string>
    	<string>samsungpay</string>
    	<string>scardcertiapp://</string>
    	<string>lottesmartpay://</string>
    	<string>lpayapp://</string>
    	<string>lotteappcard://</string>
    	<string>payco://</string>
    	<string>cloudpay://</string>
    	<string>hanamopmoasign://</string>
    	<string>hanawalletmembers://</string>
    	<string>nhappcardansimclick://</string>
    	<string>nhallonepayansimclick://</string>
    	<string>nonghyupcardansimclick://</string>
    	<string>citispay://</string>
    	<string>citicardappkr://</string>
    	<string>ispmobile://</string>
    	<string>uppay://</string>
    	<string>shinsegaeeasypayment://</string>
    	<string>wooripay://</string>
    	<string>kftc-bankpay</string>
    	<string>itms-apps</string>
    	<string>citimobileapp</string>
    	<string>kakaotalk</string>
    </array>
    <key>NSAppTransportSecurity</key>
    <dict>
    	<key>NSAllowsArbitraryLoads</key>
    	<true/>
    	<key>NSExceptionDomains</key>
    	<dict>
    	   <key>localhost</key>
    	   <dict>
    	      <key>NSExceptionAllowsInsecureHTTPLoads</key>
    	      <true/>
    	   </dict>
    	</dict>
    </dict>

react-native-webview


RN 웹뷰 내에서 onShouldStartLoadWithRequest 메소드를 이용해서 url이 바뀔 때마다 감지해서 http, https 외의 외부 앱 호출일 경우 android 경우 react-native-send-intent 등 외부 라이브러리를 이용해 intent를 호출하고 ios의 경우 Linking.openUrl을 사용해 intent를 호출한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    import {WebView} from 'react-native-webview';
    import {Linking, Platform} from 'react-native';

    ...

    const onShouldStartLoadWithRequest = () => {
      if (
          event.url.startsWith('http://') ||
          event.url.startsWith('https://') ||
          event.url.startsWith('about:blank')
        ) {
          return true;
        }
        if (Platform.OS === 'android') {
          SendIntentAndroid.openAppWithUri(event.url)
            .then(isOpened => {
              if (!isOpened) {
                alert('앱 실행에 실패했습니다');
              }
            })
            .catch(err => {
              console.log(err);
            });
        } else {
          Linking.openURL(event.url).catch(err => {
            alert(
              '앱 실행에 실패했습니다. 설치가 되어있지 않은 경우 설치하기 버튼을 눌러주세요.',
            );
          });
          return false;
        }
    };
    ...

    <WebView
       ref={webview}
       originWhitelist={['*']}
       source={/* url ... */}
       onLoadStart={() => onLoadStart()}
       onLoadProgress={({nativeEvent}) => onLoadProgress(nativeEvent)}
       onLoad={() => onLoadEnd()}
       onNavigationStateChange={navState => onChangeNavState(navState)}
       onShouldStartLoadWithRequest={event => {
         return onShouldStartLoadWithRequest(event);
       }}
       onMessage={event => onMessage(event)}
    />
This post is licensed under CC BY 4.0 by the author.