@firstlook-uat/sdk 0.2.0 โ†’ 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
- (function(v,x){typeof exports=="object"&&typeof module<"u"?x(exports):typeof define=="function"&&define.amd?define(["exports"],x):(v=typeof globalThis<"u"?globalThis:v||self,x(v.FirstLook={}))})(this,function(v){"use strict";const x=['input[type="password"]',"[data-sensitive]","[data-mask]",".uat-mask"];function T(a){var t,e,s,i,n,o,c,l,h,d;if(!a.endpoint)throw new Error("[FirstLook] 'endpoint' is required. Set your Supabase ingest-session URL.");return{projectId:a.projectId,apiKey:a.apiKey,userId:a.userId,role:a.role,context:a.context??{},endpoint:a.endpoint,triggers:{tapCount:((t=a.triggers)==null?void 0:t.tapCount)??5,deepLink:((e=a.triggers)==null?void 0:e.deepLink)??!0,shake:((s=a.triggers)==null?void 0:s.shake)??!0,customCheck:(i=a.triggers)==null?void 0:i.customCheck},security:{watermark:((n=a.security)==null?void 0:n.watermark)??!0,maskSelectors:((o=a.security)==null?void 0:o.maskSelectors)??x},recording:{domSnapshot:((c=a.recording)==null?void 0:c.domSnapshot)??!0,voice:((l=a.recording)==null?void 0:l.voice)??!0,maxDuration:((h=a.recording)==null?void 0:h.maxDuration)??600,snapshotInterval:((d=a.recording)==null?void 0:d.snapshotInterval)??1e3}}}function y(){return{userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language,screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio,touchSupport:"ontouchstart"in window||navigator.maxTouchPoints>0}}class C{constructor(){this.listeners=new Map}on(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),()=>this.off(t,e)}off(t,e){var s;(s=this.listeners.get(t))==null||s.delete(e)}emit(t){var e,s;(e=this.listeners.get(t.type))==null||e.forEach(i=>{try{i(t)}catch(n){console.error("[FirstLook] Event listener error:",n)}}),(s=this.listeners.get("*"))==null||s.forEach(i=>{try{i(t)}catch(n){console.error("[FirstLook] Event listener error:",n)}})}removeAll(){this.listeners.clear()}}class I{constructor(t){this.events=t,this.quests=[],this.results=[],this.currentIndex=-1,this.sessionStartTime=0,this.blocked=!1,this.pendingFeedbacks=[]}loadQuests(t){this.quests=[...t].sort((e,s)=>e.order-s.order),this.results=[],this.currentIndex=-1,this.blocked=!1}startSession(t){this.sessionStartTime=t,this.advance()}getCurrentQuest(){return this.blocked||this.currentIndex<0||this.currentIndex>=this.quests.length?null:this.quests[this.currentIndex]}getStatus(){return{total:this.quests.length,completed:this.results.filter(t=>t.status==="COMPLETED").length,failed:this.results.filter(t=>t.status==="FAILED").length,current:this.currentIndex,isBlocked:this.blocked,isFinished:this.currentIndex>=this.quests.length}}getQuestStatuses(){return this.quests.map((t,e)=>{const s=this.results.find(i=>i.questId===t.id);return s?s.status:e===this.currentIndex&&!this.blocked?"ACTIVE":this.blocked&&e>=this.currentIndex?"BLOCKED":"PENDING"})}completeCurrentQuest(t,e){const s=this.getCurrentQuest();if(!s)return null;const i={questId:s.id,status:"COMPLETED",timestamp:Date.now(),relativeTime:Date.now()-this.sessionStartTime,voiceMemoBlob:e,logs:t,feedbacks:this.drainFeedbacks()};return this.results.push(i),this.events.emit({type:"quest:completed",questId:s.id}),this.advance(),i}failCurrentQuest(t,e,s){const i=this.getCurrentQuest();if(!i)return null;const n={questId:i.id,status:"FAILED",timestamp:Date.now(),relativeTime:Date.now()-this.sessionStartTime,voiceMemoBlob:s,logs:t,comment:e,feedbacks:this.drainFeedbacks()};return this.results.push(n),this.events.emit({type:"quest:failed",questId:i.id}),i.blocking?(this.blocked=!0,this.events.emit({type:"quest:blocked",questId:i.id})):this.advance(),n}addFeedback(t){this.pendingFeedbacks.push(t)}getResults(){return[...this.results]}drainFeedbacks(){const t=this.pendingFeedbacks;return this.pendingFeedbacks=[],t}advance(){this.currentIndex++,this.currentIndex<this.quests.length&&this.events.emit({type:"quest:started",questId:this.quests[this.currentIndex].id})}}function r(a,t,e){const s=document.createElement(a);if(t)for(const[i,n]of Object.entries(t))i==="className"?s.className=n:s.setAttribute(i,n);if(e)for(const i of e)typeof i=="string"?s.appendChild(document.createTextNode(i)):s.appendChild(i);return s}function q(){return`fl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`}function M(a){const t=Math.floor(a/60),e=a%60;return`${t.toString().padStart(2,"0")}:${e.toString().padStart(2,"0")}`}class E{constructor(t,e){this.shadowRoot=t,this.callbacks=e,this.minimized=!1,this.timerInterval=null,this.startTime=0,this.root=document.createElement("div"),this.root.className="fl-quest-overlay",this.shadowRoot.appendChild(this.root)}renderQuest(t,e,s){this.minimized=!1,this.root.className="fl-quest-overlay",this.root.innerHTML="";const i=e.indexOf("ACTIVE"),n=r("div",{className:"fl-quest-header"},[r("div",{className:"fl-quest-header-left"},[r("span",{className:"fl-quest-badge"},[`Q${i+1}/${e.length}`]),r("span",{className:"fl-quest-title"},[t.title])]),this.createMinimizeBtn()]);this.root.appendChild(n);const o=r("div",{className:"fl-quest-content"}),c=r("div",{className:"fl-quest-progress"});for(const f of e){const m=f==="COMPLETED"?"fl-completed":f==="FAILED"?"fl-failed":f==="ACTIVE"?"fl-active":"";c.appendChild(r("div",{className:`fl-quest-progress-dot ${m}`}))}const l=r("div",{className:"fl-quest-body"},[c,r("p",{className:"fl-quest-description"},[t.description]),r("div",{className:"fl-quest-memo-row"},[this.createButton("fl-btn fl-btn-memo","๐Ÿ“ ใƒกใƒข",()=>this.renderFeedbackModal(t.title))]),r("div",{className:"fl-quest-actions"},[this.createButton("fl-btn fl-btn-ok","โœ“ OK",this.callbacks.onOk),this.createButton("fl-btn fl-btn-ng","โœ— NG",this.callbacks.onNg)])]);o.appendChild(l),s&&o.appendChild(r("div",{className:"fl-voice-indicator"},[r("span",{className:"fl-voice-dot"}),"Recording..."]));const h=r("div",{className:"fl-status-bar"});s&&h.appendChild(r("span",{className:"fl-status-recording"},[r("span",{className:"fl-voice-dot"}),"REC"]));const d=r("span",{className:"fl-status-timer"},["00:00"]);h.appendChild(d),o.appendChild(h),this.root.appendChild(o),this.startTimer(d),this.root.onclick=()=>{this.minimized&&(this.minimized=!1,this.root.className="fl-quest-overlay",this.root.innerHTML="",this.root.appendChild(n),this.root.appendChild(o),this.startTimer(d))}}renderFeedbackModal(t,e="memo"){const s=r("div",{className:"fl-feedback-modal"}),i=e==="ng",l=r("div",{className:i?"fl-quest-header fl-quest-header-ng":"fl-quest-header"},[r("div",{className:"fl-quest-header-left"},[r("span",{className:i?"fl-quest-badge fl-badge-ng":"fl-quest-badge"},[i?"NG":"๐Ÿ“"]),r("span",{className:"fl-quest-title"},[t])])]);s.appendChild(l);const h=r("div",{className:"fl-quest-content"}),d=document.createElement("textarea");d.className="fl-feedback-textarea fl-feedback-textarea-modal",d.placeholder=i?"ไฝ•ใŒใ†ใพใใ„ใ‹ใชใ‹ใฃใŸใ‹ๆ•™ใˆใฆใใ ใ•ใ„...":"ๆฐ—ใฅใ„ใŸใ“ใจใ‚’ใƒกใƒข...๐Ÿ“",d.rows=3,h.appendChild(d);const f=r("div",{className:"fl-quest-actions fl-feedback-modal-actions"},[this.createButton("fl-btn fl-btn-finish","้€ไฟก",()=>{const m=d.value.trim();if(!m&&!i){s.remove();return}i?this.callbacks.onNgWithFeedback(m):this.callbacks.onMemo(m),s.remove()}),this.createButton("fl-btn fl-btn-skip","ใ‚ญใƒฃใƒณใ‚ปใƒซ",()=>{i&&this.callbacks.onNgWithFeedback(""),s.remove()})]);h.appendChild(f),s.appendChild(h),this.root.appendChild(s),d.focus()}renderSummary(t,e,s){this.stopTimer(),this.root.className="fl-quest-overlay",this.root.innerHTML="";const i=r("div",{className:"fl-summary"},[r("div",{className:"fl-summary-icon"},[t===s?"๐ŸŽ‰":"๐Ÿ“‹"]),r("div",{className:"fl-summary-title"},[t===s?"All Quests Complete!":"Session Complete"]),r("div",{className:"fl-summary-subtitle"},[`${t+e} of ${s} quests attempted`]),r("div",{className:"fl-summary-stats"},[this.createStat(t.toString(),"Passed","fl-ok"),this.createStat(e.toString(),"Failed","fl-ng")]),this.createButton("fl-btn fl-btn-finish","Finish & Upload",this.callbacks.onFinish)]);this.root.appendChild(i)}renderBlocked(t){this.stopTimer(),this.root.className="fl-quest-overlay",this.root.innerHTML="";const e=r("div",{className:"fl-summary"},[r("div",{className:"fl-summary-icon"},["๐Ÿ›‘"]),r("div",{className:"fl-summary-title"},["Blocked"]),r("div",{className:"fl-summary-subtitle"},[`Quest "${t}" is blocking. Session halted.`]),this.createButton("fl-btn fl-btn-finish","Finish & Upload",this.callbacks.onFinish)]);this.root.appendChild(e)}destroy(){this.stopTimer(),this.root.remove()}createMinimizeBtn(){const t=r("button",{className:"fl-quest-minimize-btn"},["โˆ’"]);return t.onclick=e=>{e.stopPropagation(),this.minimized=!0,this.root.className="fl-quest-overlay fl-minimized",this.root.innerHTML="";const s=r("span",{className:"fl-minimize-icon"},["๐Ÿ”"]);this.root.appendChild(s),this.callbacks.onMinimize()},t}createButton(t,e,s){const i=r("button",{className:t});return i.textContent=e,i.onclick=n=>{n.stopPropagation(),s()},i}createStat(t,e,s){return r("div",{className:"fl-stat"},[r("div",{className:`fl-stat-value ${s}`},[t]),r("div",{className:"fl-stat-label"},[e])])}startTimer(t){this.stopTimer(),this.startTime||(this.startTime=Date.now()),this.timerInterval=setInterval(()=>{const e=Math.floor((Date.now()-this.startTime)/1e3);t.textContent=M(e)},1e3)}stopTimer(){this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}}const R=100,D=2*1024*1024;class L{constructor(t,e,s){this.config=t,this.events=e,this.maskSelector=s,this.actionLogs=[],this.snapshots=[],this.snapshotTimer=null,this.startTime=0,this.running=!1,this.handleClick=this.onClick.bind(this),this.handleScroll=this.throttle(this.onScroll.bind(this),500),this.handleInput=this.onInput.bind(this)}start(){this.running||(this.running=!0,this.startTime=Date.now(),this.actionLogs=[],this.snapshots=[],document.addEventListener("click",this.handleClick,{capture:!0,passive:!0}),document.addEventListener("scroll",this.handleScroll,{capture:!0,passive:!0}),document.addEventListener("input",this.handleInput,{capture:!0,passive:!0}),this.config.recording.domSnapshot&&(this.captureSnapshot(),this.snapshotTimer=setInterval(()=>this.captureSnapshot(),this.config.recording.snapshotInterval)),this.events.emit({type:"recording:started"}))}stop(){this.running&&(this.running=!1,document.removeEventListener("click",this.handleClick,{capture:!0}),document.removeEventListener("scroll",this.handleScroll,{capture:!0}),document.removeEventListener("input",this.handleInput,{capture:!0}),this.snapshotTimer&&(clearInterval(this.snapshotTimer),this.snapshotTimer=null),this.captureSnapshot(),this.events.emit({type:"recording:stopped"}))}getActionLogs(){return[...this.actionLogs]}getSnapshots(){return[...this.snapshots]}getElapsedMs(){return this.running?Date.now()-this.startTime:0}addLog(t){this.actionLogs.push({...t,timestamp:Date.now()-this.startTime})}onClick(t){const e=t.target;this.addLog({type:"click",target:k(e)})}onScroll(){this.addLog({type:"scroll",value:`${window.scrollX},${window.scrollY}`})}onInput(t){const e=t.target;if(e.hasAttribute("data-fl-masked"))this.addLog({type:"input",target:k(e),value:"[MASKED]"});else{const s=e.value;this.addLog({type:"input",target:k(e),value:s==null?void 0:s.slice(0,100)})}}captureSnapshot(){try{const t=document.documentElement.cloneNode(!0);if(this.maskSelector){const n=t.querySelectorAll(this.maskSelector);for(const o of n)(o instanceof HTMLInputElement||o instanceof HTMLTextAreaElement)&&(o.value="***"),o.textContent="***"}const e=t.querySelectorAll("script");for(const n of e)n.remove();const s=t.querySelector("#firstlook-sdk-root");s==null||s.remove();const i=t.outerHTML;if(i.length>D)return;this.snapshots.length>=R&&this.snapshots.shift(),this.snapshots.push({type:"dom-snapshot",timestamp:Date.now()-this.startTime,data:i})}catch{}}throttle(t,e){let s=0;return(...i)=>{const n=Date.now();n-s>=e&&(s=n,t(...i))}}}function k(a){var n;const t=a.tagName.toLowerCase(),e=a.id?`#${a.id}`:"",s=a.className&&typeof a.className=="string"?`.${a.className.trim().split(/\s+/).slice(0,2).join(".")}`:"",i=((n=a.textContent)==null?void 0:n.trim().slice(0,30))||"";return`${t}${e}${s}${i?` "${i}"`:""}`}class A{constructor(t){this.events=t,this.mediaRecorder=null,this.stream=null,this.chunks=[],this._recording=!1}get recording(){return this._recording}async start(){if(!this._recording)try{this.stream=await navigator.mediaDevices.getUserMedia({audio:!0});const t=this.getSupportedMimeType(),e=t?{mimeType:t}:{};this.mediaRecorder=new MediaRecorder(this.stream,e),this.chunks=[],this.mediaRecorder.ondataavailable=s=>{s.data.size>0&&this.chunks.push(s.data)},this.mediaRecorder.start(1e3),this._recording=!0}catch(t){console.warn("[FirstLook] Voice recording unavailable:",t),this.events.emit({type:"error",error:new Error(`Voice recording failed: ${t.message}`)})}}async stopAsync(){return!this._recording||!this.mediaRecorder?null:new Promise(t=>{this.mediaRecorder.onstop=()=>{var s;const e=this.chunks.length>0?new Blob(this.chunks,{type:((s=this.mediaRecorder)==null?void 0:s.mimeType)||"audio/webm"}):null;this.cleanup(),t(e)},this.mediaRecorder.stop()})}async captureSnippet(t=1e4){return await this.start(),new Promise(e=>{setTimeout(async()=>{const s=await this.stopAsync();e(s)},t)})}cleanup(){if(this._recording=!1,this.stream){for(const t of this.stream.getTracks())t.stop();this.stream=null}this.mediaRecorder=null,this.chunks=[]}getSupportedMimeType(){const t=["audio/webm;codecs=opus","audio/webm","audio/ogg;codecs=opus","audio/mp4"];for(const e of t)if(typeof MediaRecorder<"u"&&MediaRecorder.isTypeSupported(e))return e;return""}}class N{constructor(t,e){this.shadowRoot=t,this.config=e,this.container=null,this.refreshInterval=null,this.cachedIp=null}show(){this.container||(this.container=document.createElement("div"),this.container.className="fl-watermark",this.renderTiles(),this.shadowRoot.appendChild(this.container),this.fetchIp().then(()=>this.renderTiles()),this.refreshInterval=setInterval(()=>this.renderTiles(),6e4))}hide(){this.container&&(this.container.remove(),this.container=null),this.refreshInterval&&(clearInterval(this.refreshInterval),this.refreshInterval=null)}async fetchIp(){if(!this.cachedIp)try{const t=await fetch("https://api.ipify.org?format=text");t.ok&&(this.cachedIp=await t.text())}catch{this.cachedIp="N/A"}}renderTiles(){if(!this.container)return;this.container.innerHTML="";const t=this.cachedIp??"",e=new Date().toISOString().slice(0,19),s=t?`${this.config.userId} | ${e} | ${t}`:`${this.config.userId} | ${e}`,i=320,n=80,o=Math.ceil(window.innerWidth/i)+1,c=Math.ceil(window.innerHeight/n)+1;for(let l=0;l<c;l++)for(let h=0;h<o;h++){const d=document.createElement("span");d.className="fl-watermark-tile",d.textContent=s,d.style.left=`${h*i}px`,d.style.top=`${l*n}px`,this.container.appendChild(d)}}}class F{constructor(t){this.selectors=t,this.observer=null,this.maskedElements=new Set}start(){this.scanAndMark(),this.observer=new MutationObserver(()=>this.scanAndMark()),this.observer.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["type","class","data-sensitive","data-mask"]})}stop(){var t;(t=this.observer)==null||t.disconnect(),this.observer=null;for(const e of this.maskedElements)e.removeAttribute("data-fl-masked");this.maskedElements.clear()}isMasked(t){return t.hasAttribute("data-fl-masked")}getCombinedSelector(){return this.selectors.join(", ")}scanAndMark(){const t=this.getCombinedSelector();if(t)try{const e=document.querySelectorAll(t);for(const s of e)this.maskedElements.has(s)||(s.setAttribute("data-fl-masked","true"),this.maskedElements.add(s));for(const s of this.maskedElements)document.contains(s)||this.maskedElements.delete(s)}catch{}}}const z="firstlook_uat",P=1,u={sessions:"sessions",recordings:"recordings",annotations:"annotations",uploadQueue:"upload_queue"};class O{constructor(){this.db=null}async open(){if(!this.db)return new Promise((t,e)=>{const s=indexedDB.open(z,P);s.onupgradeneeded=()=>{const i=s.result;i.objectStoreNames.contains(u.sessions)||i.createObjectStore(u.sessions,{keyPath:"sessionId"}),i.objectStoreNames.contains(u.recordings)||i.createObjectStore(u.recordings,{autoIncrement:!0}).createIndex("sessionId","sessionId",{unique:!1}),i.objectStoreNames.contains(u.annotations)||i.createObjectStore(u.annotations,{autoIncrement:!0}).createIndex("sessionId","sessionId",{unique:!1}),i.objectStoreNames.contains(u.uploadQueue)||i.createObjectStore(u.uploadQueue,{autoIncrement:!0})},s.onsuccess=()=>{this.db=s.result,t()},s.onerror=()=>e(s.error)})}async saveSession(t){await this.put(u.sessions,t)}async getSession(t){return this.get(u.sessions,t)}async saveRecording(t,e){await this.put(u.recordings,{sessionId:t,...e})}async saveAnnotation(t,e){await this.put(u.annotations,{sessionId:t,...e})}async getAnnotations(t){return this.getAllByIndex(u.annotations,"sessionId",t)}async enqueueUpload(t){await this.put(u.uploadQueue,{payload:t,createdAt:Date.now(),attempts:0})}async getPendingUploads(){const t=this.ensureDb();return new Promise((e,s)=>{const o=t.transaction(u.uploadQueue,"readonly").objectStore(u.uploadQueue).openCursor(),c=[];o.onsuccess=()=>{const l=o.result;l?(c.push({key:l.key,payload:l.value.payload,attempts:l.value.attempts,lastAttemptAt:l.value.lastAttemptAt}),l.continue()):e(c)},o.onerror=()=>s(o.error)})}async removeFromQueue(t){const e=this.ensureDb();return new Promise((s,i)=>{const o=e.transaction(u.uploadQueue,"readwrite").objectStore(u.uploadQueue).delete(t);o.onsuccess=()=>s(),o.onerror=()=>i(o.error)})}async incrementAttempts(t){const e=this.ensureDb();return new Promise((s,i)=>{const n=e.transaction(u.uploadQueue,"readwrite"),o=n.objectStore(u.uploadQueue),c=o.get(t);c.onsuccess=()=>{const l=c.result;l&&(l.attempts=(l.attempts??0)+1,l.lastAttemptAt=Date.now(),o.put(l,t))},n.oncomplete=()=>s(),n.onerror=()=>i(n.error)})}async clearSession(t){const s=this.ensureDb().transaction([u.sessions,u.recordings,u.annotations],"readwrite");s.objectStore(u.sessions).delete(t);for(const i of[u.recordings,u.annotations]){const c=s.objectStore(i).index("sessionId").openCursor(IDBKeyRange.only(t));c.onsuccess=()=>{const l=c.result;l&&(l.delete(),l.continue())}}return new Promise((i,n)=>{s.oncomplete=()=>i(),s.onerror=()=>n(s.error)})}close(){var t;(t=this.db)==null||t.close(),this.db=null}ensureDb(){if(!this.db)throw new Error("[FirstLook] Storage not opened. Call open() first.");return this.db}async put(t,e){const s=this.ensureDb();return new Promise((i,n)=>{const o=s.transaction(t,"readwrite");o.objectStore(t).put(e),o.oncomplete=()=>i(),o.onerror=()=>n(o.error)})}async get(t,e){const s=this.ensureDb();return new Promise((i,n)=>{const c=s.transaction(t,"readonly").objectStore(t).get(e);c.onsuccess=()=>i(c.result),c.onerror=()=>n(c.error)})}async getAllByIndex(t,e,s){const i=this.ensureDb();return new Promise((n,o)=>{const h=i.transaction(t,"readonly").objectStore(t).index(e).getAll(s);h.onsuccess=()=>n(h.result),h.onerror=()=>o(h.error)})}}const S=["#e17055","#6c5ce7","#00b894","#fdcb6e","#ffffff"];class Q{constructor(t){this.shadowRoot=t,this.overlay=null,this.canvas=null,this.ctx=null,this.drawing=!1,this.currentPath=[],this.paths=[],this.selectedColor=S[0],this.screenshotDataUrl=""}async open(){return this.screenshotDataUrl=await this.captureScreenshot(),new Promise(t=>{this.overlay=r("div",{className:"fl-annotation-overlay"});const e=r("div",{className:"fl-annotation-canvas-wrap"});this.canvas=document.createElement("canvas"),this.canvas.className="fl-annotation-canvas",e.appendChild(this.canvas),this.overlay.appendChild(e);const s=r("input",{className:"fl-annotation-comment",type:"text",placeholder:"Add a comment..."}),i=r("div",{className:"fl-color-picker"});for(const l of S){const h=r("div",{className:`fl-color-swatch ${l===this.selectedColor?"fl-selected":""}`});h.style.background=l,h.onclick=()=>{this.selectedColor=l,i.querySelectorAll(".fl-color-swatch").forEach(d=>d.classList.remove("fl-selected")),h.classList.add("fl-selected")},i.appendChild(h)}const n=r("button",{className:"fl-btn fl-btn-ok"},["Submit"]);n.onclick=()=>{const l={screenshotDataUrl:this.getAnnotatedImage(),drawings:[...this.paths],comment:s.value,timestamp:Date.now()};this.close(),t(l)};const o=r("button",{className:"fl-btn fl-btn-ng"},["Cancel"]);o.onclick=()=>{this.close(),t(null)};const c=r("div",{className:"fl-annotation-toolbar"},[i,s,n,o]);this.overlay.appendChild(c),this.shadowRoot.appendChild(this.overlay),this.initCanvas()})}close(){var t;(t=this.overlay)==null||t.remove(),this.overlay=null,this.canvas=null,this.ctx=null,this.paths=[],this.currentPath=[]}async captureScreenshot(){try{const t=window.innerWidth,e=window.innerHeight,s=document.documentElement.cloneNode(!0),i=s.querySelector("#firstlook-sdk-root");i==null||i.remove();const n=s.querySelectorAll('[data-fl-masked], input[type="password"]');for(const p of n)(p instanceof HTMLInputElement||p instanceof HTMLTextAreaElement)&&(p.value="[MASKED]"),p.textContent="[MASKED]";for(const p of s.querySelectorAll("script"))p.remove();const o=s.querySelector("body");if(o){const p=window.getComputedStyle(document.body);o.style.backgroundColor=p.backgroundColor,o.style.color=p.color,o.style.fontFamily=p.fontFamily,o.style.margin="0",o.style.overflow="hidden"}const c=new XMLSerializer().serializeToString(s),l=`<svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${e}">
1
+ (function(v,k){typeof exports=="object"&&typeof module<"u"?k(exports):typeof define=="function"&&define.amd?define(["exports"],k):(v=typeof globalThis<"u"?globalThis:v||self,k(v.FirstLook={}))})(this,function(v){"use strict";const k=['input[type="password"]',"[data-sensitive]","[data-mask]",".uat-mask"];function q(a){var t,e,s,i,n,o,l,c,d,h,p;if(!a.endpoint)throw new Error("[FirstLook] 'endpoint' is required. Set your Supabase ingest-session URL.");return{projectId:a.projectId,apiKey:a.apiKey,userId:a.userId,role:a.role,context:a.context??{},endpoint:a.endpoint,triggers:{tapCount:((t=a.triggers)==null?void 0:t.tapCount)??5,deepLink:((e=a.triggers)==null?void 0:e.deepLink)??!0,shake:((s=a.triggers)==null?void 0:s.shake)??!0,keyboard:((i=a.triggers)==null?void 0:i.keyboard)??!0,customCheck:(n=a.triggers)==null?void 0:n.customCheck},security:{watermark:((o=a.security)==null?void 0:o.watermark)??!0,maskSelectors:((l=a.security)==null?void 0:l.maskSelectors)??k},recording:{domSnapshot:((c=a.recording)==null?void 0:c.domSnapshot)??!0,voice:((d=a.recording)==null?void 0:d.voice)??!0,maxDuration:((h=a.recording)==null?void 0:h.maxDuration)??600,snapshotInterval:((p=a.recording)==null?void 0:p.snapshotInterval)??1e3}}}function S(){return{userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language,screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio,touchSupport:"ontouchstart"in window||navigator.maxTouchPoints>0}}class I{constructor(){this.listeners=new Map}on(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),()=>this.off(t,e)}off(t,e){var s;(s=this.listeners.get(t))==null||s.delete(e)}emit(t){var e,s;(e=this.listeners.get(t.type))==null||e.forEach(i=>{try{i(t)}catch(n){console.error("[FirstLook] Event listener error:",n)}}),(s=this.listeners.get("*"))==null||s.forEach(i=>{try{i(t)}catch(n){console.error("[FirstLook] Event listener error:",n)}})}removeAll(){this.listeners.clear()}}class M{constructor(t){this.events=t,this.quests=[],this.results=[],this.currentIndex=-1,this.sessionStartTime=0,this.blocked=!1,this.pendingFeedbacks=[]}loadQuests(t){this.quests=[...t].sort((e,s)=>e.order-s.order),this.results=[],this.currentIndex=-1,this.blocked=!1}startSession(t){this.sessionStartTime=t,this.advance()}getCurrentQuest(){return this.blocked||this.currentIndex<0||this.currentIndex>=this.quests.length?null:this.quests[this.currentIndex]}getStatus(){return{total:this.quests.length,completed:this.results.filter(t=>t.status==="COMPLETED").length,failed:this.results.filter(t=>t.status==="FAILED").length,current:this.currentIndex,isBlocked:this.blocked,isFinished:this.currentIndex>=this.quests.length}}getQuestStatuses(){return this.quests.map((t,e)=>{const s=this.results.find(i=>i.questId===t.id);return s?s.status:e===this.currentIndex&&!this.blocked?"ACTIVE":this.blocked&&e>=this.currentIndex?"BLOCKED":"PENDING"})}completeCurrentQuest(t,e){const s=this.getCurrentQuest();if(!s)return null;const i={questId:s.id,status:"COMPLETED",timestamp:Date.now(),relativeTime:Date.now()-this.sessionStartTime,voiceMemoBlob:e==null?void 0:e.voiceMemo,logs:t,comment:e==null?void 0:e.comment,concern:(e==null?void 0:e.concern)??!1,feedbacks:this.drainFeedbacks()};return this.results.push(i),this.events.emit({type:"quest:completed",questId:s.id}),this.advance(),i}failCurrentQuest(t,e,s){const i=this.getCurrentQuest();if(!i)return null;const n={questId:i.id,status:"FAILED",timestamp:Date.now(),relativeTime:Date.now()-this.sessionStartTime,voiceMemoBlob:s,logs:t,comment:e,concern:!1,feedbacks:this.drainFeedbacks()};return this.results.push(n),this.events.emit({type:"quest:failed",questId:i.id}),i.blocking?(this.blocked=!0,this.events.emit({type:"quest:blocked",questId:i.id})):this.advance(),n}addFeedback(t){this.pendingFeedbacks.push(t)}getResults(){return[...this.results]}drainFeedbacks(){const t=this.pendingFeedbacks;return this.pendingFeedbacks=[],t}advance(){this.currentIndex++,this.currentIndex<this.quests.length&&this.events.emit({type:"quest:started",questId:this.quests[this.currentIndex].id})}}function r(a,t,e){const s=document.createElement(a);if(t)for(const[i,n]of Object.entries(t))i==="className"?s.className=n:s.setAttribute(i,n);if(e)for(const i of e)typeof i=="string"?s.appendChild(document.createTextNode(i)):s.appendChild(i);return s}function E(){return`fl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`}function R(a){const t=Math.floor(a/60),e=a%60;return`${t.toString().padStart(2,"0")}:${e.toString().padStart(2,"0")}`}class L{constructor(t,e){this.shadowRoot=t,this.callbacks=e,this.minimized=!1,this.timerInterval=null,this.startTime=0,this.root=document.createElement("div"),this.root.className="fl-quest-overlay";for(const s of["click","mousedown","mouseup","pointerdown","pointerup","touchstart","touchend"])this.root.addEventListener(s,i=>i.stopPropagation());this.shadowRoot.appendChild(this.root)}renderQuest(t,e,s){this.minimized=!1,this.root.className="fl-quest-overlay",this.root.innerHTML="";const i=e.indexOf("ACTIVE"),n=r("div",{className:"fl-quest-header"},[r("div",{className:"fl-quest-header-left"},[r("span",{className:"fl-quest-badge"},[`Q${i+1}/${e.length}`]),r("span",{className:"fl-quest-title"},[t.title])]),this.createMinimizeBtn()]);this.root.appendChild(n);const o=r("div",{className:"fl-quest-content"}),l=r("div",{className:"fl-quest-progress"});for(const p of e){const g=p==="COMPLETED"?"fl-completed":p==="FAILED"?"fl-failed":p==="ACTIVE"?"fl-active":"";l.appendChild(r("div",{className:`fl-quest-progress-dot ${g}`}))}const c=r("div",{className:"fl-quest-body"},[l,r("p",{className:"fl-quest-description"},[t.description]),r("div",{className:"fl-quest-memo-row"},[this.createButton("fl-btn fl-btn-memo","๐Ÿ“ ใƒกใƒขใ‚’ๆฎ‹ใ™",()=>this.renderFeedbackModal(t.title))]),r("div",{className:"fl-quest-actions"},[this.createButton("fl-btn fl-btn-ok","โœ“ OK",()=>this.renderFeedbackModal(t.title,"ok")),this.createButton("fl-btn fl-btn-concern","โš  ๆฐ—ใซใชใ‚‹",()=>this.renderFeedbackModal(t.title,"concern")),this.createButton("fl-btn fl-btn-ng","โœ— NG",this.callbacks.onNg)])]);o.appendChild(c),s&&o.appendChild(r("div",{className:"fl-voice-indicator"},[r("span",{className:"fl-voice-dot"}),"Recording..."]));const d=r("div",{className:"fl-status-bar"});s&&d.appendChild(r("span",{className:"fl-status-recording"},[r("span",{className:"fl-voice-dot"}),"REC"]));const h=r("span",{className:"fl-status-timer"},["00:00"]);d.appendChild(h),o.appendChild(d),this.root.appendChild(o),this.startTimer(h),this.root.onclick=()=>{this.minimized&&(this.minimized=!1,this.root.className="fl-quest-overlay",this.root.innerHTML="",this.root.appendChild(n),this.root.appendChild(o),this.startTimer(h))}}renderFeedbackModal(t,e="memo"){const s=r("div",{className:"fl-feedback-modal"}),i={ng:"fl-quest-header fl-quest-header-ng",ok:"fl-quest-header fl-quest-header-ok",concern:"fl-quest-header fl-quest-header-concern",memo:"fl-quest-header"},n={ng:"NG",ok:"โœ“ OK",concern:"โš  ๆฐ—ใซใชใ‚‹",memo:"๐Ÿ“"},o={ng:"fl-quest-badge fl-badge-ng",ok:"fl-quest-badge fl-badge-ok",concern:"fl-quest-badge fl-badge-concern",memo:"fl-quest-badge"},l=r("div",{className:i[e]},[r("div",{className:"fl-quest-header-left"},[r("span",{className:o[e]},[n[e]]),r("span",{className:"fl-quest-title"},[t])])]);s.appendChild(l);const c=r("div",{className:"fl-quest-content"}),d={ng:"ไฝ•ใŒใ†ใพใใ„ใ‹ใชใ‹ใฃใŸใ‹ๆ•™ใˆใฆใใ ใ•ใ„...",ok:"ใ‚ณใƒกใƒณใƒˆ๏ผˆไปปๆ„๏ผ‰",concern:"ๆฐ—ใซใชใฃใŸ็‚นใ‚’ๆ•™ใˆใฆใใ ใ•ใ„...",memo:"ๆฐ—ใฅใ„ใŸใ“ใจใ‚’ใƒกใƒข...๐Ÿ“"},h=document.createElement("textarea");h.className="fl-feedback-textarea fl-feedback-textarea-modal",h.placeholder=d[e],h.rows=3,c.appendChild(h);const p="้€ไฟก",g=e==="ok"?"ใ‚นใ‚ญใƒƒใƒ—":"ใ‚ญใƒฃใƒณใ‚ปใƒซ",f=r("div",{className:"fl-quest-actions fl-feedback-modal-actions"},[this.createButton("fl-btn fl-btn-skip",g,()=>{e==="ng"?this.callbacks.onNgWithFeedback(""):e==="ok"&&this.callbacks.onOkWithFeedback(""),s.remove()}),this.createButton("fl-btn fl-btn-finish",p,()=>{const m=h.value.trim();if(e==="ng")this.callbacks.onNgWithFeedback(m);else if(e==="ok")this.callbacks.onOkWithFeedback(m);else if(e==="concern"){if(!m)return;this.callbacks.onConcern(m)}else{if(!m){s.remove();return}this.callbacks.onMemo(m)}s.remove()})]);c.appendChild(f),s.appendChild(c),this.root.appendChild(s),h.focus()}renderSummary(t,e,s){this.stopTimer(),this.root.className="fl-quest-overlay",this.root.innerHTML="";const i=r("div",{className:"fl-summary"},[r("div",{className:"fl-summary-icon"},[t===s?"๐ŸŽ‰":"๐Ÿ“‹"]),r("div",{className:"fl-summary-title"},[t===s?"All Quests Complete!":"Session Complete"]),r("div",{className:"fl-summary-subtitle"},[`${t+e} of ${s} quests attempted`]),r("div",{className:"fl-summary-stats"},[this.createStat(t.toString(),"Passed","fl-ok"),this.createStat(e.toString(),"Failed","fl-ng")]),this.createButton("fl-btn fl-btn-finish","Finish & Upload",this.callbacks.onFinish)]);this.root.appendChild(i)}renderBlocked(t){this.stopTimer(),this.root.className="fl-quest-overlay",this.root.innerHTML="";const e=r("div",{className:"fl-summary"},[r("div",{className:"fl-summary-icon"},["๐Ÿ›‘"]),r("div",{className:"fl-summary-title"},["Blocked"]),r("div",{className:"fl-summary-subtitle"},[`Quest "${t}" is blocking. Session halted.`]),this.createButton("fl-btn fl-btn-finish","Finish & Upload",this.callbacks.onFinish)]);this.root.appendChild(e)}destroy(){this.stopTimer(),this.root.remove()}createMinimizeBtn(){const t=r("button",{className:"fl-quest-minimize-btn"},["โˆ’"]);return t.onclick=e=>{e.stopPropagation(),this.minimized=!0,this.root.className="fl-quest-overlay fl-minimized",this.root.innerHTML="";const s=r("span",{className:"fl-minimize-icon"},["๐Ÿ”"]);this.root.appendChild(s),this.callbacks.onMinimize()},t}createButton(t,e,s){const i=r("button",{className:t});return i.textContent=e,i.onclick=n=>{n.stopPropagation(),s()},i}createStat(t,e,s){return r("div",{className:"fl-stat"},[r("div",{className:`fl-stat-value ${s}`},[t]),r("div",{className:"fl-stat-label"},[e])])}startTimer(t){this.stopTimer(),this.startTime||(this.startTime=Date.now()),this.timerInterval=setInterval(()=>{const e=Math.floor((Date.now()-this.startTime)/1e3);t.textContent=R(e)},1e3)}stopTimer(){this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}}const D=100,A=2*1024*1024;class N{constructor(t,e,s){this.config=t,this.events=e,this.maskSelector=s,this.actionLogs=[],this.snapshots=[],this.snapshotTimer=null,this.startTime=0,this.running=!1,this.handleClick=this.onClick.bind(this),this.handleScroll=this.throttle(this.onScroll.bind(this),500),this.handleInput=this.onInput.bind(this)}start(){this.running||(this.running=!0,this.startTime=Date.now(),this.actionLogs=[],this.snapshots=[],document.addEventListener("click",this.handleClick,{capture:!0,passive:!0}),document.addEventListener("scroll",this.handleScroll,{capture:!0,passive:!0}),document.addEventListener("input",this.handleInput,{capture:!0,passive:!0}),this.config.recording.domSnapshot&&(this.captureSnapshot(),this.snapshotTimer=setInterval(()=>this.captureSnapshot(),this.config.recording.snapshotInterval)),this.events.emit({type:"recording:started"}))}stop(){this.running&&(this.running=!1,document.removeEventListener("click",this.handleClick,{capture:!0}),document.removeEventListener("scroll",this.handleScroll,{capture:!0}),document.removeEventListener("input",this.handleInput,{capture:!0}),this.snapshotTimer&&(clearInterval(this.snapshotTimer),this.snapshotTimer=null),this.captureSnapshot(),this.events.emit({type:"recording:stopped"}))}getActionLogs(){return[...this.actionLogs]}getSnapshots(){return[...this.snapshots]}getElapsedMs(){return this.running?Date.now()-this.startTime:0}addLog(t){this.actionLogs.push({...t,timestamp:Date.now()-this.startTime})}onClick(t){const e=t.target;this.addLog({type:"click",target:w(e)})}onScroll(){this.addLog({type:"scroll",value:`${window.scrollX},${window.scrollY}`})}onInput(t){const e=t.target;if(e.hasAttribute("data-fl-masked"))this.addLog({type:"input",target:w(e),value:"[MASKED]"});else{const s=e.value;this.addLog({type:"input",target:w(e),value:s==null?void 0:s.slice(0,100)})}}captureSnapshot(){try{const t=document.documentElement.cloneNode(!0);if(this.maskSelector){const n=t.querySelectorAll(this.maskSelector);for(const o of n)(o instanceof HTMLInputElement||o instanceof HTMLTextAreaElement)&&(o.value="***"),o.textContent="***"}const e=t.querySelectorAll("script");for(const n of e)n.remove();const s=t.querySelector("#firstlook-sdk-root");s==null||s.remove();const i=t.outerHTML;if(i.length>A)return;this.snapshots.length>=D&&this.snapshots.shift(),this.snapshots.push({type:"dom-snapshot",timestamp:Date.now()-this.startTime,data:i})}catch{}}throttle(t,e){let s=0;return(...i)=>{const n=Date.now();n-s>=e&&(s=n,t(...i))}}}function w(a){var n;const t=a.tagName.toLowerCase(),e=a.id?`#${a.id}`:"",s=a.className&&typeof a.className=="string"?`.${a.className.trim().split(/\s+/).slice(0,2).join(".")}`:"",i=((n=a.textContent)==null?void 0:n.trim().slice(0,30))||"";return`${t}${e}${s}${i?` "${i}"`:""}`}class F{constructor(t){this.events=t,this.mediaRecorder=null,this.stream=null,this.chunks=[],this._recording=!1}get recording(){return this._recording}async start(){if(!this._recording)try{this.stream=await navigator.mediaDevices.getUserMedia({audio:!0});const t=this.getSupportedMimeType(),e=t?{mimeType:t}:{};this.mediaRecorder=new MediaRecorder(this.stream,e),this.chunks=[],this.mediaRecorder.ondataavailable=s=>{s.data.size>0&&this.chunks.push(s.data)},this.mediaRecorder.start(1e3),this._recording=!0}catch(t){console.warn("[FirstLook] Voice recording unavailable:",t),this.events.emit({type:"error",error:new Error(`Voice recording failed: ${t.message}`)})}}async stopAsync(){return!this._recording||!this.mediaRecorder?null:new Promise(t=>{this.mediaRecorder.onstop=()=>{var s;const e=this.chunks.length>0?new Blob(this.chunks,{type:((s=this.mediaRecorder)==null?void 0:s.mimeType)||"audio/webm"}):null;this.cleanup(),t(e)},this.mediaRecorder.stop()})}async captureSnippet(t=1e4){return await this.start(),new Promise(e=>{setTimeout(async()=>{const s=await this.stopAsync();e(s)},t)})}cleanup(){if(this._recording=!1,this.stream){for(const t of this.stream.getTracks())t.stop();this.stream=null}this.mediaRecorder=null,this.chunks=[]}getSupportedMimeType(){const t=["audio/webm;codecs=opus","audio/webm","audio/ogg;codecs=opus","audio/mp4"];for(const e of t)if(typeof MediaRecorder<"u"&&MediaRecorder.isTypeSupported(e))return e;return""}}class z{constructor(t,e){this.shadowRoot=t,this.config=e,this.container=null,this.refreshInterval=null,this.cachedIp=null}show(){this.container||(this.container=document.createElement("div"),this.container.className="fl-watermark",this.renderTiles(),this.shadowRoot.appendChild(this.container),this.fetchIp().then(()=>this.renderTiles()),this.refreshInterval=setInterval(()=>this.renderTiles(),6e4))}hide(){this.container&&(this.container.remove(),this.container=null),this.refreshInterval&&(clearInterval(this.refreshInterval),this.refreshInterval=null)}async fetchIp(){if(!this.cachedIp)try{const t=await fetch("https://api.ipify.org?format=text");t.ok&&(this.cachedIp=await t.text())}catch{this.cachedIp="N/A"}}renderTiles(){if(!this.container)return;this.container.innerHTML="";const t=this.cachedIp??"",e=new Date().toISOString().slice(0,19),s=t?`${this.config.userId} | ${e} | ${t}`:`${this.config.userId} | ${e}`,i=320,n=80,o=Math.ceil(window.innerWidth/i)+1,l=Math.ceil(window.innerHeight/n)+1;for(let c=0;c<l;c++)for(let d=0;d<o;d++){const h=document.createElement("span");h.className="fl-watermark-tile",h.textContent=s,h.style.left=`${d*i}px`,h.style.top=`${c*n}px`,this.container.appendChild(h)}}}class P{constructor(t){this.selectors=t,this.observer=null,this.maskedElements=new Set}start(){this.scanAndMark(),this.observer=new MutationObserver(()=>this.scanAndMark()),this.observer.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["type","class","data-sensitive","data-mask"]})}stop(){var t;(t=this.observer)==null||t.disconnect(),this.observer=null;for(const e of this.maskedElements)e.removeAttribute("data-fl-masked");this.maskedElements.clear()}isMasked(t){return t.hasAttribute("data-fl-masked")}getCombinedSelector(){return this.selectors.join(", ")}scanAndMark(){const t=this.getCombinedSelector();if(t)try{const e=document.querySelectorAll(t);for(const s of e)this.maskedElements.has(s)||(s.setAttribute("data-fl-masked","true"),this.maskedElements.add(s));for(const s of this.maskedElements)document.contains(s)||this.maskedElements.delete(s)}catch{}}}const O="firstlook_uat",Q=1,u={sessions:"sessions",recordings:"recordings",annotations:"annotations",uploadQueue:"upload_queue"};class B{constructor(){this.db=null}async open(){if(!this.db)return new Promise((t,e)=>{const s=indexedDB.open(O,Q);s.onupgradeneeded=()=>{const i=s.result;i.objectStoreNames.contains(u.sessions)||i.createObjectStore(u.sessions,{keyPath:"sessionId"}),i.objectStoreNames.contains(u.recordings)||i.createObjectStore(u.recordings,{autoIncrement:!0}).createIndex("sessionId","sessionId",{unique:!1}),i.objectStoreNames.contains(u.annotations)||i.createObjectStore(u.annotations,{autoIncrement:!0}).createIndex("sessionId","sessionId",{unique:!1}),i.objectStoreNames.contains(u.uploadQueue)||i.createObjectStore(u.uploadQueue,{autoIncrement:!0})},s.onsuccess=()=>{this.db=s.result,t()},s.onerror=()=>e(s.error)})}async saveSession(t){await this.put(u.sessions,t)}async getSession(t){return this.get(u.sessions,t)}async saveRecording(t,e){await this.put(u.recordings,{sessionId:t,...e})}async saveAnnotation(t,e){await this.put(u.annotations,{sessionId:t,...e})}async getAnnotations(t){return this.getAllByIndex(u.annotations,"sessionId",t)}async enqueueUpload(t){await this.put(u.uploadQueue,{payload:t,createdAt:Date.now(),attempts:0})}async getPendingUploads(){const t=this.ensureDb();return new Promise((e,s)=>{const o=t.transaction(u.uploadQueue,"readonly").objectStore(u.uploadQueue).openCursor(),l=[];o.onsuccess=()=>{const c=o.result;c?(l.push({key:c.key,payload:c.value.payload,attempts:c.value.attempts,lastAttemptAt:c.value.lastAttemptAt}),c.continue()):e(l)},o.onerror=()=>s(o.error)})}async removeFromQueue(t){const e=this.ensureDb();return new Promise((s,i)=>{const o=e.transaction(u.uploadQueue,"readwrite").objectStore(u.uploadQueue).delete(t);o.onsuccess=()=>s(),o.onerror=()=>i(o.error)})}async incrementAttempts(t){const e=this.ensureDb();return new Promise((s,i)=>{const n=e.transaction(u.uploadQueue,"readwrite"),o=n.objectStore(u.uploadQueue),l=o.get(t);l.onsuccess=()=>{const c=l.result;c&&(c.attempts=(c.attempts??0)+1,c.lastAttemptAt=Date.now(),o.put(c,t))},n.oncomplete=()=>s(),n.onerror=()=>i(n.error)})}async clearSession(t){const s=this.ensureDb().transaction([u.sessions,u.recordings,u.annotations],"readwrite");s.objectStore(u.sessions).delete(t);for(const i of[u.recordings,u.annotations]){const l=s.objectStore(i).index("sessionId").openCursor(IDBKeyRange.only(t));l.onsuccess=()=>{const c=l.result;c&&(c.delete(),c.continue())}}return new Promise((i,n)=>{s.oncomplete=()=>i(),s.onerror=()=>n(s.error)})}close(){var t;(t=this.db)==null||t.close(),this.db=null}ensureDb(){if(!this.db)throw new Error("[FirstLook] Storage not opened. Call open() first.");return this.db}async put(t,e){const s=this.ensureDb();return new Promise((i,n)=>{const o=s.transaction(t,"readwrite");o.objectStore(t).put(e),o.oncomplete=()=>i(),o.onerror=()=>n(o.error)})}async get(t,e){const s=this.ensureDb();return new Promise((i,n)=>{const l=s.transaction(t,"readonly").objectStore(t).get(e);l.onsuccess=()=>i(l.result),l.onerror=()=>n(l.error)})}async getAllByIndex(t,e,s){const i=this.ensureDb();return new Promise((n,o)=>{const d=i.transaction(t,"readonly").objectStore(t).index(e).getAll(s);d.onsuccess=()=>n(d.result),d.onerror=()=>o(d.error)})}}const T=["#e17055","#6c5ce7","#00b894","#fdcb6e","#ffffff"];class H{constructor(t){this.shadowRoot=t,this.overlay=null,this.canvas=null,this.ctx=null,this.drawing=!1,this.currentPath=[],this.paths=[],this.selectedColor=T[0],this.screenshotDataUrl=""}async open(){return this.screenshotDataUrl=await this.captureScreenshot(),new Promise(t=>{this.overlay=r("div",{className:"fl-annotation-overlay"});const e=r("div",{className:"fl-annotation-canvas-wrap"});this.canvas=document.createElement("canvas"),this.canvas.className="fl-annotation-canvas",e.appendChild(this.canvas),this.overlay.appendChild(e);const s=r("input",{className:"fl-annotation-comment",type:"text",placeholder:"Add a comment..."}),i=r("div",{className:"fl-color-picker"});for(const c of T){const d=r("div",{className:`fl-color-swatch ${c===this.selectedColor?"fl-selected":""}`});d.style.background=c,d.onclick=()=>{this.selectedColor=c,i.querySelectorAll(".fl-color-swatch").forEach(h=>h.classList.remove("fl-selected")),d.classList.add("fl-selected")},i.appendChild(d)}const n=r("button",{className:"fl-btn fl-btn-ok"},["Submit"]);n.onclick=()=>{const c={screenshotDataUrl:this.getAnnotatedImage(),drawings:[...this.paths],comment:s.value,timestamp:Date.now()};this.close(),t(c)};const o=r("button",{className:"fl-btn fl-btn-ng"},["Cancel"]);o.onclick=()=>{this.close(),t(null)};const l=r("div",{className:"fl-annotation-toolbar"},[i,s,n,o]);this.overlay.appendChild(l),this.shadowRoot.appendChild(this.overlay),this.initCanvas()})}close(){var t;(t=this.overlay)==null||t.remove(),this.overlay=null,this.canvas=null,this.ctx=null,this.paths=[],this.currentPath=[]}async captureScreenshot(){try{const t=window.innerWidth,e=window.innerHeight,s=document.documentElement.cloneNode(!0),i=s.querySelector("#firstlook-sdk-root");i==null||i.remove();const n=s.querySelectorAll('[data-fl-masked], input[type="password"]');for(const f of n)(f instanceof HTMLInputElement||f instanceof HTMLTextAreaElement)&&(f.value="[MASKED]"),f.textContent="[MASKED]";for(const f of s.querySelectorAll("script"))f.remove();const o=s.querySelector("body");if(o){const f=window.getComputedStyle(document.body);o.style.backgroundColor=f.backgroundColor,o.style.color=f.color,o.style.fontFamily=f.fontFamily,o.style.margin="0",o.style.overflow="hidden"}const l=new XMLSerializer().serializeToString(s),c=`<svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${e}">
2
2
  <foreignObject width="100%" height="100%">
3
- ${c}
3
+ ${l}
4
4
  </foreignObject>
5
- </svg>`,h=new Blob([l],{type:"image/svg+xml;charset=utf-8"}),d=URL.createObjectURL(h),f=document.createElement("canvas");f.width=t*window.devicePixelRatio,f.height=e*window.devicePixelRatio;const m=f.getContext("2d");return m.scale(window.devicePixelRatio,window.devicePixelRatio),new Promise(p=>{const g=new Image;g.onload=()=>{m.drawImage(g,0,0,t,e),URL.revokeObjectURL(d),p(f.toDataURL("image/png"))},g.onerror=()=>{URL.revokeObjectURL(d),p(this.captureScreenshotFallback(t,e))},g.src=d})}catch{return this.captureScreenshotFallback(window.innerWidth,window.innerHeight)}}captureScreenshotFallback(t,e){const s=document.createElement("canvas");s.width=t,s.height=e;const i=s.getContext("2d"),n=window.getComputedStyle(document.body);return i.fillStyle=n.backgroundColor||"#ffffff",i.fillRect(0,0,t,e),i.fillStyle="#333",i.font="14px sans-serif",i.fillText(`URL: ${window.location.href}`,20,30),i.fillText(`Time: ${new Date().toISOString()}`,20,50),i.fillText("(SVG capture unavailable โ€” metadata view)",20,70),s.toDataURL("image/png")}initCanvas(){if(!this.canvas)return;const t=new Image;t.onload=()=>{const e=window.innerWidth*.9,s=window.innerHeight*.7,i=Math.min(e/t.width,s/t.height,1);this.canvas.width=t.width*i,this.canvas.height=t.height*i,this.ctx=this.canvas.getContext("2d"),this.ctx.drawImage(t,0,0,this.canvas.width,this.canvas.height),this.canvas.addEventListener("pointerdown",this.onDrawStart.bind(this)),this.canvas.addEventListener("pointermove",this.onDrawMove.bind(this)),this.canvas.addEventListener("pointerup",this.onDrawEnd.bind(this)),this.canvas.addEventListener("pointerleave",this.onDrawEnd.bind(this))},t.src=this.screenshotDataUrl}onDrawStart(t){this.drawing=!0;const e=this.canvas.getBoundingClientRect();this.currentPath=[{x:t.clientX-e.left,y:t.clientY-e.top}]}onDrawMove(t){if(!this.drawing||!this.ctx)return;const e=this.canvas.getBoundingClientRect(),s={x:t.clientX-e.left,y:t.clientY-e.top};if(this.currentPath.push(s),this.ctx.strokeStyle=this.selectedColor,this.ctx.lineWidth=3,this.ctx.lineCap="round",this.ctx.lineJoin="round",this.currentPath.length>=2){const i=this.currentPath[this.currentPath.length-2];this.ctx.beginPath(),this.ctx.moveTo(i.x,i.y),this.ctx.lineTo(s.x,s.y),this.ctx.stroke()}}onDrawEnd(){this.drawing&&this.currentPath.length>0&&this.paths.push({points:[...this.currentPath],color:this.selectedColor,width:3}),this.drawing=!1,this.currentPath=[]}getAnnotatedImage(){var t;return((t=this.canvas)==null?void 0:t.toDataURL("image/png"))||this.screenshotDataUrl}}class B{constructor(t){this.onShake=t,this.lastX=0,this.lastY=0,this.lastZ=0,this.shakeCount=0,this.lastShakeTime=0,this.handler=null,this.THRESHOLD=15,this.SHAKE_INTERVAL=400,this.REQUIRED_SHAKES=2}async start(){if(typeof DeviceMotionEvent.requestPermission=="function")try{if(await DeviceMotionEvent.requestPermission()!=="granted")return}catch{return}this.handler=this.onMotion.bind(this),window.addEventListener("devicemotion",this.handler,{passive:!0})}stop(){this.handler&&(window.removeEventListener("devicemotion",this.handler),this.handler=null)}onMotion(t){const e=t.accelerationIncludingGravity;if(!e||e.x==null||e.y==null||e.z==null)return;const s=Math.abs(e.x-this.lastX),i=Math.abs(e.y-this.lastY),n=Math.abs(e.z-this.lastZ);if(s+i+n>this.THRESHOLD){const o=Date.now();o-this.lastShakeTime<this.SHAKE_INTERVAL?(this.shakeCount++,this.shakeCount>=this.REQUIRED_SHAKES&&(this.shakeCount=0,this.onShake())):this.shakeCount=1,this.lastShakeTime=o}this.lastX=e.x,this.lastY=e.y,this.lastZ=e.z}}class H{constructor(t,e){this.shadowRoot=t,this.events=e,this.annotations=[],this.active=!1,this.onKeydown=s=>{s.ctrlKey&&s.shiftKey&&s.key==="R"&&(s.preventDefault(),this.trigger())},this.annotationCanvas=new Q(t),this.shakeTrigger=new B(()=>this.trigger())}async start(){await this.shakeTrigger.start(),document.addEventListener("keydown",this.onKeydown)}stop(){this.shakeTrigger.stop(),document.removeEventListener("keydown",this.onKeydown)}getAnnotations(){return[...this.annotations]}async trigger(){if(!this.active){this.active=!0;try{const t=await this.annotationCanvas.open();t&&(this.annotations.push(t),this.events.emit({type:"report:submitted"}))}finally{this.active=!1}}}}class j{constructor(t,e){this.requiredTaps=t,this.onActivate=e,this.tapCount=0,this.tapTimer=null,this.handler=null}start(){this.handler=this.onTap.bind(this),document.addEventListener("pointerdown",this.handler,{passive:!0})}stop(){this.handler&&(document.removeEventListener("pointerdown",this.handler),this.handler=null),this.reset()}onTap(t){this.tapCount++,this.tapTimer&&clearTimeout(this.tapTimer),this.tapCount>=this.requiredTaps?(this.reset(),this.onActivate()):this.tapTimer=setTimeout(()=>this.reset(),800)}reset(){this.tapCount=0,this.tapTimer&&(clearTimeout(this.tapTimer),this.tapTimer=null)}}class U{constructor(t){this.onActivate=t,this.hashHandler=null}start(){this.checkUrl()&&this.onActivate(),this.hashHandler=()=>{this.checkUrl()&&this.onActivate()},window.addEventListener("hashchange",this.hashHandler)}stop(){this.hashHandler&&(window.removeEventListener("hashchange",this.hashHandler),this.hashHandler=null)}checkUrl(){const t=new URLSearchParams(window.location.search);return t.has("firstlook")||t.has("uat-mode")}}class ${constructor(t,e){this.shadowRoot=t,this.callbacks=e,this.root=null,this.outsideClickHandler=null}show(){if(this.root)return;this.root=r("div",{className:"fl-debug-menu"});const t=[{icon:"โ–ถ",label:"Start Session",action:this.callbacks.onStartSession},{icon:"๐Ÿ“ธ",label:"Report Issue",action:this.callbacks.onReportIssue},{icon:"โœ–",label:"Close SDK",action:this.callbacks.onClose}];for(const e of t){const s=r("button",{className:"fl-debug-menu-item"},[r("span",{className:"fl-debug-menu-item-icon"},[e.icon]),e.label]);s.onclick=i=>{i.stopPropagation(),this.hide(),e.action()},this.root.appendChild(s)}this.shadowRoot.appendChild(this.root),setTimeout(()=>{this.outsideClickHandler=e=>{this.root&&!this.root.contains(e.target)&&this.hide()},document.addEventListener("click",this.outsideClickHandler,{capture:!0})},100)}hide(){var t;(t=this.root)==null||t.remove(),this.root=null,this.outsideClickHandler&&(document.removeEventListener("click",this.outsideClickHandler,{capture:!0}),this.outsideClickHandler=null)}get visible(){return this.root!==null}}const K=`
5
+ </svg>`,d=new Blob([c],{type:"image/svg+xml;charset=utf-8"}),h=URL.createObjectURL(d),p=document.createElement("canvas");p.width=t*window.devicePixelRatio,p.height=e*window.devicePixelRatio;const g=p.getContext("2d");return g.scale(window.devicePixelRatio,window.devicePixelRatio),new Promise(f=>{const m=new Image;m.onload=()=>{g.drawImage(m,0,0,t,e),URL.revokeObjectURL(h),f(p.toDataURL("image/png"))},m.onerror=()=>{URL.revokeObjectURL(h),f(this.captureScreenshotFallback(t,e))},m.src=h})}catch{return this.captureScreenshotFallback(window.innerWidth,window.innerHeight)}}captureScreenshotFallback(t,e){const s=document.createElement("canvas");s.width=t,s.height=e;const i=s.getContext("2d"),n=window.getComputedStyle(document.body);return i.fillStyle=n.backgroundColor||"#ffffff",i.fillRect(0,0,t,e),i.fillStyle="#333",i.font="14px sans-serif",i.fillText(`URL: ${window.location.href}`,20,30),i.fillText(`Time: ${new Date().toISOString()}`,20,50),i.fillText("(SVG capture unavailable โ€” metadata view)",20,70),s.toDataURL("image/png")}initCanvas(){if(!this.canvas)return;const t=new Image;t.onload=()=>{const e=window.innerWidth*.9,s=window.innerHeight*.7,i=Math.min(e/t.width,s/t.height,1);this.canvas.width=t.width*i,this.canvas.height=t.height*i,this.ctx=this.canvas.getContext("2d"),this.ctx.drawImage(t,0,0,this.canvas.width,this.canvas.height),this.canvas.addEventListener("pointerdown",this.onDrawStart.bind(this)),this.canvas.addEventListener("pointermove",this.onDrawMove.bind(this)),this.canvas.addEventListener("pointerup",this.onDrawEnd.bind(this)),this.canvas.addEventListener("pointerleave",this.onDrawEnd.bind(this))},t.src=this.screenshotDataUrl}onDrawStart(t){this.drawing=!0;const e=this.canvas.getBoundingClientRect();this.currentPath=[{x:t.clientX-e.left,y:t.clientY-e.top}]}onDrawMove(t){if(!this.drawing||!this.ctx)return;const e=this.canvas.getBoundingClientRect(),s={x:t.clientX-e.left,y:t.clientY-e.top};if(this.currentPath.push(s),this.ctx.strokeStyle=this.selectedColor,this.ctx.lineWidth=3,this.ctx.lineCap="round",this.ctx.lineJoin="round",this.currentPath.length>=2){const i=this.currentPath[this.currentPath.length-2];this.ctx.beginPath(),this.ctx.moveTo(i.x,i.y),this.ctx.lineTo(s.x,s.y),this.ctx.stroke()}}onDrawEnd(){this.drawing&&this.currentPath.length>0&&this.paths.push({points:[...this.currentPath],color:this.selectedColor,width:3}),this.drawing=!1,this.currentPath=[]}getAnnotatedImage(){var t;return((t=this.canvas)==null?void 0:t.toDataURL("image/png"))||this.screenshotDataUrl}}class U{constructor(t){this.onShake=t,this.lastX=0,this.lastY=0,this.lastZ=0,this.shakeCount=0,this.lastShakeTime=0,this.handler=null,this.THRESHOLD=15,this.SHAKE_INTERVAL=400,this.REQUIRED_SHAKES=2}async start(){if(typeof DeviceMotionEvent.requestPermission=="function")try{if(await DeviceMotionEvent.requestPermission()!=="granted")return}catch{return}this.handler=this.onMotion.bind(this),window.addEventListener("devicemotion",this.handler,{passive:!0})}stop(){this.handler&&(window.removeEventListener("devicemotion",this.handler),this.handler=null)}onMotion(t){const e=t.accelerationIncludingGravity;if(!e||e.x==null||e.y==null||e.z==null)return;const s=Math.abs(e.x-this.lastX),i=Math.abs(e.y-this.lastY),n=Math.abs(e.z-this.lastZ);if(s+i+n>this.THRESHOLD){const o=Date.now();o-this.lastShakeTime<this.SHAKE_INTERVAL?(this.shakeCount++,this.shakeCount>=this.REQUIRED_SHAKES&&(this.shakeCount=0,this.onShake())):this.shakeCount=1,this.lastShakeTime=o}this.lastX=e.x,this.lastY=e.y,this.lastZ=e.z}}class j{constructor(t,e){this.shadowRoot=t,this.events=e,this.annotations=[],this.active=!1,this.onKeydown=s=>{s.ctrlKey&&s.shiftKey&&s.key==="R"&&(s.preventDefault(),this.trigger())},this.annotationCanvas=new H(t),this.shakeTrigger=new U(()=>this.trigger())}async start(){await this.shakeTrigger.start(),document.addEventListener("keydown",this.onKeydown)}stop(){this.shakeTrigger.stop(),document.removeEventListener("keydown",this.onKeydown)}getAnnotations(){return[...this.annotations]}async trigger(){if(!this.active){this.active=!0;try{const t=await this.annotationCanvas.open();t&&(this.annotations.push(t),this.events.emit({type:"report:submitted"}))}finally{this.active=!1}}}}class ${constructor(t,e){this.requiredTaps=t,this.onActivate=e,this.tapCount=0,this.tapTimer=null,this.handler=null}start(){this.handler=this.onTap.bind(this),document.addEventListener("pointerdown",this.handler,{passive:!0})}stop(){this.handler&&(document.removeEventListener("pointerdown",this.handler),this.handler=null),this.reset()}onTap(t){this.tapCount++,this.tapTimer&&clearTimeout(this.tapTimer),this.tapCount>=this.requiredTaps?(this.reset(),this.onActivate()):this.tapTimer=setTimeout(()=>this.reset(),800)}reset(){this.tapCount=0,this.tapTimer&&(clearTimeout(this.tapTimer),this.tapTimer=null)}}const y="firstlook:uat-active";class C{constructor(t){this.onActivate=t,this.hashHandler=null}start(){if(this.checkUrl()){this.persist(),this.onActivate();return}if(this.hasPersisted()){this.onActivate();return}this.hashHandler=()=>{this.checkUrl()&&(this.persist(),this.onActivate())},window.addEventListener("hashchange",this.hashHandler)}stop(){this.hashHandler&&(window.removeEventListener("hashchange",this.hashHandler),this.hashHandler=null)}static clearPersisted(){try{sessionStorage.removeItem(y)}catch{}}checkUrl(){const t=new URLSearchParams(window.location.search);return t.has("firstlook")||t.has("uat-mode")||t.get("uat")==="1"}persist(){try{sessionStorage.setItem(y,"1")}catch{}}hasPersisted(){try{return sessionStorage.getItem(y)==="1"}catch{return!1}}}class K{constructor(t){this.onActivate=t,this.handler=null}start(){this.handler=t=>{t.key==="U"&&t.shiftKey&&(t.ctrlKey||t.metaKey)&&(t.preventDefault(),this.onActivate())},window.addEventListener("keydown",this.handler)}stop(){this.handler&&(window.removeEventListener("keydown",this.handler),this.handler=null)}}class _{constructor(t,e){this.shadowRoot=t,this.callbacks=e,this.root=null,this.outsideClickHandler=null}show(){if(this.root)return;this.root=r("div",{className:"fl-debug-menu"});for(const e of["click","mousedown","mouseup","pointerdown","pointerup","touchstart","touchend"])this.root.addEventListener(e,s=>s.stopPropagation());const t=[{icon:"โ–ถ",label:"Start Session",action:this.callbacks.onStartSession},{icon:"๐Ÿ“ธ",label:"Report Issue",action:this.callbacks.onReportIssue},{icon:"โœ–",label:"Close SDK",action:this.callbacks.onClose}];for(const e of t){const s=r("button",{className:"fl-debug-menu-item"},[r("span",{className:"fl-debug-menu-item-icon"},[e.icon]),e.label]);s.onclick=i=>{i.stopPropagation(),this.hide(),e.action()},this.root.appendChild(s)}this.shadowRoot.appendChild(this.root),setTimeout(()=>{this.outsideClickHandler=e=>{this.root&&!this.root.contains(e.target)&&this.hide()},document.addEventListener("click",this.outsideClickHandler,{capture:!0})},100)}hide(){var t;(t=this.root)==null||t.remove(),this.root=null,this.outsideClickHandler&&(document.removeEventListener("click",this.outsideClickHandler,{capture:!0}),this.outsideClickHandler=null)}get visible(){return this.root!==null}}const W=`
6
6
  :host {
7
7
  all: initial;
8
8
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -23,6 +23,7 @@
23
23
  bottom: 24px;
24
24
  right: 24px;
25
25
  z-index: 2147483647;
26
+ pointer-events: auto;
26
27
  width: 360px;
27
28
  max-width: calc(100vw - 48px);
28
29
  background: #ffffff;
@@ -215,6 +216,7 @@
215
216
  position: fixed;
216
217
  inset: 0;
217
218
  z-index: 2147483647;
219
+ pointer-events: auto;
218
220
  background: rgba(0, 0, 0, 0.7);
219
221
  display: flex;
220
222
  flex-direction: column;
@@ -312,6 +314,7 @@
312
314
  bottom: 100px;
313
315
  right: 24px;
314
316
  z-index: 2147483646;
317
+ pointer-events: auto;
315
318
  background: #fff;
316
319
  border-radius: 12px;
317
320
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.16);
@@ -393,10 +396,16 @@
393
396
  box-shadow: 0 2px 8px rgba(99, 110, 114, 0.3);
394
397
  }
395
398
 
396
- /* === NG Voice Feedback Mode === */
399
+ /* === Feedback modal header variants === */
397
400
  .fl-quest-header-ng { background: linear-gradient(135deg, #e17055, #d63031); }
398
401
  .fl-badge-ng { background: rgba(255, 255, 255, 0.25); }
399
402
 
403
+ .fl-quest-header-ok { background: linear-gradient(135deg, #00b894, #00a381); }
404
+ .fl-badge-ok { background: rgba(255, 255, 255, 0.25); }
405
+
406
+ .fl-quest-header-concern { background: linear-gradient(135deg, #fdcb6e, #e17055); }
407
+ .fl-badge-concern { background: rgba(255, 255, 255, 0.25); }
408
+
400
409
  .fl-feedback-textarea {
401
410
  width: calc(100% - 32px);
402
411
  margin: 8px 16px 16px;
@@ -412,16 +421,25 @@
412
421
  .fl-feedback-textarea:focus { border-color: #6c5ce7; }
413
422
  .fl-feedback-textarea-modal { width: calc(100% - 32px); margin: 16px 16px 12px; }
414
423
 
424
+ /* === Concern button === */
425
+ .fl-btn-concern {
426
+ background: #fdcb6e;
427
+ color: #2d3436;
428
+ box-shadow: 0 2px 8px rgba(253, 203, 110, 0.3);
429
+ }
430
+ .fl-btn-concern:hover { box-shadow: 0 4px 12px rgba(253, 203, 110, 0.4); }
431
+
415
432
  /* === Memo button === */
416
433
  .fl-quest-memo-row { display: flex; margin-bottom: 10px; }
417
434
  .fl-btn-memo {
418
- flex: none;
435
+ flex: 1;
419
436
  background: #f0f0f8;
420
437
  color: #4a4a5a;
421
- font-size: 12px;
422
- padding: 6px 12px;
438
+ font-size: 14px;
439
+ padding: 10px 16px;
423
440
  box-shadow: none;
424
- border-radius: 8px;
441
+ border-radius: 10px;
442
+ border: 1px dashed #c8c8d8;
425
443
  }
426
444
  .fl-btn-memo:hover { background: #e4e4f0; }
427
445
 
@@ -437,5 +455,5 @@
437
455
  animation: fl-fade-in 0.15s ease-out;
438
456
  }
439
457
  .fl-feedback-modal-actions { padding: 0 16px 16px; }
440
- `,_=5;class W{constructor(){this.config=null,this.events=new C,this.state="idle",this.hostElement=null,this.shadowRoot=null,this.questManager=null,this.questOverlay=null,this.sessionRecorder=null,this.voiceRecorder=null,this.watermark=null,this.fieldMasker=null,this.storage=null,this.shakeReporter=null,this.debugMenu=null,this.tapTrigger=null,this.deepLinkTrigger=null,this.sessionId=null,this.sessionStartTime=0,this.maxDurationTimer=null,this.backupTimer=null,this.isFlushing=!1}async init(t){if(this.state!=="idle"){console.warn("[FirstLook] SDK already initialized.");return}this.config=T(t),this.storage=new O,await this.storage.open(),this.createShadowHost(),this.setupTriggers(),this.fieldMasker=new F(this.config.security.maskSelectors),this.state="initialized",this.events.emit({type:"sdk:initialized"}),this.flushUploadQueue(!0)}activate(){if(this.state!=="initialized"){console.warn("[FirstLook] Cannot activate: SDK not initialized or already active.");return}this.onActivate()}async startSession(t){var s;if(this.state!=="active")throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");(s=this.debugMenu)==null||s.hide(),this.sessionId=q(),this.sessionStartTime=Date.now(),this.questManager=new I(this.events),this.questManager.loadQuests(t),this.sessionRecorder=new L(this.config,this.events,this.fieldMasker.getCombinedSelector()),this.voiceRecorder=new A(this.events),this.shakeReporter=new H(this.shadowRoot,this.events),await this.shakeReporter.start(),this.sessionRecorder.start(),this.fieldMasker.start(),this.config.recording.voice&&await this.voiceRecorder.start(),this.config.security.watermark&&(this.watermark=new N(this.shadowRoot,this.config),this.watermark.show()),this.questOverlay=new E(this.shadowRoot,{onOk:()=>this.handleQuestOk(),onNg:()=>this.handleQuestNg(),onNgWithFeedback:i=>this.handleNgFeedbackSubmit(i),onMemo:i=>this.handleMemo(i),onFinish:()=>this.endSession(),onMinimize:()=>{}}),this.questManager.startSession(this.sessionStartTime),this.renderCurrentQuest();const e=this.config.recording.maxDuration;return e>0&&(this.maxDurationTimer=setTimeout(()=>this.endSession(),e*1e3)),this.backupTimer=setInterval(()=>this.backupSession(),3e4),this.state="recording",this.events.emit({type:"session:started",sessionId:this.sessionId}),this.sessionId}async startSessionFromRemote(t){if(this.state!=="active")throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");if(!this.config)throw new Error("[FirstLook] SDK not initialized.");const e=this.config.endpoint.replace(/\/ingest-session$/,""),s=await fetch(`${e}/export-quests?id=${encodeURIComponent(t)}`,{headers:{"X-API-Key":this.config.apiKey}});if(!s.ok)throw new Error(`[FirstLook] Failed to fetch quest set: ${s.status}`);const n=(await s.json()).quests;return this.startSession(n)}async endSession(){var i,n,o,c,l,h,d,f,m,p,g;if(this.state!=="recording"||!this.sessionId)return null;this.maxDurationTimer&&(clearTimeout(this.maxDurationTimer),this.maxDurationTimer=null),this.backupTimer&&(clearInterval(this.backupTimer),this.backupTimer=null),(i=this.sessionRecorder)==null||i.stop(),await((n=this.voiceRecorder)==null?void 0:n.stopAsync()),(o=this.shakeReporter)==null||o.stop(),(c=this.fieldMasker)==null||c.stop(),(l=this.watermark)==null||l.hide();const t=this.questManager.getResults();await Promise.allSettled(t.map(async b=>{if(b.voiceMemoBlob){try{b.voiceMemoBase64=await this.blobToBase64(b.voiceMemoBlob)}catch{}delete b.voiceMemoBlob}await Promise.allSettled(b.feedbacks.map(async w=>{if(w.voiceMemoBlob){try{w.voiceMemoBase64=await this.blobToBase64(w.voiceMemoBlob)}catch{}delete w.voiceMemoBlob}}))}));const e={sessionId:this.sessionId,projectId:this.config.projectId,userId:this.config.userId,role:this.config.role,deviceInfo:y(),startedAt:new Date(this.sessionStartTime).toISOString(),endedAt:new Date().toISOString(),duration:Math.floor((Date.now()-this.sessionStartTime)/1e3),quests:t,recordings:((h=this.sessionRecorder)==null?void 0:h.getSnapshots())??[]};await((d=this.storage)==null?void 0:d.saveSession(e));const s=((f=this.shakeReporter)==null?void 0:f.getAnnotations())??[];for(const b of s)await((m=this.storage)==null?void 0:m.saveAnnotation(this.sessionId,b));return await((p=this.storage)==null?void 0:p.enqueueUpload({session:e,annotations:s})),(g=this.questOverlay)==null||g.destroy(),this.questOverlay=null,this.state="finished",this.events.emit({type:"session:ended",sessionId:this.sessionId}),this.flushUploadQueue(!1),e}on(t,e){return this.events.on(t,e)}getState(){return this.state}destroy(){var t,e,s,i,n,o,c,l,h,d;this.maxDurationTimer&&(clearTimeout(this.maxDurationTimer),this.maxDurationTimer=null),this.backupTimer&&(clearInterval(this.backupTimer),this.backupTimer=null),(t=this.tapTrigger)==null||t.stop(),(e=this.deepLinkTrigger)==null||e.stop(),(s=this.sessionRecorder)==null||s.stop(),(i=this.shakeReporter)==null||i.stop(),(n=this.fieldMasker)==null||n.stop(),(o=this.watermark)==null||o.hide(),(c=this.questOverlay)==null||c.destroy(),(l=this.debugMenu)==null||l.hide(),(h=this.hostElement)==null||h.remove(),(d=this.storage)==null||d.close(),this.events.removeAll(),this.state="idle"}createShadowHost(){this.hostElement=document.createElement("div"),this.hostElement.id="firstlook-sdk-root",this.hostElement.style.cssText="position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none;",document.body.appendChild(this.hostElement),this.shadowRoot=this.hostElement.attachShadow({mode:"closed"});const t=document.createElement("style");t.textContent=K,this.shadowRoot.appendChild(t);const e=document.createElement("div");e.style.cssText="pointer-events:auto;",this.shadowRoot.appendChild(e)}setupTriggers(){const t=this.config.triggers;this.tapTrigger=new j(t.tapCount,()=>this.onActivate()),this.tapTrigger.start(),t.deepLink&&(this.deepLinkTrigger=new U(()=>this.onActivate()),this.deepLinkTrigger.start()),t.customCheck&&t.customCheck()&&this.onActivate()}onActivate(){var t,e;this.state==="initialized"&&(this.state="active",(t=this.tapTrigger)==null||t.stop(),(e=this.deepLinkTrigger)==null||e.stop(),this.events.emit({type:"sdk:activated"}),this.debugMenu=new $(this.shadowRoot,{onStartSession:()=>{this.events.emit({type:"sdk:activated"})},onReportIssue:()=>{var s,i;(i=(s=this.shakeReporter)==null?void 0:s.trigger)==null||i.call(s)},onClose:()=>{this.destroy()}}),this.debugMenu.show())}handleQuestOk(){if(!this.questManager||!this.sessionRecorder)return;const t=this.sessionRecorder.getActionLogs();this.questManager.completeCurrentQuest(t),this.renderCurrentQuest()}handleQuestNg(){if(!this.questManager||!this.questOverlay)return;const t=this.questManager.getCurrentQuest();t&&this.questOverlay.renderFeedbackModal(t.title,"ng")}handleNgFeedbackSubmit(t){if(!this.questManager||!this.sessionRecorder)return;const e=this.sessionRecorder.getActionLogs();this.questManager.failCurrentQuest(e,t||void 0),this.renderCurrentQuest()}handleMemo(t){if(!this.questManager||!t)return;const e={comment:t,timestamp:Date.now(),relativeTime:Date.now()-this.sessionStartTime};this.questManager.addFeedback(e)}renderCurrentQuest(){var s;if(!this.questManager||!this.questOverlay)return;const t=this.questManager.getStatus();if(t.isBlocked){const i=this.questManager.getCurrentQuest();this.questOverlay.renderBlocked((i==null?void 0:i.title)??"Unknown");return}if(t.isFinished){this.questOverlay.renderSummary(t.completed,t.failed,t.total);return}const e=this.questManager.getCurrentQuest();e&&this.questOverlay.renderQuest(e,this.questManager.getQuestStatuses(),((s=this.voiceRecorder)==null?void 0:s.recording)??!1)}async flushUploadQueue(t){if(!(!this.storage||!this.config||this.isFlushing)){this.isFlushing=!0;try{if(t){const s=navigator.connection;if(s&&s.type&&s.type!=="wifi"&&s.effectiveType!=="4g")return}const e=await this.storage.getPendingUploads();for(const s of e){if(s.attempts>=_){await this.storage.removeFromQueue(s.key);continue}if(s.attempts>0){const i=Math.pow(2,s.attempts)*1e3,n=s.lastAttemptAt??0;if(Date.now()-n<i)continue}try{(await fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:JSON.stringify(s.payload)})).ok?await this.storage.removeFromQueue(s.key):await this.storage.incrementAttempts(s.key)}catch{await this.storage.incrementAttempts(s.key)}}}finally{this.isFlushing=!1}}}async backupSession(){var t;if(!(!this.storage||!this.sessionId||!this.config||!this.sessionRecorder))try{const e={sessionId:this.sessionId,projectId:this.config.projectId,userId:this.config.userId,role:this.config.role,deviceInfo:y(),startedAt:new Date(this.sessionStartTime).toISOString(),duration:Math.floor((Date.now()-this.sessionStartTime)/1e3),quests:((t=this.questManager)==null?void 0:t.getResults())??[],recordings:this.sessionRecorder.getSnapshots()};await this.storage.saveSession(e)}catch{}}blobToBase64(t){return new Promise((e,s)=>{const i=new FileReader;i.onloadend=()=>e(i.result),i.onerror=()=>s(i.error),i.readAsDataURL(t)})}}v.FirstLookSDK=W,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})});
458
+ `,X=5;class V{constructor(){this.config=null,this.events=new I,this.state="idle",this.hostElement=null,this.shadowRoot=null,this.questManager=null,this.questOverlay=null,this.sessionRecorder=null,this.voiceRecorder=null,this.watermark=null,this.fieldMasker=null,this.storage=null,this.shakeReporter=null,this.debugMenu=null,this.tapTrigger=null,this.deepLinkTrigger=null,this.keyboardTrigger=null,this.sessionId=null,this.sessionStartTime=0,this.maxDurationTimer=null,this.backupTimer=null,this.isFlushing=!1}async init(t){if(this.state!=="idle"){console.warn("[FirstLook] SDK already initialized.");return}this.config=q(t),this.storage=new B,await this.storage.open(),this.createShadowHost(),this.setupTriggers(),this.fieldMasker=new P(this.config.security.maskSelectors),this.state="initialized",this.events.emit({type:"sdk:initialized"}),this.flushUploadQueue(!0)}activate(){if(this.state!=="initialized"){console.warn("[FirstLook] Cannot activate: SDK not initialized or already active.");return}this.onActivate()}async startSession(t){var s;if(this.state!=="active")throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");(s=this.debugMenu)==null||s.hide(),this.sessionId=E(),this.sessionStartTime=Date.now(),this.questManager=new M(this.events),this.questManager.loadQuests(t),this.sessionRecorder=new N(this.config,this.events,this.fieldMasker.getCombinedSelector()),this.voiceRecorder=new F(this.events),this.shakeReporter=new j(this.shadowRoot,this.events),await this.shakeReporter.start(),this.sessionRecorder.start(),this.fieldMasker.start(),this.config.recording.voice&&await this.voiceRecorder.start(),this.config.security.watermark&&(this.watermark=new z(this.shadowRoot,this.config),this.watermark.show()),this.questOverlay=new L(this.shadowRoot,{onOkWithFeedback:i=>this.handleQuestOk(i),onConcern:i=>this.handleConcern(i),onNg:()=>this.handleQuestNg(),onNgWithFeedback:i=>this.handleNgFeedbackSubmit(i),onMemo:i=>this.handleMemo(i),onFinish:()=>this.endSession(),onMinimize:()=>{}}),this.questManager.startSession(this.sessionStartTime),this.renderCurrentQuest();const e=this.config.recording.maxDuration;return e>0&&(this.maxDurationTimer=setTimeout(()=>this.endSession(),e*1e3)),this.backupTimer=setInterval(()=>this.backupSession(),3e4),this.state="recording",this.events.emit({type:"session:started",sessionId:this.sessionId}),this.sessionId}async startSessionFromRemote(t){if(this.state!=="active")throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");if(!this.config)throw new Error("[FirstLook] SDK not initialized.");const e=this.config.endpoint.replace(/\/ingest-session$/,""),s=await fetch(`${e}/export-quests?id=${encodeURIComponent(t)}`,{headers:{"X-API-Key":this.config.apiKey}});if(!s.ok)throw new Error(`[FirstLook] Failed to fetch quest set: ${s.status}`);const n=(await s.json()).quests;return this.startSession(n)}async endSession(){var i,n,o,l,c,d,h,p,g,f,m;if(this.state!=="recording"||!this.sessionId)return null;this.maxDurationTimer&&(clearTimeout(this.maxDurationTimer),this.maxDurationTimer=null),this.backupTimer&&(clearInterval(this.backupTimer),this.backupTimer=null),(i=this.sessionRecorder)==null||i.stop(),await((n=this.voiceRecorder)==null?void 0:n.stopAsync()),(o=this.shakeReporter)==null||o.stop(),(l=this.fieldMasker)==null||l.stop(),(c=this.watermark)==null||c.hide();const t=this.questManager.getResults();await Promise.allSettled(t.map(async b=>{if(b.voiceMemoBlob){try{b.voiceMemoBase64=await this.blobToBase64(b.voiceMemoBlob)}catch{}delete b.voiceMemoBlob}await Promise.allSettled(b.feedbacks.map(async x=>{if(x.voiceMemoBlob){try{x.voiceMemoBase64=await this.blobToBase64(x.voiceMemoBlob)}catch{}delete x.voiceMemoBlob}}))}));const e={sessionId:this.sessionId,projectId:this.config.projectId,userId:this.config.userId,role:this.config.role,deviceInfo:S(),startedAt:new Date(this.sessionStartTime).toISOString(),endedAt:new Date().toISOString(),duration:Math.floor((Date.now()-this.sessionStartTime)/1e3),quests:t,recordings:((d=this.sessionRecorder)==null?void 0:d.getSnapshots())??[]};await((h=this.storage)==null?void 0:h.saveSession(e));const s=((p=this.shakeReporter)==null?void 0:p.getAnnotations())??[];for(const b of s)await((g=this.storage)==null?void 0:g.saveAnnotation(this.sessionId,b));return await((f=this.storage)==null?void 0:f.enqueueUpload({session:e,annotations:s})),(m=this.questOverlay)==null||m.destroy(),this.questOverlay=null,this.state="finished",this.events.emit({type:"session:ended",sessionId:this.sessionId}),this.flushUploadQueue(!1),e}on(t,e){return this.events.on(t,e)}getState(){return this.state}destroy(){var t,e,s,i,n,o,l,c,d,h,p;this.maxDurationTimer&&(clearTimeout(this.maxDurationTimer),this.maxDurationTimer=null),this.backupTimer&&(clearInterval(this.backupTimer),this.backupTimer=null),(t=this.tapTrigger)==null||t.stop(),(e=this.deepLinkTrigger)==null||e.stop(),(s=this.keyboardTrigger)==null||s.stop(),C.clearPersisted(),(i=this.sessionRecorder)==null||i.stop(),(n=this.shakeReporter)==null||n.stop(),(o=this.fieldMasker)==null||o.stop(),(l=this.watermark)==null||l.hide(),(c=this.questOverlay)==null||c.destroy(),(d=this.debugMenu)==null||d.hide(),(h=this.hostElement)==null||h.remove(),(p=this.storage)==null||p.close(),this.events.removeAll(),this.state="idle"}createShadowHost(){this.hostElement=document.createElement("div"),this.hostElement.id="firstlook-sdk-root",this.hostElement.style.cssText="position:fixed;inset:0;z-index:2147483647;pointer-events:none;",document.body.appendChild(this.hostElement),this.shadowRoot=this.hostElement.attachShadow({mode:"closed"});const t=document.createElement("style");t.textContent=W,this.shadowRoot.appendChild(t)}setupTriggers(){const t=this.config.triggers;this.tapTrigger=new $(t.tapCount,()=>this.onActivate()),this.tapTrigger.start(),t.deepLink&&(this.deepLinkTrigger=new C(()=>this.onActivate()),this.deepLinkTrigger.start()),t.keyboard&&(this.keyboardTrigger=new K(()=>this.onActivate()),this.keyboardTrigger.start()),t.customCheck&&t.customCheck()&&this.onActivate()}onActivate(){var t,e,s;this.state==="initialized"&&(this.state="active",(t=this.tapTrigger)==null||t.stop(),(e=this.deepLinkTrigger)==null||e.stop(),(s=this.keyboardTrigger)==null||s.stop(),this.events.emit({type:"sdk:activated"}),this.debugMenu=new _(this.shadowRoot,{onStartSession:()=>{this.events.emit({type:"sdk:activated"})},onReportIssue:()=>{var i,n;(n=(i=this.shakeReporter)==null?void 0:i.trigger)==null||n.call(i)},onClose:()=>{this.destroy()}}),this.debugMenu.show())}handleQuestOk(t){if(!this.questManager||!this.sessionRecorder)return;const e=this.sessionRecorder.getActionLogs();this.questManager.completeCurrentQuest(e,{comment:t||void 0}),this.renderCurrentQuest()}handleConcern(t){if(!this.questManager||!this.sessionRecorder)return;const e=this.sessionRecorder.getActionLogs();this.questManager.completeCurrentQuest(e,{comment:t,concern:!0}),this.renderCurrentQuest()}handleQuestNg(){if(!this.questManager||!this.questOverlay)return;const t=this.questManager.getCurrentQuest();t&&this.questOverlay.renderFeedbackModal(t.title,"ng")}handleNgFeedbackSubmit(t){if(!this.questManager||!this.sessionRecorder)return;const e=this.sessionRecorder.getActionLogs();this.questManager.failCurrentQuest(e,t||void 0),this.renderCurrentQuest()}handleMemo(t){if(!this.questManager||!t)return;const e={comment:t,timestamp:Date.now(),relativeTime:Date.now()-this.sessionStartTime};this.questManager.addFeedback(e)}renderCurrentQuest(){var s;if(!this.questManager||!this.questOverlay)return;const t=this.questManager.getStatus();if(t.isBlocked){const i=this.questManager.getCurrentQuest();this.questOverlay.renderBlocked((i==null?void 0:i.title)??"Unknown");return}if(t.isFinished){this.questOverlay.renderSummary(t.completed,t.failed,t.total);return}const e=this.questManager.getCurrentQuest();e&&this.questOverlay.renderQuest(e,this.questManager.getQuestStatuses(),((s=this.voiceRecorder)==null?void 0:s.recording)??!1)}async flushUploadQueue(t){if(!(!this.storage||!this.config||this.isFlushing)){this.isFlushing=!0;try{if(t){const s=navigator.connection;if(s&&s.type&&s.type!=="wifi"&&s.effectiveType!=="4g")return}const e=await this.storage.getPendingUploads();for(const s of e){if(s.attempts>=X){await this.storage.removeFromQueue(s.key);continue}if(s.attempts>0){const i=Math.pow(2,s.attempts)*1e3,n=s.lastAttemptAt??0;if(Date.now()-n<i)continue}try{(await fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.config.apiKey},body:JSON.stringify(s.payload)})).ok?await this.storage.removeFromQueue(s.key):await this.storage.incrementAttempts(s.key)}catch{await this.storage.incrementAttempts(s.key)}}}finally{this.isFlushing=!1}}}async backupSession(){var t;if(!(!this.storage||!this.sessionId||!this.config||!this.sessionRecorder))try{const e={sessionId:this.sessionId,projectId:this.config.projectId,userId:this.config.userId,role:this.config.role,deviceInfo:S(),startedAt:new Date(this.sessionStartTime).toISOString(),duration:Math.floor((Date.now()-this.sessionStartTime)/1e3),quests:((t=this.questManager)==null?void 0:t.getResults())??[],recordings:this.sessionRecorder.getSnapshots()};await this.storage.saveSession(e)}catch{}}blobToBase64(t){return new Promise((e,s)=>{const i=new FileReader;i.onloadend=()=>e(i.result),i.onerror=()=>s(i.error),i.readAsDataURL(t)})}}v.FirstLookSDK=V,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})});
441
459
  //# sourceMappingURL=firstlook.umd.js.map