웹앱 연동 (deprecated)

❗️

이 문서는 Deprecate 되었습니다.
웹앱 연동를 참고하여 웹앱 연동을 해주세요.

WebView를 통해 자사 웹사이트를 랜더링하는 경우, 아래 설정을 통해 웹사이트에 포함된 핵클 JavaScript SDK를 웹사이트 코드 변경없이 핵클 React Native SDK 기능과 동일하게 사용할 수 있습니다.


React Native 웹앱 연동을 위해서 아래의 작업이 필요합니다.

  • React Native SDK에서 WebView를 렌더링하는 시점에 브릿지를 추가합니다.
  • WebView에서 사용하는 JavaScript SDK를 래핑하여 ReactNative 전용 Client를 생성해야합니다.

React Native

React Native SDK가 설치된 클라이언트 사이드에서 아래 작업을 진행해주세요.

Github: 0.0.3

  1. 위 소스 코드의 useHackleWebviewManager 를 프로젝트에 추가합니다.
    1-1. 기존에 사용하던 useHackleWebviewManager가 있다면 스크립트를 덮어써주세요.
  2. useHackleWebviewManager hook을 통해 WebView에 prop을 전달합니다.

ReactNative WebView JS Injection

구분설명
hackleInjectedJavaScript웹뷰의 JavaScript SDK가 React Native SDK에게 기능을 대신 처리하도록 메세지를 전달합니다.
onHackleMessagejavaScript SDK로 부터 전달받은 메세지를 React Native SDK에서 처리합니다.
const hackleClient = createInstance("YOUR_SDK_KEY");

function App() {
  const webviewRef = useRef(null);
  const { hackleInjectedJavaScript, onHackleMessage, isHackleMessageEvent } =
        useHackleWebviewManager({
          postMessage: (message) => webviewRef.current?.postMessage(message),
          hackleClient,
        });

  return (
      <HackleProvider hackleClient={hackleClient}>
        <SafeAreaView>
          <View>
            <WebView
              ref={webviewRef}
              source={{ uri: "web_url" }}
              injectedJavaScriptBeforeContentLoaded={hackleInjectedJavaScript}
              onMessage={(e) => {
                if (isHackleMessageEvent(e)) {
                  onHackleMessage(e.nativeEvent.data);
                  return;
                }
              }}
            />
          </View>
        </SafeAreaView>
      </HackleProvider>
    );
}

웹뷰에서 발생하는 자동 수집 이벤트 연동

JavaScript SDK 11.51.0 이상 버전에서 지원하는 기능입니다.

웹뷰 내 웹사이트에서 발생하는 $page_view$engagement는 비활성화 상태입니다.
웹뷰 브릿지를 설정할 때 HackleWebViewConfig를 설정하여 자동 수집 이벤트를 각각 활성화할 수 있습니다.

const hackleWebViewConfig = new HackleWebViewConfigBuilder()
  .automaticScreenTracking(true)
  .automaticEngagementTracking(true)
  .build();

const { hackleInjectedJavaScript, onHackleMessage, isHackleMessageEvent } =
  useHackleWebviewManager({
    postMessage: (message: string) => webviewRef.current?.postMessage(message),
    hackleClient,
  }, hackleWebViewConfig);

설정 옵션

설정기능기본값지원 브릿지 버전
automaticScreenTracking웹사이트에서 발생해는 $page_view 수집 여부false0.0.3 +
automaticEngagementTracking웹사이트에서 발생하는 engagement 수집 여부false0.0.3 +


JavaScript

JavaScript SDK가 설치 된 웹 사이드에서 아래 작업을 진행해주세요.

ℹ️

JavaScript 브릿지 코드는 다음과 같은 동작을 합니다.

  • JavaScript SDK의 hackleClient와 동일한 인터페이스를 갖는 Wrapper Client를 생성합니다.
  • Web 환경에서 발생하는 track, variation, featureFlag 등의 요청을 받아서 React Native SDK로 비동기 전달합니다.
  • 이를 위한 브릿지 형태의 코드이고, 모든 메서드가 Promise를 반환한다는 점이 기존 hackleClient와의 차이입니다.
🚧

React Native에서 브릿지 작업이 선행되어야합니다.

  • ReactNative WebView에 useHackleWebviewManager 를 적용해야합니다.
  • 위 작업이 선행되지 않은 경우 웹앱 연동이 정상적으로 이루어지지 않습니다.

Github: Full Source Code

