npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tenqube/visual-reward-react-native

v1.4.6

Published

Tenqube Visual Reward SDK for React Native

Readme

React Native SDK

React Native WebView 기반 콘텐츠 SDK를 앱에 연동합니다.

준비

요구사항

  • React Native 0.77+
  • Android minSdk 24 / compileSdk 35 / Kotlin Gradle Plugin 2.0.21+
  • iOS 15.1+ (RN 0.77+ 프로젝트 하한)

npm

네 패키지를 함께 설치합니다. 최신 버전은 npm에서 확인할 수 있습니다.

npm install \
  @tenqube/visual-reward-react-native \
  react-native-webview \
  react-native-google-mobile-ads \
  @react-native-async-storage/async-storage

iOS 는 추가로 pod install 을 실행합니다.

cd ios && pod install

GMA 버전별 동작

| react-native-google-mobile-ads | 광고 기능 | | --- | --- | | 14.7.0 이상 | 정상 동작 | | 14.7.0 미만 | 광고 비활성 (RN 0.82 이상에서는 빌드 실패) | | 미설치 | 광고 비활성 (크래시 없음) |

AndroidManifest 설정

네이티브 광고를 사용하는 경우

App ID 만 선언하면 됩니다. 등록 방법은 App ID를 참고하세요.

네이티브 광고를 사용하지 않는 경우

App ID 검사를 건너뛰는 키를 넣습니다. tools:node="remove" 로 노드를 제거해야 합니다. 선언만 지우면 react-native-google-mobile-ads 가 라이브러리 매니페스트에서 채우는 빈 값이 병합돼 Invalid application ID 로 크래시합니다.

<!-- android/app/src/main/AndroidManifest.xml -->
<!-- manifest 태그에 xmlns:tools="http://schemas.android.com/tools" 가 필요합니다 -->
<meta-data
    android:name="com.google.android.gms.ads.APPLICATION_ID"
    tools:node="remove" />
<meta-data
    android:name="com.google.android.gms.ads.INTEGRATION_MANAGER"
    android:value="webview" />

Info.plist 설정

IDFA 수익화에 필요한 키는 광고 사용 여부와 무관하게 넣습니다.

<!-- ios/<프로젝트>/Info.plist -->
<key>NSUserTrackingUsageDescription</key>
<string>맞춤형 광고 제공을 위해 기기 식별자를 사용합니다.</string>

네이티브 광고를 사용하는 경우

App ID 만 선언하면 됩니다. 등록 방법은 App ID를 참고하세요.

네이티브 광고를 사용하지 않는 경우

App ID 검사를 건너뛰는 키를 넣습니다. 없으면 앱 시작 시 GADInvalidInitializationException 으로 크래시합니다.

<!-- ios/<프로젝트>/Info.plist -->
<key>GADIntegrationManager</key>
<string>webview</string>

사용법

동작 흐름

initialize()가 서버에서 설정을 로드하고, 이후 open()이 리워드 웹뷰를 엽니다.

sequenceDiagram
    participant A as Host App
    participant B as VisualReward SDK
    participant C as VisualReward 서버
    participant D as WebView 화면

    A->>B: initialize({ appKey, ... })
    B->>C: config 요청
    C-->>B: config 응답
    B-->>A: onSuccess()

    A->>B: open({ serviceName, userId, ... })
    B->>D: 웹뷰 열기

    D->>B: 웹뷰 닫힘
    B-->>A: onClose

VisualRewardContainer 마운트

리워드 웹뷰를 띄우는 컨테이너입니다. 앱 루트에 한 번 넣습니다. 없으면 open()OPEN_FAILED 로 실패합니다.

import { VisualRewardContainer } from '@tenqube/visual-reward-react-native';

export default function App() {
  return (
    <>
      <YourNavigator />
      <VisualRewardContainer />
    </>
  );
}

디버그 설정

initialize() 전에 호출하세요.

import { VisualReward } from '@tenqube/visual-reward-react-native';

VisualReward.setDebugEnabled(__DEV__);

초기화

앱 시작 시 한 번만 호출합니다.

import { useEffect } from 'react';
import { VisualReward } from '@tenqube/visual-reward-react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

