웹앱 연동
웹앱에 대해서는 문서를 참고해주세요.
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
- 위 소스 코드의
useHackleWebviewManager를 프로젝트에 추가합니다. useHackleWebviewManagerhook을 통해 WebView에 prop을 전달합니다.
ReactNative WebView JS Injection
| 구분 | 설명 |
|---|---|
hackleInjectedJavaScript | 웹뷰의 JavaScript SDK가 React Native SDK에게 기능을 대신 처리하도록 메세지를 전달합니다. |
onHackleMessage | javaScript 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 지원
github 의 react-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>
);
}
Updated 4 days ago
