@firstlook-uat/sdk 0.2.0
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/README.md +139 -0
- package/dist/firstlook.es.js +1598 -0
- package/dist/firstlook.es.js.map +1 -0
- package/dist/firstlook.umd.js +441 -0
- package/dist/firstlook.umd.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,441 @@
|
|
|
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}">
|
|
2
|
+
<foreignObject width="100%" height="100%">
|
|
3
|
+
${c}
|
|
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=`
|
|
6
|
+
:host {
|
|
7
|
+
all: initial;
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
9
|
+
font-size: 14px;
|
|
10
|
+
color: #1a1a2e;
|
|
11
|
+
line-height: 1.5;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
* {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* === Quest Overlay === */
|
|
21
|
+
.fl-quest-overlay {
|
|
22
|
+
position: fixed;
|
|
23
|
+
bottom: 24px;
|
|
24
|
+
right: 24px;
|
|
25
|
+
z-index: 2147483647;
|
|
26
|
+
width: 360px;
|
|
27
|
+
max-width: calc(100vw - 48px);
|
|
28
|
+
background: #ffffff;
|
|
29
|
+
border-radius: 16px;
|
|
30
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.16), 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
animation: fl-slide-up 0.3s ease-out;
|
|
33
|
+
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.fl-quest-overlay.fl-minimized {
|
|
37
|
+
width: 56px;
|
|
38
|
+
height: 56px;
|
|
39
|
+
border-radius: 50%;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
background: #6c5ce7;
|
|
45
|
+
box-shadow: 0 4px 16px rgba(108, 92, 231, 0.4);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.fl-quest-overlay.fl-minimized .fl-quest-content {
|
|
49
|
+
display: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.fl-quest-overlay.fl-minimized .fl-minimize-icon {
|
|
53
|
+
display: block;
|
|
54
|
+
color: #fff;
|
|
55
|
+
font-size: 24px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.fl-minimize-icon {
|
|
59
|
+
display: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@keyframes fl-slide-up {
|
|
63
|
+
from { transform: translateY(20px); opacity: 0; }
|
|
64
|
+
to { transform: translateY(0); opacity: 1; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.fl-quest-header {
|
|
68
|
+
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
|
|
69
|
+
color: #fff;
|
|
70
|
+
padding: 14px 16px;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: space-between;
|
|
74
|
+
gap: 8px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.fl-quest-header-left {
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 8px;
|
|
81
|
+
flex: 1;
|
|
82
|
+
min-width: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.fl-quest-badge {
|
|
86
|
+
background: rgba(255, 255, 255, 0.2);
|
|
87
|
+
border-radius: 12px;
|
|
88
|
+
padding: 2px 10px;
|
|
89
|
+
font-size: 11px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
white-space: nowrap;
|
|
92
|
+
letter-spacing: 0.5px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.fl-quest-title {
|
|
96
|
+
font-size: 14px;
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
overflow: hidden;
|
|
99
|
+
text-overflow: ellipsis;
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.fl-quest-minimize-btn {
|
|
104
|
+
background: rgba(255, 255, 255, 0.2);
|
|
105
|
+
border: none;
|
|
106
|
+
color: #fff;
|
|
107
|
+
width: 28px;
|
|
108
|
+
height: 28px;
|
|
109
|
+
border-radius: 50%;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
font-size: 16px;
|
|
115
|
+
flex-shrink: 0;
|
|
116
|
+
transition: background 0.15s;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.fl-quest-minimize-btn:hover {
|
|
120
|
+
background: rgba(255, 255, 255, 0.3);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.fl-quest-body {
|
|
124
|
+
padding: 16px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.fl-quest-description {
|
|
128
|
+
font-size: 14px;
|
|
129
|
+
color: #4a4a5a;
|
|
130
|
+
margin-bottom: 16px;
|
|
131
|
+
line-height: 1.6;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.fl-quest-progress {
|
|
135
|
+
display: flex;
|
|
136
|
+
gap: 4px;
|
|
137
|
+
margin-bottom: 16px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.fl-quest-progress-dot {
|
|
141
|
+
height: 4px;
|
|
142
|
+
flex: 1;
|
|
143
|
+
border-radius: 2px;
|
|
144
|
+
background: #e0e0e0;
|
|
145
|
+
transition: background 0.3s;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.fl-quest-progress-dot.fl-completed { background: #00b894; }
|
|
149
|
+
.fl-quest-progress-dot.fl-failed { background: #e17055; }
|
|
150
|
+
.fl-quest-progress-dot.fl-active { background: #6c5ce7; }
|
|
151
|
+
|
|
152
|
+
.fl-quest-actions {
|
|
153
|
+
display: flex;
|
|
154
|
+
gap: 10px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.fl-btn {
|
|
158
|
+
flex: 1;
|
|
159
|
+
padding: 10px 16px;
|
|
160
|
+
border-radius: 10px;
|
|
161
|
+
border: none;
|
|
162
|
+
font-size: 14px;
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
transition: transform 0.1s, box-shadow 0.15s;
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
justify-content: center;
|
|
169
|
+
gap: 6px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.fl-btn:active { transform: scale(0.97); }
|
|
173
|
+
|
|
174
|
+
.fl-btn-ok {
|
|
175
|
+
background: #00b894;
|
|
176
|
+
color: #fff;
|
|
177
|
+
box-shadow: 0 2px 8px rgba(0, 184, 148, 0.3);
|
|
178
|
+
}
|
|
179
|
+
.fl-btn-ok:hover { box-shadow: 0 4px 12px rgba(0, 184, 148, 0.4); }
|
|
180
|
+
|
|
181
|
+
.fl-btn-ng {
|
|
182
|
+
background: #e17055;
|
|
183
|
+
color: #fff;
|
|
184
|
+
box-shadow: 0 2px 8px rgba(225, 112, 85, 0.3);
|
|
185
|
+
}
|
|
186
|
+
.fl-btn-ng:hover { box-shadow: 0 4px 12px rgba(225, 112, 85, 0.4); }
|
|
187
|
+
|
|
188
|
+
/* === Voice recording indicator === */
|
|
189
|
+
.fl-voice-indicator {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
gap: 8px;
|
|
193
|
+
padding: 8px 16px;
|
|
194
|
+
background: #fff3f0;
|
|
195
|
+
border-top: 1px solid rgba(225, 112, 85, 0.1);
|
|
196
|
+
font-size: 12px;
|
|
197
|
+
color: #e17055;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.fl-voice-dot {
|
|
201
|
+
width: 8px;
|
|
202
|
+
height: 8px;
|
|
203
|
+
border-radius: 50%;
|
|
204
|
+
background: #e17055;
|
|
205
|
+
animation: fl-pulse 1.2s ease-in-out infinite;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@keyframes fl-pulse {
|
|
209
|
+
0%, 100% { opacity: 1; }
|
|
210
|
+
50% { opacity: 0.3; }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* === Annotation overlay === */
|
|
214
|
+
.fl-annotation-overlay {
|
|
215
|
+
position: fixed;
|
|
216
|
+
inset: 0;
|
|
217
|
+
z-index: 2147483647;
|
|
218
|
+
background: rgba(0, 0, 0, 0.7);
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-direction: column;
|
|
221
|
+
align-items: center;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
animation: fl-fade-in 0.2s ease-out;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@keyframes fl-fade-in {
|
|
227
|
+
from { opacity: 0; }
|
|
228
|
+
to { opacity: 1; }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.fl-annotation-canvas-wrap {
|
|
232
|
+
position: relative;
|
|
233
|
+
border-radius: 8px;
|
|
234
|
+
overflow: hidden;
|
|
235
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.fl-annotation-canvas {
|
|
239
|
+
display: block;
|
|
240
|
+
cursor: crosshair;
|
|
241
|
+
max-width: 90vw;
|
|
242
|
+
max-height: 70vh;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.fl-annotation-toolbar {
|
|
246
|
+
display: flex;
|
|
247
|
+
gap: 8px;
|
|
248
|
+
margin-top: 16px;
|
|
249
|
+
align-items: center;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.fl-annotation-toolbar .fl-btn {
|
|
253
|
+
flex: none;
|
|
254
|
+
padding: 10px 20px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.fl-annotation-comment {
|
|
258
|
+
width: 300px;
|
|
259
|
+
padding: 10px 14px;
|
|
260
|
+
border-radius: 10px;
|
|
261
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
262
|
+
background: rgba(255, 255, 255, 0.15);
|
|
263
|
+
color: #fff;
|
|
264
|
+
font-size: 14px;
|
|
265
|
+
outline: none;
|
|
266
|
+
backdrop-filter: blur(4px);
|
|
267
|
+
transition: border-color 0.15s;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.fl-annotation-comment::placeholder { color: rgba(255, 255, 255, 0.5); }
|
|
271
|
+
.fl-annotation-comment:focus { border-color: #a29bfe; }
|
|
272
|
+
|
|
273
|
+
.fl-color-picker { display: flex; gap: 4px; }
|
|
274
|
+
|
|
275
|
+
.fl-color-swatch {
|
|
276
|
+
width: 24px;
|
|
277
|
+
height: 24px;
|
|
278
|
+
border-radius: 50%;
|
|
279
|
+
border: 2px solid transparent;
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
transition: transform 0.1s;
|
|
282
|
+
}
|
|
283
|
+
.fl-color-swatch:hover { transform: scale(1.15); }
|
|
284
|
+
.fl-color-swatch.fl-selected {
|
|
285
|
+
border-color: #fff;
|
|
286
|
+
box-shadow: 0 0 0 2px rgba(108, 92, 231, 0.6);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* === Watermark === */
|
|
290
|
+
.fl-watermark {
|
|
291
|
+
position: fixed;
|
|
292
|
+
inset: 0;
|
|
293
|
+
z-index: 2147483640;
|
|
294
|
+
pointer-events: none;
|
|
295
|
+
overflow: hidden;
|
|
296
|
+
opacity: 0.03;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.fl-watermark-tile {
|
|
300
|
+
position: absolute;
|
|
301
|
+
font-size: 12px;
|
|
302
|
+
font-family: monospace;
|
|
303
|
+
color: #000;
|
|
304
|
+
white-space: nowrap;
|
|
305
|
+
transform: rotate(-30deg);
|
|
306
|
+
user-select: none;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* === Debug Menu === */
|
|
310
|
+
.fl-debug-menu {
|
|
311
|
+
position: fixed;
|
|
312
|
+
bottom: 100px;
|
|
313
|
+
right: 24px;
|
|
314
|
+
z-index: 2147483646;
|
|
315
|
+
background: #fff;
|
|
316
|
+
border-radius: 12px;
|
|
317
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.16);
|
|
318
|
+
padding: 8px;
|
|
319
|
+
min-width: 200px;
|
|
320
|
+
animation: fl-slide-up 0.2s ease-out;
|
|
321
|
+
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.fl-debug-menu-item {
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 10px;
|
|
328
|
+
padding: 10px 14px;
|
|
329
|
+
border: none;
|
|
330
|
+
background: none;
|
|
331
|
+
width: 100%;
|
|
332
|
+
text-align: left;
|
|
333
|
+
border-radius: 8px;
|
|
334
|
+
cursor: pointer;
|
|
335
|
+
font-size: 14px;
|
|
336
|
+
color: #1a1a2e;
|
|
337
|
+
transition: background 0.15s;
|
|
338
|
+
}
|
|
339
|
+
.fl-debug-menu-item:hover { background: #f0f0f8; }
|
|
340
|
+
|
|
341
|
+
.fl-debug-menu-item-icon {
|
|
342
|
+
font-size: 18px;
|
|
343
|
+
width: 24px;
|
|
344
|
+
text-align: center;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* === Session status bar === */
|
|
348
|
+
.fl-status-bar {
|
|
349
|
+
display: flex;
|
|
350
|
+
align-items: center;
|
|
351
|
+
gap: 8px;
|
|
352
|
+
padding: 6px 16px;
|
|
353
|
+
background: #f8f8fc;
|
|
354
|
+
border-top: 1px solid rgba(0, 0, 0, 0.04);
|
|
355
|
+
font-size: 11px;
|
|
356
|
+
color: #888;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.fl-status-recording {
|
|
360
|
+
display: flex;
|
|
361
|
+
align-items: center;
|
|
362
|
+
gap: 4px;
|
|
363
|
+
color: #e17055;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.fl-status-timer {
|
|
367
|
+
margin-left: auto;
|
|
368
|
+
font-variant-numeric: tabular-nums;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* === Completion summary === */
|
|
372
|
+
.fl-summary { padding: 20px 16px; text-align: center; }
|
|
373
|
+
.fl-summary-icon { font-size: 48px; margin-bottom: 12px; }
|
|
374
|
+
.fl-summary-title { font-size: 18px; font-weight: 700; color: #1a1a2e; margin-bottom: 4px; }
|
|
375
|
+
.fl-summary-subtitle { font-size: 13px; color: #888; margin-bottom: 16px; }
|
|
376
|
+
.fl-summary-stats { display: flex; justify-content: center; gap: 24px; margin-bottom: 16px; }
|
|
377
|
+
.fl-stat { text-align: center; }
|
|
378
|
+
.fl-stat-value { font-size: 24px; font-weight: 700; }
|
|
379
|
+
.fl-stat-value.fl-ok { color: #00b894; }
|
|
380
|
+
.fl-stat-value.fl-ng { color: #e17055; }
|
|
381
|
+
.fl-stat-label { font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
382
|
+
|
|
383
|
+
.fl-btn-finish {
|
|
384
|
+
background: #6c5ce7;
|
|
385
|
+
color: #fff;
|
|
386
|
+
width: 100%;
|
|
387
|
+
box-shadow: 0 2px 8px rgba(108, 92, 231, 0.3);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.fl-btn-skip {
|
|
391
|
+
background: #636e72;
|
|
392
|
+
color: #fff;
|
|
393
|
+
box-shadow: 0 2px 8px rgba(99, 110, 114, 0.3);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* === NG Voice Feedback Mode === */
|
|
397
|
+
.fl-quest-header-ng { background: linear-gradient(135deg, #e17055, #d63031); }
|
|
398
|
+
.fl-badge-ng { background: rgba(255, 255, 255, 0.25); }
|
|
399
|
+
|
|
400
|
+
.fl-feedback-textarea {
|
|
401
|
+
width: calc(100% - 32px);
|
|
402
|
+
margin: 8px 16px 16px;
|
|
403
|
+
padding: 10px 12px;
|
|
404
|
+
border-radius: 8px;
|
|
405
|
+
border: 1px solid #ddd;
|
|
406
|
+
font-size: 13px;
|
|
407
|
+
font-family: inherit;
|
|
408
|
+
resize: vertical;
|
|
409
|
+
outline: none;
|
|
410
|
+
transition: border-color 0.15s;
|
|
411
|
+
}
|
|
412
|
+
.fl-feedback-textarea:focus { border-color: #6c5ce7; }
|
|
413
|
+
.fl-feedback-textarea-modal { width: calc(100% - 32px); margin: 16px 16px 12px; }
|
|
414
|
+
|
|
415
|
+
/* === Memo button === */
|
|
416
|
+
.fl-quest-memo-row { display: flex; margin-bottom: 10px; }
|
|
417
|
+
.fl-btn-memo {
|
|
418
|
+
flex: none;
|
|
419
|
+
background: #f0f0f8;
|
|
420
|
+
color: #4a4a5a;
|
|
421
|
+
font-size: 12px;
|
|
422
|
+
padding: 6px 12px;
|
|
423
|
+
box-shadow: none;
|
|
424
|
+
border-radius: 8px;
|
|
425
|
+
}
|
|
426
|
+
.fl-btn-memo:hover { background: #e4e4f0; }
|
|
427
|
+
|
|
428
|
+
/* === Feedback modal === */
|
|
429
|
+
.fl-feedback-modal {
|
|
430
|
+
position: absolute;
|
|
431
|
+
inset: 0;
|
|
432
|
+
background: #fff;
|
|
433
|
+
border-radius: 16px;
|
|
434
|
+
z-index: 10;
|
|
435
|
+
display: flex;
|
|
436
|
+
flex-direction: column;
|
|
437
|
+
animation: fl-fade-in 0.15s ease-out;
|
|
438
|
+
}
|
|
439
|
+
.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"})});
|
|
441
|
+
//# sourceMappingURL=firstlook.umd.js.map
|