import{createInstance as e,DefaultBrowserPropertyProvider as t,WebViewLifecycleCompositeManager as n,Decision as r,DecisionReason as o,FeatureFlagDecision as i}from"@hackler/javascript-sdk";var s=function(e,t){return s=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},s(e,t)};function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}s(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var a=function(){return a=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var o in t=arguments[n])Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o]);return e},a.apply(this,arguments)};function c(e,t,n,r){return new(n||(n=Promise))(function(o,i){function s(e){try{a(r.next(e))}catch(e){i(e)}}function u(e){try{a(r.throw(e))}catch(e){i(e)}}function a(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(s,u)}a((r=r.apply(e,t||[])).next())})}function p(e,t){var n,r,o,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},s=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return s.next=u(0),s.throw=u(1),s.return=u(2),"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function u(u){return function(a){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;s&&(s=0,u[0]&&(i=0)),i;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return i.label++,{value:u[1],done:!1};case 5:i.label++,r=u[1],u=[0];continue;case 7:u=i.ops.pop(),i.trys.pop();continue;default:if(!(o=i.trys,(o=o.length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){i=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){i.label=u[1];break}if(6===u[0]&&i.label<o[1]){i.label=o[1],o=u;break}if(o&&i.label<o[2]){i.label=o[2],i.ops.push(u);break}o[2]&&i.ops.pop(),i.trys.pop();continue}u=t.call(e,i)}catch(e){u=[6,e],r=0}finally{n=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,a])}}}"function"==typeof SuppressedError&&SuppressedError;var f=function(){function e(){this.listeners={}}return e.prototype.on=function(e,t){this.listeners[e]=(this.listeners[e]||[]).concat(t)},e.prototype.off=function(e,t){this.listeners[e]=(this.listeners[e]||[]).filter(function(e){return e!==t})},e.prototype.emit=function(e,t){(this.listeners[e]||[]).forEach(function(e){e(t)})},e}(),l=function(){function e(e){this.parameters=e,this.parameters=e}return e.prototype.get=function(e,t){var n=this.parameters[e];return null==n?t:null==t||typeof n==typeof t?n:t},e}(),d=function(){function e(e){this.configFetcher=e,this.configFetcher=e}return e.prototype.get=function(e,t){return c(this,void 0,void 0,function(){var n,r,o,i;return p(this,function(s){switch(s.label){case 0:return s.trys.push([0,2,,3]),n=typeof t,[4,this.configFetcher(e,t,n)];case 1:if(null==(r=s.sent()))throw new Error("invoke result data not exists");switch(o=r.configValue,n){case"number":return[2,Number(o)];case"boolean":return[2,Boolean(o)];default:return[2,o]}case 2:return i=s.sent(),console.error("Unexpected exception while deciding remote config parameter[".concat(e,"]. Returning default value. : ").concat(i)),[2,t];case 3:return[2]}})})},e}(),h=function(){function e(e){this.port=e,this.cleanUp=function(){}}return e.prototype.addEventListener=function(e){window.addEventListener("message",e,!0),this.cleanUp=function(){return window.removeEventListener("message",e,!0)}},e}(),v=function(){function e(e){this.invocator=e}return e.prototype.onPageStarted=function(e,t){this.track(e,t)},e.prototype.onPageEnded=function(e,t){},e.prototype.track=function(t,n){var r,o={type:"track",payload:{event:{key:e.PAGE_VIEW_EVENT_KEY,properties:(r={},r[e.PAGE_NAME_PROPERTY_KEY]=t.pageTitle,r)}}};this.invocator.invoke(o,{onTimeout:function(){}}).catch(function(){})},e.PAGE_VIEW_EVENT_KEY="$page_view",e.PAGE_NAME_PROPERTY_KEY="$page_name",e}();const y=[];for(let e=0;e<256;++e)y.push((e+256).toString(16).slice(1));let m;const g=new Uint8Array(16);var E={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function w(e,t,n){if(E.randomUUID&&!e)return E.randomUUID();const r=(e=e||{}).random??e.rng?.()??function(){if(!m){if("undefined"==typeof crypto||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");m=crypto.getRandomValues.bind(crypto)}return m(g)}();if(r.length<16)throw new Error("Random bytes length must be >= 16");return r[6]=15&r[6]|64,r[8]=63&r[8]|128,function(e,t=0){return(y[e[t+0]]+y[e[t+1]]+y[e[t+2]]+y[e[t+3]]+"-"+y[e[t+4]]+y[e[t+5]]+"-"+y[e[t+6]]+y[e[t+7]]+"-"+y[e[t+8]]+y[e[t+9]]+"-"+y[e[t+10]]+y[e[t+11]]+y[e[t+12]]+y[e[t+13]]+y[e[t+14]]+y[e[t+15]]).toLowerCase()}(r)}var P=function(){function e(e,t,n,r){this.id=e,this.type=t,this.payload=n,this.browserProperties=r}return e.parseOrNull=function(t){try{if(e.MESSAGE_FIELD_NAME in t){var n=t[e.MESSAGE_FIELD_NAME];return new e(n.id,n.type,n.payload,n.browserProperties)}return null}catch(e){return null}},e.from=function(t,n,r,o){return new e(t,n,r,o)},e.prototype.toDto=function(){var t;return(t={})[e.MESSAGE_FIELD_NAME]={id:this.id,type:this.type,payload:this.payload,browserProperties:this.browserProperties},t},e.MESSAGE_FIELD_NAME="_hackle_message",e}();var U=function(){function e(e,t){var n=this;this.transceiver=e,this.browserPropertyProvider=t,this.pendingResolvers=new Map;this.transceiver.addEventListener(function(e){var t,r=e;if(r.data&&"undefined"!==r.data)try{var o=JSON.parse(r.data),i=P.parseOrNull(o);if(!i)return;var s=i.id,u=i.payload;null===(t=n.pendingResolvers.get(s))||void 0===t||t(u),n.pendingResolvers.delete(s)}catch(e){console.log("[DEBUG] Hackle: Failed to parse message. If message not sent by hackle, please ignore this. ".concat(e))}}),this.cleanup=function(){return n.transceiver.cleanUp()}}return e.prototype.createId=function(){return w()},e.prototype.getBrowserProperties=function(){return this.browserPropertyProvider.getBrowserProperties()},e.prototype.serialize=function(e,t,n,r){void 0===r&&(r={});var o=P.from(e,t,n,a(a({},this.getBrowserProperties()),r));return JSON.stringify(o.toDto())},e.prototype.invoke=function(e,t){var n=this,r=t.timeoutMillis,o=void 0===r?5e3:r,i=t.onTimeout,s=this.createId();return function(e,t){var n=t.timeoutMillis,r=void 0===n?5e3:n,o=t.onTimeout;return new Promise(function(t,n){e(t,n),setTimeout(function(){o(t,n)},r)})}(function(t){n.transceiver.port.postMessage(n.serialize(s,e.type,e.payload,e.browserProperties)),n.pendingResolvers.set(s,t)},{timeoutMillis:o,onTimeout:function(e){return e(i())}})},e}(),b=function(){function e(e,t){this.invocator=e,this.browserPropertyProvider=t}return e.prototype.onEngagement=function(t,n){var r,o={type:"track",payload:{event:{key:e.ENGAGEMENT_EVENT_KEY,properties:(r={},r[e.ENGAGEMENT_PAGE_NAME_KEY]=t.page.pageTitle,r[e.ENGAGEMENT_TIME_PROPERTY_KEY]=t.durationMillis,r)}},browserProperties:this.browserPropertyProvider.getBrowserProperties(t.page)};this.invocator.invoke(o,{onTimeout:function(){}}).catch(function(){})},e.ENGAGEMENT_EVENT_KEY="$engagement",e.ENGAGEMENT_TIME_PROPERTY_KEY="$engagement_time_ms",e.ENGAGEMENT_PAGE_NAME_KEY="$page_name",e}(),k={automaticScreenTracking:!1,automaticEngagementTracking:!1};function T(e,t,n,r,o){var i=function(){var e,t,n,r;try{var o=null===(t=null===(e=window._hackleApp)||void 0===e?void 0:e.getWebViewConfig)||void 0===t?void 0:t.call(e);if(!o)return k;var i=JSON.parse(o);return{automaticScreenTracking:null!==(n=i.automaticScreenTracking)&&void 0!==n?n:k.automaticScreenTracking,automaticEngagementTracking:null!==(r=i.automaticEngagementTracking)&&void 0!==r?r:k.automaticEngagementTracking}}catch(e){console.error("[DEBUG] Hackle: Failed to parse web view config. ".concat(e))}return k}(),s=new U(n,o),u=new v(s),a=new b(s,o);i.automaticScreenTracking&&r.addPageListener(u),i.automaticEngagementTracking&&r.addEngagementListener(a);var c=new N(e,t,s);return r.initialize(),c}var _=function(){function e(){this.injectFlag="_hackle_injected"}return e.prototype.isInjectedEnvironment=function(){return!("undefined"==typeof window||!window.ReactNativeWebView)&&!0===window[this.injectFlag]},e.prototype.createInstance=function(e,r){if(this.isInjectedEnvironment()){var o=new t,i=o.getBrowserProperties().browserName,s=new n("string"==typeof i?i:null);return T(e,r,new h(window.ReactNativeWebView),s,o)}return new I(e,r)},e}(),N=function(e){function t(t,n,r){var o=e.call(this)||this;return o.sdkKey=t,o.config=n,o.messenger=r,o}return u(t,e),t.prototype.onInitialized=function(e){return Promise.resolve({success:!0})},t.prototype.getSessionId=function(){return c(this,void 0,void 0,function(){return p(this,function(e){switch(e.label){case 0:return[4,this.messenger.invoke({type:"getSessionId",payload:null},{onTimeout:function(){return{sessionId:""}}})];case 1:return[2,e.sent().sessionId]}})})},t.prototype.getUser=function(){return c(this,void 0,void 0,function(){return p(this,function(e){switch(e.label){case 0:return[4,this.messenger.invoke({type:"getUser",payload:null},{onTimeout:function(){return{user:{}}}})];case 1:return[2,e.sent().user]}})})},t.prototype.emitUserUpdated=function(){this.emit("user-updated",JSON.stringify(this.getUser()))},t.prototype.setUser=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,this.messenger.invoke({type:"setUser",payload:{user:e}},{onTimeout:function(){}}).then(function(){t.emitUserUpdated()})]})})},t.prototype.setUserId=function(e){return c(this,void 0,void 0,function(){var t,n=this;return p(this,function(r){return t=e,null==e&&(t=null),[2,this.messenger.invoke({type:"setUserId",payload:{userId:t}},{onTimeout:function(){}}).then(function(){n.emitUserUpdated()})]})})},t.prototype.setDeviceId=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,this.messenger.invoke({type:"setDeviceId",payload:{deviceId:e}},{onTimeout:function(){}}).then(function(){t.emitUserUpdated()})]})})},t.prototype.setUserProperty=function(e,t){return c(this,void 0,void 0,function(){var n=this;return p(this,function(r){return[2,this.messenger.invoke({type:"setUserProperty",payload:{key:e,value:t}},{onTimeout:function(){}}).then(function(){n.emitUserUpdated()})]})})},t.prototype.setUserProperties=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,this.messenger.invoke({type:"setUserProperties",payload:{properties:e}},{onTimeout:function(){}}).then(function(){t.emitUserUpdated()})]})})},t.prototype.updateUserProperties=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,this.messenger.invoke({type:"updateUserProperties",payload:{operations:e.toRecord()}},{onTimeout:function(){}}).then(function(){t.emitUserUpdated()})]})})},t.prototype.updatePushSubscriptions=function(e){return this.messenger.invoke({type:"updatePushSubscriptions",payload:{operations:e.toRecord()}},{onTimeout:function(){}})},t.prototype.updateSmsSubscriptions=function(e){return this.messenger.invoke({type:"updateSmsSubscriptions",payload:{operations:e.toRecord()}},{onTimeout:function(){}})},t.prototype.updateKakaoSubscriptions=function(e){return this.messenger.invoke({type:"updateKakaoSubscriptions",payload:{operations:e.toRecord()}},{onTimeout:function(){}})},t.prototype.setPhoneNumber=function(e){return this.messenger.invoke({type:"setPhoneNumber",payload:{phoneNumber:e}},{onTimeout:function(){}})},t.prototype.unsetPhoneNumber=function(){return this.messenger.invoke({type:"unsetPhoneNumber",payload:null},{onTimeout:function(){}})},t.prototype.resetUser=function(){return c(this,void 0,void 0,function(){var e=this;return p(this,function(t){return[2,this.messenger.invoke({type:"resetUser",payload:null},{onTimeout:function(){}}).then(function(){e.emitUserUpdated()})]})})},t.prototype.variation=function(e){return c(this,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return[4,this.messenger.invoke({type:"variation",payload:{experimentKey:e}},{onTimeout:function(){return{variation:"A"}}})];case 1:return[2,t.sent().variation]}})})},t.prototype.variationDetail=function(e){return c(this,void 0,void 0,function(){var t,n,i,s;return p(this,function(u){switch(u.label){case 0:return u.trys.push([0,2,,3]),[4,this.messenger.invoke({type:"variationDetail",payload:{experimentKey:e}},{onTimeout:function(){throw new Error("timeout")}})];case 1:return t=u.sent(),n=t.variation,i=t.reason,s=t.parameters,[2,r.of(n,i,new l(null!=s?s:{}))];case 2:return u.sent(),[2,r.of("A",o.EXCEPTION)];case 3:return[2]}})})},t.prototype.isFeatureOn=function(e){return c(this,void 0,void 0,function(){return p(this,function(t){switch(t.label){case 0:return[4,this.messenger.invoke({type:"isFeatureOn",payload:{featureKey:e}},{onTimeout:function(){return{isOn:!1}}})];case 1:return[2,t.sent().isOn]}})})},t.prototype.featureFlagDetail=function(e){return c(this,void 0,void 0,function(){var t,n,r,s;return p(this,function(u){switch(u.label){case 0:return u.trys.push([0,2,,3]),[4,this.messenger.invoke({type:"featureFlagDetail",payload:{featureKey:e}},{onTimeout:function(){throw new Error("timeout")}})];case 1:return t=u.sent(),n=t.isOn,r=t.reason,s=t.parameters,[2,new i(n,r,new l(null!=s?s:{}),void 0)];case 2:return u.sent(),[2,i.off(o.EXCEPTION)];case 3:return[2]}})})},t.prototype.track=function(e){return this.messenger.invoke({type:"track",payload:{event:e}},{onTimeout:function(){}})},t.prototype.trackPageView=function(){return c(this,void 0,void 0,function(){return p(this,function(e){return[2]})})},t.prototype.remoteConfig=function(){var e=this;return new d(function(t,n,r){return e.messenger.invoke({type:"remoteConfig",payload:{key:t,defaultValue:n,valueType:r}},{onTimeout:function(){return n}})})},t.prototype.showUserExplorer=function(){return this.messenger.invoke({type:"showUserExplorer",payload:null},{onTimeout:function(){}})},t.prototype.hideUserExplorer=function(){return c(this,void 0,void 0,function(){return p(this,function(e){return[2,this.messenger.invoke({type:"hideUserExplorer",payload:null},{onTimeout:function(){}})]})})},t.prototype.fetch=function(){return this.messenger.invoke({type:"fetch",payload:null},{onTimeout:function(){}})},t.prototype.getDisplayedInAppMessage=function(){return console.log("[DEBUG] Hackle: getDisplayedInAppMessage is not supported in React Native WebView environment."),Promise.resolve(null)},t}(f),I=function(t){function n(n,r){var o=t.call(this)||this;return o.client=e(n,r),o}return u(n,t),n.prototype.emitUserUpdated=function(){this.emit("user-updated",JSON.stringify(this.getUser()))},n.prototype.getSessionId=function(){return Promise.resolve(this.client.getSessionId())},n.prototype.getUser=function(){return Promise.resolve(this.client.getUser())},n.prototype.setUser=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,Promise.resolve(this.client.setUser(e)).then(function(){t.emitUserUpdated()})]})})},n.prototype.setUserId=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,Promise.resolve(this.client.setUserId(e)).then(function(){t.emitUserUpdated()})]})})},n.prototype.setDeviceId=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,Promise.resolve(this.client.setDeviceId(e)).then(function(){t.emitUserUpdated()})]})})},n.prototype.setUserProperty=function(e,t){return c(this,void 0,void 0,function(){var n=this;return p(this,function(r){return[2,Promise.resolve(this.client.setUserProperty(e,t)).then(function(){n.emitUserUpdated()})]})})},n.prototype.setUserProperties=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,Promise.resolve(this.client.setUserProperties(e)).then(function(){t.emitUserUpdated()})]})})},n.prototype.updateUserProperties=function(e){return c(this,void 0,void 0,function(){var t=this;return p(this,function(n){return[2,Promise.resolve(this.client.updateUserProperties(e)).then(function(){t.emitUserUpdated()})]})})},n.prototype.updatePushSubscriptions=function(e){return Promise.resolve(this.client.updatePushSubscriptions(e))},n.prototype.updateSmsSubscriptions=function(e){return Promise.resolve(this.client.updateSmsSubscriptions(e))},n.prototype.updateKakaoSubscriptions=function(e){return Promise.resolve(this.client.updateKakaoSubscriptions(e))},n.prototype.setPhoneNumber=function(e){return Promise.resolve(this.client.setPhoneNumber(e))},n.prototype.unsetPhoneNumber=function(){return Promise.resolve(this.client.unsetPhoneNumber())},n.prototype.resetUser=function(){return c(this,void 0,void 0,function(){var e=this;return p(this,function(t){return[2,Promise.resolve(this.client.resetUser()).then(function(){e.emitUserUpdated()})]})})},n.prototype.variation=function(e){return Promise.resolve(this.client.variation(e))},n.prototype.variationDetail=function(e){return Promise.resolve(this.client.variationDetail(e))},n.prototype.isFeatureOn=function(e){return Promise.resolve(this.client.isFeatureOn(e))},n.prototype.featureFlagDetail=function(e){return Promise.resolve(this.client.featureFlagDetail(e))},n.prototype.track=function(e){return Promise.resolve(this.client.track(e))},n.prototype.trackPageView=function(e){return Promise.resolve(this.client.trackPageView(e))},n.prototype.remoteConfig=function(){var e=this;return new d(function(t,n,r){var o=e.client.remoteConfig().get(t,n);return Promise.resolve({configValue:o})})},n.prototype.showUserExplorer=function(){return Promise.resolve(this.client.showUserExplorer())},n.prototype.hideUserExplorer=function(){return Promise.resolve(this.client.hideUserExplorer())},n.prototype.fetch=function(){return Promise.resolve(this.client.fetch())},n.prototype.onInitialized=function(e){return this.client.onInitialized(e)},n.prototype.getDisplayedInAppMessage=function(){return Promise.resolve(this.client.getDisplayedInAppMessageView())},n}(f);export{_ as default};
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@hackler/javascript-sdk")):"function"==typeof define&&define.amd?define(["@hackler/javascript-sdk"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).HackleManager=t(e.Hackle)}(this,function(e){"use strict";var t=function(e,n){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},t(e,n)};function n(e,n){if("function"!=typeof n&&null!==n)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");function r(){this.constructor=e}t(e,n),e.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}var r=function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var o in t=arguments[n])Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o]);return e},r.apply(this,arguments)};function o(e,t,n,r){return new(n||(n=Promise))(function(o,i){function s(e){try{a(r.next(e))}catch(e){i(e)}}function u(e){try{a(r.throw(e))}catch(e){i(e)}}function a(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n(function(e){e(t)})).then(s,u)}a((r=r.apply(e,t||[])).next())})}function i(e,t){var n,r,o,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},s=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return s.next=u(0),s.throw=u(1),s.return=u(2),"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function u(u){return function(a){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;s&&(s=0,u[0]&&(i=0)),i;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return i.label++,{value:u[1],done:!1};case 5:i.label++,r=u[1],u=[0];continue;case 7:u=i.ops.pop(),i.trys.pop();continue;default:if(!(o=i.trys,(o=o.length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){i=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){i.label=u[1];break}if(6===u[0]&&i.label<o[1]){i.label=o[1],o=u;break}if(o&&i.label<o[2]){i.label=o[2],i.ops.push(u);break}o[2]&&i.ops.pop(),i.trys.pop();continue}u=t.call(e,i)}catch(e){u=[6,e],r=0}finally{n=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,a])}}}"function"==typeof SuppressedError&&SuppressedError;var s=function(){function e(){this.listeners={}}return e.prototype.on=function(e,t){this.listeners[e]=(this.listeners[e]||[]).concat(t)},e.prototype.off=function(e,t){this.listeners[e]=(this.listeners[e]||[]).filter(function(e){return e!==t})},e.prototype.emit=function(e,t){(this.listeners[e]||[]).forEach(function(e){e(t)})},e}(),u=function(){function e(e){this.parameters=e,this.parameters=e}return e.prototype.get=function(e,t){var n=this.parameters[e];return null==n?t:null==t||typeof n==typeof t?n:t},e}(),a=function(){function e(e){this.configFetcher=e,this.configFetcher=e}return e.prototype.get=function(e,t){return o(this,void 0,void 0,function(){var n,r,o,s;return i(this,function(i){switch(i.label){case 0:return i.trys.push([0,2,,3]),n=typeof t,[4,this.configFetcher(e,t,n)];case 1:if(null==(r=i.sent()))throw new Error("invoke result data not exists");switch(o=r.configValue,n){case"number":return[2,Number(o)];case"boolean":return[2,Boolean(o)];default:return[2,o]}case 2:return s=i.sent(),console.error("Unexpected exception while deciding remote config parameter[".concat(e,"]. Returning default value. : ").concat(s)),[2,t];case 3:return[2]}})})},e}(),c=function(){function e(e){this.port=e,this.cleanUp=function(){}}return e.prototype.addEventListener=function(e){window.addEventListener("message",e,!0),this.cleanUp=function(){return window.removeEventListener("message",e,!0)}},e}(),p=function(){function e(e){this.invocator=e}return e.prototype.onPageStarted=function(e,t){this.track(e,t)},e.prototype.onPageEnded=function(e,t){},e.prototype.track=function(t,n){var r,o={type:"track",payload:{event:{key:e.PAGE_VIEW_EVENT_KEY,properties:(r={},r[e.PAGE_NAME_PROPERTY_KEY]=t.pageTitle,r)}}};this.invocator.invoke(o,{onTimeout:function(){}}).catch(function(){})},e.PAGE_VIEW_EVENT_KEY="$page_view",e.PAGE_NAME_PROPERTY_KEY="$page_name",e}();const f=[];for(let e=0;e<256;++e)f.push((e+256).toString(16).slice(1));let l;const d=new Uint8Array(16);var h={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function v(e,t,n){if(h.randomUUID&&!e)return h.randomUUID();const r=(e=e||{}).random??e.rng?.()??function(){if(!l){if("undefined"==typeof crypto||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");l=crypto.getRandomValues.bind(crypto)}return l(d)}();if(r.length<16)throw new Error("Random bytes length must be >= 16");return r[6]=15&r[6]|64,r[8]=63&r[8]|128,function(e,t=0){return(f[e[t+0]]+f[e[t+1]]+f[e[t+2]]+f[e[t+3]]+"-"+f[e[t+4]]+f[e[t+5]]+"-"+f[e[t+6]]+f[e[t+7]]+"-"+f[e[t+8]]+f[e[t+9]]+"-"+f[e[t+10]]+f[e[t+11]]+f[e[t+12]]+f[e[t+13]]+f[e[t+14]]+f[e[t+15]]).toLowerCase()}(r)}var y=function(){function e(e,t,n,r){this.id=e,this.type=t,this.payload=n,this.browserProperties=r}return e.parseOrNull=function(t){try{if(e.MESSAGE_FIELD_NAME in t){var n=t[e.MESSAGE_FIELD_NAME];return new e(n.id,n.type,n.payload,n.browserProperties)}return null}catch(e){return null}},e.from=function(t,n,r,o){return new e(t,n,r,o)},e.prototype.toDto=function(){var t;return(t={})[e.MESSAGE_FIELD_NAME]={id:this.id,type:this.type,payload:this.payload,browserProperties:this.browserProperties},t},e.MESSAGE_FIELD_NAME="_hackle_message",e}();var m=function(){function e(e,t){var n=this;this.transceiver=e,this.browserPropertyProvider=t,this.pendingResolvers=new Map;this.transceiver.addEventListener(function(e){var t,r=e;if(r.data&&"undefined"!==r.data)try{var o=JSON.parse(r.data),i=y.parseOrNull(o);if(!i)return;var s=i.id,u=i.payload;null===(t=n.pendingResolvers.get(s))||void 0===t||t(u),n.pendingResolvers.delete(s)}catch(e){console.log("[DEBUG] Hackle: Failed to parse message. If message not sent by hackle, please ignore this. ".concat(e))}}),this.cleanup=function(){return n.transceiver.cleanUp()}}return e.prototype.createId=function(){return v()},e.prototype.getBrowserProperties=function(){return this.browserPropertyProvider.getBrowserProperties()},e.prototype.serialize=function(e,t,n,o){void 0===o&&(o={});var i=y.from(e,t,n,r(r({},this.getBrowserProperties()),o));return JSON.stringify(i.toDto())},e.prototype.invoke=function(e,t){var n=this,r=t.timeoutMillis,o=void 0===r?5e3:r,i=t.onTimeout,s=this.createId();return function(e,t){var n=t.timeoutMillis,r=void 0===n?5e3:n,o=t.onTimeout;return new Promise(function(t,n){e(t,n),setTimeout(function(){o(t,n)},r)})}(function(t){n.transceiver.port.postMessage(n.serialize(s,e.type,e.payload,e.browserProperties)),n.pendingResolvers.set(s,t)},{timeoutMillis:o,onTimeout:function(e){return e(i())}})},e}(),g=function(){function e(e,t){this.invocator=e,this.browserPropertyProvider=t}return e.prototype.onEngagement=function(t,n){var r,o={type:"track",payload:{event:{key:e.ENGAGEMENT_EVENT_KEY,properties:(r={},r[e.ENGAGEMENT_PAGE_NAME_KEY]=t.page.pageTitle,r[e.ENGAGEMENT_TIME_PROPERTY_KEY]=t.durationMillis,r)}},browserProperties:this.browserPropertyProvider.getBrowserProperties(t.page)};this.invocator.invoke(o,{onTimeout:function(){}}).catch(function(){})},e.ENGAGEMENT_EVENT_KEY="$engagement",e.ENGAGEMENT_TIME_PROPERTY_KEY="$engagement_time_ms",e.ENGAGEMENT_PAGE_NAME_KEY="$page_name",e}(),E={automaticScreenTracking:!1,automaticEngagementTracking:!1};function w(e,t,n,r,o){var i=function(){var e,t,n,r;try{var o=null===(t=null===(e=window._hackleApp)||void 0===e?void 0:e.getWebViewConfig)||void 0===t?void 0:t.call(e);if(!o)return E;var i=JSON.parse(o);return{automaticScreenTracking:null!==(n=i.automaticScreenTracking)&&void 0!==n?n:E.automaticScreenTracking,automaticEngagementTracking:null!==(r=i.automaticEngagementTracking)&&void 0!==r?r:E.automaticEngagementTracking}}catch(e){console.error("[DEBUG] Hackle: Failed to parse web view config. ".concat(e))}return E}(),s=new m(n,o),u=new p(s),a=new g(s,o);i.automaticScreenTracking&&r.addPageListener(u),i.automaticEngagementTracking&&r.addEngagementListener(a);var c=new U(e,t,s);return r.initialize(),c}var P=function(){function t(){this.injectFlag="_hackle_injected"}return t.prototype.isInjectedEnvironment=function(){return!("undefined"==typeof window||!window.ReactNativeWebView)&&!0===window[this.injectFlag]},t.prototype.createInstance=function(t,n){if(this.isInjectedEnvironment()){var r=new e.DefaultBrowserPropertyProvider,o=r.getBrowserProperties().browserName,i=new e.WebViewLifecycleCompositeManager("string"==typeof o?o:null);return w(t,n,new c(window.ReactNativeWebView),i,r)}return new b(t,n)},t}(),U=function(t){function r(e,n,r){var o=t.call(this)||this;return o.sdkKey=e,o.config=n,o.messenger=r,o}return n(r,t),r.prototype.onInitialized=function(e){return Promise.resolve({success:!0})},r.prototype.getSessionId=function(){return o(this,void 0,void 0,function(){return i(this,function(e){switch(e.label){case 0:return[4,this.messenger.invoke({type:"getSessionId",payload:null},{onTimeout:function(){return{sessionId:""}}})];case 1:return[2,e.sent().sessionId]}})})},r.prototype.getUser=function(){return o(this,void 0,void 0,function(){return i(this,function(e){switch(e.label){case 0:return[4,this.messenger.invoke({type:"getUser",payload:null},{onTimeout:function(){return{user:{}}}})];case 1:return[2,e.sent().user]}})})},r.prototype.emitUserUpdated=function(){this.emit("user-updated",JSON.stringify(this.getUser()))},r.prototype.setUser=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,this.messenger.invoke({type:"setUser",payload:{user:e}},{onTimeout:function(){}}).then(function(){t.emitUserUpdated()})]})})},r.prototype.setUserId=function(e){return o(this,void 0,void 0,function(){var t,n=this;return i(this,function(r){return t=e,null==e&&(t=null),[2,this.messenger.invoke({type:"setUserId",payload:{userId:t}},{onTimeout:function(){}}).then(function(){n.emitUserUpdated()})]})})},r.prototype.setDeviceId=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,this.messenger.invoke({type:"setDeviceId",payload:{deviceId:e}},{onTimeout:function(){}}).then(function(){t.emitUserUpdated()})]})})},r.prototype.setUserProperty=function(e,t){return o(this,void 0,void 0,function(){var n=this;return i(this,function(r){return[2,this.messenger.invoke({type:"setUserProperty",payload:{key:e,value:t}},{onTimeout:function(){}}).then(function(){n.emitUserUpdated()})]})})},r.prototype.setUserProperties=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,this.messenger.invoke({type:"setUserProperties",payload:{properties:e}},{onTimeout:function(){}}).then(function(){t.emitUserUpdated()})]})})},r.prototype.updateUserProperties=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,this.messenger.invoke({type:"updateUserProperties",payload:{operations:e.toRecord()}},{onTimeout:function(){}}).then(function(){t.emitUserUpdated()})]})})},r.prototype.updatePushSubscriptions=function(e){return this.messenger.invoke({type:"updatePushSubscriptions",payload:{operations:e.toRecord()}},{onTimeout:function(){}})},r.prototype.updateSmsSubscriptions=function(e){return this.messenger.invoke({type:"updateSmsSubscriptions",payload:{operations:e.toRecord()}},{onTimeout:function(){}})},r.prototype.updateKakaoSubscriptions=function(e){return this.messenger.invoke({type:"updateKakaoSubscriptions",payload:{operations:e.toRecord()}},{onTimeout:function(){}})},r.prototype.setPhoneNumber=function(e){return this.messenger.invoke({type:"setPhoneNumber",payload:{phoneNumber:e}},{onTimeout:function(){}})},r.prototype.unsetPhoneNumber=function(){return this.messenger.invoke({type:"unsetPhoneNumber",payload:null},{onTimeout:function(){}})},r.prototype.resetUser=function(){return o(this,void 0,void 0,function(){var e=this;return i(this,function(t){return[2,this.messenger.invoke({type:"resetUser",payload:null},{onTimeout:function(){}}).then(function(){e.emitUserUpdated()})]})})},r.prototype.variation=function(e){return o(this,void 0,void 0,function(){return i(this,function(t){switch(t.label){case 0:return[4,this.messenger.invoke({type:"variation",payload:{experimentKey:e}},{onTimeout:function(){return{variation:"A"}}})];case 1:return[2,t.sent().variation]}})})},r.prototype.variationDetail=function(t){return o(this,void 0,void 0,function(){var n,r,o,s;return i(this,function(i){switch(i.label){case 0:return i.trys.push([0,2,,3]),[4,this.messenger.invoke({type:"variationDetail",payload:{experimentKey:t}},{onTimeout:function(){throw new Error("timeout")}})];case 1:return n=i.sent(),r=n.variation,o=n.reason,s=n.parameters,[2,e.Decision.of(r,o,new u(null!=s?s:{}))];case 2:return i.sent(),[2,e.Decision.of("A",e.DecisionReason.EXCEPTION)];case 3:return[2]}})})},r.prototype.isFeatureOn=function(e){return o(this,void 0,void 0,function(){return i(this,function(t){switch(t.label){case 0:return[4,this.messenger.invoke({type:"isFeatureOn",payload:{featureKey:e}},{onTimeout:function(){return{isOn:!1}}})];case 1:return[2,t.sent().isOn]}})})},r.prototype.featureFlagDetail=function(t){return o(this,void 0,void 0,function(){var n,r,o,s;return i(this,function(i){switch(i.label){case 0:return i.trys.push([0,2,,3]),[4,this.messenger.invoke({type:"featureFlagDetail",payload:{featureKey:t}},{onTimeout:function(){throw new Error("timeout")}})];case 1:return n=i.sent(),r=n.isOn,o=n.reason,s=n.parameters,[2,new e.FeatureFlagDecision(r,o,new u(null!=s?s:{}),void 0)];case 2:return i.sent(),[2,e.FeatureFlagDecision.off(e.DecisionReason.EXCEPTION)];case 3:return[2]}})})},r.prototype.track=function(e){return this.messenger.invoke({type:"track",payload:{event:e}},{onTimeout:function(){}})},r.prototype.trackPageView=function(){return o(this,void 0,void 0,function(){return i(this,function(e){return[2]})})},r.prototype.remoteConfig=function(){var e=this;return new a(function(t,n,r){return e.messenger.invoke({type:"remoteConfig",payload:{key:t,defaultValue:n,valueType:r}},{onTimeout:function(){return n}})})},r.prototype.showUserExplorer=function(){return this.messenger.invoke({type:"showUserExplorer",payload:null},{onTimeout:function(){}})},r.prototype.hideUserExplorer=function(){return o(this,void 0,void 0,function(){return i(this,function(e){return[2,this.messenger.invoke({type:"hideUserExplorer",payload:null},{onTimeout:function(){}})]})})},r.prototype.fetch=function(){return this.messenger.invoke({type:"fetch",payload:null},{onTimeout:function(){}})},r.prototype.getDisplayedInAppMessage=function(){return console.log("[DEBUG] Hackle: getDisplayedInAppMessage is not supported in React Native WebView environment."),Promise.resolve(null)},r}(s),b=function(t){function r(n,r){var o=t.call(this)||this;return o.client=e.createInstance(n,r),o}return n(r,t),r.prototype.emitUserUpdated=function(){this.emit("user-updated",JSON.stringify(this.getUser()))},r.prototype.getSessionId=function(){return Promise.resolve(this.client.getSessionId())},r.prototype.getUser=function(){return Promise.resolve(this.client.getUser())},r.prototype.setUser=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,Promise.resolve(this.client.setUser(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.setUserId=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,Promise.resolve(this.client.setUserId(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.setDeviceId=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,Promise.resolve(this.client.setDeviceId(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.setUserProperty=function(e,t){return o(this,void 0,void 0,function(){var n=this;return i(this,function(r){return[2,Promise.resolve(this.client.setUserProperty(e,t)).then(function(){n.emitUserUpdated()})]})})},r.prototype.setUserProperties=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,Promise.resolve(this.client.setUserProperties(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.updateUserProperties=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(n){return[2,Promise.resolve(this.client.updateUserProperties(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.updatePushSubscriptions=function(e){return Promise.resolve(this.client.updatePushSubscriptions(e))},r.prototype.updateSmsSubscriptions=function(e){return Promise.resolve(this.client.updateSmsSubscriptions(e))},r.prototype.updateKakaoSubscriptions=function(e){return Promise.resolve(this.client.updateKakaoSubscriptions(e))},r.prototype.setPhoneNumber=function(e){return Promise.resolve(this.client.setPhoneNumber(e))},r.prototype.unsetPhoneNumber=function(){return Promise.resolve(this.client.unsetPhoneNumber())},r.prototype.resetUser=function(){return o(this,void 0,void 0,function(){var e=this;return i(this,function(t){return[2,Promise.resolve(this.client.resetUser()).then(function(){e.emitUserUpdated()})]})})},r.prototype.variation=function(e){return Promise.resolve(this.client.variation(e))},r.prototype.variationDetail=function(e){return Promise.resolve(this.client.variationDetail(e))},r.prototype.isFeatureOn=function(e){return Promise.resolve(this.client.isFeatureOn(e))},r.prototype.featureFlagDetail=function(e){return Promise.resolve(this.client.featureFlagDetail(e))},r.prototype.track=function(e){return Promise.resolve(this.client.track(e))},r.prototype.trackPageView=function(e){return Promise.resolve(this.client.trackPageView(e))},r.prototype.remoteConfig=function(){var e=this;return new a(function(t,n,r){var o=e.client.remoteConfig().get(t,n);return Promise.resolve({configValue:o})})},r.prototype.showUserExplorer=function(){return Promise.resolve(this.client.showUserExplorer())},r.prototype.hideUserExplorer=function(){return Promise.resolve(this.client.hideUserExplorer())},r.prototype.fetch=function(){return Promise.resolve(this.client.fetch())},r.prototype.onInitialized=function(e){return this.client.onInitialized(e)},r.prototype.getDisplayedInAppMessage=function(){return Promise.resolve(this.client.getDisplayedInAppMessageView())},r}(s);return P});

위 소스 코드를 프로젝트에 추가합니다.

  • esm 사용을 권장합니다. default export 형태로 모듈이 제공됩니다.
  • 모듈을 사용할 수 없는 환경이라면 umd를 사용하세요.
    • 전역 스코프에 HackleManager 라는 이름으로 모듈이 제공됩니다.

소스코드 연동

소스 코드를 npm package로 제공하고 있지 않습니다.

아래의 단계별 연동 가이드보다 더 자세한 연동 방법은 아래의 레포지토리에서 예제를 통해 확인해보세요.


TypeScript 지원

githubreact-native-webview-integration-js-bridge 를 build하면 타입 선언 파일 (d.ts)가 emit 됩니다.

react-native-webview-nextjs-integration 를 참고하세요.

lib 디렉토리를 활용해 file dependency를 참조하는 예시를 제공합니다.


인스턴스 생성

아래와 같이 createInstance 를 호출하면 인스턴스를 생성할 수 있습니다.

import 경로는 설치한 환경에 따라 기입해주세요.

  • 생성한 인스턴스를 export 하여 사용합니다.
import HackleManager from "./web-view-integration";

const manager = new HackleManager();
const hackleClient = manager.createInstance(process.env.NEXT_PUBLIC_HACKLE_SDK_KEY!, {
  // debug log toggle
  debug: true
});

export default hackleClient;

메서드 호출

모든 메서드의 반환 타입이 Promise 임을 인지해야 합니다.

지원하는 메서드의 종류는 JavaScript/ReactNative SDK 문서를 참고해주세요.

import hackleClient from './client';

async function getVariation() {
  const variation = await hackleClient.variation(42);
  if (variation === 'A') {
    // if variation is A do something...
  } else {
    // else
  }
}

React/Vue/Next.js를 사용하고 있나요?

❗️

React Native WebView 웹앱연동 환경에서는 react-sdk 가 지원되지 않습니다.

React Native의 WebView 환경에서는 react-sdk를 그대로 사용할 수 없습니다.

React Native WebView 안에서 Vue, React, Next.js 등의 라이브러리/프레임워크를 사용하고 있다면 아래의 가이드를 참고해주세요.

Examples


React

기존의 react-sdk의 Custom hooks와 유사한 형태의 hooks를 만들어서 사용할 수 있습니다.

Example에 포함된 hooks 들을 참고하여 필요한 hooks를 작성해서 사용하세요.


📘

왜 Loading 처리를 해야하나요?

메시지 송수신의 지연시간이 길지는 않으나, 로딩 처리를 하는 것을 권장합니다.

  • 실험(variation)의 결과 값을 받아온 이후에 사용자가 최초에 본 것과 다른 화면을 보게 되는 것을 방지하고자 합니다.

Github: Source

import useVariation from "../hooks/useVariation";

interface VariationTesterProps {}

export default function VariationTester({}: VariationTesterProps) {
  const { data: variation } = useVariation(40, "A");

  return <pre style={{ fontSize: 54 }}>{variation}</pre>;
}
import useFeature from "../hooks/useFeature";

interface FeatureTesterProps {}

export default function FeatureTester({}: FeatureTesterProps) {
  const { data: isOn } = useFeature(40, false);

  return <pre style={{ fontSize: 54 }}>{isOn ? "On" : "Off"}</pre>;
}
import useRemoteConfig from "../hooks/useRemoteConfig";

export default function RemoteConfig() {
  const { config } = useRemoteConfig(
    "targeting_rule_test",
    "String 타입의 기본 값",
  );

  return (
    <pre style={{ fontSize: 54, height: 400, backgroundColor: "#75be6b" }}>
      {config}
    </pre>
  );
}
import hackleClient from "./modules/client";

export default function CustomTracker() {
  return (
    <div>
      <button
        onClick={() =>
          hackleClient.track({
            key: "click_button",
            properties: {
              button_name: "custom",
            },
          })
        }>
        Track Custom Event
      </button>
    </div>
  );
}