export default function App() {
  useEffect(() => {
    VisualReward.initialize({
      appKey: 'app_key',      // 발급된 app_key
      adTestMode: false,      // 테스트 광고 활성화 여부
      storage: AsyncStorage,  // config 캐시 저장소
      timeoutMs: 5000,        // 기본값 (ms)
      onSuccess: () => { /* SDK 준비 완료 */ },
      onFailure: (e) => { /* 초기화 실패 */ },
    });
  }, []);

  return <VisualRewardContainer />;
}

::: info onSuccess/onFailure리워드 설정 로드 결과에 대한 콜백입니다. 광고 엔진 초기화는 백그라운드에서 진행되며 실패해도 리워드 기능은 정상 동작합니다(광고만 비활성). :::

VisualReward.initialize 파라미터

| 파라미터 | 타입 | 필수 | 설명 | 기본값 | | --- | --- | :---: | --- | --- | | appKey | string | ✔ | 발급받은 app_key. stage별로 발급 요청이 필요합니다. | | | adTestMode | boolean | | 테스트 광고 활성화 여부. 광고 엔진 첫 초기화 시점의 값만 적용됩니다. | false | | storage | AsyncStorageLike | ✔ | config 캐시 저장소. @react-native-async-storage/async-storage 를 그대로 넘기면 됩니다. 없으면 네트워크 실패 시 폴백할 값이 없어 open() 이 열리지 않습니다. | | | timeoutMs | number | | open() 이 초기화 완료를 기다리는 상한(ms). 초과 시 open()onErrorINITIALIZE_TIMEOUT 입니다. | 5000 | | showDefaultErrorUi | boolean | | open() 이 실패했을 때 SDK 가 디폴트 에러 페이지를 띄운 뒤 onError 를 호출합니다. 원격 안내 페이지를 먼저 띄우고, 실패하면 앱에 동봉된 HTML 로 떨어집니다. <VisualRewardContainer /> 가 마운트돼 있어야 합니다. 화면에 표시되는 진단 코드는 디폴트 에러 페이지 code 를 참고하세요. | true | | onSuccess | () => void | | 초기화 성공 콜백 | | | onFailure | (error: VisualRewardError) => void | | 초기화 실패 콜백 | |

::: warning 이미 onError 로 안내하고 있다면 showDefaultErrorUi: false 로 끄세요 기본값이 켜져 있어 그대로 두면 안내가 두 번 뜹니다. :::

웹뷰 열기

SDK 초기화 성공 이후에 호출합니다.

import { VisualReward } from '@tenqube/visual-reward-react-native';

await VisualReward.open({
  serviceName: 'anyService', // 서비스 식별자
  userId: user.id,           // 사용자 식별자
  locale: 'ko',
  extra: undefined,
  onClose: () => { /* 웹뷰 닫힘 */ },
  onError: (e) => { /* 에러 처리 */ },
});

VisualReward.open 파라미터

| 파라미터 | 타입 | 필수 | 설명 | 기본값 | | --- | --- | :---: | --- | --- | | serviceName | string | ✔ | 서비스 식별자. 사용할 값은 별도로 전달드립니다. 빈 문자열이면 서비스 구획 없이 사이트 루트로 진입합니다. | | | userId | string | ✔ | 사용자 식별자. 비어 있으면 USER_ID_NOT_SET 에러가 발생합니다. | | | locale | string | | BCP 47 언어 서브태그(ISO 639-1, 2자리 소문자) | "ko" | | extra | string | | 포스트백 추가 데이터(JSON serialize된 문자열) | | | onClose | () => void | | 웹뷰가 닫힐 때 호출되는 콜백 | | | onError | (error: VisualRewardError) => void | | 에러 발생 시 호출되는 콜백 | |

::: info 한 번의 open()onClose 또는 onError 중 정확히 하나를 받습니다. <VisualRewardContainer /> 가 마운트돼 있지 않으면 OPEN_FAILED 로 알립니다. 닫힘 트리거가 여러 번 발생해도 콜백은 한 번만 통보됩니다. :::

호스트 WebView 브릿지

호스트 앱이 직접 소유한 WebView의 웹 페이지에서 리워드 웹뷰를 열 수 있습니다. 네이티브에서 메시지를 SDK 로 넘긴 뒤, 웹 페이지에서 호출합니다.

