@buoy-gg/sentry 0.0.0-stage → 7.0.77

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.
Files changed (113) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +69 -2
  3. package/lib/commonjs/SentryRoot.js +1 -0
  4. package/lib/commonjs/audit/auditSource.js +1 -0
  5. package/lib/commonjs/audit/pii.js +1 -0
  6. package/lib/commonjs/audit/pii.test.js +1 -0
  7. package/lib/commonjs/audit/rules.js +1 -0
  8. package/lib/commonjs/audit/rules.test.js +1 -0
  9. package/lib/commonjs/capture/attach.js +1 -0
  10. package/lib/commonjs/capture/attach.test.js +1 -0
  11. package/lib/commonjs/capture/sentrySDK.js +1 -0
  12. package/lib/commonjs/capture/sentrySDK.web.js +1 -0
  13. package/lib/commonjs/components/SentryIcon.js +1 -0
  14. package/lib/commonjs/components/SentryIcon.native.js +1 -0
  15. package/lib/commonjs/components/SentryModal.js +1 -0
  16. package/lib/commonjs/devtools/SentryAuditView.js +1 -0
  17. package/lib/commonjs/devtools/SentryBody.js +1 -0
  18. package/lib/commonjs/devtools/SentryCostView.js +1 -0
  19. package/lib/commonjs/devtools/SentryDropsView.js +1 -0
  20. package/lib/commonjs/devtools/SentryHeader.js +1 -0
  21. package/lib/commonjs/devtools/SentryToolView.js +1 -0
  22. package/lib/commonjs/devtools/attachStatus.js +1 -0
  23. package/lib/commonjs/devtools/layout.js +1 -0
  24. package/lib/commonjs/devtools/useSentryController.js +1 -0
  25. package/lib/commonjs/drops/dropStore.js +1 -0
  26. package/lib/commonjs/drops/dropStore.test.js +1 -0
  27. package/lib/commonjs/drops/observe.js +1 -0
  28. package/lib/commonjs/drops/types.js +1 -0
  29. package/lib/commonjs/index.js +1 -0
  30. package/lib/commonjs/package.json +1 -0
  31. package/lib/commonjs/preset.js +1 -0
  32. package/lib/commonjs/quota/plans.js +1 -0
  33. package/lib/commonjs/quota/usage.js +1 -0
  34. package/lib/commonjs/quota/usage.test.js +1 -0
  35. package/lib/commonjs/store/sanitize.js +1 -0
  36. package/lib/commonjs/store/sentryEnvelopeStore.js +1 -0
  37. package/lib/commonjs/sync/sentrySyncAdapter.js +1 -0
  38. package/lib/commonjs/sync/sentrySyncAdapter.test.js +1 -0
  39. package/lib/commonjs/types.js +1 -0
  40. package/lib/module/SentryRoot.js +1 -0
  41. package/lib/module/audit/auditSource.js +1 -0
  42. package/lib/module/audit/pii.js +1 -0
  43. package/lib/module/audit/pii.test.js +1 -0
  44. package/lib/module/audit/rules.js +1 -0
  45. package/lib/module/audit/rules.test.js +1 -0
  46. package/lib/module/capture/attach.js +1 -0
  47. package/lib/module/capture/attach.test.js +1 -0
  48. package/lib/module/capture/sentrySDK.js +1 -0
  49. package/lib/module/capture/sentrySDK.web.js +1 -0
  50. package/lib/module/components/SentryIcon.js +1 -0
  51. package/lib/module/components/SentryIcon.native.js +1 -0
  52. package/lib/module/components/SentryModal.js +1 -0
  53. package/lib/module/devtools/SentryAuditView.js +1 -0
  54. package/lib/module/devtools/SentryBody.js +1 -0
  55. package/lib/module/devtools/SentryCostView.js +1 -0
  56. package/lib/module/devtools/SentryDropsView.js +1 -0
  57. package/lib/module/devtools/SentryHeader.js +1 -0
  58. package/lib/module/devtools/SentryToolView.js +1 -0
  59. package/lib/module/devtools/attachStatus.js +1 -0
  60. package/lib/module/devtools/layout.js +1 -0
  61. package/lib/module/devtools/useSentryController.js +1 -0
  62. package/lib/module/drops/dropStore.js +1 -0
  63. package/lib/module/drops/dropStore.test.js +1 -0
  64. package/lib/module/drops/observe.js +1 -0
  65. package/lib/module/drops/types.js +1 -0
  66. package/lib/module/index.js +1 -0
  67. package/lib/module/preset.js +1 -0
  68. package/lib/module/quota/plans.js +1 -0
  69. package/lib/module/quota/usage.js +1 -0
  70. package/lib/module/quota/usage.test.js +1 -0
  71. package/lib/module/store/sanitize.js +1 -0
  72. package/lib/module/store/sentryEnvelopeStore.js +1 -0
  73. package/lib/module/sync/sentrySyncAdapter.js +1 -0
  74. package/lib/module/sync/sentrySyncAdapter.test.js +1 -0
  75. package/lib/module/types.js +1 -0
  76. package/lib/typescript/SentryRoot.d.ts +17 -0
  77. package/lib/typescript/audit/auditSource.d.ts +18 -0
  78. package/lib/typescript/audit/pii.d.ts +32 -0
  79. package/lib/typescript/audit/pii.test.d.ts +8 -0
  80. package/lib/typescript/audit/rules.d.ts +57 -0
  81. package/lib/typescript/audit/rules.test.d.ts +7 -0
  82. package/lib/typescript/capture/attach.d.ts +18 -0
  83. package/lib/typescript/capture/attach.test.d.ts +8 -0
  84. package/lib/typescript/capture/sentrySDK.d.ts +4 -0
  85. package/lib/typescript/capture/sentrySDK.web.d.ts +4 -0
  86. package/lib/typescript/components/SentryIcon.d.ts +3 -0
  87. package/lib/typescript/components/SentryIcon.native.d.ts +3 -0
  88. package/lib/typescript/components/SentryModal.d.ts +16 -0
  89. package/lib/typescript/devtools/SentryAuditView.d.ts +19 -0
  90. package/lib/typescript/devtools/SentryBody.d.ts +15 -0
  91. package/lib/typescript/devtools/SentryCostView.d.ts +20 -0
  92. package/lib/typescript/devtools/SentryDropsView.d.ts +17 -0
  93. package/lib/typescript/devtools/SentryHeader.d.ts +21 -0
  94. package/lib/typescript/devtools/SentryToolView.d.ts +14 -0
  95. package/lib/typescript/devtools/attachStatus.d.ts +13 -0
  96. package/lib/typescript/devtools/layout.d.ts +16 -0
  97. package/lib/typescript/devtools/useSentryController.d.ts +69 -0
  98. package/lib/typescript/drops/dropStore.d.ts +48 -0
  99. package/lib/typescript/drops/dropStore.test.d.ts +7 -0
  100. package/lib/typescript/drops/observe.d.ts +33 -0
  101. package/lib/typescript/drops/types.d.ts +50 -0
  102. package/lib/typescript/index.d.ts +30 -0
  103. package/lib/typescript/preset.d.ts +36 -0
  104. package/lib/typescript/quota/plans.d.ts +37 -0
  105. package/lib/typescript/quota/usage.d.ts +56 -0
  106. package/lib/typescript/quota/usage.test.d.ts +8 -0
  107. package/lib/typescript/store/sanitize.d.ts +18 -0
  108. package/lib/typescript/store/sentryEnvelopeStore.d.ts +49 -0
  109. package/lib/typescript/sync/sentrySyncAdapter.d.ts +64 -0
  110. package/lib/typescript/sync/sentrySyncAdapter.test.d.ts +2 -0
  111. package/lib/typescript/types.d.ts +61 -0
  112. package/lib/web/index.mjs +1 -0
  113. package/package.json +99 -3
