rn-backstage 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +109 -2
  2. package/lib/commonjs/Backstage.js +19 -2
  3. package/lib/commonjs/Backstage.js.map +1 -1
  4. package/lib/commonjs/bug-report.js +173 -0
  5. package/lib/commonjs/bug-report.js.map +1 -0
  6. package/lib/commonjs/components/BackstagePanel.js +100 -17
  7. package/lib/commonjs/components/BackstagePanel.js.map +1 -1
  8. package/lib/commonjs/components/BugReportComposer.js +510 -0
  9. package/lib/commonjs/components/BugReportComposer.js.map +1 -0
  10. package/lib/commonjs/components/FlagsTab.js +21 -11
  11. package/lib/commonjs/components/FlagsTab.js.map +1 -1
  12. package/lib/commonjs/components/FloatingPill.js +82 -22
  13. package/lib/commonjs/components/FloatingPill.js.map +1 -1
  14. package/lib/commonjs/components/InfoTab.js +5 -1
  15. package/lib/commonjs/components/InfoTab.js.map +1 -1
  16. package/lib/commonjs/components/LogItem.js +6 -0
  17. package/lib/commonjs/components/LogItem.js.map +1 -1
  18. package/lib/commonjs/components/LogsTab.js +3 -3
  19. package/lib/commonjs/components/LogsTab.js.map +1 -1
  20. package/lib/commonjs/components/NetworkTab.js +3 -2
  21. package/lib/commonjs/components/NetworkTab.js.map +1 -1
  22. package/lib/commonjs/components/StorageTab.js +672 -0
  23. package/lib/commonjs/components/StorageTab.js.map +1 -0
  24. package/lib/commonjs/constants.js +88 -6
  25. package/lib/commonjs/constants.js.map +1 -1
  26. package/lib/commonjs/index.js +84 -0
  27. package/lib/commonjs/index.js.map +1 -1
  28. package/lib/commonjs/types.js +2 -0
  29. package/lib/commonjs/types.js.map +1 -1
  30. package/lib/module/Backstage.js +19 -2
  31. package/lib/module/Backstage.js.map +1 -1
  32. package/lib/module/bug-report.js +164 -0
  33. package/lib/module/bug-report.js.map +1 -0
  34. package/lib/module/components/BackstagePanel.js +101 -18
  35. package/lib/module/components/BackstagePanel.js.map +1 -1
  36. package/lib/module/components/BugReportComposer.js +505 -0
  37. package/lib/module/components/BugReportComposer.js.map +1 -0
  38. package/lib/module/components/FlagsTab.js +23 -13
  39. package/lib/module/components/FlagsTab.js.map +1 -1
  40. package/lib/module/components/FloatingPill.js +86 -26
  41. package/lib/module/components/FloatingPill.js.map +1 -1
  42. package/lib/module/components/InfoTab.js +6 -2
  43. package/lib/module/components/InfoTab.js.map +1 -1
  44. package/lib/module/components/LogItem.js +7 -1
  45. package/lib/module/components/LogItem.js.map +1 -1
  46. package/lib/module/components/LogsTab.js +3 -3
  47. package/lib/module/components/LogsTab.js.map +1 -1
  48. package/lib/module/components/NetworkTab.js +3 -2
  49. package/lib/module/components/NetworkTab.js.map +1 -1
  50. package/lib/module/components/StorageTab.js +667 -0
  51. package/lib/module/components/StorageTab.js.map +1 -0
  52. package/lib/module/constants.js +88 -6
  53. package/lib/module/constants.js.map +1 -1
  54. package/lib/module/index.js +14 -0
  55. package/lib/module/index.js.map +1 -1
  56. package/lib/module/types.js +4 -0
  57. package/lib/module/types.js.map +1 -1
  58. package/lib/typescript/Backstage.d.ts.map +1 -1
  59. package/lib/typescript/bug-report.d.ts +12 -0
  60. package/lib/typescript/bug-report.d.ts.map +1 -0
  61. package/lib/typescript/components/BackstagePanel.d.ts +4 -1
  62. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  63. package/lib/typescript/components/BugReportComposer.d.ts +17 -0
  64. package/lib/typescript/components/BugReportComposer.d.ts.map +1 -0
  65. package/lib/typescript/components/FlagsTab.d.ts.map +1 -1
  66. package/lib/typescript/components/FloatingPill.d.ts +2 -0
  67. package/lib/typescript/components/FloatingPill.d.ts.map +1 -1
  68. package/lib/typescript/components/InfoTab.d.ts.map +1 -1
  69. package/lib/typescript/components/LogItem.d.ts.map +1 -1
  70. package/lib/typescript/components/LogsTab.d.ts.map +1 -1
  71. package/lib/typescript/components/NetworkTab.d.ts.map +1 -1
  72. package/lib/typescript/components/StorageTab.d.ts +9 -0
  73. package/lib/typescript/components/StorageTab.d.ts.map +1 -0
  74. package/lib/typescript/constants.d.ts +74 -4
  75. package/lib/typescript/constants.d.ts.map +1 -1
  76. package/lib/typescript/index.d.ts +13 -1
  77. package/lib/typescript/index.d.ts.map +1 -1
  78. package/lib/typescript/types.d.ts +55 -0
  79. package/lib/typescript/types.d.ts.map +1 -1
  80. package/package.json +7 -6
  81. package/src/Backstage.tsx +0 -209
  82. package/src/ThemeContext.tsx +0 -38
  83. package/src/components/BackstagePanel.tsx +0 -258
  84. package/src/components/FlagsTab.tsx +0 -198
  85. package/src/components/FloatingPill.tsx +0 -153
  86. package/src/components/InfoTab.tsx +0 -230
  87. package/src/components/JsonTreeView.tsx +0 -239
  88. package/src/components/LogItem.tsx +0 -144
  89. package/src/components/LogsTab.tsx +0 -217
  90. package/src/components/NetworkItem.tsx +0 -425
  91. package/src/components/NetworkTab.tsx +0 -240
  92. package/src/components/TabBar.tsx +0 -154
  93. package/src/constants.ts +0 -114
  94. package/src/index.ts +0 -18
  95. package/src/log-interceptor.ts +0 -148
  96. package/src/network-interceptor.ts +0 -468
  97. package/src/types.ts +0 -213
  98. package/src/utils/formatTimestamp.ts +0 -22
  99. package/src/utils/stringify.ts +0 -90