네이티브: 메시지 전달

호스트 WebView 의 onMessage 에 SDK 핸들러를 꽂습니다.

import React, { useRef } from 'react';
import WebView from 'react-native-webview';
import { VisualReward } from '@tenqube/visual-reward-react-native';

export function HostWebViewScreen() {
  // `useRef<WebView>(null)` 은 react-native-webview 16 미만에서 타입 에러가 납니다.
  const webViewRef = useRef<React.ElementRef<typeof WebView>>(null);

  return (
    <WebView
      ref={webViewRef}
      source={{ uri: 'https://your-page.example.com' }}
      onMessage={VisualReward.hostWebViewOnMessage(webViewRef)}
    />
  );
}

페이로드 파싱과 오리진 검증은 SDK 가 처리합니다. 같은 WebView 에서 호스트 자체 메시지도 쓴다면 onMessage 를 넘기세요 — 브릿지 메시지가 아닌 것만 그쪽으로 전달됩니다.

onMessage={VisualReward.hostWebViewOnMessage(webViewRef, {
  onMessage: (event) => { /* 호스트 메시지 처리 */ },
})}

| 파라미터 | 타입 | 필수 | 설명 | 기본값 | | --- | --- | :---: | --- | --- | | webView | RefObject<WebView> | ✔ | 호스트 WebView ref. 결과 콜백 주입에 씁니다 | | | options.onMessage | (event) => void | | 브릿지 메시지가 아닌 onMessage 이벤트를 받습니다 | |

::: details 메시지 처리를 직접 하려면 — handleWebBridgeMessage onMessage 를 호스트가 완전히 통제해야 하는 경우에 씁니다. 페이로드 파싱과 fncName 필터를 직접 하고, 오리진 검증에 쓸 URL 도 직접 넘겨야 합니다.

onMessage={(event) => {
  const payload = JSON.parse(event.nativeEvent.data);
  if (payload.fncName !== VisualReward.WEB_BRIDGE_HANDLER) return;
  const webView = webViewRef.current;
  if (!webView) return;
  VisualReward.handleWebBridgeMessage(payload.params ?? {}, event.nativeEvent.url, webView);
}}

url 로 잘못된 값(페이지가 보낸 값, source.uri 등)을 넘기면 오리진 검증이 조용히 무력화됩니다. 그래서 hostWebViewOnMessage 를 권장합니다 — 위 네 가지를 SDK 가 처리합니다. :::

웹: postMessage 호출

window.ReactNativeWebView.postMessage(JSON.stringify({
  fncName: 'visualRewardBridge',
  params: {
    userId: 'u-123',            // 필수: 사용자 식별자
    serviceName: 'anyService',  // 필수: 서비스 식별자
    locale: 'ko',               // 선택: 기본 "ko"
    extra: '',                  // 선택: 포스트백 추가 데이터(JSON serialize된 문자열)
  },
}));

| 파라미터 | 타입 | 필수 | 설명 | 기본값 | | --- | --- | :---: | --- | --- | | userId | string | ✔ | 사용자 식별자 | | | serviceName | string | ✔ | 서비스 식별자. 사용할 값은 별도로 전달드립니다. 값이 빈 문자열이면 사이트 루트로 진입합니다(키를 생략하거나 문자열이 아니면 INVALID_PAYLOAD). | | | locale | string | | BCP 47 언어 서브태그 | "ko" | | extra | string | | 포스트백 추가 데이터(JSON serialize된 문자열) | |

응답 콜백은 페이지에서 전역 객체로 등록합니다.

window.visualRewardCallback = {
  onClose: function () {},                // 리워드 웹뷰 닫힘
  onError: function (code, message) {},
};

onErrorcode는 네이티브 에러 타입에 대응합니다.

