diff --git a/static/js/banditMl.min.js b/static/js/banditMl.min.js index b3ae282..eda5b7e 100644 --- a/static/js/banditMl.min.js +++ b/static/js/banditMl.min.js @@ -1 +1 @@ -window.banditml=window.banditml||{},banditml.BanditAPI=function(t,e={},n={}){this.storage=window.localStorage,this.banditApikey=t,this.sessionIdKey="BanditMLSessionId",this.lastActionTimeKey="BanditMLLastActionTime",this.recClassByExperimentId=e,this.decisionsLoggedById={};this.config=Object.assign({debugMode:!1,debugOptions:{forceVariantSlug:null},sessionLengthHrs:.5,banditHostUrl:"https://app.banditml.com/api/",getSessionId:null},n),this.banditDecisionEndpoint=`${this.config.banditHostUrl}decision`,this.banditLogRewardEndpoint=`${this.config.banditHostUrl}reward`,this.banditLogDecisionEndpoint=`${this.config.banditHostUrl}log_decision`,this.banditValidationEndpoint=`${this.config.banditHostUrl}validate`,this.banditLogErrorEndpoint=`${this.config.banditHostUrl}log_error`,this.ipUrl="https://api.ipify.org?format=json",this.reservedFeatures=["ipAddress"]},banditml.BanditAPI.prototype.addDecisionHandler=function(t,e,n){const i=this,o=i.recClassByExperimentId[n],s=document.getElementsByClassName(o)[0];s&&document.addEventListener("scroll",function(){!i.decisionsLoggedById[e.id]&&s.getBoundingClientRect().bottom<=window.innerHeight&&(i.config.debugMode&&console.log("User has seen decision. Auto logging it."),i.logDecision(t,e,n),i.decisionsLoggedById[e.id]=!0)})},banditml.BanditAPI.prototype.lastDecisionKey=function(t){return`BanditMLLastDecision-${t}`},banditml.BanditAPI.prototype.isTimeExpired=function(t,e){return((new Date).getTime()-t)/36e5>e},banditml.BanditAPI.prototype.uuidv4=function(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,t=>(t^crypto.getRandomValues(new Uint8Array(1))[0]&15>>t/4).toString(16))},banditml.BanditAPI.prototype.getLastDecision=function(t){return this.getItemFromStorage(this.lastDecisionKey(t))},banditml.BanditAPI.prototype.updateLastDecision=function(t,e){this.setItemInStorage(this.lastDecisionKey(e),t)},banditml.BanditAPI.prototype.updateSessionId=function(){let t=this.getItemFromStorage(this.sessionIdKey),e=this.getItemFromStorage(this.lastActionTimeKey);return t&&e&&!this.isTimeExpired(e,this.config.sessionLengthHrs)||(t=this.uuidv4(),this.setItemInStorage(this.sessionIdKey,t),this.setItemInStorage(this.lastActionTimeKey,(new Date).getTime())),t},banditml.BanditAPI.prototype.clearSession=function(t){this.storage.removeItem(this.sessionIdKey),this.storage.removeItem(this.lastActionTimeKey),this.clearContext(t)},banditml.BanditAPI.prototype.getSessionId=function(){let t,e="";return this.config.getSessionId?(t=this.config.getSessionId(),e="Looks like you are using your own getSessionId function. Double check this isn't returning null."):t=this.getItemFromStorage(this.sessionIdKey)||this.updateSessionId(),this.assert(t&&"string"==typeof t,`sessionId needs to be non-null string, somehow it's ${t} instead.`+e),t},banditml.BanditAPI.prototype.logError=function(t,e,n){const i={Authorization:`ApiKey ${this.banditApikey}`};if(!this.config.debugMode)return e=e||{},n&&Object.assign(e,{e:n.toString(),errName:n.name,errMessage:n.message}),this.asyncPostRequest(this.banditLogErrorEndpoint,i,{message:t,data:e});console.error(t),n&&console.error(n)},banditml.BanditAPI.prototype.assert=function(t,e,n){if(!t){if(e=e||"Assertion failed.",this.config.debugMode&&(e+=" Contact support@banditml.com for assistance."),this.logError(e,n),"undefined"!=typeof Error)throw new Error(e);throw e}},banditml.BanditAPI.prototype.isFunction=function(t){if(!t)return!1;const e={}.toString.call(t);return"[object Function]"===e||"[object AsyncFunction]"===e},banditml.BanditAPI.prototype.asyncGetRequest=async function(t,e={},n={}){e&&Object.keys(e).length&&(t+="?");for(const n in e){let i=e[n];if(null!=n&&null!=i){const e=typeof i;let o;t+=`${n}=${o="number"===e||"string"===e?i:encodeURIComponent(JSON.stringify(i))}&`}}const i=await fetch(t,{method:"GET",headers:n});return await i.json()},banditml.BanditAPI.prototype.asyncPostRequest=async function(t="",e={},n={}){e.hasOwnProperty("Content-Type")||(e["Content-Type"]="application/json");const i=await fetch(t,{method:"POST",headers:e,body:JSON.stringify(n)});return await i.json()},banditml.BanditAPI.prototype.getItemFromStorage=function(t){return JSON.parse(this.storage.getItem(t))},banditml.BanditAPI.prototype.contextName=function(t){return`banditMLContext-${t}`},banditml.BanditAPI.prototype.contextValidationKey=function(t){return`banditMLContextValidation-${t}`},banditml.BanditAPI.prototype.serverSideCacheKey=function(t){return`banditMLServerSideCache-${t}`},banditml.BanditAPI.prototype.getContext=function(t){return this.getItemFromStorage(this.contextName(t))||{}},banditml.BanditAPI.prototype.isValidArray=function(t,e){return!!Array.isArray(t)&&(!!e||t.every(t=>typeof t===e))},banditml.BanditAPI.prototype.validateAndFilterFeaturesInContext=function(t,e){const n=this;let i={context:t,contextValidation:e},o={};for(const s in t){if(n.reservedFeatures.includes(s)){o[s]=t[s];continue}if(e.hasOwnProperty(s)){const a=t[s],r=e[s],d=r.type;Object.assign(i,{value:a,featureSpec:r,featureType:d});try{if(null==a)n.config.debugMode&&console.warn(`Not including ${s} in context due to null value.`);else if("N"===d){const t=typeof a;n.assert("number"==typeof a,`Feature ${s} is expected to be numeric, but ${a} of type ${t} was passed.`,i)}else"C"===d?n.assert("string"==typeof a,`Feature ${s} is a categorical that expects a string, but ${a} is not a string.`,i):"P"===d&&n.assert("string"==typeof a||n.isValidArray(a,"string"),`Feature ${s} is a product set that expects an array or string, but ${a} is not an array or string.`,i);o[s]=a}catch(t){const e=`Not including ${s} in context due to invalid/unrecognized value.`;this.logError(e,{featureName:s},t)}}else console.warn(`Feature ${s} is not defined in experiment context. Including it, but check experiment dash.`),o[s]=t[s]}return o},banditml.BanditAPI.prototype.validateAndFilterContext=function(t,e){const n=this,i={context:t,experimentId:e};n.assert("object"==typeof t&&null!==t,"Context must be a non-null object.",i);let o=n.getItemFromStorage(n.contextValidationKey(e));if(!o||n.isTimeExpired(o.generated_at_ms,4)){return n.asyncGetRequest(url=n.banditValidationEndpoint,params={experimentId:e},headers={Authorization:`ApiKey ${n.banditApikey}`}).then(i=>(o=i,n.setItemInStorage(n.contextValidationKey(e),o),n.validateAndFilterFeaturesInContext(t,o)))}return n.validateAndFilterFeaturesInContext(t,o)},banditml.BanditAPI.prototype.setItemInStorage=function(t,e){this.storage.setItem(t,JSON.stringify(e))},banditml.BanditAPI.prototype.setContext=async function(t,e){try{let n=this.validateAndFilterContext(t,e);return n.then&&(n=await n),this.setItemInStorage(this.contextName(e),n),n||{}}catch(t){return this.logError("Failed to set context",{context:n,experimentId:e},t),n||{}}},banditml.BanditAPI.prototype.clearContext=function(t){this.storage.removeItem(this.contextName(t)),this.storage.removeItem(this.serverSideCacheKey(t))},banditml.BanditAPI.prototype.updateContext=async function(t,e){this.assert("object"==typeof t&&null!==t,"newContext must be a non-null object."),this.assert(e&&"string"==typeof e,`experimentId must be non-null string. Got ${e} instead`);let n=this.getContext(e);return n=null==n?t:Object.assign({},n,t),(n=this.setContext(n,e)).then&&(n=await n),this.updateSessionId(),this.config.debugMode&&(console.log("Updated context."),console.log(n)),n},banditml.BanditAPI.prototype.getControlRecs=async function(t){let e;if(this.assert(Array.isArray(t)||this.isFunction(t),"defaultDecisionIds must be an array or function."),Array.isArray(t))e=t;else{let n=t();e=n&&n.then?await n:n}return e},banditml.BanditAPI.prototype.setRecs=async function(t=null,e=null,n=null,i=null){const o=this;if(t.then&&(t=await t),o.validateDecisionIds(t),e){o.assert(o.isFunction(e),"filterRecs must be a function.");let n=e(t,i);n&&(t=n.then?await n:n)}if(o.config.debugMode&&(console.log("After filtering, the following recs will be shown and logged:"),console.log(t)),n){o.assert(o.isFunction(n),"populateDecisions must be a function.");let e=n(t);e&&(e.then&&(e=await e),o.config.debugMode&&e!==t&&console.warn(`populateDecisions function is returning a different result (${e}) than filterRecs (${t}). Ensure that populateDecisions is not modifying decisions.`))}return t},banditml.BanditAPI.prototype.getDecision=async function(t,e=null,n=null,i=null,o=!0){const s=this;function a(o,a){return a=a||{},Object.assign(a,{experimentId:t}),s.logError("Error getting decision, setting your default recs instead.",a,o),s.setRecs(s.getControlRecs(e),n,i)}null!==t&&s.assert(null!==t&&"string"==typeof t,"experimentId needs to be non-null string.");let r=s.getContext(t);if(!("ipAddress"in r))try{let e=s.asyncGetRequest(s.ipUrl,params={},headers={"Content-Type":"application/json",Accept:"application/json"});const n=(await e).ip;r.ipAddress=n,(r=s.updateContext(r,t)).then&&(r=await r)}catch(t){return a(t,{context:r})}let d=s.getItemFromStorage(s.serverSideCacheKey(t));const c=s.config.debugOptions.forceVariantSlug;return c&&console.log(`Forcing variant: ${c}`),s.asyncGetRequest(url=s.banditDecisionEndpoint,params={context:r,experimentId:t,cache:d,forceVariantSlug:c},headers={Authorization:`ApiKey ${s.banditApikey}`}).then(async a=>{let d=a,c=d.decision.cache;null!=c?s.setItemInStorage(s.serverSideCacheKey(t),c):s.config.debugMode&&console.log("Null cache passed back from Bandit ML server.");let l,u=d.decision.ids.reduce((t,e,n)=>(t[e]=d.decision.scores[n],t),{});if(s.config.debugMode&&(console.log("Got a decision from Bandit."),console.log(d)),"D"===d.decision.type){const t=d.decision.ids;l=e&&a.decision.isControl?await s.getControlRecs(e):t,l=await s.setRecs(l,n,i,d.decision.variantSlug),d.decision.ids=l}else l=d.decision.ids,await s.setRecs(l,n,i,d.decision.variantSlug);return d.decision.scores=d.decision.ids.map(t=>u[t]),o&&(s.config.debugMode&&(console.log("Will log decision when user sees it"),console.log(d)),s.addDecisionHandler(r,d,t)),a}).catch(t=>a(t))},banditml.BanditAPI.prototype.validateDecisionIds=function(t){const e=typeof t;this.assert(Array.isArray(t)||"number"===e||"string"===e,"decision IDs must be an array, number, or string")},banditml.BanditAPI.prototype.logDecision=function(t,e,n){const i=e.decision;this.validateDecisionIds(i.ids);const o={Authorization:`ApiKey ${this.banditApikey}`},s=this.getSessionId();this.asyncPostRequest(this.banditLogDecisionEndpoint,o,{id:e.id,context:t,decision:i,experimentId:n,mdpId:s,variantId:i.variantId}).then(t=>(this.config.debugMode&&(console.log("Successfully logged decision"),console.log(t)),this.updateLastDecision(i,n),t)).catch(i=>{this.logError("Failed to log decision.",{context:t,decisionResponse:e,experimentId:n,mdpId:s},i)})},banditml.BanditAPI.prototype.logReward=function(t,e=null,n=null,i=null){const o={Authorization:`ApiKey ${this.banditApikey}`};this.assert(t&&"object"==typeof t,"Reward needs to be a non-empty object.");const s={decisionId:i,decision:n,metrics:t,experimentId:e,mdpId:this.getSessionId()};this.asyncPostRequest(this.banditLogRewardEndpoint,o,s).then(t=>(this.config.debugMode&&(console.log("Successfully logged reward."),console.log(t)),t)).catch(t=>{this.logError("Failed to log reward",s,t)})},window.BanditAPI=window.banditml.BanditAPI; +window.banditml=window.banditml||{},banditml.BanditAPI=function(t,e={},n={}){this.storage=window.localStorage,this.banditApikey=t,this.sessionIdKey="BanditMLSessionId",this.lastActionTimeKey="BanditMLLastActionTime",this.recClassByExperimentId=e,this.decisionsLoggedById={};this.config=Object.assign({debugMode:!1,debugOptions:{forceVariantSlug:null},sessionLengthHrs:.5,banditHostUrl:"https://app.banditml.com/api/",getSessionId:null},n),this.banditDecisionEndpoint=`${this.config.banditHostUrl}decision`,this.banditLogRewardEndpoint=`${this.config.banditHostUrl}reward`,this.banditLogDecisionEndpoint=`${this.config.banditHostUrl}log_decision`,this.banditValidationEndpoint=`${this.config.banditHostUrl}validate`,this.banditLogErrorEndpoint=`${this.config.banditHostUrl}log_error`,this.ipUrl="https://api.ipify.org?format=json",this.reservedFeatures=["ipAddress"]},banditml.BanditAPI.prototype.addDecisionHandler=function(t,e,n){const i=this,o=i.recClassByExperimentId[n],s=document.getElementsByClassName(o)[0];s&&document.addEventListener("scroll",function(){!i.decisionsLoggedById[e.id]&&s.getBoundingClientRect().bottom<=window.innerHeight&&(i.config.debugMode&&console.log("User has seen decision. Auto logging it."),i.logDecision(t,e,n),i.decisionsLoggedById[e.id]=!0)})},banditml.BanditAPI.prototype.lastDecisionKey=function(t){return`BanditMLLastDecision-${t}`},banditml.BanditAPI.prototype.isTimeExpired=function(t,e){return((new Date).getTime()-t)/36e5>e},banditml.BanditAPI.prototype.uuidv4=function(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,t=>(t^crypto.getRandomValues(new Uint8Array(1))[0]&15>>t/4).toString(16))},banditml.BanditAPI.prototype.getLastDecision=function(t){return this.getItemFromStorage(this.lastDecisionKey(t))},banditml.BanditAPI.prototype.updateLastDecision=function(t,e){this.setItemInStorage(this.lastDecisionKey(e),t)},banditml.BanditAPI.prototype.updateSessionId=function(){let t=this.getItemFromStorage(this.sessionIdKey),e=this.getItemFromStorage(this.lastActionTimeKey);return t&&e&&!this.isTimeExpired(e,this.config.sessionLengthHrs)||(t=this.uuidv4(),this.setItemInStorage(this.sessionIdKey,t),this.setItemInStorage(this.lastActionTimeKey,(new Date).getTime())),t},banditml.BanditAPI.prototype.clearSession=function(t){this.storage.removeItem(this.sessionIdKey),this.storage.removeItem(this.lastActionTimeKey),this.clearContext(t)},banditml.BanditAPI.prototype.getSessionId=function(){let t,e="";return this.config.getSessionId?(t=this.config.getSessionId(),e="Looks like you are using your own getSessionId function. Double check this isn't returning null."):t=this.getItemFromStorage(this.sessionIdKey)||this.updateSessionId(),this.assert(t&&"string"==typeof t,`sessionId needs to be non-null string, somehow it's ${t} instead.`+e),t},banditml.BanditAPI.prototype.logError=function(t,e,n){const i={Authorization:`ApiKey ${this.banditApikey}`};if(!this.config.debugMode)return e=e||{},n&&Object.assign(e,{e:n.toString(),errName:n.name,errMessage:n.message}),this.asyncPostRequest(this.banditLogErrorEndpoint,i,{message:t,data:e});console.error(t),n&&console.error(n)},banditml.BanditAPI.prototype.assert=function(t,e,n){if(!t){if(e=e||"Assertion failed.",this.config.debugMode&&(e+=" Contact support@banditml.com for assistance."),this.logError(e,n),"undefined"!=typeof Error)throw new Error(e);throw e}},banditml.BanditAPI.prototype.isFunction=function(t){if(!t)return!1;const e={}.toString.call(t);return"[object Function]"===e||"[object AsyncFunction]"===e},banditml.BanditAPI.prototype.asyncGetRequest=async function(t,e={},n={}){e&&Object.keys(e).length&&(t+="?");for(const n in e){let i=e[n];if(null!=n&&null!=i){const e=typeof i;let o;t+=`${n}=${o="number"===e||"string"===e?i:encodeURIComponent(JSON.stringify(i))}&`}}const i=await fetch(t,{method:"GET",headers:n});return await i.json()},banditml.BanditAPI.prototype.asyncPostRequest=async function(t="",e={},n={}){e.hasOwnProperty("Content-Type")||(e["Content-Type"]="application/json");const i=await fetch(t,{method:"POST",headers:e,body:JSON.stringify(n)});return await i.json()},banditml.BanditAPI.prototype.getItemFromStorage=function(t){return JSON.parse(this.storage.getItem(t))},banditml.BanditAPI.prototype.contextName=function(t){return`banditMLContext-${t}`},banditml.BanditAPI.prototype.contextValidationKey=function(t){return`banditMLContextValidation-${t}`},banditml.BanditAPI.prototype.serverSideCacheKey=function(t){return`banditMLServerSideCache-${t}`},banditml.BanditAPI.prototype.getContext=function(t){return this.getItemFromStorage(this.contextName(t))||{}},banditml.BanditAPI.prototype.isValidArray=function(t,e){return!!Array.isArray(t)&&(!!e||t.every(t=>typeof t===e))},banditml.BanditAPI.prototype.validateAndFilterFeaturesInContext=function(t,e){const n=this;let i={context:t,contextValidation:e},o={};for(const s in t){if(n.reservedFeatures.includes(s)){o[s]=t[s];continue}if(e.hasOwnProperty(s)){const a=t[s],r=e[s],d=r.type;Object.assign(i,{value:a,featureSpec:r,featureType:d});try{if(null==a)n.config.debugMode&&console.warn(`Not including ${s} in context due to null value.`);else if("N"===d){const t=typeof a;n.assert("number"==typeof a,`Feature ${s} is expected to be numeric, but ${a} of type ${t} was passed.`,i)}else"C"===d?n.assert("string"==typeof a,`Feature ${s} is a categorical that expects a string, but ${a} is not a string.`,i):"P"===d&&n.assert("string"==typeof a||n.isValidArray(a,"string"),`Feature ${s} is a product set that expects an array or string, but ${a} is not an array or string.`,i);o[s]=a}catch(t){const e=`Not including ${s} in context due to invalid/unrecognized value.`;this.logError(e,{featureName:s},t)}}else o[s]=t[s]}return o},banditml.BanditAPI.prototype.validateAndFilterContext=function(t,e){const n=this,i={context:t,experimentId:e};n.assert("object"==typeof t&&null!==t,"Context must be a non-null object.",i);let o=n.getItemFromStorage(n.contextValidationKey(e));if(!o||n.isTimeExpired(o.generated_at_ms,4)){return n.asyncGetRequest(url=n.banditValidationEndpoint,params={experimentId:e},headers={Authorization:`ApiKey ${n.banditApikey}`}).then(i=>(o=i,n.setItemInStorage(n.contextValidationKey(e),o),n.validateAndFilterFeaturesInContext(t,o)))}return n.validateAndFilterFeaturesInContext(t,o)},banditml.BanditAPI.prototype.setItemInStorage=function(t,e){this.storage.setItem(t,JSON.stringify(e))},banditml.BanditAPI.prototype.setContext=async function(t,e){try{let n=this.validateAndFilterContext(t,e);return n.then&&(n=await n),this.setItemInStorage(this.contextName(e),n),n||{}}catch(t){return this.logError("Failed to set context",{context:n,experimentId:e},t),n||{}}},banditml.BanditAPI.prototype.clearContext=function(t){this.storage.removeItem(this.contextName(t)),this.storage.removeItem(this.serverSideCacheKey(t))},banditml.BanditAPI.prototype.updateContext=async function(t,e){this.assert("object"==typeof t&&null!==t,"newContext must be a non-null object."),this.assert(e&&"string"==typeof e,`experimentId must be non-null string. Got ${e} instead`);let n=this.getContext(e);return n=null==n?t:Object.assign({},n,t),(n=this.setContext(n,e)).then&&(n=await n),this.updateSessionId(),this.config.debugMode&&(console.log("Updated context."),console.log(n)),n},banditml.BanditAPI.prototype.getControlRecs=async function(t){let e;if(this.assert(Array.isArray(t)||this.isFunction(t),"defaultDecisionIds must be an array or function."),Array.isArray(t))e=t;else{let n=t();e=n&&n.then?await n:n}return e},banditml.BanditAPI.prototype.setRecs=async function(t=null,e=null,n=null,i=null){const o=this;if(t.then&&(t=await t),o.validateDecisionIds(t),e){o.assert(o.isFunction(e),"filterRecs must be a function.");let n=e(t,i);n&&(t=n.then?await n:n)}if(o.config.debugMode&&(console.log("After filtering, the following recs will be shown and logged:"),console.log(t)),n){o.assert(o.isFunction(n),"populateDecisions must be a function.");let e=n(t);e&&(e.then&&(e=await e),o.config.debugMode&&e!==t&&console.warn(`populateDecisions function is returning a different result (${e}) than filterRecs (${t}). Ensure that populateDecisions is not modifying decisions.`))}return t},banditml.BanditAPI.prototype.getDecision=async function(t,e=null,n=null,i=null,o=!0){const s=this;function a(o,a){return a=a||{},Object.assign(a,{experimentId:t}),s.logError("Error getting decision, setting your default recs instead.",a,o),s.setRecs(s.getControlRecs(e),n,i)}null!==t&&s.assert(null!==t&&"string"==typeof t,"experimentId needs to be non-null string.");let r=s.getContext(t);if(!("ipAddress"in r)){let e;try{try{let t=s.asyncGetRequest(s.ipUrl,params={},headers={"Content-Type":"application/json",Accept:"application/json"});e=await t}catch(t){e={ip:"unset-IP-session-"+this.getSessionId()}}const n=e.ip;r.ipAddress=n,(r=s.updateContext(r,t)).then&&(r=await r)}catch(t){return a(t,{context:r})}}let d=s.getItemFromStorage(s.serverSideCacheKey(t));const c=s.config.debugOptions.forceVariantSlug;return c&&console.log(`Forcing variant: ${c}`),s.asyncGetRequest(url=s.banditDecisionEndpoint,params={context:r,experimentId:t,cache:d,forceVariantSlug:c},headers={Authorization:`ApiKey ${s.banditApikey}`}).then(async a=>{let d=a,c=d.decision.cache;null!=c?s.setItemInStorage(s.serverSideCacheKey(t),c):s.config.debugMode&&console.log("Null cache passed back from Bandit ML server.");let l,u=d.decision.ids.reduce((t,e,n)=>(t[e]=d.decision.scores[n],t),{});if(s.config.debugMode&&(console.log("Got a decision from Bandit."),console.log(d)),"D"===d.decision.type){const t=d.decision.ids;l=e&&a.decision.isControl?await s.getControlRecs(e):t,l=await s.setRecs(l,n,i,d.decision.variantSlug),d.decision.ids=l}else l=d.decision.ids,await s.setRecs(l,n,i,d.decision.variantSlug);return d.decision.scores=d.decision.ids.map(t=>u[t]),o&&(s.config.debugMode&&(console.log("Will log decision when user sees it"),console.log(d)),s.addDecisionHandler(r,d,t)),a}).catch(t=>a(t))},banditml.BanditAPI.prototype.validateDecisionIds=function(t){const e=typeof t;this.assert(Array.isArray(t)||"number"===e||"string"===e,"decision IDs must be an array, number, or string")},banditml.BanditAPI.prototype.logDecision=function(t,e,n){const i=e.decision;this.validateDecisionIds(i.ids);const o={Authorization:`ApiKey ${this.banditApikey}`},s=this.getSessionId();this.asyncPostRequest(this.banditLogDecisionEndpoint,o,{id:e.id,context:t,decision:i,experimentId:n,mdpId:s,variantId:i.variantId}).then(t=>(this.config.debugMode&&(console.log("Successfully logged decision"),console.log(t)),this.updateLastDecision(i,n),t)).catch(i=>{this.logError("Failed to log decision.",{context:t,decisionResponse:e,experimentId:n,mdpId:s},i)})},banditml.BanditAPI.prototype.logReward=function(t,e=null,n=null,i=null){const o={Authorization:`ApiKey ${this.banditApikey}`};this.assert(t&&"object"==typeof t,"Reward needs to be a non-empty object.");const s={decisionId:i,decision:n,metrics:t,experimentId:e,mdpId:this.getSessionId()};this.asyncPostRequest(this.banditLogRewardEndpoint,o,s).then(t=>(this.config.debugMode&&(console.log("Successfully logged reward."),console.log(t)),t)).catch(t=>{this.logError("Failed to log reward",s,t)})},window.BanditAPI=window.banditml.BanditAPI;