@@ -1,468 +0,0 @@
1
- import { NetworkState } from './types'
2
- import type { NetworkEntry } from './types'
3
- import { DEFAULT_MAX_NETWORK_ENTRIES, DEFAULT_MAX_NETWORK_BODY_SIZE } from './constants'
4
-
5
- // ─── Types ───────────────────────────────────────────────────────────────────
6
-
7
- type NetworkCallback = (entry: NetworkEntry) => void
8
-
9
- interface NetworkInterceptorOptions {
10
- filters?: string[]
11
- maxBodySize?: number
12
- }
13
-
14
- // ─── State ───────────────────────────────────────────────────────────────────
15
-
16
- let activeCallback: NetworkCallback | null = null
17
- let isInstalled = false
18
- let interceptorOptions: NetworkInterceptorOptions = {}
19
-
20
- // Track if we're inside a network callback context.
21
- // Used by the log interceptor to auto-filter network-related console.logs.
22
- let _activeNetworkCallbacks = 0
23
-
24
- /**
25
- * Returns true if the current call stack originated from within a network
26
- * response handler (XHR load/error, fetch .then()). Used by the log
27
- * interceptor to suppress network-related console.logs from the Logs tab.
28
- */
29
- export function isInsideNetworkCallback(): boolean {
30
- return _activeNetworkCallbacks > 0
31
- }
32
-
33
- function enterNetworkContext(): void {
34
- _activeNetworkCallbacks++
35
- }
36
-
37
- function exitNetworkContext(): void {
38
- // Defer decrement through microtask queue so developer's .then() chains
39
- // that fire after our return are also covered.
40
- queueMicrotask(() => {
41
- _activeNetworkCallbacks--
42
- })
43
- }
44
-
45
- // Store originals before patching
46
- const originals: {
47
- fetch: typeof global.fetch | null
48
- XMLHttpRequest: typeof global.XMLHttpRequest | null
49
- } = {
50
- fetch: null,
51
- XMLHttpRequest: null,
52
- }
53
-
54
- // ─── Helpers ─────────────────────────────────────────────────────────────────
55
-
56
- function generateId(): string {
57
- return `net_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
58
- }
59
-
60
- function shouldCapture(url: string, filters: string[]): boolean {
61
- if (filters.length === 0) return true
62
- const lower = url.toLowerCase()
63
- return !filters.some(f => lower.includes(f.toLowerCase()))
64
- }
65
-
66
- function truncateBody(body?: string, maxSize?: number): string | undefined {
67
- if (!body) return undefined
68
- const limit = maxSize ?? DEFAULT_MAX_NETWORK_BODY_SIZE
69
- if (body.length <= limit) return body
70
- return body.substring(0, limit) + '\n... [truncated]'
71
- }
72
-
73
- function parseHeaders(headers: Headers | undefined): Record<string, string> | undefined {
74
- if (!headers) return undefined
75
- const result: Record<string, string> = {}
76
- headers.forEach((value: string, key: string) => {
77
- result[key] = value
78
- })
79
- return Object.keys(result).length > 0 ? result : undefined
80
- }
81
-
82
- /**
83
- * Generates a cURL command string from a NetworkEntry.
84
- */
85
- export function toCurl(entry: NetworkEntry): string {
86
- const parts: string[] = ['curl']
87
-
88
- // Method
89
- if (entry.method && entry.method !== 'GET') {
90
- parts.push(`-X ${entry.method}`)
91
- }
92
-
93
- // Request headers
94
- if (entry.requestHeaders) {
95
- for (const [key, value] of Object.entries(entry.requestHeaders)) {
96
- parts.push(`-H '${key}: ${value}'`)
97
- }
98
- }
99
-
100
- // Request body
101
- if (entry.requestBody) {
102
- // Escape single quotes in body
103
- const escaped = entry.requestBody.replace(/'/g, "'\\''")
104
- parts.push(`-d '${escaped}'`)
105
- }
106
-
107
- // URL (must be last)
108
- parts.push(`'${entry.url}'`)
109
-
110
- return parts.join(' \\\n ')
111
- }
112
-
113
- // ─── Fetch Interceptor ──────────────────────────────────────────────────────
114
-
115
- function installFetchInterceptor(): void {
116
- const originalFetch = global.fetch
117
- if (!originalFetch) return
118
- originals.fetch = originalFetch
119
-
120
- global.fetch = async function interceptedFetch(
121
- input: RequestInfo | URL,
122
- init?: RequestInit,
123
- ): Promise<Response> {
124
- const url =
125
- typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url
126
- const method =
127
- init?.method ??
128
- (typeof input !== 'string' && !(input instanceof URL) ? input.method : 'GET') ??
129
- 'GET'
130
-
131
- // Check filters
132
- if (!shouldCapture(url, interceptorOptions.filters ?? [])) {
133
- return originalFetch(input, init)
134
- }
135
-
136
- // Build entry
137
- const entry: NetworkEntry = {
138
- id: generateId(),
139
- method: method.toUpperCase(),
140
- url,
141
- startTime: Date.now(),
142
- state: NetworkState.pending,
143
- }
144
-
145
- // Capture request headers
146
- if (init?.headers) {
147
- try {
148
- const h = new Headers(init.headers)
149
- entry.requestHeaders = parseHeaders(h)
150
- } catch {
151
- // Skip if headers can't be parsed
152
- }
153
- }
154
-
155
- // Capture request body
156
- if (init?.body) {
157
- try {
158
- entry.requestBody = truncateBody(
159
- typeof init.body === 'string' ? init.body : JSON.stringify(init.body),
160
- interceptorOptions.maxBodySize,
161
- )
162
- } catch {
163
- entry.requestBody = '[unable to serialize body]'
164
- }
165
- }
166
-
167
- // Notify: request started
168
- activeCallback?.(entry)
169
-
170
- try {
171
- const response = await originalFetch(input, init)
172
-
173
- entry.endTime = Date.now()
174
- entry.duration = entry.endTime - entry.startTime
175
- entry.status = response.status
176
- entry.statusText = response.statusText
177
- entry.responseHeaders = parseHeaders(response.headers)
178
- entry.state = NetworkState.completed
179
-
180
- // Clone response to read body without consuming it
181
- try {
182
- const clone = response.clone()
183
- const text = await clone.text()
184
- entry.responseSize = text.length
185
- entry.responseBody = truncateBody(text, interceptorOptions.maxBodySize)
186
- } catch {
187
- // Body may not be readable (e.g., stream already consumed)
188
- }
189
-
190
- // Notify: request completed
191
- activeCallback?.(entry)
192
-
193
- // Mark network context so developer's .then() console.logs are filtered
194
- enterNetworkContext()
195
-
196
- // Return the ORIGINAL response untouched
197
- // Developer's .then() runs in the next microtask — exitNetworkContext
198
- // defers via queueMicrotask to cover it.
199
- const result = response
200
- exitNetworkContext()
201
- return result
202
- } catch (err) {
203
- entry.endTime = Date.now()
204
- entry.duration = entry.endTime - entry.startTime
205
- entry.state = NetworkState.error
206
- entry.error = err instanceof Error ? err.message : String(err)
207
-
208
- // Notify: request failed
209
- activeCallback?.(entry)
210
-
211
- // Re-throw the original error
212
- throw err
213
- }
214
- }
215
- }
216
-
217
- // ─── XMLHttpRequest Interceptor ──────────────────────────────────────────────
218
-
219
- function installXHRInterceptor(): void {
220
- const OriginalXHR = global.XMLHttpRequest
221
- if (!OriginalXHR) return
222
- originals.XMLHttpRequest = OriginalXHR
223
-
224
- global.XMLHttpRequest = function InterceptedXHR() {
225
- const xhr = new OriginalXHR()
226
- const entry: NetworkEntry = {
227
- id: generateId(),
228
- method: 'GET',
229
- url: '',
230
- startTime: Date.now(),
231
- state: NetworkState.pending,
232
- requestHeaders: {},
233
- }
234
-
235
- let shouldTrack = true
236
-
237
- // Intercept open()
238
- const originalOpen = xhr.open.bind(xhr)
239
- xhr.open = function (method: string, url: string, ...rest: unknown[]) {
240
- entry.method = method.toUpperCase()
241
- entry.url = url
242
- shouldTrack = shouldCapture(url, interceptorOptions.filters ?? [])
243
- // @ts-expect-error — forwarding args to original
244
- return originalOpen(method, url, ...rest)
245
- }
246
-
247
- // Intercept setRequestHeader()
248
- const originalSetRequestHeader = xhr.setRequestHeader.bind(xhr)
249
- xhr.setRequestHeader = function (name: string, value: string) {
250
- if (shouldTrack && entry.requestHeaders) {
251
- entry.requestHeaders[name] = value
252
- }
253
- return originalSetRequestHeader(name, value)
254
- }
255
-
256
- // Intercept send()
257
- const originalSend = xhr.send.bind(xhr)
258
- xhr.send = function (
259
- body?:
260
- | string
261
- | Document
262
- | Blob
263
- | ArrayBufferView
264
- | ArrayBuffer
265
- | FormData
266
- | URLSearchParams
267
- | null,
268
- ) {
269
- if (!shouldTrack) {
270
- return originalSend(body)
271
- }
272
-
273
- entry.startTime = Date.now()
274
-
275
- // Capture request body
276
- if (body) {
277
- try {
278
- entry.requestBody = truncateBody(
279
- typeof body === 'string' ? body : JSON.stringify(body),
280
- interceptorOptions.maxBodySize,
281
- )
282
- } catch {
283
- entry.requestBody = '[unable to serialize body]'
284
- }
285
- }
286
-
287
- // Clean up empty request headers
288
- if (entry.requestHeaders && Object.keys(entry.requestHeaders).length === 0) {
289
- entry.requestHeaders = undefined
290
- }
291
-
292
- // Notify: request started
293
- activeCallback?.(entry)
294
-
295
- // Listen for completion
296
- xhr.addEventListener('load', () => {
297
- enterNetworkContext()
298
-
299
- entry.endTime = Date.now()
300
- entry.duration = entry.endTime - entry.startTime
301
- entry.status = xhr.status
302
- entry.statusText = xhr.statusText
303
- entry.state = NetworkState.completed
304
-
305
- // Parse response headers
306
- const rawHeaders = xhr.getAllResponseHeaders()
307
- if (rawHeaders) {
308
- const parsed: Record<string, string> = {}
309
- rawHeaders
310
- .trim()
311
- .split(/[\r\n]+/)
312
- .forEach(line => {
313
- const idx = line.indexOf(': ')
314
- if (idx > 0) {
315
- parsed[line.substring(0, idx).toLowerCase()] = line.substring(idx + 2)
316
- }
317
- })
318
- entry.responseHeaders = Object.keys(parsed).length > 0 ? parsed : undefined
319
- }
320
-
321
- // Capture response body
322
- try {
323
- const responseText =
324
- xhr.responseType === '' || xhr.responseType === 'text'
325
- ? xhr.responseText
326
- : JSON.stringify(xhr.response)
327
- entry.responseSize = responseText?.length
328
- entry.responseBody = truncateBody(responseText, interceptorOptions.maxBodySize)
329
- } catch {
330
- // Response may not be text-readable
331
- }
332
-
333
- activeCallback?.(entry)
334
- exitNetworkContext()
335
- })
336
-
337
- xhr.addEventListener('error', () => {
338
- enterNetworkContext()
339
- entry.endTime = Date.now()
340
- entry.duration = entry.endTime - entry.startTime
341
- entry.state = NetworkState.error
342
- entry.error = 'Network request failed'
343
- activeCallback?.(entry)
344
- exitNetworkContext()
345
- })
346
-
347
- xhr.addEventListener('timeout', () => {
348
- enterNetworkContext()
349
- entry.endTime = Date.now()
350
- entry.duration = entry.endTime - entry.startTime
351
- entry.state = NetworkState.error
352
- entry.error = 'Request timed out'
353
- activeCallback?.(entry)
354
- exitNetworkContext()
355
- })
356
-
357
- xhr.addEventListener('abort', () => {
358
- enterNetworkContext()
359
- entry.endTime = Date.now()
360
- entry.duration = entry.endTime - entry.startTime
361
- entry.state = NetworkState.error
362
- entry.error = 'Request aborted'
363
- activeCallback?.(entry)
364
- exitNetworkContext()
365
- })
366
-
367
- return originalSend(body)
368
- }
369
-
370
- return xhr
371
- } as unknown as typeof XMLHttpRequest
372
-
373
- // Copy static properties and prototype
374
- Object.defineProperty(global.XMLHttpRequest, 'UNSENT', { value: 0 })
375
- Object.defineProperty(global.XMLHttpRequest, 'OPENED', { value: 1 })
376
- Object.defineProperty(global.XMLHttpRequest, 'HEADERS_RECEIVED', { value: 2 })
377
- Object.defineProperty(global.XMLHttpRequest, 'LOADING', { value: 3 })
378
- Object.defineProperty(global.XMLHttpRequest, 'DONE', { value: 4 })
379
- global.XMLHttpRequest.prototype = OriginalXHR.prototype
380
- }
381
-
382
- // ─── Install / Uninstall ─────────────────────────────────────────────────────
383
-
384
- export function installNetworkInterceptor(
385
- callback: NetworkCallback,
386
- options: NetworkInterceptorOptions = {},
387
- ): void {
388
- if (isInstalled) {
389
- // Update callback and options if already installed
390
- activeCallback = callback
391
- interceptorOptions = options
392
- return
393
- }
394
-
395
- activeCallback = callback
396
- interceptorOptions = options
397
- isInstalled = true
398
-
399
- installFetchInterceptor()
400
- installXHRInterceptor()
401
- }
402
-
403
- export function uninstallNetworkInterceptor(): void {
404
- if (!isInstalled) return
405
-
406
- // Restore originals
407
- if (originals.fetch) {
408
- global.fetch = originals.fetch
409
- originals.fetch = null
410
- }
411
- if (originals.XMLHttpRequest) {
412
- global.XMLHttpRequest = originals.XMLHttpRequest
413
- originals.XMLHttpRequest = null
414
- }
415
-
416
- activeCallback = null
417
- isInstalled = false
418
- }
419
-
420
- // ─── Network Buffer ──────────────────────────────────────────────────────────
421
-
422
- export class NetworkBuffer {
423
- private entries: Map<string, NetworkEntry> = new Map()
424
- private order: string[] = []
425
- private maxSize: number
426
-
427
- constructor(maxSize = DEFAULT_MAX_NETWORK_ENTRIES) {
428
- this.maxSize = maxSize
429
- }
430
-
431
- /**
432
- * Upsert a network entry. If the entry already exists (same id),
433
- * update it in place (e.g., pending → completed). Otherwise add new.
434
- */
435
- upsert(entry: NetworkEntry): void {
436
- if (this.entries.has(entry.id)) {
437
- // Update existing entry (pending → completed/error)
438
- this.entries.set(entry.id, entry)
439
- } else {
440
- // Add new entry at the front
441
- this.order.unshift(entry.id)
442
- this.entries.set(entry.id, entry)
443
-
444
- // Evict oldest if over capacity
445
- while (this.order.length > this.maxSize) {
446
- const oldId = this.order.pop()
447
- if (oldId) this.entries.delete(oldId)
448
- }
449
- }
450
- }
451
-
452
- getAll(): NetworkEntry[] {
453
- return this.order.map(id => this.entries.get(id)!).filter(Boolean)
454
- }
455
-
456
- clear(): void {
457
- this.entries.clear()
458
- this.order = []
459
- }
460
-
461
- get hasErrors(): boolean {
462
- return this.order.some(id => this.entries.get(id)?.state === NetworkState.error)
463
- }
464
-
465
- get size(): number {
466
- return this.order.length
467
- }
468
- }
package/src/types.ts DELETED
@@ -1,213 +0,0 @@
1
- import type { ReactNode } from 'react'
2
- import type { StyleProp, TextStyle, ViewStyle } from 'react-native'
3
-
4
- // ─── Log Types ───────────────────────────────────────────────────────────────
5
-
6
- export enum LogLevel {
7
- log = 'log',
8
- debug = 'debug',
9
- info = 'info',
10
- warn = 'warn',
11
- error = 'error',
12
- }
13
-
14
- export interface LogEntry {
15
- id: string
16
- level: LogLevel
17
- message: string
18
- data?: unknown
19
- timestamp: number
20
- }
21
-
22
- // ─── Network Types ───────────────────────────────────────────────────────────
23
-
24
- export enum NetworkState {
25
- pending = 'pending',
26
- completed = 'completed',
27
- error = 'error',
28
- }
29
-
30
- export interface NetworkEntry {
31
- id: string
32
- method: string
33
- url: string
34
- startTime: number
35
- endTime?: number
36
- duration?: number
37
- status?: number
38
- statusText?: string
39
- requestHeaders?: Record<string, string>
40
- responseHeaders?: Record<string, string>
41
- requestBody?: string
42
- responseBody?: string
43
- responseSize?: number
44
- error?: string
45
- state: NetworkState
46
- }
47
-
48
- // ─── App Info ────────────────────────────────────────────────────────────────
49
-
50
- export interface AppInfoItem {
51
- label: string
52
- value: string
53
- }
54
-
55
- // ─── Quick Actions ───────────────────────────────────────────────────────────
56
-
57
- export interface QuickAction {
58
- title: string
59
- onPress: () => void
60
- closeOnPress?: boolean
61
- icon?: string
62
- destructive?: boolean
63
- testID?: string
64
- }
65
-
66
- // ─── Feature Flags ─────────────────────────────────────────────────────────
67
-
68
- export interface FeatureFlag {
69
- /** Unique key for this flag */
70
- key: string
71
- /** Display label */
72
- label: string
73
- /** Current value */
74
- value: boolean
75
- /** Optional description shown below the label */
76
- description?: string
77
- }
78
-
79
- // ─── Extensible Tabs ─────────────────────────────────────────────────────────
80
-
81
- export interface BackstageTab {
82
- key: string
83
- title: string
84
- icon?: string
85
- render: () => ReactNode
86
- }
87
-
88
- // ─── Theme ───────────────────────────────────────────────────────────────────
89
-
90
- export interface BackstageTheme {
91
- background: string
92
- surface: string
93
- surfaceElevated: string
94
- border: string
95
- text: string
96
- textSecondary: string
97
- textMuted: string
98
- accent: string
99
- accentDim: string
100
- error: string
101
- errorDim: string
102
- warning: string
103
- warningDim: string
104
- success: string
105
- info: string
106
- infoDim: string
107
- debugColor: string
108
- debugDim: string
109
- }
110
-
111
- // ─── Main Props ──────────────────────────────────────────────────────────────
112
-
113
- export interface BackstageProps {
114
- /** Whether the floating pill trigger is visible. Default: true */
115
- visible?: boolean
116
-
117
- /** Theme preference: 'light', 'dark', or 'auto' (follows device setting). Default: 'auto' */
118
- theme?: 'light' | 'dark' | 'auto'
119
-
120
- /** App version string (e.g., "1.2.3") */
121
- appVersion?: string
122
-
123
- /** Build number (e.g., "42") */
124
- buildNumber?: string
125
-
126
- /** Bundle identifier (e.g., "com.example.app") */
127
- bundleId?: string
128
-
129
- /** Additional device/app information rows */
130
- deviceInfo?: AppInfoItem[]
131
-
132
- /** State object to display in the tree viewer (Redux store, Zustand, etc.) */
133
- state?: Record<string, unknown>
134
-
135
- /** Custom action buttons in the Info tab */
136
- quickActions?: QuickAction[]
137
-
138
- /** Feature flags to display with toggle switches */
139
- featureFlags?: FeatureFlag[]
140
-
141
- /** Callback when a feature flag is toggled */
142
- onToggleFeatureFlag?: (key: string, value: boolean) => void
143
-
144
- /** Maximum number of logs to retain in memory. Default: 500 */
145
- maxLogs?: number
146
-
147
- /** Log messages containing these strings will be excluded */
148
- logFilters?: string[]
149
-
150
- /** Callback triggered when user copies logs */
151
- onCopyLogs?: (logs: string) => void
152
-
153
- /** Additional custom tabs beyond Info and Logs */
154
- extraTabs?: BackstageTab[]
155
-
156
- /** Extra content rendered at the bottom of the Info tab */
157
- children?: ReactNode
158
-
159
- /** Custom styles overrides */
160
- styles?: BackstageStyleOverrides
161
-
162
- /** Initial X position for the floating pill */
163
- initialX?: number
164
-
165
- /** Initial Y position for the floating pill */
166
- initialY?: number
167
-
168
- /** Text displayed on the floating pill. Defaults to appVersion or "DEV" */
169
- pillText?: string
170
-
171
- /** Whether to enable network request interception. Default: true */
172
- enableNetworkInspector?: boolean
173
-
174
- /** Maximum number of network entries to retain in memory. Default: 500 */
175
- maxNetworkEntries?: number
176
-
177
- /** Maximum body size (bytes) to capture per request/response. Default: 65536 (64KB) */
178
- maxNetworkBodySize?: number
179
-
180
- /** URL substrings to exclude from network capture */
181
- networkFilters?: string[]
182
-
183
- /** Auto-filter console.logs from network callbacks (e.g., Axios interceptors) out of the Logs tab. Default: true */
184
- autoFilterNetworkLogs?: boolean
185
-
186
- /** Max nesting depth for JSON tree views (state tree, log data, network bodies). Default: 10 */
187
- jsonMaxDepth?: number
188
- }
189
-
190
- export interface BackstageStyleOverrides {
191
- pillStyle?: StyleProp<ViewStyle>
192
- pillTextStyle?: StyleProp<TextStyle>
193
- panelStyle?: StyleProp<ViewStyle>
194
- headerTitleStyle?: StyleProp<TextStyle>
195
- sectionTitleStyle?: StyleProp<TextStyle>
196
- infoLabelStyle?: StyleProp<TextStyle>
197
- infoValueStyle?: StyleProp<TextStyle>
198
- actionButtonStyle?: StyleProp<ViewStyle>
199
- actionButtonTitleStyle?: StyleProp<TextStyle>
200
- logTimestampStyle?: StyleProp<TextStyle>
201
- logMessageStyle?: StyleProp<TextStyle>
202
- }
203
-
204
- // ─── Ref Methods ─────────────────────────────────────────────────────────────
205
-
206
- export interface BackstageRef {
207
- /** Open the backstage panel */
208
- open: () => void
209
- /** Close the backstage panel */
210
- close: () => void
211
- /** Clear all captured logs */
212
- clearLogs: () => void
213
- }
@@ -1,22 +0,0 @@
1
- /**
2
- * Formats a timestamp (Date.now()) into a human-readable local time string.
3
- * Format: "h:mm:ss.SSS AM/PM"
4
- * No external dependencies — uses native Date API.
5
- */
6
- export function formatTimestamp(timestamp: number): string {
7
- const date = new Date(timestamp)
8
- let hours = date.getHours()
9
- const minutes = date.getMinutes()
10
- const seconds = date.getSeconds()
11
- const milliseconds = date.getMilliseconds()
12
- const ampm = hours >= 12 ? 'PM' : 'AM'
13
-
14
- hours = hours % 12
15
- hours = hours || 12
16
-
17
- const mm = minutes.toString().padStart(2, '0')
18
- const ss = seconds.toString().padStart(2, '0')
19
- const ms = milliseconds.toString().padStart(3, '0')
20
-
21
- return `${hours}:${mm}:${ss}.${ms} ${ampm}`
22
- }