@buoy-gg/bug-links 0.0.0-stage → 7.0.80
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/BuoyBugLinks.podspec +18 -0
- package/LICENSE +58 -0
- package/README.md +25 -2
- package/android/build.gradle +31 -0
- package/android/consumer-rules.pro +8 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/gg/buoy/buglinks/frames/BuoyFramesModule.kt +53 -0
- package/android/src/main/java/gg/buoy/buglinks/frames/BuoyFramesPackage.kt +11 -0
- package/android/src/main/java/gg/buoy/buglinks/frames/FrameEngine.kt +707 -0
- package/android/src/main/java/gg/buoy/buglinks/frames/FrameWalker.kt +181 -0
- package/ios/BuoyFrames.m +17 -0
- package/ios/BuoyFrames.swift +640 -0
- package/ios/BuoyFramesWalk.swift +213 -0
- package/lib/commonjs/components/BugLinksBody.js +1 -0
- package/lib/commonjs/components/TeamBugs.js +1 -0
- package/lib/commonjs/components/focus.js +1 -0
- package/lib/commonjs/components/overlays.js +1 -0
- package/lib/commonjs/components/topBanners.js +1 -0
- package/lib/commonjs/engine/client.js +1 -0
- package/lib/commonjs/engine/credential.js +1 -0
- package/lib/commonjs/engine/linksStore.js +1 -0
- package/lib/commonjs/engine/loader.js +1 -0
- package/lib/commonjs/engine/opener.js +1 -0
- package/lib/commonjs/engine/otherTabs.js +1 -0
- package/lib/commonjs/engine/phoneCheck.js +1 -0
- package/lib/commonjs/engine/phoneCrypto.js +1 -0
- package/lib/commonjs/engine/randomBytes.js +1 -0
- package/lib/commonjs/engine/reporter.js +1 -0
- package/lib/commonjs/engine/sandbox.js +1 -0
- package/lib/commonjs/engine/settings.js +1 -0
- package/lib/commonjs/engine/tabStorage.js +1 -0
- package/lib/commonjs/engine/teamKeys.js +1 -0
- package/lib/commonjs/engine/teamList.js +1 -0
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/media/BuoyMask.js +1 -0
- package/lib/commonjs/media/frames.js +1 -0
- package/lib/commonjs/media/nativeFrames.js +1 -0
- package/lib/commonjs/media/nativeFrames.web.js +1 -0
- package/lib/commonjs/media/webFrames.js +14 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +1 -0
- package/lib/commonjs/sync/bugLinksSyncAdapter.js +1 -0
- package/lib/module/components/BugLinksBody.js +1 -0
- package/lib/module/components/TeamBugs.js +1 -0
- package/lib/module/components/focus.js +1 -0
- package/lib/module/components/overlays.js +1 -0
- package/lib/module/components/topBanners.js +1 -0
- package/lib/module/engine/client.js +1 -0
- package/lib/module/engine/credential.js +1 -0
- package/lib/module/engine/linksStore.js +1 -0
- package/lib/module/engine/loader.js +1 -0
- package/lib/module/engine/opener.js +1 -0
- package/lib/module/engine/otherTabs.js +1 -0
- package/lib/module/engine/phoneCheck.js +1 -0
- package/lib/module/engine/phoneCrypto.js +1 -0
- package/lib/module/engine/randomBytes.js +1 -0
- package/lib/module/engine/reporter.js +1 -0
- package/lib/module/engine/sandbox.js +1 -0
- package/lib/module/engine/settings.js +1 -0
- package/lib/module/engine/tabStorage.js +1 -0
- package/lib/module/engine/teamKeys.js +1 -0
- package/lib/module/engine/teamList.js +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/media/BuoyMask.js +1 -0
- package/lib/module/media/frames.js +1 -0
- package/lib/module/media/nativeFrames.js +1 -0
- package/lib/module/media/nativeFrames.web.js +1 -0
- package/lib/module/media/webFrames.js +14 -0
- package/lib/module/preset.js +1 -0
- package/lib/module/sync/bugLinksSyncAdapter.js +1 -0
- package/lib/typescript/components/BugLinksBody.d.ts +2 -0
- package/lib/typescript/components/TeamBugs.d.ts +8 -0
- package/lib/typescript/components/focus.d.ts +6 -0
- package/lib/typescript/components/overlays.d.ts +10 -0
- package/lib/typescript/components/topBanners.d.ts +35 -0
- package/lib/typescript/engine/client.d.ts +78 -0
- package/lib/typescript/engine/credential.d.ts +11 -0
- package/lib/typescript/engine/linksStore.d.ts +132 -0
- package/lib/typescript/engine/loader.d.ts +172 -0
- package/lib/typescript/engine/opener.d.ts +21 -0
- package/lib/typescript/engine/otherTabs.d.ts +52 -0
- package/lib/typescript/engine/phoneCheck.d.ts +8 -0
- package/lib/typescript/engine/phoneCrypto.d.ts +16 -0
- package/lib/typescript/engine/randomBytes.d.ts +7 -0
- package/lib/typescript/engine/reporter.d.ts +53 -0
- package/lib/typescript/engine/sandbox.d.ts +83 -0
- package/lib/typescript/engine/settings.d.ts +42 -0
- package/lib/typescript/engine/tabStorage.d.ts +6 -0
- package/lib/typescript/engine/teamKeys.d.ts +37 -0
- package/lib/typescript/engine/teamList.d.ts +63 -0
- package/lib/typescript/index.d.ts +23 -0
- package/lib/typescript/media/BuoyMask.d.ts +19 -0
- package/lib/typescript/media/frames.d.ts +34 -0
- package/lib/typescript/media/nativeFrames.d.ts +2 -0
- package/lib/typescript/media/nativeFrames.web.d.ts +2 -0
- package/lib/typescript/media/webFrames.d.ts +178 -0
- package/lib/typescript/preset.d.ts +50 -0
- package/lib/typescript/sync/bugLinksSyncAdapter.d.ts +41 -0
- package/lib/web/index.mjs +20 -0
- package/package.json +119 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{View as o}from"react-native";import{jsx as a}from"react/jsx-runtime";export function BuoyMask({children:e,style:t,testID:s}){return a(o,{nativeID:"buoy-mask",collapsable:false,style:t,testID:s,children:e})}export function BuoyUnmask({children:e,style:t,testID:s}){return a(o,{nativeID:"buoy-unmask",collapsable:false,style:t,testID:s,children:e})}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{Platform as M}from"react-native";import{loadFramesModule as I}from"./nativeFrames";import{addDroppedWhy as x,describeDroppedWhy as O,DROPPED_TO_FIT as S,fitBugCaseMedia as W,validateBugCaseMedia as D}from"@buoy-gg/shared-ui";let q=null;const l=(()=>{try{return I()}catch(t){q=t instanceof Error?t.message:String(t);return void 0}})();let s={};let E=false;export function mediaUnavailableReason(){if(q)return"Pictures failed to load on this build.";if(!l){if(M.OS==="web")return"No pictures here: this page has no window to draw.";if(M.OS!=="ios"&&M.OS!=="android")return"No pictures on this platform yet.";return"Pictures need a dev build."}return null}export function configureMedia(t={}){const i=s;s={...s,...t};const n=["maskAllText","maskAllImages","maskAllInputs"].some(d=>i[d]===false&&s[d]!==false);E=true;if(!l)return;const a=s.pictures!==false||s.video!=="off";try{l.configure({pictures:s.pictures!==false,video:s.video??"manual",maskAllText:s.maskAllText!==false,maskAllImages:s.maskAllImages!==false,maskAllInputs:s.maskAllInputs!==false,mode:s.mode==="customer"?"customer":"internal"});if(s.video==="off"||s.mode==="customer")l.stopVideo();if(!a||n||i.mode!=="customer"&&s.mode==="customer")l.clear()}catch{}}export function mediaConfig(){return s}export function ensureMediaStarted(){if(!E)configureMedia()}export function startRecording(){if(s.video!=="off"&&s.mode!=="customer")l?.startVideo(false)}export function stopRecording(){l?.stopVideo()}export function markBugHere(){l?.mark()}export function resumeMedia(){try{l?.resume()}catch{}}export function probeFrame(){return l?l.probe():Promise.resolve({ok:false,reason:mediaUnavailableReason()})}export async function addMediaToCase(t){const i=mediaUnavailableReason();if(i)return u(t,i);if(s.pictures===false&&s.video==="off")return t;let n;let a=null;try{a=await N(l.captureLast(),P).catch(e=>({ok:false,reason:e instanceof Error?e.message:String(e)}));n=await N(l.takeMedia(),P)}catch(e){return u(t,`Pictures failed: ${e instanceof Error?e.message:String(e)}`)}const d=t.clock?.active?t.clock.offsetMs??0:0;const m=t.touches.filter(e=>e.kind==="press"||e.kind==="longPress"||e.kind==="toggle"||e.kind==="slider");const w=n.frames.map(e=>({t:e.t+d,wall:e.t,kind:e.kind,w:e.w,h:e.h,jpeg:e.jpeg,boxes:e.boxes}));const b=new Set;const k=e=>{const r=[];for(const o of w){if(o.kind!=="tap"||o.tap!==void 0)continue;for(const f of m){if(f.seq===void 0||b.has(f.seq))continue;const y=e(o,f);if(y===void 0)continue;if(y>=0&&y<3e3)r.push({f:o,seq:f.seq,gap:y})}}r.sort((o,f)=>o.gap-f.gap||f.seq-o.seq||f.f.wall-o.f.wall);for(const o of r){if(o.f.tap!==void 0||b.has(o.seq))continue;o.f.tap=o.seq;b.add(o.seq)}};k((e,r)=>{if(typeof r.wall!=="number")return void 0;const o=e.wall-r.wall;if(o>=2e3||o<-1e3)return void 0;return o>=0?o:2e3-o});const A=e=>e<2e3?e:void 0;k((e,r)=>typeof r.wall==="number"?void 0:A(e.t-r.t));if(d)k((e,r)=>typeof r.wall==="number"?void 0:A(e.wall-r.t));const _=new Set(w.map(e=>e.tap).filter(e=>e!==void 0));const p=w.filter(e=>e.kind==="tap"&&e.tap===void 0).sort((e,r)=>(e.wall??e.t)-(r.wall??r.t));const g=m.filter(e=>e.seq!==void 0&&!_.has(e.seq)&&typeof e.wall!=="number").sort((e,r)=>e.seq-r.seq);if(p.length&&p.length===g.length){const e=!!t.clock?.active||p.every((r,o)=>Math.abs(r.t-g[o].t)>6e4);if(e)p.forEach((r,o)=>r.tap=g[o].seq)}else if(p.length&&p.length<g.length&&t.clock?.active){const e=g.slice(g.length-p.length);p.forEach((r,o)=>r.tap=e[o].seq)}const v=w.filter(e=>e.tap!==void 0).sort((e,r)=>e.wall-r.wall);const $=v.map(e=>e.tap).sort((e,r)=>e-r);v.forEach((e,r)=>e.tap=$[r]);const h=D({frames:w,marks:n.marks.map(e=>e+d),settings:n.settings,dropped:n.dropped,droppedWhy:T(n)});let c=h&&h.frames.length?{...t,media:h}:t;if(!h||!h.frames.length){const e=Object.entries(h?.droppedWhy??T(n));const r=e.length?e.map(([o,f])=>f>1?`${o} (${f} times)`:o).join("; "):"no frame was taken";c=u(c,`No pictures: ${r}`)}else if(!h.frames.some(e=>e.kind==="last")){c=u(c,"No picture from right before the report. The newest picture is from earlier.")}else if(a&&!a.ok&&typeof n.lastAgeMs==="number"&&n.lastAgeMs>R){const e=a.reason?` (${a.reason})`:"";c=u(c,`No new picture right before the report${e}. The main picture is ${Math.round(n.lastAgeMs/1e3)} s old.`)}if(n.stopped)c=u(c,n.stopped);return c}function T(t){let i={};let n=0;for(const[d,m]of Object.entries(t.reasons??{})){if(typeof m!=="number"||!(m>0))continue;i=x(i,F[d]??d,m);n+=m}const a=typeof t.dropped==="number"&&Number.isFinite(t.dropped)?t.dropped:0;return x(i,S,a-n)}const F={busy:"another picture was still being taken"};export function shrinkMedia(t,i){if(!t.media)return t;const n=W(t.media.frames,t.media.marks,{frames:150,bytes:i});if(!n.dropped)return t;const a={...t.media,frames:n.frames,dropped:t.media.dropped+n.dropped,droppedWhy:x(t.media.droppedWhy??{},S,n.dropped)};const d={...t,media:a};if(a.frames.length)return u(d,`Some pictures were left out to fit the case (${n.dropped}).`);delete d.media;return u(d,`No pictures fit in the case. ${a.dropped} were left out: ${O(a.droppedWhy)}.`)}const P=8e3;function N(t,i){let n;return Promise.race([t,new Promise((a,d)=>{n=setTimeout(()=>d(new Error("the phone took too long")),i)})]).finally(()=>clearTimeout(n))}const R=1e4;function u(t,i){return{...t,missing:[...t.missing,{part:"media",reason:i}]}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{NativeModules as e,Platform as r}from"react-native";export function loadFramesModule(){if(r.OS!=="ios"&&r.OS!=="android")return void 0;return e.BuoyFrames}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{WebFrameEngine as r}from"./webFrames";let n;export function loadFramesModule(){if(typeof document==="undefined"||typeof window==="undefined")return void 0;if(!n){n=new r;const i=globalThis;if(i.__BUOY_E2E__===true){const e=n;i.__BUOY_FRAMES_TEST__={probe:()=>e.probe(),configure:o=>e.configure(o),takeMedia:()=>e.takeMedia(),resume:()=>e.resume()}}}return n}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";const I="#1F1F1F";const M=2;const gt=.6;const yt=1280;const bt=10;const wt=3e4;const Z=6e4;const At=6*1024*1024;const Tt=250;const vt=100;const P="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";const tt="[data-buoy-root],[data-buoy-mount],[data-buoy-portal-host],[id='floating-devtools-root'],[id='dial-devtools-root'],[id='buoy-tool-host']";const Et="[id='dial-devtools-root'],[id='buoy-tool-host'],[role='dialog'][aria-label='Dial Menu']";const xt="[id='buoy-mask'],[data-buoy-mask],.buoy-mask,.sentry-mask,.sentry-block,.sentry-react-native-mask,[data-sentry-mask]";const V="[id='buoy-unmask'],[data-buoy-unmask],.buoy-unmask,.sentry-unmask,.sentry-react-native-unmask";const kt=new Set(["sentry-react-native-mask","sentry-react-native-unmask"]);const St=new Set(["checkbox","radio","range","color","file","hidden"]);const _t=new Set(["button","submit","reset"]);const z=/(^|\s)(current-password|new-password|one-time-code|cc-[a-z-]+)(\s|$)/i;const et=new Set(["IMG","PICTURE","VIDEO","CANVAS","svg","SVG","OBJECT","EMBED"]);const It=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE","TEXTAREA","SELECT"]);const Ot=new Set(["TITLE","DESC","STYLE","SCRIPT","METADATA"]);const k=e=>(e.localName||e.tagName).toUpperCase();const q="http://www.w3.org/2000/svg";const X=4|8;const S=()=>typeof performance!=="undefined"&&performance.timeOrigin?performance.timeOrigin+performance.now():Date.now();class E extends Error{}const nt=e=>e.replace(/\S/g,"n");const st=[["background",I],["background-image","none"],["color","transparent"],["-webkit-text-fill-color","transparent"],["border-color",I],["box-shadow","none"],["text-shadow","none"],["outline","none"],["filter","none"],["opacity","1"]];function Rt(e){const t=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|X);for(let s=t.nextNode();s;s=t.nextNode()){if(s.nodeType===Node.TEXT_NODE||s.nodeType===Node.CDATA_SECTION_NODE){s.data=nt(s.data);continue}const n=s;n.style?.setProperty("visibility","hidden","important");it(n)}}function it(e){for(const s of["srcset","poster","placeholder","title","href","xlink:href"])e.removeAttribute(s);const t=k(e);if(t==="IMG"||t==="IMAGE"){e.setAttribute("src",P);e.setAttribute("alt","")}else if(t==="INPUT"){e.setAttribute("value","");e.removeAttribute("src")}else if(t==="TEXTAREA")e.textContent="";else if(t==="OPTION")e.textContent=""}function G(e,t,s){if(!e||!(e.width>0)||!(e.height>0))return null;const n=Math.max(0,Math.floor(e.left-M));const i=Math.max(0,Math.floor(e.top-M));const l=Math.min(t,Math.ceil(e.right+M));const d=Math.min(s,Math.ceil(e.bottom+M));return l>n&&d>i?{x:n,y:i,width:l-n,height:d-i}:null}function ot(e,t){for(let s=e;s;s=s.parentElement){const n=s.getAttribute("data-testid");if(n){if(s===e)return n;const i=s.getBoundingClientRect();return t.width*t.height>=i.width*i.height*.8?n:""}}return""}function $t(e){const t=k(e);if(t==="INPUT"){const n=e;const i=(n.type||"text").toLowerCase();if(i==="password"||z.test(n.autocomplete??""))return"secure";if(i==="image")return"image";if(_t.has(i))return"text";return St.has(i)?"none":"input"}if(t==="TEXTAREA")return z.test(e.autocomplete??"")?"secure":"input";if(t==="SELECT")return z.test(e.autocomplete??"")?"secure":"input";if(e.isContentEditable&&!e.parentElement?.isContentEditable)return"input";if(t==="IFRAME"||t==="FRAME")return"web";if(et.has(t))return"image";if(e.getAttribute("role")==="img")return"image";const s=e.style?.backgroundImage;if(s&&s.includes("url("))return"image";return"none"}function Ct(e){const t=Array.from(e.classList).find(n=>kt.has(n));const s=e.parentElement;return t&&s&&s.childElementCount===1?s:e}function j(e){return e.replace(/\\\r?\n/g,"").replace(/\\([0-9a-f]{1,6})\s?/gi,(t,s)=>{const n=parseInt(s,16);return n>0&&n<=1114111?String.fromCodePoint(n):"\uFFFD"}).replace(/\\(.)/g,"$1")}const K=e=>/^\s*data:/i.test(e);const Mt=/@font-face\s*\{[^}]*\}/gi;function Y(e){if(!/data|\\/i.test(e))return false;const t=j(e);return(t.match(/data:[^,]*/gi)??[]).some(s=>K(s))}function rt(e){let t="";for(let s=0;s<e.length;s++){const n=e[s];const i=e.getPropertyValue(n);if(Y(i))continue;const l=e.getPropertyPriority(n);t+=`${n}: ${i}${l?" !important":""}; `}return t}function at(e){const t=e.cssText;if(!Y(t))return t;const s=e;if(e.type===5||/^\s*@font-face\b/i.test(t))return t;const n=i=>{let l="";for(let d=0;i&&d<i.length;d++)l+=at(i[d])+"\n";return l};if(s.style&&s.selectorText!==void 0)return`${s.selectorText} { ${rt(s.style)}${n(s.cssRules)}}`;if(s.cssRules&&t.includes("{"))return`${t.slice(0,t.indexOf("{"))}{
|
|
2
|
+
${n(s.cssRules)}}`;return""}function lt(e){const t=[];const s=e.replace(Mt,n=>`/*__buoy_font_${t.push(n)-1}__*/`);return Pt(s).replace(/\/\*__buoy_font_(\d+)__\*\//g,(n,i)=>t[Number(i)]??"")}function Pt(e){return e.replace(/(["'])((?:(?!\1)[^\\]|\\[\s\S])*)\1/g,(t,s,n)=>K(j(n))?'""':t).replace(/url\(\s*((?:[^"'\s)\\]|\\[\s\S])(?:[^)\\]|\\[\s\S])*)\)/gi,(t,s)=>K(j(s.trim()))?"none":t)}function Nt(e){const t=[...Array.from(document.styleSheets),...document.adoptedStyleSheets??[]];let s="";for(const n of t){let i=null;try{i=n.disabled?null:n.cssRules}catch{i=null}if(!i)continue;let l="";for(let g=0;g<i.length;g++)l+=(e?at(i[g]):i[g].cssText)+"\n";const d=n.media?.mediaText;s+=d&&d!=="all"?`@media ${d} {
|
|
3
|
+
${l}}
|
|
4
|
+
`:l}if(e)s=lt(s);return s}const w=":not(#_bq1#_bq2#_bq3#_bq4#_bq5#_bq6)";const ct="__buoy_mask";const H=e=>`${e}::before, ${e}::after, ${e}::marker`;const Lt=e=>`
|
|
5
|
+
head${w}, head${w} * { display: none !important; }
|
|
6
|
+
.__buoy_box${w} { background: ${I} !important; background-image: none !important; color: transparent !important; -webkit-text-fill-color: transparent !important; border-color: ${I} !important; box-shadow: none !important; text-shadow: none !important; outline: none !important; caret-color: transparent !important; filter: none !important; opacity: 1 !important; mask: none !important; -webkit-mask: none !important; }
|
|
7
|
+
.__buoy_box${w} *${w} { visibility: hidden !important; }
|
|
8
|
+
${H(`.__buoy_box${w}`)}, ${H(`.__buoy_box${w} *${w}`)} { content: none !important; visibility: hidden !important; }
|
|
9
|
+
.__buoy_box${w}::placeholder { color: transparent !important; }
|
|
10
|
+
.__buoy_text${w} { background: ${I} !important; color: transparent !important; -webkit-text-fill-color: transparent !important; text-shadow: none !important; border-radius: 0 !important; }
|
|
11
|
+
${e.text?`${H(`*${w}`)} { content: none !important; color: transparent !important; -webkit-text-fill-color: transparent !important; text-shadow: none !important; }`:""}
|
|
12
|
+
`;function Dt(e){const t=e.checkVisibility;if(typeof t==="function")return t.call(e,{visibilityProperty:true});try{return getComputedStyle(e).visibility!=="hidden"}catch{return true}}const N=new Map;export function buoyScreenOpen(e=window.innerWidth,t=window.innerHeight){const s=n=>n.getClientRects().length>0&&!!G(n.getBoundingClientRect(),e,t);if(Array.from(document.querySelectorAll(Et)).some(s))return true;for(const n of Array.from(document.querySelectorAll("[data-buoy-portal-host]"))){for(const i of Array.from(n.querySelectorAll("*"))){const l=i.getBoundingClientRect();if(l.width*l.height>=e*t*.2&&s(i)&&getComputedStyle(i).pointerEvents!=="none")return true}}return false}function ut(e){const t=e.currentSrc||e.src;if(!t)return null;if(t.startsWith("data:image/"))return t;const s=N.get(t);if(s!==void 0)return s;if(!e.complete||!e.naturalWidth)return null;let n=null;try{const i=document.createElement("canvas");i.width=Math.min(e.naturalWidth,1024);i.height=Math.round(e.naturalHeight/e.naturalWidth*i.width);i.getContext("2d").drawImage(e,0,0,i.width,i.height);n=i.toDataURL("image/png")}catch{n=null}if(N.size>200)N.clear();N.set(t,n);return n}export function snapshotPage(e,t=false){const s=performance.now();const n=document.documentElement.clientWidth||window.innerWidth;const i=window.innerHeight||document.documentElement.clientHeight;if(!(n>0)||!(i>0)||!document.body)throw new E("no page to draw");const l=buoyScreenOpen(n,i);const d=document.documentElement;const g=document.implementation.createHTMLDocument("");const m=g.importNode(d,true);const y=[d,...Array.from(d.querySelectorAll("*"))];const A=[m,...Array.from(m.querySelectorAll("*"))];if(y.length!==A.length)throw new E("the page changed while it was copied");const p=[];const O=document.createTreeWalker(d,X);const J=document.createTreeWalker(m,X);for(let a=O.nextNode(),o=J.nextNode();a&&o;a=O.nextNode(),o=J.nextNode()){if(a.data.trim())p.push([a,o])}const L=[];const D=[];const B=[];const T=new Set;const x=new Set;let U=0;const dt=(a,o)=>{if(!a)return;L.push(a);if(t)D.push({testID:ot(o,a),x:a.x,y:a.y,w:a.width,h:a.height})};const W=(a,o)=>{T.add(a);const r=Dt(a)?G(a.getBoundingClientRect(),n,i):null;if(r)U++;dt(r,a);o.classList.add("__buoy_box");for(const[u,h]of st)o.style?.setProperty(u,h,"important");it(o);Rt(o);if(/^(VIDEO|CANVAS|IFRAME|FRAME|OBJECT|EMBED)$/.test(k(o))){const u=a.getBoundingClientRect();const h=document.createElement("div");h.className=`${o.getAttribute("class")??""} __buoy_box`;h.setAttribute("style",`${o.getAttribute("style")??""};width:${u.width}px;height:${u.height}px;display:${getComputedStyle(a).display==="inline"?"inline-block":getComputedStyle(a).display}`);o.replaceWith(h)}};const R=(a,o)=>{for(let r=a.parentElement;r;r=r.parentElement)if(o.has(r))return true;return false};for(let a=0;a<y.length;a++){const o=y[a];const r=A[a];if(o.matches(tt)){x.add(o);B.push(r);continue}if(x.size&&R(o,x))continue;if(T.size&&R(o,T))continue;const u=k(o);if(u==="SOURCE"){r.removeAttribute("srcset");r.removeAttribute("src");continue}if(u==="SCRIPT"||u==="NOSCRIPT"||u==="LINK"&&/stylesheet|preload|modulepreload/i.test(o.getAttribute("rel")??"")||u==="STYLE"){B.push(r);continue}if(e.images){r.removeAttribute("background");const c=r.getAttribute("style");if(c&&Y(c)){const f=o.style;r.setAttribute("style",f?rt(f):lt(c))}}if(o.matches(xt)){const c=Ct(o);const f=c===o?a:y.lastIndexOf(c,a);if(f>=0&&!T.has(c))W(c,A[f]);if(!T.has(o))T.add(o);continue}if(u==="INPUT"){const c=o;const f=(c.type||"").toLowerCase();if(f==="checkbox"||f==="radio")c.checked?r.setAttribute("checked",""):r.removeAttribute("checked");else if(f==="range"||f==="color")r.setAttribute("value",c.value)}let h=$t(o);if(h==="image"&&!et.has(u)&&o.getAttribute("role")!=="img"&&Array.from(o.children).some(c=>k(c)!=="IMG")){if(e.images&&!o.closest(V))r.style.setProperty("background-image","none","important");h="none"}if(h==="none"){continue}const v=h!=="secure"&&o.closest(V)!==null;const b=h==="secure"||h==="input"&&e.inputs&&!v||h==="text"&&e.text&&!v||h==="image"&&e.images&&!v||h==="web"&&!v;if(b){W(o,r);continue}if(u==="INPUT"&&o.type==="image"){r.setAttribute("src",P);r.setAttribute("alt","")}else if(u==="INPUT")r.setAttribute("value",o.value);else if(u==="TEXTAREA")r.textContent=o.value;else if(u==="SELECT"){const c=o;Array.from(r.querySelectorAll("option")).forEach((f,mt)=>c.options[mt]?.selected?f.setAttribute("selected",""):f.removeAttribute("selected"))}else if(u==="IMG"){const c=ut(o);r.removeAttribute("srcset");r.setAttribute("alt","");r.setAttribute("src",c??P)}else if(u==="CANVAS"){let c=null;try{c=o.toDataURL("image/png")}catch{c=null}const f=document.createElement("img");f.setAttribute("src",c??P);f.setAttribute("style",`${r.getAttribute("style")??""};width:${o.clientWidth}px;height:${o.clientHeight}px`);f.className=r.getAttribute("class")??"";r.replaceWith(f)}else if(h==="image"&&o.style.backgroundImage.includes("url(")){const c=o.querySelector("img");const f=c?ut(c):null;r.style.backgroundImage=f?`url("${f}")`:"none"}}const F=new Set;for(const[a,o]of p){const r=a.parentElement;if(!r||It.has(k(r)))continue;if(r.closest("head"))continue;if(r.namespaceURI===q&&Ot.has(k(r)))continue;if(k(r)==="OPTION"&&r.closest("select"))continue;if(x.size&&(x.has(r)||R(r,x)))continue;if(T.has(r)||T.size&&R(r,T))continue;if(!e.text||r.closest(V))continue;if(r.namespaceURI===q){let b=r;while(b.parentElement&&b.parentElement.namespaceURI===q)b=b.parentElement;const c=y.indexOf(b);if(c>=0&&!T.has(b))W(b,A[c]);continue}const u=document.createRange();u.selectNodeContents(a);const h=G(u.getBoundingClientRect?.(),n,i);if(h){L.push(h);if(t&&!F.has(r))D.push({testID:ot(r,h),x:h.x,y:h.y,w:h.width,h:h.height})}if(h&&!F.has(r)){F.add(r);U++}const v=document.createElement("span");v.className="__buoy_text";for(const[b,c]of st)v.style.setProperty(b,c,"important");o.data=nt(o.data);o.replaceWith(v);v.appendChild(o)}for(let a=0;a<y.length;a++){const o=y[a];if(o===d||o===document.body)continue;if((o.scrollTop||o.scrollLeft)&&!x.has(o)){for(const r of Array.from(A[a].children)){const u=r.style;if(u)u.setProperty("translate",`${-o.scrollLeft}px ${-o.scrollTop}px`)}}}const $=window.scrollX;const C=window.scrollY;if($||C){const a=A[y.indexOf(document.body)];if(a)a.style.setProperty("translate",`${-$}px ${-C}px`);const o=document.body.getBoundingClientRect();for(let r=0;r<y.length;r++){const u=y[r];if(!(u instanceof HTMLElement)||!document.body.contains(u)||x.has(u))continue;if(getComputedStyle(u).position==="fixed"){A[r].style.setProperty("translate",`${$-(o.left+$)}px ${C-(o.top+C)}px`)}}}for(const a of B)a.remove();const _=m.querySelector("head")??m.insertBefore(document.createElement("head"),m.firstChild);while(_.firstChild)_.removeChild(_.firstChild);for(const a of Array.from(m.querySelectorAll("head")))if(a!==_)a.remove();_.setAttribute("style","display: none !important");const Q=document.createElement("style");Q.textContent=`@layer ${ct};
|
|
13
|
+
${Nt(e.images)}
|
|
14
|
+
@layer ${ct} {${Lt(e)}}`;_.appendChild(Q);m.style.setProperty("overflow","hidden");m.style.setProperty("width",`${n}px`);m.style.setProperty("height",`${i}px`);const ft=new XMLSerializer().serializeToString(m);const pt=`<svg xmlns="http://www.w3.org/2000/svg" width="${n}" height="${i}"><foreignObject x="0" y="0" width="${n}" height="${i}">${ft}</foreignObject></svg>`;return{svg:pt,w:n,h:i,rects:L,parts:D,boxes:U,buoyOpen:l,mainMs:performance.now()-s}}const Bt=5e3;function ht(e){let t;const s=new Promise((n,i)=>{t=setTimeout(()=>i(new E("the picture took too long")),Bt)});return Promise.race([Ut(e),s]).finally(()=>clearTimeout(t))}async function Ut(e){const t=Math.min(1,yt/Math.max(e.w,e.h));const s=new Image;s.decoding="async";const n=new Promise((p,O)=>{s.onload=()=>p();s.onerror=()=>O(new E("the browser couldn't draw the page"))});s.src=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e.svg)}`;await n;const i=document.createElement("canvas");i.width=Math.max(1,Math.round(e.w*t));i.height=Math.max(1,Math.round(e.h*t));const l=i.getContext("2d");if(!l)throw new E("no canvas in this browser");const d=performance.now();l.fillStyle="#ffffff";l.fillRect(0,0,i.width,i.height);l.drawImage(s,0,0,i.width,i.height);l.fillStyle=I;for(const p of e.rects)l.fillRect(p.x*t,p.y*t,p.width*t,p.height*t);const g=performance.now()-d;let m;try{m=await new Promise(p=>i.toBlob(p,"image/jpeg",gt))}catch{throw new E("this browser can't take pictures of pages")}if(!m)throw new E("couldn't make the picture");const y=new Uint8Array(await m.arrayBuffer());let A="";for(let p=0;p<y.length;p+=32768)A+=String.fromCharCode(...y.subarray(p,p+32768));return{jpeg:btoa(A),mainMs:g}}export class WebFrameEngine{enabled=false;pictures=true;customer=false;masks={text:true,images:true,inputs:true};videoFps=2;taps=[];video=[];last=null;marks=[];dropped={};frozen=false;busy=false;run=0;slowInRow=0;stoppedReason=null;lastReason=null;buttonLastT=null;downOnBuoy=false;tapTimer=null;videoTimer=null;videoOn=false;videoAlways=false;videoUntil=0;listening=false;inflight=null;pendingLast=null;easeUntil=0;configure(t={}){this.pictures=t.pictures!==false;this.customer=t.mode==="customer";this.masks={text:t.maskAllText!==false,images:t.maskAllImages!==false,inputs:t.maskAllInputs!==false};if(typeof t.videoFps==="number"&&t.videoFps>0)this.videoFps=Math.min(t.videoFps,10);this.enabled=true;this.listen();if(t.video==="always"&&!this.customer)this.startVideo(true);else if(this.videoAlways||t.video==="off"||this.customer)this.stopVideo()}async status(){return{available:true,enabled:this.enabled,mode:this.customer?"customer":"internal",stopped:this.stoppedReason}}listen(){if(this.listening||typeof document==="undefined")return;this.listening=true;document.addEventListener("pointerdown",this.onDown,{capture:true,passive:true});document.addEventListener("pointerup",this.onUp,{capture:true,passive:true})}onDown=t=>{this.downOnBuoy=false;const s=t.target instanceof Element?t.target:null;if(!s?.closest(tt))return;this.downOnBuoy=true;if(!this.enabled||this.frozen)return;if(this.tapTimer){clearTimeout(this.tapTimer);this.tapTimer=null}const n=l=>{if(l)this.buttonLastT=this.last?.t??null};if(!this.busy){void this.take("last").then(n);return}const i=(this.inflight??Promise.resolve(true)).catch(()=>false).then(()=>this.take("last")).then(n).finally(()=>{if(this.pendingLast===i)this.pendingLast=null});this.pendingLast=i};onUp=()=>{if(this.downOnBuoy){this.downOnBuoy=false;return}if(!this.enabled||!this.pictures||this.customer||this.frozen||this.stoppedReason)return;if(this.tapTimer)clearTimeout(this.tapTimer);this.tapTimer=setTimeout(()=>{this.tapTimer=null;void this.take("tap")},300)};startVideo(t){if(!this.enabled||this.customer)return;this.videoOn=true;this.videoAlways=t;if(!t){this.video=[];this.marks=[]}this.videoUntil=t?Infinity:S()+Z;if(this.videoTimer)clearInterval(this.videoTimer);this.videoTimer=setInterval(()=>{if(S()>this.videoUntil)return this.stopVideo();if(this.videoOn&&!this.frozen&&!this.stoppedReason&&!this.busy)void this.take("video")},1e3/this.videoFps)}stopVideo(){this.videoOn=false;this.videoAlways=false;if(this.videoTimer)clearInterval(this.videoTimer);this.videoTimer=null}mark(){if(this.videoOn)this.marks.push(S())}async captureLast(){for(let n=0;n<3&&(this.pendingLast||this.inflight);n++){await this.pendingLast?.catch(()=>false);await this.inflight?.catch(()=>false)}const t=this.buttonLastT;if(t!==null&&this.last?.t===t&&S()-t<1e4)return{ok:true,reason:null};const s=await this.take("last");return{ok:s,reason:s?null:this.lastReason??"no frame"}}async takeMedia(){this.frozen=true;const t=[...this.last?[this.last]:[],...this.taps,...this.video].sort((s,n)=>s.t-n.t);return{frames:t.map(s=>({...s})),marks:[...this.marks],dropped:Object.values(this.dropped).reduce((s,n)=>s+n,0),reasons:{...this.dropped},stopped:this.stoppedReason,lastAgeMs:this.last?S()-this.last.t:null,settings:{maskAllText:this.masks.text,maskAllImages:this.masks.images,maskAllInputs:this.masks.inputs}}}resume(){this.frozen=false}clear(){this.run++;this.busy=false;if(this.tapTimer)clearTimeout(this.tapTimer);this.tapTimer=null;this.inflight=null;this.pendingLast=null;this.taps=[];this.video=[];this.last=null;this.marks=[];this.dropped={};this.frozen=false;this.buttonLastT=null}stopAll(){this.run++;this.stopVideo();if(this.tapTimer)clearTimeout(this.tapTimer);this.tapTimer=null;if(this.listening){document.removeEventListener("pointerdown",this.onDown,{capture:true});document.removeEventListener("pointerup",this.onUp,{capture:true});this.listening=false}this.busy=false;this.enabled=false}async probe(){let t=null;let s=null;try{t=snapshotPage(this.masks,true)}catch(n){s=n instanceof Error?n.message:String(n)}if(!t)return{ok:false,reason:s,failed:s,parts:[],buoyOpen:false,jpeg:null,w:0,h:0};try{const{jpeg:n}=await ht(t);return{ok:true,reason:null,failed:null,parts:t.parts,buoyOpen:t.buoyOpen,jpeg:n,w:t.w,h:t.h}}catch(n){const i=n instanceof Error?n.message:String(n);return{ok:false,reason:i,failed:null,parts:t.parts,buoyOpen:t.buoyOpen,jpeg:null,w:t.w,h:t.h}}}drop(t){this.dropped[t]=(this.dropped[t]??0)+1;this.lastReason=t}take(t){const s=this.busy;const n=this.makeFrame(t);if(!s&&this.busy){const i=n.finally(()=>{if(this.inflight===i)this.inflight=null});this.inflight=i}return n}async makeFrame(t){if(!this.enabled||typeof document==="undefined")return false;if(t!=="last"&&this.frozen)return false;if(t==="tap"&&(this.customer||!this.pictures))return false;if(this.busy){this.drop("busy");return false}if(document.visibilityState==="hidden"){this.drop("app in background");return false}if(t!=="last"&&buoyScreenOpen()){this.drop("a Buoy screen was open");return false}if(t!=="last"&&S()<this.easeUntil){this.drop("skipped to keep the page fast");return false}const s=this.run;this.busy=true;let n;try{n=snapshotPage(this.masks)}catch(g){this.busy=false;this.drop(g instanceof E?g.message:"the page couldn't be checked");return false}if(n.buoyOpen&&t!=="last"){this.busy=false;this.drop("a Buoy screen was open");return false}const i=S();let l;let d=0;try{({jpeg:l,mainMs:d}=await ht(n))}catch(g){if(s===this.run){this.busy=false;this.drop(g instanceof E?g.message:"couldn't make the picture")}return false}if(s!==this.run)return false;this.busy=false;this.noteSpeed(n.mainMs+d);if(this.frozen)return false;this.keep({t:i,kind:t,w:n.w,h:n.h,jpeg:l,boxes:n.boxes});return true}noteSpeed(t){if(t>vt)this.easeUntil=S()+t*4;if(t<=Tt){this.slowInRow=0;return}if(++this.slowInRow>=3&&!this.stoppedReason){this.stoppedReason="Pictures stopped: each one took too long on this page.";this.stopVideo()}}keep(t){if(t.kind==="last")this.last=t;else if(t.kind==="tap"){this.taps.push(t);if(this.taps.length>bt)this.taps.shift()}else{this.video.push(t);const s=t.t-(this.videoAlways?wt:Z);let n=this.video.reduce((i,l)=>i+l.jpeg.length,0);while(this.video.length&&(this.video[0].t<s||n>At*4/3)){n-=this.video.shift().jpeg.length}}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{Bug as n,Lock as s,createToolFactory as i,defineToolPreset as t}from"@buoy-gg/shared-ui";import{BugLinksBody as c}from"./components/BugLinksBody";import{reportBug as l}from"./engine/reporter";import{jsx as r}from"react/jsx-runtime";export const BUG_LINKS_COLOR="#F43F5E";function a(){return null}export const reportBugToolPreset=t({id:"report-bug",name:"REPORT BUG",description:"One tap: save this bug and copy a locked link to it",slot:"both",color:BUG_LINKS_COLOR,icon:({size:o,color:e})=>r(n,{size:o,color:e??BUG_LINKS_COLOR}),component:a,launchMode:"toggle-only",onPress:o=>{o?.closeMenu?.();void l()}});export const bugLinksToolPreset=t({id:"bug-links",name:"BUG LINKS",description:"Report with a note, open a teammate's bug link, and see your links",slot:"both",color:BUG_LINKS_COLOR,icon:({size:o,color:e})=>r(s,{size:o,color:e??BUG_LINKS_COLOR}),component:c,modal:{subtitle:"locked bug reports your team can load"}});export const createBugLinksTool=i(bugLinksToolPreset);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{captureBugCase as f,createSyncAdapter as k}from"@buoy-gg/shared-ui";import{myLinks as c,subscribeMyLinks as y,loadMyLinks as d}from"../engine/linksStore";import{blockedCalls as m,endBugCase as g,leaveSandbox as w,loadAgain as b,loadBugCase as S,loadedCase as u,subscribeLoadedCase as v,undoBugCase as L}from"../engine/loader";import{openBugLink as h}from"../engine/opener";import{randomSource as I}from"../engine/randomBytes";import{addNote as B,deleteLink as A,reportBug as x,reportState as l,retryWaiting as E,sendLink as N,subscribeReportState as C}from"../engine/reporter";import{BUG_PRIORITIES as R}from"../engine/client";import{bugLinksRules as j,bugLinksServer as P,loadBugLinksSettings as p,setBugLinksServer as q,subscribeBugLinksSettings as M}from"../engine/settings";function a(e){return e&&typeof e==="object"?e:{}}function s(e,r){const t=e[r];return typeof t==="string"&&t.trim()?t.trim():void 0}function i(e,r=false){return{localId:e.localId,status:e.status,url:r?e.url:e.url?.split("#")[0],note:e.note,createdAt:e.createdAt,expiresAt:e.expiresAt,error:e.error,steps:e.header.steps,errors:e.header.errors,removed:e.removed.length,linkNeeded:e.status==="sent"&&!!e.wrapPending}}export function getBugLinksSnapshot(){const e=u();return{server:P(),randomSource:I(),report:(()=>{const r=l();return r?{...r,url:r.url?.split("#")[0]}:null})(),loaded:e?{...e,undo:void 0,source:e.source?.id?{id:e.source.id}:null}:null,links:c().slice(0,20).map(r=>i(r,false))}}export const bugLinksSyncAdapter=k({version:1,getSnapshot:getBugLinksSnapshot,subscribe(e){void d();void p();const r=[y(e),v(e),C(e),M(e)];return()=>r.forEach(t=>t())},actions:{getState:async()=>{await Promise.all([d(),p()]);return{...getBugLinksSnapshot(),blocked:await m()}},report:async e=>{const r=s(a(e),"priority");const t=R.find(o=>o===r);const n=await x({note:s(a(e),"note"),priority:t});if(!n)return{ok:false,error:l()?.message??"The report failed."};return{ok:n.status==="sent",...i(n,true)}},capture:async e=>{const r=await f({note:s(a(e),"note"),rules:j()});return{ok:true,case:r}},load:async e=>{const r=a(e);const t={replay:r.replay!==false,clock:r.clock!==false,sequence:r.sequence===true,restart:r.restart===true,otherApp:r.otherApp===true};const n=s(r,"link");if(n){const o=await h(n,t);return{ok:o.loaded,report:o}}if(r.case&&typeof r.case==="object"){const o=await S(r.case,{...t,source:{id:s(r,"id")}});return{ok:o.loaded,report:o}}throw new Error("load needs `link` (a whole bug link) or `case` (an opened bug case).")},loadAgain:async()=>{const e=await b();return{ok:e.loaded,report:e}},undo:async()=>{const e=await L();return{ok:!u()?.undoBlocked,steps:e}},end:async()=>g(),leaveSandbox:async()=>w(),listLinks:async()=>{await d();return{links:c().map(e=>i(e,true))}},retry:async e=>{const r=s(a(e),"localId");if(r)await N(r);else await E();return{ok:true,links:c().map(t=>i(t))}},addNote:async e=>{const r=a(e);const t=s(r,"localId");const n=typeof r.note==="string"?r.note:void 0;if(!t||n===void 0)throw new Error("addNote needs `localId` and `note`.");const o=await B(t,n);return o?{ok:true,...i(o)}:{ok:false,error:"No link with that localId."}},deleteLink:async e=>{const r=s(a(e),"localId");if(!r)throw new Error("deleteLink needs `localId`.");await A(r);return{ok:true}},setServer:async e=>{const r=a(e);const t=r.server===null?null:s(r,"server");if(t===void 0)throw new Error('setServer needs `server`, like "http://localhost:8898", or null.');return{ok:true,server:await q(t)}}}});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type Run = (label: string, work: () => Promise<string | void>) => Promise<void>;
|
|
2
|
+
export declare function TeamBugs({ run, busy, onLoaded }: {
|
|
3
|
+
run: Run;
|
|
4
|
+
busy: string | null;
|
|
5
|
+
onLoaded: () => void;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=TeamBugs.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function focusLink(localId: string | null): void;
|
|
2
|
+
export declare function focusedLink(): string | null;
|
|
3
|
+
export declare function wantsSignIn(): boolean;
|
|
4
|
+
export declare function askSignIn(next: boolean): void;
|
|
5
|
+
export declare function subscribeFocus(listener: () => void): () => void;
|
|
6
|
+
//# sourceMappingURL=focus.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const REPORT_TOAST_ID = "bug-links-toast";
|
|
2
|
+
/**
|
|
3
|
+
* The loaded-bug pill: the impersonate banner's look, floating at the top.
|
|
4
|
+
* The name opens the details. ↻ is Load again, the check after a fix. ✕ is
|
|
5
|
+
* Restore my app (Undo), or End once Undo can no longer finish.
|
|
6
|
+
*/
|
|
7
|
+
export declare function BugLinksOverlay(): import("react").JSX.Element | null;
|
|
8
|
+
/** Show the toast when a report starts. Tuck the clock chip while a case is loaded. Idempotent. */
|
|
9
|
+
export declare function watchBugLinksOverlays(): void;
|
|
10
|
+
//# sourceMappingURL=overlays.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type Store<S> = {
|
|
2
|
+
subscribe: (l: () => void) => () => void;
|
|
3
|
+
getSnapshot?: () => S;
|
|
4
|
+
getState?: () => S;
|
|
5
|
+
};
|
|
6
|
+
type ImpersonateSnapshot = {
|
|
7
|
+
isActive?: boolean;
|
|
8
|
+
currentUser?: unknown;
|
|
9
|
+
showBanner?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type ScenariosSnapshot = {
|
|
12
|
+
active?: unknown;
|
|
13
|
+
};
|
|
14
|
+
export declare function subscribeTopBanners(listener: () => void): () => void;
|
|
15
|
+
export declare function impersonateBannerUp(): boolean;
|
|
16
|
+
export declare function scenarioBannerUp(): boolean;
|
|
17
|
+
/** One number for useSyncExternalStore: which banners are up, as bits. */
|
|
18
|
+
export declare function topBannersKey(): number;
|
|
19
|
+
/** Impersonate's pill spot, which the loaded pill shares when it's alone. */
|
|
20
|
+
export declare const PILL_TOP = 50;
|
|
21
|
+
/** One pill's height plus a gap. */
|
|
22
|
+
export declare const PILL_STACK = 46;
|
|
23
|
+
/** Where the loaded pill goes: under every banner that's up. */
|
|
24
|
+
export declare function pillTop(key: number, safeTop: number): number;
|
|
25
|
+
/** Tests pass fake stores, or null for "not installed". */
|
|
26
|
+
export declare function setTopBannerStoresForTests(stores: {
|
|
27
|
+
impersonate?: Store<ImpersonateSnapshot> | null;
|
|
28
|
+
scenarios?: Store<ScenariosSnapshot> | null;
|
|
29
|
+
}): void;
|
|
30
|
+
/** Back to real lookups, as the app does them. */
|
|
31
|
+
export declare function resetTopBannersForTests(): void;
|
|
32
|
+
/** Old name, kept for the tests written before scenarios counted. */
|
|
33
|
+
export declare function setImpersonateStoreForTests(store: Store<ImpersonateSnapshot> | null): void;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=topBanners.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface UploadResult {
|
|
2
|
+
id: string;
|
|
3
|
+
editToken: string;
|
|
4
|
+
expiresAt: string | null;
|
|
5
|
+
cardRevision?: number;
|
|
6
|
+
/** "saved", "none" (no wrap sent), or "refused" (that team key is retired). */
|
|
7
|
+
wrapStatus?: string;
|
|
8
|
+
/** When the case has no wrap: whether the team has keys set up (a current key, or all retired). */
|
|
9
|
+
teamKeys?: boolean;
|
|
10
|
+
/** The team (or person) the case belongs to, from a server that says. */
|
|
11
|
+
owner?: string;
|
|
12
|
+
}
|
|
13
|
+
/** What the phone sends besides the blob, for the team list. */
|
|
14
|
+
export interface TeamExtras {
|
|
15
|
+
/** The locked card, base64url. */
|
|
16
|
+
card?: string;
|
|
17
|
+
/** The team wrap, as JSON. */
|
|
18
|
+
wrap?: string;
|
|
19
|
+
/** The card revision the card was sealed for (a note edit). */
|
|
20
|
+
cardRevision?: number;
|
|
21
|
+
/** The team (or account) this case was first meant for. */
|
|
22
|
+
owner?: string;
|
|
23
|
+
/** How soon it needs a fix, the tester's pick. Left out: normal. */
|
|
24
|
+
priority?: BugPriority;
|
|
25
|
+
}
|
|
26
|
+
/** How soon a bug needs a fix, most urgent first. */
|
|
27
|
+
export type BugPriority = "urgent" | "high" | "normal" | "low";
|
|
28
|
+
export declare const BUG_PRIORITIES: readonly BugPriority[];
|
|
29
|
+
export type BugLinksErrorCode = "redirected" | "cancelled" | "offline" | "signed_out" | "needs_pro" | "too_big" | "not_found" | "expired" | "rate_limited"
|
|
30
|
+
/** The case changed on the server (another edit, or a newer card). */
|
|
31
|
+
| "conflict"
|
|
32
|
+
/** The sender's team changed since this case was first sent. */
|
|
33
|
+
| "team_changed"
|
|
34
|
+
/** A teammate set the bug's priority, so the sender's own pick can't change it now. */
|
|
35
|
+
| "priority_set" | "server";
|
|
36
|
+
export declare class BugLinksHttpError extends Error {
|
|
37
|
+
readonly code: BugLinksErrorCode;
|
|
38
|
+
readonly status?: number | undefined;
|
|
39
|
+
/** Worth trying again later (offline, rate limited, a 5xx). */
|
|
40
|
+
readonly retry: boolean;
|
|
41
|
+
/** The server's answer, for a 409's revisions. */
|
|
42
|
+
readonly detail: Record<string, unknown>;
|
|
43
|
+
constructor(code: BugLinksErrorCode, message: string, status?: number | undefined,
|
|
44
|
+
/** Worth trying again later (offline, rate limited, a 5xx). */
|
|
45
|
+
retry?: boolean,
|
|
46
|
+
/** The server's answer, for a 409's revisions. */
|
|
47
|
+
detail?: Record<string, unknown>);
|
|
48
|
+
}
|
|
49
|
+
export declare function uploadBlob(origin: string, credential: string, blob: Uint8Array, options?: {
|
|
50
|
+
uploadId?: string;
|
|
51
|
+
editToken?: string;
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
extras?: TeamExtras;
|
|
54
|
+
}): Promise<UploadResult>;
|
|
55
|
+
/** expiresAt: a date, null for "never", or undefined when the server didn't say. */
|
|
56
|
+
export declare function replaceBlob(origin: string, id: string, editToken: string, blob: Uint8Array, extras?: TeamExtras): Promise<{
|
|
57
|
+
expiresAt: string | null | undefined;
|
|
58
|
+
cardRevision?: number;
|
|
59
|
+
}>;
|
|
60
|
+
/** A late team wrap, with the edit token. Sending the same wrap again is fine. */
|
|
61
|
+
export declare function sendWrap(origin: string, id: string, editToken: string, wrap: string, caseTag: string): Promise<void>;
|
|
62
|
+
/** The sender's own priority pick, after the send, with the edit token. A 409 means a teammate set one. */
|
|
63
|
+
export declare function sendPriority(origin: string, id: string, editToken: string, priority: BugPriority): Promise<void>;
|
|
64
|
+
/** The team's public keys: roots, signed versions, and the one to use now. Null when the server has no team keys route. */
|
|
65
|
+
/** A team list call with this person's sign-in: JSON in, JSON out. */
|
|
66
|
+
export declare function teamJson<T>(origin: string, path: string, credential: string, init?: {
|
|
67
|
+
method?: string;
|
|
68
|
+
body?: unknown;
|
|
69
|
+
}): Promise<T>;
|
|
70
|
+
export declare function fetchTeamKeys(origin: string, credential: string): Promise<unknown | null>;
|
|
71
|
+
export declare function deleteRemote(origin: string, id: string, auth: {
|
|
72
|
+
editToken?: string;
|
|
73
|
+
credential?: string;
|
|
74
|
+
}): Promise<void>;
|
|
75
|
+
/** Take back a send by its upload id, when its answer (the case id) never came. */
|
|
76
|
+
export declare function deleteUpload(origin: string, uploadId: string, credential: string): Promise<void>;
|
|
77
|
+
export declare function fetchBlob(origin: string, id: string, credential: string): Promise<Uint8Array>;
|
|
78
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** For tests and for hosts that hold the credential somewhere else. */
|
|
2
|
+
export declare function setBugLinksCredentialProvider(next: (() => string | null) | null): void;
|
|
3
|
+
export declare function currentCredential(): string | null;
|
|
4
|
+
/**
|
|
5
|
+
* A short fingerprint of who a credential speaks for: the person in a sign-in
|
|
6
|
+
* token (it refreshes, the person doesn't), or the key itself. Not a secret
|
|
7
|
+
* and not proof of anything; it only tells "still the same sign-in" apart from
|
|
8
|
+
* "someone else signed in since this case was made".
|
|
9
|
+
*/
|
|
10
|
+
export declare function credentialOwner(token: string | null): string | null;
|
|
11
|
+
//# sourceMappingURL=credential.d.ts.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This phone's own bug links, kept on the phone.
|
|
3
|
+
*
|
|
4
|
+
* A link's key lives only in the link, so if the chat message is lost the
|
|
5
|
+
* case would be too. The reporter's phone keeps every link it made, key
|
|
6
|
+
* included, so they can find it again in the tool.
|
|
7
|
+
*
|
|
8
|
+
* Each locked blob is saved under its own storage keys, in 1 MB chunks
|
|
9
|
+
* (Android's AsyncStorage refuses one huge value), only while it is still
|
|
10
|
+
* needed: until it is sent, and for the few newest links so a note can be
|
|
11
|
+
* added later.
|
|
12
|
+
*/
|
|
13
|
+
import { type BugCardFields, type BugCaseHeader, type ScrubbedItem } from "@buoy-gg/shared-ui";
|
|
14
|
+
import type { BugPriority } from "./client";
|
|
15
|
+
export type MyBugLinkStatus =
|
|
16
|
+
/** Being captured, locked or sent right now. */
|
|
17
|
+
"sending"
|
|
18
|
+
/** Saved on the phone; Buoy will send it when it can (offline, signed out). */
|
|
19
|
+
| "waiting" | "sent"
|
|
20
|
+
/** Won't send without a change (needs Pro, too big). */
|
|
21
|
+
| "failed"
|
|
22
|
+
/** Deleted here; the server copy still has to go. */
|
|
23
|
+
| "deleting";
|
|
24
|
+
export interface MyBugLink {
|
|
25
|
+
localId: string;
|
|
26
|
+
/** Server id, once sent. */
|
|
27
|
+
id?: string;
|
|
28
|
+
/** base64url key for the part of the link after `#`. */
|
|
29
|
+
key: string;
|
|
30
|
+
editToken?: string;
|
|
31
|
+
server?: string;
|
|
32
|
+
/** The full link, key included, once sent. */
|
|
33
|
+
url?: string;
|
|
34
|
+
header: BugCaseHeader;
|
|
35
|
+
note?: string;
|
|
36
|
+
/** The tester's pick. Missing: normal. */
|
|
37
|
+
priority?: BugPriority;
|
|
38
|
+
/** A priority change made after the send that the server hasn't taken yet. */
|
|
39
|
+
priorityPending?: boolean;
|
|
40
|
+
/** The screen the bug was on (scrubbed, like the case), for a title when there's no note. */
|
|
41
|
+
screen?: string;
|
|
42
|
+
createdAt: string;
|
|
43
|
+
expiresAt?: string | null;
|
|
44
|
+
status: MyBugLinkStatus;
|
|
45
|
+
error?: string;
|
|
46
|
+
errorCode?: string;
|
|
47
|
+
/** A send cancelled with no answer was taken back once; once more makes sure a slow upload didn't land after. */
|
|
48
|
+
takenBackOnce?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The server an upload last went to, set right before it. With no case id,
|
|
51
|
+
* it says where a copy may be that no answer told us about. (`server` is
|
|
52
|
+
* only where the case is meant to go.)
|
|
53
|
+
*/
|
|
54
|
+
triedServer?: string;
|
|
55
|
+
/** Failed sends so far; the next wait grows with it. */
|
|
56
|
+
attempts: number;
|
|
57
|
+
/** Who made it: a short fingerprint of the sign-in, so a retry never sends it as someone else. */
|
|
58
|
+
owner?: string;
|
|
59
|
+
/** A note was added and the server copy still has the old case. */
|
|
60
|
+
needsPut?: boolean;
|
|
61
|
+
/** Goes up with each new blob, so a send clears needsPut only for the blob it sent. */
|
|
62
|
+
blobVersion?: number;
|
|
63
|
+
nextTryAt?: number;
|
|
64
|
+
/** What the scrubber took out: paths and kinds, never the values. */
|
|
65
|
+
removed: ScrubbedItem[];
|
|
66
|
+
/** Team keys: the case's tag and key proof hash (also in the blob's header). Missing on links made before team keys. */
|
|
67
|
+
caseTag?: string;
|
|
68
|
+
keyProofHash?: string;
|
|
69
|
+
/** What the team list's card shows, from the scrubbed case. Locked only when it's sent. */
|
|
70
|
+
card?: BugCardFields;
|
|
71
|
+
/** The card revision the server has. A note edit seals the next one. */
|
|
72
|
+
cardRevision?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Sent, but not yet locked to the team ("Link needed" in the team list).
|
|
75
|
+
* Buoy sends the wrap later. Such a link is never trimmed, since its key
|
|
76
|
+
* lives only here and in the link until then.
|
|
77
|
+
*/
|
|
78
|
+
wrapPending?: boolean;
|
|
79
|
+
/** The team (or account) this case was first sent for. Every retry says so, so it never lands in a new team. */
|
|
80
|
+
teamOwner?: string;
|
|
81
|
+
/** Why the wrap waits, for the Bug links screen. */
|
|
82
|
+
wrapWait?: {
|
|
83
|
+
code: string;
|
|
84
|
+
message: string;
|
|
85
|
+
teamKeyId?: string;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export declare function loadMyLinks(): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Before a first upload on a phone: puts a row's unsaved change on disk.
|
|
91
|
+
* False if it still can't be saved. A row that never reached disk, or a
|
|
92
|
+
* browser tab (which checks each change itself), needs nothing.
|
|
93
|
+
*/
|
|
94
|
+
export declare function saveRowNow(localId: string): Promise<boolean>;
|
|
95
|
+
/** Keep an upload's answer that the browser couldn't save, until a list save works. */
|
|
96
|
+
export declare function holdUploadResult(localId: string, facts: Partial<MyBugLink>): void;
|
|
97
|
+
export declare function hasHeldUploadResult(localId: string): boolean;
|
|
98
|
+
/** Try to save the list now (and with it any held upload answers). */
|
|
99
|
+
export declare function saveHeldUploadResults(): Promise<boolean>;
|
|
100
|
+
/** True for a row of this tab's that never reached disk (see unsavedNew). */
|
|
101
|
+
export declare function isUnsavedNew(localId: string): boolean;
|
|
102
|
+
/** Another tab changed the list: show its links here too. */
|
|
103
|
+
export declare function reloadMyLinks(): Promise<void>;
|
|
104
|
+
export declare function myLinks(): readonly MyBugLink[];
|
|
105
|
+
/**
|
|
106
|
+
* Whether this phone still has the case, so a note can go in. Only the
|
|
107
|
+
* newest sent links keep it (a note waiting to go keeps it too).
|
|
108
|
+
*/
|
|
109
|
+
export declare function canAddNote(link: MyBugLink): boolean;
|
|
110
|
+
export declare function findMyLink(localId: string): MyBugLink | undefined;
|
|
111
|
+
export declare function subscribeMyLinks(listener: () => void): () => void;
|
|
112
|
+
/** Save a new link and its blob. False when the blob only lives in memory (storage full or gone). */
|
|
113
|
+
export declare function addMyLink(link: MyBugLink, blob: string): Promise<boolean>;
|
|
114
|
+
/**
|
|
115
|
+
* `patch` may be a function of the row as it is in the write queue, for a
|
|
116
|
+
* check that must not race other writes. It returns null to change nothing.
|
|
117
|
+
*/
|
|
118
|
+
export declare function updateMyLink(localId: string, patch: Partial<MyBugLink> | ((link: MyBugLink) => Partial<MyBugLink> | null)): Promise<MyBugLink | undefined>;
|
|
119
|
+
/** False when the removal didn't reach disk: the row stays, here too, so a retry can finish it. */
|
|
120
|
+
export declare function removeMyLink(localId: string): Promise<boolean>;
|
|
121
|
+
export declare function readBlob(localId: string): Promise<string | null>;
|
|
122
|
+
/**
|
|
123
|
+
* The case's blob, or null when it is gone. Throws when a read fails: the
|
|
124
|
+
* blob may still be there, so a send waits and tries again instead of
|
|
125
|
+
* calling it lost (Codex r73).
|
|
126
|
+
*/
|
|
127
|
+
export declare function readBlobChecked(localId: string): Promise<string | null>;
|
|
128
|
+
/** `rowVersion`: the row's blobVersion this blob belongs to. */
|
|
129
|
+
export declare function writeBlob(localId: string, blob: string, rowVersion?: number): Promise<boolean>;
|
|
130
|
+
/** Tests only. */
|
|
131
|
+
export declare function resetMyLinksForTests(): void;
|
|
132
|
+
//# sourceMappingURL=linksStore.d.ts.map
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load a bug case into this app, so the bug shows up here.
|
|
3
|
+
*
|
|
4
|
+
* check the case -> save an undo point -> sandbox the web -> set the clock
|
|
5
|
+
* -> load every state source -> go to the screen -> load late stores -> report
|
|
6
|
+
*
|
|
7
|
+
* Every piece goes through the same tool adapters MCP uses (Network, Clock,
|
|
8
|
+
* Routes) and the Time Machine state providers, so this package imports no
|
|
9
|
+
* tool. A tool that isn't installed is a line in the report, never an error.
|
|
10
|
+
*
|
|
11
|
+
* A case is untrusted: it came from someone else's phone. Its shape is
|
|
12
|
+
* checked before anything changes, its route must be an app path, its state
|
|
13
|
+
* reaches providers only by their own ids, and its text never runs.
|
|
14
|
+
*
|
|
15
|
+
* With replay on (the default), nothing loads unless every real web call can
|
|
16
|
+
* be blocked first: a case's state with live servers would send someone
|
|
17
|
+
* else's data. Undo keeps calls blocked until the app's own state is back.
|
|
18
|
+
*
|
|
19
|
+
* The case and the undo point are saved on the phone (if they fit), so both
|
|
20
|
+
* survive a reload. That is the fix loop: load the bug, change the code,
|
|
21
|
+
* reload, tap Load again, and see if it's gone.
|
|
22
|
+
*/
|
|
23
|
+
import { type BugCase } from "@buoy-gg/shared-ui";
|
|
24
|
+
import { BLOCK_RULE_NAME } from "./sandbox";
|
|
25
|
+
export interface LoadStep {
|
|
26
|
+
part: string;
|
|
27
|
+
ok: boolean;
|
|
28
|
+
detail: string;
|
|
29
|
+
/** Loaded fine, but worth knowing (this phone differs from the reporter's). Never counts as a failure. */
|
|
30
|
+
warn?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface LoadReport {
|
|
33
|
+
loadedAt: string;
|
|
34
|
+
steps: LoadStep[];
|
|
35
|
+
/** Recorded answers installed, and calls the case had no usable answer for. */
|
|
36
|
+
replay: {
|
|
37
|
+
answers: number;
|
|
38
|
+
skipped: number;
|
|
39
|
+
active: boolean;
|
|
40
|
+
} | null;
|
|
41
|
+
/** False when nothing was loaded (replay was asked for and couldn't start). */
|
|
42
|
+
loaded: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface LoadedCase {
|
|
45
|
+
/** Where it came from: the link's id. Never the key. */
|
|
46
|
+
source: {
|
|
47
|
+
id?: string;
|
|
48
|
+
} | null;
|
|
49
|
+
/** Ties the saved copy to this load, so Load again never opens another case. */
|
|
50
|
+
loadId: string;
|
|
51
|
+
/** The case is saved too, so "Load again" works after a reload. */
|
|
52
|
+
canLoadAgain: boolean;
|
|
53
|
+
title: string;
|
|
54
|
+
createdAt: string;
|
|
55
|
+
report: LoadReport;
|
|
56
|
+
undo: UndoPoint;
|
|
57
|
+
options: Pick<LoadOptions, "replay" | "clock" | "sequence" | "otherApp">;
|
|
58
|
+
/** Undo stopped short: the bar offers End next to it. */
|
|
59
|
+
undoBlocked?: boolean;
|
|
60
|
+
/** The app's own state from before is gone: End is the only way out. */
|
|
61
|
+
undoGone?: boolean;
|
|
62
|
+
/** Why the last Undo, End or Real calls didn't finish. The bar shows it. */
|
|
63
|
+
notice?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Saved before a load changes anything, and replaced when it's done. A
|
|
66
|
+
* record still "loading" after a restart is a load that died partway:
|
|
67
|
+
* Restore puts the app back from the undo copy.
|
|
68
|
+
*/
|
|
69
|
+
phase?: "loading";
|
|
70
|
+
/** Web calls are blocked for this case. Other browser tabs read it (Network's caseRecordBlocks). */
|
|
71
|
+
block?: boolean;
|
|
72
|
+
}
|
|
73
|
+
interface UndoPoint {
|
|
74
|
+
/** Ties the state saved on disk to this undo point. */
|
|
75
|
+
undoId: string;
|
|
76
|
+
route: string | null;
|
|
77
|
+
network: {
|
|
78
|
+
enabled: boolean;
|
|
79
|
+
rules: unknown[];
|
|
80
|
+
} | null;
|
|
81
|
+
clock: {
|
|
82
|
+
active: boolean;
|
|
83
|
+
virtualIso?: string;
|
|
84
|
+
rate?: number;
|
|
85
|
+
} | null;
|
|
86
|
+
/** The app's state saved apart (see stateKey), so undo works after a reload. */
|
|
87
|
+
stateSaved: boolean;
|
|
88
|
+
}
|
|
89
|
+
export interface LoadOptions {
|
|
90
|
+
/** Answer web calls from the recording and block the rest. Default true. */
|
|
91
|
+
replay?: boolean;
|
|
92
|
+
/** Set the app clock to the reporter's time. Default true. */
|
|
93
|
+
clock?: boolean;
|
|
94
|
+
/** Load a case from another app anyway (its state may not fit this app). Default false. */
|
|
95
|
+
otherApp?: boolean;
|
|
96
|
+
/** Where the case came from (the link's id), for the bar. */
|
|
97
|
+
source?: {
|
|
98
|
+
id?: string;
|
|
99
|
+
} | null;
|
|
100
|
+
/** Give each call its recorded answers in order, not just the newest. For polling and retry bugs. */
|
|
101
|
+
sequence?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Restart the app's JS after loading, then load the case again. For apps
|
|
104
|
+
* that read a value only at start (a feature flag, a config): the saved
|
|
105
|
+
* state is in place before they read it.
|
|
106
|
+
*/
|
|
107
|
+
restart?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Asked when the load's turn comes, before anything changes. False stops
|
|
110
|
+
* it: the team list says no once its screen closed or the sign-in changed.
|
|
111
|
+
*/
|
|
112
|
+
stillWanted?: () => boolean;
|
|
113
|
+
}
|
|
114
|
+
/** True while a load, undo or end is running; the UI holds its buttons. */
|
|
115
|
+
export declare function bugCaseBusy(): boolean;
|
|
116
|
+
export declare function loadedCase(): LoadedCase | null;
|
|
117
|
+
export declare function subscribeLoadedCase(listener: () => void): () => void;
|
|
118
|
+
/** An app path, never a URL to somewhere else. */
|
|
119
|
+
export declare function safeRoute(href: unknown): string | null;
|
|
120
|
+
/** Check a case's shape (the same check unlock runs) before anything changes. */
|
|
121
|
+
export declare function checkCase(kase: unknown): BugCase;
|
|
122
|
+
export declare function loadBugCase(input: unknown, options?: LoadOptions): Promise<LoadReport>;
|
|
123
|
+
/** "Oct 22, 8:52:02 PM" in this phone's time zone, for people; the ISO time when the platform can't format. */
|
|
124
|
+
export declare function readableTime(iso: string): string;
|
|
125
|
+
/** Which recorded calls have no answer, and why: the first few, so the pill's details can say. */
|
|
126
|
+
export declare function skippedText(skipped: {
|
|
127
|
+
method: string;
|
|
128
|
+
url: string;
|
|
129
|
+
reason: string;
|
|
130
|
+
}[]): string;
|
|
131
|
+
/** Stop answering from the recording; the app talks to real servers again. */
|
|
132
|
+
export declare function leaveSandbox(): Promise<{
|
|
133
|
+
ok: boolean;
|
|
134
|
+
detail?: string;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Put the app back the way it was before the first case loaded. Real web
|
|
138
|
+
* calls come back last, after the app's own state, clock and screen: while
|
|
139
|
+
* the case's state is still in the app, it must not reach a real server.
|
|
140
|
+
*/
|
|
141
|
+
export declare function undoBugCase(): Promise<LoadStep[]>;
|
|
142
|
+
/**
|
|
143
|
+
* Stop being "a loaded case" without putting the app's state back, for when
|
|
144
|
+
* the old state is gone (an app restart before the undo point was saved).
|
|
145
|
+
* The clock and Network rules still go back; the app keeps what it has.
|
|
146
|
+
*/
|
|
147
|
+
export declare function endBugCase(): Promise<{
|
|
148
|
+
ok: boolean;
|
|
149
|
+
detail?: string;
|
|
150
|
+
}>;
|
|
151
|
+
/** Tests: forget what's in memory, as an app restart would; what's on disk stays. */
|
|
152
|
+
export declare function forgetLoadedForTests(): void;
|
|
153
|
+
/** After a reload: a case was loaded before. Bring back the bar, so it can be undone or loaded again. */
|
|
154
|
+
export declare function restoreLoadedAfterReload(): Promise<LoadedCase | null>;
|
|
155
|
+
/**
|
|
156
|
+
* Load the same case again: the check after a fix. Reload the app's code
|
|
157
|
+
* first (Fast Refresh or a full reload), then call this.
|
|
158
|
+
*/
|
|
159
|
+
export declare function loadAgain(): Promise<LoadReport>;
|
|
160
|
+
export interface BlockedCall {
|
|
161
|
+
method: string;
|
|
162
|
+
url: string;
|
|
163
|
+
at: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The calls the sandbox blocked since this case loaded: the app asked for
|
|
167
|
+
* something the reporter's phone never did, so there was no answer to give.
|
|
168
|
+
* A teammate sees these instead of an app that "just broke".
|
|
169
|
+
*/
|
|
170
|
+
export declare function blockedCalls(): Promise<BlockedCall[]>;
|
|
171
|
+
export { BLOCK_RULE_NAME };
|
|
172
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a bug link on this phone: fetch the locked case, open it with the key
|
|
3
|
+
* from the link, and load it.
|
|
4
|
+
*
|
|
5
|
+
* Fetching needs a person's Buoy sign-in on the team (an app's own key can
|
|
6
|
+
* send cases but not read them). The key stays on the phone.
|
|
7
|
+
*/
|
|
8
|
+
import { type BugCase } from "@buoy-gg/shared-ui";
|
|
9
|
+
import { type LoadOptions, type LoadReport } from "./loader";
|
|
10
|
+
/**
|
|
11
|
+
* Fetching sends this person's Buoy sign-in, so it only ever goes to buoy.gg
|
|
12
|
+
* or the server set in the tool. A link from anywhere else could be a trap
|
|
13
|
+
* that collects sign-ins.
|
|
14
|
+
*/
|
|
15
|
+
export declare function trustedOrigin(link: string): string;
|
|
16
|
+
export declare function fetchBugLink(link: string): Promise<{
|
|
17
|
+
kase: BugCase;
|
|
18
|
+
id: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function openBugLink(link: string, options?: Omit<LoadOptions, "source">): Promise<LoadReport>;
|
|
21
|
+
//# sourceMappingURL=opener.d.ts.map
|