์›น์•ฑ ์—ฐ๋™

๐Ÿšง

React Native ์›น์•ฑ ์—ฐ๋™์„ ์œ„ํ•ด์„œ ์•„๋ž˜์˜ ์ž‘์—…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  • React Native SDK์—์„œ WebView๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ์‹œ์ ์— ๋ธŒ๋ฆฟ์ง€๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • WebView์—์„œ ์‚ฌ์šฉํ•˜๋Š” JavaScript SDK๋ฅผ ๋ž˜ํ•‘ํ•˜์—ฌ ReactNative ์ „์šฉ Client๋ฅผ ์ƒ์„ฑํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“˜

์›น์•ฑ์— ๋Œ€ํ•ด์„œ๋Š” ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

WebView๋ฅผ ํ†ตํ•ด ์ž์‚ฌ ์›น์‚ฌ์ดํŠธ๋ฅผ ๋žœ๋”๋งํ•˜๋Š” ๊ฒฝ์šฐ, ์•„๋ž˜ ์„ค์ •์„ ํ†ตํ•ด ์›น์‚ฌ์ดํŠธ์— ํฌํ•จ๋œ ํ•ตํด JavaScript SDK๋ฅผ ์›น์‚ฌ์ดํŠธ ์ฝ”๋“œ ๋ณ€๊ฒฝ์—†์ด ํ•ตํด React Native SDK ๊ธฐ๋Šฅ๊ณผ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


React Native

React Native SDK๊ฐ€ ์„ค์น˜๋œ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ์•„๋ž˜ ์ž‘์—…์„ ์ง„ํ–‰ํ•ด์ฃผ์„ธ์š”.

