blocfeed 0.8.1 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0 — 2026-03-03
4
+
5
+ ### New features
6
+
7
+ - **Voice feedback** — Users can record audio via the microphone and have it automatically transcribed to text using OpenAI Whisper. The transcribed text populates the feedback textarea for review before submission.
8
+ - Enabled via `config.voice.enabled: true` (off by default)
9
+ - Configurable max duration (`maxDurationMs`, default 60s) with auto-stop
10
+ - Audio codec negotiation: WebM/Opus → WebM → Ogg/Opus
11
+ - Voice button (mic icon) appears in the review phase; pulsing red dot + timer during recording
12
+ - "Transcribing..." spinner while audio is sent to the server
13
+ - Graceful error handling: microphone denied, transcription failed, no speech detected
14
+ - Auto-hidden on unsupported browsers (no `getUserMedia` or `MediaRecorder`)
15
+ - Requires project-level opt-in via dashboard toggle (`voice_feedback_enabled`)
16
+
17
+ ### Improvements
18
+
19
+ - New types exported: `VoiceConfig`.
20
+ - Engine exports: `isVoiceSupported`, `startVoiceRecording`, `transcribeAudio`, `VoiceSession` for headless usage.
21
+ - `BlocFeedStrings` extended with `voiceButton`, `voiceStopButton`, `voiceRecordingText`, `voiceTranscribingText`, `voiceDeniedText`, `voiceErrorText`, `voiceEmptyText`.
22
+ - `BlocFeedState` extended with `voiceRecording`, `voiceElapsedMs`, `voiceTranscribing` fields.
23
+ - `BlocFeedController` extended with `startVoice()` and `stopVoice()` methods.
24
+
25
+ ### Platform changes (blocfeed-frontend)
26
+
27
+ - New SQL migration `scripts/040_voice_feedback.sql` adds `voice_feedback_enabled BOOLEAN NOT NULL DEFAULT FALSE` column to projects.
28
+ - New `POST /api/feedback/voice` endpoint: accepts raw audio body with `X-Blocfeed-Id` header, validates project + feature flag, calls OpenAI Whisper API, returns transcribed text.
29
+ - Dashboard Settings page adds a "Voice Feedback" toggle card under General tab.
30
+ - New server action `updateVoiceFeedbackEnabled` for toggling the feature per project.
31
+
32
+ ---
33
+
34
+ ## 0.9.0 — 2026-02-25
35
+
36
+ ### New features
37
+
38
+ - **Click tracking during video recording** — Every user click between recording start and stop is automatically captured and included with the feedback. Each click event records:
39
+ - **Timestamp** (ms offset from recording start, synced to video timeline)
40
+ - **Page path** (`window.location.pathname`)
41
+ - **Element tag** (e.g. `<button>`, `<a>`, `<div>`)
42
+ - **Text snippet** (truncated text content of the clicked element)
43
+ - **Component name** (React component name via `data-blocfeed-component` or React fiber introspection)
44
+ - No configuration needed — click tracking is automatic when `recording.enabled` is `true`.
45
+
46
+ ### Improvements
47
+
48
+ - New types exported: `ClickEvent`.
49
+ - Engine exports: `drainClickEvents`, `clearClickEvents` for headless usage.
50
+ - `FeedbackPayload` extended with optional `click_events` field.
51
+
52
+ ### Platform changes (blocfeed-frontend)
53
+
54
+ - New SQL migration `scripts/034_click_events.sql` adds `meta_click_events` JSONB column.
55
+ - `POST /api/feedback` now extracts and persists click events from `metadata._clickEvents`.
56
+ - Dashboard Video tab shows a **click timeline** below the video player. Each entry displays the timestamp, element tag, component name, text snippet, and page path. Clicking a timeline entry **seeks the video** to that moment.
57
+
58
+ ---
59
+
3
60
  ## 0.8.1 — 2026-02-24
4
61
 
5
62
  ### Bug fixes
package/README.md CHANGED
@@ -16,6 +16,8 @@ Drop-in in-app feedback widget for **Next.js** and **React**:
16
16
  - **Conditional display** — show/hide widget by route pattern or custom predicate
17
17
  - **Programmatic API** — `ref.open()`, `ref.close()`, `ref.submit(msg)` via React ref
18
18
  - **Video recording** — record a short screen capture clip (via `getDisplayMedia`) to show bug reproduction steps
19
+ - **Click tracking** — automatically captures every click during video recording with page path, component name, element tag, and text
20
+ - **Voice feedback** — record audio via microphone, auto-transcribe with Whisper, and populate the feedback textarea
19
21
  - **Secret leak detection** — scans client-side code for exposed API keys, database credentials, and tokens
20
22
 
21
23
  Docs live in `docs/` (start at `docs/index.md`). Architecture pointers are in `ARCHITECTURE.md`.
@@ -140,6 +142,12 @@ All configuration is passed via the `config` prop on `<BlocFeedWidget>` or `<Blo
140
142
  videoBitsPerSecond: 2_500_000,
141
143
  },
142
144
 
145
+ // Voice feedback (mic recording + transcription)
146
+ voice: {
147
+ enabled: true,
148
+ maxDurationMs: 60_000,
149
+ },
150
+
143
151
  // Screenshot defaults
144
152
  capture: {
145
153
  element: true,
@@ -294,6 +302,20 @@ config={{
294
302
  }}
295
303
  ```
296
304
 
305
+ Voice feedback strings are also localizable:
306
+
307
+ ```tsx
308
+ strings: {
309
+ voiceButton: "Voice",
310
+ voiceStopButton: "Stop",
311
+ voiceRecordingText: "Recording…",
312
+ voiceTranscribingText: "Transcribing…",
313
+ voiceDeniedText: "Microphone access was denied",
314
+ voiceErrorText: "Voice transcription failed. Please type instead.",
315
+ voiceEmptyText: "No speech detected. Try again or type instead.",
316
+ }
317
+ ```
318
+
297
319
  All fields are optional — only override the ones you need. Category labels are also localizable:
298
320
 
299
321
  ```tsx
@@ -739,6 +761,122 @@ strings: {
739
761
  }
740
762
  ```
741
763
 
764
+ ### Click tracking during recording
765
+
766
+ When video recording is active, BlocFeed automatically captures every user click on the page. Each click event records:
767
+
768
+ | Field | Description |
769
+ |-------|-------------|
770
+ | `timestampMs` | Milliseconds from recording start (synced to video timeline) |
771
+ | `path` | `window.location.pathname` at the time of click |
772
+ | `tagName` | HTML tag of the clicked element (e.g. `button`, `a`) |
773
+ | `textSnippet` | Truncated text content of the clicked element |
774
+ | `componentName` | React component name (via `data-blocfeed-component` or fiber introspection) |
775
+
776
+ Click events are included in the payload as `metadata._clickEvents` and displayed as a **click timeline** in the dashboard Video tab. Clicking a timeline entry seeks the video to that moment.
777
+
778
+ No extra configuration is needed — click tracking is automatic when `recording.enabled` is `true`. Up to 200 click events are captured per recording session. Clicks on the BlocFeed widget itself are excluded.
779
+
780
+ #### Headless click tracking
781
+
782
+ ```ts
783
+ import { drainClickEvents, clearClickEvents } from "blocfeed/engine";
784
+
785
+ const events = drainClickEvents(); // returns ClickEvent[]
786
+ clearClickEvents(); // reset the buffer
787
+ ```
788
+
789
+ ## Voice Feedback
790
+
791
+ Let users speak their feedback instead of typing. Audio is recorded via the microphone, transcribed server-side with OpenAI Whisper, and the text populates the feedback textarea for review before submission.
792
+
793
+ ```tsx
794
+ <BlocFeedWidget
795
+ blocfeed_id="bf_..."
796
+ config={{
797
+ voice: {
798
+ enabled: true, // default: false
799
+ maxDurationMs: 60_000, // default: 60s — auto-stops at this limit
800
+ },
801
+ }}
802
+ />
803
+ ```
804
+
805
+ ### Setup
806
+
807
+ Voice feedback requires two things:
808
+
809
+ 1. **Enable in the SDK** — set `voice.enabled: true` in the widget config
810
+ 2. **Enable per project** — toggle "Voice Feedback" on in the BlocFeed dashboard under **Settings > General**
811
+
812
+ The project-level toggle (`voice_feedback_enabled`) must be enabled for the transcription API to accept requests.
813
+
814
+ ### `VoiceConfig` options
815
+
816
+ | Option | Type | Default | Description |
817
+ |--------|------|---------|-------------|
818
+ | `enabled` | `boolean` | `false` | Show the Voice button in the review phase |
819
+ | `maxDurationMs` | `number` | `60000` | Maximum recording duration in ms. Auto-stops when reached |
820
+
821
+ ### How it works
822
+
823
+ 1. A **Voice** button (mic icon) appears in the review phase (after element selection)
824
+ 2. Clicking it triggers the browser's microphone permission prompt
825
+ 3. A pulsing red dot + elapsed timer shows during recording
826
+ 4. Recording stops when the user clicks **Stop** or the max duration is reached
827
+ 5. A "Transcribing..." spinner appears while audio is sent to the server
828
+ 6. The transcribed text populates the textarea — the user can edit before submitting
829
+ 7. If no speech is detected or transcription fails, an inline message is shown
830
+
831
+ ### Browser support
832
+
833
+ Voice recording requires `getUserMedia` and `MediaRecorder` with WebM/Ogg audio support. The Voice button is **automatically hidden** on unsupported browsers.
834
+
835
+ | Browser | Supported |
836
+ |---------|-----------|
837
+ | Chrome / Edge | Yes |
838
+ | Firefox | Yes |
839
+ | Safari 14.5+ | Yes (with Opus codec) |
840
+
841
+ ### Localization
842
+
843
+ Override voice-related UI text via `config.ui.strings`:
844
+
845
+ ```tsx
846
+ strings: {
847
+ voiceButton: "Voice",
848
+ voiceStopButton: "Stop",
849
+ voiceRecordingText: "Recording…",
850
+ voiceTranscribingText: "Transcribing…",
851
+ voiceDeniedText: "Microphone access was denied",
852
+ voiceErrorText: "Voice transcription failed. Please type instead.",
853
+ voiceEmptyText: "No speech detected. Try again or type instead.",
854
+ }
855
+ ```
856
+
857
+ ### Headless voice API
858
+
859
+ ```ts
860
+ import {
861
+ isVoiceSupported,
862
+ startVoiceRecording,
863
+ transcribeAudio,
864
+ } from "blocfeed/engine";
865
+
866
+ if (isVoiceSupported()) {
867
+ const session = await startVoiceRecording({ config: { maxDurationMs: 30_000 } });
868
+ // session.stop() to stop early
869
+ const { blob, mime, durationMs } = await session.result;
870
+
871
+ const { text } = await transcribeAudio({
872
+ blob,
873
+ mime,
874
+ blocfeedId: "bf_...",
875
+ });
876
+ console.log("Transcribed:", text);
877
+ }
878
+ ```
879
+
742
880
  ### Known limitations
743
881
 
744
882
  The default screenshot engine (`html-to-image`) has known issues with:
@@ -855,6 +993,8 @@ import type {
855
993
  SecurityConfig,
856
994
  SecurityFinding,
857
995
  SecuritySnapshot,
996
+ ClickEvent,
997
+ VoiceConfig,
858
998
  // ... and more
859
999
  } from "blocfeed";
