@buoy-gg/bug-links 0.0.0-stage → 7.0.81

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,181 @@
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
+ package gg.buoy.buglinks.frames
5
+
6
+ import android.graphics.Matrix
7
+ import android.graphics.RectF
8
+ import android.os.SystemClock
9
+ import android.text.InputType
10
+ import android.text.method.PasswordTransformationMethod
11
+ import android.view.SurfaceView
12
+ import android.view.TextureView
13
+ import android.view.View
14
+ import android.view.ViewGroup
15
+ import android.webkit.WebView
16
+ import android.widget.EditText
17
+ import android.widget.ImageView
18
+ import android.widget.TextView
19
+
20
+ data class MaskSettings(var text: Boolean = true, var images: Boolean = true, var inputs: Boolean = true)
21
+
22
+ /** One root to walk: a window's root view and where it sits in the frame, in px. */
23
+ class WalkRoot(val view: View, val dx: Float, val dy: Float)
24
+
25
+ class Walk {
26
+ /** Every part to box, by view, in frame points. */
27
+ val parts = HashMap<View, RectF>()
28
+ var buoyOpen = false
29
+ var failed: String? = null
30
+ }
31
+
32
+ enum class PartKind { NONE, TEXT, INPUT, SECURE, IMAGE, WEB, OPAQUE }
33
+
34
+ object FrameWalker {
35
+ val buoyOpenIds = setOf("dial-devtools-root", "buoy-tool-host")
36
+ const val BUOY_ROOT = "floating-devtools-root"
37
+ const val MASK = "buoy-mask"
38
+ const val UNMASK = "buoy-unmask"
39
+ private const val GROW = 2f // H10
40
+
41
+ private class Item(val view: View, val root: WalkRoot, val unmask: Boolean, val inBuoy: Boolean)
42
+
43
+ fun walk(roots: List<WalkRoot>, frameW: Float, frameH: Float, density: Float, settings: MaskSettings, budgetMs: Long): Walk {
44
+ val out = Walk()
45
+ val start = SystemClock.uptimeMillis()
46
+ val stack = ArrayList<Item>()
47
+ for (r in roots.asReversed()) stack.add(Item(r.view, r, unmask = false, inBuoy = false))
48
+ val screenArea = frameW * frameH
49
+ var visited = 0
50
+ while (stack.isNotEmpty()) {
51
+ val item = stack.removeAt(stack.size - 1)
52
+ visited++
53
+ if (visited and 127 == 0 && SystemClock.uptimeMillis() - start > budgetMs) {
54
+ out.failed = "the screen has too many views to check in time"
55
+ return out
56
+ }
57
+ val v = item.view
58
+ if (v.visibility != View.VISIBLE || v.alpha <= 0f) continue
59
+
60
+ val id = nativeId(v) ?: sentryMarker(v)
61
+ var unmask = item.unmask
62
+ var inBuoy = item.inBuoy
63
+ if (id != null) {
64
+ if (id in buoyOpenIds) {
65
+ if (rect(v, item.root, frameW, frameH, density) != null) out.buoyOpen = true
66
+ continue
67
+ }
68
+ if (id == BUOY_ROOT) inBuoy = true
69
+ if (id == UNMASK) unmask = true
70
+ }
71
+
72
+ val kind = classify(v)
73
+ val box = when {
74
+ kind == PartKind.SECURE -> true // H5
75
+ inBuoy -> {
76
+ val r = rect(v, item.root, frameW, frameH, density)
77
+ r != null && r.width() * r.height() < screenArea * 0.6f && hasContent(v) // F5
78
+ }
79
+ id == MASK -> true // H6, H8
80
+ kind == PartKind.TEXT -> settings.text && !unmask
81
+ kind == PartKind.INPUT -> settings.inputs && !unmask
82
+ kind == PartKind.IMAGE -> settings.images && !unmask
83
+ kind == PartKind.WEB || kind == PartKind.OPAQUE -> !unmask // H5a
84
+ else -> false
85
+ }
86
+ if (box) rect(v, item.root, frameW, frameH, density)?.let { out.parts[v] = it }
87
+ // H12: only skip children a box clips. Inputs are walked for secure fields.
88
+ if (box && clips(v) && kind != PartKind.INPUT) continue
89
+ if (v is ViewGroup) {
90
+ for (i in v.childCount - 1 downTo 0) {
91
+ val c = v.getChildAt(i) ?: continue
92
+ stack.add(Item(c, item.root, unmask, inBuoy))
93
+ }
94
+ }
95
+ }
96
+ return out
97
+ }
98
+
99
+ fun nativeId(v: View): String? = v.getTag(com.facebook.react.R.id.view_tag_native_id) as? String
100
+
101
+ fun testId(v: View): String? = v.getTag(com.facebook.react.R.id.react_test_id) as? String
102
+
103
+ /**
104
+ * Sentry's <Sentry.Mask> and <Sentry.Unmask> count as ours, so an app that
105
+ * already marked its private parts for Sentry is covered here too.
106
+ */
107
+ private fun sentryMarker(v: View): String? = when (v.javaClass.simpleName) {
108
+ "RNSentryReplayMask" -> MASK
109
+ "RNSentryReplayUnmask" -> UNMASK
110
+ else -> null
111
+ }
112
+
113
+ fun classify(v: View): PartKind {
114
+ if (v is WebView) return PartKind.WEB
115
+ if (v is TextView) {
116
+ if (isSecret(v)) return PartKind.SECURE
117
+ return if (v is EditText) PartKind.INPUT else PartKind.TEXT
118
+ }
119
+ if (v is ImageView) return if (v.drawable == null) PartKind.NONE else PartKind.IMAGE
120
+ if (v is SurfaceView || v is TextureView) return PartKind.OPAQUE
121
+ val name = v.javaClass.name
122
+ if (name.contains("Secure")) return PartKind.SECURE
123
+ if (name.contains("TextInput") || name.contains("EditText")) return PartKind.INPUT
124
+ if (name.contains("WebView")) return PartKind.WEB
125
+ if (name.contains("PdfView") || name.contains("PDFView")) return PartKind.OPAQUE
126
+ for (w in listOf("Compose", "Skia", "MapView", "Video", "Player", "Camera", "Preview", "GLView", "Canvas", "Chart")) {
127
+ if (name.contains(w)) return PartKind.OPAQUE
128
+ }
129
+ if (name.contains("Image") || name.contains("Svg") || name.contains("SVG") || name.contains("Lottie") || name.contains("Rive")) return PartKind.IMAGE
130
+ if (name.contains("Text") || name.contains("Paragraph") || name.contains("Label")) return PartKind.TEXT
131
+ return PartKind.NONE
132
+ }
133
+
134
+ private fun isSecret(t: TextView): Boolean {
135
+ if (t.transformationMethod is PasswordTransformationMethod) return true
136
+ val type = t.inputType
137
+ val variation = type and InputType.TYPE_MASK_VARIATION
138
+ val cls = type and InputType.TYPE_MASK_CLASS
139
+ return (cls == InputType.TYPE_CLASS_TEXT && (variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
140
+ variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) ||
141
+ (cls == InputType.TYPE_CLASS_NUMBER && variation == InputType.TYPE_NUMBER_VARIATION_PASSWORD)
142
+ }
143
+
144
+ private fun clips(v: View): Boolean = v !is ViewGroup || v.childCount == 0 || v.clipChildren
145
+
146
+ private fun hasContent(v: View): Boolean = v !is ViewGroup || v.childCount == 0 || v.background != null
147
+
148
+ private val corners = FloatArray(8)
149
+
150
+ /**
151
+ * Where the view is in the frame, in points: all four corners mapped through
152
+ * every parent's scale, turn and scroll, then the box around them, grown (H10)
153
+ * and clipped to the frame (H9). null when none of it shows.
154
+ */
155
+ fun rect(v: View, root: WalkRoot, frameW: Float, frameH: Float, density: Float): RectF? {
156
+ val r = RectF(0f, 0f, v.width.toFloat(), v.height.toFloat())
157
+ var cur: View = v
158
+ while (cur !== root.view) {
159
+ val m: Matrix = cur.matrix
160
+ if (!m.isIdentity) {
161
+ corners[0] = r.left; corners[1] = r.top; corners[2] = r.right; corners[3] = r.top
162
+ corners[4] = r.right; corners[5] = r.bottom; corners[6] = r.left; corners[7] = r.bottom
163
+ m.mapPoints(corners)
164
+ r.set(
165
+ minOf(corners[0], corners[2], corners[4], corners[6]), minOf(corners[1], corners[3], corners[5], corners[7]),
166
+ maxOf(corners[0], corners[2], corners[4], corners[6]), maxOf(corners[1], corners[3], corners[5], corners[7]),
167
+ )
168
+ }
169
+ r.offset(cur.left.toFloat(), cur.top.toFloat())
170
+ val p = cur.parent as? View ?: return null // not attached under this root
171
+ r.offset(-p.scrollX.toFloat(), -p.scrollY.toFloat())
172
+ cur = p
173
+ }
174
+ r.offset(root.dx, root.dy)
175
+ val out = RectF(r.left / density - GROW, r.top / density - GROW, r.right / density + GROW, r.bottom / density + GROW)
176
+ if (!out.intersect(0f, 0f, frameW, frameH)) return null
177
+ if (out.width() <= 0f || out.height() <= 0f) return null
178
+ out.set(kotlin.math.floor(out.left), kotlin.math.floor(out.top), kotlin.math.ceil(out.right), kotlin.math.ceil(out.bottom))
179
+ return out
180
+ }
181
+ }
@@ -0,0 +1,17 @@
1
+ // Bridge for the Swift module in BuoyFrames.swift. A plain RN module, so it
2
+ // works on the old architecture and, through the interop layer, the new one.
3
+ #import <React/RCTBridgeModule.h>
4
+
5
+ @interface RCT_EXTERN_MODULE(BuoyFrames, NSObject)
6
+ RCT_EXTERN_METHOD(configure:(NSDictionary *)options)
7
+ RCT_EXTERN_METHOD(status:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
8
+ RCT_EXTERN_METHOD(takeMedia:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
9
+ RCT_EXTERN_METHOD(captureLast:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
10
+ RCT_EXTERN_METHOD(probe:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
11
+ RCT_EXTERN_METHOD(resume)
12
+ RCT_EXTERN_METHOD(startVideo:(BOOL)always)
13
+ RCT_EXTERN_METHOD(stopVideo)
14
+ RCT_EXTERN_METHOD(mark)
15
+ RCT_EXTERN_METHOD(clear)
16
+ RCT_EXTERN_METHOD(invalidate)
17
+ @end