@firstlook-uat/sdk 0.4.0 โ 0.4.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.
- package/dist/firstlook.es.js +191 -101
- package/dist/firstlook.es.js.map +1 -1
- package/dist/firstlook.umd.js +50 -3
- package/dist/firstlook.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/firstlook.umd.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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 C(a){var t,e,s,i,n,o,l,c,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,keyboard:((s=a.triggers)==null?void 0:s.keyboard)??!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:((l=a.recording)==null?void 0:l.domSnapshot)??!0,voice:((c=a.recording)==null?void 0:c.voice)??!0,maxDuration:((h=a.recording)==null?void 0:h.maxDuration)??600,snapshotInterval:((d=a.recording)==null?void 0:d.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 R(){return`fl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,8)}`}function E(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",this.root.style.pointerEvents="auto";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 f of e){const g=f==="COMPLETED"?"fl-completed":f==="FAILED"?"fl-failed":f==="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 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={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"}),h={ng:"ไฝใใใพใใใใชใใฃใใๆใใฆใใ ใใ...",ok:"ใณใกใณใ๏ผไปปๆ๏ผ",concern:"ๆฐใซใชใฃใ็นใๆใใฆใใ ใใ...",memo:"ๆฐใฅใใใใจใใกใข...๐"},d=document.createElement("textarea");d.className="fl-feedback-textarea fl-feedback-textarea-modal",d.placeholder=h[e],d.rows=3,c.appendChild(d);const f="้ไฟก",g=e==="ok"?"ในใญใใ":"ใญใฃใณใปใซ",p=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",f,()=>{const m=d.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(p),s.appendChild(c),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=E(e)},1e3)}stopTimer(){this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}}const A=100,D=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>D)return;this.snapshots.length>=A&&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 P{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 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=`${c*n}px`,this.container.appendChild(d)}}}class z{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 h=i.transaction(t,"readonly").objectStore(t).index(e).getAll(s);h.onsuccess=()=>n(h.result),h.onerror=()=>o(h.error)})}}const T=["#e17055","#6c5ce7","#00b894","#fdcb6e","#ffffff"];class j{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 h=r("div",{className:`fl-color-swatch ${c===this.selectedColor?"fl-selected":""}`});h.style.background=c,h.onclick=()=>{this.selectedColor=c,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 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 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 l=new XMLSerializer().serializeToString(s),c=`<svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${e}">
|
|
1
|
+
(function(x,w){typeof exports=="object"&&typeof module<"u"?w(exports):typeof define=="function"&&define.amd?define(["exports"],w):(x=typeof globalThis<"u"?globalThis:x||self,w(x.FirstLook={}))})(this,function(x){"use strict";const w=['input[type="password"]',"[data-sensitive]","[data-mask]",".uat-mask"];function I(a){var t,e,s,i,n,o,l,c,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,keyboard:((s=a.triggers)==null?void 0:s.keyboard)??!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)??w},recording:{domSnapshot:((l=a.recording)==null?void 0:l.domSnapshot)??!0,voice:((c=a.recording)==null?void 0:c.voice)??!0,maxDuration:((h=a.recording)==null?void 0:h.maxDuration)??600,snapshotInterval:((d=a.recording)==null?void 0:d.snapshotInterval)??1e3}}}function C(){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 M{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 L{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(),checklist:e==null?void 0:e.checklist};return this.results.push(i),this.events.emit({type:"quest:completed",questId:s.id}),this.advance(),i}failCurrentQuest(t,e,s,i){const n=this.getCurrentQuest();if(!n)return null;const o={questId:n.id,status:"FAILED",timestamp:Date.now(),relativeTime:Date.now()-this.sessionStartTime,voiceMemoBlob:s,logs:t,comment:e,concern:!1,feedbacks:this.drainFeedbacks(),checklist:i};return this.results.push(o),this.events.emit({type:"quest:failed",questId:n.id}),n.blocking?(this.blocked=!0,this.events.emit({type:"quest:blocked",questId:n.id})):this.advance(),o}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")}`}function A(a){const t=a.split(`
|
|
2
|
+
`),e=[],s=[];let i=!1;for(const n of t){const o=n.trim(),l=o.match(/^(?:(\d+)[\.\)]\s+|[-*]\s+)(.+)$/);l?(i=!0,s.push({index:s.length,text:l[2],checked:!1})):!i&&o&&e.push(o)}return{preamble:e.join(`
|
|
3
|
+
`),items:s}}class D{constructor(t,e){this.shadowRoot=t,this.callbacks=e,this.minimized=!1,this.timerInterval=null,this.startTime=0,this.checklist=[],this.root=document.createElement("div"),this.root.className="fl-quest-overlay",this.root.style.pointerEvents="auto";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 f of e){const p=f==="COMPLETED"?"fl-completed":f==="FAILED"?"fl-failed":f==="ACTIVE"?"fl-active":"";l.appendChild(r("div",{className:`fl-quest-progress-dot ${p}`}))}const c=A(t.description);this.checklist=c.items;const h=r("div",{className:"fl-quest-body"});if(h.appendChild(l),c.items.length>0){const f=r("div",{className:"fl-checklist-counter"}),p=()=>{const b=this.checklist.filter(k=>k.checked).length;f.textContent=`โ ${b}/${this.checklist.length} ็ขบ่ชๆธใฟ`};p(),h.appendChild(f),c.preamble&&h.appendChild(r("p",{className:"fl-quest-description"},[c.preamble]));const m=r("ul",{className:"fl-checklist"});for(const b of this.checklist){const k=r("li",{className:"fl-checklist-item"}),v=document.createElement("input");v.type="checkbox",v.className="fl-checklist-checkbox",v.checked=b.checked;const G=r("span",{className:"fl-checklist-text"},[b.text]);v.onchange=()=>{b.checked=v.checked,k.classList.toggle("fl-checked",b.checked),p()},k.onclick=J=>{J.target!==v&&(v.checked=!v.checked,b.checked=v.checked,k.classList.toggle("fl-checked",b.checked),p())},k.appendChild(v),k.appendChild(G),m.appendChild(k)}h.appendChild(m)}else h.appendChild(r("p",{className:"fl-quest-description"},[t.description]));h.appendChild(r("div",{className:"fl-quest-memo-row"},[this.createButton("fl-btn fl-btn-memo","๐ ใกใขใๆฎใ",()=>this.renderFeedbackModal(t.title))])),h.appendChild(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(h),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 g=r("span",{className:"fl-status-timer"},["00:00"]);d.appendChild(g),o.appendChild(d),this.root.appendChild(o),this.startTimer(g),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(g))}}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"}),h={ng:"ไฝใใใพใใใใชใใฃใใๆใใฆใใ ใใ...",ok:"ใณใกใณใ๏ผไปปๆ๏ผ",concern:"ๆฐใซใชใฃใ็นใๆใใฆใใ ใใ...",memo:"ๆฐใฅใใใใจใใกใข...๐"},d=document.createElement("textarea");d.className="fl-feedback-textarea fl-feedback-textarea-modal",d.placeholder=h[e],d.rows=3,c.appendChild(d);const g="้ไฟก",f=e==="ok"?"ในใญใใ":"ใญใฃใณใปใซ",p=r("div",{className:"fl-quest-actions fl-feedback-modal-actions"},[this.createButton("fl-btn fl-btn-skip",f,()=>{e==="ng"?this.callbacks.onNgWithFeedback(""):e==="ok"&&this.callbacks.onOkWithFeedback(""),s.remove()}),this.createButton("fl-btn fl-btn-finish",g,()=>{const m=d.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(p),s.appendChild(c),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)}getChecklist(){return this.checklist.length>0?[...this.checklist]:void 0}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 N=100,F=2*1024*1024;class P{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:y(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:y(e),value:"[MASKED]"});else{const s=e.value;this.addLog({type:"input",target:y(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>F)return;this.snapshots.length>=N&&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 y(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 z{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 O{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 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=`${c*n}px`,this.container.appendChild(d)}}}class Q{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 B="firstlook_uat",j=1,u={sessions:"sessions",recordings:"recordings",annotations:"annotations",uploadQueue:"upload_queue"};class ${constructor(){this.db=null}async open(){if(!this.db)return new Promise((t,e)=>{const s=indexedDB.open(B,j);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 h=i.transaction(t,"readonly").objectStore(t).index(e).getAll(s);h.onsuccess=()=>n(h.result),h.onerror=()=>o(h.error)})}}const T=["#e17055","#6c5ce7","#00b894","#fdcb6e","#ffffff"];class U{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 h=r("div",{className:`fl-color-swatch ${c===this.selectedColor?"fl-selected":""}`});h.style.background=c,h.onclick=()=>{this.selectedColor=c,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 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 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 l=new XMLSerializer().serializeToString(s),c=`<svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${e}">
|
|
2
4
|
<foreignObject width="100%" height="100%">
|
|
3
5
|
${l}
|
|
4
6
|
</foreignObject>
|
|
5
|
-
</svg>`,h=new Blob([c],{type:"image/svg+xml;charset=utf-8"}),d=URL.createObjectURL(h),
|
|
7
|
+
</svg>`,h=new Blob([c],{type:"image/svg+xml;charset=utf-8"}),d=URL.createObjectURL(h),g=document.createElement("canvas");g.width=t*window.devicePixelRatio,g.height=e*window.devicePixelRatio;const f=g.getContext("2d");return f.scale(window.devicePixelRatio,window.devicePixelRatio),new Promise(p=>{const m=new Image;m.onload=()=>{f.drawImage(m,0,0,t,e),URL.revokeObjectURL(d),p(g.toDataURL("image/png"))},m.onerror=()=>{URL.revokeObjectURL(d),p(this.captureScreenshotFallback(t,e))},m.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 K{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 U(t)}start(){document.addEventListener("keydown",this.onKeydown)}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 H{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 S="firstlook:uat-active";class q{constructor(t){this.onActivate=t,this.handlers=[]}start(){if(this.checkUrl()){this.persist(),this.onActivate();return}if(this.hasPersisted()){this.onActivate();return}const t=()=>{this.checkUrl()&&(this.persist(),this.onActivate())};window.addEventListener("hashchange",t),this.handlers.push(["hashchange",t]),window.addEventListener("popstate",t),this.handlers.push(["popstate",t])}stop(){for(const[t,e]of this.handlers)window.removeEventListener(t,e);this.handlers=[]}static clearPersisted(){try{sessionStorage.removeItem(S)}catch{}}checkUrl(){const t=new URLSearchParams(window.location.search);if(t.has("firstlook")||t.has("uat-mode")||t.get("uat")==="1")return!0;const e=window.location.hash,s=e.indexOf("?");if(s!==-1){const i=new URLSearchParams(e.substring(s));if(i.has("firstlook")||i.has("uat-mode")||i.get("uat")==="1")return!0}return!1}persist(){try{sessionStorage.setItem(S,"1")}catch{}}hasPersisted(){try{return sessionStorage.getItem(S)==="1"}catch{return!1}}}class _{constructor(t){this.onActivate=t,this.handler=null}start(){this.handler=t=>{t.code==="KeyU"&&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 W{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"}),this.root.style.pointerEvents="auto";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 V=`
|
|
6
8
|
:host {
|
|
7
9
|
all: initial;
|
|
8
10
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
@@ -211,6 +213,51 @@
|
|
|
211
213
|
50% { opacity: 0.3; }
|
|
212
214
|
}
|
|
213
215
|
|
|
216
|
+
/* === Checklist === */
|
|
217
|
+
.fl-checklist-counter {
|
|
218
|
+
font-size: 13px;
|
|
219
|
+
font-weight: 600;
|
|
220
|
+
color: #6c5ce7;
|
|
221
|
+
margin-bottom: 8px;
|
|
222
|
+
}
|
|
223
|
+
.fl-checklist {
|
|
224
|
+
list-style: none;
|
|
225
|
+
padding: 0;
|
|
226
|
+
margin: 0 0 12px;
|
|
227
|
+
max-height: 240px;
|
|
228
|
+
overflow-y: auto;
|
|
229
|
+
-webkit-overflow-scrolling: touch;
|
|
230
|
+
}
|
|
231
|
+
.fl-checklist-item {
|
|
232
|
+
display: flex;
|
|
233
|
+
align-items: flex-start;
|
|
234
|
+
gap: 8px;
|
|
235
|
+
padding: 8px 4px;
|
|
236
|
+
border-bottom: 1px solid rgba(0,0,0,.06);
|
|
237
|
+
font-size: 13px;
|
|
238
|
+
color: #4a4a5a;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
min-height: 40px;
|
|
241
|
+
transition: background .2s;
|
|
242
|
+
}
|
|
243
|
+
.fl-checklist-item:last-child { border-bottom: none; }
|
|
244
|
+
.fl-checklist-item:active { background: rgba(108,92,231,.05); }
|
|
245
|
+
.fl-checklist-item.fl-checked { color: #aaa; }
|
|
246
|
+
.fl-checklist-item.fl-checked .fl-checklist-text { text-decoration: line-through; }
|
|
247
|
+
.fl-checklist-checkbox {
|
|
248
|
+
margin-top: 2px;
|
|
249
|
+
width: 18px;
|
|
250
|
+
height: 18px;
|
|
251
|
+
accent-color: #6c5ce7;
|
|
252
|
+
flex-shrink: 0;
|
|
253
|
+
cursor: pointer;
|
|
254
|
+
}
|
|
255
|
+
.fl-checklist-text {
|
|
256
|
+
flex: 1;
|
|
257
|
+
line-height: 1.4;
|
|
258
|
+
word-break: break-word;
|
|
259
|
+
}
|
|
260
|
+
|
|
214
261
|
/* === Annotation overlay === */
|
|
215
262
|
.fl-annotation-overlay {
|
|
216
263
|
position: fixed;
|
|
@@ -455,5 +502,5 @@
|
|
|
455
502
|
animation: fl-fade-in 0.15s ease-out;
|
|
456
503
|
}
|
|
457
504
|
.fl-feedback-modal-actions { padding: 0 16px 16px; }
|
|
458
|
-
`,W=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.issueReporter=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=C(t),this.storage=new B,await this.storage.open(),this.createShadowHost(),this.setupTriggers(),this.fieldMasker=new z(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=R(),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.issueReporter=new U(this.shadowRoot,this.events),this.issueReporter.start(),this.sessionRecorder.start(),this.fieldMasker.start(),this.config.recording.voice&&await this.voiceRecorder.start(),this.config.security.watermark&&(this.watermark=new P(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,h,d,f,g,p,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.issueReporter)==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 k=>{if(k.voiceMemoBlob){try{k.voiceMemoBase64=await this.blobToBase64(k.voiceMemoBlob)}catch{}delete k.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:((h=this.sessionRecorder)==null?void 0:h.getSnapshots())??[]};await((d=this.storage)==null?void 0:d.saveSession(e));const s=((f=this.issueReporter)==null?void 0:f.getAnnotations())??[];for(const b of s)await((g=this.storage)==null?void 0:g.saveAnnotation(this.sessionId,b));return await((p=this.storage)==null?void 0:p.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,h,d,f,g;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(),q.clearPersisted(),(i=this.sessionRecorder)==null||i.stop(),(n=this.voiceRecorder)==null||n.stopAsync().catch(()=>{}),(o=this.issueReporter)==null||o.stop(),(l=this.fieldMasker)==null||l.stop(),(c=this.watermark)==null||c.hide(),(h=this.questOverlay)==null||h.destroy(),(d=this.debugMenu)==null||d.hide(),(f=this.hostElement)==null||f.remove(),(g=this.storage)==null||g.close(),this.events.removeAll(),this.config=null,this.questManager=null,this.questOverlay=null,this.sessionRecorder=null,this.voiceRecorder=null,this.issueReporter=null,this.fieldMasker=null,this.debugMenu=null,this.tapTrigger=null,this.deepLinkTrigger=null,this.keyboardTrigger=null,this.storage=null,this.hostElement=null,this.shadowRoot=null,this.sessionId=null,this.state="idle"}createShadowHost(){var e;(e=document.getElementById("firstlook-sdk-root"))==null||e.remove(),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=_,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 q(()=>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 H(this.shadowRoot,{onStartSession:()=>{this.events.emit({type:"sdk:activated"})},onReportIssue:()=>{var i;(i=this.issueReporter)==null||i.trigger()},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>=W){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"})});
|
|
505
|
+
`,X=5;class Y{constructor(){this.config=null,this.events=new M,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.issueReporter=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=I(t),this.storage=new $,await this.storage.open(),this.createShadowHost(),this.setupTriggers(),this.fieldMasker=new Q(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 L(this.events),this.questManager.loadQuests(t),this.sessionRecorder=new P(this.config,this.events,this.fieldMasker.getCombinedSelector()),this.voiceRecorder=new z(this.events),this.issueReporter=new K(this.shadowRoot,this.events),this.issueReporter.start(),this.sessionRecorder.start(),this.fieldMasker.start(),this.config.recording.voice&&await this.voiceRecorder.start(),this.config.security.watermark&&(this.watermark=new O(this.shadowRoot,this.config),this.watermark.show()),this.questOverlay=new D(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,h,d,g,f,p,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.issueReporter)==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 k=>{if(k.voiceMemoBlob){try{k.voiceMemoBase64=await this.blobToBase64(k.voiceMemoBlob)}catch{}delete k.voiceMemoBlob}}))}));const e={sessionId:this.sessionId,projectId:this.config.projectId,userId:this.config.userId,role:this.config.role,deviceInfo:C(),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=((g=this.issueReporter)==null?void 0:g.getAnnotations())??[];for(const b of s)await((f=this.storage)==null?void 0:f.saveAnnotation(this.sessionId,b));return await((p=this.storage)==null?void 0:p.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,h,d,g,f;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(),q.clearPersisted(),(i=this.sessionRecorder)==null||i.stop(),(n=this.voiceRecorder)==null||n.stopAsync().catch(()=>{}),(o=this.issueReporter)==null||o.stop(),(l=this.fieldMasker)==null||l.stop(),(c=this.watermark)==null||c.hide(),(h=this.questOverlay)==null||h.destroy(),(d=this.debugMenu)==null||d.hide(),(g=this.hostElement)==null||g.remove(),(f=this.storage)==null||f.close(),this.events.removeAll(),this.config=null,this.questManager=null,this.questOverlay=null,this.sessionRecorder=null,this.voiceRecorder=null,this.issueReporter=null,this.fieldMasker=null,this.debugMenu=null,this.tapTrigger=null,this.deepLinkTrigger=null,this.keyboardTrigger=null,this.storage=null,this.hostElement=null,this.shadowRoot=null,this.sessionId=null,this.state="idle"}createShadowHost(){var e;(e=document.getElementById("firstlook-sdk-root"))==null||e.remove(),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=V,this.shadowRoot.appendChild(t)}setupTriggers(){const t=this.config.triggers;this.tapTrigger=new H(t.tapCount,()=>this.onActivate()),this.tapTrigger.start(),t.deepLink&&(this.deepLinkTrigger=new q(()=>this.onActivate()),this.deepLinkTrigger.start()),t.keyboard&&(this.keyboardTrigger=new _(()=>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 W(this.shadowRoot,{onStartSession:()=>{this.events.emit({type:"sdk:activated"})},onReportIssue:()=>{var i;(i=this.issueReporter)==null||i.trigger()},onClose:()=>{this.destroy()}}),this.debugMenu.show())}handleQuestOk(t){var i;if(!this.questManager||!this.sessionRecorder)return;const e=this.sessionRecorder.getActionLogs(),s=(i=this.questOverlay)==null?void 0:i.getChecklist();this.questManager.completeCurrentQuest(e,{comment:t||void 0,checklist:s}),this.renderCurrentQuest()}handleConcern(t){var i;if(!this.questManager||!this.sessionRecorder)return;const e=this.sessionRecorder.getActionLogs(),s=(i=this.questOverlay)==null?void 0:i.getChecklist();this.questManager.completeCurrentQuest(e,{comment:t,concern:!0,checklist:s}),this.renderCurrentQuest()}handleQuestNg(){if(!this.questManager||!this.questOverlay)return;const t=this.questManager.getCurrentQuest();t&&this.questOverlay.renderFeedbackModal(t.title,"ng")}handleNgFeedbackSubmit(t){var i;if(!this.questManager||!this.sessionRecorder)return;const e=this.sessionRecorder.getActionLogs(),s=(i=this.questOverlay)==null?void 0:i.getChecklist();this.questManager.failCurrentQuest(e,t||void 0,void 0,s),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:C(),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)})}}x.FirstLookSDK=Y,Object.defineProperty(x,Symbol.toStringTag,{value:"Module"})});
|
|
459
506
|
//# sourceMappingURL=firstlook.umd.js.map
|