| code | 의미 | | --- | --- | | NETWORK_ERROR | 설정 조회 네트워크 오류 | | INITIALIZE_TIMEOUT | open() 이 설정 조회를 timeoutMs 까지 기다렸으나 오지 않음. initialize() 의 실패 콜백으로는 오지 않습니다 | | INVALID_APP_KEY | 존재하지 않는 app_key | | SERVER_ERROR | 설정 서버 오류 (그 외 2xx 아닌 응답) | | PARSE_ERROR | 설정 응답 파싱 실패 | | NOT_INITIALIZED | 앱이 initialize()를 호출하기 전에 열기 시도 | | USER_ID_NOT_SET | userId 누락 또는 빈 문자열 | | OPEN_FAILED | 리워드 화면 진입 실패 | | INVALID_PAYLOAD | serviceName 누락 (브릿지 자체 검증). userId 누락은 USER_ID_NOT_SET 으로 옵니다. 깨진 JSON 은 호스트의 onMessage 에서 먼저 걸러지므로 이 코드로 오지 않습니다. |

네이티브 광고 설정

네이티브 광고를 서빙하는 데 필요한 설정입니다. App ID는 광고 요청에, app-ads.txt는 이 앱의 광고 인벤토리를 누가 판매할 수 있는지 선언하는 데 쓰입니다. 둘 다 없으면 광고가 채워지지 않습니다.

App ID

AdMob/Ad Manager App ID가 필요합니다. 없다면 요청해주세요. 양 플랫폼 모두에 선언합니다.

app.json 에 넣는 것을 권장합니다. react-native-google-mobile-ads 가 이 값을 읽어 Android 는 매니페스트 placeholder 를, iOS 는 빌드된 앱의 Info.plist(GADApplicationIdentifier)를 채워줍니다. 양 플랫폼을 한 곳에서 관리할 수 있고, 아래의 매니페스트 병합 충돌도 생기지 않습니다.

// app.json
{
  "react-native-google-mobile-ads": {
    "android_app_id": "ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy",
    "ios_app_id": "ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"
  }
}

네이티브 파일에 직접 선언하려면 아래와 같이 합니다.

<!-- android/app/src/main/AndroidManifest.xml -->
<!-- manifest 태그에 xmlns:tools="http://schemas.android.com/tools" 가 필요합니다 -->
<meta-data
    android:name="com.google.android.gms.ads.APPLICATION_ID"
    android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"
    tools:replace="android:value" />
<!-- ios/<프로젝트>/Info.plist -->
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy</string>

::: warning Android 는 tools:replace="android:value" 가 필요합니다. react-native-google-mobile-ads 가 라이브러리 매니페스트에서 같은 meta-data 를 이미 선언하므로 (app.json 에 값이 없으면 빈 값), 값이 다르면 매니페스트 병합이 실패합니다.

Manifest merger failed : Attribute meta-data#com.google.android.gms.ads.APPLICATION_ID@value
  value=(ca-app-pub-...) from AndroidManifest.xml
  is also present at [:react-native-google-mobile-ads] AndroidManifest.xml value=()

또한 iOS 에서 app.jsonios_app_id 를 쓰면 그 패키지의 빌드 페이즈가 Info.plist 에 값을 주입하므로, Info.plist 에 직접 적은 값과 둘 중 하나만 쓰세요. :::

app-ads.txt

