웹앱 연동

React Native SDK 3.26.0 버전 이상, Javascript SDK 11.46.0 버전 이상 사용을 권장합니다.
📘

웹앱에 대해서는 문서를 참고해주세요.

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.2

  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

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,Decision as t,DecisionReason as r,FeatureFlagDecision as n}from"@hackler/javascript-sdk";import{v4 as o}from"uuid";var s=function(e,t){return s=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},s(e,t)};function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}s(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function u(e,t,r,n){return new(r||(r=Promise))(function(o,s){function i(e){try{c(n.next(e))}catch(e){s(e)}}function u(e){try{c(n.throw(e))}catch(e){s(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r(function(e){e(t)})).then(i,u)}c((n=n.apply(e,t||[])).next())})}function c(e,t){var r,n,o,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=u(0),i.throw=u(1),i.return=u(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(r)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(s=0)),s;)try{if(r=1,n&&(o=2&u[0]?n.return:u[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,u[1])).done)return o;switch(n=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return s.label++,{value:u[1],done:!1};case 5:s.label++,n=u[1],u=[0];continue;case 7:u=s.ops.pop(),s.trys.pop();continue;default:if(!(o=s.trys,(o=o.length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){s=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){s.label=u[1];break}if(6===u[0]&&s.label<o[1]){s.label=o[1],o=u;break}if(o&&s.label<o[2]){s.label=o[2],s.ops.push(u);break}o[2]&&s.ops.pop(),s.trys.pop();continue}u=t.call(e,s)}catch(e){u=[6,e],n=0}finally{r=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}}"function"==typeof SuppressedError&&SuppressedError;var a=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}(),p=function(){function e(e){this.parameters=e,this.parameters=e}return e.prototype.get=function(e,t){var r=this.parameters[e];return null==r?t:null==t||typeof r==typeof t?r:t},e}(),f=function(){function e(e){this.configFetcher=e,this.configFetcher=e}return e.prototype.get=function(e,t){return u(this,void 0,void 0,function(){var r,n,o;return c(this,function(s){switch(s.label){case 0:return s.trys.push([0,2,,3]),[4,this.configFetcher(e,t)];case 1:if(!(r=s.sent()))throw new Error("invoke result data not exists");switch(n=r.configValue,typeof t){case"number":return[2,Number(n)];case"boolean":return[2,Boolean(n)];default:return[2,n]}case 2:return o=s.sent(),console.error("Unexpected exception while deciding remote config parameter[".concat(e,"]. Returning default value. : ").concat(o)),[2,t];case 3:return[2]}})})},e}();function l(e,t){var r=t.timeoutMillis,n=void 0===r?5e3:r,o=t.onTimeout;return new Promise(function(t,r){e(t,r),setTimeout(function(){o(t,r)},n)})}var d=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,t){if(this.isInjectedEnvironment()){var r=new h(window.ReactNativeWebView);return new v(e,t,r)}return new m(e,t)},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(e){function s(t,r,n){var o=e.call(this)||this;return o.sdkKey=t,o.config=r,o.messageTransceiver=n,o.messageFieldName="_hackle_message",o.resolverRecord=new Map,o.messageTransceiver.addEventListener(function(e){var t,r=e;if(r.data&&"undefined"!==r.data)try{var n=JSON.parse(r.data);if(o.messageFieldName in n){var s=n[o.messageFieldName],i=s.id,u=s.payload;i&&(null===(t=o.resolverRecord.get(i))||void 0===t||t(u),o.resolverRecord.delete(i))}}catch(e){console.log("[DEBUG] Hackle: Failed to parse message. If message not sent by hackle, please ignore this. ".concat(e))}}),o}return i(s,e),s.prototype.onInitialized=function(e){return Promise.resolve({success:!0})},s.prototype.createMessage=function(e,t,r){var n;return JSON.stringify(((n={})[this.messageFieldName]={id:e,type:t,payload:r},n))},s.prototype.createId=function(){return o()},s.prototype.getSessionId=function(){return u(this,void 0,void 0,function(){var e,t=this;return c(this,function(r){switch(r.label){case 0:return e=this.createId(),[4,l(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"getSessionId",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e({sessionId:""})}})];case 1:return[2,r.sent().sessionId]}})})},s.prototype.getUser=function(){return u(this,void 0,void 0,function(){var e,t=this;return c(this,function(r){switch(r.label){case 0:return e=this.createId(),[4,l(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"getUser",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e({user:{}})}})];case 1:return[2,r.sent().user]}})})},s.prototype.emitUserUpdated=function(){this.emit("user-updated",JSON.stringify(this.getUser()))},s.prototype.setUser=function(e){return u(this,void 0,void 0,function(){var t,r=this;return c(this,function(n){return t=this.createId(),[2,l(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"setUser",{user:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.setUserId=function(e){return u(this,void 0,void 0,function(){var t,r=this;return c(this,function(n){return t=this.createId(),[2,l(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"setUserId",{userId:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.setDeviceId=function(e){return u(this,void 0,void 0,function(){var t,r=this;return c(this,function(n){return t=this.createId(),[2,l(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"setDeviceId",{deviceId:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.setUserProperty=function(e,t){return u(this,void 0,void 0,function(){var r,n=this;return c(this,function(o){return r=this.createId(),[2,l(function(o){n.messageTransceiver.port.postMessage(n.createMessage(r,"setUserProperty",{key:e,value:t})),n.resolverRecord.set(r,o)},{onTimeout:function(e){return e()}}).then(function(){n.emitUserUpdated()})]})})},s.prototype.setUserProperties=function(e){return u(this,void 0,void 0,function(){var t,r=this;return c(this,function(n){return t=this.createId(),[2,l(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"setUserProperties",{properties:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.updateUserProperties=function(e){return u(this,void 0,void 0,function(){var t,r=this;return c(this,function(n){return t=this.createId(),[2,l(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"updateUserProperties",{operations:e.toRecord()})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.updatePushSubscriptions=function(e){var t=this,r=this.createId();return l(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updatePushSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.updateSmsSubscriptions=function(e){var t=this,r=this.createId();return l(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateSmsSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.updateKakaoSubscriptions=function(e){var t=this,r=this.createId();return l(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateKakaoSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.setPhoneNumber=function(e){var t=this,r=this.createId();return l(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setPhoneNumber",{phoneNumber:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.unsetPhoneNumber=function(){var e=this,t=this.createId();return l(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"unsetPhoneNumber",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e()}})},s.prototype.resetUser=function(){return u(this,void 0,void 0,function(){var e,t=this;return c(this,function(r){return e=this.createId(),[2,l(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"resetUser",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e()}}).then(function(){t.emitUserUpdated()})]})})},s.prototype.variation=function(e){return u(this,void 0,void 0,function(){var t,r=this;return c(this,function(n){switch(n.label){case 0:return t=this.createId(),[4,l(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"variation",{experimentKey:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e({variation:"A"})}})];case 1:return[2,n.sent().variation]}})})},s.prototype.variationDetail=function(e){return u(this,void 0,void 0,function(){var n,o,s,i,u,a=this;return c(this,function(c){switch(c.label){case 0:n=this.createId(),c.label=1;case 1:return c.trys.push([1,3,,4]),[4,l(function(t){a.messageTransceiver.port.postMessage(a.createMessage(n,"variationDetail",{experimentKey:e})),a.resolverRecord.set(n,t)},{onTimeout:function(e,t){return t()}})];case 2:return o=c.sent(),s=o.variation,i=o.reason,u=o.parameters,[2,t.of(s,i,new p(null!=u?u:{}))];case 3:return c.sent(),[2,t.of("A",r.EXCEPTION)];case 4:return[2]}})})},s.prototype.isFeatureOn=function(e){return u(this,void 0,void 0,function(){var t,r=this;return c(this,function(n){switch(n.label){case 0:return t=this.createId(),[4,l(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"isFeatureOn",{featureKey:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e({isOn:!1})}})];case 1:return[2,n.sent().isOn]}})})},s.prototype.featureFlagDetail=function(e){return u(this,void 0,void 0,function(){var t,o,s,i,u,a=this;return c(this,function(c){switch(c.label){case 0:t=this.createId(),c.label=1;case 1:return c.trys.push([1,3,,4]),[4,l(function(r){a.messageTransceiver.port.postMessage(a.createMessage(t,"featureFlagDetail",{featureKey:e})),a.resolverRecord.set(t,r)},{onTimeout:function(e,t){return t()}})];case 2:return o=c.sent(),s=o.isOn,i=o.reason,u=o.parameters,[2,new n(s,i,new p(null!=u?u:{}),void 0)];case 3:return c.sent(),[2,n.off(r.EXCEPTION)];case 4:return[2]}})})},s.prototype.track=function(e){var t=this,r=this.createId();return l(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"track",{event:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.trackPageView=function(){return u(this,void 0,void 0,function(){return c(this,function(e){return[2]})})},s.prototype.remoteConfig=function(){var e=this;return new f(function(t,r){var n=e.createId();return l(function(o){e.messageTransceiver.port.postMessage(e.createMessage(n,"remoteConfig",{key:t,defaultValue:r})),e.resolverRecord.set(n,o)},{onTimeout:function(e){return e(r)}})})},s.prototype.showUserExplorer=function(){var e=this,t=this.createId();return l(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"showUserExplorer",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e()}})},s.prototype.hideUserExplorer=function(){return u(this,void 0,void 0,function(){var e,t=this;return c(this,function(r){return e=this.createId(),[2,l(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"hideUserExplorer",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e()}})]})})},s.prototype.fetch=function(){var e=this,t=this.createId();return l(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"fetch",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e()}})},s}(a),m=function(t){function r(r,n){var o=t.call(this)||this;return o.client=e(r,n),o}return i(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 u(this,void 0,void 0,function(){var t=this;return c(this,function(r){return[2,Promise.resolve(this.client.setUser(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.setUserId=function(e){return u(this,void 0,void 0,function(){var t=this;return c(this,function(r){return[2,Promise.resolve(this.client.setUserId(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.setDeviceId=function(e){return u(this,void 0,void 0,function(){var t=this;return c(this,function(r){return[2,Promise.resolve(this.client.setDeviceId(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.setUserProperty=function(e,t){return u(this,void 0,void 0,function(){var r=this;return c(this,function(n){return[2,Promise.resolve(this.client.setUserProperty(e,t)).then(function(){r.emitUserUpdated()})]})})},r.prototype.setUserProperties=function(e){return u(this,void 0,void 0,function(){var t=this;return c(this,function(r){return[2,Promise.resolve(this.client.setUserProperties(e)).then(function(){t.emitUserUpdated()})]})})},r.prototype.updateUserProperties=function(e){return u(this,void 0,void 0,function(){var t=this;return c(this,function(r){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 u(this,void 0,void 0,function(){var e=this;return c(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 f(function(t,r){var n=e.client.remoteConfig().get(t,r);return Promise.resolve({configValue:n})})},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}(a);export{d as default};
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@hackler/javascript-sdk"),require("uuid")):"function"==typeof define&&define.amd?define(["@hackler/javascript-sdk","uuid"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).HackleManager=t(e.Hackle,e.uuid)}(this,function(e,t){"use strict";var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},r(e,t)};function n(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}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function o(e,t,r,n){return new(r||(r=Promise))(function(o,i){function s(e){try{c(n.next(e))}catch(e){i(e)}}function u(e){try{c(n.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r(function(e){e(t)})).then(s,u)}c((n=n.apply(e,t||[])).next())})}function i(e,t){var r,n,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(c){return function(u){if(r)throw new TypeError("Generator is already executing.");for(;s&&(s=0,u[0]&&(i=0)),i;)try{if(r=1,n&&(o=2&u[0]?n.return:u[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,u[1])).done)return o;switch(n=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++,n=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],n=0}finally{r=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}}"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 r=this.parameters[e];return null==r?t:null==t||typeof r==typeof t?r:t},e}(),c=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 r,n,o;return i(this,function(i){switch(i.label){case 0:return i.trys.push([0,2,,3]),[4,this.configFetcher(e,t)];case 1:if(!(r=i.sent()))throw new Error("invoke result data not exists");switch(n=r.configValue,typeof t){case"number":return[2,Number(n)];case"boolean":return[2,Boolean(n)];default:return[2,n]}case 2:return o=i.sent(),console.error("Unexpected exception while deciding remote config parameter[".concat(e,"]. Returning default value. : ").concat(o)),[2,t];case 3:return[2]}})})},e}();function a(e,t){var r=t.timeoutMillis,n=void 0===r?5e3:r,o=t.onTimeout;return new Promise(function(t,r){e(t,r),setTimeout(function(){o(t,r)},n)})}var p=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,t){if(this.isInjectedEnvironment()){var r=new f(window.ReactNativeWebView);return new l(e,t,r)}return new d(e,t)},e}(),f=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}(),l=function(r){function s(e,t,n){var o=r.call(this)||this;return o.sdkKey=e,o.config=t,o.messageTransceiver=n,o.messageFieldName="_hackle_message",o.resolverRecord=new Map,o.messageTransceiver.addEventListener(function(e){var t,r=e;if(r.data&&"undefined"!==r.data)try{var n=JSON.parse(r.data);if(o.messageFieldName in n){var i=n[o.messageFieldName],s=i.id,u=i.payload;s&&(null===(t=o.resolverRecord.get(s))||void 0===t||t(u),o.resolverRecord.delete(s))}}catch(e){console.log("[DEBUG] Hackle: Failed to parse message. If message not sent by hackle, please ignore this. ".concat(e))}}),o}return n(s,r),s.prototype.onInitialized=function(e){return Promise.resolve({success:!0})},s.prototype.createMessage=function(e,t,r){var n;return JSON.stringify(((n={})[this.messageFieldName]={id:e,type:t,payload:r},n))},s.prototype.createId=function(){return t.v4()},s.prototype.getSessionId=function(){return o(this,void 0,void 0,function(){var e,t=this;return i(this,function(r){switch(r.label){case 0:return e=this.createId(),[4,a(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"getSessionId",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e({sessionId:""})}})];case 1:return[2,r.sent().sessionId]}})})},s.prototype.getUser=function(){return o(this,void 0,void 0,function(){var e,t=this;return i(this,function(r){switch(r.label){case 0:return e=this.createId(),[4,a(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"getUser",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e({user:{}})}})];case 1:return[2,r.sent().user]}})})},s.prototype.emitUserUpdated=function(){this.emit("user-updated",JSON.stringify(this.getUser()))},s.prototype.setUser=function(e){return o(this,void 0,void 0,function(){var t,r=this;return i(this,function(n){return t=this.createId(),[2,a(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"setUser",{user:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.setUserId=function(e){return o(this,void 0,void 0,function(){var t,r=this;return i(this,function(n){return t=this.createId(),[2,a(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"setUserId",{userId:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.setDeviceId=function(e){return o(this,void 0,void 0,function(){var t,r=this;return i(this,function(n){return t=this.createId(),[2,a(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"setDeviceId",{deviceId:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.setUserProperty=function(e,t){return o(this,void 0,void 0,function(){var r,n=this;return i(this,function(o){return r=this.createId(),[2,a(function(o){n.messageTransceiver.port.postMessage(n.createMessage(r,"setUserProperty",{key:e,value:t})),n.resolverRecord.set(r,o)},{onTimeout:function(e){return e()}}).then(function(){n.emitUserUpdated()})]})})},s.prototype.setUserProperties=function(e){return o(this,void 0,void 0,function(){var t,r=this;return i(this,function(n){return t=this.createId(),[2,a(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"setUserProperties",{properties:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.updateUserProperties=function(e){return o(this,void 0,void 0,function(){var t,r=this;return i(this,function(n){return t=this.createId(),[2,a(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"updateUserProperties",{operations:e.toRecord()})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e()}}).then(function(){r.emitUserUpdated()})]})})},s.prototype.updatePushSubscriptions=function(e){var t=this,r=this.createId();return a(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updatePushSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.updateSmsSubscriptions=function(e){var t=this,r=this.createId();return a(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateSmsSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.updateKakaoSubscriptions=function(e){var t=this,r=this.createId();return a(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateKakaoSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.setPhoneNumber=function(e){var t=this,r=this.createId();return a(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setPhoneNumber",{phoneNumber:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.unsetPhoneNumber=function(){var e=this,t=this.createId();return a(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"unsetPhoneNumber",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e()}})},s.prototype.resetUser=function(){return o(this,void 0,void 0,function(){var e,t=this;return i(this,function(r){return e=this.createId(),[2,a(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"resetUser",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e()}}).then(function(){t.emitUserUpdated()})]})})},s.prototype.variation=function(e){return o(this,void 0,void 0,function(){var t,r=this;return i(this,function(n){switch(n.label){case 0:return t=this.createId(),[4,a(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"variation",{experimentKey:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e({variation:"A"})}})];case 1:return[2,n.sent().variation]}})})},s.prototype.variationDetail=function(t){return o(this,void 0,void 0,function(){var r,n,o,s,c,p=this;return i(this,function(i){switch(i.label){case 0:r=this.createId(),i.label=1;case 1:return i.trys.push([1,3,,4]),[4,a(function(e){p.messageTransceiver.port.postMessage(p.createMessage(r,"variationDetail",{experimentKey:t})),p.resolverRecord.set(r,e)},{onTimeout:function(e,t){return t()}})];case 2:return n=i.sent(),o=n.variation,s=n.reason,c=n.parameters,[2,e.Decision.of(o,s,new u(null!=c?c:{}))];case 3:return i.sent(),[2,e.Decision.of("A",e.DecisionReason.EXCEPTION)];case 4:return[2]}})})},s.prototype.isFeatureOn=function(e){return o(this,void 0,void 0,function(){var t,r=this;return i(this,function(n){switch(n.label){case 0:return t=this.createId(),[4,a(function(n){r.messageTransceiver.port.postMessage(r.createMessage(t,"isFeatureOn",{featureKey:e})),r.resolverRecord.set(t,n)},{onTimeout:function(e){return e({isOn:!1})}})];case 1:return[2,n.sent().isOn]}})})},s.prototype.featureFlagDetail=function(t){return o(this,void 0,void 0,function(){var r,n,o,s,c,p=this;return i(this,function(i){switch(i.label){case 0:r=this.createId(),i.label=1;case 1:return i.trys.push([1,3,,4]),[4,a(function(e){p.messageTransceiver.port.postMessage(p.createMessage(r,"featureFlagDetail",{featureKey:t})),p.resolverRecord.set(r,e)},{onTimeout:function(e,t){return t()}})];case 2:return n=i.sent(),o=n.isOn,s=n.reason,c=n.parameters,[2,new e.FeatureFlagDecision(o,s,new u(null!=c?c:{}),void 0)];case 3:return i.sent(),[2,e.FeatureFlagDecision.off(e.DecisionReason.EXCEPTION)];case 4:return[2]}})})},s.prototype.track=function(e){var t=this,r=this.createId();return a(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"track",{event:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e()}})},s.prototype.trackPageView=function(){return o(this,void 0,void 0,function(){return i(this,function(e){return[2]})})},s.prototype.remoteConfig=function(){var e=this;return new c(function(t,r){var n=e.createId();return a(function(o){e.messageTransceiver.port.postMessage(e.createMessage(n,"remoteConfig",{key:t,defaultValue:r})),e.resolverRecord.set(n,o)},{onTimeout:function(e){return e(r)}})})},s.prototype.showUserExplorer=function(){var e=this,t=this.createId();return a(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"showUserExplorer",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e()}})},s.prototype.hideUserExplorer=function(){return o(this,void 0,void 0,function(){var e,t=this;return i(this,function(r){return e=this.createId(),[2,a(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"hideUserExplorer",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e()}})]})})},s.prototype.fetch=function(){var e=this,t=this.createId();return a(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"fetch",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e()}})},s}(s),d=function(t){function r(r,n){var o=t.call(this)||this;return o.client=e.createInstance(r,n),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(r){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(r){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(r){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 r=this;return i(this,function(n){return[2,Promise.resolve(this.client.setUserProperty(e,t)).then(function(){r.emitUserUpdated()})]})})},r.prototype.setUserProperties=function(e){return o(this,void 0,void 0,function(){var t=this;return i(this,function(r){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(r){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 c(function(t,r){var n=e.client.remoteConfig().get(t,r);return Promise.resolve({configValue:n})})},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}(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>
  );
}