Github: Source

  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{a(n.next(e))}catch(e){s(e)}}function u(e){try{a(n.throw(e))}catch(e){s(e)}}function a(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r(function(e){e(t)})).then(i,u)}a((n=n.apply(e,t||[])).next())})}function a(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(a){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,a])}}}"function"==typeof SuppressedError&&SuppressedError;var c=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}();function p(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 l=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 d(e,t,r)}return new h(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}(),d=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.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 a(this,function(r){return e=this.createId(),[2,p(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"getSessionId",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e("")}})]})})},s.prototype.getUser=function(){return u(this,void 0,void 0,function(){var e,t=this;return a(this,function(r){return e=this.createId(),[2,p(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"getUser",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e({})}})]})})},s.prototype.setUser=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setUser",{user:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.setUserId=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setUserId",{userId:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.setDeviceId=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setDeviceId",{deviceId:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.setUserProperty=function(e,t){var r=this,n=this.createId();return p(function(o){r.messageTransceiver.port.postMessage(r.createMessage(n,"setUserProperty",{key:e,value:t})),r.resolverRecord.set(n,o)},{onTimeout:function(e){return e(null)}})},s.prototype.setUserProperties=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setUserProperties",{properties:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.updateUserProperties=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateUserProperties",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.updatePushSubscriptions=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updatePushSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.updateSmsSubscriptions=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateSmsSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.updateKakaoSubscriptions=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateKakaoSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.setPhoneNumber=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setPhoneNumber",{phoneNumber:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},s.prototype.unsetPhoneNumber=function(){var e=this,t=this.createId();return p(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"unsetPhoneNumber",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e(null)}})},s.prototype.resetUser=function(){var e=this,t=this.createId();return p(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"resetUser",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e(null)}})},s.prototype.variation=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"variation",{experimentKey:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e("A")}})},s.prototype.variationDetail=function(e){return u(this,void 0,void 0,function(){var n,o,s,i,u,c,l=this;return a(this,function(a){switch(a.label){case 0:n=this.createId(),a.label=1;case 1:return a.trys.push([1,3,,4]),[4,p(function(t){l.messageTransceiver.port.postMessage(l.createMessage(n,"variationDetail",{experimentKey:e})),l.resolverRecord.set(n,t)},{onTimeout:function(e,t){return t()}})];case 2:return o=a.sent(),i=(s=o).variation,u=s.reason,c=s.parameters,[2,t.of(i,u,new v(null!=c?c:{}))];case 3:return a.sent(),[2,t.of("A",r.EXCEPTION)];case 4:return[2]}})})},s.prototype.isFeatureOn=function(e){var t=this,r=this.createId();return p(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"isFeatureOn",{featureKey:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(!1)}})},s.prototype.featureFlagDetail=function(e){return u(this,void 0,void 0,function(){var t,o,s,i,u,c,l=this;return a(this,function(a){switch(a.label){case 0:t=this.createId(),a.label=1;case 1:return a.trys.push([1,3,,4]),[4,p(function(r){l.messageTransceiver.port.postMessage(l.createMessage(t,"featureFlagDetail",{featureKey:e})),l.resolverRecord.set(t,r)},{onTimeout:function(e,t){return t()}})];case 2:return o=a.sent(),i=(s=o).isOn,u=s.reason,c=s.parameters,[2,new n(i,u,new v(null!=c?c:{}),void 0)];case 3:return a.sent(),[2,n.off(r.EXCEPTION)];case 4:return[2]}})})},s.prototype.track=function(e,t){var r=this,n=this.createId();return p(function(o){r.messageTransceiver.port.postMessage(r.createMessage(n,"track",{event:e,user:t})),r.resolverRecord.set(n,o)},{onTimeout:function(e){return e(null)}})},s.prototype.trackPageView=function(){return u(this,void 0,void 0,function(){return a(this,function(e){return[2]})})},s.prototype.remoteConfig=function(){var e=this;return new m(function(t,r){var n=e.createId();return p(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 p(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"showUserExplorer",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e(null)}})},s.prototype.hideUserExplorer=function(){return u(this,void 0,void 0,function(){var e,t=this;return a(this,function(r){return e=this.createId(),[2,p(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"hideUserExplorer",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e(null)}})]})})},s.prototype.fetch=function(){var e=this,t=this.createId();return p(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"fetch",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e(null)}})},s}(c),h=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 this.emitUserUpdated(),Promise.resolve(this.client.setUser(e))},r.prototype.setUserId=function(e){return this.emitUserUpdated(),Promise.resolve(this.client.setUserId(e))},r.prototype.setDeviceId=function(e){return this.client.setDeviceId(e),this.emit("user-updated",e),Promise.resolve()},r.prototype.setUserProperty=function(e,t){return this.emitUserUpdated(),Promise.resolve(this.client.setUserProperty(e,t))},r.prototype.setUserProperties=function(e){return this.emitUserUpdated(),Promise.resolve(this.client.setUserProperties(e))},r.prototype.updateUserProperties=function(e){return this.emitUserUpdated(),Promise.resolve(this.client.updateUserProperties(e))},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 this.emitUserUpdated(),Promise.resolve(this.client.resetUser())},r.prototype.variation=function(e){var t=this;return new Promise(function(r){setTimeout(function(){r(t.client.variation(e))},3e3)})},r.prototype.variationDetail=function(e){return Promise.resolve(this.client.variationDetail(e))},r.prototype.isFeatureOn=function(e){var t=this;return new Promise(function(r){setTimeout(function(){r(t.client.isFeatureOn(e))},3e3)})},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(){return this.client.remoteConfig()},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}(c),v=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}(),m=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;return a(this,function(o){switch(o.label){case 0:return o.trys.push([0,2,,3]),[4,this.configFetcher(e,t)];case 1:if(!(r=o.sent()))throw new Error("invoke result data not exists");switch(typeof t){case"number":return[2,Number(r)];case"boolean":return[2,Boolean(r)];default:return[2,r]}case 2:return n=o.sent(),console.error("Unexpected exception while deciding remote config parameter[".concat(e,"]. Returning default value. : ").concat(n)),[2,t];case 3:return[2]}})})},e}();export{l 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,s){function i(e){try{a(n.next(e))}catch(e){s(e)}}function u(e){try{a(n.throw(e))}catch(e){s(e)}}function a(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r(function(e){e(t)})).then(i,u)}a((n=n.apply(e,t||[])).next())})}function s(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(a){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,a])}}}"function"==typeof SuppressedError&&SuppressedError;var i=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}();function u(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 a=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 c(window.ReactNativeWebView);return new p(e,t,r)}return new l(e,t)},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(r){function i(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 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 n(i,r),i.prototype.createMessage=function(e,t,r){var n;return JSON.stringify(((n={})[this.messageFieldName]={id:e,type:t,payload:r},n))},i.prototype.createId=function(){return t.v4()},i.prototype.getSessionId=function(){return o(this,void 0,void 0,function(){var e,t=this;return s(this,function(r){return e=this.createId(),[2,u(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"getSessionId",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e("")}})]})})},i.prototype.getUser=function(){return o(this,void 0,void 0,function(){var e,t=this;return s(this,function(r){return e=this.createId(),[2,u(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"getUser",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e({})}})]})})},i.prototype.setUser=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setUser",{user:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.setUserId=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setUserId",{userId:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.setDeviceId=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setDeviceId",{deviceId:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.setUserProperty=function(e,t){var r=this,n=this.createId();return u(function(o){r.messageTransceiver.port.postMessage(r.createMessage(n,"setUserProperty",{key:e,value:t})),r.resolverRecord.set(n,o)},{onTimeout:function(e){return e(null)}})},i.prototype.setUserProperties=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setUserProperties",{properties:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.updateUserProperties=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateUserProperties",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.updatePushSubscriptions=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updatePushSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.updateSmsSubscriptions=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateSmsSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.updateKakaoSubscriptions=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"updateKakaoSubscriptions",{operations:e.toRecord()})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.setPhoneNumber=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"setPhoneNumber",{phoneNumber:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(null)}})},i.prototype.unsetPhoneNumber=function(){var e=this,t=this.createId();return u(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"unsetPhoneNumber",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e(null)}})},i.prototype.resetUser=function(){var e=this,t=this.createId();return u(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"resetUser",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e(null)}})},i.prototype.variation=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"variation",{experimentKey:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e("A")}})},i.prototype.variationDetail=function(t){return o(this,void 0,void 0,function(){var r,n,o,i,a,c,p=this;return s(this,function(s){switch(s.label){case 0:r=this.createId(),s.label=1;case 1:return s.trys.push([1,3,,4]),[4,u(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=s.sent(),i=(o=n).variation,a=o.reason,c=o.parameters,[2,e.Decision.of(i,a,new f(null!=c?c:{}))];case 3:return s.sent(),[2,e.Decision.of("A",e.DecisionReason.EXCEPTION)];case 4:return[2]}})})},i.prototype.isFeatureOn=function(e){var t=this,r=this.createId();return u(function(n){t.messageTransceiver.port.postMessage(t.createMessage(r,"isFeatureOn",{featureKey:e})),t.resolverRecord.set(r,n)},{onTimeout:function(e){return e(!1)}})},i.prototype.featureFlagDetail=function(t){return o(this,void 0,void 0,function(){var r,n,o,i,a,c,p=this;return s(this,function(s){switch(s.label){case 0:r=this.createId(),s.label=1;case 1:return s.trys.push([1,3,,4]),[4,u(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=s.sent(),i=(o=n).isOn,a=o.reason,c=o.parameters,[2,new e.FeatureFlagDecision(i,a,new f(null!=c?c:{}),void 0)];case 3:return s.sent(),[2,e.FeatureFlagDecision.off(e.DecisionReason.EXCEPTION)];case 4:return[2]}})})},i.prototype.track=function(e,t){var r=this,n=this.createId();return u(function(o){r.messageTransceiver.port.postMessage(r.createMessage(n,"track",{event:e,user:t})),r.resolverRecord.set(n,o)},{onTimeout:function(e){return e(null)}})},i.prototype.trackPageView=function(){return o(this,void 0,void 0,function(){return s(this,function(e){return[2]})})},i.prototype.remoteConfig=function(){var e=this;return new d(function(t,r){var n=e.createId();return u(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)}})})},i.prototype.showUserExplorer=function(){var e=this,t=this.createId();return u(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"showUserExplorer",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e(null)}})},i.prototype.hideUserExplorer=function(){return o(this,void 0,void 0,function(){var e,t=this;return s(this,function(r){return e=this.createId(),[2,u(function(r){t.messageTransceiver.port.postMessage(t.createMessage(e,"hideUserExplorer",null)),t.resolverRecord.set(e,r)},{onTimeout:function(e){return e(null)}})]})})},i.prototype.fetch=function(){var e=this,t=this.createId();return u(function(r){e.messageTransceiver.port.postMessage(e.createMessage(t,"fetch",null)),e.resolverRecord.set(t,r)},{onTimeout:function(e){return e(null)}})},i}(i),l=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 this.emitUserUpdated(),Promise.resolve(this.client.setUser(e))},r.prototype.setUserId=function(e){return this.emitUserUpdated(),Promise.resolve(this.client.setUserId(e))},r.prototype.setDeviceId=function(e){return this.client.setDeviceId(e),this.emit("user-updated",e),Promise.resolve()},r.prototype.setUserProperty=function(e,t){return this.emitUserUpdated(),Promise.resolve(this.client.setUserProperty(e,t))},r.prototype.setUserProperties=function(e){return this.emitUserUpdated(),Promise.resolve(this.client.setUserProperties(e))},r.prototype.updateUserProperties=function(e){return this.emitUserUpdated(),Promise.resolve(this.client.updateUserProperties(e))},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 this.emitUserUpdated(),Promise.resolve(this.client.resetUser())},r.prototype.variation=function(e){var t=this;return new Promise(function(r){setTimeout(function(){r(t.client.variation(e))},3e3)})},r.prototype.variationDetail=function(e){return Promise.resolve(this.client.variationDetail(e))},r.prototype.isFeatureOn=function(e){var t=this;return new Promise(function(r){setTimeout(function(){r(t.client.isFeatureOn(e))},3e3)})},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(){return this.client.remoteConfig()},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}(i),f=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}(),d=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;return s(this,function(o){switch(o.label){case 0:return o.trys.push([0,2,,3]),[4,this.configFetcher(e,t)];case 1:if(!(r=o.sent()))throw new Error("invoke result data not exists");switch(typeof t){case"number":return[2,Number(r)];case"boolean":return[2,Boolean(r)];default:return[2,r]}case 2:return n=o.sent(),console.error("Unexpected exception while deciding remote config parameter[".concat(e,"]. Returning default value. : ").concat(n)),[2,t];case 3:return[2]}})})},e}();return a});

์œ„ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ํ”„๋กœ์ ํŠธ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

  • esm ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. default export ํ˜•ํƒœ๋กœ ๋ชจ๋“ˆ์ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
  • ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ํ™˜๊ฒฝ์ด๋ผ๋ฉด umd๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
    • ์ „์—ญ ์Šค์ฝ”ํ”„์— HackleManager ๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋ชจ๋“ˆ์ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

์†Œ์Šค์ฝ”๋“œ ์—ฐ๋™

์†Œ์Šค ์ฝ”๋“œ๋ฅผ npm package๋กœ ์ œ๊ณตํ•˜๊ณ  ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์•„๋ž˜์˜ ๋‹จ๊ณ„๋ณ„ ์—ฐ๋™ ๊ฐ€์ด๋“œ๋ณด๋‹ค ๋” ์ž์„ธํ•œ ์—ฐ๋™ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์˜ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์—์„œ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•ด๋ณด์„ธ์š”.


์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

์•„๋ž˜์™€ ๊ฐ™์ด createInstance ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import ๊ฒฝ๋กœ๋Š” ์„ค์น˜ํ•œ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๊ธฐ์ž…ํ•ด์ฃผ์„ธ์š”.

  • ์ƒ์„ฑํ•œ ์ธ์Šคํ„ด์Šค๋ฅผ export ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
import HackleManager from "./web-view-integration";

const manager = new HackleManager();
const hackleClient = manager.createInstance(process.env.NEXT_PUBLIC_HACKLE_SDK_KEY!);

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๋ฅผ ์ž‘์„ฑํ•ด์„œ ์‚ฌ์šฉํ•˜์„ธ์š”.

Suspensable hooks

React Native์™€ JavaScript SDK๊ฐ€ ๋น„๋™๊ธฐ ๋ฉ”์‹œ์ง€ ํ†ต์‹ ์„ ํ•˜๋Š” ๋™์•ˆ Suspesne ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์„ ์–ธ์ ์ธ Loading ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“˜

์™œ Loading ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ•˜๋‚˜์š”?

๋ฉ”์‹œ์ง€ ์†ก์ˆ˜์‹ ์˜ ์ง€์—ฐ์‹œ๊ฐ„์ด ๊ธธ์ง€๋Š” ์•Š์œผ๋‚˜, ๋กœ๋”ฉ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

  • ์‹คํ—˜(variation)์˜ ๊ฒฐ๊ณผ ๊ฐ’์„ ๋ฐ›์•„์˜จ ์ดํ›„์— ์‚ฌ์šฉ์ž๊ฐ€ ์ตœ์ดˆ์— ๋ณธ ๊ฒƒ๊ณผ ๋‹ค๋ฅธ ํ™”๋ฉด์„ ๋ณด๊ฒŒ ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

Github: Source

import { Suspense } from "react";
import VariationTester from "./components/VariationTester";
import HackleProvider from "./context";
import hackleClient from "./modules/client";
import Loader from "./components/Loader";
import FeatureTester from "./components/FeatureTester";

function App() {
  return (
    <main>
      <HackleProvider hackleClient={hackleClient}>
        <div>
          <h2>Experiment</h2>
          <Suspense fallback={<Loader />}>
            <VariationTester />
          </Suspense>
        </div>
        <div>
          <h2>FeatureFlag</h2>
          <Suspense fallback={<Loader />}>
            <FeatureTester />
          </Suspense>
        </div>
      </HackleProvider>
    </main>
  );
}

export default App;
import useVariation from "../hooks/useVariation";

interface VariationTesterProps {}

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

  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, {
    suspense: true,
  });

  return <pre style={{ fontSize: 54 }}>{isOn ? "On" : "Off"}</pre>;
}