@@ -0,0 +1 @@
1
+ "use strict";import W,{useCallback as B}from"react";import{View as s,Text as d,FlatList as D,ScrollView as E,StyleSheet as x}from"react-native";import{night as l,ToolEmptyState as I,LOG_LIST_PRESET as w,CompactRow as _,DevToolsCard as m,InfoRow as c,Pill as f,CopyButton as F,NightSectionLabel as g,RelativeTime as L,formatBytes as y}from"@buoy-gg/shared-ui";import{DataViewer as $}from"@buoy-gg/shared-ui/dataViewer";import{SENTRY_BODY_TOP_INSET as S}from"./layout";import{STATUS_META as k}from"./attachStatus";import{SentryCostView as v}from"./SentryCostView";import{SentryDropsView as R}from"./SentryDropsView";import{SentryAuditView as A}from"./SentryAuditView";import{monoFont as z}from"@buoy-gg/shared-ui";import{jsx as e,jsxs as a,Fragment as O}from"react/jsx-runtime";const P=z;const H={event:"#F87171",transaction:"#A78BFA",session:"#34D399",sessions:"#34D399",client_report:"#FBBF24",log:"#60A5FA",attachment:"#F472B6",profile:"#FB923C",profile_chunk:"#FB923C",replay_event:"#22D3EE",replay_recording:"#22D3EE",user_report:"#F472B6",feedback:"#F472B6"};const V={client_report:"report",replay_recording:"replay",replay_event:"replay",profile_chunk:"profile",user_report:"feedback"};function h(t){return H[t]??"#9CA3AF"}function j(t){return V[t]??t}function M(t){const n=new Date(t);const r=i=>String(i).padStart(2,"0");return`${r(n.getHours())}:${r(n.getMinutes())}:${r(n.getSeconds())}`}function N({envelope:t,onPress:n}){const r=t.items[0];const i=t.items.reduce((u,T)=>u+(T.spanCount??0),0);const p=t.items.length-1;const C=`${r?.summary??"envelope"}${i>0?` \xB7 ${i} span${i===1?"":"s"}`:""}`;return e(_,{statusDotColor:h(r?.type??""),statusLabel:j(r?.type??"envelope"),statusSublabel:p>0?`+${p} more`:void 0,primaryText:C,secondaryText:p>0?t.items.map(u=>u.type).join(" + "):void 0,badgeText:y(t.totalBytes),badgeColor:l.color.textSecondary,bottomRightText:e(L,{timestamp:t.timestamp}),showChevron:true,onPress:()=>n(t.id),testID:`sentry-envelope-row-${t.id}`})}function Y({c:t}){const n=k[t.status];return a(s,{style:o.meta,children:[e(s,{testID:"sentry-status-chip",accessible:true,accessibilityLabel:`Sentry capture: ${n.label}`,children:e(f,{label:n.label,color:n.color,variant:"quiet",dot:true})}),a(d,{style:o.metaTotals,testID:"sentry-totals",children:[t.rows.length," \xB7 ",y(t.totalBytes)]})]})}function q({status:t}){const n=t==="attached"?"Attached to the Sentry client. Envelopes will appear here as the app sends them.":t==="sdk-not-found"?"@sentry/react-native isn't installed in this app (or isn't resolvable). Pass getClient to installSentryCapture / SentryRoot to attach explicitly.":t==="no-client"?"Sentry SDK found, but no client appeared \u2014 is Sentry.init() being called?":"Looking for the app's Sentry client\u2026";return e(I,{testID:"sentry-empty",otto:t==="attached"?"guard":"scanning",title:"No envelopes captured",message:n})}function G(t){try{return JSON.stringify(t,null,2)??String(t)}catch{return String(t)}}function b({value:t,testID:n}){const r=t!==null&&typeof t==="object";if(!r){const i=G(t);return a(s,{style:o.payloadBlock,children:[e(s,{style:o.payloadCopy,children:e(F,{value:i,size:12})}),e(d,{style:o.payload,selectable:true,testID:n,children:i})]})}return e(s,{style:o.payloadTree,testID:n,children:e($,{title:"",data:t,rawMode:true,showTypeFilter:true,initialExpanded:false,testIDPrefix:n?`${n}-node`:void 0})})}function J({item:t,index:n}){return a(m,{testID:`sentry-item-${n}`,contentStyle:o.cardBody,children:[a(s,{style:o.itemHead,children:[e(f,{label:t.type,color:h(t.type),variant:"outline"}),a(d,{style:o.itemMeta,children:[y(t.bytes),t.spanCount!=null?` \xB7 ${t.spanCount} span${t.spanCount===1?"":"s"}`:""]})]}),e(d,{style:o.itemSummary,testID:`sentry-item-${n}-summary`,children:t.summary}),t.binary?a(d,{style:o.itemBinary,children:["binary payload (",y(t.bytes),")"]}):t.payload!==void 0?e(b,{value:t.payload,testID:`sentry-item-${n}-payload`}):null]})}function K({envelope:t}){return a(E,{style:o.container,contentContainerStyle:o.detailContent,testID:"sentry-envelope-detail",children:[a(m,{contentStyle:o.cardBody,children:[e(c,{label:"Captured",value:M(t.timestamp)}),e(c,{label:"Size",value:y(t.totalBytes)}),e(c,{label:"Items",value:String(t.items.length)}),e(c,{label:"Origin",value:t.origin}),t.eventId?e(c,{label:"Event ID",value:t.eventId,mono:true,copyable:true}):null]}),e(g,{style:o.sectionLabel,children:"ITEMS"}),t.items.map((n,r)=>e(J,{item:n,index:r},r)),t.header!==void 0?a(O,{children:[e(g,{style:o.sectionLabel,children:"ENVELOPE HEADER"}),e(m,{contentStyle:o.cardBody,children:e(b,{value:t.header,testID:"sentry-envelope-header"})})]}):null]})}export function SentryBody({c:t}){const n=B(({item:r})=>e(N,{envelope:r,onPress:t.openDetail}),[t.openDetail]);if(t.tab==="cost"){return e(v,{c:t})}if(t.tab==="drops"){return e(R,{c:t})}if(t.tab==="audit"){return e(A,{c:t})}if(t.view==="detail"&&t.selected){return e(K,{envelope:t.selected})}return e(D,{style:o.container,data:t.rows,keyExtractor:r=>String(r.id),renderItem:n,ListHeaderComponent:e(Y,{c:t}),ListEmptyComponent:e(q,{status:t.status}),contentContainerStyle:o.listContent,...w,testID:"sentry-envelope-list"})}const o=x.create({container:{flex:1,backgroundColor:"transparent"},listContent:{paddingTop:S,paddingBottom:12},meta:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8,paddingHorizontal:12,paddingBottom:8},metaTotals:{color:l.color.textTertiary,fontSize:11,fontVariant:["tabular-nums"]},detailContent:{paddingTop:S,paddingBottom:24},sectionLabel:{marginHorizontal:12,marginTop:10,marginBottom:2},cardBody:{gap:8},itemHead:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8},itemMeta:{color:l.color.textSecondary,fontSize:11,fontVariant:["tabular-nums"]},itemSummary:{color:l.color.text,fontSize:12,fontWeight:"600"},itemBinary:{color:l.color.textTertiary,fontSize:11,fontStyle:"italic"},payloadBlock:{backgroundColor:l.color.fill,borderRadius:6,padding:8,paddingRight:26},payloadTree:{backgroundColor:l.color.fill,borderRadius:6,marginHorizontal:-4,paddingVertical:4,overflow:"hidden"},payloadCopy:{position:"absolute",top:4,right:4,zIndex:1},payload:{color:l.color.textSecondary,fontSize:11,fontFamily:P,lineHeight:16}});
@@ -0,0 +1 @@
1
+ "use strict";import P from"react";import{View as u,Text as g,ScrollView as w,StyleSheet as C}from"react-native";import{night as d,ChipStrip as N,useChipTap as _,NightChip as x,NightSectionLabel as F,NightFootnote as b,DevToolsCard as S,InfoRow as y,formatBytes as k,formatDuration as B}from"@buoy-gg/shared-ui";import{SENTRY_BODY_TOP_INSET as D}from"./layout";import{CATEGORY_META as T,SENTRY_PLANS as I,SENTRY_PLANS_VERIFIED_ON as j}from"../quota/plans";import{quotaFraction as E}from"../quota/usage";import{jsx as n,jsxs as a}from"react/jsx-runtime";function L(t){if(t===null)return d.color.textTertiary;if(t>=1)return"#F87171";if(t>=.6)return"#FBBF24";return"#34D399"}function c(t,r){if(r==="bytes")return k(t);if(t>=1e6)return`${(t/1e6).toFixed(1)}M`;if(t>=1e3)return`${(t/1e3).toFixed(1)}k`;return String(t)}function $(t){if(t===null)return"\u2014";if(!Number.isFinite(t))return"not on plan";if(t>=10)return`${Math.round(t)}\xD7 quota`;return`${Math.round(t*100)}% of quota`}function R({category:t,session:r,projected:s,quota:l,usable:h}){const i=h?E(s,l):null;const m=L(i);const e=i===null||!Number.isFinite(i)?i===null?0:100:Math.min(100,i*100);const p=h?`, about ${c(s,t.format)} per month, ${$(i)}`:", projection needs a longer session";const f=l>0?`, quota ${c(l,t.format)}`:", not included in this plan";return a(S,{testID:`sentry-cost-${t.key}`,contentStyle:o.cardBody,accessible:true,accessibilityLabel:`${t.label}: ${c(r,t.format)} this session${p}${f}`,children:[a(u,{style:o.head,children:[n(g,{style:o.label,children:t.label}),a(g,{style:o.session,children:[c(r,t.format)," this session"]})]}),n(u,{style:o.barTrack,children:n(u,{style:[o.barFill,{width:`${e}%`,backgroundColor:m}]})}),n(y,{label:"Projected",value:h?`${c(s,t.format)}/mo \xB7 ${$(i)}`:"needs a longer session",valueColor:m}),n(y,{label:"Quota",value:l>0?c(l,t.format):"not included"}),n(b,{style:o.hint,children:t.hint})]})}export function SentryCostView({c:t}){const{usage:r,projection:s,plan:l}=t;const{scrollProps:h,onTouchStart:i,isTap:m}=_();return a(w,{style:o.container,contentContainerStyle:o.content,testID:"sentry-cost-view",children:[n(N,{scrollProps:h,contentStyle:o.planStrip,sentryLabel:"ignore devtools sentry plan strip scroll",children:I.map(e=>n(u,{onTouchStart:i,children:n(x,{label:e.name,selected:e.id===l.id,onPress:()=>{if(m())t.setPlanId(e.id)},testID:`sentry-plan-${e.id}`})},e.id))}),a(b,{style:o.windowNote,children:[t.rows.length," envelope",t.rows.length===1?"":"s"," over"," ",B(r.windowMs),s.usable?" \xB7 projection extrapolates this rate to 30 days":""]}),T.map(e=>n(R,{category:e,session:r.totals[e.key],projected:s.totals[e.key],quota:l.quota[e.key],usable:s.usable},e.key)),r.contributors.length>0?a(u,{testID:"sentry-cost-contributors",children:[n(F,{style:o.sectionLabel,children:"WHAT COSTS THE MOST"}),n(S,{contentStyle:o.contribBody,children:r.contributors.slice(0,8).map(e=>{const p=T.find(f=>f.key===e.category);return n(y,{label:e.label,value:`${c(e.units,p?.format??"count")} ${p?.label.toLowerCase()??e.category}${e.occurrences>1?` \xD7${e.occurrences}`:""}`},`${e.category}:${e.label}`)})})]}):null,a(b,{style:o.footnote,children:["Sentry bills on receipt \u2014 data their server-side sampling later drops is still charged, so the only lossless lever is client config, and on mobile that ships frozen in the binary. Quotas as published ",j,"."]})]})}const o=C.create({container:{flex:1,backgroundColor:"transparent"},content:{paddingTop:D,paddingBottom:24},planStrip:{paddingHorizontal:12,gap:8},windowNote:{marginHorizontal:12,marginTop:6},sectionLabel:{marginHorizontal:12,marginTop:12,marginBottom:2},cardBody:{gap:6},hint:{marginTop:2,marginLeft:0,fontSize:d.font.micro,lineHeight:d.font.micro*1.4},contribBody:{gap:0},head:{flexDirection:"row",alignItems:"baseline",justifyContent:"space-between",gap:8},label:{color:d.color.text,fontSize:13,fontWeight:"700"},session:{color:d.color.textSecondary,fontSize:11,fontVariant:["tabular-nums"]},barTrack:{height:10,borderRadius:5,backgroundColor:d.color.fill,overflow:"hidden"},barFill:{height:10,borderRadius:5},footnote:{marginHorizontal:12,marginTop:10}});
@@ -0,0 +1 @@
1
+ "use strict";import R,{useCallback as u,useState as y}from"react";import{ScrollView as g,StyleSheet as x,View as b}from"react-native";import{ChipStrip as h,useChipTap as S,CompactRow as f,NightFootnote as C,Pill as r,RelativeTime as _,ToolEmptyState as T}from"@buoy-gg/shared-ui";import{SENTRY_BODY_TOP_INSET as v}from"./layout";import{DROP_REASON_META as s}from"../drops/types";import{jsx as t,jsxs as l}from"react/jsx-runtime";const a={before_send:"#F87171",sample_rate:"#60A5FA",event_processor:"#FBBF24",rn_discard:"#A78BFA",client_report:"#FB923C",native:"#F472B6",other:"#9CA3AF"};export function SentryDropsView({c:o}){const[i,p]=y(null);const{scrollProps:d}=S();const m=u(e=>p(c=>c===e?null:e),[]);if(o.drops.length===0){return t(T,{testID:"sentry-drops-empty",otto:"scanning",title:"Nothing dropped yet",message:"Events the SDK discards before sending will be listed here with the reason \u2014 a beforeSend that returned null, a sample-rate roll, dedupe, or a native rate limit."})}return l(g,{style:n.container,contentContainerStyle:n.content,testID:"sentry-drops-view",children:[t(h,{scrollProps:d,contentStyle:n.summaryStrip,sentryLabel:"ignore devtools sentry drop summary scroll",children:o.dropSummary.map(e=>t(r,{label:`${e.units} \xD7 ${s[e.reason].label}`,color:a[e.reason],variant:"tinted",size:"sm",uppercase:false,testID:`sentry-drop-summary-${e.reason}`},e.reason))}),o.dropRows.map(e=>t(f,{statusDotColor:a[e.reason],statusLabel:e.category,statusSublabel:e.source,primaryText:e.detail,secondaryAccessory:t(r,{label:s[e.reason].label,color:a[e.reason],variant:"outline",uppercase:false}),badgeText:e.quantity>1?`\xD7${e.quantity}`:void 0,badgeColor:a[e.reason],bottomRightText:t(_,{timestamp:e.timestamp}),isExpanded:i===e.id,expandedGlowColor:a[e.reason],expandedContent:l(b,{style:n.expanded,children:[t(r,{label:s[e.reason].label,color:a[e.reason],variant:"outline",uppercase:false}),t(C,{style:n.explanation,children:s[e.reason].explanation})]}),showChevron:true,onPress:()=>m(e.id),testID:`sentry-drop-row-${e.id}`},e.id))]})}const n=x.create({container:{flex:1,backgroundColor:"transparent"},content:{paddingTop:v,paddingBottom:24},summaryStrip:{paddingHorizontal:12,gap:8,paddingBottom:4},expanded:{gap:6,alignItems:"flex-start"},explanation:{marginTop:2,marginLeft:0}});
@@ -0,0 +1 @@
1
+ "use strict";import f from"react";import{TouchableOpacity as d,StyleSheet as b,View as h}from"react-native";import{ModalHeader as o,TabSelector as y,night as r,Trash2 as p}from"@buoy-gg/shared-ui";import{jsx as e,jsxs as s}from"react/jsx-runtime";const u=[{key:"feed",label:"Feed"},{key:"cost",label:"Cost"},{key:"drops",label:"Drops"},{key:"audit",label:"Audit"}];export function SentryHeader({c:t,onBack:l}){if(t.view==="detail"){const i=t.selected?t.selected.items.map(c=>c.type).join(" + ")||"Envelope":"Envelope";return s(o,{children:[e(o.Navigation,{onBack:t.closeDetail}),e(o.Content,{title:i})]})}const a=t.rows.length>0;return s(o,{children:[l?e(o.Navigation,{onBack:l}):null,e(o.Content,{title:"",noMargin:true,children:e(h,{style:n.tabs,children:e(y,{tabs:u,activeTab:t.tab,onTabChange:i=>t.setTab(i),testIDPrefix:"sentry-tab"})})}),e(o.Actions,{children:e(d,{onPress:t.clear,disabled:!a,style:[n.actionBtn,!a&&n.actionBtnDisabled],accessibilityLabel:"Clear captured envelopes",testID:"sentry-clear",children:e(p,{size:14,color:a?r.color.textSecondary:r.color.textTertiary})})})]})}const n=b.create({tabs:{maxWidth:360},actionBtn:{width:32,height:32,borderRadius:8,backgroundColor:r.color.buttonSurface,borderWidth:1,borderColor:r.color.border,alignItems:"center",justifyContent:"center"},actionBtnDisabled:{opacity:.55}});
@@ -0,0 +1 @@
1
+ "use strict";import f from"react";import{View as e,StyleSheet as l}from"react-native";import{useSentryController as n}from"./useSentryController";import{SentryHeader as s}from"./SentryHeader";import{SentryBody as i}from"./SentryBody";import{jsx as r,jsxs as c}from"react/jsx-runtime";export function SentryToolView(){const t=n();return c(e,{style:o.root,children:[r(e,{style:o.headerSlot,children:r(s,{c:t})}),r(e,{style:o.body,children:r(i,{c:t})})]})}const o=l.create({root:{flex:1,flexDirection:"column",backgroundColor:"transparent"},headerSlot:{flexGrow:0,flexShrink:0},body:{flex:1}});
@@ -0,0 +1 @@
1
+ "use strict";export const STATUS_META={attached:{label:"Attached",color:"#34D399"},searching:{label:"Searching\u2026",color:"#FBBF24"},"sdk-not-found":{label:"SDK not found",color:"#F87171"},"no-client":{label:"No client",color:"#F87171"}};
@@ -0,0 +1 @@
1
+ "use strict";export const SENTRY_BODY_TOP_INSET=16;
@@ -0,0 +1 @@
1
+ "use strict";import{useCallback as g,useMemo as o,useState as l,useSyncExternalStore as u}from"react";import{clearEnvelopes as O,getAttachStatus as I,getEnvelopes as v,getMirroredUsage as E,subscribe as w}from"../store/sentryEnvelopeStore";import{DEFAULT_PLAN_ID as R,getPlan as q}from"../quota/plans";import{computeUsage as G,projectMonthly as H}from"../quota/usage";import{getDrops as M,subscribeDrops as J,summarizeDrops as K,clearDrops as Q}from"../drops/dropStore";import{auditConfig as W}from"../audit/rules";import{readAuditInput as X}from"../audit/auditSource";import{scanForPii as Y}from"../audit/pii";const Z=20;export function useSentryController(){const t=u(w,v,v);const P=u(w,I,I);const[s,h]=l("feed");const[A,a]=l("list");const[d,p]=l(null);const[b,C]=l(R);const U=o(()=>[...t].reverse(),[t]);const n=u(w,E,E);const i=o(()=>G(t),[t]);const f=o(()=>{if(!n?.session)return i;return{totals:n.session,windowMs:n.windowMs??i.windowMs,contributors:n.topContributors??i.contributors}},[n,i]);const _=o(()=>H(f),[f]);const k=o(()=>q(b),[b]);const r=u(J,M,M);const x=o(()=>[...r].reverse(),[r]);const B=o(()=>K(r),[r]);const F=o(()=>{if(s!=="audit")return null;const e=X();return e?{findings:W(e)}:null},[s]);const L=o(()=>{if(s!=="audit")return[];const e=t.slice(-Z);const c=[];const y=new Set;for(const z of e){for(const S of z.items){if(S.payload===void 0)continue;for(const m of Y(S.payload)){const D=`${m.kind}:${m.path}`;if(y.has(D))continue;y.add(D);c.push(m)}}}return c},[s,t]);const N=o(()=>d==null?void 0:t.find(e=>e.id===d),[t,d]);const j=o(()=>t.reduce((e,c)=>e+c.totalBytes,0),[t]);const T=g(e=>{p(e);a("detail")},[]);const V=g(()=>{a("list");p(null)},[]);const $=g(()=>{O();Q();a("list");p(null)},[]);return{tab:s,setTab:h,view:A,envelopes:t,rows:U,selected:N,status:P,totalBytes:j,usage:f,projection:_,plan:k,setPlanId:C,drops:r,dropRows:x,dropSummary:B,audit:F,piiFindings:L,openDetail:T,closeDetail:V,clear:$}}export{formatBytes}from"@buoy-gg/shared-ui";
@@ -0,0 +1 @@
1
+ "use strict";import{createToolStore as f}from"@buoy-gg/shared-ui";const y=300;const a=f({id:"sentry-drops",max:y,order:"append",coalesce:"frame"});const u=new Map;const l=(e,r)=>`${e==="rn_discard"?"event_processor":e}|${r}`;export function recordDrop(e){if(e.source==="observed")tallyObserved(u,e);a.add({...e,id:a.allocId()})}export function reconcileReported(e,r){const t=[];for(const o of r){const i=l(o.reason,o.category);const n=e.get(i)??0;const c=Math.min(n,o.quantity);if(c>0)e.set(i,n-c);const s=o.quantity-c;if(s<=0)continue;t.push(s===o.quantity?o:{...o,quantity:s,detail:o.detail.replace(/^\d+/,String(s))})}return t}export function tallyObserved(e,r){const t=l(r.reason,r.category);e.set(t,(e.get(t)??0)+r.quantity)}export function recordReportedDrops(e){for(const r of reconcileReported(u,e))a.add({...r,id:a.allocId()})}export function subscribeDrops(e){return a.subscribe(e)}export function getDrops(){return a.getSnapshot()}export function clearDrops(){u.clear();a.clear()}export function replaceDrops(e){a.set(e)}const _={before_send:"before_send",event_processor:"event_processor",sample_rate:"sample_rate",ratelimit_backoff:"native",network_error:"native",queue_overflow:"native",cache_overflow:"native",send_error:"native"};export function dropsFromClientReport(e,r){const t=e?.discarded_events;if(!Array.isArray(t))return[];const o=[];for(const i of t){const n=i;const c=typeof n.reason==="string"?n.reason:"";const s=typeof n.category==="string"?n.category:"unknown";const d=typeof n.quantity==="number"?n.quantity:1;const p=_[c]??"other";o.push({timestamp:r,reason:p,category:s,quantity:d,detail:p==="other"&&c?`${d} ${s} \u2014 SDK reason "${c}"`:`${d} ${s}`,source:"reported"})}return o}const g="sentry.rn.discard_reason";const v={empty_back_navigation:"back navigation that produced no spans",no_route_info:"no route info was available",no_child_spans:"the transaction had no child spans",discarded_latest_navigation:"a newer navigation superseded it"};export function rnDiscardReason(e){const r=e?.contexts?.trace?.data;const t=r?.[g];return typeof t==="string"?t:null}export function dropFromRnDiscard(e,r,t){return{timestamp:t,reason:"rn_discard",category:"transaction",quantity:1,detail:`${r} \u2014 ${v[e]??e}`,source:"observed"}}export function dropFromNativeLog(e,r){const t=e.toLowerCase();const o=t.includes("429")||t.includes("rate limit");const i=t.includes("413")||t.includes("payload too large");const n=t.includes("discard")||t.includes("dropp");if(!o&&!i&&!n)return null;return{timestamp:r,reason:"native",category:o?"rate-limited":i?"oversize":"unknown",quantity:1,detail:e.trim().slice(0,300),source:"reported"}}export function summarizeDrops(e){const r=new Map;for(const t of e){const o=r.get(t.reason);if(o){o.units+=t.quantity;o.records+=1}else{r.set(t.reason,{units:t.quantity,records:1})}}return[...r.entries()].map(([t,o])=>({reason:t,...o})).sort((t,o)=>o.units-t.units)}
@@ -0,0 +1 @@
1
+ "use strict";import{describe as s,it as o,expect as t}from"vitest";import{dropsFromClientReport as n,dropFromNativeLog as i,dropFromRnDiscard as p,rnDiscardReason as d,summarizeDrops as u,reconcileReported as c,tallyObserved as l}from"./dropStore";s("dropsFromClientReport",()=>{o("maps the SDK's reasons onto ours and keeps the quantity",()=>{const e=n({discarded_events:[{reason:"before_send",category:"error",quantity:3},{reason:"sample_rate",category:"transaction",quantity:7}]},1e3);t(e).toHaveLength(2);t(e[0]).toMatchObject({reason:"before_send",category:"error",quantity:3});t(e[1]).toMatchObject({reason:"sample_rate",quantity:7})});o("files transport-level reasons under native",()=>{const[e]=n({discarded_events:[{reason:"ratelimit_backoff",category:"error",quantity:1}]},1e3);t(e.reason).toBe("native")});o("keeps an unrecognized reason visible instead of hiding it",()=>{const[e]=n({discarded_events:[{reason:"some_future_reason",category:"span",quantity:2}]},1e3);t(e.reason).toBe("other");t(e.detail).toContain("some_future_reason")});o("returns nothing for a payload with no discarded events",()=>{t(n({},1e3)).toEqual([]);t(n(null,1e3)).toEqual([]);t(n("nonsense",1e3)).toEqual([])});o("defaults a missing quantity to one rather than zero",()=>{const[e]=n({discarded_events:[{reason:"before_send",category:"error"}]},1e3);t(e.quantity).toBe(1)})});s("rnDiscardReason",()=>{o("reads the reason RN tracing stamps on the root span",()=>{const e={contexts:{trace:{data:{"sentry.rn.discard_reason":"no_child_spans"}}}};t(d(e)).toBe("no_child_spans")});o("returns null for an ordinary transaction",()=>{t(d({contexts:{trace:{data:{}}}})).toBeNull();t(d({})).toBeNull();t(d(null)).toBeNull()});o("turns the reason into copy that names the cause",()=>{const e=p("empty_back_navigation","/cart",1e3);t(e.reason).toBe("rn_discard");t(e.category).toBe("transaction");t(e.detail).toContain("/cart");t(e.detail).toContain("back navigation")});o("falls back to the raw reason when it is one we have no copy for",()=>{const e=p("brand_new_reason","/x",1e3);t(e.detail).toContain("brand_new_reason")})});s("dropFromNativeLog",()=>{o("recognizes a rate limit",()=>{const e=i("Transport: rate limit reached, backing off",1e3);t(e?.reason).toBe("native");t(e?.category).toBe("rate-limited")});o("recognizes an oversize rejection",()=>{const e=i("HTTP 413 payload too large",1e3);t(e?.category).toBe("oversize")});o("ignores ordinary native chatter",()=>{t(i("Sentry SDK initialized",1e3)).toBeNull();t(i("Sending envelope to https://\u2026",1e3)).toBeNull()});o("caps a very long native line so one log cannot dominate the panel",()=>{const e=i(`discarded ${"x".repeat(1e3)}`,1e3);t(e).not.toBeNull();t(e.detail.length).toBeLessThanOrEqual(300)})});s("summarizeDrops",()=>{const e=(r,a)=>({id:Math.random(),timestamp:0,reason:r,category:"error",quantity:a,detail:"",source:"observed"});o("totals units per reason and ranks the worst first",()=>{const r=u([e("sample_rate",2),e("before_send",5),e("before_send",4)]);t(r[0]).toEqual({reason:"before_send",units:9,records:2});t(r[1]).toEqual({reason:"sample_rate",units:2,records:1})});o("is empty for no drops",()=>{t(u([])).toEqual([])})});s("observed and reported drops of the same event",()=>{const e={timestamp:1,reason:"before_send",category:"error",quantity:1,detail:"[checkout] order failed",source:"observed"};o("counts a beforeSend drop once when the client report repeats it",()=>{const r=new Map;l(r,e);const a=c(r,[{timestamp:2,reason:"before_send",category:"error",quantity:1,detail:"1 error",source:"reported"}]);t(a).toEqual([])});o("keeps the part of a report we didn't see",()=>{const r=new Map;l(r,e);const a=c(r,[{timestamp:2,reason:"before_send",category:"error",quantity:3,detail:"3 error",source:"reported"}]);t(a).toHaveLength(1);t(a[0].quantity).toBe(2);t(a[0].detail).toBe("2 error")});o("leaves other reasons alone",()=>{const r=new Map;l(r,e);const a=c(r,[{timestamp:2,reason:"sample_rate",category:"error",quantity:1,detail:"1 error",source:"reported"}]);t(a).toHaveLength(1)})});
@@ -0,0 +1 @@
1
+ "use strict";import{dropFromNativeLog as d,dropsFromClientReport as h,recordReportedDrops as m,dropFromRnDiscard as b,recordDrop as f,rnDiscardReason as l}from"./dropStore";const u="__buoySentryDropWatch";function p(o,e){const n=o;if(e==="transaction")return n?.transaction??"transaction";const t=n?.exception?.values?.[0];if(t?.value)return String(t.value);if(t?.type)return String(t.type);const c=n?.message?.formatted??n?.message;return typeof c==="string"?c:"event"}function y(o,e){if(typeof o!=="function")return o;if(o[u])return o;const n=(t,c)=>{const s=p(t,e);let r;try{r=o(t,c)}catch(a){try{f({timestamp:Date.now(),reason:"before_send",category:e==="transaction"?"transaction":"error",quantity:1,detail:`${s} \u2014 the callback threw`,source:"observed"})}catch{}throw a}const i=a=>{if(a!==null)return;try{f({timestamp:Date.now(),reason:"before_send",category:e==="transaction"?"transaction":"error",quantity:1,detail:s,source:"observed"})}catch{}};if(r&&typeof r.then==="function"){return r.then(a=>{i(a);return a})}i(r);return r};n[u]=true;return n}export function observeDrops(o){const e=o;if(!e)return false;let n=false;try{const t=e.getOptions?.();if(t){t.beforeSend=y(t.beforeSend,"error");t.beforeSendTransaction=y(t.beforeSendTransaction,"transaction");n=true}}catch{}try{e.on?.("beforeEnvelope",t=>{try{const c=t?.[1];if(!Array.isArray(c))return;const s=Date.now();for(const r of c){if(!Array.isArray(r))continue;const[i,a]=r;if(i?.type==="client_report"){m(h(a,s))}}}catch{}});n=true}catch{}try{const t=e.getOptions?.();if(t){const c=t.beforeSendTransaction;const s=(r,i)=>{try{const a=l(r);if(a){f(b(a,p(r,"transaction"),Date.now()))}}catch{}return typeof c==="function"?c(r,i):r};s[u]=true;t.beforeSendTransaction=s}}catch{}return n}export function noteNativeLog(o){try{const e=d(o,Date.now());if(e)f(e)}catch{}}
@@ -0,0 +1 @@
1
+ "use strict";export const DROP_REASON_META={before_send:{label:"beforeSend returned null",explanation:"Your own callback discarded it. This is the most common cause of a missing event, and it leaves no trace anywhere else."},sample_rate:{label:"sampled out",explanation:"A sample-rate roll went against it. Expected \u2014 unless the rate is lower than you meant it to be."},event_processor:{label:"dropped by a processor",explanation:"An integration returned null \u2014 usually dedupe (a repeat of an event already sent) or an inbound filter such as ignoreErrors."},rn_discard:{label:"discarded by RN tracing",explanation:"React Native tracing judged the transaction empty \u2014 no child spans, no route info, or a back navigation that went nowhere."},client_report:{label:"reported dropped by the SDK",explanation:"The SDK's own client report counted this as discarded. It arrives one envelope late, so it names a count rather than the event."},native:{label:"rejected natively",explanation:"The native transport refused it \u2014 a 429 rate limit, a payload over the size cap, or an offline cache eviction. Only visible with debug enabled."},other:{label:"dropped",explanation:"The SDK recorded a discard under a reason this tool doesn't model."}};
@@ -0,0 +1 @@
1
+ "use strict";export{sentryToolPreset,createSentryTool}from"./preset";export{SentryRoot}from"./SentryRoot";export{installSentryCapture,attachToClient,parseEnvelope}from"./capture/attach";export{SentryModal}from"./components/SentryModal";export{SentryIcon,SENTRY_ICON_COLOR}from"./components/SentryIcon";export{SentryHeader}from"./devtools/SentryHeader";export{SentryBody}from"./devtools/SentryBody";export{useSentryController,formatBytes}from"./devtools/useSentryController";export{SentryCostView}from"./devtools/SentryCostView";export{SentryToolView}from"./devtools/SentryToolView";export{SENTRY_PLANS,SENTRY_PLANS_VERIFIED_ON,DEFAULT_PLAN_ID,CATEGORY_META,getPlan}from"./quota/plans";export{computeUsage,projectMonthly,quotaFraction}from"./quota/usage";export{SentryDropsView}from"./devtools/SentryDropsView";export{getDrops,subscribeDrops,clearDrops,replaceDrops,summarizeDrops,dropsFromClientReport,dropFromNativeLog,dropFromRnDiscard,rnDiscardReason}from"./drops/dropStore";export{observeDrops,noteNativeLog}from"./drops/observe";export{DROP_REASON_META}from"./drops/types";export{SentryAuditView}from"./devtools/SentryAuditView";export{auditConfig,worstSeverity}from"./audit/rules";export{scanForPii,PII_KIND_LABEL}from"./audit/pii";export{sentrySyncAdapter}from"./sync/sentrySyncAdapter";export{subscribe,getEnvelopes,getAttachStatus,clearEnvelopes,replaceEnvelopes,replaceAttachStatus,replaceUsage,getMirroredUsage,onClear}from"./store/sentryEnvelopeStore";
@@ -0,0 +1 @@
1
+ "use strict";import f from"react";import{createToolFactory as r,defineToolPreset as n}from"@buoy-gg/shared-ui";import{SentryIcon as s,SENTRY_ICON_COLOR as i}from"./components/SentryIcon";import{SentryModal as c}from"./components/SentryModal";import{installSentryCapture as l}from"./capture/attach";import{jsx as p}from"react/jsx-runtime";const o=globalThis;const a=typeof o.ErrorUtils?.setGlobalHandler==="function"||typeof o.RN$registerExceptionListener==="function";if(a&&typeof __DEV__!=="undefined"&&__DEV__){l()}export const sentryToolPreset=n({id:"sentry",name:"SENTRY",description:"Envelopes the app sends to Sentry",slot:"both",icon:({size:t,color:e})=>p(s,{size:t,color:e??i}),component:c});export const createSentryTool=r(sentryToolPreset);
@@ -0,0 +1 @@
1
+ "use strict";export const SENTRY_PLANS_VERIFIED_ON="2026-09";const e=1024*1024*1024;export const SENTRY_PLANS=[{id:"developer",name:"Developer (free)",quota:{errors:5e3,spans:5e6,replays:50,logBytes:5*e,attachmentBytes:1*e}},{id:"team",name:"Team",quota:{errors:5e4,spans:5e6,replays:50,logBytes:5*e,attachmentBytes:1*e}},{id:"business",name:"Business",quota:{errors:5e4,spans:5e6,replays:50,logBytes:5*e,attachmentBytes:1*e}}];export const DEFAULT_PLAN_ID="developer";export function getPlan(t){return SENTRY_PLANS.find(s=>s.id===t)??SENTRY_PLANS[0]}export const CATEGORY_META=[{key:"errors",label:"Errors",format:"count",hint:"One per captured event, including repeats on issues you've ignored."},{key:"spans",label:"Spans",format:"count",hint:"Billed individually \u2014 one transaction is a bundle of many."},{key:"replays",label:"Replays",format:"count",hint:"One per session, whatever its length."},{key:"logBytes",label:"Logs",format:"bytes",hint:"Billed by volume received."},{key:"attachmentBytes",label:"Attachments",format:"bytes",hint:"Screenshots and view hierarchies attached to events."}];
@@ -0,0 +1 @@
1
+ "use strict";const i=()=>({errors:0,spans:0,replays:0,logBytes:0,attachmentBytes:0});function y(t){return t?.type==="feedback"}function m(t){return t.summary||"transaction"}export function computeUsage(t){const n=i();const a=new Map;const e=(o,s,r)=>{n[s]+=r;const u=`${s}:${o}`;const c=a.get(u);if(c){c.units+=r;c.occurrences+=1}else{a.set(u,{label:o,category:s,units:r,occurrences:1})}};for(const o of t){for(const s of o.items){switch(s.type){case"event":if(!y(s.payload))e(s.summary,"errors",1);break;case"transaction":e(m(s),"spans",s.spanCount??1);break;case"replay_event":e("session replay","replays",1);break;case"log":e("logs","logBytes",s.bytes);break;case"attachment":e(s.summary||"attachment","attachmentBytes",s.bytes);break;default:break}}}const f=t[0]?.timestamp??0;const l=t[t.length-1]?.timestamp??0;return{totals:n,windowMs:Math.max(0,l-f),contributors:[...a.values()].sort((o,s)=>s.units-o.units)}}export const MIN_PROJECTION_WINDOW_MS=3e4;const b=30*24*60*60*1e3;export function projectMonthly(t){if(t.windowMs<MIN_PROJECTION_WINDOW_MS){return{usable:false,totals:i()}}const n=b/t.windowMs;const a=i();for(const e of Object.keys(a)){a[e]=Math.round(t.totals[e]*n)}return{usable:true,totals:a}}export function quotaFraction(t,n){if(n<=0)return t>0?Infinity:null;return t/n}
@@ -0,0 +1 @@
1
+ "use strict";import{describe as y,it as s,expect as e}from"vitest";import{computeUsage as n,projectMonthly as l,quotaFraction as c,MIN_PROJECTION_WINDOW_MS as i}from"./usage";let m=1;function o(t,p=1e3){const r=t.map(a=>({type:a.type??"event",bytes:a.bytes??100,summary:a.summary??"",spanCount:a.spanCount,payload:a.payload,binary:a.binary}));return{id:m++,timestamp:p,origin:"js",items:r,totalBytes:r.reduce((a,u)=>a+u.bytes,0)}}y("computeUsage",()=>{s("counts one error per event",()=>{const{totals:t}=n([o([{type:"event",summary:"boom"}]),o([{type:"event",summary:"boom again"}])]);e(t.errors).toBe(2)});s("counts a transaction as its SPANS, not as one unit",()=>{const{totals:t}=n([o([{type:"transaction",summary:"/checkout",spanCount:12}])]);e(t.spans).toBe(12);e(t.errors).toBe(0)});s("falls back to a single span when the count is unknown",()=>{const{totals:t}=n([o([{type:"transaction",summary:"//",spanCount:void 0}])]);e(t.spans).toBe(1)});s("does not bill a feedback event as an error",()=>{const{totals:t}=n([o([{type:"event",summary:"feedback",payload:{type:"feedback"}}])]);e(t.errors).toBe(0)});s("meters logs and attachments by bytes, replays by session",()=>{const{totals:t}=n([o([{type:"log",bytes:2048}]),o([{type:"attachment",bytes:4096,summary:"screenshot.png"}]),o([{type:"replay_event",bytes:500}])]);e(t.logBytes).toBe(2048);e(t.attachmentBytes).toBe(4096);e(t.replays).toBe(1)});s("ignores item types it does not model rather than guessing",()=>{const{totals:t}=n([o([{type:"session"},{type:"client_report"},{type:"profile"}])]);e(t).toEqual({errors:0,spans:0,replays:0,logBytes:0,attachmentBytes:0})});s("groups contributors by label and ranks them by units",()=>{const{contributors:t}=n([o([{type:"transaction",summary:"/cheap",spanCount:2}]),o([{type:"transaction",summary:"/expensive",spanCount:30}]),o([{type:"transaction",summary:"/expensive",spanCount:30}])]);e(t[0]).toMatchObject({label:"/expensive",units:60,occurrences:2});e(t[1]).toMatchObject({label:"/cheap",units:2})});s("measures the window between first and last envelope",()=>{const t=n([o([{type:"event"}],1e4),o([{type:"event"}],7e4)]);e(t.windowMs).toBe(6e4)});s("has an empty, zeroed report for no envelopes",()=>{const t=n([]);e(t.windowMs).toBe(0);e(t.contributors).toEqual([]);e(t.totals.errors).toBe(0)})});y("projectMonthly",()=>{s("refuses to project from a window too short to mean anything",()=>{const t=n([o([{type:"event"}],0),o([{type:"event"}],i-1)]);e(l(t).usable).toBe(false)});s("extrapolates the observed rate to 30 days",()=>{const t=60*60*1e3;const p={totals:{errors:10,spans:0,replays:0,logBytes:0,attachmentBytes:0},windowMs:t,contributors:[]};const r=l(p);e(r.usable).toBe(true);e(r.totals.errors).toBe(7200)})});y("quotaFraction",()=>{s("reports usage as a fraction of the allowance",()=>{e(c(2500,5e3)).toBe(.5)});s("returns null for an unused category the plan excludes",()=>{e(c(0,0)).toBeNull()});s("is infinite when a plan excludes a category the app still uses",()=>{e(c(5,0)).toBe(Infinity)})});
@@ -0,0 +1 @@
1
+ "use strict";const y=8;const f=200;const g=200;export const SNAPSHOT_STRING_LIMIT=2*1024;export function capString(r,t=SNAPSHOT_STRING_LIMIT){if(r.length<=t)return r;return`${r.slice(0,t)}\u2026 [${r.length-t} more]`}function e(r,t,i){if(r===null)return null;if(typeof r==="string")return capString(r);if(typeof r==="boolean")return r;if(typeof r==="number")return Number.isFinite(r)?r:String(r);if(typeof r==="undefined")return"undefined";if(typeof r==="bigint")return`${String(r)}n`;if(typeof r==="function"){return`\u0192 ${r.name||"(anonymous)"}`}if(typeof r==="symbol")return r.toString();if(r instanceof Error){return{name:r.name,message:capString(r.message),stack:r.stack?capString(r.stack,4*1024):r.stack}}if(t>=y)return Array.isArray(r)?"[Array]":"{\u2026}";const o=r;if(i.has(o))return"[Circular]";i.add(o);try{if(Array.isArray(r)){const n=r.slice(0,f).map(c=>e(c,t+1,i));if(r.length>f)n.push(`\u2026 ${r.length-f} more`);return n}const s={};for(const n of Object.keys(o).slice(0,g)){try{s[n]=e(o[n],t+1,i)}catch{s[n]="[Unserializable]"}}return s}finally{i.delete(o)}}export function sanitizePayload(r){const t=new WeakSet;return e(r,0,t)}
@@ -0,0 +1 @@
1
+ "use strict";import{createToolStore as c}from"@buoy-gg/shared-ui";const u=500;const t=c({id:"sentry",max:u,order:"append",coalesce:"frame"});let r="searching";let n=null;export function recordEnvelope(e,o={}){t.add({...e,id:t.allocId()});if(o.urgent)t.flushSync(true)}export function setAttachStatus(e){if(r===e)return;r=e;t.notify()}export function subscribe(e){return t.subscribe(e)}export function getEnvelopes(){return t.getSnapshot()}export function getAttachStatus(){return r}export function clearEnvelopes(){t.clear()}export function replaceEnvelopes(e){t.set(e)}export function getMirroredUsage(){return n}export function replaceUsage(e){n=e??null;t.notify()}export function replaceAttachStatus(e){setAttachStatus(e)}export function onClear(e){return t.onClear(e)}
@@ -0,0 +1 @@
1
+ "use strict";import{approxJsonSize as i}from"@buoy-gg/shared-ui";import{subscribe as u,getEnvelopes as p,getAttachStatus as l,clearEnvelopes as b}from"../store/sentryEnvelopeStore";import{computeUsage as y,projectMonthly as m}from"../quota/usage";import{getDrops as d,clearDrops as f,summarizeDrops as g,subscribeDrops as E}from"../drops/dropStore";const h=50;export function wireDrops(t){const s=e=>e.category==="span"||e.category==="transaction";const o=t.filter(e=>!s(e)).slice(-20);const r=t.filter(s).slice(-(h-o.length));return[...o,...r].sort((e,n)=>e.timestamp-n.timestamp||e.id-n.id)}const w=1.5*1024*1024;const c=32*1024;const D=Object.freeze({__buoyTruncated:true,note:"Payload omitted \u2014 inspect it on-device."});const a=new WeakMap;function M(t){const s=a.get(t);if(s)return s;const o=i(t,c);const r=o.bytes<=c?{envelope:t,bytes:o.bytes}:(()=>{const e={...t,items:t.items.map(n=>({...n,payload:n.payload===void 0?void 0:D}))};return{envelope:e,bytes:i(e,c).bytes}})();a.set(t,r);return r}function S(t){const s=[];let o=48;for(let r=t.length-1;r>=0;r--){const e=M(t[r]);if(o+e.bytes>w&&s.length>0)break;s.push(e.envelope);o+=e.bytes}s.reverse();return s}export const sentrySyncAdapter={version:1,getSnapshot(){const t=p();const s=d();const o=y(t);const r=m(o);return{status:l(),envelopes:S(t),usage:{session:o.totals,projectedMonthly:r.usable?r.totals:null,windowMs:o.windowMs,topContributors:o.contributors.slice(0,8).map(e=>({label:e.label,category:e.category,units:e.units}))},drops:{summary:g(s),recent:wireDrops(s)}}},subscribe(t){const s=u(t);const o=E(t);return()=>{s();o()}},actions:{clearEnvelopes(){b();f();return{cleared:true}}}};
@@ -0,0 +1 @@
1
+ "use strict";import{describe as p,expect as t,it as i}from"vitest";import{wireDrops as a}from"./sentrySyncAdapter";const s=(r,e,o="other")=>({id:r,timestamp:r,reason:o,category:e,quantity:1,detail:`${e} ${r}`,source:"reported"});p("wireDrops",()=>{i("keeps a dropped error on the wire when hundreds of span drops came after it",()=>{const r=[s(1,"error","before_send"),...Array.from({length:200},(o,n)=>s(n+2,"span"))];const e=a(r);t(e.length).toBeLessThanOrEqual(50);t(e.some(o=>o.reason==="before_send")).toBe(true);t(e[0].id).toBe(1)})});
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SentryRoot — optional mount-once helper that (re)starts envelope capture.
3
+ *
4
+ * The preset already installs capture at import time, so most apps don't need
5
+ * this. It exists for the case where auto-require of `@sentry/react-native`
6
+ * can't resolve (some monorepo/Metro setups): pass the app's own `getClient`
7
+ * to attach deterministically.
8
+ *
9
+ * ```tsx
10
+ * import * as Sentry from '@sentry/react-native';
11
+ * <SentryRoot getClient={Sentry.getClient} />
12
+ * ```
13
+ */
14
+ export declare function SentryRoot({ getClient }?: {
15
+ getClient?: () => unknown;
16
+ }): null;
17
+ //# sourceMappingURL=SentryRoot.d.ts.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Holds the live client so the audit can read the RESOLVED options.
3
+ *
4
+ * The audit needs what `Sentry.init` actually produced — defaults merged,
5
+ * plugin config applied — which only exists on the client. Keeping the
6
+ * reference here (rather than passing a client through the UI) means the
7
+ * audit view stays a pure function of a plain options object, and the whole
8
+ * rule set can be tested with no SDK present.
9
+ */
10
+ import type { AuditInput } from "./rules";
11
+ export declare function setAuditSource(next: unknown): void;
12
+ /**
13
+ * Snapshot the options for the audit, or null when no client is attached.
14
+ * Options are shallow-copied so a later SDK mutation can't change a finding
15
+ * after it was rendered.
16
+ */
17
+ export declare function readAuditInput(): AuditInput | null;
18
+ //# sourceMappingURL=auditSource.d.ts.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * PII scan — find personal data in what the app is about to send.
3
+ *
4
+ * Runs over captured envelope payloads and reports the JSON path of each
5
+ * hit, so a finding points at the field to fix rather than at "an event".
6
+ *
7
+ * Two design rules keep this honest:
8
+ *
9
+ * - **Never store the value.** A scanner that keeps the secret it found is
10
+ * a second copy of the leak. Findings carry a path, a kind, and a
11
+ * redacted sample with the middle removed.
12
+ * - **Prefer precision over recall.** Every false positive costs the
13
+ * reader trust in the whole list, so patterns are anchored and the
14
+ * obviously-safe keys the SDK sets itself are skipped.
15
+ */
16
+ export type PiiKind = "email" | "auth-token" | "credit-card" | "phone" | "coordinates" | "sensitive-key";
17
+ export interface PiiFinding {
18
+ kind: PiiKind;
19
+ /** Dotted path inside the payload, e.g. `contexts.user.email`. */
20
+ path: string;
21
+ /** Value with its middle removed — enough to recognize, not to use. */
22
+ sample: string;
23
+ }
24
+ /**
25
+ * Scan one sanitized payload. Returns at most `limit` findings — a payload
26
+ * that leaks in fifty places has one problem, not fifty, and an unbounded
27
+ * list would dominate the panel.
28
+ */
29
+ export declare function scanForPii(payload: unknown, limit?: number): PiiFinding[];
30
+ /** Copy for each kind, shown next to the finding. */
31
+ export declare const PII_KIND_LABEL: Record<PiiKind, string>;
32
+ //# sourceMappingURL=pii.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A PII scanner is only useful if its findings are trusted, so the two
3
+ * properties worth pinning are: it never keeps the secret it found, and it
4
+ * stays quiet on the SDK's own metadata (which is full of hex ids and a DSN
5
+ * public key that naive patterns match).
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=pii.test.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Config audit — grade the SDK options the app actually resolved.
3
+ *
4
+ * `Sentry.init` merges your options with defaults, plugin config, and
5
+ * environment; what runs is rarely what you typed. Every rule here reads the
6
+ * RESOLVED options off the live client, so it reports the config that is
7
+ * really shipping.
8
+ *
9
+ * A finding must say what to change, not just that something is set — a
10
+ * grader that only scolds is noise. Rules are pure: options in, findings
11
+ * out, no client access.
12
+ */
13
+ export type FindingSeverity = "critical" | "warning" | "info";
14
+ export type FindingCategory = "quota" | "privacy" | "correctness";
15
+ export interface AuditFinding {
16
+ id: string;
17
+ severity: FindingSeverity;
18
+ category: FindingCategory;
19
+ title: string;
20
+ /** What the current config does, and what to do instead. */
21
+ detail: string;
22
+ }
23
+ /** The subset of ReactNativeOptions the rules read. Deliberately loose: the
24
+ * installed SDK version decides what's actually present. */
25
+ export interface AuditableOptions {
26
+ dsn?: unknown;
27
+ release?: unknown;
28
+ dist?: unknown;
29
+ environment?: unknown;
30
+ debug?: unknown;
31
+ sampleRate?: unknown;
32
+ tracesSampleRate?: unknown;
33
+ tracesSampler?: unknown;
34
+ profilesSampleRate?: unknown;
35
+ replaysSessionSampleRate?: unknown;
36
+ replaysOnErrorSampleRate?: unknown;
37
+ sendDefaultPii?: unknown;
38
+ attachScreenshot?: unknown;
39
+ attachViewHierarchy?: unknown;
40
+ enableLogs?: unknown;
41
+ enableAutoConsoleLogs?: unknown;
42
+ maxBreadcrumbs?: unknown;
43
+ enableNative?: unknown;
44
+ enableAutoSessionTracking?: unknown;
45
+ [key: string]: unknown;
46
+ }
47
+ export interface AuditInput {
48
+ options: AuditableOptions;
49
+ /** Integration names registered on the client. */
50
+ integrations: string[];
51
+ /** Whether the bundle is a dev build (`__DEV__`). */
52
+ isDev: boolean;
53
+ }
54
+ export declare function auditConfig(input: AuditInput): AuditFinding[];
55
+ /** Highest severity present, for the summary chip. */
56
+ export declare function worstSeverity(findings: AuditFinding[]): FindingSeverity | null;
57
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The audit's value is that a finding is trustworthy: every one should be
3
+ * something a developer would actually change. So these pin both directions
4
+ * — the rule fires when it should, and stays quiet when it shouldn't.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=rules.test.d.ts.map
@@ -0,0 +1,18 @@
1
+ import type { CapturedEnvelope } from "../types";
2
+ /**
3
+ * Parse one raw envelope (`[headers, items]`, each item `[itemHeader, payload]`)
4
+ * into a captured record. Fully defensive — a malformed envelope is recorded
5
+ * as an opaque entry rather than thrown on.
6
+ */
7
+ export declare function parseEnvelope(raw: unknown): Omit<CapturedEnvelope, "id">;
8
+ /** Subscribe to one client's `beforeEnvelope`. Returns true when hooked. */
9
+ export declare function attachToClient(client: unknown): boolean;
10
+ /**
11
+ * Install envelope capture. Idempotent; safe to call from both the preset's
12
+ * import-time side effect and `SentryRoot`. Polls for a client until one
13
+ * exists (Sentry.init usually runs after module evaluation).
14
+ */
15
+ export declare function installSentryCapture(options?: {
16
+ getClient?: () => unknown;
17
+ }): void;
18
+ //# sourceMappingURL=attach.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * parseEnvelope sits on the app's error-reporting path and reads whatever
3
+ * shape the installed SDK version happens to produce. Two properties matter
4
+ * more than any field it extracts: it must never throw, and the span count
5
+ * must be right (spans are billed individually).
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=attach.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ export declare function loadSentrySDK(): {
2
+ getClient?: () => unknown;
3
+ } | null;
4
+ //# sourceMappingURL=sentrySDK.d.ts.map
@@ -0,0 +1,4 @@
1
+ export declare function loadSentrySDK(): {
2
+ getClient?: () => unknown;
3
+ } | null;
4
+ //# sourceMappingURL=sentrySDK.web.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { SentryIcon } from '@buoy-gg/shared-ui';
2
+ export declare const SENTRY_ICON_COLOR = "#9945FF";
3
+ //# sourceMappingURL=SentryIcon.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { SentryIcon } from '@buoy-gg/shared-ui';
2
+ export declare const SENTRY_ICON_COLOR = "#9945FF";
3
+ //# sourceMappingURL=SentryIcon.native.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SentryModal — the tool the floating menu mounts (launchMode "self-modal").
3
+ * JsModal shell + shared header/body, same composition as the Console tool.
4
+ */
5
+ import React from "react";
6
+ import type { ModalState } from "@buoy-gg/shared-ui";
7
+ interface SentryModalProps {
8
+ visible: boolean;
9
+ onClose: () => void;
10
+ onBack?: () => void;
11
+ onMinimize?: (state: ModalState) => void;
12
+ persistenceKey?: string;
13
+ }
14
+ export declare function SentryModal({ visible, onClose, onBack, onMinimize, persistenceKey, }: SentryModalProps): React.JSX.Element;
15
+ export {};
16
+ //# sourceMappingURL=SentryModal.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * SentryAuditView — the resolved config, graded, plus PII found in what the
3
+ * app has actually sent this session.
4
+ *
5
+ * Config findings come from the live client's merged options; PII findings
6
+ * come from scanning captured payloads. They're shown together because they
7
+ * answer the same question before a release: is this safe and affordable to
8
+ * ship?
9
+ *
10
+ * A finding is a paragraph, not a row, so these are `DevToolsCard`s — the
11
+ * same card the Network tool builds its detail sections from — labelled with
12
+ * the shared `Pill` and grouped under `NightSectionLabel`.
13
+ */
14
+ import React from "react";
15
+ import type { SentryController } from "./useSentryController";
16
+ export declare function SentryAuditView({ c }: {
17
+ c: SentryController;
18
+ }): React.JSX.Element;
19
+ //# sourceMappingURL=SentryAuditView.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SentryBody — envelope list + envelope detail sub-view.
3
+ *
4
+ * Both are built from the shared row and card primitives every other tool
5
+ * uses — `CompactRow` for the feed (the same row Events, Network, Redux and
6
+ * react-query render), `DevToolsCard` + `InfoRow` + `Pill` for the detail —
7
+ * so an envelope reads like a request or an action rather than like a
8
+ * different app.
9
+ */
10
+ import React from "react";
11
+ import type { SentryController } from "./useSentryController";
12
+ export declare function SentryBody({ c }: {
13
+ c: SentryController;
14
+ }): React.JSX.Element;
15
+ //# sourceMappingURL=SentryBody.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * SentryCostView — what this session would cost, in Sentry's own billing units.
3
+ *
4
+ * Two readings, deliberately separated: what actually left the device
5
+ * ("this session"), and what that rate implies for a month against a plan
6
+ * quota. The projection is a straight line from one session on one device
7
+ * — good for catching a config that is wildly off, not a forecast — so it
8
+ * labels itself as such and refuses to draw at all on a too-short window.
9
+ *
10
+ * Built from the shared card/row/chip primitives: the plan picker is a
11
+ * `ChipStrip` of `NightChip`s (the same strip react-query filters with), each
12
+ * category is a `DevToolsCard`, and its two readings are `InfoRow`s — so a
13
+ * quota row lines up with a header row in any other tool.
14
+ */
15
+ import React from "react";
16
+ import type { SentryController } from "./useSentryController";
17
+ export declare function SentryCostView({ c }: {
18
+ c: SentryController;
19
+ }): React.JSX.Element;
20
+ //# sourceMappingURL=SentryCostView.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SentryDropsView — "where did my event go?"
3
+ *
4
+ * A summary strip by reason, then the individual records newest-first. Each
5
+ * row says which seam saw it: `observed` means we watched it happen,
6
+ * `reported` means the SDK told us afterwards and can only give a count.
7
+ * Tapping a row opens what the reason actually means.
8
+ *
9
+ * Same `ChipStrip` + `CompactRow` pairing as react-query's status filters and
10
+ * the Events timeline, so a drop reads like any other captured record.
11
+ */
12
+ import React from "react";
13
+ import type { SentryController } from "./useSentryController";
14
+ export declare function SentryDropsView({ c }: {
15
+ c: SentryController;
16
+ }): React.JSX.Element;
17
+ //# sourceMappingURL=SentryDropsView.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SentryHeader — the Sentry tool's header.
3
+ *
4
+ * Deliberately the same shape as Console's and Network's: one `ModalHeader`
5
+ * row holding the tab selector and the action icons, nothing stacked above
6
+ * it. Capture status and totals used to ride here and made this tool's bar
7
+ * taller than every other tool's; they live in the feed now (see
8
+ * `SentryBody`), which is where the rest of the captured traffic is.
9
+ *
10
+ * The detail view swaps the whole bar for a back-navigable envelope header.
11
+ */
12
+ import React from "react";
13
+ import type { SentryController } from "./useSentryController";
14
+ interface SentryHeaderProps {
15
+ c: SentryController;
16
+ /** App-stack back (from the floating menu's AppHost), list view only. */
17
+ onBack?: () => void;
18
+ }
19
+ export declare function SentryHeader({ c, onBack }: SentryHeaderProps): React.JSX.Element;
20
+ export {};
21
+ //# sourceMappingURL=SentryHeader.d.ts.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * SentryToolView — the tool without JsModal, for anywhere that supplies its
3
+ * own window: the desktop dashboard panel, and web mirrors.
4
+ *
5
+ * Exists so the desktop renders THIS component rather than re-implementing
6
+ * the tool: two implementations of one tool is how they drift.
7
+ *
8
+ * The header is wrapped in a plain, flex-less View on purpose. `ModalHeader`
9
+ * is styled `flex: 1` for JsModal's row-direction header slot; dropped
10
+ * straight into a column it grows vertically and splits the panel in half.
11
+ */
12
+ import React from "react";
13
+ export declare function SentryToolView(): React.JSX.Element;
14
+ //# sourceMappingURL=SentryToolView.d.ts.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * How the capture layer's state reads to a human.
3
+ *
4
+ * Its own module because the feed renders it and the desktop panel may want
5
+ * it too — and because the header, which used to own it, must stay the plain
6
+ * tabs-and-actions bar every other tool has.
7
+ */
8
+ import type { SentryAttachStatus } from "../types";
9
+ export declare const STATUS_META: Record<SentryAttachStatus, {
10
+ label: string;
11
+ color: string;
12
+ }>;
13
+ //# sourceMappingURL=attachStatus.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Layout constants shared by the tool's four tabs.
3
+ *
4
+ * Lives in its own module rather than in `SentryBody` because the tab views
5
+ * import it and `SentryBody` imports them — the constant travelling the other
6
+ * way would close a require cycle.
7
+ */
8
+ /**
9
+ * Gap between the tab bar and the first thing in a tab.
10
+ *
11
+ * All four tabs open directly under the bar, so they have to clear it by the
12
+ * same amount or switching tabs makes the content jump. A list whose rows
13
+ * carry their own vertical margin subtracts that margin from this.
14
+ */
15
+ export declare const SENTRY_BODY_TOP_INSET = 16;
16
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * useSentryController — shared view state for the Sentry tool's header and
3
+ * body (same split as the Console tool, so the desktop can reuse both pieces
4
+ * unchanged).
5
+ */
6
+ import type { CapturedEnvelope, SentryAttachStatus } from "../types";
7
+ import { type SentryPlan } from "../quota/plans";
8
+ import { projectMonthly, type UsageReport } from "../quota/usage";
9
+ import type { DropRecord, DropReason } from "../drops/types";
10
+ import { type AuditFinding } from "../audit/rules";
11
+ import { type PiiFinding } from "../audit/pii";
12
+ export type SentryView = "list" | "detail";
13
+ /** Top-level tab: the envelope feed, the billing-unit meter over it, why
14
+ * events went missing, or the config/privacy grade. */
15
+ export type SentryTab = "feed" | "cost" | "drops" | "audit";
16
+ export interface SentryController {
17
+ /** Which top-level tab is showing. */
18
+ tab: SentryTab;
19
+ setTab: (tab: SentryTab) => void;
20
+ /** Current sub-view: envelope list or one envelope's detail. */
21
+ view: SentryView;
22
+ /** All captured envelopes, oldest first (as stored). */
23
+ envelopes: CapturedEnvelope[];
24
+ /** Rows for the list, newest first. */
25
+ rows: CapturedEnvelope[];
26
+ /** The envelope open in the detail view, if any. */
27
+ selected: CapturedEnvelope | undefined;
28
+ /** Capture layer status. */
29
+ status: SentryAttachStatus;
30
+ /** Sum of captured bytes across the buffer. */
31
+ totalBytes: number;
32
+ /** Billing units derived from the captured envelopes. */
33
+ usage: UsageReport;
34
+ /** Straight-line monthly projection of the observed rate. */
35
+ projection: ReturnType<typeof projectMonthly>;
36
+ /** Plan the meter measures against. */
37
+ plan: SentryPlan;
38
+ setPlanId: (planId: string) => void;
39
+ /** Drops recorded this session, oldest first (as stored). */
40
+ drops: DropRecord[];
41
+ /** Drops newest-first, for the list. */
42
+ dropRows: DropRecord[];
43
+ /** Drops grouped by reason, most units first. */
44
+ dropSummary: {
45
+ reason: DropReason;
46
+ units: number;
47
+ records: number;
48
+ }[];
49
+ /** Graded config, or null when no client is attached to read options from. */
50
+ audit: {
51
+ findings: AuditFinding[];
52
+ } | null;
53
+ /** Personal data found in payloads sent this session. */
54
+ piiFindings: PiiFinding[];
55
+ openDetail: (id: number) => void;
56
+ closeDetail: () => void;
57
+ clear: () => void;
58
+ }
59
+ export declare function useSentryController(): SentryController;
60
+ /**
61
+ * Byte formatting for envelope sizes and quota volumes.
62
+ *
63
+ * Re-exported from shared-ui rather than kept local: this tool prints byte
64
+ * counts spanning a 1 KB log line to a 5 GB plan allowance, and the shared
65
+ * formatter is the one that carries units past MB — a local one rendered the
66
+ * free plan's log quota as "5120.00 MB".
67
+ */
68
+ export { formatBytes } from "@buoy-gg/shared-ui";
69
+ //# sourceMappingURL=useSentryController.d.ts.map