@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.
Files changed (100) hide show
  1. package/BuoyBugLinks.podspec +18 -0
  2. package/LICENSE +58 -0
  3. package/README.md +25 -2
  4. package/android/build.gradle +31 -0
  5. package/android/consumer-rules.pro +8 -0
  6. package/android/src/main/AndroidManifest.xml +1 -0
  7. package/android/src/main/java/gg/buoy/buglinks/frames/BuoyFramesModule.kt +53 -0
  8. package/android/src/main/java/gg/buoy/buglinks/frames/BuoyFramesPackage.kt +11 -0
  9. package/android/src/main/java/gg/buoy/buglinks/frames/FrameEngine.kt +707 -0
  10. package/android/src/main/java/gg/buoy/buglinks/frames/FrameWalker.kt +181 -0
  11. package/ios/BuoyFrames.m +17 -0
  12. package/ios/BuoyFrames.swift +640 -0
  13. package/ios/BuoyFramesWalk.swift +213 -0
  14. package/lib/commonjs/components/BugLinksBody.js +1 -0
  15. package/lib/commonjs/components/TeamBugs.js +1 -0
  16. package/lib/commonjs/components/focus.js +1 -0
  17. package/lib/commonjs/components/overlays.js +1 -0
  18. package/lib/commonjs/components/topBanners.js +1 -0
  19. package/lib/commonjs/engine/client.js +1 -0
  20. package/lib/commonjs/engine/credential.js +1 -0
  21. package/lib/commonjs/engine/linksStore.js +1 -0
  22. package/lib/commonjs/engine/loader.js +1 -0
  23. package/lib/commonjs/engine/opener.js +1 -0
  24. package/lib/commonjs/engine/otherTabs.js +1 -0
  25. package/lib/commonjs/engine/phoneCheck.js +1 -0
  26. package/lib/commonjs/engine/phoneCrypto.js +1 -0
  27. package/lib/commonjs/engine/randomBytes.js +1 -0
  28. package/lib/commonjs/engine/reporter.js +1 -0
  29. package/lib/commonjs/engine/sandbox.js +1 -0
  30. package/lib/commonjs/engine/settings.js +1 -0
  31. package/lib/commonjs/engine/tabStorage.js +1 -0
  32. package/lib/commonjs/engine/teamKeys.js +1 -0
  33. package/lib/commonjs/engine/teamList.js +1 -0
  34. package/lib/commonjs/index.js +1 -0
  35. package/lib/commonjs/media/BuoyMask.js +1 -0
  36. package/lib/commonjs/media/frames.js +1 -0
  37. package/lib/commonjs/media/nativeFrames.js +1 -0
  38. package/lib/commonjs/media/nativeFrames.web.js +1 -0
  39. package/lib/commonjs/media/webFrames.js +14 -0
  40. package/lib/commonjs/package.json +1 -0
  41. package/lib/commonjs/preset.js +1 -0
  42. package/lib/commonjs/sync/bugLinksSyncAdapter.js +1 -0
  43. package/lib/module/components/BugLinksBody.js +1 -0
  44. package/lib/module/components/TeamBugs.js +1 -0
  45. package/lib/module/components/focus.js +1 -0
  46. package/lib/module/components/overlays.js +1 -0
  47. package/lib/module/components/topBanners.js +1 -0
  48. package/lib/module/engine/client.js +1 -0
  49. package/lib/module/engine/credential.js +1 -0
  50. package/lib/module/engine/linksStore.js +1 -0
  51. package/lib/module/engine/loader.js +1 -0
  52. package/lib/module/engine/opener.js +1 -0
  53. package/lib/module/engine/otherTabs.js +1 -0
  54. package/lib/module/engine/phoneCheck.js +1 -0
  55. package/lib/module/engine/phoneCrypto.js +1 -0
  56. package/lib/module/engine/randomBytes.js +1 -0
  57. package/lib/module/engine/reporter.js +1 -0
  58. package/lib/module/engine/sandbox.js +1 -0
  59. package/lib/module/engine/settings.js +1 -0
  60. package/lib/module/engine/tabStorage.js +1 -0
  61. package/lib/module/engine/teamKeys.js +1 -0
  62. package/lib/module/engine/teamList.js +1 -0
  63. package/lib/module/index.js +1 -0
  64. package/lib/module/media/BuoyMask.js +1 -0
  65. package/lib/module/media/frames.js +1 -0
  66. package/lib/module/media/nativeFrames.js +1 -0
  67. package/lib/module/media/nativeFrames.web.js +1 -0
  68. package/lib/module/media/webFrames.js +14 -0
  69. package/lib/module/preset.js +1 -0
  70. package/lib/module/sync/bugLinksSyncAdapter.js +1 -0
  71. package/lib/typescript/components/BugLinksBody.d.ts +2 -0
  72. package/lib/typescript/components/TeamBugs.d.ts +8 -0
  73. package/lib/typescript/components/focus.d.ts +6 -0
  74. package/lib/typescript/components/overlays.d.ts +10 -0
  75. package/lib/typescript/components/topBanners.d.ts +35 -0
  76. package/lib/typescript/engine/client.d.ts +78 -0
  77. package/lib/typescript/engine/credential.d.ts +11 -0
  78. package/lib/typescript/engine/linksStore.d.ts +132 -0
  79. package/lib/typescript/engine/loader.d.ts +172 -0
  80. package/lib/typescript/engine/opener.d.ts +21 -0
  81. package/lib/typescript/engine/otherTabs.d.ts +52 -0
  82. package/lib/typescript/engine/phoneCheck.d.ts +8 -0
  83. package/lib/typescript/engine/phoneCrypto.d.ts +16 -0
  84. package/lib/typescript/engine/randomBytes.d.ts +7 -0
  85. package/lib/typescript/engine/reporter.d.ts +53 -0
  86. package/lib/typescript/engine/sandbox.d.ts +83 -0
  87. package/lib/typescript/engine/settings.d.ts +42 -0
  88. package/lib/typescript/engine/tabStorage.d.ts +6 -0
  89. package/lib/typescript/engine/teamKeys.d.ts +37 -0
  90. package/lib/typescript/engine/teamList.d.ts +63 -0
  91. package/lib/typescript/index.d.ts +23 -0
  92. package/lib/typescript/media/BuoyMask.d.ts +19 -0
  93. package/lib/typescript/media/frames.d.ts +34 -0
  94. package/lib/typescript/media/nativeFrames.d.ts +2 -0
  95. package/lib/typescript/media/nativeFrames.web.d.ts +2 -0
  96. package/lib/typescript/media/webFrames.d.ts +178 -0
  97. package/lib/typescript/preset.d.ts +50 -0
  98. package/lib/typescript/sync/bugLinksSyncAdapter.d.ts +41 -0
  99. package/lib/web/index.mjs +20 -0
  100. package/package.json +119 -3