860
1000
  ```
@@ -0,0 +1,2 @@
1
+ 'use strict';function y(){return typeof window<"u"&&typeof document<"u"}function Ae(e){let t=globalThis.CSS;return typeof t?.escape=="function"?t.escape(e):e.replace(/[^a-zA-Z0-9_-]/g,n=>{let o=n.codePointAt(0);return o===void 0?"":`\\${o.toString(16)} `})}function se(e){return {x:e.x,y:e.y,width:e.width,height:e.height}}function it(e){return {x:e.x+window.scrollX,y:e.y+window.scrollY,width:e.width,height:e.height}}function at(e){return e.replace(/\s+/g," ").trim()}function ce(e,t=140){let n=e.textContent;if(!n)return;let o=at(n);if(o)return o.length<=t?o:`${o.slice(0,t-1)}\u2026`}function st(e){let t=1;for(let n=e.previousElementSibling;n;n=n.previousElementSibling)n.tagName===e.tagName&&(t+=1);return t}var Ce=["data-testid","data-test-id","data-test","data-qa","data-cy"],Re="data-blocfeed-component";function le(e){let t=e.closest(`[${Re}]`);if(!t)return;let o=t.getAttribute(Re)?.trim();return o||void 0}function ct(e){for(let t of Ce){let n=e.closest(`[${t}]`);if(!n)continue;let r=n.getAttribute(t)?.trim();if(r)return r}}function xe(e){try{let t=e,n=Object.getOwnPropertyNames(t);for(let o of n)if(o.startsWith("__reactFiber$")||o.startsWith("__reactInternalInstance$")){let r=t[o];if(r&&typeof r=="object")return r}}catch{}return null}function U(e){if(e.length<=1||e.length===2&&e[0]===e[0].toLowerCase())return true;let t=e[0];return t>="a"&&t<="z"}function $(e){if(e){for(let t of e)if(typeof t.name=="string"&&t.name&&!U(t.name))return t.name}}function P(e){if(e&&typeof e!="string"){if(typeof e=="function"){let t=e;return typeof t.displayName=="string"&&t.displayName?t.displayName:typeof t.name=="string"&&t.name?t.name:void 0}if(typeof e=="object"){let t=e,n=t.displayName;if(typeof n=="string"&&n)return n;let o=t.render;if(typeof o=="function"){let i=o;if(typeof i.displayName=="string"&&i.displayName)return i.displayName;if(typeof i.name=="string"&&i.name)return i.name}let r=t.type;return P(r)}}}function ue(e){let t=xe(e);if(!t)return;let n=$(t._debugInfo);if(n)return n;let o=t._debugOwner!==void 0;if(o){let c=t._debugOwner;for(let u=0;c&&u<50;u+=1){let w=$(c._debugInfo);if(w)return w;let m=P(c.type)??P(c.elementType);if(m&&!U(m))return m;c=c._debugOwner;}}if(t._owner!==void 0&&t._owner!==t._debugOwner){let c=t._owner;for(let u=0;c&&u<50;u+=1){let w=$(c._debugInfo);if(w)return w;let m=P(c.type)??P(c.elementType);if(m&&!U(m))return m;c=c._owner;}}let i=t,a=o?80:25;for(let c=0;i&&c<a;c+=1){let u=$(i._debugInfo);if(u)return u;let w=P(i.type)??P(i.elementType);if(w&&!U(w))return w;i=i.return;}let s=e.parentElement;for(let c=0;s&&c<15;c+=1){let u=xe(s);if(u){let w=$(u._debugInfo);if(w)return w;let m=P(u.type)??P(u.elementType);if(m&&!U(m))return m;if(u._debugOwner){let l=P(u._debugOwner.type)??P(u._debugOwner.elementType);if(l&&!U(l))return l}if(u._owner&&u._owner!==u._debugOwner){let l=P(u._owner.type)??P(u._owner.elementType);if(l&&!U(l))return l}}s=s.parentElement;}}function lt(e){let t=e.tagName.toLowerCase(),n=e.getAttribute("id");if(n)return `#${Ae(n)}`;for(let o of Ce){let r=e.getAttribute(o);if(r)return `${t}[${o}="${Ae(r)}"]`}return `${t}:nth-of-type(${st(e)})`}function ut(e,t=10){let n=[],o=e;for(;o&&n.length<t;){let r=lt(o);if(n.unshift(r),r.startsWith("#"))break;o=o.parentElement;}return n.join(" > ")}function Pe(e,t){if(!t||t.length===0)return false;for(let n of t)if(e.closest(n))return true;return false}function de(e,t){if(!e||Pe(e,t.ignoreSelectors))return null;let n=e;for(;n;){if(Pe(n,t.ignoreSelectors))return null;if(t.isSelectable?.(n)??dt(n))return n;n=n.parentElement;}return null}function dt(e){let t=e.tagName;return !(t==="HTML"||t==="BODY")}function Me(e){let t=e.getBoundingClientRect(),n={selector:ut(e),tagName:e.tagName.toLowerCase(),rect:se(t),pageRect:it(t)},o=e.getAttribute("id");o&&(n.id=o);let r=e.className;typeof r=="string"&&r.trim()&&(n.className=r);let i=ce(e);i&&(n.textSnippet=i);let a=le(e)??ue(e);a&&(n.componentName=a);let s=ct(e);return s&&(n.testId=s),n}var fe=null;async function Fe(){return fe||(fe=import('html-to-image')),fe}async function ft(e){return await new Promise((t,n)=>{let o=new Image;o.onload=()=>t({width:o.naturalWidth,height:o.naturalHeight}),o.onerror=()=>n(new Error("Failed to load generated screenshot")),o.src=e;})}async function De(e,t){let{width:n,height:o}=await ft(e);return {dataUrl:e,mime:t,width:n,height:o}}function q(e){if(e?.aborted)throw new Error("Aborted")}function Le(){return {async captureElement(e,t){if(!y())throw new Error("captureElement can only run in the browser");q(t.signal);let n=await Fe();q(t.signal);let o=t.mime==="image/jpeg"?await n.toJpeg(e,{quality:t.quality??.92,pixelRatio:t.pixelRatio}):await n.toPng(e,{pixelRatio:t.pixelRatio});return q(t.signal),await De(o,t.mime)},async captureFullPage(e){if(!y())throw new Error("captureFullPage can only run in the browser");q(e.signal);let t=document.documentElement,n=Math.max(t.scrollWidth,t.clientWidth),o=Math.max(t.scrollHeight,t.clientHeight),r=Math.min(1,e.maxDimension/Math.max(n,o)),i=Math.max(1,Math.round(n*r)),a=Math.max(1,Math.round(o*r)),s=await Fe();q(e.signal);let c=e.mime==="image/jpeg"?await s.toJpeg(t,{width:i,height:a,quality:e.quality??.92,pixelRatio:e.pixelRatio}):await s.toPng(t,{width:i,height:a,pixelRatio:e.pixelRatio});return q(e.signal),await De(c,e.mime)}}}function mt(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return "Unknown error"}}function h(e,t,n){let o={kind:e,message:mt(t)};return n&&(o.detail=n),o}var pt=12e3,gt=2048,ht=.92;function Ie(){return Date.now()}function bt(e){return new Promise((t,n)=>{let o=()=>n(new Error("Aborted"));if(e.aborted){o();return}e.addEventListener("abort",o,{once:true});})}async function Be(e,t,n){let o=new Promise((i,a)=>{let s=setTimeout(()=>a(new Error("Timeout")),t);typeof s.unref=="function"&&s.unref();}),r=[e,o];return n&&r.push(bt(n)),await Promise.race(r)}function wt(e){if(!y())return 1;let t=window.devicePixelRatio||1,n=e?.pixelRatio??Math.min(t,2);return Math.max(.1,n)}function yt(e){return !!(e?.element||e?.fullPage)}function Ne(e){let t={mime:e.mime,pixelRatio:e.pixelRatio,maxDimension:e.maxDimension};return e.includeQuality&&(t.quality=e.quality),e.signal&&(t.signal=e.signal),t}async function Oe(e){let{selectionElement:t,capture:n,signal:o}=e;if(!y()||!yt(n))return;let r=Ie(),i=[],a=n?.timeoutMs??pt,s=n?.maxDimension??gt,c=n?.mime??"image/png",u=n?.quality??ht,w=n?.adapter??Le(),m={},l=wt(n);if(n?.element&&t)try{let p=t.getBoundingClientRect(),d=Math.min(1,s/Math.max(p.width,p.height)),k=Math.min(l,l*d),_=await Be(Promise.resolve(w.captureElement(t,{...Ne({mime:c,quality:u,pixelRatio:k,maxDimension:s,includeQuality:c==="image/jpeg",...o?{signal:o}:{}})})),a,o);m.element=_;}catch(p){if(o?.aborted)throw p;i.push(h("capture_failed",p,{target:"element"}));}if(n?.fullPage)try{let p=await Be(Promise.resolve(w.captureFullPage(Ne({mime:c,quality:u,pixelRatio:l,maxDimension:s,includeQuality:c==="image/jpeg",...o?{signal:o}:{}}))),a,o);m.fullPage=p;}catch(p){if(o?.aborted)throw p;i.push(h("capture_failed",p,{target:"fullPage"}));}let b=Ie(),f={startedAt:r,finishedAt:b,durationMs:Math.max(0,b-r)};return i.length>0&&(f.errors=i),{...m,diagnostics:f}}function Et(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function vt(){return y()?{url:window.location.href,title:document.title,referrer:document.referrer||void 0,userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height},scroll:{x:window.scrollX,y:window.scrollY},devicePixelRatio:window.devicePixelRatio||1,timezone:Et()}:{}}function St(e){if(!e)return {};let t={};return e.id&&(t.userId=e.id),e.email&&(t.userEmail=e.email),e.name&&(t.userName=e.name),t}async function Ue(e){let{config:t,context:n,user:o}=e;if(t?.enabled===false)return {};let r={...vt(),...St(o)},i=t?.enrich;if(!i)return r;try{let a=await i(n);return {...r,...a}}catch(a){let s=h("unknown",a);return {...r,blocfeedMetadataError:s.message}}}var me="blocfeed-queue",kt=50;function pe(){if(!y())return [];try{let e=localStorage.getItem(me);if(!e)return [];let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return []}}function ge(e){if(y())try{e.length===0?localStorage.removeItem(me):localStorage.setItem(me,JSON.stringify(e));}catch{}}function _t(e){let t={...e};if(t.screenshots){let n={...t.screenshots};n.element&&(n.element={...n.element,dataUrl:""}),n.fullPage&&(n.fullPage={...n.fullPage,dataUrl:""}),t.screenshots=n;}return delete t.video,t}function he(e){let t=pe(),n=_t(e);for(n.metadata={...n.metadata,_queued:true},t.push({payload:n,timestamp:Date.now()});t.length>kt;)t.shift();ge(t);}function Ve(){let e=pe();return e.length===0?[]:(ge([]),e.map(t=>t.payload))}function En(){ge([]);}function vn(){return pe().length}var Tt=200,X=[],He=0,ee=false;function qe(e){let t=e.target;if(!(t instanceof Element)||t.closest("[data-blocfeed-ui]"))return;let n={timestampMs:Date.now()-He,path:window.location.pathname,tagName:t.tagName.toLowerCase()},o=ce(t,100);o&&(n.textSnippet=o);let r=le(t)??ue(t);r&&(n.componentName=r),X.length<Tt&&X.push(n);}function ze(){ee||!y()||(ee=true,X=[],He=Date.now(),document.addEventListener("click",qe,{capture:true,passive:true}));}function je(){ee&&(ee=false,document.removeEventListener("click",qe,{capture:true}));}function At(){return [...X]}function Rt(){X=[];}var xt=3e4,Pt=25e5,$e="video/webm",Ct=250,Mt=1e3,Ft=["video/webm;codecs=vp9","video/webm;codecs=vp8","video/webm"];function Xe(){if(typeof MediaRecorder>"u")return null;for(let e of Ft)if(MediaRecorder.isTypeSupported(e))return e;return null}function Dt(){return !y()||typeof navigator?.mediaDevices?.getDisplayMedia!="function"?false:Xe()!==null}async function be(e){let{config:t,signal:n}=e;if(!y()||typeof navigator?.mediaDevices?.getDisplayMedia!="function")throw h("recording_failed",new Error("Screen recording is not supported in this browser"));let o=Xe();if(!o)throw h("recording_failed",new Error("No supported video codec found (WebM required)"));if(n?.aborted)throw h("aborted",new Error("Recording aborted before start"));let r;try{r=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:"browser"},audio:!1});}catch(d){let k=d instanceof DOMException&&d.name==="NotAllowedError"?"Screen share permission denied":"Failed to start screen share";throw h("recording_failed",new Error(k))}if(n?.aborted)throw r.getTracks().forEach(d=>d.stop()),h("aborted",new Error("Recording aborted after permission"));let i=t?.maxDurationMs??xt,a=t?.videoBitsPerSecond??Pt,s=new MediaRecorder(r,{mimeType:o,videoBitsPerSecond:a}),c=[],u=[],w=0,m=null,l=null,b=false,f=()=>{je(),m!==null&&(clearInterval(m),m=null),l!==null&&(clearTimeout(l),l=null),r.getTracks().forEach(d=>d.stop());},p=new Promise((d,k)=>{s.ondataavailable=v=>{v.data.size>0&&c.push(v.data);},s.onstop=()=>{if(b)return;b=true,f();let v=Date.now()-w,C=new Blob(c,{type:$e}),L=URL.createObjectURL(C);d({mime:$e,blobUrl:L,blob:C,durationMs:v,sizeBytes:C.size});},s.onerror=()=>{b||(b=true,f(),k(h("recording_failed",new Error("MediaRecorder error"))));};let _=r.getVideoTracks()[0];if(_&&_.addEventListener("ended",()=>{s.state!=="inactive"&&s.stop();}),n){let v=()=>{b||(b=true,s.state!=="inactive"&&s.stop(),f(),k(h("aborted",new Error("Recording aborted"))));};n.addEventListener("abort",v,{once:true});}});return s.start(Mt),ze(),w=Date.now(),m=setInterval(()=>{let d=Date.now()-w;for(let k of u)k(d);},Ct),l=setTimeout(()=>{!b&&s.state!=="inactive"&&s.stop();},i),{result:p,stop(){!b&&s.state!=="inactive"&&s.stop();},onTick(d){u.push(d);},abort(){b||(b=true,s.state!=="inactive"&&s.stop(),f());}}}var Lt=6e4,Ze="audio/webm",It=250,Bt=["audio/webm;codecs=opus","audio/webm","audio/ogg;codecs=opus"];function We(){if(typeof MediaRecorder>"u")return null;for(let e of Bt)if(MediaRecorder.isTypeSupported(e))return e;return null}function Nt(){return !y()||typeof navigator?.mediaDevices?.getUserMedia!="function"?false:We()!==null}async function we(e){let{config:t,signal:n}=e;if(!y()||typeof navigator?.mediaDevices?.getUserMedia!="function")throw h("recording_failed",new Error("Microphone recording is not supported in this browser"));let o=We();if(!o)throw h("recording_failed",new Error("No supported audio codec found"));if(n?.aborted)throw h("aborted",new Error("Voice recording aborted before start"));let r;try{r=await navigator.mediaDevices.getUserMedia({audio:!0});}catch(p){let d=p instanceof DOMException&&p.name==="NotAllowedError"?"Microphone permission denied":"Failed to access microphone";throw h("recording_failed",new Error(d))}if(n?.aborted)throw r.getTracks().forEach(p=>p.stop()),h("aborted",new Error("Voice recording aborted after permission"));let i=t?.maxDurationMs??Lt,a=new MediaRecorder(r,{mimeType:o}),s=[],c=[],u=0,w=null,m=null,l=false,b=()=>{w!==null&&(clearInterval(w),w=null),m!==null&&(clearTimeout(m),m=null),r.getTracks().forEach(p=>p.stop());},f=new Promise((p,d)=>{if(a.ondataavailable=k=>{k.data.size>0&&s.push(k.data);},a.onstop=()=>{if(l)return;l=true,b();let k=Date.now()-u,_=new Blob(s,{type:Ze});p({blob:_,mime:Ze,durationMs:k});},a.onerror=()=>{l||(l=true,b(),d(h("recording_failed",new Error("MediaRecorder error"))));},n){let k=()=>{l||(l=true,a.state!=="inactive"&&a.stop(),b(),d(h("aborted",new Error("Voice recording aborted"))));};n.addEventListener("abort",k,{once:true});}});return a.start(1e3),u=Date.now(),w=setInterval(()=>{let p=Date.now()-u;for(let d of c)d(p);},It),m=setTimeout(()=>{!l&&a.state!=="inactive"&&a.stop();},i),{result:f,stop(){!l&&a.state!=="inactive"&&a.stop();},onTick(p){c.push(p);},abort(){l||(l=true,a.state!=="inactive"&&a.stop(),b());}}}var Ot=12e3,Ut=2,Vt=500,Ht=2e3,ye="https://blocfeed.com/api/feedback",qt="https://blocfeed.com/api/feedback/voice",Ke=0;function Qe(e,t){return new Promise((n,o)=>{if(t?.aborted){o(new Error("Aborted"));return}let r=setTimeout(n,e),i=()=>{clearTimeout(r),o(new Error("Aborted"));};t?.addEventListener("abort",i,{once:true});})}function zt(e){return e>=500&&e<=599}function jt(e){let[t,n]=e.split(",",2);if(!t||!n)throw new Error("Invalid data URL");let r=/data:(.*?);base64/.exec(t)?.[1]||"application/octet-stream",i=atob(n),a=new Uint8Array(i.length);for(let s=0;s<i.length;s+=1)a[s]=i.charCodeAt(s);return new Blob([a],{type:r})}function $t(e){let t={},n={},o={...e};if(o.screenshots){let r={},i={...o.screenshots};i.element&&(t.element=i.element.dataUrl,r.element={mime:i.element.mime,width:i.element.width,height:i.element.height},i.element={...i.element,dataUrl:""}),i.fullPage&&(t.fullPage=i.fullPage.dataUrl,r.fullPage={mime:i.fullPage.mime,width:i.fullPage.width,height:i.fullPage.height},i.fullPage={...i.fullPage,dataUrl:""}),o.screenshots=i,(r.element||r.fullPage)&&(o.screenshot_intent=r);}return o.video&&(n.blob=o.video.blob,o.video_intent={mime:o.video.mime,durationMs:o.video.durationMs,sizeBytes:o.video.sizeBytes},delete o.video),{lean:o,extracted:t,extractedVideo:n}}async function Ye(e,t,n){let o=jt(t);await fetch(e,{method:"PUT",body:o,headers:{"content-type":o.type},...n?{signal:n}:{}});}async function Xt(e){let{feedbackId:t,extracted:n,screenshots:o,signal:r}=e,i={};n.element&&o?.element&&(i.element={dataUrl:n.element,mime:o.element.mime,width:o.element.width,height:o.element.height}),n.fullPage&&o?.fullPage&&(i.fullPage={dataUrl:n.fullPage,mime:o.fullPage.mime,width:o.fullPage.width,height:o.fullPage.height}),Object.keys(i).length!==0&&await fetch(`${ye}/${t}/screenshots`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(i),...r?{signal:r}:{}});}async function Zt(e){await fetch(`${ye}/${e.feedbackId}/video`,{method:"POST",body:e.blob,headers:{"content-type":e.blob.type},...e.signal?{signal:e.signal}:{}});}async function Ge(e){let{signal:t,transport:n}=e;if(Date.now()-Ke<Ht)return {ok:false,error:h("configuration",new Error("Please wait before submitting again"))};let r=n?.timeoutMs??Ot,i=n?.maxAttempts??Ut,a=n?.backoffMs??Vt,s=!!(e.payload.screenshots?.element?.dataUrl||e.payload.screenshots?.fullPage?.dataUrl),c=!!e.payload.video?.blob,u=s||c,{lean:w,extracted:m,extractedVideo:l}=u?$t(e.payload):{lean:e.payload,extracted:{},extractedVideo:{}},b={...m,...e.screenshotDataUrls};for(let f=1;f<=i;f+=1){let p=new AbortController,d=setTimeout(()=>p.abort(),r),k=()=>p.abort();t&&t.addEventListener("abort",k,{once:true});try{let _=await fetch(ye,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(w),signal:p.signal});if(_.ok){Ke=Date.now();let v;try{v=await _.json();}catch{}if((b.element||b.fullPage)&&v){let M=v.upload_urls;if(M){let H=[];b.element&&M.element&&H.push(Ye(M.element,b.element,t)),b.fullPage&&M.fullPage&&H.push(Ye(M.fullPage,b.fullPage,t));try{await Promise.all(H);}catch{}}else if(v.feedback_id)try{await Xt({feedbackId:v.feedback_id,extracted:b,screenshots:e.payload.screenshots,...t?{signal:t}:{}});}catch{}}if(l.blob&&v){let M=v.upload_urls;if(M?.video)try{await fetch(M.video,{method:"PUT",body:l.blob,headers:{"content-type":l.blob.type},...t?{signal:t}:{}});}catch{}else if(v.feedback_id)try{await Zt({feedbackId:v.feedback_id,blob:l.blob,...t?{signal:t}:{}});}catch{}}let L={ok:!0,status:_.status};return v&&(L.apiResponse=v),L}if(f<i&&zt(_.status)){let v=.85+Math.random()*.3,C=Math.round(a*2**(f-1)*v);await Qe(C,t);continue}return {ok:!1,status:_.status,error:h("api_failed",new Error(`HTTP ${_.status}`))}}catch(_){if(p.signal.aborted||t?.aborted)return {ok:false,error:h("aborted",_)};if(f<i){let v=.85+Math.random()*.3,C=Math.round(a*2**(f-1)*v);await Qe(C,t);continue}return {ok:false,error:h("api_failed",_)}}finally{clearTimeout(d),t&&t.removeEventListener("abort",k);}}return {ok:false,error:h("api_failed",new Error("Failed"))}}async function Je(e){let{blob:t,mime:n,blocfeedId:o,signal:r}=e,i=await fetch(qt,{method:"POST",headers:{"Content-Type":n,"X-Blocfeed-Id":o},body:t,...r?{signal:r}:{}});if(!i.ok){let s=await i.json().catch(()=>({error:"Unknown error"}));throw h("api_failed",new Error(s?.error??`Voice API returned ${i.status}`))}let a=await i.json();return {text:a.text??"",warning:a.warning}}function Ee(e){let t=null,n=null,o=(...r)=>{n=r,t===null&&(t=requestAnimationFrame(()=>{if(t=null,!n)return;let i=n;n=null,e(...i);}));};return o.cancel=()=>{t!==null&&cancelAnimationFrame(t),t=null,n=null;},o}function te(e){return e instanceof Element?!!e.closest("[data-blocfeed-ui]"):false}function ne(e){e.stopPropagation(),e.stopImmediatePropagation?.();}function et(e,t){if(!y())throw new Error("BlocFeed picker can only run in a browser environment.");let n=e.ignoreSelectors,o=e.isSelectable,r={};n&&n.length>0&&(r.ignoreSelectors=n),o&&(r.isSelectable=o);let i=null,a=null,s=(f,p=false)=>{if(!f){i=null,a=null,t.onHover(null);return}let d=se(f.getBoundingClientRect()),k=`${Math.round(d.x)}:${Math.round(d.y)}:${Math.round(d.width)}:${Math.round(d.height)}`;!p&&f===i&&k===a||(i=f,a=k,t.onHover({element:f,rect:d}));},c=Ee(f=>{if(te(f.target))return;let p=document.elementFromPoint(f.clientX,f.clientY),d=de(p,r);s(d);}),u=Ee(()=>{i&&s(i,true);}),w=f=>{te(f.target)||(ne(f),f.pointerType==="mouse"&&f.preventDefault());},m=f=>{te(f.target)||(ne(f),f.pointerType==="mouse"&&f.preventDefault());},l=f=>{if(te(f.target))return;ne(f),f.preventDefault();let p=document.elementFromPoint(f.clientX,f.clientY),d=de(p,r);d&&t.onSelect({element:d,descriptor:Me(d)});},b=f=>{f.key==="Escape"&&(ne(f),f.preventDefault(),t.onCancel());};return window.addEventListener("pointermove",c,{capture:true,passive:true}),window.addEventListener("pointerdown",w,{capture:true}),window.addEventListener("pointerup",m,{capture:true}),window.addEventListener("click",l,{capture:true}),window.addEventListener("keydown",b,{capture:true}),window.addEventListener("scroll",u,{capture:true,passive:true}),window.addEventListener("resize",u,{passive:true}),{stop(){window.removeEventListener("pointermove",c,{capture:true}),window.removeEventListener("pointerdown",w,{capture:true}),window.removeEventListener("pointerup",m,{capture:true}),window.removeEventListener("click",l,{capture:true}),window.removeEventListener("keydown",b,{capture:true}),window.removeEventListener("scroll",u,{capture:true}),window.removeEventListener("resize",u),c.cancel(),u.cancel(),t.onHover(null);}}}async function ve(e){let{signal:t,transport:n}=e,o={ok:false};try{let r={payload:e.payload,...t?{signal:t}:{},...n?{transport:n}:{}};o.api=await Ge(r),o.ok=!!o.api?.ok;}catch(r){o.api={ok:false,error:h("api_failed",r)},o.ok=false;}return {payload:e.payload,result:o}}var Wt=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function Kt(e){let t=[...Wt,...e?.ignoreSelectors??[]],n=Array.from(new Set(t));return {...e,ignoreSelectors:n}}function tt(){return {phase:"idle"}}function Qt(e){if(e.ok)return false;let t=e.api;return t?t.status&&t.status>=400&&t.status<500||t.error?.kind==="aborted"||t.error?.kind==="configuration"?false:!t.ok:true}function no(e){let t=e,n=tt(),o=new Set,r=new Set,i=null,a=null,s=null,c=null,u=0,w=null,m=null,l=null,b=null,f=()=>{for(let g of o)g(n);},p=g=>{for(let E of r)E(g);},d=g=>{n=g,f();},k=()=>{u+=1,c?.abort(),c=null;},_=()=>{i?.stop(),i=null,p(null),s!==null&&y()&&(document.documentElement.style.cursor=s,s=null);},v=()=>{m&&(m.abort(),m=null),l&&(URL.revokeObjectURL(l.blobUrl),l=null);},C=()=>{b&&(b.abort(),b=null);},L=()=>{k(),_(),v(),C(),a=null,d(tt());},M=()=>{if(!y())return;_(),a=null;let g=Kt(t.picker);s=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",d({phase:"picking"}),i=et(g,{onHover:p,onSelect:({element:E,descriptor:x})=>{a=E,_(),d({phase:"review",selection:x});},onCancel:()=>{L();}});},H=()=>{let g=Ve();if(g.length!==0)for(let E of g)ve({payload:E,...t.transport?{transport:t.transport}:{}}).catch(()=>{he(E);});};if(y()){setTimeout(H,1e3);let g=()=>H();window.addEventListener("online",g),w=()=>window.removeEventListener("online",g);}return {getState:()=>n,subscribe(g){return o.add(g),()=>o.delete(g)},subscribeHover(g){return r.add(g),()=>r.delete(g)},start(){n.phase==="capturing"||n.phase==="submitting"||n.phase==="recording"||n.phase!=="picking"&&M();},stop(){L();},clearSelection(){n.phase==="capturing"||n.phase==="submitting"||n.phase==="recording"||M();},setConfig(g){t=g;},async submit(g,E){if(!y()){let T=h("configuration",new Error("BlocFeed submit can only run in the browser"));return d({phase:"error",lastError:T}),{ok:false}}let x=t.blocfeed_id?.trim?.()??"";if(!x){let F={phase:"error",lastError:h("configuration",new Error("Missing blocfeed_id. Create a project in BlocFeed and pass its blocfeed_id."))};return n.selection&&(F.selection=n.selection),d(F),{ok:false}}if(n.phase==="capturing"||n.phase==="submitting"||n.phase==="recording")return {ok:false};let A=u+1;u=A,c?.abort(),c=new AbortController;let R=c.signal,S=n.selection,D=E?.capture?{...t.capture,...E.capture}:t.capture,I=!!(D?.element||D?.fullPage),z={phase:I?"capturing":"submitting"};S&&(z.selection=S),d(z);try{let T=I?await Oe({selectionElement:a,capture:D,signal:R}):void 0;if(R.aborted||u!==A)return {ok:!1};let F={phase:"submitting"};S&&(F.selection=S),T&&(F.capture=T),d(F);let B={};S&&(B.selection=S),T&&(B.capture=T);let j=await Ue({config:t.metadata,context:B,...t.user?{user:t.user}:{}}),O={version:1,createdAt:new Date().toISOString(),blocfeed_id:x,message:g,metadata:j};E?.category&&(O.category=E.category),t.user&&(O.user=t.user),S&&(O.selection=S),T&&(O.screenshots=T),l&&(O.video=l);let{result:N}=await ve({payload:O,signal:R,...t.transport?{transport:t.transport}:{}});if(R.aborted||u!==A)return N;if(N.ok){l&&(URL.revokeObjectURL(l.blobUrl),l=null);let ae={phase:"success",lastSubmit:N};return S&&(ae.selection=S),T&&(ae.capture=T),d(ae),N}Qt(N)&&he(O);let rt=N.api?.error??h("unknown",new Error("Submission failed")),ie={phase:"error",lastSubmit:N,lastError:rt};return S&&(ie.selection=S),T&&(ie.capture=T),d(ie),N}catch(T){if(R.aborted||u!==A)return {ok:false};let B={phase:"error",lastError:R.aborted?h("aborted",T):h("unknown",T)};return S&&(B.selection=S),d(B),{ok:false}}finally{u===A&&(c=null);}},async startRecording(){if(n.phase!=="review"||!t.recording?.enabled)return;let g=n.selection,E={phase:"recording",recordingElapsedMs:0};g&&(E.selection=g),d(E);try{let x={config:t.recording};c&&(x.signal=c.signal);let A=await be(x);m=A,A.onTick(D=>{if(n.phase==="recording"){let I={phase:"recording",recordingElapsedMs:D};g&&(I.selection=g),d(I);}});let R=await A.result;m=null,l=R;let S={phase:"review",video:R};g&&(S.selection=g),d(S);}catch(x){m=null;let R={phase:"review",lastError:x?.kind?x:h("recording_failed",x)};g&&(R.selection=g),d(R);}},stopRecording(){n.phase!=="recording"||!m||m.stop();},removeVideo(){l&&(URL.revokeObjectURL(l.blobUrl),l=null);let g=n.selection,E={phase:"review"};g&&(E.selection=g),d(E);},async startVoice(){if(n.phase!=="review")return;let g=t.voice;if(!g?.enabled)return;C();let E=n.selection,x={phase:"review",voiceRecording:true,voiceElapsedMs:0};E&&(x.selection=E),l&&(x.video=l),d(x);try{let A={config:g};c&&(A.signal=c.signal);let R=await we(A);b=R,R.onTick(B=>{let j={phase:"review",voiceRecording:!0,voiceElapsedMs:B};E&&(j.selection=E),l&&(j.video=l),d(j);});let S=await R.result;b=null;let D={phase:"review",voiceRecording:!1,voiceTranscribing:!0};E&&(D.selection=E),l&&(D.video=l),d(D);let I={blob:S.blob,mime:S.mime,blocfeedId:t.blocfeed_id};c&&(I.signal=c.signal);let{text:Te,warning:z}=await Je(I),T={phase:"review",voiceRecording:!1,voiceTranscribing:!1};E&&(T.selection=E),l&&(T.video=l),d(T);let F={text:Te};return z&&(F.warning=z),F}catch(A){C();let S={phase:"review",voiceRecording:false,voiceTranscribing:false,lastError:A?.kind?A:h("recording_failed",A)};E&&(S.selection=E),l&&(S.video=l),d(S);return}},stopVoice(){b&&b.stop();},__unsafeGetSelectedElement(){return a},destroy(){L(),o.clear(),r.clear(),w?.(),w=null;}}}var Z=[],W=[],nt=20,ot=15,oe=[],K={},Q=null,Y=null,G=null,re=false,Yt=["blocfeed.com","google-analytics.com","googletagmanager.com","analytics.google.com","googleads.g.doubleclick.net","googlesyndication.com","googleadservices.com","google.com/pagead","google.com/ads","facebook.net","facebook.com/tr","connect.facebook.net","graph.facebook.com","api.mixpanel.com","api-js.mixpanel.com","api.amplitude.com","api2.amplitude.com","api.segment.io","cdn.segment.com","vars.hotjar.com","in.hotjar.com","script.hotjar.com","heapanalytics.com","fullstory.com/s/fs.js","rs.fullstory.com","app.posthog.com","us.posthog.com","eu.posthog.com","api-iam.intercom.io","widget.intercom.io","sentry.io/api","browser.sentry-cdn.com","browser-intake-datadoghq.com","clarity.ms","js.hs-analytics.net","api.hubapi.com","forms.hubspot.com","plausible.io/api","client.crisp.chat","js.driftt.com","r.logrocket.com","app.pendo.io","events.launchdarkly.com","app.launchdarkly.com","grammarly.com","gnar.grammarly.com","capi.grammarly.com","api.languagetool.org","api.languagetoolplus.com","fonts.googleapis.com","fonts.gstatic.com","cdn.cookielaw.org","consent.cookiebot.com","js.stripe.com","api.stripe.com/v1/tokens","google.com/recaptcha","hcaptcha.com","bam.nr-data.net","rec.smartlook.com","o2.mouseflow.com","api.luckyorange.com","static.zdassets.com","ekr.zdassets.com"];function Gt(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}function Jt(e,t){let n=t.map(Gt).join(" "),o=t.find(i=>i instanceof Error),r={level:e,message:n.slice(0,2e3),timestamp:Date.now()};for(o?.stack&&(r.stack=o.stack.slice(0,2e3)),Z.push(r);Z.length>nt;)Z.shift();}function Se(e){let t=e.url.toLowerCase();for(let n of oe)if(t.includes(n))return;for(W.push(e);W.length>ot;)W.shift();}function io(e={}){if(re||!y())return;re=true,nt=e.consoleLimit??20,ot=e.networkLimit??15;let t=e.ignoreUrls;if(t!==void 0?oe=t.map(n=>n.toLowerCase()):oe=[...Yt],e.console!==false){let n=e.consoleLevels??["error","warn"];for(let o of n)K[o]=console[o],console[o]=(...r)=>{Jt(o,r),K[o]?.apply(console,r);};}if(e.network!==false&&typeof window.fetch=="function"){Q=window.fetch;let n=Q;window.fetch=async function(r,i){let a=typeof r=="string"?r:r instanceof URL?r.toString():r.url,s=(i?.method??"GET").toUpperCase(),c=Date.now();try{let u=await n.call(window,r,i);return u.ok||Se({url:a.slice(0,500),method:s,status:u.status,statusText:u.statusText,timestamp:c,durationMs:Date.now()-c}),u}catch(u){throw Se({url:a.slice(0,500),method:s,status:0,statusText:u instanceof Error?u.message:"Network error",timestamp:c,durationMs:Date.now()-c}),u}};}if(e.network!==false&&typeof XMLHttpRequest<"u"){Y=XMLHttpRequest.prototype.open,G=XMLHttpRequest.prototype.send;let n=Y,o=G;XMLHttpRequest.prototype.open=function(r,i,...a){return this.__bf_method=r.toUpperCase(),this.__bf_url=String(i),n.apply(this,[r,i,...a])},XMLHttpRequest.prototype.send=function(...r){let i=this.__bf_method||"GET",a=this.__bf_url||"",s=Date.now();return this.addEventListener("loadend",function(){if(this.status>=400||this.status===0){let c={url:a.slice(0,500),method:i,status:this.status,timestamp:s,durationMs:Date.now()-s};this.statusText&&(c.statusText=this.statusText),Se(c);}}),o.apply(this,r)};}}function ao(){if(re){for(let[e,t]of Object.entries(K))console[e]=t;for(let e of Object.keys(K))delete K[e];Q&&(window.fetch=Q,Q=null),Y&&(XMLHttpRequest.prototype.open=Y,Y=null),G&&(XMLHttpRequest.prototype.send=G,G=null),oe=[],re=false;}}function so(){return {consoleLogs:[...Z],networkErrors:[...W]}}function co(){Z=[],W=[];}var en=[{name:"supabase_service_role_key",pattern:/SUPABASE_SERVICE_ROLE_KEY["'=:\s]+[A-Za-z0-9_.=-]{30,}/},{name:"supabase_db_password",pattern:/SUPABASE_DB_PASSWORD["'=:\s]+[^\s"']{6,}/},{name:"postgres_password",pattern:/POSTGRES_PASSWORD["'=:\s]+[^\s"']{6,}/},{name:"database_url_with_creds",pattern:/(?:postgres|postgresql|mysql|mongodb|redis|amqp):\/\/[^:]+:[^@\s"']+@/},{name:"aws_access_key",pattern:/AKIA[0-9A-Z]{16}/},{name:"aws_secret_key",pattern:/AWS_SECRET_ACCESS_KEY["'=:\s]+[A-Za-z0-9/+=]{30,}/},{name:"aws_session_token",pattern:/AWS_SESSION_TOKEN["'=:\s]+[A-Za-z0-9/+=]{30,}/},{name:"stripe_secret_key",pattern:/sk_live_[a-zA-Z0-9]{10,}/},{name:"stripe_secret_key_test",pattern:/sk_test_[a-zA-Z0-9]{10,}/},{name:"stripe_restricted_key",pattern:/rk_(?:live|test)_[a-zA-Z0-9]{10,}/},{name:"github_pat",pattern:/ghp_[A-Za-z0-9_]{36,}/},{name:"github_oauth",pattern:/gho_[A-Za-z0-9_]{36,}/},{name:"github_user_token",pattern:/ghu_[A-Za-z0-9_]{36,}/},{name:"github_server_token",pattern:/ghs_[A-Za-z0-9_]{36,}/},{name:"github_fine_grained",pattern:/github_pat_[A-Za-z0-9_]{22,}/},{name:"openai_api_key",pattern:/sk-[a-zA-Z0-9]{20,}(?![a-zA-Z0-9_])/},{name:"anthropic_api_key",pattern:/sk-ant-[a-zA-Z0-9\-_]{20,}/},{name:"private_key",pattern:/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/},{name:"sendgrid_api_key",pattern:/SG\.[a-zA-Z0-9_-]{22,}\.[a-zA-Z0-9_-]{22,}/},{name:"twilio_auth_token",pattern:/TWILIO_AUTH_TOKEN["'=:\s]+[a-f0-9]{32}/},{name:"generic_secret_key",pattern:/[A-Z_]{2,}_SECRET_KEY["'=:\s]+[^\s"']{8,}/},{name:"generic_secret",pattern:/[A-Z_]{2,}_SECRET["'=:\s]+[^\s"']{8,}/},{name:"generic_password",pattern:/[A-Z_]{2,}_PASSWORD["'=:\s]+[^\s"']{6,}/},{name:"generic_private_key_var",pattern:/[A-Z_]{2,}_PRIVATE_KEY["'=:\s]+[^\s"']{8,}/}],V=[],_e=0,ke=false;function tn(e){let t=e.slice(0,80);return t.length<=6?"***":t.slice(0,6)+"***"}function nn(e,t,n,o){if(V.some(a=>a.rule===e&&a.source===t))return;let i={rule:e,source:t,hint:tn(n),timestamp:Date.now()};o&&(i.location=o),V.push(i);}function J(e,t,n,o){for(let{name:r,pattern:i}of n){let a=i.exec(e);a&&nn(r,t,a[0],o);}}function on(e){try{let t=window;if(t.__NEXT_DATA__){let n=JSON.stringify(t.__NEXT_DATA__);J(n,"hydration",e,"__NEXT_DATA__");}if(t.__NUXT__){let n=JSON.stringify(t.__NUXT__);J(n,"hydration",e,"__NUXT__");}}catch{}}function rn(e){try{document.querySelectorAll("script:not([src])").forEach((n,o)=>{let r=n.textContent;r&&r.length>0&&J(r,"scripts",e,`inline-script[${o}]`);});}catch{}}function an(e){try{document.querySelectorAll("meta[content]").forEach(n=>{let o=n.getAttribute("content"),r=n.getAttribute("name")||n.getAttribute("property")||"";o&&o.length>10&&J(o,"meta",e,r?`meta[${r}]`:void 0);});}catch{}}function sn(e){try{document.querySelectorAll("[data-api-key], [data-secret], [data-token], [data-password]").forEach(n=>{let o=n.attributes;for(let r=0;r<o.length;r++){let i=o[r];i.name.startsWith("data-")&&i.value.length>10&&J(i.value,"dom",e,`${n.tagName.toLowerCase()}[${i.name}]`);}});}catch{}}function fo(e={}){if(ke||!y()||(ke=true,e.secretScan===false))return;let t=[...en,...e.customPatterns??[]],n=e.scanTargets??["hydration","scripts","meta","dom"];setTimeout(()=>{_e=Date.now(),n.includes("hydration")&&on(t),n.includes("scripts")&&rn(t),n.includes("meta")&&an(t),n.includes("dom")&&sn(t);let o=e.notify??"both";V.length>0&&(o==="console"||o==="both")&&console.warn(`[BlocFeed Security] Found ${V.length} potential secret(s) exposed in client code:`,V.map(r=>`${r.rule} (${r.source}${r.location?`: ${r.location}`:""})`));},100);}function mo(){return {findings:[...V],scannedAt:_e}}function po(){V=[],_e=0,ke=false;}
2
+ exports.a=y;exports.b=se;exports.c=Le;exports.d=Oe;exports.e=Ue;exports.f=he;exports.g=Ve;exports.h=En;exports.i=vn;exports.j=At;exports.k=Rt;exports.l=Dt;exports.m=be;exports.n=Nt;exports.o=we;exports.p=Je;exports.q=no;exports.r=io;exports.s=ao;exports.t=so;exports.u=co;exports.v=fo;exports.w=mo;exports.x=po;
@@ -0,0 +1,2 @@
1
+ function y(){return typeof window<"u"&&typeof document<"u"}function Ae(e){let t=globalThis.CSS;return typeof t?.escape=="function"?t.escape(e):e.replace(/[^a-zA-Z0-9_-]/g,n=>{let o=n.codePointAt(0);return o===void 0?"":`\\${o.toString(16)} `})}function se(e){return {x:e.x,y:e.y,width:e.width,height:e.height}}function it(e){return {x:e.x+window.scrollX,y:e.y+window.scrollY,width:e.width,height:e.height}}function at(e){return e.replace(/\s+/g," ").trim()}function ce(e,t=140){let n=e.textContent;if(!n)return;let o=at(n);if(o)return o.length<=t?o:`${o.slice(0,t-1)}\u2026`}function st(e){let t=1;for(let n=e.previousElementSibling;n;n=n.previousElementSibling)n.tagName===e.tagName&&(t+=1);return t}var Ce=["data-testid","data-test-id","data-test","data-qa","data-cy"],Re="data-blocfeed-component";function le(e){let t=e.closest(`[${Re}]`);if(!t)return;let o=t.getAttribute(Re)?.trim();return o||void 0}function ct(e){for(let t of Ce){let n=e.closest(`[${t}]`);if(!n)continue;let r=n.getAttribute(t)?.trim();if(r)return r}}function xe(e){try{let t=e,n=Object.getOwnPropertyNames(t);for(let o of n)if(o.startsWith("__reactFiber$")||o.startsWith("__reactInternalInstance$")){let r=t[o];if(r&&typeof r=="object")return r}}catch{}return null}function U(e){if(e.length<=1||e.length===2&&e[0]===e[0].toLowerCase())return true;let t=e[0];return t>="a"&&t<="z"}function $(e){if(e){for(let t of e)if(typeof t.name=="string"&&t.name&&!U(t.name))return t.name}}function P(e){if(e&&typeof e!="string"){if(typeof e=="function"){let t=e;return typeof t.displayName=="string"&&t.displayName?t.displayName:typeof t.name=="string"&&t.name?t.name:void 0}if(typeof e=="object"){let t=e,n=t.displayName;if(typeof n=="string"&&n)return n;let o=t.render;if(typeof o=="function"){let i=o;if(typeof i.displayName=="string"&&i.displayName)return i.displayName;if(typeof i.name=="string"&&i.name)return i.name}let r=t.type;return P(r)}}}function ue(e){let t=xe(e);if(!t)return;let n=$(t._debugInfo);if(n)return n;let o=t._debugOwner!==void 0;if(o){let c=t._debugOwner;for(let u=0;c&&u<50;u+=1){let w=$(c._debugInfo);if(w)return w;let m=P(c.type)??P(c.elementType);if(m&&!U(m))return m;c=c._debugOwner;}}if(t._owner!==void 0&&t._owner!==t._debugOwner){let c=t._owner;for(let u=0;c&&u<50;u+=1){let w=$(c._debugInfo);if(w)return w;let m=P(c.type)??P(c.elementType);if(m&&!U(m))return m;c=c._owner;}}let i=t,a=o?80:25;for(let c=0;i&&c<a;c+=1){let u=$(i._debugInfo);if(u)return u;let w=P(i.type)??P(i.elementType);if(w&&!U(w))return w;i=i.return;}let s=e.parentElement;for(let c=0;s&&c<15;c+=1){let u=xe(s);if(u){let w=$(u._debugInfo);if(w)return w;let m=P(u.type)??P(u.elementType);if(m&&!U(m))return m;if(u._debugOwner){let l=P(u._debugOwner.type)??P(u._debugOwner.elementType);if(l&&!U(l))return l}if(u._owner&&u._owner!==u._debugOwner){let l=P(u._owner.type)??P(u._owner.elementType);if(l&&!U(l))return l}}s=s.parentElement;}}function lt(e){let t=e.tagName.toLowerCase(),n=e.getAttribute("id");if(n)return `#${Ae(n)}`;for(let o of Ce){let r=e.getAttribute(o);if(r)return `${t}[${o}="${Ae(r)}"]`}return `${t}:nth-of-type(${st(e)})`}function ut(e,t=10){let n=[],o=e;for(;o&&n.length<t;){let r=lt(o);if(n.unshift(r),r.startsWith("#"))break;o=o.parentElement;}return n.join(" > ")}function Pe(e,t){if(!t||t.length===0)return false;for(let n of t)if(e.closest(n))return true;return false}function de(e,t){if(!e||Pe(e,t.ignoreSelectors))return null;let n=e;for(;n;){if(Pe(n,t.ignoreSelectors))return null;if(t.isSelectable?.(n)??dt(n))return n;n=n.parentElement;}return null}function dt(e){let t=e.tagName;return !(t==="HTML"||t==="BODY")}function Me(e){let t=e.getBoundingClientRect(),n={selector:ut(e),tagName:e.tagName.toLowerCase(),rect:se(t),pageRect:it(t)},o=e.getAttribute("id");o&&(n.id=o);let r=e.className;typeof r=="string"&&r.trim()&&(n.className=r);let i=ce(e);i&&(n.textSnippet=i);let a=le(e)??ue(e);a&&(n.componentName=a);let s=ct(e);return s&&(n.testId=s),n}var fe=null;async function Fe(){return fe||(fe=import('html-to-image')),fe}async function ft(e){return await new Promise((t,n)=>{let o=new Image;o.onload=()=>t({width:o.naturalWidth,height:o.naturalHeight}),o.onerror=()=>n(new Error("Failed to load generated screenshot")),o.src=e;})}async function De(e,t){let{width:n,height:o}=await ft(e);return {dataUrl:e,mime:t,width:n,height:o}}function q(e){if(e?.aborted)throw new Error("Aborted")}function Le(){return {async captureElement(e,t){if(!y())throw new Error("captureElement can only run in the browser");q(t.signal);let n=await Fe();q(t.signal);let o=t.mime==="image/jpeg"?await n.toJpeg(e,{quality:t.quality??.92,pixelRatio:t.pixelRatio}):await n.toPng(e,{pixelRatio:t.pixelRatio});return q(t.signal),await De(o,t.mime)},async captureFullPage(e){if(!y())throw new Error("captureFullPage can only run in the browser");q(e.signal);let t=document.documentElement,n=Math.max(t.scrollWidth,t.clientWidth),o=Math.max(t.scrollHeight,t.clientHeight),r=Math.min(1,e.maxDimension/Math.max(n,o)),i=Math.max(1,Math.round(n*r)),a=Math.max(1,Math.round(o*r)),s=await Fe();q(e.signal);let c=e.mime==="image/jpeg"?await s.toJpeg(t,{width:i,height:a,quality:e.quality??.92,pixelRatio:e.pixelRatio}):await s.toPng(t,{width:i,height:a,pixelRatio:e.pixelRatio});return q(e.signal),await De(c,e.mime)}}}function mt(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return "Unknown error"}}function h(e,t,n){let o={kind:e,message:mt(t)};return n&&(o.detail=n),o}var pt=12e3,gt=2048,ht=.92;function Ie(){return Date.now()}function bt(e){return new Promise((t,n)=>{let o=()=>n(new Error("Aborted"));if(e.aborted){o();return}e.addEventListener("abort",o,{once:true});})}async function Be(e,t,n){let o=new Promise((i,a)=>{let s=setTimeout(()=>a(new Error("Timeout")),t);typeof s.unref=="function"&&s.unref();}),r=[e,o];return n&&r.push(bt(n)),await Promise.race(r)}function wt(e){if(!y())return 1;let t=window.devicePixelRatio||1,n=e?.pixelRatio??Math.min(t,2);return Math.max(.1,n)}function yt(e){return !!(e?.element||e?.fullPage)}function Ne(e){let t={mime:e.mime,pixelRatio:e.pixelRatio,maxDimension:e.maxDimension};return e.includeQuality&&(t.quality=e.quality),e.signal&&(t.signal=e.signal),t}async function Oe(e){let{selectionElement:t,capture:n,signal:o}=e;if(!y()||!yt(n))return;let r=Ie(),i=[],a=n?.timeoutMs??pt,s=n?.maxDimension??gt,c=n?.mime??"image/png",u=n?.quality??ht,w=n?.adapter??Le(),m={},l=wt(n);if(n?.element&&t)try{let p=t.getBoundingClientRect(),d=Math.min(1,s/Math.max(p.width,p.height)),k=Math.min(l,l*d),_=await Be(Promise.resolve(w.captureElement(t,{...Ne({mime:c,quality:u,pixelRatio:k,maxDimension:s,includeQuality:c==="image/jpeg",...o?{signal:o}:{}})})),a,o);m.element=_;}catch(p){if(o?.aborted)throw p;i.push(h("capture_failed",p,{target:"element"}));}if(n?.fullPage)try{let p=await Be(Promise.resolve(w.captureFullPage(Ne({mime:c,quality:u,pixelRatio:l,maxDimension:s,includeQuality:c==="image/jpeg",...o?{signal:o}:{}}))),a,o);m.fullPage=p;}catch(p){if(o?.aborted)throw p;i.push(h("capture_failed",p,{target:"fullPage"}));}let b=Ie(),f={startedAt:r,finishedAt:b,durationMs:Math.max(0,b-r)};return i.length>0&&(f.errors=i),{...m,diagnostics:f}}function Et(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return}}function vt(){return y()?{url:window.location.href,title:document.title,referrer:document.referrer||void 0,userAgent:navigator.userAgent,language:navigator.language,platform:navigator.platform,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:window.screen.width,height:window.screen.height},scroll:{x:window.scrollX,y:window.scrollY},devicePixelRatio:window.devicePixelRatio||1,timezone:Et()}:{}}function St(e){if(!e)return {};let t={};return e.id&&(t.userId=e.id),e.email&&(t.userEmail=e.email),e.name&&(t.userName=e.name),t}async function Ue(e){let{config:t,context:n,user:o}=e;if(t?.enabled===false)return {};let r={...vt(),...St(o)},i=t?.enrich;if(!i)return r;try{let a=await i(n);return {...r,...a}}catch(a){let s=h("unknown",a);return {...r,blocfeedMetadataError:s.message}}}var me="blocfeed-queue",kt=50;function pe(){if(!y())return [];try{let e=localStorage.getItem(me);if(!e)return [];let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return []}}function ge(e){if(y())try{e.length===0?localStorage.removeItem(me):localStorage.setItem(me,JSON.stringify(e));}catch{}}function _t(e){let t={...e};if(t.screenshots){let n={...t.screenshots};n.element&&(n.element={...n.element,dataUrl:""}),n.fullPage&&(n.fullPage={...n.fullPage,dataUrl:""}),t.screenshots=n;}return delete t.video,t}function he(e){let t=pe(),n=_t(e);for(n.metadata={...n.metadata,_queued:true},t.push({payload:n,timestamp:Date.now()});t.length>kt;)t.shift();ge(t);}function Ve(){let e=pe();return e.length===0?[]:(ge([]),e.map(t=>t.payload))}function En(){ge([]);}function vn(){return pe().length}var Tt=200,X=[],He=0,ee=false;function qe(e){let t=e.target;if(!(t instanceof Element)||t.closest("[data-blocfeed-ui]"))return;let n={timestampMs:Date.now()-He,path:window.location.pathname,tagName:t.tagName.toLowerCase()},o=ce(t,100);o&&(n.textSnippet=o);let r=le(t)??ue(t);r&&(n.componentName=r),X.length<Tt&&X.push(n);}function ze(){ee||!y()||(ee=true,X=[],He=Date.now(),document.addEventListener("click",qe,{capture:true,passive:true}));}function je(){ee&&(ee=false,document.removeEventListener("click",qe,{capture:true}));}function At(){return [...X]}function Rt(){X=[];}var xt=3e4,Pt=25e5,$e="video/webm",Ct=250,Mt=1e3,Ft=["video/webm;codecs=vp9","video/webm;codecs=vp8","video/webm"];function Xe(){if(typeof MediaRecorder>"u")return null;for(let e of Ft)if(MediaRecorder.isTypeSupported(e))return e;return null}function Dt(){return !y()||typeof navigator?.mediaDevices?.getDisplayMedia!="function"?false:Xe()!==null}async function be(e){let{config:t,signal:n}=e;if(!y()||typeof navigator?.mediaDevices?.getDisplayMedia!="function")throw h("recording_failed",new Error("Screen recording is not supported in this browser"));let o=Xe();if(!o)throw h("recording_failed",new Error("No supported video codec found (WebM required)"));if(n?.aborted)throw h("aborted",new Error("Recording aborted before start"));let r;try{r=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:"browser"},audio:!1});}catch(d){let k=d instanceof DOMException&&d.name==="NotAllowedError"?"Screen share permission denied":"Failed to start screen share";throw h("recording_failed",new Error(k))}if(n?.aborted)throw r.getTracks().forEach(d=>d.stop()),h("aborted",new Error("Recording aborted after permission"));let i=t?.maxDurationMs??xt,a=t?.videoBitsPerSecond??Pt,s=new MediaRecorder(r,{mimeType:o,videoBitsPerSecond:a}),c=[],u=[],w=0,m=null,l=null,b=false,f=()=>{je(),m!==null&&(clearInterval(m),m=null),l!==null&&(clearTimeout(l),l=null),r.getTracks().forEach(d=>d.stop());},p=new Promise((d,k)=>{s.ondataavailable=v=>{v.data.size>0&&c.push(v.data);},s.onstop=()=>{if(b)return;b=true,f();let v=Date.now()-w,C=new Blob(c,{type:$e}),L=URL.createObjectURL(C);d({mime:$e,blobUrl:L,blob:C,durationMs:v,sizeBytes:C.size});},s.onerror=()=>{b||(b=true,f(),k(h("recording_failed",new Error("MediaRecorder error"))));};let _=r.getVideoTracks()[0];if(_&&_.addEventListener("ended",()=>{s.state!=="inactive"&&s.stop();}),n){let v=()=>{b||(b=true,s.state!=="inactive"&&s.stop(),f(),k(h("aborted",new Error("Recording aborted"))));};n.addEventListener("abort",v,{once:true});}});return s.start(Mt),ze(),w=Date.now(),m=setInterval(()=>{let d=Date.now()-w;for(let k of u)k(d);},Ct),l=setTimeout(()=>{!b&&s.state!=="inactive"&&s.stop();},i),{result:p,stop(){!b&&s.state!=="inactive"&&s.stop();},onTick(d){u.push(d);},abort(){b||(b=true,s.state!=="inactive"&&s.stop(),f());}}}var Lt=6e4,Ze="audio/webm",It=250,Bt=["audio/webm;codecs=opus","audio/webm","audio/ogg;codecs=opus"];function We(){if(typeof MediaRecorder>"u")return null;for(let e of Bt)if(MediaRecorder.isTypeSupported(e))return e;return null}function Nt(){return !y()||typeof navigator?.mediaDevices?.getUserMedia!="function"?false:We()!==null}async function we(e){let{config:t,signal:n}=e;if(!y()||typeof navigator?.mediaDevices?.getUserMedia!="function")throw h("recording_failed",new Error("Microphone recording is not supported in this browser"));let o=We();if(!o)throw h("recording_failed",new Error("No supported audio codec found"));if(n?.aborted)throw h("aborted",new Error("Voice recording aborted before start"));let r;try{r=await navigator.mediaDevices.getUserMedia({audio:!0});}catch(p){let d=p instanceof DOMException&&p.name==="NotAllowedError"?"Microphone permission denied":"Failed to access microphone";throw h("recording_failed",new Error(d))}if(n?.aborted)throw r.getTracks().forEach(p=>p.stop()),h("aborted",new Error("Voice recording aborted after permission"));let i=t?.maxDurationMs??Lt,a=new MediaRecorder(r,{mimeType:o}),s=[],c=[],u=0,w=null,m=null,l=false,b=()=>{w!==null&&(clearInterval(w),w=null),m!==null&&(clearTimeout(m),m=null),r.getTracks().forEach(p=>p.stop());},f=new Promise((p,d)=>{if(a.ondataavailable=k=>{k.data.size>0&&s.push(k.data);},a.onstop=()=>{if(l)return;l=true,b();let k=Date.now()-u,_=new Blob(s,{type:Ze});p({blob:_,mime:Ze,durationMs:k});},a.onerror=()=>{l||(l=true,b(),d(h("recording_failed",new Error("MediaRecorder error"))));},n){let k=()=>{l||(l=true,a.state!=="inactive"&&a.stop(),b(),d(h("aborted",new Error("Voice recording aborted"))));};n.addEventListener("abort",k,{once:true});}});return a.start(1e3),u=Date.now(),w=setInterval(()=>{let p=Date.now()-u;for(let d of c)d(p);},It),m=setTimeout(()=>{!l&&a.state!=="inactive"&&a.stop();},i),{result:f,stop(){!l&&a.state!=="inactive"&&a.stop();},onTick(p){c.push(p);},abort(){l||(l=true,a.state!=="inactive"&&a.stop(),b());}}}var Ot=12e3,Ut=2,Vt=500,Ht=2e3,ye="https://blocfeed.com/api/feedback",qt="https://blocfeed.com/api/feedback/voice",Ke=0;function Qe(e,t){return new Promise((n,o)=>{if(t?.aborted){o(new Error("Aborted"));return}let r=setTimeout(n,e),i=()=>{clearTimeout(r),o(new Error("Aborted"));};t?.addEventListener("abort",i,{once:true});})}function zt(e){return e>=500&&e<=599}function jt(e){let[t,n]=e.split(",",2);if(!t||!n)throw new Error("Invalid data URL");let r=/data:(.*?);base64/.exec(t)?.[1]||"application/octet-stream",i=atob(n),a=new Uint8Array(i.length);for(let s=0;s<i.length;s+=1)a[s]=i.charCodeAt(s);return new Blob([a],{type:r})}function $t(e){let t={},n={},o={...e};if(o.screenshots){let r={},i={...o.screenshots};i.element&&(t.element=i.element.dataUrl,r.element={mime:i.element.mime,width:i.element.width,height:i.element.height},i.element={...i.element,dataUrl:""}),i.fullPage&&(t.fullPage=i.fullPage.dataUrl,r.fullPage={mime:i.fullPage.mime,width:i.fullPage.width,height:i.fullPage.height},i.fullPage={...i.fullPage,dataUrl:""}),o.screenshots=i,(r.element||r.fullPage)&&(o.screenshot_intent=r);}return o.video&&(n.blob=o.video.blob,o.video_intent={mime:o.video.mime,durationMs:o.video.durationMs,sizeBytes:o.video.sizeBytes},delete o.video),{lean:o,extracted:t,extractedVideo:n}}async function Ye(e,t,n){let o=jt(t);await fetch(e,{method:"PUT",body:o,headers:{"content-type":o.type},...n?{signal:n}:{}});}async function Xt(e){let{feedbackId:t,extracted:n,screenshots:o,signal:r}=e,i={};n.element&&o?.element&&(i.element={dataUrl:n.element,mime:o.element.mime,width:o.element.width,height:o.element.height}),n.fullPage&&o?.fullPage&&(i.fullPage={dataUrl:n.fullPage,mime:o.fullPage.mime,width:o.fullPage.width,height:o.fullPage.height}),Object.keys(i).length!==0&&await fetch(`${ye}/${t}/screenshots`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(i),...r?{signal:r}:{}});}async function Zt(e){await fetch(`${ye}/${e.feedbackId}/video`,{method:"POST",body:e.blob,headers:{"content-type":e.blob.type},...e.signal?{signal:e.signal}:{}});}async function Ge(e){let{signal:t,transport:n}=e;if(Date.now()-Ke<Ht)return {ok:false,error:h("configuration",new Error("Please wait before submitting again"))};let r=n?.timeoutMs??Ot,i=n?.maxAttempts??Ut,a=n?.backoffMs??Vt,s=!!(e.payload.screenshots?.element?.dataUrl||e.payload.screenshots?.fullPage?.dataUrl),c=!!e.payload.video?.blob,u=s||c,{lean:w,extracted:m,extractedVideo:l}=u?$t(e.payload):{lean:e.payload,extracted:{},extractedVideo:{}},b={...m,...e.screenshotDataUrls};for(let f=1;f<=i;f+=1){let p=new AbortController,d=setTimeout(()=>p.abort(),r),k=()=>p.abort();t&&t.addEventListener("abort",k,{once:true});try{let _=await fetch(ye,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(w),signal:p.signal});if(_.ok){Ke=Date.now();let v;try{v=await _.json();}catch{}if((b.element||b.fullPage)&&v){let M=v.upload_urls;if(M){let H=[];b.element&&M.element&&H.push(Ye(M.element,b.element,t)),b.fullPage&&M.fullPage&&H.push(Ye(M.fullPage,b.fullPage,t));try{await Promise.all(H);}catch{}}else if(v.feedback_id)try{await Xt({feedbackId:v.feedback_id,extracted:b,screenshots:e.payload.screenshots,...t?{signal:t}:{}});}catch{}}if(l.blob&&v){let M=v.upload_urls;if(M?.video)try{await fetch(M.video,{method:"PUT",body:l.blob,headers:{"content-type":l.blob.type},...t?{signal:t}:{}});}catch{}else if(v.feedback_id)try{await Zt({feedbackId:v.feedback_id,blob:l.blob,...t?{signal:t}:{}});}catch{}}let L={ok:!0,status:_.status};return v&&(L.apiResponse=v),L}if(f<i&&zt(_.status)){let v=.85+Math.random()*.3,C=Math.round(a*2**(f-1)*v);await Qe(C,t);continue}return {ok:!1,status:_.status,error:h("api_failed",new Error(`HTTP ${_.status}`))}}catch(_){if(p.signal.aborted||t?.aborted)return {ok:false,error:h("aborted",_)};if(f<i){let v=.85+Math.random()*.3,C=Math.round(a*2**(f-1)*v);await Qe(C,t);continue}return {ok:false,error:h("api_failed",_)}}finally{clearTimeout(d),t&&t.removeEventListener("abort",k);}}return {ok:false,error:h("api_failed",new Error("Failed"))}}async function Je(e){let{blob:t,mime:n,blocfeedId:o,signal:r}=e,i=await fetch(qt,{method:"POST",headers:{"Content-Type":n,"X-Blocfeed-Id":o},body:t,...r?{signal:r}:{}});if(!i.ok){let s=await i.json().catch(()=>({error:"Unknown error"}));throw h("api_failed",new Error(s?.error??`Voice API returned ${i.status}`))}let a=await i.json();return {text:a.text??"",warning:a.warning}}function Ee(e){let t=null,n=null,o=(...r)=>{n=r,t===null&&(t=requestAnimationFrame(()=>{if(t=null,!n)return;let i=n;n=null,e(...i);}));};return o.cancel=()=>{t!==null&&cancelAnimationFrame(t),t=null,n=null;},o}function te(e){return e instanceof Element?!!e.closest("[data-blocfeed-ui]"):false}function ne(e){e.stopPropagation(),e.stopImmediatePropagation?.();}function et(e,t){if(!y())throw new Error("BlocFeed picker can only run in a browser environment.");let n=e.ignoreSelectors,o=e.isSelectable,r={};n&&n.length>0&&(r.ignoreSelectors=n),o&&(r.isSelectable=o);let i=null,a=null,s=(f,p=false)=>{if(!f){i=null,a=null,t.onHover(null);return}let d=se(f.getBoundingClientRect()),k=`${Math.round(d.x)}:${Math.round(d.y)}:${Math.round(d.width)}:${Math.round(d.height)}`;!p&&f===i&&k===a||(i=f,a=k,t.onHover({element:f,rect:d}));},c=Ee(f=>{if(te(f.target))return;let p=document.elementFromPoint(f.clientX,f.clientY),d=de(p,r);s(d);}),u=Ee(()=>{i&&s(i,true);}),w=f=>{te(f.target)||(ne(f),f.pointerType==="mouse"&&f.preventDefault());},m=f=>{te(f.target)||(ne(f),f.pointerType==="mouse"&&f.preventDefault());},l=f=>{if(te(f.target))return;ne(f),f.preventDefault();let p=document.elementFromPoint(f.clientX,f.clientY),d=de(p,r);d&&t.onSelect({element:d,descriptor:Me(d)});},b=f=>{f.key==="Escape"&&(ne(f),f.preventDefault(),t.onCancel());};return window.addEventListener("pointermove",c,{capture:true,passive:true}),window.addEventListener("pointerdown",w,{capture:true}),window.addEventListener("pointerup",m,{capture:true}),window.addEventListener("click",l,{capture:true}),window.addEventListener("keydown",b,{capture:true}),window.addEventListener("scroll",u,{capture:true,passive:true}),window.addEventListener("resize",u,{passive:true}),{stop(){window.removeEventListener("pointermove",c,{capture:true}),window.removeEventListener("pointerdown",w,{capture:true}),window.removeEventListener("pointerup",m,{capture:true}),window.removeEventListener("click",l,{capture:true}),window.removeEventListener("keydown",b,{capture:true}),window.removeEventListener("scroll",u,{capture:true}),window.removeEventListener("resize",u),c.cancel(),u.cancel(),t.onHover(null);}}}async function ve(e){let{signal:t,transport:n}=e,o={ok:false};try{let r={payload:e.payload,...t?{signal:t}:{},...n?{transport:n}:{}};o.api=await Ge(r),o.ok=!!o.api?.ok;}catch(r){o.api={ok:false,error:h("api_failed",r)},o.ok=false;}return {payload:e.payload,result:o}}var Wt=["[data-blocfeed-ui]","[data-blocfeed-ignore]"];function Kt(e){let t=[...Wt,...e?.ignoreSelectors??[]],n=Array.from(new Set(t));return {...e,ignoreSelectors:n}}function tt(){return {phase:"idle"}}function Qt(e){if(e.ok)return false;let t=e.api;return t?t.status&&t.status>=400&&t.status<500||t.error?.kind==="aborted"||t.error?.kind==="configuration"?false:!t.ok:true}function no(e){let t=e,n=tt(),o=new Set,r=new Set,i=null,a=null,s=null,c=null,u=0,w=null,m=null,l=null,b=null,f=()=>{for(let g of o)g(n);},p=g=>{for(let E of r)E(g);},d=g=>{n=g,f();},k=()=>{u+=1,c?.abort(),c=null;},_=()=>{i?.stop(),i=null,p(null),s!==null&&y()&&(document.documentElement.style.cursor=s,s=null);},v=()=>{m&&(m.abort(),m=null),l&&(URL.revokeObjectURL(l.blobUrl),l=null);},C=()=>{b&&(b.abort(),b=null);},L=()=>{k(),_(),v(),C(),a=null,d(tt());},M=()=>{if(!y())return;_(),a=null;let g=Kt(t.picker);s=document.documentElement.style.cursor,document.documentElement.style.cursor="crosshair",d({phase:"picking"}),i=et(g,{onHover:p,onSelect:({element:E,descriptor:x})=>{a=E,_(),d({phase:"review",selection:x});},onCancel:()=>{L();}});},H=()=>{let g=Ve();if(g.length!==0)for(let E of g)ve({payload:E,...t.transport?{transport:t.transport}:{}}).catch(()=>{he(E);});};if(y()){setTimeout(H,1e3);let g=()=>H();window.addEventListener("online",g),w=()=>window.removeEventListener("online",g);}return {getState:()=>n,subscribe(g){return o.add(g),()=>o.delete(g)},subscribeHover(g){return r.add(g),()=>r.delete(g)},start(){n.phase==="capturing"||n.phase==="submitting"||n.phase==="recording"||n.phase!=="picking"&&M();},stop(){L();},clearSelection(){n.phase==="capturing"||n.phase==="submitting"||n.phase==="recording"||M();},setConfig(g){t=g;},async submit(g,E){if(!y()){let T=h("configuration",new Error("BlocFeed submit can only run in the browser"));return d({phase:"error",lastError:T}),{ok:false}}let x=t.blocfeed_id?.trim?.()??"";if(!x){let F={phase:"error",lastError:h("configuration",new Error("Missing blocfeed_id. Create a project in BlocFeed and pass its blocfeed_id."))};return n.selection&&(F.selection=n.selection),d(F),{ok:false}}if(n.phase==="capturing"||n.phase==="submitting"||n.phase==="recording")return {ok:false};let A=u+1;u=A,c?.abort(),c=new AbortController;let R=c.signal,S=n.selection,D=E?.capture?{...t.capture,...E.capture}:t.capture,I=!!(D?.element||D?.fullPage),z={phase:I?"capturing":"submitting"};S&&(z.selection=S),d(z);try{let T=I?await Oe({selectionElement:a,capture:D,signal:R}):void 0;if(R.aborted||u!==A)return {ok:!1};let F={phase:"submitting"};S&&(F.selection=S),T&&(F.capture=T),d(F);let B={};S&&(B.selection=S),T&&(B.capture=T);let j=await Ue({config:t.metadata,context:B,...t.user?{user:t.user}:{}}),O={version:1,createdAt:new Date().toISOString(),blocfeed_id:x,message:g,metadata:j};E?.category&&(O.category=E.category),t.user&&(O.user=t.user),S&&(O.selection=S),T&&(O.screenshots=T),l&&(O.video=l);let{result:N}=await ve({payload:O,signal:R,...t.transport?{transport:t.transport}:{}});if(R.aborted||u!==A)return N;if(N.ok){l&&(URL.revokeObjectURL(l.blobUrl),l=null);let ae={phase:"success",lastSubmit:N};return S&&(ae.selection=S),T&&(ae.capture=T),d(ae),N}Qt(N)&&he(O);let rt=N.api?.error??h("unknown",new Error("Submission failed")),ie={phase:"error",lastSubmit:N,lastError:rt};return S&&(ie.selection=S),T&&(ie.capture=T),d(ie),N}catch(T){if(R.aborted||u!==A)return {ok:false};let B={phase:"error",lastError:R.aborted?h("aborted",T):h("unknown",T)};return S&&(B.selection=S),d(B),{ok:false}}finally{u===A&&(c=null);}},async startRecording(){if(n.phase!=="review"||!t.recording?.enabled)return;let g=n.selection,E={phase:"recording",recordingElapsedMs:0};g&&(E.selection=g),d(E);try{let x={config:t.recording};c&&(x.signal=c.signal);let A=await be(x);m=A,A.onTick(D=>{if(n.phase==="recording"){let I={phase:"recording",recordingElapsedMs:D};g&&(I.selection=g),d(I);}});let R=await A.result;m=null,l=R;let S={phase:"review",video:R};g&&(S.selection=g),d(S);}catch(x){m=null;let R={phase:"review",lastError:x?.kind?x:h("recording_failed",x)};g&&(R.selection=g),d(R);}},stopRecording(){n.phase!=="recording"||!m||m.stop();},removeVideo(){l&&(URL.revokeObjectURL(l.blobUrl),l=null);let g=n.selection,E={phase:"review"};g&&(E.selection=g),d(E);},async startVoice(){if(n.phase!=="review")return;let g=t.voice;if(!g?.enabled)return;C();let E=n.selection,x={phase:"review",voiceRecording:true,voiceElapsedMs:0};E&&(x.selection=E),l&&(x.video=l),d(x);try{let A={config:g};c&&(A.signal=c.signal);let R=await we(A);b=R,R.onTick(B=>{let j={phase:"review",voiceRecording:!0,voiceElapsedMs:B};E&&(j.selection=E),l&&(j.video=l),d(j);});let S=await R.result;b=null;let D={phase:"review",voiceRecording:!1,voiceTranscribing:!0};E&&(D.selection=E),l&&(D.video=l),d(D);let I={blob:S.blob,mime:S.mime,blocfeedId:t.blocfeed_id};c&&(I.signal=c.signal);let{text:Te,warning:z}=await Je(I),T={phase:"review",voiceRecording:!1,voiceTranscribing:!1};E&&(T.selection=E),l&&(T.video=l),d(T);let F={text:Te};return z&&(F.warning=z),F}catch(A){C();let S={phase:"review",voiceRecording:false,voiceTranscribing:false,lastError:A?.kind?A:h("recording_failed",A)};E&&(S.selection=E),l&&(S.video=l),d(S);return}},stopVoice(){b&&b.stop();},__unsafeGetSelectedElement(){return a},destroy(){L(),o.clear(),r.clear(),w?.(),w=null;}}}var Z=[],W=[],nt=20,ot=15,oe=[],K={},Q=null,Y=null,G=null,re=false,Yt=["blocfeed.com","google-analytics.com","googletagmanager.com","analytics.google.com","googleads.g.doubleclick.net","googlesyndication.com","googleadservices.com","google.com/pagead","google.com/ads","facebook.net","facebook.com/tr","connect.facebook.net","graph.facebook.com","api.mixpanel.com","api-js.mixpanel.com","api.amplitude.com","api2.amplitude.com","api.segment.io","cdn.segment.com","vars.hotjar.com","in.hotjar.com","script.hotjar.com","heapanalytics.com","fullstory.com/s/fs.js","rs.fullstory.com","app.posthog.com","us.posthog.com","eu.posthog.com","api-iam.intercom.io","widget.intercom.io","sentry.io/api","browser.sentry-cdn.com","browser-intake-datadoghq.com","clarity.ms","js.hs-analytics.net","api.hubapi.com","forms.hubspot.com","plausible.io/api","client.crisp.chat","js.driftt.com","r.logrocket.com","app.pendo.io","events.launchdarkly.com","app.launchdarkly.com","grammarly.com","gnar.grammarly.com","capi.grammarly.com","api.languagetool.org","api.languagetoolplus.com","fonts.googleapis.com","fonts.gstatic.com","cdn.cookielaw.org","consent.cookiebot.com","js.stripe.com","api.stripe.com/v1/tokens","google.com/recaptcha","hcaptcha.com","bam.nr-data.net","rec.smartlook.com","o2.mouseflow.com","api.luckyorange.com","static.zdassets.com","ekr.zdassets.com"];function Gt(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}function Jt(e,t){let n=t.map(Gt).join(" "),o=t.find(i=>i instanceof Error),r={level:e,message:n.slice(0,2e3),timestamp:Date.now()};for(o?.stack&&(r.stack=o.stack.slice(0,2e3)),Z.push(r);Z.length>nt;)Z.shift();}function Se(e){let t=e.url.toLowerCase();for(let n of oe)if(t.includes(n))return;for(W.push(e);W.length>ot;)W.shift();}function io(e={}){if(re||!y())return;re=true,nt=e.consoleLimit??20,ot=e.networkLimit??15;let t=e.ignoreUrls;if(t!==void 0?oe=t.map(n=>n.toLowerCase()):oe=[...Yt],e.console!==false){let n=e.consoleLevels??["error","warn"];for(let o of n)K[o]=console[o],console[o]=(...r)=>{Jt(o,r),K[o]?.apply(console,r);};}if(e.network!==false&&typeof window.fetch=="function"){Q=window.fetch;let n=Q;window.fetch=async function(r,i){let a=typeof r=="string"?r:r instanceof URL?r.toString():r.url,s=(i?.method??"GET").toUpperCase(),c=Date.now();try{let u=await n.call(window,r,i);return u.ok||Se({url:a.slice(0,500),method:s,status:u.status,statusText:u.statusText,timestamp:c,durationMs:Date.now()-c}),u}catch(u){throw Se({url:a.slice(0,500),method:s,status:0,statusText:u instanceof Error?u.message:"Network error",timestamp:c,durationMs:Date.now()-c}),u}};}if(e.network!==false&&typeof XMLHttpRequest<"u"){Y=XMLHttpRequest.prototype.open,G=XMLHttpRequest.prototype.send;let n=Y,o=G;XMLHttpRequest.prototype.open=function(r,i,...a){return this.__bf_method=r.toUpperCase(),this.__bf_url=String(i),n.apply(this,[r,i,...a])},XMLHttpRequest.prototype.send=function(...r){let i=this.__bf_method||"GET",a=this.__bf_url||"",s=Date.now();return this.addEventListener("loadend",function(){if(this.status>=400||this.status===0){let c={url:a.slice(0,500),method:i,status:this.status,timestamp:s,durationMs:Date.now()-s};this.statusText&&(c.statusText=this.statusText),Se(c);}}),o.apply(this,r)};}}function ao(){if(re){for(let[e,t]of Object.entries(K))console[e]=t;for(let e of Object.keys(K))delete K[e];Q&&(window.fetch=Q,Q=null),Y&&(XMLHttpRequest.prototype.open=Y,Y=null),G&&(XMLHttpRequest.prototype.send=G,G=null),oe=[],re=false;}}function so(){return {consoleLogs:[...Z],networkErrors:[...W]}}function co(){Z=[],W=[];}var en=[{name:"supabase_service_role_key",pattern:/SUPABASE_SERVICE_ROLE_KEY["'=:\s]+[A-Za-z0-9_.=-]{30,}/},{name:"supabase_db_password",pattern:/SUPABASE_DB_PASSWORD["'=:\s]+[^\s"']{6,}/},{name:"postgres_password",pattern:/POSTGRES_PASSWORD["'=:\s]+[^\s"']{6,}/},{name:"database_url_with_creds",pattern:/(?:postgres|postgresql|mysql|mongodb|redis|amqp):\/\/[^:]+:[^@\s"']+@/},{name:"aws_access_key",pattern:/AKIA[0-9A-Z]{16}/},{name:"aws_secret_key",pattern:/AWS_SECRET_ACCESS_KEY["'=:\s]+[A-Za-z0-9/+=]{30,}/},{name:"aws_session_token",pattern:/AWS_SESSION_TOKEN["'=:\s]+[A-Za-z0-9/+=]{30,}/},{name:"stripe_secret_key",pattern:/sk_live_[a-zA-Z0-9]{10,}/},{name:"stripe_secret_key_test",pattern:/sk_test_[a-zA-Z0-9]{10,}/},{name:"stripe_restricted_key",pattern:/rk_(?:live|test)_[a-zA-Z0-9]{10,}/},{name:"github_pat",pattern:/ghp_[A-Za-z0-9_]{36,}/},{name:"github_oauth",pattern:/gho_[A-Za-z0-9_]{36,}/},{name:"github_user_token",pattern:/ghu_[A-Za-z0-9_]{36,}/},{name:"github_server_token",pattern:/ghs_[A-Za-z0-9_]{36,}/},{name:"github_fine_grained",pattern:/github_pat_[A-Za-z0-9_]{22,}/},{name:"openai_api_key",pattern:/sk-[a-zA-Z0-9]{20,}(?![a-zA-Z0-9_])/},{name:"anthropic_api_key",pattern:/sk-ant-[a-zA-Z0-9\-_]{20,}/},{name:"private_key",pattern:/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/},{name:"sendgrid_api_key",pattern:/SG\.[a-zA-Z0-9_-]{22,}\.[a-zA-Z0-9_-]{22,}/},{name:"twilio_auth_token",pattern:/TWILIO_AUTH_TOKEN["'=:\s]+[a-f0-9]{32}/},{name:"generic_secret_key",pattern:/[A-Z_]{2,}_SECRET_KEY["'=:\s]+[^\s"']{8,}/},{name:"generic_secret",pattern:/[A-Z_]{2,}_SECRET["'=:\s]+[^\s"']{8,}/},{name:"generic_password",pattern:/[A-Z_]{2,}_PASSWORD["'=:\s]+[^\s"']{6,}/},{name:"generic_private_key_var",pattern:/[A-Z_]{2,}_PRIVATE_KEY["'=:\s]+[^\s"']{8,}/}],V=[],_e=0,ke=false;function tn(e){let t=e.slice(0,80);return t.length<=6?"***":t.slice(0,6)+"***"}function nn(e,t,n,o){if(V.some(a=>a.rule===e&&a.source===t))return;let i={rule:e,source:t,hint:tn(n),timestamp:Date.now()};o&&(i.location=o),V.push(i);}function J(e,t,n,o){for(let{name:r,pattern:i}of n){let a=i.exec(e);a&&nn(r,t,a[0],o);}}function on(e){try{let t=window;if(t.__NEXT_DATA__){let n=JSON.stringify(t.__NEXT_DATA__);J(n,"hydration",e,"__NEXT_DATA__");}if(t.__NUXT__){let n=JSON.stringify(t.__NUXT__);J(n,"hydration",e,"__NUXT__");}}catch{}}function rn(e){try{document.querySelectorAll("script:not([src])").forEach((n,o)=>{let r=n.textContent;r&&r.length>0&&J(r,"scripts",e,`inline-script[${o}]`);});}catch{}}function an(e){try{document.querySelectorAll("meta[content]").forEach(n=>{let o=n.getAttribute("content"),r=n.getAttribute("name")||n.getAttribute("property")||"";o&&o.length>10&&J(o,"meta",e,r?`meta[${r}]`:void 0);});}catch{}}function sn(e){try{document.querySelectorAll("[data-api-key], [data-secret], [data-token], [data-password]").forEach(n=>{let o=n.attributes;for(let r=0;r<o.length;r++){let i=o[r];i.name.startsWith("data-")&&i.value.length>10&&J(i.value,"dom",e,`${n.tagName.toLowerCase()}[${i.name}]`);}});}catch{}}function fo(e={}){if(ke||!y()||(ke=true,e.secretScan===false))return;let t=[...en,...e.customPatterns??[]],n=e.scanTargets??["hydration","scripts","meta","dom"];setTimeout(()=>{_e=Date.now(),n.includes("hydration")&&on(t),n.includes("scripts")&&rn(t),n.includes("meta")&&an(t),n.includes("dom")&&sn(t);let o=e.notify??"both";V.length>0&&(o==="console"||o==="both")&&console.warn(`[BlocFeed Security] Found ${V.length} potential secret(s) exposed in client code:`,V.map(r=>`${r.rule} (${r.source}${r.location?`: ${r.location}`:""})`));},100);}function mo(){return {findings:[...V],scannedAt:_e}}function po(){V=[],_e=0,ke=false;}
2
+ export{y as a,se as b,Le as c,Oe as d,Ue as e,he as f,Ve as g,En as h,vn as i,At as j,Rt as k,Dt as l,be as m,Nt as n,we as o,Je as p,no as q,io as r,ao as s,so as t,co as u,fo as v,mo as w,po as x};
@@ -203,6 +203,12 @@ interface RecordingConfig {
203
203
  /** Video bitrate in bits/s. Default: 2 500 000 (2.5 Mbps) */
204
204
  videoBitsPerSecond?: number;
205
205
  }
206
+ interface VoiceConfig {
207
+ /** Enable voice feedback mic button. Default: false */
208
+ enabled?: boolean;
209
+ /** Maximum recording duration in milliseconds. Default: 60 000 (60 s) */
210
+ maxDurationMs?: number;
211
+ }
206
212
  interface VideoAsset {
207
213
  mime: VideoMime;
208
214
  /** Object URL (blob:) for local preview. Revoked after submission. */
@@ -219,6 +225,18 @@ interface VideoIntent {
219
225
  durationMs: number;
220
226
  sizeBytes: number;
221
227
  }
228
+ interface ClickEvent {
229
+ /** Ms offset from recording start — maps to video timeline. */
230
+ timestampMs: number;
231
+ /** window.location.pathname at click time. */
232
+ path: string;
233
+ /** HTML tag name (lowercase). */
234
+ tagName: string;
235
+ /** Text content (truncated). */
236
+ textSnippet?: string;
237
+ /** React/annotated component name. */
238
+ componentName?: string;
239
+ }
222
240
  interface CaptureConfig {
223
241
  element?: boolean;
224
242
  fullPage?: boolean;
@@ -279,6 +297,8 @@ interface BlocFeedConfig {
279
297
  security?: SecurityConfig;
280
298
  /** Video recording configuration. */
281
299
  recording?: RecordingConfig;
300
+ /** Voice feedback configuration (audio recording + transcription). */
301
+ voice?: VoiceConfig;
282
302
  ui?: {
283
303
  /** z-index for the widget overlay/panel. */
284
304
  zIndex?: number;
@@ -334,6 +354,8 @@ interface FeedbackPayload {
334
354
  video?: VideoAsset;
335
355
  /** Lightweight video metadata sent instead of the blob. */
336
356
  video_intent?: VideoIntent;
357
+ /** Click events captured during video recording. */
358
+ click_events?: ClickEvent[];
337
359
  /** First-class user identity. */
338
360
  user?: BlocFeedUser;
339
361
  metadata: Record<string, unknown>;
@@ -365,6 +387,12 @@ interface BlocFeedState {
365
387
  recordingElapsedMs?: number;
366
388
  lastSubmit?: SubmitResult;
367
389
  lastError?: BlocFeedError;
390
+ /** True while voice recording is active. */
391
+ voiceRecording?: boolean;
392
+ /** Elapsed voice recording time in ms. */
393
+ voiceElapsedMs?: number;
394
+ /** True while audio is being transcribed. */
395
+ voiceTranscribing?: boolean;
368
396
  }
369
397
 
370
398
  type HoverInfo = {
@@ -389,10 +417,15 @@ interface BlocFeedController {
389
417
  startRecording: () => Promise<void>;
390
418
  stopRecording: () => void;
391
419
  removeVideo: () => void;
420
+ startVoice: () => Promise<{
421
+ text: string;
422
+ warning?: string;
423
+ } | undefined>;
424
+ stopVoice: () => void;
392
425
  /** Internal: used by UI to access the live element handle. */
393
426
  __unsafeGetSelectedElement: () => Element | null;
394
427
  destroy: () => void;
395
428
  }
396
429
  declare function createBlocFeedController(config: BlocFeedConfig): BlocFeedController;
397
430
 
398
- export { type VideoMime as A, type BlocFeedConfig as B, type CaptureConfig as C, type DiagnosticsConfig as D, type ElementDescriptor as E, type FeedbackCategory as F, type StateListener as G, type HoverListener as H, type ImageAsset as I, createBlocFeedController as J, type MaybePromise as M, type NetworkEntry as N, type PickerConfig as P, type RecordingConfig as R, type SubmitResult as S, type ThemeConfig as T, type VideoAsset as V, type WidgetPosition as W, type BlocFeedHandle as a, type BlocFeedState as b, type BlocFeedController as c, type BlocFeedError as d, type BlocFeedStrings as e, type BlocFeedUser as f, type CaptureDiagnostics as g, type CaptureResult as h, type ConsoleEntry as i, type FeedbackApiResponse as j, type FeedbackPayload as k, type MetadataConfig as l, type MetadataContext as m, type Rect as n, type ScreenshotAdapter as o, type ScreenshotAdapterOptions as p, type ScreenshotIntent as q, type ScreenshotMime as r, type SecurityConfig as s, type SecurityFinding as t, type SecuritySnapshot as u, type SessionPhase as v, type TransportConfig as w, type TransportResult as x, type TriggerStyle as y, type VideoIntent as z };
431
+ export { type VideoMime as A, type BlocFeedConfig as B, type CaptureConfig as C, type DiagnosticsConfig as D, type ElementDescriptor as E, type FeedbackCategory as F, type VoiceConfig as G, type ClickEvent as H, type ImageAsset as I, type HoverListener as J, type StateListener as K, createBlocFeedController as L, type MaybePromise as M, type NetworkEntry as N, type PickerConfig as P, type RecordingConfig as R, type SubmitResult as S, type ThemeConfig as T, type VideoAsset as V, type WidgetPosition as W, type BlocFeedHandle as a, type BlocFeedState as b, type BlocFeedController as c, type BlocFeedError as d, type BlocFeedStrings as e, type BlocFeedUser as f, type CaptureDiagnostics as g, type CaptureResult as h, type ConsoleEntry as i, type FeedbackApiResponse as j, type FeedbackPayload as k, type MetadataConfig as l, type MetadataContext as m, type Rect as n, type ScreenshotAdapter as o, type ScreenshotAdapterOptions as p, type ScreenshotIntent as q, type ScreenshotMime as r, type SecurityConfig as s, type SecurityFinding as t, type SecuritySnapshot as u, type SessionPhase as v, type TransportConfig as w, type TransportResult as x, type TriggerStyle as y, type VideoIntent as z };
@@ -203,6 +203,12 @@ interface RecordingConfig {
203
203
  /** Video bitrate in bits/s. Default: 2 500 000 (2.5 Mbps) */
204
204
  videoBitsPerSecond?: number;
205
205
  }
206
+ interface VoiceConfig {
207
+ /** Enable voice feedback mic button. Default: false */
208
+ enabled?: boolean;
209
+ /** Maximum recording duration in milliseconds. Default: 60 000 (60 s) */
210
+ maxDurationMs?: number;
211
+ }
206
212
  interface VideoAsset {
207
213
  mime: VideoMime;
208
214
  /** Object URL (blob:) for local preview. Revoked after submission. */
@@ -219,6 +225,18 @@ interface VideoIntent {
219
225
  durationMs: number;
220
226
  sizeBytes: number;
221
227
  }
228
+ interface ClickEvent {
229
+ /** Ms offset from recording start — maps to video timeline. */
230
+ timestampMs: number;
231
+ /** window.location.pathname at click time. */
232
+ path: string;
233
+ /** HTML tag name (lowercase). */
234
+ tagName: string;
235
+ /** Text content (truncated). */
236
+ textSnippet?: string;
237
+ /** React/annotated component name. */
238
+ componentName?: string;
239
+ }
222
240
  interface CaptureConfig {
223
241
  element?: boolean;
224
242
  fullPage?: boolean;
@@ -279,6 +297,8 @@ interface BlocFeedConfig {
279
297
  security?: SecurityConfig;
280
298
  /** Video recording configuration. */
281
299
  recording?: RecordingConfig;
300
+ /** Voice feedback configuration (audio recording + transcription). */
301
+ voice?: VoiceConfig;
282
302
  ui?: {
283
303
  /** z-index for the widget overlay/panel. */
284
304
  zIndex?: number;
@@ -334,6 +354,8 @@ interface FeedbackPayload {
334
354
  video?: VideoAsset;
335
355
  /** Lightweight video metadata sent instead of the blob. */
336
356
  video_intent?: VideoIntent;
357
+ /** Click events captured during video recording. */
358
+ click_events?: ClickEvent[];
337
359
  /** First-class user identity. */
338
360
  user?: BlocFeedUser;
339
361
  metadata: Record<string, unknown>;
@@ -365,6 +387,12 @@ interface BlocFeedState {
365
387
  recordingElapsedMs?: number;
366
388
  lastSubmit?: SubmitResult;
367
389
  lastError?: BlocFeedError;
390
+ /** True while voice recording is active. */
391
+ voiceRecording?: boolean;
392
+ /** Elapsed voice recording time in ms. */
393
+ voiceElapsedMs?: number;
394
+ /** True while audio is being transcribed. */
395
+ voiceTranscribing?: boolean;
368
396
  }
369
397
 
370
398
  type HoverInfo = {
@@ -389,10 +417,15 @@ interface BlocFeedController {
389
417
  startRecording: () => Promise<void>;
390
418
  stopRecording: () => void;
391
419
  removeVideo: () => void;
420
+ startVoice: () => Promise<{
421
+ text: string;
422
+ warning?: string;
423
+ } | undefined>;
424
+ stopVoice: () => void;
392
425
  /** Internal: used by UI to access the live element handle. */
393
426
  __unsafeGetSelectedElement: () => Element | null;
394
427
  destroy: () => void;
395
428
  }
396
429
  declare function createBlocFeedController(config: BlocFeedConfig): BlocFeedController;
397
430
 
398
- export { type VideoMime as A, type BlocFeedConfig as B, type CaptureConfig as C, type DiagnosticsConfig as D, type ElementDescriptor as E, type FeedbackCategory as F, type StateListener as G, type HoverListener as H, type ImageAsset as I, createBlocFeedController as J, type MaybePromise as M, type NetworkEntry as N, type PickerConfig as P, type RecordingConfig as R, type SubmitResult as S, type ThemeConfig as T, type VideoAsset as V, type WidgetPosition as W, type BlocFeedHandle as a, type BlocFeedState as b, type BlocFeedController as c, type BlocFeedError as d, type BlocFeedStrings as e, type BlocFeedUser as f, type CaptureDiagnostics as g, type CaptureResult as h, type ConsoleEntry as i, type FeedbackApiResponse as j, type FeedbackPayload as k, type MetadataConfig as l, type MetadataContext as m, type Rect as n, type ScreenshotAdapter as o, type ScreenshotAdapterOptions as p, type ScreenshotIntent as q, type ScreenshotMime as r, type SecurityConfig as s, type SecurityFinding as t, type SecuritySnapshot as u, type SessionPhase as v, type TransportConfig as w, type TransportResult as x, type TriggerStyle as y, type VideoIntent as z };
431
+ export { type VideoMime as A, type BlocFeedConfig as B, type CaptureConfig as C, type DiagnosticsConfig as D, type ElementDescriptor as E, type FeedbackCategory as F, type VoiceConfig as G, type ClickEvent as H, type ImageAsset as I, type HoverListener as J, type StateListener as K, createBlocFeedController as L, type MaybePromise as M, type NetworkEntry as N, type PickerConfig as P, type RecordingConfig as R, type SubmitResult as S, type ThemeConfig as T, type VideoAsset as V, type WidgetPosition as W, type BlocFeedHandle as a, type BlocFeedState as b, type BlocFeedController as c, type BlocFeedError as d, type BlocFeedStrings as e, type BlocFeedUser as f, type CaptureDiagnostics as g, type CaptureResult as h, type ConsoleEntry as i, type FeedbackApiResponse as j, type FeedbackPayload as k, type MetadataConfig as l, type MetadataContext as m, type Rect as n, type ScreenshotAdapter as o, type ScreenshotAdapterOptions as p, type ScreenshotIntent as q, type ScreenshotMime as r, type SecurityConfig as s, type SecurityFinding as t, type SecuritySnapshot as u, type SessionPhase as v, type TransportConfig as w, type TransportResult as x, type TriggerStyle as y, type VideoIntent as z };
package/dist/engine.cjs CHANGED
@@ -1 +1 @@
1
- 'use strict';var chunkORVW2RTZ_cjs=require('./chunk-ORVW2RTZ.cjs');function c(o){if(o?.aborted)throw new Error("Aborted")}async function P(o){return await new Promise((t,e)=>{let r=new Image;r.onload=()=>t({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>e(new Error("Failed to load generated screenshot")),r.src=o;})}async function l(o,t){let{width:e,height:r}=await P(o);return {dataUrl:o,mime:t,width:e,height:r}}function B(o){return {async captureElement(t,e){if(!chunkORVW2RTZ_cjs.a())throw new Error("captureElement can only run in the browser");c(e.signal);let r={scale:e.pixelRatio},n=e.mime==="image/jpeg"?await o.domToJpeg(t,{...r,quality:e.quality??.92}):await o.domToPng(t,r);return c(e.signal),await l(n,e.mime)},async captureFullPage(t){if(!chunkORVW2RTZ_cjs.a())throw new Error("captureFullPage can only run in the browser");c(t.signal);let e=document.documentElement,r=Math.max(e.scrollWidth,e.clientWidth),n=Math.max(e.scrollHeight,e.clientHeight),a=Math.min(1,t.maxDimension/Math.max(r,n)),s={width:Math.max(1,Math.round(r*a)),height:Math.max(1,Math.round(n*a)),scale:t.pixelRatio},i=t.mime==="image/jpeg"?await o.domToJpeg(e,{...s,quality:t.quality??.92}):await o.domToPng(e,s);return c(t.signal),await l(i,t.mime)}}}function k(o){let[t,e]=o.split(",",2);if(!t||!e)throw new Error("Invalid data URL");let n=/data:(.*?);base64/.exec(t)?.[1]||"application/octet-stream",a=atob(e),s=new Uint8Array(a.length);for(let i=0;i<a.length;i+=1)s[i]=a.charCodeAt(i);return new Blob([s],{type:n})}Object.defineProperty(exports,"clearDiagnostics",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.p}});Object.defineProperty(exports,"clearQueue",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.h}});Object.defineProperty(exports,"clearSecurityFindings",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.s}});Object.defineProperty(exports,"collectMetadata",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.e}});Object.defineProperty(exports,"createBlocFeedController",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.l}});Object.defineProperty(exports,"createHtmlToImageAdapter",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.c}});Object.defineProperty(exports,"dequeueAll",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.g}});Object.defineProperty(exports,"drainDiagnostics",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.o}});Object.defineProperty(exports,"enqueue",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.f}});Object.defineProperty(exports,"getQueueSize",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.i}});Object.defineProperty(exports,"getSecurityFindings",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.r}});Object.defineProperty(exports,"installDiagnostics",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.m}});Object.defineProperty(exports,"isRecordingSupported",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.j}});Object.defineProperty(exports,"runCapture",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.d}});Object.defineProperty(exports,"runSecretScan",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.q}});Object.defineProperty(exports,"startRecording",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.k}});Object.defineProperty(exports,"uninstallDiagnostics",{enumerable:true,get:function(){return chunkORVW2RTZ_cjs.n}});exports.createModernScreenshotAdapter=B;exports.dataUrlToBlob=k;
1
+ 'use strict';var chunkD626QU52_cjs=require('./chunk-D626QU52.cjs');function s(o){if(o?.aborted)throw new Error("Aborted")}async function D(o){return await new Promise((t,e)=>{let r=new Image;r.onload=()=>t({width:r.naturalWidth,height:r.naturalHeight}),r.onerror=()=>e(new Error("Failed to load generated screenshot")),r.src=o;})}async function l(o,t){let{width:e,height:r}=await D(o);return {dataUrl:o,mime:t,width:e,height:r}}function v(o){return {async captureElement(t,e){if(!chunkD626QU52_cjs.a())throw new Error("captureElement can only run in the browser");s(e.signal);let r={scale:e.pixelRatio},n=e.mime==="image/jpeg"?await o.domToJpeg(t,{...r,quality:e.quality??.92}):await o.domToPng(t,r);return s(e.signal),await l(n,e.mime)},async captureFullPage(t){if(!chunkD626QU52_cjs.a())throw new Error("captureFullPage can only run in the browser");s(t.signal);let e=document.documentElement,r=Math.max(e.scrollWidth,e.clientWidth),n=Math.max(e.scrollHeight,e.clientHeight),i=Math.min(1,t.maxDimension/Math.max(r,n)),c={width:Math.max(1,Math.round(r*i)),height:Math.max(1,Math.round(n*i)),scale:t.pixelRatio},a=t.mime==="image/jpeg"?await o.domToJpeg(e,{...c,quality:t.quality??.92}):await o.domToPng(e,c);return s(t.signal),await l(a,t.mime)}}}function H(o){let[t,e]=o.split(",",2);if(!t||!e)throw new Error("Invalid data URL");let n=/data:(.*?);base64/.exec(t)?.[1]||"application/octet-stream",i=atob(e),c=new Uint8Array(i.length);for(let a=0;a<i.length;a+=1)c[a]=i.charCodeAt(a);return new Blob([c],{type:n})}Object.defineProperty(exports,"clearClickEvents",{enumerable:true,get:function(){return chunkD626QU52_cjs.k}});Object.defineProperty(exports,"clearDiagnostics",{enumerable:true,get:function(){return chunkD626QU52_cjs.u}});Object.defineProperty(exports,"clearQueue",{enumerable:true,get:function(){return chunkD626QU52_cjs.h}});Object.defineProperty(exports,"clearSecurityFindings",{enumerable:true,get:function(){return chunkD626QU52_cjs.x}});Object.defineProperty(exports,"collectMetadata",{enumerable:true,get:function(){return chunkD626QU52_cjs.e}});Object.defineProperty(exports,"createBlocFeedController",{enumerable:true,get:function(){return chunkD626QU52_cjs.q}});Object.defineProperty(exports,"createHtmlToImageAdapter",{enumerable:true,get:function(){return chunkD626QU52_cjs.c}});Object.defineProperty(exports,"dequeueAll",{enumerable:true,get:function(){return chunkD626QU52_cjs.g}});Object.defineProperty(exports,"drainClickEvents",{enumerable:true,get:function(){return chunkD626QU52_cjs.j}});Object.defineProperty(exports,"drainDiagnostics",{enumerable:true,get:function(){return chunkD626QU52_cjs.t}});Object.defineProperty(exports,"enqueue",{enumerable:true,get:function(){return chunkD626QU52_cjs.f}});Object.defineProperty(exports,"getQueueSize",{enumerable:true,get:function(){return chunkD626QU52_cjs.i}});Object.defineProperty(exports,"getSecurityFindings",{enumerable:true,get:function(){return chunkD626QU52_cjs.w}});Object.defineProperty(exports,"installDiagnostics",{enumerable:true,get:function(){return chunkD626QU52_cjs.r}});Object.defineProperty(exports,"isRecordingSupported",{enumerable:true,get:function(){return chunkD626QU52_cjs.l}});Object.defineProperty(exports,"isVoiceSupported",{enumerable:true,get:function(){return chunkD626QU52_cjs.n}});Object.defineProperty(exports,"runCapture",{enumerable:true,get:function(){return chunkD626QU52_cjs.d}});Object.defineProperty(exports,"runSecretScan",{enumerable:true,get:function(){return chunkD626QU52_cjs.v}});Object.defineProperty(exports,"startRecording",{enumerable:true,get:function(){return chunkD626QU52_cjs.m}});Object.defineProperty(exports,"startVoiceRecording",{enumerable:true,get:function(){return chunkD626QU52_cjs.o}});Object.defineProperty(exports,"transcribeAudio",{enumerable:true,get:function(){return chunkD626QU52_cjs.p}});Object.defineProperty(exports,"uninstallDiagnostics",{enumerable:true,get:function(){return chunkD626QU52_cjs.s}});exports.createModernScreenshotAdapter=v;exports.dataUrlToBlob=H;