vue3-game-analytics
Version:
Comprehensive analytics tracking system for Vue 3 game applications
2 lines (1 loc) • 19.9 kB
JavaScript
(function(v,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],h):(v=typeof globalThis<"u"?globalThis:v||self,h(v.Vue3GameAnalytics={},v.Vue))})(this,function(v,h){"use strict";var S=(e=>(e.BUFFER_OVERFLOW="BUFFER_OVERFLOW",e.API_SUBMISSION_FAILED="API_SUBMISSION_FAILED",e.INVALID_EVENT="INVALID_EVENT",e.CONFIGURATION_ERROR="CONFIGURATION_ERROR",e.CONSENT_REQUIRED="CONSENT_REQUIRED",e.DO_NOT_TRACK="DO_NOT_TRACK",e))(S||{});function p(e,t=100){let o,u,i=0;function c(...s){const r=Date.now(),a=t-(r-i);return a<=0||a>t?(o&&(clearTimeout(o),o=void 0),i=r,u=e.apply(this,s)):o||(o=setTimeout(()=>{i=Date.now(),o=void 0,u=e.apply(this,s)},a)),u}return c}function R(e=1e3){let t=0,o=performance.now(),u=0;function i(){t++;const c=performance.now(),s=c-o;s>=e&&(u=Math.round(t*1e3/s),t=0,o=c),requestAnimationFrame(i)}return requestAnimationFrame(i),function(){return u}}function L(e,t=2e3){typeof window<"u"&&"requestIdleCallback"in window?window.requestIdleCallback(e,{timeout:t}):setTimeout(e,1)}function P(){return typeof window>"u"||typeof navigator>"u"?{}:{deviceType:F(),browser:M(),os:$(),screenResolution:H(),orientation:G(),connectionType:N(),language:navigator.language||"unknown",userAgent:navigator.userAgent,doNotTrack:z(),timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone}}function F(){const e=navigator.userAgent;return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(e)?window.innerWidth>767||/iPad/i.test(e)?"tablet":"mobile":"desktop"}function M(){var u,i,c,s,r,a;const e=navigator.userAgent;let t="unknown",o="unknown";return/Chrome/.test(e)&&!/Chromium|Edge|Edg|OPR|Opera/.test(e)?(t="Chrome",o=((u=e.match(/Chrome\/(\d+\.\d+)/))==null?void 0:u[1])||""):/Firefox/.test(e)?(t="Firefox",o=((i=e.match(/Firefox\/(\d+\.\d+)/))==null?void 0:i[1])||""):/Safari/.test(e)&&!/Chrome|Chromium|Edge|Edg|OPR|Opera/.test(e)?(t="Safari",o=((c=e.match(/Version\/(\d+\.\d+)/))==null?void 0:c[1])||""):/Edge|Edg/.test(e)?(t="Edge",o=((s=e.match(/Edge\/(\d+\.\d+)|Edg\/(\d+\.\d+)/))==null?void 0:s[1])||""):/OPR|Opera/.test(e)?(t="Opera",o=((r=e.match(/OPR\/(\d+\.\d+)|Opera\/(\d+\.\d+)/))==null?void 0:r[1])||""):/MSIE|Trident/.test(e)&&(t="Internet Explorer",o=((a=e.match(/MSIE (\d+\.\d+)|rv:(\d+\.\d+)/))==null?void 0:a[1])||""),`${t} ${o}`.trim()}function $(){var o,u,i,c,s,r;const e=navigator.userAgent;let t="unknown";if(/Windows NT/.test(e)){t="Windows";const a=(o=e.match(/Windows NT (\d+\.\d+)/))==null?void 0:o[1];if(a)switch(a){case"10.0":t+=" 10";break;case"6.3":t+=" 8.1";break;case"6.2":t+=" 8";break;case"6.1":t+=" 7";break;case"6.0":t+=" Vista";break;case"5.2":t+=" XP";break;case"5.1":t+=" XP";break;default:t+=` ${a}`;break}}else if(/Mac OS X/.test(e)){t="macOS";const a=(i=(u=e.match(/Mac OS X (\d+[._]\d+)/))==null?void 0:u[1])==null?void 0:i.replace("_",".");a&&(t+=` ${a}`)}else if(/iPhone|iPad|iPod/.test(e)){t="iOS";const a=(s=(c=e.match(/OS (\d+[._]\d+)/))==null?void 0:c[1])==null?void 0:s.replace("_",".");a&&(t+=` ${a}`)}else if(/Android/.test(e)){t="Android";const a=(r=e.match(/Android (\d+\.\d+)/))==null?void 0:r[1];a&&(t+=` ${a}`)}else/Linux/.test(e)&&(t="Linux");return t}function H(){if(typeof screen>"u")return"unknown";const e=screen.width||window.innerWidth,t=screen.height||window.innerHeight;return`${e}x${t}`}function G(){return typeof window>"u"||typeof screen>"u"?"unknown":screen.orientation?screen.orientation.type.includes("portrait")?"portrait":"landscape":window.innerHeight>window.innerWidth?"portrait":"landscape"}function N(){const e=navigator.connection||navigator.mozConnection||navigator.webkitConnection;return e&&(e.effectiveType||e.type)||"unknown"}function z(){return navigator.doNotTrack?navigator.doNotTrack==="1"?"yes":"no":navigator.msDoNotTrack?navigator.msDoNotTrack==="1"?"yes":"no":window.doNotTrack?window.doNotTrack==="1"?"yes":"no":"unspecified"}function X(e){const t=()=>{e({isOnline:navigator.onLine,connectionType:N()})};window.addEventListener("online",t),window.addEventListener("offline",t);const o=navigator.connection||navigator.mozConnection||navigator.webkitConnection;return o&&o.addEventListener("change",t),t(),()=>{window.removeEventListener("online",t),window.removeEventListener("offline",t),o&&o.removeEventListener("change",t)}}function D(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function k(e,t){const o={};if(!e||!e.attributes)return o;for(let u=0;u<e.attributes.length;u++){const i=e.attributes[u];if(i.name.startsWith("data-")){const c=i.name.substring(5);if(c.startsWith(t+"-")){const s=c.substring(t.length+1);o[s]=i.value}}}return o}function U(e){const t=new WeakSet;return JSON.stringify(e,(o,u)=>{if(typeof u=="function")return"[Function]";if(u instanceof Error)return{message:u.message,stack:u.stack,name:u.name};if(u instanceof HTMLElement)return`[HTMLElement ${u.tagName.toLowerCase()}]`;if(typeof u=="symbol")return u.toString();if(typeof u=="object"&&u!==null){if(t.has(u))return"[Circular]";t.add(u)}return u})}function Y(e,t){try{const o=U(t);return localStorage.setItem(e,o),!0}catch(o){return console.error("Failed to persist data to storage:",o),!1}}function Q(e){try{const t=localStorage.getItem(e);return t===null?null:JSON.parse(t)}catch(t){return console.error("Failed to retrieve data from storage:",t),null}}function W(e){return e>=1?!0:e<=0?!1:Math.random()<=e}const b="vue3-game-analytics-events",q="vue3-game-analytics-config",O={batchSize:50,flushInterval:6e4,maxQueueSize:1e3,trackClicks:!0,trackTouches:!0,trackKeyboard:!0,trackErrors:!0,trackNetwork:!0,trackPerformance:!1,sampleRate:1,collectEnvironmentData:!0,throttleHighFrequencyEvents:!0,throttleInterval:100,debug:!1,verboseLogging:!1,visibleTracking:!1,anonymizeIp:!0,respectDoNotTrack:!0,consentRequired:!0},V={capacity:1e3},n=h.reactive({events:[],config:{apiEndpoint:"",gameId:"",playId:"",...O},isEnabled:!1,isDebugMode:!1,hasConsent:!1,bufferInfo:{capacity:V.capacity,size:0,overflowCount:0},networkStatus:{isOnline:!0,lastConnectionType:"unknown"},pendingFlush:!1,failedSubmissions:0,lastFlushTime:null}),A={isTrackingAllowed:h.computed(()=>!(!n.isEnabled||n.config.consentRequired&&!n.hasConsent||n.config.respectDoNotTrack&&(navigator.doNotTrack==="1"||window.doNotTrack==="1"||navigator.msDoNotTrack==="1"))),eventCount:h.computed(()=>n.events.length),shouldShowDebugInfo:h.computed(()=>n.isDebugMode||!!n.config.debug||!!n.config.visibleTracking)},g={initialize(e){n.config={...O,...e},n.isDebugMode=!!n.config.debug;const t=Q(b);t&&Array.isArray(t)&&(n.events=t,n.bufferInfo.size=t.length,g.logDebug(`Loaded ${t.length} events from storage`)),n.isEnabled=!0,n.config.flushInterval&&n.config.flushInterval>0&&setInterval(()=>{n.events.length>0&&g.flushEvents()},n.config.flushInterval),n.networkStatus.isOnline=navigator.onLine,typeof window<"u"&&window.addEventListener("beforeunload",()=>{g.saveEventsToStorage(),n.events.length>0&&navigator.onLine&&g.flushEvents()}),g.logDebug("Analytics store initialized",n.config)},setConsent(e){n.hasConsent=e,g.logDebug(`User consent set to: ${e}`),e||(g.clearEvents(),localStorage.removeItem(b),localStorage.removeItem(q))},setDebugMode(e){n.isDebugMode=e,g.logDebug(`Debug mode ${e?"enabled":"disabled"}`)},updateNetworkStatus(e){const t=!n.networkStatus.isOnline;n.networkStatus={isOnline:e.isOnline,lastConnectionType:e.connectionType},t&&e.isOnline&&n.events.length>0&&g.flushEvents(),g.logDebug("Network status updated",e)},trackEvent(e){if(!A.isTrackingAllowed.value){g.logDebug("Event not tracked: tracking not allowed",e);return}if(n.config.sampleRate!==void 0&&n.config.sampleRate<1&&!W(n.config.sampleRate)){g.logDebug("Event sampled out",e);return}const t={id:D(),timestamp:Date.now(),type:e.type||"custom",gameId:e.gameId||n.config.gameId,playId:e.playId||n.config.playId,...e};if(n.config.collectEnvironmentData&&!t.environmentData&&(t.environmentData=P()),n.config.eventTransformer)try{const o=n.config.eventTransformer(t);g.addEventToQueue(o)}catch(o){console.error("Error in event transformer:",o),g.addEventToQueue(t)}else g.addEventToQueue(t)},addEventToQueue(e){n.events.push(e),n.bufferInfo.size=n.events.length,g.logDebug("Event tracked",e),n.config.maxQueueSize&&n.events.length>n.config.maxQueueSize&&(n.events=n.events.slice(-n.config.maxQueueSize),n.bufferInfo.size=n.events.length,n.bufferInfo.overflowCount++,g.logDebug(`Queue overflow: removed oldest events. Current size: ${n.events.length}`)),n.config.batchSize&&n.events.length>=n.config.batchSize&&g.flushEvents(),L(()=>{g.saveEventsToStorage()})},saveEventsToStorage(){n.events.length>0&&(Y(b,n.events),g.logDebug(`Saved ${n.events.length} events to storage`))},async flushEvents(){if(!(n.events.length===0||n.pendingFlush)){if(!n.networkStatus.isOnline){g.logDebug("Flush skipped: device is offline");return}n.pendingFlush=!0,g.logDebug(`Flushing ${n.events.length} events to ${n.config.apiEndpoint}`);try{const e=[...n.events],t=await fetch(n.config.apiEndpoint,{method:"POST",headers:{"Content-Type":"application/json","X-Game-ID":n.config.gameId,"X-Play-ID":n.config.playId},body:JSON.stringify({events:e})});if(!t.ok)throw new Error(`API responded with status ${t.status}`);n.events=n.events.filter(o=>!e.some(u=>u.id===o.id)),n.bufferInfo.size=n.events.length,n.lastFlushTime=Date.now(),localStorage.removeItem(b),g.logDebug(`Successfully sent ${e.length} events`)}catch(e){n.failedSubmissions++,console.error("Failed to send events:",e),n.config.trackErrors&&g.trackEvent({type:"error",target:"analytics",error:{message:e instanceof Error?e.message:String(e),code:S.API_SUBMISSION_FAILED}})}finally{n.pendingFlush=!1}}},clearEvents(){n.events=[],n.bufferInfo.size=0,g.logDebug("Event queue cleared")},filterEvents(e){const t=n.events.length;n.events=n.events.filter(e),n.bufferInfo.size=n.events.length,g.logDebug(`Filtered events: removed ${t-n.events.length} events`)},exportEvents(){return JSON.stringify(n.events)},logDebug(e,t){(n.isDebugMode||n.config.debug)&&(n.config.verboseLogging&&t?console.debug(`[GameAnalytics] ${e}`,t):console.debug(`[GameAnalytics] ${e}`))}};let _=null;function y(){return _||(_={...h.readonly(n),...A,...g}),_}function j(e){const t=y(),o=[],u=e.throttleInterval||100,i=e.throttleHighFrequencyEvents!==!1;if(e.trackClicks!==!1){const c=r=>{if(!r.target)return;const a=r.target,l={type:"click",target:w(a),coordinates:{x:r.clientX,y:r.clientY,screenX:r.screenX,screenY:r.screenY},elementData:{type:a.tagName.toLowerCase(),attributes:k(a,"game")}};t.trackEvent(l),t.shouldShowDebugInfo&&e.visibleTracking&&C(r.clientX,r.clientY)},s=i?p(c,u):c;document.addEventListener("click",s),o.push({element:document,type:"click",handler:s})}if(e.trackTouches!==!1){const c=l=>{if(!l.target||!l.touches.length)return;const d=l.touches[0],f=l.target,m={type:"touch_start",target:w(f),coordinates:{x:d.clientX,y:d.clientY,screenX:d.screenX,screenY:d.screenY},elementData:{type:f.tagName.toLowerCase(),attributes:k(f,"game")}};t.trackEvent(m),t.shouldShowDebugInfo&&e.visibleTracking&&C(d.clientX,d.clientY)},s=l=>{if(!l.target||!l.changedTouches.length)return;const d=l.changedTouches[0],f=l.target,m={type:"touch_end",target:w(f),coordinates:{x:d.clientX,y:d.clientY,screenX:d.screenX,screenY:d.screenY},elementData:{type:f.tagName.toLowerCase(),attributes:k(f,"game")}};t.trackEvent(m)},r=i?p(c,u):c,a=i?p(s,u):s;document.addEventListener("touchstart",r),document.addEventListener("touchend",a),o.push({element:document,type:"touchstart",handler:r}),o.push({element:document,type:"touchend",handler:a})}if(e.trackKeyboard!==!1){const c=r=>{if(["Control","Shift","Alt","Meta"].includes(r.key)||r.repeat)return;const a=r.target,l={type:"keydown",target:w(a),elementData:{type:a.tagName.toLowerCase(),key:r.key,keyCode:r.keyCode}};t.trackEvent(l)},s=i?p(c,u):c;document.addEventListener("keydown",s),o.push({element:document,type:"keydown",handler:s})}if(e.trackErrors!==!1){const c=r=>{var l;const a={type:"error",error:{message:r.message,stack:(l=r.error)==null?void 0:l.stack}};t.trackEvent(a)};window.addEventListener("error",c),o.push({element:window,type:"error",handler:c});const s=r=>{var l,d;const a={type:"error",error:{message:((l=r.reason)==null?void 0:l.message)||"Unhandled promise rejection",stack:(d=r.reason)==null?void 0:d.stack,code:"UNHANDLED_REJECTION"}};t.trackEvent(a)};window.addEventListener("unhandledrejection",s),o.push({element:window,type:"unhandledrejection",handler:s})}return()=>{o.forEach(({element:c,type:s,handler:r})=>{c.removeEventListener(s,r)})}}function w(e){if(e.id)return`#${e.id}`;const t=e.getAttribute("data-game-id");if(t)return`[data-game-id="${t}"]`;const o=e.getAttribute("name");return o?`[name="${o}"]`:e.className&&typeof e.className=="string"?`.${e.className.trim().replace(/\s+/g,".")}`:e.tagName.toLowerCase()}function C(e,t){const o=document.createElement("div");o.style.position="absolute",o.style.width="20px",o.style.height="20px",o.style.borderRadius="50%",o.style.backgroundColor="rgba(255, 0, 0, 0.5)",o.style.left=`${e-10}px`,o.style.top=`${t-10}px`,o.style.pointerEvents="none",o.style.zIndex="10000",o.style.transform="scale(0)",o.style.transition="transform 0.2s ease-out, opacity 0.5s ease-out",document.body.appendChild(o),setTimeout(()=>{o.style.transform="scale(1)"},10),setTimeout(()=>{o.style.opacity="0",setTimeout(()=>{o.parentNode&&o.parentNode.removeChild(o)},500)},1e3)}const B={install(e,t){if(!t.apiEndpoint){console.error("[GameAnalytics] apiEndpoint is required");return}if(!t.gameId){console.error("[GameAnalytics] gameId is required");return}t.playId||(t.playId=D()),e.config.globalProperties._gameAnalyticsOptions=t,e.config.globalProperties._eventQueue=[],e.config.globalProperties._analyticsReady=!1;let o,u,i;const c=e.mount;e.mount=function(r){const a=c.call(this,r);try{const l=y();l.initialize(t),o=j(t),u=X(m=>{l.updateNetworkStatus(m)});let d;t.trackPerformance&&(d=R(),i=setInterval(()=>{const m=d?d():0;l.trackEvent({type:"performance",metadata:{fps:m,memoryUsage:window.performance&&window.performance.memory?Math.round(window.performance.memory.usedJSHeapSize/(1024*1024)):void 0}})},3e4)),e.config.globalProperties._consentSetting!==void 0&&(l.setConsent(e.config.globalProperties._consentSetting),delete e.config.globalProperties._consentSetting);const f=e.config.globalProperties._eventQueue;f&&f.length>0&&(f.forEach(m=>{l.trackEvent(m)}),e.config.globalProperties._eventQueue=[]),e.config.globalProperties._analyticsReady=!0}catch(l){console.error("[GameAnalytics] Failed to initialize analytics store:",l),e.config.globalProperties._analyticsReady=!0}return a},e.config.globalProperties.$gameAnalytics={trackEvent:r=>{if(e.config.globalProperties._analyticsReady)try{y().trackEvent(r)}catch(a){console.error("[GameAnalytics] Error tracking event:",a)}else e.config.globalProperties._eventQueue.push(r)},flushEvents:()=>{if(e.config.globalProperties._analyticsReady)try{y().flushEvents()}catch(r){console.error("[GameAnalytics] Error flushing events:",r)}},enableDebug:()=>{if(e.config.globalProperties._analyticsReady)try{y().setDebugMode(!0)}catch(r){console.error("[GameAnalytics] Error enabling debug mode:",r)}},disableDebug:()=>{if(e.config.globalProperties._analyticsReady)try{y().setDebugMode(!1)}catch(r){console.error("[GameAnalytics] Error disabling debug mode:",r)}},clearEvents:()=>{if(e.config.globalProperties._analyticsReady)try{y().clearEvents()}catch(r){console.error("[GameAnalytics] Error clearing events:",r)}e.config.globalProperties._eventQueue=[]},getEventCount:()=>{if(e.config.globalProperties._analyticsReady)try{return y().eventCount}catch(r){return console.error("[GameAnalytics] Error getting event count:",r),0}return e.config.globalProperties._eventQueue.length},setConsent:r=>{if(e.config.globalProperties._analyticsReady)try{y().setConsent(r)}catch(a){console.error("[GameAnalytics] Error setting consent:",a)}else e.config.globalProperties._consentSetting=r}};const s=e.unmount;e.unmount=function(){try{if(o&&o(),u&&u(),i&&clearInterval(i),e.config.globalProperties._analyticsReady)try{y().flushEvents()}catch(r){console.error("[GameAnalytics] Error flushing events during unmount:",r)}}catch(r){console.error("[GameAnalytics] Error during cleanup:",r)}s.call(this)}}};function K(){const e=y(),t=new WeakMap,o=(i,c,s)=>{const r=s.value||{},a=r.event||s.arg||"click",l=r.target||i.getAttribute("data-track-id")||J(i),d=r.context||{};let f={type:a,target:l,elementData:{type:i.tagName.toLowerCase(),attributes:k(i,"game")},metadata:d};if(c instanceof MouseEvent)f.coordinates={x:c.clientX,y:c.clientY,screenX:c.screenX,screenY:c.screenY};else if(c instanceof TouchEvent&&c.touches.length>0){const m=c.touches[0];f.coordinates={x:m.clientX,y:m.clientY,screenX:m.screenX,screenY:m.screenY}}e.trackEvent(f)},u=(i,c,s,r)=>{var f;const a=((f=s.value)==null?void 0:f.throttle)||300;let l=t.get(i);l||(l={},t.set(i,l));const d=`${c}_${a}`;return l[d]||(l[d]=p(r,a)),l[d]};return{mounted(i,c){(c.arg?[c.arg]:["click"]).forEach(r=>{const l=u(i,r,c,f=>o(i,f,c));i.addEventListener(r,l);const d=i._trackHandlers||{};d[r]=l,i._trackHandlers=d})},updated(i,c){i._trackHandlers&&Object.entries(i._trackHandlers).forEach(([a,l])=>{i.removeEventListener(a,l)});const s=c.arg?[c.arg]:["click"],r={};s.forEach(a=>{const d=u(i,a,c,f=>o(i,f,c));i.addEventListener(a,d),r[a]=d}),i._trackHandlers=r},unmounted(i){i._trackHandlers&&(Object.entries(i._trackHandlers).forEach(([c,s])=>{i.removeEventListener(c,s)}),delete i._trackHandlers),t.delete(i)}}}function J(e){if(e.id)return`#${e.id}`;const t=e.getAttribute("data-game-id");if(t)return`[data-game-id="${t}"]`;if(e.className&&typeof e.className=="string"){const o=e.className.trim().split(/\s+/);if(o.length>0)return`.${o.join(".")}`}return e.tagName.toLowerCase()}function Z(e){e.directive("track",K())}function ee(){const e=y();function t(s){e.trackEvent(s)}function o(s,r={}){const{type:a="interaction",throttleInterval:l=100,metadata:d={}}=r;return p((m,ne={})=>{const E=h.unref(s);if(!E)return;const I=E.getBoundingClientRect();e.trackEvent({type:m||a,target:E.id?`#${E.id}`:E.tagName.toLowerCase(),coordinates:{x:I.left+I.width/2,y:I.top+I.height/2},elementData:{type:E.tagName.toLowerCase(),state:E.getAttribute("data-state")||void 0,attributes:Array.from(E.attributes).filter(T=>T.name.startsWith("data-game-")).reduce((T,x)=>(T[x.name.replace("data-game-","")]=x.value,T),{})},metadata:{...d,...ne}})},l)}function u(s){const r={};return{start:(a={})=>{r[s]=Date.now(),e.trackEvent({type:"interaction_start",target:s,metadata:{interactionId:s,...a}})},end:(a={})=>{const l=r[s];if(!l){console.warn(`[GameAnalytics] No start time found for timed interaction "${s}"`);return}const d=Date.now()-l;return delete r[s],e.trackEvent({type:"interaction_end",target:s,duration:d,metadata:{interactionId:s,...a}}),d},cancel:a=>{const l=r[s];if(!l)return;const d=Date.now()-l;delete r[s],e.trackEvent({type:"interaction_cancelled",target:s,duration:d,metadata:{interactionId:s,reason:a}})}}}function i(s){e.trackEvent({type:"game_state_change",gameState:s})}async function c(){return e.flushEvents()}return{trackEvent:t,trackElement:o,trackTimedInteraction:u,trackGameState:i,flushEvents:c,clearEvents:()=>e.clearEvents(),enableDebug:()=>e.setDebugMode(!0),disableDebug:()=>e.setDebugMode(!1),isDebugMode:h.computed(()=>e.isDebugMode),setConsent:s=>e.setConsent(s),hasConsent:h.computed(()=>e.hasConsent),eventCount:h.computed(()=>e.eventCount),isEnabled:h.computed(()=>e.isEnabled),isOnline:h.computed(()=>e.networkStatus.isOnline)}}const te={install(e,t){e.use(B,t),Z(e)}};v.AnalyticsErrorCode=S,v.default=te,v.generateUUID=D,v.useGameAnalytics=ee,Object.defineProperties(v,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});