@@ -0,0 +1,213 @@
1
+ // What to box in a frame (research/bug-links-2026-10/media/SPEC.md, H rules).
2
+ // Runs on the main thread. It never throws: anything it can't finish is a
3
+ // reason, and the frame is dropped (H2).
4
+ import UIKit
5
+ import WebKit
6
+ import MapKit
7
+ import AVFoundation
8
+
9
+ struct BuoyMaskSettings {
10
+ var text = true
11
+ var images = true
12
+ var inputs = true
13
+ }
14
+
15
+ /// One walk of the app's windows.
16
+ struct BuoyWalk {
17
+ /// Every part to box, by view, in the frame's coordinates (points).
18
+ var parts: [ObjectIdentifier: CGRect] = [:]
19
+ /// A Buoy screen (dial or tool sheet) is open (F5).
20
+ var buoyOpen = false
21
+ /// Set when the walk couldn't finish. The frame must be dropped.
22
+ var failed: String?
23
+ }
24
+
25
+ enum BuoyPartKind { case none, text, input, secure, image, web, opaque }
26
+
27
+ @available(iOSApplicationExtension, unavailable)
28
+ enum BuoyWalker {
29
+ /// Buoy's own views, by nativeID. The dial and tool sheets mean Buoy is open.
30
+ static let buoyOpenIDs: Set<String> = ["dial-devtools-root", "buoy-tool-host"]
31
+ static let buoyRootID = "floating-devtools-root"
32
+ static let maskID = "buoy-mask"
33
+ static let unmaskID = "buoy-unmask"
34
+ /// A part's box grows this much on every side (H10).
35
+ static let grow: CGFloat = 2
36
+
37
+ static func walk(_ windows: [UIWindow], in frame: CGRect, settings: BuoyMaskSettings, budgetMs: Double) -> BuoyWalk {
38
+ var out = BuoyWalk()
39
+ let start = CACurrentMediaTime()
40
+ var visited = 0
41
+ // An explicit stack, not recursion: a deep tree can't overflow it.
42
+ struct Item { let view: UIView; let window: UIWindow; let unmask: Bool; let inBuoy: Bool }
43
+ var stack: [Item] = []
44
+ for w in windows.reversed() { stack.append(Item(view: w, window: w, unmask: false, inBuoy: false)) }
45
+ let screenArea = max(1, frame.width * frame.height)
46
+
47
+ while let item = stack.popLast() {
48
+ visited += 1
49
+ if visited & 127 == 0, (CACurrentMediaTime() - start) * 1000 > budgetMs {
50
+ out.failed = "the screen has too many views to check in time"
51
+ return out
52
+ }
53
+ let v = item.view
54
+ // Only views that can't show any pixel are skipped. Faint is still visible.
55
+ if v.isHidden || v.alpha <= 0 || v.layer.opacity <= 0 { continue }
56
+
57
+ let id = nativeID(v) ?? sentryMarker(v)
58
+ var unmask = item.unmask
59
+ var inBuoy = item.inBuoy
60
+
61
+ if let id {
62
+ if buoyOpenIDs.contains(id) {
63
+ if rect(of: v, in: item.window, frame: frame) != nil { out.buoyOpen = true }
64
+ continue
65
+ }
66
+ if id == buoyRootID { inBuoy = true }
67
+ if id == unmaskID { unmask = true }
68
+ }
69
+
70
+ // Some system views crash when looked into (Sentry found the iOS 26 camera
71
+ // chrome). Box them whole from their plain frame and skip what's inside.
72
+ if fragile(v) {
73
+ if let r = plainRect(of: v, in: item.window, frame: frame) { out.parts[ObjectIdentifier(v)] = r }
74
+ continue
75
+ }
76
+
77
+ let kind = classify(v)
78
+ var box = false
79
+ if kind == .secure {
80
+ box = true // H5: always, Mask or not, settings or not
81
+ } else if inBuoy {
82
+ // F5: Buoy's floating button never shows. Its full-screen holder isn't boxed.
83
+ if let r = rect(of: v, in: item.window, frame: frame), r.width * r.height < screenArea * 0.6, hasContent(v) {
84
+ box = true
85
+ }
86
+ } else if id == maskID {
87
+ box = true // H6, and H8: Mask beats Unmask
88
+ } else {
89
+ switch kind {
90
+ case .text: box = settings.text && !unmask
91
+ case .input: box = settings.inputs && !unmask
92
+ case .image: box = settings.images && !unmask
93
+ case .web, .opaque: box = !unmask // H5a
94
+ case .secure, .none: box = false
95
+ }
96
+ }
97
+
98
+ if box, let r = rect(of: v, in: item.window, frame: frame) {
99
+ out.parts[ObjectIdentifier(v)] = r
100
+ }
101
+ // H12: skip what's inside a box only when the box clips it. A child can
102
+ // draw past a parent that doesn't clip, so walk it too.
103
+ if box && clips(v) && kind != .input { continue }
104
+ // A Mask box covers the whole part, but secure fields inside still get
105
+ // their own box in case the Mask view doesn't clip.
106
+ for s in v.subviews.reversed() {
107
+ stack.append(Item(view: s, window: item.window, unmask: unmask, inBuoy: inBuoy))
108
+ }
109
+ }
110
+ return out
111
+ }
112
+
113
+ /// RN's nativeID: `nativeID` on the old architecture, `nativeId` on Fabric.
114
+ static func nativeID(_ v: UIView) -> String? {
115
+ for name in ["nativeID", "nativeId"] {
116
+ let sel = NSSelectorFromString(name)
117
+ if v.responds(to: sel), let s = v.perform(sel)?.takeUnretainedValue() as? String, !s.isEmpty { return s }
118
+ }
119
+ return nil
120
+ }
121
+
122
+ /// Sentry's <Sentry.Mask> and <Sentry.Unmask> count as ours, so an app
123
+ /// that already marked its private parts for Sentry is covered here too.
124
+ static func sentryMarker(_ v: UIView) -> String? {
125
+ let name = NSStringFromClass(type(of: v))
126
+ if name.hasPrefix("RNSentryReplayMask") { return maskID }
127
+ if name.hasPrefix("RNSentryReplayUnmask") { return unmaskID }
128
+ return nil
129
+ }
130
+
131
+ static func classify(_ v: UIView) -> BuoyPartKind {
132
+ if let f = v as? UITextField { return f.isSecureTextEntry ? .secure : .input }
133
+ if let t = v as? UITextView { return t.isSecureTextEntry ? .secure : (t.isEditable ? .input : .text) }
134
+ if v is WKWebView { return .web }
135
+ if v is UILabel { return .text }
136
+ if let iv = v as? UIImageView {
137
+ // Bar backgrounds and empty image views are app chrome, not content.
138
+ if iv.image == nil { return .none }
139
+ if let sup = iv.superview, NSStringFromClass(type(of: sup)).hasPrefix("_UIBarBackground") { return .none }
140
+ return .image
141
+ }
142
+ if v is MKMapView { return .opaque }
143
+ let layer = v.layer
144
+ if layer is AVPlayerLayer || layer is CAMetalLayer { return .opaque }
145
+ if let gl = NSClassFromString("CAEAGLLayer"), layer.isKind(of: gl) { return .opaque }
146
+ let name = NSStringFromClass(type(of: v))
147
+ if name.contains("Secure") { return .secure }
148
+ if name.contains("TextInput") || name.contains("TextField") || name.contains("SearchBar") { return .input }
149
+ // SFSafariView also holds sign-in pages (ASWebAuthenticationSession).
150
+ if name.contains("WebView") || name.contains("SFSafariView") { return .web }
151
+ if name.contains("PDFView") { return .opaque }
152
+ // RN's own root views hold the whole app. Never treat them as one opaque part.
153
+ if name.hasPrefix("RCTSurface") || name.hasPrefix("RCTRoot") { return .none }
154
+ if name.contains("UIHostingView") { return .opaque } // SwiftUI inside UIKit
155
+ // UIKit's own private parts (like a text field's _UITextLayoutCanvasView)
156
+ // belong to the control above them, which was already sorted.
157
+ if name.hasPrefix("_UI") { return .none }
158
+ for word in ["Skia", "MapView", "Video", "Player", "Camera", "Preview", "GLView", "Metal", "Canvas", "Chart"] {
159
+ if name.contains(word) { return .opaque }
160
+ }
161
+ if name.contains("Image") || name.contains("Svg") || name.contains("SVG") || name.contains("Lottie") || name.contains("Rive") { return .image }
162
+ // Fabric "Paragraph", old-architecture "RCTTextView", UIKit and SDK labels.
163
+ if name.contains("Paragraph") || name.contains("Text") || name.contains("Label") { return .text }
164
+ return .none
165
+ }
166
+
167
+ static func clips(_ v: UIView) -> Bool { v.clipsToBounds || v.layer.masksToBounds || v.subviews.isEmpty }
168
+
169
+ /// Something that draws: a leaf, or a view with a fill.
170
+ static func hasContent(_ v: UIView) -> Bool {
171
+ if v.subviews.isEmpty { return true }
172
+ if let c = v.backgroundColor, c.cgColor.alpha > 0 { return true }
173
+ return v.layer.contents != nil
174
+ }
175
+
176
+ /// Where the view is in the frame, now and in its animation, grown by H10 and
177
+ /// clipped to the frame (H9). nil when none of it is in the frame.
178
+ /// Class names whose layers crash when copied, like CameraUI's ModeLoupeLayer.
179
+ static let fragileNames = ["CameraUI.ChromeSwiftUIView"]
180
+
181
+ static func fragile(_ v: UIView) -> Bool {
182
+ let name = NSStringFromClass(type(of: v))
183
+ return fragileNames.contains { name.contains($0) }
184
+ }
185
+
186
+ /// Like rect(of:), without the animation layer, for views that crash on it.
187
+ /// An old-architecture view runs inside a new-architecture wrapper that
188
+ /// draws its background and padding. The wrapper is the part people see.
189
+ static func shownView(_ v: UIView) -> UIView {
190
+ if let sup = v.superview, NSStringFromClass(type(of: sup)).contains("LegacyViewManagerInterop") { return sup }
191
+ return v
192
+ }
193
+
194
+ static func plainRect(of v: UIView, in window: UIWindow, frame: CGRect) -> CGRect? {
195
+ let v = shownView(v)
196
+ var r = v.convert(v.bounds, to: window)
197
+ r = r.offsetBy(dx: window.frame.minX - frame.minX, dy: window.frame.minY - frame.minY)
198
+ r = r.insetBy(dx: -grow, dy: -grow).intersection(CGRect(origin: .zero, size: frame.size))
199
+ return r.isNull || r.width <= 0 || r.height <= 0 ? nil : r.integral
200
+ }
201
+
202
+ static func rect(of v: UIView, in window: UIWindow, frame: CGRect) -> CGRect? {
203
+ let v = shownView(v)
204
+ var r = v.convert(v.bounds, to: window)
205
+ if let p = v.layer.presentation(), let wp = window.layer.presentation() {
206
+ let moving = p.convert(p.bounds, to: wp)
207
+ if moving.width.isFinite && moving.height.isFinite { r = r.union(moving) }
208
+ }
209
+ r = r.offsetBy(dx: window.frame.minX - frame.minX, dy: window.frame.minY - frame.minY)
210
+ r = r.insetBy(dx: -grow, dy: -grow).intersection(CGRect(origin: .zero, size: frame.size))
211
+ return r.isNull || r.width <= 0 || r.height <= 0 ? nil : r.integral
212
+ }
213
+ }
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.BugLinksBody=BugLinksBody;var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _linksStore=require("../engine/linksStore");var _loader=require("../engine/loader");var _opener=require("../engine/opener");var _randomBytes=require("../engine/randomBytes");var _credential=require("../engine/credential");var _reporter=require("../engine/reporter");var _client=require("../engine/client");var _teamKeys=require("../engine/teamKeys");var _settings=require("../engine/settings");var _focus=require("./focus");var _TeamBugs=require("./TeamBugs");var _jsxRuntime=require("react/jsx-runtime");const count=(e,n)=>`${e} ${n}${e===1?"":"s"}`;const STATUS={sending:{label:"Sending",tone:"info"},waiting:{label:"Waiting",tone:"warning"},sent:{label:"Sent",tone:"accent"},failed:{label:"Not sent",tone:"danger"},deleting:{label:"Deleting",tone:"neutral"}};function useStore(e,n){return(0,_react.useSyncExternalStore)(e,n,n)}function when(e){const n=new Date(e);return Number.isNaN(n.getTime())?e:n.toLocaleString()}let core;function useHostClose(){if(core===void 0){try{core=require("@buoy-gg/core")}catch{core=null}}return core?.useToolHost?.().close}let lastOpened="";function BugLinksBody(){const e=(0,_sharedUi.useSafeAreaInsets)();const n=useStore(_linksStore.subscribeMyLinks,_linksStore.myLinks);const s=useStore(_loader.subscribeLoadedCase,_loader.loadedCase);const a=useStore(_settings.subscribeBugLinksSettings,_settings.bugLinksServer);const u=useStore(_focus.subscribeFocus,_focus.focusedLink);const y=useStore(_focus.subscribeFocus,_focus.wantsSignIn);const v=!(0,_credential.currentCredential)()||n.some(t=>t.errorCode==="signed_out");const c=u?n.find(t=>t.localId===u):void 0;const[g,b]=(0,_react.useState)("");const[h,p]=(0,_react.useState)("normal");const[l,S]=(0,_react.useState)(()=>lastOpened);const[m,C]=(0,_react.useState)(false);const[x,T]=(0,_react.useState)(null);const[r,w]=(0,_react.useState)(null);const[j,N]=(0,_react.useState)(null);const[I,B]=(0,_react.useState)([]);const L=useHostClose();(0,_react.useEffect)(()=>{if(!s?.report.replay?.active)return B([]);let t=true;const o=()=>void(0,_loader.blockedCalls)().then(D=>t&&B(D));o();const d=setInterval(o,3e3);return()=>{t=false;clearInterval(d)}},[s?.loadId,s?.report.replay?.active]);(0,_react.useEffect)(()=>{void(0,_linksStore.loadMyLinks)();void(0,_settings.loadBugLinksSettings)();void(0,_reporter.retryWaiting)();return()=>(0,_focus.focusLink)(null)},[]);const i=async(t,o)=>{_reactNative.Keyboard.dismiss();T(t);w(null);try{const d=await o();if(d)w({text:d,tone:"secondary",from:t})}catch(d){w({text:d instanceof Error?d.message:String(d),tone:"danger",from:t})}finally{T(null)}};const f=(0,_randomBytes.randomSource)();return(0,_jsxRuntime.jsxs)(_reactNative.ScrollView,{style:styles.scroll,contentContainerStyle:[styles.content,{paddingBottom:e.bottom+32}],keyboardShouldPersistTaps:"handled",children:[y?(0,_jsxRuntime.jsx)(_sharedUi.NightCard,{children:(0,_jsxRuntime.jsx)(_sharedUi.BuoySignInCard,{onClose:()=>{(0,_focus.askSignIn)(false);void(0,_reporter.retryWaiting)()}})}):v?(0,_jsxRuntime.jsxs)(_sharedUi.NightCard,{children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.title,children:"Sign in to send bug links"}),(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{children:"Your team opens bug links with their own Buoy sign-in. Cases you make now wait on this phone until you sign in."}),(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-sign-in",label:"Sign in to Buoy",variant:"primary",onPress:()=>(0,_focus.askSignIn)(true)})]}):null,c?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_sharedUi.NightSectionLabel,{children:"Add a note"}),(0,_jsxRuntime.jsx)(LinkRow,{link:c,focused:true,run:i},c.localId)]}):null,(0,_jsxRuntime.jsx)(_sharedUi.NightSectionLabel,{children:"Report a bug"}),(0,_jsxRuntime.jsxs)(_sharedUi.NightCard,{children:[(0,_jsxRuntime.jsx)(_reactNative.TextInput,{testID:"bug-links-note",value:g,onChangeText:b,placeholder:"What went wrong? (you can skip this)",placeholderTextColor:_sharedUi.night.color.placeholder,style:styles.input,multiline:true}),(0,_jsxRuntime.jsx)(PriorityPicker,{value:h,onChange:p,testIDPrefix:"bug-links-priority"}),(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-report",label:x==="report"?"Saving\u2026":"Send a bug link",variant:"primary",disabled:!!x||!f,onPress:()=>void i("report",async()=>{const t=await(0,_reporter.reportBug)({note:g.trim()||void 0,priority:h});if(t){b("");p("normal")}})}),(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{children:f?"Buoy saves the app's state, web calls, taps and screen. It takes out private data and locks the case on this phone. Only your team can open the link.":_randomBytes.NO_RANDOM_MESSAGE})]}),(0,_credential.currentCredential)()?(0,_jsxRuntime.jsx)(_TeamBugs.TeamBugs,{run:i,busy:x,onLoaded:()=>L?.()}):null,(0,_credential.currentCredential)()&&(r?.from==="assign"||r?.from==="status"||r?.from?.startsWith("load-"))?(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{tone:r.tone,style:styles.message,children:r.text}):null,s?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_sharedUi.NightSectionLabel,{children:"Loaded bug case"}),(0,_jsxRuntime.jsxs)(_sharedUi.NightCard,{children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.title,children:s.title}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.meta,children:`Reported ${when(s.createdAt)}`}),s.report.steps.map((t,o)=>(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.step,(!t.ok||t.warn)&&styles.bad],testID:`bug-links-step-${o}`,children:`${t.warn?"\u26A0":t.ok?"\u2713":"\u2717"} ${t.part}: ${t.detail}`},o)),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.rowButtons,children:[s.canLoadAgain?(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-body-load-again",label:"Load again",size:"md",fullWidth:false,onPress:()=>void i("again",async()=>{const t=await(0,_loader.loadAgain)();if(!t.loaded)throw new Error(t.steps.find(o=>!o.ok)?.detail??"The case didn't load.")})}):null,s.report.replay?.active?(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-body-live",label:"Use live requests",size:"md",fullWidth:false,onPress:()=>void i("live",async()=>{const t=await(0,_loader.leaveSandbox)();if(!t.ok)throw new Error(t.detail)})}):null,s.undoGone?null:(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-body-undo",label:"Restore my app",variant:"destructive",size:"md",fullWidth:false,onPress:()=>void i("undo",async()=>{const t=await(0,_loader.undoBugCase)();if((0,_loader.loadedCase)())throw new Error(t.find(o=>!o.ok&&o.part==="web calls")?.detail??t.find(o=>!o.ok)?.detail??"Restore didn't finish.");return"The app is back the way it was."})}),s.undoBlocked?(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-body-end",label:"End (keep the app as it is)",variant:"destructive",size:"md",fullWidth:false,onPress:()=>void i("end",async()=>{const t=await(0,_loader.endBugCase)();if(!t.ok)throw new Error(t.detail)})}):null]}),I.length?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.step,styles.bad],children:`Blocked ${I.length} calls the reporter's phone never made:`}),I.slice(0,8).map((t,o)=>(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.meta,numberOfLines:1,children:`${t.method} ${t.url}`},o))]}):null,(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{children:"Fixed it? Reload the app, then tap Load again to see if the bug is gone."})]})]}):null,(0,_jsxRuntime.jsx)(_sharedUi.NightSectionLabel,{children:"Open a bug link"}),(0,_jsxRuntime.jsxs)(_sharedUi.NightCard,{children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.pasteRow,children:[(0,_jsxRuntime.jsx)(_reactNative.TextInput,{testID:"bug-links-open-input",value:l,onChangeText:S,placeholder:"https://buoy.gg/bug/\u2026#\u2026",placeholderTextColor:_sharedUi.night.color.placeholder,autoCapitalize:"none",autoCorrect:false,style:[styles.input,styles.pasteInput]}),(0,_jsxRuntime.jsx)(Small,{label:"Paste",testID:"bug-links-open-paste",onPress:()=>void(0,_sharedUi.readClipboardText)().then(t=>{const o=t?.trim();if(o)S(o)})})]}),r?.from==="open"?(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{tone:r.tone,style:styles.message,children:r.text}):null,(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-open",label:x==="open"?"Opening\u2026":"Load bug",disabled:!!x||!l.trim(),onPress:()=>void i("open",async()=>{const t=await(0,_opener.openBugLink)(l.trim(),{restart:m});if(!t.loaded)throw new Error(t.steps.find(o=>!o.ok)?.detail??"The case didn't load.");lastOpened=l.trim();if(!m)L?.();return m?"Loaded. The app restarts now, then loads it again.":"Loaded. The bar shows what came back."})}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.switchRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.meta,children:"Restart after loading (for values the app reads at start)"}),(0,_jsxRuntime.jsx)(_sharedUi.NightSwitch,{testID:"bug-links-open-restart",value:m,onValueChange:C})]}),(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{children:"Loading needs a dev build. Web calls are answered from the case, and every other call is blocked."})]}),r&&r.from!=="open"&&r.from!=="assign"&&r.from!=="status"&&!r.from?.startsWith("load-")?(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{tone:r.tone,style:styles.message,children:r.text}):null,(0,_jsxRuntime.jsx)(_sharedUi.NightSectionLabel,{children:`Your bug links (${n.length})`}),n.length===0?(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{children:"Links you send from this phone show up here, so you never lose one."}):null,n.filter(t=>t!==c).map(t=>(0,_jsxRuntime.jsx)(LinkRow,{link:t,focused:false,run:i},t.localId)),(0,_jsxRuntime.jsx)(_sharedUi.NightSectionLabel,{children:"Server"}),(0,_jsxRuntime.jsxs)(_sharedUi.NightCard,{children:[(0,_jsxRuntime.jsx)(_reactNative.TextInput,{testID:"bug-links-server",value:j??a,onChangeText:N,autoCapitalize:"none",autoCorrect:false,style:styles.input}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.rowButtons,children:[(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-server-save",label:"Save",size:"md",fullWidth:false,disabled:j===null,onPress:()=>void i("server",async()=>{await(0,_settings.setBugLinksServer)(j.trim());N(null);return"Saved."})}),a!==_settings.DEFAULT_SERVER?(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:"bug-links-server-reset",label:"Use buoy.gg",size:"md",fullWidth:false,onPress:()=>void i("server",async()=>void await(0,_settings.setBugLinksServer)(null))}):null]}),(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{children:`Random numbers for the lock: ${f==="expo-uuid"?"Expo's native UUIDs":f==="getRandomValues"?"crypto.getRandomValues":"none found"}.`})]})]})}const PRIORITY_LABEL={urgent:"Urgent",high:"High",normal:"Normal",low:"Low"};function PriorityPicker({value:e,onChange:n,testIDPrefix:s}){return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.priority,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.meta,children:"How soon should it be fixed?"}),(0,_jsxRuntime.jsx)(_sharedUi.NightSegmented,{size:"sm",tabs:_client.BUG_PRIORITIES.map(a=>({key:a,label:PRIORITY_LABEL[a]})),activeKey:e,onChange:a=>n(a),testIDPrefix:s})]})}function LinkRow({link:e,focused:n,run:s}){const[a,u]=(0,_react.useState)(n);const[y,v]=(0,_react.useState)(e.note??"");const[c,g]=(0,_react.useState)(false);const b=(0,_linksStore.canAddNote)(e);const h=(0,_react.useRef)(null);(0,_react.useEffect)(()=>{if(!n)return;u(true);const l=setTimeout(()=>h.current?.focus(),400);return()=>clearTimeout(l)},[n]);const p=STATUS[e.status];return(0,_jsxRuntime.jsxs)(_sharedUi.NightCard,{style:styles.linkCard,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.linkTop,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.title,numberOfLines:2,children:e.note?.split("\n")[0]||(e.screen?`Bug on ${e.screen}`:`Bug from ${when(e.createdAt)}`)}),(0,_jsxRuntime.jsx)(_sharedUi.NightBadge,{label:p.label,tone:p.tone})]}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.meta,children:`${e.priority&&e.priority!=="normal"?`${PRIORITY_LABEL[e.priority]} \xB7 `:""}${when(e.createdAt)} \xB7 ${count(e.header.steps,"step")} \xB7 ${count(e.header.errors,"error")} \xB7 ${count(e.removed.length,"private value")} removed`}),e.error&&e.status!=="sent"?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.meta,styles.bad],children:e.error}):null,e.status==="sent"&&e.needsPut?(0,_jsxRuntime.jsx)(_reactNative.Text,{testID:`bug-links-note-pending-${e.localId}`,style:[styles.meta,styles.bad],children:`Note waiting to send. ${e.error??"Buoy will try again."}`}):null,e.status==="sent"&&e.wrapPending?(0,_jsxRuntime.jsx)(_reactNative.Text,{testID:`bug-links-wrap-wait-${e.localId}`,style:[styles.meta,styles.bad],children:`Not locked to your team yet, so only this link opens it. ${e.wrapWait?.message??"Buoy will try again."}`}):null,a?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(PriorityPicker,{value:e.priority??"normal",onChange:l=>void s("priority",async()=>void await(0,_reporter.setLinkPriority)(e.localId,l)),testIDPrefix:`bug-links-priority-${e.localId}`}),(0,_jsxRuntime.jsx)(_reactNative.TextInput,{ref:h,testID:`bug-links-note-${e.localId}`,value:y,onChangeText:v,placeholder:"What went wrong?",placeholderTextColor:_sharedUi.night.color.placeholder,style:styles.input,multiline:true}),(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:`bug-links-note-save-${e.localId}`,label:"Save note",size:"md",onPress:()=>void s("note",async()=>{const l=await(0,_reporter.addNote)(e.localId,y);u(false);(0,_focus.focusLink)(null);return l?.needsPut?"Note saved on this phone. It goes up when Buoy can reach the server.":"Note saved. The link stays the same."})})]}):null,(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.rowButtons,children:[e.url?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(Small,{label:"Copy",onPress:()=>void(0,_sharedUi.copyToClipboard)(e.url),testID:`bug-links-copy-${e.localId}`}),(0,_jsxRuntime.jsx)(Small,{label:"Share",onPress:()=>void _reactNative.Share.share({message:e.url}).catch(()=>{}),testID:`bug-links-share-${e.localId}`})]}):null,e.errorCode==="other_account"?(0,_jsxRuntime.jsx)(Small,{label:"Send as me",onPress:()=>void s("send",async()=>void await(0,_reporter.sendLink)(e.localId,{asMe:true})),testID:`bug-links-send-as-me-${e.localId}`}):e.status==="waiting"||e.status==="failed"?(0,_jsxRuntime.jsx)(Small,{label:"Send now",onPress:()=>void s("send",async()=>void await(0,_reporter.sendLink)(e.localId)),testID:`bug-links-send-${e.localId}`}):null,e.status==="sent"&&e.wrapPending&&e.wrapWait?.code==="new_root"&&e.wrapWait.teamKeyId&&e.server?(0,_jsxRuntime.jsx)(Small,{label:"Trust new team key",onPress:()=>void s("send",async()=>{await(0,_teamKeys.trustTeamRoot)(e.server,e.wrapWait.teamKeyId,(0,_credential.currentCredential)());await(0,_reporter.wrapLater)(e.localId);return"Locked to your team's new key."}),testID:`bug-links-trust-root-${e.localId}`}):e.status==="sent"&&e.wrapPending?(0,_jsxRuntime.jsx)(Small,{label:"Lock to team",onPress:()=>void s("send",async()=>void await(0,_reporter.wrapLater)(e.localId)),testID:`bug-links-wrap-${e.localId}`}):null,!a&&b?(0,_jsxRuntime.jsx)(Small,{label:"Note",onPress:()=>u(true),testID:`bug-links-edit-${e.localId}`}):null,c?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(Small,{label:"Delete for everyone?",danger:true,onPress:()=>void s("delete",async()=>void await(0,_reporter.deleteLink)(e.localId)),testID:`bug-links-delete-confirm-${e.localId}`}),(0,_jsxRuntime.jsx)(Small,{label:"Keep",onPress:()=>g(false),testID:`bug-links-delete-keep-${e.localId}`})]}):(0,_jsxRuntime.jsx)(Small,{label:"Delete",danger:true,onPress:()=>e.id?g(true):void s("delete",async()=>void await(0,_reporter.deleteLink)(e.localId)),testID:`bug-links-delete-${e.localId}`})]})]})}function Small({label:e,onPress:n,testID:s,danger:a}){return(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{testID:s,onPress:n,accessibilityRole:"button",accessibilityLabel:e,style:styles.small,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.smallText,a&&styles.bad],children:e})})}const styles=_reactNative.StyleSheet.create({scroll:{flex:1},priority:{gap:6},content:{padding:_sharedUi.night.gutter,gap:8},input:{color:_sharedUi.night.color.text,backgroundColor:_sharedUi.night.color.surfaceElevated,borderRadius:_sharedUi.night.radius.input,paddingHorizontal:12,paddingVertical:10,fontSize:15,marginBottom:10,minHeight:44},title:{color:_sharedUi.night.color.text,fontSize:15,fontWeight:"600",flexShrink:1},pasteRow:{flexDirection:"row",alignItems:"center",gap:6},pasteInput:{flex:1},meta:{color:_sharedUi.night.color.textSecondary,fontSize:12,marginTop:2},step:{color:_sharedUi.night.color.textSecondary,fontSize:13,marginTop:4},bad:{color:_sharedUi.night.color.danger},rowButtons:{flexDirection:"row",flexWrap:"wrap",gap:8,marginTop:10},linkCard:{marginBottom:8},linkTop:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8},small:{paddingHorizontal:10,paddingVertical:6,borderRadius:_sharedUi.night.radius.chip,backgroundColor:_sharedUi.night.color.fill},smallText:{color:_sharedUi.night.color.accent,fontSize:13,fontWeight:"600"},message:{marginVertical:4},switchRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8,marginTop:10}});
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.TeamBugs=TeamBugs;var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _settings=require("../engine/settings");var _teamList=require("../engine/teamList");var _jsxRuntime=require("react/jsx-runtime");const VIEW_LABEL={open:"Open",mine:"Mine",all:"All"};const PRIORITY={urgent:{label:"Urgent",tone:"danger"},high:{label:"High",tone:"warning"},normal:{label:"Normal",tone:"neutral"},low:{label:"Low",tone:"info"}};const STATUS_LABEL={new:"New",seen:"Seen",fixed:"Fixed",wont_fix:"Won't fix"};function ago(e){const s=Math.max(0,(Date.now()-new Date(e).getTime())/1e3);if(Number.isNaN(s))return e;if(s<60)return"now";if(s<3600)return`${Math.floor(s/60)}m`;if(s<86400)return`${Math.floor(s/3600)}h`;return`${Math.floor(s/86400)}d`}function TeamBugs({run:e,busy:s,onLoaded:l}){const n=(0,_react.useSyncExternalStore)(_teamList.subscribeTeamBugs,_teamList.teamBugs,_teamList.teamBugs);const[d,o]=(0,_react.useState)("open");const[m,c]=(0,_react.useState)(null);(0,_react.useSyncExternalStore)(_settings.subscribeBugLinksSettings,_settings.bugLinksServer,_settings.bugLinksServer);const[,g]=(0,_react.useState)(0);const h=(0,_teamList.whoNow)();(0,_react.useEffect)(()=>{const t=setInterval(()=>g(f=>(0,_teamList.whoNow)()!==h?f+1:f),1e3);return()=>clearInterval(t)},[h]);(0,_react.useEffect)(()=>{c(null);void(0,_teamList.loadTeamBugs)()},[h]);(0,_react.useEffect)(()=>()=>(0,_teamList.forgetTeamBugs)(),[]);const i=n.kind==="ready"?n.me:"";const a=n.kind==="ready"?n.bugs:[];const r=(0,_teamList.teamView)(a,d,i);const u={open:(0,_teamList.teamView)(a,"open",i).length,mine:(0,_teamList.teamView)(a,"mine",i).length,all:a.length};return(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.head,children:[(0,_jsxRuntime.jsx)(_sharedUi.NightSectionLabel,{children:"Team bugs"}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{testID:"bug-links-team-refresh",accessibilityRole:"button",accessibilityLabel:"Refresh",onPress:()=>void(0,_teamList.loadTeamBugs)(),style:styles.small,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.smallText,children:n.kind==="loading"||n.kind==="ready"&&n.refreshing?"Loading\u2026":"Refresh"})})]}),n.kind==="blocked"?(0,_jsxRuntime.jsx)(_reactNative.Text,{testID:"bug-links-team-blocked",style:[styles.meta,n.code!=="offline"&&styles.bad],children:n.message}):(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_sharedUi.NightSegmented,{size:"sm",tabs:["open","mine","all"].map(t=>({key:t,label:`${VIEW_LABEL[t]} ${u[t]}`})),activeKey:d,onChange:t=>o(t),testIDPrefix:"bug-links-team-view"}),n.kind==="ready"&&r.length===0?(0,_jsxRuntime.jsx)(_reactNative.Text,{testID:"bug-links-team-empty",style:styles.meta,children:d==="mine"?"Nothing is yours right now.":"No bugs here."}):null,r.map(t=>(0,_jsxRuntime.jsx)(TeamRow,{bug:t,me:i,open:m===t.id,onPress:()=>c(m===t.id?null:t.id),run:e,busy:s,onLoaded:l},t.id)),(0,_teamList.canLoadTeamBugs)()?null:(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{children:"This is a release build, so Load is off here. Open the bug in your dev build to load it."})]})]})}function TeamRow({bug:e,me:s,open:l,onPress:n,run:d,busy:o,onLoaded:m}){const c=e.status==="fixed"||e.status==="wont_fix";const g=PRIORITY[e.priority];const h=[e.reporter,ago(e.createdAt),e.build,e.assigneeName?`${e.assigneeName==="You"?"You have":`${e.assigneeName} has`} it`:null,c?STATUS_LABEL[e.status]:null,e.repeatOf?"Repeat":null].filter(Boolean).join(" \xB7 ");const i=(a,r,u)=>void d(a,async()=>{await(0,_teamList.changeTeamBug)(e.id,r);return u});return(0,_jsxRuntime.jsxs)(_sharedUi.NightCard,{style:styles.card,children:[(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{testID:`bug-links-team-row-${e.id}`,accessibilityRole:"button",accessibilityLabel:e.title,onPress:n,style:styles.inset,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.top,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.title,numberOfLines:l?4:2,children:e.title}),e.priority!=="normal"?(0,_jsxRuntime.jsx)(_sharedUi.NightBadge,{label:g.label,tone:g.tone}):null]}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.meta,numberOfLines:2,children:h}),e.error&&e.error!==e.title?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.meta,styles.bad],numberOfLines:l?3:1,children:e.error}):null]}),l?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.actions,children:[(0,_teamList.canLoadTeamBugs)()?(0,_jsxRuntime.jsx)(_sharedUi.NightButton,{testID:`bug-links-team-load-${e.id}`,label:o===`load-${e.id}`?"Loading\u2026":"Load",variant:"primary",disabled:!!o||!e.canOpen,onPress:()=>void d(`load-${e.id}`,async()=>{const a=await(0,_teamList.loadTeamBug)(e.id);if(!a.loaded)throw new Error(a.steps.find(t=>!t.ok)?.detail??"The case didn't load.");const r=(0,_teamList.teamBugs)();const u=r.kind==="ready"&&r.bugs.find(t=>t.id===e.id)?.status==="new";if(u)void(0,_teamList.changeTeamBug)(e.id,{status:"seen"}).catch(()=>{});m();return"Loaded. The bar shows what came back."})}):null,e.canOpen?null:(0,_jsxRuntime.jsx)(_sharedUi.NightFootnote,{tone:"danger",children:"This phone can't open this bug, so it can't load here."}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.row,children:[e.assignee===s?(0,_jsxRuntime.jsx)(Small,{label:"Let go",disabled:!!o,testID:`bug-links-team-unassign-${e.id}`,onPress:()=>i("assign",{assignee:null},"Let go.")}):(0,_jsxRuntime.jsx)(Small,{label:e.assignee?"Take it from them":"Take it",disabled:!!o,testID:`bug-links-team-assign-${e.id}`,onPress:()=>i("assign",{assignee:s},"It's yours.")}),c?(0,_jsxRuntime.jsx)(Small,{label:"Still broken",disabled:!!o,testID:`bug-links-team-reopen-${e.id}`,onPress:()=>i("status",{status:"seen"},"Opened again.")}):(0,_jsxRuntime.jsx)(Small,{label:"Fixed",disabled:!!o,testID:`bug-links-team-fixed-${e.id}`,onPress:()=>i("status",{status:"fixed"},"Marked fixed.")})]})]}):null]})}function Small({label:e,onPress:s,testID:l,disabled:n}){return(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{testID:l,onPress:s,disabled:n,accessibilityRole:"button",accessibilityLabel:e,style:[styles.small,n&&styles.off],children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.smallText,children:e})})}const styles=_reactNative.StyleSheet.create({head:{flexDirection:"row",alignItems:"center",justifyContent:"space-between"},card:{marginBottom:8},top:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8},title:{color:_sharedUi.night.color.text,fontSize:15,fontWeight:"600",flexShrink:1},meta:{color:_sharedUi.night.color.textSecondary,fontSize:12,marginTop:2},bad:{color:_sharedUi.night.color.danger},inset:{paddingHorizontal:6,paddingVertical:2},actions:{marginTop:10,gap:8,paddingHorizontal:6},row:{flexDirection:"row",flexWrap:"wrap",gap:8},small:{paddingHorizontal:10,paddingVertical:6,borderRadius:_sharedUi.night.radius.chip,backgroundColor:_sharedUi.night.color.fill},off:{opacity:.5},smallText:{color:_sharedUi.night.color.accent,fontSize:13,fontWeight:"600"}});
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.askSignIn=askSignIn;exports.focusLink=focusLink;exports.focusedLink=focusedLink;exports.subscribeFocus=subscribeFocus;exports.wantsSignIn=wantsSignIn;let focused=null;let signIn=false;const listeners=new Set;function focusLink(n){focused=n;for(const e of listeners)e()}function focusedLink(){return focused}function wantsSignIn(){return signIn}function askSignIn(n){signIn=n;for(const e of listeners)e()}function subscribeFocus(n){listeners.add(n);return()=>listeners.delete(n)}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.BugLinksOverlay=BugLinksOverlay;exports.REPORT_TOAST_ID=void 0;exports.watchBugLinksOverlays=watchBugLinksOverlays;var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _loader=require("../engine/loader");var _reporter=require("../engine/reporter");var _focus=require("./focus");var _topBanners=require("./topBanners");var _jsxRuntime=require("react/jsx-runtime");const REPORT_TOAST_ID=exports.REPORT_TOAST_ID="bug-links-toast";const CLOCK_CHIP_ID="clock-chip";const HIDE_SENT_AFTER_MS=12e3;const STRIP_CHROME=64;const isOpen=t=>(0,_sharedUi.getFloatingTools)().some(o=>o.id===t);function Button({label:t,onPress:o,testID:e,tone:i,disabled:n}){return(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{testID:e,accessibilityRole:"button",accessibilityLabel:t,accessibilityState:{disabled:!!n},disabled:n,onPress:o,style:[styles.button,n&&styles.dim],hitSlop:{top:8,bottom:8,left:4,right:4},children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.buttonText,i==="accent"&&styles.accent,i==="danger"&&styles.danger],children:t})})}const LOGBOX_CLEARANCE=typeof __DEV__!=="undefined"&&__DEV__?116:64;function ReportToast({id:t,revealToken:o}){const e=(0,_react.useSyncExternalStore)(_reporter.subscribeReportState,_reporter.reportState,_reporter.reportState);const[i,n]=(0,_react.useState)(null);const{width:l}=(0,_reactNative.useWindowDimensions)();(0,_react.useEffect)(()=>{if(e?.phase!=="sent")return;const d=setTimeout(()=>{(0,_sharedUi.dismissFloatingTool)(REPORT_TOAST_ID);(0,_reporter.dismissReportState)()},HIDE_SENT_AFTER_MS);return()=>clearTimeout(d)},[e?.phase,e?.localId]);if(!e)return null;const r=e.phase==="capturing"||e.phase==="sending";const a=e.phase==="sent"?e.removed?`${e.removed} private ${e.removed===1?"value":"values"} removed`:"Locked on this phone":e.phase==="waiting"?"Saved on this phone":e.phase==="failed"?"Not sent":"Taking out private data";return(0,_jsxRuntime.jsx)(_sharedUi.FloatingTool,{id:t,revealToken:o,variant:"strip",dragFromSurface:true,defaultPlacement:"bottom",bottomClearance:LOGBOX_CLEARANCE,accessibilityLabel:"Move or hide the bug report",handleAdornment:(0,_jsxRuntime.jsx)(_sharedUi.Bug,{size:12,color:_sharedUi.night.color.textSecondary}),children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[styles.row,{maxWidth:l-STRIP_CHROME}],testID:"bug-links-toast",children:[r?(0,_jsxRuntime.jsx)(_reactNative.ActivityIndicator,{size:"small",color:_sharedUi.night.color.accent}):(0,_jsxRuntime.jsx)(_sharedUi.Bug,{size:18,color:e.phase==="failed"?_sharedUi.night.color.danger:_sharedUi.night.color.accent}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.text,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.title,numberOfLines:2,testID:"bug-links-toast-title",children:e.message}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.detail,numberOfLines:2,children:a})]}),e.phase==="sent"&&e.url&&e.localId?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-toast-share",label:"Share",tone:"accent",onPress:()=>void _reactNative.Share.share({message:e.url}).catch(()=>{})}),(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-toast-note",label:"Note",onPress:()=>{(0,_focus.focusLink)(e.localId);(0,_sharedUi.dismissFloatingTool)(REPORT_TOAST_ID);(0,_sharedUi.openBuoyTool)("bug-links")}}),i===e.localId?(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-toast-delete-confirm",label:"Delete for all?",tone:"danger",onPress:()=>void(0,_reporter.deleteLink)(e.localId).catch(()=>{})}):(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-toast-delete",label:"Delete",tone:"danger",onPress:()=>n(e.localId)})]}):null,e.phase==="waiting"&&e.localId&&e.code==="signed_out"?(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-toast-sign-in",label:"Sign in",tone:"accent",onPress:()=>{(0,_focus.askSignIn)(true);(0,_sharedUi.dismissFloatingTool)(REPORT_TOAST_ID);(0,_sharedUi.openBuoyTool)("bug-links")}}):e.phase==="waiting"&&e.localId&&e.code==="other_account"?(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-toast-send-as-me",label:"Send as me",tone:"accent",onPress:()=>void(0,_reporter.sendLink)(e.localId,{asMe:true})}):e.phase==="waiting"&&e.localId?(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-toast-retry",label:"Retry",tone:"accent",onPress:()=>void(0,_reporter.sendLink)(e.localId)}):null,e.phase==="sending"&&e.localId?(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-toast-cancel",label:"Cancel",onPress:()=>void(0,_reporter.deleteLink)(e.localId).catch(()=>{})}):null,!r?(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{testID:"bug-links-toast-close",accessibilityRole:"button",accessibilityLabel:"Close",onPress:()=>{(0,_sharedUi.dismissFloatingTool)(REPORT_TOAST_ID);(0,_reporter.dismissReportState)()},style:styles.icon,children:(0,_jsxRuntime.jsx)(_sharedUi.X,{size:16,color:_sharedUi.night.color.textSecondary})}):null]})})}function PillButton({testID:t,label:o,onPress:e,disabled:i,children:n,round:l}){return(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{testID:t,accessibilityRole:"button",accessibilityLabel:o,accessibilityState:{disabled:!!i},disabled:i,onPress:e,activeOpacity:.7,hitSlop:{top:8,bottom:8,left:4,right:4},style:[l?pill.close:pill.square,i&&styles.dim],children:n})}function BugLinksOverlay(){const t=(0,_react.useSyncExternalStore)(_loader.subscribeLoadedCase,_loader.loadedCase,_loader.loadedCase);const o=(0,_react.useSyncExternalStore)(_loader.subscribeLoadedCase,_loader.bugCaseBusy,_loader.bugCaseBusy);const e=(0,_react.useSyncExternalStore)(_topBanners.subscribeTopBanners,_topBanners.topBannersKey,_topBanners.topBannersKey);const i=(0,_sharedUi.useSafeAreaInsets)({minBottom:0});const{width:n}=(0,_reactNative.useWindowDimensions)();const[l,r]=(0,_react.useState)(false);const[a,d]=(0,_react.useState)(0);const u=!!t?.report.replay?.active;(0,_react.useEffect)(()=>{if(!u)return d(0);let s=true;const c=()=>void(0,_loader.blockedCalls)().then(I=>s&&d(I.length));c();const w=setInterval(c,3e3);return()=>{s=false;clearInterval(w)}},[u,t?.loadId]);(0,_react.useEffect)(()=>{if(!t)r(false)},[t]);(0,_react.useEffect)(()=>{if(t?.notice)r(true)},[t?.notice]);if(!t)return null;const g=t.report.steps.filter(s=>!s.ok).length;const h=a>0||g>0||!!t.notice;const p=t.notice?t.notice:[a?`${a} ${a===1?"request":"requests"} blocked`:null,g?`${g} ${g===1?"part":"parts"} didn't load`:null,u?null:"Live requests"].filter(Boolean).join(" \xB7 ");const f={"this phone":"This phone differs",clock:"No Clock tool"};const x=[...new Set(t.report.steps.filter(s=>s.warn).map(s=>f[s.part]??"See the details"))];const b=p||x.join(" \xB7 ");const y=t.title==="Bug case"?"Bug loaded":t.title;const m=(0,_topBanners.pillTop)(e,i.top);const k=t.undoGone?()=>void(0,_loader.endBugCase)():()=>{void(0,_loader.undoBugCase)().then(()=>{if((0,_loader.loadedCase)()?.undoBlocked)r(true)})};return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[pill.wrap,{top:m}],pointerEvents:"box-none",children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[pill.banner,h&&pill.bannerWarn],testID:"bug-links-loaded-bar",children:[(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{testID:"bug-links-loaded-open",style:pill.who,onPress:()=>r(s=>!s),activeOpacity:.7,accessibilityRole:"button",accessibilityLabel:l?"Hide what loaded":"Show what loaded",children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:pill.avatar,children:(0,_jsxRuntime.jsx)(_sharedUi.Bug,{size:13,color:"#fff"})}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:{maxWidth:Math.min(200,n-150)},children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:pill.name,numberOfLines:1,children:y}),b?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[pill.detail,h&&pill.warn],numberOfLines:1,testID:"bug-links-loaded-detail",children:b}):null]})]}),t.canLoadAgain?(0,_jsxRuntime.jsx)(PillButton,{testID:"bug-links-load-again",label:"Load again",disabled:o,onPress:()=>void(0,_loader.loadAgain)().catch(()=>{}),children:o?(0,_jsxRuntime.jsx)(_reactNative.ActivityIndicator,{size:"small",color:_sharedUi.night.color.accent}):(0,_jsxRuntime.jsx)(_reactNative.Text,{style:pill.again,children:"\u21BB"})}):null,(0,_jsxRuntime.jsx)(PillButton,{testID:"bug-links-undo",label:t.undoGone?"End the bug case":"Restore my app",disabled:o,onPress:k,round:true,children:(0,_jsxRuntime.jsx)(_sharedUi.X,{size:12,color:"rgba(255, 255, 255, 0.5)"})})]}),l?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[pill.card,{width:Math.min(340,n-24)}],testID:"bug-links-loaded-details",children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:pill.cardTitle,numberOfLines:2,children:t.title}),t.notice?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[pill.line,pill.warn],children:t.notice}):null,t.report.steps.map((s,c)=>(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[pill.line,(!s.ok||s.warn)&&pill.warn],testID:`bug-links-loaded-step-${c}`,children:`${s.ok&&!s.warn?"\u2713":"\u26A0"} ${s.part}: ${s.detail}`},c)),a?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[pill.line,pill.warn],children:`\u26A0 ${a} ${a===1?"request":"requests"} blocked`}):null,(0,_jsxRuntime.jsxs)(_reactNative.View,{style:pill.cardButtons,children:[u?(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-leave-sandbox",label:"Use live requests",disabled:o,onPress:()=>void(0,_loader.leaveSandbox)()}):null,(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-loaded-more",label:"Bug Links",onPress:()=>{r(false);(0,_sharedUi.openBuoyTool)("bug-links")}})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:pill.cardButtons,children:[t.canLoadAgain?(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-details-load-again",label:o?"Working\u2026":"\u21BB Load again",disabled:o,onPress:()=>void(0,_loader.loadAgain)().catch(()=>{})}):null,t.undoGone?null:(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-details-undo",label:"Restore my app",tone:"accent",disabled:o,onPress:()=>void(0,_loader.undoBugCase)()}),t.undoBlocked||t.undoGone?(0,_jsxRuntime.jsx)(Button,{testID:"bug-links-end",label:"End",tone:"danger",disabled:o,onPress:()=>void(0,_loader.endBugCase)()}):null]})]}):null]})}const toast={id:REPORT_TOAST_ID,render:t=>(0,_jsxRuntime.jsx)(ReportToast,{...t})};let watching=false;function watchBugLinksOverlays(){if(watching)return;watching=true;let t=null;(0,_reporter.subscribeReportState)(()=>{const n=(0,_reporter.reportState)();const l=n?`${n.localId}:${n.phase}`:null;if(n&&l!==t&&!isOpen(REPORT_TOAST_ID))(0,_sharedUi.openFloatingTool)(toast);if(!n&&isOpen(REPORT_TOAST_ID))(0,_sharedUi.dismissFloatingTool)(REPORT_TOAST_ID);t=l});let o=null;let e=false;const i=()=>{if(e)return;const n=(0,_loader.loadedCase)()?.loadId??null;if(!n||o===n||!isOpen(CLOCK_CHIP_ID))return;e=true;setTimeout(()=>{e=false;const l=(0,_loader.loadedCase)()?.loadId??null;if(l&&o!==l&&isOpen(CLOCK_CHIP_ID)){o=l;(0,_sharedUi.dismissFloatingTool)(CLOCK_CHIP_ID)}},0)};(0,_loader.subscribeLoadedCase)(i);(0,_sharedUi.subscribeFloatingTools)(i);i()}const styles=_reactNative.StyleSheet.create({row:{flexDirection:"row",alignItems:"center",gap:4,paddingLeft:8,paddingRight:2,paddingVertical:6,flexShrink:1},column:{flexShrink:1,paddingBottom:4},buttons:{flexDirection:"row",justifyContent:"flex-end",gap:4,paddingRight:2},text:{flexShrink:1,flexGrow:1,minWidth:0,marginLeft:4},title:{color:_sharedUi.night.color.text,fontSize:14,fontWeight:"600"},detail:{color:_sharedUi.night.color.textSecondary,fontSize:11},button:{paddingHorizontal:7,height:32,alignItems:"center",justifyContent:"center",borderRadius:16},buttonText:{color:_sharedUi.night.color.text,fontSize:13,fontWeight:"600"},accent:{color:_sharedUi.night.color.accent},dim:{opacity:.4},danger:{color:_sharedUi.night.color.danger},icon:{width:28,height:32,alignItems:"center",justifyContent:"center",borderRadius:16}});const pill=_reactNative.StyleSheet.create({wrap:{position:"absolute",left:0,right:0,alignItems:"center",zIndex:9999},banner:{flexDirection:"row",alignItems:"center",backgroundColor:"rgba(20, 20, 20, 0.94)",borderWidth:1,borderColor:"#34C75940",borderRadius:22,paddingVertical:7,paddingLeft:7,paddingRight:10,gap:10,marginTop:3,shadowColor:"#000",shadowOffset:{width:0,height:6},shadowOpacity:.3,shadowRadius:14,elevation:10},bannerWarn:{borderColor:_sharedUi.night.color.warning+"80"},who:{flexDirection:"row",alignItems:"center",gap:10,flexShrink:1},avatar:{width:24,height:24,borderRadius:12,backgroundColor:_sharedUi.night.color.warning,alignItems:"center",justifyContent:"center"},name:{fontSize:14,fontWeight:"600",color:"#fff"},detail:{fontSize:11,color:_sharedUi.night.color.textSecondary},warn:{color:_sharedUi.night.color.warning},again:{color:_sharedUi.night.color.accent,fontSize:16,fontWeight:"700",lineHeight:19},square:{width:28,height:28,borderRadius:8,borderWidth:1,borderColor:"#34C75940",backgroundColor:"rgba(52, 199, 89, 0.15)",alignItems:"center",justifyContent:"center"},close:{width:22,height:22,borderRadius:11,backgroundColor:"rgba(128, 128, 128, 0.16)",alignItems:"center",justifyContent:"center",marginLeft:-4},card:{marginTop:8,backgroundColor:"rgba(20, 20, 20, 0.97)",borderWidth:1,borderColor:_sharedUi.night.color.border,borderRadius:16,padding:12,gap:4,shadowColor:"#000",shadowOffset:{width:0,height:8},shadowOpacity:.35,shadowRadius:18,elevation:12},cardTitle:{color:"#fff",fontSize:14,fontWeight:"600",marginBottom:4},line:{color:_sharedUi.night.color.textSecondary,fontSize:12,lineHeight:17},cardButtons:{flexDirection:"row",flexWrap:"wrap",justifyContent:"flex-end",gap:4,marginTop:4}});
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.PILL_TOP=exports.PILL_STACK=void 0;exports.impersonateBannerUp=impersonateBannerUp;exports.pillTop=pillTop;exports.resetTopBannersForTests=resetTopBannersForTests;exports.scenarioBannerUp=scenarioBannerUp;exports.setImpersonateStoreForTests=setImpersonateStoreForTests;exports.setTopBannerStoresForTests=setTopBannerStoresForTests;exports.subscribeTopBanners=subscribeTopBanners;exports.topBannersKey=topBannersKey;var _sharedUi=require("@buoy-gg/shared-ui");let impersonate=null;let scenarios=null;let fixed=false;function impersonateStore(){if(impersonate||fixed)return impersonate??null;let e=(0,_sharedUi.getRegisteredToolModule)("@buoy-gg/impersonate")?.impersonateStore??null;if(!e){try{e=require("@buoy-gg/impersonate").impersonateStore??null}catch{e=null}}impersonate=e;return e}function scenariosStore(){if(scenarios||fixed)return scenarios??null;let e=(0,_sharedUi.getRegisteredToolModule)("@buoy-gg/scenarios")?.scenariosStore??null;if(!e){try{e=require("@buoy-gg/scenarios").scenariosStore??null}catch{e=null}}scenarios=e;return e}const read=e=>e?.getSnapshot?.()??e?.getState?.();function subscribeTopBanners(e){const n=[impersonateStore()?.subscribe(e),scenariosStore()?.subscribe(e)];return()=>n.forEach(r=>r?.())}function impersonateBannerUp(){const e=read(impersonateStore());return!!(e?.isActive&&e.currentUser&&e.showBanner)}function scenarioBannerUp(){return!!read(scenariosStore())?.active}function topBannersKey(){return(impersonateBannerUp()?1:0)|(scenarioBannerUp()?2:0)}const PILL_TOP=exports.PILL_TOP=50;const PILL_STACK=exports.PILL_STACK=46;const SCENARIO_GAP=52;const SCENARIO_STACK=50;function pillTop(e,n){let r=PILL_TOP;if(e&1)r=Math.max(r,PILL_TOP+PILL_STACK);if(e&2)r=Math.max(r,n+SCENARIO_GAP+SCENARIO_STACK);return r}function setTopBannerStoresForTests(e){fixed=true;impersonate=e.impersonate??null;scenarios=e.scenarios??null}function resetTopBannersForTests(){fixed=false;impersonate=null;scenarios=null}function setImpersonateStoreForTests(e){setTopBannerStoresForTests({impersonate:e,scenarios:null})}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.BugLinksHttpError=exports.BUG_PRIORITIES=void 0;exports.deleteRemote=deleteRemote;exports.deleteUpload=deleteUpload;exports.fetchBlob=fetchBlob;exports.fetchTeamKeys=fetchTeamKeys;exports.replaceBlob=replaceBlob;exports.sendPriority=sendPriority;exports.sendWrap=sendWrap;exports.teamJson=teamJson;exports.uploadBlob=uploadBlob;var _sharedUi=require("@buoy-gg/shared-ui");const BUG_PRIORITIES=exports.BUG_PRIORITIES=["urgent","high","normal","low"];function extraHeaders(e){const t={};if(e?.card)t["X-Buoy-Card"]=e.card;if(e?.wrap)t["X-Buoy-Wrap"]=(0,_sharedUi.base64UrlEncode)(new TextEncoder().encode(e.wrap));if(e?.cardRevision)t["X-Buoy-Card-Revision"]=String(e.cardRevision);if(e?.owner)t["X-Buoy-Owner"]=e.owner;if(e?.priority&&e.priority!=="normal")t["X-Buoy-Priority"]=e.priority;return t}class BugLinksHttpError extends Error{constructor(t,o,n,s=false,r={}){super(o);this.code=t;this.status=n;this.retry=s;this.detail=r;this.name="BugLinksHttpError"}}exports.BugLinksHttpError=BugLinksHttpError;const TIMEOUT_MS=3e4;const originOf=e=>/^[a-z][a-z0-9+.-]*:\/\/[^/?#]+/i.exec(e)?.[0].toLowerCase()??null;function movedTo(e,t){const o=e?originOf(e):null;return!!o&&o!==originOf(t)}async function call(e,t,o,n){const s=Date.now()+TIMEOUT_MS;const r=typeof AbortController!=="undefined"?new AbortController:null;const i=r?setTimeout(()=>r.abort(),TIMEOUT_MS):null;const d=()=>r?.abort();n?.signal?.addEventListener?.("abort",d);let u;let c;try{u=await beforeDeadline(fetch(`${e.replace(/\/+$/,"")}${t}`,{...o,redirect:"error",signal:r?.signal,__buoyInternal:true}),s);if(u.redirected||movedTo(u.url,e))throw new BugLinksHttpError("redirected","The bug links server sent this call somewhere else, so Buoy stopped.");const a=u.status;const l=a===204||a===304?"":await beforeDeadline(u.text(),s);c={status:a,ok:a>=200&&a<300,text:async()=>l,json:async()=>JSON.parse(l)}}catch(a){if(a instanceof BugLinksHttpError)throw a;if(n?.signal?.aborted)throw new BugLinksHttpError("cancelled","Cancelled.");throw new BugLinksHttpError("offline","Couldn't reach the bug links server. Buoy will try again.",void 0,true)}finally{if(i)clearTimeout(i);n?.signal?.removeEventListener?.("abort",d)}return c}function beforeDeadline(e,t){return new Promise((o,n)=>{const s=setTimeout(()=>n(new Error("timed out")),Math.max(0,t-Date.now()));e.then(r=>{clearTimeout(s);o(r)},r=>{clearTimeout(s);n(r)})})}async function fail(e){let t={};try{t=await e.json()}catch{}const o=typeof t.message==="string"?t.message:`The server said ${e.status}.`;if(e.status===401)throw new BugLinksHttpError("signed_out",o,401);if(e.status===402||t.error==="needs_pro")throw new BugLinksHttpError("needs_pro",o,e.status);if(e.status===404)throw new BugLinksHttpError("not_found",o,404,false,t);if(e.status===410)throw new BugLinksHttpError("expired",o,410);if(e.status===413)throw new BugLinksHttpError("too_big",o,413);if(e.status===429)throw new BugLinksHttpError("rate_limited",o,429,true);if(e.status===409)throw new BugLinksHttpError(t.error==="team_changed"||t.error==="priority_set"?t.error:"conflict",o,409,false,t);throw new BugLinksHttpError("server",o,e.status,e.status>=500)}const blobHeaders={"Content-Type":"text/plain","X-Buoy-Encoding":"base64url"};async function uploadBlob(e,t,o,n={}){const s=await call(e,"/api/bugs",{method:"POST",headers:{...blobHeaders,"X-Buoy-Auth":t,...n.uploadId?{"X-Buoy-Upload-Id":n.uploadId}:{},...n.editToken?{"X-Buoy-Edit-Token":n.editToken}:{},...extraHeaders(n.extras)},body:(0,_sharedUi.base64UrlEncode)(o)},{signal:n.signal});if(s.status===404)throw new BugLinksHttpError("not_found",`${e} doesn't take bug links. Check the server in the Bug links screen.`,404);if(s.status!==201&&s.status!==200)return fail(s);const r=await s.json();if(typeof r.id!=="string"||typeof r.editToken!=="string"){throw new BugLinksHttpError("server","The server's answer had no link id.",s.status,true)}return{id:r.id,editToken:r.editToken,expiresAt:typeof r.expiresAt==="string"?r.expiresAt:null,cardRevision:typeof r.cardRevision==="number"?r.cardRevision:void 0,wrapStatus:typeof r.wrapStatus==="string"?r.wrapStatus:void 0,teamKeys:!!r.currentKey||r.teamKeys===true,owner:typeof r.owner==="string"&&r.owner?r.owner:void 0}}async function replaceBlob(e,t,o,n,s){const r=await call(e,`/api/bugs/${encodeURIComponent(t)}`,{method:"PUT",headers:{...blobHeaders,"X-Buoy-Edit-Token":o,...extraHeaders(s)},body:(0,_sharedUi.base64UrlEncode)(n)});if(!r.ok)return fail(r);const i=await r.json().catch(()=>({}));return{expiresAt:typeof i.expiresAt==="string"?i.expiresAt:i.expiresAt===null?null:void 0,cardRevision:typeof i.cardRevision==="number"?i.cardRevision:void 0}}async function sendWrap(e,t,o,n,s){const r=await call(e,`/api/bugs/${encodeURIComponent(t)}`,{method:"PATCH",headers:{"Content-Type":"application/json","X-Buoy-Edit-Token":o},body:`{"wrap":${n},"caseTag":${JSON.stringify(s)}}`});if(!r.ok)return fail(r)}async function sendPriority(e,t,o,n){const s=await call(e,`/api/bugs/${encodeURIComponent(t)}`,{method:"PATCH",headers:{"Content-Type":"application/json","X-Buoy-Edit-Token":o},body:JSON.stringify({priority:n})});if(!s.ok)return fail(s)}async function teamJson(e,t,o,n={}){const s={"X-Buoy-Auth":o};if(n.body!==void 0)s["Content-Type"]="application/json";const r=await call(e,t,{method:n.method??"GET",headers:s,...n.body!==void 0?{body:JSON.stringify(n.body)}:{}});if(!r.ok)return fail(r);if(r.status===204)return{};const i=await r.json();return i}async function fetchTeamKeys(e,t){const o=await call(e,"/api/bugs/team-key",{method:"GET",headers:{"X-Buoy-Auth":t}});if(o.status===404)return null;if(!o.ok)return fail(o);return o.json()}async function deleteRemote(e,t,o){const n={};if(o.editToken)n["X-Buoy-Edit-Token"]=o.editToken;if(o.credential)n["X-Buoy-Auth"]=o.credential;const s=await call(e,`/api/bugs/${encodeURIComponent(t)}`,{method:"DELETE",headers:n});if(!s.ok&&s.status!==404&&s.status!==410)return fail(s)}async function deleteUpload(e,t,o){const n=await call(e,`/api/bugs?upload=${encodeURIComponent(t)}`,{method:"DELETE",headers:{"X-Buoy-Auth":o}});if(!n.ok&&n.status!==404)return fail(n)}async function fetchBlob(e,t,o){const n=await call(e,`/api/bugs/${encodeURIComponent(t)}`,{method:"GET",headers:{"X-Buoy-Auth":o,Accept:"text/plain","X-Buoy-Encoding":"base64url"}});if(!n.ok)return fail(n);const s=(0,_sharedUi.base64UrlDecode)((await n.text()).trim());if(!s)throw new BugLinksHttpError("server","The server sent a case that isn't base64url.",n.status);return s}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.credentialOwner=credentialOwner;exports.currentCredential=currentCredential;exports.setBugLinksCredentialProvider=setBugLinksCredentialProvider;var license=_interopRequireWildcard(require("@buoy-gg/license"));var _sharedUi=require("@buoy-gg/shared-ui");function _interopRequireWildcard(r,t){if("function"==typeof WeakMap)var n=new WeakMap,a=new WeakMap;return(_interopRequireWildcard=function(e,i){if(!i&&e&&e.__esModule)return e;var o,c,u={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return u;if(o=i?a:n){if(o.has(e))return o.get(e);o.set(e,u)}for(const s in e)"default"!==s&&{}.hasOwnProperty.call(e,s)&&((c=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,s))&&(c.get||c.set)?o(u,s,c):u[s]=e[s]);return u})(r,t)}let provider=null;function setBugLinksCredentialProvider(r){provider=r}function currentCredential(){if(provider)return provider();try{const r=license.SessionManager;const t=r?.getAccessToken();if(t)return t}catch{}try{return license.LicenseManager.getState().licenseKey??null}catch{return null}}function credentialOwner(r){if(!r)return null;const t=r.split(".");if(t.length===3){try{const n=(0,_sharedUi.base64UrlDecode)(t[1]);let a="";if(n)for(let i=0;i<n.length;i++)a+=String.fromCharCode(n[i]);const e=JSON.parse(a).sub;if(typeof e==="string"&&e)return`sub:${fnv1a(e)}`}catch{}}return`key:${fnv1a(r)}`}function fnv1a(r){let t=2166136261;for(let n=0;n<r.length;n++){t^=r.charCodeAt(n);t=Math.imul(t,16777619)>>>0}return t.toString(16).padStart(8,"0")}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.addMyLink=addMyLink;exports.canAddNote=canAddNote;exports.findMyLink=findMyLink;exports.hasHeldUploadResult=hasHeldUploadResult;exports.holdUploadResult=holdUploadResult;exports.isUnsavedNew=isUnsavedNew;exports.loadMyLinks=loadMyLinks;exports.myLinks=myLinks;exports.readBlob=readBlob;exports.readBlobChecked=readBlobChecked;exports.reloadMyLinks=reloadMyLinks;exports.removeMyLink=removeMyLink;exports.resetMyLinksForTests=resetMyLinksForTests;exports.saveHeldUploadResults=saveHeldUploadResults;exports.saveRowNow=saveRowNow;exports.subscribeMyLinks=subscribeMyLinks;exports.updateMyLink=updateMyLink;exports.writeBlob=writeBlob;var _sharedUi=require("@buoy-gg/shared-ui");var _otherTabs=require("./otherTabs");var _randomBytes=require("./randomBytes");const STRICT={strict:true};const MAX_LINKS=50;const KEEP_SENT_BLOBS=10;const CHUNK_CHARS=1e6;const MAX_CHUNKS=12;let links=[];let loaded=null;let listRead=false;const memoryBlobs=new Map;const droppedBlobs=new Set;const listeners=new Set;const linksKey=()=>_sharedUi.devToolsStorageKeys.bugLinks.links();const LINKS_LOCK="buoy-bug-links-links";const blobKey=t=>`${_sharedUi.devToolsStorageKeys.bugLinks.links()}_blob_${t}`;function parseLinks(t){const e=t?JSON.parse(t):[];return Array.isArray(e)?e.filter(n=>!!n&&typeof n==="object"&&typeof n.localId==="string"&&typeof n.key==="string"):[]}function parseSaved(t){try{return parseLinks(t)}catch{if(t&&!listBroken){listBroken=true;void _sharedUi.persistentStorage.setItem(`${linksKey()}_broken`,t).catch(()=>{})}return[]}}let listBroken=false;function withSavedRows(t){if(listRead)return;const e=t.map(n=>n.status==="sending"?{...n,status:"waiting"}:n);links=[...links,...e.filter(n=>!links.some(s=>s.localId===n.localId))];listRead=true}const BLOB_OF=/^(bl_[a-z0-9]+_[a-z0-9]+)(?:_[a-z0-9]+_\d{1,2})?$/;async function sweepBlobs(){if((0,_otherTabs.tabsMatter)()||!listRead||listBroken)return;const t=`${blobKey("")}`;let e;try{e=await _sharedUi.persistentStorage.getAllKeys()}catch{return}for(const n of e){if(!listRead)return;if(!n.startsWith(t))continue;const s=BLOB_OF.exec(n.slice(t.length))?.[1];if(!s||links.some(r=>r.localId===s))continue;await _sharedUi.persistentStorage.removeItem(n).catch(()=>{})}}function loadMyLinks(){if(!loaded){loaded=(async()=>{try{withSavedRows(parseSaved(await(0,_sharedUi.getItemChecked)(linksKey())));void change(sweepBlobs)}catch{loaded=null}emit()})()}return loaded}const unsavedNew=new Set;const unsavedRows=new Set;function saveRowNow(t){if((0,_otherTabs.tabsMatter)()||!unsavedRows.has(t)&&!unsavedNew.has(t))return Promise.resolve(true);return change(async()=>{if(await(0,_sharedUi.storageIsMemoryOnly)())return true;if(unsavedNew.has(t)){let e;try{e=parseSaved(await(0,_sharedUi.getItemChecked)(linksKey())).some(n=>n.localId===t)}catch{return false}if(!e)return true}return trimAndSave()})}const heldFacts=new Map;function withFacts(t){const e=heldFacts.get(t.localId);if(!e)return t;return{...t,...e,status:t.status==="deleting"?"deleting":e.status??t.status}}function holdUploadResult(t,e){heldFacts.set(t,{...heldFacts.get(t),...e});links=links.map(withFacts);emit()}function hasHeldUploadResult(t){return heldFacts.has(t)}function saveHeldUploadResults(){return change(()=>trimAndSave())}async function readListStrict(){return parseLinks(await readKey(linksKey()))}async function fromDisk(){const t=await readListStrict();const e=new Set(t.map(s=>s.localId));const n=links.filter(s=>unsavedNew.has(s.localId)&&!e.has(s.localId));for(const s of e)unsavedNew.delete(s);return[...n,...t]}let changes=Promise.resolve();function change(t){const e=changes.catch(()=>{}).then(()=>changeNow(t));changes=e.catch(()=>{});return e}function changeNow(t){return(0,_otherTabs.withTabLock)(LINKS_LOCK,async()=>{await loadMyLinks();if((0,_otherTabs.tabsMatter)()){try{links=(await fromDisk()).map(withFacts)}catch(e){if(globalThis.localStorage)throw e}}return t()})}function isUnsavedNew(t){return unsavedNew.has(t)}async function reloadMyLinks(){await change(async()=>emit())}function myLinks(){return links}function canAddNote(t){if(t.status!=="sent"||t.needsPut)return true;if(droppedBlobs.has(t.localId))return false;const e=links.filter(n=>n.status==="sent"&&!n.needsPut);return e.findIndex(n=>n.localId===t.localId)<KEEP_SENT_BLOBS}function findMyLink(t){return links.find(e=>e.localId===t)}function subscribeMyLinks(t){listeners.add(t);return()=>listeners.delete(t)}function addMyLink(t,e){return change(async()=>{links=[t,...links.filter(r=>r.localId!==t.localId)];unsavedNew.add(t.localId);const n=await writeBlob(t.localId,e,t.blobVersion??0);const s=await trimAndSave();return n&&s})}function updateMyLink(t,e){return change(async()=>{if(typeof e==="function"){const a=links.find(i=>i.localId===t);const o=a?e(a):null;if(!o)return a;e=o}const n=e;let s;links=links.map(a=>a.localId===t?s={...a,...n,...a.status==="deleting"?{status:"deleting"}:{}}:a);const r=await trimAndSave();if(!r&&!(0,_otherTabs.tabsMatter)())unsavedRows.add(t);return r||!(0,_otherTabs.tabsMatter)()?s:findMyLink(t)})}function removeMyLink(t){return change(async()=>{const e=links.findIndex(s=>s.localId===t);const n=links[e];links=links.filter(s=>s.localId!==t);if(await trimAndSave()){unsavedNew.delete(t);heldFacts.delete(t);await dropBlob(t);return true}if(n&&!(0,_otherTabs.tabsMatter)()&&!findMyLink(t)){links=[...links.slice(0,e),n,...links.slice(e)];unsavedRows.add(t);emit()}return false})}async function readKey(t){const e=(0,_otherTabs.tabsMatter)()?globalThis.localStorage:void 0;return e?e.getItem(t):_sharedUi.persistentStorage.getItem(t)}async function readBlob(t){return readBlobChecked(t).catch(()=>null)}async function readBlobChecked(t){const e=memoryBlobs.get(t);const n=links.find(i=>i.localId===t);if(e&&(!n||e.version===(n.blobVersion??0)))return e.blob;const s=i=>(0,_otherTabs.tabsMatter)()?readKey(i):(0,_sharedUi.getItemChecked)(i);const r=await s(blobKey(t));const a=r?/^chunks:(\d{1,2}):([a-z0-9]+)$/.exec(r):null;if(!a)return r;const o=[];for(let i=0;i<Number(a[1]);i++){const c=await s(`${blobKey(t)}_${a[2]}_${i}`);if(c===null)return null;o.push(c)}return o.join("")}let writeSeq=0;function chunkVersion(){try{return Array.from((0,_randomBytes.randomBytes)(6),t=>t.toString(36).padStart(2,"0")).join("")}catch{return`${Date.now().toString(36)}${(++writeSeq).toString(36)}`}}async function writeBlob(t,e,n=0){const s=memoryBlobs.get(t);const r=await writeBlobToDisk(t,e,n);if(!r&&(0,_otherTabs.tabsMatter)()&&!unsavedNew.has(t)){if(s)memoryBlobs.set(t,s);else memoryBlobs.delete(t)}return r}async function writeBlobToDisk(t,e,n){memoryBlobs.set(t,{version:n,blob:e});droppedBlobs.delete(t);try{const s=await readKey(blobKey(t)).catch(()=>null);if(e.length<=CHUNK_CHARS){if(!await(0,_sharedUi.setItemChecked)(blobKey(t),e,STRICT))return false;memoryBlobs.delete(t);await dropChunks(t,s);return true}const r=Math.ceil(e.length/CHUNK_CHARS);if(r>MAX_CHUNKS)return false;const a=chunkVersion();try{for(let o=0;o<r;o++){if(!await(0,_sharedUi.setItemChecked)(`${blobKey(t)}_${a}_${o}`,e.slice(o*CHUNK_CHARS,(o+1)*CHUNK_CHARS),STRICT))throw new Error("not saved")}if(!await(0,_sharedUi.setItemChecked)(blobKey(t),`chunks:${r}:${a}`,STRICT))throw new Error("not saved")}catch{await dropChunks(t,`chunks:${r}:${a}`);return false}memoryBlobs.delete(t);await dropChunks(t,s);return true}catch{return false}}async function dropChunks(t,e){const n=e?/^chunks:(\d{1,2}):([a-z0-9]+)$/.exec(e):null;if(!n)return;for(let s=0;s<Number(n[1]);s++){await _sharedUi.persistentStorage.removeItem(`${blobKey(t)}_${n[2]}_${s}`).catch(()=>{})}}async function dropBlob(t){memoryBlobs.delete(t);const e=await readKey(blobKey(t)).catch(()=>null);await _sharedUi.persistentStorage.removeItem(blobKey(t)).catch(()=>{});await dropChunks(t,e)}async function trimAndSave(){let t=links;const e=[];while(t.length>MAX_LINKS){const s=t.map(a=>a.status==="sent"&&!a.needsPut&&!a.wrapPending&&!a.priorityPending).lastIndexOf(true);if(s<0)break;const r=t[s].localId;e.push(r);t=t.filter(a=>a.localId!==r)}let n=true;try{if(!listRead&&!(0,_otherTabs.tabsMatter)()){const s=new Set(links.map(r=>r.localId));withSavedRows(parseSaved(await(0,_sharedUi.getItemChecked)(linksKey())));t=[...t,...links.filter(r=>!s.has(r.localId))]}n=await(0,_sharedUi.setItemChecked)(linksKey(),JSON.stringify(t),STRICT)}catch{n=false}if(n){unsavedNew.clear();unsavedRows.clear();heldFacts.clear();links=links.filter(r=>!e.includes(r.localId));for(const r of e)await dropBlob(r);const s=t.filter(r=>r.status==="sent"&&!r.needsPut);for(const r of s.slice(KEEP_SENT_BLOBS)){if(droppedBlobs.has(r.localId))continue;droppedBlobs.add(r.localId);await dropBlob(r.localId)}}else if((0,_otherTabs.tabsMatter)()){try{links=(await fromDisk()).map(withFacts)}catch{}}emit();return n}function emit(){for(const t of listeners)t()}function resetMyLinksForTests(){unsavedNew.clear();unsavedRows.clear();heldFacts.clear();listBroken=false;links=[];loaded=null;listRead=false;memoryBlobs.clear();droppedBlobs.clear()}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"BLOCK_RULE_NAME",{enumerable:true,get:function(){return _sandbox.BLOCK_RULE_NAME}});exports.blockedCalls=blockedCalls;exports.bugCaseBusy=bugCaseBusy;exports.checkCase=checkCase;exports.endBugCase=endBugCase;exports.forgetLoadedForTests=forgetLoadedForTests;exports.leaveSandbox=leaveSandbox;exports.loadAgain=loadAgain;exports.loadBugCase=loadBugCase;exports.loadedCase=loadedCase;exports.readableTime=readableTime;exports.restoreLoadedAfterReload=restoreLoadedAfterReload;exports.safeRoute=safeRoute;exports.skippedText=skippedText;exports.subscribeLoadedCase=subscribeLoadedCase;exports.undoBugCase=undoBugCase;var _sharedUi=require("@buoy-gg/shared-ui");var _phoneCheck=require("./phoneCheck");var _tabStorage=require("./tabStorage");var _otherTabs=require("./otherTabs");var _sandbox=require("./sandbox");const MAX_SAVED_CHARS=15e5;const caseKey=()=>`${_sharedUi.devToolsStorageKeys.bugLinks.loaded()}_case`;const stateKey=()=>`${_sharedUi.devToolsStorageKeys.bugLinks.loaded()}_undo_state`;const restartKey=()=>`${_sharedUi.devToolsStorageKeys.bugLinks.loaded()}_restart`;const LATE_WAITS_MS=[300,400,800];let loaded=null;let queue=Promise.resolve();let busy=0;function serial(t,e={}){if(!e.quiet){busy++;emit()}const a=()=>(0,_otherTabs.withStepLock)(t);const r=queue.then(a,a);queue=r.catch(()=>{});return r.finally(()=>{if(e.quiet)return;busy--;emit()})}function bugCaseBusy(){return busy>0}let stateUndo;let stateUndoLate=[];let stateUndoKeys={};const MAX_LATE_COPIES=24;const listeners=new Set;function loadedCase(){return loaded}function subscribeLoadedCase(t){listeners.add(t);return()=>listeners.delete(t)}function emit(){for(const t of listeners)t()}const hasTool=t=>!!(0,_sharedUi.getToolAdapters)()[t];async function tool(t,e,a){return await(0,_sharedUi.dispatchToolAction)(t,e,a)}const sleep=t=>new Promise(e=>setTimeout(e,t));function safeRoute(t){if(typeof t!=="string")return null;const e=t.trim();if(!e.startsWith("/")||e.startsWith("//")||e.length>2e3)return null;if(/[\u0000-\u001f\\]/.test(e)||/^\/[a-z][a-z0-9+.-]*:/i.test(e))return null;return e}function checkCase(t){try{return(0,_sharedUi.validateBugCase)(t)}catch(e){throw new Error(e instanceof Error?e.message:"This isn't a bug case Buoy can load.")}}function newLoadId(){return`ld_${Date.now().toString(36)}_${Math.floor(Math.random()*1e9).toString(36)}`}async function loadBugCase(t,e={}){const a=checkCase(t);return serial(()=>doLoad(a,e))}function readableTime(t){try{const e=new Date(t).toLocaleString(void 0,{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",second:"2-digit"});return e&&e!=="Invalid Date"?e:t}catch{return t}}function skippedText(t){if(!t.length)return"";const e=s=>s.replace(/^[a-z]+:\/\/[^/]+/i,"").split("?")[0]||"/";const a=t.slice(0,3).map(s=>`${s.method.toUpperCase()} ${e(s.url)} (${s.reason})`);const r=t.length>3?`, and ${t.length-3} more`:"";return` ${t.length===1?"1 recorded call has":`${t.length} recorded calls have`} no answer, so ${t.length===1?"it's":"they're"} blocked: ${a.join("; ")}${r}.`}function stepList(){const t=[];const e=(a,r,s)=>{const o=t.findIndex(l=>l.part===a);if(o>=0)t[o]={part:a,ok:r,detail:s};else t.push({part:a,ok:r,detail:s})};return{steps:t,step:e}}function statePart(t){return`state: ${(0,_sharedUi.getStateSnapshotProviders)().find(e=>e.id===t)?.label??t}`}async function doLoad(t,e){const a=new Date().toISOString();const{steps:r,step:s}=stepList();const o=e.replay!==false;if(e.stillWanted&&!e.stillWanted()){s("app",false,"The Bug Links screen closed, so Buoy stopped loading this bug.");return{loadedAt:a,steps:r,replay:null,loaded:false}}const l=e.otherApp?null:(0,_phoneCheck.otherAppReason)(t.app,(0,_phoneCheck.thisPhone)());if(l){s("app",false,`${l} Its state could break this app, so nothing was loaded. Load it anyway only if this app shares its data.`);return{loadedAt:a,steps:r,replay:null,loaded:false}}const n=await(0,_otherTabs.tabsProblem)();if(n){s("app",false,n);return{loadedAt:a,steps:r,replay:null,loaded:false}}await stillCurrent();const c=!!loaded?.undo;const d=loaded?.undo??await saveUndoPoint();if(c&&!stateUndo){const h=await readUndoState(d);if(h){stateUndo=h.state;stateUndoKeys=h.keys;stateUndoLate=h.late}}const w=(h,y,b)=>copyBeforeWrite(d,h,y,b);const p=newLoadId();const i=(t.note??"").split("\n")[0].slice(0,80)||(t.route?.pathname?`Bug on ${t.route.pathname.slice(0,70)}`:"Bug case");const f={replay:e.replay,clock:e.clock,sequence:e.sequence,otherApp:e.otherApp};if(!c){const h={source:e.source?.id?{id:e.source.id}:null,loadId:p,canLoadAgain:false,title:i,createdAt:t.createdAt,report:{loadedAt:a,steps:[],replay:null,loaded:false},undo:d,options:f,phase:"loading",block:o};if(!await persistRecord(h)&&inBrowser()){s("saved",false,"The phone couldn't save that a case is loading (its storage may be full), so nothing was loaded.");return{loadedAt:a,steps:r,replay:null,loaded:false}}}if(c&&loaded&&!await persistRecord({...loaded,phase:"loading"},o||blocksCalls(loaded))&&inBrowser()){await persistLoaded();s("saved",false,"The phone couldn't save that a case is loading (its storage may be full), so nothing new was loaded.");return{loadedAt:a,steps:r,replay:null,loaded:false}}const u=async()=>{if(!c)await _sharedUi.persistentStorage.removeItem(_sharedUi.devToolsStorageKeys.bugLinks.loaded()).catch(()=>{});else await persistLoaded()};const g=await(0,_otherTabs.tabsProblem)();if(g){await u();s("app",false,g);return{loadedAt:a,steps:r,replay:null,loaded:false}}if(e.stillWanted&&!e.stillWanted()){await u();s("app",false,"The Bug Links screen closed, so Buoy stopped loading this bug.");return{loadedAt:a,steps:r,replay:null,loaded:false}}if(!o&&loaded&&blocksCalls(loaded)){if(!await restoreNetwork(d)){await u();s("web calls",false,"Couldn't put your Network rules back, so the last case's answers stay on. Nothing new was loaded.");return{loadedAt:a,steps:r,replay:null,loaded:false}}await persistRecord({...loaded,phase:"loading"},false)}let m=null;if(o){const h=async I=>{await u();s("web calls",false,`${I} Nothing was loaded, so no real server got this case's data. Load with replay off to use real web calls.`);return{loadedAt:a,steps:r,replay:null,loaded:false}};if(!hasTool("network"))return h("The Network tool isn't installed, so Buoy can't block real web calls.");const y=(0,_sandbox.buildSandbox)(t,{sequence:e.sequence});let b=null;if(c)b=await tool("network","listOverrideRules").catch(()=>null);if(e.stillWanted&&!e.stillWanted()){await u();s("app",false,"The Bug Links screen closed, so Buoy stopped loading this bug.");return{loadedAt:a,steps:r,replay:null,loaded:false}}const T=await tool("network","replaceOverrideRules",{rules:y.rules,enabled:true}).catch(I=>({ok:false,error:I instanceof Error?I.message:String(I),active:false}));if(!T||T.ok===false)return h(`Couldn't set up replay: ${T?.error??"unknown"}.`);if(T.active===false){if(!c)await restoreNetwork(d);return h("This build can't fake web calls (release builds never do).")}if(T.saved===false){if(!c)await restoreNetwork(d);else if(b?.rules){await tool("network","replaceOverrideRules",{rules:b.rules,enabled:b.enabled!==false}).catch(()=>{})}return h("The phone couldn't save the block on real web calls (its storage may be full).")}m={answers:y.answers,skipped:y.skipped.length,active:true};s("web calls",true,`${y.answers} recorded answers. Every other call is blocked.${skippedText(y.skipped)}`)}if(e.clock!==false){const h=t.clock?.active?t.clock.virtualIso:t.createdAt;if(!hasTool("clock")){if(t.clock?.active)s("clock",false,"The reporter's app had its clock set by Buoy, but the Clock tool isn't installed here, so the app keeps today's time.");else r.push({part:"clock",ok:true,warn:true,detail:"The Clock tool isn't installed, so the app keeps today's time."})}else if(typeof h==="string"&&!Number.isNaN(Date.parse(h))){await tool("clock","setTime",{time:h,freeze:t.clock?.rate===0}).then(()=>s("clock",true,`App time set to ${readableTime(h)} (the reporter's time).`)).catch(y=>s("clock",false,`Couldn't set the clock: ${y instanceof Error?y.message:String(y)}`))}}const v=readState(t,s);const $=await restoreAll(v,s,w);const k=safeRoute(t.route?.href)??safeRoute(t.route?.pathname);if(k){if(!hasTool("route-events")){s("screen",false,`The Routes tool isn't installed. Go to ${k} by hand.`)}else{await tool("route-events","navigate",{path:k,reset:true}).catch(()=>tool("route-events","navigate",{path:k,replace:true})).then(()=>s("screen",true,`Went to ${k}.`)).catch(h=>s("screen",false,`Couldn't go to ${k}: ${h instanceof Error?h.message:String(h)}`))}}else if(t.route){s("screen",false,"The case's screen isn't an app path, so Buoy didn't go there.")}await restoreLate(v,$,s,w);const C=(0,_phoneCheck.phoneDifferences)(t.app,(0,_phoneCheck.thisPhone)());if(C.length){r.push({part:"this phone",ok:true,warn:true,detail:`Not like the reporter's, so a bug that needs these may not show. ${C.join(" ")}`})}const S={loadedAt:a,steps:r,replay:m,loaded:true};loaded={source:e.source?.id?{id:e.source.id}:null,loadId:p,canLoadAgain:false,title:i,createdAt:t.createdAt,report:S,undo:d,options:f};let A=await persistLoaded();const{canLoadAgain:L,onDisk:O}=await saveCase(p,t);if(L){loaded={...loaded,canLoadAgain:L};A=await persistLoaded()&&A}if(!A&&m?.active){S.steps.push({part:"saved",ok:false,detail:"The phone couldn't save that a case is loaded. Restore your app before you restart it, or remove the bug case rules in the Network tool after."})}emit();if($.size)void watchLate(v,$,p,w);const _=L&&O&&A&&d.stateSaved;if(e.restart&&_){await _tabStorage.tabStorage.setItem(restartKey(),p).catch(()=>{});S.steps.push({part:"restart",ok:true,detail:"Restarting the app, then loading the case again."});void(0,_sharedUi.reloadApp)("auto","bug links: load and restart").catch(()=>{void _tabStorage.tabStorage.removeItem(restartKey()).catch(()=>{})})}else if(e.restart){S.steps.push({part:"restart",ok:false,detail:L?"The phone couldn't save everything a restart needs, so Buoy didn't restart. Restore your app before you restart it by hand.":"This case is too big to keep on the phone, so Buoy can't load it again after a restart."})}return S}function readState(t,e){try{const a=t.state?(0,_sharedUi.superJsonDeserialize)(JSON.parse(JSON.stringify(t.state)),{skipBadAnnotations:true}):{};if(a&&typeof a==="object"&&!Array.isArray(a)){const r=a;const s=r.storage;if(s&&typeof s==="object"&&!Array.isArray(s)&&"secure"in s){const{secure:o,...l}=s;r.storage=l}return r}e("state",false,"The case's state isn't an object, so none of it was loaded.")}catch{e("state",false,"The case's state couldn't be read.")}return{}}async function restoreAll(t,e,a){const r=new Map(Object.keys(t).map(s=>[s,{skipped:Infinity,ids:null}]));await restorePass(t,r,e,a);return r}async function restorePass(t,e,a,r){for(const s of(0,_sharedUi.getStateSnapshotProviders)()){const o=e.get(s.id);if(!o||!s.status().canRestore)continue;try{const l=r?await r(s,t[s.id],o.ids):{ok:true,scope:o.ids??void 0,later:[]};if(!l.ok){e.delete(s.id);a?.(statePart(s.id),false,"Buoy couldn't save a copy of it first, so it wasn't loaded. (Restore couldn't have put it back.)");continue}const n=l.scope&&!l.scope.length?{ok:true,applied:0,skipped:[],warnings:[]}:await s.restore(t[s.id],l.scope?{includeKeys:l.scope}:void 0);if(l.unclaim)await l.unclaim(n.skipped.map(w=>w.id));const c=[...new Set([...n.skipped.map(w=>w.id),...l.later])];const d=c.length;if(d===0)e.delete(s.id);else e.set(s.id,{skipped:d,ids:c});if(a&&(o.skipped===Infinity||d===0)){a(statePart(s.id),n.ok,`${n.applied} loaded${d?`, ${d} not there yet`:""}.`)}}catch(l){e.delete(s.id);a?.(statePart(s.id),false,l instanceof Error?l.message:String(l))}}}async function restoreLate(t,e,a,r){for(const o of LATE_WAITS_MS){if(!e.size)break;await sleep(o);await restorePass(t,e,a,r)}const s=new Map((0,_sharedUi.getStateSnapshotProviders)().map(o=>[o.id,o]));for(const[o,l]of e){const n=s.get(o);a(statePart(o),false,!n?"This app has no such source to load it into.":l.skipped!==Infinity?`${l.skipped} stores never mounted, so they weren't loaded.`:`It wasn't ready${n.status().detail?` (${n.status().detail})`:""}, so it wasn't loaded.`)}}const WATCH_LATE_MS=2e4;async function watchLate(t,e,a,r){const s=Date.now()+WATCH_LATE_MS;while(e.size&&Date.now()<s){await sleep(1e3);const o=await serial(async()=>{if(loaded?.loadId!==a||!await stillCurrent())return true;const l=new Map(e);const n=[];await restorePass(t,e,(p,i,f)=>n.push({part:p,ok:i,detail:f}),r);const c=[...l.keys()].some(p=>!e.has(p)||e.get(p).skipped<l.get(p).skipped);if(!c||!loaded||loaded.loadId!==a)return false;const d=new Set([...e.keys()].map(statePart));const w=loaded.report.steps.filter(p=>!n.some(i=>i.part===p.part)&&!(p.part.startsWith("state:")&&!p.ok&&!d.has(p.part)));loaded={...loaded,report:{...loaded.report,steps:[...w,...n]}};await persistLoaded();emit();return false},{quiet:true});if(o)return}}async function saveUndoPoint(){const t={};const e={};for(const n of(0,_sharedUi.getStateSnapshotProviders)()){if(!n.status().canCapture)continue;try{t[n.id]=(await n.capture()).data;e[n.id]=await itemsHeld(n,t[n.id])}catch{}}stateUndo=t;stateUndoKeys=e;stateUndoLate=[];const a=newLoadId();const r=await writeUndoCopy(a);const s=hasTool("network")?await tool("network","listOverrideRules").then(n=>({enabled:!!n?.enabled,rules:(n?.rules??[]).filter(c=>!(0,_sandbox.isSandboxRule)(c))})).catch(()=>null):null;const o=hasTool("clock")?await tool("clock","getState").then(n=>({active:!!n?.active,virtualIso:n?.virtualIso,rate:n?.rate})).catch(()=>null):null;const l=(0,_sharedUi.getSafeCurrentRoute)();return{undoId:a,route:l?.href??l?.pathname??null,network:s,clock:o,stateSaved:r}}function withoutSecureValues(t){const e=t.storage;if(!e||typeof e!=="object"||Array.isArray(e)||!("secure"in e))return t;const{secure:a,...r}=e;return{...t,storage:r}}async function noticed(t){if(loaded){loaded={...loaded,notice:t};await persistLoaded();emit()}return{ok:false,detail:t}}async function restoreNetwork(t){if(!hasTool("network"))return false;const e=await tool("network","replaceOverrideRules",{rules:t?.network?.rules??[],enabled:t?.network?.enabled??true}).catch(()=>({ok:false}));return!!e&&e.ok!==false&&e.saved!==false}const NETWORK_BACK_HELP="Make sure the Network tool is on and you're signed in to Buoy, then try again.";function leaveSandbox(){return serial(async()=>{if(!await stillCurrent())return{ok:true,detail:ALREADY_DONE};const t=loaded?.undo??(await readPersisted())?.undo;if(!await restoreNetwork(t))return noticed(`Web calls are still answered from the case. ${NETWORK_BACK_HELP}`);if(loaded){loaded={...loaded,phase:void 0,block:false,notice:void 0,report:{...loaded.report,replay:loaded.report.replay&&{...loaded.report.replay,active:false}}};await persistLoaded();emit()}return{ok:true}})}async function writeUndoCopy(t){if(!stateUndo)return false;let e=false;try{const a=JSON.stringify({undoId:t,state:(0,_sharedUi.superJsonSerialize)(withoutSecureValues(stateUndo??{})),keys:stateUndoKeys,late:stateUndoLate.map(r=>({id:r.id,ids:r.ids,data:(0,_sharedUi.superJsonSerialize)(withoutSecureValues({[r.id]:r.data})[r.id])}))});if(a.length<=MAX_SAVED_CHARS)e=await(0,_sharedUi.setItemChecked)(stateKey(),a)}catch{e=false}if(!e)await _sharedUi.persistentStorage.removeItem(stateKey()).catch(()=>{});return e}async function readUndoState(t){if(stateUndo)return{state:stateUndo,keys:stateUndoKeys,late:stateUndoLate};if(!t.stateSaved)return void 0;try{const e=await _sharedUi.persistentStorage.getItem(stateKey());if(!e)return void 0;const a=JSON.parse(e);if(a.undoId!==t.undoId||!a.state)return void 0;const r=(0,_sharedUi.superJsonDeserialize)(a.state);if(!r||typeof r!=="object")return void 0;const s=n=>Array.isArray(n)?n.filter(c=>typeof c==="string"):null;const o={};if(a.keys&&typeof a.keys==="object")for(const[n,c]of Object.entries(a.keys))o[n]=s(c);const l=Array.isArray(a.late)?a.late.flatMap(n=>typeof n?.id==="string"?[{id:n.id,ids:s(n.ids),data:(0,_sharedUi.superJsonDeserialize)(n.data)}]:[]):[];return{state:r,keys:o,late:l}}catch{return void 0}}async function itemsHeld(t,e){if(!t.previewItems)return null;try{return(await t.previewItems(e,e)).items.map(a=>a.key)}catch{return null}}function covered(t,e){return!!stateUndoKeys[t]?.includes(e)||stateUndoLate.some(a=>a.id===t&&!!a.ids?.includes(e))}async function pushCopy(t,e,a){if(stateUndoLate.length>=MAX_LATE_COPIES||!e.status().canCapture)return;let r;try{r=(await e.capture()).data}catch{return}const s={id:e.id,ids:a,data:r};stateUndoLate.push(s);t.stateSaved=await writeUndoCopy(t.undoId);return s}function inList(t,e){if(t.includes(e))return true;const a=e.indexOf("/");return e.startsWith("mmkv:")&&a>5&&t.includes(e.slice(0,a))}async function copyBeforeWrite(t,e,a,r){if(!stateUndo)return{ok:true,scope:r??void 0,later:[]};const s=e.id;if(!e.previewItems){if(r){const i=r.filter(u=>!covered(s,u));if(!i.length)return{ok:true,scope:r,later:[]};const f=await pushCopy(t,e,i);if(!f)return{ok:false,later:[]};return{ok:true,scope:r,later:[],unclaim:async u=>{if(!u.length||!f.ids)return;f.ids=f.ids.filter(g=>!inList(u,g));if(!f.ids.length)stateUndoLate.splice(stateUndoLate.indexOf(f),1);t.stateSaved=await writeUndoCopy(t.undoId)}}}if(s in stateUndo||stateUndoLate.some(i=>i.id===s))return{ok:true,later:[]};return{ok:!!await pushCopy(t,e,null),later:[]}}let o;try{o=(await e.previewItems(a)).items}catch{return{ok:false,later:[]}}if(r)o=o.filter(i=>inList(r,i.key));const l=new Set(await itemsHeld(e,a)??o.map(i=>i.key));const n=o.filter(i=>i.verdict!=="wont-apply");const c=o.filter(i=>i.verdict==="wont-apply"&&l.has(i.key)).map(i=>i.key);const d=n.filter(i=>!covered(s,i.key));if(d.length){if(stateUndoLate.length>=MAX_LATE_COPIES||!e.status().canCapture)return{ok:false,later:[]};let i;try{i=(await e.capture()).data}catch{return{ok:false,later:[]}}const f=new Set(await itemsHeld(e,i)??[]);const u=d.filter(g=>f.has(g.key)||g.verdict==="added").map(g=>g.key);if(u.length){stateUndoLate.push({id:s,ids:u,data:i});t.stateSaved=await writeUndoCopy(t.undoId)}}const w=n.map(i=>i.key).filter(i=>covered(s,i));const p=n.map(i=>i.key).filter(i=>!w.includes(i));return{ok:true,scope:w,later:[...c,...p]}}function undoBugCase(){return serial(async()=>{if(!await stillCurrent())return[{part:"app",ok:true,detail:ALREADY_DONE}];return doUndo()})}async function restoreLateCopies(t,e,a){const r=new Map;const s=new Map((0,_sharedUi.getStateSnapshotProviders)().map(n=>[n.id,n]));const o=new Map;for(const n of t)o.set(n.id,(o.get(n.id)??0)+1);const l="isn't open now. Open its screen and tap Restore my app again, or tap End.";for(const[n,c]of[...t.entries()].reverse()){if(a&&!a.has(c))continue;const d=`${statePart(c.id)} (opened during the case${(o.get(c.id)??0)>1?`, copy ${n+1}`:""})`;const w=f=>{if(c.ids)r.set(c,f)};const p=s.get(c.id);if(!p||!p.status().canRestore){e(d,false,`It ${l}`);w(a?.get(c)??[]);continue}const i=a?.get(c);try{const f=await p.restore(c.data,i?.length?{includeKeys:i}:c.ids?{includeKeys:c.ids}:void 0);const u=f.skipped.map(g=>g.id);if(f.ok&&!u.length)e(d,true,`${f.applied} put back.`);else{e(d,false,`${u.length||"Some"} ${l}`);w(u)}}catch(f){e(d,false,f instanceof Error?f.message:String(f))}}return r}async function doUndo(){const t=loaded??await readPersisted();const{steps:e,step:a}=stepList();if(!t)return e;const r=t.undo;const s=blocksCalls(t);const o=async(u,g=false)=>{loaded={...t,undoBlocked:true,undoGone:g||t.undoGone,notice:u};await persistLoaded();emit();a("web calls",false,s?`${u} So web calls stay blocked. Tap End to keep the app as it is now and go back to real calls, or clear the app's data.`:`${u} Tap End to keep the app as it is now, or clear the app's data.`);return e};const l=r.undoId?await readUndoState(r):void 0;if(!l)return o("The app's own state from before the load is gone.",true);const n=l.state;if(r.clock&&hasTool("clock")){const u=r.clock.active&&r.clock.virtualIso?tool("clock","setTime",{time:r.clock.virtualIso,freeze:r.clock.rate===0}).then(()=>r.clock.rate&&r.clock.rate!==1?tool("clock","setRate",{rate:r.clock.rate}):void 0):tool("clock","reset");await u.then(()=>a("clock",true,"The clock is back.")).catch(()=>{})}let c=await restoreLateCopies(l.late,a);const d=async(u,g,m)=>{const v=l.keys[u.id];return{ok:true,scope:m??v??void 0,later:[]}};const w=await restoreAll(n,a,d);const p=safeRoute(r.route);let i=true;if(p&&hasTool("route-events")){await tool("route-events","navigate",{path:p,reset:true}).catch(()=>tool("route-events","navigate",{path:p,replace:true})).catch(()=>{i=false})}await restoreLate(n,w,a,d);for(const u of LATE_WAITS_MS){if(!c.size)break;await sleep(u);c=await restoreLateCopies(l.late,a,c)}const f=e.filter(u=>!u.ok&&u.part.startsWith("state"));if(f.length||!i){return o(`${f.length?`${f.map(u=>u.part.replace("state: ","")).join(", ")} didn't come back.`:""}${i?"":" The app couldn't go back to its screen."}`.trim())}if(s){if(!await restoreNetwork(r))return o(`The app is back, but Buoy couldn't put your Network rules back. ${NETWORK_BACK_HELP}`);a("web calls",true,"Real web calls are back.")}loaded=null;stateUndo=void 0;stateUndoLate=[];stateUndoKeys={};for(const u of[_sharedUi.devToolsStorageKeys.bugLinks.loaded(),caseKey(),stateKey()]){await _sharedUi.persistentStorage.removeItem(u).catch(()=>{})}emit();return e}function endBugCase(){return serial(async()=>{if(!await stillCurrent())return{ok:true,detail:ALREADY_DONE};const t=loaded??await readPersisted();const e=t?.undo.clock;if(e&&hasTool("clock")){await(e.active&&e.virtualIso?tool("clock","setTime",{time:e.virtualIso,freeze:e.rate===0}).then(()=>e.rate&&e.rate!==1?tool("clock","setRate",{rate:e.rate}):void 0):tool("clock","reset")).catch(()=>{})}const a=!!t&&blocksCalls(t);if(a&&!await restoreNetwork(t.undo)){return noticed(`The case is still loaded and web calls stay blocked. ${NETWORK_BACK_HELP}`)}loaded=null;stateUndo=void 0;stateUndoLate=[];stateUndoKeys={};for(const r of[_sharedUi.devToolsStorageKeys.bugLinks.loaded(),caseKey(),stateKey()]){await _sharedUi.persistentStorage.removeItem(r).catch(()=>{})}emit();return{ok:true}})}async function saveCase(t,e){try{const a=JSON.stringify({loadId:t,kase:e});if(a.length<=MAX_SAVED_CHARS){const r=await(0,_sharedUi.setItemChecked)(caseKey(),a);return{canLoadAgain:true,onDisk:r}}}catch{}await _sharedUi.persistentStorage.removeItem(caseKey()).catch(()=>{});return{canLoadAgain:false,onDisk:false}}async function persistLoaded(){if(!loaded)return false;recordSaved=await persistRecord(loaded);if(!recordSaved){const t=(0,_otherTabs.tabsMatter)()?globalThis.localStorage:void 0;let e=null;try{e=t?t.getItem(_sharedUi.devToolsStorageKeys.bugLinks.loaded()):await _sharedUi.persistentStorage.getItem(_sharedUi.devToolsStorageKeys.bugLinks.loaded())}catch{e=null}recordSaved=!!e&&JSON.parse(e).loadId===loaded.loadId}return recordSaved}let recordSaved=true;function blocksCalls(t){if(t.phase==="loading")return t.block===true;return t.options?.replay!==false&&!!t.report?.replay?.active}async function stillCurrent(){if(!loaded||!(0,_otherTabs.tabsMatter)())return true;if(!recordSaved)return true;let t;try{const a=globalThis.localStorage;t=a?a.getItem(_sharedUi.devToolsStorageKeys.bugLinks.loaded()):await _sharedUi.persistentStorage.getItem(_sharedUi.devToolsStorageKeys.bugLinks.loaded())}catch{return true}let e;if(t===null){if(!recordSaved)return true}else{try{e=JSON.parse(t).loadId}catch{return true}if(e===loaded.loadId)return true}loaded=null;stateUndo=void 0;stateUndoLate=[];stateUndoKeys={};emit();return false}const ALREADY_DONE="Another tab of this app already finished this bug case.";async function persistRecord(t,e){const a=e??blocksCalls(t);try{return await(0,_sharedUi.setItemChecked)(_sharedUi.devToolsStorageKeys.bugLinks.loaded(),JSON.stringify({...t,block:a}))}catch{return false}}function inBrowser(){if(typeof navigator!=="undefined"&&navigator.product==="ReactNative")return false;return typeof window!=="undefined"&&typeof document!=="undefined"}async function readPersisted(){try{const t=await _sharedUi.persistentStorage.getItem(_sharedUi.devToolsStorageKeys.bugLinks.loaded());return t?JSON.parse(t):null}catch{return null}}function forgetLoadedForTests(){loaded=null;stateUndo=void 0;stateUndoLate=[];stateUndoKeys={}}async function restoreLoadedAfterReload(){const t=await serial(async()=>{if(loaded)return false;const e=await readPersisted();if(!e)return false;const a=await _tabStorage.tabStorage.getItem(restartKey()).catch(()=>null);if(a){await _tabStorage.tabStorage.removeItem(restartKey()).catch(()=>{});if(a===e.loadId){loaded=e;emit();return true}}loaded=e.undo?.undoId&&e.loadId?e:{...e,undoBlocked:true,undoGone:true};if(loaded.phase==="loading"){loaded={...loaded,notice:"This bug case didn't finish loading. Restore my app puts your app back the way it was."}}emit();return false});if(t&&await(0,_sharedUi.waitForToolRegistry)(8e3)){const e=await loadAgain().catch(a=>({loaded:false,steps:[{part:"app",ok:false,detail:a instanceof Error?a.message:String(a)}]}));if(!e.loaded&&loaded){const a=e.steps.find(r=>!r.ok)?.detail??"The case didn't load again after the restart.";await serial(async()=>{if(await stillCurrent())await noticed(`${a} The app restarted with the case's saved state only.`)})}}return loaded}function loadAgain(){return serial(()=>doLoadAgain())}async function doLoadAgain(){const t=loaded??await readPersisted();if(!t)throw new Error("No bug case is loaded. Open the bug link again.");if(!t.canLoadAgain)throw new Error("This case was too big to keep on the phone. Open the bug link again.");let e=null;try{e=JSON.parse(await _sharedUi.persistentStorage.getItem(caseKey())??"null")}catch{e=null}if(!e||e.loadId!==t.loadId)throw new Error("The saved case doesn't match the one loaded. Open the bug link again.");if(!loaded){loaded=t;emit()}return doLoad(checkCase(e.kase),{...t.options,source:t.source})}async function blockedCalls(){const t=loaded;if(!t?.report.replay?.active||!hasTool("network"))return[];try{const e=await tool("network","listOverrideRules");const a=e?.rules?.find(s=>s.name===_sandbox.BLOCK_RULE_NAME)?.id;if(!a)return[];const r=await(0,_sharedUi.readToolSnapshot)("network");return(r?.events??[]).filter(s=>s.override?.ruleId===a).map(s=>({method:s.method??"GET",url:s.url??"",at:s.timestamp??0})).sort((s,o)=>o.at-s.at).slice(0,50)}catch{return[]}}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.fetchBugLink=fetchBugLink;exports.openBugLink=openBugLink;exports.trustedOrigin=trustedOrigin;var _sharedUi=require("@buoy-gg/shared-ui");var _client=require("./client");var _credential=require("./credential");var _loader=require("./loader");var _phoneCrypto=require("./phoneCrypto");var _settings=require("./settings");function linkOrigin(r){const e=/^(https?:\/\/[^/?#]+)\/bug\//i.exec(r.trim());return e?e[1].toLowerCase():null}function trustedOrigin(r){const e=linkOrigin(r);if(!e)throw new Error("That isn't a bug link. It looks like https://buoy.gg/bug/<id>#<key>.");const i=new Set([_settings.DEFAULT_SERVER,"https://www.buoy.gg",(0,_settings.bugLinksServer)()].map(t=>t.toLowerCase()));if(!i.has(e)){throw new Error(`This link is for ${e}. Buoy only sends your sign-in to ${(0,_settings.bugLinksServer)()}. If you trust ${e}, set it as the server in Bug links first.`)}return e}async function fetchBugLink(r){const e=(0,_sharedUi.parseBugLink)(r);if(!e)throw new Error("That isn't a whole bug link. It should look like https://buoy.gg/bug/<id>#<key>.");await(0,_settings.loadBugLinksSettings)();const i=trustedOrigin(r);const t=(0,_credential.currentCredential)();if(!t)throw new Error("Sign in to Buoy to open bug links.");const n=await(0,_client.fetchBlob)(i,e.id,t);const{kase:o}=await(0,_sharedUi.unlockBugCase)(n,e.key,{crypto:(0,_phoneCrypto.caseCrypto)()});return{kase:o,id:e.id}}async function openBugLink(r,e={}){const{kase:i,id:t}=await fetchBugLink(r);return(0,_loader.loadBugCase)(i,{...e,source:{id:t}})}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.listenForOtherTabs=listenForOtherTabs;exports.setTabLocksForTests=setTabLocksForTests;exports.tabsMatter=tabsMatter;exports.tabsProblem=tabsProblem;exports.withStepLock=withStepLock;exports.withTabLock=withTabLock;const TAB_LOCK="buoy-bug-links-tab";const STEP_LOCK="buoy-bug-links-step";let testLocks=null;let testFrame=false;function inBrowser(){if(typeof navigator!=="undefined"&&navigator.product==="ReactNative")return false;return typeof window!=="undefined"&&typeof document!=="undefined"}function isFrame(){if(testLocks)return testFrame;try{return window.top!==window}catch{return true}}function lockManager(){if(testLocks)return testLocks;if(!inBrowser())return null;return navigator.locks??null}function tabsMatter(){return!!testLocks||inBrowser()}let holding=null;let heldTabLock=false;function listenForOtherTabs(){if(holding)return holding;const e=tabsMatter()?lockManager():null;if(!e||isFrame())return holding=Promise.resolve();holding=new Promise(t=>{void e.request(TAB_LOCK,{mode:"shared"},()=>{heldTabLock=true;t();return new Promise(()=>{})}).catch(()=>{heldTabLock=false;t()})});return holding}async function tabsProblem(){if(!tabsMatter())return null;const e=lockManager();if(!e)return"This browser can't keep the app's other tabs safe from a bug case (it has no Web Locks). Load it in a current browser.";if(isFrame())return"This app is inside a frame. Open it in its own tab to load a bug case.";await listenForOtherTabs();let t;try{const s=await e.query();t=new Set((s.held??[]).filter(n=>n.name===TAB_LOCK).map((n,o)=>n.clientId??`unknown-${o}`)).size}catch{return"Buoy couldn't count the app's tabs, so it didn't load the bug case."}const r=t-(heldTabLock?1:0);if(r<=0)return null;return`This app is open in ${r===1?"another tab":`${r} other tabs`}. A bug case changes the data every tab shares, so close the other tabs, then load it.`}function withStepLock(e){return withTabLock(STEP_LOCK,e)}function withTabLock(e,t){if(!tabsMatter())return t();const r=lockManager();if(!r)return Promise.reject(new Error("This browser can't keep bug links safe across tabs. Update it to use bug links."));return r.request(e,{mode:"exclusive"},t)}function setTabLocksForTests(e,t=false){testLocks=e;testFrame=t;holding=null;heldTabLock=false}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.otherAppReason=otherAppReason;exports.phoneDifferences=phoneDifferences;exports.thisPhone=thisPhone;var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");const SIZE_SLACK=.1;const major=o=>(o??"").split(".")[0];function phoneDifferences(o,a){if(!o)return[];const n=[];const t=(r,l,f)=>n.push(`${r} was ${l}; here it's ${f}.`);if(o.id&&a.id&&o.id!==a.id)t("The app",o.id,a.id);if(o.platform&&a.platform&&o.platform!==a.platform)t("The platform",o.platform,a.platform);const d=!o.platform||!a.platform||o.platform===a.platform;const i=!!o.version&&!!a.version&&o.version!==a.version;const c=d&&!!o.build&&!!a.build&&o.build!==a.build;if(i||c){const r=!!o.version&&!!a.version;const l=f=>[r&&f.version,c&&f.build&&`build ${f.build}`].filter(Boolean).join(", ");t("The app version",l(o),l(a))}if(o.os&&a.os&&o.platform===a.platform&&major(o.os)!==major(a.os)){t(`${o.platform==="ios"?"iOS":o.platform==="android"?"Android":"The OS"}`,o.os,a.os)}if(o.locale&&a.locale&&o.locale!==a.locale)t("The language",o.locale,a.locale);if(o.timeZone&&a.timeZone&&o.timeZone!==a.timeZone)t("The time zone",o.timeZone,a.timeZone);if(o.appearance&&a.appearance&&o.appearance!==a.appearance){t("The phone",`in ${o.appearance} mode`,`in ${a.appearance} mode`)}const e=o.window;const p=a.window;if(e&&p&&e.width>0&&e.height>0&&p.width>0&&p.height>0){const r=(l,f)=>Math.abs(l-f)/l>SIZE_SLACK;if(r(e.width,p.width)||r(e.height,p.height)){t("The screen",`${Math.round(e.width)}x${Math.round(e.height)}`,`${Math.round(p.width)}x${Math.round(p.height)}`)}}if(e?.fontScale&&p?.fontScale&&Math.abs(e.fontScale-p.fontScale)>=.05){t("The text size",`${e.fontScale}x`,`${p.fontScale}x`)}return n}const PLATFORM_NAMES={ios:"an iOS app",android:"an Android app",macos:"a macOS app",windows:"a Windows app"};const appOn=o=>PLATFORM_NAMES[o]??`a ${o} app`;function otherAppReason(o,a){if(!o)return null;const n=!o.platform||!a.platform||o.platform===a.platform;if(n&&o.id&&a.id&&o.id!==a.id)return`This case is from another app (${o.id}), and this is ${a.id}.`;const t=d=>d==="web";if(o.platform&&a.platform&&t(o.platform)!==t(a.platform)){return t(o.platform)?`This case is from a web app, and this is ${appOn(a.platform)}.`:`This case is from ${appOn(o.platform)}, and this is a web app.`}return null}function thisPhone(){let o;let a;try{({locale:o,timeZone:a}=Intl.DateTimeFormat().resolvedOptions())}catch{}const n=_reactNative.Dimensions.get("window");return{platform:_reactNative.Platform.OS,os:String(_reactNative.Platform.Version),...(0,_sharedUi.bugCaseAppInfo)(),appearance:_reactNative.Appearance.getColorScheme()??void 0,locale:o,timeZone:a,window:{width:n.width,height:n.height,scale:_reactNative.PixelRatio.get(),fontScale:_reactNative.PixelRatio.getFontScale()}}}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.caseCrypto=caseCrypto;exports.gunzipCapped=gunzipCapped;exports.phoneCaseCrypto=phoneCaseCrypto;exports.phoneTeamKeyCrypto=phoneTeamKeyCrypto;exports.teamKeyCrypto=teamKeyCrypto;var _aes=require("@noble/ciphers/aes");var _nist=require("@noble/curves/nist");var _hmac=require("@noble/hashes/hmac");var _sha=require("@noble/hashes/sha2");var _fflate=require("fflate");var _sharedUi=require("@buoy-gg/shared-ui");var _randomBytes=require("./randomBytes");function phoneCaseCrypto(){return{randomBytes:_randomBytes.randomBytes,seal:async(e,r,t,n)=>(0,_aes.gcm)(e,r,n).encrypt(t),open:async(e,r,t,n)=>(0,_aes.gcm)(e,r,n).decrypt(t),gzip:async e=>(0,_fflate.gzipSync)(e,{level:6}),gunzip:async(e,r)=>gunzipCapped(e,r)}}function caseCrypto(){try{return(0,_sharedUi.webCaseCrypto)()}catch{return phoneCaseCrypto()}}function phoneTeamKeyCrypto(){return{randomBytes:_randomBytes.randomBytes,sha256:async e=>(0,_sha.sha256)(e),hmacSha256:async(e,r)=>(0,_hmac.hmac)(_sha.sha256,e.length?e:new Uint8Array(32),r),seal:async(e,r,t,n)=>(0,_aes.gcm)(e,r,n).encrypt(t),open:async(e,r,t,n)=>(0,_aes.gcm)(e,r,n).decrypt(t),p256Generate:async()=>{const e=_nist.p256.utils.randomSecretKey((0,_randomBytes.randomBytes)(48));return{d:e,pub:_nist.p256.getPublicKey(e,false)}},p256Ecdh:async(e,r)=>{if(!_nist.p256.utils.isValidPublicKey(r,false))throw new Error("That public key isn't on the curve.");return _nist.p256.getSharedSecret(e.d,r,true).slice(1)},p256Sign:async(e,r)=>{const t=_nist.p256.sign(r,e.d,{prehash:true,lowS:false});return t instanceof Uint8Array?t:t.toCompactRawBytes()},p256Verify:async(e,r,t)=>_nist.p256.verify(t,r,e,{prehash:true,lowS:false})}}function teamKeyCrypto(){try{return(0,_sharedUi.webTeamKeyCrypto)()}catch{return phoneTeamKeyCrypto()}}function gunzipCapped(e,r){const t=[];let n=0;let o=false;const i=new _fflate.Gunzip((s,u)=>{n+=s.length;if(n>r)throw new _sharedUi.BugCaseLockError("too_big",`This case unzips past ${r} bytes.`);t.push(s);if(u)o=true});const a=64*1024;for(let s=0;s<e.length;s+=a){i.push(e.subarray(s,s+a),s+a>=e.length)}if(!o)throw new _sharedUi.BugCaseLockError("bad_blob","This case ended before it finished unzipping.");const c=new Uint8Array(n);let p=0;for(const s of t){c.set(s,p);p+=s.length}return c}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.NO_RANDOM_MESSAGE=void 0;exports.bytesFromUuids=bytesFromUuids;exports.randomBytes=randomBytes;exports.randomSource=randomSource;var _sharedUi=require("@buoy-gg/shared-ui");const UUID_V4=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;function randomSource(){const e=globalThis;if(typeof e.crypto?.getRandomValues==="function")return"getRandomValues";if(typeof e.expo?.uuidv4==="function")return"expo-uuid";return null}const NO_RANDOM_MESSAGE=exports.NO_RANDOM_MESSAGE="This app has no safe random numbers, so Buoy can't lock a bug case. Add expo-modules-core (any Expo app has it), react-native-get-random-values or react-native-quick-crypto.";function randomBytes(e){const n=globalThis;const t=randomSource();if(t==="getRandomValues")return n.crypto.getRandomValues(new Uint8Array(e));if(t==="expo-uuid")return bytesFromUuids(e,()=>n.expo.uuidv4());throw new _sharedUi.BugCaseLockError("no_crypto",NO_RANDOM_MESSAGE)}function bytesFromUuids(e,n){const t=new Uint8Array(e);let s=0;let u=0;let r=0;let c="";const p=(o,i)=>{u=u<<i|o;r+=i;while(r>=8&&s<e){r-=8;t[s++]=u>>r&255}u&=(1<<r)-1};while(s<e){const o=String(n());if(!UUID_V4.test(o)||o===c){throw new _sharedUi.BugCaseLockError("no_crypto","Expo's UUID source gave a value that isn't a fresh v4 UUID.")}c=o;const i=o.replace(/-/g,"");for(let a=0;a<32&&s<e;a++){if(a===12)continue;const d=parseInt(i[a],16);if(a===16)p(d&3,2);else p(d,4)}}return t}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.addNote=addNote;exports.copyWithin=copyWithin;exports.deleteLink=deleteLink;exports.dismissReportState=dismissReportState;exports.reportBug=reportBug;exports.reportState=reportState;exports.resetReporterForTests=resetReporterForTests;exports.retryWaiting=retryWaiting;exports.sendLink=sendLink;exports.setLinkPriority=setLinkPriority;exports.subscribeReportState=subscribeReportState;exports.wrapLater=wrapLater;var _sharedUi=require("@buoy-gg/shared-ui");var _client=require("./client");var _credential=require("./credential");var _linksStore=require("./linksStore");var _otherTabs=require("./otherTabs");var _phoneCrypto=require("./phoneCrypto");var _teamKeys=require("./teamKeys");var _randomBytes=require("./randomBytes");var _settings=require("./settings");var _frames=require("../media/frames");const MAX_SEND_BYTES=7.5*1024*1024;let current=null;let inFlight=null;const listeners=new Set;const timers=new Map;const sending=new Map;const cancelled=new Set;function perLink(e,n){const t=sending.get(e)??Promise.resolve(null);const r=()=>(0,_otherTabs.withTabLock)(`buoy-bug-links-link-${e}`,async()=>{if((0,_otherTabs.tabsMatter)())await(0,_linksStore.reloadMyLinks)();return n()});const a=t.then(r,r);const o=a.then(()=>(0,_linksStore.findMyLink)(e)??null,()=>(0,_linksStore.findMyLink)(e)??null);sending.set(e,o);void o.finally(()=>{if(sending.get(e)===o)sending.delete(e)});return a}const RETRY_WAITS_MS=[5e3,3e4,12e4,6e5,18e5];const COPY_WAIT_MS=1500;async function copyWithin(e,n){let t;const r=new Promise(a=>{t=setTimeout(()=>a(false),n)});try{return await Promise.race([(0,_sharedUi.copyToClipboard)(e).catch(()=>false),r])}finally{clearTimeout(t)}}function reportState(){return current}function subscribeReportState(e){listeners.add(e);return()=>listeners.delete(e)}function dismissReportState(){current=null;emit()}function setState(e){current=e;emit()}function emit(){for(const e of listeners)e()}function newLocalId(){return`bl_${Date.now().toString(36)}_${Math.floor(Math.random()*1e9).toString(36)}`}function cardFieldsOf(e){const n=e.console?.find(t=>t.level==="error")?.text;return{note:e.note||void 0,app:e.app?.id||void 0,version:e.app?.version||void 0,build:e.app?.build||void 0,route:e.route?.pathname||void 0,error:n?n.split("\n")[0]:void 0}}async function firstSendExtras(e,n,t,r){if(!e.caseTag||!e.card)return e.priority?{extras:{priority:e.priority}}:{};const a=(0,_phoneCrypto.teamKeyCrypto)();const o={...e.card,note:(await(0,_sharedUi.unlockBugCase)(n,e.key,{crypto:(0,_phoneCrypto.caseCrypto)()})).kase.note||void 0};const s=await(0,_sharedUi.sealCard)(a,e.key,e.caseTag,1,o,n);const y=await(0,_teamKeys.pickTeamKey)(t,r);const m=y.kind==="ok"?JSON.stringify(await(0,_sharedUi.wrapCaseKey)(a,y.target,e.key,e.caseTag)):void 0;return{extras:{card:s,wrap:m,owner:e.teamOwner,priority:e.priority},pick:y}}function ownerOfPick(e){return e?.kind==="ok"?e.target.owner:e?.owner}function wrapState(e,n,t,r){if(!e.caseTag||t==="saved")return{wrapPending:false,wrapWait:void 0};if(r&&(!n||n.kind==="off")){return{wrapPending:true,wrapWait:{code:"no_current",message:"Your team turned on its key. Buoy locks this bug to it soon."}}}if(!n||n.kind==="off")return{wrapPending:false,wrapWait:void 0};if(n.kind==="wait")return{wrapPending:true,wrapWait:{code:n.code,message:n.message,teamKeyId:n.teamKeyId}};return{wrapPending:true,wrapWait:{code:"no_current",message:"Your team's key changed. Buoy locks this bug to the new one soon."}}}function showFor(e,n,t,r={}){if(cancelled.has(e))return;if(current&&current.localId!==e&&current.localId!==null)return;const a=(0,_linksStore.findMyLink)(e);setState({localId:e,phase:n,message:t,removed:a?.removed.length??0,url:a?.url,...r})}function reportBug(e={}){if(inFlight)return inFlight;inFlight=(async()=>{setState({localId:null,phase:"capturing",message:"Saving the bug\u2026",removed:0});try{await Promise.all([(0,_linksStore.loadMyLinks)(),(0,_settings.loadBugLinksSettings)()]);let n=await(0,_frames.addMediaToCase)(await(0,_sharedUi.captureBugCase)({note:e.note,rules:(0,_settings.bugLinksRules)()}));const t=await(0,_sharedUi.newCaseKeyParts)((0,_phoneCrypto.teamKeyCrypto)());let r;try{r=await(0,_sharedUi.lockBugCase)(n,(0,_phoneCrypto.caseCrypto)(),t)}catch(s){if(!(s instanceof _sharedUi.BugCaseLockError&&s.code==="too_big"&&n.media))throw s;n=(0,_frames.shrinkMedia)(n,0);r=await(0,_sharedUi.lockBugCase)(n,(0,_phoneCrypto.caseCrypto)(),t)}for(const s of[2*1024*1024,512*1024,0]){if(r.blob.length<=MAX_SEND_BYTES||!n.media)break;n=(0,_frames.shrinkMedia)(n,s);r=await(0,_sharedUi.lockBugCase)(n,(0,_phoneCrypto.caseCrypto)(),t)}const a=newLocalId();const o=await(0,_linksStore.addMyLink)({localId:a,key:r.key,header:r.header,note:n.note,priority:e.priority&&e.priority!=="normal"?e.priority:void 0,screen:n.route?.pathname,createdAt:n.createdAt,status:"sending",attempts:0,server:(0,_settings.bugLinksServer)(),owner:(0,_credential.credentialOwner)((0,_credential.currentCredential)())??void 0,removed:n.scrub.items,caseTag:t.caseTag,keyProofHash:t.keyProofHash,card:cardFieldsOf(n),cardRevision:1},(0,_sharedUi.base64UrlEncode)(r.blob));if(!o){await(0,_linksStore.updateMyLink)(a,{error:"This phone's storage is full, so the case lives only while the app stays open."})}return await sendLink(a)}catch(n){const t=n instanceof _sharedUi.ScrubLimitError?"This bug was too big to check for private data, so nothing was sent.":n instanceof _sharedUi.BugCaseLockError?n.message:`Couldn't save the bug: ${n instanceof Error?n.message:String(n)}`;setState({localId:null,phase:"failed",message:t,removed:0});return null}finally{inFlight=null;(0,_frames.resumeMedia)()}})();return inFlight}function sendLink(e,n={}){const t=sending.get(e);if(t&&!n.asMe)return t;return perLink(e,()=>doSend(e,n)).then(async r=>{if(r?.status==="sent"&&r.priorityPending)return await setLinkPriority(e).catch(()=>null)??(0,_linksStore.findMyLink)(e)??r;return r})}async function doSend(e,n){await(0,_settings.loadBugLinksSettings)();let t=(0,_linksStore.findMyLink)(e);if(!t){cancelled.delete(e);return null}if(t.status==="deleting")return finishDelete(t);if(t.id&&!t.needsPut){if((0,_linksStore.hasHeldUploadResult)(e)&&!await(0,_linksStore.saveHeldUploadResults)())schedule(e,RETRY_WAITS_MS[1]);return(0,_linksStore.findMyLink)(e)??t}clearTimer(e);if(!t.id&&!(0,_settings.bugLinksSettingsRead)()){return wait(t,"Buoy couldn't read its settings just now. It will try again.","storage_read")}const r=t.id?t.server??(0,_settings.bugLinksServer)():(0,_settings.bugLinksServer)();let a;try{a=await(0,_linksStore.readBlobChecked)(e)}catch{return wait(t,"Buoy couldn't read this saved bug just now. It will try again.","storage_read")}const o=a?(0,_sharedUi.base64UrlDecode)(a):null;if(!o){await(0,_linksStore.updateMyLink)(e,{status:"failed",error:"The saved case is gone from this phone.",errorCode:"lost"});showFor(e,"failed","The saved case is gone from this phone.");return(0,_linksStore.findMyLink)(e)??null}if(t.id&&t.needsPut){const i=(0,_credential.currentCredential)();const f=i?(0,_credential.credentialOwner)(i):null;if(f&&t.owner&&f!==t.owner&&!n.asMe){return wait(t,"Someone else is signed in now. Open Bug links and tap Send as me to add the note anyway.","other_account",false)}const d=t.blobVersion??0;const w=t;let g;if(w.card){const{kase:u}=await(0,_sharedUi.unlockBugCase)(o,w.key,{crypto:(0,_phoneCrypto.caseCrypto)()});g={...w.card,note:u.note||void 0}}const l=async u=>{let p;if(w.caseTag&&g){const c=await(0,_sharedUi.sealCard)((0,_phoneCrypto.teamKeyCrypto)(),w.key,w.caseTag,u,g,o);p={card:c,cardRevision:u}}return(0,_client.replaceBlob)(r,w.id,w.editToken??"",o,p)};try{let u;try{u=await l((t.cardRevision??1)+1)}catch(v){const L=v instanceof _client.BugLinksHttpError&&v.code==="conflict"?v.detail.cardRevision:void 0;if(typeof L!=="number")throw v;if(L!==t.cardRevision)t=await(0,_linksStore.updateMyLink)(e,{cardRevision:L})??{...t,cardRevision:L};u=await l(L+1)}const{expiresAt:p,cardRevision:c}=u;const h=(0,_linksStore.findMyLink)(e);return await(0,_linksStore.updateMyLink)(e,{needsPut:(h?.blobVersion??0)!==d,...g&&(h?.blobVersion??0)===d?{card:g,note:g.note}:{},expiresAt:p===void 0?t.expiresAt:p,cardRevision:c??t.cardRevision,error:void 0,errorCode:void 0,attempts:0})??null}catch(u){if(u instanceof _client.BugLinksHttpError&&u.retry)return wait(t,"The note will reach the server when this phone is back online.",u.code);if(u instanceof _client.BugLinksHttpError&&u.code==="conflict")return wait(t,"The bug changed on the server at the same time. Buoy will send the note again.","conflict");const p=u instanceof Error?u.message:String(u);return wait(t,`${p} Buoy will send the note again.`,u instanceof _client.BugLinksHttpError?u.code:"error")}}const s=(0,_credential.currentCredential)();if(!s)return wait(t,"Sign in to Buoy to send this bug. It's saved on the phone.","signed_out");const y=(0,_credential.credentialOwner)(s);if(!t.owner&&y){const i=await(0,_linksStore.updateMyLink)(e,{owner:y});if((0,_otherTabs.tabsMatter)()&&!(0,_linksStore.isUnsavedNew)(e)&&i?.owner!==y){return wait(t,"The browser's storage is full, so Buoy can't keep track of this send. Free some space; it will try again.","storage_full")}t=i??{...t,owner:y}}if(t.owner&&y!==t.owner){if(!n.asMe){return wait(t,"Someone else is signed in now. Open Bug links and tap Send as me to send it anyway.","other_account",false)}const i=await(0,_linksStore.updateMyLink)(e,{owner:y??void 0,teamOwner:void 0});if((0,_otherTabs.tabsMatter)()&&!(0,_linksStore.isUnsavedNew)(e)&&(i?.owner!==(y??void 0)||i?.teamOwner!==void 0)){return wait(t,"The browser's storage is full, so Buoy can't keep track of this send. Free some space; it will try again.","storage_full")}t=i??{...t,owner:y??void 0,teamOwner:void 0}}const m=t.removed.length;if(!t.id&&t.triedServer&&t.triedServer!==r){try{await(0,_client.deleteUpload)(t.triedServer,e,s)}catch(i){if(i instanceof _client.BugLinksHttpError&&i.code==="signed_out"){return wait(t,"Buoy couldn't sign you in to the old server to move this bug. It's saved on the phone.","signed_out")}if(i instanceof _client.BugLinksHttpError&&i.retry&&i.status!==void 0){return wait(t,"Buoy needs the old server once more before it moves this bug. It will try again.",i.code)}}if(t.teamOwner){const i=await(0,_linksStore.updateMyLink)(e,{teamOwner:void 0});if((0,_otherTabs.tabsMatter)()&&!(0,_linksStore.isUnsavedNew)(e)&&i?.teamOwner!==void 0){return wait(t,"The browser's storage is full, so Buoy can't keep track of this send. Free some space; it will try again.","storage_full")}t=i??{...t,teamOwner:void 0}}}showFor(e,"sending","Sending the bug link\u2026");const b=await(0,_linksStore.updateMyLink)(e,{status:"sending",server:r,triedServer:r});if((0,_otherTabs.tabsMatter)()&&!(0,_linksStore.isUnsavedNew)(e)&&b?.triedServer!==r){return wait(t,"The browser's storage is full, so Buoy can't keep track of this send. Free some space; it will try again.","storage_full")}if(cancelled.has(e)||!(0,_linksStore.findMyLink)(e)||(0,_linksStore.findMyLink)(e)?.status==="deleting"){cancelled.delete(e);const i=(0,_linksStore.findMyLink)(e);return i?finishDelete(i):null}const k="The browser's storage is full, so Buoy can't keep track of this send. Free some space; it will try again.";if(!t.editToken){const i=(0,_sharedUi.base64UrlEncode)((0,_randomBytes.randomBytes)(32));const f=await(0,_linksStore.updateMyLink)(e,{editToken:i});if((0,_otherTabs.tabsMatter)()&&!(0,_linksStore.isUnsavedNew)(e)&&f?.editToken!==i)return wait(t,k,"storage_full");t=f??{...t,editToken:i}}try{if(t.caseTag&&!t.teamOwner){const c=ownerOfPick(await(0,_teamKeys.pickTeamKey)(r,s,{fresh:true}));if(c){const h=await(0,_linksStore.updateMyLink)(e,{teamOwner:c});if((0,_otherTabs.tabsMatter)()&&!(0,_linksStore.isUnsavedNew)(e)&&h?.teamOwner!==c)return wait(t,k,"storage_full");t=h??{...t,teamOwner:c}}}if(!await(0,_linksStore.saveRowNow)(e))return wait(t,k,"storage_full");const{extras:i,pick:f}=await firstSendExtras(t,o,r,s);const d=await(0,_client.uploadBlob)(r,s,o,{uploadId:e,editToken:t.editToken,extras:i});const w=async()=>{const c={id:d.id,editToken:d.editToken,server:r,status:"deleting"};const h=(0,_linksStore.findMyLink)(e)?await(0,_linksStore.updateMyLink)(e,c):void 0;if(h&&h.id!==d.id)(0,_linksStore.holdUploadResult)(e,c);if(h&&!await(0,_linksStore.saveRowNow)(e)){schedule(e,RETRY_WAITS_MS[1]);return null}try{await(0,_client.deleteRemote)(r,d.id,{editToken:d.editToken});cancelled.delete(e);if((0,_linksStore.findMyLink)(e)&&!await(0,_linksStore.removeMyLink)(e))schedule(e,RETRY_WAITS_MS[1])}catch{if(h)schedule(e,RETRY_WAITS_MS[1])}return null};if(cancelled.has(e)||!(0,_linksStore.findMyLink)(e))return w();const g=(0,_sharedUi.bugLinkUrl)(r,d.id,t.key);const l={id:d.id,editToken:d.editToken,expiresAt:d.expiresAt,url:g,status:"sent",error:void 0,errorCode:void 0,nextTryAt:void 0,attempts:0,cardRevision:d.cardRevision??1,...wrapState(t,f,d.wrapStatus,d.teamKeys),...!t.teamOwner&&d.owner?{teamOwner:d.owner}:{}};const u=await(0,_linksStore.updateMyLink)(e,l);if((0,_otherTabs.tabsMatter)()&&!(0,_linksStore.isUnsavedNew)(e)&&u?.id!==d.id){(0,_linksStore.holdUploadResult)(e,l);schedule(e,RETRY_WAITS_MS[0]);const c=await copyWithin(g,COPY_WAIT_MS);showFor(e,"sent",c?"Bug link copied":"Bug link ready",{url:g,removed:m,copied:c});return(0,_linksStore.findMyLink)(e)??{...t,...l}}if(!u||cancelled.has(e)||u.status==="deleting")return w();const p=await copyWithin(g,COPY_WAIT_MS);showFor(e,"sent",p?"Bug link copied":"Bug link ready",{url:g,removed:m,copied:p});if((0,_linksStore.findMyLink)(e)?.needsPut)return doSend(e,n);return u??null}catch(i){if(cancelled.has(e)){cancelled.delete(e);schedule(e,RETRY_WAITS_MS[0]);return null}if(i instanceof _client.BugLinksHttpError&&i.retry)return wait(t,i.message,i.code);if(i instanceof _client.BugLinksHttpError&&i.code==="signed_out"){return wait(t,"Buoy couldn't sign you in to send this bug. It's saved on the phone.","signed_out")}if(i instanceof _client.BugLinksHttpError&&i.code==="conflict"&&i.status===409&&!(0,_linksStore.findMyLink)(e)?.id){try{await(0,_client.deleteUpload)(r,e,s)}catch(w){return wait(t,w instanceof Error?w.message:String(w),w instanceof _client.BugLinksHttpError?w.code:"error")}return wait(t,"Buoy is sending this bug again.","conflict")}const f=i instanceof _client.BugLinksHttpError&&i.code==="needs_pro"?"Bug links need Buoy Pro. The case is saved on this phone.":i instanceof Error?i.message:String(i);const d=i instanceof _client.BugLinksHttpError?i.code:"error";await(0,_linksStore.updateMyLink)(e,{status:"failed",error:f,errorCode:d});showFor(e,"failed",f,{code:d});return(0,_linksStore.findMyLink)(e)??null}}async function wait(e,n,t,r=true){const a=(0,_linksStore.findMyLink)(e.localId)??e;const o=a.attempts+1;const s=RETRY_WAITS_MS[Math.min(o-1,RETRY_WAITS_MS.length-1)];await(0,_linksStore.updateMyLink)(e.localId,{status:a.id||a.status==="deleting"?a.status:"waiting",error:n,errorCode:t,attempts:o,nextTryAt:r?Date.now()+s:void 0});if(r)schedule(e.localId,s);showFor(e.localId,"waiting",n,{code:t});return(0,_linksStore.findMyLink)(e.localId)??null}function schedule(e,n){clearTimer(e);timers.set(e,setTimeout(()=>{timers.delete(e);void sendLink(e)},n))}function clearTimer(e){const n=timers.get(e);if(n)clearTimeout(n);timers.delete(e)}async function retryWaiting(){await(0,_linksStore.loadMyLinks)();for(const{localId:e}of(0,_linksStore.myLinks)()){try{let n=(0,_linksStore.findMyLink)(e);if(!n)continue;if(n.errorCode==="other_account"&&n.status!=="deleting")continue;if(n.status==="waiting"||n.status==="deleting"||n.needsPut)await sendLink(e);else if(n.status==="sent"&&n.wrapPending)await wrapLater(e);n=(0,_linksStore.findMyLink)(e);if(n?.status==="sent"&&n.priorityPending)await setLinkPriority(e)}catch{}}}function wrapLater(e){return perLink(e,async()=>{const n=(0,_linksStore.findMyLink)(e);if(!n?.id||!n.wrapPending||!n.caseTag||!n.editToken||!n.server||n.status!=="sent")return n??null;const t=(0,_credential.currentCredential)();if(!t||n.owner&&(0,_credential.credentialOwner)(t)!==n.owner){return await(0,_linksStore.updateMyLink)(e,{wrapWait:{code:"other_account",message:"Sign in as the person who sent this bug, so Buoy can lock it to their team."}})??null}const r=await(0,_teamKeys.pickTeamKey)(n.server,t,{fresh:true});if(n.teamOwner&&ownerOfPick(r)&&ownerOfPick(r)!==n.teamOwner){return await(0,_linksStore.updateMyLink)(e,{wrapWait:{code:"team_changed",message:"Your team changed since this bug was sent. Only its link opens it."}})??null}if(r.kind==="off"){if(r.owner&&n.teamOwner&&r.owner===n.teamOwner)return await(0,_linksStore.updateMyLink)(e,{wrapPending:false,wrapWait:void 0})??null;return await(0,_linksStore.updateMyLink)(e,{wrapWait:{code:"off",message:"Your team list isn't reachable right now. Buoy will try again."}})??null}if(r.kind==="wait")return await(0,_linksStore.updateMyLink)(e,{wrapWait:{code:r.code,message:r.message,teamKeyId:r.teamKeyId}})??null;try{const a=await(0,_sharedUi.wrapCaseKey)((0,_phoneCrypto.teamKeyCrypto)(),r.target,n.key,n.caseTag);await(0,_client.sendWrap)(n.server,n.id,n.editToken,JSON.stringify(a),n.caseTag);return await(0,_linksStore.updateMyLink)(e,{wrapPending:false,wrapWait:void 0})??null}catch(a){const o=a instanceof Error?a.message:String(a);return await(0,_linksStore.updateMyLink)(e,{wrapWait:{code:a instanceof _client.BugLinksHttpError?a.code:"error",message:o}})??null}})}async function setLinkPriority(e,n){if(n){const t=await(0,_linksStore.updateMyLink)(e,r=>r.status==="deleting"?null:{priority:n,priorityPending:true});if(!t||t.status==="deleting")return t??null}return perLink(e,async()=>{const t=(0,_linksStore.findMyLink)(e);if(!t||!t.priorityPending)return t??null;if(!t.id||!t.editToken||!t.server||t.status!=="sent")return t;if(t.owner&&(0,_credential.credentialOwner)((0,_credential.currentCredential)())!==t.owner){throw new Error("Someone else is signed in now. Sign in as the person who sent this bug to change its priority.")}const r=t.priority??"normal";try{await(0,_client.sendPriority)(t.server,t.id,t.editToken,r);return await(0,_linksStore.updateMyLink)(e,a=>(a.priority??"normal")===r?{priorityPending:false}:null)??null}catch(a){if(a instanceof _client.BugLinksHttpError&&a.code==="priority_set"){const o=_client.BUG_PRIORITIES.find(s=>s===a.detail.priority);await(0,_linksStore.updateMyLink)(e,{priorityPending:false,...o?{priority:o}:{}});throw new Error("A teammate already set this bug's priority, so yours wasn't changed.")}if(a instanceof _client.BugLinksHttpError&&(a.retry||a.code==="conflict"))return t;await(0,_linksStore.updateMyLink)(e,o=>(o.priority??"normal")===r?{priorityPending:false}:null);throw a}})}async function addNote(e,n){const t=await perLink(e,()=>writeNote(e,n));if(t?.id)return perLink(e,()=>doSend(e,{}));return t}async function writeNote(e,n){const t=(0,_linksStore.findMyLink)(e);if(!t)return null;let r;try{r=await(0,_linksStore.readBlobChecked)(e)}catch{throw new Error("Buoy couldn't read this saved bug just now. Try again.")}const a=r?(0,_sharedUi.base64UrlDecode)(r):null;if(!a)throw new Error("This case is no longer on the phone, so a note can't be added here.");const o=(0,_phoneCrypto.caseCrypto)();const{kase:s}=await(0,_sharedUi.unlockBugCase)(a,t.key,{crypto:o});const y=(0,_sharedUi.scrubBugCase)({note:n.trim().slice(0,4e3)},(0,_settings.bugLinksRules)());s.note=y.value.note||void 0;const m=s.scrub.items.filter(c=>!c.path.startsWith("$.note"));const b=[...m,...y.removed.map(c=>({...c,path:`$.note${c.path.slice(1)}`}))];const k={};for(const c of b)k[c.kind]=(k[c.kind]??0)+1;s.scrub={removed:b.length,byKind:k,items:b};const i=await(0,_sharedUi.lockBugCase)(s,o,t.caseTag&&t.keyProofHash?{key:t.key,caseTag:t.caseTag,keyProofHash:t.keyProofHash}:{key:t.key});const f=(0,_linksStore.findMyLink)(e);if(!f||f.status==="deleting")return null;const d=(f.blobVersion??0)+1;const w="Buoy couldn't save the note (the storage may be full), so it wasn't added.";if((f.id||f.triedServer)&&!f.needsPut){const c=await(0,_linksStore.updateMyLink)(e,{needsPut:true});if(!await rowKept(e,c,h=>!!h.needsPut))throw new Error(w)}else if(!await(0,_linksStore.saveRowNow)(e)){throw new Error(w)}const g=(0,_otherTabs.tabsMatter)()&&!(0,_linksStore.isUnsavedNew)(e);const l=async()=>{if(r)await(0,_linksStore.writeBlob)(e,r,f.blobVersion??0);if((f.id||f.triedServer)&&!f.needsPut)await(0,_linksStore.updateMyLink)(e,{needsPut:false})};const u=await(0,_linksStore.writeBlob)(e,(0,_sharedUi.base64UrlEncode)(i.blob),d);if(!u&&g){await l();throw new Error(w)}const p=await(0,_linksStore.updateMyLink)(e,{note:s.note,card:t.card?{...t.card,note:s.note||void 0}:void 0,header:i.header,removed:s.scrub.items,needsPut:!!f.id||!!f.triedServer,blobVersion:d,...u?{}:{error:"The phone couldn't save the note. It goes up if the app stays open."}});if(g&&p?.blobVersion!==d){await l();throw new Error(w)}return p??null}async function deleteLink(e){const n=(0,_linksStore.findMyLink)(e);if(!n)return;clearTimer(e);if(current?.localId===e)dismissReportState();if(sending.has(e)&&!n.id){cancelled.add(e);const t=await(0,_linksStore.updateMyLink)(e,{status:"deleting",errorCode:void 0});if(!await rowKept(e,t,r=>r.status==="deleting")){cancelled.delete(e);throw new Error("The storage is full, so Buoy can't keep track of this cancel. Free some space, then cancel it again.")}void perLink(e,()=>doSend(e,{}));return}await perLink(e,async()=>{const t=(0,_linksStore.findMyLink)(e);if(!t)return null;const r=t.status==="deleting"?t:await(0,_linksStore.updateMyLink)(e,{status:"deleting",errorCode:void 0,nextTryAt:void 0});if(!await rowKept(e,r,a=>a.status==="deleting")){throw new Error("The storage is full, so Buoy can't keep track of this delete. Free some space, then delete it again.")}return r?finishDelete(r):null})}async function rowKept(e,n,t){if((0,_otherTabs.tabsMatter)())return(0,_linksStore.isUnsavedNew)(e)||!!n&&t(n);return(0,_linksStore.saveRowNow)(e)}async function finishDelete(e){cancelled.delete(e.localId);if(!await(0,_linksStore.saveRowNow)(e.localId)){schedule(e.localId,RETRY_WAITS_MS[1]);return(0,_linksStore.findMyLink)(e.localId)??e}if(!e.id&&e.takenBackOnce&&e.nextTryAt&&e.nextTryAt>Date.now()){schedule(e.localId,e.nextTryAt-Date.now());return e}try{if(e.id&&e.server){await(0,_client.deleteRemote)(e.server,e.id,{editToken:e.editToken,credential:(0,_credential.currentCredential)()??void 0})}else if(e.triedServer){const n=(0,_credential.currentCredential)();if(!n)return wait(e,"Sign in to Buoy to finish deleting this bug link.","signed_out");if(e.owner&&(0,_credential.credentialOwner)(n)!==e.owner){return wait(e,"Sign in as the person who sent this bug to finish deleting it.","other_account",false)}await(0,_client.deleteUpload)(e.triedServer,e.localId,n);if(!e.takenBackOnce){const t=await(0,_linksStore.updateMyLink)(e.localId,{takenBackOnce:true,error:void 0,errorCode:void 0,nextTryAt:Date.now()+RETRY_WAITS_MS[2]});schedule(e.localId,RETRY_WAITS_MS[2]);return t??null}}if(!await(0,_linksStore.removeMyLink)(e.localId)){schedule(e.localId,RETRY_WAITS_MS[1]);return(0,_linksStore.findMyLink)(e.localId)??null}return null}catch(n){if(n instanceof _client.BugLinksHttpError&&n.code==="signed_out"){return wait(e,"Sign in to Buoy to finish deleting this bug link.","signed_out")}const t=n instanceof _client.BugLinksHttpError&&n.retry;if(t)schedule(e.localId,RETRY_WAITS_MS[1]);await(0,_linksStore.updateMyLink)(e.localId,{error:n instanceof Error?n.message:String(n),errorCode:n instanceof _client.BugLinksHttpError?n.code:"error"});return(0,_linksStore.findMyLink)(e.localId)??null}}function resetReporterForTests(){for(const e of timers.values())clearTimeout(e);timers.clear();sending.clear();cancelled.clear();current=null;inFlight=null}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.SANDBOX_PREFIX=exports.BLOCK_RULE_NAME=void 0;exports.buildSandbox=buildSandbox;exports.graphqlOperation=graphqlOperation;exports.graphqlVariablesKey=graphqlVariablesKey;exports.isSandboxRule=isSandboxRule;exports.urlGlob=urlGlob;const SANDBOX_PREFIX=exports.SANDBOX_PREFIX="Bug case: ";const BLOCK_RULE_NAME=exports.BLOCK_RULE_NAME=`${SANDBOX_PREFIX}blocked (no recorded answer)`;const MAX_ANSWER_RULES=49;const MAX_DELAY_MS=1e4;const DROP_HEADERS=/^(content-length|content-encoding|transfer-encoding|set-cookie|connection|keep-alive)$/i;const REMOVED=/\[removed:[a-z]+\]/g;const HAS_REMOVED=/\[removed:[a-z]+\]/;const CACHE_BUSTER=/^(_|t|ts|time|timestamp|cb|cachebust|cachebuster|nocache|rnd|rand|random|nonce|bust)$/i;function urlGlob(t){const r=t.replace(REMOVED,"*");const n=r.indexOf("?");if(n<0)return r;const o=r.indexOf("#",n);const i=r.slice(n+1,o<0?void 0:o);const y=i.split("&").map(d=>{const f=d.indexOf("=");return f>0&&CACHE_BUSTER.test(d.slice(0,f))?`${d.slice(0,f)}=*`:d});return`${r.slice(0,n+1)}${y.join("&")}${o<0?"":r.slice(o)}`}function jsonType(t){if(!t)return false;const r=Object.entries(t).find(([n])=>n.toLowerCase()==="content-type")?.[1]??"";return/^application\/(?:[\w.-]+\+)?json\b/i.test(r.trim())}function bodyText(t,r,n){if(t===void 0)return void 0;if(n===true){try{return JSON.stringify(t)}catch{return void 0}}if(typeof t==="string")return n===false||!jsonType(r)?t:JSON.stringify(t);if(t===null)return"null";try{return JSON.stringify(t)}catch{return void 0}}function graphqlOperation(t){const r=t;if(!r||typeof r!=="object"||Array.isArray(r))return void 0;const n=r;const o=!!n.extensions&&typeof n.extensions==="object"&&!Array.isArray(n.extensions);if(typeof n.query!=="string"&&!o)return void 0;const i=n.operationName;return typeof i==="string"&&/^[A-Za-z0-9_]{1,100}$/.test(i)?i:void 0}function canonicalJson(t){const r=n=>Array.isArray(n)?n.map(r):n&&typeof n==="object"?Object.fromEntries(Object.keys(n).sort().map(o=>[o,r(n[o])])):n;return JSON.stringify(r(t))??"null"}function graphqlVariablesKey(t){const r=graphqlVariablesRaw(t);return r!==void 0&&r.length<=4e3&&!HAS_REMOVED.test(r)?r:void 0}function looksGraphql(t){const r=n=>{if(!n||typeof n!=="object"||Array.isArray(n))return false;const o=n;return typeof o.query==="string"||!!o.extensions&&typeof o.extensions==="object"&&!Array.isArray(o.extensions)};return Array.isArray(t)?t.some(r):r(t)}function graphqlVariablesRaw(t){const r=t;const n=r&&typeof r==="object"?r.variables??{}:void 0;return n===void 0?void 0:canonicalJson(n)}const MAX_INPUT_CHARS=4e3;function parsedBody(t){if(t===void 0)return{kind:"none"};return{kind:"json",value:t}}function callInput(t,r){let n;if(r)n=graphqlVariablesRaw(t.requestData)??"{}";else{const o=parsedBody(t.requestData);if(o.kind==="none")return{raw:"\0none",why:"it had no body, and other calls to this URL had one"};n=canonicalJson(o.value)}if(HAS_REMOVED.test(n))return{raw:n,why:"its input had private data taken out, so it can't be told apart from the others"};if(n.length>MAX_INPUT_CHARS)return{raw:n,why:"its input is too long to match"};return{raw:n,key:n}}const BINARY_TYPE=/^(application\/(octet-stream|pdf|zip|gzip|x-protobuf|protobuf|x-msgpack|msgpack|wasm|vnd\.[^;]*)|image\/(?!svg)|audio\/|video\/|font\/)/i;const TEXT_SUFFIX=/\+(json|xml|yaml|csv|text)\b|^text\//i;function isBinaryAnswer(t){const r=Object.entries(t.responseHeaders??{}).find(([o])=>o.toLowerCase()==="content-type")?.[1];if(typeof r!=="string")return false;const n=r.trim();return BINARY_TYPE.test(n)&&!TEXT_SUFFIX.test(n)}function headersFor(t){if(!t.responseHeaders)return void 0;const r={};for(const[n,o]of Object.entries(t.responseHeaders)){if(typeof o!=="string"||DROP_HEADERS.test(n)||HAS_REMOVED.test(o))continue;r[n]=o}return Object.keys(r).length?r:void 0}function shortName(t){const r=t.url.replace(/^[a-z]+:\/\/[^/]+/i,"").split(/[?#]/)[0]||"/";return`${t.method.toUpperCase()} ${r.slice(0,60)}`}function answer(t){const r=t.durationMs?Math.min(MAX_DELAY_MS,Math.max(0,Math.round(t.durationMs))):void 0;if(t.error){return{kind:"fail",failKind:/time(d)?[ -]?out/i.test(t.error)?"timeout":"network",delayMs:r}}return{kind:"respond",status:Math.min(599,Math.max(200,t.status??200)),headers:headersFor(t),body:bodyText(t.responseData,t.responseHeaders,t.responseJson)??"",delayMs:r}}function specificity(t){return t.urlPattern.replace(/\*/g,"").length+(t.graphqlOperation?1e5:0)+(t.graphqlVariables!==void 0?2e5:0)+(t.bodyJson!==void 0?4e5:0)}function buildSandbox(t,r={}){const n=[...t.network??[]].reverse();const o=[];const i=new Map;n.forEach((e,s)=>{if(!e||typeof e.url!=="string"||typeof e.method!=="string")return;let a=true;if(e.bodyOmitted&&!e.error){o.push({method:e.method,url:e.url,reason:"its answer was too big to record"});a=false}else if(!e.error&&isBinaryAnswer(e)){o.push({method:e.method,url:e.url,reason:"its answer was binary, and Buoy records answers as text"});a=false}else if(typeof e.status!=="number"&&!e.error){o.push({method:e.method,url:e.url,reason:"it had no answer yet when the bug was sent"});a=false}const u=e.method.toUpperCase();const c=graphqlOperation(e.requestData);const h=callInput(e,c);const A=urlGlob(e.url);const O=`${u} ${A} ${c??""}`;const g=`${O} ${h.raw}`;const $=i.get(g)??{key:g,family:O,pattern:A,method:u,op:c,input:h,calls:[]};$.calls.push({call:e,at:s,usable:a});i.set(g,$)});const y=e=>`${e.call.status??""} ${e.call.error??""} ${bodyText(e.call.responseData,e.call.responseHeaders,e.call.responseJson)??""}`;for(const[e,s]of[...i]){if(!HAS_REMOVED.test(s.input.raw)||s.calls.length<2)continue;const a=s.calls.filter(u=>u.usable);if(a.length===s.calls.length&&new Set(a.map(y)).size<=1)continue;i.delete(e);s.calls.forEach((u,c)=>i.set(`${e} #${c}`,{...s,key:`${e} #${c}`,calls:[u]}))}const d=new Map;for(const e of i.values())d.set(e.family,(d.get(e.family)??0)+1);const f=new Set([...d].filter(([,e])=>e>1).map(([e])=>e));const p=e=>`${e.method} ${e.pattern}`;const m=new Map;for(const e of i.values())m.set(p(e),(m.get(p(e))??0)+1);for(const e of i.values()){if(e.op)continue;if((m.get(p(e))??0)>1||e.calls.some(s=>looksGraphql(s.call.requestData)))f.add(e.family)}const b=new Set;for(const e of i.values()){if(e.calls.some(s=>s.call.bodyOmitted&&s.call.requestData===void 0&&!/^(GET|HEAD)$/.test(e.method)))b.add(p(e))}for(const e of i.values())if(b.has(p(e)))f.add(e.family);for(const[e,s]of i){const a=s.calls.filter(u=>u.usable);if(f.has(s.family)&&s.input.key===void 0){for(const{call:u}of a)o.push({method:u.method,url:u.url,reason:s.input.why??"its input can't be matched"});i.delete(e)}else if(!a.length)i.delete(e);else s.calls=a}const w=[];let k=0;for(const e of i.values()){const s=r.sequence?e.calls:e.calls.slice(-1);s.forEach(({call:a,at:u},c)=>{const h=c===s.length-1;w.push({at:u,order:k++,rule:{name:`${SANDBOX_PREFIX}${shortName(a)}${s.length>1?` (${c+1} of ${s.length})`:""}`,enabled:true,sandbox:true,urlPattern:e.pattern,methods:[e.method],graphqlOperation:e.op,...e.op?{graphqlSingle:true}:{},...f.has(e.family)?e.op?{graphqlVariables:e.input.key}:{bodyJson:e.input.key}:{},times:h?null:1,...answer(a)}})})}let l=[...w];if(l.length>MAX_ANSWER_RULES){const e=[...l].sort((s,a)=>a.at-s.at||(a.rule.times===null?0:1)-(s.rule.times===null?0:1));l=e.slice(0,MAX_ANSWER_RULES);for(const s of e.slice(MAX_ANSWER_RULES)){o.push({method:s.rule.methods?.[0]??"?",url:s.rule.urlPattern,reason:"over the 50-rule limit"})}}l.sort((e,s)=>specificity(s.rule)-specificity(e.rule)||e.order-s.order);const x=l.map(e=>e.rule);const E=l.length;x.push({name:BLOCK_RULE_NAME,enabled:true,sandbox:true,urlPattern:"*",kind:"fail",failKind:"network",times:null});return{rules:x,answers:E,skipped:o}}function isSandboxRule(t){return typeof t?.name==="string"&&t.name.startsWith(SANDBOX_PREFIX)}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.DEFAULT_SERVER=void 0;exports.bugLinksRules=bugLinksRules;exports.bugLinksServer=bugLinksServer;exports.bugLinksSettingsRead=bugLinksSettingsRead;exports.checkServer=checkServer;exports.configureBugLinks=configureBugLinks;exports.loadBugLinksSettings=loadBugLinksSettings;exports.resetBugLinksSettingsForTests=resetBugLinksSettingsForTests;exports.setBugLinksServer=setBugLinksServer;exports.subscribeBugLinksSettings=subscribeBugLinksSettings;exports.teamKeyPins=teamKeyPins;var _sharedUi=require("@buoy-gg/shared-ui");var _frames=require("../media/frames");const DEFAULT_SERVER=exports.DEFAULT_SERVER="https://buoy.gg";let configured={};let saved={};let loaded=null;let settingsRead=false;let savedSeq=0;function bugLinksSettingsRead(){return settingsRead}const listeners=new Set;const PRIVATE_HOST=/^(localhost|127(\.\d{1,3}){3}|10(\.\d{1,3}){3}|192\.168(\.\d{1,3}){2}|172\.(1[6-9]|2\d|3[01])(\.\d{1,3}){2}|\[::1\]|[a-z0-9-]+\.local)$/i;const SERVER_URL=/^(https?):\/\/([a-z0-9.-]+|\[[0-9a-f:]+\])(:\d{1,5})?(?:[/?#].*)?$/i;function checkServer(e){const t=SERVER_URL.exec(e.trim());if(!t)return"That isn't a web address.";if(t[1].toLowerCase()==="https")return null;if(PRIVATE_HOST.test(t[2]))return null;return"Use https, or http only for this Mac or your local network."}function origin(e){const t=SERVER_URL.exec(e.trim());return`${t[1].toLowerCase()}://${t[2].toLowerCase()}${t[3]??""}`}function configureBugLinks(e){if(e.server&&checkServer(e.server)){throw new Error(`configureBugLinks: ${checkServer(e.server)}`)}configured={...configured,...e,server:e.server?origin(e.server):configured.server};if(e.media)(0,_frames.configureMedia)(e.media);emit()}function loadBugLinksSettings(){if(!loaded){loaded=(async()=>{const e=savedSeq;let t;try{t=await(0,_sharedUi.getItemChecked)(_sharedUi.devToolsStorageKeys.bugLinks.settings())}catch{loaded=null;emit();return}if(e!==savedSeq)return;try{const r=t?JSON.parse(t):{};if(typeof r.server==="string"&&!checkServer(r.server))saved={server:origin(r.server)}}catch{saved={}}settingsRead=true;emit()})()}return loaded}function bugLinksServer(){return saved.server??configured.server??DEFAULT_SERVER}function bugLinksRules(){return configured.rules}const KEY_ID=/^[A-Za-z0-9_-]{43}$/;function teamKeyPins(){let e=configured.teamKey;if(e===void 0){try{e=process.env.EXPO_PUBLIC_BUOY_TEAM_KEY}catch{e=void 0}}const t=(Array.isArray(e)?e:typeof e==="string"?e.split(","):[]).map(r=>String(r).trim()).filter(Boolean);if(!t.length)return null;if(t.some(r=>!KEY_ID.test(r)))console.warn("[bug-links] teamKey must be the 43-letter key id from the team list's admin page.");return t.filter(r=>KEY_ID.test(r))}async function setBugLinksServer(e){let t={};if(e!==null){const r=checkServer(e);if(r)throw new Error(r);t={server:origin(e)}}if(!await(0,_sharedUi.setItemChecked)(_sharedUi.devToolsStorageKeys.bugLinks.settings(),JSON.stringify(t),{strict:true}).catch(()=>false)){throw new Error("Buoy couldn't save the server (the storage may be full). Free some space, then try again.")}savedSeq++;saved=t;settingsRead=true;emit();return bugLinksServer()}function subscribeBugLinksSettings(e){listeners.add(e);return()=>listeners.delete(e)}function emit(){for(const e of listeners)e()}function resetBugLinksSettingsForTests(){configured={};saved={};loaded=null;settingsRead=false}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.tabStorage=void 0;var _sharedUi=require("@buoy-gg/shared-ui");function tab(){if(typeof navigator!=="undefined"&&navigator.product==="ReactNative")return null;try{return globalThis.sessionStorage??null}catch{return null}}const tabStorage=exports.tabStorage={async getItem(t){const e=tab();if(!e)return _sharedUi.persistentStorage.getItem(t);try{return e.getItem(t)}catch{return null}},async setItem(t,e){const r=tab();if(!r)return _sharedUi.persistentStorage.setItem(t,e);try{r.setItem(t,e)}catch{}},async removeItem(t){const e=tab();if(!e)return _sharedUi.persistentStorage.removeItem(t);try{e.removeItem(t)}catch{}}};