앱 스토어 등록정보에 있는 개발자 웹사이트의 도메인 루트app-ads.txt 를 아래 내용 그대로 게시해주세요(https://<개발자-웹사이트>/app-ads.txt).

::: warning 이미 app-ads.txt 를 운영 중이라면 기존 줄을 지우지 말고 아래 목록을 덧붙이세요. 중복 줄은 제거해도 됩니다. 목록이 갱신되면 다시 전달드립니다. :::

::: details app-ads.txt 전체 내용 (127줄) <<< @/public/app-ads.txt :::

문제 해결

에러 타입

onFailure/onError 콜백에서 e.code 로 분기합니다.

| 타입 | 설명 | 전달 경로 | | --- | --- | --- | | NETWORK_ERROR | 네트워크 연결 실패 | onFailure / open()onError | | INITIALIZE_TIMEOUT | open() 이 설정을 timeoutMs 까지 기다렸으나 오지 않음 | open()onError | | INVALID_APP_KEY | 존재하지 않는 app_key | onFailure / onError | | SERVER_ERROR | 설정 서버 오류 | onFailure / onError | | PARSE_ERROR | 설정 응답 파싱 실패 | onFailure / onError | | NOT_INITIALIZED | initialize() 호출 없이 open() 호출 | onError | | USER_ID_NOT_SET | open()userId가 빈 문자열 | onError | | OPEN_FAILED | 리워드 화면 진입 실패 (예: <VisualRewardContainer /> 미마운트) | onError |

디폴트 에러 페이지 code

showDefaultErrorUi 가 켜져 있을 때 뜨는 화면의 코드입니다. 문구는 사용자가 취할 수 있는 행동 기준으로 3종이고, 화면 하단의 code원인별로 다릅니다. 사용자가 알려준 코드로 원인을 좁힐 수 있습니다.

| code | 원인 | 안내 문구 | | --- | --- | --- | | N01 | open()userId 가 비어 있음 | 필수 정보가 존재하지 않습니다. | | N02 | open JS 인터페이스 요청 페이로드 오류 | 〃 | | N03 | 네트워크 오류(DNS·연결 실패), 잘못된 serviceName 혹은 누락, 페이지 로드 실패 | 네트워크에 연결할 수 없습니다. | | N04 | open() 이 설정 조회를 timeoutMs 안에 받지 못함 | 〃 | | N05 | initialize()open() 호출 | 일시적인 오류가 발생했습니다. (앱 재시작 안내) | | N06 | 설정 서버 오류 | 일시적인 오류가 발생했습니다. | | N07 | 설정 응답 파싱 실패 | 〃 | | N08 | 존재하지 않는 앱 키 | 〃 | | N09 | <VisualRewardContainer /> 가 마운트돼 있지 않음 | 〃 |

증상별 점검

::: details 광고가 나오지 않아요 react-native-google-mobile-ads 선언 여부와 버전(14.7.0 이상), 양 플랫폼의 App ID를 확인하세요. 비활성 사유는 VisualReward 로그 태그에 남습니다. :::

::: details 네이티브 광고만 서빙되지 않아요 호스트가 mobileAds().initialize() 를 직접 호출하고 있는지 확인하세요. 광고 엔진 초기화는 SDK 가 담당합니다. :::

::: details 앱 시작 시 Invalid application ID 로 크래시해요 App ID 없이 쓰는 구성인데 tools:node="remove" 를 넣지 않은 경우입니다. AndroidManifest 설정을 참고하세요. :::

::: details Manifest merger failed 로 Android 빌드가 안 돼요 App ID 를 앱 매니페스트에 직접 선언했다면 tools:replace="android:value" 가 필요합니다. App ID를 참고하세요. :::

::: details iOS 빌드가 Pods/fmt 의 consteval 에러로 안 돼요 call to consteval function … is not a constant expressionSDK 와 무관한 RN 툴체인 문제입니다. RN 이 고정한 fmt 버전이 Xcode 26 의 clang 에서 컴파일되지 않습니다(핀은 릴리스마다 다릅니다). fmt 12.1.0 을 고정한 RN 릴리스로 올리거나, RN 쪽 이슈를 따라가세요. :::

::: details OPEN_FAILED 에러가 발생해요 앱 루트에 <VisualRewardContainer /> 가 마운트돼 있는지 확인하세요. :::

::: details NOT_INITIALIZED 에러가 발생해요 initialize() 성공 이후에 open()·브릿지를 호출하세요. :::

::: details NETWORK_ERROR 에러가 발생해요 설정 조회가 네트워크 단계에서 실패한 경우입니다. VisualReward 로그의 원인을 먼저 확인하세요.

호스트를 찾지 못했다는 오류(Unable to resolve host / cannot find host)면 DNS 조회 실패입니다. 서버 장애가 아니라 기기 쪽 문제이므로 다음을 확인하세요.

  • 기기의 비공개 DNS(Private DNS) 설정
  • 광고 차단 앱 · VPN 사용 여부 (도메인에 reward 가 포함돼 차단 목록에 걸리는 사례가 있습니다)
  • 사내망 · 학교망 프록시 정책
  • 브라우저에서 https://visual.reward.tenqube.com 접속 가능 여부

일시적인 실패라면 다음 open() 이 설정을 자동으로 다시 조회하므로 앱 재시작 없이 회복됩니다. :::

해결되지 않으면 setDebugEnabled(true) 로 로그를 켠 뒤 VisualReward 태그 로그와 함께 문의해주세요.